schnorr.go raw

   1  package schnorr
   2  
   3  import (
   4  	"errors"
   5  
   6  	"next.orly.dev/pkg/p256k1"
   7  )
   8  
   9  // Signature is a 64-byte BIP-340 Schnorr signature.
  10  // This is a value object - immutable once created.
  11  type Signature = p256k1.SchnorrSignature
  12  
  13  // XOnlyPubkey is a 32-byte x-only public key (BIP-340).
  14  // X-only keys implicitly have even Y coordinate.
  15  type XOnlyPubkey = p256k1.XOnlyPubkey
  16  
  17  // KeyPair represents a secret/public key pair for Schnorr signing.
  18  // This is an aggregate root for key management within the Schnorr context.
  19  type KeyPair = p256k1.KeyPair
  20  
  21  // PublicKey is an alias for the core public key type.
  22  type PublicKey = p256k1.PublicKey
  23  
  24  // =============================================================================
  25  // Key Management
  26  // =============================================================================
  27  
  28  // NewKeyPair creates a key pair from a 32-byte private key.
  29  func NewKeyPair(privateKey []byte) (*KeyPair, error) {
  30  	return p256k1.KeyPairCreate(privateKey)
  31  }
  32  
  33  // GenerateKeyPair generates a new random key pair.
  34  func GenerateKeyPair() (*KeyPair, error) {
  35  	return p256k1.KeyPairGenerate()
  36  }
  37  
  38  // ParseXOnlyPubkey parses a 32-byte x-only public key.
  39  func ParseXOnlyPubkey(input []byte) (*XOnlyPubkey, error) {
  40  	return p256k1.XOnlyPubkeyParse(input)
  41  }
  42  
  43  // XOnlyFromPubkey converts a full public key to x-only format.
  44  // Returns the x-only key and parity (1 if Y was odd, 0 if even).
  45  func XOnlyFromPubkey(pubkey *PublicKey) (*XOnlyPubkey, int, error) {
  46  	return p256k1.XOnlyPubkeyFromPubkey(pubkey)
  47  }
  48  
  49  // =============================================================================
  50  // Signature Creation (Domain Service)
  51  // =============================================================================
  52  
  53  // Sign creates a BIP-340 Schnorr signature.
  54  //
  55  // Parameters:
  56  //   - message: The 32-byte message hash to sign
  57  //   - keypair: The key pair to sign with
  58  //   - auxRand: Optional 32-byte auxiliary randomness (can be nil)
  59  //
  60  // The auxiliary randomness provides additional security against side-channel
  61  // attacks. If nil, a deterministic fallback is used.
  62  //
  63  // Returns a 64-byte signature (r || s).
  64  func Sign(message []byte, keypair *KeyPair, auxRand []byte) (*Signature, error) {
  65  	if len(message) != 32 {
  66  		return nil, errors.New("message must be 32 bytes")
  67  	}
  68  	if keypair == nil {
  69  		return nil, errors.New("keypair cannot be nil")
  70  	}
  71  
  72  	var sig Signature
  73  	if err := p256k1.SchnorrSign(sig[:], message, keypair, auxRand); err != nil {
  74  		return nil, err
  75  	}
  76  	return &sig, nil
  77  }
  78  
  79  // SignRaw creates a signature and writes it to the provided 64-byte buffer.
  80  // This is a lower-level function that avoids allocation.
  81  func SignRaw(sig64 []byte, message []byte, keypair *KeyPair, auxRand []byte) error {
  82  	return p256k1.SchnorrSign(sig64, message, keypair, auxRand)
  83  }
  84  
  85  // =============================================================================
  86  // Signature Verification (Domain Service)
  87  // =============================================================================
  88  
  89  // Verify verifies a BIP-340 Schnorr signature.
  90  //
  91  // Parameters:
  92  //   - sig: The 64-byte signature
  93  //   - message: The 32-byte message hash
  94  //   - pubkey: The x-only public key
  95  //
  96  // Returns true if the signature is valid, false otherwise.
  97  func Verify(sig *Signature, message []byte, pubkey *XOnlyPubkey) bool {
  98  	return p256k1.SchnorrVerify(sig[:], message, pubkey)
  99  }
 100  
 101  // VerifyRaw verifies a signature from raw byte slices.
 102  func VerifyRaw(sig64, message []byte, pubkey *XOnlyPubkey) bool {
 103  	return p256k1.SchnorrVerify(sig64, message, pubkey)
 104  }
 105  
 106  // =============================================================================
 107  // Batch Verification
 108  // =============================================================================
 109  
 110  // VerifyItem represents a single signature to verify in a batch.
 111  type VerifyItem struct {
 112  	Signature *Signature
 113  	Message   []byte
 114  	Pubkey    *XOnlyPubkey
 115  }
 116  
 117  // VerifyBatch verifies multiple Schnorr signatures efficiently.
 118  //
 119  // Batch verification uses the Strauss algorithm to share doublings across
 120  // all verifications, making it significantly faster than individual verification
 121  // when verifying multiple signatures.
 122  //
 123  // Returns true only if ALL signatures are valid.
 124  func VerifyBatch(items []VerifyItem) bool {
 125  	// Convert to the internal batch format
 126  	batchItems := make([]p256k1.BatchSchnorrItem, len(items))
 127  	for i, item := range items {
 128  		batchItems[i] = p256k1.BatchSchnorrItem{
 129  			Signature: item.Signature[:],
 130  			Message:   item.Message,
 131  			Pubkey:    item.Pubkey,
 132  		}
 133  	}
 134  	return p256k1.SchnorrBatchVerify(batchItems)
 135  }
 136  
 137  // =============================================================================
 138  // Signature Serialization
 139  // =============================================================================
 140  
 141  // ParseSignature parses a 64-byte Schnorr signature.
 142  func ParseSignature(sig64 []byte) (*Signature, error) {
 143  	if len(sig64) != 64 {
 144  		return nil, errors.New("signature must be 64 bytes")
 145  	}
 146  	var sig Signature
 147  	copy(sig[:], sig64)
 148  	return &sig, nil
 149  }
 150  
 151  // Bytes returns the signature as a 64-byte slice.
 152  func Bytes(sig *Signature) []byte {
 153  	return sig[:]
 154  }
 155  
 156  // R returns the R component (first 32 bytes) of the signature.
 157  func R(sig *Signature) []byte {
 158  	return sig[:32]
 159  }
 160  
 161  // S returns the S component (last 32 bytes) of the signature.
 162  func S(sig *Signature) []byte {
 163  	return sig[32:]
 164  }
 165  
 166  // =============================================================================
 167  // X-Only Public Key Operations
 168  // =============================================================================
 169  
 170  // SerializeXOnly serializes an x-only public key to 32 bytes.
 171  func SerializeXOnly(pubkey *XOnlyPubkey) [32]byte {
 172  	return pubkey.Serialize()
 173  }
 174  
 175  // CompareXOnly compares two x-only public keys lexicographically.
 176  // Returns <0 if a < b, >0 if a > b, 0 if equal.
 177  func CompareXOnly(a, b *XOnlyPubkey) int {
 178  	return p256k1.XOnlyPubkeyCmp(a, b)
 179  }
 180