This release is a pre-release and may not be stable for production use.
TN-Proto
TN-Proto lets applications exchange encrypted data together with authenticated provenance and use contracts. Applications can open selected fields, calculate a result, and publish a new object that retains its contributing sources and contracts. The Python SDK uses the canonical Rust implementation through PyO3.
Key access is access to encrypted data. Group keys determine which fields a reader can open. Application rules can add restrictions when needed. With the required keys already loaded in session and a publication in sealed:
data = session.unseal(
sealed, purpose="read", decide=lambda _: True
)
The callback adds no application permission check. TN still verifies the publication and requires the matching keys. purpose="read" labels the operation; it does not grant access. Keys cover groups of fields, so fields that need independent access belong in separate groups.
Why TN exists · Install · First object · Bank and vendor · Application rules · Python API · Keys and providers · Book examples · Evidence
Why TN exists
A bank asks a vendor to total transaction amounts. The vendor needs the amounts, while customer identities must stay private. The bank also needs to recognize the returned report, identify its inputs, and keep the conditions under which those inputs were supplied.
That problem continues after the first handoff. Another service may combine the report with licensed data, a cache may serve it later, and an archive may need the exact version used in an earlier decision. Encryption, source identity, and use context need to survive those steps together.
TN puts encrypted field groups and authenticated governance in a signed object that an application can store or send through its existing transport. Different readers can open different groups of the same object. When an application produces a result, it can retain references to the contributing publications and their contracts as part of that new signed publication.
This is useful for selective data sharing, licensed-data processing, derived reports, and work that must remain explainable across services. The application supplies the calculation, chooses its recipients, and owns its database transactions and delivery. TN supplies the object operations used inside that work.
Install
python -m pip install "tn-proto==2026.9.13b5"
Python 3.10 or newer. Linux x86-64 and Windows x64 wheels include Rust and require no Rust toolchain to install. This is a beta of the Python SDK; see the release notes for changes. Other language packages have separate interfaces and release schedules.
Your first object
1. Start a session
For a complete example, save the sample agents.md in your working folder. It supplies the contract metadata used when creating the greeting object. This walkthrough carries that metadata without evaluating additional contract rules.
Run the following Python snippets in order, in the same process:
from pathlib import Path
import tn
session = tn.Session(Path("agents.md").read_text(encoding="utf-8"))
policy = session.policy("hello.message")
The session has its own identity and encryption keys. This example creates fresh keys and uses them for the whole walkthrough.
2. Create an object and read a field
message = session.create({"message": "Hello, world!"}, policy)
print(message.get("message"))
Hello, world!
create returns a data object with an initial signed copy. get reads a field.
3. Change the field
message.set("message", "Hello again!")
print(message.get("message"))
Hello again!
The working data has changed. The previous signed copy still contains the original greeting.
4. Seal the changed object
The creating session has the publishing capabilities needed to seal its changes:
publication = message.seal(
purpose="send", to="local-reader", decide=lambda _: True
)
seal creates a new encrypted, signed publication, keeping the contract and the reference to the preceding version. Here, the callback adds no application restriction. to records an intended destination; it does not distribute keys or deliver the bytes.
5. Save and read the publication
publication.write("greeting.tn")
saved = tn.GovernedObject.read("greeting.tn")
write saves the signed bytes. read verifies the saved publication; its business fields remain encrypted.
6. Unseal with the keys
The same session already holds the keys for its saved publication:
received = session.unseal(
saved, purpose="read", decide=lambda _: True
)
print(received.get("message"))
Hello again!
unseal verifies the publication and decrypts the message. A session without the required keys cannot open it, even with decide=lambda _: True.
Close the session when finished:
session.close()
The complete walkthrough is available as hello.py, alongside its policy file.
From a greeting to a bank report
The bank/vendor program extends these operations to two applications. Its setup creates separate signing identities and assigns their group capabilities explicitly:
| Application | Can open | Can publish |
|---|---|---|
| Bank | amounts, identity, and governance |
Both business groups and governance |
| Vendor | amounts and governance |
amounts and governance |
The bank publishes two batches. One contains amounts 12 and 18; the other contains 5. Customer names are in the separate identity group. The vendor receives the same signed publications but has no key for that group.
The following code runs from the repository root after installing the SDK. configured supplies the identities, group assignments, and application decisions described in the example's setup. publish_inputs creates the two bank publications. The vendor totals both inputs and publishes only the aggregate:
from python.examples.bank_vendor import configured, publish_inputs
with configured() as parties:
first_input, second_input = publish_inputs(parties)
first = parties.vendor.unseal(first_input, purpose="aggregate")
second = parties.vendor.unseal(second_input, purpose="aggregate")
total = sum(first.get("values")) + sum(second.get("values"))
first.include(second)
first.set("aggregate", total)
first.select(["amounts"], fields={"amounts": ["aggregate"]})
first.attach(parties.report_contract)
result = first.seal(purpose="report")
report = parties.bank.unseal(result, purpose="inspect-report")
print(report.get("aggregate"))
print(len(report.policies))
print(len(report.governance.sources))
35
2
2
Python computes the total. include records the second input and its contract; it does not merge business values. select keeps only the aggregate and removes the original values and unopened identity ciphertext from the output. attach adds the vendor's reporting contract while retaining the bank's contract. The bank receives a result with two contracts and references to both source publications.
Run python python/examples/bank_vendor.py for the complete demonstration, including refused identity access and a refused marketing use. Its tests check the result, group boundary, contracts, and sources.
Optional application rules
TN's governed SDK enforces when a governance decision happens and what contract context is supplied. On receipt, it presents the authenticated writer, carried contracts, requested use, and selected groups before opening business data. The receiving application decides whether that use is allowed.
Key possession can be the application's entire access rule, as in the greeting. An application can also require a specific writer or an accepted contract. This separate example uses the same agents.md file:
from pathlib import Path
import tn
with tn.Session(Path("agents.md").read_text(encoding="utf-8")) as session:
policy = session.policy("hello.message")
publication = session.create({"message": "Hello, world!"}, policy).snapshot
def accepted(context):
return (
context.writer == session.did
and len(context.policies) == 1
and context.policies[0].matches_contract(policy)
)
data = session.unseal(publication, purpose="read", decide=accepted)
print(data.get("message"))
The decision returns a boolean. False refuses the operation; errors also stop it. The contract check compares the exact contract, including its authority. Comparing only a policy name would not establish that match.
For repeated work, configure decisions with session.configure_receive, configure_attach, and configure_release, then bind them with session.workflow(receive=..., release=...). A workflow runs its configured decisions on every operation. The configuration guide shows the arguments and routing behavior.
| Decision | Context available to the application |
|---|---|
| Receive | Verified writer, contracts, source references, requested use, and selected groups; no business plaintext |
| Attach | Requesting authority, proposed contract, existing contracts, and working data |
| Release | Publishing writer, current result, contracts, sources, purpose, optional complete UseContext, and destination |
The wire format carries contracts and provenance. Applications interpret those contracts and decide permitted use; TN does not execute policy prose. Applications also control what they do with plaintext after opening it. A signature authenticates a publication and its declared sources. Checking the calculation requires application evidence beyond that signature.
Python API
Objects and sessions
| Type | Role |
|---|---|
Session |
Owns an identity, group capabilities, policy selection, and operation configuration |
DataObject |
Holds working data, inherited contracts, source references, and its last signed snapshot |
GovernedObject |
Holds a signed publication for verification, storage, or transport |
Governance |
Describes a contract, its authority, references, and dataset bindings |
UseContext(application, purpose, operation) |
Identifies the requested use |
Workflow |
Binds configured input and output purposes for repeated operations |
GovernanceView, AdmittedObject, OpenedObject |
Expose the separate governance, acceptance, and opening stages |
The full set of types is in tn.governed; common types such as Session and DataObject are also exported from tn. Provider interfaces are in tn.providers. Session(policy_text, groups=...) creates fresh local material. Session.from_config(path) loads an existing configuration. A context manager or close() ends the session independently of other sessions.
| Setup call | Configures |
|---|---|
session.policy(name) |
Selects the contract for a new object |
session.configure_receive(use=..., groups=..., object_type=..., decide=...) |
Input type, groups, complete use, and decision |
session.configure_attach(decide=...) |
Authority to add a contract |
session.configure_release(use=..., to=..., object_type=..., decide=...) |
Output type, destination, complete use, and decision |
session.workflow(receive=..., release=...) |
A workflow bound to the named input and output purposes |
UseContext carries the application, purpose, and operation. The to destination is decision context; callers choose the transport. A workflow captures its configuration when bound, and its evaluators run on subsequent operations.
Object operations
In this table, session owns the work, data is a DataObject, publication is a GovernedObject, and work is a configured Workflow. Calls show the usual arguments; linked references give full signatures and defaults.
| Operation | Python call | Result or effect |
|---|---|---|
| Create | session.create(fields, policy, group="default") |
Working data with an initial signed publication |
| Unseal / receive | session.unseal(source, purpose=..., decide=..., groups=...) or work.unseal(source) |
Verify, decide the use, and open selected business groups; receive is the corresponding name |
| Inspect | data.inspect() / publication.inspect() |
Detached working state / authenticated envelope metadata and encrypted blocks |
| Get | data.get(name, group=...) |
Read a field; omit name to read its opened group |
| Set | data.set(name, value, group=...) |
Change a field; name=None replaces a group's business fields |
| Select | data.select(groups, fields=...) |
Retain named groups and optional fields in the result |
| Include | data.include(other) |
Retain a contributor's source references and contracts without merging values |
| Attach | data.attach(policy, decide=...) / work.attach(data, policy) |
Add a contract through an explicit or configured attachment decision while retaining existing contracts |
| Seal / release | data.seal(purpose=..., to=..., decide=...) or work.seal(data) |
Check publication, encrypt, and sign a new version; release is the corresponding name |
| Read | tn.GovernedObject.read(path_or_stream) |
Load and verify a signed publication |
| Write | publication.write(path_or_stream) |
Save its exact signed bytes |
| Forward | publication.forward() |
Return signed bytes for the application's transport |
| Verify | session.verify(wire) |
Authenticate and validate a governed publication |
| Accept | session.governance(publication).accept(use=..., groups=..., decide=...) |
Bind the accepted object, reader, use, and selected groups |
| Open | session.open(admitted, groups) |
Open the admitted groups with the reader's capabilities |
When group is omitted, get and set use the working object's primary group. data.snapshot is its last signed publication. history, revision, and has_unreleased_changes describe its local progress; data.copy() creates a separate working copy. Pending edits must be released before a working object can write or forward its snapshot. Use select explicitly to remove unopened ciphertext from a result when it should not travel onward.
Session.seal(draft) originates a GovernedDraft, built with session.draft(...) and draft.group(...). Use DataObject.seal or Workflow.seal to publish working data. Module-level tn.seal and tn.unseal belong to the separate portable-envelope API; the governed lifecycle described here uses session and workflow methods.
The complete Python API guide documents signatures, defaults, callbacks, group selection, publication history, and transport. The object-operation reference maps the verbs to their native operations. Type signatures describe the exposed Python types.
Policy revisions, datasets, and lineage
| API | Use |
|---|---|
PolicyRevisionDraft, PolicyRevision, PolicyDag |
Publish, admit, and select exact contract revisions with explicit parent and authority decisions |
DatasetEditionDraft, DatasetEdition, DatasetCatalog |
Bind an accepted dataset edition to its exact source publication, contracts, and eligible use |
DatasetSelection |
Carry a catalog-approved selection into unseal(..., selection=...) |
LineageVerifier |
Verify retained source publications through an application-supplied resolver |
ObjectRegisters |
Configure optional signed creation and release metadata records |
Edition names and source references need accepted records behind them. The catalog example constructs those records; the dataset and lineage guide explains how receipt binds them to the requested source and use.
Common failures are VerificationError for invalid publications, NotEntitled for missing group capabilities, UseDenied for a refused decision, and NotAPublisher for missing publication capabilities. The error reference covers callback failures, closed sessions, and register errors as well.
Keys, providers, and revocation
A fresh session is convenient for a first run. Deployed applications need identities and keys that survive restarts, plus explicit assignments for other readers. FileKeyStore.create(...) provisions a local store once; FileKeyStore.open(path) reopens it. It implements both identity and key resolution. The store contains unencrypted credentials protected by filesystem access controls. Keep it in private application storage.
The persistent-key examples run setup, publication, and reading in separate processes. They cover BTN, JWE, and HIBE. BTN uses reader kits and publisher state; JWE uses recipient keys; HIBE uses scoped hierarchical keys. The capability constructors accept existing material through the same session interfaces.
Applications can connect their own infrastructure through five provider contracts:
| Interface | Method and responsibility |
|---|---|
IdentityProvider |
resolve(application) supplies the application's native signing identity |
KeyProvider |
resolve(identity) supplies assigned group capabilities as a KeySet |
GovernanceProvider |
policy and workflow supply configuration; accept, attach, and release decide operations |
CatalogProvider |
resolve(request) supplies an accepted edition selection and its exact publication |
RegisterProvider |
record(event) retains an already signed publication and event metadata |
Providers(identity, keys, governance, catalog=..., registers=...) composes these interfaces. Its session(application, workflows=...) method creates a session with the assigned capabilities. The provider guide, type signatures, and executable setup examples cover each contract.
Giving a reader the relevant group capabilities gives it decryption authority without requiring an online key-release service for each read. Key stores and live authorization services are also supported through providers. Key resolution happens at session setup; the session retains that capability snapshot. An adapter can consult a live authorization service whenever a configured receive, attach, or release decision runs.
Preserve evidence when excluding future access
Revocation must not destroy required evidence. That requirement motivates BTN's forward-only exclusion. A revoked reader is excluded from future ciphertexts produced with the updated publisher state. Retained keys still open historical publications they covered. An application can therefore retain the publications and capabilities needed to inspect earlier work while changing who receives new data.
The application must retain that evidence and set its retention policy. Revocation cannot recall plaintext or keys already copied by a reader. Current BTN cover compression preserves existing wire formats and reader kits; the BTN guide explains its bounds, byte costs, and compatibility tests.
Enterprise patterns from the book
The TN Enterprise Patterns book follows portfolio reporting across data suppliers, analytics, model operations, review, and records management. The SDK includes all 15 executable patterns. Each combines the object API with a concrete application responsibility:
| Application need | Patterns and worked behavior |
|---|---|
| Return the same accepted result after a retry | Request/reply, outbox/inbox, and durable workflow retain exact results and recovery state |
| Give each reader an appropriate view | Gateway and tenant repository select data and check account boundaries |
| Combine the right inputs | Aggregation and pipeline check contributors, representations, and completeness |
| Reuse historical data for a current request | Cache, CQRS, and data product retain source identities and evaluate the selected use |
| Coordinate publication and change | Monolith, publish/subscribe, saga, archive, and migration separate decisions from transaction and delivery state |
The examples use SQLite for their application records. They show where to commit business state, how to handle duplicates, and what to retain for recovery. TN operations supply signed objects within those transactions; application code owns the transaction and transport behavior.
Evidence and paper reproducibility
The release checks execute every Python block in this README, the standalone greeting, and the governed API, provider, persistent-key, bank/vendor, and enterprise tests against installed wheels. The release workflow builds and verifies Linux and Windows wheels before publication.
| Claim to inspect | Executable evidence |
|---|---|
| Invalid signatures and altered signed content are refused before admission | Python signature tests and native content-integrity tests |
| A vendor opens amounts while identity data stays unavailable | Bank/vendor example and assertions |
| A result retains the bank and vendor contracts and both source references | Bank/vendor result checks |
| Refusal and evaluator failure precede business decryption | Instrumented native receipt tests |
| Compressed BTN covers exclude revoked readers and preserve covered historical access | Real-key compression tests |
| Cover geometry and serialized costs match the implementation | BTN derivation, scoped tests, and encoding arithmetic |
| Paper test counts and measurements have a defined artifact scope | Paper reproducibility review |
From a checkout of this release, install the test dependencies and run the example checks. The Rust command also requires a Rust toolchain:
python -m pip install "tn-proto[test]==2026.9.13b5"
python python/examples/bank_vendor.py
python -m pytest python/tests/test_bank_vendor_example.py python/examples/enterprise -q
cargo test --locked -p tn-btn
The paper's historical experiment pins SDK revision c83a46a57310fcaccc832e50d6dbd75bd477b5b5. Its 34 Python cases and ten native tests describe the separate experiment suite. Current SDK release checks have their own scope. The September 13 paper review records the inspected artifacts and the changes present in beta 2; this documentation release does not rerun or replace those historical measurements.
For example, the current BTN implementation uses one difference entry for a single revoked leaf in a height-eight tree, reducing overhead from 545 to 132 bytes. The paper's retained tables measured the earlier walker. This comparison measures serialized overhead; it includes no new latency measurements. The separate experiment still needs an identified public, immutable archive; this SDK checkout alone does not reproduce every paper result.
The Python guide's security boundaries describe visible envelope metadata, equality leakage, and the external information needed to check history completeness. Signatures authenticate declared provenance; applications remain responsible for checking calculations, required inputs, and permitted disclosure.
Source, support, and license
The implementation is in crypto/tn-core, the PyO3 bindings in crypto/tn-core-py, and the Python package in python. Use GitHub issues for reproducible bugs and documentation corrections, including the package version and a minimal example with private data removed.
Dual-licensed under the MIT License or the Apache License, Version 2.0.
Metadata
Release files for tn-proto 2026.9.13b5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tn_proto-2026.9.13b5-cp39-abi3-win_amd64.whl | CPython 3.9 | abi3 | Windows x86-64 | Details |
| tn_proto-2026.9.13b5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.9 | abi3 | Linux glibc 2.17+ x86-64 | Details |
Total release size: 6.8 MB
Release files / tn_proto-2026.9.13b5-cp39-abi3-win_amd64.whl
| Download URL | tn_proto-2026.9.13b5-cp39-abi3-win_amd64.whl |
|---|---|
| Size | 3.4 MB |
| Tags | CPython 3.9 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
87c8142d8a13a893bf56a233b6c9733cd49446ebcbdb0ef6219dcbf19d06cbdf
|
|
BLAKE2b-256 checksum How to use checksums |
34cffa927324beeb644eb608ac09b0f9b9ee32043d565d13b5571956e3090ba0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / tn_proto-2026.9.13b5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | tn_proto-2026.9.13b5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 3.4 MB |
| Tags | CPython 3.9 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
9d94c7ffc6607ff460360f13f73e982d954f8235df2f2183c0bff2739eed6e58
|
|
BLAKE2b-256 checksum How to use checksums |
d3703e8bb91d398652a556a5a5fcfa9216483b98642aebd4f990f5bf19c60a59
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|