@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.
204 lines (193 loc) • 7.74 kB
text/typescript
import { KeyBytes, BIP340_PUBLIC_KEY_MULTIBASE_PREFIX, HdWallet } from '@did-btcr2/common';
import { sha256 } from '@noble/hashes/sha2';
import { CURVE, getPublicKey, utils } from '@noble/secp256k1';
import { HDKey } from '@scure/bip32';
import { generateMnemonic, mnemonicToSeed } from '@scure/bip39';
import { wordlist } from '@scure/bip39/wordlists/english';
import { base58btc } from 'multiformats/bases/base58';
/**
* Static class of general utility functions for the did-btcr2 spec implementation
* @class GeneralUtils
* @type {GeneralUtils}
*/
export class GeneralUtils {
/**
* Helper function to encode a secp256k1 key in SchnorrSecp256k1 Multikey Format
* @param {KeyBytes} xOnlyKeyBytes
* @returns {PublicKeyMultibase}
*/
public static encode(xOnlyKeyBytes: KeyBytes): string {
if (xOnlyKeyBytes.length !== 32) {
throw new Error('x-only public key must be 32 bytes');
}
const prefix = Array.from(BIP340_PUBLIC_KEY_MULTIBASE_PREFIX);
const x = Array.from(xOnlyKeyBytes);
// Set the prefix and the public key bytes
const multikeyBytes = new Uint8Array([...prefix, ...x]);
// Encode the public key as a multibase base58btc string
return base58btc.encode(multikeyBytes);
}
/**
* Converts a bigint to a buffer
* @param {bigint} value The bigint to convert
* @returns {Buffer} The buffer representation of the bigint
*/
static bigintToBuffer(value: bigint): Buffer {
const hex = value.toString(16).padStart(64, '0');
return Buffer.fromHex(hex);
}
/**
* Generates a new mnemonic phrase and HD wallet
* @returns {HdWallet} Promise resolving to a new hdwallet object w/ mnemonic and hdkey
* @throws {Error} if the public key bytes cannot be derived
*/
static async generateHdWallet(): Promise<HdWallet> {
// Generate random mnemonic phrase.
const mnemonic = generateMnemonic(wordlist, 128);
// Generate seed from random mnemonic phrase.
const seed = await mnemonicToSeed(mnemonic);
// Generate HDKey from seed.
const hdkey = HDKey.fromMasterSeed(seed);
// Ensure HDKey returns valid
if (!hdkey) {
throw new Error('Failed to derive hd wallet');
}
return { mnemonic, hdkey };
}
static generateCompressedSecp256k1KeyPair(){
const privateKey = utils.randomPrivateKey();
if(!utils.isValidPrivateKey(privateKey)) {
throw new Error('Invalid private key');
}
return { privateKey, publicKey: getPublicKey(privateKey, true) };
};
/**
* Recovers an HDKey from a mnemonic phrase
* @param {string} mnemonic The mnemonic phrase to recover the HDKey from
* @param {Uint8Array} seed Optional seed to recover the HDKey from
* @returns {HDKey} Promise resolving to the recovered HDKey
* @throws Error if the HDKey cannot be recovered
*/
static async recoverHdWallet(mnemonic: string, seed?: Uint8Array): Promise<HDKey> {
seed ??= await mnemonicToSeed(mnemonic);
// Generate HDKey from seed.
const hdkey = HDKey.fromMasterSeed(seed);
// Ensure HDKey returns valid
if (!hdkey) {
throw new Error('Failed to recover hdkey');
}
// Return the HDKey
return hdkey;
}
/**
* Recovers a secp256k1 privateKey from its original entropy
* @param {Uint8Array} xorEntropy The original entropy to recover the privateKey from
* @param {Uint8Array} salt The salt used to tweak the privateKey
* @returns {Uint8Array} The recovered privateKey
* @throws {Error} if the privateKey cannot be recovered
*/
static recoverTweakedRawPrivateKey(xorEntropy: Uint8Array, salt: Uint8Array): Uint8Array {
// If entropy is not 32 bytes, hash it to get a deterministic 32-byte private key
if (xorEntropy.length !== 32) {
xorEntropy = sha256(xorEntropy);
}
const entropy = this.XNOR(xorEntropy, salt);
// Convert entropy to hex
const hexEntropy = Buffer.from(entropy).toString('hex');
// Convert hexEntropy to BigInt
const privateKey = BigInt(`0x${hexEntropy}`);
// Ensure private key is in valid secp256k1 range1
if (privateKey < BigInt(1) || privateKey >= CURVE.n) {
throw new Error('Invalid private key derived from entropy');
}
// The valid 32-byte private key
return entropy;
}
/**
* Recovers a secp256k1 privateKey from its original entropy
* @param {Uint8Array} entropy The entropy to recover the privateKey from
* @returns {Uint8Array} The recovered privateKey
* @throws {Error} if the privateKey cannot be recovered
*/
static recoverRawPrivateKey(entropy: Uint8Array): Uint8Array {
// If entropy is not 32 bytes, hash it to get a deterministic 32-byte private key
if (entropy.length !== 32) {
entropy = sha256(entropy);
}
// Convert entropy to hex
const hexEntropy = Buffer.from(entropy).toString('hex');
// Convert hexEntropy to BigInt
const privateKey = BigInt(`0x${hexEntropy}`);
// Ensure private key is in valid secp256k1 range1
if (privateKey < BigInt(1) || privateKey >= CURVE.n) {
throw new Error('Invalid private key derived from entropy');
}
// The valid 32-byte private key
return entropy;
}
/**
* Tweak the entropy with a salt using XOR
* @param {Uint8Array} entropy The entropy to tweak
* @param {Uint8Array} salt The salt to tweak the entropy with
* @returns {Uint8Array} The tweaked entropy
*/
static XOR(entropy: Uint8Array, salt: Uint8Array): Uint8Array {
const tweaked = new Uint8Array(entropy.length);
for (let i = 0; i < entropy.length; i++) {
tweaked[i] = entropy[i] ^ salt[i % salt.length]; // XOR with repeating salt
}
return tweaked;
}
/**
* Untweak the entropy with a salt using XNOR
*
* @param {Uint8Array} tweakedEntropy The tweaked entropy to untweak
* @param {Uint8Array} salt The salt to untweak the entropy with
* @returns {Uint8Array} The original entropy
*/
static XNOR(tweakedEntropy: Uint8Array, salt: Uint8Array): Uint8Array {
const originalEntropy = new Uint8Array(tweakedEntropy.length);
for (let i = 0; i < tweakedEntropy.length; i++) {
originalEntropy[i] = tweakedEntropy[i] ^ salt[i % salt.length]; // XOR with salt again
}
return originalEntropy;
}
/**
* Recovers an HDKey from a mnemonic phrase
* @param {string} mnemonic The mnemonic phrase to recover the HDKey from
* @param {string} path The path to derive the child key from
* @returns {Uint8Array} Promise resolving to the recovered private key bytes
* @throws {Error} if the HDKey cannot be recovered
*/
static async recoverHdChildFromMnemonic(mnemonic: string, path: string): Promise<Uint8Array> {
// Generate HDKey from seed.
const hdkey = await this.recoverHdWallet(mnemonic);
// Ensure HDKey returns valid
if (!hdkey) {
throw new Error('Failed to recover hdkey');
}
// Return the privateKey of the derived childKey
const childPrivKeyBytes = hdkey.derive(path).privateKey;
if (!childPrivKeyBytes) {
throw new Error('Failed to recover child private key');
}
return childPrivKeyBytes;
}
/**
* Derives a child key from an HDKey
* @param {HDKey} hdkey The HDKey to derive the child key from
* @param {string} path The path to derive the child key from
* @returns {HDKey} A Promise resolving to the child key
* @throws {Error} Error if the child key cannot be derived
*/
static deriveChildKey(hdkey: HDKey, path: string): HDKey {
// Derive child key from HDKey.
const childKey = hdkey.derive(path);
// Ensure child key returns valid
if (!childKey) {
throw new Error(`Failed to derive child key`);
}
// Return the child key
return childKey;
}
}