Sealed-mode disclosure reference
Terminology. The canonical vocabulary on every surface is proof (grouped in folders); API responses emit the canonical names only (proof_id, proof_url, folder_slug). Where this spec shows receipt / matter / bundle_id, it is documenting a frozen on-disk/on-chain format, a stable filename (RECEIPTS.md), or a legacy route that remains accepted inbound — proof and receipt denote the same record. Full alias map: the compatibility map.
This is the sealed-mode disclosure reference: when to disclose a sealed Satsignal proof, what to share, with whom, and what the recipient does. For the product overview and a guided starting point, see the docs at https://satsignal.cloud/docs.html.
Audience: holders of a sealed Satsignal proof who need to publish, prove possession, or hand over evidence — usually attorneys, journalists, investigators, compliance teams, or anyone preserving artifacts before a controlled reveal.
This doc is operational guidance — when to disclose, what to share, with whom, and what the recipient does — alongside the cryptographic construction it relies on.
What you have, and what each piece does
When you anchor a sealed proof, you walk away with three artifacts:
- The original file — stays with you, never reaches Satsignal.
- The
.mbntbundle — a small zip containingmanifest.json(with the master salt),canonical.json(with the salted commitments), and optionallyproofs.json(with per-chunk leaf commitments). - The on-chain transaction — public, permanent, identified by a
txid. Reveals only that some file was anchored at the timestamp.
The bundle is the bearer artifact: anyone holding it can verify the seal against any candidate file. Lose it to an adversary, and the seal is broken (they can confirm "yes, you held this thing"). Lose it entirely, and the proof can no longer be verified — though the chain entry remains forever.
Scope of unseal proofs
An unseal proof shows that someone held a sealed fingerprint at a specific time AND can now reveal the underlying data that matches that commitment. The four-clause Satsignal scope statement applies in full — see What it proves, and what it doesn't.
- Not authorship. Whoever held the fingerprint at anchor time is not necessarily the author. A bad actor who obtains a payload can anchor it; the proof shows custody-at-anchor-time, not creation.
- Not truth. The payload's claims may be factually wrong, fabricated, or misleading. A proof binds the bytes; it does not certify what they mean.
- Not pre-existence. A proof shows someone held the fingerprint by anchor time. It does not show the underlying content existed before that time.
- Not legal compliance. A proof is not, by itself, eIDAS-qualified evidence, an e-signature, FRE 901/902 self-authentication, EU AI Act compliance, FINRA recordkeeping, or any other regulatory determination. Whether a proof is sufficient evidence for any particular legal, regulatory, or commercial purpose is a judgement you and your advisers make — not a determination Satsignal makes for you.
Unsealing reveals the data; it does not retroactively grant authorship, pre-existence, or legal compliance — those remain out of scope for any Satsignal proof, sealed or not.
Three modes of disclosure
Sealed mode supports three disclosure shapes. Pick the one that matches your threat model.
Mode 1 — Full unseal (public)
What you do: publish the original file + the .mbnt bundle together. Anyone who reads the publication can verify against the public chain.
When to use:
- Coordinated press release or court filing where the document is going to become public anyway.
- Establishing pre-existing possession in a filing or rebuttal — "this document existed in this exact form on date T, before any of the events the other party describes."
What's revealed:
- The file (because you published it).
- That you anchored it on date T (on-chain timestamp).
- The salted commitments in the bundle, plus the master salt — which, combined with the file, lets anyone verify.
What stays private:
- Nothing. Full unseal is full reveal.
Operational notes:
- Treat the publication as a one-way action. There is no resealing.
- Mention the txid prominently in your publication so anyone can cross-check on a public BSV block explorer (whatsonchain.com, gorillapool, etc.).
Caveat — grindable plaintext. Mode 1 reveals the original file together with the salt. For files with high inherent entropy this is fine — there is nothing useful for an adversary to "guess." But for small or low-entropy plaintexts (a short yes/no answer, a numeric range, a name from a known shortlist), publishing the file + salt means anyone can also brute-force any related sealed commitment that might have been one of those plausible answers: they grind candidates, HMAC each one with the disclosed salt, and check against any sealed commitment they hold. The on-chain anchor still proves you held this exact file by date T (that is sealed mode's primary job); but the privacy of related sealed records that share a salt scheme degrades the moment a low-entropy Mode-1 publication lands.
If the plaintext is small or low-entropy, prefer Mode 2 or Mode 3 — they don't disclose the salt to outsiders. The same caveat applies to the chunk-row case; see /spec-merkle-row §4.1 for the HMAC-vs-SHA-only discussion.
Mode 2 — Selective disclosure (one chunk only)
What you do: publish a single page (PDF), row (CSV), key (JSON), or line (text) of the original — not the rest. Plus the per-leaf salt for that chunk and the Merkle path proving the chunk's membership in the tree.
When to use:
- You need to corroborate one specific claim drawn from a large document, without revealing the rest.
- A leak has surfaced one page of your document; you can confirm that page is from the genuine artifact you anchored, without ratifying the rest.
What's revealed:
- The single chunk you chose.
- The per-leaf salt for that chunk's index (one-way derived from the master salt — knowing it does NOT let an adversary derive other per-leaf salts; HKDF guarantees independence).
- The Merkle path (sibling commitments) needed to verify membership.
What stays private:
- The master salt (not revealed).
- All other chunks (their per-leaf salts cannot be derived from the one you revealed; brute-forcing them requires the master salt).
- The byte-exact and content-canonical commitments (still under master-salt HMAC).
Limit: each selective disclosure reveals one chunk independently. Doing it ten times reveals ten chunks (along with their ten salts) — the rest of the document still stays sealed.
Tooling note: the browser verifier handles full-bundle verification. A single-chunk selective disclosure is verified from the chunk's canonical bytes, its Merkle proof path, and — for sealed carriers only — its per-leaf salt. The leaf rule branches on the carrier algo, so a single "always HMAC the salt" recipe is wrong for the standard profile:
- Standard / unsalted carrier (
algo: "sha256"): the leaf is the baresha256(utf8(value)). There is no HMAC and no per-leaf salt —salt_b64is absent; do not read or require it. Recompute withprintf %s "$value" | sha256sum. - Sealed carrier (
algo: "merkle-hmac-sha256",salt_version: "salt_v1"): the leaf isHMAC-SHA256(per_leaf_salt, utf8(value)), whereper_leaf_salt = base64decode(salt_b64). Recompute withopenssl dgst -sha256 -mac HMAC -macopt hexkey:<salt-hex>(raw-key HMAC over the 32 salt bytes —hexkey:, neverkey:).
Then walk the proof path to the root: for each step {side, hash}, set frontier = sha256(hash + frontier) when side is "L", else frontier = sha256(frontier + hash) (+ is raw-byte concatenation). The final frontier must equal the committed chunk_merkle.root, which must equal the on-chain marker. The native (anchor-committed) tree duplicates the last node on an odd level — an unpaired last node self-pairs, so its proof step is {side:"R", hash:<its own hash>} and its parent is sha256(h + h).
Mode 3 — Private adversarial verification (legal/discovery)
What you do: hand the original file + the .mbnt bundle to a specific recipient (opposing counsel, regulator, court-appointed expert) under a non-disclosure or controlled-discovery agreement.
When to use:
- Discovery in a legal proceeding where the artifact is exhibit material, not yet public.
- Internal investigations where the artifact must be confirmed by an outside auditor without public exposure.
What's revealed:
- To the recipient: everything (file + bundle + chain entry).
- To the public: nothing. The chain entry alone still reveals only "some sealed proof was anchored at time T."
Operational notes:
- The recipient's access is governed by your written agreement — Satsignal cannot enforce confidentiality on third parties. Treat the bundle hand-off the way you would treat the file hand-off.
- Once the bundle leaves your possession, you cannot withdraw the ability to verify. The recipient retains a permanent capability.
- For court use, the chain anchor + standard verification flow gives the timestamp third-party-auditable. The expert can re-verify on any block explorer; the cryptographic claims do not depend on Satsignal staying online.
How a recipient verifies
Whichever disclosure mode you use, the recipient's job is the same in shape: recompute the commitments locally, compare to the canonical doc, then confirm the canonical doc's hash matches the on-chain marker.
Full unseal: one-click via /verify
- Open
https://proof.satsignal.cloud/verify(or a saved local copy of that page). - Drop the
.mbntbundle into the verifier. - Drop the original file.
- Click Verify.
The verifier auto-detects sealed mode (via canonical.subject.kind == "file_anchor"), reads the master salt from the manifest, recomputes HMAC + per-leaf HKDF + Merkle root, compares to the bundle's stored values, then fetches the on-chain transaction and confirms the canonical-doc hash matches.
Outcome: a single card showing one of the verifier's real labels — "Fully verified" (bundle + original file both check out and the chain matches), "Anchored proof verified" (bundle alone, no original file supplied — a qualified success, not an error), "File does not match" / "Proof does not match its anchor" (a recompute failed), or "Chain lookup failed".
Manual / scripted verification (for skeptical recipients)
When the recipient prefers to verify with off-the-shelf tools rather than trust a single web page, walk them through this:
# 1. Open the bundle
unzip -d bundle/ <bundle>.mbnt
# 2. Read the master salt (base64url, padding stripped) and decode to
# 32 bytes. salt_b64 uses the URL-safe alphabet (- and _) with no
# "=" padding, so translate to standard base64 and re-pad before
# `base64 -d` (plain `base64 -d` rejects it as "invalid input").
SALT_B64=$(jq -r .salt_b64 bundle/manifest.json)
STD=$(printf '%s' "$SALT_B64" | tr '_-' '/+')
while [ $(( ${#STD} % 4 )) -ne 0 ]; do STD="${STD}="; done
printf '%s' "$STD" | base64 -d > /tmp/master_salt
wc -c /tmp/master_salt # must be 32
# 3. Recompute byte_exact_commitment and compare. The commitment is
# HMAC-SHA256 keyed on the RAW 32 salt bytes (not the ASCII of the
# hex string), so pass the hex via `hexkey:` — `key:` would HMAC
# with the wrong key and never match a genuine proof.
openssl dgst -sha256 -mac HMAC -macopt hexkey:$(xxd -p -c 99 /tmp/master_salt) \
< /path/to/original_file
# Compare the hex after "= " to canonical.json's
# subject.proofs.byte_exact.commitment.
# 4. (Optional) For chunk_merkle: derive per-leaf salts via HKDF,
# HMAC each chunk's canonical bytes with its derived salt, build
# Merkle root, compare to canonical.json's
# subject.proofs.chunk_merkle.root.
# (Per-leaf salt = HKDF-SHA256 of the master salt over the chunk
# index; leaves are HMAC-SHA256 of canonical chunk bytes; the root
# is the standard pairwise hash up the tree.)
# 5. Hash the STORED canonical.json bytes directly and compare the
# sha256[:40] to the on-chain document_hash. Do NOT re-canonicalize
# (no JCS / no jq re-serialization): the bundle stores the exact bytes
# that were hashed on-chain (web/storage.py and `mb bundle` both write
# the minimal canonical form), so hashing the file verbatim is both
# simpler and correct. A re-serializer such as jq does NOT NFC-normalize
# and would false-"tamper" a valid bundle whose canonical.json contains
# non-ASCII / non-NFC content.
sha256sum bundle/canonical.json | cut -c1-40
# Compare to:
# - manifest.json's `doc_hash_expected` field
# - the on-chain MBNT OP_RETURN's document_hash
# 6. Fetch the tx from any BSV block explorer and confirm the
# OP_RETURN payload starts with bytes "MBNT" and contains the
# same document_hash.
If steps 3, 5, and 6 all match, the file existed in this exact form at the time of the on-chain transaction. The chain timestamp is established by the block timestamp; the rest is a deterministic computation anyone can re-run.
Operational hygiene before disclosure
These are the things that go wrong in practice. None are cryptographic; all are procedural.
- Verify your own bundle first. Before any disclosure, run the verifier on your local copy of (file + bundle) against your local copy of the chain entry. Confirm "Fully verified" comes up. If it doesn't, something has corrupted the bundle or the file (or diverged from what you anchored). Catch this before you hand anything over.
- Make multiple copies of the bundle, in different places. The bundle is the only thing you can lose to make the proof unverifiable. The chain anchor is permanent, but without the bundle you can't tie any candidate file to it. Treat the bundle like a private key backup.
- Note the txid in the same place you note the file. If your document-management system stores the file, record the txid as metadata next to it. Years later, the chain entry is the only thing pointing to "this was anchored when."
- Server-side bundle is kept until you delete the proof (mirror submissions only) — unless you set an explicit retain window. Satsignal's copy at
/bundle/<id>.mbntis retained until you delete the proof; if you picked an explicit retain window at submit time, it auto-deletes when that window expires. Proofs anchored before 2026-06-11 keep the retention window recorded at anchor time. Download your bundle locally either way. Blind submissions never produce a server-side copy, so there is nothing to fall back on — the bundle that downloaded at submit is your only copy. The chain entry is permanent regardless. - Don't share the bundle URL casually. Anyone with the URL can fetch the bundle, which contains the salt — combined with the file they can verify. The URL is bearer.
- Witness the disclosure when stakes are high. For court or formal legal use, conduct the verification step in front of the opposing party (or their auditor). Run it from a clean machine; use a verifier file you saved from
/verifybefore you anchored, not one fetched at disclosure time. This forecloses "you-hosted-a-rigged-verifier" objections.
Pitfalls
"I lost the bundle but I have the file."
The proof is unverifiable. There is no recovery path. The chain entry exists but no one — including you — can prove which file it corresponds to without the master salt.
Mitigation: bundle backup discipline at the moment of notarization. Email a copy to a personal address you control; print and lock it; commit a copy to a private repo. Anywhere you'd back up a passphrase.
"I lost the file but I have the bundle."
Same — unverifiable. The bundle commits to the file's HMAC; without the file you cannot reconstruct the input to the HMAC.
Mitigation: same. The file and the bundle together are what proves possession. Lose either, the seal is broken.
"I want to selectively disclose more chunks later."
Each selective disclosure reveals the per-leaf salt for that chunk index. Doing it incrementally is fine — disclosing chunk 3 doesn't unseal chunks 4, 5, 6. But the chunks you already disclosed stay disclosed; you cannot retract.
Mitigation: think before each reveal about what stays sealed afterward. If you might want to disclose chunk 7 later, that's safe even if you've disclosed chunk 3. If you might want to unsay disclosure of chunk 3, you can't.
"I want to anchor the same file twice with different salts."
This works fine — sealed mode does not deduplicate by file hash, by design (per spec §7.2). Each submission with mode=sealed produces an independent proof with its own salt and proof id. The chain shows two entries, but neither reveals that they correspond to the same underlying file.
Mitigation: if you want a second anchor for the same file (different recipient, different timeframe), just submit again. Two sealed proofs for the same file cannot be correlated by anyone who doesn't hold both bundles.
"An adversary is watching the chain."
They learn: timestamps, sizes (via the chain transaction's bytes), the fact that some sealed proofs are being anchored. They do NOT learn which files. If your operational threat model includes correlation of broadcast timing, IP addresses, and metadata patterns, sealed mode does not solve that — use Tor / a clean VPN / a dedicated machine for the submission.
Quick reference
| If you want to... | Do this |
|---|---|
| Make the proof public | Mode 1: publish bundle + file together. |
| Reveal one piece without unsealing the rest | Mode 2: publish chunk + per-leaf salt + Merkle path. |
| Hand over to one party (legal) | Mode 3: deliver bundle + file under your existing confidentiality agreement. |
| Verify your own seal before disclosing | Drop bundle + file into /verify; expect "Fully verified". |
| Re-verify years from now without us | Save a copy of the verifier HTML file plus the bundle plus the original file. The chain step needs any working BSV block explorer. |
When to NOT use sealed mode
If your goal is simply to publish a timestamp — code releases, public attestations, customer-facing receipts (the documents themselves), any artifact whose existence is already public — use the default public mode at https://proof.satsignal.cloud/. Sealed mode adds operational discipline (bundle is bearer, no recovery on loss) that's only worth taking on when the existence of the proof is itself sensitive.
The two modes are not "more secure vs. less secure." They're for different situations:
- Public timestamp = "the world should know this existed at time T."
- Sealed envelope = "I should be able to prove this existed at time T, when I choose to and not before."
Most use cases want the first. A small but valuable set need the second.
Questions about this specification? Email hello@satsignal.cloud.