1package txscript
2
3import (
4	"bytes"
5	"testing"
6
7	"github.com/btcsuite/btcd/wire"
8)
9
10// TestParsePkScript ensures that the supported script types can be parsed
11// correctly and re-derived into its raw byte representation.
12func TestParsePkScript(t *testing.T) {
13	t.Parallel()
14
15	tests := []struct {
16		name     string
17		pkScript []byte
18		valid    bool
19	}{
20		{
21			name:     "empty output script",
22			pkScript: []byte{},
23			valid:    false,
24		},
25		{
26			name: "valid P2PKH",
27			pkScript: []byte{
28				// OP_DUP
29				0x76,
30				// OP_HASH160
31				0xa9,
32				// OP_DATA_20
33				0x14,
34				// <20-byte pubkey hash>
35				0xf0, 0x7a, 0xb8, 0xce, 0x72, 0xda, 0x4e, 0x76,
36				0x0b, 0x74, 0x7d, 0x48, 0xd6, 0x65, 0xec, 0x96,
37				0xad, 0xf0, 0x24, 0xf5,
38				// OP_EQUALVERIFY
39				0x88,
40				// OP_CHECKSIG
41				0xac,
42			},
43			valid: true,
44		},
45		// Invalid P2PKH - same as above but replaced OP_CHECKSIG with
46		// OP_CHECKSIGVERIFY.
47		{
48			name: "invalid P2PKH",
49			pkScript: []byte{
50				// OP_DUP
51				0x76,
52				// OP_HASH160
53				0xa9,
54				// OP_DATA_20
55				0x14,
56				// <20-byte pubkey hash>
57				0xf0, 0x7a, 0xb8, 0xce, 0x72, 0xda, 0x4e, 0x76,
58				0x0b, 0x74, 0x7d, 0x48, 0xd6, 0x65, 0xec, 0x96,
59				0xad, 0xf0, 0x24, 0xf5,
60				// OP_EQUALVERIFY
61				0x88,
62				// OP_CHECKSIGVERIFY
63				0xad,
64			},
65			valid: false,
66		},
67		{
68			name: "valid P2SH",
69			pkScript: []byte{
70				// OP_HASH160
71				0xA9,
72				// OP_DATA_20
73				0x14,
74				// <20-byte script hash>
75				0xec, 0x6f, 0x7a, 0x5a, 0xa8, 0xf2, 0xb1, 0x0c,
76				0xa5, 0x15, 0x04, 0x52, 0x3a, 0x60, 0xd4, 0x03,
77				0x06, 0xf6, 0x96, 0xcd,
78				// OP_EQUAL
79				0x87,
80			},
81			valid: true,
82		},
83		// Invalid P2SH - same as above but replaced OP_EQUAL with
84		// OP_EQUALVERIFY.
85		{
86			name: "invalid P2SH",
87			pkScript: []byte{
88				// OP_HASH160
89				0xA9,
90				// OP_DATA_20
91				0x14,
92				// <20-byte script hash>
93				0xec, 0x6f, 0x7a, 0x5a, 0xa8, 0xf2, 0xb1, 0x0c,
94				0xa5, 0x15, 0x04, 0x52, 0x3a, 0x60, 0xd4, 0x03,
95				0x06, 0xf6, 0x96, 0xcd,
96				// OP_EQUALVERIFY
97				0x88,
98			},
99			valid: false,
100		},
101		{
102			name: "valid v0 P2WSH",
103			pkScript: []byte{
104				// OP_0
105				0x00,
106				// OP_DATA_32
107				0x20,
108				// <32-byte script hash>
109				0xec, 0x6f, 0x7a, 0x5a, 0xa8, 0xf2, 0xb1, 0x0c,
110				0xa5, 0x15, 0x04, 0x52, 0x3a, 0x60, 0xd4, 0x03,
111				0x06, 0xf6, 0x96, 0xcd, 0x06, 0xf6, 0x96, 0xcd,
112				0x06, 0xf6, 0x96, 0xcd, 0x06, 0xf6, 0x96, 0xcd,
113			},
114			valid: true,
115		},
116		// Invalid v0 P2WSH - same as above but missing one byte.
117		{
118			name: "invalid v0 P2WSH",
119			pkScript: []byte{
120				// OP_0
121				0x00,
122				// OP_DATA_32
123				0x20,
124				// <32-byte script hash>
125				0xec, 0x6f, 0x7a, 0x5a, 0xa8, 0xf2, 0xb1, 0x0c,
126				0xa5, 0x15, 0x04, 0x52, 0x3a, 0x60, 0xd4, 0x03,
127				0x06, 0xf6, 0x96, 0xcd, 0x06, 0xf6, 0x96, 0xcd,
128				0x06, 0xf6, 0x96, 0xcd, 0x06, 0xf6, 0x96,
129			},
130			valid: false,
131		},
132		{
133			name: "valid v0 P2WPKH",
134			pkScript: []byte{
135				// OP_0
136				0x00,
137				// OP_DATA_20
138				0x14,
139				// <20-byte pubkey hash>
140				0xec, 0x6f, 0x7a, 0x5a, 0xa8, 0xf2, 0xb1, 0x0c,
141				0xa5, 0x15, 0x04, 0x52, 0x3a, 0x60, 0xd4, 0x03,
142				0x06, 0xf6, 0x96, 0xcd,
143			},
144			valid: true,
145		},
146		// Invalid v0 P2WPKH - same as above but missing one byte.
147		{
148			name: "invalid v0 P2WPKH",
149			pkScript: []byte{
150				// OP_0
151				0x00,
152				// OP_DATA_20
153				0x14,
154				// <20-byte pubkey hash>
155				0xec, 0x6f, 0x7a, 0x5a, 0xa8, 0xf2, 0xb1, 0x0c,
156				0xa5, 0x15, 0x04, 0x52, 0x3a, 0x60, 0xd4, 0x03,
157				0x06, 0xf6, 0x96,
158			},
159			valid: false,
160		},
161	}
162
163	for _, test := range tests {
164		t.Run(test.name, func(t *testing.T) {
165			pkScript, err := ParsePkScript(test.pkScript)
166			switch {
167			case err != nil && test.valid:
168				t.Fatalf("unable to parse valid pkScript=%x: %v",
169					test.pkScript, err)
170			case err == nil && !test.valid:
171				t.Fatalf("successfully parsed invalid pkScript=%x",
172					test.pkScript)
173			}
174
175			if !test.valid {
176				return
177			}
178
179			if !bytes.Equal(pkScript.Script(), test.pkScript) {
180				t.Fatalf("expected to re-derive pkScript=%x, "+
181					"got pkScript=%x", test.pkScript,
182					pkScript.Script())
183			}
184		})
185	}
186}
187
188// TestComputePkScript ensures that we can correctly re-derive an output's
189// pkScript by looking at the input's signature script/witness attempting to
190// spend it.
191func TestComputePkScript(t *testing.T) {
192	t.Parallel()
193
194	tests := []struct {
195		name      string
196		sigScript []byte
197		witness   wire.TxWitness
198		class     ScriptClass
199		pkScript  []byte
200	}{
201		{
202			name:      "empty sigScript and witness",
203			sigScript: nil,
204			witness:   nil,
205			class:     NonStandardTy,
206			pkScript:  nil,
207		},
208		{
209			name: "P2PKH sigScript",
210			sigScript: []byte{
211				// OP_DATA_73,
212				0x49,
213				// <73-byte sig>
214				0x30, 0x44, 0x02, 0x20, 0x65, 0x92, 0xd8, 0x8e,
215				0x1d, 0x0a, 0x4a, 0x3c, 0xc5, 0x9f, 0x92, 0xae,
216				0xfe, 0x62, 0x54, 0x74, 0xa9, 0x4d, 0x13, 0xa5,
217				0x9f, 0x84, 0x97, 0x78, 0xfc, 0xe7, 0xdf, 0x4b,
218				0xe0, 0xc2, 0x28, 0xd8, 0x02, 0x20, 0x2d, 0xea,
219				0x36, 0x96, 0x19, 0x1f, 0xb7, 0x00, 0xc5, 0xa7,
220				0x7e, 0x22, 0xd9, 0xfb, 0x6b, 0x42, 0x67, 0x42,
221				0xa4, 0x2c, 0xac, 0xdb, 0x74, 0xa2, 0x7c, 0x43,
222				0xcd, 0x89, 0xa0, 0xf9, 0x44, 0x54, 0x12, 0x74,
223				0x01,
224				// OP_DATA_33
225				0x21,
226				// <33-byte compressed pubkey>
227				0x02, 0x7d, 0x56, 0x12, 0x09, 0x75, 0x31, 0xc2,
228				0x17, 0xfd, 0xd4, 0xd2, 0xe1, 0x7a, 0x35, 0x4b,
229				0x17, 0xf2, 0x7a, 0xef, 0x30, 0x9f, 0xb2, 0x7f,
230				0x1f, 0x1f, 0x7b, 0x73, 0x7d, 0x9a, 0x24, 0x49,
231				0x90,
232			},
233			witness: nil,
234			class:   PubKeyHashTy,
235			pkScript: []byte{
236				// OP_DUP
237				0x76,
238				// OP_HASH160
239				0xa9,
240				// OP_DATA_20
241				0x14,
242				// <20-byte pubkey hash>
243				0xf0, 0x7a, 0xb8, 0xce, 0x72, 0xda, 0x4e, 0x76,
244				0x0b, 0x74, 0x7d, 0x48, 0xd6, 0x65, 0xec, 0x96,
245				0xad, 0xf0, 0x24, 0xf5,
246				// OP_EQUALVERIFY
247				0x88,
248				// OP_CHECKSIG
249				0xac,
250			},
251		},
252		{
253			name: "NP2WPKH sigScript",
254			// Since this is a NP2PKH output, the sigScript is a
255			// data push of a serialized v0 P2WPKH script.
256			sigScript: []byte{
257				// OP_DATA_16
258				0x16,
259				// <22-byte redeem script>
260				0x00, 0x14, 0x1d, 0x7c, 0xd6, 0xc7, 0x5c, 0x2e,
261				0x86, 0xf4, 0xcb, 0xf9, 0x8e, 0xae, 0xd2, 0x21,
262				0xb3, 0x0b, 0xd9, 0xa0, 0xb9, 0x28,
263			},
264			// NP2PKH outputs include a witness, but it is not
265			// needed to reconstruct the pkScript.
266			witness: nil,
267			class:   ScriptHashTy,
268			pkScript: []byte{
269				// OP_HASH160
270				0xa9,
271				// OP_DATA_20
272				0x14,
273				// <20-byte script hash>
274				0x90, 0x1c, 0x86, 0x94, 0xc0, 0x3f, 0xaf, 0xd5,
275				0x52, 0x28, 0x10, 0xe0, 0x33, 0x0f, 0x26, 0xe6,
276				0x7a, 0x85, 0x33, 0xcd,
277				// OP_EQUAL
278				0x87,
279			},
280		},
281		{
282			name: "P2SH sigScript",
283			sigScript: []byte{
284				0x00, 0x49, 0x30, 0x46, 0x02, 0x21, 0x00, 0xda,
285				0xe6, 0xb6, 0x14, 0x1b, 0xa7, 0x24, 0x4f, 0x54,
286				0x62, 0xb6, 0x2a, 0x3b, 0x27, 0x59, 0xde, 0xe4,
287				0x46, 0x76, 0x19, 0x4e, 0x6c, 0x56, 0x8d, 0x5b,
288				0x1c, 0xda, 0x96, 0x2d, 0x4f, 0x6d, 0x79, 0x02,
289				0x21, 0x00, 0xa6, 0x6f, 0x60, 0x34, 0x46, 0x09,
290				0x0a, 0x22, 0x3c, 0xec, 0x30, 0x33, 0xd9, 0x86,
291				0x24, 0xd2, 0x73, 0xa8, 0x91, 0x55, 0xa5, 0xe6,
292				0x96, 0x66, 0x0b, 0x6a, 0x50, 0xa3, 0x46, 0x45,
293				0xbb, 0x67, 0x01, 0x48, 0x30, 0x45, 0x02, 0x21,
294				0x00, 0xe2, 0x73, 0x49, 0xdb, 0x93, 0x82, 0xe1,
295				0xf8, 0x8d, 0xae, 0x97, 0x5c, 0x71, 0x19, 0xb7,
296				0x79, 0xb6, 0xda, 0x43, 0xa8, 0x4f, 0x16, 0x05,
297				0x87, 0x11, 0x9f, 0xe8, 0x12, 0x1d, 0x85, 0xae,
298				0xee, 0x02, 0x20, 0x6f, 0x23, 0x2d, 0x0a, 0x7b,
299				0x4b, 0xfa, 0xcd, 0x56, 0xa0, 0x72, 0xcc, 0x2a,
300				0x44, 0x81, 0x31, 0xd1, 0x0d, 0x73, 0x35, 0xf9,
301				0xa7, 0x54, 0x8b, 0xee, 0x1f, 0x70, 0xc5, 0x71,
302				0x0b, 0x37, 0x9e, 0x01, 0x47, 0x52, 0x21, 0x03,
303				0xab, 0x11, 0x5d, 0xa6, 0xdf, 0x4f, 0x54, 0x0b,
304				0xd6, 0xc9, 0xc4, 0xbe, 0x5f, 0xdd, 0xcc, 0x24,
305				0x58, 0x8e, 0x7c, 0x2c, 0xaf, 0x13, 0x82, 0x28,
306				0xdd, 0x0f, 0xce, 0x29, 0xfd, 0x65, 0xb8, 0x7c,
307				0x21, 0x02, 0x15, 0xe8, 0xb7, 0xbf, 0xfe, 0x8d,
308				0x9b, 0xbd, 0x45, 0x81, 0xf9, 0xc3, 0xb6, 0xf1,
309				0x6d, 0x67, 0x08, 0x36, 0xc3, 0x0b, 0xb2, 0xe0,
310				0x3e, 0xfd, 0x9d, 0x41, 0x03, 0xb5, 0x59, 0xeb,
311				0x67, 0xcd, 0x52, 0xae,
312			},
313			witness: nil,
314			class:   ScriptHashTy,
315			pkScript: []byte{
316				// OP_HASH160
317				0xA9,
318				// OP_DATA_20
319				0x14,
320				// <20-byte script hash>
321				0x12, 0xd6, 0x9c, 0xd3, 0x38, 0xa3, 0x8d, 0x0d,
322				0x77, 0x83, 0xcf, 0x22, 0x64, 0x97, 0x63, 0x3d,
323				0x3c, 0x20, 0x79, 0xea,
324				// OP_EQUAL
325				0x87,
326			},
327		},
328		// Invalid P2SH (non push-data only script).
329		{
330			name:      "invalid P2SH sigScript",
331			sigScript: []byte{0x6b, 0x65, 0x6b}, // kek
332			witness:   nil,
333			class:     NonStandardTy,
334			pkScript:  nil,
335		},
336		{
337			name:      "P2WSH witness",
338			sigScript: nil,
339			witness: [][]byte{
340				[]byte{},
341				// Witness script.
342				[]byte{
343					0x21, 0x03, 0x82, 0x62, 0xa6, 0xc6,
344					0xce, 0xc9, 0x3c, 0x2d, 0x3e, 0xcd,
345					0x6c, 0x60, 0x72, 0xef, 0xea, 0x86,
346					0xd0, 0x2f, 0xf8, 0xe3, 0x32, 0x8b,
347					0xbd, 0x02, 0x42, 0xb2, 0x0a, 0xf3,
348					0x42, 0x59, 0x90, 0xac, 0xac,
349				},
350			},
351			class: WitnessV0ScriptHashTy,
352			pkScript: []byte{
353				// OP_0
354				0x00,
355				// OP_DATA_32
356				0x20,
357				// <32-byte script hash>
358				0x01, 0xd5, 0xd9, 0x2e, 0xff, 0xa6, 0xff, 0xba,
359				0x3e, 0xfa, 0x37, 0x9f, 0x98, 0x30, 0xd0, 0xf7,
360				0x56, 0x18, 0xb1, 0x33, 0x93, 0x82, 0x71, 0x52,
361				0xd2, 0x6e, 0x43, 0x09, 0x00, 0x0e, 0x88, 0xb1,
362			},
363		},
364		{
365			name:      "P2WPKH witness",
366			sigScript: nil,
367			witness: [][]byte{
368				// Signature is not needed to re-derive the
369				// pkScript.
370				[]byte{},
371				// Compressed pubkey.
372				[]byte{
373					0x03, 0x82, 0x62, 0xa6, 0xc6, 0xce,
374					0xc9, 0x3c, 0x2d, 0x3e, 0xcd, 0x6c,
375					0x60, 0x72, 0xef, 0xea, 0x86, 0xd0,
376					0x2f, 0xf8, 0xe3, 0x32, 0x8b, 0xbd,
377					0x02, 0x42, 0xb2, 0x0a, 0xf3, 0x42,
378					0x59, 0x90, 0xac,
379				},
380			},
381			class: WitnessV0PubKeyHashTy,
382			pkScript: []byte{
383				// OP_0
384				0x00,
385				// OP_DATA_20
386				0x14,
387				// <20-byte pubkey hash>
388				0x1d, 0x7c, 0xd6, 0xc7, 0x5c, 0x2e, 0x86, 0xf4,
389				0xcb, 0xf9, 0x8e, 0xae, 0xd2, 0x21, 0xb3, 0x0b,
390				0xd9, 0xa0, 0xb9, 0x28,
391			},
392		},
393		// Invalid v0 P2WPKH - same as above but missing a byte on the
394		// public key.
395		{
396			name:      "invalid P2WPKH witness",
397			sigScript: nil,
398			witness: [][]byte{
399				// Signature is not needed to re-derive the
400				// pkScript.
401				[]byte{},
402				// Malformed compressed pubkey.
403				[]byte{
404					0x03, 0x82, 0x62, 0xa6, 0xc6, 0xce,
405					0xc9, 0x3c, 0x2d, 0x3e, 0xcd, 0x6c,
406					0x60, 0x72, 0xef, 0xea, 0x86, 0xd0,
407					0x2f, 0xf8, 0xe3, 0x32, 0x8b, 0xbd,
408					0x02, 0x42, 0xb2, 0x0a, 0xf3, 0x42,
409					0x59, 0x90,
410				},
411			},
412			class:    WitnessV0PubKeyHashTy,
413			pkScript: nil,
414		},
415	}
416
417	for _, test := range tests {
418		t.Run(test.name, func(t *testing.T) {
419			valid := test.pkScript != nil
420			pkScript, err := ComputePkScript(
421				test.sigScript, test.witness,
422			)
423			if err != nil && valid {
424				t.Fatalf("unable to compute pkScript: %v", err)
425			}
426
427			if !valid {
428				return
429			}
430
431			if pkScript.Class() != test.class {
432				t.Fatalf("expected pkScript of type %v, got %v",
433					test.class, pkScript.Class())
434			}
435			if !bytes.Equal(pkScript.Script(), test.pkScript) {
436				t.Fatalf("expected pkScript=%x, got pkScript=%x",
437					test.pkScript, pkScript.Script())
438			}
439		})
440	}
441}
442