Five sealed bids, one anchor, one row revealed.
Five bidders sealed an amount each. The whole
table was committed on chain in a single transaction
(merkle-row-sealed-v1). Later, only one row was
disclosed — the other four stay HMAC-sealed under salts
the auditor never sees. Selective disclosure is the property a
plain log can’t give you.
Cheaper infrastructure can’t do all three.
Signed logs, append-only DBs, transparency logs — any of those can give you tamper-evidence. None of them can give you tamper-evidence plus a public timestamp plus selective disclosure to a counterparty who doesn’t trust you. Anchoring one Merkle root binds all five bids to a moment in BSV chain order; per-leaf salts let you reveal one bidder without unsealing any other.
- Late edits to any bid. No bidder can change a bid after the anchor — every row is folded into the Merkle root, and the root’s SHA-256 is already in a Bitcoin SV block.
- Pre-reveal copying. No bidder could have read
another’s amount before the anchor — each
leaf is
HMAC-SHA256(salt_i, canonical_row_i)with a per-leaf salt the holder keeps private. - Operator collusion. Satsignal can’t forge an earlier commitment with a different table — a BSV miner independently signed the anchor, and anyone can verify the chain timestamp without us.
- Sibling unsealing. Disclosing row 2 reveals row 2’s salt only. Per-leaf salts are derived via HKDF, so revealing one does not let anyone derive (or brute-force) any other bidder’s salt.
Five bids became one Merkle root.
Each row was a {bidder, bid_minor} JSON object,
JCS-canonicalized to a fixed byte string. A 32-byte master salt
fed an HKDF derivation to produce one
salt_i per row; each leaf is
HMAC-SHA256(salt_i, canonical_row_i). The five
leaves were Merkle-rooted (binary tree, last-leaf-dup, SHA-256
inner nodes), and a tiny commit doc binding the root +
leaf count was anchored on chain — one transaction, one
OP_RETURN, the whole table sealed.
The commit doc (132 bytes canonical)
{
"leaf_count": 5,
"root": "a67956a2d0ca871b665da9d99fb1ac2dc3690d9484bcac23dd09c2c5ae477bf7",
"scheme": "satsignal-merkle-row-sealed-v1"
}
On-chain anchor
One transaction. One Merkle root. Five sealed leaves.
- Scheme
merkle-row-sealed-v1- Bundle ID
007dff5816814594- Commit-doc SHA-256
f3dd7420…608ef3584- Merkle root
a67956a2…ae477bf7- Leaf count
- 5
- Transaction
d623cb94…53a6c4dcc8- Anchored
- 2026-05-08 (BSV mainnet)
Public lookup, no auth
Resolve commit-doc SHA → bundle + txid.
Anyone can hit
/lookup_hash on the public proof endpoint with
the commit-doc SHA-256 and get back the bundle id, txid, and
anchor time — no API key, no Satsignal account
required.
curl 'https://proof.satsignal.cloud/lookup_hash?sha=f3dd742039a4c287d23f735cb59833f669d1ff96dc50fab0c173a24608ef3584'Open the live lookup →
One bidder disclosed. The other four stay sealed.
To answer one auditor question (“what was bidder gamma’s amount?”) the holder publishes a single reveal payload: the row, its canonical bytes, the per-leaf salt for index 2 only, the leaf’s commitment, and the Merkle inclusion path back to the root. Nothing about rows 0, 1, 3, or 4 is exposed — not their bidders, not their amounts, not their salts.
Single-row reveal payload (row index 2)
{
"version": "satsignal-merkle-row-sealed-v1",
"leaf_index": 2,
"leaf_count": 5,
"label": "row-2",
"row": { "bidder": "gamma", "bid_minor": 48 },
"row_canonical_b64": "eyJiaWRfbWlub3IiOjQ4LCJiaWRkZXIiOiJnYW1tYSJ9",
"salt_b64": "2BempkijPVIw5QHTSqw4hSh34j15Xgx4fEONhjc1tRE=",
"commitment_hex": "fe1a6ecc7e0ecfecab0335e3f18aa189d8c9cce35b7fb642148d9ae551208586",
"proof": [
{"sib": "42b18e56...0587eb", "side": "R"},
{"sib": "80119518...bce7f3", "side": "L"},
{"sib": "97912b95...182d99", "side": "R"}
],
"root_hex": "a67956a2d0ca871b665da9d99fb1ac2dc3690d9484bcac23dd09c2c5ae477bf7"
}
- Bidder gamma: bid 48 minor units. Re-canonicalize the row, HMAC under the supplied salt, walk the Merkle path — the result is the on-chain root.
- Bidders 0, 1, 3, 4: the auditor sees only the three sibling commitments inside the inclusion path. Each is a 32-byte HMAC under a salt the auditor has never seen. There is no candidate set to brute-force, because the salt space is 2256.
- The master salt: stays with the holder. Per-leaf
salts are HKDF-derived from it; revealing
salt_2doesn’t leak the master and doesn’t let anyone derivesalt_0, salt_1, salt_3, salt_4.
Anyone can re-run the four binding checks.
Verification is fully client-side; nothing leaves the
auditor’s machine except the one
/lookup_hash call that resolves the commit-doc
SHA-256 to the on-chain transaction. Three independent
paths:
In a browser.
Open
/verify
and find the Sealed-row reveal card. Click
Try the live sample to auto-load this demo’s
commit doc + reveal, or drop your own pair of files. All
four binding checks (root match, row binding, leaf
commitment, Merkle path) run client-side; the on-chain
commitment then resolves via the public
/lookup_hash.
From the command line.
Stdlib only — no pip install, no Satsignal repo dependency:
curl -O https://satsignal.cloud/merkle_row.py curl -O https://satsignal.cloud/samples/\ merkle-row/sealed-commit.json curl -O https://satsignal.cloud/samples/\ merkle-row/sealed-reveal-row-2.json python3 merkle_row.py verify-sealed \ --reveal sealed-reveal-row-2.json \ --commit-doc sealed-commit.json
By hand, in any language.
The byte-level scheme is at
/spec-merkle-row.
Four checks, in order: (1) JCS-canonicalize
reveal.row and confirm it equals
row_canonical_b64; (2) HMAC under
salt_b64 matches commitment_hex;
(3) walk the Merkle path back to root_hex;
(4) the SHA-256 of canonicalize(commit_doc)
resolves to the on-chain transaction via
/lookup_hash. Reference helpers ride only on
SHA-256, HMAC, and HKDF.
Build this in your own integration.
Everything on this page was produced by the helper below and the public anchor API. Plain Python, stdlib only, no Satsignal SDK to install. Drop it into a CI step, an agent runtime, or a one-off shell.
merkle_row.py
The helper that built this demo’s sealed Merkle
row, derived per-leaf salts via HKDF, and produces
single-row reveals. Subcommands
build-sealed / reveal /
verify-sealed. Same byte-for-byte semantics
as the in-browser verifier.
Quickstart
~30 lines of curl + python to anchor your own commit
on chain — with an API key, a JSON body, and a
receipt back. The same /api/v1/anchors
endpoint that produced the transaction at the top of
this page.