@bitgo/utxo-lib
Version:
Client-side Bitcoin JavaScript library
231 lines • 10 kB
TypeScript
/**
* 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