-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
Copy pathbip341.js
151 lines (151 loc) · 4.91 KB
/
bip341.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
exports.tweakKey =
exports.tapTweakHash =
exports.tapleafHash =
exports.findScriptPath =
exports.calculateScriptTreeMerkleRoot =
exports.toHashTree =
exports.rootHashFromPath =
exports.MAX_TAPTREE_DEPTH =
exports.LEAF_VERSION_TAPSCRIPT =
void 0;
const buffer_1 = require('buffer');
const ecc_lib_1 = require('../ecc_lib');
const bcrypto = require('../crypto');
const bufferutils_1 = require('../bufferutils');
const types_1 = require('../types');
exports.LEAF_VERSION_TAPSCRIPT = 0xc0;
exports.MAX_TAPTREE_DEPTH = 128;
const isHashBranch = ht => 'left' in ht && 'right' in ht;
/**
* Calculates the root hash from a given control block and leaf hash.
* @param controlBlock - The control block buffer.
* @param leafHash - The leaf hash buffer.
* @returns The root hash buffer.
* @throws {TypeError} If the control block length is less than 33.
*/
function rootHashFromPath(controlBlock, leafHash) {
if (controlBlock.length < 33)
throw new TypeError(
`The control-block length is too small. Got ${controlBlock.length}, expected min 33.`,
);
const m = (controlBlock.length - 33) / 32;
let kj = leafHash;
for (let j = 0; j < m; j++) {
const ej = controlBlock.slice(33 + 32 * j, 65 + 32 * j);
if (kj.compare(ej) < 0) {
kj = tapBranchHash(kj, ej);
} else {
kj = tapBranchHash(ej, kj);
}
}
return kj;
}
exports.rootHashFromPath = rootHashFromPath;
/**
* Build a hash tree of merkle nodes from the scripts binary tree.
* @param scriptTree - the tree of scripts to pairwise hash.
*/
function toHashTree(scriptTree) {
if ((0, types_1.isTapleaf)(scriptTree))
return { hash: tapleafHash(scriptTree) };
const hashes = [toHashTree(scriptTree[0]), toHashTree(scriptTree[1])];
hashes.sort((a, b) => a.hash.compare(b.hash));
const [left, right] = hashes;
return {
hash: tapBranchHash(left.hash, right.hash),
left,
right,
};
}
exports.toHashTree = toHashTree;
/**
* Calculates the Merkle root from an array of Taproot leaf hashes.
*
* @param {Buffer[]} leafHashes - Array of Taproot leaf hashes.
* @returns {Buffer} - The Merkle root.
*/
function calculateScriptTreeMerkleRoot(leafHashes) {
if (!leafHashes || leafHashes.length === 0) {
return undefined;
}
// sort the leaf nodes
leafHashes.sort(Buffer.compare);
// create the initial hash node
let currentLevel = leafHashes;
// build Merkle Tree
while (currentLevel.length > 1) {
const nextLevel = [];
for (let i = 0; i < currentLevel.length; i += 2) {
const left = currentLevel[i];
const right = i + 1 < currentLevel.length ? currentLevel[i + 1] : left;
nextLevel.push(
i + 1 < currentLevel.length ? tapBranchHash(left, right) : left,
);
}
currentLevel = nextLevel;
}
return currentLevel[0];
}
exports.calculateScriptTreeMerkleRoot = calculateScriptTreeMerkleRoot;
/**
* Given a HashTree, finds the path from a particular hash to the root.
* @param node - the root of the tree
* @param hash - the hash to search for
* @returns - array of sibling hashes, from leaf (inclusive) to root
* (exclusive) needed to prove inclusion of the specified hash. undefined if no
* path is found
*/
function findScriptPath(node, hash) {
if (isHashBranch(node)) {
const leftPath = findScriptPath(node.left, hash);
if (leftPath !== undefined) return [...leftPath, node.right.hash];
const rightPath = findScriptPath(node.right, hash);
if (rightPath !== undefined) return [...rightPath, node.left.hash];
} else if (node.hash.equals(hash)) {
return [];
}
return undefined;
}
exports.findScriptPath = findScriptPath;
function tapleafHash(leaf) {
const version = leaf.version || exports.LEAF_VERSION_TAPSCRIPT;
return bcrypto.taggedHash(
'TapLeaf',
buffer_1.Buffer.concat([
buffer_1.Buffer.from([version]),
serializeScript(leaf.output),
]),
);
}
exports.tapleafHash = tapleafHash;
function tapTweakHash(pubKey, h) {
return bcrypto.taggedHash(
'TapTweak',
buffer_1.Buffer.concat(h ? [pubKey, h] : [pubKey]),
);
}
exports.tapTweakHash = tapTweakHash;
function tweakKey(pubKey, h) {
if (!buffer_1.Buffer.isBuffer(pubKey)) return null;
if (pubKey.length !== 32) return null;
if (h && h.length !== 32) return null;
const tweakHash = tapTweakHash(pubKey, h);
const res = (0, ecc_lib_1.getEccLib)().xOnlyPointAddTweak(pubKey, tweakHash);
if (!res || res.xOnlyPubkey === null) return null;
return {
parity: res.parity,
x: buffer_1.Buffer.from(res.xOnlyPubkey),
};
}
exports.tweakKey = tweakKey;
function tapBranchHash(a, b) {
return bcrypto.taggedHash('TapBranch', buffer_1.Buffer.concat([a, b]));
}
function serializeScript(s) {
const varintLen = bufferutils_1.varuint.encodingLength(s.length);
const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better
bufferutils_1.varuint.encode(s.length, buffer);
return buffer_1.Buffer.concat([buffer, s]);
}