Skip to content

Commit 5b4e88c

Browse files
committed
Simplify HashTree processing, remove footgun
* More clearly show the continuation and base cases in findScriptPath * Return undefined not empty path when no path is found * This would lead to generating an invalid witness * Tighten the type for HashTree to not allow 1-sided branch nodes
1 parent 89785be commit 5b4e88c

File tree

5 files changed

+54
-35
lines changed

5 files changed

+54
-35
lines changed

src/payments/p2tr.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ function p2tr(a, opts) {
152152
version: o.redeemVersion,
153153
});
154154
const path = (0, taprootutils_1.findScriptPath)(hashTree, leafHash);
155+
if (!path) return;
155156
const outputKey = tweakKey(a.internalPubkey, hashTree.hash, _ecc());
156157
if (!outputKey) return;
157158
const controlBock = buffer_1.Buffer.concat(

src/payments/taprootutils.d.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22
import { Tapleaf, Taptree } from '../types';
33
export declare const LEAF_VERSION_TAPSCRIPT = 192;
44
export declare function rootHashFromPath(controlBlock: Buffer, tapleafMsg: Buffer): Buffer;
5-
export interface HashTree {
5+
interface HashLeaf {
66
hash: Buffer;
7-
left?: HashTree;
8-
right?: HashTree;
97
}
8+
interface HashBranch {
9+
hash: Buffer;
10+
left: HashTree;
11+
right: HashTree;
12+
}
13+
export declare type HashTree = HashLeaf | HashBranch;
1014
/**
1115
* Build the hash tree from the scripts binary tree.
1216
* The binary tree can be balanced or not.
@@ -20,8 +24,9 @@ export declare function toHashTree(scriptTree: Taptree): HashTree;
2024
* Given a MAST tree, it finds the path of a particular hash.
2125
* @param node - the root of the tree
2226
* @param hash - the hash to search for
23-
* @returns - and array of hashes representing the path, or an empty array if no pat is found
27+
* @returns - and array of hashes representing the path, undefined if no path is found
2428
*/
25-
export declare function findScriptPath(node: HashTree, hash: Buffer): Buffer[];
29+
export declare function findScriptPath(node: HashTree, hash: Buffer): Buffer[] | undefined;
2630
export declare function tapleafHash(leaf: Tapleaf): Buffer;
2731
export declare function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer;
32+
export {};

src/payments/taprootutils.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ function rootHashFromPath(controlBlock, tapleafMsg) {
2121
return k[m];
2222
}
2323
exports.rootHashFromPath = rootHashFromPath;
24+
const isHashBranch = ht => 'left' in ht && 'right' in ht;
2425
/**
2526
* Build the hash tree from the scripts binary tree.
2627
* The binary tree can be balanced or not.
@@ -46,22 +47,21 @@ exports.toHashTree = toHashTree;
4647
* Given a MAST tree, it finds the path of a particular hash.
4748
* @param node - the root of the tree
4849
* @param hash - the hash to search for
49-
* @returns - and array of hashes representing the path, or an empty array if no pat is found
50+
* @returns - and array of hashes representing the path, undefined if no path is found
5051
*/
5152
function findScriptPath(node, hash) {
52-
if (node.left) {
53-
if (node.left.hash.equals(hash)) return node.right ? [node.right.hash] : [];
54-
const leftPath = findScriptPath(node.left, hash);
55-
if (leftPath.length)
56-
return node.right ? [node.right.hash].concat(leftPath) : leftPath;
57-
}
58-
if (node.right) {
59-
if (node.right.hash.equals(hash)) return node.left ? [node.left.hash] : [];
60-
const rightPath = findScriptPath(node.right, hash);
61-
if (rightPath.length)
62-
return node.left ? [node.left.hash].concat(rightPath) : rightPath;
53+
if (!isHashBranch(node)) {
54+
if (node.hash.equals(hash)) {
55+
return [];
56+
} else {
57+
return undefined;
58+
}
6359
}
64-
return [];
60+
const leftPath = findScriptPath(node.left, hash);
61+
if (leftPath !== undefined) return [node.right.hash, ...leftPath];
62+
const rightPath = findScriptPath(node.right, hash);
63+
if (rightPath !== undefined) return [node.left.hash, ...rightPath];
64+
return undefined;
6565
}
6666
exports.findScriptPath = findScriptPath;
6767
function tapleafHash(leaf) {

ts_src/payments/p2tr.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment {
169169
version: o.redeemVersion,
170170
});
171171
const path = findScriptPath(hashTree, leafHash);
172+
if (!path) return;
172173
const outputKey = tweakKey(a.internalPubkey, hashTree.hash, _ecc());
173174
if (!outputKey) return;
174175
const controlBock = NBuffer.concat(

ts_src/payments/taprootutils.ts

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,21 @@ export function rootHashFromPath(
2727
return k[m];
2828
}
2929

30-
export interface HashTree {
30+
interface HashLeaf {
3131
hash: Buffer;
32-
left?: HashTree;
33-
right?: HashTree;
3432
}
3533

34+
interface HashBranch {
35+
hash: Buffer;
36+
left: HashTree;
37+
right: HashTree;
38+
}
39+
40+
const isHashBranch = (ht: HashTree): ht is HashBranch =>
41+
'left' in ht && 'right' in ht;
42+
43+
export type HashTree = HashLeaf | HashBranch;
44+
3645
/**
3746
* Build the hash tree from the scripts binary tree.
3847
* The binary tree can be balanced or not.
@@ -59,24 +68,27 @@ export function toHashTree(scriptTree: Taptree): HashTree {
5968
* Given a MAST tree, it finds the path of a particular hash.
6069
* @param node - the root of the tree
6170
* @param hash - the hash to search for
62-
* @returns - and array of hashes representing the path, or an empty array if no pat is found
71+
* @returns - and array of hashes representing the path, undefined if no path is found
6372
*/
64-
export function findScriptPath(node: HashTree, hash: Buffer): Buffer[] {
65-
if (node.left) {
66-
if (node.left.hash.equals(hash)) return node.right ? [node.right.hash] : [];
67-
const leftPath = findScriptPath(node.left, hash);
68-
if (leftPath.length)
69-
return node.right ? [node.right.hash].concat(leftPath) : leftPath;
73+
export function findScriptPath(
74+
node: HashTree,
75+
hash: Buffer,
76+
): Buffer[] | undefined {
77+
if (!isHashBranch(node)) {
78+
if (node.hash.equals(hash)) {
79+
return [];
80+
} else {
81+
return undefined;
82+
}
7083
}
7184

72-
if (node.right) {
73-
if (node.right.hash.equals(hash)) return node.left ? [node.left.hash] : [];
74-
const rightPath = findScriptPath(node.right, hash);
75-
if (rightPath.length)
76-
return node.left ? [node.left.hash].concat(rightPath) : rightPath;
77-
}
85+
const leftPath = findScriptPath(node.left, hash);
86+
if (leftPath !== undefined) return [node.right.hash, ...leftPath];
87+
88+
const rightPath = findScriptPath(node.right, hash);
89+
if (rightPath !== undefined) return [node.left.hash, ...rightPath];
7890

79-
return [];
91+
return undefined;
8092
}
8193

8294
export function tapleafHash(leaf: Tapleaf): Buffer {

0 commit comments

Comments
 (0)