uiapi.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. // Copyright 2019 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // The go-ethereum library is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. package core
  17. import (
  18. "context"
  19. "encoding/json"
  20. "errors"
  21. "fmt"
  22. "io/ioutil"
  23. "math/big"
  24. "github.com/ethereum/go-ethereum/accounts"
  25. "github.com/ethereum/go-ethereum/accounts/keystore"
  26. "github.com/ethereum/go-ethereum/common"
  27. "github.com/ethereum/go-ethereum/common/math"
  28. "github.com/ethereum/go-ethereum/crypto"
  29. )
  30. // SignerUIAPI implements methods Clef provides for a UI to query, in the bidirectional communication
  31. // channel.
  32. // This API is considered secure, since a request can only
  33. // ever arrive from the UI -- and the UI is capable of approving any action, thus we can consider these
  34. // requests pre-approved.
  35. // NB: It's very important that these methods are not ever exposed on the external service
  36. // registry.
  37. type UIServerAPI struct {
  38. extApi *SignerAPI
  39. am *accounts.Manager
  40. }
  41. // NewUIServerAPI creates a new UIServerAPI
  42. func NewUIServerAPI(extapi *SignerAPI) *UIServerAPI {
  43. return &UIServerAPI{extapi, extapi.am}
  44. }
  45. // List available accounts. As opposed to the external API definition, this method delivers
  46. // the full Account object and not only Address.
  47. // Example call
  48. // {"jsonrpc":"2.0","method":"clef_listAccounts","params":[], "id":4}
  49. func (s *UIServerAPI) ListAccounts(ctx context.Context) ([]accounts.Account, error) {
  50. var accs []accounts.Account
  51. for _, wallet := range s.am.Wallets() {
  52. accs = append(accs, wallet.Accounts()...)
  53. }
  54. return accs, nil
  55. }
  56. // rawWallet is a JSON representation of an accounts.Wallet interface, with its
  57. // data contents extracted into plain fields.
  58. type rawWallet struct {
  59. URL string `json:"url"`
  60. Status string `json:"status"`
  61. Failure string `json:"failure,omitempty"`
  62. Accounts []accounts.Account `json:"accounts,omitempty"`
  63. }
  64. // ListWallets will return a list of wallets that clef manages
  65. // Example call
  66. // {"jsonrpc":"2.0","method":"clef_listWallets","params":[], "id":5}
  67. func (s *UIServerAPI) ListWallets() []rawWallet {
  68. wallets := make([]rawWallet, 0) // return [] instead of nil if empty
  69. for _, wallet := range s.am.Wallets() {
  70. status, failure := wallet.Status()
  71. raw := rawWallet{
  72. URL: wallet.URL().String(),
  73. Status: status,
  74. Accounts: wallet.Accounts(),
  75. }
  76. if failure != nil {
  77. raw.Failure = failure.Error()
  78. }
  79. wallets = append(wallets, raw)
  80. }
  81. return wallets
  82. }
  83. // DeriveAccount requests a HD wallet to derive a new account, optionally pinning
  84. // it for later reuse.
  85. // Example call
  86. // {"jsonrpc":"2.0","method":"clef_deriveAccount","params":["ledger://","m/44'/60'/0'", false], "id":6}
  87. func (s *UIServerAPI) DeriveAccount(url string, path string, pin *bool) (accounts.Account, error) {
  88. wallet, err := s.am.Wallet(url)
  89. if err != nil {
  90. return accounts.Account{}, err
  91. }
  92. derivPath, err := accounts.ParseDerivationPath(path)
  93. if err != nil {
  94. return accounts.Account{}, err
  95. }
  96. if pin == nil {
  97. pin = new(bool)
  98. }
  99. return wallet.Derive(derivPath, *pin)
  100. }
  101. // fetchKeystore retrieves the encrypted keystore from the account manager.
  102. func fetchKeystore(am *accounts.Manager) *keystore.KeyStore {
  103. return am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
  104. }
  105. // ImportRawKey stores the given hex encoded ECDSA key into the key directory,
  106. // encrypting it with the passphrase.
  107. // Example call (should fail on password too short)
  108. // {"jsonrpc":"2.0","method":"clef_importRawKey","params":["1111111111111111111111111111111111111111111111111111111111111111","test"], "id":6}
  109. func (s *UIServerAPI) ImportRawKey(privkey string, password string) (accounts.Account, error) {
  110. key, err := crypto.HexToECDSA(privkey)
  111. if err != nil {
  112. return accounts.Account{}, err
  113. }
  114. if err := ValidatePasswordFormat(password); err != nil {
  115. return accounts.Account{}, fmt.Errorf("password requirements not met: %v", err)
  116. }
  117. // No error
  118. return fetchKeystore(s.am).ImportECDSA(key, password)
  119. }
  120. // OpenWallet initiates a hardware wallet opening procedure, establishing a USB
  121. // connection and attempting to authenticate via the provided passphrase. Note,
  122. // the method may return an extra challenge requiring a second open (e.g. the
  123. // Trezor PIN matrix challenge).
  124. // Example
  125. // {"jsonrpc":"2.0","method":"clef_openWallet","params":["ledger://",""], "id":6}
  126. func (s *UIServerAPI) OpenWallet(url string, passphrase *string) error {
  127. wallet, err := s.am.Wallet(url)
  128. if err != nil {
  129. return err
  130. }
  131. pass := ""
  132. if passphrase != nil {
  133. pass = *passphrase
  134. }
  135. return wallet.Open(pass)
  136. }
  137. // ChainId returns the chainid in use for Eip-155 replay protection
  138. // Example call
  139. // {"jsonrpc":"2.0","method":"clef_chainId","params":[], "id":8}
  140. func (s *UIServerAPI) ChainId() math.HexOrDecimal64 {
  141. return (math.HexOrDecimal64)(s.extApi.chainID.Uint64())
  142. }
  143. // SetChainId sets the chain id to use when signing transactions.
  144. // Example call to set Ropsten:
  145. // {"jsonrpc":"2.0","method":"clef_setChainId","params":["3"], "id":8}
  146. func (s *UIServerAPI) SetChainId(id math.HexOrDecimal64) math.HexOrDecimal64 {
  147. s.extApi.chainID = new(big.Int).SetUint64(uint64(id))
  148. return s.ChainId()
  149. }
  150. // Export returns encrypted private key associated with the given address in web3 keystore format.
  151. // Example
  152. // {"jsonrpc":"2.0","method":"clef_export","params":["0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a"], "id":4}
  153. func (s *UIServerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) {
  154. // Look up the wallet containing the requested signer
  155. wallet, err := s.am.Find(accounts.Account{Address: addr})
  156. if err != nil {
  157. return nil, err
  158. }
  159. if wallet.URL().Scheme != keystore.KeyStoreScheme {
  160. return nil, fmt.Errorf("account is not a keystore-account")
  161. }
  162. return ioutil.ReadFile(wallet.URL().Path)
  163. }
  164. // Import tries to import the given keyJSON in the local keystore. The keyJSON data is expected to be
  165. // in web3 keystore format. It will decrypt the keyJSON with the given passphrase and on successful
  166. // decryption it will encrypt the key with the given newPassphrase and store it in the keystore.
  167. // Example (the address in question has privkey `11...11`):
  168. // {"jsonrpc":"2.0","method":"clef_import","params":[{"address":"19e7e376e7c213b7e7e7e46cc70a5dd086daff2a","crypto":{"cipher":"aes-128-ctr","ciphertext":"33e4cd3756091d037862bb7295e9552424a391a6e003272180a455ca2a9fb332","cipherparams":{"iv":"b54b263e8f89c42bb219b6279fba5cce"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"e4ca94644fd30569c1b1afbbc851729953c92637b7fe4bb9840bbb31ffbc64a5"},"mac":"f4092a445c2b21c0ef34f17c9cd0d873702b2869ec5df4439a0c2505823217e7"},"id":"216c7eac-e8c1-49af-a215-fa0036f29141","version":3},"test","yaddayadda"], "id":4}
  169. func (api *UIServerAPI) Import(ctx context.Context, keyJSON json.RawMessage, oldPassphrase, newPassphrase string) (accounts.Account, error) {
  170. be := api.am.Backends(keystore.KeyStoreType)
  171. if len(be) == 0 {
  172. return accounts.Account{}, errors.New("password based accounts not supported")
  173. }
  174. if err := ValidatePasswordFormat(newPassphrase); err != nil {
  175. return accounts.Account{}, fmt.Errorf("password requirements not met: %v", err)
  176. }
  177. return be[0].(*keystore.KeyStore).Import(keyJSON, oldPassphrase, newPassphrase)
  178. }
  179. // New creates a new password protected Account. The private key is protected with
  180. // the given password. Users are responsible to backup the private key that is stored
  181. // in the keystore location that was specified when this API was created.
  182. // This method is the same as New on the external API, the difference being that
  183. // this implementation does not ask for confirmation, since it's initiated by
  184. // the user
  185. func (api *UIServerAPI) New(ctx context.Context) (common.Address, error) {
  186. return api.extApi.newAccount()
  187. }
  188. // Other methods to be added, not yet implemented are:
  189. // - Ruleset interaction: add rules, attest rulefiles
  190. // - Store metadata about accounts, e.g. naming of accounts