@@ -10,8 +10,9 @@ const lazy = require('./lazy');
10
10
const bech32_1 = require ( 'bech32' ) ;
11
11
const testecc_1 = require ( './testecc' ) ;
12
12
const OPS = bscript . OPS ;
13
- const TAPROOT_VERSION = 0x01 ;
13
+ const TAPROOT_WITNESS_VERSION = 0x01 ;
14
14
const ANNEX_PREFIX = 0x50 ;
15
+ const LEAF_VERSION_MASK = 0b11111110 ;
15
16
function p2tr ( a , opts ) {
16
17
if (
17
18
! a . address &&
@@ -40,11 +41,15 @@ function p2tr(a, opts) {
40
41
witness : types_1 . typeforce . maybe (
41
42
types_1 . typeforce . arrayOf ( types_1 . typeforce . Buffer ) ,
42
43
) ,
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 ( {
46
46
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
+ ) ,
47
51
} ) ,
52
+ redeemVersion : types_1 . typeforce . maybe ( types_1 . typeforce . Number ) ,
48
53
} ,
49
54
a ,
50
55
) ;
@@ -58,13 +63,13 @@ function p2tr(a, opts) {
58
63
data : buffer_1 . Buffer . from ( data ) ,
59
64
} ;
60
65
} ) ;
66
+ // remove annex if present, ignored by taproot
61
67
const _witness = lazy . value ( ( ) => {
62
68
if ( ! a . witness || ! a . witness . length ) return ;
63
69
if (
64
70
a . witness . length >= 2 &&
65
71
a . witness [ a . witness . length - 1 ] [ 0 ] === ANNEX_PREFIX
66
72
) {
67
- // remove annex, ignored by taproot
68
73
return a . witness . slice ( 0 , - 1 ) ;
69
74
}
70
75
return a . witness . slice ( ) ;
@@ -74,17 +79,16 @@ function p2tr(a, opts) {
74
79
lazy . prop ( o , 'address' , ( ) => {
75
80
if ( ! o . pubkey ) return ;
76
81
const words = bech32_1 . bech32m . toWords ( o . pubkey ) ;
77
- words . unshift ( TAPROOT_VERSION ) ;
82
+ words . unshift ( TAPROOT_WITNESS_VERSION ) ;
78
83
return bech32_1 . bech32m . encode ( network . bech32 , words ) ;
79
84
} ) ;
80
85
lazy . prop ( o , 'hash' , ( ) => {
81
86
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 ;
84
88
const w = _witness ( ) ;
85
89
if ( w && w . length > 1 ) {
86
90
const controlBlock = w [ w . length - 1 ] ;
87
- const leafVersion = controlBlock [ 0 ] & 0b11111110 ;
91
+ const leafVersion = controlBlock [ 0 ] & LEAF_VERSION_MASK ;
88
92
const script = w [ w . length - 2 ] ;
89
93
const leafHash = ( 0 , taprootutils_1 . tapLeafHash ) ( script , leafVersion ) ;
90
94
return ( 0 , taprootutils_1 . rootHashFromPath ) ( controlBlock , leafHash ) ;
@@ -95,8 +99,25 @@ function p2tr(a, opts) {
95
99
if ( ! o . pubkey ) return ;
96
100
return bscript . compile ( [ OPS . OP_1 , o . pubkey ] ) ;
97
101
} ) ;
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
+ } ;
100
121
} ) ;
101
122
lazy . prop ( o , 'pubkey' , ( ) => {
102
123
if ( a . pubkey ) return a . pubkey ;
@@ -118,29 +139,25 @@ function p2tr(a, opts) {
118
139
if ( ! a . witness || a . witness . length !== 1 ) return ;
119
140
return a . witness [ 0 ] ;
120
141
} ) ;
121
- lazy . prop ( o , 'input' , ( ) => {
122
- // todo
123
- } ) ;
124
142
lazy . prop ( o , 'witness' , ( ) => {
125
143
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 ) {
127
145
// todo: optimize/cache
128
- const hashTree = ( 0 , taprootutils_1 . toHashTree ) ( a . scriptsTree ) ;
146
+ const hashTree = ( 0 , taprootutils_1 . toHashTree ) ( a . scriptTree ) ;
129
147
const leafHash = ( 0 , taprootutils_1 . tapLeafHash ) (
130
- a . scriptLeaf . output ,
131
- a . scriptLeaf . version ,
148
+ a . redeem . output ,
149
+ o . redeemVersion ,
132
150
) ;
133
151
const path = ( 0 , taprootutils_1 . findScriptPath ) ( hashTree , leafHash ) ;
134
152
const outputKey = tweakKey ( a . internalPubkey , hashTree . hash , _ecc ( ) ) ;
135
153
if ( ! outputKey ) return ;
136
- const version = a . scriptLeaf . version || 0xc0 ;
137
154
const controlBock = buffer_1 . Buffer . concat (
138
155
[
139
- buffer_1 . Buffer . from ( [ version | outputKey . parity ] ) ,
156
+ buffer_1 . Buffer . from ( [ o . redeemVersion | outputKey . parity ] ) ,
140
157
a . internalPubkey ,
141
158
] . concat ( path . reverse ( ) ) ,
142
159
) ;
143
- return [ a . scriptLeaf . output , controlBock ] ;
160
+ return [ a . redeem . output , controlBock ] ;
144
161
}
145
162
if ( a . signature ) return [ a . signature ] ;
146
163
} ) ;
@@ -150,7 +167,7 @@ function p2tr(a, opts) {
150
167
if ( a . address ) {
151
168
if ( network && network . bech32 !== _address ( ) . prefix )
152
169
throw new TypeError ( 'Invalid prefix or Network mismatch' ) ;
153
- if ( _address ( ) . version !== TAPROOT_VERSION )
170
+ if ( _address ( ) . version !== TAPROOT_WITNESS_VERSION )
154
171
throw new TypeError ( 'Invalid address version' ) ;
155
172
if ( _address ( ) . data . length !== 32 )
156
173
throw new TypeError ( 'Invalid address data' ) ;
@@ -182,11 +199,32 @@ function p2tr(a, opts) {
182
199
if ( ! _ecc ( ) . isXOnlyPoint ( pubkey ) )
183
200
throw new TypeError ( 'Invalid pubkey for p2tr' ) ;
184
201
}
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 ;
187
204
if ( ! a . hash . equals ( hash ) ) throw new TypeError ( 'Hash mismatch' ) ;
188
205
}
189
206
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
+ }
190
228
if ( witness && witness . length ) {
191
229
if ( witness . length === 1 ) {
192
230
// key spending
@@ -215,7 +253,7 @@ function p2tr(a, opts) {
215
253
throw new TypeError ( 'Internal pubkey mismatch' ) ;
216
254
if ( ! _ecc ( ) . isXOnlyPoint ( internalPubkey ) )
217
255
throw new TypeError ( 'Invalid internalPubkey for p2tr witness' ) ;
218
- const leafVersion = controlBlock [ 0 ] & 0b11111110 ;
256
+ const leafVersion = controlBlock [ 0 ] & LEAF_VERSION_MASK ;
219
257
const script = witness [ witness . length - 2 ] ;
220
258
const leafHash = ( 0 , taprootutils_1 . tapLeafHash ) ( script , leafVersion ) ;
221
259
const hash = ( 0 , taprootutils_1 . rootHashFromPath ) (
@@ -248,3 +286,9 @@ function tweakKey(pubKey, h, eccLib) {
248
286
x : buffer_1 . Buffer . from ( res . xOnlyPubkey ) ,
249
287
} ;
250
288
}
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