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