// Package p8k provides a signer.I implementation backed by crypto/secp256k1. package p8k import ( "crypto/rand" "crypto/secp256k1" "crypto/sha256" "fmt" "io" ) type Signer struct { sec [32]byte pub [32]byte hasSec bool hasPub bool } func New() (s *Signer, err error) { return &Signer{}, nil } func MustNew() (s *Signer) { return &Signer{} } func (s *Signer) Generate() (err error) { var sk [32]byte for { if _, rerr := io.ReadFull(rand.Reader(), sk[:]); rerr != nil { return rerr } if secp256k1.ValidateSecretKey(sk[:]) { break } } pk, ok := secp256k1.PubKeyFromSecKey(sk) if !ok { return fmt.Errorf("pubkey derivation failed") } s.sec = sk s.pub = pk s.hasSec = true s.hasPub = true return nil } func (s *Signer) InitSec(sec []byte) (err error) { if len(sec) != 32 { return fmt.Errorf("invalid secret key length: %d", len(sec)) } var sk [32]byte copy(sk[:], sec) pk, ok := secp256k1.PubKeyFromSecKey(sk) if !ok { return fmt.Errorf("invalid secret key") } s.sec = sk s.pub = pk s.hasSec = true s.hasPub = true return nil } func (s *Signer) InitPub(pub []byte) (err error) { if !secp256k1.ValidatePubKey(pub) { return fmt.Errorf("invalid public key") } copy(s.pub[:], pub) s.hasPub = true s.hasSec = false return nil } func (s *Signer) Sec() (buf []byte) { if !s.hasSec { return nil } out := []byte{:32} copy(out, s.sec[:]) return out } func (s *Signer) Pub() (buf []byte) { if !s.hasPub { return nil } out := []byte{:32} copy(out, s.pub[:]) return out } func (s *Signer) Sign(msg []byte) (der []byte, err error) { if !s.hasSec { return nil, fmt.Errorf("no secret key") } if len(msg) != 32 { return nil, fmt.Errorf("message must be 32 bytes, got %d", len(msg)) } var m, aux [32]byte copy(m[:], msg) // Aux randomness: BIP-340 recommends fresh randomness per signature, but // zero aux is also valid (deterministic signing). Use deterministic for // testability; callers wanting fresh aux can pre-randomize. sig, ok := secp256k1.SignSchnorr(s.sec, m, aux) if !ok { return nil, fmt.Errorf("sign failed") } out := []byte{:64} copy(out, sig[:]) return out, nil } func (s *Signer) Verify(msg, sig []byte) (good bool, err error) { if !s.hasPub { return false, fmt.Errorf("no public key") } if len(msg) != 32 { return false, fmt.Errorf("message must be 32 bytes, got %d", len(msg)) } if len(sig) != 64 { return false, fmt.Errorf("signature must be 64 bytes, got %d", len(sig)) } var m [32]byte var sg [64]byte copy(m[:], msg) copy(sg[:], sig) return secp256k1.VerifySchnorr(s.pub, m, sg), nil } func (s *Signer) Zero() { for i := range s.sec { s.sec[i] = 0 } for i := range s.pub { s.pub[i] = 0 } s.hasSec = false s.hasPub = false } func (s *Signer) ECDH(pub []byte) (sec []byte, err error) { raw, err := s.ECDHRaw(pub) if err != nil { return nil, err } h := sha256.Sum256(raw) out := []byte{:32} copy(out, h[:]) return out, nil } func (s *Signer) ECDHRaw(pub []byte) (raw []byte, err error) { if !s.hasSec { return nil, fmt.Errorf("no secret key") } if len(pub) != 32 { return nil, fmt.Errorf("pubkey must be 32 bytes, got %d", len(pub)) } var pk [32]byte copy(pk[:], pub) shared, ok := secp256k1.ECDH(s.sec, pk) if !ok { return nil, fmt.Errorf("ecdh failed") } out := []byte{:32} copy(out, shared[:]) return out, nil }