Skip to content

Commit

Permalink
Refactor privatekey into new SchnorrKeyPair to represent upstream's API
Browse files Browse the repository at this point in the history
  • Loading branch information
elichai committed Nov 30, 2020
1 parent 1113c26 commit cd0bd2b
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 88 deletions.
162 changes: 162 additions & 0 deletions schnorr_keypair.go
Original file line number Diff line number Diff line change
@@ -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)))
}
}
88 changes: 0 additions & 88 deletions secp256k1.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit cd0bd2b

Please sign in to comment.