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