This commit introduces a new `ecmultWindowedVar` function that implements optimized windowed multiplication for scalar multiplication, significantly improving performance during verification operations. The existing `Ecmult` function is updated to utilize this new implementation, converting points to affine coordinates for efficiency. Additionally, the `EcmultConst` function is retained for constant-time operations. The changes also include enhancements to the generator multiplication context, utilizing precomputed byte points for improved efficiency. Overall, these optimizations lead to a notable reduction in operation times for cryptographic computations.
306 lines
7.3 KiB
Go
306 lines
7.3 KiB
Go
package signer
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"p256k1.mleku.dev"
|
|
)
|
|
|
|
// P256K1Signer implements the I and Gen interfaces using the p256k1 package
|
|
type P256K1Signer struct {
|
|
keypair *p256k1.KeyPair
|
|
xonlyPub *p256k1.XOnlyPubkey
|
|
hasSecret bool // Whether we have the secret key (if false, can only verify)
|
|
}
|
|
|
|
// NewP256K1Signer creates a new P256K1Signer instance
|
|
func NewP256K1Signer() *P256K1Signer {
|
|
return &P256K1Signer{
|
|
hasSecret: false,
|
|
}
|
|
}
|
|
|
|
// Generate creates a fresh new key pair from system entropy, and ensures it is even (so ECDH works)
|
|
func (s *P256K1Signer) Generate() error {
|
|
kp, err := p256k1.KeyPairGenerate()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Ensure even Y coordinate for ECDH compatibility
|
|
// Get x-only pubkey and check parity
|
|
xonly, parity, err := p256k1.XOnlyPubkeyFromPubkey(kp.Pubkey())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If parity is 1 (odd Y), negate the secret key
|
|
if parity == 1 {
|
|
seckey := kp.Seckey()
|
|
if !p256k1.ECSeckeyNegate(seckey) {
|
|
return errors.New("failed to negate secret key")
|
|
}
|
|
// Recreate keypair with negated secret key
|
|
kp, err = p256k1.KeyPairCreate(seckey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Get x-only pubkey again (should be even now)
|
|
xonly, _, err = p256k1.XOnlyPubkeyFromPubkey(kp.Pubkey())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
s.keypair = kp
|
|
s.xonlyPub = xonly
|
|
s.hasSecret = true
|
|
|
|
return nil
|
|
}
|
|
|
|
// InitSec initialises the secret (signing) key from the raw bytes, and also derives the public key
|
|
func (s *P256K1Signer) InitSec(sec []byte) error {
|
|
if len(sec) != 32 {
|
|
return errors.New("secret key must be 32 bytes")
|
|
}
|
|
|
|
kp, err := p256k1.KeyPairCreate(sec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Ensure even Y coordinate for ECDH compatibility
|
|
xonly, parity, err := p256k1.XOnlyPubkeyFromPubkey(kp.Pubkey())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If parity is 1 (odd Y), negate the secret key and recompute public key
|
|
// With windowed optimization, this is now much faster than before
|
|
if parity == 1 {
|
|
seckey := kp.Seckey()
|
|
if !p256k1.ECSeckeyNegate(seckey) {
|
|
return errors.New("failed to negate secret key")
|
|
}
|
|
// Recreate keypair with negated secret key
|
|
// This is now optimized with windowed precomputed tables
|
|
kp, err = p256k1.KeyPairCreate(seckey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
xonly, _, err = p256k1.XOnlyPubkeyFromPubkey(kp.Pubkey())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
s.keypair = kp
|
|
s.xonlyPub = xonly
|
|
s.hasSecret = true
|
|
|
|
return nil
|
|
}
|
|
|
|
// InitPub initializes the public (verification) key from raw bytes, this is expected to be an x-only 32 byte pubkey
|
|
func (s *P256K1Signer) InitPub(pub []byte) error {
|
|
if len(pub) != 32 {
|
|
return errors.New("public key must be 32 bytes")
|
|
}
|
|
|
|
xonly, err := p256k1.XOnlyPubkeyParse(pub)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.xonlyPub = xonly
|
|
s.keypair = nil
|
|
s.hasSecret = false
|
|
|
|
return nil
|
|
}
|
|
|
|
// Sec returns the secret key bytes
|
|
func (s *P256K1Signer) Sec() []byte {
|
|
if !s.hasSecret || s.keypair == nil {
|
|
return nil
|
|
}
|
|
return s.keypair.Seckey()
|
|
}
|
|
|
|
// Pub returns the public key bytes (x-only schnorr pubkey)
|
|
func (s *P256K1Signer) Pub() []byte {
|
|
if s.xonlyPub == nil {
|
|
return nil
|
|
}
|
|
serialized := s.xonlyPub.Serialize()
|
|
return serialized[:]
|
|
}
|
|
|
|
// Sign creates a signature using the stored secret key
|
|
func (s *P256K1Signer) Sign(msg []byte) (sig []byte, err error) {
|
|
if !s.hasSecret || s.keypair == nil {
|
|
return nil, errors.New("no secret key available for signing")
|
|
}
|
|
|
|
if len(msg) != 32 {
|
|
return nil, errors.New("message must be 32 bytes")
|
|
}
|
|
|
|
var sig64 [64]byte
|
|
if err := p256k1.SchnorrSign(sig64[:], msg, s.keypair, nil); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return sig64[:], nil
|
|
}
|
|
|
|
// Verify checks a message hash and signature match the stored public key
|
|
func (s *P256K1Signer) Verify(msg, sig []byte) (valid bool, err error) {
|
|
if s.xonlyPub == nil {
|
|
return false, errors.New("no public key available for verification")
|
|
}
|
|
|
|
if len(msg) != 32 {
|
|
return false, errors.New("message must be 32 bytes")
|
|
}
|
|
|
|
if len(sig) != 64 {
|
|
return false, errors.New("signature must be 64 bytes")
|
|
}
|
|
|
|
valid = p256k1.SchnorrVerify(sig, msg, s.xonlyPub)
|
|
return valid, nil
|
|
}
|
|
|
|
// Zero wipes the secret key to prevent memory leaks
|
|
func (s *P256K1Signer) Zero() {
|
|
if s.keypair != nil {
|
|
s.keypair.Clear()
|
|
s.keypair = nil
|
|
}
|
|
s.hasSecret = false
|
|
// Note: x-only pubkey doesn't contain sensitive data, but we can clear it too
|
|
s.xonlyPub = nil
|
|
}
|
|
|
|
// ECDH returns a shared secret derived using Elliptic Curve Diffie-Hellman on the I secret and provided pubkey
|
|
func (s *P256K1Signer) ECDH(pub []byte) (secret []byte, err error) {
|
|
if !s.hasSecret || s.keypair == nil {
|
|
return nil, errors.New("no secret key available for ECDH")
|
|
}
|
|
|
|
if len(pub) != 32 {
|
|
return nil, errors.New("public key must be 32 bytes")
|
|
}
|
|
|
|
// Convert x-only pubkey (32 bytes) to compressed public key (33 bytes) with even Y
|
|
var compressedPub [33]byte
|
|
compressedPub[0] = 0x02 // Even Y
|
|
copy(compressedPub[1:], pub)
|
|
|
|
// Parse the compressed public key
|
|
var pubkey p256k1.PublicKey
|
|
if err := p256k1.ECPubkeyParse(&pubkey, compressedPub[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Compute ECDH shared secret using standard ECDH (hashes the point)
|
|
var sharedSecret [32]byte
|
|
if err := p256k1.ECDH(sharedSecret[:], &pubkey, s.keypair.Seckey(), nil); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return sharedSecret[:], nil
|
|
}
|
|
|
|
// P256K1Gen implements the Gen interface for nostr BIP-340 key generation
|
|
type P256K1Gen struct {
|
|
keypair *p256k1.KeyPair
|
|
xonlyPub *p256k1.XOnlyPubkey
|
|
compressedPub *p256k1.PublicKey
|
|
}
|
|
|
|
// NewP256K1Gen creates a new P256K1Gen instance
|
|
func NewP256K1Gen() *P256K1Gen {
|
|
return &P256K1Gen{}
|
|
}
|
|
|
|
// Generate gathers entropy and derives pubkey bytes for matching, this returns the 33 byte compressed form for checking the oddness of the Y coordinate
|
|
func (g *P256K1Gen) Generate() (pubBytes []byte, err error) {
|
|
kp, err := p256k1.KeyPairGenerate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
g.keypair = kp
|
|
|
|
// Get compressed public key (33 bytes)
|
|
var pubkey p256k1.PublicKey = *kp.Pubkey()
|
|
|
|
var compressed [33]byte
|
|
n := p256k1.ECPubkeySerialize(compressed[:], &pubkey, p256k1.ECCompressed)
|
|
if n != 33 {
|
|
return nil, errors.New("failed to serialize compressed public key")
|
|
}
|
|
|
|
g.compressedPub = &pubkey
|
|
|
|
return compressed[:], nil
|
|
}
|
|
|
|
// Negate flips the public key Y coordinate between odd and even
|
|
func (g *P256K1Gen) Negate() {
|
|
if g.keypair == nil {
|
|
return
|
|
}
|
|
|
|
// Negate the secret key
|
|
seckey := g.keypair.Seckey()
|
|
if !p256k1.ECSeckeyNegate(seckey) {
|
|
return
|
|
}
|
|
|
|
// Recreate keypair with negated secret key
|
|
kp, err := p256k1.KeyPairCreate(seckey)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
g.keypair = kp
|
|
|
|
// Update compressed pubkey
|
|
var pubkey p256k1.PublicKey = *kp.Pubkey()
|
|
var compressed [33]byte
|
|
p256k1.ECPubkeySerialize(compressed[:], &pubkey, p256k1.ECCompressed)
|
|
g.compressedPub = &pubkey
|
|
|
|
// Update x-only pubkey
|
|
xonly, err := kp.XOnlyPubkey()
|
|
if err == nil {
|
|
g.xonlyPub = xonly
|
|
}
|
|
}
|
|
|
|
// KeyPairBytes returns the raw bytes of the secret and public key, this returns the 32 byte X-only pubkey
|
|
func (g *P256K1Gen) KeyPairBytes() (secBytes, cmprPubBytes []byte) {
|
|
if g.keypair == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
secBytes = g.keypair.Seckey()
|
|
|
|
if g.xonlyPub == nil {
|
|
xonly, err := g.keypair.XOnlyPubkey()
|
|
if err != nil {
|
|
return secBytes, nil
|
|
}
|
|
g.xonlyPub = xonly
|
|
}
|
|
|
|
serialized := g.xonlyPub.Serialize()
|
|
cmprPubBytes = serialized[:]
|
|
|
|
return secBytes, cmprPubBytes
|
|
}
|