did-btcr2-js

ADR 007: KMS Package Boundary

Status: Accepted

Date: 2025-10-28

Commit: 0893492

Context

Before this commit, key management logic lived inside packages/method/src/core/key-manager/, mixing three distinct concerns into one folder:

  1. Key primitives: signing, verification, encoding. These belong to @did-btcr2/keypair (and, for Multikey-wrapped keys, to @did-btcr2/cryptosuite).
  2. Key lifecycle: generate, import, list, track an “active” key, remove. Neither keypair nor cryptosuite had an opinion here.
  3. Method-specific key usage: SingletonBeacon acquiring a keypair by service.id to sign a Bitcoin PSBT, for example. That’s protocol code.

The mixing produced several concrete pains:

There was also a forward-looking concern: the team already knew that an HD-wallet app (Rolohex) would consume the did:btcr2 library, and that app would want to manage a thousand keys through a structured lifecycle: generate, import, tag, derive, rotate. Either the lifecycle primitives were going to live in a dedicated package with a clean interface, or every wallet consumer would grow its own adapter layer around the internals of method.

Options considered

  1. Keep key management in method. It’s DID-specific anyway and every operation goes through method. Minimum disruption. Blocks HSM/hardware/cloud pluggability; keeps the key-lifecycle surface invisible from outside method; forces every consumer into the same in-memory store shape.
  2. Fold key management into keypair. Same package owns everything key-related. But keypair is a primitive package: it owns “what is a secp256k1 key pair.” Loading it with store abstractions and active-key bookkeeping contaminates a package whose strength is that it’s small and crypto-only.
  3. Separate @did-btcr2/kms package with its own KeyManager interface and pluggable KeyValueStore. Lifecycle concerns live in kms. Primitives stay in keypair. method consumes kms by interface, so consumers can swap in HSM/hardware/cloud without touching method at all.

Decision

Option 3. On 2025-10-28 (commit 0893492), @did-btcr2/kms@0.1.0 is initialized. The package boundary is defined by:

The package-graph consequence is clean: kms depends on keypair (for the SchnorrKeyPair return shape) and common (for shared types). method depends on kms. Consumers who never sign: pure resolvers: never import kms.

Consequences

Positive

Negative

Explicitly accepted trade-offs

References