Status: Accepted
Date: 2026-03-06
Commit: b47b92f
A reference implementation of a DID method lives or dies by its test vectors. Test vectors are canonical (input, output) pairs: the spec-compliant answer to “given this DID, these updates, this network, what does a compliant implementation produce?” Three distinct consumers need them:
Before this commit, vectors were generated ad-hoc. Each test had its own setup: some built a DID inline, some loaded hand-crafted JSON, some mocked Bitcoin responses. The drift was predictable: vectors fell out of sync with the spec as it evolved, vectors for one flow were inconsistent with vectors for another, and when a downstream Rust implementation wanted parity vectors, there was no canonical set to hand over.
Three structural problems had to be solved together:
create to update to fund to announce to resolve) has real-world side effects: funding a beacon address costs sats on mainnet or testnet faucet grants on testnet. Running it as one monolithic script means every downstream step reruns every upstream step, which is wasteful in the best case and impossible in the worst (re-running fund after a successful fund is a double-spend).The decision window also surfaced a sub-question about the workflow itself. The spec’s create/update/resolve flows have been refining; some steps that used to be distinct (announce as a standalone step, resolve-live as a separate command from resolve) had become noise over time. The methodology commit was the natural place to collapse that noise.
On vector generation shape:
On storage location:
packages/method/tests/fixtures/. Simple; tightly coupled to the library’s test layout. Hard for downstream Rust/Python projects to consume without git-subtreeing our test dir.test-vectors/ directory. Slightly easier to consume externally, but downstream projects still pin to a specific library release to pick up vector updates.did-btcr2-test-suite). This library references them via submodule at packages/method/lib/data/. Downstream implementations consume the same external repo directly.On workflow shape:
generate-vector create, generate-vector update, etc. Each phase is a separate invocation with its own arguments.--offline flag for steps that would normally hit the network. Keeps CI, offline runs, and partial-vector generation ergonomic.Stepped CLI tool + structured artifacts + external submodule + --offline flag per phase.
The tool. packages/method/lib/generate-vector.ts is a tsx-executed script exposing subcommands:
generate-vector create --type k --network regtest ...
generate-vector update --hash <vector-hash> [--offline]
generate-vector fund --hash <vector-hash>
generate-vector announce --hash <vector-hash>
generate-vector resolve --hash <vector-hash> [--offline]
generate-vector list [--network ...] [--type ...]
Each subcommand is independently invocable, writes its artifacts to disk, and can be re-run in the rare case that intermediate state changes.
Structured storage. Vectors live under packages/method/lib/data/{network}/{type}/{hash}/ with three subdirectories:
create/: genesis bytes, genesis document (for external), initial DID resolutionupdate/: patch set, signed update, announcement artifactsresolve/: resolution input (sidecar), resolution outputThe hash in the path is a stable identifier derived from the vector’s genesis inputs: the same inputs always produce the same hash, so vectors are content-addressable within the tree.
External submodule. The lib/data/ directory is a git submodule pointing at did-btcr2-test-suite. Vector commits happen inside the submodule and are pushed to the test-suite repo; the parent repo just bumps the submodule pointer. Downstream language implementations clone the same submodule, giving every compliant implementation access to the same authoritative vector set without any of them depending on this TypeScript library.
Workflow collapse. This commit takes the opportunity to clean up accumulated noise:
--step <name> flag replaced by a positional action argument (generate-vector create is ergonomic; generate-vector --step create is not).announce merged into update: update announces by default; --offline skips the announcement. Two commands were doing one conceptual thing.resolve and resolve-live merged into resolve: resolves live by default; --offline uses only sidecar data. Same reason.resolve no longer requires a prior update step: if no update exists, it resolves the initial DID state. This matches what a real consumer does: resolve anything at any point in its lifecycle.Supporting code changes. Along with the methodology, the commit makes DidBtcr2.create() synchronous: there was no reason for it to be async and the async signature infected downstream call sites with await noise. lib/ files across packages get linting and type-checking via lib/tsconfig.json; they were previously **/lib/*-ignored and prone to bit-rot.
Positive
lib/data/bitcoin/k1/qqps9pu0/create/input.json and reads the input directly; no need to run code to see what’s being tested.--offline paths let CI and contributors without Bitcoin access run the generator meaningfully. create, update --offline, resolve --offline round-trips through the whole stepped workflow without any network access.Negative
git submodule update --init is an easy fix but an easy-to-forget one.{network}/{type}/{hash}/ hierarchy is fixed. Changing the taxonomy (adding a dimension, renaming k or x) requires migrating every vector. Mitigation: the generator tool could handle migrations, but no one has had to yet.Explicitly accepted trade-offs
tsx script, not a compiled binary. It lives under lib/, runs on demand via pnpm generate:vector, and has direct access to this library’s current implementation. Users of the generator are contributors to this repo, not end users of did:btcr2: so shipping it as a published CLI would be overreach.create/input.json does not embed a spec-version field. Which spec version a vector corresponds to is known by which commit of the submodule it lives in. This keeps the JSON files uncluttered; version-correlation is a submodule-commit-history problem, not a schema problem.main only; there are no long-lived branches per spec version. Vectors for older spec versions are accessible by checking out older submodule commits, not by switching branches.packages/method/lib/generate-vector.ts: the generator CLI.packages/method/lib/data/: submodule pointing at the test-suite repo.packages/method/package.json: generate:vector script entry.