htlc_test.go raw
1 package tx
2
3 import (
4 "testing"
5
6 "github.com/btcsuite/btcd/btcec/v2"
7 btcecdsa "github.com/btcsuite/btcd/btcec/v2/ecdsa"
8
9 "git.smesh.lol/tinywallet/pkg/script"
10 )
11
12 // htlcFixture builds an HTLC and a transaction spending it, with the keys and preimage the
13 // claim and refund branches need.
14 func htlcFixture(t *testing.T) (*Tx, []byte, []byte, []byte, uint64) {
15 t.Helper()
16
17 // The claimer and the refunder are the fixture's key: one key on both branches is enough
18 // for the witness shape, and it means the signature can be checked against a pubkey that
19 // is known to match the secret.
20 claimPub := mustDecode(t, loadFixture(t).Pubkey)
21
22 hash := mustDecode(t, nodeHash)
23
24 var secretHash [32]byte
25 copy(secretHash[:], hash)
26
27 witnessScript, err := script.HTLC(claimPub, claimPub, secretHash, nodeLocktime)
28
29 if err != nil {
30 t.Fatal(err)
31 }
32
33 transaction := New()
34
35 var outpoint [32]byte
36 outpoint[0] = 0x33
37
38 transaction.AddInput(Outpoint{Hash: outpoint, Index: 0}, SequenceFinal)
39 transaction.AddOutput(90000, script.P2WSH(witnessScript))
40
41 preimage := make([]byte, 32)
42 preimage[31] = 0x01
43
44 return transaction, witnessScript, preimage, claimPub, 100000
45 }
46
47 // TestHTLCClaimWitnessOrder: the stack is preimage, signature, true selector, script, and the
48 // signature verifies over a digest that is the script's own.
49 func TestHTLCClaimWitnessOrder(t *testing.T) {
50 transaction, witnessScript, preimage, claimer, amount := htlcFixture(t)
51
52 secret := mustDecode(t, loadFixture(t).Secret)
53
54 if err := SignHTLCClaim(transaction, 0, secret, witnessScript, preimage, amount); err != nil {
55 t.Fatal(err)
56 }
57
58 witness := transaction.Inputs[0].Witness
59
60 if len(witness) != 4 {
61 t.Fatalf("the witness has %d items", len(witness))
62 }
63
64 if string(witness[0]) != string(preimage) || len(witness[2]) != 1 || witness[2][0] != 0x01 {
65 t.Fatal("the witness is not preimage, signature, selector, script")
66 }
67
68 if string(witness[3]) != string(witnessScript) {
69 t.Fatal("the witness does not carry the script it spends")
70 }
71
72 digest, err := transaction.SighashV0(0, witnessScript, amount, SighashAll)
73
74 if err != nil {
75 t.Fatal(err)
76 }
77
78 signature, err := btcecdsa.ParseDERSignature(witness[1][:len(witness[1])-1])
79
80 if err != nil {
81 t.Fatal(err)
82 }
83
84 key, err := btcec.ParsePubKey(claimer)
85
86 if err != nil {
87 t.Fatal(err)
88 }
89
90 if !signature.Verify(digest[:], key) {
91 t.Fatal("the claim signature does not verify over the script's digest")
92 }
93 }
94
95 // TestHTLCRefundWitnessOrder: the refund is signature, an empty selector, the script.
96 func TestHTLCRefundWitnessOrder(t *testing.T) {
97 transaction, witnessScript, _, _, amount := htlcFixture(t)
98
99 secret := mustDecode(t, loadFixture(t).Secret)
100
101 if err := SignHTLCRefund(transaction, 0, secret, witnessScript, amount); err != nil {
102 t.Fatal(err)
103 }
104
105 witness := transaction.Inputs[0].Witness
106
107 if len(witness) != 3 || len(witness[1]) != 0 {
108 t.Fatalf("the witness has %d items, the selector is %d bytes", len(witness), len(witness[1]))
109 }
110
111 if string(witness[2]) != string(witnessScript) {
112 t.Fatal("the witness does not carry the script it spends")
113 }
114 }
115
116 // TestADifferentScriptIsADifferentDigest: the point of committing to the witness script.
117 func TestADifferentScriptIsADifferentDigest(t *testing.T) {
118 transaction, witnessScript, _, _, amount := htlcFixture(t)
119
120 first, err := transaction.SighashV0(0, witnessScript, amount, SighashAll)
121
122 if err != nil {
123 t.Fatal(err)
124 }
125
126 var other [32]byte
127 copy(other[:], mustDecode(t, nodeHash))
128 other[0] ^= 0xff
129
130 claimPub := mustDecode(t, loadFixture(t).Pubkey)
131
132 changed, err := script.HTLC(claimPub, claimPub, other, nodeLocktime)
133
134 if err != nil {
135 t.Fatal(err)
136 }
137
138 second, err := transaction.SighashV0(0, changed, amount, SighashAll)
139
140 if err != nil {
141 t.Fatal(err)
142 }
143
144 if first == second {
145 t.Fatal("a changed witness script produced the same digest")
146 }
147 }
148
149 // nodeHash and nodeLocktime are the values pkg/script pins its compiled script against.
150 const (
151 nodeHash = "0000000000000000000000000000000000000000000000000000000000000001"
152 nodeLocktime = 500
153 )
154