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  }