Status: Accepted
Date: 2026-02-13
Commits: 5578915, fd33387, 2f56b5c
The did:btcr2 specification is developed in the open at DCDPR and versioned. The reference implementation (this repo) exists to track that spec as closely as the test suite and codebase can afford to. Between August 2025 and February 2026 the spec moved from v0.1 to v0.2, touching:
BTCR2 prefix and a consistent singular shape (SignedBTCR2Update, UnsignedBTCR2Update); the identifier/components naming was tightened (identifier to did, identifierComponents to didComponents).AggregateBeacon base class that held mutable signals, sidecar data, and a Bitcoin client reference on the instance. v0.2 specifies beacons as lightweight, stateless handlers: one instance = one configured service; signals and sidecar data are passed in as method parameters.DidBtcr2.update() to take a named-parameter object ({ sourceDocument, patches, sourceVersionId, verificationMethodId, beaconId, signingMaterial, bitcoin }) rather than a long positional argument list. Single-beacon announcements replace multi-beacon (beaconIds: Array<string>) everywhere.Promise.all) lands here. The fullBlockchainTraversal flag is removed: the resolver always walks forward through block history, with no branching.@see link needed updating.This wasn’t a small diff. Three commits spanning ~3 weeks landed the v0.2 alignment: 5578915 / fd33387 (2026-01-21) for terminology and initial renames, 2f56b5c (2026-02-13) for the beacon and update architectural changes.
The larger question this work surfaced: and the reason it deserves an ADR rather than just a PR description: is what the project’s stance is toward spec revisions. Two answers were possible.
Option 1. The implementation tracks the current spec. When the spec makes a breaking change, the library makes a breaking release. There is no dual-path support for older spec versions, no “compatibility mode” flag, no feature gating.
The v0.2 migration is the first exercise of this policy. Concretely:
Terminology (BREAKING, across multiple packages):
Read to Resolve throughout (class names, directory names, types, error classes).identifierComponents to didComponents; identifier to did; BTCR2SignedUpdate to SignedBTCR2Update; BTCR2UnsignedUpdate to UnsignedBTCR2Update.CIDAggregateBeaconError to CASBeaconError; SMTAggregateBeaconError to SMTBeaconError.MethodError to ResolveError (in resolve), MethodError to UpdateError (in update): error types specialized per operation.IDidDocument to Btcr2DidDocument; IDidVerificationMethod to Btcr2VerificationMethod; IIDidDocument to W3CDidDocument.SchnorrMultikey.fromPrivateKey to fromSecretKey; fromPublicKeyMultibase to fromVerificationMethod.Secp256k1SecretKey.fromEntropy / SchnorrKeyPair.fromEntropy to fromBigInt: the “entropy” vocabulary implied a specific seed-handling contract the API didn’t actually guarantee.Signer.signEcdsa to sign; Signer.sign to signSchnorr: to align with the PSBT signer interface that expects sign to be the ECDSA path. (Note: Signer itself is later removed by ADR 012; the rename applied during v0.2 but was short-lived.)Update.construct / Update.invoke to Update.construct / Update.sign. Resolve.processSidecarData to sidecarData; establishCurrentDocument to currentDocument; processBeaconSignals to beaconSignals; processUpdatesArray to updates; confirmDuplicateUpdate to confirmDuplicate; applyDidUpdate to applyUpdate. (Process-prefix removed: “process” added nothing to names that were already verbs.)Beacon architectural change:
The v0.1 AggregateBeacon base class held mutable state: signals, sidecar, bitcoin client: on the instance. The v0.2 Beacon base class holds only the beacon’s service. Signals, sidecar, and any transport are passed in as method parameters. BeaconFactory.establish(service) returns a Beacon typed by the service’s beacon type; callers then invoke beacon.processSignals(signals, sidecar, ...) with data they already have. This change is what makes the later sans-I/O refactor possible: stateless beacons are a prerequisite for the state-machine design in ADR 016 and ADR 025.
Related concrete fixes that came along:
process.exit(1) calls in beacon utilities with proper BeaconError exceptions.Update flow:
DidBtcr2.update() takes a single named-params object. No more 7 positional arguments.DidBtcr2.update(): validation once at the public boundary, not repeated internally.Update.sign requires secretKey: KeyBytes directly. No more optional privateKey with internal-to-bytes conversion. One path.capabilityInvocation now validated against the source document for the supplied verificationMethodId. Previously a missing capability slipped through; now it throws at the factory call.urn:zcap:root:...) inlined in Update.sign. The Appendix.deriveRootCapability() indirection added no value; one line of code is clearer than a named helper.SignedBTCR2Update, not SidecarData. The two concepts were being conflated.beaconIds: Array<string>) removed everywhere. Single-beacon (beaconId: string) is the only shape. The spec defines beacons as the per-controller authorization boundary; multi-beacon per-update never had a clear semantic.Spec links: Every @see https://dcdpr.github.io/did-btcr2/algorithms.html#... link updated from the v0.1 §4.x anchors to v0.2 §7.x anchors.
Version bumps: api 0.1.1 to 0.2.0, cli 0.2.0 to 0.3.0, common 3.1.0, cryptosuite 4.0.0 to 5.0.0, keypair 0.8.0 to 0.9.0, kms 0.1.1 to 0.2.0, method 0.19.0 to 0.20.0. Coordinated breaking release across the graph.
Positive
AggregateBeacon being the biggest. Holding spec-compliance as the forcing function produced better code than a pure refactor would have, because the spec’s stateless-beacon model was the right model and the code had drifted from it.fromEntropy implying a contract the code didn’t honor, processXxx adding noise without meaning) that were worth fixing even apart from spec alignment.Negative
git log and git blame. Cross-commit history-walking over the January-February 2026 window requires recognizing that the rename waves happened at 5578915 and 2f56b5c.Signer entirely: making the signEcdsa / signSchnorr rename moot). That’s a natural consequence of an actively-iterating spec and a young reference implementation; it is not a failure of the policy.Explicitly accepted trade-offs
CHANGELOG.md and the “Spec version” note in each release.5578915 (2026-01-21): initial v0.2 terminology pass.fd33387 (2026-01-21): continued terminology alignment.2f56b5c (2026-02-13): beacon architectural refactor and update-flow named-parameter reshape.Signer entirely, superseding the signEcdsa / signSchnorr rename made during this wave.