Update dependencies and refactor p256k crypto package

- Bumped version of lol.mleku.dev from v1.0.4 to v1.0.5.
- Added new dependencies: p256k1.mleku.dev and several indirect dependencies for improved cryptographic functionality.
- Refactored p256k package to utilize p256k1.mleku.dev/signer for signature operations, replacing the previous btcec implementation.
- Removed the secp256k1.go file, consolidating the crypto logic under the new p256k1 library.
- Updated documentation to reflect changes in the signer interface and usage.
This commit is contained in:
2025-11-02 16:43:58 +00:00
parent f092d817c9
commit 0123c2d6f5
8 changed files with 87 additions and 574 deletions

View File

@@ -4,22 +4,18 @@ package p256k
import (
"lol.mleku.dev/log"
"next.orly.dev/pkg/crypto/p256k/btcec"
p256k1signer "p256k1.mleku.dev/signer"
)
func init() {
log.T.Ln("using btcec signature library")
log.T.Ln("using p256k1.mleku.dev/signer (pure Go/Btcec)")
}
// BTCECSigner is always available but enabling it disables the use of
// github.com/bitcoin-core/secp256k1 CGO signature implementation and points it at the btec
// version.
// Signer is an alias for the BtcecSigner type from p256k1.mleku.dev/signer (btcec version).
// This is used when CGO is not available.
type Signer = p256k1signer.BtcecSigner
type Signer = btcec.Signer
type Keygen = btcec.Keygen
// Keygen is an alias for the P256K1Gen type from p256k1.mleku.dev/signer (btcec version).
type Keygen = p256k1signer.P256K1Gen
func NewKeygen() (k *Keygen) { return new(Keygen) }
var NewSecFromHex = btcec.NewSecFromHex[string]
var NewPubFromHex = btcec.NewPubFromHex[string]
var HexToBin = btcec.HexToBin
var NewKeygen = p256k1signer.NewP256K1Gen

View File

@@ -1,6 +1,9 @@
// Package p256k is a signer interface that (by default) uses the
// bitcoin/libsecp256k1 library for fast signature creation and verification of
// the BIP-340 nostr X-only signatures and public keys, and ECDH.
// Package p256k provides a signer interface that uses p256k1.mleku.dev library for
// fast signature creation and verification of BIP-340 nostr X-only signatures and
// public keys, and ECDH.
//
// Currently the ECDH is only implemented with the btcec library.
// The package provides type aliases to p256k1.mleku.dev/signer:
// - cgo: Uses the CGO-optimized version from p256k1.mleku.dev
// - btcec: Uses the btcec version from p256k1.mleku.dev
// - default: Uses the pure Go version from p256k1.mleku.dev
package p256k

View File

@@ -0,0 +1,41 @@
//go:build !cgo
package p256k
import (
"lol.mleku.dev/chk"
"next.orly.dev/pkg/encoders/hex"
"next.orly.dev/pkg/interfaces/signer"
p256k1signer "p256k1.mleku.dev/signer"
)
func NewSecFromHex[V []byte | string](skh V) (sign signer.I, err error) {
sk := make([]byte, len(skh)/2)
if _, err = hex.DecBytes(sk, []byte(skh)); chk.E(err) {
return
}
sign = p256k1signer.NewBtcecSigner()
if err = sign.InitSec(sk); chk.E(err) {
return
}
return
}
func NewPubFromHex[V []byte | string](pkh V) (sign signer.I, err error) {
pk := make([]byte, len(pkh)/2)
if _, err = hex.DecBytes(pk, []byte(pkh)); chk.E(err) {
return
}
sign = p256k1signer.NewBtcecSigner()
if err = sign.InitPub(pk); chk.E(err) {
return
}
return
}
func HexToBin(hexStr string) (b []byte, err error) {
if b, err = hex.DecAppend(b, []byte(hexStr)); chk.E(err) {
return
}
return
}

View File

@@ -6,6 +6,7 @@ import (
"lol.mleku.dev/chk"
"next.orly.dev/pkg/encoders/hex"
"next.orly.dev/pkg/interfaces/signer"
p256k1signer "p256k1.mleku.dev/signer"
)
func NewSecFromHex[V []byte | string](skh V) (sign signer.I, err error) {
@@ -13,7 +14,7 @@ func NewSecFromHex[V []byte | string](skh V) (sign signer.I, err error) {
if _, err = hex.DecBytes(sk, []byte(skh)); chk.E(err) {
return
}
sign = &Signer{}
sign = p256k1signer.NewP256K1Signer()
if err = sign.InitSec(sk); chk.E(err) {
return
}
@@ -25,7 +26,7 @@ func NewPubFromHex[V []byte | string](pkh V) (sign signer.I, err error) {
if _, err = hex.DecBytes(pk, []byte(pkh)); chk.E(err) {
return
}
sign = &Signer{}
sign = p256k1signer.NewP256K1Signer()
if err = sign.InitPub(pk); chk.E(err) {
return
}

View File

@@ -2,139 +2,19 @@
package p256k
import "C"
import (
"lol.mleku.dev/chk"
"lol.mleku.dev/errorf"
"lol.mleku.dev/log"
"next.orly.dev/pkg/crypto/ec"
"next.orly.dev/pkg/crypto/ec/secp256k1"
"next.orly.dev/pkg/interfaces/signer"
p256k1signer "p256k1.mleku.dev/signer"
)
func init() {
log.T.Ln("using bitcoin/secp256k1 signature library")
log.T.Ln("using p256k1.mleku.dev/signer (CGO)")
}
// Signer implements the signer.I interface.
//
// Either the Sec or Pub must be populated, the former is for generating
// signatures, the latter is for verifying them.
//
// When using this library only for verification, a constructor that converts
// from bytes to PubKey is needed prior to calling Verify.
type Signer struct {
// SecretKey is the secret key.
SecretKey *SecKey
// PublicKey is the public key.
PublicKey *PubKey
// BTCECSec is needed for ECDH as currently the CGO bindings don't include it
BTCECSec *btcec.SecretKey
skb, pkb []byte
}
// Signer is an alias for the P256K1Signer type from p256k1.mleku.dev/signer (cgo version).
type Signer = p256k1signer.P256K1Signer
var _ signer.I = &Signer{}
// Keygen is an alias for the P256K1Gen type from p256k1.mleku.dev/signer (cgo version).
type Keygen = p256k1signer.P256K1Gen
// Generate a new Signer key pair using the CGO bindings to libsecp256k1
func (s *Signer) Generate() (err error) {
var cs *Sec
var cx *XPublicKey
if s.skb, s.pkb, cs, cx, err = Generate(); chk.E(err) {
return
}
s.SecretKey = &cs.Key
s.PublicKey = cx.Key
s.BTCECSec, _ = btcec.PrivKeyFromBytes(s.skb)
return
}
func (s *Signer) InitSec(skb []byte) (err error) {
var cs *Sec
var cx *XPublicKey
// var cp *PublicKey
if s.pkb, cs, cx, err = FromSecretBytes(skb); chk.E(err) {
if err.Error() != "provided secret generates a public key with odd Y coordinate, fixed version returned" {
log.E.Ln(err)
return
}
}
s.skb = skb
s.SecretKey = &cs.Key
s.PublicKey = cx.Key
// s.ECPublicKey = cp.Key
// needed for ecdh
s.BTCECSec, _ = btcec.PrivKeyFromBytes(s.skb)
return
}
func (s *Signer) InitPub(pub []byte) (err error) {
var up *Pub
if up, err = PubFromBytes(pub); chk.E(err) {
return
}
s.PublicKey = &up.Key
s.pkb = up.PubB()
return
}
func (s *Signer) Sec() (b []byte) {
if s == nil {
return nil
}
return s.skb
}
func (s *Signer) Pub() (b []byte) {
if s == nil {
return nil
}
return s.pkb
}
// func (s *Signer) ECPub() (b []byte) { return s.pkb }
func (s *Signer) Sign(msg []byte) (sig []byte, err error) {
if s.SecretKey == nil {
err = errorf.E("p256k: I secret not initialized")
return
}
u := ToUchar(msg)
if sig, err = Sign(u, s.SecretKey); chk.E(err) {
return
}
return
}
func (s *Signer) Verify(msg, sig []byte) (valid bool, err error) {
if s.PublicKey == nil {
err = errorf.E("p256k: Pubkey not initialized")
return
}
var uMsg, uSig *Uchar
if uMsg, err = Msg(msg); chk.E(err) {
return
}
if uSig, err = Sig(sig); chk.E(err) {
return
}
valid = Verify(uMsg, uSig, s.PublicKey)
if !valid {
err = errorf.E("p256k: invalid signature")
}
return
}
func (s *Signer) ECDH(pubkeyBytes []byte) (secret []byte, err error) {
var pub *secp256k1.PublicKey
if pub, err = secp256k1.ParsePubKey(
append(
[]byte{0x02},
pubkeyBytes...,
),
); chk.E(err) {
return
}
secret = btcec.GenerateSharedSecret(s.BTCECSec, pub)
return
}
func (s *Signer) Zero() { Zero(s.SecretKey) }
var NewKeygen = p256k1signer.NewP256K1Gen

View File

@@ -1,426 +0,0 @@
//go:build cgo
package p256k
import (
"crypto/rand"
"unsafe"
"lol.mleku.dev/chk"
"lol.mleku.dev/errorf"
"lol.mleku.dev/log"
"next.orly.dev/pkg/crypto/ec/schnorr"
"next.orly.dev/pkg/crypto/ec/secp256k1"
"next.orly.dev/pkg/crypto/sha256"
)
/*
#cgo LDFLAGS: -lsecp256k1
#include <secp256k1.h>
#include <secp256k1_schnorrsig.h>
#include <secp256k1_extrakeys.h>
*/
import "C"
type (
Context = C.secp256k1_context
Uchar = C.uchar
Cint = C.int
SecKey = C.secp256k1_keypair
PubKey = C.secp256k1_xonly_pubkey
ECPubKey = C.secp256k1_pubkey
)
var (
ctx *Context
)
func CreateContext() *Context {
return C.secp256k1_context_create(
C.SECP256K1_CONTEXT_SIGN |
C.SECP256K1_CONTEXT_VERIFY,
)
}
func GetRandom() (u *Uchar) {
rnd := make([]byte, 32)
_, _ = rand.Read(rnd)
return ToUchar(rnd)
}
func AssertLen(b []byte, length int, name string) (err error) {
if len(b) != length {
err = errorf.E("%s should be %d bytes, got %d", name, length, len(b))
}
return
}
func RandomizeContext(ctx *C.secp256k1_context) {
C.secp256k1_context_randomize(ctx, GetRandom())
return
}
func CreateRandomContext() (c *Context) {
c = CreateContext()
RandomizeContext(c)
return
}
func init() {
if ctx = CreateContext(); ctx == nil {
panic("failed to create secp256k1 context")
}
}
func ToUchar(b []byte) (u *Uchar) { return (*Uchar)(unsafe.Pointer(&b[0])) }
type Sec struct {
Key SecKey
}
func GenSec() (sec *Sec, err error) {
if _, _, sec, _, err = Generate(); chk.E(err) {
return
}
return
}
func SecFromBytes(sk []byte) (sec *Sec, err error) {
sec = new(Sec)
if C.secp256k1_keypair_create(ctx, &sec.Key, ToUchar(sk)) != 1 {
err = errorf.E("failed to parse private key")
return
}
return
}
func (s *Sec) Sec() *SecKey { return &s.Key }
func (s *Sec) Pub() (p *Pub, err error) {
p = new(Pub)
if C.secp256k1_keypair_xonly_pub(ctx, &p.Key, nil, s.Sec()) != 1 {
err = errorf.E("pubkey derivation failed")
return
}
return
}
// type PublicKey struct {
// Key *C.secp256k1_pubkey
// }
//
// func NewPublicKey() *PublicKey {
// return &PublicKey{
// Key: &C.secp256k1_pubkey{},
// }
// }
type XPublicKey struct {
Key *C.secp256k1_xonly_pubkey
}
func NewXPublicKey() *XPublicKey {
return &XPublicKey{
Key: &C.secp256k1_xonly_pubkey{},
}
}
// FromSecretBytes parses and processes what should be a secret key. If it is a correct key within the curve order, but
// with a public key having an odd Y coordinate, it returns an error with the fixed key.
func FromSecretBytes(skb []byte) (
pkb []byte,
sec *Sec,
pub *XPublicKey,
// ecPub *PublicKey,
err error,
) {
xpkb := make([]byte, schnorr.PubKeyBytesLen)
// clen := C.size_t(secp256k1.PubKeyBytesLenCompressed - 1)
pkb = make([]byte, schnorr.PubKeyBytesLen)
var parity Cint
// ecPub = NewPublicKey()
pub = NewXPublicKey()
sec = &Sec{}
uskb := ToUchar(skb)
res := C.secp256k1_keypair_create(ctx, &sec.Key, uskb)
if res != 1 {
err = errorf.E("failed to create secp256k1 keypair")
return
}
// C.secp256k1_keypair_pub(ctx, ecPub.Key, &sec.Key)
// C.secp256k1_ec_pubkey_serialize(ctx, ToUchar(ecpkb), &clen, ecPub.Key,
// C.SECP256K1_EC_COMPRESSED)
// if ecpkb[0] != 2 {
// log.W.ToSliceOfBytes("odd pubkey from %0x -> %0x", skb, ecpkb)
// Negate(skb)
// uskb = ToUchar(skb)
// res = C.secp256k1_keypair_create(ctx, &sec.Key, uskb)
// if res != 1 {
// err = errorf.E("failed to create secp256k1 keypair")
// return
// }
// C.secp256k1_keypair_pub(ctx, ecPub.Key, &sec.Key)
// C.secp256k1_ec_pubkey_serialize(ctx, ToUchar(ecpkb), &clen, ecPub.Key, C.SECP256K1_EC_COMPRESSED)
// C.secp256k1_keypair_xonly_pub(ctx, pub.Key, &parity, &sec.Key)
// err = errors.New("provided secret generates a public key with odd Y coordinate, fixed version returned")
// }
C.secp256k1_keypair_xonly_pub(ctx, pub.Key, &parity, &sec.Key)
C.secp256k1_xonly_pubkey_serialize(ctx, ToUchar(xpkb), pub.Key)
pkb = xpkb
// log.I.S(sec, pub, skb, pkb)
return
}
// Generate gathers entropy to generate a full set of bytes and CGO values of it and derived from it to perform
// signature and ECDH operations.
func Generate() (
skb, pkb []byte,
sec *Sec,
pub *XPublicKey,
err error,
) {
skb = make([]byte, secp256k1.SecKeyBytesLen)
pkb = make([]byte, schnorr.PubKeyBytesLen)
upkb := ToUchar(pkb)
var parity Cint
pub = NewXPublicKey()
sec = &Sec{}
for {
if _, err = rand.Read(skb); chk.E(err) {
return
}
uskb := ToUchar(skb)
if res := C.secp256k1_keypair_create(ctx, &sec.Key, uskb); res != 1 {
err = errorf.E("failed to create secp256k1 keypair")
continue
}
C.secp256k1_keypair_xonly_pub(ctx, pub.Key, &parity, &sec.Key)
C.secp256k1_xonly_pubkey_serialize(ctx, upkb, pub.Key)
break
}
return
}
// Negate inverts a secret key so an odd prefix bit becomes even and vice versa.
func Negate(uskb []byte) { C.secp256k1_ec_seckey_negate(ctx, ToUchar(uskb)) }
type ECPub struct {
Key ECPubKey
}
// ECPubFromSchnorrBytes converts a BIP-340 public key to its even standard 33 byte encoding.
//
// This function is for the purpose of getting a key to do ECDH from an x-only key.
func ECPubFromSchnorrBytes(xkb []byte) (pub *ECPub, err error) {
if err = AssertLen(xkb, schnorr.PubKeyBytesLen, "pubkey"); chk.E(err) {
return
}
pub = &ECPub{}
p := append([]byte{0}, xkb...)
if C.secp256k1_ec_pubkey_parse(
ctx, &pub.Key, ToUchar(p),
secp256k1.PubKeyBytesLenCompressed,
) != 1 {
err = errorf.E("failed to parse pubkey from %0x", p)
log.I.S(pub)
return
}
return
}
// // ECPubFromBytes parses a pubkey from 33 bytes to the bitcoin-core/secp256k1 struct.
// func ECPubFromBytes(pkb []byte) (pub *ECPub, err error) {
// if err = AssertLen(pkb, secp256k1.PubKeyBytesLenCompressed, "pubkey"); chk.E(err) {
// return
// }
// pub = &ECPub{}
// if C.secp256k1_ec_pubkey_parse(ctx, &pub.Key, ToUchar(pkb),
// secp256k1.PubKeyBytesLenCompressed) != 1 {
// err = errorf.E("failed to parse pubkey from %0x", pkb)
// log.I.S(pub)
// return
// }
// return
// }
// Pub is a schnorr BIP-340 public key.
type Pub struct {
Key PubKey
}
// PubFromBytes creates a public key from raw bytes.
func PubFromBytes(pk []byte) (pub *Pub, err error) {
if err = AssertLen(pk, schnorr.PubKeyBytesLen, "pubkey"); chk.E(err) {
return
}
pub = new(Pub)
if C.secp256k1_xonly_pubkey_parse(ctx, &pub.Key, ToUchar(pk)) != 1 {
err = errorf.E("failed to parse pubkey from %0x", pk)
return
}
return
}
// PubB returns the contained public key as bytes.
func (p *Pub) PubB() (b []byte) {
b = make([]byte, schnorr.PubKeyBytesLen)
C.secp256k1_xonly_pubkey_serialize(ctx, ToUchar(b), &p.Key)
return
}
// Pub returns the public key as a PubKey.
func (p *Pub) Pub() *PubKey { return &p.Key }
// ToBytes returns the contained public key as bytes.
func (p *Pub) ToBytes() (b []byte, err error) {
b = make([]byte, schnorr.PubKeyBytesLen)
if C.secp256k1_xonly_pubkey_serialize(ctx, ToUchar(b), p.Pub()) != 1 {
err = errorf.E("pubkey serialize failed")
return
}
return
}
// Sign a message and return a schnorr BIP-340 64 byte signature.
func Sign(msg *Uchar, sk *SecKey) (sig []byte, err error) {
sig = make([]byte, schnorr.SignatureSize)
c := CreateRandomContext()
if C.secp256k1_schnorrsig_sign32(
c, ToUchar(sig), msg, sk,
GetRandom(),
) != 1 {
err = errorf.E("failed to sign message")
return
}
return
}
// SignFromBytes Signs a message using a provided secret key and message as raw bytes.
func SignFromBytes(msg, sk []byte) (sig []byte, err error) {
var umsg *Uchar
if umsg, err = Msg(msg); chk.E(err) {
return
}
var sec *Sec
if sec, err = SecFromBytes(sk); chk.E(err) {
return
}
return Sign(umsg, sec.Sec())
}
// Msg checks that a message hash is correct, and converts it for use with a Signer.
func Msg(b []byte) (id *Uchar, err error) {
if err = AssertLen(b, sha256.Size, "id"); chk.E(err) {
return
}
id = ToUchar(b)
return
}
// Sig checks that a signature bytes is correct, and converts it for use with a Signer.
func Sig(b []byte) (sig *Uchar, err error) {
if err = AssertLen(b, schnorr.SignatureSize, "sig"); chk.E(err) {
return
}
sig = ToUchar(b)
return
}
// Verify a message signature matches the provided PubKey.
func Verify(msg, sig *Uchar, pk *PubKey) (valid bool) {
return C.secp256k1_schnorrsig_verify(ctx, sig, msg, 32, pk) == 1
}
// VerifyFromBytes a signature from the raw bytes of the message hash, signature and public key
func VerifyFromBytes(msg, sig, pk []byte) (err error) {
var umsg, usig *Uchar
if umsg, err = Msg(msg); chk.E(err) {
return
}
if usig, err = Sig(sig); chk.E(err) {
return
}
var pub *Pub
if pub, err = PubFromBytes(pk); chk.E(err) {
return
}
valid := Verify(umsg, usig, pub.Pub())
if !valid {
err = errorf.E("failed to verify signature")
}
return
}
// Zero wipes the memory of a SecKey by overwriting it three times with random data and then
// zeroing it.
func Zero(sk *SecKey) {
b := (*[96]byte)(unsafe.Pointer(sk))[:96]
for range 3 {
rand.Read(b)
// reverse the order and negate
lb := len(b)
l := lb / 2
for j := range l {
b[j] = ^b[lb-1-j]
}
}
for i := range b {
b[i] = 0
}
}
// Keygen is an implementation of a key miner designed to be used for vanity key generation with X-only BIP-340 keys.
type Keygen struct {
secBytes, comprPubBytes []byte
secUchar, cmprPubUchar *Uchar
sec *Sec
// ecpub *PublicKey
cmprLen C.size_t
}
// NewKeygen allocates the required buffers for deriving a key. This should only be done once to avoid garbage and make
// the key mining as fast as possible.
//
// This allocates everything and creates proper CGO variables needed for the generate function so they only need to be
// allocated once per thread.
func NewKeygen() (k *Keygen) {
k = new(Keygen)
k.cmprLen = C.size_t(secp256k1.PubKeyBytesLenCompressed)
k.secBytes = make([]byte, secp256k1.SecKeyBytesLen)
k.comprPubBytes = make([]byte, secp256k1.PubKeyBytesLenCompressed)
k.secUchar = ToUchar(k.secBytes)
k.cmprPubUchar = ToUchar(k.comprPubBytes)
k.sec = &Sec{}
// k.ecpub = NewPublicKey()
return
}
// Generate takes a pair of buffers for the secret and ec pubkey bytes and gathers new entropy and returns a valid
// secret key and the compressed pubkey bytes for the partial collision search.
//
// The first byte of pubBytes must be sliced off before deriving the hex/Bech32 forms of the nostr public key.
func (k *Keygen) Generate() (
sec *Sec,
pub *XPublicKey,
pubBytes []byte,
err error,
) {
if _, err = rand.Read(k.secBytes); chk.E(err) {
return
}
if res := C.secp256k1_keypair_create(
ctx, &k.sec.Key, k.secUchar,
); res != 1 {
err = errorf.E("failed to create secp256k1 keypair")
return
}
var parity Cint
C.secp256k1_keypair_xonly_pub(ctx, pub.Key, &parity, &sec.Key)
// C.secp256k1_keypair_pub(ctx, k.ecpub.Key, &k.sec.Key)
// C.secp256k1_ec_pubkey_serialize(ctx, k.cmprPubUchar, &k.cmprLen, k.ecpub.Key,
// C.SECP256K1_EC_COMPRESSED)
// pubBytes = k.comprPubBytes
C.secp256k1_xonly_pubkey_serialize(ctx, ToUchar(pubBytes), pub.Key)
// pubBytes =
return
}