UNPKG

@bitgo/utxo-lib

Version:

Client-side Bitcoin JavaScript library

231 lines 10 kB
/** * This module provides taproot utilities including the legacy MuSig2 key aggregation * algorithm used by BitGo's deprecated `p2tr` script type (chains 30, 31). * * ## Legacy p2tr vs Standard p2trMusig2 * * BitGo supports two taproot address types: * * 1. **Legacy `p2tr` (chains 30, 31) - DEPRECATED** * - Uses the `aggregateMuSigPubkeys()` function in this module * - Based on an older MuSig2 variant that predates BIP327 * - Expects 32-byte x-only pubkeys with even Y coordinates * - Sorts keys AFTER x-only conversion * - Corresponds to MuSig2 before the 32-byte to 33-byte key change * - See: https://github.com/jonasnick/bips/pull/37 * * 2. **Standard `p2trMusig2` (chains 40, 41) - RECOMMENDED** * - Uses the `@brandonblack/musig` library (standard BIP327 implementation) * - Uses full 33-byte compressed pubkeys throughout aggregation * - Key order affects the resulting aggregate key * - Fully compatible with the BIP327 specification * * ## Key Difference * * The critical difference is **when x-only conversion happens**: * - Legacy: Converts to x-only BEFORE sorting (produces order-independent keys) * - Standard: Uses 33-byte keys throughout (key order matters) * * For the same two pubkeys, these methods produce DIFFERENT aggregate keys because * the sort order differs between 33-byte and 32-byte representations. * * See `modules/utxo-lib/bip-0327/README.md` for detailed comparison and test cases. */ import { TapTree as PsbtTapTree } from 'bip174/src/lib/interfaces'; import { payments as bpayments } from 'bitcoinjs-lib'; /** * The 0x02 prefix indicating an even Y coordinate which is implicitly assumed * on all 32 byte x-only pub keys as defined in BIP340. */ export declare const EVEN_Y_COORD_PREFIX: Buffer<ArrayBuffer>; export declare const INITIAL_TAPSCRIPT_VERSION = 192; export interface TinySecp256k1Interface { isXOnlyPoint(p: Uint8Array): boolean; xOnlyPointAddTweak(p: Uint8Array, tweak: Uint8Array): XOnlyPointAddTweakResult | null; pointFromScalar(sk: Uint8Array, compressed?: boolean): Uint8Array | null; pointMultiply(a: Uint8Array, b: Uint8Array): Uint8Array | null; pointAdd(a: Uint8Array, b: Uint8Array): Uint8Array | null; privateAdd(d: Uint8Array, tweak: Uint8Array): Uint8Array | null; privateNegate(d: Uint8Array): Uint8Array; } /** * Aggregates a list of public keys into a single public key using the legacy MuSig2 algorithm. * * This implements the deprecated key aggregation method used by BitGo's `p2tr` script type * (chains 30, 31). It corresponds to an older variant of MuSig2 that predates the change * from 32-byte to 33-byte keys in the BIP327 specification. * * ## Algorithm * * The implementation follows the MuSig2 key aggregation scheme: * * ``` * P = sum_i (μ_i * P_i) * ``` * * where: * - `P_i` is the public key of the i-th signer * - `μ_i` is the MuSig coefficient computed as: * - `L = TaggedHash("KeyAgg list", P_1 || P_2 || ... || P_n)` * - `μ_i = TaggedHash("KeyAgg coefficient", L || P_i)` for most keys * - `μ_i = 1` for the second unique key (optimization to save an exponentiation) * * ## Key Characteristics (Legacy Variant) * * 1. **X-only pubkeys**: Expects 32-byte x-only pubkeys (assumes even Y coordinates) * 2. **Pre-aggregation sorting**: Sorts keys in ascending order BEFORE aggregation * 3. **Order-independent**: Due to sorting, key order doesn't affect the result * 4. **33-byte internal representation**: Internally prepends 0x02 prefix for elliptic curve operations * * ## Differences from Standard MuSig2 (BIP327) * * The standard MuSig2 implementation (`@brandonblack/musig` library): * - Uses 33-byte compressed pubkeys throughout * - Does NOT sort keys before aggregation (key order matters) * - Produces different aggregate keys even for the same set of pubkeys * * ## Reference Implementation * * This corresponds to `key_agg_bitgo_p2tr_legacy()` in the Python reference implementation * at `modules/utxo-lib/bip-0327/reference.py`. * * @param ecc Elliptic curve implementation * @param pubkeys List of 32-byte x-only public keys to aggregate (must have even Y coordinates) * @returns 32-byte Buffer representing the x-only aggregate public key * @throws {Error} if fewer than 2 pubkeys provided or elliptic curve operations fail * * @see modules/utxo-lib/bip-0327/README.md for detailed comparison with standard MuSig2 * @see https://github.com/jonasnick/bips/pull/37 for the MuSig2 specification change */ export declare function aggregateMuSigPubkeys(ecc: TinySecp256k1Interface, pubkeys: Buffer[]): Uint8Array; /** * Encodes the length of a script as a bitcoin variable length integer. * @param script * @returns */ export declare function serializeScriptSize(script: Buffer): Buffer; /** * Gets a tapleaf tagged hash from a script. * @param script * @returns */ export declare function hashTapLeaf(script: Buffer, leafVersion?: number): Buffer; /** * Creates a lexicographically sorted tapbranch from two child taptree nodes * and returns its tagged hash. * @param child1 * @param child2 * @returns the tagged tapbranch hash */ export declare function hashTapBranch(child1: Buffer, child2: Buffer): Buffer; export declare function calculateTapTweak(pubkey: Uint8Array, taptreeRoot?: Uint8Array): Uint8Array; /** * Tweaks a privkey, using the tagged hash of its pubkey, and (optionally) a taptree root * @param ecc Elliptic curve implementation * @param pubkey public key, used to calculate the tweak * @param privkey the privkey to tweak * @param taptreeRoot the taptree root tagged hash * @returns {Buffer} the tweaked privkey */ export declare function tapTweakPrivkey(ecc: TinySecp256k1Interface, pubkey: Uint8Array, privkey: Uint8Array, taptreeRoot?: Uint8Array): Uint8Array; export interface XOnlyPointAddTweakResult { parity: 1 | 0; xOnlyPubkey: Uint8Array; } /** * Tweaks an internal pubkey, using the tagged hash of itself, and (optionally) a taptree root * @param ecc Elliptic curve implementation * @param pubkey the internal pubkey to tweak * @param taptreeRoot the taptree root tagged hash * @returns {TweakedPubkey} the tweaked pubkey */ export declare function tapTweakPubkey(ecc: TinySecp256k1Interface, pubkey: Uint8Array, taptreeRoot?: Buffer): XOnlyPointAddTweakResult; export interface Taptree { root: Buffer; paths: Buffer[][]; } /** * Gets the root hash and hash-paths of a taptree from the depth-first * construction used in BIP-0371 PSBTs * @param tree * @returns {Taptree} the tree, represented by its root hash, and the paths to * that root from each of the input scripts */ export declare function getDepthFirstTaptree(tree: PsbtTapTree): Taptree; /** * Gets the root hash of a taptree using a weighted Huffman construction from a * list of scripts and corresponding weights. * @param scripts * @param weights * @returns {Taptree} the tree, represented by its root hash, and the paths to that root from each of the input scripts */ export declare function getHuffmanTaptree(scripts: Buffer[], weights: Array<number | undefined>): Taptree; export declare function getControlBlock(parity: 0 | 1, pubkey: Uint8Array, path: Buffer[], leafVersion?: number): Buffer; export interface KeyPathWitness { spendType: 'Key'; signature: Buffer; annex?: Buffer; } export interface ScriptPathWitness { spendType: 'Script'; scriptSig: Buffer[]; tapscript: Buffer; controlBlock: Buffer; annex?: Buffer; } export interface ControlBlock { parity: number; internalPubkey: Buffer; leafVersion: number; path: Buffer[]; } /** * Parses a taproot witness stack and extracts key data elements. * @param witnessStack * @returns {ScriptPathWitness|KeyPathWitness} an object representing the * parsed witness for a script path or key path spend. * @throws {Error} if the witness stack does not conform to the BIP 341 script validation rules */ export declare function parseTaprootWitness(witnessStack: Buffer[]): ScriptPathWitness | KeyPathWitness; /** * Parses a taproot control block. * @param ecc Elliptic curve implementation * @param controlBlock the control block to parse * @returns {ControlBlock} the parsed control block * @throws {Error} if the witness stack does not conform to the BIP 341 script validation rules */ export declare function parseControlBlock(ecc: TinySecp256k1Interface, controlBlock: Buffer): ControlBlock; /** * Calculates the tapleaf hash from a control block and script. * @param ecc Elliptic curve implementation * @param controlBlock the control block, either raw or parsed * @param tapscript the leaf script corresdponding to the control block * @returns {Buffer} the tapleaf hash */ export declare function getTapleafHash(ecc: TinySecp256k1Interface, controlBlock: Buffer | ControlBlock, tapscript: Buffer): Buffer; /** * Calculates the taptree root hash from a control block and script. * @param ecc Elliptic curve implementation * @param controlBlock the control block, either raw or parsed * @param tapscript the leaf script corresdponding to the control block * @param tapleafHash the leaf hash if already calculated * @returns {Buffer} the taptree root hash */ export declare function getTaptreeRoot(ecc: TinySecp256k1Interface, controlBlock: Buffer | ControlBlock, tapscript: Buffer, tapleafHash?: Buffer): Buffer; export declare function getTweakedOutputKey(payment: bpayments.Payment): Buffer; /** * @returns output script for either script path input controlBlock * & leafScript OR key path input internalPubKey & taptreeRoot */ export declare function createTaprootOutputScript(p2trArgs: { internalPubKey: Buffer; taptreeRoot: Buffer; } | { controlBlock: Buffer; leafScript: Buffer; }): Buffer; /** * @returns x-only taproot output key (tapOutputKey) */ export declare function getTaprootOutputKey(outputScript: Buffer | (number | Buffer)[]): Buffer; //# sourceMappingURL=taproot.d.ts.map