@@ -9,11 +9,19 @@ const lazy = require('./lazy');
9
9
const bech32_1 = require ( 'bech32' ) ;
10
10
const OPS = bscript . OPS ;
11
11
const TAPROOT_VERSION = 0x01 ;
12
+ const ANNEX_PREFIX = 0x50 ;
12
13
// witness: {signature}
13
14
// input: <>
14
15
// output: OP_1 {pubKey}
15
16
function p2tr ( a , opts ) {
16
- if ( ! a . address && ! a . output && ! a . pubkey && ! a . output && ! a . internalPubkey )
17
+ if (
18
+ ! a . address &&
19
+ ! a . output &&
20
+ ! a . pubkey &&
21
+ ! a . output &&
22
+ ! a . internalPubkey &&
23
+ ! ( a . witness && a . witness . length > 1 )
24
+ )
17
25
throw new TypeError ( 'Not enough data' ) ;
18
26
opts = Object . assign ( { validate : true } , opts || { } ) ;
19
27
( 0 , types_1 . typeforce ) (
@@ -43,7 +51,17 @@ function p2tr(a, opts) {
43
51
data : Buffer . from ( data ) ,
44
52
} ;
45
53
} ) ;
46
- // todo: clean-up withness (annex), etc
54
+ const _witness = lazy . value ( ( ) => {
55
+ if ( ! a . witness || ! a . witness . length ) return ;
56
+ if (
57
+ a . witness . length >= 2 &&
58
+ a . witness [ a . witness . length - 1 ] [ 0 ] === ANNEX_PREFIX
59
+ ) {
60
+ // remove annex, ignored by taproot
61
+ return a . witness . slice ( 0 , - 1 ) ;
62
+ }
63
+ return a . witness . slice ( ) ;
64
+ } ) ;
47
65
const network = a . network || networks_1 . bitcoin ;
48
66
const o = { name : 'p2tr' , network } ;
49
67
lazy . prop ( o , 'address' , ( ) => {
@@ -55,6 +73,7 @@ function p2tr(a, opts) {
55
73
lazy . prop ( o , 'hash' , ( ) => {
56
74
if ( a . hash ) return a . hash ;
57
75
if ( a . scriptsTree ) return ( 0 , merkle_1 . computeMastRoot ) ( a . scriptsTree ) ;
76
+ // todo: compute from witness
58
77
return null ;
59
78
} ) ;
60
79
lazy . prop ( o , 'output' , ( ) => {
@@ -65,11 +84,17 @@ function p2tr(a, opts) {
65
84
if ( a . pubkey ) return a . pubkey ;
66
85
if ( a . output ) return a . output . slice ( 2 ) ;
67
86
if ( a . address ) return _address ( ) . data ;
68
- if ( a . internalPubkey ) {
69
- const tweakedKey = ( 0 , types_1 . tweakPublicKey ) ( a . internalPubkey , o . hash ) ;
87
+ if ( o . internalPubkey ) {
88
+ const tweakedKey = ( 0 , types_1 . tweakPublicKey ) ( o . internalPubkey , o . hash ) ;
70
89
if ( tweakedKey ) return tweakedKey . x ;
71
90
}
72
91
} ) ;
92
+ lazy . prop ( o , 'internalPubkey' , ( ) => {
93
+ if ( a . internalPubkey ) return a . internalPubkey ;
94
+ const witness = _witness ( ) ;
95
+ if ( witness && witness . length > 1 )
96
+ return witness [ witness . length - 1 ] . slice ( 1 , 33 ) ;
97
+ } ) ;
73
98
lazy . prop ( o , 'signature' , ( ) => {
74
99
if ( a . witness ?. length !== 1 ) return ;
75
100
return a . witness [ 0 ] ;
@@ -78,6 +103,7 @@ function p2tr(a, opts) {
78
103
// todo: not sure
79
104
} ) ;
80
105
lazy . prop ( o , 'witness' , ( ) => {
106
+ if ( a . witness ) return a . witness ;
81
107
if ( ! a . signature ) return ;
82
108
return [ a . signature ] ;
83
109
} ) ;
@@ -109,7 +135,6 @@ function p2tr(a, opts) {
109
135
throw new TypeError ( 'Pubkey mismatch' ) ;
110
136
else pubkey = a . output . slice ( 2 ) ;
111
137
}
112
- // todo: optimze o.hash?
113
138
if ( a . internalPubkey ) {
114
139
const tweakedKey = ( 0 , types_1 . tweakPublicKey ) ( a . internalPubkey , o . hash ) ;
115
140
if ( tweakedKey === null )
@@ -126,13 +151,59 @@ function p2tr(a, opts) {
126
151
const hash = ( 0 , merkle_1 . computeMastRoot ) ( a . scriptsTree ) ;
127
152
if ( ! a . hash . equals ( hash ) ) throw new TypeError ( 'Hash mismatch' ) ;
128
153
}
129
- if ( a . witness ) {
130
- if ( a . witness . length !== 1 ) throw new TypeError ( 'Witness is invalid' ) ;
131
- // todo: recheck
132
- // if (!bscript.isCanonicalScriptSignature(a.witness[0]))
133
- // throw new TypeError('Witness has invalid signature');
134
- if ( a . signature && ! a . signature . equals ( a . witness [ 0 ] ) )
135
- throw new TypeError ( 'Signature mismatch' ) ;
154
+ // todo: review cache
155
+ const witness = _witness ( ) ;
156
+ if ( witness && witness . length ) {
157
+ if ( witness . length === 1 ) {
158
+ // key spending
159
+ if ( a . signature && ! a . signature . equals ( witness [ 0 ] ) )
160
+ throw new TypeError ( 'Signature mismatch' ) ;
161
+ // todo: recheck
162
+ // if (!bscript.isSchnorSignature(a.pubkey, a.witness[0]))
163
+ // throw new TypeError('Witness has invalid signature');
164
+ } else {
165
+ // script path spending
166
+ const controlBlock = witness [ witness . length - 1 ] ;
167
+ if ( controlBlock . length < 33 )
168
+ throw new TypeError (
169
+ `The control-block length is too small. Got ${
170
+ controlBlock . length
171
+ } , expected min 33.`,
172
+ ) ;
173
+ if ( ( controlBlock . length - 33 ) % 32 !== 0 )
174
+ throw new TypeError (
175
+ `The control-block length of ${ controlBlock . length } is incorrect!` ,
176
+ ) ;
177
+ const m = ( controlBlock . length - 33 ) / 32 ;
178
+ if ( m > 128 )
179
+ throw new TypeError (
180
+ `The script path is too long. Got ${ m } , expected max 128.` ,
181
+ ) ;
182
+ const internalPubkey = controlBlock . slice ( 1 , 33 ) ;
183
+ if ( a . internalPubkey && ! a . internalPubkey . equals ( internalPubkey ) )
184
+ throw new TypeError ( 'Internal pubkey mismatch' ) ;
185
+ const internalPubkeyPoint = ( 0 , types_1 . liftX ) ( internalPubkey ) ;
186
+ if ( ! internalPubkeyPoint )
187
+ throw new TypeError ( 'Invalid internalPubkey for p2tr witness' ) ;
188
+ const leafVersion = controlBlock [ 0 ] & 0b11111110 ;
189
+ const script = witness [ witness . length - 2 ] ;
190
+ const tweak = ( 0 , types_1 . computeTweakFromScriptPath ) (
191
+ controlBlock ,
192
+ script ,
193
+ internalPubkey ,
194
+ m ,
195
+ leafVersion ,
196
+ ) ;
197
+ const outputKey = ( 0 , types_1 . tweakPublicKey ) ( internalPubkey , tweak ) ;
198
+ if ( ! outputKey )
199
+ // todo: needs test data
200
+ throw new TypeError ( 'Invalid outputKey for p2tr witness' ) ;
201
+ if ( pubkey . length && ! pubkey . equals ( outputKey . x ) )
202
+ throw new TypeError ( 'Pubkey mismatch for p2tr witness' ) ;
203
+ const controlBlockOddParity = ( controlBlock [ 0 ] & 1 ) === 1 ;
204
+ if ( outputKey . isOdd !== controlBlockOddParity )
205
+ throw new Error ( 'Incorrect parity' ) ;
206
+ }
136
207
}
137
208
}
138
209
return Object . assign ( o , a ) ;
0 commit comments