diff --git a/crypto/crypto.go b/crypto/crypto.go index b3a8d730b4..672fb069af 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -198,7 +198,9 @@ func Sign(hash []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) { return nil, fmt.Errorf("hash is required to be exactly 32 bytes (%d)", len(hash)) } - sig, err = secp256k1.Sign(hash, common.LeftPadBytes(prv.D.Bytes(), prv.Params().BitSize/8)) + seckey := common.LeftPadBytes(prv.D.Bytes(), prv.Params().BitSize/8) + defer zeroBytes(seckey) + sig, err = secp256k1.Sign(hash, seckey) return } @@ -337,3 +339,9 @@ func PubkeyToAddress(p ecdsa.PublicKey) common.Address { pubBytes := FromECDSAPub(&p) return common.BytesToAddress(Sha3(pubBytes[1:])[12:]) } + +func zeroBytes(bytes []byte) { + for i := range bytes { + bytes[i] = 0 + } +} diff --git a/crypto/secp256k1/secp256.go b/crypto/secp256k1/secp256.go index 7baa456bff..88b43034f2 100644 --- a/crypto/secp256k1/secp256.go +++ b/crypto/secp256k1/secp256.go @@ -19,7 +19,7 @@ package secp256k1 // TODO: set USE_SCALAR_4X64 depending on platform? /* -#cgo CFLAGS: -I./secp256k1 +#cgo CFLAGS: -I./libsecp256k1 #cgo darwin CFLAGS: -I/usr/local/include #cgo freebsd CFLAGS: -I/usr/local/include #cgo linux,arm CFLAGS: -I/usr/local/arm/include @@ -33,7 +33,8 @@ package secp256k1 #define USE_SCALAR_8X32 #define USE_SCALAR_INV_BUILTIN #define NDEBUG -#include "./secp256k1/src/secp256k1.c" +#include "./libsecp256k1/src/secp256k1.c" +#include "./libsecp256k1/src/modules/recovery/main_impl.h" */ import "C" @@ -48,48 +49,51 @@ import ( //#define USE_FIELD_5X64 /* - Todo: - > Centralize key management in module - > add pubkey/private key struct - > Dont let keys leave module; address keys as ints - + TODO: > store private keys in buffer and shuffle (deters persistance on swap disc) - > Byte permutation (changing) + > byte permutation (changing) > xor with chaning random block (to deter scanning memory for 0x63) (stream cipher?) - - On Disk - > Store keys in wallets - > use slow key derivation function for wallet encryption key (2 seconds) + > on disk: store keys in wallets */ -func init() { - //takes 10ms to 100ms - C.secp256k1_start(3) // SECP256K1_START_SIGN | SECP256K1_START_VERIFY -} +// holds ptr to secp256k1_context_struct (see secp256k1/include/secp256k1.h) +var context *C.secp256k1_context -func Stop() { - C.secp256k1_stop() +func init() { + // around 20 ms on a modern CPU. + context = C.secp256k1_context_create(3) // SECP256K1_START_SIGN | SECP256K1_START_VERIFY } func GenerateKeyPair() ([]byte, []byte) { - - pubkey_len := C.int(65) - const seckey_len = 32 - - var pubkey []byte = make([]byte, pubkey_len) - var seckey []byte = randentropy.GetEntropyCSPRNG(seckey_len) - - var pubkey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&pubkey[0])) + var seckey []byte = randentropy.GetEntropyCSPRNG(32) var seckey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&seckey[0])) + var pubkey64 []byte = make([]byte, 64) // secp256k1_pubkey + var pubkey65 []byte = make([]byte, 65) // 65 byte uncompressed pubkey + pubkey64_ptr := (*C.secp256k1_pubkey)(unsafe.Pointer(&pubkey64[0])) + pubkey65_ptr := (*C.uchar)(unsafe.Pointer(&pubkey65[0])) + ret := C.secp256k1_ec_pubkey_create( - pubkey_ptr, &pubkey_len, - seckey_ptr, 0) + context, + pubkey64_ptr, + seckey_ptr, + ) if ret != C.int(1) { - return GenerateKeyPair() //invalid secret, try again + return GenerateKeyPair() // invalid secret, try again } - return pubkey, seckey + + var output_len C.size_t + + C.secp256k1_ec_pubkey_serialize( // always returns 1 + context, + pubkey65_ptr, + &output_len, + pubkey64_ptr, + 0, // SECP256K1_EC_COMPRESSED + ) + + return pubkey65, seckey } func GeneratePubKey(seckey []byte) ([]byte, error) { @@ -97,17 +101,16 @@ func GeneratePubKey(seckey []byte) ([]byte, error) { return nil, err } - pubkey_len := C.int(65) - const seckey_len = 32 + var pubkey []byte = make([]byte, 64) + var pubkey_ptr *C.secp256k1_pubkey = (*C.secp256k1_pubkey)(unsafe.Pointer(&pubkey[0])) - var pubkey []byte = make([]byte, pubkey_len) - - var pubkey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&pubkey[0])) var seckey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&seckey[0])) ret := C.secp256k1_ec_pubkey_create( - pubkey_ptr, &pubkey_len, - seckey_ptr, 0) + context, + pubkey_ptr, + seckey_ptr, + ) if ret != C.int(1) { return nil, errors.New("Unable to generate pubkey from seckey") @@ -117,38 +120,48 @@ func GeneratePubKey(seckey []byte) ([]byte, error) { } func Sign(msg []byte, seckey []byte) ([]byte, error) { + msg_ptr := (*C.uchar)(unsafe.Pointer(&msg[0])) + seckey_ptr := (*C.uchar)(unsafe.Pointer(&seckey[0])) + + sig := make([]byte, 65) + sig_ptr := (*C.secp256k1_ecdsa_recoverable_signature)(unsafe.Pointer(&sig[0])) + nonce := randentropy.GetEntropyCSPRNG(32) + ndata_ptr := unsafe.Pointer(&nonce[0]) - var sig []byte = make([]byte, 65) - var recid C.int + noncefp_ptr := &(*C.secp256k1_nonce_function_default) - var msg_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&msg[0])) - var sig_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&sig[0])) - var seckey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&seckey[0])) - - var noncefp_ptr = &(*C.secp256k1_nonce_function_default) - var ndata_ptr = unsafe.Pointer(&nonce[0]) - - if C.secp256k1_ec_seckey_verify(seckey_ptr) != C.int(1) { + if C.secp256k1_ec_seckey_verify(context, seckey_ptr) != C.int(1) { return nil, errors.New("Invalid secret key") } - ret := C.secp256k1_ecdsa_sign_compact( - msg_ptr, + ret := C.secp256k1_ecdsa_sign_recoverable( + context, sig_ptr, + msg_ptr, seckey_ptr, noncefp_ptr, ndata_ptr, - &recid) + ) - sig[64] = byte(int(recid)) - - if ret != C.int(1) { - // nonce invalid, retry - return Sign(msg, seckey) + if ret == C.int(0) { + return Sign(msg, seckey) //invalid secret, try again } - return sig, nil + sig_serialized := make([]byte, 65) + sig_serialized_ptr := (*C.uchar)(unsafe.Pointer(&sig_serialized[0])) + var recid C.int + + C.secp256k1_ecdsa_recoverable_signature_serialize_compact( + context, + sig_serialized_ptr, // 64 byte compact signature + &recid, + sig_ptr, // 65 byte "recoverable" signature + ) + + sig_serialized[64] = byte(int(recid)) // add back recid to get 65 bytes sig + + return sig_serialized, nil } @@ -157,26 +170,13 @@ func VerifySeckeyValidity(seckey []byte) error { return errors.New("priv key is not 32 bytes") } var seckey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&seckey[0])) - ret := C.secp256k1_ec_seckey_verify(seckey_ptr) + ret := C.secp256k1_ec_seckey_verify(context, seckey_ptr) if int(ret) != 1 { return errors.New("invalid seckey") } return nil } -func VerifyPubkeyValidity(pubkey []byte) error { - if len(pubkey) != 65 { - return errors.New("pub key is not 65 bytes") - } - var pubkey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&pubkey[0])) - ret := C.secp256k1_ec_pubkey_verify(pubkey_ptr, 65) - if int(ret) != 1 { - return errors.New("invalid pubkey") - } - - return nil -} - func VerifySignatureValidity(sig []byte) bool { //64+1 if len(sig) != 65 { @@ -231,36 +231,58 @@ func VerifySignature(msg []byte, sig []byte, pubkey1 []byte) error { return nil } -//recovers the public key from the signature -//recovery of pubkey means correct signature +// recovers a public key from the signature func RecoverPubkey(msg []byte, sig []byte) ([]byte, error) { if len(sig) != 65 { return nil, errors.New("Invalid signature length") } - var pubkey []byte = make([]byte, 65) + msg_ptr := (*C.uchar)(unsafe.Pointer(&msg[0])) + sig_ptr := (*C.uchar)(unsafe.Pointer(&sig[0])) - var msg_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&msg[0])) - var sig_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&sig[0])) - var pubkey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&pubkey[0])) + pubkey := make([]byte, 64) + /* + this slice is used for both the recoverable signature and the + resulting serialized pubkey (both types in libsecp256k1 are 65 + bytes). this saves one allocation of 65 bytes, which is nice as + pubkey recovery is one bottleneck during load in Ethereum + */ + bytes65 := make([]byte, 65) - var pubkeylen C.int + pubkey_ptr := (*C.secp256k1_pubkey)(unsafe.Pointer(&pubkey[0])) + recoverable_sig_ptr := (*C.secp256k1_ecdsa_recoverable_signature)(unsafe.Pointer(&bytes65[0])) - ret := C.secp256k1_ecdsa_recover_compact( - msg_ptr, + recid := C.int(sig[64]) + ret := C.secp256k1_ecdsa_recoverable_signature_parse_compact( + context, + recoverable_sig_ptr, sig_ptr, + recid) + + if ret == C.int(0) { + return nil, errors.New("Failed to parse signature") + } + + ret = C.secp256k1_ecdsa_recover( + context, pubkey_ptr, - &pubkeylen, - C.int(0), - C.int(sig[64]), + recoverable_sig_ptr, + msg_ptr, ) if ret == C.int(0) { return nil, errors.New("Failed to recover public key") - } else if pubkeylen != C.int(65) { - return nil, errors.New("Impossible Error: Invalid recovered public key length") } else { - return pubkey, nil + serialized_pubkey_ptr := (*C.uchar)(unsafe.Pointer(&bytes65[0])) + + var output_len C.size_t + C.secp256k1_ec_pubkey_serialize( // always returns 1 + context, + serialized_pubkey_ptr, + &output_len, + pubkey_ptr, + 0, // SECP256K1_EC_COMPRESSED + ) + return bytes65, nil } - return nil, errors.New("Impossible Error: func RecoverPubkey has reached an unreachable state") } diff --git a/crypto/secp256k1/secp256_test.go b/crypto/secp256k1/secp256_test.go index deeec98d50..45c448f3c6 100644 --- a/crypto/secp256k1/secp256_test.go +++ b/crypto/secp256k1/secp256_test.go @@ -18,169 +18,130 @@ package secp256k1 import ( "bytes" - "fmt" - "log" + "encoding/hex" "testing" "github.com/ethereum/go-ethereum/crypto/randentropy" ) -const TESTS = 10000 // how many tests -const SigSize = 65 //64+1 +const TestCount = 10000 -func Test_Secp256_00(t *testing.T) { - - var nonce []byte = randentropy.GetEntropyCSPRNG(32) //going to get bitcoins stolen! - - if len(nonce) != 32 { - t.Fatal() - } - -} - -//tests for Malleability -//highest bit of S must be 0; 32nd byte -func CompactSigTest(sig []byte) { - - var b int = int(sig[32]) - if b < 0 { - log.Panic() - } - if ((b >> 7) == 1) != ((b & 0x80) == 0x80) { - log.Panic("b= %v b2= %v \n", b, b>>7) - } - if (b & 0x80) == 0x80 { - log.Panic("b= %v b2= %v \n", b, b&0x80) - } -} - -//test pubkey/private generation -func Test_Secp256_01(t *testing.T) { - pubkey, seckey := GenerateKeyPair() +func TestPrivkeyGenerate(t *testing.T) { + _, seckey := GenerateKeyPair() if err := VerifySeckeyValidity(seckey); err != nil { - t.Fatal() - } - if err := VerifyPubkeyValidity(pubkey); err != nil { - t.Fatal() + t.Errorf("seckey not valid: %s", err) } } -//test size of messages -func Test_Secp256_02s(t *testing.T) { +func TestSignatureValidity(t *testing.T) { pubkey, seckey := GenerateKeyPair() msg := randentropy.GetEntropyCSPRNG(32) - sig, _ := Sign(msg, seckey) - CompactSigTest(sig) - if sig == nil { - t.Fatal("Signature nil") + sig, err := Sign(msg, seckey) + if err != nil { + t.Errorf("signature error: %s", err) } + compactSigCheck(t, sig) if len(pubkey) != 65 { - t.Fail() + t.Errorf("pubkey length mismatch: want: 65 have: %d", len(pubkey)) } if len(seckey) != 32 { - t.Fail() + t.Errorf("seckey length mismatch: want: 32 have: %d", len(seckey)) } - if len(sig) != 64+1 { - t.Fail() + if len(sig) != 65 { + t.Errorf("sig length mismatch: want: 65 have: %d", len(sig)) + } + recid := int(sig[64]) + if recid > 4 || recid < 0 { + t.Errorf("sig recid mismatch: want: within 0 to 4 have: %d", int(sig[64])) } - if int(sig[64]) > 4 { - t.Fail() - } //should be 0 to 4 } -//test signing message -func Test_Secp256_02(t *testing.T) { +func TestSignAndRecover(t *testing.T) { pubkey1, seckey := GenerateKeyPair() msg := randentropy.GetEntropyCSPRNG(32) - sig, _ := Sign(msg, seckey) - if sig == nil { - t.Fatal("Signature nil") - } - - pubkey2, _ := RecoverPubkey(msg, sig) - if pubkey2 == nil { - t.Fatal("Recovered pubkey invalid") - } - if bytes.Equal(pubkey1, pubkey2) == false { - t.Fatal("Recovered pubkey does not match") - } - - err := VerifySignature(msg, sig, pubkey1) + sig, err := Sign(msg, seckey) if err != nil { - t.Fatal("Signature invalid") + t.Errorf("signature error: %s", err) + } + pubkey2, err := RecoverPubkey(msg, sig) + if err != nil { + t.Errorf("recover error: %s", err) + } + if !bytes.Equal(pubkey1, pubkey2) { + t.Errorf("pubkey mismatch: want: %x have: %x", pubkey1, pubkey2) + } + err = VerifySignature(msg, sig, pubkey1) + if err != nil { + t.Errorf("signature verification error: %s", err) } } -//test pubkey recovery -func Test_Secp256_02a(t *testing.T) { - pubkey1, seckey1 := GenerateKeyPair() - msg := randentropy.GetEntropyCSPRNG(32) - sig, _ := Sign(msg, seckey1) - - if sig == nil { - t.Fatal("Signature nil") - } - err := VerifySignature(msg, sig, pubkey1) - if err != nil { - t.Fatal("Signature invalid") - } - - pubkey2, _ := RecoverPubkey(msg, sig) - if len(pubkey1) != len(pubkey2) { - t.Fatal() - } - for i, _ := range pubkey1 { - if pubkey1[i] != pubkey2[i] { - t.Fatal() - } - } - if bytes.Equal(pubkey1, pubkey2) == false { - t.Fatal() +func TestRandomMessagesWithSameKey(t *testing.T) { + pubkey, seckey := GenerateKeyPair() + keys := func() ([]byte, []byte) { + // Sign function zeroes the privkey so we need a new one in each call + newkey := make([]byte, len(seckey)) + copy(newkey, seckey) + return pubkey, newkey } + signAndRecoverWithRandomMessages(t, keys) } -//test random messages for the same pub/private key -func Test_Secp256_03(t *testing.T) { - _, seckey := GenerateKeyPair() - for i := 0; i < TESTS; i++ { +func TestRandomMessagesWithRandomKeys(t *testing.T) { + keys := func() ([]byte, []byte) { + pubkey, seckey := GenerateKeyPair() + return pubkey, seckey + } + signAndRecoverWithRandomMessages(t, keys) +} + +func signAndRecoverWithRandomMessages(t *testing.T, keys func() ([]byte, []byte)) { + for i := 0; i < TestCount; i++ { + pubkey1, seckey := keys() msg := randentropy.GetEntropyCSPRNG(32) - sig, _ := Sign(msg, seckey) - CompactSigTest(sig) + sig, err := Sign(msg, seckey) + if err != nil { + t.Fatalf("signature error: %s", err) + } + if sig == nil { + t.Fatal("signature is nil") + } + compactSigCheck(t, sig) + // TODO: why do we flip around the recovery id? sig[len(sig)-1] %= 4 - pubkey2, _ := RecoverPubkey(msg, sig) + + pubkey2, err := RecoverPubkey(msg, sig) + if err != nil { + t.Fatalf("recover error: %s", err) + } if pubkey2 == nil { - t.Fail() + t.Error("pubkey is nil") + } + if !bytes.Equal(pubkey1, pubkey2) { + t.Fatalf("pubkey mismatch: want: %x have: %x", pubkey1, pubkey2) } } } -//test random messages for different pub/private keys -func Test_Secp256_04(t *testing.T) { - for i := 0; i < TESTS; i++ { - pubkey1, seckey := GenerateKeyPair() - msg := randentropy.GetEntropyCSPRNG(32) - sig, _ := Sign(msg, seckey) - CompactSigTest(sig) +func TestRecoveryOfRandomSignature(t *testing.T) { + pubkey1, seckey := GenerateKeyPair() + msg := randentropy.GetEntropyCSPRNG(32) + sig, err := Sign(msg, seckey) + if err != nil { + t.Errorf("signature error: %s", err) + } - if sig[len(sig)-1] >= 4 { - t.Fail() - } + for i := 0; i < TestCount; i++ { + sig = randSig() pubkey2, _ := RecoverPubkey(msg, sig) - if pubkey2 == nil { - t.Fail() - } - if bytes.Equal(pubkey1, pubkey2) == false { - t.Fail() + // recovery can sometimes work, but if so should always give wrong pubkey + if bytes.Equal(pubkey1, pubkey2) { + t.Fatalf("iteration: %d: pubkey mismatch: do NOT want %x: ", i, pubkey2) } } } -//test random signatures against fixed messages; should fail - -//crashes: -// -SIPA look at this - func randSig() []byte { sig := randentropy.GetEntropyCSPRNG(65) sig[32] &= 0x70 @@ -188,67 +149,83 @@ func randSig() []byte { return sig } -func Test_Secp256_06a_alt0(t *testing.T) { +func TestRandomMessagesAgainstValidSig(t *testing.T) { pubkey1, seckey := GenerateKeyPair() msg := randentropy.GetEntropyCSPRNG(32) sig, _ := Sign(msg, seckey) - if sig == nil { - t.Fail() - } - if len(sig) != 65 { - t.Fail() - } - for i := 0; i < TESTS; i++ { - sig = randSig() - pubkey2, _ := RecoverPubkey(msg, sig) - - if bytes.Equal(pubkey1, pubkey2) == true { - t.Fail() - } - - if pubkey2 != nil && VerifySignature(msg, sig, pubkey2) != nil { - t.Fail() - } - - if VerifySignature(msg, sig, pubkey1) == nil { - t.Fail() - } - } -} - -//test random messages against valid signature: should fail - -func Test_Secp256_06b(t *testing.T) { - pubkey1, seckey := GenerateKeyPair() - msg := randentropy.GetEntropyCSPRNG(32) - sig, _ := Sign(msg, seckey) - - fail_count := 0 - for i := 0; i < TESTS; i++ { + for i := 0; i < TestCount; i++ { msg = randentropy.GetEntropyCSPRNG(32) pubkey2, _ := RecoverPubkey(msg, sig) - if bytes.Equal(pubkey1, pubkey2) == true { - t.Fail() + // recovery can sometimes work, but if so should always give wrong pubkey + if bytes.Equal(pubkey1, pubkey2) { + t.Fatalf("iteration: %d: pubkey mismatch: do NOT want %x: ", i, pubkey2) } - - if pubkey2 != nil && VerifySignature(msg, sig, pubkey2) != nil { - t.Fail() - } - - if VerifySignature(msg, sig, pubkey1) == nil { - t.Fail() - } - } - if fail_count != 0 { - fmt.Printf("ERROR: Accepted signature for %v of %v random messages\n", fail_count, TESTS) } } -func TestInvalidKey(t *testing.T) { - p1 := make([]byte, 32) - err := VerifySeckeyValidity(p1) +func TestZeroPrivkey(t *testing.T) { + zeroedBytes := make([]byte, 32) + err := VerifySeckeyValidity(zeroedBytes) if err == nil { - t.Errorf("pvk %x varify sec key should have returned error", p1) + t.Errorf("zeroed bytes should have returned error") + } +} + +// Useful when the underlying libsecp256k1 API changes to quickly +// check only recover function without use of signature function +func TestRecoverSanity(t *testing.T) { + msg, _ := hex.DecodeString("ce0677bb30baa8cf067c88db9811f4333d131bf8bcf12fe7065d211dce971008") + sig, _ := hex.DecodeString("90f27b8b488db00b00606796d2987f6a5f59ae62ea05effe84fef5b8b0e549984a691139ad57a3f0b906637673aa2f63d1f55cb1a69199d4009eea23ceaddc9301") + pubkey1, _ := hex.DecodeString("04e32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a0a2b2667f7e725ceea70c673093bf67663e0312623c8e091b13cf2c0f11ef652") + pubkey2, err := RecoverPubkey(msg, sig) + if err != nil { + t.Fatalf("recover error: %s", err) + } + if !bytes.Equal(pubkey1, pubkey2) { + t.Errorf("pubkey mismatch: want: %x have: %x", pubkey1, pubkey2) + } +} + +// tests for malleability +// highest bit of signature ECDSA s value must be 0, in the 33th byte +func compactSigCheck(t *testing.T, sig []byte) { + var b int = int(sig[32]) + if b < 0 { + t.Errorf("highest bit is negative: %d", b) + } + if ((b >> 7) == 1) != ((b & 0x80) == 0x80) { + t.Errorf("highest bit: %d bit >> 7: %d", b, b>>7) + } + if (b & 0x80) == 0x80 { + t.Errorf("highest bit: %d bit & 0x80: %d", b, b&0x80) + } +} + +// godep go test -v -run=XXX -bench=BenchmarkSignRandomInputEachRound +// add -benchtime=10s to benchmark longer for more accurate average +func BenchmarkSignRandomInputEachRound(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + _, seckey := GenerateKeyPair() + msg := randentropy.GetEntropyCSPRNG(32) + b.StartTimer() + if _, err := Sign(msg, seckey); err != nil { + b.Fatal(err) + } + } +} + +//godep go test -v -run=XXX -bench=BenchmarkRecoverRandomInputEachRound +func BenchmarkRecoverRandomInputEachRound(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + _, seckey := GenerateKeyPair() + msg := randentropy.GetEntropyCSPRNG(32) + sig, _ := Sign(msg, seckey) + b.StartTimer() + if _, err := RecoverPubkey(msg, sig); err != nil { + b.Fatal(err) + } } }