You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

account.go 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. package atto
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "math/big"
  6. "strings"
  7. "filippo.io/edwards25519"
  8. "golang.org/x/crypto/blake2b"
  9. )
  10. // ErrAccountNotFound is used when an account could not be found by the
  11. // queried node.
  12. var ErrAccountNotFound = fmt.Errorf("account has not yet been opened")
  13. // ErrAccountManipulated is used when it seems like an account has been
  14. // manipulated. This probably means someone is trying to steal funds.
  15. var ErrAccountManipulated = fmt.Errorf("the received account info has been manipulated")
  16. // Account holds the public key and address of a Nano account.
  17. type Account struct {
  18. PublicKey *big.Int
  19. Address string
  20. }
  21. type blockInfo struct {
  22. Error string `json:"error"`
  23. Contents Block `json:"contents"`
  24. }
  25. // NewAccount creates a new Account and populates both its fields.
  26. func NewAccount(privateKey *big.Int) (a Account, err error) {
  27. a.PublicKey = derivePublicKey(privateKey)
  28. a.Address, err = getAddress(a.PublicKey)
  29. return
  30. }
  31. // NewAccountFromAddress creates a new Account and populates both its
  32. // fields.
  33. func NewAccountFromAddress(address string) (a Account, err error) {
  34. a.Address = address
  35. a.PublicKey, err = getPublicKeyFromAddress(address)
  36. return
  37. }
  38. func derivePublicKey(privateKey *big.Int) *big.Int {
  39. hashBytes := blake2b.Sum512(bigIntToBytes(privateKey, 32))
  40. scalar := edwards25519.NewScalar().SetBytesWithClamping(hashBytes[:32])
  41. publicKeyBytes := edwards25519.NewIdentityPoint().ScalarBaseMult(scalar).Bytes()
  42. return big.NewInt(0).SetBytes(publicKeyBytes)
  43. }
  44. func getAddress(publicKey *big.Int) (string, error) {
  45. base32PublicKey := base32Encode(publicKey)
  46. hasher, err := blake2b.New(5, nil)
  47. if err != nil {
  48. return "", err
  49. }
  50. publicKeyBytes := bigIntToBytes(publicKey, 32)
  51. if _, err := hasher.Write(publicKeyBytes); err != nil {
  52. return "", err
  53. }
  54. hashBytes := hasher.Sum(nil)
  55. base32Hash := base32Encode(big.NewInt(0).SetBytes(revertBytes(hashBytes)))
  56. address := "nano_" +
  57. strings.Repeat("1", 52-len(base32PublicKey)) + base32PublicKey +
  58. strings.Repeat("1", 8-len(base32Hash)) + base32Hash
  59. return address, nil
  60. }
  61. // FetchAccountInfo fetches the AccountInfo of Account from the given
  62. // node.
  63. //
  64. // It is also verified, that the retreived AccountInfo is valid by
  65. // doing a block_info RPC for the frontier, verifying the signature
  66. // and ensuring that no fields have been changed in the account_info
  67. // response.
  68. //
  69. // May return ErrAccountNotFound or ErrAccountManipulated.
  70. //
  71. // If ErrAccountNotFound is returned, FirstReceive can be used to
  72. // create a first Block and AccountInfo and create the account by then
  73. // submitting this Block.
  74. func (a Account) FetchAccountInfo(node string) (i AccountInfo, err error) {
  75. requestBody := fmt.Sprintf(`{`+
  76. `"action": "account_info",`+
  77. `"account": "%s",`+
  78. `"representative": "true"`+
  79. `}`, a.Address)
  80. responseBytes, err := doRPC(requestBody, node)
  81. if err != nil {
  82. return
  83. }
  84. if err = json.Unmarshal(responseBytes, &i); err != nil {
  85. return
  86. }
  87. // Need to check i.Error because of
  88. // https://github.com/nanocurrency/nano-node/issues/1782.
  89. if i.Error == "Account not found" {
  90. err = ErrAccountNotFound
  91. } else if i.Error != "" {
  92. err = fmt.Errorf("could not fetch account info: %s", i.Error)
  93. } else {
  94. i.PublicKey = a.PublicKey
  95. i.Address = a.Address
  96. err = a.verifyInfo(i, node)
  97. }
  98. return
  99. }
  100. // verifyInfo gets the frontier block of info, ensures that Hash,
  101. // Representative and Balance match and verifies it's signature.
  102. func (a Account) verifyInfo(info AccountInfo, node string) error {
  103. requestBody := fmt.Sprintf(`{`+
  104. `"action": "block_info",`+
  105. `"json_block": "true",`+
  106. `"hash": "%s"`+
  107. `}`, info.Frontier)
  108. responseBytes, err := doRPC(requestBody, node)
  109. if err != nil {
  110. return err
  111. }
  112. var block blockInfo
  113. if err = json.Unmarshal(responseBytes, &block); err != nil {
  114. return err
  115. }
  116. if info.Error != "" {
  117. return fmt.Errorf("could not get block info: %s", info.Error)
  118. }
  119. hash, err := block.Contents.Hash()
  120. if err != nil {
  121. return err
  122. }
  123. if err = block.Contents.verifySignature(a); err == errInvalidSignature ||
  124. info.Frontier != hash ||
  125. info.Representative != block.Contents.Representative ||
  126. info.Balance != block.Contents.Balance {
  127. return ErrAccountManipulated
  128. }
  129. return err
  130. }
  131. // FetchPending fetches all unreceived blocks of Account from node.
  132. func (a Account) FetchPending(node string) ([]Pending, error) {
  133. requestBody := fmt.Sprintf(`{`+
  134. `"action": "pending", `+
  135. `"account": "%s", `+
  136. `"include_only_confirmed": "true", `+
  137. `"source": "true"`+
  138. `}`, a.Address)
  139. responseBytes, err := doRPC(requestBody, node)
  140. if err != nil {
  141. return nil, err
  142. }
  143. var pending internalPending
  144. err = json.Unmarshal(responseBytes, &pending)
  145. // Need to check pending.Error because of
  146. // https://github.com/nanocurrency/nano-node/issues/1782.
  147. if err == nil && pending.Error != "" {
  148. err = fmt.Errorf("could not fetch unreceived sends: %s", pending.Error)
  149. }
  150. return internalPendingToPending(pending), err
  151. }
  152. // FirstReceive creates the first receive block of an account. The block
  153. // will still be missing its signature and work. FirstReceive will also
  154. // return AccountInfo, which can be used to create further blocks.
  155. func (a Account) FirstReceive(pending Pending, representative string) (AccountInfo, Block, error) {
  156. block := Block{
  157. Type: "state",
  158. SubType: SubTypeReceive,
  159. Account: a.Address,
  160. Previous: "0000000000000000000000000000000000000000000000000000000000000000",
  161. Representative: representative,
  162. Balance: pending.Amount,
  163. Link: pending.Hash,
  164. }
  165. hash, err := block.Hash()
  166. if err != nil {
  167. return AccountInfo{}, Block{}, err
  168. }
  169. info := AccountInfo{
  170. Frontier: hash,
  171. Representative: block.Representative,
  172. Balance: block.Balance,
  173. PublicKey: a.PublicKey,
  174. Address: a.Address,
  175. }
  176. return info, block, err
  177. }