github.com/ethersphere/bee/v2@v2.2.0/pkg/crypto/clef/clef.go (about) 1 // Copyright 2020 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package clef 6 7 import ( 8 "crypto/ecdsa" 9 "errors" 10 "fmt" 11 "math/big" 12 "os" 13 "path/filepath" 14 "runtime" 15 16 "github.com/ethereum/go-ethereum/accounts" 17 "github.com/ethereum/go-ethereum/common" 18 "github.com/ethereum/go-ethereum/common/hexutil" 19 "github.com/ethereum/go-ethereum/core/types" 20 "github.com/ethersphere/bee/v2/pkg/crypto" 21 "github.com/ethersphere/bee/v2/pkg/crypto/eip712" 22 ) 23 24 var ( 25 ErrNoAccounts = errors.New("no accounts found in clef") 26 ErrAccountNotAvailable = errors.New("account not available in clef") 27 clefRecoveryMessage = []byte("public key recovery message") 28 ) 29 30 // ExternalSignerInterface is the interface for the clef client from go-ethereum. 31 type ExternalSignerInterface interface { 32 SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) 33 SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) 34 Accounts() []accounts.Account 35 } 36 37 // Client is the interface for rpc.RpcClient. 38 type Client interface { 39 Call(result interface{}, method string, args ...interface{}) error 40 } 41 42 type clefSigner struct { 43 client Client // low-level rpc client to clef as ExternalSigner does not implement account_signTypedData 44 clef ExternalSignerInterface 45 account accounts.Account // the account this signer will use 46 pubKey *ecdsa.PublicKey // the public key for the account 47 } 48 49 // DefaultIpcPath returns the os-dependent default ipc path for clef. 50 func DefaultIpcPath() (string, error) { 51 socket := "clef.ipc" 52 // on windows clef uses top level pipes 53 if runtime.GOOS == "windows" { 54 return `\\.\pipe\` + socket, nil 55 } 56 57 home, err := os.UserHomeDir() 58 if err != nil { 59 return "", err 60 } 61 62 // on mac os clef defaults to ~/Library/Signer/clef.ipc 63 if runtime.GOOS == "darwin" { 64 return filepath.Join(home, "Library", "Signer", socket), nil 65 } 66 67 // on unix clef defaults to ~/.clef/clef.ipc 68 return filepath.Join(home, ".clef", socket), nil 69 } 70 71 func selectAccount(clef ExternalSignerInterface, ethAddress *common.Address) (accounts.Account, error) { 72 // get the list of available ethereum accounts 73 clefAccounts := clef.Accounts() 74 if len(clefAccounts) == 0 { 75 return accounts.Account{}, ErrNoAccounts 76 } 77 78 if ethAddress == nil { 79 // pick the first account as the one we use 80 return clefAccounts[0], nil 81 } 82 83 for _, availableAccount := range clefAccounts { 84 if availableAccount.Address == *ethAddress { 85 return availableAccount, nil 86 } 87 } 88 return accounts.Account{}, ErrAccountNotAvailable 89 } 90 91 // NewSigner creates a new connection to the signer at endpoint. 92 // If ethAddress is nil the account with index 0 will be selected. Otherwise it will verify the requested account actually exists. 93 // As clef does not expose public keys it signs a test message to recover the public key. 94 func NewSigner(clef ExternalSignerInterface, client Client, recoverFunc crypto.RecoverFunc, ethAddress *common.Address) (signer crypto.Signer, err error) { 95 account, err := selectAccount(clef, ethAddress) 96 if err != nil { 97 return nil, err 98 } 99 100 // clef currently does not expose the public key 101 // sign some data so we can recover it 102 sig, err := clef.SignData(account, accounts.MimetypeTextPlain, clefRecoveryMessage) 103 if err != nil { 104 return nil, err 105 } 106 107 pubKey, err := recoverFunc(sig, clefRecoveryMessage) 108 if err != nil { 109 return nil, err 110 } 111 112 return &clefSigner{ 113 client: client, 114 clef: clef, 115 account: account, 116 pubKey: pubKey, 117 }, nil 118 } 119 120 // PublicKey returns the public key recovered during creation. 121 func (c *clefSigner) PublicKey() (*ecdsa.PublicKey, error) { 122 return c.pubKey, nil 123 } 124 125 // SignData signs with the text/plain type which is the standard Ethereum prefix method. 126 func (c *clefSigner) Sign(data []byte) ([]byte, error) { 127 return c.clef.SignData(c.account, accounts.MimetypeTextPlain, data) 128 } 129 130 // SignTx signs an ethereum transaction. 131 func (c *clefSigner) SignTx(transaction *types.Transaction, chainID *big.Int) (*types.Transaction, error) { 132 // chainId is nil here because it is set on the clef side 133 tx, err := c.clef.SignTx(c.account, transaction, nil) 134 if err != nil { 135 return nil, err 136 } 137 138 if chainID.Cmp(tx.ChainId()) != 0 { 139 return nil, fmt.Errorf("misconfigured signer: wrong chain id %d; wanted %d", tx.ChainId(), chainID) 140 } 141 142 return tx, nil 143 } 144 145 // EthereumAddress returns the ethereum address this signer uses. 146 func (c *clefSigner) EthereumAddress() (common.Address, error) { 147 return c.account.Address, nil 148 } 149 150 // SignTypedData signs data according to eip712. 151 func (c *clefSigner) SignTypedData(typedData *eip712.TypedData) ([]byte, error) { 152 var sig hexutil.Bytes 153 err := c.client.Call(&sig, "account_signTypedData", c.account.Address, typedData) 154 if err != nil { 155 return nil, err 156 } 157 158 return sig, nil 159 }