From cd0bd2bb1790cd66650066d024bddfb772e54eb5 Mon Sep 17 00:00:00 2001 From: Elichai Turkel Date: Sun, 8 Nov 2020 15:37:53 +0200 Subject: [PATCH] Refactor privatekey into new SchnorrKeyPair to represent upstream's API --- schnorr_keypair.go | 162 +++++++++++++++++++++++++++++++++++++++++++++ secp256k1.go | 88 ------------------------ 2 files changed, 162 insertions(+), 88 deletions(-) create mode 100644 schnorr_keypair.go diff --git a/schnorr_keypair.go b/schnorr_keypair.go new file mode 100644 index 0000000..4addcd4 --- /dev/null +++ b/schnorr_keypair.go @@ -0,0 +1,162 @@ +package secp256k1 + +// #include "./depend/secp256k1/include/secp256k1_extrakeys.h" +// #include "./depend/secp256k1/include/secp256k1_schnorrsig.h" +import "C" +import "C" +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "github.com/pkg/errors" + "unsafe" +) + +// errZeroedKeyPair is the error returned when using a zeroed pubkey +var errZeroedKeyPair = errors.New("the key pair is zeroed, which isn't a valid SchnorrKeyPair") + +// SchnorrKeyPair is a type representing a pair of Secp256k1 private and public keys. +// This can be used to create Schnorr signatures +type SchnorrKeyPair struct { + keypair C.secp256k1_keypair +} + +// SerializedPrivateKey is a byte array representing the storage representation of a SchnorrKeyPair +type SerializedPrivateKey [SerializedPrivateKeySize]byte + +// String returns the SchnorrKeyPair as the hexadecimal string +func (key SerializedPrivateKey) String() string { + return hex.EncodeToString(key[:]) +} + +// String returns the SchnorrKeyPair as the hexadecimal string +func (key SchnorrKeyPair) String() string { + return key.SerializePrivateKey().String() +} + +// DeserializePrivateKey returns a SchnorrKeyPair type from a 32 byte private key. +// will verify it's a valid private key(Group Order > key > 0) +func DeserializePrivateKey(data *SerializedPrivateKey) (*SchnorrKeyPair, error) { + key := SchnorrKeyPair{} + cPtrPrivateKey := (*C.uchar)(&data[0]) + + ret := C.secp256k1_keypair_create(context, &key.keypair, cPtrPrivateKey) + if ret != 1 { + return nil, errors.New("invalid SchnorrKeyPair (zero or bigger than the group order)") + } + return &key, nil +} + +// DeserializePrivateKeyFromSlice returns a SchnorrKeyPair type from a serialized private key slice. +// will verify that it's 32 byte and it's a valid private key(Group Order > key > 0) +func DeserializePrivateKeyFromSlice(data []byte) (key *SchnorrKeyPair, err error) { + if len(data) != SerializedPrivateKeySize { + return nil, errors.Errorf("invalid private key length got %d, expected %d", len(data), + SerializedPrivateKeySize) + } + + serializedKey := &SerializedPrivateKey{} + copy(serializedKey[:], data) + return DeserializePrivateKey(serializedKey) +} + +// GeneratePrivateKey generates a random valid private key from `crypto/rand` +func GeneratePrivateKey() (key *SchnorrKeyPair, err error) { + rawKey := SerializedPrivateKey{} + for { + n, err := rand.Read(rawKey[:]) + if err != nil || n != len(rawKey) { + return nil, err + } + key, err = DeserializePrivateKey(&rawKey) + if err == nil { + return key, nil + } + } +} + +// SerializePrivateKey returns the private key in the keypair. +func (key *SchnorrKeyPair) SerializePrivateKey() *SerializedPrivateKey { + // TODO: Replace with upstream function when merged: https://github.com/bitcoin-core/secp256k1/pull/845 + ret := SerializedPrivateKey{} + for i := 0; i < 32; i++ { + ret[i] = byte(key.keypair.data[i]) + } + return &ret +} + +// Add a tweak to the public key by doing `key + tweak % Group Order` and adjust the pub/priv keys according to parity. this adds it in place. +// This is meant for creating BIP-32(HD) wallets +func (key *SchnorrKeyPair) Add(tweak [32]byte) error { + if key.isZeroed() { + return errors.WithStack(errZeroedKeyPair) + } + cPtrTweak := (*C.uchar)(&tweak[0]) + ret := C.secp256k1_keypair_xonly_tweak_add(context, &key.keypair, cPtrTweak) + if ret != 1 { + return errors.New("failed Adding to private key. Tweak is bigger than the order or the complement of the private key") + } + return nil +} + +// SchnorrPublicKey generates a PublicKey for the corresponding private key. +func (key *SchnorrKeyPair) SchnorrPublicKey() (*SchnorrPublicKey, error) { + pubkey, _, err := key.schnorrPublicKeyInternal() + return pubkey, err +} + +func (key *SchnorrKeyPair) schnorrPublicKeyInternal() (pubkey *SchnorrPublicKey, wasOdd bool, err error) { + if key.isZeroed() { + return nil, false, errors.WithStack(errZeroedKeyPair) + } + pubkey = &SchnorrPublicKey{} + cParity := C.int(42) + ret := C.secp256k1_keypair_xonly_pub(context, &pubkey.pubkey, &cParity, &key.keypair) + if ret != 1 { + return nil, false, errors.New("the keypair contains invalid data") + } + + return pubkey, parityBitToBool(cParity), nil + +} + +// SchnorrSign creates a schnorr signature using the private key and the input hashed message. +// Notice: the [32] byte array *MUST* be a hash of a message. +func (key *SchnorrKeyPair) SchnorrSign(hash *Hash) (*SchnorrSignature, error) { + var auxilaryRand [32]byte + n, err := rand.Read(auxilaryRand[:]) + if err != nil || n != len(auxilaryRand) { + return nil, err + } + return key.schnorrSignInternal(hash, &auxilaryRand) +} + +func (key *SchnorrKeyPair) schnorrSignInternal(hash *Hash, auxiliaryRand *[32]byte) (*SchnorrSignature, error) { + if key.isZeroed() { + return nil, errors.WithStack(errZeroedKeyPair) + } + signature := SchnorrSignature{} + cPtrSig := (*C.uchar)(&signature.signature[0]) + cPtrHash := (*C.uchar)(&hash[0]) + cPtrAux := unsafe.Pointer(auxiliaryRand) + ret := C.secp256k1_schnorrsig_sign(context, cPtrSig, cPtrHash, &key.keypair, C.secp256k1_nonce_function_bip340, cPtrAux) + if ret != 1 { + return nil, errors.New("failed Signing. You should call `DeserializePrivateKey` before calling this") + } + return &signature, nil + +} +func (key *SchnorrKeyPair) isZeroed() bool { + return isZeroed(key.keypair.data[:32]) || isZeroed(key.keypair.data[32:64]) || isZeroed(key.keypair.data[64:]) +} + +func parityBitToBool(parity C.int) bool { + switch parity { + case 0: + return false + case 1: + return true + default: + panic(fmt.Sprintf("should never happen, parity should always be 1 or 0, instead got: %d", int(parity))) + } +} diff --git a/secp256k1.go b/secp256k1.go index 7097ac9..4b0710e 100644 --- a/secp256k1.go +++ b/secp256k1.go @@ -76,91 +76,3 @@ func (hash *Hash) SetBytes(newHash []byte) error { func (hash *Hash) String() string { return hex.EncodeToString(hash[:]) } - -// PrivateKey is a type representing a Secp256k1 private key. -// This private key can be used to create Schnorr/ECDSA signatures -type PrivateKey struct { - privateKey [SerializedPrivateKeySize]byte -} - -// SerializedPrivateKey is a byte array representing the storage representation of a PrivateKey -type SerializedPrivateKey [SerializedPrivateKeySize]byte - -// String returns the PrivateKey as the hexadecimal string -func (key *SerializedPrivateKey) String() string { - return hex.EncodeToString(key[:]) -} - -// String returns the PrivateKey as the hexadecimal string -func (key *PrivateKey) String() string { - return key.Serialize().String() -} - -// DeserializePrivateKey returns a PrivateKey type from a 32 byte private key. -// will verify it's a valid private key(Group Order > key > 0) -func DeserializePrivateKey(data *SerializedPrivateKey) (key *PrivateKey, err error) { - cPtr := (*C.uchar)(&data[0]) - - ret := C.secp256k1_ec_seckey_verify(C.secp256k1_context_no_precomp, cPtr) - if ret != 1 { - return nil, errors.New("invalid PrivateKey (zero or bigger than the group order)") - } - - return &PrivateKey{*data}, nil -} - -// DeserializePrivateKeyFromSlice returns a PrivateKey type from a serialized private key slice. -// will verify that it's 32 byte and it's a valid private key(Group Order > key > 0) -func DeserializePrivateKeyFromSlice(data []byte) (key *PrivateKey, err error) { - if len(data) != SerializedPrivateKeySize { - return nil, errors.Errorf("invalid private key length got %d, expected %d", len(data), - SerializedPrivateKeySize) - } - - serializedKey := &SerializedPrivateKey{} - copy(serializedKey[:], data) - return DeserializePrivateKey(serializedKey) -} - -// GeneratePrivateKey generates a random valid private key from `crypto/rand` -func GeneratePrivateKey() (key *PrivateKey, err error) { - key = &PrivateKey{} - cPtr := (*C.uchar)(&key.privateKey[0]) - for { - n, tmpErr := rand.Read(key.privateKey[:]) - if tmpErr != nil || n != len(key.privateKey) { - return nil, tmpErr - } - ret := C.secp256k1_ec_seckey_verify(C.secp256k1_context_no_precomp, cPtr) - if ret == 1 { - return - } - } -} - -// Serialize a private key -func (key *PrivateKey) Serialize() *SerializedPrivateKey { - ret := SerializedPrivateKey(key.privateKey) - return &ret -} - -// Negate a private key in place. -func (key *PrivateKey) Negate() { - cPtr := (*C.uchar)(&key.privateKey[0]) - ret := C.secp256k1_ec_privkey_negate(C.secp256k1_context_no_precomp, cPtr) - if ret != 1 { - panic("Failed Negating the private key. Should never happen") - } -} - -// Add a tweak to the public key by doing `key + tweak % Group Order`. this adds it in place. -// This is meant for creating BIP-32(HD) wallets -func (key *PrivateKey) Add(tweak [32]byte) error { - cPtrKey := (*C.uchar)(&key.privateKey[0]) - cPtrTweak := (*C.uchar)(&tweak[0]) - ret := C.secp256k1_ec_privkey_tweak_add(C.secp256k1_context_no_precomp, cPtrKey, cPtrTweak) - if ret != 1 { - return errors.New("failed Adding to private key. Tweak is bigger than the order or the complement of the private key") - } - return nil -}