diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index f42605d32b..d4d3098494 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -72,14 +72,6 @@ func BenchmarkSha3(b *testing.B) { fmt.Println(amount, ":", time.Since(start)) } -func Test0Key(t *testing.T) { - key := common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000") - _, err := secp256k1.GeneratePubKey(key) - if err == nil { - t.Errorf("expected error due to zero privkey") - } -} - func TestSign(t *testing.T) { key, _ := HexToECDSA(testPrivHex) addr := common.HexToAddress(testAddrHex) diff --git a/crypto/secp256k1/curve.go b/crypto/secp256k1/curve.go index 6e44a6771f..ccc5f5ce27 100644 --- a/crypto/secp256k1/curve.go +++ b/crypto/secp256k1/curve.go @@ -33,7 +33,6 @@ package secp256k1 import ( "crypto/elliptic" - "io" "math/big" "sync" "unsafe" @@ -257,31 +256,6 @@ func (BitCurve *BitCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) { return BitCurve.ScalarMult(BitCurve.Gx, BitCurve.Gy, k) } -var mask = []byte{0xff, 0x1, 0x3, 0x7, 0xf, 0x1f, 0x3f, 0x7f} - -//TODO: double check if it is okay -// GenerateKey returns a public/private key pair. The private key is generated -// using the given reader, which must return random data. -func (BitCurve *BitCurve) GenerateKey(rand io.Reader) (priv []byte, x, y *big.Int, err error) { - byteLen := (BitCurve.BitSize + 7) >> 3 - priv = make([]byte, byteLen) - - for x == nil { - _, err = io.ReadFull(rand, priv) - if err != nil { - return - } - // We have to mask off any excess bits in the case that the size of the - // underlying field is not a whole number of bytes. - priv[0] &= mask[BitCurve.BitSize%8] - // This is because, in tests, rand will return all zeros and we don't - // want to get the point at infinity and loop forever. - priv[1] ^= 0x42 - x, y = BitCurve.ScalarBaseMult(priv) - } - return -} - // Marshal converts a point into the form specified in section 4.3.6 of ANSI // X9.62. func (BitCurve *BitCurve) Marshal(x, y *big.Int) []byte { diff --git a/crypto/secp256k1/ext.h b/crypto/secp256k1/ext.h new file mode 100644 index 0000000000..ee759fde69 --- /dev/null +++ b/crypto/secp256k1/ext.h @@ -0,0 +1,87 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// secp256k1_context_create_sign_verify creates a context for signing and signature verification. +static secp256k1_context* secp256k1_context_create_sign_verify() { + return secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); +} + +// secp256k1_ecdsa_recover_pubkey recovers the public key of an encoded compact signature. +// +// Returns: 1: recovery was successful +// 0: recovery was not successful +// Args: ctx: pointer to a context object (cannot be NULL) +// Out: pubkey_out: the serialized 65-byte public key of the signer (cannot be NULL) +// In: sigdata: pointer to a 65-byte signature with the recovery id at the end (cannot be NULL) +// msgdata: pointer to a 32-byte message (cannot be NULL) +static int secp256k1_ecdsa_recover_pubkey( + const secp256k1_context* ctx, + unsigned char *pubkey_out, + const unsigned char *sigdata, + const unsigned char *msgdata +) { + secp256k1_ecdsa_recoverable_signature sig; + secp256k1_pubkey pubkey; + + if (!secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &sig, sigdata, (int)sigdata[64])) { + return 0; + } + if (!secp256k1_ecdsa_recover(ctx, &pubkey, &sig, msgdata)) { + return 0; + } + size_t outputlen = 65; + return secp256k1_ec_pubkey_serialize(ctx, pubkey_out, &outputlen, &pubkey, SECP256K1_EC_UNCOMPRESSED); +} + +// secp256k1_pubkey_scalar_mul multiplies a point by a scalar in constant time. +// +// Returns: 1: multiplication was successful +// 0: scalar was invalid (zero or overflow) +// Args: ctx: pointer to a context object (cannot be NULL) +// Out: point: the multiplied point (usually secret) +// In: point: pointer to a 64-byte public point, +// encoded as two 256bit big-endian numbers. +// scalar: a 32-byte scalar with which to multiply the point +int secp256k1_pubkey_scalar_mul(const secp256k1_context* ctx, unsigned char *point, const unsigned char *scalar) { + int ret = 0; + int overflow = 0; + secp256k1_fe feX, feY; + secp256k1_gej res; + secp256k1_ge ge; + secp256k1_scalar s; + ARG_CHECK(point != NULL); + ARG_CHECK(scalar != NULL); + (void)ctx; + + secp256k1_fe_set_b32(&feX, point); + secp256k1_fe_set_b32(&feY, point+32); + secp256k1_ge_set_xy(&ge, &feX, &feY); + secp256k1_scalar_set_b32(&s, scalar, &overflow); + if (overflow || secp256k1_scalar_is_zero(&s)) { + ret = 0; + } else { + secp256k1_ecmult_const(&res, &ge, &s); + secp256k1_ge_set_gej(&ge, &res); + /* Note: can't use secp256k1_pubkey_save here because it is not constant time. */ + secp256k1_fe_normalize(&ge.x); + secp256k1_fe_normalize(&ge.y); + secp256k1_fe_get_b32(point, &ge.x); + secp256k1_fe_get_b32(point+32, &ge.y); + ret = 1; + } + secp256k1_scalar_clear(&s); + return ret; +} diff --git a/crypto/secp256k1/notes.go b/crypto/secp256k1/notes.go deleted file mode 100644 index 49fcf8e2d5..0000000000 --- a/crypto/secp256k1/notes.go +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package secp256k1 - -/* - sipa, int secp256k1_ecdsa_pubkey_create(unsigned char *pubkey, int *pubkeylen, const unsigned char *seckey, int compressed); - is that how i generate private/public keys? - HaltingState: you pass in a random 32-byte string as seckey - HaltingState: if it is valid, the corresponding pubkey is put in pubkey - and true is returned - otherwise, false is returned - around 1 in 2^128 32-byte strings are invalid, so the odds of even ever seeing one is extremely rare - - private keys are mathematically numbers - each has a corresponding point on the curve as public key - a private key is just a number - a public key is a point with x/y coordinates - almost every 256-bit number is a valid private key (one with a point on the curve corresponding to it) - HaltingState: ok? - - more than half of random points are not on the curve - and actually, it is less than the square root, not less than half, sorry :) -!!! - a private key is a NUMBER - a public key is a POINT - half the x,y values in the field are not on the curve, a private key is an integer. - - HaltingState: yes, n,q = private keys; N,Q = corresponding public keys (N=n*G, Q=q*G); then it follows that n*Q = n*q*G = q*n*G = q*N - that's the reason ECDH works - multiplication is associative and commutativ -*/ - -/* - sipa, ok; i am doing compact signatures and I want to know; can someone change the signature to get another valid signature for same message without the private key - because i know they can do that for the normal 72 byte signatures that openssl was putting out - HaltingState: if you don't enforce non-malleability, yes - HaltingState: if you force the highest bit of t - - it _creates_ signatures that already satisfy that condition - but it will accept ones that don't - maybe i should change that, and be strict - yes; i want some way to know signature is valid but fails malleability - well if the highest bit of S is 1, you can take its complement - and end up with a valid signature - that is canonical -*/ - -/* - - sipa, I am signing messages and highest bit of the compact signature is 1!!! - if (b & 0x80) == 0x80 { - log.Panic("b= %v b2= %v \n", b, b&0x80) - } - what bit? -* Pengoo has quit (Ping timeout: 272 seconds) - the highest bit of the first byte of signature - it's the highest bit of S - so the 32nd byte - wtf - -*/ - -/* - For instance, nonces are used in HTTP digest access authentication to calculate an MD5 digest - of the password. The nonces are different each time the 401 authentication challenge - response code is presented, thus making replay attacks virtually impossible. - -can verify client/server match without sending password over network -*/ - -/* - one thing I dont get about armory for instance, -is how the hot-wallet can generate new addresses without -knowing the master key -*/ - -/* - i am yelling at the telehash people for using secp256r1 -instead of secp256k1; they thing r1 is "more secure" despite fact that -there is no implementation that works and wrapping it is now taking -up massive time, lol - ... - - You know that the *r curves are selected via an undisclosed -secret process, right? - HaltingState: telehash is offtopic for this channel. -*/ -/* - For instance, nonces are used in HTTP digest access authentication to calculate an MD5 digest - of the password. The nonces are different each time the 401 authentication challenge - response code is presented, thus making replay attacks virtually impossible. - -can verify client/server match without sending password over network -*/ - -/* -void secp256k1_start(void); -void secp256k1_stop(void); - - * Verify an ECDSA signature. - * Returns: 1: correct signature - * 0: incorrect signature - * -1: invalid public key - * -2: invalid signature - * -int secp256k1_ecdsa_verify(const unsigned char *msg, int msglen, - const unsigned char *sig, int siglen, - const unsigned char *pubkey, int pubkeylen); - -http://www.nilsschneider.net/2013/01/28/recovering-bitcoin-private-keys.html - -Why did this work? ECDSA requires a random number for each signature. If this random -number is ever used twice with the same private key it can be recovered. -This transaction was generated by a hardware bitcoin wallet using a pseudo-random number -generator that was returning the same “random” number every time. - -Nonce is 32 bytes? - - * Create an ECDSA signature. - * Returns: 1: signature created - * 0: nonce invalid, try another one - * In: msg: the message being signed - * msglen: the length of the message being signed - * seckey: pointer to a 32-byte secret key (assumed to be valid) - * nonce: pointer to a 32-byte nonce (generated with a cryptographic PRNG) - * Out: sig: pointer to a 72-byte array where the signature will be placed. - * siglen: pointer to an int, which will be updated to the signature length (<=72). - * -int secp256k1_ecdsa_sign(const unsigned char *msg, int msglen, - unsigned char *sig, int *siglen, - const unsigned char *seckey, - const unsigned char *nonce); - - - * Create a compact ECDSA signature (64 byte + recovery id). - * Returns: 1: signature created - * 0: nonce invalid, try another one - * In: msg: the message being signed - * msglen: the length of the message being signed - * seckey: pointer to a 32-byte secret key (assumed to be valid) - * nonce: pointer to a 32-byte nonce (generated with a cryptographic PRNG) - * Out: sig: pointer to a 64-byte array where the signature will be placed. - * recid: pointer to an int, which will be updated to contain the recovery id. - * -int secp256k1_ecdsa_sign_compact(const unsigned char *msg, int msglen, - unsigned char *sig64, - const unsigned char *seckey, - const unsigned char *nonce, - int *recid); - - * Recover an ECDSA public key from a compact signature. - * Returns: 1: public key successfully recovered (which guarantees a correct signature). - * 0: otherwise. - * In: msg: the message assumed to be signed - * msglen: the length of the message - * compressed: whether to recover a compressed or uncompressed pubkey - * recid: the recovery id (as returned by ecdsa_sign_compact) - * Out: pubkey: pointer to a 33 or 65 byte array to put the pubkey. - * pubkeylen: pointer to an int that will contain the pubkey length. - * - -recovery id is between 0 and 3 - -int secp256k1_ecdsa_recover_compact(const unsigned char *msg, int msglen, - const unsigned char *sig64, - unsigned char *pubkey, int *pubkeylen, - int compressed, int recid); - - - * Verify an ECDSA secret key. - * Returns: 1: secret key is valid - * 0: secret key is invalid - * In: seckey: pointer to a 32-byte secret key - * -int secp256k1_ecdsa_seckey_verify(const unsigned char *seckey); - -** Just validate a public key. - * Returns: 1: valid public key - * 0: invalid public key - * -int secp256k1_ecdsa_pubkey_verify(const unsigned char *pubkey, int pubkeylen); - -** Compute the public key for a secret key. - * In: compressed: whether the computed public key should be compressed - * seckey: pointer to a 32-byte private key. - * Out: pubkey: pointer to a 33-byte (if compressed) or 65-byte (if uncompressed) - * area to store the public key. - * pubkeylen: pointer to int that will be updated to contains the pubkey's - * length. - * Returns: 1: secret was valid, public key stores - * 0: secret was invalid, try again. - * -int secp256k1_ecdsa_pubkey_create(unsigned char *pubkey, int *pubkeylen, const unsigned char *seckey, int compressed); -*/ diff --git a/crypto/secp256k1/pubkey_scalar_mul.h b/crypto/secp256k1/pubkey_scalar_mul.h deleted file mode 100644 index 0511545ec0..0000000000 --- a/crypto/secp256k1/pubkey_scalar_mul.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -/** Multiply point by scalar in constant time. - * Returns: 1: multiplication was successful - * 0: scalar was invalid (zero or overflow) - * Args: ctx: pointer to a context object (cannot be NULL) - * Out: point: the multiplied point (usually secret) - * In: point: pointer to a 64-byte bytepublic point, - encoded as two 256bit big-endian numbers. - * scalar: a 32-byte scalar with which to multiply the point - */ -int secp256k1_pubkey_scalar_mul(const secp256k1_context* ctx, unsigned char *point, const unsigned char *scalar) { - int ret = 0; - int overflow = 0; - secp256k1_fe feX, feY; - secp256k1_gej res; - secp256k1_ge ge; - secp256k1_scalar s; - ARG_CHECK(point != NULL); - ARG_CHECK(scalar != NULL); - (void)ctx; - - secp256k1_fe_set_b32(&feX, point); - secp256k1_fe_set_b32(&feY, point+32); - secp256k1_ge_set_xy(&ge, &feX, &feY); - secp256k1_scalar_set_b32(&s, scalar, &overflow); - if (overflow || secp256k1_scalar_is_zero(&s)) { - ret = 0; - } else { - secp256k1_ecmult_const(&res, &ge, &s); - secp256k1_ge_set_gej(&ge, &res); - /* Note: can't use secp256k1_pubkey_save here because it is not constant time. */ - secp256k1_fe_normalize(&ge.x); - secp256k1_fe_normalize(&ge.y); - secp256k1_fe_get_b32(point, &ge.x); - secp256k1_fe_get_b32(point+32, &ge.y); - ret = 1; - } - secp256k1_scalar_clear(&s); - return ret; -} - diff --git a/crypto/secp256k1/secp256.go b/crypto/secp256k1/secp256.go index 2c5f614504..ffa186108d 100644 --- a/crypto/secp256k1/secp256.go +++ b/crypto/secp256k1/secp256.go @@ -14,10 +14,9 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . +// Package secp256k1 wraps the bitcoin secp256k1 C library. package secp256k1 -// TODO: set USE_SCALAR_4X64 depending on platform? - /* #cgo CFLAGS: -I./libsecp256k1 #cgo CFLAGS: -I./libsecp256k1/src/ @@ -29,7 +28,7 @@ package secp256k1 #define NDEBUG #include "./libsecp256k1/src/secp256k1.c" #include "./libsecp256k1/src/modules/recovery/main_impl.h" -#include "pubkey_scalar_mul.h" +#include "ext.h" typedef void (*callbackFunc) (const char* msg, void* data); extern void secp256k1GoPanicIllegal(const char* msg, void* data); @@ -45,16 +44,6 @@ import ( "github.com/ethereum/go-ethereum/crypto/randentropy" ) -//#define USE_FIELD_5X64 - -/* - TODO: - > store private keys in buffer and shuffle (deters persistence on swap disc) - > byte permutation (changing) - > xor with chaning random block (to deter scanning memory for 0x63) (stream cipher?) -*/ - -// holds ptr to secp256k1_context_struct (see secp256k1/include/secp256k1.h) var ( context *C.secp256k1_context N *big.Int @@ -67,127 +56,53 @@ func init() { HalfN, _ = new(big.Int).SetString("7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0", 16) // around 20 ms on a modern CPU. - context = C.secp256k1_context_create(3) // SECP256K1_START_SIGN | SECP256K1_START_VERIFY + context = C.secp256k1_context_create_sign_verify() C.secp256k1_context_set_illegal_callback(context, C.callbackFunc(C.secp256k1GoPanicIllegal), nil) C.secp256k1_context_set_error_callback(context, C.callbackFunc(C.secp256k1GoPanicError), nil) } var ( - ErrInvalidMsgLen = errors.New("invalid message length for signature recovery") + ErrInvalidMsgLen = errors.New("invalid message length, need 32 bytes") ErrInvalidSignatureLen = errors.New("invalid signature length") ErrInvalidRecoveryID = errors.New("invalid signature recovery id") + ErrInvalidKey = errors.New("invalid private key") + ErrSignFailed = errors.New("signing failed") + ErrRecoverFailed = errors.New("recovery failed") ) -func GenerateKeyPair() ([]byte, []byte) { - 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( - context, - pubkey64_ptr, - seckey_ptr, - ) - - if ret != C.int(1) { - return GenerateKeyPair() // invalid secret, try again - } - - 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) { - if err := VerifySeckeyValidity(seckey); err != nil { - return nil, err - } - - var pubkey []byte = make([]byte, 64) - var pubkey_ptr *C.secp256k1_pubkey = (*C.secp256k1_pubkey)(unsafe.Pointer(&pubkey[0])) - - var seckey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&seckey[0])) - - ret := C.secp256k1_ec_pubkey_create( - context, - pubkey_ptr, - seckey_ptr, - ) - - if ret != C.int(1) { - return nil, errors.New("Unable to generate pubkey from seckey") - } - - return pubkey, nil -} - +// Sign creates a recoverable ECDSA signature. +// The produced signature is in the 65-byte [R || S || V] format where V is 0 or 1. 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]) - - noncefp_ptr := &(*C.secp256k1_nonce_function_default) - - if C.secp256k1_ec_seckey_verify(context, seckey_ptr) != C.int(1) { - return nil, errors.New("Invalid secret key") + if len(msg) != 32 { + return nil, ErrInvalidMsgLen } - - ret := C.secp256k1_ecdsa_sign_recoverable( - context, - sig_ptr, - msg_ptr, - seckey_ptr, - noncefp_ptr, - ndata_ptr, - ) - - if ret == C.int(0) { - return Sign(msg, seckey) //invalid secret, try again - } - - 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 - -} - -func VerifySeckeyValidity(seckey []byte) error { if len(seckey) != 32 { - return errors.New("priv key is not 32 bytes") + return nil, ErrInvalidKey } - var seckey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&seckey[0])) - ret := C.secp256k1_ec_seckey_verify(context, seckey_ptr) - if int(ret) != 1 { - return errors.New("invalid seckey") + seckeydata := (*C.uchar)(unsafe.Pointer(&seckey[0])) + if C.secp256k1_ec_seckey_verify(context, seckeydata) != 1 { + return nil, ErrInvalidKey } - return nil + + var ( + msgdata = (*C.uchar)(unsafe.Pointer(&msg[0])) + nonce = randentropy.GetEntropyCSPRNG(32) + noncefunc = &(*C.secp256k1_nonce_function_default) + noncefuncData = unsafe.Pointer(&nonce[0]) + sigstruct C.secp256k1_ecdsa_recoverable_signature + ) + if C.secp256k1_ecdsa_sign_recoverable(context, &sigstruct, msgdata, seckeydata, noncefunc, noncefuncData) == 0 { + return nil, ErrSignFailed + } + + var ( + sig = make([]byte, 65) + sigdata = (*C.uchar)(unsafe.Pointer(&sig[0])) + recid C.int + ) + C.secp256k1_ecdsa_recoverable_signature_serialize_compact(context, sigdata, &recid, &sigstruct) + sig[64] = byte(recid) // add back recid to get 65 bytes sig + return sig, nil } // RecoverPubkey returns the the public key of the signer. @@ -202,49 +117,15 @@ func RecoverPubkey(msg []byte, sig []byte) ([]byte, error) { return nil, err } - msg_ptr := (*C.uchar)(unsafe.Pointer(&msg[0])) - sig_ptr := (*C.uchar)(unsafe.Pointer(&sig[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) - pubkey_ptr := (*C.secp256k1_pubkey)(unsafe.Pointer(&pubkey[0])) - recoverable_sig_ptr := (*C.secp256k1_ecdsa_recoverable_signature)(unsafe.Pointer(&bytes65[0])) - 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, - recoverable_sig_ptr, - msg_ptr, + var ( + pubkey = make([]byte, 65) + sigdata = (*C.uchar)(unsafe.Pointer(&sig[0])) + msgdata = (*C.uchar)(unsafe.Pointer(&msg[0])) ) - if ret == C.int(0) { - return nil, errors.New("Failed to recover public key") + if C.secp256k1_ecdsa_recover_pubkey(context, (*C.uchar)(unsafe.Pointer(&pubkey[0])), sigdata, msgdata) == 0 { + return nil, ErrRecoverFailed } - - 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 pubkey, nil } func checkSignature(sig []byte) error { diff --git a/crypto/secp256k1/secp256_test.go b/crypto/secp256k1/secp256_test.go index a35e04e742..ec28b8e39e 100644 --- a/crypto/secp256k1/secp256_test.go +++ b/crypto/secp256k1/secp256_test.go @@ -18,6 +18,9 @@ package secp256k1 import ( "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" "encoding/hex" "testing" @@ -26,15 +29,41 @@ import ( const TestCount = 1000 -func TestPrivkeyGenerate(t *testing.T) { - _, seckey := GenerateKeyPair() - if err := VerifySeckeyValidity(seckey); err != nil { - t.Errorf("seckey not valid: %s", err) +func generateKeyPair() (pubkey, privkey []byte) { + key, err := ecdsa.GenerateKey(S256(), rand.Reader) + if err != nil { + panic(err) + } + pubkey = elliptic.Marshal(S256(), key.X, key.Y) + privkey = make([]byte, 32) + readBits(privkey, key.D) + return pubkey, privkey +} + +func randSig() []byte { + sig := randentropy.GetEntropyCSPRNG(65) + sig[32] &= 0x70 + sig[64] %= 4 + return sig +} + +// 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) } } func TestSignatureValidity(t *testing.T) { - pubkey, seckey := GenerateKeyPair() + pubkey, seckey := generateKeyPair() msg := randentropy.GetEntropyCSPRNG(32) sig, err := Sign(msg, seckey) if err != nil { @@ -57,7 +86,7 @@ func TestSignatureValidity(t *testing.T) { } func TestInvalidRecoveryID(t *testing.T) { - _, seckey := GenerateKeyPair() + _, seckey := generateKeyPair() msg := randentropy.GetEntropyCSPRNG(32) sig, _ := Sign(msg, seckey) sig[64] = 99 @@ -68,7 +97,7 @@ func TestInvalidRecoveryID(t *testing.T) { } func TestSignAndRecover(t *testing.T) { - pubkey1, seckey := GenerateKeyPair() + pubkey1, seckey := generateKeyPair() msg := randentropy.GetEntropyCSPRNG(32) sig, err := Sign(msg, seckey) if err != nil { @@ -84,7 +113,7 @@ func TestSignAndRecover(t *testing.T) { } func TestRandomMessagesWithSameKey(t *testing.T) { - pubkey, seckey := GenerateKeyPair() + pubkey, seckey := generateKeyPair() keys := func() ([]byte, []byte) { return pubkey, seckey } @@ -93,7 +122,7 @@ func TestRandomMessagesWithSameKey(t *testing.T) { func TestRandomMessagesWithRandomKeys(t *testing.T) { keys := func() ([]byte, []byte) { - pubkey, seckey := GenerateKeyPair() + pubkey, seckey := generateKeyPair() return pubkey, seckey } signAndRecoverWithRandomMessages(t, keys) @@ -129,7 +158,7 @@ func signAndRecoverWithRandomMessages(t *testing.T, keys func() ([]byte, []byte) } func TestRecoveryOfRandomSignature(t *testing.T) { - pubkey1, _ := GenerateKeyPair() + pubkey1, _ := generateKeyPair() msg := randentropy.GetEntropyCSPRNG(32) for i := 0; i < TestCount; i++ { @@ -141,15 +170,8 @@ func TestRecoveryOfRandomSignature(t *testing.T) { } } -func randSig() []byte { - sig := randentropy.GetEntropyCSPRNG(65) - sig[32] &= 0x70 - sig[64] %= 4 - return sig -} - func TestRandomMessagesAgainstValidSig(t *testing.T) { - pubkey1, seckey := GenerateKeyPair() + pubkey1, seckey := generateKeyPair() msg := randentropy.GetEntropyCSPRNG(32) sig, _ := Sign(msg, seckey) @@ -163,14 +185,6 @@ func TestRandomMessagesAgainstValidSig(t *testing.T) { } } -func TestZeroPrivkey(t *testing.T) { - zeroedBytes := make([]byte, 32) - err := VerifySeckeyValidity(zeroedBytes) - if err == nil { - 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) { @@ -186,23 +200,8 @@ func TestRecoverSanity(t *testing.T) { } } -// 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) - } -} - func BenchmarkSign(b *testing.B) { - _, seckey := GenerateKeyPair() + _, seckey := generateKeyPair() msg := randentropy.GetEntropyCSPRNG(32) b.ResetTimer() @@ -213,7 +212,7 @@ func BenchmarkSign(b *testing.B) { func BenchmarkRecover(b *testing.B) { msg := randentropy.GetEntropyCSPRNG(32) - _, seckey := GenerateKeyPair() + _, seckey := generateKeyPair() sig, _ := Sign(msg, seckey) b.ResetTimer()