forked from lightningnetwork/lnd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
script_utils.go
278 lines (236 loc) · 9.6 KB
/
script_utils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
package lnwallet
import (
"bytes"
"fmt"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
)
var (
// TODO(roasbeef): remove these and use the one's defined in txscript
// within testnet-L.
SequenceLockTimeSeconds = uint32(1 << 22)
SequenceLockTimeMask = uint32(0x0000ffff)
OP_CHECKSEQUENCEVERIFY byte = txscript.OP_NOP3
)
// scriptHashPkScript generates a pay-to-script-hash public key script paying
// to the hash160 of the passed redeem script.
func scriptHashPkScript(redeemScript []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder()
bldr.AddOp(txscript.OP_HASH160)
bldr.AddData(btcutil.Hash160(redeemScript))
bldr.AddOp(txscript.OP_EQUAL)
return bldr.Script()
}
// getFundingPkScript generates the non-p2sh'd multisig script for 2 of 2
// pubkeys.
func genFundingPkScript(aPub, bPub []byte) ([]byte, error) {
if len(aPub) != 33 || len(bPub) != 33 {
return nil, fmt.Errorf("Pubkey size error. Compressed pubkeys only")
}
// Swap to sort pubkeys if needed. Keys are sorted in lexicographical
// order. The signatures within the scriptSig must also adhere to the
// order, ensuring that the signatures for each public key appears
// in the proper order on the stack.
if bytes.Compare(aPub, bPub) == -1 {
aPub, bPub = bPub, aPub
}
bldr := txscript.NewScriptBuilder()
bldr.AddOp(txscript.OP_2)
bldr.AddData(aPub) // Add both pubkeys (sorted).
bldr.AddData(bPub)
bldr.AddOp(txscript.OP_2)
bldr.AddOp(txscript.OP_CHECKMULTISIG)
return bldr.Script()
}
// fundMultiSigOut create the redeemScript for the funding transaction, and
// also a TxOut paying to the p2sh of the multi-sig redeemScript. Give it the
// two pubkeys and it'll give you the p2sh'd txout. You don't have to remember
// the p2sh preimage, as long as you remember the pubkeys involved.
func fundMultiSigOut(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, error) {
if amt < 0 {
return nil, nil, fmt.Errorf("can't create FundTx script with " +
"negative coins")
}
// p2shify
redeemScript, err := genFundingPkScript(aPub, bPub)
if err != nil {
return nil, nil, err
}
pkScript, err := scriptHashPkScript(redeemScript)
if err != nil {
return nil, nil, err
}
return redeemScript, wire.NewTxOut(amt, pkScript), nil
}
// spendMultiSig generates the scriptSig required to redeem the 2-of-2 p2sh
// multi-sig output.
func spendMultiSig(redeemScript, pubA, sigA, pubB, sigB []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder()
// add a 0 for some multisig fun
bldr.AddOp(txscript.OP_0)
// When initially generating the redeemScript, we sorted the serialized
// public keys in descending order. So we do a quick comparison in order
// ensure the signatures appear on the Script Virual Machine stack in
// the correct order.
if bytes.Compare(pubA, pubB) == -1 {
bldr.AddData(sigB)
bldr.AddData(sigA)
} else {
bldr.AddData(sigA)
bldr.AddData(sigB)
}
// preimage goes on AT THE ENDDDD
bldr.AddData(redeemScript)
// that's all, get bytes
return bldr.Script()
}
// findScriptOutputIndex finds the index of the public key script output
// matching 'script'. Additionally, a boolean is returned indicating if
// a matching output was found at all.
// NOTE: The search stops after the first matching script is found.
func findScriptOutputIndex(tx *wire.MsgTx, script []byte) (bool, uint32) {
found := false
index := uint32(0)
for i, txOut := range tx.TxOut {
if bytes.Equal(txOut.PkScript, script) {
found = true
index = uint32(i)
break
}
}
return found, index
}
// senderHTLCScript constructs the public key script for an outgoing HTLC
// output payment for the sender's commitment transaction.
func senderHTLCScript(absoluteTimeout, relativeTimeout uint32, senderKey,
receiverKey *btcec.PublicKey, revokeHash, paymentHash []byte) ([]byte, error) {
builder := txscript.NewScriptBuilder()
// Was the pre-image to the payment hash presented?
builder.AddOp(txscript.OP_HASH160)
builder.AddOp(txscript.OP_DUP)
builder.AddData(paymentHash)
builder.AddOp(txscript.OP_EQUAL)
// How about the pre-image for our commitment revocation hash?
builder.AddOp(txscript.OP_SWAP)
builder.AddData(revokeHash)
builder.AddOp(txscript.OP_EQUAL)
builder.AddOp(txscript.OP_SWAP)
builder.AddOp(txscript.OP_ADD)
// If either is present, then the receiver can claim immediately.
builder.AddOp(txscript.OP_IF)
builder.AddData(receiverKey.SerializeCompressed())
// Otherwise, we (the sender) need to wait for an absolute HTLC
// timeout, then afterwards a relative timeout before we claim re-claim
// the unsettled funds. This delay gives the other party a chance to
// present the pre-image to the revocation hash in the event that the
// sender (at this time) broadcasts this commitment transaction after
// it has been revoked.
builder.AddOp(txscript.OP_ELSE)
builder.AddInt64(int64(absoluteTimeout))
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
builder.AddInt64(int64(relativeTimeout))
builder.AddOp(OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_2DROP)
builder.AddData(senderKey.SerializeCompressed())
builder.AddOp(txscript.OP_ENDIF)
builder.AddOp(txscript.OP_CHECKSIG)
return builder.Script()
}
// senderHTLCScript constructs the public key script for an incoming HTLC
// output payment for the receiver's commitment transaction.
func receiverHTLCScript(absoluteTimeout, relativeTimeout uint32, senderKey,
receiverKey *btcec.PublicKey, revokeHash, paymentHash []byte) ([]byte, error) {
builder := txscript.NewScriptBuilder()
// Was the pre-image to the payment hash presented?
builder.AddOp(txscript.OP_HASH160)
builder.AddOp(txscript.OP_DUP)
builder.AddData(paymentHash)
builder.AddOp(txscript.OP_EQUAL)
builder.AddOp(txscript.OP_IF)
// If so, let the receiver redeem after a relative timeout. This added
// delay gives the sender (at this time) an opportunity to re-claim the
// pending HTLC in the event that the receiver (at this time) broadcasts
// this old commitment transaction after it has been revoked.
builder.AddInt64(int64(relativeTimeout))
builder.AddOp(OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_2DROP)
builder.AddData(receiverKey.SerializeCompressed())
// Otherwise, if the sender has the revocation pre-image to the
// receiver's commitment transactions, then let them claim the
// funds immediately.
builder.AddOp(txscript.OP_ELSE)
builder.AddData(revokeHash)
builder.AddOp(txscript.OP_EQUAL)
// If not, then the sender needs to wait for the HTLC timeout. This
// clause may be executed if the receiver fails to present the r-value
// in time. This prevents the pending funds from being locked up
// indefinately.
builder.AddOp(txscript.OP_NOTIF)
builder.AddInt64(int64(absoluteTimeout))
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
builder.AddOp(txscript.OP_DROP)
builder.AddOp(txscript.OP_ENDIF)
builder.AddData(senderKey.SerializeCompressed())
builder.AddOp(txscript.OP_ENDIF)
builder.AddOp(txscript.OP_CHECKSIG)
return builder.Script()
}
// lockTimeToSequence converts the passed relative locktime to a sequence
// number in accordance to BIP-68.
// See: https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki
// * (Compatibility)
func lockTimeToSequence(isSeconds bool, locktime uint32) uint32 {
if !isSeconds {
// The locktime is to be expressed in confirmations. Apply the
// mask to restrict the number of confirmations to 65,535 or
// 1.25 years.
return SequenceLockTimeMask & locktime
}
// Set the 22nd bit which indicates the lock time is in seconds, then
// shift the locktime over by 9 since the time granularity is in
// 512-second intervals (2^9). This results in a max lock-time of
// 33,554,431 seconds, or 1.06 years.
return SequenceLockTimeSeconds | (locktime >> 9)
}
// commitScriptToSelf constructs the public key script for the output on the
// commitment transaction paying to the "owner" of said commitment transaction.
// If the other party learns of the pre-image to the revocation hash, then they
// can claim all the settled funds in the channel, plus the unsettled funds.
func commitScriptToSelf(csvTimeout uint32, selfKey, theirKey *btcec.PublicKey, revokeHash []byte) ([]byte, error) {
// This script is spendable under two conditions: either the 'csvTimeout'
// has passed and we can redeem our funds, or they have the pre-image
// to 'revokeHash'.
builder := txscript.NewScriptBuilder()
// If the pre-image for the revocation hash is presented, then allow a
// spend provided the proper signature.
builder.AddOp(txscript.OP_HASH160)
builder.AddData(revokeHash)
builder.AddOp(txscript.OP_EQUAL)
builder.AddOp(txscript.OP_IF)
builder.AddData(theirKey.SerializeCompressed())
builder.AddOp(txscript.OP_ELSE)
// Otherwise, we can re-claim our funds after a CSV delay of
// 'csvTimeout' timeout blocks, and a valid signature.
builder.AddInt64(int64(csvTimeout))
builder.AddOp(OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
builder.AddData(selfKey.SerializeCompressed())
builder.AddOp(txscript.OP_ENDIF)
builder.AddOp(txscript.OP_CHECKSIG)
return builder.Script()
}
// commitScriptUnencumbered constructs the public key script on the commitment
// transaction paying to the "other" party. This output is spendable
// immediately, requiring no contestation period.
func commitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) {
// This script goes to the "other" party, and it spendable immediately.
builder := txscript.NewScriptBuilder()
builder.AddOp(txscript.OP_DUP)
builder.AddOp(txscript.OP_HASH160)
builder.AddData(btcutil.Hash160(key.SerializeCompressed()))
builder.AddOp(txscript.OP_EQUALVERIFY)
builder.AddOp(txscript.OP_CHECKSIG)
return builder.Script()
}