Skip to content

Commit 6814797

Browse files
rjl493456442karalabe
authored andcommitted
accounts, cmd, contracts, les: integrate clef for transaction signing (#19783)
* accounts, cmd, contracts, les: integrate clef for transaction signing * accounts, cmd/checkpoint-admin, signer/core: minor fixups
1 parent 59a3198 commit 6814797

File tree

8 files changed

+61
-120
lines changed

8 files changed

+61
-120
lines changed

accounts/abi/bind/auth.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"io/ioutil"
2424

2525
"github.com/ethereum/go-ethereum/accounts"
26+
"github.com/ethereum/go-ethereum/accounts/external"
2627
"github.com/ethereum/go-ethereum/accounts/keystore"
2728
"github.com/ethereum/go-ethereum/common"
2829
"github.com/ethereum/go-ethereum/core/types"
@@ -43,7 +44,7 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) {
4344
return NewKeyedTransactor(key.PrivateKey), nil
4445
}
4546

46-
// NewKeystoreTransactor is a utility method to easily create a transaction signer from
47+
// NewKeyStoreTransactor is a utility method to easily create a transaction signer from
4748
// an decrypted key from a keystore
4849
func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) {
4950
return &TransactOpts{
@@ -79,3 +80,17 @@ func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts {
7980
},
8081
}
8182
}
83+
84+
// NewClefTransactor is a utility method to easily create a transaction signer
85+
// with a clef backend.
86+
func NewClefTransactor(clef *external.ExternalSigner, account accounts.Account) *TransactOpts {
87+
return &TransactOpts{
88+
From: account.Address,
89+
Signer: func(signer types.Signer, address common.Address, transaction *types.Transaction) (*types.Transaction, error) {
90+
if address != account.Address {
91+
return nil, errors.New("not authorized to sign this account")
92+
}
93+
return clef.SignTx(account, transaction, nil) // Clef enforces its own chain id
94+
},
95+
}
96+
}

accounts/external/backend.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,18 +182,21 @@ func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]by
182182

183183
func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
184184
res := ethapi.SignTransactionResult{}
185-
to := common.NewMixedcaseAddress(*tx.To())
186185
data := hexutil.Bytes(tx.Data())
186+
var to *common.MixedcaseAddress
187+
if tx.To() != nil {
188+
t := common.NewMixedcaseAddress(*tx.To())
189+
to = &t
190+
}
187191
args := &core.SendTxArgs{
188192
Data: &data,
189193
Nonce: hexutil.Uint64(tx.Nonce()),
190194
Value: hexutil.Big(*tx.Value()),
191195
Gas: hexutil.Uint64(tx.Gas()),
192196
GasPrice: hexutil.Big(*tx.GasPrice()),
193-
To: &to,
197+
To: to,
194198
From: common.NewMixedcaseAddress(account.Address),
195199
}
196-
197200
if err := api.client.Call(&res, "account_signTransaction", args); err != nil {
198201
return nil, err
199202
}

cmd/checkpoint-admin/common.go

Lines changed: 8 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,13 @@
1717
package main
1818

1919
import (
20-
"io/ioutil"
2120
"strconv"
22-
"strings"
2321

24-
"github.com/ethereum/go-ethereum/accounts/keystore"
22+
"github.com/ethereum/go-ethereum/accounts"
23+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
24+
"github.com/ethereum/go-ethereum/accounts/external"
2525
"github.com/ethereum/go-ethereum/cmd/utils"
2626
"github.com/ethereum/go-ethereum/common"
27-
"github.com/ethereum/go-ethereum/console"
2827
"github.com/ethereum/go-ethereum/contracts/checkpointoracle"
2928
"github.com/ethereum/go-ethereum/ethclient"
3029
"github.com/ethereum/go-ethereum/params"
@@ -111,56 +110,11 @@ func newContract(client *rpc.Client) (common.Address, *checkpointoracle.Checkpoi
111110
return addr, contract
112111
}
113112

114-
// promptPassphrase prompts the user for a passphrase.
115-
// Set confirmation to true to require the user to confirm the passphrase.
116-
func promptPassphrase(confirmation bool) string {
117-
passphrase, err := console.Stdin.PromptPassword("Passphrase: ")
113+
// newClefSigner sets up a clef backend and returns a clef transaction signer.
114+
func newClefSigner(ctx *cli.Context) *bind.TransactOpts {
115+
clef, err := external.NewExternalSigner(ctx.String(clefURLFlag.Name))
118116
if err != nil {
119-
utils.Fatalf("Failed to read passphrase: %v", err)
117+
utils.Fatalf("Failed to create clef signer %v", err)
120118
}
121-
122-
if confirmation {
123-
confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
124-
if err != nil {
125-
utils.Fatalf("Failed to read passphrase confirmation: %v", err)
126-
}
127-
if passphrase != confirm {
128-
utils.Fatalf("Passphrases do not match")
129-
}
130-
}
131-
return passphrase
132-
}
133-
134-
// getPassphrase obtains a passphrase given by the user. It first checks the
135-
// --password command line flag and ultimately prompts the user for a
136-
// passphrase.
137-
func getPassphrase(ctx *cli.Context) string {
138-
passphraseFile := ctx.String(utils.PasswordFileFlag.Name)
139-
if passphraseFile != "" {
140-
content, err := ioutil.ReadFile(passphraseFile)
141-
if err != nil {
142-
utils.Fatalf("Failed to read passphrase file '%s': %v",
143-
passphraseFile, err)
144-
}
145-
return strings.TrimRight(string(content), "\r\n")
146-
}
147-
// Otherwise prompt the user for the passphrase.
148-
return promptPassphrase(false)
149-
}
150-
151-
// getKey retrieves the user key through specified key file.
152-
func getKey(ctx *cli.Context) *keystore.Key {
153-
// Read key from file.
154-
keyFile := ctx.GlobalString(keyFileFlag.Name)
155-
keyJson, err := ioutil.ReadFile(keyFile)
156-
if err != nil {
157-
utils.Fatalf("Failed to read the keyfile at '%s': %v", keyFile, err)
158-
}
159-
// Decrypt key with passphrase.
160-
passphrase := getPassphrase(ctx)
161-
key, err := keystore.DecryptKey(keyJson, passphrase)
162-
if err != nil {
163-
utils.Fatalf("Failed to decrypt user key '%s': %v", keyFile, err)
164-
}
165-
return key
119+
return bind.NewClefTransactor(clef, accounts.Account{Address: common.HexToAddress(ctx.String(signerFlag.Name))})
166120
}

cmd/checkpoint-admin/exec.go

Lines changed: 25 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
"time"
2727

2828
"github.com/ethereum/go-ethereum/accounts"
29-
"github.com/ethereum/go-ethereum/accounts/abi/bind"
3029
"github.com/ethereum/go-ethereum/cmd/utils"
3130
"github.com/ethereum/go-ethereum/common"
3231
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -46,10 +45,9 @@ var commandDeploy = cli.Command{
4645
Flags: []cli.Flag{
4746
nodeURLFlag,
4847
clefURLFlag,
48+
signerFlag,
4949
signersFlag,
5050
thresholdFlag,
51-
keyFileFlag,
52-
utils.PasswordFileFlag,
5351
},
5452
Action: utils.MigrateFlags(deploy),
5553
}
@@ -60,12 +58,10 @@ var commandSign = cli.Command{
6058
Flags: []cli.Flag{
6159
nodeURLFlag,
6260
clefURLFlag,
61+
signerFlag,
6362
indexFlag,
6463
hashFlag,
6564
oracleFlag,
66-
keyFileFlag,
67-
signerFlag,
68-
utils.PasswordFileFlag,
6965
},
7066
Action: utils.MigrateFlags(sign),
7167
}
@@ -75,10 +71,10 @@ var commandPublish = cli.Command{
7571
Usage: "Publish a checkpoint into the oracle",
7672
Flags: []cli.Flag{
7773
nodeURLFlag,
74+
clefURLFlag,
75+
signerFlag,
7876
indexFlag,
7977
signaturesFlag,
80-
keyFileFlag,
81-
utils.PasswordFileFlag,
8278
},
8379
Action: utils.MigrateFlags(publish),
8480
}
@@ -108,11 +104,11 @@ func deploy(ctx *cli.Context) error {
108104
}
109105
fmt.Printf("\nSignatures needed to publish: %d\n", needed)
110106

111-
// Retrieve the private key, create an abigen transactor and an RPC client
112-
transactor := bind.NewKeyedTransactor(getKey(ctx).PrivateKey)
113-
client := newClient(ctx)
107+
// setup clef signer, create an abigen transactor and an RPC client
108+
transactor, client := newClefSigner(ctx), newClient(ctx)
114109

115110
// Deploy the checkpoint oracle
111+
fmt.Println("Sending deploy request to Clef...")
116112
oracle, tx, _, err := contract.DeployCheckpointOracle(transactor, client, addrs, big.NewInt(int64(params.CheckpointFrequency)),
117113
big.NewInt(int64(params.CheckpointProcessConfirmations)), big.NewInt(int64(needed)))
118114
if err != nil {
@@ -158,9 +154,7 @@ func sign(ctx *cli.Context) error {
158154
node = newRPCClient(ctx.GlobalString(nodeURLFlag.Name))
159155

160156
checkpoint := getCheckpoint(ctx, node)
161-
chash = checkpoint.Hash()
162-
cindex = checkpoint.SectionIndex
163-
address = getContractAddr(node)
157+
chash, cindex, address = checkpoint.Hash(), checkpoint.SectionIndex, getContractAddr(node)
164158

165159
// Check the validity of checkpoint
166160
reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
@@ -207,43 +201,24 @@ func sign(ctx *cli.Context) error {
207201
fmt.Printf("Oracle => %s\n", address.Hex())
208202
fmt.Printf("Index %4d => %s\n", cindex, chash.Hex())
209203

210-
switch {
211-
case ctx.GlobalIsSet(clefURLFlag.Name):
212-
// Sign checkpoint in clef mode.
213-
signer = ctx.String(signerFlag.Name)
204+
// Sign checkpoint in clef mode.
205+
signer = ctx.String(signerFlag.Name)
214206

215-
if !offline {
216-
if err := isAdmin(common.HexToAddress(signer)); err != nil {
217-
return err
218-
}
219-
}
220-
clef := newRPCClient(ctx.GlobalString(clefURLFlag.Name))
221-
p := make(map[string]string)
222-
buf := make([]byte, 8)
223-
binary.BigEndian.PutUint64(buf, cindex)
224-
p["address"] = address.Hex()
225-
p["message"] = hexutil.Encode(append(buf, chash.Bytes()...))
226-
if err := clef.Call(&signature, "account_signData", accounts.MimetypeDataWithValidator, signer, p); err != nil {
227-
utils.Fatalf("Failed to sign checkpoint, err %v", err)
207+
if !offline {
208+
if err := isAdmin(common.HexToAddress(signer)); err != nil {
209+
return err
228210
}
229-
case ctx.GlobalIsSet(keyFileFlag.Name):
230-
// Sign checkpoint in raw private key file mode.
231-
key := getKey(ctx)
232-
signer = key.Address.Hex()
211+
}
212+
clef := newRPCClient(ctx.String(clefURLFlag.Name))
213+
p := make(map[string]string)
214+
buf := make([]byte, 8)
215+
binary.BigEndian.PutUint64(buf, cindex)
216+
p["address"] = address.Hex()
217+
p["message"] = hexutil.Encode(append(buf, chash.Bytes()...))
233218

234-
if !offline {
235-
if err := isAdmin(key.Address); err != nil {
236-
return err
237-
}
238-
}
239-
sig, err := crypto.Sign(sighash(cindex, address, chash), key.PrivateKey)
240-
if err != nil {
241-
utils.Fatalf("Failed to sign checkpoint, err %v", err)
242-
}
243-
sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
244-
signature = common.Bytes2Hex(sig)
245-
default:
246-
utils.Fatalf("Please specify clef URL or private key file path to sign checkpoint")
219+
fmt.Println("Sending signing request to Clef...")
220+
if err := clef.Call(&signature, "account_signData", accounts.MimetypeDataWithValidator, signer, p); err != nil {
221+
utils.Fatalf("Failed to sign checkpoint, err %v", err)
247222
}
248223
fmt.Printf("Signer => %s\n", signer)
249224
fmt.Printf("Signature => %s\n", signature)
@@ -326,7 +301,8 @@ func publish(ctx *cli.Context) error {
326301
fmt.Printf("Sentry number => %d\nSentry hash => %s\n", recent.Number, recent.Hash().Hex())
327302

328303
// Publish the checkpoint into the oracle
329-
tx, err := oracle.RegisterCheckpoint(getKey(ctx).PrivateKey, checkpoint.SectionIndex, checkpoint.Hash().Bytes(), recent.Number, recent.Hash(), sigs)
304+
fmt.Println("Sending publish request to Clef...")
305+
tx, err := oracle.RegisterCheckpoint(newClefSigner(ctx), checkpoint.SectionIndex, checkpoint.Hash().Bytes(), recent.Number, recent.Hash(), sigs)
330306
if err != nil {
331307
utils.Fatalf("Register contract failed %v", err)
332308
}

cmd/checkpoint-admin/main.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,7 @@ func init() {
5959
}
6060
app.Flags = []cli.Flag{
6161
oracleFlag,
62-
keyFileFlag,
6362
nodeURLFlag,
64-
clefURLFlag,
65-
utils.PasswordFileFlag,
6663
}
6764
cli.CommandHelpTemplate = commandHelperTemplate
6865
}
@@ -85,10 +82,6 @@ var (
8582
Name: "threshold",
8683
Usage: "Minimal number of signatures required to approve a checkpoint",
8784
}
88-
keyFileFlag = cli.StringFlag{
89-
Name: "keyfile",
90-
Usage: "The private key file (keyfile signature is not recommended)",
91-
}
9285
nodeURLFlag = cli.StringFlag{
9386
Name: "rpc",
9487
Value: "http://localhost:8545",
@@ -101,7 +94,7 @@ var (
10194
}
10295
signerFlag = cli.StringFlag{
10396
Name: "signer",
104-
Usage: "Signer address for clef mode signing",
97+
Usage: "Signer address for clef signing",
10598
}
10699
signersFlag = cli.StringFlag{
107100
Name: "signers",

contracts/checkpointoracle/oracle.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ package checkpointoracle
2020
//go:generate abigen --sol contract/oracle.sol --pkg contract --out contract/oracle.go
2121

2222
import (
23-
"crypto/ecdsa"
2423
"errors"
2524
"math/big"
2625

@@ -73,7 +72,7 @@ func (oracle *CheckpointOracle) LookupCheckpointEvents(blockLogs [][]*types.Log,
7372
//
7473
// Notably all signatures given should be transformed to "ethereum style" which transforms
7574
// v from 0/1 to 27/28 according to the yellow paper.
76-
func (oracle *CheckpointOracle) RegisterCheckpoint(key *ecdsa.PrivateKey, index uint64, hash []byte, rnum *big.Int, rhash [32]byte, sigs [][]byte) (*types.Transaction, error) {
75+
func (oracle *CheckpointOracle) RegisterCheckpoint(opts *bind.TransactOpts, index uint64, hash []byte, rnum *big.Int, rhash [32]byte, sigs [][]byte) (*types.Transaction, error) {
7776
var (
7877
r [][32]byte
7978
s [][32]byte
@@ -87,5 +86,5 @@ func (oracle *CheckpointOracle) RegisterCheckpoint(key *ecdsa.PrivateKey, index
8786
s = append(s, common.BytesToHash(sigs[i][32:64]))
8887
v = append(v, sigs[i][64])
8988
}
90-
return oracle.contract.SetCheckpoint(bind.NewKeyedTransactor(key), rnum, rhash, common.BytesToHash(hash), index, v, r, s)
89+
return oracle.contract.SetCheckpoint(opts, rnum, rhash, common.BytesToHash(hash), index, v, r, s)
9190
}

les/sync_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"testing"
2323
"time"
2424

25+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
2526
"github.com/ethereum/go-ethereum/core"
2627
"github.com/ethereum/go-ethereum/crypto"
2728
"github.com/ethereum/go-ethereum/light"
@@ -82,7 +83,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) {
8283
data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...)
8384
sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey)
8485
sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
85-
if _, err := server.pm.reg.contract.RegisterCheckpoint(signerKey, cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil {
86+
if _, err := server.pm.reg.contract.RegisterCheckpoint(bind.NewKeyedTransactor(signerKey), cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil {
8687
t.Error("register checkpoint failed", err)
8788
}
8889
server.backend.Commit()

signer/core/cliui.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp
171171
fmt.Printf("Account: %s\n", request.Address.String())
172172
fmt.Printf("messages:\n")
173173
for _, nvt := range request.Messages {
174-
fmt.Printf("%v\n", nvt.Pprint(1))
174+
fmt.Printf("\u00a0\u00a0%v\n", strings.TrimSpace(nvt.Pprint(1)))
175175
}
176176
fmt.Printf("raw data: \n%q\n", request.Rawdata)
177177
fmt.Printf("data hash: %v\n", request.Hash)

0 commit comments

Comments
 (0)