UNPKG

liquidjs-lib

Version:

Client-side Liquid JavaScript library

169 lines (168 loc) 6.08 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.findScriptPath = exports.toHashTree = exports.tapLeafHash = exports.BIP341Factory = exports.LEAF_VERSION_TAPSCRIPT = void 0; const crypto_1 = require('./crypto'); const ecpair_1 = require('ecpair'); const bufferutils_1 = require('./bufferutils'); exports.LEAF_VERSION_TAPSCRIPT = 0xc4; function BIP341Factory(ecc) { return { taprootSignKey: taprootSignKey(ecc), taprootSignScriptStack: taprootSignScriptStack(ecc), taprootOutputScript: taprootOutputScript(ecc), }; } exports.BIP341Factory = BIP341Factory; // hash TaprootLeaf object, could be use to identify a leaf in a MAST tree function tapLeafHash(leaf) { const leafVersion = leaf.version || exports.LEAF_VERSION_TAPSCRIPT; const script = Buffer.from(leaf.scriptHex, 'hex'); const bufferWriter = bufferutils_1.BufferWriter.withCapacity( 1 + (0, bufferutils_1.varSliceSize)(script), ); bufferWriter.writeUInt8(leafVersion); bufferWriter.writeVarSlice(script); return (0, crypto_1.taggedHash)('TapLeaf/elements', bufferWriter.end()); } exports.tapLeafHash = tapLeafHash; // recursively build the Taproot tree from a ScriptTree structure function toHashTree(leaves, withScriptHex = false) { switch (leaves.length) { case 0: return { hash: Buffer.alloc(32) }; case 1: const leaf = leaves[0]; const version = leaf.version || exports.LEAF_VERSION_TAPSCRIPT; if ((version & 1) !== 0) { throw new Error('Invalid leaf version'); } return { hash: tapLeafHash(leaf), scriptHex: withScriptHex ? leaf.scriptHex : undefined, }; default: // 2 or more entries const middleIndex = Math.ceil(leaves.length / 2); const left = toHashTree(leaves.slice(0, middleIndex), withScriptHex); const right = toHashTree(leaves.slice(middleIndex), withScriptHex); let leftHash = left.hash; let rightHash = right.hash; // check if left is greater than right if (left.hash.compare(right.hash) > 0) { [leftHash, rightHash] = [rightHash, leftHash]; } return { left, right, hash: (0, crypto_1.taggedHash)( 'TapBranch/elements', Buffer.concat([leftHash, rightHash]), ), }; } } exports.toHashTree = toHashTree; /** * Given a MAST tree, it finds the path of a particular hash. * @param node - the root of the tree * @param hash - the hash to search for * @returns - and array of hashes representing the path, or an empty array if no pat is found */ function findScriptPath(node, hash) { if (node.left) { if (node.left.hash.equals(hash)) return node.right ? [node.right.hash] : []; const leftPath = findScriptPath(node.left, hash); if (leftPath.length) return node.right ? leftPath.concat([node.right.hash]) : leftPath; } if (node.right) { if (node.right.hash.equals(hash)) return node.left ? [node.left.hash] : []; const rightPath = findScriptPath(node.right, hash); if (rightPath.length) return node.left ? rightPath.concat([node.left.hash]) : rightPath; } return []; } exports.findScriptPath = findScriptPath; function tweakPublicKey(publicKey, hash, ecc) { const XOnlyPubKey = publicKey.slice(1, 33); const toTweak = Buffer.concat([XOnlyPubKey, hash]); const tweakHash = (0, crypto_1.taggedHash)('TapTweak/elements', toTweak); const tweaked = ecc.xOnlyPointAddTweak(XOnlyPubKey, tweakHash); if (!tweaked) throw new Error('Invalid tweaked key'); return tweaked; } // compute a segwit V1 output script function taprootOutputScript(ecc) { return (internalPublicKey, tree) => { let treeHash = Buffer.alloc(0); if (tree) { treeHash = tree.hash; } const { xOnlyPubkey } = tweakPublicKey(internalPublicKey, treeHash, ecc); return Buffer.concat([Buffer.from([0x51, 0x20]), xOnlyPubkey]); }; } /** * Compute the taproot part of the witness stack needed to spend a P2TR output via script path * TAPROOT_WITNESS = [SCRIPT, CONTROL_BLOCK] * WITNESS_STACK = [...INPUTS, TAPROOT_WITNESS] <- u need to add the script's inputs to the stack * @param internalPublicKey the taproot internal public key * @param leaf the leaf to use to sign the taproot coin * @param path the path to the leaf in the MAST tree see findScriptPath function */ function taprootSignScriptStack(ecc) { return (internalPublicKey, leaf, treeRootHash, path) => { const { parity } = tweakPublicKey(internalPublicKey, treeRootHash, ecc); const parityBit = Buffer.of( (leaf.version || exports.LEAF_VERSION_TAPSCRIPT) + parity, ); const control = Buffer.concat([ parityBit, internalPublicKey.slice(1), ...path, ]); return [Buffer.from(leaf.scriptHex, 'hex'), control]; }; } // Order of the curve (N) - 1 const N_LESS_1 = Buffer.from( 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140', 'hex', ); // 1 represented as 32 bytes BE const ONE = Buffer.from( '0000000000000000000000000000000000000000000000000000000000000001', 'hex', ); // Compute the witness signature for a P2TR output (key path) function taprootSignKey(ecc) { return (messageHash, key) => { const signingEcPair = (0, ecpair_1.ECPairFactory)(ecc).fromPrivateKey(key); const privateKey = signingEcPair.publicKey[0] === 2 ? signingEcPair.privateKey : ecc.privateAdd(ecc.privateSub(N_LESS_1, key), ONE); const tweakHash = (0, crypto_1.taggedHash)( 'TapTweak/elements', signingEcPair.publicKey.slice(1, 33), ); const newPrivateKey = ecc.privateAdd(privateKey, tweakHash); if (newPrivateKey === null) throw new Error('Invalid Tweak'); const signed = ecc.signSchnorr(messageHash, newPrivateKey); const ok = ecc.verifySchnorr( messageHash, (0, ecpair_1.ECPairFactory)(ecc) .fromPrivateKey(Buffer.from(newPrivateKey)) .publicKey.slice(1), signed, ); if (!ok) throw new Error('Invalid Signature'); return Buffer.from(signed); }; }