* switch from fork (tendermint/btcd) to orig package (btcsuite/btcd); also
- remove obsolete check in test `size != -1` is always true
- WIP as the serialization still needs to be wrapped
* WIP: wrap signature & privkey, pubkey needs to be wrapped as well
* wrap pubkey too
* use "github.com/ethereum/go-ethereum/crypto/secp256k1" if cgo is
available, else use "github.com/btcsuite/btcd/btcec" and take care of
lower-S when verifying
Annoyingly, had to disable pruning when importing
github.com/ethereum/go-ethereum/ :-/
* update comment
* update comment
* emulate signature_nocgo.go for additional benchmarks:
592bf6a59c/crypto/signature_nocgo.go (L60-L76)
* use our format (r || s) in lower-s form when in the non-cgo case
* remove comment about using the C library directly
* vendor github.com/btcsuite/btcd too
* Add test for the !cgo case
* update changelog pending
Closes #3162 #3163
Refs #1958, #2091, tendermint/btcd#1
pull/3252/head
@ -0,0 +1,24 @@ | |||
// +build cgo | |||
package secp256k1 | |||
import ( | |||
"github.com/ethereum/go-ethereum/crypto/secp256k1" | |||
"github.com/tendermint/tendermint/crypto" | |||
) | |||
// Sign creates an ECDSA signature on curve Secp256k1, using SHA256 on the msg. | |||
func (privKey PrivKeySecp256k1) Sign(msg []byte) ([]byte, error) { | |||
rsv, err := secp256k1.Sign(crypto.Sha256(msg), privKey[:]) | |||
if err != nil { | |||
return nil, err | |||
} | |||
// we do not need v in r||s||v: | |||
rs := rsv[:len(rsv)-1] | |||
return rs, nil | |||
} | |||
func (pubKey PubKeySecp256k1) VerifyBytes(msg []byte, sig []byte) bool { | |||
return secp256k1.VerifySignature(pubKey[:], crypto.Sha256(msg), sig) | |||
} |
@ -0,0 +1,71 @@ | |||
// +build !cgo | |||
package secp256k1 | |||
import ( | |||
"math/big" | |||
secp256k1 "github.com/btcsuite/btcd/btcec" | |||
"github.com/tendermint/tendermint/crypto" | |||
) | |||
// used to reject malleable signatures | |||
// see: | |||
// - https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/signature_nocgo.go#L90-L93 | |||
// - https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/crypto.go#L39 | |||
var secp256k1N, _ = new(big.Int).SetString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16) | |||
var secp256k1halfN = new(big.Int).Div(secp256k1N, big.NewInt(2)) | |||
// Sign creates an ECDSA signature on curve Secp256k1, using SHA256 on the msg. | |||
// The returned signature will be of the form R || S (in lower-S form). | |||
func (privKey PrivKeySecp256k1) Sign(msg []byte) ([]byte, error) { | |||
priv, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey[:]) | |||
sig, err := priv.Sign(crypto.Sha256(msg)) | |||
if err != nil { | |||
return nil, err | |||
} | |||
sigBytes := serializeSig(sig) | |||
return sigBytes, nil | |||
} | |||
// VerifyBytes verifies a signature of the form R || S. | |||
// It rejects signatures which are not in lower-S form. | |||
func (pubKey PubKeySecp256k1) VerifyBytes(msg []byte, sigStr []byte) bool { | |||
if len(sigStr) != 64 { | |||
return false | |||
} | |||
pub, err := secp256k1.ParsePubKey(pubKey[:], secp256k1.S256()) | |||
if err != nil { | |||
return false | |||
} | |||
// parse the signature: | |||
signature := signatureFromBytes(sigStr) | |||
// Reject malleable signatures. libsecp256k1 does this check but btcec doesn't. | |||
// see: https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/signature_nocgo.go#L90-L93 | |||
if signature.S.Cmp(secp256k1halfN) > 0 { | |||
return false | |||
} | |||
return signature.Verify(crypto.Sha256(msg), pub) | |||
} | |||
// Read Signature struct from R || S. Caller needs to ensure | |||
// that len(sigStr) == 64. | |||
func signatureFromBytes(sigStr []byte) *secp256k1.Signature { | |||
return &secp256k1.Signature{ | |||
new(big.Int).SetBytes(sigStr[:32]), | |||
new(big.Int).SetBytes(sigStr[32:64]), | |||
} | |||
} | |||
// Serialize signature to R || S. | |||
// R, S are padded to 32 bytes respectively. | |||
func serializeSig(sig *secp256k1.Signature) []byte { | |||
rBytes := sig.R.Bytes() | |||
sBytes := sig.S.Bytes() | |||
sigBytes := make([]byte, 64) | |||
// 0 pad the byte arrays from the left if they aren't big enough. | |||
copy(sigBytes[32-len(rBytes):32], rBytes) | |||
copy(sigBytes[64-len(sBytes):64], sBytes) | |||
return sigBytes | |||
} |
@ -0,0 +1,39 @@ | |||
// +build !cgo | |||
package secp256k1 | |||
import ( | |||
"testing" | |||
secp256k1 "github.com/btcsuite/btcd/btcec" | |||
"github.com/stretchr/testify/require" | |||
) | |||
// Ensure that signature verification works, and that | |||
// non-canonical signatures fail. | |||
// Note: run with CGO_ENABLED=0 or go test -tags !cgo. | |||
func TestSignatureVerificationAndRejectUpperS(t *testing.T) { | |||
msg := []byte("We have lingered long enough on the shores of the cosmic ocean.") | |||
for i := 0; i < 500; i++ { | |||
priv := GenPrivKey() | |||
sigStr, err := priv.Sign(msg) | |||
require.NoError(t, err) | |||
sig := signatureFromBytes(sigStr) | |||
require.False(t, sig.S.Cmp(secp256k1halfN) > 0) | |||
pub := priv.PubKey() | |||
require.True(t, pub.VerifyBytes(msg, sigStr)) | |||
// malleate: | |||
sig.S.Sub(secp256k1.S256().CurveParams.N, sig.S) | |||
require.True(t, sig.S.Cmp(secp256k1halfN) > 0) | |||
malSigStr := serializeSig(sig) | |||
require.False(t, pub.VerifyBytes(msg, malSigStr), | |||
"VerifyBytes incorrect with malleated & invalid S. sig=%v, key=%v", | |||
sig, | |||
priv, | |||
) | |||
} | |||
} |