accounts, core, crypto, internal: use normalised V during signature handling (#3455)

To address increasing complexity in code that handles signatures, this PR
discards all notion of "different" signature types at the library level. Both
the crypto and accounts package is reduced to only be able to produce plain
canonical secp256k1 signatures. This makes the crpyto APIs much cleaner,
simpler and harder to abuse.
This commit is contained in:
Péter Szilágyi 2017-01-05 12:35:23 +02:00 committed by Felix Lange
parent 0fac8cba47
commit 08eea0f0e4
10 changed files with 91 additions and 201 deletions

View File

@ -52,7 +52,7 @@ func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts {
if address != keyAddr { if address != keyAddr {
return nil, errors.New("not authorized to sign this account") return nil, errors.New("not authorized to sign this account")
} }
signature, err := crypto.SignEthereum(signer.Hash(tx).Bytes(), key) signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -136,13 +136,12 @@ func (am *Manager) DeleteAccount(a Account, passphrase string) error {
return err return err
} }
// Sign calculates a ECDSA signature for the given hash. // Sign calculates a ECDSA signature for the given hash. The produced signature
// Note, Ethereum signatures have a particular format as described in the // is in the [R || S || V] format where V is 0 or 1.
// yellow paper. Use the SignEthereum function to calculate a signature
// in Ethereum format.
func (am *Manager) Sign(addr common.Address, hash []byte) ([]byte, error) { func (am *Manager) Sign(addr common.Address, hash []byte) ([]byte, error) {
am.mu.RLock() am.mu.RLock()
defer am.mu.RUnlock() defer am.mu.RUnlock()
unlockedKey, found := am.unlocked[addr] unlockedKey, found := am.unlocked[addr]
if !found { if !found {
return nil, ErrLocked return nil, ErrLocked
@ -150,28 +149,16 @@ func (am *Manager) Sign(addr common.Address, hash []byte) ([]byte, error) {
return crypto.Sign(hash, unlockedKey.PrivateKey) return crypto.Sign(hash, unlockedKey.PrivateKey)
} }
// SignEthereum calculates a ECDSA signature for the given hash. // SignWithPassphrase signs hash if the private key matching the given address
// The signature has the format as described in the Ethereum yellow paper. // can be decrypted with the given passphrase. The produced signature is in the
func (am *Manager) SignEthereum(addr common.Address, hash []byte) ([]byte, error) { // [R || S || V] format where V is 0 or 1.
am.mu.RLock()
defer am.mu.RUnlock()
unlockedKey, found := am.unlocked[addr]
if !found {
return nil, ErrLocked
}
return crypto.SignEthereum(hash, unlockedKey.PrivateKey)
}
// SignWithPassphrase signs hash if the private key matching the given
// address can be decrypted with the given passphrase.
func (am *Manager) SignWithPassphrase(addr common.Address, passphrase string, hash []byte) (signature []byte, err error) { func (am *Manager) SignWithPassphrase(addr common.Address, passphrase string, hash []byte) (signature []byte, err error) {
_, key, err := am.getDecryptedKey(Account{Address: addr}, passphrase) _, key, err := am.getDecryptedKey(Account{Address: addr}, passphrase)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer zeroKey(key.PrivateKey) defer zeroKey(key.PrivateKey)
return crypto.SignEthereum(hash, key.PrivateKey) return crypto.Sign(hash, key.PrivateKey)
} }
// Unlock unlocks the given account indefinitely. // Unlock unlocks the given account indefinitely.

View File

@ -53,7 +53,7 @@ func TestBlockEncoding(t *testing.T) {
tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), big.NewInt(50000), big.NewInt(10), nil) tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), big.NewInt(50000), big.NewInt(10), nil)
tx1, _ = tx1.WithSignature(HomesteadSigner{}, common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b11b")) tx1, _ = tx1.WithSignature(HomesteadSigner{}, common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100"))
fmt.Println(block.Transactions()[0].Hash()) fmt.Println(block.Transactions()[0].Hash())
fmt.Println(tx1.data) fmt.Println(tx1.data)
fmt.Println(tx1.Hash()) fmt.Println(tx1.Hash())

View File

@ -199,9 +199,9 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
var V byte var V byte
if isProtectedV((*big.Int)(dec.V)) { if isProtectedV((*big.Int)(dec.V)) {
V = normaliseV(NewEIP155Signer(deriveChainId((*big.Int)(dec.V))), (*big.Int)(dec.V)) V = byte((new(big.Int).Sub((*big.Int)(dec.V), deriveChainId((*big.Int)(dec.V))).Uint64()) - 35)
} else { } else {
V = byte(((*big.Int)(dec.V)).Uint64()) V = byte(((*big.Int)(dec.V)).Uint64() - 27)
} }
if !crypto.ValidateSignatureValues(V, (*big.Int)(dec.R), (*big.Int)(dec.S), false) { if !crypto.ValidateSignatureValues(V, (*big.Int)(dec.R), (*big.Int)(dec.S), false) {
return ErrInvalidSig return ErrInvalidSig
@ -272,51 +272,6 @@ func (tx *Transaction) Size() common.StorageSize {
return common.StorageSize(c) return common.StorageSize(c)
} }
/*
// From returns the address derived from the signature (V, R, S) using secp256k1
// elliptic curve and an error if it failed deriving or upon an incorrect
// signature.
//
// From Uses the homestead consensus rules to determine whether the signature is
// valid.
//
// From caches the address, allowing it to be used regardless of
// Frontier / Homestead. however, the first time called it runs
// signature validations, so we need two versions. This makes it
// easier to ensure backwards compatibility of things like package rpc
// where eth_getblockbynumber uses tx.From() and needs to work for
// both txs before and after the first homestead block. Signatures
// valid in homestead are a subset of valid ones in Frontier)
func (tx *Transaction) From() (common.Address, error) {
if tx.signer == nil {
return common.Address{}, errNoSigner
}
if from := tx.from.Load(); from != nil {
return from.(common.Address), nil
}
pubkey, err := tx.signer.PublicKey(tx)
if err != nil {
return common.Address{}, err
}
var addr common.Address
copy(addr[:], crypto.Keccak256(pubkey[1:])[12:])
tx.from.Store(addr)
return addr, nil
}
// SignatureValues returns the ECDSA signature values contained in the transaction.
func (tx *Transaction) SignatureValues() (v byte, r *big.Int, s *big.Int, err error) {
if tx.signer == nil {
return 0, nil, nil,errNoSigner
}
return normaliseV(tx.signer, tx.data.V), new(big.Int).Set(tx.data.R),new(big.Int).Set(tx.data.S), nil
}
*/
// AsMessage returns the transaction as a core.Message. // AsMessage returns the transaction as a core.Message.
// //
// AsMessage requires a signer to derive the sender. // AsMessage requires a signer to derive the sender.

View File

@ -53,7 +53,7 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer {
// SignECDSA signs the transaction using the given signer and private key // SignECDSA signs the transaction using the given signer and private key
func SignECDSA(s Signer, tx *Transaction, prv *ecdsa.PrivateKey) (*Transaction, error) { func SignECDSA(s Signer, tx *Transaction, prv *ecdsa.PrivateKey) (*Transaction, error) {
h := s.Hash(tx) h := s.Hash(tx)
sig, err := crypto.SignEthereum(h[:], prv) sig, err := crypto.Sign(h[:], prv)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -91,11 +91,6 @@ func Sender(signer Signer, tx *Transaction) (common.Address, error) {
return addr, nil return addr, nil
} }
// SignatureValues returns the ECDSA signature values contained in the transaction.
func SignatureValues(signer Signer, tx *Transaction) (v byte, r *big.Int, s *big.Int) {
return normaliseV(signer, tx.data.V), new(big.Int).Set(tx.data.R), new(big.Int).Set(tx.data.S)
}
type Signer interface { type Signer interface {
// Hash returns the rlp encoded hash for signatures // Hash returns the rlp encoded hash for signatures
Hash(tx *Transaction) common.Hash Hash(tx *Transaction) common.Hash
@ -143,17 +138,16 @@ func (s EIP155Signer) PublicKey(tx *Transaction) ([]byte, error) {
return nil, ErrInvalidChainId return nil, ErrInvalidChainId
} }
V := normaliseV(s, tx.data.V) V := byte(new(big.Int).Sub(tx.data.V, s.chainIdMul).Uint64() - 35)
if !crypto.ValidateSignatureValues(V, tx.data.R, tx.data.S, true) { if !crypto.ValidateSignatureValues(V, tx.data.R, tx.data.S, true) {
return nil, ErrInvalidSig return nil, ErrInvalidSig
} }
// encode the signature in uncompressed format // encode the signature in uncompressed format
R, S := tx.data.R.Bytes(), tx.data.S.Bytes() R, S := tx.data.R.Bytes(), tx.data.S.Bytes()
sig := make([]byte, 65) sig := make([]byte, 65)
copy(sig[32-len(R):32], R) copy(sig[32-len(R):32], R)
copy(sig[64-len(S):64], S) copy(sig[64-len(S):64], S)
sig[64] = V - 27 sig[64] = V
// recover the public key from the signature // recover the public key from the signature
hash := s.Hash(tx) hash := s.Hash(tx)
@ -167,8 +161,8 @@ func (s EIP155Signer) PublicKey(tx *Transaction) ([]byte, error) {
return pub, nil return pub, nil
} }
// WithSignature returns a new transaction with the given signature. // WithSignature returns a new transaction with the given signature. This signature
// This signature needs to be formatted as described in the yellow paper (v+27). // needs to be in the [R || S || V] format where V is 0 or 1.
func (s EIP155Signer) WithSignature(tx *Transaction, sig []byte) (*Transaction, error) { func (s EIP155Signer) WithSignature(tx *Transaction, sig []byte) (*Transaction, error) {
if len(sig) != 65 { if len(sig) != 65 {
panic(fmt.Sprintf("wrong size for snature: got %d, want 65", len(sig))) panic(fmt.Sprintf("wrong size for snature: got %d, want 65", len(sig)))
@ -179,7 +173,7 @@ func (s EIP155Signer) WithSignature(tx *Transaction, sig []byte) (*Transaction,
cpy.data.S = new(big.Int).SetBytes(sig[32:64]) cpy.data.S = new(big.Int).SetBytes(sig[32:64])
cpy.data.V = new(big.Int).SetBytes([]byte{sig[64]}) cpy.data.V = new(big.Int).SetBytes([]byte{sig[64]})
if s.chainId.BitLen() > 0 { if s.chainId.BitLen() > 0 {
cpy.data.V = big.NewInt(int64(sig[64] - 27 + 35)) cpy.data.V = big.NewInt(int64(sig[64] + 35))
cpy.data.V.Add(cpy.data.V, s.chainIdMul) cpy.data.V.Add(cpy.data.V, s.chainIdMul)
} }
return cpy, nil return cpy, nil
@ -201,7 +195,7 @@ func (s EIP155Signer) Hash(tx *Transaction) common.Hash {
func (s EIP155Signer) SigECDSA(tx *Transaction, prv *ecdsa.PrivateKey) (*Transaction, error) { func (s EIP155Signer) SigECDSA(tx *Transaction, prv *ecdsa.PrivateKey) (*Transaction, error) {
h := s.Hash(tx) h := s.Hash(tx)
sig, err := crypto.SignEthereum(h[:], prv) sig, err := crypto.Sign(h[:], prv)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -217,8 +211,8 @@ func (s HomesteadSigner) Equal(s2 Signer) bool {
return ok return ok
} }
// WithSignature returns a new transaction with the given snature. // WithSignature returns a new transaction with the given signature. This signature
// This snature needs to be formatted as described in the yellow paper (v+27). // needs to be in the [R || S || V] format where V is 0 or 1.
func (hs HomesteadSigner) WithSignature(tx *Transaction, sig []byte) (*Transaction, error) { func (hs HomesteadSigner) WithSignature(tx *Transaction, sig []byte) (*Transaction, error) {
if len(sig) != 65 { if len(sig) != 65 {
panic(fmt.Sprintf("wrong size for snature: got %d, want 65", len(sig))) panic(fmt.Sprintf("wrong size for snature: got %d, want 65", len(sig)))
@ -226,13 +220,13 @@ func (hs HomesteadSigner) WithSignature(tx *Transaction, sig []byte) (*Transacti
cpy := &Transaction{data: tx.data} cpy := &Transaction{data: tx.data}
cpy.data.R = new(big.Int).SetBytes(sig[:32]) cpy.data.R = new(big.Int).SetBytes(sig[:32])
cpy.data.S = new(big.Int).SetBytes(sig[32:64]) cpy.data.S = new(big.Int).SetBytes(sig[32:64])
cpy.data.V = new(big.Int).SetBytes([]byte{sig[64]}) cpy.data.V = new(big.Int).SetBytes([]byte{sig[64] + 27})
return cpy, nil return cpy, nil
} }
func (hs HomesteadSigner) SignECDSA(tx *Transaction, prv *ecdsa.PrivateKey) (*Transaction, error) { func (hs HomesteadSigner) SignECDSA(tx *Transaction, prv *ecdsa.PrivateKey) (*Transaction, error) {
h := hs.Hash(tx) h := hs.Hash(tx)
sig, err := crypto.SignEthereum(h[:], prv) sig, err := crypto.Sign(h[:], prv)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -243,7 +237,7 @@ func (hs HomesteadSigner) PublicKey(tx *Transaction) ([]byte, error) {
if tx.data.V.BitLen() > 8 { if tx.data.V.BitLen() > 8 {
return nil, ErrInvalidSig return nil, ErrInvalidSig
} }
V := byte(tx.data.V.Uint64()) V := byte(tx.data.V.Uint64() - 27)
if !crypto.ValidateSignatureValues(V, tx.data.R, tx.data.S, true) { if !crypto.ValidateSignatureValues(V, tx.data.R, tx.data.S, true) {
return nil, ErrInvalidSig return nil, ErrInvalidSig
} }
@ -252,7 +246,7 @@ func (hs HomesteadSigner) PublicKey(tx *Transaction) ([]byte, error) {
sig := make([]byte, 65) sig := make([]byte, 65)
copy(sig[32-len(r):32], r) copy(sig[32-len(r):32], r)
copy(sig[64-len(s):64], s) copy(sig[64-len(s):64], s)
sig[64] = V - 27 sig[64] = V
// recover the public key from the snature // recover the public key from the snature
hash := hs.Hash(tx) hash := hs.Hash(tx)
@ -273,8 +267,8 @@ func (s FrontierSigner) Equal(s2 Signer) bool {
return ok return ok
} }
// WithSignature returns a new transaction with the given snature. // WithSignature returns a new transaction with the given signature. This signature
// This snature needs to be formatted as described in the yellow paper (v+27). // needs to be in the [R || S || V] format where V is 0 or 1.
func (fs FrontierSigner) WithSignature(tx *Transaction, sig []byte) (*Transaction, error) { func (fs FrontierSigner) WithSignature(tx *Transaction, sig []byte) (*Transaction, error) {
if len(sig) != 65 { if len(sig) != 65 {
panic(fmt.Sprintf("wrong size for snature: got %d, want 65", len(sig))) panic(fmt.Sprintf("wrong size for snature: got %d, want 65", len(sig)))
@ -282,13 +276,13 @@ func (fs FrontierSigner) WithSignature(tx *Transaction, sig []byte) (*Transactio
cpy := &Transaction{data: tx.data} cpy := &Transaction{data: tx.data}
cpy.data.R = new(big.Int).SetBytes(sig[:32]) cpy.data.R = new(big.Int).SetBytes(sig[:32])
cpy.data.S = new(big.Int).SetBytes(sig[32:64]) cpy.data.S = new(big.Int).SetBytes(sig[32:64])
cpy.data.V = new(big.Int).SetBytes([]byte{sig[64]}) cpy.data.V = new(big.Int).SetBytes([]byte{sig[64] + 27})
return cpy, nil return cpy, nil
} }
func (fs FrontierSigner) SignECDSA(tx *Transaction, prv *ecdsa.PrivateKey) (*Transaction, error) { func (fs FrontierSigner) SignECDSA(tx *Transaction, prv *ecdsa.PrivateKey) (*Transaction, error) {
h := fs.Hash(tx) h := fs.Hash(tx)
sig, err := crypto.SignEthereum(h[:], prv) sig, err := crypto.Sign(h[:], prv)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -313,7 +307,7 @@ func (fs FrontierSigner) PublicKey(tx *Transaction) ([]byte, error) {
return nil, ErrInvalidSig return nil, ErrInvalidSig
} }
V := byte(tx.data.V.Uint64()) V := byte(tx.data.V.Uint64() - 27)
if !crypto.ValidateSignatureValues(V, tx.data.R, tx.data.S, false) { if !crypto.ValidateSignatureValues(V, tx.data.R, tx.data.S, false) {
return nil, ErrInvalidSig return nil, ErrInvalidSig
} }
@ -322,7 +316,7 @@ func (fs FrontierSigner) PublicKey(tx *Transaction) ([]byte, error) {
sig := make([]byte, 65) sig := make([]byte, 65)
copy(sig[32-len(r):32], r) copy(sig[32-len(r):32], r)
copy(sig[64-len(s):64], s) copy(sig[64-len(s):64], s)
sig[64] = V - 27 sig[64] = V
// recover the public key from the snature // recover the public key from the snature
hash := fs.Hash(tx) hash := fs.Hash(tx)
@ -336,18 +330,6 @@ func (fs FrontierSigner) PublicKey(tx *Transaction) ([]byte, error) {
return pub, nil return pub, nil
} }
// normaliseV returns the Ethereum version of the V parameter
func normaliseV(s Signer, v *big.Int) byte {
if s, ok := s.(EIP155Signer); ok {
stdV := v.BitLen() <= 8 && (v.Uint64() == 27 || v.Uint64() == 28)
if s.chainId.BitLen() > 0 && !stdV {
nv := byte((new(big.Int).Sub(v, s.chainIdMul).Uint64()) - 35 + 27)
return nv
}
}
return byte(v.Uint64())
}
// deriveChainId derives the chain id from the given v parameter // deriveChainId derives the chain id from the given v parameter
func deriveChainId(v *big.Int) *big.Int { func deriveChainId(v *big.Int) *big.Int {
if v.BitLen() <= 64 { if v.BitLen() <= 64 {

View File

@ -47,7 +47,7 @@ var (
common.FromHex("5544"), common.FromHex("5544"),
).WithSignature( ).WithSignature(
HomesteadSigner{}, HomesteadSigner{},
common.Hex2Bytes("98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a31c"), common.Hex2Bytes("98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a301"),
) )
) )

View File

@ -89,21 +89,15 @@ func ecrecoverFunc(in []byte) []byte {
r := common.BytesToBig(in[64:96]) r := common.BytesToBig(in[64:96])
s := common.BytesToBig(in[96:128]) s := common.BytesToBig(in[96:128])
// Treat V as a 256bit integer v := in[63] - 27
vbig := common.Bytes2Big(in[32:64])
v := byte(vbig.Uint64())
// tighter sig s values in homestead only apply to tx sigs // tighter sig s values in homestead only apply to tx sigs
if !crypto.ValidateSignatureValues(v, r, s, false) { if common.Bytes2Big(in[32:63]).BitLen() > 0 || !crypto.ValidateSignatureValues(v, r, s, false) {
glog.V(logger.Detail).Infof("ECRECOVER error: v, r or s value invalid") glog.V(logger.Detail).Infof("ECRECOVER error: v, r or s value invalid")
return nil return nil
} }
// v needs to be at the end for libsecp256k1
// v needs to be at the end and normalized for libsecp256k1 pubKey, err := crypto.Ecrecover(in[:32], append(in[64:128], v))
vbignormal := new(big.Int).Sub(vbig, big.NewInt(27))
vnormal := byte(vbignormal.Uint64())
rsv := append(in[64:128], vnormal)
pubKey, err := crypto.Ecrecover(in[:32], rsv)
// make sure the public key is a valid one // make sure the public key is a valid one
if err != nil { if err != nil {
glog.V(logger.Detail).Infoln("ECRECOVER error: ", err) glog.V(logger.Detail).Infoln("ECRECOVER error: ", err)

View File

@ -167,25 +167,19 @@ func GenerateKey() (*ecdsa.PrivateKey, error) {
return ecdsa.GenerateKey(secp256k1.S256(), rand.Reader) return ecdsa.GenerateKey(secp256k1.S256(), rand.Reader)
} }
// ValidateSignatureValues verifies whether the signature values are valid with
// the given chain rules. The v value is assumed to be either 0 or 1.
func ValidateSignatureValues(v byte, r, s *big.Int, homestead bool) bool { func ValidateSignatureValues(v byte, r, s *big.Int, homestead bool) bool {
if r.Cmp(common.Big1) < 0 || s.Cmp(common.Big1) < 0 { if r.Cmp(common.Big1) < 0 || s.Cmp(common.Big1) < 0 {
return false return false
} }
vint := uint32(v)
// reject upper range of s values (ECDSA malleability) // reject upper range of s values (ECDSA malleability)
// see discussion in secp256k1/libsecp256k1/include/secp256k1.h // see discussion in secp256k1/libsecp256k1/include/secp256k1.h
if homestead && s.Cmp(secp256k1.HalfN) > 0 { if homestead && s.Cmp(secp256k1.HalfN) > 0 {
return false return false
} }
// Frontier: allow s to be in full N range // Frontier: allow s to be in full N range
if s.Cmp(secp256k1.N) >= 0 { return r.Cmp(secp256k1.N) < 0 && s.Cmp(secp256k1.N) < 0 && (v == 0 || v == 1)
return false
}
if r.Cmp(secp256k1.N) < 0 && (vint == 27 || vint == 28) {
return true
} else {
return false
}
} }
func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) { func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) {
@ -199,14 +193,13 @@ func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) {
} }
// Sign calculates an ECDSA signature. // Sign calculates an ECDSA signature.
//
// This function is susceptible to choosen plaintext attacks that can leak // This function is susceptible to choosen plaintext attacks that can leak
// information about the private key that is used for signing. Callers must // information about the private key that is used for signing. Callers must
// be aware that the given hash cannot be choosen by an adversery. Common // be aware that the given hash cannot be choosen by an adversery. Common
// solution is to hash any input before calculating the signature. // solution is to hash any input before calculating the signature.
// //
// Note: the calculated signature is not Ethereum compliant. The yellow paper // The produced signature is in the [R || S || V] format where V is 0 or 1.
// dictates Ethereum singature to have a V value with and offset of 27 v in [27,28].
// Use SignEthereum to get an Ethereum compliant signature.
func Sign(data []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) { func Sign(data []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) {
if len(data) != 32 { if len(data) != 32 {
return nil, fmt.Errorf("hash is required to be exactly 32 bytes (%d)", len(data)) return nil, fmt.Errorf("hash is required to be exactly 32 bytes (%d)", len(data))
@ -218,20 +211,6 @@ func Sign(data []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) {
return return
} }
// SignEthereum calculates an Ethereum ECDSA signature.
// This function is susceptible to choosen plaintext attacks that can leak
// information about the private key that is used for signing. Callers must
// be aware that the given hash cannot be freely choosen by an adversery.
// Common solution is to hash the message before calculating the signature.
func SignEthereum(data []byte, prv *ecdsa.PrivateKey) ([]byte, error) {
sig, err := Sign(data, prv)
if err != nil {
return nil, err
}
sig[64] += 27 // as described in the yellow paper
return sig, err
}
func Encrypt(pub *ecdsa.PublicKey, message []byte) ([]byte, error) { func Encrypt(pub *ecdsa.PublicKey, message []byte) ([]byte, error) {
return ecies.Encrypt(rand.Reader, ecies.ImportECDSAPublic(pub), message, nil, nil) return ecies.Encrypt(rand.Reader, ecies.ImportECDSAPublic(pub), message, nil, nil)
} }

View File

@ -80,22 +80,15 @@ func Test0Key(t *testing.T) {
} }
} }
func testSign(signfn func([]byte, *ecdsa.PrivateKey) ([]byte, error), t *testing.T) { func TestSign(t *testing.T) {
key, _ := HexToECDSA(testPrivHex) key, _ := HexToECDSA(testPrivHex)
addr := common.HexToAddress(testAddrHex) addr := common.HexToAddress(testAddrHex)
msg := Keccak256([]byte("foo")) msg := Keccak256([]byte("foo"))
sig, err := signfn(msg, key) sig, err := Sign(msg, key)
if err != nil { if err != nil {
t.Errorf("Sign error: %s", err) t.Errorf("Sign error: %s", err)
} }
// signfn can return a recover id of either [0,1] or [27,28].
// In the latter case its an Ethereum signature, adjust recover id.
if sig[64] == 27 || sig[64] == 28 {
sig[64] -= 27
}
recoveredPub, err := Ecrecover(msg, sig) recoveredPub, err := Ecrecover(msg, sig)
if err != nil { if err != nil {
t.Errorf("ECRecover error: %s", err) t.Errorf("ECRecover error: %s", err)
@ -117,34 +110,15 @@ func testSign(signfn func([]byte, *ecdsa.PrivateKey) ([]byte, error), t *testing
} }
} }
func TestSign(t *testing.T) { func TestInvalidSign(t *testing.T) {
testSign(Sign, t) if _, err := Sign(make([]byte, 1), nil); err == nil {
}
func TestSignEthereum(t *testing.T) {
testSign(SignEthereum, t)
}
func testInvalidSign(signfn func([]byte, *ecdsa.PrivateKey) ([]byte, error), t *testing.T) {
_, err := signfn(make([]byte, 1), nil)
if err == nil {
t.Errorf("expected sign with hash 1 byte to error") t.Errorf("expected sign with hash 1 byte to error")
} }
if _, err := Sign(make([]byte, 33), nil); err == nil {
_, err = signfn(make([]byte, 33), nil)
if err == nil {
t.Errorf("expected sign with hash 33 byte to error") t.Errorf("expected sign with hash 33 byte to error")
} }
} }
func TestInvalidSign(t *testing.T) {
testInvalidSign(Sign, t)
}
func TestInvalidSignEthereum(t *testing.T) {
testInvalidSign(SignEthereum, t)
}
func TestNewContractAddress(t *testing.T) { func TestNewContractAddress(t *testing.T) {
key, _ := HexToECDSA(testPrivHex) key, _ := HexToECDSA(testPrivHex)
addr := common.HexToAddress(testAddrHex) addr := common.HexToAddress(testAddrHex)
@ -207,38 +181,38 @@ func TestValidateSignatureValues(t *testing.T) {
secp256k1nMinus1 := new(big.Int).Sub(secp256k1.N, common.Big1) secp256k1nMinus1 := new(big.Int).Sub(secp256k1.N, common.Big1)
// correct v,r,s // correct v,r,s
check(true, 27, one, one) check(true, 0, one, one)
check(true, 28, one, one) check(true, 1, one, one)
// incorrect v, correct r,s, // incorrect v, correct r,s,
check(false, 30, one, one) check(false, 2, one, one)
check(false, 26, one, one) check(false, 3, one, one)
// incorrect v, combinations of incorrect/correct r,s at lower limit // incorrect v, combinations of incorrect/correct r,s at lower limit
check(false, 2, zero, zero)
check(false, 2, zero, one)
check(false, 2, one, zero)
check(false, 2, one, one)
// correct v for any combination of incorrect r,s
check(false, 0, zero, zero) check(false, 0, zero, zero)
check(false, 0, zero, one) check(false, 0, zero, one)
check(false, 0, one, zero) check(false, 0, one, zero)
check(false, 0, one, one)
// correct v for any combination of incorrect r,s check(false, 1, zero, zero)
check(false, 27, zero, zero) check(false, 1, zero, one)
check(false, 27, zero, one) check(false, 1, one, zero)
check(false, 27, one, zero)
check(false, 28, zero, zero)
check(false, 28, zero, one)
check(false, 28, one, zero)
// correct sig with max r,s // correct sig with max r,s
check(true, 27, secp256k1nMinus1, secp256k1nMinus1) check(true, 0, secp256k1nMinus1, secp256k1nMinus1)
// correct v, combinations of incorrect r,s at upper limit // correct v, combinations of incorrect r,s at upper limit
check(false, 27, secp256k1.N, secp256k1nMinus1) check(false, 0, secp256k1.N, secp256k1nMinus1)
check(false, 27, secp256k1nMinus1, secp256k1.N) check(false, 0, secp256k1nMinus1, secp256k1.N)
check(false, 27, secp256k1.N, secp256k1.N) check(false, 0, secp256k1.N, secp256k1.N)
// current callers ensures r,s cannot be negative, but let's test for that too // current callers ensures r,s cannot be negative, but let's test for that too
// as crypto package could be used stand-alone // as crypto package could be used stand-alone
check(false, 27, minusOne, one) check(false, 0, minusOne, one)
check(false, 27, one, minusOne) check(false, 0, one, minusOne)
} }
func checkhash(t *testing.T, name string, f func([]byte) []byte, msg, exp []byte) { func checkhash(t *testing.T, name string, f func([]byte) []byte, msg, exp []byte) {

View File

@ -287,11 +287,19 @@ func signHash(data []byte) []byte {
// Sign calculates an Ethereum ECDSA signature for: // Sign calculates an Ethereum ECDSA signature for:
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) // keccack256("\x19Ethereum Signed Message:\n" + len(message) + message))
// //
// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
// where the V value will be 27 or 28 for legacy reasons.
//
// The key used to calculate the signature is decrypted with the given password. // The key used to calculate the signature is decrypted with the given password.
// //
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
func (s *PrivateAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) { func (s *PrivateAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) {
return s.b.AccountManager().SignWithPassphrase(addr, passwd, signHash(data)) signature, err := s.b.AccountManager().SignWithPassphrase(addr, passwd, signHash(data))
if err != nil {
return nil, err
}
signature[64] += 27 // SignWithPassphrase uses canonical secp256k1 signatures (v = 0 or 1), transform to yellow paper
return signature, nil
} }
// EcRecover returns the address for the account that was used to create the signature. // EcRecover returns the address for the account that was used to create the signature.
@ -300,15 +308,19 @@ func (s *PrivateAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr c
// hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message}) // hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message})
// addr = ecrecover(hash, signature) // addr = ecrecover(hash, signature)
// //
// Note, the signature must conform to the secp256k1 curve R, S and V values, where
// the V value must be be 27 or 28 for legacy reasons.
//
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover
func (s *PrivateAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) { func (s *PrivateAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) {
if len(sig) != 65 { if len(sig) != 65 {
return common.Address{}, fmt.Errorf("signature must be 65 bytes long") return common.Address{}, fmt.Errorf("signature must be 65 bytes long")
} }
// see crypto.Ecrecover description if sig[64] != 27 && sig[64] != 28 {
if sig[64] == 27 || sig[64] == 28 { return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)")
sig[64] -= 27
} }
sig[64] -= 27 // Transform yellow paper signatures to canonical secp256k1 form
rpk, err := crypto.Ecrecover(signHash(data), sig) rpk, err := crypto.Ecrecover(signHash(data), sig)
if err != nil { if err != nil {
return common.Address{}, err return common.Address{}, err
@ -964,7 +976,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(txHash common.Hash) (ma
func (s *PublicTransactionPoolAPI) sign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { func (s *PublicTransactionPoolAPI) sign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
signer := types.MakeSigner(s.b.ChainConfig(), s.b.CurrentBlock().Number()) signer := types.MakeSigner(s.b.ChainConfig(), s.b.CurrentBlock().Number())
signature, err := s.b.AccountManager().SignEthereum(addr, signer.Hash(tx).Bytes()) signature, err := s.b.AccountManager().Sign(addr, signer.Hash(tx).Bytes())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1046,11 +1058,10 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen
} }
tx := args.toTransaction() tx := args.toTransaction()
signer := types.MakeSigner(s.b.ChainConfig(), s.b.CurrentBlock().Number()) signer := types.MakeSigner(s.b.ChainConfig(), s.b.CurrentBlock().Number())
signature, err := s.b.AccountManager().SignEthereum(args.From, signer.Hash(tx).Bytes()) signature, err := s.b.AccountManager().Sign(args.From, signer.Hash(tx).Bytes())
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
return submitTransaction(ctx, s.b, tx, signature) return submitTransaction(ctx, s.b, tx, signature)
} }
@ -1084,11 +1095,19 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod
// Sign calculates an ECDSA signature for: // Sign calculates an ECDSA signature for:
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message). // keccack256("\x19Ethereum Signed Message:\n" + len(message) + message).
// //
// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
// where the V value will be 27 or 28 for legacy reasons.
//
// The account associated with addr must be unlocked. // The account associated with addr must be unlocked.
// //
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) { func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
return s.b.AccountManager().SignEthereum(addr, signHash(data)) signature, err := s.b.AccountManager().Sign(addr, signHash(data))
if err == nil {
// Sign uses canonical secp256k1 signatures (v = 0 or 1), transform to yellow paper
signature[64] += 27
}
return signature, err
} }
// SignTransactionResult represents a RLP encoded signed transaction. // SignTransactionResult represents a RLP encoded signed transaction.