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

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

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, SigningScheme } 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(id, data, scheme: SigningScheme) { /* call HSM, return SignatureBytes */ }
  async verify(id, data, sig, scheme) { /* ... */ }
  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