@@ -2,10 +2,13 @@ import BIP32Factory from 'bip32';
2
2
import ECPairFactory from 'ecpair' ;
3
3
import * as ecc from 'tiny-secp256k1' ;
4
4
import { describe , it } from 'mocha' ;
5
+ import { PsbtInput , TapLeafScript } from 'bip174/src/lib/interfaces' ;
5
6
import { regtestUtils } from './_regtest' ;
6
7
import * as bitcoin from '../..' ;
7
8
import { Taptree } from '../../src/types' ;
8
- import { toXOnly , tapTreeToList } from '../../src/psbt/bip371' ;
9
+ import { toXOnly , tapTreeToList , tapTreeFromList } from '../../src/psbt/bip371' ;
10
+ import { witnessStackToScriptWitness } from '../../src/psbt/psbtutils' ;
11
+ import { TapLeaf } from 'bip174/src/lib/interfaces' ;
9
12
10
13
const rng = require ( 'randombytes' ) ;
11
14
const regtest = regtestUtils . network ;
@@ -485,8 +488,112 @@ describe('bitcoinjs-lib (transaction with taproot)', () => {
485
488
value : sendAmount ,
486
489
} ) ;
487
490
} ) ;
491
+
492
+ it ( 'can create (and broadcast via 3PBP) a taproot script-path spend Transaction - custom finalizer' , async ( ) => {
493
+
494
+
495
+ const leafCount = 8 ;
496
+ const leaves = Array . from ( { length : leafCount } ) . map (
497
+ ( _ , index ) =>
498
+ ( {
499
+ depth : 3 ,
500
+ leafVersion : 192 ,
501
+ script : bitcoin . script . fromASM ( `OP_ADD OP_${ index * 2 } OP_EQUAL` ) ,
502
+ } as TapLeaf ) ,
503
+ ) ;
504
+ const scriptTree = tapTreeFromList ( leaves ) ;
505
+
506
+ for ( let leafIndex = 1 ; leafIndex < leafCount ; leafIndex ++ ) {
507
+ const redeem = {
508
+ output : bitcoin . script . fromASM ( `OP_ADD OP_${ leafIndex * 2 } OP_EQUAL` ) ,
509
+ redeemVersion : 192 ,
510
+ } ;
511
+
512
+ const internalKey = bip32 . fromSeed ( rng ( 64 ) , regtest ) ;
513
+ const { output, witness } = bitcoin . payments . p2tr ( {
514
+ internalPubkey : toXOnly ( internalKey . publicKey ) ,
515
+ scriptTree,
516
+ redeem,
517
+ network : regtest ,
518
+ } ) ;
519
+
520
+
521
+ // amount from faucet
522
+ const amount = 42e4 ;
523
+ // amount to send
524
+ const sendAmount = amount - 1e4 ;
525
+ // get faucet
526
+ const unspent = await regtestUtils . faucetComplex ( output ! , amount ) ;
527
+
528
+ const psbt = new bitcoin . Psbt ( { network : regtest } ) ;
529
+ psbt . addInput ( {
530
+ hash : unspent . txId ,
531
+ index : 0 ,
532
+ witnessUtxo : { value : amount , script : output ! } ,
533
+ } ) ;
534
+
535
+ const tapLeafScript : TapLeafScript = {
536
+ leafVersion : redeem . redeemVersion ,
537
+ script : redeem . output ,
538
+ controlBlock : witness ! [ witness ! . length - 1 ] ,
539
+ } ;
540
+ psbt . updateInput ( 0 , { tapLeafScript : [ tapLeafScript ] } ) ;
541
+
542
+ const sendAddress =
543
+ 'bcrt1pqknex3jwpsaatu5e5dcjw70nac3fr5k5y3hcxr4hgg6rljzp59nqs6a0vh' ;
544
+ psbt . addOutput ( {
545
+ value : sendAmount ,
546
+ address : sendAddress ,
547
+ } ) ;
548
+
549
+ const leafIndexFinalizerFn = buildLeafIndexFinalizer (
550
+ tapLeafScript ,
551
+ leafIndex ,
552
+ ) ;
553
+ psbt . finalizeInput ( 0 , leafIndexFinalizerFn ) ;
554
+ console . log ( '### psbt finalized' , psbt . toBase64 ( ) ) ;
555
+ const tx = psbt . extractTransaction ( ) ;
556
+ const rawTx = tx . toBuffer ( ) ;
557
+ const hex = rawTx . toString ( 'hex' ) ;
558
+
559
+ console . log ( '### hex' , hex )
560
+ await regtestUtils . broadcast ( hex ) ;
561
+ console . log ( '### verify' )
562
+ await regtestUtils . verify ( {
563
+ txId : tx . getId ( ) ,
564
+ address : sendAddress ! ,
565
+ vout : 0 ,
566
+ value : sendAmount ,
567
+ } ) ;
568
+ }
569
+ } ) ;
488
570
} ) ;
489
571
572
+ function buildLeafIndexFinalizer (
573
+ tapLeafScript : TapLeafScript ,
574
+ leafIndex : number ,
575
+ ) {
576
+ return (
577
+ inputIndex : number ,
578
+ _input : PsbtInput ,
579
+ _tapLeafHashToFinalize ?: Buffer ,
580
+ ) : {
581
+ finalScriptWitness : Buffer | undefined ;
582
+ } => {
583
+ try {
584
+ const scriptSolution = [
585
+ bitcoin . script . fromASM ( `OP_${ leafIndex } OP_${ leafIndex } ` ) ,
586
+ ] ;
587
+ const witness = scriptSolution
588
+ . concat ( tapLeafScript . script )
589
+ . concat ( tapLeafScript . controlBlock ) ;
590
+ return { finalScriptWitness : witnessStackToScriptWitness ( witness ) } ;
591
+ } catch ( err ) {
592
+ throw new Error ( `Can not finalize taproot input #${ inputIndex } : ${ err } ` ) ;
593
+ }
594
+ } ;
595
+ }
596
+
490
597
// Order of the curve (N) - 1
491
598
const N_LESS_1 = Buffer . from (
492
599
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140' ,
0 commit comments