did-btcr2-js

@did-btcr2/key-manager

Key management interface for did-btcr2-js. Generate, import, sign, verify, and digest with stored secp256k1 keys.

Part of the did-btcr2-js monorepo.

Renamed package. Previously published as @did-btcr2/kms. The API surface is unchanged: only the import path moved. See ADR-033 for the rationale.

Summary

The KeyManager interface defines how the rest of the SDK obtains signatures without ever seeing raw secret bytes. This package ships LocalKeyManager (an in-process reference implementation) plus the KeyManagerSigner adapter that wraps any KeyManager into a Signer compatible with @did-btcr2/method.

The signer can be constructed with or without an explicit keyId. Without one, it resolves to the key manager’s active key at sign-time and caches its public key on first read. See ADR-034 for the capability pattern and active-key resolution semantics.

Install

npm install @did-btcr2/key-manager

Or with pnpm:

pnpm add @did-btcr2/key-manager

Unlike @did-btcr2/method and @did-btcr2/api (ESM-only), this package ships both ESM and CJS builds. Requires Node >= 22.

Key Exports

Concern Entry point
Backend interface KeyManager, KeyEntry, KeyIdentifier, SigningScheme, VerifyScheme
Reference implementation LocalKeyManager
Signer adapter KeyManagerSigner
Storage abstraction KeyValueStore, MemoryStore
Lifecycle options GenerateKeyOptions, ImportKeyOptions, SignOptions, VerifyOptions
Key listing / active key listKeys(), activeKeyId

Quick Start

import { KeyManagerSigner, LocalKeyManager } from '@did-btcr2/key-manager';
import { SchnorrKeyPair } from '@did-btcr2/keypair';

const km    = new LocalKeyManager();
const kp    = SchnorrKeyPair.generate();
const keyId = km.importKey(kp, { setActive: true, tags: { purpose: 'did-update' } });

// Sign through the KeyManagerSigner; secret bytes never leave the manager.
const signer = new KeyManagerSigner(km, keyId);

const sig = signer.sign(new Uint8Array(32), 'bip340');

// Watch-only entries are allowed: pubkey-only, sign() throws KEY_NOT_SIGNER.
const watchOnly = new SchnorrKeyPair({ publicKey: kp.publicKey });
const watchId   = km.importKey(watchOnly);
// new KeyManagerSigner(km, watchId).sign(data, 'bip340') -> KeyManagerError

Building a custom backend

Implement the KeyManager interface and advertise capabilities. The minimum surface:

import type { KeyManager, KeyEntry, SignOptions, VerifyOptions } from '@did-btcr2/key-manager';

class RemoteKeyManager implements KeyManager {
  readonly canExport = false;

  async generateKey(opts) { /* call into HSM, return KeyEntry */ }
  async importKey(kp, opts) { /* store, return KeyIdentifier */ }
  async sign(data, id?, options?: SignOptions) { /* call HSM, return SignatureBytes */ }
  async verify(signature, data, id?, options?: VerifyOptions) { /* ... */ }
  async digest(data) { /* ... */ }
  async getPublicKey(id) { /* ... */ }
  async removeKey(id, opts?) { /* ... */ }
  // No exportKey(): capability flag tells callers not to try.
}

@did-btcr2/api and @did-btcr2/method both route signing through the Signer interface, so any backend that produces a KeyManagerSigner works end-to-end.

Architecture Principles

Build & Test

# From packages/key-manager/
pnpm build              # Compile ESM + CJS + type declarations
pnpm build:tests        # Compile tests to tests/compiled/
pnpm test               # Run the test suite with coverage
pnpm lint               # ESLint (zero warnings tolerated)

Documentation

License

MPL-2.0