Skip to content

Commit 5f71529

Browse files
committed
feat: initial support to taproot to update psbt
1 parent 6826cde commit 5f71529

File tree

2 files changed

+54
-17
lines changed

2 files changed

+54
-17
lines changed

src/descriptors.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,23 +307,28 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
307307
try {
308308
payment = p2pkh({ output, network });
309309
isSegwit = false;
310+
isTaproot = false;
310311
} catch (e) {}
311312
try {
312313
payment = p2sh({ output, network });
313314
// It assumes that an addr(SH_ADDRESS) is always a add(SH_WPKH) address
314315
isSegwit = true;
316+
isTaproot = false;
315317
} catch (e) {}
316318
try {
317319
payment = p2wpkh({ output, network });
318320
isSegwit = true;
321+
isTaproot = false;
319322
} catch (e) {}
320323
try {
321324
payment = p2wsh({ output, network });
322325
isSegwit = true;
326+
isTaproot = false;
323327
} catch (e) {}
324328
try {
325329
payment = p2tr({ output, network });
326330
isSegwit = true;
331+
isTaproot = true;
327332
} catch (e) {}
328333
if (!payment) {
329334
throw new Error(`Error: invalid address ${matchedAddress}`);
@@ -332,6 +337,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
332337
//pk(KEY)
333338
else if (canonicalExpression.match(RE.rePkAnchored)) {
334339
isSegwit = false;
340+
isTaproot = false;
335341
const keyExpression = canonicalExpression.match(
336342
RE.reNonSegwitKeyExp
337343
)?.[0];
@@ -355,6 +361,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
355361
//pkh(KEY) - legacy
356362
else if (canonicalExpression.match(RE.rePkhAnchored)) {
357363
isSegwit = false;
364+
isTaproot = false;
358365
const keyExpression = canonicalExpression.match(
359366
RE.reNonSegwitKeyExp
360367
)?.[0];
@@ -377,6 +384,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
377384
//sh(wpkh(KEY)) - nested segwit
378385
else if (canonicalExpression.match(RE.reShWpkhAnchored)) {
379386
isSegwit = true;
387+
isTaproot = false;
380388
const keyExpression = canonicalExpression.match(RE.reSegwitKeyExp)?.[0];
381389
if (!keyExpression)
382390
throw new Error(`Error: keyExpression could not me extracted`);
@@ -402,6 +410,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
402410
//wpkh(KEY) - native segwit
403411
else if (canonicalExpression.match(RE.reWpkhAnchored)) {
404412
isSegwit = true;
413+
isTaproot = false;
405414
const keyExpression = canonicalExpression.match(RE.reSegwitKeyExp)?.[0];
406415
if (!keyExpression)
407416
throw new Error(`Error: keyExpression could not me extracted`);
@@ -422,6 +431,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
422431
//sh(wsh(miniscript))
423432
else if (canonicalExpression.match(RE.reShWshMiniscriptAnchored)) {
424433
isSegwit = true;
434+
isTaproot = false;
425435
miniscript = canonicalExpression.match(RE.reShWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(wsh(->HERE<-))
426436
if (!miniscript)
427437
throw new Error(`Error: could not get miniscript in ${descriptor}`);
@@ -462,6 +472,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
462472
//isSegwit false because we know it's a P2SH of a miniscript and not a
463473
//P2SH that embeds a witness payment.
464474
isSegwit = false;
475+
isTaproot = false;
465476
miniscript = canonicalExpression.match(RE.reShMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(->HERE<-)
466477
if (!miniscript)
467478
throw new Error(`Error: could not get miniscript in ${descriptor}`);
@@ -505,6 +516,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
505516
//wsh(miniscript)
506517
else if (canonicalExpression.match(RE.reWshMiniscriptAnchored)) {
507518
isSegwit = true;
519+
isTaproot = false;
508520
miniscript = canonicalExpression.match(RE.reWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found wsh(->HERE<-)
509521
if (!miniscript)
510522
throw new Error(`Error: could not get miniscript in ${descriptor}`);

src/psbt.ts

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
// Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
22
// Distributed under the MIT software license
33

4-
import type { PsbtInput, Bip32Derivation } from 'bip174/src/lib/interfaces';
4+
import type {
5+
PsbtInput,
6+
Bip32Derivation,
7+
TapBip32Derivation
8+
} from 'bip174/src/lib/interfaces';
59
import type { KeyInfo } from './types';
610
import {
711
payments,
@@ -238,22 +242,43 @@ export function updatePsbt({
238242
input.nonWitnessUtxo = Transaction.fromHex(txHex).toBuffer();
239243
}
240244

241-
const bip32Derivation = keysInfo
242-
.filter(
243-
(keyInfo: KeyInfo) =>
244-
keyInfo.pubkey && keyInfo.masterFingerprint && keyInfo.path
245-
)
246-
.map((keyInfo: KeyInfo): Bip32Derivation => {
247-
const pubkey = keyInfo.pubkey;
248-
if (!pubkey)
249-
throw new Error(`key ${keyInfo.keyExpression} missing pubkey`);
250-
return {
251-
masterFingerprint: keyInfo.masterFingerprint!,
252-
pubkey,
253-
path: keyInfo.path!
254-
};
255-
});
256-
if (bip32Derivation.length) input.bip32Derivation = bip32Derivation;
245+
if (isTaproot) {
246+
const tapBip32Derivation = keysInfo
247+
.filter(
248+
(keyInfo: KeyInfo) =>
249+
keyInfo.pubkey && keyInfo.masterFingerprint && keyInfo.path
250+
)
251+
.map((keyInfo: KeyInfo): TapBip32Derivation => {
252+
const pubkey = keyInfo.pubkey;
253+
if (!pubkey)
254+
throw new Error(`key ${keyInfo.keyExpression} missing pubkey`);
255+
return {
256+
masterFingerprint: keyInfo.masterFingerprint!,
257+
pubkey,
258+
path: keyInfo.path!,
259+
leafHashes: [] // Empty array for basic taproot key spend
260+
};
261+
});
262+
if (tapBip32Derivation.length)
263+
input.tapBip32Derivation = tapBip32Derivation;
264+
} else {
265+
const bip32Derivation = keysInfo
266+
.filter(
267+
(keyInfo: KeyInfo) =>
268+
keyInfo.pubkey && keyInfo.masterFingerprint && keyInfo.path
269+
)
270+
.map((keyInfo: KeyInfo): Bip32Derivation => {
271+
const pubkey = keyInfo.pubkey;
272+
if (!pubkey)
273+
throw new Error(`key ${keyInfo.keyExpression} missing pubkey`);
274+
return {
275+
masterFingerprint: keyInfo.masterFingerprint!,
276+
pubkey,
277+
path: keyInfo.path!
278+
};
279+
});
280+
if (bip32Derivation.length) input.bip32Derivation = bip32Derivation;
281+
}
257282
if (isSegwit && txHex !== undefined) {
258283
//There's no need to put both witnessUtxo and nonWitnessUtxo
259284
input.witnessUtxo = { script: scriptPubKey, value };

0 commit comments

Comments
 (0)