This commit introduces the implementation of Schnorr signatures following BIP-340, including the `SchnorrSign` and `SchnorrVerify` functions. It also adds comprehensive tests to validate the signing and verification processes, ensuring correct functionality with both standard and auxiliary randomness. The tests cover various scenarios, including signature generation, verification, and edge cases, enhancing the robustness of the secp256k1 implementation. Additionally, new files for Schnorr signature operations and tests have been created, contributing to the overall cryptographic capabilities of the library.
167 lines
3.7 KiB
Go
167 lines
3.7 KiB
Go
package p256k1
|
|
|
|
import (
|
|
"errors"
|
|
"unsafe"
|
|
)
|
|
|
|
// XOnlyPubkey represents an x-only public key (32 bytes, just X coordinate)
|
|
// Following BIP-340 specification
|
|
type XOnlyPubkey struct {
|
|
data [32]byte
|
|
}
|
|
|
|
// KeyPair represents a keypair consisting of a secret key and public key
|
|
// Used for Schnorr signatures
|
|
type KeyPair struct {
|
|
seckey [32]byte
|
|
pubkey PublicKey
|
|
}
|
|
|
|
// XOnlyPubkeyParse parses a 32-byte sequence into an x-only public key
|
|
func XOnlyPubkeyParse(input32 []byte) (*XOnlyPubkey, error) {
|
|
if len(input32) != 32 {
|
|
return nil, errors.New("input must be 32 bytes")
|
|
}
|
|
|
|
// Create a point from X coordinate
|
|
var x FieldElement
|
|
if err := x.setB32(input32); err != nil {
|
|
return nil, errors.New("invalid X coordinate")
|
|
}
|
|
|
|
// Try to recover Y coordinate (check if point is on curve)
|
|
var point GroupElementAffine
|
|
if !point.setXOVar(&x, false) {
|
|
// Try with odd Y
|
|
if !point.setXOVar(&x, true) {
|
|
return nil, errors.New("X coordinate does not correspond to a valid point")
|
|
}
|
|
}
|
|
|
|
// Verify point is valid
|
|
if !point.isValid() {
|
|
return nil, errors.New("invalid point")
|
|
}
|
|
|
|
// Create x-only pubkey (just X coordinate)
|
|
var xonly XOnlyPubkey
|
|
copy(xonly.data[:], input32)
|
|
return &xonly, nil
|
|
}
|
|
|
|
// Serialize serializes an x-only public key to 32 bytes
|
|
func (xonly *XOnlyPubkey) Serialize() [32]byte {
|
|
return xonly.data
|
|
}
|
|
|
|
// XOnlyPubkeyFromPubkey converts a PublicKey to an XOnlyPubkey
|
|
// Returns the x-only pubkey and parity (1 if Y was odd, 0 if even)
|
|
func XOnlyPubkeyFromPubkey(pubkey *PublicKey) (*XOnlyPubkey, int, error) {
|
|
if pubkey == nil {
|
|
return nil, 0, errors.New("pubkey cannot be nil")
|
|
}
|
|
|
|
// Load public key
|
|
var pt GroupElementAffine
|
|
pt.fromBytes(pubkey.data[:])
|
|
if pt.isInfinity() {
|
|
return nil, 0, errors.New("invalid public key")
|
|
}
|
|
|
|
// Normalize Y coordinate
|
|
pt.y.normalize()
|
|
|
|
// Check parity
|
|
parity := 0
|
|
if pt.y.isOdd() {
|
|
parity = 1
|
|
// Negate point if Y is odd to get even Y
|
|
pt.negate(&pt)
|
|
}
|
|
|
|
// Extract X coordinate
|
|
var xonly XOnlyPubkey
|
|
pt.x.normalize()
|
|
pt.x.getB32(xonly.data[:])
|
|
|
|
return &xonly, parity, nil
|
|
}
|
|
|
|
// XOnlyPubkeyCmp compares two x-only public keys lexicographically
|
|
// Returns: <0 if xonly1 < xonly2, >0 if xonly1 > xonly2, 0 if equal
|
|
func XOnlyPubkeyCmp(xonly1, xonly2 *XOnlyPubkey) int {
|
|
if xonly1 == nil || xonly2 == nil {
|
|
panic("xonly pubkey cannot be nil")
|
|
}
|
|
|
|
for i := 31; i >= 0; i-- {
|
|
if xonly1.data[i] < xonly2.data[i] {
|
|
return -1
|
|
}
|
|
if xonly1.data[i] > xonly2.data[i] {
|
|
return 1
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// KeyPairCreate creates a keypair from a secret key
|
|
func KeyPairCreate(seckey []byte) (*KeyPair, error) {
|
|
if len(seckey) != 32 {
|
|
return nil, errors.New("secret key must be 32 bytes")
|
|
}
|
|
|
|
if !ECSeckeyVerify(seckey) {
|
|
return nil, errors.New("invalid secret key")
|
|
}
|
|
|
|
// Create public key
|
|
var pubkey PublicKey
|
|
if err := ECPubkeyCreate(&pubkey, seckey); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
kp := &KeyPair{}
|
|
copy(kp.seckey[:], seckey)
|
|
kp.pubkey = pubkey
|
|
|
|
return kp, nil
|
|
}
|
|
|
|
// KeyPairGenerate generates a new random keypair
|
|
func KeyPairGenerate() (*KeyPair, error) {
|
|
seckey, pubkey, err := ECKeyPairGenerate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
kp := &KeyPair{}
|
|
copy(kp.seckey[:], seckey)
|
|
kp.pubkey = *pubkey
|
|
|
|
return kp, nil
|
|
}
|
|
|
|
// Seckey returns the secret key
|
|
func (kp *KeyPair) Seckey() []byte {
|
|
return kp.seckey[:]
|
|
}
|
|
|
|
// Pubkey returns the public key
|
|
func (kp *KeyPair) Pubkey() *PublicKey {
|
|
return &kp.pubkey
|
|
}
|
|
|
|
// XOnlyPubkey returns the x-only public key
|
|
func (kp *KeyPair) XOnlyPubkey() (*XOnlyPubkey, error) {
|
|
xonly, _, err := XOnlyPubkeyFromPubkey(&kp.pubkey)
|
|
return xonly, err
|
|
}
|
|
|
|
// Clear clears the keypair to prevent leaking sensitive information
|
|
func (kp *KeyPair) Clear() {
|
|
memclear(unsafe.Pointer(&kp.seckey[0]), 32)
|
|
kp.pubkey.data = [64]byte{}
|
|
}
|