-
Notifications
You must be signed in to change notification settings - Fork 9
/
clef.go
149 lines (125 loc) · 4.49 KB
/
clef.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package clef
import (
"crypto/ecdsa"
"errors"
"math/big"
"os"
"path/filepath"
"runtime"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/gauss-project/aurorafs/pkg/crypto"
"github.com/gauss-project/aurorafs/pkg/crypto/eip712"
)
var (
ErrNoAccounts = errors.New("no accounts found in clef")
ErrAccountNotAvailable = errors.New("account not available in clef")
clefRecoveryMessage = []byte("public key recovery message")
)
// ExternalSignerInterface is the interface for the clef client from go-ethereum.
type ExternalSignerInterface interface {
SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error)
SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
Accounts() []accounts.Account
}
// Client is the interface for rpc.RpcClient.
type Client interface {
Call(result interface{}, method string, args ...interface{}) error
}
type clefSigner struct {
client Client // low-level rpc client to clef as ExternalSigner does not implement account_signTypedData
clef ExternalSignerInterface
account accounts.Account // the account this signer will use
pubKey *ecdsa.PublicKey // the public key for the account
}
func (c *clefSigner) PrivateKey() *ecdsa.PrivateKey {
panic("implement me")
}
// DefaultIpcPath returns the os-dependent default ipc path for clef.
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
}
func selectAccount(clef ExternalSignerInterface, ethAddress *common.Address) (accounts.Account, error) {
// get the list of available ethereum accounts
clefAccounts := clef.Accounts()
if len(clefAccounts) == 0 {
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
}
}
return accounts.Account{}, ErrAccountNotAvailable
}
// 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
}
// 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{
client: client,
clef: clef,
account: account,
pubKey: pubKey,
}, nil
}
// PublicKey returns the public key recovered during creation.
func (c *clefSigner) PublicKey() (*ecdsa.PublicKey, error) {
return c.pubKey, nil
}
// SignData signs with the text/plain type which is the standard Ethereum prefix method.
func (c *clefSigner) Sign(data []byte) ([]byte, error) {
return c.clef.SignData(c.account, accounts.MimetypeTextPlain, data)
}
// SignTx signs an ethereum transaction.
func (c *clefSigner) SignTx(transaction *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
// chainId is nil here because it is set on the clef side
return c.clef.SignTx(c.account, transaction, nil)
}
// EthereumAddress returns the ethereum address this signer uses.
func (c *clefSigner) EthereumAddress() (common.Address, error) {
return c.account.Address, nil
}
// 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
}