Skip to content

Commit 299308a

Browse files
committed
Support p2tr with 1 script and no tree
* Also added caching of `hashTree`, per todo. * Added a test for this functionality
1 parent 5b4e88c commit 299308a

File tree

3 files changed

+43
-12
lines changed

3 files changed

+43
-12
lines changed

src/payments/p2tr.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ function p2tr(a, opts) {
7373
}
7474
return a.witness.slice();
7575
});
76+
const _hashTree = lazy.value(() => {
77+
if (a.scriptTree) return (0, taprootutils_1.toHashTree)(a.scriptTree);
78+
if (a.hash) return { hash: a.hash };
79+
return;
80+
});
7681
const network = a.network || networks_1.bitcoin;
7782
const o = { name: 'p2tr', network };
7883
lazy.prop(o, 'address', () => {
@@ -82,8 +87,8 @@ function p2tr(a, opts) {
8287
return bech32_1.bech32m.encode(network.bech32, words);
8388
});
8489
lazy.prop(o, 'hash', () => {
85-
if (a.hash) return a.hash;
86-
if (a.scriptTree) return (0, taprootutils_1.toHashTree)(a.scriptTree).hash;
90+
const hashTree = _hashTree();
91+
if (hashTree) return hashTree.hash;
8792
const w = _witness();
8893
if (w && w.length > 1) {
8994
const controlBlock = w[w.length - 1];
@@ -144,9 +149,8 @@ function p2tr(a, opts) {
144149
});
145150
lazy.prop(o, 'witness', () => {
146151
if (a.witness) return a.witness;
147-
if (a.scriptTree && a.redeem && a.redeem.output && a.internalPubkey) {
148-
// todo: optimize/cache
149-
const hashTree = (0, taprootutils_1.toHashTree)(a.scriptTree);
152+
const hashTree = _hashTree();
153+
if (hashTree && a.redeem && a.redeem.output && a.internalPubkey) {
150154
const leafHash = (0, taprootutils_1.tapleafHash)({
151155
output: a.redeem.output,
152156
version: o.redeemVersion,
@@ -204,7 +208,7 @@ function p2tr(a, opts) {
204208
throw new TypeError('Invalid pubkey for p2tr');
205209
}
206210
if (a.hash && a.scriptTree) {
207-
const hash = (0, taprootutils_1.toHashTree)(a.scriptTree).hash;
211+
const hash = _hashTree().hash;
208212
if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch');
209213
}
210214
const witness = _witness();

test/fixtures/p2tr.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,28 @@
312312
"witness": null
313313
}
314314
},
315+
{
316+
"description": "address, pubkey, and output from internalPubkey redeem, and hash (one leaf, no tree)",
317+
"arguments": {
318+
"internalPubkey": "aba457d16a8d59151c387f24d1eb887efbe24644c1ee64b261282e7baebdb247",
319+
"redeem": {
320+
"output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4 OP_CHECKSIG"
321+
},
322+
"hash": "b424dea09f840b932a00373cdcdbd25650b8c3acfe54a9f4a641a286721b8d26"
323+
},
324+
"expected": {
325+
"name": "p2tr",
326+
"address": "bc1pnxyp0ahcg53jzgrzj57hnlgdtqtzn7qqhmgjgczk8hzhcltq974qazepzf",
327+
"pubkey": "998817f6f84523212062953d79fd0d581629f800bed12460563dc57c7d602faa",
328+
"output": "OP_1 998817f6f84523212062953d79fd0d581629f800bed12460563dc57c7d602faa",
329+
"signature": null,
330+
"input": null,
331+
"witness": [
332+
"2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4ac",
333+
"c0aba457d16a8d59151c387f24d1eb887efbe24644c1ee64b261282e7baebdb247"
334+
]
335+
}
336+
},
315337
{
316338
"description": "address, pubkey, output and hash from internalPubkey and a script tree with seven leafs (2)",
317339
"arguments": {

ts_src/payments/p2tr.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment {
8888
return a.witness.slice();
8989
});
9090

91+
const _hashTree = lazy.value(() => {
92+
if (a.scriptTree) return toHashTree(a.scriptTree);
93+
if (a.hash) return { hash: a.hash };
94+
return;
95+
});
96+
9197
const network = a.network || BITCOIN_NETWORK;
9298
const o: Payment = { name: 'p2tr', network };
9399

@@ -100,8 +106,8 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment {
100106
});
101107

102108
lazy.prop(o, 'hash', () => {
103-
if (a.hash) return a.hash;
104-
if (a.scriptTree) return toHashTree(a.scriptTree).hash;
109+
const hashTree = _hashTree();
110+
if (hashTree) return hashTree.hash;
105111
const w = _witness();
106112
if (w && w.length > 1) {
107113
const controlBlock = w[w.length - 1];
@@ -161,9 +167,8 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment {
161167

162168
lazy.prop(o, 'witness', () => {
163169
if (a.witness) return a.witness;
164-
if (a.scriptTree && a.redeem && a.redeem.output && a.internalPubkey) {
165-
// todo: optimize/cache
166-
const hashTree = toHashTree(a.scriptTree);
170+
const hashTree = _hashTree();
171+
if (hashTree && a.redeem && a.redeem.output && a.internalPubkey) {
167172
const leafHash = tapleafHash({
168173
output: a.redeem.output,
169174
version: o.redeemVersion,
@@ -227,7 +232,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment {
227232
}
228233

229234
if (a.hash && a.scriptTree) {
230-
const hash = toHashTree(a.scriptTree).hash;
235+
const hash = _hashTree()!.hash;
231236
if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch');
232237
}
233238

0 commit comments

Comments
 (0)