UNPKG

@did-btcr2/method

Version:

Javascript/TypeScript reference implementation of did:btcr2 method, a censorship resistant DID Method using the Bitcoin blockchain as a Verifiable Data Registry to announce changes to the DID document. Core package of the did-btcr2-js monorepo.

276 lines (256 loc) 11.4 kB
import { MethodError, DidMethodError, Maybe, KeyBytes } from '@did-btcr2/common'; import { DidService } from '@web5/dids'; import { networks, payments } from 'bitcoinjs-lib'; import { BeaconFactory } from '../core/beacon/factory.js'; import { BeaconService, BeaconServiceAddress } from '../interfaces/ibeacon.js'; import { Appendix } from './appendix.js'; import { DidDocument } from './did-document.js'; export interface GenerateBeaconParams { identifier: string; publicKey: KeyBytes; network: networks.Network; type: string; } /** * Required parameters for generating Beacon Services. * @interface GenerateBitcoinAddrsParams * @type {GenerateBitcoinAddrsParams} */ export interface GenerateBitcoinAddrsParams { publicKey: KeyBytes; network: networks.Network; } /** * Required parameters for generating Beacon Services. * @interface GenerateBeaconServicesParams * @type {GenerateBeaconServicesParams} */ export interface GenerateBeaconServicesParams { publicKey: KeyBytes; network: networks.Network beaconType: string; } /** * Static class of utility functions for the Beacon Service * @class BeaconUtils * @type {BeaconUtils} */ export class BeaconUtils { /** * Converts a BIP21 Bitcoin URI to a Bitcoin address * @param {string} uri The BIP21 Bitcoin URI to convert * @returns {string} The Bitcoin address extracted from the URI * @throws {DidMethodError} if the URI is not a valid Bitcoin URI */ public static parseBitcoinAddress(uri: string): string { if (!uri.startsWith('bitcoin:')) { throw new DidMethodError('Invalid Bitcoin URI format', { type: 'BEACON_ERROR' }); } return uri.replace('bitcoin:', '').split('?')[0]; // Extracts address from "bitcoin:<address>?params" } /** * Validates that the given object is a Beacon Service * @param {BeaconService} obj The object to validate * @returns {boolean} A boolean indicating whether the object is a Beacon Service */ public static isBeaconService(obj: Maybe<BeaconService>): boolean { // Return false if the given obj is not a valid DidService. if(!Appendix.isDidService(obj)) return false; // Return false if the type is not a valid beacon service type. if(!['SingletonBeacon', 'CIDAggregateBeacon', 'SMTAggregateBeacon'].includes(obj.type)) return false; // Return false if the serviceEndpoint is not a valid BIP21 bitcoin address. if ([obj.serviceEndpoint].flat().some(ep => typeof ep === 'string' && !ep.startsWith('bitcoin:'))) return false; // Return false if the casType exists and is not a string. if(obj.casType && typeof obj.casType !== 'string') return false; // Else return true return true; } /** * Extracts the services from a given DID Document * @param {DidDocument} didDocument The DID Document to extract the services from * @returns {DidService[]} An array of DidService objects * @throws {TypeError} if the didDocument is not provided */ public static getBeaconServices(didDocument: DidDocument): BeaconService[] { // Filter out any invalid did service objects. const didServices: DidService[] = didDocument.service?.filter(Appendix.isDidService) ?? []; // Filter for valid beacon service objects. return (didServices.filter(this.isBeaconService) ?? []) as BeaconService[]; } /** * Generate all 3 Beacon Service Endpoints for a given public key. * @param {GenerateBitcoinAddrsParams} params Required parameters for generating Beacon Services. * @param {KeyBytes} params.publicKey Public key bytes used to generate the beacon object serviceEndpoint. * @param {Network} params.network Bitcoin network interface from bitcoinlib-js. * @returns {Array<Array<string>>} 2D Array of bitcoin addresses (p2pkh, p2wpkh, p2tr). * @throws {DidMethodError} if the bitcoin address is invalid. */ public static generateBeaconAddresses({ identifier, publicKey, network }: { identifier: string; publicKey: KeyBytes; network: networks.Network; }): Array<Array<string>> { try { const p2pkh = payments.p2pkh({ pubkey: publicKey, network }).address; const p2wpkh = payments.p2wpkh({ pubkey: publicKey, network }).address; const p2tr = payments.p2tr({ network, internalPubkey: publicKey.slice(1, 33) }).address; if (!p2pkh || !p2wpkh || !p2tr) { throw new DidMethodError('Failed to generate bitcoin addresses'); } return [ [`${identifier}#initialP2PKH`, p2pkh], [`${identifier}#initialP2WPKH`, p2wpkh], [`${identifier}#initialP2TR`, p2tr] ]; } catch (error) { console.error(error); process.exit(1); } } /** * Generate a set of Beacon Services for a given public key. * @param {GenerateBeaconServicesParams} params Required parameters for generating Beacon Services. * @param {KeyBytes} params.publicKey Public key bytes used to generate the beacon object serviceEndpoint. * @param {Network} params.network Bitcoin network interface from bitcoinlib-js. * @param {string} params.beaconType The type of beacon service to create. * @param {string} params.addressType The type of address to create (p2pkh, p2wpkh, p2tr). * @returns {BeaconService} A BeaconService object. * @throws {DidMethodError} if the bitcoin address is invalid. */ public static generateBeaconService({ id, publicKey: pubkey, network, addressType, type }: { id: string; publicKey: KeyBytes; network: networks.Network; addressType: 'p2pkh' | 'p2wpkh' | 'p2tr'; type: string; }): BeaconService { try { if(!id.includes('#')) { id = `${id}#initial${addressType.toUpperCase()}`; } const serviceEndpoint = `bitcoin:${payments[addressType]({ pubkey, network }).address}`; return { id, type, serviceEndpoint, }; } catch (error) { console.error(error); process.exit(1); } } /** * Generate a custom Beacon Service. * @param {GenerateBeaconServicesParams} params Required parameters for generating Beacon Services. * @returns */ public static generateBeaconServiceCustom({ id, publicKey: pubkey, network, addressType, type }: { id: string; publicKey: KeyBytes; network: networks.Network; addressType: 'p2pkh' | 'p2wpkh' | 'p2tr'; type: string; }): BeaconService { try { if(!id.includes('#')) { throw new MethodError( 'Invalid id format. It should include a fragment identifier (e.g., #initialP2PKH).', 'BEACON_ERROR', { id } ); } const serviceEndpoint = `bitcoin:${payments[addressType]({ pubkey, network }).address}`; return { id, type, serviceEndpoint, }; } catch (error) { console.error(error); process.exit(1); } } /** * Generate beacon services. * @param {GenerateBeaconServicesParams} params Required parameters for generating Beacon Services. * @param {string} params.network The name of the Bitcoin network to use. * @param {Uint8Array} params.publicKey Byte array representation of a public key used to generate a new btcr2 key-id-type. * @param {string} params.beaconType Optional beacon type to use (default: SingletonBeacon). * @returns {DidService[]} Array of DidService objects. */ public static generateBeaconServices({ identifier, network, type, publicKey }: { identifier: string; publicKey: KeyBytes; network: networks.Network; type: string; }): Array<BeaconService> { // Generate the bitcoin addresses const bitcoinAddrs = this.generateBeaconAddresses({ identifier, publicKey, network, }); // Map the bitcoin addresses to the beacon service return bitcoinAddrs.map(([id, address]) => { // Convert the address to a BIP-21 URI const serviceEndpoint = `bitcoin:${address}`; // Create the beacon object const beacon = BeaconFactory.establish({ id, type, serviceEndpoint }); // Return the beacon service return beacon.service; }); } /** * Generate a single beacon service. * @param {GenerateBeaconParams} params Required parameters for generating a single Beacon Service. * @param {string} params.identifier The identifier for the beacon service. * @param {string} params.network The name of the Bitcoin network to use. * @param {Uint8Array} params.publicKey Byte array representation of a public key used to generate a new btcr2 key-id-type. * @param {string} params.type The type of beacon service to create. * @returns {BeaconService} A BeaconService object. * @throws {DidMethodError} if the bitcoin address is invalid. */ public static generateBeacon({ identifier, network, type, publicKey }: { identifier: string; publicKey: KeyBytes; network: networks.Network; type: string; }): BeaconService { // Generate the bitcoin addresses const bitcoinAddrs = this.generateBeaconAddresses({ identifier, publicKey, network, }); // Map the bitcoin addresses to the beacon service const beacon = bitcoinAddrs.map(([id, address]) => { // Convert the address to a BIP-21 URI const serviceEndpoint = `bitcoin:${address}`; // Create the beacon object const beacon = BeaconFactory.establish({ id, type, serviceEndpoint }); // Return the beacon service return beacon.service; }); return beacon[0]; } /** * Manufacture a pre-filled Beacon using the BeaconFactory. * @param {BeaconServicesParams} params Required parameters for generating a single Beacon Service. * @param {string} params.serviceId The type of service being created (#initialP2PKH, #initialP2WPKH, #initialP2TR). * @param {string} params.beaconType The type of beacon service being created (SingletonBeacon, SMTAggregatorBeacon). * @param {string} params.bitcoinAddress The bitcoin address to use for the service endpoint. * @returns {BeaconService} One BeaconService object. */ public static manufactureBeacon(params: BeaconService): BeaconService { return BeaconFactory.establish(params).service; } /** * Convert beacon service endpoints from BIP-21 URIs to addresses. * @param {Array<BeaconService>} beacons The list of beacon services. * @returns {Array<BeaconServiceAddress>} An array of beacon services with address: bitcoinAddress. */ public static toBeaconServiceAddress(beacons: Array<BeaconService>): Array<BeaconServiceAddress> { return beacons.map((beacon) => ({ ...beacon, address: beacon.serviceEndpoint.replace('bitcoin:', '')})); } /** * Create a map of address => beaconService with address field. * @param {Array<BeaconService>} beacons The list of beacon services. * @returns {Map<string, BeaconServiceAddress>} A map of address => beaconService. */ public static getBeaconServiceAddressMap(beacons: Array<BeaconService>): Map<string, BeaconServiceAddress> { const beaconAddrs = this.toBeaconServiceAddress(beacons); return new Map<string, BeaconServiceAddress>(beaconAddrs.map((beacon) => ([beacon.address, beacon]))); } /** * Get the beacon service ids from a list of beacon services. * @param {DidDocument} didDocument The DID Document to extract the services from. * @returns {string[]} An array of beacon service ids. */ public static getBeaconServiceIds(didDocument: DidDocument): string[] { return this.getBeaconServices(didDocument).map((beacon) => beacon.id); } }