change fallback to p256k1 64 bit limb library
This commit is contained in:
3
go.mod
3
go.mod
@@ -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
7
go.sum
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user