BRC-220: define the certificate's field values - #247
codenlighten wants to merge 2 commits into
Conversation
Section Certificate names twelve required fields and defines none of their values. Two implementations filled the gap differently and could not verify each other's certificates, though their proofHash values, digests, trees and on-chain records were identical. This defines each field as the reference implementation writes it, with two reader rules: a verifier rejects a version it does not implement, and a reader rejects base64 that is not base64.
There was a problem hiding this comment.
🟡 Changes recommended
The added ECDSA signature format disambiguation incorrectly asserts DER signatures are never 64 bytes, which can lead to misclassification and should be corrected in the spec text.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Updates BRC-220’s §Certificate to precisely define each required certificate field’s JSON type/encoding and the anchor/merkle structures, aligning readers/verifiers with the reference implementation to avoid cross-implementation incompatibilities.
Changes:
- Specifies canonical values/types for the 12 required certificate fields (including
version,mode,encoding, and timestamp formatting). - Defines
anchormembers and batchedmerkleinclusion proof structure and folding rule. - Clarifies SPV envelope byte/endianness expectations for
blockHashandmerkleProof.nodes, plus a header/hash-height check.
File summaries
| File | Description |
|---|---|
| apps/0220.md | Expands §Certificate field definitions, anchor/merkle structure, and adds SPV envelope byte-order clarifications. |
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| | `createdAt` | `createdAtUnix` as an ISO 8601 UTC timestamp with milliseconds, e.g. `"2026-01-01T00:00:00.000Z"`. The milliseconds are always `000`, because only whole seconds enter the canonical bytes; a verifier recovers `createdAtUnix` as the whole seconds the timestamp denotes. Advisory only — see §Verification. | | ||
| | `anchor` | the object below | | ||
|
|
||
| For `ECDSA-secp256k1`, `publicKey` is 33 bytes (compressed) or 65 (uncompressed), and `signature` is 64 bytes (`r ‖ s`, each a 32-byte big-endian integer) or DER. A verifier tells them apart by the bytes: DER begins with `0x30` and is not 64 bytes long. `S` is not normalised: the certificate commits, through `proofHash`, to the exact bytes the signer produced, so the malleated form of a signature is a different certificate rather than a forgery of this one. |
There was a problem hiding this comment.
You're right that DER can be 64 bytes long, so the sentence was wrong as a statement about DER. Fixed in 72f788f. It now states the decoding rule the reference implementation applies: exactly 64 bytes is r ‖ s, and anything else must parse as DER. A 64-byte DER signature is therefore read as r ‖ s, does not verify, and should be sent as r ‖ s instead.
… height, and its fields are filed for BRC-220 (#169) Carries the base64 length rule and the optional header height from #168, and the certificate field definitions from #167, now filed upstream as bsv-blockchain/BRCs#247. The batch-leaf draft says that leafIndex counts from 0 and how i is written in the vector, as review of bsv-blockchain/BRCs#246 asked. Additive, or refuses only malformed input.
DER-encoded ECDSA signatures can be 64 bytes long when r and s are unusually short, so 'DER is not 64 bytes long' was false as a statement about DER. The rule itself is what the reference implementation applies: exactly 64 bytes is r || s, anything else must parse as DER.
There was a problem hiding this comment.
🟡 Changes recommended
The updated text makes a deterministic-verification claim about 64-byte DER signatures that isn’t strictly guaranteed and should be corrected for spec accuracy.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 1
- Review effort level: Lite
| | `createdAt` | `createdAtUnix` as an ISO 8601 UTC timestamp with milliseconds, e.g. `"2026-01-01T00:00:00.000Z"`. The milliseconds are always `000`, because only whole seconds enter the canonical bytes; a verifier recovers `createdAtUnix` as the whole seconds the timestamp denotes. Advisory only — see §Verification. | | ||
| | `anchor` | the object below | | ||
|
|
||
| For `ECDSA-secp256k1`, `publicKey` is 33 bytes (compressed) or 65 (uncompressed), and `signature` is 64 bytes (`r ‖ s`, each a 32-byte big-endian integer) or DER. A verifier reads a 64-byte `signature` as `r ‖ s`, and any other `signature` as DER, which must parse as DER. DER can itself be 64 bytes long — only when `r` and `s` are unusually short — and is then read as `r ‖ s` and does not verify; a signer holding such a signature sends it as `r ‖ s` instead. `S` is not normalised: the certificate commits, through `proofHash`, to the exact bytes the signer produced, so the malleated form of a signature is a different certificate rather than a forgery of this one. |
This pull request:
Amends or corrects an existing standard, without the need for creating a new one
Summary
§Certificate names the twelve fields a certificate must carry and defines none of their values. Two implementations filled that gap differently. The reference implementation and
@smartledger/bsv8.3.0–9.8.0 computed identicalproofHashvalues, signature digests, Merkle trees and on-chain records, and neither could verify a single certificate the other issued:versionwas"1.0"against1,modewas a name against a byte,encodingmeant the JSON spelling against the byte form, and theanchorand batchpathhad different shapes. This PR defines each field as the reference implementation writes it.What it adds to §Certificate
versionis the string"1.0".modeis"full"or"hybrid", and batching is marked onanchor.type, not on the mode.encodingis"hex"or"base64"and applies topublicKeyandsignatureonly. Hex is lowercase with no prefix.createdAtis ISO 8601 with.000milliseconds, because only whole seconds enter the canonical bytes.anchorobject's six members, and themerkleobject, with the{ hash, side }audit path and its fold rule written out.r ‖ sor DER signatures, told apart by the bytes;Snot normalised.versionit does not implement. A reader rejects base64 that is not base64: a character outside both RFC 4648 alphabets, padding anywhere but the end, or a length no byte string encodes. Without the second rule a corrupted field decodes quietly to different bytes: Node'sBuffer.from, for one, skips unknown characters and truncates impossible lengths.It also adds one paragraph to §SPV envelope: the byte order of
blockHashandmerkleProof.nodes, and the header-height check.What it does not change
Writers are unaffected, and every certificate the reference implementation has issued already conforms. The canonical proof bytes,
proofHash, the on-chain record and the verification steps are untouched. The two reader rules refuse nothing a conformant writer produces.This stays within amending an existing standard. It writes down what the reference implementation already writes, so no issued certificate is affected, and the two reader rules refuse only input no conformant writer produces.
The examples
Both examples are certificates produced by the reference implementation's own code. They verify: the signature,
proofHash, the on-chain record, and the batch inclusion. The anchoring transactions and blocks are test fixtures, and the text says so. Themerkleexample is leaf 4 of the five-proof batch vector in #246.Relationship to #246
The two are independent and touch different lines. The ECDSA byte forms sit in §Certificate, under the fields they describe, rather than in §Algorithms, which #246 edits. This branch merges cleanly with #246 in either order; that was checked.
The reasoning behind each decision, including the reader rules and why
networkis descriptive rather than a closed set, is in BRC220_CERTIFICATE_FIELDS_AMENDMENT.md. A test there reads this text's examples and checks them against the reference implementation's certificates.