What's on chain — a worked MBNT anchor
A Satsignal anchor is a single BSV transaction with one OP_FALSE OP_RETURN output that carries an MBNT payload. Everything else about a Satsignal receipt — the canonical document, attached files, manifest leaves, sealed reveals — lives off chain. The chain sees a 20-byte hash and a few bytes of indexable metadata.
This page walks one real preview-tier anchor end to end so you can reproduce the parse yourself with no Satsignal account, no installed software, and nothing more than a hex viewer.
1. The transaction
Pick any preview-tier anchor. Below is one we use as a worked example:
- txid
7e0603213afe0922b28e38bd574dc9f37d949c2616de50abaaea3b046d85ebbc - block 948394 (mined by CUVVE)
- size 237 bytes total
- WhatsOnChain whatsonchain.com/tx/7e0603…ebbc
- Bitails bitails.io/tx/7e0603…ebbc
Open either explorer link and you'll see one P2PKH input, one P2PKH change output, and one OP_RETURN output. The OP_RETURN is what carries the Satsignal commitment.
2. The OP_RETURN script
The full scriptPubKey of the OP_RETURN output is 36 bytes:
6a 22 4d 42 4e 54 01 01 00 06
ae 5c 19 9f 31 a0 63 6f 0b ee d0 f1 c8 3e 04 b0 8e 1a c9 ae
05 04 d5 b0 b0 c6
Two-byte script wrapper, then 34 bytes of payload:
| bytes | role | meaning |
|---|---|---|
6a | OP_RETURN | output is unspendable, used for data |
22 | push opcode | push the next 34 (0x22) bytes literally |
4d 42 4e 54 | magic | ASCII "MBNT" — Satsignal's protocol prefix |
01 | version | MBNT v1 |
01 | subtype | generic (see /spec-mbnt §4) |
00 06 | tlv_len | 6 bytes of TLV section follow the hash |
ae5c…c9ae | doc_hash | first 20 bytes of sha256(canonical_doc) |
05 04 d5 b0 b0 c6 | TLV | tag=0x05 issuer_id, len=4, value 4 B |
That's the entire on-chain commitment. A non-Satsignal verifier reading this transaction has everything they need to walk from txid to a 20-byte document fingerprint.
3. What the doc_hash commits to
The 20 bytes ae5c199f31a0636f0beed0f1c83e04b08e1ac9ae are sha256(canonical_doc_bytes)[:20], where canonical_doc_bytes is the JCS-canonical UTF-8 encoding of the receipt's canonical-doc JSON. Canonicalization rules: NFC-normalize all strings, sort all dict keys, no whitespace, no floats, no NaN/Infinity. The same recipe merkle_row.py and commit_reveal.py use.
A third party who has been handed (txid, canonical.json) can verify the binding with stdlib JSON + SHA-256 in about ten lines:
import json, hashlib
def canonicalize(obj):
# JCS: sort keys, no whitespace, ensure_ascii=False
return json.dumps(obj, sort_keys=True, separators=(",", ":"),
ensure_ascii=False).encode("utf-8")
doc = json.load(open("canonical.json"))
expected = hashlib.sha256(canonicalize(doc)).digest()[:20].hex()
assert expected == "ae5c199f31a0636f0beed0f1c83e04b08e1ac9ae"
If expected == doc_hash, the canonical document was committed in this transaction at the chain's confirmation time. If they differ, the document has been modified or the txid points to a different proof — full stop, no partial credit.
4. What the issuer_id TLV commits to
The trailing 6 bytes 05 04 d5 b0 b0 c6 are an issuer_id TLV:
issuer_id = sha256(operator_did_utf8)[:4]
For d5b0b0c6 the matching DID is did:web:satsignal.cloud — Satsignal's hosted preview tier. Every preview-tier anchor carries the same 4 bytes. This is the protocol's discoverability handle: chain indexers can enumerate Satsignal-issued anchors without needing any side-channel knowledge.
It also means every preview-tier anchor is publicly chain-tagged with this fingerprint. For workloads where unlinkability matters (sealed-bid auctions among non-trivial counterparties, whistleblower-class users) the operator should suppress the TLV per-receipt (publish.issuer = False) so the payload shrinks to the strict 28-byte header. See /spec-mbnt §11 for the full property and how to opt out.
5. Confirmation depth
A transaction in the mempool is a commitment to the broadcast network. A transaction in a block is a commitment to chain history. Verifiers SHOULD display confirmation count next to the txid and apply a minimum-depth gate appropriate to the use case:
| use case | minimum |
|---|---|
| audit-trail / evidence dispositioning | ≥ 1 |
| sealed-bid auction reveal-after / fairness | ≥ 1 |
| settlement / counterparty-risk gating | ≥ 6 |
Anchors typically land within 3–8 confirmations (~30–80 minutes on BSV mainnet). Both WhatsOnChain and Bitails return a confirmations field on the tx-JSON endpoint (/v1/bsv/main/tx/hash/<txid> for WoC).
The Satsignal /verify page reads this for you and shows the count next to the on-chain match pill.
6. What is NOT on chain
A 36-byte script can carry a 20-byte hash and 4 bytes of discoverability metadata. It cannot — and is deliberately not designed to — carry:
- the canonical document itself
- any attached files
- the manifest leaves of a manifest-mode anchor
- the sealed reveals or salts of a sealed-mode anchor
- the auction policy of a commit-reveal anchor
All of these live in the off-chain bundle. The chain commits to them via the 20-byte hash; the bundle holds the bytes. A verifier who has only the txid can confirm the anchor exists and read (version, subtype, doc_hash, issuer_id). To verify any specific claim about the document, they need the bundle.
This split is the entire architectural wedge: large-by-bytes content stays off chain, large-by-truth commitment goes on chain.
7. Reproducing the parse from scratch
Three steps, no Satsignal API:
- Fetch the raw transaction hex from a public BSV node:
`` curl -s https://api.whatsonchain.com/v1/bsv/main/tx/7e0603...ebbc/hex ``
- Walk inputs and outputs (varint-prefixed, see BSV tx format). For each output's scriptPubKey, look for
00 6a(OP_FALSE OP_RETURN) followed by a single push whose first 4 bytes areMBNT. - Parse the payload per
/spec-mbnt§2 — magic, version, subtype, tlv_len, 20-byte doc_hash, 0–192 bytes of TLVs.
A 25-line reference implementation lives in opreturn.find_mbnt_payload_in_raw_tx in the source repo. JavaScript port lives in parseMbnt() on the /verify page.
8. Going further
- The wire-format spec —
/spec-mbnt— covers magic, version, subtype registry, TLV registry, and forward-compat rules. - The verifier —
/verify— does this parse for you and recomputes the canonical-doc hash from a dropped bundle. - Sealed mode —
/spec— covers what the bundle holds for commit-reveal workloads. - Manifest mode — referenced in
/spec-mbnt§5 — covers Merkle-rooted batches of items under one anchor.
The chain anchoring is the entire product wedge. It is also the part that has nothing proprietary about it — anyone with a hex viewer and ten lines of Python can verify a Satsignal anchor.
Source: docs/notary_spec/WHATS_ON_CHAIN.md.
Email hello@satsignal.cloud
for clarifications.