forked from mit-dci/lit
/
close.go
322 lines (267 loc) · 9.57 KB
/
close.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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
package qln
import (
"bytes"
"fmt"
"log"
"github.com/mit-dci/lit/btcutil/chaincfg/chainhash"
"github.com/mit-dci/lit/btcutil/txscript"
"github.com/mit-dci/lit/wire"
"github.com/mit-dci/lit/crypto/fastsha256"
"github.com/mit-dci/lit/lnutil"
"github.com/mit-dci/lit/portxo"
"github.com/mit-dci/lit/sig64"
)
/* CloseChannel --- cooperative close
This is the simplified close which sends to the same outputs as a break tx,
just with no timeouts.
Users might want a more advanced close function which allows multiple outputs.
They can exchange txouts and sigs. That could be "fancyClose", but this is
just close, so only a signature is sent by the initiator, and the receiver
doesn't reply, as the channel is closed.
*/
// CoopClose requests a cooperative close of the channel
func (nd *LitNode) CoopClose(q *Qchan) error {
nd.RemoteMtx.Lock()
_, ok := nd.RemoteCons[q.Peer()]
nd.RemoteMtx.Unlock()
if !ok {
return fmt.Errorf("not connected to peer %d ", q.Peer())
}
if q.CloseData.Closed {
return fmt.Errorf("can't close (%d,%d): already closed",
q.KeyGen.Step[3]&0x7fffffff, q.KeyGen.Step[4]&0x7fffffff)
}
tx, err := q.SimpleCloseTx()
if err != nil {
return err
}
sig, err := nd.SignSimpleClose(q, tx)
if err != nil {
return err
}
// Save something, just so the UI marks it as closed, and
// we don't accept payments on this channel anymore.
// save channel state as closed. We know the txid... even though that
// txid may not actually happen.
q.CloseData.Closed = true
q.CloseData.CloseTxid = tx.TxHash()
err = nd.SaveQchanUtxoData(q)
if err != nil {
return err
}
var signature [64]byte
copy(signature[:], sig[:])
// Save something to db... TODO
// Should save something, just so the UI marks it as closed, and
// we don't accept payments on this channel anymore.
outMsg := lnutil.NewCloseReqMsg(q.Peer(), q.Op, signature)
nd.OmniOut <- outMsg
return nil
}
// CloseReqHandler takes in a close request from a remote host, signs and
// responds with a close response. Obviously later there will be some judgment
// over what to do, but for now it just signs whatever it's requested to.
func (nd *LitNode) CloseReqHandler(msg lnutil.CloseReqMsg) {
opArr := lnutil.OutPointToBytes(msg.Outpoint)
// get channel
q, err := nd.GetQchan(opArr)
if err != nil {
log.Printf("CloseReqHandler GetQchan err %s", err.Error())
return
}
if nd.SubWallet[q.Coin()] == nil {
log.Printf("Not connected to coin type %d\n", q.Coin())
}
// verify their sig? should do that before signing our side just to be safe
// TODO -- yeah we need to verify their sig
// build close tx
tx, err := q.SimpleCloseTx()
if err != nil {
log.Printf("CloseReqHandler SimpleCloseTx err %s", err.Error())
return
}
// sign close
mySig, err := nd.SignSimpleClose(q, tx)
if err != nil {
log.Printf("CloseReqHandler SignSimpleClose err %s", err.Error())
return
}
myBigSig := sig64.SigDecompress(mySig)
theirBigSig := sig64.SigDecompress(msg.Signature)
// put the sighash all byte on the end of both signatures
myBigSig = append(myBigSig, byte(txscript.SigHashAll))
theirBigSig = append(theirBigSig, byte(txscript.SigHashAll))
pre, swap, err := lnutil.FundTxScript(q.MyPub, q.TheirPub)
if err != nil {
log.Printf("CloseReqHandler FundTxScript err %s", err.Error())
return
}
// swap if needed
if swap {
tx.TxIn[0].Witness = SpendMultiSigWitStack(pre, theirBigSig, myBigSig)
} else {
tx.TxIn[0].Witness = SpendMultiSigWitStack(pre, myBigSig, theirBigSig)
}
log.Printf(lnutil.TxToString(tx))
// save channel state to db as closed.
q.CloseData.Closed = true
q.CloseData.CloseTxid = tx.TxHash()
err = nd.SaveQchanUtxoData(q)
if err != nil {
log.Printf("CloseReqHandler SaveQchanUtxoData err %s", err.Error())
return
}
// broadcast
err = nd.SubWallet[q.Coin()].PushTx(tx)
if err != nil {
log.Printf("CloseReqHandler NewOutgoingTx err %s", err.Error())
return
}
return
}
// GetCloseTxos takes in a tx and sets the QcloseTXO fields based on the tx.
// It also returns the spendable (u)txos generated by the close.
// TODO way too long. Need to split up.
// TODO problem with collisions, and insufficiently high elk receiver...?
func (q *Qchan) GetCloseTxos(tx *wire.MsgTx) ([]portxo.PorTxo, error) {
if tx == nil {
return nil, fmt.Errorf("IngesGetCloseTxostCloseTx: nil tx")
}
txid := tx.TxHash()
// double check -- does this tx actually close the channel?
if !(len(tx.TxIn) == 1 && lnutil.OutPointsEqual(tx.TxIn[0].PreviousOutPoint, q.Op)) {
return nil, fmt.Errorf("tx %s doesn't spend channel outpoint %s",
txid.String(), q.Op.String())
}
var shIdx, pkhIdx uint32
var pkhIsMine bool
cTxos := make([]portxo.PorTxo, 1)
myPKHPkSript := lnutil.DirectWPKHScript(q.MyRefundPub)
shIdx = 999 // set high here to detect if there's no SH output
// Classify outputs. Assumes only 1 SH output. Later recognize HTLC outputs
for i, out := range tx.TxOut {
if len(out.PkScript) == 34 {
shIdx = uint32(i)
}
if bytes.Equal(myPKHPkSript, out.PkScript) {
pkhIdx = uint32(i)
pkhIsMine = true
}
}
// if pkh is mine, grab it.
if pkhIsMine {
log.Printf("got PKH output from channel close")
var pkhTxo portxo.PorTxo // create new utxo and copy into it
pkhTxo.Op.Hash = txid
pkhTxo.Op.Index = pkhIdx
pkhTxo.Height = q.CloseData.CloseHeight
// keypath same, use different
pkhTxo.KeyGen = q.KeyGen
// same keygen as underlying channel, but use is refund
pkhTxo.KeyGen.Step[2] = UseChannelRefund
pkhTxo.Mode = portxo.TxoP2WPKHComp
pkhTxo.Value = tx.TxOut[pkhIdx].Value
// PKH, could omit this
pkhTxo.PkScript = tx.TxOut[pkhIdx].PkScript
cTxos[0] = pkhTxo
}
// get state hint based on pkh match. If pkh is mine, that's their TX & hint.
// if there's no PKH output for me, the TX is mine, so use my hint.
var comNum uint64
if pkhIsMine {
comNum = GetStateIdxFromTx(tx, q.GetChanHint(false))
} else {
comNum = GetStateIdxFromTx(tx, q.GetChanHint(true))
}
if comNum > q.State.StateIdx { // future state, uhoh. Crash for now.
log.Printf("indicated state %d but we know up to %d",
comNum, q.State.StateIdx)
return cTxos, nil
}
// if we didn't get the pkh, and the comNum is current, we get the SH output.
// also we probably closed ourselves. Regular timeout
if !pkhIsMine && shIdx < 999 && comNum != 0 && comNum == q.State.StateIdx {
theirElkPoint, err := q.ElkPoint(false, comNum)
if err != nil {
return nil, err
}
// build script to store in porTxo, make pubkeys
timeoutPub := lnutil.AddPubsEZ(q.MyHAKDBase, theirElkPoint)
revokePub := lnutil.CombinePubs(q.TheirHAKDBase, theirElkPoint)
script := lnutil.CommitScript(revokePub, timeoutPub, q.Delay)
// script check. redundant / just in case
genSH := fastsha256.Sum256(script)
if !bytes.Equal(genSH[:], tx.TxOut[shIdx].PkScript[2:34]) {
log.Printf("got different observed and generated SH scripts.\n")
log.Printf("in %s:%d, see %x\n", txid, shIdx, tx.TxOut[shIdx].PkScript)
log.Printf("generated %x \n", genSH)
log.Printf("revokable pub %x\ntimeout pub %x\n", revokePub, timeoutPub)
}
// create the ScriptHash, timeout portxo.
var shTxo portxo.PorTxo // create new utxo and copy into it
// use txidx's elkrem as it may not be most recent
elk, err := q.ElkSnd.AtIndex(comNum)
if err != nil {
return nil, err
}
// keypath is the same, except for use
shTxo.KeyGen = q.KeyGen
shTxo.Op.Hash = txid
shTxo.Op.Index = shIdx
shTxo.Height = q.CloseData.CloseHeight
shTxo.KeyGen.Step[2] = UseChannelHAKDBase
elkpoint := lnutil.ElkPointFromHash(elk)
addhash := chainhash.DoubleHashH(append(elkpoint[:], q.MyHAKDBase[:]...))
shTxo.PrivKey = addhash
shTxo.Mode = portxo.TxoP2WSHComp
shTxo.Value = tx.TxOut[shIdx].Value
shTxo.Seq = uint32(q.Delay)
shTxo.PreSigStack = make([][]byte, 1) // revoke SH has one presig item
shTxo.PreSigStack[0] = nil // and that item is a nil (timeout)
shTxo.PkScript = script
cTxos[0] = shTxo
}
// if we got the pkh, and the comNum is too old, we can get the SH. Justice.
if pkhIsMine && comNum != 0 && comNum < q.State.StateIdx {
// ---------- revoked SH is mine
// invalid previous state, can be grabbed!
// make MY elk points
myElkPoint, err := q.ElkPoint(true, comNum)
if err != nil {
return nil, err
}
timeoutPub := lnutil.AddPubsEZ(q.TheirHAKDBase, myElkPoint)
revokePub := lnutil.CombinePubs(q.MyHAKDBase, myElkPoint)
script := lnutil.CommitScript(revokePub, timeoutPub, q.Delay)
// script check
wshScript := lnutil.P2WSHify(script)
if !bytes.Equal(wshScript[:], tx.TxOut[shIdx].PkScript) {
log.Printf("got different observed and generated SH scripts.\n")
log.Printf("in %s:%d, see %x\n", txid, shIdx, tx.TxOut[shIdx].PkScript)
log.Printf("generated %x \n", wshScript)
log.Printf("revokable pub %x\ntimeout pub %x\n", revokePub, timeoutPub)
}
// myElkHashR added to HAKD private key
elk, err := q.ElkRcv.AtIndex(comNum)
if err != nil {
return nil, err
}
var shTxo portxo.PorTxo // create new utxo and copy into it
shTxo.KeyGen = q.KeyGen
shTxo.Op.Hash = txid
shTxo.Op.Index = shIdx
shTxo.Height = q.CloseData.CloseHeight
shTxo.KeyGen.Step[2] = UseChannelHAKDBase
shTxo.PrivKey = lnutil.ElkScalar(elk)
// just return the elkScalar and let
// something modify it before export due to the seq=1 flag.
shTxo.PkScript = script
shTxo.Value = tx.TxOut[shIdx].Value
shTxo.Mode = portxo.TxoP2WSHComp
shTxo.Seq = 1 // 1 means grab immediately
shTxo.PreSigStack = make([][]byte, 1) // timeout SH has one presig item
shTxo.PreSigStack[0] = []byte{0x01} // and that item is a 1 (justice)
cTxos = append(cTxos, shTxo)
}
return cTxos, nil
}