did-btcr2-js

ADR 011: Test Vector Generation Methodology

Status: Accepted

Date: 2026-03-06

Commit: b47b92f

Context

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:

  1. This library’s own test suite, to assert behavior stays correct across refactors.
  2. Downstream language implementations (Rust, Python, Go) performing cross-implementation parity tests: “my Rust resolver produces the same output as your TypeScript resolver on this vector.”
  3. Spec reviewers and auditors who want to see concrete inputs and outputs for each flow, not just prose descriptions.

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:

  1. Reproducibility. A vector should be derivable from a seed. Anyone with the code should be able to regenerate the same vectors byte-for-byte. That means every step: key generation, genesis creation, update signing, funding, broadcasting, resolution: needs to be a step with deterministic inputs and outputs.
  2. Stepped workflow. The full lifecycle (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).
  3. Shareability with external implementations. TypeScript-generated vectors need to be consumable by a Rust parity test without coupling the TypeScript library to the Rust one. Vectors have to live somewhere both projects can point to.

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.

Options considered

On vector generation shape:

  1. Ad-hoc scripts per test. What existed. Each test owns vector setup. Drift-prone; no canonical set.
  2. Generated in CI per build. A build-time hook generates fresh vectors. Deterministic across builds only if the seeds are checked in; then effectively equivalent to committed vectors. Complicates CI with fund-dependent steps.
  3. Stepped CLI tool writing structured artifacts. A command-line tool that exposes each lifecycle step individually, writes inspectable JSON artifacts, and can be re-run per-step. Vectors are committed.

On storage location:

  1. In-repo under 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.
  2. In-repo under a top-level test-vectors/ directory. Slightly easier to consume externally, but downstream projects still pin to a specific library release to pick up vector updates.
  3. External repository consumed as a git submodule. Vectors are their own project (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:

  1. Single monolithic run. One command, all phases. No intermediate inspection; no partial-failure recovery; re-running is all-or-nothing.
  2. Per-phase CLI subcommands. generate-vector create, generate-vector update, etc. Each phase is a separate invocation with its own arguments.
  3. Per-phase subcommands with --offline flag for steps that would normally hit the network. Keeps CI, offline runs, and partial-vector generation ergonomic.

Decision

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:

The 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:

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.

Consequences

Positive

Negative

Explicitly accepted trade-offs

References