What you can prove with Satsignal.
Three verifiable outcomes, three live primitives, one bearer-auth API. Each outcome is an audit-trail artifact an outside auditor or regulator can verify without trusting Satsignal — co-attested by Bitcoin SV miners, verified in any browser. Works for any file, too.
Three things a receipt proves.
Each maps to a live API primitive. Two of them — what an agent was allowed to do, and what evidence it used or produced — build in selective disclosure: reveal one component or one row to an auditor while the rest stay sealed.
What an agent committed to
Anchor a hashed decision before anyone reveals theirs. Sealed bids, model evaluations, prediction markets, multi-agent voting — nobody, including the operator, can rewrite when the commitment was made.
What an agent was allowed to do
Anchor a snapshot of the system policy, user instruction, tool permissions, budget, and model config the agent was running under. Auditors verify any single component without seeing the others.
What evidence it used or produced
Anchor a batch of up to 10,000 items in one on-chain
receipt. Selective disclosure: any holder later
proves a single item was in the batch — without
revealing the others. Right pattern for high-volume agent
runs; same shape via merkle-row-sealed-v1
for tabular data.
Four properties worth checking when you evaluate a receipt format.
Plenty of products in this space stop at “a hash existed at a moment.” That’s table stakes; what comes after is where formats diverge. Four properties that matter operationally:
Selective disclosure of low-entropy rows
A timestamp on a 50KB document is fine; a timestamp on a single bid amount, yes/no vote, or one row of a small enum leaks the answer to anyone who can enumerate the candidate set. Look for a scheme that HMACs each leaf under a per-leaf salt so the holder can reveal one row of a sealed table without unsealing the others.
Verification that doesn’t depend on us
If a receipt only verifies through the issuer’s servers, the issuer staying online is part of your trust story. Anchoring on a public chain pushes that dependency onto the chain itself. The shape to look for: stdlib-only verifier, public-explorer lookup, no SDK requirement, no account.
Byte-identical helpers across runtimes
If the receipt format quietly canonicalizes differently in Python and JavaScript, a Python-anchored commitment won’t verify in JS and the spec is the issuer’s SDK rather than its docs. Look for a published byte-level spec and matching helpers in at least two runtimes that produce the same canonical bytes from the same inputs.
Receipts small enough to print, scan, or text
Most chain-anchored receipt formats ship a multi-megabyte signed PDF or a JSON-LD certificate envelope. That fits in an inbox; it doesn’t fit on a paper invoice, a printed lab result, or in a QR code on a field inspector’s clipboard. Look for a format whose entire receipt bundle is small enough to print on the artifact it’s proving — under a kilobyte for typical commitments. The mobile-first behaviors (scan-to-verify, deep links, offline distribution) fall out of the size for free.
Committed in seconds. Confirmed on-chain. Verified forever.
Three states, one receipt. None of them require trusting us.
Hash — locally.
Your client (browser, agent, or script) hashes a commitment, snapshot, manifest, or file and POSTs the fingerprint. The payload itself never leaves your device. An independent miner accepts and signs the anchor in seconds.
Included — in a block.
The anchor is mined into a Bitcoin SV block (about ten minutes). Your filename, notes, payload, and any of the items inside a manifest never appear on chain — only the fingerprint.
Anyone — reverifies.
Drop the receipt and the original payload into the in-browser verifier. It re-hashes, walks the Merkle path if needed, fetches the on-chain transaction from a public explorer, and renders three pills: Committed (miner signed it), Confirmed (chain says so), Verified (your copy matches).
Commit now, reveal later — without trusting a clock.
Sealed bids, evaluation scores, model benchmarks, prediction markets, multi-agent voting: anywhere two parties need to commit BEFORE either reveals, the public chain is doing something nothing cheaper can. Anchor a hash now; publish the payload later. Anyone can prove the commitment predates the reveal — without a trusted timestamp service, without either party being online, without anyone able to edit history later.
Wrap, hash, anchor.
Wrap your payload (a bid, a score, a vote —
anything JSON-shaped) with a fresh 32-byte random nonce.
Canonicalize. Hash with SHA-256. Anchor the hash via
POST /api/v1/anchors with
category: "commitment". Keep the wrapped
form private.
Publish the wrapped form.
After the deadline (the auction closes, the evaluation ends, the prediction window expires), share the wrapped form. The nonce is what makes this safe: without it, an attacker could enumerate likely values; with it, the hash binds you to one specific committed thing.
Anyone re-hashes and checks.
A verifier canonicalizes the revealed wrapped form, SHA-256s it, and confirms the hash matches the on-chain commitment. The block timestamp proves the commit predates the reveal. No trust in either party, no trust in Satsignal.
- /commit_reveal.py
— Python 3 stdlib only. CLI with
commit/reveal/verifysubcommands. - /commit-reveal.js
— ES module for browser + Node 18+. Exports
makeCommitandverifyReveal.
Prove what an agent was allowed to do at the time it acted.
An incident review — a regulator, a customer, a court — will ask: what system policy was in force, what instruction was the agent responding to, which tools could it call, what was its budget, and which model and config was it running? A policy snapshot answers all five at once with a single on-chain commitment.
The snapshot is a JSON object with a SHA-256 fingerprint of
each component. Anchor the snapshot via
POST /api/v1/anchors with
category: "policy_snapshot"; the receipt
binds to all five hashes simultaneously, so an auditor with
just the system prompt can verify the
system_policy_hash matches the receipt without
ever seeing the budget config — selective disclosure of
the agent’s configuration.
system_policy_hash— sha256 of the system prompt or policy document.user_instruction_hash— sha256 of the user-supplied task or query.tool_permissions_hash— sha256 over the canonicalized tool list / call permissions.budget_limits_hash— sha256 over the canonicalized budget caps (USD, tokens, time, etc.).model_config_hash— sha256 over the canonicalized model + sampling parameters.
Hash each component, build the snapshot.
Before the agent acts, the runtime hashes the five components and assembles a snapshot JSON. The whole snapshot is canonicalized + sha256’d — that hash is what gets anchored.
POST with category=policy_snapshot.
Hand the snapshot’s sha256 to the existing
/api/v1/anchors endpoint. The receipt binds
the on-chain timestamp to the entire five-field
configuration. Save the receipt’s
bundle_id alongside every action the agent
takes from this point on.
Anyone with one component can verify.
An auditor receives the snapshot JSON and any of the original components. They re-hash and check — without ever needing to see the others. The chain timestamp proves the configuration predates the action.
- /policy_snapshot.py
— Python 3 stdlib-only CLI. Subcommands:
hash-component,build,verify. Selective-disclosure verify (one field at a time) just works. - /example_agent_snapshot.py
— runnable agent example. Hashes its own five
components, optionally POSTs the snapshot to
/api/v1/anchorswhenSATSIGNAL_API_KEYis set, then takes a (deterministic) action. Replace thedecide()stub with a real agent loop and you have an audit-trail-clean configuration anchor in ~30 lines of glue.
Anchor a batch — reveal one item at a time.
Per-action anchoring doesn’t scale. Manifest mode is the structural answer. Up to ten thousand items (decisions, tool calls, outputs, evidence files) collapse into one receipt — and any holder can later prove a single item was in the batch without revealing the others.
Each item is just a {label, sha256_hex} pair.
The server combines the leaves into a Merkle tree and
anchors the root. The receipt bundle ships every leaf with
its inclusion path, so a recipient who only sees one item
can still verify it belongs to the receipt — no need
to see the other 9,999.
Hash each item, collect the leaves.
Your client hashes each artifact (a tool-call log, a page of output, an evidence file) into a leaf with a readable label. Only the leaf fingerprints reach our server — the underlying bytes stay with you.
POST the leaves; one chain spend.
Send the leaves to /api/v1/anchors as
{items: [...]}. The server combines them into
a Merkle tree and anchors the root. One miner-signed
anchor covers up to 10,000 items.
Recipient verifies that one item.
Hand a single item plus its inclusion path to whoever needs to verify it. They reproduce the leaf hash, walk the path to the root, and confirm the root matches what was anchored on chain. The other 9,999 stay private.
Two row-level schemes you can pin against.
For tables (CSV rows, JSON records, eval results, vote ledgers) where the disclosure unit is a single row, two documented schemes ride on top of the API:
merkle-row-v1— leaf issha256(canonicalize(row)); anchored via manifest mode. Right when row contents are high-entropy (free-text bodies, UUIDs, large payloads).merkle-row-sealed-v1— leaf isHMAC-SHA256(salt_i, canonicalize(row))with per-leaf salts derived via HKDF; anchored viacategory=commitmentover a tiny commit doc. Right when row contents are low-entropy ("yes"/"no", single bids, small enums) where a plain SHA-256 leaf would be brute-forceable.
Both schemes verify selectively: revealing one row never requires revealing any other. The full byte-level spec is at /spec-merkle-row; stdlib-only helpers with byte-identical canonical bytes across runtimes:
- /merkle_row.py — Python (stdlib-only).
- /merkle-row.js — ESM module for browsers and Node 18+.
Live samples, anchored on chain
2026-05-08:
all txids + artifact index;
merkle-row-v1 txid
4fc609d8…,
merkle-row-sealed-v1 txid
d623cb94….
The client helpers reproduce both Merkle roots byte-for-byte
from the row inputs; the on-chain commitment sha for the
sealed sample resolves publicly via
/lookup_hash.
Foreign-format interop (AAR,
C2PA, Visa TAP, RFC 3161 dual-attest) lives at
/samples/chain-anchor/RECEIPTS.md
— five live BSV-mainnet samples for the
chain-anchor/v1
scheme.
The same receipt works for ordinary files.
The three outcomes above are the agent-shaped wedge. Satsignal still does what it always did: tamper-evident receipts for any file, with format-aware fingerprints so a re-saved PDF or re-exported CSV still verifies against the content hash even when the byte-exact hash changes.
| File | Content fingerprint | Section breakdown |
|---|---|---|
| extracted text, normalized | per page | |
| JSON | RFC 8785 canonical | per top-level key |
| CSV | RFC 4180 normalized | per data row |
| Source / text | NFC-normalized, line-ending stable | per line |
| ZIP / DOCX / XLSX | entry manifest | per contained file |
| Image (JPG/PNG/WebP) | raw pixel data, metadata stripped | per tile |
| Anything else | byte-exact hash only | n/a |