Skip to content

Commit 87f103f

Browse files
committed
Add createTapTreeUsingHuffmanConstructor
1 parent 508dc6a commit 87f103f

File tree

3 files changed

+80
-2
lines changed

3 files changed

+80
-2
lines changed

src/psbt/bip371.d.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/// <reference types="node" />
2-
import { Taptree } from '../types';
2+
import { Taptree, HuffmanTapTreeNode } from '../types';
33
import { PsbtInput, PsbtOutput, TapLeaf } from 'bip174/src/lib/interfaces';
44
export declare const toXOnly: (pubKey: Buffer) => Buffer;
55
/**
@@ -38,4 +38,10 @@ export declare function tapTreeToList(tree: Taptree): TapLeaf[];
3838
* @returns the corresponding taptree, or throws an exception if the tree cannot be reconstructed
3939
*/
4040
export declare function tapTreeFromList(leaves?: TapLeaf[]): Taptree;
41+
/**
42+
* Construct a Taptree where the leaves with the highest likelihood of use are closer to the root.
43+
* @param nodes A list of nodes where each element contains a weight (likelihood of use) and
44+
* a node which could be a Tapleaf or a branch in a Taptree
45+
*/
46+
export declare function createTapTreeUsingHuffmanConstructor(nodes: HuffmanTapTreeNode[]): Taptree;
4147
export declare function checkTaprootInputForSigs(input: PsbtInput, action: string): boolean;

src/psbt/bip371.js

+31
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22
Object.defineProperty(exports, '__esModule', { value: true });
33
exports.checkTaprootInputForSigs =
4+
exports.createTapTreeUsingHuffmanConstructor =
45
exports.tapTreeFromList =
56
exports.tapTreeToList =
67
exports.tweakInternalPubKey =
@@ -18,6 +19,7 @@ const psbtutils_1 = require('./psbtutils');
1819
const bip341_1 = require('../payments/bip341');
1920
const payments_1 = require('../payments');
2021
const psbtutils_2 = require('./psbtutils');
22+
const sortutils_1 = require('../sortutils');
2123
const toXOnly = pubKey => (pubKey.length === 32 ? pubKey : pubKey.slice(1, 33));
2224
exports.toXOnly = toXOnly;
2325
/**
@@ -155,6 +157,35 @@ function tapTreeFromList(leaves = []) {
155157
return instertLeavesInTree(leaves);
156158
}
157159
exports.tapTreeFromList = tapTreeFromList;
160+
/**
161+
* Construct a Taptree where the leaves with the highest likelihood of use are closer to the root.
162+
* @param nodes A list of nodes where each element contains a weight (likelihood of use) and
163+
* a node which could be a Tapleaf or a branch in a Taptree
164+
*/
165+
function createTapTreeUsingHuffmanConstructor(nodes) {
166+
if (nodes.length === 0)
167+
throw new Error('Cannot create taptree from empty list.');
168+
const compare = (a, b) => a.weight - b.weight;
169+
const sortedNodes = [...nodes].sort(compare); // Sort array in ascending order of weight
170+
let newNode;
171+
let nodeA, nodeB;
172+
while (sortedNodes.length > 1) {
173+
// Construct a new node from the two nodes with the least weight
174+
nodeA = sortedNodes.shift(); // There will always be an element to pop
175+
nodeB = sortedNodes.shift(); // because loop ends when length <= 1
176+
newNode = {
177+
weight: nodeA.weight + nodeB.weight,
178+
node: [nodeA.node, nodeB.node],
179+
};
180+
// Place newNode back into array
181+
(0, sortutils_1.insertIntoSortedArray)(sortedNodes, newNode, compare);
182+
}
183+
// Last node is the root node
184+
const root = sortedNodes.shift();
185+
return root.node;
186+
}
187+
exports.createTapTreeUsingHuffmanConstructor =
188+
createTapTreeUsingHuffmanConstructor;
158189
function checkTaprootInputForSigs(input, action) {
159190
const sigs = extractTaprootSigs(input);
160191
return sigs.some(sig =>

ts_src/psbt/bip371.ts

+42-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { Taptree, Tapleaf, isTapleaf, isTaptree } from '../types';
1+
import {
2+
Taptree,
3+
Tapleaf,
4+
isTapleaf,
5+
isTaptree,
6+
HuffmanTapTreeNode,
7+
} from '../types';
28
import {
39
PsbtInput,
410
PsbtOutput,
@@ -26,6 +32,7 @@ import {
2632
import { p2tr } from '../payments';
2733

2834
import { signatureBlocksAction } from './psbtutils';
35+
import { insertIntoSortedArray } from '../sortutils';
2936

3037
export const toXOnly = (pubKey: Buffer) =>
3138
pubKey.length === 32 ? pubKey : pubKey.slice(1, 33);
@@ -196,6 +203,40 @@ export function tapTreeFromList(leaves: TapLeaf[] = []): Taptree {
196203
return instertLeavesInTree(leaves);
197204
}
198205

206+
/**
207+
* Construct a Taptree where the leaves with the highest likelihood of use are closer to the root.
208+
* @param nodes A list of nodes where each element contains a weight (likelihood of use) and
209+
* a node which could be a Tapleaf or a branch in a Taptree
210+
*/
211+
export function createTapTreeUsingHuffmanConstructor(
212+
nodes: HuffmanTapTreeNode[],
213+
): Taptree {
214+
if (nodes.length === 0)
215+
throw new Error('Cannot create taptree from empty list.');
216+
217+
const compare = (a: HuffmanTapTreeNode, b: HuffmanTapTreeNode) =>
218+
a.weight - b.weight;
219+
const sortedNodes = [...nodes].sort(compare); // Sort array in ascending order of weight
220+
221+
let newNode: HuffmanTapTreeNode;
222+
let nodeA: HuffmanTapTreeNode, nodeB: HuffmanTapTreeNode;
223+
while (sortedNodes.length > 1) {
224+
// Construct a new node from the two nodes with the least weight
225+
nodeA = sortedNodes.shift()!; // There will always be an element to pop
226+
nodeB = sortedNodes.shift()!; // because loop ends when length <= 1
227+
newNode = {
228+
weight: nodeA.weight + nodeB.weight,
229+
node: [nodeA.node, nodeB.node],
230+
};
231+
// Place newNode back into array
232+
insertIntoSortedArray(sortedNodes, newNode, compare);
233+
}
234+
235+
// Last node is the root node
236+
const root = sortedNodes.shift()!;
237+
return root.node;
238+
}
239+
199240
export function checkTaprootInputForSigs(
200241
input: PsbtInput,
201242
action: string,

0 commit comments

Comments
 (0)