change fallback to p256k1 64 bit limb library

This commit is contained in:
2025-11-28 11:48:32 +00:00
parent c622989d81
commit 9cf5f60a4e
3 changed files with 126 additions and 88 deletions

3
go.mod
View File

@@ -15,10 +15,11 @@ require (
golang.org/x/net v0.47.0
lol.mleku.dev v1.0.5
lukechampine.com/frand v1.5.1
p256k1.mleku.dev v1.0.3
)
require (
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/templexxx/cpu v0.0.1 // indirect
golang.org/x/sys v0.38.0 // indirect

7
go.sum
View File

@@ -4,8 +4,8 @@ github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -24,7 +24,6 @@ golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0c
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
@@ -35,3 +34,5 @@ lol.mleku.dev v1.0.5 h1:irwfwz+Scv74G/2OXmv05YFKOzUNOVZ735EAkYgjgM8=
lol.mleku.dev v1.0.5/go.mod h1:JlsqP0CZDLKRyd85XGcy79+ydSRqmFkrPzYFMYxQ+zs=
lukechampine.com/frand v1.5.1 h1:fg0eRtdmGFIxhP5zQJzM1lFDbD6CUfu/f+7WgAZd5/w=
lukechampine.com/frand v1.5.1/go.mod h1:4VstaWc2plN4Mjr10chUD46RAVGWhpkZ5Nja8+Azp0Q=
p256k1.mleku.dev v1.0.3 h1:2SBEH9XhNAotO1Ik8ejODjChTqc06Z/6ncQhrYkAdRA=
p256k1.mleku.dev v1.0.3/go.mod h1:cWkZlx6Tu7CTmIxonFbdjhdNfkY3VbjjY5TFEILiTnY=

View File

@@ -4,8 +4,8 @@ package p8k
import (
"crypto/rand"
"git.mleku.dev/mleku/nostr/crypto/ec/schnorr"
"git.mleku.dev/mleku/nostr/crypto/ec/secp256k1"
p256k1 "p256k1.mleku.dev"
secp "git.mleku.dev/mleku/nostr/crypto/p8k"
"git.mleku.dev/mleku/nostr/interfaces/signer"
"lol.mleku.dev/errorf"
@@ -19,15 +19,16 @@ type Signer struct {
pubKey []byte
keypair secp.Keypair
// Pure Go fallback implementation
// Pure Go fallback implementation using p256k1.mleku.dev
fallback *FallbackSigner
}
// FallbackSigner implements the signer.I interface using pure Go btcec/secp256k1
// FallbackSigner implements the signer.I interface using pure Go p256k1.mleku.dev
type FallbackSigner struct {
privKey *secp256k1.SecretKey
pubKey *secp256k1.PublicKey
xonlyPub []byte
secKey []byte
keypair *p256k1.KeyPair
xonlyPub []byte
xonlyPubkey *p256k1.XOnlyPubkey // Store the parsed x-only pubkey for verification
}
// Ensure Signer implements signer.I
@@ -165,11 +166,7 @@ func (s *Signer) Pub() []byte {
// This is needed for ECDH operations like NIP-44.
func (s *Signer) PubCompressed() (compressed []byte, err error) {
if s.fallback != nil {
// For fallback, we need to derive the compressed key from the x-only key
if s.fallback.pubKey == nil {
return nil, errorf.E("public key not initialized")
}
return s.fallback.pubKey.SerializeCompressed(), nil
return s.fallback.PubCompressed()
}
if len(s.keypair) == 0 {
@@ -309,25 +306,30 @@ func (s *Signer) ECDHRaw(pub []byte) (sharedX []byte, err error) {
return
}
// FallbackSigner method implementations
// FallbackSigner method implementations using p256k1.mleku.dev
// Generate creates a fresh new key pair from system entropy
func (s *FallbackSigner) Generate() (err error) {
// Generate a new private key
if s.privKey, err = secp256k1.GenerateSecretKey(); err != nil {
s.secKey = make([]byte, 32)
if _, err = rand.Read(s.secKey); err != nil {
return errorf.E("failed to generate private key: %w", err)
}
// Derive public key
if s.pubKey = s.privKey.PubKey(); s.pubKey == nil {
return errorf.E("failed to derive public key")
// Create keypair
if s.keypair, err = p256k1.KeyPairCreate(s.secKey); err != nil {
return errorf.E("failed to create keypair: %w", err)
}
// Get x-only public key (32 bytes) - compressed without the 0x02/0x03 prefix
compressed := s.pubKey.SerializeCompressed()
s.xonlyPub = make([]byte, 32)
copy(s.xonlyPub, compressed[1:])
// Get x-only public key
xonlyPk, e := s.keypair.XOnlyPubkey()
if e != nil {
return errorf.E("failed to derive x-only public key: %w", e)
}
s.xonlyPubkey = xonlyPk
serialized := xonlyPk.Serialize()
s.xonlyPub = serialized[:]
return nil
}
@@ -337,22 +339,23 @@ func (s *FallbackSigner) InitSec(sec []byte) (err error) {
return errorf.E("secret key must be 32 bytes")
}
// Create private key from bytes
s.privKey = secp256k1.SecKeyFromBytes(sec)
if s.privKey.Key.IsZero() {
return errorf.E("invalid secret key")
s.secKey = make([]byte, 32)
copy(s.secKey, sec)
// Create keypair
if s.keypair, err = p256k1.KeyPairCreate(s.secKey); err != nil {
return errorf.E("failed to create keypair: %w", err)
}
// Derive public key
if s.pubKey = s.privKey.PubKey(); s.pubKey == nil {
return errorf.E("failed to derive public key")
// Get x-only public key
xonlyPk, e := s.keypair.XOnlyPubkey()
if e != nil {
return errorf.E("failed to derive x-only public key: %w", e)
}
// Get x-only public key (32 bytes) - compressed without the 0x02/0x03 prefix
compressed := s.pubKey.SerializeCompressed()
s.xonlyPub = make([]byte, 32)
copy(s.xonlyPub, compressed[1:])
s.xonlyPubkey = xonlyPk
serialized := xonlyPk.Serialize()
s.xonlyPub = serialized[:]
return nil
}
@@ -362,23 +365,21 @@ func (s *FallbackSigner) InitPub(pub []byte) (err error) {
return errorf.E("public key must be 32 bytes")
}
s.xonlyPub = make([]byte, 32)
copy(s.xonlyPub, pub)
// Parse the x-only public key into a full public key for verification
if s.pubKey, err = schnorr.ParsePubKey(pub); err != nil {
return errorf.E("failed to parse public key: %w", err)
// Parse x-only public key
xonlyPk, e := p256k1.XOnlyPubkeyParse(pub)
if e != nil {
return errorf.E("failed to parse public key: %w", e)
}
s.xonlyPubkey = xonlyPk
s.xonlyPub = make([]byte, 32)
copy(s.xonlyPub, pub)
return nil
}
// Sec returns the secret key bytes
func (s *FallbackSigner) Sec() []byte {
if s.privKey == nil {
return nil
}
return s.privKey.Serialize()
return s.secKey
}
// Pub returns the public key bytes (x-only schnorr pubkey)
@@ -386,60 +387,66 @@ func (s *FallbackSigner) Pub() []byte {
return s.xonlyPub
}
// PubCompressed returns the compressed public key (33 bytes with 0x02/0x03 prefix)
func (s *FallbackSigner) PubCompressed() (compressed []byte, err error) {
if s.keypair == nil {
return nil, errorf.E("keypair not initialized")
}
pk := s.keypair.Pubkey()
compressed = make([]byte, 33)
p256k1.ECPubkeySerialize(compressed, pk, p256k1.ECCompressed)
return compressed, nil
}
// Sign creates a signature using the stored secret key
func (s *FallbackSigner) Sign(msg []byte) (sig []byte, err error) {
if s.privKey == nil {
return nil, errorf.E("private key not initialized")
if s.keypair == nil {
return nil, errorf.E("keypair not initialized")
}
// Generate auxiliary randomness for BIP-340
var auxRand [32]byte
if _, err = rand.Read(auxRand[:]); err != nil {
auxRand := make([]byte, 32)
if _, err = rand.Read(auxRand); err != nil {
return nil, errorf.E("failed to generate aux randomness: %w", err)
}
// Sign using Schnorr
var schnorrSig *schnorr.Signature
if schnorrSig, err = schnorr.Sign(
s.privKey, msg, schnorr.CustomNonce(auxRand),
); err != nil {
sig = make([]byte, 64)
if err = p256k1.SchnorrSign(sig, msg, s.keypair, auxRand); err != nil {
return nil, errorf.E("failed to sign: %w", err)
}
return schnorrSig.Serialize(), nil
return sig, nil
}
// Verify checks a message hash and signature match the stored public key
func (s *FallbackSigner) Verify(msg, sig []byte) (valid bool, err error) {
if s.pubKey == nil {
if s.xonlyPubkey == nil {
return false, errorf.E("public key not initialized")
}
// Parse signature
var schnorrSig *schnorr.Signature
if schnorrSig, err = schnorr.ParseSignature(sig); err != nil {
return false, errorf.E("failed to parse signature: %w", err)
}
// Verify signature
valid = schnorrSig.Verify(msg, s.pubKey)
valid = p256k1.SchnorrVerify(sig, msg, s.xonlyPubkey)
return valid, nil
}
// Zero wipes the secret key
func (s *FallbackSigner) Zero() {
if s.privKey != nil {
privKeyBytes := s.privKey.Serialize()
for i := range privKeyBytes {
privKeyBytes[i] = 0
if s.secKey != nil {
for i := range s.secKey {
s.secKey[i] = 0
}
s.privKey = nil
s.secKey = nil
}
if s.xonlyPub != nil {
for i := range s.xonlyPub {
s.xonlyPub[i] = 0
}
s.xonlyPub = nil
}
s.keypair = nil
s.xonlyPubkey = nil
}
// ECDH returns a shared secret
@@ -449,20 +456,31 @@ func (s *FallbackSigner) ECDH(pub []byte) (secret []byte, err error) {
// ECDHRaw returns the raw shared secret (x-coordinate only)
func (s *FallbackSigner) ECDHRaw(pub []byte) (sharedX []byte, err error) {
if s.privKey == nil {
if s.secKey == nil {
return nil, errorf.E("private key not initialized")
}
var pubKeyFull []byte
var pk p256k1.PublicKey
if len(pub) == 33 {
// Already compressed format
pubKeyFull = pub
// Already compressed format - create public key from secret key first to get proper format
// then parse the compressed format
if e := p256k1.ECPubkeyParse(&pk, pub); e != nil {
return nil, errorf.E("failed to parse compressed public key: %w", e)
}
} else if len(pub) == 32 {
// X-only format: try with 0x02 (even y), then 0x03 (odd y)
pubKeyFull = make([]byte, 33)
pubKeyFull := make([]byte, 33)
pubKeyFull[0] = 0x02 // compressed even y
copy(pubKeyFull[1:], pub)
if e := p256k1.ECPubkeyParse(&pk, pubKeyFull); e != nil {
// Try odd y
pubKeyFull[0] = 0x03
if e = p256k1.ECPubkeyParse(&pk, pubKeyFull); e != nil {
return nil, errorf.E("failed to parse x-only public key: %w", e)
}
}
} else {
return nil, errorf.E(
"public key must be 32 bytes (x-only) or 33 bytes (compressed), got %d bytes",
@@ -470,21 +488,39 @@ func (s *FallbackSigner) ECDHRaw(pub []byte) (sharedX []byte, err error) {
)
}
// Parse the public key
var parsedPub *secp256k1.PublicKey
if parsedPub, err = secp256k1.ParsePubKey(pubKeyFull); err != nil {
// If 32-byte x-only and even y failed, try odd y
if len(pub) == 32 {
pubKeyFull[0] = 0x03
if parsedPub, err = secp256k1.ParsePubKey(pubKeyFull); err != nil {
return nil, err
}
} else {
return nil, err
}
// Compute ECDH
sharedX = make([]byte, 32)
if err = p256k1.ECDHXOnly(sharedX, &pk, s.secKey); err != nil {
return nil, errorf.E("failed to compute ECDH: %w", err)
}
// Compute ECDH
sharedX = secp256k1.GenerateSharedSecret(s.privKey, parsedPub)
return sharedX, nil
}
// GetModuleStatus returns information about which modules are available
func (s *Signer) GetModuleStatus() map[string]bool {
status := make(map[string]bool)
if s.fallback != nil {
status["implementation"] = false // false means using fallback
status["schnorr"] = true
status["ecdh"] = true
status["recovery"] = true
return status
}
status["implementation"] = true // true means using libsecp256k1
// Check if schnorr module is available by trying to create a test keypair
testKey := make([]byte, 32)
for i := range testKey {
testKey[i] = 1
}
_, err := s.ctx.CreateKeypair(testKey)
status["schnorr"] = err == nil
// ECDH and recovery would need similar checks
status["ecdh"] = true // Assume available if libsecp256k1 loaded
status["recovery"] = true // Assume available if libsecp256k1 loaded
return status
}