p8k.mx raw

   1  // Package p8k provides a signer.I implementation backed by crypto/secp256k1.
   2  package p8k
   3  
   4  import (
   5  	"crypto/rand"
   6  	"crypto/secp256k1"
   7  	"crypto/sha256"
   8  	"fmt"
   9  	"io"
  10  )
  11  
  12  type Signer struct {
  13  	sec [32]byte
  14  	pub [32]byte
  15  	hasSec bool
  16  	hasPub bool
  17  }
  18  
  19  func New() (s *Signer, err error) {
  20  	return &Signer{}, nil
  21  }
  22  
  23  func MustNew() (s *Signer) {
  24  	return &Signer{}
  25  }
  26  
  27  func (s *Signer) Generate() (err error) {
  28  	var sk [32]byte
  29  	for {
  30  		if _, rerr := io.ReadFull(rand.Reader(), sk[:]); rerr != nil {
  31  			return rerr
  32  		}
  33  		if secp256k1.ValidateSecretKey(sk[:]) {
  34  			break
  35  		}
  36  	}
  37  	pk, ok := secp256k1.PubKeyFromSecKey(sk)
  38  	if !ok {
  39  		return fmt.Errorf("pubkey derivation failed")
  40  	}
  41  	s.sec = sk
  42  	s.pub = pk
  43  	s.hasSec = true
  44  	s.hasPub = true
  45  	return nil
  46  }
  47  
  48  func (s *Signer) InitSec(sec []byte) (err error) {
  49  	if len(sec) != 32 {
  50  		return fmt.Errorf("invalid secret key length: %d", len(sec))
  51  	}
  52  	var sk [32]byte
  53  	copy(sk[:], sec)
  54  	pk, ok := secp256k1.PubKeyFromSecKey(sk)
  55  	if !ok {
  56  		return fmt.Errorf("invalid secret key")
  57  	}
  58  	s.sec = sk
  59  	s.pub = pk
  60  	s.hasSec = true
  61  	s.hasPub = true
  62  	return nil
  63  }
  64  
  65  func (s *Signer) InitPub(pub []byte) (err error) {
  66  	if !secp256k1.ValidatePubKey(pub) {
  67  		return fmt.Errorf("invalid public key")
  68  	}
  69  	copy(s.pub[:], pub)
  70  	s.hasPub = true
  71  	s.hasSec = false
  72  	return nil
  73  }
  74  
  75  func (s *Signer) Sec() (buf []byte) {
  76  	if !s.hasSec {
  77  		return nil
  78  	}
  79  	out := []byte{:32}
  80  	copy(out, s.sec[:])
  81  	return out
  82  }
  83  
  84  func (s *Signer) Pub() (buf []byte) {
  85  	if !s.hasPub {
  86  		return nil
  87  	}
  88  	out := []byte{:32}
  89  	copy(out, s.pub[:])
  90  	return out
  91  }
  92  
  93  func (s *Signer) Sign(msg []byte) (der []byte, err error) {
  94  	if !s.hasSec {
  95  		return nil, fmt.Errorf("no secret key")
  96  	}
  97  	if len(msg) != 32 {
  98  		return nil, fmt.Errorf("message must be 32 bytes, got %d", len(msg))
  99  	}
 100  	var m, aux [32]byte
 101  	copy(m[:], msg)
 102  	// Aux randomness: BIP-340 recommends fresh randomness per signature, but
 103  	// zero aux is also valid (deterministic signing). Use deterministic for
 104  	// testability; callers wanting fresh aux can pre-randomize.
 105  	sig, ok := secp256k1.SignSchnorr(s.sec, m, aux)
 106  	if !ok {
 107  		return nil, fmt.Errorf("sign failed")
 108  	}
 109  	out := []byte{:64}
 110  	copy(out, sig[:])
 111  	return out, nil
 112  }
 113  
 114  func (s *Signer) Verify(msg, sig []byte) (good bool, err error) {
 115  	if !s.hasPub {
 116  		return false, fmt.Errorf("no public key")
 117  	}
 118  	if len(msg) != 32 {
 119  		return false, fmt.Errorf("message must be 32 bytes, got %d", len(msg))
 120  	}
 121  	if len(sig) != 64 {
 122  		return false, fmt.Errorf("signature must be 64 bytes, got %d", len(sig))
 123  	}
 124  	var m [32]byte
 125  	var sg [64]byte
 126  	copy(m[:], msg)
 127  	copy(sg[:], sig)
 128  	return secp256k1.VerifySchnorr(s.pub, m, sg), nil
 129  }
 130  
 131  func (s *Signer) Zero() {
 132  	for i := range s.sec {
 133  		s.sec[i] = 0
 134  	}
 135  	for i := range s.pub {
 136  		s.pub[i] = 0
 137  	}
 138  	s.hasSec = false
 139  	s.hasPub = false
 140  }
 141  
 142  func (s *Signer) ECDH(pub []byte) (sec []byte, err error) {
 143  	raw, err := s.ECDHRaw(pub)
 144  	if err != nil {
 145  		return nil, err
 146  	}
 147  	h := sha256.Sum256(raw)
 148  	out := []byte{:32}
 149  	copy(out, h[:])
 150  	return out, nil
 151  }
 152  
 153  func (s *Signer) ECDHRaw(pub []byte) (raw []byte, err error) {
 154  	if !s.hasSec {
 155  		return nil, fmt.Errorf("no secret key")
 156  	}
 157  	if len(pub) != 32 {
 158  		return nil, fmt.Errorf("pubkey must be 32 bytes, got %d", len(pub))
 159  	}
 160  	var pk [32]byte
 161  	copy(pk[:], pub)
 162  	shared, ok := secp256k1.ECDH(s.sec, pk)
 163  	if !ok {
 164  		return nil, fmt.Errorf("ecdh failed")
 165  	}
 166  	out := []byte{:32}
 167  	copy(out, shared[:])
 168  	return out, nil
 169  }
 170