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