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