You need to sign in or sign up before continuing.
clef.go 4.56 KB
Newer Older
1 2 3 4 5 6 7 8 9
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package clef

import (
	"crypto/ecdsa"
	"errors"
10
	"math/big"
11 12 13 14 15
	"os"
	"path/filepath"
	"runtime"

	"github.com/ethereum/go-ethereum/accounts"
16
	"github.com/ethereum/go-ethereum/common"
17
	"github.com/ethereum/go-ethereum/common/hexutil"
18
	"github.com/ethereum/go-ethereum/core/types"
19
	"github.com/ethersphere/bee/pkg/crypto"
20
	"github.com/ethersphere/bee/pkg/crypto/eip712"
21 22 23
)

var (
24 25 26
	ErrNoAccounts          = errors.New("no accounts found in clef")
	ErrAccountNotAvailable = errors.New("account not available in clef")
	clefRecoveryMessage    = []byte("public key recovery message")
27 28
)

29
// ExternalSignerInterface is the interface for the clef client from go-ethereum.
30 31
type ExternalSignerInterface interface {
	SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error)
32
	SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
33 34 35
	Accounts() []accounts.Account
}

36 37 38 39 40
// Client is the interface for rpc.RpcClient.
type Client interface {
	Call(result interface{}, method string, args ...interface{}) error
}

41
type clefSigner struct {
42
	client  Client // low-level rpc client to clef as ExternalSigner does not implement account_signTypedData
43 44 45 46 47
	clef    ExternalSignerInterface
	account accounts.Account // the account this signer will use
	pubKey  *ecdsa.PublicKey // the public key for the account
}

48
// DefaultIpcPath returns the os-dependent default ipc path for clef.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
func DefaultIpcPath() (string, error) {
	socket := "clef.ipc"
	// on windows clef uses top level pipes
	if runtime.GOOS == "windows" {
		return `\\.\pipe\` + socket, nil
	}

	home, err := os.UserHomeDir()
	if err != nil {
		return "", err
	}

	// on mac os clef defaults to ~/Library/Signer/clef.ipc
	if runtime.GOOS == "darwin" {
		return filepath.Join(home, "Library", "Signer", socket), nil
	}

	// on unix clef defaults to ~/.clef/clef.ipc
	return filepath.Join(home, ".clef", socket), nil
}

70
func selectAccount(clef ExternalSignerInterface, ethAddress *common.Address) (accounts.Account, error) {
71 72 73
	// get the list of available ethereum accounts
	clefAccounts := clef.Accounts()
	if len(clefAccounts) == 0 {
74 75 76 77 78 79 80 81 82 83 84 85
		return accounts.Account{}, ErrNoAccounts
	}

	if ethAddress == nil {
		// pick the first account as the one we use
		return clefAccounts[0], nil
	}

	for _, availableAccount := range clefAccounts {
		if availableAccount.Address == *ethAddress {
			return availableAccount, nil
		}
86
	}
87 88
	return accounts.Account{}, ErrAccountNotAvailable
}
89

90 91 92 93 94 95 96 97
// NewSigner creates a new connection to the signer at endpoint.
// If ethAddress is nil the account with index 0 will be selected. Otherwise it will verify the requested account actually exists.
// As clef does not expose public keys it signs a test message to recover the public key.
func NewSigner(clef ExternalSignerInterface, client Client, recoverFunc crypto.RecoverFunc, ethAddress *common.Address) (signer crypto.Signer, err error) {
	account, err := selectAccount(clef, ethAddress)
	if err != nil {
		return nil, err
	}
98 99 100 101 102 103 104 105 106 107 108 109 110 111

	// clef currently does not expose the public key
	// sign some data so we can recover it
	sig, err := clef.SignData(account, accounts.MimetypeTextPlain, clefRecoveryMessage)
	if err != nil {
		return nil, err
	}

	pubKey, err := recoverFunc(sig, clefRecoveryMessage)
	if err != nil {
		return nil, err
	}

	return &clefSigner{
112
		client:  client,
113 114 115 116 117 118
		clef:    clef,
		account: account,
		pubKey:  pubKey,
	}, nil
}

119
// PublicKey returns the public key recovered during creation.
120 121 122 123
func (c *clefSigner) PublicKey() (*ecdsa.PublicKey, error) {
	return c.pubKey, nil
}

124
// SignData signs with the text/plain type which is the standard Ethereum prefix method.
125 126 127
func (c *clefSigner) Sign(data []byte) ([]byte, error) {
	return c.clef.SignData(c.account, accounts.MimetypeTextPlain, data)
}
128

129
// SignTx signs an ethereum transaction.
130
func (c *clefSigner) SignTx(transaction *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
131 132 133 134
	// chainId is nil here because it is set on the clef side
	return c.clef.SignTx(c.account, transaction, nil)
}

135
// EthereumAddress returns the ethereum address this signer uses.
136 137 138
func (c *clefSigner) EthereumAddress() (common.Address, error) {
	return c.account.Address, nil
}
139 140 141 142 143 144 145 146 147 148 149

// SignTypedData signs data according to eip712.
func (c *clefSigner) SignTypedData(typedData *eip712.TypedData) ([]byte, error) {
	var sig hexutil.Bytes
	err := c.client.Call(&sig, "account_signTypedData", c.account.Address, typedData)
	if err != nil {
		return nil, err
	}

	return sig, nil
}