Sanning Proof
Offline verification for the Sanning evidence plane: standalone, dependency-light verification kernels in Python and TypeScript, plus the ratified specs and the conformance corpus both kernels are gated against, byte for byte. No network, no accounts, no vendor code in the trust path — anyone can check a piece of anchored evidence with nothing but this package.
- Python —
sanning_proof(PyPI:sanning-proof), at the repo root. - TypeScript —
@sanning/proof(npm), ints/— browser + Node ≥ 20, ESM.
Verify a signed envelope fetched from any Arweave gateway:
import json
import urllib.request
from sanning_proof import verify_envelope
raw = urllib.request.urlopen("https://arweave.net/raw/<tx_id>").read()
result = verify_envelope(json.loads(raw))
assert result.ok # spec_version + payload binding + Ed25519 signature
import { verifyEnvelope } from "@sanning/proof";
const env = await (await fetch("https://arweave.net/raw/<tx_id>")).json();
const result = await verifyEnvelope(env);
result.ok; // spec_version + payload binding + Ed25519 signature
[!NOTE] Verification never needs an account or a server. If you are producing evidence — anchoring agent steps, running a fleet under continuous tamper-evidence — the hosted plane is console.sanning.io and the write SDK is
@sanning/anchor.
Install
pip install sanning-proof # Python
npm install @sanning/proof # TypeScript
What a verdict proves — and what it doesn't
result.ok proves exactly this: the holder of the private key matching the envelope's public_key signed exactly these bytes, and the payload binding holds.
It deliberately does not prove:
- Whose key that is. The key ↔ identity binding comes from out of band — for example an enrollment roster. The kernel checks cryptography, not identity.
- That the envelope is on-chain. Fetch the transaction from a gateway yourself and re-verify — which is exactly what the example above does.
- When it happened.
signed_atis the signer's claim; witnessed time comes from the Arweave block the envelope landed in. - What the raw data was. Envelopes are content-blind: they commit to hashes. Raw bytes never pass through this kernel unless the producer explicitly disclosed them alongside the proof.
For a verification tool, the guarantee boundary is the product — a verifier that overclaims is worse than no verifier at all.
Verify more things
Bind an artifact you hold to the provenance an envelope commits to (reverse lookup):
import hashlib
artifact_hash = hashlib.sha256(open("model.pkl", "rb").read()).hexdigest()
result = verify_envelope(envelope, expected_content_hash=artifact_hash)
print(result.content_hash_ok, result.content_role) # True, "asset"
Verify an external-commitment envelope (sanning.mlflow/v1) against the committed bytes:
result = verify_envelope(envelope, payload_bytes=canonical_bytes)
Verify an inclusion-proof bundle — proves a leaf event was in a signed checkpoint:
from sanning_proof import verify_proof_bundle
bundle = json.load(open("proof-bundle.json"))
result = verify_proof_bundle(bundle)
assert result.ok and result.inclusion_ok
The TypeScript kernel has full parity, including the RFC 9162 Merkle primitives (leafHash / merkleRoot / auditPath / verifyInclusion) — see ts/README.md.
The CLI
The TypeScript package ships a turnkey CLI. Pinned exit codes — 0 verified · 1 failed · 2 malformed · 3 gateway-unavailable — safe to gate CI on.
Verify any evidence or agent-proof bundle, fully offline; optionally re-fetch checkpoints on-chain by passing gateways:
npx @sanning/proof verify <bundle.json> [gateway1,gateway2,...] [--logs <logs.json>]
--logs binds disclosed raw logs to their committed hashes (evidence bundles only).
Compose an attested export — turn a source trace bundle plus operator attestation records into one signed, offline-verifiable sanning.evidence/v1 bundle that verifies like any other:
npx @sanning/proof export <source-bundle.json> --attestations <att.json> --key <exporter.hex> -o export.json
npx @sanning/proof verify export.json
Programmatic equivalent: composeExport(sourceBundle, attestations, { privateKey }). Full format: specs/evidence-export.md.
Signing (producers)
Most producers never call the kernel directly — the write SDK (@sanning/anchor), the agent daemon, and the MLflow plugin all sign through it. For a custom producer:
from sanning_proof import sign_envelope, signing_key_from_seed_hex
key = signing_key_from_seed_hex("<32-byte seed hex>")
envelope = sign_envelope({...}, key) # the envelope minus `signature`, per the spec
The standard
This repo is the authoritative home of the evidence plane's standards layer — the contract is public alongside the reference verifiers:
specs/envelope-spec.md— the producer-neutral Verifiable Event Envelope family contract (ratified v1.0, amended through v1.4, 2026-07-15).specs/evidence-bundle.md— thesanning.evidence/v1report wrapper.specs/evidence-export.md— the attested-export format behindproof export.specs/architecture.md— the kernel / producer / connector / transport factoring standard.specs/governance.md— who decides, and how.test-vectors/— the conformance corpus (current cut: tagtest-vectors-v2.0, per-file SHA-256 inCORPUS-v2.md); generated bytools/gen-vectors/, never hand-edited.
Three envelope profiles are registered against the family contract: sanning.agent/v1 (the agent daemon's inline-payload profile), sanning.mlflow/v1 (the MLflow plugin's external-commitment profile), and sanning.events/v1 (the anchor SDK's minimal-disclosure profile). The Python kernel accepts all three; the TypeScript kernel accepts sanning.agent/v1 and sanning.events/v1 (the mlflow dialect is Python-only). Additive minors are accepted within a major; unknown majors and malformed versions fail closed.
Conformance discipline: both kernels reproduce the corpus byte for byte — JCS-canonical bytes, payload hashes, envelope-for-signature bytes, deterministic signatures, Merkle roots, audit paths. If this package disagrees with a vector, the package is wrong — never the vector.
Kernel scope
The kernel is deliberately small: canonicalization (RFC 8785), SHA-256 hashing, Ed25519 sign/verify, RFC 9162 binary Merkle inclusion proofs, and the profile registry — no I/O, no networking, no key lifecycle. Gateway fetching, attestation polling, and key storage belong to the products that import it. Dependencies are deliberately minimal: Python has exactly two (PyNaCl, jcs); TypeScript has exactly two (@noble/ed25519, canonicalize).
Development
python3 -m venv .venv && .venv/bin/pip install -e .[dev]
.venv/bin/pytest -q # the conformance gate is the contract
.venv/bin/black src tests
The TypeScript kernel lives in ts/ and is conformance-gated in CI against the same test-vectors/ directory.
Contributing
Issues and pull requests are welcome. If you believe a kernel disagrees with a spec or a corpus vector, open an issue titled contract conflict: <spec> §<section> with the smallest reproduction — see specs/governance.md §6.
Security
Please report vulnerabilities privately via GitHub Security Advisories ("Report a vulnerability" under this repo's Security tab) and do not open a public issue.
License
MIT. The verifier is deliberately open-licensed so anyone can audit it and verify evidence independently of Sanning.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
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 sanning_proof-0.4.0.tar.gz.
File metadata
- Download URL: sanning_proof-0.4.0.tar.gz
- Upload date:
- Size: 375.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
90913ed2c7a223dc3170e75b02ff80ce66c916629096c0c976998ffbb93837d6
|
|
| MD5 |
1db9dc46984721e2299418a0090797ad
|
|
| BLAKE2b-256 |
aacbab59cb808401eea1500dd6eb8591cd9a55c7a8610b3718145e4746999dd2
|
Provenance
The following attestation bundles were made for sanning_proof-0.4.0.tar.gz:
Publisher:
release.yml on sanning-io/proof
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sanning_proof-0.4.0.tar.gz -
Subject digest:
90913ed2c7a223dc3170e75b02ff80ce66c916629096c0c976998ffbb93837d6 - Sigstore transparency entry: 2498983722
- Sigstore integration time:
-
Permalink:
sanning-io/proof@bd20478944ffc0aa689dacd8e50f69c5d47ad107 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/sanning-io
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@bd20478944ffc0aa689dacd8e50f69c5d47ad107 -
Trigger Event:
push
-
Statement type:
File details
Details for the file sanning_proof-0.4.0-py3-none-any.whl.
File metadata
- Download URL: sanning_proof-0.4.0-py3-none-any.whl
- Upload date:
- Size: 34.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f634103e081cad4f1830bb4d22f9fac02e96d13b858132cec7995dd51d2e2865
|
|
| MD5 |
60165bcd6ec9bfad1d655f5198010ef1
|
|
| BLAKE2b-256 |
611696fafa7b22ef6dcde938d94696de648d1f741793cf96d45ce6aa08e370b1
|
Provenance
The following attestation bundles were made for sanning_proof-0.4.0-py3-none-any.whl:
Publisher:
release.yml on sanning-io/proof
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sanning_proof-0.4.0-py3-none-any.whl -
Subject digest:
f634103e081cad4f1830bb4d22f9fac02e96d13b858132cec7995dd51d2e2865 - Sigstore transparency entry: 2498983734
- Sigstore integration time:
-
Permalink:
sanning-io/proof@bd20478944ffc0aa689dacd8e50f69c5d47ad107 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/sanning-io
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@bd20478944ffc0aa689dacd8e50f69c5d47ad107 -
Trigger Event:
push
-
Statement type: