|
| 1 | +'use strict'; |
| 2 | +Object.defineProperty(exports, '__esModule', { value: true }); |
| 3 | +exports.tweakKey = exports.tapTweakHash = exports.tapleafHash = exports.findScriptPath = exports.toHashTree = exports.rootHashFromPath = exports.MAX_TAPTREE_DEPTH = exports.LEAF_VERSION_TAPSCRIPT = void 0; |
| 4 | +const buffer_1 = require('buffer'); |
| 5 | +const ecc_lib_1 = require('../ecc_lib'); |
| 6 | +const bcrypto = require('../crypto'); |
| 7 | +const bufferutils_1 = require('../bufferutils'); |
| 8 | +const types_1 = require('../types'); |
| 9 | +exports.LEAF_VERSION_TAPSCRIPT = 0xc0; |
| 10 | +exports.MAX_TAPTREE_DEPTH = 128; |
| 11 | +const isHashBranch = ht => 'left' in ht && 'right' in ht; |
| 12 | +function rootHashFromPath(controlBlock, leafHash) { |
| 13 | + if (controlBlock.length < 33) |
| 14 | + throw new TypeError( |
| 15 | + `The control-block length is too small. Got ${ |
| 16 | + controlBlock.length |
| 17 | + }, expected min 33.`, |
| 18 | + ); |
| 19 | + const m = (controlBlock.length - 33) / 32; |
| 20 | + let kj = leafHash; |
| 21 | + for (let j = 0; j < m; j++) { |
| 22 | + const ej = controlBlock.slice(33 + 32 * j, 65 + 32 * j); |
| 23 | + if (kj.compare(ej) < 0) { |
| 24 | + kj = tapBranchHash(kj, ej); |
| 25 | + } else { |
| 26 | + kj = tapBranchHash(ej, kj); |
| 27 | + } |
| 28 | + } |
| 29 | + return kj; |
| 30 | +} |
| 31 | +exports.rootHashFromPath = rootHashFromPath; |
| 32 | +/** |
| 33 | + * Build a hash tree of merkle nodes from the scripts binary tree. |
| 34 | + * @param scriptTree - the tree of scripts to pairwise hash. |
| 35 | + */ |
| 36 | +function toHashTree(scriptTree) { |
| 37 | + if ((0, types_1.isTapleaf)(scriptTree)) |
| 38 | + return { hash: tapleafHash(scriptTree) }; |
| 39 | + const hashes = [toHashTree(scriptTree[0]), toHashTree(scriptTree[1])]; |
| 40 | + hashes.sort((a, b) => a.hash.compare(b.hash)); |
| 41 | + const [left, right] = hashes; |
| 42 | + return { |
| 43 | + hash: tapBranchHash(left.hash, right.hash), |
| 44 | + left, |
| 45 | + right, |
| 46 | + }; |
| 47 | +} |
| 48 | +exports.toHashTree = toHashTree; |
| 49 | +/** |
| 50 | + * Given a HashTree, finds the path from a particular hash to the root. |
| 51 | + * @param node - the root of the tree |
| 52 | + * @param hash - the hash to search for |
| 53 | + * @returns - array of sibling hashes, from leaf (inclusive) to root |
| 54 | + * (exclusive) needed to prove inclusion of the specified hash. undefined if no |
| 55 | + * path is found |
| 56 | + */ |
| 57 | +function findScriptPath(node, hash) { |
| 58 | + if (isHashBranch(node)) { |
| 59 | + const leftPath = findScriptPath(node.left, hash); |
| 60 | + if (leftPath !== undefined) return [...leftPath, node.right.hash]; |
| 61 | + const rightPath = findScriptPath(node.right, hash); |
| 62 | + if (rightPath !== undefined) return [...rightPath, node.left.hash]; |
| 63 | + } else if (node.hash.equals(hash)) { |
| 64 | + return []; |
| 65 | + } |
| 66 | + return undefined; |
| 67 | +} |
| 68 | +exports.findScriptPath = findScriptPath; |
| 69 | +function tapleafHash(leaf) { |
| 70 | + const version = leaf.version || exports.LEAF_VERSION_TAPSCRIPT; |
| 71 | + return bcrypto.taggedHash( |
| 72 | + 'TapLeaf', |
| 73 | + buffer_1.Buffer.concat([ |
| 74 | + buffer_1.Buffer.from([version]), |
| 75 | + serializeScript(leaf.output), |
| 76 | + ]), |
| 77 | + ); |
| 78 | +} |
| 79 | +exports.tapleafHash = tapleafHash; |
| 80 | +function tapTweakHash(pubKey, h) { |
| 81 | + return bcrypto.taggedHash( |
| 82 | + 'TapTweak', |
| 83 | + buffer_1.Buffer.concat(h ? [pubKey, h] : [pubKey]), |
| 84 | + ); |
| 85 | +} |
| 86 | +exports.tapTweakHash = tapTweakHash; |
| 87 | +function tweakKey(pubKey, h) { |
| 88 | + if (!buffer_1.Buffer.isBuffer(pubKey)) return null; |
| 89 | + if (pubKey.length !== 32) return null; |
| 90 | + if (h && h.length !== 32) return null; |
| 91 | + const tweakHash = tapTweakHash(pubKey, h); |
| 92 | + const res = (0, ecc_lib_1.getEccLib)().xOnlyPointAddTweak(pubKey, tweakHash); |
| 93 | + if (!res || res.xOnlyPubkey === null) return null; |
| 94 | + return { |
| 95 | + parity: res.parity, |
| 96 | + x: buffer_1.Buffer.from(res.xOnlyPubkey), |
| 97 | + }; |
| 98 | +} |
| 99 | +exports.tweakKey = tweakKey; |
| 100 | +function tapBranchHash(a, b) { |
| 101 | + return bcrypto.taggedHash('TapBranch', buffer_1.Buffer.concat([a, b])); |
| 102 | +} |
| 103 | +function serializeScript(s) { |
| 104 | + const varintLen = bufferutils_1.varuint.encodingLength(s.length); |
| 105 | + const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better |
| 106 | + bufferutils_1.varuint.encode(s.length, buffer); |
| 107 | + return buffer_1.Buffer.concat([buffer, s]); |
| 108 | +} |
0 commit comments