From 4fd85b75052863cb7ae24aa3713c6d9a81561ae1 Mon Sep 17 00:00:00 2001 From: codenlighten Date: Sat, 27 Jun 2026 08:14:13 -0400 Subject: [PATCH] =?UTF-8?q?Add=20BRC-141:=20NotaryHash=20=E2=80=94=20priva?= =?UTF-8?q?cy-preserving=20signed-hash=20notarization=20with=20SPV-verifia?= =?UTF-8?q?ble=20certificates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + apps/0141.md | 119 +++++++++++++++++++++++++++++++++++++++++++++++++ apps/README.md | 1 + 3 files changed, 121 insertions(+) create mode 100644 apps/0141.md diff --git a/README.md b/README.md index 0ab622f..4f6915e 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,7 @@ BRC | Standard 138 | [Single-Use Signed Proofs for Request Authentication](./peer-to-peer/0138.md) 139 | [Multicast Shard Manifest Announcement Protocol](./transactions/0139.md) 140 | [Threshold Key Sharing and Backup via Shamir's Secret Sharing Scheme](./key-derivation/0140.md) +141 | [NotaryHash — Privacy-Preserving Signed-Hash Notarization with SPV-Verifiable Certificates](./apps/0141.md) ## License diff --git a/apps/0141.md b/apps/0141.md new file mode 100644 index 0000000..9c49233 --- /dev/null +++ b/apps/0141.md @@ -0,0 +1,119 @@ +# BRC-141: NotaryHash — Privacy-Preserving Signed-Hash Notarization with SPV-Verifiable Certificates + +Gregory Ward, CTO, SmartLedger (codenlighten1@gmail.com) · https://github.com/codenlighten + +## Abstract + +This proposal defines a transaction format and a self-contained certificate for anchoring a *signed hash* to the Bitcoin blockchain. A signer proves they signed a specific hash; the on-chain anchor fixes that proof in time at the block in which it is mined. The notarizing service never receives the original document and never handles any client private key. Certificates are independently verifiable — offline for the signature and proof integrity, and against block headers alone (SPV) 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 SPV envelope, 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 canonical proof bytes, anchors them in an `OP_RETURN`, and returns a certificate. +- **Verifier** re-checks the signature and proof bytes offline and confirms the anchor. + +### Algorithms + +| family | ids | hash | +|--------|-----|------| +| ECDSA | `ECDSA-secp256k1` | SHA-256 | +| ML-DSA (FIPS 204) | `ML-DSA-44`, `ML-DSA-65`, `ML-DSA-87` | SHA-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](https://www.rfc-editor.org/rfc/rfc6962) Merkle root, domain-separated (`leaf = SHA256(0x00 ‖ d)`, `node = SHA256(0x01 ‖ l ‖ r)`, split at the largest power of two `< n`, last leaf never duplicated). + +### Certificate + +A self-contained JSON object, canonicalised via [RFC 8785 (JCS)](https://www.rfc-editor.org/rfc/rfc8785) 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: + +```json +"spv": { + "rawTx": "", + "blockHash": "", + "blockHeight": 0, + "merkleProof": { "index": 0, "nodes": ["", "*", "..."] }, + "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 (BUMP) or BRC-62 (BEEF) 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. **Signature** — `verify(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 coinbase 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 + +- BRC-9: [Simplified Payment Verification](../transactions/0009.md) +- BRC-10: [Merkle proof standardised format](../transactions/0010.md) +- BRC-11: [TSC Proof Format with Heights](../transactions/0011.md) +- BRC-12: [Raw Transaction Format](../transactions/0012.md) +- BRC-62: [Background Evaluation Extended Format (BEEF)](../transactions/0062.md) +- BRC-74: [BSV Unified Merkle Path (BUMP)](../transactions/0074.md) +- [RFC 6962: Certificate Transparency (Merkle trees)](https://www.rfc-editor.org/rfc/rfc6962) +- [RFC 8785: JSON Canonicalization Scheme (JCS)](https://www.rfc-editor.org/rfc/rfc8785) +- FIPS 204: Module-Lattice-Based Digital Signature Standard (ML-DSA) +- FIPS 205: Stateless Hash-Based Digital Signature Standard (SLH-DSA) diff --git a/apps/README.md b/apps/README.md index 0998132..03d833a 100644 --- a/apps/README.md +++ b/apps/README.md @@ -6,3 +6,4 @@ BRC | Standard ------|------------------ 102 | [The deployment-info.json Specification](./0102.md) 122 | [Auditable Real-time Inference Architecture (ARIA)](./0122.md) +141 | [NotaryHash — Privacy-Preserving Signed-Hash Notarization with SPV-Verifiable Certificates](./0141.md)