TN protocol: attested logging with the btn broadcast cipher
Project description
tn-proto
tn-proto keeps every record readable only by the people you've authorized - and leaves cryptographic proof that it did. Fields are encrypted per reader, so the wrong people simply can't decrypt them; each entry is signed by your device and hash-chained, so anyone can verify offline - from the log file alone - who was allowed to read what, and that nothing was altered after the fact.
Installation
pip install tn-proto
Quickstart
The first run mints a ceremony under ./.tn/ - nothing to configure.
Python
import tn
tn.init() # or tn.init("billing") for a named project
tn.info("order.created", order_id="A100", amount=4999)
tn.warning("order.flagged", order_id="A100", reason="hold")
for entry in tn.read():
print(entry.level, entry.event_type, entry.fields)
info order.created {'amount': 4999, 'order_id': 'A100'}
warning order.flagged {'order_id': 'A100', 'reason': 'hold'}
tn.read() hands you back decrypted fields. The same entry as written to disk is sealed: your values are encrypted into the group, the row is signed and hash-chained.
{
"device_identity": "did:key:z6MkeWpUKjEJ8PNmJWT4X4kXudbcaJ3kWVkKZ21vBdksX3x5",
"event_type": "order.created",
"level": "info",
"sequence": 1,
"prev_hash": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
"row_hash": "sha256:9ab903c5772710619b2b5a43b41b7ff0b13dd2651fafcb64b054232cd8022c3e",
"signature": "jvOHz3BfZwPx57eHwzXgUhhe8xlwkAZfFH5J7swZcrg1edWuBo0iyIdKNrMw7ggZ…",
"default": {
"ciphertext": "twEB/kT5M/D6sO4HPyjtmpkX6oCxc5SPm41fpXf/Ukbr9GMAAAAAAAEAFJ+sOaP…",
"field_hashes": { "amount": "hmac-sha256:v1:35e986d4e9152723…" }
}
}
order_id and amount appear nowhere in the clear: only the ciphertext sealed to the default group, plus equality-search hashes. Anyone without a reader key sees exactly this.
TypeScript / Node - byte-identical records:
import * as tn from "@cyaxios/tn-proto";
await tn.init();
tn.info("order.created", { order_id: "A100", amount: 4999 });
for (const entry of tn.read()) console.log(entry.level, entry.event_type, entry.fields);
await tn.close();
Set TN_NO_STDOUT=1 to silence the stdout echo. There's no explicit flush in Python - the SDK drains on interpreter exit (tn.flush_and_close() if you want to force it).
What you get
TN does two jobs at once: it keeps each record from reaching the wrong eyes, and it leaves a verifiable receipt that you kept it that way.
Control who can read what
- Private by default - field values are encrypted on disk. The wrong people don't get a redacted view; they get ciphertext they can't open.
- Per-reader - one entry can be sealed for several named parties, each with their own key, so each sees only what they're authorized to.
- Revocable - cut a reader off and the next entry is already beyond their reach; everyone else keeps reading, no rekeying.
Prove you did
- Signed - each entry carries an Ed25519 signature from the device that wrote it: who wrote it, provably.
- Tamper-evident - entries are hash-chained, so altering, reordering, or deleting one fails verification.
You confirm all of it offline, from the log file and a public key alone - no server to trust, no vendor's word to take.
The verbs
| Verb | What it does |
|---|---|
tn.init() / tn.init("name") |
resolve or create a ceremony and bind it as the module runtime |
tn.use("name") |
open or create a named ceremony as a standalone handle (t = tn.use("billing"); t.info(...)); unlike init it doesn't rebind the module default, so you can write to several projects in one process |
tn.info / .warning / .error / .debug |
one signed, encrypted entry at that level |
tn.log("evt", level="trace") |
severity-less entry (level= sets a custom level). Unlike the level verbs, it returns the written entry - the signed, encrypted envelope - as a dict whose str() is valid JSON, so you can forward it straight to a downstream path (a POST, a queue, a webhook): requests.post(url, json=tn.log("evt", k=v)) or data=str(tn.log(...)). The level verbs are fire-and-forget and return nothing. |
tn.read() |
iterate decoded Entry objects |
tn.watch() |
tail the log live |
tn.export / tn.absorb |
produce or install a .tnpkg bundle |
with tn.scope(request_id="r-42"): |
layer request-context fields onto every entry inside the block (metadata that rides along; this is not a group) |
tn.log is not an alias of tn.info - it emits with whatever level= you pass (default ""), and it ignores the level threshold, so it always emits. Reach for it when you need a level outside debug/info/warning/error.
Reading: all entries, this run, admin
for e in tn.read(): # default: every entry on disk (all_runs=True)
...
for e in tn.read(all_runs=False): # only what THIS process emitted
...
for e in tn.read(log="admin"): # the admin log (ceremony lifecycle: tn.* events)
print(e.level, e.event_type)
info tn.ceremony.init
info tn.group.added
tn.read(verify=True) re-checks every signature and the full hash chain as it reads, and raises the moment something doesn't add up.
The CLI
Installing the package gives you a tn command (tn-js for the TypeScript build). It's non-interactive by default - safe to drop straight into CI and containers.
| Command | What it does |
|---|---|
tn init [name] |
provision identity + ceremony under ./.tn/ |
tn read [--all-runs] |
decoded entries to stdout |
tn info --event <type> [--field k=v]… |
emit one attested entry from the shell |
tn add_recipient <group> <name> |
mint a reader kit for someone, wrapped as a .tnpkg |
tn invite |
invite a reader (by email/label) |
tn group |
add / inspect groups |
tn rotate |
rotate group keys; emit one per-reader .tnpkg |
tn absorb / tn import |
install a .tnpkg someone sent you |
tn export / tn compile / tn bundle |
produce .tnpkg bundles from your keystore |
tn wallet |
vault: status, link, restore, sync |
tn account / tn vault |
manage the vault account / emit vault events |
tn streams |
list ceremonies under ./.tn/ |
tn validate |
validate the project's config tree |
tn show env / tn show profiles |
reflective inspection (secrets redacted) |
tn seal / tn verify / tn canonical |
attest / verify / canonicalize envelopes from stdin |
tn init # provision in ./.tn/
tn info --event order.created --field order_id=A100 --field amount=4999
tn read --all-runs # include entries from prior runs
python -m tn.watch ./tn.yaml | jq . # follow the log live
How log sharing works
You never share a password or a private key. Access is cryptographic:
- Identity (DID). Every device has its own identity - a public
did:key:z6Mk…derived from its Ed25519 key. Private keys never leave the machine. - Groups. Events land in named groups (default:
default); each group is its own encrypted domain with its own reader list. Readers ofpaymentscan decryptpaymentsevents, and only those. - Reader kits. To let someone read a group, you mint a kit addressed to their DID and send it. They absorb it and can decrypt that group - and nothing else.
- Revocation. Revoke a reader and future entries are encrypted to exclude them; every other reader keeps working, no rekeying.
Grant access (Python):
import tn
tn.init()
result = tn.admin.add_recipient(
group="default",
recipient_did="did:key:z6Mk…", # the reader's real Ed25519 DID
out_path="./alice.btn.mykit",
)
print(result.leaf_index, result.kit_path) # -> 1 alice.btn.mykit
Or one-shot from the CLI (mints the kit and wraps a .tnpkg):
$ tn add_recipient default alice
[tn add_recipient] wrote /your/cwd/alice.tnpkg
[tn add_recipient] group: default
[tn add_recipient] recipient: did:key:zLabel-alice
The CLI synthesizes a friendly label DID (did:key:zLabel-alice) from the name so you can try sharing without copying real keys around; production readers are addressed by their own did:key:z6Mk….
Revoke when you need to:
tn.admin.revoke_recipient(group="default", leaf_index=1)
Groups and field routing
A group is an encrypted domain with its own reader list; routing a field into it means that field's value is sealed to that group's readers and to no one else. One command creates the group and routes fields in a single step:
tn group add payments --fields order_id,amount,card_last4 # creates the group + routes the fields
From then on, any tn.info(...) carrying order_id, amount, or card_last4 seals those into payments; every other field stays in default. The same in-process:
import tn
tn.init()
tn.ensure_group(tn.current_config(), "payments",
fields=["order_id", "amount", "card_last4"])
tn.info("order.created", order_id="A100", amount=4999, note="ship today")
# order_id + amount -> sealed to 'payments'
# note -> 'default'
You can also hand-edit the groups: and fields: blocks in tn.yaml (see Configuration); the SDK picks up the change in the same process.
Bundles (.tnpkg)
A .tnpkg is a signed zip with a manifest and body files - the unit everything is shared as.
# Producer: seal a kit so only the named DID can open it.
tn.export("alice.tnpkg", kind="kit_bundle", to_did="did:key:z6Mk…", seal_for_recipient=True)
# Reader: absorb merges it into your current ceremony (run tn.init() first).
tn.init()
receipt = tn.absorb("./alice.tnpkg")
print(receipt.kind, receipt.accepted_count, receipt.deduped_count) # -> kit_bundle 1 0
Rotation
tn rotate writes a new generation of group keys and emits one .tnpkg per surviving reader. Hand each reader their file (vault push, CI artifact, email); they run tn absorb. A revoked reader isn't in the new generation, so they keep old entries but can't read anything after the rotation.
$ tn rotate
[tn rotate] rotated 1 group(s); emitted 1 .tnpkg artifact(s) into /your/cwd/rotated_20260612T194533Z
default: epoch=1
-> did_key_zLabel-alice.tnpkg
Non-custodial vault backup
Your keys live on your machine and nowhere else - so nobody, us included, can read your data. The optional vault at vault.tn-proto.org is the safety net for when that machine dies.
┌────────────────────────────────────────────────────┐
│ vault.tn-proto.org │
│ stores ciphertext it CANNOT decrypt: │
│ - your encrypted group keys │
│ - your config (tn.yaml) │
└────────────────────────────────────────────────────┘
▲ │
backup: │ │ restore:
keys + config │ ▼ your mnemonic
┌────────────────────────────────────────────────────┐
│ your machine │
│ .tn/<project>/keys/ -> backed up to the vault │
│ .tn/<project>/logs/ -> 100% local, never sent │
└────────────────────────────────────────────────────┘
- Keys & config only. Your
tn.yamland encrypted group keys are backed up. Your.ndjsonlog files are 100% local and never uploaded. - Zero-knowledge. The vault holds ciphertext it cannot read; recovery is gated by a mnemonic phrase only you hold.
Your first init prints a claim link
Unless you pass --no-link, the first tn init mints your device identity, pushes the encrypted keys + config, and prints a claim link:
$ tn init demoproj
[tn init] Ceremony local_233ff998 created at ./.tn/demoproj/tn.yaml
[tn init] project: demoproj
[tn init] cipher: btn
[tn init] keystore: ./.tn/demoproj/keys
[tn init] Backed up to https://vault.tn-proto.org
[tn init] vault_id: 01KTYX… # id of the pending backup
[tn init] expires: 2026-06-13 17:56 # the claim link is good for ~24h
[tn init] CLAIM URL - open this in your browser to attach the project to your account:
https://vault.tn-proto.org/claim/01KTYX…#k=••••••••
[tn init] Already have a vault account, or want to attach this project later?
[tn init] 1. Sign in at https://vault.tn-proto.org/account
[tn init] 2. On the Projects tab, mint a connect code
[tn init] 3. Run: tn account connect <code> --yaml ./.tn/demoproj/tn.yaml
Open the claim link and a vault page attaches this backup to your account (Google or passkey), so you can restore it on any machine later. Two parts of that URL matter:
/claim/01KTYX…points at the encrypted backup thisinitjust pushed.#k=••••••••is the decryption key, carried in the URL fragment. Browsers never send the fragment to the server, so the claim page decrypts in your browser and the vault still never sees your key. That is what keeps it zero-knowledge.
Treat the whole link like a password: anyone holding it (fragment included) can claim that backup, and it stops working after the expires: time. Already have an account? Skip the link and use the sign-in + connect-code steps it prints.
Key recovery. Sign in at https://vault.tn-proto.org/account (the dashboard also lets you invite readers by email and trigger rotations). To recover on a new machine:
tn wallet status # is this machine linked, and to what
tn wallet restore # rebuild every ceremony's keystore from your recovery phrase
Turn it off. You are never tied to the vault:
tn init --no-link # fully offline; never contacts a vault
export TN_NO_LINK=1 # same, as an environment switch
export TN_VAULT_URL="https://my-vault…" # or point at your own
Profiles
A profile is a named bundle of three independent guarantees - pick the trade-off, not the knobs:
- Encryption - always on. Field values are encrypted into their groups in every profile. There is no plaintext mode; this is the floor.
- Signing - an Ed25519 signature from the writing device on each entry, proving authorship. The evidence profiles keep it; the lightweight ones drop it for speed.
- Chaining (verification) - the hash link from each entry to the one before it. This is what makes the log tamper-evident and ordered, and what
read(verify=True)checks. The evidence profiles keep it.
tn.init(profile="audit")
| Profile | Encrypt | Sign | Chain | Use it for |
|---|---|---|---|---|
transaction (default) |
✓ | ✓ | ✓ | grants, payments, agent actions, security events - full evidence |
audit |
✓ | ✓ | ✓ | normal business events; same evidence, buffered for throughput |
secure_log |
✓ | ✓ | - | signed app logs where authorship matters more than ordering |
telemetry |
✓ | - | - | high-volume traces / metrics; near-zero overhead, stdout |
stdout |
✓ | - | - | dev / notebook scratchpad, encryption still on |
Configuration (tn.yaml)
tn.yaml is generated by tn init, and the CLI and SDK keep it in sync for you (tn group add, tn.ensure_group, the vault verbs all write it). You normally never edit it by hand - reach for a verb instead. It is plain YAML, so you can hand-edit it once you know the schema, but a malformed file can break loading or field routing, so treat that as an advanced path. It is shown here in full (comments are explanatory; the emitted file has none) so you can see what the tools manage:
ceremony:
id: local_f2bb8224 # ceremony identifier
mode: local # local | linked (linked = backed by a vault)
linked_vault: '' # vault URL; empty when offline
linked_project_id: '' # vault-side project id; filled by `tn wallet link`
sync_logs: false # also sync ndjson logs to the vault
cipher: btn # ceremony-wide cipher
sign: true # Ed25519-sign every row
admin_log_location: ./admin/default.ndjson # tn.* admin events; read via tn.read(log="admin")
log_level: debug # debug | info | warning | error
profile: transaction # evidence profile (see Profiles)
chain: true # maintain the per-event-type hash chain
project_name: demoproj # human label; sent as X-Project-Name on vault push
logs:
path: ./logs/default.ndjson # main user-log ndjson destination
keystore:
path: ./keys # holds local.private, *.btn.state, etc.
device:
device_identity: did:key:z6Mk… # this machine's DID
handlers: # output sinks; replaces the implicit default
- kind: file.rotating
name: main
path: ./logs/default.ndjson
max_bytes: 5242880
backup_count: 5
- kind: stdout
public_fields: # fields always written in the clear (additive to defaults)
- timestamp
- event_id
- event_type
- level
default_policy: private # policy for fields not routed to any group
groups:
default:
policy: private
cipher: btn
recipients:
- recipient_identity: did:key:z6Mk… # you
payments: # a group you added
policy: private
cipher: btn
fields: [order_id, amount, card_last4] # route these fields into 'payments'
recipients:
- recipient_identity: did:key:z6Mk… # who may read 'payments'
tn.agents: # reserved protocol group, auto-injected for agent policy
policy: private
cipher: btn
fields: [instruction, use_for, do_not_use_for, consequences, on_violation_or_error, policy]
recipients:
- recipient_identity: did:key:z6Mk…
fields: {} # field-routing overrides; groups carry their own
llm_classifier: # optional auto-classification of fields into groups
enabled: false
provider: ''
model: ''
A single tn.info(...) can fan one event into several groups, each encrypted to that group's readers only. Log and admin paths also accept templated paths ({event_class}, {date}, {event_id}, …) so events sort themselves on disk. Calling tn.init("billing") against a project creates a named stream that shares the project's identity (.tn/default/keys) while owning its own log.
Every
tn.yamlfield - groups, field routing, ciphers, handlers, profiles, ceremony/link state - is documented in thetn.yamlreference.
Scoped lifecycle
For tests and short-lived processes that shouldn't leave a ./.tn/ behind:
with tn.session() as s:
s.info("order.created", order_id="A100")
# block exit: the ephemeral ceremony is torn down; the global runtime is restored
For long-running services, call tn.init() once at startup; the runtime lives for the process and drains on exit.
Containers & CI
No home directory, no baking keys into an image. Set one secret - TN_API_KEY - and the container trades it with the vault for its keystore on first boot, then runs normally. If a keystore already exists on disk, that wins and the env var is ignored. Full guide: running in containers and CI.
Environment variables
Everything has a sensible default; these override it. tn show env prints the full canonical surface (secrets redacted).
| Variable | What it does |
|---|---|
TN_API_KEY |
Container/CI bootstrap: traded with the vault for this project's keystore on first boot. Ignored when a local keystore already exists. |
TN_NO_LINK=1 |
Never auto-link a fresh ceremony to a vault - fully offline. |
TN_VAULT_URL |
Base URL of the vault to use. Default: https://vault.tn-proto.org. |
TN_NO_STDOUT=1 |
Silence the stdout echo of each entry. |
TN_IDENTITY_DIR |
Directory holding your identity.json. Default: the OS data dir (~/.local/share/tn, %APPDATA%\tn). |
TN_YAML |
Explicit path to tn.yaml for init / discovery. |
TN_HOME |
Root for shared TN state. Default: ~/.tn. |
TN_STRICT=1 |
Disable ceremony auto-discovery; init() must be given an explicit project. |
AI coding agents - tn-skills
tn-skills teaches your AI coding agent to use TN correctly. With it installed, the agent routes PII into the right encrypted group, calls tn.init once at startup (not inside a request handler), never logs a secret like a CVV, and cites the right regulation when a file's domain matches one of its built-in industry kits - so agent-written code doesn't quietly tell the wrong thing to the wrong people.
Install it in Claude Code:
/plugin marketplace add cyaxios/tn-skills
/plugin install tn-logging@tn-skills
For other AI tools, drop the repo's AGENTS.md into your agent. The bundled skills and industry kits are documented at https://github.com/cyaxios/tn-skills.
One core, every language
The wire format is identical across Python, Node, and the browser, checked on every change - write in one, read in another.
| Runtime | Install |
|---|---|
| Python | pip install tn-proto |
| Node / TypeScript | npm install @cyaxios/tn-proto |
| Browser | the @cyaxios/tn-proto/core subpath (no Node deps) |
Troubleshooting
| Symptom | Likely cause |
|---|---|
tn.read() shows entries from previous runs |
the default is all_runs=True; pass all_runs=False to scope to this run |
tn.watch shows no tn.* events |
by design - pass log="admin" for ceremony events |
no ceremony found on tn.absorb |
absorb merges into an existing ceremony; run tn.init() first |
KeystoreConflict: state … diverged |
another process mutated the same ceremony; re-run the admin verb to pick up fresh state |
Documentation
- Getting started · Python cookbook · TypeScript cookbook
- Groups, readers, bundles, rotation · Running in containers and CI
- Profiles · tn.yaml reference · protocol spec
License
Dual-licensed under the MIT License or the Apache License, Version 2.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tn_proto-0.6.2-cp39-abi3-win_amd64.whl.
File metadata
- Download URL: tn_proto-0.6.2-cp39-abi3-win_amd64.whl
- Upload date:
- Size: 2.0 MB
- Tags: CPython 3.9+, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b6041d646b8513784b8ab27679e75cdbb7fad0c4db2bf1ede8811ccc46c90987
|
|
| MD5 |
59ca68b046771e2258c78f0df2e4d4b5
|
|
| BLAKE2b-256 |
b24cb31dc4654b62585524782cc96bfa677143df9a3d960f062258af12befb75
|
File details
Details for the file tn_proto-0.6.2-cp39-abi3-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: tn_proto-0.6.2-cp39-abi3-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 2.3 MB
- Tags: CPython 3.9+, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d6a17c68892dc0f0d3ed9e02df387aa1b82316ce08c8561bfc551a452246fbe1
|
|
| MD5 |
30593ce5352750a410bba0025066cd28
|
|
| BLAKE2b-256 |
f3d0146b8a4084dd30f839d4e1efce1102cc1c1989fadf604249cdd3d26fa214
|
File details
Details for the file tn_proto-0.6.2-cp39-abi3-musllinux_1_2_aarch64.whl.
File metadata
- Download URL: tn_proto-0.6.2-cp39-abi3-musllinux_1_2_aarch64.whl
- Upload date:
- Size: 2.2 MB
- Tags: CPython 3.9+, musllinux: musl 1.2+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
59abf7ba057dda880644022389bfffbd6e20c5350a8da5e9c994965bf7ef0194
|
|
| MD5 |
63b84a7ed32c890ab8971b52bb446257
|
|
| BLAKE2b-256 |
18cb55cc2e7728deb4ccae93aa22fe69560376ac296d924e353df64824deb6b6
|
File details
Details for the file tn_proto-0.6.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: tn_proto-0.6.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 2.1 MB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
28d0a4bb52a0b9479766ff5f0bd9c61cdc9d2f4f77ad5a6691213b0099da58b4
|
|
| MD5 |
1e0446eeafd16717babe7372e320a686
|
|
| BLAKE2b-256 |
d577c4d17ef2640b3a31a65e1650a30f8bf601be2e614368176ac3f1d35bb025
|
File details
Details for the file tn_proto-0.6.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: tn_proto-0.6.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 2.0 MB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
78dcc8f872b9da8d7deab07a7e6c6b532b67a914d97c8047262805b52c4a5301
|
|
| MD5 |
e5c2fe038d55227bf3922655154603a9
|
|
| BLAKE2b-256 |
5ee5acaca16c20d4fa264b81baf2baffd60116b98383ebddb461ff92a963a5f7
|
File details
Details for the file tn_proto-0.6.2-cp39-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: tn_proto-0.6.2-cp39-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.9 MB
- Tags: CPython 3.9+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
18c2871d069ee1382c301a53bd79edbd3bd81825b1e1a3c28ae5d72cc00bc082
|
|
| MD5 |
fe8bc68213b93f3aa3e20c2dd79fdd42
|
|
| BLAKE2b-256 |
edc628e367fa7b8afca29a2e1ac664e84cb9d08db0a1145ddf2a7826e0b051e8
|
File details
Details for the file tn_proto-0.6.2-cp39-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: tn_proto-0.6.2-cp39-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 2.1 MB
- Tags: CPython 3.9+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b1a36a010b344bd67f6d1baab2ea1bb8cf5a439f3881114de99535fa74214f3
|
|
| MD5 |
9ee61cffa7ad1654bb949508ef10bacf
|
|
| BLAKE2b-256 |
f3cf7fe32e24a8a8bcbc7036cda2935b05284810716a9f4f21b5058eac0b0711
|