Beersy
BRC-220

NotaryHash — Privacy-Preserving Signed-Hash Notarization with SPV-Verifiable Certificates

Proving something existed and was signed at a certain time normally means publishing the thing itself. This puts only a hash on the chain and issues a anyone can check, so the content stays private.

Gregory Ward5 min read
hashproofHashsignature

Summary

Why
Notarization services that require trusting the issuer's database or a block explorer defeat the point of a trustless proof.
What
BRC-220 defines a transaction format and self-contained certificate for anchoring a signed hash on the Bitcoin blockchain, verifiable offline and via without trusting the issuing service.
How
A signer hashes and signs content locally and sends only the hash, public key and signature to a service, which builds a fixed binary proof, it in an , and returns a certificate that a verifier checks offline plus against a via an SPV .

What this lets you do

  • Prove you signed a document's hash without revealing the document
  • Anchor a signed hash on-chain via an OP_RETURN output
  • Verify a certificate offline using only the signature and proof bytes
  • Confirm the on-chain anchor using only block headers (SPV), no API trust needed
  • Batch many proofs into one transaction with a

Written by claude-sonnet-5 from the specification text. Where the two differ, the original is correct.

Reference for an AI

Everything an assistant needs to answer questions about BRC-220 accurately, including what it depends on.

The specification

Abstract

This proposal defines a transaction format and a self-contained for anchoring a signed hash to the Bitcoin blockchain. A signer proves they signed a specific hash; the on-chain fixes that proof in time at the block in which it is mined. The notarizing service never receives the original document and never any client private key. Certificates are independently verifiable — offline for the signature and proof integrity, and against alone () for the anchor — with no dependency on the issuing service or any chain-indexing API.

Motivation

Many "blockchain notarization" services require trusting the issuer's database, or a block explorer, to attest that a record exists on-chain. This proposal removes that trust and standardises an interoperable, privacy-preserving format so that any party can produce and verify these proofs:

  1. Privacy. Only SHA-256(content) and a signature over it leave the client; the document itself is never disclosed.
  2. Determinism. The integrity root is a fixed length-prefixed binary encoding (never JSON.stringify, which is not stable across implementations), so any implementation in any language reproduces identical bytes.
  3. Post-quantum readiness. Classical (ECDSA) and post-quantum (ML-DSA, SLH-DSA) signatures are first-class, so proofs intended to last remain verifiable as classical schemes weaken.
  4. Trustless verification. A certificate carries an , so a verifier confirms the anchor using only Bitcoin block headers (BRC-9, BRC-10/BRC-11).

Specification

Roles

  • Signer hashes content locally, signs the hash locally, and submits {algorithm, payloadHash, publicKey, signature}.
  • Service verifies the signature, builds , anchors them in an OP_RETURN, and returns a certificate.
  • Verifier re-checks the signature and proof bytes offline and confirms the anchor.

Algorithms

familyidshash
ECDSAECDSA-secp256k1SHA-256
ML-DSA (FIPS 204)ML-DSA-44, ML-DSA-65, ML-DSA-87SHA-256
SLH-DSA (FIPS 205)SLH-DSA-{SHA2,SHAKE}-{128,192,256}{s,f} (12 parameter sets)SHA-256

The signer signs the 32-byte payloadHash directly; post-quantum schemes apply their own internal hashing.

Canonical proof bytes (integrity root)

proofHash = SHA-256(canonicalBytes), where:

lp("NotaryHash/1.0") || u8(version=1) ||
lp(algorithm) || lp(hashAlgorithm) ||
lp(payloadHash) || lp(publicKey) || lp(signature) ||
u64be(createdAtUnix)

lp(x) = u32be(len(x)) || x; u8/u64be are unsigned big-endian integers. All multi-byte fields are length-prefixed, so field boundaries are unambiguous regardless of content. This binary encoding — never JSON — is what makes the proof reproducible across implementations.

On-chain record (OP_FALSE OP_RETURN)

A safe data output whose pushes are discriminated by the mode/kind byte at push index 2:

  • full (mode = 0): "NOTARYHASH" | u8(1) | u8(0) | algorithm | hashAlgorithm | payloadHash | proofHash | publicKey | signature.
  • hybrid (mode = 1): as full, but the final two pushes are SHA-256(publicKey) and SHA-256(signature); the full blobs live in the certificate (keeps large post-quantum records small on-chain).
  • batch (kind = 2): "NOTARYHASH" | u8(1) | u8(2) | merkleRoot(32) | u32be(leafCount). One transaction anchors many proofs under an RFC 6962 , domain-separated (leaf = SHA256(0x00 ‖ d), node = SHA256(0x01 ‖ l ‖ r), split at the largest power of two < n, last never duplicated).

Certificate

A self-contained JSON object, canonicalised via RFC 8785 (JCS) for hashing and transport. Required fields: protocol, version, mode, algorithm, hashAlgorithm, payloadHash, publicKey, signature, encoding, proofHash, createdAt, anchor. A batched certificate additionally carries a merkle inclusion proof {root, leafIndex, leafCount, path[]} whose folding (leaf → root) must equal the on-chain batch root.

SPV envelope (additive)

Attached once the anchoring transaction is mined:

"spv": {
  "rawTx": "<hex>",
  "blockHash": "<hex>",
  "blockHeight": 0,
  "merkleProof": { "index": 0, "nodes": ["<hex>", "*", "..."] },
  "format": "TSC"
}

merkleProof is a Merkle inclusion proof of txid under the block's Merkle root, expressed in the BRC-10/BRC-11 (TSC) model; a BRC-74 () or BRC-62 () encoding MAY be substituted by setting format accordingly. The SPV envelope is not part of the canonical proof bytes, so adding it never changes proofHash and never invalidates a previously issued certificate.

Verification

A certificate is valid if and only if all of the following hold:

  1. Signatureverify(algorithm, payloadHash, signature, publicKey) is true. (offline)
  2. Proof integrity — the recomputed proofHash equals certificate.proofHash. (offline)
  3. Anchor — one of:
    • SPV (preferred): txid = reverse(SHA256(SHA256(rawTx))) equals anchor.txid; the OP_RETURN read from rawTx matches the certificate fields; folding merkleProof from txid yields a root equal to the Merkle root of the block header for spv.blockHash at spv.blockHeight. The verifier trusts only a block header, obtained from any source it chooses (a synced header chain, or several independent sources cross-checked) — not a provider's word about the transaction (BRC-9).
    • Direct: read the OP_RETURN at anchor.txid from a chain provider (legacy certificates with no SPV envelope).

Steps 1–2 require no network. The proof-of-existence time is the block time of anchor.txid; createdAt is an advisory client field only.

What a certificate proves

A specific public key signed a specific hash, and that proof was anchored on-chain at/by the block timestamp. It does not establish who submitted it (any party holding a valid (hash, signature, publicKey) triple may re-anchor it; the attestation remains valid), nor anything about the document's contents (a verifier needs the document to recompute the hash).

Security considerations

  • The SPV trust model reduces to obtaining a correct block header. A single header source is a single point of trust; a multi-source quorum, or a proof-of-work-validated header chain, removes it. Implementations SHOULD make the header source explicit.
  • Provider-supplied data is self-checking: a raw transaction is accepted only if its double-SHA-256 equals the txid already held, so a provider cannot substitute different bytes.

Implementations

A reference implementation (service, client SDK, and a dependency-light standalone verifier) is available, including:

  • a standalone certificate verifier that depends only on the protocol's own modules (signature + proofHash + SPV anchor), never on the issuing service;
  • a confirmation poller that attaches the SPV envelope once the anchoring transaction is mined; and
  • published test vectors: a complete certificate-with-SPV-envelope golden vector, txidFromRawTx checked against the Bitcoin genesis transaction, and the Merkle fold checked against the real block-170 (two-transaction) proof.

The format has been demonstrated end-to-end on BSV mainnet: a certificate verifies fully offline against independently obtained block headers, with no trust in the issuing service.

References

Was this helpful?

Search Beersy

Search standards by number, title, author or topic