diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go index 5d5ad0034..e51f0bd8e 100644 --- a/accounts/abi/bind/auth.go +++ b/accounts/abi/bind/auth.go @@ -23,6 +23,7 @@ import ( "io/ioutil" "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/external" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -43,7 +44,7 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { return NewKeyedTransactor(key.PrivateKey), nil } -// NewKeystoreTransactor is a utility method to easily create a transaction signer from +// NewKeyStoreTransactor is a utility method to easily create a transaction signer from // an decrypted key from a keystore func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) { return &TransactOpts{ @@ -79,3 +80,17 @@ func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts { }, } } + +// NewClefTransactor is a utility method to easily create a transaction signer +// with a clef backend. +func NewClefTransactor(clef *external.ExternalSigner, account accounts.Account) *TransactOpts { + return &TransactOpts{ + From: account.Address, + Signer: func(signer types.Signer, address common.Address, transaction *types.Transaction) (*types.Transaction, error) { + if address != account.Address { + return nil, errors.New("not authorized to sign this account") + } + return clef.SignTx(account, transaction, nil) // Clef enforces its own chain id + }, + } +} diff --git a/accounts/external/backend.go b/accounts/external/backend.go index 23037f52d..705c98722 100644 --- a/accounts/external/backend.go +++ b/accounts/external/backend.go @@ -182,18 +182,21 @@ func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]by func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { res := ethapi.SignTransactionResult{} - to := common.NewMixedcaseAddress(*tx.To()) data := hexutil.Bytes(tx.Data()) + var to *common.MixedcaseAddress + if tx.To() != nil { + t := common.NewMixedcaseAddress(*tx.To()) + to = &t + } args := &core.SendTxArgs{ Data: &data, Nonce: hexutil.Uint64(tx.Nonce()), Value: hexutil.Big(*tx.Value()), Gas: hexutil.Uint64(tx.Gas()), GasPrice: hexutil.Big(*tx.GasPrice()), - To: &to, + To: to, From: common.NewMixedcaseAddress(account.Address), } - if err := api.client.Call(&res, "account_signTransaction", args); err != nil { return nil, err } diff --git a/cmd/checkpoint-admin/common.go b/cmd/checkpoint-admin/common.go index 1f4a34a41..107cd1de0 100644 --- a/cmd/checkpoint-admin/common.go +++ b/cmd/checkpoint-admin/common.go @@ -17,14 +17,13 @@ package main import ( - "io/ioutil" "strconv" - "strings" - "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/external" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/console" "github.com/ethereum/go-ethereum/contracts/checkpointoracle" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/params" @@ -111,56 +110,11 @@ func newContract(client *rpc.Client) (common.Address, *checkpointoracle.Checkpoi return addr, contract } -// promptPassphrase prompts the user for a passphrase. -// Set confirmation to true to require the user to confirm the passphrase. -func promptPassphrase(confirmation bool) string { - passphrase, err := console.Stdin.PromptPassword("Passphrase: ") +// newClefSigner sets up a clef backend and returns a clef transaction signer. +func newClefSigner(ctx *cli.Context) *bind.TransactOpts { + clef, err := external.NewExternalSigner(ctx.String(clefURLFlag.Name)) if err != nil { - utils.Fatalf("Failed to read passphrase: %v", err) + utils.Fatalf("Failed to create clef signer %v", err) } - - if confirmation { - confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ") - if err != nil { - utils.Fatalf("Failed to read passphrase confirmation: %v", err) - } - if passphrase != confirm { - utils.Fatalf("Passphrases do not match") - } - } - return passphrase -} - -// getPassphrase obtains a passphrase given by the user. It first checks the -// --password command line flag and ultimately prompts the user for a -// passphrase. -func getPassphrase(ctx *cli.Context) string { - passphraseFile := ctx.String(utils.PasswordFileFlag.Name) - if passphraseFile != "" { - content, err := ioutil.ReadFile(passphraseFile) - if err != nil { - utils.Fatalf("Failed to read passphrase file '%s': %v", - passphraseFile, err) - } - return strings.TrimRight(string(content), "\r\n") - } - // Otherwise prompt the user for the passphrase. - return promptPassphrase(false) -} - -// getKey retrieves the user key through specified key file. -func getKey(ctx *cli.Context) *keystore.Key { - // Read key from file. - keyFile := ctx.GlobalString(keyFileFlag.Name) - keyJson, err := ioutil.ReadFile(keyFile) - if err != nil { - utils.Fatalf("Failed to read the keyfile at '%s': %v", keyFile, err) - } - // Decrypt key with passphrase. - passphrase := getPassphrase(ctx) - key, err := keystore.DecryptKey(keyJson, passphrase) - if err != nil { - utils.Fatalf("Failed to decrypt user key '%s': %v", keyFile, err) - } - return key + return bind.NewClefTransactor(clef, accounts.Account{Address: common.HexToAddress(ctx.String(signerFlag.Name))}) } diff --git a/cmd/checkpoint-admin/exec.go b/cmd/checkpoint-admin/exec.go index 02c4f35cc..1ce975f49 100644 --- a/cmd/checkpoint-admin/exec.go +++ b/cmd/checkpoint-admin/exec.go @@ -26,7 +26,6 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -46,10 +45,9 @@ var commandDeploy = cli.Command{ Flags: []cli.Flag{ nodeURLFlag, clefURLFlag, + signerFlag, signersFlag, thresholdFlag, - keyFileFlag, - utils.PasswordFileFlag, }, Action: utils.MigrateFlags(deploy), } @@ -60,12 +58,10 @@ var commandSign = cli.Command{ Flags: []cli.Flag{ nodeURLFlag, clefURLFlag, + signerFlag, indexFlag, hashFlag, oracleFlag, - keyFileFlag, - signerFlag, - utils.PasswordFileFlag, }, Action: utils.MigrateFlags(sign), } @@ -75,10 +71,10 @@ var commandPublish = cli.Command{ Usage: "Publish a checkpoint into the oracle", Flags: []cli.Flag{ nodeURLFlag, + clefURLFlag, + signerFlag, indexFlag, signaturesFlag, - keyFileFlag, - utils.PasswordFileFlag, }, Action: utils.MigrateFlags(publish), } @@ -108,11 +104,11 @@ func deploy(ctx *cli.Context) error { } fmt.Printf("\nSignatures needed to publish: %d\n", needed) - // Retrieve the private key, create an abigen transactor and an RPC client - transactor := bind.NewKeyedTransactor(getKey(ctx).PrivateKey) - client := newClient(ctx) + // setup clef signer, create an abigen transactor and an RPC client + transactor, client := newClefSigner(ctx), newClient(ctx) // Deploy the checkpoint oracle + fmt.Println("Sending deploy request to Clef...") oracle, tx, _, err := contract.DeployCheckpointOracle(transactor, client, addrs, big.NewInt(int64(params.CheckpointFrequency)), big.NewInt(int64(params.CheckpointProcessConfirmations)), big.NewInt(int64(needed))) if err != nil { @@ -158,9 +154,7 @@ func sign(ctx *cli.Context) error { node = newRPCClient(ctx.GlobalString(nodeURLFlag.Name)) checkpoint := getCheckpoint(ctx, node) - chash = checkpoint.Hash() - cindex = checkpoint.SectionIndex - address = getContractAddr(node) + chash, cindex, address = checkpoint.Hash(), checkpoint.SectionIndex, getContractAddr(node) // Check the validity of checkpoint reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) @@ -207,43 +201,24 @@ func sign(ctx *cli.Context) error { fmt.Printf("Oracle => %s\n", address.Hex()) fmt.Printf("Index %4d => %s\n", cindex, chash.Hex()) - switch { - case ctx.GlobalIsSet(clefURLFlag.Name): - // Sign checkpoint in clef mode. - signer = ctx.String(signerFlag.Name) + // Sign checkpoint in clef mode. + signer = ctx.String(signerFlag.Name) - if !offline { - if err := isAdmin(common.HexToAddress(signer)); err != nil { - return err - } - } - clef := newRPCClient(ctx.GlobalString(clefURLFlag.Name)) - p := make(map[string]string) - buf := make([]byte, 8) - binary.BigEndian.PutUint64(buf, cindex) - p["address"] = address.Hex() - p["message"] = hexutil.Encode(append(buf, chash.Bytes()...)) - if err := clef.Call(&signature, "account_signData", accounts.MimetypeDataWithValidator, signer, p); err != nil { - utils.Fatalf("Failed to sign checkpoint, err %v", err) + if !offline { + if err := isAdmin(common.HexToAddress(signer)); err != nil { + return err } - case ctx.GlobalIsSet(keyFileFlag.Name): - // Sign checkpoint in raw private key file mode. - key := getKey(ctx) - signer = key.Address.Hex() + } + clef := newRPCClient(ctx.String(clefURLFlag.Name)) + p := make(map[string]string) + buf := make([]byte, 8) + binary.BigEndian.PutUint64(buf, cindex) + p["address"] = address.Hex() + p["message"] = hexutil.Encode(append(buf, chash.Bytes()...)) - if !offline { - if err := isAdmin(key.Address); err != nil { - return err - } - } - sig, err := crypto.Sign(sighash(cindex, address, chash), key.PrivateKey) - if err != nil { - utils.Fatalf("Failed to sign checkpoint, err %v", err) - } - sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - signature = common.Bytes2Hex(sig) - default: - utils.Fatalf("Please specify clef URL or private key file path to sign checkpoint") + fmt.Println("Sending signing request to Clef...") + if err := clef.Call(&signature, "account_signData", accounts.MimetypeDataWithValidator, signer, p); err != nil { + utils.Fatalf("Failed to sign checkpoint, err %v", err) } fmt.Printf("Signer => %s\n", signer) fmt.Printf("Signature => %s\n", signature) @@ -326,7 +301,8 @@ func publish(ctx *cli.Context) error { fmt.Printf("Sentry number => %d\nSentry hash => %s\n", recent.Number, recent.Hash().Hex()) // Publish the checkpoint into the oracle - tx, err := oracle.RegisterCheckpoint(getKey(ctx).PrivateKey, checkpoint.SectionIndex, checkpoint.Hash().Bytes(), recent.Number, recent.Hash(), sigs) + fmt.Println("Sending publish request to Clef...") + tx, err := oracle.RegisterCheckpoint(newClefSigner(ctx), checkpoint.SectionIndex, checkpoint.Hash().Bytes(), recent.Number, recent.Hash(), sigs) if err != nil { utils.Fatalf("Register contract failed %v", err) } diff --git a/cmd/checkpoint-admin/main.go b/cmd/checkpoint-admin/main.go index 39eae6878..1fdec60a0 100644 --- a/cmd/checkpoint-admin/main.go +++ b/cmd/checkpoint-admin/main.go @@ -59,10 +59,7 @@ func init() { } app.Flags = []cli.Flag{ oracleFlag, - keyFileFlag, nodeURLFlag, - clefURLFlag, - utils.PasswordFileFlag, } cli.CommandHelpTemplate = commandHelperTemplate } @@ -85,10 +82,6 @@ var ( Name: "threshold", Usage: "Minimal number of signatures required to approve a checkpoint", } - keyFileFlag = cli.StringFlag{ - Name: "keyfile", - Usage: "The private key file (keyfile signature is not recommended)", - } nodeURLFlag = cli.StringFlag{ Name: "rpc", Value: "http://localhost:8545", @@ -101,7 +94,7 @@ var ( } signerFlag = cli.StringFlag{ Name: "signer", - Usage: "Signer address for clef mode signing", + Usage: "Signer address for clef signing", } signersFlag = cli.StringFlag{ Name: "signers", diff --git a/contracts/checkpointoracle/oracle.go b/contracts/checkpointoracle/oracle.go index 702e27d95..13ff236f2 100644 --- a/contracts/checkpointoracle/oracle.go +++ b/contracts/checkpointoracle/oracle.go @@ -20,7 +20,6 @@ package checkpointoracle //go:generate abigen --sol contract/oracle.sol --pkg contract --out contract/oracle.go import ( - "crypto/ecdsa" "errors" "math/big" @@ -73,7 +72,7 @@ func (oracle *CheckpointOracle) LookupCheckpointEvents(blockLogs [][]*types.Log, // // Notably all signatures given should be transformed to "ethereum style" which transforms // v from 0/1 to 27/28 according to the yellow paper. -func (oracle *CheckpointOracle) RegisterCheckpoint(key *ecdsa.PrivateKey, index uint64, hash []byte, rnum *big.Int, rhash [32]byte, sigs [][]byte) (*types.Transaction, error) { +func (oracle *CheckpointOracle) RegisterCheckpoint(opts *bind.TransactOpts, index uint64, hash []byte, rnum *big.Int, rhash [32]byte, sigs [][]byte) (*types.Transaction, error) { var ( r [][32]byte s [][32]byte @@ -87,5 +86,5 @@ func (oracle *CheckpointOracle) RegisterCheckpoint(key *ecdsa.PrivateKey, index s = append(s, common.BytesToHash(sigs[i][32:64])) v = append(v, sigs[i][64]) } - return oracle.contract.SetCheckpoint(bind.NewKeyedTransactor(key), rnum, rhash, common.BytesToHash(hash), index, v, r, s) + return oracle.contract.SetCheckpoint(opts, rnum, rhash, common.BytesToHash(hash), index, v, r, s) } diff --git a/les/sync_test.go b/les/sync_test.go index 634be8e6d..f5d1ad5bc 100644 --- a/les/sync_test.go +++ b/les/sync_test.go @@ -22,6 +22,7 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/light" @@ -82,7 +83,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey) sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - 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 { + 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 { t.Error("register checkpoint failed", err) } server.backend.Commit() diff --git a/signer/core/cliui.go b/signer/core/cliui.go index 46a13f1e4..381c40be3 100644 --- a/signer/core/cliui.go +++ b/signer/core/cliui.go @@ -171,7 +171,7 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp fmt.Printf("Account: %s\n", request.Address.String()) fmt.Printf("messages:\n") for _, nvt := range request.Messages { - fmt.Printf("%v\n", nvt.Pprint(1)) + fmt.Printf("\u00a0\u00a0%v\n", strings.TrimSpace(nvt.Pprint(1))) } fmt.Printf("raw data: \n%q\n", request.Rawdata) fmt.Printf("data hash: %v\n", request.Hash)