github.com/aidoskuneen/adk-node@v0.0.0-20220315131952-2e32567cb7f4/signer/core/uiapi.go (about)

     1  // Copyright 2021 The adkgo Authors
     2  // This file is part of the adkgo library (adapted for adkgo from go--ethereum v1.10.8).
     3  //
     4  // the adkgo 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 adkgo 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 adkgo 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/aidoskuneen/adk-node/accounts"
    28  	"github.com/aidoskuneen/adk-node/accounts/keystore"
    29  	"github.com/aidoskuneen/adk-node/common"
    30  	"github.com/aidoskuneen/adk-node/common/math"
    31  	"github.com/aidoskuneen/adk-node/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'/60'/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 ECDSA 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.HexToECDSA(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).ImportECDSA(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 (e.g. the
   136  // Trezor PIN matrix challenge).
   137  // Example
   138  // {"jsonrpc":"2.0","method":"clef_openWallet","params":["ledger://",""], "id":6}
   139  func (s *UIServerAPI) OpenWallet(url string, passphrase *string) error {
   140  	wallet, err := s.am.Wallet(url)
   141  	if err != nil {
   142  		return err
   143  	}
   144  	pass := ""
   145  	if passphrase != nil {
   146  		pass = *passphrase
   147  	}
   148  	return wallet.Open(pass)
   149  }
   150  
   151  // ChainId returns the chainid in use for Eip-155 replay protection
   152  // Example call
   153  // {"jsonrpc":"2.0","method":"clef_chainId","params":[], "id":8}
   154  func (s *UIServerAPI) ChainId() math.HexOrDecimal64 {
   155  	return (math.HexOrDecimal64)(s.extApi.chainID.Uint64())
   156  }
   157  
   158  // SetChainId sets the chain id to use when signing transactions.
   159  // Example call to set Ropsten:
   160  // {"jsonrpc":"2.0","method":"clef_setChainId","params":["3"], "id":8}
   161  func (s *UIServerAPI) SetChainId(id math.HexOrDecimal64) math.HexOrDecimal64 {
   162  	s.extApi.chainID = new(big.Int).SetUint64(uint64(id))
   163  	return s.ChainId()
   164  }
   165  
   166  // Export returns encrypted private key associated with the given address in web3 keystore format.
   167  // Example
   168  // {"jsonrpc":"2.0","method":"clef_export","params":["0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a"], "id":4}
   169  func (s *UIServerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) {
   170  	// Look up the wallet containing the requested signer
   171  	wallet, err := s.am.Find(accounts.Account{Address: addr})
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  	if wallet.URL().Scheme != keystore.KeyStoreScheme {
   176  		return nil, fmt.Errorf("account is not a keystore-account")
   177  	}
   178  	return ioutil.ReadFile(wallet.URL().Path)
   179  }
   180  
   181  // Import tries to import the given keyJSON in the local keystore. The keyJSON data is expected to be
   182  // in web3 keystore format. It will decrypt the keyJSON with the given passphrase and on successful
   183  // decryption it will encrypt the key with the given newPassphrase and store it in the keystore.
   184  // Example (the address in question has privkey `11...11`):
   185  // {"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}
   186  func (api *UIServerAPI) Import(ctx context.Context, keyJSON json.RawMessage, oldPassphrase, newPassphrase string) (accounts.Account, error) {
   187  	be := api.am.Backends(keystore.KeyStoreType)
   188  
   189  	if len(be) == 0 {
   190  		return accounts.Account{}, errors.New("password based accounts not supported")
   191  	}
   192  	if err := ValidatePasswordFormat(newPassphrase); err != nil {
   193  		return accounts.Account{}, fmt.Errorf("password requirements not met: %v", err)
   194  	}
   195  	return be[0].(*keystore.KeyStore).Import(keyJSON, oldPassphrase, newPassphrase)
   196  }
   197  
   198  // New creates a new password protected Account. The private key is protected with
   199  // the given password. Users are responsible to backup the private key that is stored
   200  // in the keystore location that was specified when this API was created.
   201  // This method is the same as New on the external API, the difference being that
   202  // this implementation does not ask for confirmation, since it's initiated by
   203  // the user
   204  func (api *UIServerAPI) New(ctx context.Context) (common.Address, error) {
   205  	return api.extApi.newAccount()
   206  }
   207  
   208  // Other methods to be added, not yet implemented are:
   209  // - Ruleset interaction: add rules, attest rulefiles
   210  // - Store metadata about accounts, e.g. naming of accounts