/
interfaces.go
346 lines (285 loc) · 10.4 KB
/
interfaces.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
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
package sidecar
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/pool/codec"
)
// Version is the version of the sidecar ticket format.
type Version uint8
const (
// VersionDefault is the initial version of the ticket format.
VersionDefault Version = 0
// VersionUnannouncedZeroConf is the fist version of the ticket format
// that added support for unannounced and zero conf channels.
VersionUnannouncedZeroConf Version = 1
)
// State is the state a sidecar ticket currently is in. Each updater of the
// ticket is responsible for also setting the state correctly after adding their
// data according to their role.
type State uint8
const (
// StateCreated is the state a ticket is in after it's been first
// created and the offer information was added but not signed yet.
StateCreated State = 0
// StateOffered is the state a ticket is in after the offer data was
// signed by the sidecar provider.
StateOffered State = 1
// StateRegistered is the state a ticket is in after the recipient has
// registered it in their system and added their information.
StateRegistered State = 2
// StateOrdered is the state a ticket is in after the bid order for the
// sidecar channel was submitted and the order information was added to
// the ticket. The ticket now also contains a signature of the provider
// over the order digest.
StateOrdered State = 3
// StateExpectingChannel is the state a ticket is in after it's been
// returned to the channel recipient and their node was instructed to
// start listening for batch events for it.
StateExpectingChannel State = 4
// StateCompleted is the state a ticket is in after the sidecar channel
// was successfully opened and the bid order completed.
StateCompleted State = 5
// StateCanceled is the state a ticket is in after the sidecar channel
// bid order was canceled by the taker.
StateCanceled State = 6
)
// String returns the string representation of a sidecar ticket state.
func (s State) String() string {
switch s {
case StateCreated:
return "created"
case StateOffered:
return "offered"
case StateRegistered:
return "registered"
case StateOrdered:
return "ordered"
case StateExpectingChannel:
return "expecting channel"
case StateCompleted:
return "completed"
case StateCanceled:
return "canceled"
default:
return fmt.Sprintf("unknown <%d>", s)
}
}
// IsTerminal returns true if the ticket is in a final state and will not be
// updated ever again.
func (s State) IsTerminal() bool {
switch s {
case StateCompleted, StateCanceled:
return true
default:
return false
}
}
// Offer is a struct holding the information that a sidecar channel provider is
// committing to when offering to buy a channel for the recipient. The sidecar
// channel flow is initiated by the provider creating a ticket and adding its
// signed offer to it.
type Offer struct {
// Capacity is the channel capacity of the sidecar channel in satoshis.
Capacity btcutil.Amount
// PushAmt is the amount in satoshis that will be pushed to the
// recipient of the channel to kick start them with inbound capacity.
// If this is non-zero then the provider must pay for the push amount as
// well as all other costs from their Pool account. Makers can opt out
// of supporting push amounts when submitting ask orders so this will
// reduce the matching chances somewhat.
PushAmt btcutil.Amount
// LeaseDurationBlocks is the number of blocks the offered channel in
// this offer would be leased for.
LeaseDurationBlocks uint32
// SignPubKey is the public key for corresponding to the private key
// that signed the SigOfferDigest below and, in a later state, the
// SigOrderDigest of the Order struct.
SignPubKey *btcec.PublicKey
// SigOfferDigest is a signature over the offer digest, signed with the
// private key that corresponds to the SignPubKey above.
SigOfferDigest *ecdsa.Signature
// Auto determines if the provider requires that the ticket be
// completed using an automated negotiation sequence.
Auto bool
// UnannouncedChannel determines if this ticket is interested in
// unannounced channels or not.
UnannouncedChannel bool
// ZeroConfChannel determines if this ticket is interested in zero conf
// channels or not.
ZeroConfChannel bool
}
// Recipient is a struct holding the information about the recipient of the
// sidecar channel in question.
type Recipient struct {
// NodePubKey is the recipient nodes' identity public key that is
// advertised in the bid order.
NodePubKey *btcec.PublicKey
// MultiSigPubKey is a public key to which the recipient node has the
// private key to. It is the key that will be used as one of the 2-of-2
// multisig keys of the channel funding transaction output and is
// advertised in the bid order.
MultiSigPubKey *btcec.PublicKey
// MultiSigKeyIndex is the derivation index of the MultiSigPubKey.
MultiSigKeyIndex uint32
}
// Order is a struct holding the information about the sidecar bid order after
// it's been submitted by the provider.
type Order struct {
// BidNonce is the order nonce of the bid order that was submitted for
// purchasing the sidecar channel.
BidNonce [32]byte
// SigOrderDigest is a signature over the order digest, signed with the
// private key that corresponds to the SignPubKey in the Offer struct.
SigOrderDigest *ecdsa.Signature
}
// Execution is a struct holding information about the sidecar bid order during
// its execution.
type Execution struct {
// PendingChannelID is the pending channel ID of the currently
// registered funding shim that was added by the recipient node to
// receive the channel.
PendingChannelID [32]byte
}
// Ticket is a struct holding all the information for establishing/buying a
// sidecar channel. It is meant to be used in a PSBT like manner where each
// participant incrementally adds their information according to their role and
// the current step.
type Ticket struct {
// ID is a pseudorandom identifier of the ticket.
ID [8]byte
// Version is the version of the ticket serialization format.
Version Version
// State is the current state of the ticket. The state is updated by
// each participant and is therefore not covered in any of the signature
// digests.
State State
// Offer contains the initial conditions offered by the sidecar channel
// provider. Every ticket must start with an offer and therefore this
// member can never be empty or nil.
Offer Offer
// Recipient contains the information about the receiver node of the
// sidecar channel. This field must be set for all states greater or
// equal to StateRegistered.
Recipient *Recipient
// Order contains the information about the order that was submitted for
// leasing the sidecar channel represented by this ticket. This field
// must be set for all states greater or equal to StateOrdered.
Order *Order
// Execution contains the information about the execution part of the
// sidecar channel. This information is not meant to be exchanged
// between the participating parties but is included in the ticket to
// make it easy to serialize/deserialize a ticket state within the local
// database.
Execution *Execution
}
// NewTicket creates a new sidecar ticket with the given version and offer
// information.
func NewTicket(capacity, pushAmt btcutil.Amount,
duration uint32, offerPubKey *btcec.PublicKey,
auto bool, unannounced, zeroConf bool) (*Ticket, error) {
// The ticket should always use the minimum version that supports
// all the features encoded in it so it can be used by clients using
// older pool versions.
version := VersionDefault
if unannounced || zeroConf {
version = VersionUnannouncedZeroConf
}
t := &Ticket{
Version: version,
State: StateOffered,
Offer: Offer{
Capacity: capacity,
PushAmt: pushAmt,
LeaseDurationBlocks: duration,
SignPubKey: offerPubKey,
Auto: auto,
UnannouncedChannel: unannounced,
ZeroConfChannel: zeroConf,
},
}
// The linter flags this, even though crypto/rand is being used...
if _, err := rand.Read(t.ID[:]); err != nil { // nolint:gosec
return nil, err
}
return t, nil
}
// OfferDigest returns a message digest over the offer fields.
func (t *Ticket) OfferDigest() ([32]byte, error) {
var (
msg bytes.Buffer
result [sha256.Size]byte
)
switch t.Version {
case VersionDefault:
err := codec.WriteElements(
&msg, t.ID[:], uint8(t.Version), t.Offer.Capacity,
t.Offer.PushAmt, t.Offer.Auto,
)
if err != nil {
return result, err
}
case VersionUnannouncedZeroConf:
err := codec.WriteElements(
&msg, t.ID[:], uint8(t.Version), t.Offer.Capacity,
t.Offer.PushAmt, t.Offer.Auto,
t.Offer.UnannouncedChannel, t.Offer.ZeroConfChannel,
)
if err != nil {
return result, err
}
default:
return result, fmt.Errorf("unknown version %d", t.Version)
}
return sha256.Sum256(msg.Bytes()), nil
}
// OrderDigest returns a message digest over the order fields.
func (t *Ticket) OrderDigest() ([32]byte, error) {
var (
msg bytes.Buffer
result [sha256.Size]byte
)
if t.State < StateOrdered || t.Order == nil {
return result, fmt.Errorf("invalid state for order digest")
}
switch t.Version {
case VersionDefault:
err := codec.WriteElements(
&msg, t.ID[:], uint8(t.Version), t.Offer.Capacity,
t.Offer.PushAmt, t.Order.BidNonce[:],
)
if err != nil {
return result, err
}
case VersionUnannouncedZeroConf:
err := codec.WriteElements(
&msg, t.ID[:], uint8(t.Version), t.Offer.Capacity,
t.Offer.PushAmt, t.Offer.Auto,
t.Offer.UnannouncedChannel, t.Offer.ZeroConfChannel,
)
if err != nil {
return result, err
}
default:
return result, fmt.Errorf("unknown version %d", t.Version)
}
return sha256.Sum256(msg.Bytes()), nil
}
// Store is the interface a persistent storage must implement for storing and
// retrieving sidecar tickets.
type Store interface {
// AddSidecar adds a record for the sidecar order to the database.
AddSidecar(sidecar *Ticket) error
// UpdateSidecar updates a sidecar order in the database.
UpdateSidecar(sidecar *Ticket) error
// Sidecar retrieves a specific sidecar by its ID and provider signing
// key (offer signature pubkey) or returns ErrNoSidecar if it's not
// found.
Sidecar(id [8]byte, offerSignPubKey *btcec.PublicKey) (*Ticket, error)
// Sidecars retrieves all known sidecar orders from the database.
Sidecars() ([]*Ticket, error)
}