@@ -10,8 +10,9 @@ const lazy = require('./lazy');
1010const bech32_1 = require ( 'bech32' ) ;
1111const testecc_1 = require ( './testecc' ) ;
1212const OPS = bscript . OPS ;
13- const TAPROOT_VERSION = 0x01 ;
13+ const TAPROOT_WITNESS_VERSION = 0x01 ;
1414const ANNEX_PREFIX = 0x50 ;
15+ const LEAF_VERSION_MASK = 0b11111110 ;
1516function p2tr ( a , opts ) {
1617 if (
1718 ! a . address &&
@@ -40,11 +41,15 @@ function p2tr(a, opts) {
4041 witness : types_1 . typeforce . maybe (
4142 types_1 . typeforce . arrayOf ( types_1 . typeforce . Buffer ) ,
4243 ) ,
43- // scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ?
44- scriptLeaf : types_1 . typeforce . maybe ( {
45- version : types_1 . typeforce . maybe ( types_1 . typeforce . Number ) ,
44+ scriptTree : types_1 . typeforce . maybe ( taprootutils_1 . isTapTree ) ,
45+ redeem : types_1 . typeforce . maybe ( {
4646 output : types_1 . typeforce . maybe ( types_1 . typeforce . Buffer ) ,
47+ redeemVersion : types_1 . typeforce . maybe ( types_1 . typeforce . Number ) ,
48+ witness : types_1 . typeforce . maybe (
49+ types_1 . typeforce . arrayOf ( types_1 . typeforce . Buffer ) ,
50+ ) ,
4751 } ) ,
52+ redeemVersion : types_1 . typeforce . maybe ( types_1 . typeforce . Number ) ,
4853 } ,
4954 a ,
5055 ) ;
@@ -58,13 +63,13 @@ function p2tr(a, opts) {
5863 data : buffer_1 . Buffer . from ( data ) ,
5964 } ;
6065 } ) ;
66+ // remove annex if present, ignored by taproot
6167 const _witness = lazy . value ( ( ) => {
6268 if ( ! a . witness || ! a . witness . length ) return ;
6369 if (
6470 a . witness . length >= 2 &&
6571 a . witness [ a . witness . length - 1 ] [ 0 ] === ANNEX_PREFIX
6672 ) {
67- // remove annex, ignored by taproot
6873 return a . witness . slice ( 0 , - 1 ) ;
6974 }
7075 return a . witness . slice ( ) ;
@@ -74,17 +79,16 @@ function p2tr(a, opts) {
7479 lazy . prop ( o , 'address' , ( ) => {
7580 if ( ! o . pubkey ) return ;
7681 const words = bech32_1 . bech32m . toWords ( o . pubkey ) ;
77- words . unshift ( TAPROOT_VERSION ) ;
82+ words . unshift ( TAPROOT_WITNESS_VERSION ) ;
7883 return bech32_1 . bech32m . encode ( network . bech32 , words ) ;
7984 } ) ;
8085 lazy . prop ( o , 'hash' , ( ) => {
8186 if ( a . hash ) return a . hash ;
82- if ( a . scriptsTree )
83- return ( 0 , taprootutils_1 . toHashTree ) ( a . scriptsTree ) . hash ;
87+ if ( a . scriptTree ) return ( 0 , taprootutils_1 . toHashTree ) ( a . scriptTree ) . hash ;
8488 const w = _witness ( ) ;
8589 if ( w && w . length > 1 ) {
8690 const controlBlock = w [ w . length - 1 ] ;
87- const leafVersion = controlBlock [ 0 ] & 0b11111110 ;
91+ const leafVersion = controlBlock [ 0 ] & LEAF_VERSION_MASK ;
8892 const script = w [ w . length - 2 ] ;
8993 const leafHash = ( 0 , taprootutils_1 . tapLeafHash ) ( script , leafVersion ) ;
9094 return ( 0 , taprootutils_1 . rootHashFromPath ) ( controlBlock , leafHash ) ;
@@ -95,8 +99,25 @@ function p2tr(a, opts) {
9599 if ( ! o . pubkey ) return ;
96100 return bscript . compile ( [ OPS . OP_1 , o . pubkey ] ) ;
97101 } ) ;
98- lazy . prop ( o , 'scriptLeaf' , ( ) => {
99- if ( a . scriptLeaf ) return a . scriptLeaf ;
102+ lazy . prop ( o , 'redeemVersion' , ( ) => {
103+ if ( a . redeemVersion ) return a . redeemVersion ;
104+ if (
105+ a . redeem &&
106+ a . redeem . redeemVersion !== undefined &&
107+ a . redeem . redeemVersion !== null
108+ ) {
109+ return a . redeem . redeemVersion ;
110+ }
111+ return taprootutils_1 . LEAF_VERSION_TAPSCRIPT ;
112+ } ) ;
113+ lazy . prop ( o , 'redeem' , ( ) => {
114+ const witness = _witness ( ) ; // witness without annex
115+ if ( ! witness || witness . length < 2 ) return ;
116+ return {
117+ output : witness [ witness . length - 2 ] ,
118+ witness : witness . slice ( 0 , - 2 ) ,
119+ redeemVersion : witness [ witness . length - 1 ] [ 0 ] & LEAF_VERSION_MASK ,
120+ } ;
100121 } ) ;
101122 lazy . prop ( o , 'pubkey' , ( ) => {
102123 if ( a . pubkey ) return a . pubkey ;
@@ -118,29 +139,25 @@ function p2tr(a, opts) {
118139 if ( ! a . witness || a . witness . length !== 1 ) return ;
119140 return a . witness [ 0 ] ;
120141 } ) ;
121- lazy . prop ( o , 'input' , ( ) => {
122- // todo
123- } ) ;
124142 lazy . prop ( o , 'witness' , ( ) => {
125143 if ( a . witness ) return a . witness ;
126- if ( a . scriptsTree && a . scriptLeaf && a . internalPubkey ) {
144+ if ( a . scriptTree && a . redeem && a . redeem . output && a . internalPubkey ) {
127145 // todo: optimize/cache
128- const hashTree = ( 0 , taprootutils_1 . toHashTree ) ( a . scriptsTree ) ;
146+ const hashTree = ( 0 , taprootutils_1 . toHashTree ) ( a . scriptTree ) ;
129147 const leafHash = ( 0 , taprootutils_1 . tapLeafHash ) (
130- a . scriptLeaf . output ,
131- a . scriptLeaf . version ,
148+ a . redeem . output ,
149+ o . redeemVersion ,
132150 ) ;
133151 const path = ( 0 , taprootutils_1 . findScriptPath ) ( hashTree , leafHash ) ;
134152 const outputKey = tweakKey ( a . internalPubkey , hashTree . hash , _ecc ( ) ) ;
135153 if ( ! outputKey ) return ;
136- const version = a . scriptLeaf . version || 0xc0 ;
137154 const controlBock = buffer_1 . Buffer . concat (
138155 [
139- buffer_1 . Buffer . from ( [ version | outputKey . parity ] ) ,
156+ buffer_1 . Buffer . from ( [ o . redeemVersion | outputKey . parity ] ) ,
140157 a . internalPubkey ,
141158 ] . concat ( path . reverse ( ) ) ,
142159 ) ;
143- return [ a . scriptLeaf . output , controlBock ] ;
160+ return [ a . redeem . output , controlBock ] ;
144161 }
145162 if ( a . signature ) return [ a . signature ] ;
146163 } ) ;
@@ -150,7 +167,7 @@ function p2tr(a, opts) {
150167 if ( a . address ) {
151168 if ( network && network . bech32 !== _address ( ) . prefix )
152169 throw new TypeError ( 'Invalid prefix or Network mismatch' ) ;
153- if ( _address ( ) . version !== TAPROOT_VERSION )
170+ if ( _address ( ) . version !== TAPROOT_WITNESS_VERSION )
154171 throw new TypeError ( 'Invalid address version' ) ;
155172 if ( _address ( ) . data . length !== 32 )
156173 throw new TypeError ( 'Invalid address data' ) ;
@@ -182,11 +199,32 @@ function p2tr(a, opts) {
182199 if ( ! _ecc ( ) . isXOnlyPoint ( pubkey ) )
183200 throw new TypeError ( 'Invalid pubkey for p2tr' ) ;
184201 }
185- if ( a . hash && a . scriptsTree ) {
186- const hash = ( 0 , taprootutils_1 . toHashTree ) ( a . scriptsTree ) . hash ;
202+ if ( a . hash && a . scriptTree ) {
203+ const hash = ( 0 , taprootutils_1 . toHashTree ) ( a . scriptTree ) . hash ;
187204 if ( ! a . hash . equals ( hash ) ) throw new TypeError ( 'Hash mismatch' ) ;
188205 }
189206 const witness = _witness ( ) ;
207+ // compare the provided redeem data with the one computed from witness
208+ if ( a . redeem && o . redeem ) {
209+ if ( a . redeem . redeemVersion ) {
210+ if ( a . redeem . redeemVersion !== o . redeem . redeemVersion )
211+ throw new TypeError ( 'Redeem.redeemVersion and witness mismatch' ) ;
212+ }
213+ if ( a . redeem . output ) {
214+ if ( bscript . decompile ( a . redeem . output ) . length === 0 )
215+ throw new TypeError ( 'Redeem.output is invalid' ) ;
216+ // output redeem is constructed from the witness
217+ if ( o . redeem . output && ! a . redeem . output . equals ( o . redeem . output ) )
218+ throw new TypeError ( 'Redeem.output and witness mismatch' ) ;
219+ }
220+ if ( a . redeem . witness ) {
221+ if (
222+ o . redeem . witness &&
223+ ! stacksEqual ( a . redeem . witness , o . redeem . witness )
224+ )
225+ throw new TypeError ( 'Redeem.witness and witness mismatch' ) ;
226+ }
227+ }
190228 if ( witness && witness . length ) {
191229 if ( witness . length === 1 ) {
192230 // key spending
@@ -215,7 +253,7 @@ function p2tr(a, opts) {
215253 throw new TypeError ( 'Internal pubkey mismatch' ) ;
216254 if ( ! _ecc ( ) . isXOnlyPoint ( internalPubkey ) )
217255 throw new TypeError ( 'Invalid internalPubkey for p2tr witness' ) ;
218- const leafVersion = controlBlock [ 0 ] & 0b11111110 ;
256+ const leafVersion = controlBlock [ 0 ] & LEAF_VERSION_MASK ;
219257 const script = witness [ witness . length - 2 ] ;
220258 const leafHash = ( 0 , taprootutils_1 . tapLeafHash ) ( script , leafVersion ) ;
221259 const hash = ( 0 , taprootutils_1 . rootHashFromPath ) (
@@ -248,3 +286,9 @@ function tweakKey(pubKey, h, eccLib) {
248286 x : buffer_1 . Buffer . from ( res . xOnlyPubkey ) ,
249287 } ;
250288}
289+ function stacksEqual ( a , b ) {
290+ if ( a . length !== b . length ) return false ;
291+ return a . every ( ( x , i ) => {
292+ return x . equals ( b [ i ] ) ;
293+ } ) ;
294+ }
0 commit comments