Skip to content

BRC-220: define the certificate's field values - #247

Open
codenlighten wants to merge 2 commits into
bsv-blockchain:masterfrom
codenlighten:brc-220-certificate-field-values
Open

codenlighten wants to merge 2 commits into
bsv-blockchain:masterfrom
codenlighten:brc-220-certificate-field-values

Conversation

@codenlighten

Copy link
Copy Markdown
Contributor

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/bsv 8.3.0–9.8.0 computed identical proofHash values, signature digests, Merkle trees and on-chain records, and neither could verify a single certificate the other issued: version was "1.0" against 1, mode was a name against a byte, encoding meant the JSON spelling against the byte form, and the anchor and batch path had different shapes. This PR defines each field as the reference implementation writes it.

What it adds to §Certificate

  • Every certificate field, in a table. version is the string "1.0". mode is "full" or "hybrid", and batching is marked on anchor.type, not on the mode. encoding is "hex" or "base64" and applies to publicKey and signature only. Hex is lowercase with no prefix. createdAt is ISO 8601 with .000 milliseconds, because only whole seconds enter the canonical bytes.
  • The anchor object's six members, and the merkle object, with the { hash, side } audit path and its fold rule written out.
  • The ECDSA byte forms: 33- or 65-byte keys; 64-byte r ‖ s or DER signatures, told apart by the bytes; S not normalised.
  • Two reader rules. A verifier rejects a version it 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's Buffer.from, for one, skips unknown characters and truncates impossible lengths.

It also adds one paragraph to §SPV envelope: the byte order of blockHash and merkleProof.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. The merkle example 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 network is 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.

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 anchor members and batched merkle inclusion proof structure and folding rule.
  • Clarifies SPV envelope byte/endianness expectations for blockHash and merkleProof.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.

Comment thread apps/0220.md Outdated
| `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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

codenlighten added a commit to codenlighten/smartledger-bsv that referenced this pull request Sep 11, 2026
… 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.
Copilot AI review requested due to automatic review settings September 11, 2026 14:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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

Comment thread apps/0220.md
| `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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants