diff --git a/accounts/accounts.go b/accounts/accounts.go index 4c4401f8a3..cd7bfc7da2 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -91,7 +91,7 @@ type Wallet interface { // chain state reader. SelfDerive(base DerivationPath, chain ethereum.ChainStateReader) - // SignCliqueHeader requests the wallet to sign the hash of a given Clique header + // SignData requests the wallet to sign the hash of the given data // It looks up the account specified either solely via its address contained within, // or optionally with the aid of any location metadata from the embedded URL field. // @@ -101,7 +101,7 @@ type Wallet interface { // about which fields or actions are needed. The user may retry by providing // the needed details via SignHashWithPassphrase, or by other means (e.g. unlock // the account in a keystore). - SignCliqueHeader(account Account, header *types.Header) ([]byte, error) + SignData(account Account, mimeType string, data []byte) ([]byte, error) // Signtext requests the wallet to sign the hash of a given piece of data, prefixed // by the Ethereum prefix scheme diff --git a/accounts/external/backend.go b/accounts/external/backend.go index d879223566..8f53204f1c 100644 --- a/accounts/external/backend.go +++ b/accounts/external/backend.go @@ -18,6 +18,7 @@ package external import ( "fmt" + "github.com/ethereum/go-ethereum/crypto" "math/big" "sync" @@ -151,11 +152,11 @@ func (api *ExternalSigner) signHash(account accounts.Account, hash []byte) ([]by return []byte{}, fmt.Errorf("operation not supported on external signers") } -// SignCliqueHeader implements accounts.Wallet, attempting to sign the given clique header -func (api *ExternalSigner) SignCliqueHeader(account accounts.Account, header *types.Header) ([]byte, error) { +// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed +func (api *ExternalSigner) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { // TODO! Replace this with a call to clef SignData with correct mime-type for Clique, once we // have that in place - return api.signHash(account, accounts.CliqueHash(header).Bytes()) + return api.signHash(account, crypto.Keccak256(data)) } func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]byte, error) { diff --git a/accounts/keystore/wallet.go b/accounts/keystore/wallet.go index b9d09e9929..8f3dfed410 100644 --- a/accounts/keystore/wallet.go +++ b/accounts/keystore/wallet.go @@ -17,6 +17,7 @@ package keystore import ( + "github.com/ethereum/go-ethereum/crypto" "math/big" ethereum "github.com/ethereum/go-ethereum" @@ -91,9 +92,9 @@ func (w *keystoreWallet) signHash(account accounts.Account, hash []byte) ([]byte return w.keystore.SignHash(account, hash) } -// SignCliqueHeader implements accounts.Wallet, attempting to sign the given clique header -func (w *keystoreWallet) SignCliqueHeader(account accounts.Account, header *types.Header) ([]byte, error) { - return w.signHash(account, accounts.CliqueHash(header).Bytes()) +// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed +func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { + return w.signHash(account, crypto.Keccak256(data)) } func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) { diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index 14a133406c..85ba23a165 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -20,6 +20,7 @@ package usbwallet import ( "context" "fmt" + "github.com/ethereum/go-ethereum/crypto" "io" "math/big" "sync" @@ -501,9 +502,9 @@ func (w *wallet) signHash(account accounts.Account, hash []byte) ([]byte, error) return nil, accounts.ErrNotSupported } -// SignCliqueHeader implements accounts.Wallet, attempting to sign the given clique header -func (w *wallet) SignCliqueHeader(account accounts.Account, header *types.Header) ([]byte, error) { - return w.signHash(account, accounts.CliqueHash(header).Bytes()) +// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed +func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { + return w.signHash(account, crypto.Keccak256(data)) } func (w *wallet) SignText(account accounts.Account, text []byte) ([]byte, error) { diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index ae5e974bef..538701cb7f 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -20,6 +20,8 @@ package clique import ( "bytes" "errors" + "github.com/ethereum/go-ethereum/crypto/sha3" + "github.com/ethereum/go-ethereum/rlp" "math/big" "math/rand" "sync" @@ -136,7 +138,7 @@ var ( // SignerFn is a signer callback function to request a header to be signed by a // backing account. -type SignerFn func(accounts.Account, *types.Header) ([]byte, error) +type SignerFn func(accounts.Account, string, []byte) ([]byte, error) // ecrecover extracts the Ethereum account address from a signed header. func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) { @@ -152,7 +154,7 @@ func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, er signature := header.Extra[len(header.Extra)-extraSeal:] // Recover the public key and the Ethereum address - pubkey, err := crypto.Ecrecover(accounts.CliqueHash(header).Bytes(), signature) + pubkey, err := crypto.Ecrecover(SealHash(header).Bytes(), signature) if err != nil { return common.Address{}, err } @@ -613,7 +615,7 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results c log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle)) } // Sign all the things! - sighash, err := signFn(accounts.Account{Address: signer}, header) + sighash, err := signFn(accounts.Account{Address: signer}, "application/x-clique-header", CliqueRLP(header)) if err != nil { return err } @@ -630,7 +632,7 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results c select { case results <- block.WithSeal(header): default: - log.Warn("Sealing result is not read by miner", "sealhash", c.SealHash(header)) + log.Warn("Sealing result is not read by miner", "sealhash", SealHash(header)) } }() @@ -660,7 +662,31 @@ func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int { // SealHash returns the hash of a block prior to it being sealed. func (c *Clique) SealHash(header *types.Header) common.Hash { - return accounts.CliqueHash(header) + return SealHash(header) +} + +// SealHash returns the hash of a block prior to it being sealed. +func SealHash(header *types.Header) (hash common.Hash) { + hasher := sha3.NewKeccak256() + rlp.Encode(hasher, []interface{}{ + header.ParentHash, + header.UncleHash, + header.Coinbase, + header.Root, + header.TxHash, + header.ReceiptHash, + header.Bloom, + header.Difficulty, + header.Number, + header.GasLimit, + header.GasUsed, + header.Time, + header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short + header.MixDigest, + header.Nonce, + }) + hasher.Sum(hash[:0]) + return hash } // Close implements consensus.Engine. It's a noop for clique as there are no background threads. @@ -678,3 +704,31 @@ func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API { Public: false, }} } + +// CliqueRLP returns the rlp bytes which needs to be signed for the proof-of-authority +// sealing. The RLP to sign consists of the entire header apart from the 65 byte signature +// contained at the end of the extra data. +// +// Note, the method requires the extra data to be at least 65 bytes, otherwise it +// panics. This is done to avoid accidentally using both forms (signature present +// or not), which could be abused to produce different hashes for the same header. +func CliqueRLP(header *types.Header) []byte { + data, _ := rlp.EncodeToBytes([]interface{}{ + header.ParentHash, + header.UncleHash, + header.Coinbase, + header.Root, + header.TxHash, + header.ReceiptHash, + header.Bloom, + header.Difficulty, + header.Number, + header.GasLimit, + header.GasUsed, + header.Time, + header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short + header.MixDigest, + header.Nonce, + }) + return data +} diff --git a/eth/backend.go b/eth/backend.go index bb8f8f9d28..695c72f653 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -432,7 +432,7 @@ func (s *Ethereum) StartMining(threads int) error { log.Error("Etherbase account unavailable locally", "err", err) return fmt.Errorf("signer missing: %v", err) } - clique.Authorize(eb, wallet.SignCliqueHeader) + clique.Authorize(eb, wallet.SignData) } // If mining is started, we can disable the transaction rejection mechanism // introduced to speed sync times.