did-btcr2-js

@did-btcr2/cryptosuite

TypeScript implementation of the Data Integrity BIP340 Cryptosuite v0.1 specification.

Part of the did-btcr2-js monorepo.

Summary

This package produces and verifies method-agnostic Data Integrity proofs; its primary consumer is did:btcr2, whose updates it authenticates. A secured document is any JSON object plus a proof block carrying a BIP-340 Schnorr signature over the JCS-canonicalized payload.

The cryptosuite is what links the abstract Signer interface from @did-btcr2/keypair to the on-disk shape of a signed DID update.

Install

npm install @did-btcr2/cryptosuite

Or with pnpm:

pnpm add @did-btcr2/cryptosuite

The package ships both ESM (dist/esm/) and CJS (dist/cjs/) via conditional exports, so it works with import and require out of the box.

Key Exports

Concern Entry point
Build / verify proofs BIP340Cryptosuite, Cryptosuite, VerificationResult
Proof primitive BIP340DataIntegrityProof, DataIntegrityProof, DataIntegrityProofObject
Document payload types UnsecuredDocument, SecuredDocument
Verification method wrapper SchnorrMultikey, Multikey, MultikeyObject
Construction helpers FromSecretKey, FromPublicKey
Proof options DataIntegrityProofOptions

Quick Start

Sign an update

import { SchnorrMultikey } from '@did-btcr2/cryptosuite';
import { Secp256k1SecretKey } from '@did-btcr2/keypair';

const controller   = 'did:btcr2:k1q5p...';
const id           = '#initialKey';
const secretKeyBytes = new Secp256k1SecretKey(rawSecretBytes).bytes; // 32-byte Uint8Array
const multikey     = SchnorrMultikey.fromSecretKey(id, controller, secretKeyBytes);

const unsigned = { /* UnsecuredDocument: any JSON object to add a proof to (e.g. an unsigned did:btcr2 update) */ };
const config = {
  '@context'         : ['https://w3id.org/security/v2', 'https://w3id.org/zcap/v1', 'https://w3id.org/json-ld-patch/v1', 'https://btcr2.dev/context/v1'],
  type               : 'DataIntegrityProof' as const,
  cryptosuite        : 'bip340-jcs-2025',
  proofPurpose       : 'capabilityInvocation',
  verificationMethod : `${controller}${id}`,
};
const proof = multikey.toCryptosuite().createProof(unsigned, config);
// proof.cryptosuite === 'bip340-jcs-2025'

Verify a signed update

import { SchnorrMultikey } from '@did-btcr2/cryptosuite';

// Reconstruct the multikey from the DID verification method.
// verificationMethod is a DidVerificationMethod with id, controller, publicKeyMultibase.
const multikey = SchnorrMultikey.fromVerificationMethod(verificationMethod);
const result   = multikey.toCryptosuite().verifyProof(signedUpdate);

if (!result.verified) {
  throw new Error('DI proof failed');
}

Sign via an external Signer (HSM, KMS, hardware wallet)

import { SchnorrMultikey } from '@did-btcr2/cryptosuite';

// kmsSigner implements Signer: { publicKey: Uint8Array; sign(data, scheme): SignatureBytes }
const controller = 'did:btcr2:k1q5p...';
const id         = '#initialKey';
const multikey   = SchnorrMultikey.fromSigner(id, controller, kmsSigner);

const config = {
  '@context'         : ['https://w3id.org/security/v2', 'https://w3id.org/zcap/v1', 'https://w3id.org/json-ld-patch/v1', 'https://btcr2.dev/context/v1'],
  type               : 'DataIntegrityProof' as const,
  cryptosuite        : 'bip340-jcs-2025',
  proofPurpose       : 'capabilityInvocation',
  verificationMethod : `${controller}${id}`,
};
const proof = multikey.toCryptosuite().createProof(unsigned, config);

fromSigner seeds the multikey’s public key from kmsSigner.publicKey and delegates all signing to the signer, so no secret bytes ever enter the JS process. A public-key mismatch at construction time throws immediately.

Architecture Principles

Build & Test

# From packages/cryptosuite/
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