messages.go raw
1 package api
2
3 import (
4 "encoding/binary"
5 "fmt"
6 )
7
8 // The request and result payloads, one pair per method. Each parser refuses a payload with
9 // bytes left over: a frame that carries more than its method understands is a version
10 // disagreement, and guessing is how a wallet signs the wrong amount.
11
12 // errTrailing is a payload with bytes after the fields the method knows.
13 func errTrailing(method Method, rest []byte) error {
14 if len(rest) == 0 {
15 return nil
16 }
17
18 return fmt.Errorf("%w: %s carries %d unexplained byte(s)", ErrBadFrame, method, len(rest))
19 }
20
21 // ChainStatus is one chain's state, as a page would draw it.
22 type ChainStatus struct {
23 // Chain is the wallet's chain name.
24 Chain string
25 // Profile is the chain profile name.
26 Profile string
27 // HRP is the bech32 prefix the chain's addresses use.
28 HRP string
29 // Account is the BIP32 account.
30 Account uint32
31 // HasWallet reports whether a sealed seed exists.
32 HasWallet bool
33 // Unlocked reports whether the account is in memory.
34 Unlocked bool
35 // Tip and Filters are the last known chain position.
36 Tip uint32
37 Filters uint32
38 // Running reports whether the chain said a sync was in progress.
39 Running bool
40 // Receive and Change are the address cursors.
41 Receive uint32
42 Change uint32
43 // Matches is how many heights the store recorded.
44 Matches uint32
45 }
46
47 // Encode renders a status.
48 func (s ChainStatus) Encode() []byte {
49 var out []byte
50
51 out = PutText(out, s.Chain)
52 out = PutText(out, s.Profile)
53 out = PutText(out, s.HRP)
54 out = PutUint32(out, s.Account)
55 out = PutBool(out, s.HasWallet)
56 out = PutBool(out, s.Unlocked)
57 out = PutUint32(out, s.Tip)
58 out = PutUint32(out, s.Filters)
59 out = PutBool(out, s.Running)
60 out = PutUint32(out, s.Receive)
61 out = PutUint32(out, s.Change)
62
63 return PutUint32(out, s.Matches)
64 }
65
66 // ParseChainStatus reads a status.
67 func ParseChainStatus(payload []byte) (ChainStatus, error) {
68 var (
69 status ChainStatus
70 err error
71 )
72
73 if status.Chain, payload, err = ReadText(payload); err != nil {
74 return ChainStatus{}, err
75 }
76
77 if status.Profile, payload, err = ReadText(payload); err != nil {
78 return ChainStatus{}, err
79 }
80
81 if status.HRP, payload, err = ReadText(payload); err != nil {
82 return ChainStatus{}, err
83 }
84
85 if status.Account, payload, err = ReadUint32(payload); err != nil {
86 return ChainStatus{}, err
87 }
88
89 if status.HasWallet, payload, err = ReadBool(payload); err != nil {
90 return ChainStatus{}, err
91 }
92
93 if status.Unlocked, payload, err = ReadBool(payload); err != nil {
94 return ChainStatus{}, err
95 }
96
97 if status.Tip, payload, err = ReadUint32(payload); err != nil {
98 return ChainStatus{}, err
99 }
100
101 if status.Filters, payload, err = ReadUint32(payload); err != nil {
102 return ChainStatus{}, err
103 }
104
105 if status.Running, payload, err = ReadBool(payload); err != nil {
106 return ChainStatus{}, err
107 }
108
109 if status.Receive, payload, err = ReadUint32(payload); err != nil {
110 return ChainStatus{}, err
111 }
112
113 if status.Change, payload, err = ReadUint32(payload); err != nil {
114 return ChainStatus{}, err
115 }
116
117 if status.Matches, payload, err = ReadUint32(payload); err != nil {
118 return ChainStatus{}, err
119 }
120
121 return status, errTrailing(MethodStatus, payload)
122 }
123
124 // ChainRequest names one chain.
125 type ChainRequest struct {
126 // Chain is the wallet's chain name.
127 Chain string
128 }
129
130 // Encode renders the request.
131 func (r ChainRequest) Encode() []byte { return PutText(nil, r.Chain) }
132
133 // ParseChainRequest reads the request.
134 func ParseChainRequest(payload []byte) (ChainRequest, error) {
135 chain, rest, err := ReadText(payload)
136
137 if err != nil {
138 return ChainRequest{}, err
139 }
140
141 return ChainRequest{Chain: chain}, errTrailing(MethodStatus, rest)
142 }
143
144 // SetupRequest is a create or a restore: a chain, words, and the passphrase that seals them.
145 type SetupRequest struct {
146 // Chain is the wallet's chain name.
147 Chain string
148 // Words is the word count for a create, and ignored for a restore.
149 Words uint16
150 // Phrase is the words for a restore, and empty for a create.
151 Phrase string
152 // Passphrase seals the seed at rest.
153 Passphrase string
154 }
155
156 // Encode renders the request.
157 func (r SetupRequest) Encode() []byte {
158 var out []byte
159
160 out = PutText(out, r.Chain)
161 out = PutUint32(out, uint32(r.Words))
162 out = PutText(out, r.Phrase)
163
164 return PutText(out, r.Passphrase)
165 }
166
167 // ParseSetupRequest reads the request.
168 func ParseSetupRequest(method Method, payload []byte) (SetupRequest, error) {
169 var (
170 request SetupRequest
171 words uint32
172 err error
173 )
174
175 if request.Chain, payload, err = ReadText(payload); err != nil {
176 return SetupRequest{}, err
177 }
178
179 if words, payload, err = ReadUint32(payload); err != nil {
180 return SetupRequest{}, err
181 }
182
183 if request.Phrase, payload, err = ReadText(payload); err != nil {
184 return SetupRequest{}, err
185 }
186
187 if request.Passphrase, payload, err = ReadText(payload); err != nil {
188 return SetupRequest{}, err
189 }
190
191 request.Words = uint16(words)
192
193 return request, errTrailing(method, payload)
194 }
195
196 // SetupResult is the first address, and on a create the words to write down.
197 type SetupResult struct {
198 // Chain is the wallet's chain name.
199 Chain string
200 // Mnemonic is the phrase, empty on a restore.
201 Mnemonic string
202 // Address is the first receive address.
203 Address string
204 // Path is the address's derivation path, for a person who wants to see it.
205 Path string
206 }
207
208 // Encode renders the result.
209 func (r SetupResult) Encode() []byte {
210 var out []byte
211
212 out = PutText(out, r.Chain)
213 out = PutText(out, r.Mnemonic)
214 out = PutText(out, r.Address)
215
216 return PutText(out, r.Path)
217 }
218
219 // ParseSetupResult reads the result.
220 func ParseSetupResult(payload []byte) (SetupResult, error) {
221 var (
222 result SetupResult
223 err error
224 )
225
226 if result.Chain, payload, err = ReadText(payload); err != nil {
227 return SetupResult{}, err
228 }
229
230 if result.Mnemonic, payload, err = ReadText(payload); err != nil {
231 return SetupResult{}, err
232 }
233
234 if result.Address, payload, err = ReadText(payload); err != nil {
235 return SetupResult{}, err
236 }
237
238 if result.Path, payload, err = ReadText(payload); err != nil {
239 return SetupResult{}, err
240 }
241
242 return result, errTrailing(MethodCreate, payload)
243 }
244
245 // UnlockRequest is a chain and the passphrase that opens it.
246 type UnlockRequest struct {
247 // Chain is the wallet's chain name.
248 Chain string
249 // Passphrase opens the sealed seed.
250 Passphrase string
251 }
252
253 // Encode renders the request.
254 func (r UnlockRequest) Encode() []byte {
255 return PutText(PutText(nil, r.Chain), r.Passphrase)
256 }
257
258 // ParseUnlockRequest reads the request.
259 func ParseUnlockRequest(payload []byte) (UnlockRequest, error) {
260 var (
261 request UnlockRequest
262 err error
263 )
264
265 if request.Chain, payload, err = ReadText(payload); err != nil {
266 return UnlockRequest{}, err
267 }
268
269 if request.Passphrase, payload, err = ReadText(payload); err != nil {
270 return UnlockRequest{}, err
271 }
272
273 return request, errTrailing(MethodUnlock, payload)
274 }
275
276 // AddressesRequest asks for a run of addresses.
277 type AddressesRequest struct {
278 // Chain is the wallet's chain name.
279 Chain string
280 // Change selects the change branch.
281 Change bool
282 // From is the first index.
283 From uint32
284 // Count is how many to derive.
285 Count uint16
286 }
287
288 // Encode renders the request.
289 func (r AddressesRequest) Encode() []byte {
290 var out []byte
291
292 out = PutText(out, r.Chain)
293 out = PutBool(out, r.Change)
294 out = PutUint32(out, r.From)
295
296 return PutUint32(out, uint32(r.Count))
297 }
298
299 // ParseAddressesRequest reads the request.
300 func ParseAddressesRequest(payload []byte) (AddressesRequest, error) {
301 var (
302 request AddressesRequest
303 count uint32
304 err error
305 )
306
307 if request.Chain, payload, err = ReadText(payload); err != nil {
308 return AddressesRequest{}, err
309 }
310
311 if request.Change, payload, err = ReadBool(payload); err != nil {
312 return AddressesRequest{}, err
313 }
314
315 if request.From, payload, err = ReadUint32(payload); err != nil {
316 return AddressesRequest{}, err
317 }
318
319 if count, payload, err = ReadUint32(payload); err != nil {
320 return AddressesRequest{}, err
321 }
322
323 request.Count = uint16(count)
324
325 return request, errTrailing(MethodAddresses, payload)
326 }
327
328 // AddressesResult is a run of addresses with their indexes and paths.
329 type AddressesResult struct {
330 // Addresses are the bech32 texts.
331 Addresses []string
332 // Indexes are their indexes on the branch.
333 Indexes []uint32
334 // Paths are their derivation paths.
335 Paths []string
336 }
337
338 // Encode renders the result.
339 func (r AddressesResult) Encode() []byte {
340 var out []byte
341
342 out = PutTexts(out, r.Addresses)
343
344 out = PutUint32s(out, r.Indexes)
345
346 return PutTexts(out, r.Paths)
347 }
348
349 // ParseAddressesResult reads the result.
350 func ParseAddressesResult(payload []byte) (AddressesResult, error) {
351 var (
352 result AddressesResult
353 err error
354 )
355
356 if result.Addresses, payload, err = ReadTexts(payload); err != nil {
357 return AddressesResult{}, err
358 }
359
360 if result.Indexes, payload, err = ReadUint32s(payload); err != nil {
361 return AddressesResult{}, err
362 }
363
364 if result.Paths, payload, err = ReadTexts(payload); err != nil {
365 return AddressesResult{}, err
366 }
367
368 return result, errTrailing(MethodAddresses, payload)
369 }
370
371 // SyncRequest asks a chain to advance.
372 type SyncRequest struct {
373 // Chain is the wallet's chain name.
374 Chain string
375 // Lookahead is how far past the cursor to match, or zero for the default.
376 Lookahead uint32
377 }
378
379 // Encode renders the request.
380 func (r SyncRequest) Encode() []byte {
381 return PutUint32(PutText(nil, r.Chain), r.Lookahead)
382 }
383
384 // ParseSyncRequest reads the request.
385 func ParseSyncRequest(payload []byte) (SyncRequest, error) {
386 var (
387 request SyncRequest
388 err error
389 )
390
391 if request.Chain, payload, err = ReadText(payload); err != nil {
392 return SyncRequest{}, err
393 }
394
395 if request.Lookahead, payload, err = ReadUint32(payload); err != nil {
396 return SyncRequest{}, err
397 }
398
399 return request, errTrailing(MethodSync, payload)
400 }
401
402 // SyncResult is what a sync did.
403 type SyncResult struct {
404 // From is the height the sync started at.
405 From uint32
406 // Tip and Filters are where the chain ended up.
407 Tip uint32
408 Filters uint32
409 // Scripts is how many scripts were matched.
410 Scripts uint32
411 // Matched is every height the chain matched.
412 Matched []uint32
413 // New is the part of Matched the store had not seen.
414 New []uint32
415 }
416
417 // Encode renders the result.
418 func (r SyncResult) Encode() []byte {
419 var out []byte
420
421 out = PutUint32(out, r.From)
422 out = PutUint32(out, r.Tip)
423 out = PutUint32(out, r.Filters)
424 out = PutUint32(out, r.Scripts)
425 out = PutHeights(out, r.Matched)
426
427 return PutHeights(out, r.New)
428 }
429
430 // ParseSyncResult reads the result.
431 func ParseSyncResult(payload []byte) (SyncResult, error) {
432 var (
433 result SyncResult
434 err error
435 )
436
437 if result.From, payload, err = ReadUint32(payload); err != nil {
438 return SyncResult{}, err
439 }
440
441 if result.Tip, payload, err = ReadUint32(payload); err != nil {
442 return SyncResult{}, err
443 }
444
445 if result.Filters, payload, err = ReadUint32(payload); err != nil {
446 return SyncResult{}, err
447 }
448
449 if result.Scripts, payload, err = ReadUint32(payload); err != nil {
450 return SyncResult{}, err
451 }
452
453 if result.Matched, payload, err = ReadHeights(payload); err != nil {
454 return SyncResult{}, err
455 }
456
457 if result.New, payload, err = ReadHeights(payload); err != nil {
458 return SyncResult{}, err
459 }
460
461 return result, errTrailing(MethodSync, payload)
462 }
463
464 // Heights is a height list, which is what a match answer is.
465 type Heights struct {
466 // Heights are ascending.
467 Heights []uint32
468 }
469
470 // Encode renders the result.
471 func (h Heights) Encode() []byte { return PutHeights(nil, h.Heights) }
472
473 // ParseHeights reads the result.
474 func ParseHeights(method Method, payload []byte) (Heights, error) {
475 heights, rest, err := ReadHeights(payload)
476
477 if err != nil {
478 return Heights{}, err
479 }
480
481 return Heights{Heights: heights}, errTrailing(method, rest)
482 }
483
484 // Chains is the list of chains a wallet drives.
485 type Chains struct {
486 // Names are the chain names, in a stable order.
487 Names []string
488 }
489
490 // Encode renders the list.
491 func (c Chains) Encode() []byte { return PutTexts(nil, c.Names) }
492
493 // ParseChains reads the list.
494 func ParseChains(payload []byte) (Chains, error) {
495 names, rest, err := ReadTexts(payload)
496
497 if err != nil {
498 return Chains{}, err
499 }
500
501 return Chains{Names: names}, errTrailing(MethodChains, rest)
502 }
503
504 // PutUint32s appends a number list: a count, then each number.
505 func PutUint32s(buffer []byte, values []uint32) []byte {
506 buffer = binary.LittleEndian.AppendUint16(buffer, uint16(len(values)))
507
508 for _, value := range values {
509 buffer = PutUint32(buffer, value)
510 }
511
512 return buffer
513 }
514
515 // ReadUint32s reads a number list.
516 func ReadUint32s(payload []byte) ([]uint32, []byte, error) {
517 if len(payload) < 2 {
518 return nil, nil, fmt.Errorf("%w: a number count", ErrBadFrame)
519 }
520
521 count := int(binary.LittleEndian.Uint16(payload[0:2]))
522 payload = payload[2:]
523
524 if len(payload) < count*4 {
525 return nil, nil, fmt.Errorf("%w: %d numbers in %d bytes", ErrBadFrame, count, len(payload))
526 }
527
528 values := make([]uint32, 0, count)
529
530 for index := 0; index < count; index++ {
531 values = append(values, binary.LittleEndian.Uint32(payload[index*4:]))
532 }
533
534 return values, payload[count*4:], nil
535 }
536
537 // Coin is one spendable output the wallet holds.
538 type Coin struct {
539 // TxID is the creating transaction, in wire order.
540 TxID [32]byte
541 // Vout is the output's position.
542 Vout uint32
543 // Value is the amount in satoshis.
544 Value uint64
545 // Height is the confirming block, or zero when unconfirmed.
546 Height uint32
547 }
548
549 // CoinList is the wallet's coins.
550 type CoinList struct {
551 // Coins are newest height first.
552 Coins []Coin
553 }
554
555 // Encode renders the list: a count, then each coin's four fields.
556 func (c CoinList) Encode() []byte {
557 buffer := binary.LittleEndian.AppendUint16(nil, uint16(len(c.Coins)))
558
559 for _, coin := range c.Coins {
560 buffer = append(buffer, coin.TxID[:]...)
561 buffer = binary.LittleEndian.AppendUint32(buffer, coin.Vout)
562 buffer = binary.LittleEndian.AppendUint64(buffer, coin.Value)
563 buffer = binary.LittleEndian.AppendUint32(buffer, coin.Height)
564 }
565
566 return buffer
567 }
568
569 // ParseCoinList reads the list.
570 func ParseCoinList(payload []byte) (CoinList, error) {
571 const size = 32 + 4 + 8 + 4
572
573 if len(payload) < 2 {
574 return CoinList{}, fmt.Errorf("%w: a coin count", ErrBadFrame)
575 }
576
577 count := int(binary.LittleEndian.Uint16(payload[0:2]))
578 payload = payload[2:]
579
580 if len(payload) != count*size {
581 return CoinList{}, fmt.Errorf("%w: %d coins in %d bytes", ErrBadFrame, count, len(payload))
582 }
583
584 coins := make([]Coin, 0, count)
585
586 for index := 0; index < count; index++ {
587 raw := payload[index*size : (index+1)*size]
588
589 var coin Coin
590 copy(coin.TxID[:], raw[0:32])
591
592 coin.Vout = binary.LittleEndian.Uint32(raw[32:36])
593 coin.Value = binary.LittleEndian.Uint64(raw[36:44])
594 coin.Height = binary.LittleEndian.Uint32(raw[44:48])
595
596 coins = append(coins, coin)
597 }
598
599 return CoinList{Coins: coins}, nil
600 }
601