schnorr.go raw
1 //go:build !js && !wasm && !tinygo && !wasm32
2
3 package p256k1
4
5 import (
6 "errors"
7 "sync"
8 "unsafe"
9 )
10
11 // BIP-340 nonce tag
12 var bip340NonceTag = []byte("BIP0340/nonce")
13
14 // BIP-340 aux tag
15 var bip340AuxTag = []byte("BIP0340/aux")
16
17 // BIP-340 challenge tag
18 var bip340ChallengeTag = []byte("BIP0340/challenge")
19
20 // Zero mask for BIP-340 nonce generation (precomputed TaggedHash("BIP0340/aux", 0x0000...00))
21 var zeroMask = [32]byte{
22 84, 241, 105, 207, 201, 226, 229, 114,
23 116, 128, 68, 31, 144, 186, 37, 196,
24 136, 244, 97, 199, 11, 94, 165, 220,
25 170, 247, 175, 105, 39, 10, 165, 20,
26 }
27
28 // Global precomputed context for Schnorr verification
29 // This eliminates the overhead of context creation per verification call
30 var (
31 schnorrVerifyContext *secp256k1_context
32 schnorrVerifyContextOnce sync.Once
33 )
34
35 // initSchnorrVerifyContext initializes the global Schnorr verification context
36 func initSchnorrVerifyContext() {
37 schnorrVerifyContext = &secp256k1_context{
38 ecmult_gen_ctx: secp256k1_ecmult_gen_context{built: 1},
39 declassify: 0,
40 }
41 }
42
43 // getSchnorrVerifyContext returns the precomputed Schnorr verification context
44 func getSchnorrVerifyContext() *secp256k1_context {
45 schnorrVerifyContextOnce.Do(initSchnorrVerifyContext)
46 return schnorrVerifyContext
47 }
48
49 // NonceFunctionBIP340 implements BIP-340 nonce generation
50 // Optimized to use fixed-size buffers to avoid heap allocations
51 func NonceFunctionBIP340(nonce32 []byte, msg []byte, key32 []byte, xonlyPk32 []byte, auxRand32 []byte) error {
52 if len(nonce32) != 32 {
53 return errors.New("nonce32 must be 32 bytes")
54 }
55 if len(key32) != 32 {
56 return errors.New("key32 must be 32 bytes")
57 }
58 if len(xonlyPk32) != 32 {
59 return errors.New("xonlyPk32 must be 32 bytes")
60 }
61 if len(msg) != 32 {
62 return errors.New("msg must be 32 bytes")
63 }
64
65 // Mask key with aux random data
66 var maskedKey [32]byte
67 if auxRand32 != nil && len(auxRand32) == 32 {
68 // TaggedHash("BIP0340/aux", aux_rand32)
69 auxHash := TaggedHash(bip340AuxTag, auxRand32)
70 for i := 0; i < 32; i++ {
71 maskedKey[i] = key32[i] ^ auxHash[i]
72 }
73 } else {
74 // Use zero mask
75 for i := 0; i < 32; i++ {
76 maskedKey[i] = key32[i] ^ zeroMask[i]
77 }
78 }
79
80 // TaggedHash("BIP0340/nonce", masked_key || xonly_pk || msg)
81 // Use fixed-size buffer to avoid heap allocation (32 + 32 + 32 = 96 bytes)
82 var nonceInput [96]byte
83 copy(nonceInput[0:32], maskedKey[:])
84 copy(nonceInput[32:64], xonlyPk32)
85 copy(nonceInput[64:96], msg)
86
87 nonceHash := TaggedHash(bip340NonceTag, nonceInput[:])
88 copy(nonce32, nonceHash[:])
89
90 // Clear sensitive data
91 memclear(unsafe.Pointer(&maskedKey[0]), 32)
92 memclear(unsafe.Pointer(&nonceInput[0]), 96)
93
94 return nil
95 }
96
97 // SchnorrSignature represents a 64-byte Schnorr signature (r || s)
98 type SchnorrSignature [64]byte
99
100 // SchnorrSign creates a Schnorr signature following BIP-340
101 func SchnorrSign(sig64 []byte, msg32 []byte, keypair *KeyPair, auxRand32 []byte) error {
102 if len(sig64) != 64 {
103 return errors.New("signature must be 64 bytes")
104 }
105 if len(msg32) != 32 {
106 return errors.New("message must be 32 bytes")
107 }
108 if keypair == nil {
109 return errors.New("keypair cannot be nil")
110 }
111
112 // Load secret key
113 var sk Scalar
114 if !sk.setB32Seckey(keypair.seckey[:]) {
115 return errors.New("invalid secret key")
116 }
117
118 // Load public key
119 var pk GroupElementAffine
120 pk.fromBytes(keypair.pubkey.data[:])
121 if pk.isInfinity() {
122 return errors.New("invalid public key")
123 }
124
125 // Negate secret key if Y coordinate is odd (BIP-340 requires even Y)
126 pk.y.normalize()
127 var skBytes [32]byte
128 sk.getB32(skBytes[:])
129
130 if pk.y.isOdd() {
131 sk.negate(&sk)
132 sk.getB32(skBytes[:]) // Update skBytes with negated key
133 // Update pk to have even Y
134 pk.negate(&pk)
135 }
136
137 // Get x-only public key (X coordinate)
138 var pkX [32]byte
139 pk.x.normalize()
140 pk.x.getB32(pkX[:])
141
142 // Generate nonce (use the possibly-negated secret key)
143 var nonce32 [32]byte
144 if err := NonceFunctionBIP340(nonce32[:], msg32, skBytes[:], pkX[:], auxRand32); err != nil {
145 return err
146 }
147
148 // Parse nonce scalar
149 var k Scalar
150 if !k.setB32Seckey(nonce32[:]) {
151 return errors.New("nonce generation failed")
152 }
153
154 if k.isZero() {
155 return errors.New("nonce is zero")
156 }
157
158 // Compute R = k * G
159 var rj GroupElementJacobian
160 EcmultGen(&rj, &k)
161
162 // Convert to affine
163 var r GroupElementAffine
164 r.setGEJ(&rj)
165 r.y.normalize()
166
167 // If R.y is odd, negate k
168 if r.y.isOdd() {
169 k.negate(&k)
170 // Recompute R with negated k
171 EcmultGen(&rj, &k)
172 r.setGEJ(&rj)
173 }
174
175 // Extract r = X(R)
176 r.x.normalize()
177 var r32 [32]byte
178 r.x.getB32(r32[:])
179 copy(sig64[:32], r32[:])
180
181 // Compute challenge e = TaggedHash("BIP0340/challenge", r || pk || msg)
182 // Use fixed-size buffer to avoid heap allocation (32 + 32 + 32 = 96 bytes)
183 var challengeInput [96]byte
184 copy(challengeInput[0:32], r32[:])
185 copy(challengeInput[32:64], pkX[:])
186 copy(challengeInput[64:96], msg32)
187
188 challengeHash := TaggedHash(bip340ChallengeTag, challengeInput[:])
189 var e Scalar
190 e.setB32(challengeHash[:])
191
192 // Compute s = k + e * sk
193 var s Scalar
194 s.mul(&e, &sk)
195 s.add(&s, &k)
196
197 // Serialize s
198 var s32 [32]byte
199 s.getB32(s32[:])
200 copy(sig64[32:], s32[:])
201
202 // Clear sensitive data
203 sk.clear()
204 k.clear()
205 e.clear()
206 s.clear()
207 memclear(unsafe.Pointer(&nonce32[0]), 32)
208 memclear(unsafe.Pointer(&pkX[0]), 32)
209 memclear(unsafe.Pointer(&skBytes[0]), 32)
210 rj.clear()
211 r.clear()
212
213 return nil
214 }
215
216 // SchnorrVerifyOld is the deprecated original implementation of SchnorrVerify.
217 // Deprecated: Use SchnorrVerify instead, which uses the C-translated implementation.
218 func SchnorrVerifyOld(sig64 []byte, msg32 []byte, xonlyPubkey *XOnlyPubkey) bool {
219 if len(sig64) != 64 {
220 return false
221 }
222 if len(msg32) != 32 {
223 return false
224 }
225 if xonlyPubkey == nil {
226 return false
227 }
228
229 // Extract r and s from signature
230 var r32 [32]byte
231 var s32 [32]byte
232 copy(r32[:], sig64[:32])
233 copy(s32[:], sig64[32:])
234
235 // Parse r as field element
236 var rx FieldElement
237 if err := rx.setB32(r32[:]); err != nil {
238 return false
239 }
240
241 // Check if r corresponds to a valid point
242 var r GroupElementAffine
243 if !r.setXOVar(&rx, false) {
244 // Try with odd Y
245 if !r.setXOVar(&rx, true) {
246 return false
247 }
248 }
249
250 // Parse s as scalar
251 var s Scalar
252 s.setB32(s32[:])
253 if s.isZero() {
254 return false
255 }
256
257 // Compute challenge e = TaggedHash("BIP0340/challenge", r || pk || msg)
258 // Use fixed-size buffer to avoid heap allocation (32 + 32 + 32 = 96 bytes)
259 var challengeInput [96]byte
260 copy(challengeInput[0:32], r32[:])
261 copy(challengeInput[32:64], xonlyPubkey.data[:])
262 copy(challengeInput[64:96], msg32)
263
264 challengeHash := TaggedHash(bip340ChallengeTag, challengeInput[:])
265 var e Scalar
266 e.setB32(challengeHash[:])
267
268 // Compute R = s*G - e*P
269 // First compute s*G
270 var sG GroupElementJacobian
271 EcmultGen(&sG, &s)
272
273 // Compute e*P where P is the x-only pubkey
274 // We need to reconstruct P with even Y
275 var pk GroupElementAffine
276 pk.x.setB32(xonlyPubkey.data[:])
277 // Always use even Y for x-only pubkey
278 if !pk.setXOVar(&pk.x, false) {
279 return false
280 }
281
282 // Use optimized variable-time multiplication for verification
283 // (constant-time is not required for public verification operations)
284 var pkJac GroupElementJacobian
285 pkJac.setGE(&pk)
286 var eP GroupElementJacobian
287 Ecmult(&eP, &pkJac, &e)
288
289 // Negate eP
290 var negEP GroupElementJacobian
291 negEP.negate(&eP)
292
293 // R = sG + (-eP)
294 var R GroupElementJacobian
295 R.addVar(&sG, &negEP)
296
297 // Convert R to affine
298 var RAff GroupElementAffine
299 RAff.setGEJ(&R)
300
301 if RAff.isInfinity() {
302 return false
303 }
304
305 // Check if R.y is even
306 RAff.y.normalize()
307 if RAff.y.isOdd() {
308 // Negate R
309 var negR GroupElementAffine
310 negR.negate(&RAff)
311 RAff = negR
312 }
313
314 // Compare X(R) with r
315 RAff.x.normalize()
316 var computedR [32]byte
317 RAff.x.getB32(computedR[:])
318
319 for i := 0; i < 32; i++ {
320 if computedR[i] != r32[i] {
321 return false
322 }
323 }
324
325 return true
326 }
327
328 // SchnorrVerify verifies a Schnorr signature following BIP-340.
329 // This is the new implementation translated from C secp256k1_schnorrsig_verify.
330 // Uses precomputed context for optimal performance.
331 func SchnorrVerify(sig64 []byte, msg32 []byte, xonlyPubkey *XOnlyPubkey) bool {
332 if len(sig64) != 64 {
333 return false
334 }
335 if len(msg32) != 32 {
336 return false
337 }
338 if xonlyPubkey == nil {
339 return false
340 }
341
342 // Use precomputed context (initialized once, reused across calls)
343 ctx := getSchnorrVerifyContext()
344
345 // Convert x-only pubkey to secp256k1_xonly_pubkey format
346 var secp_xonly secp256k1_xonly_pubkey
347 copy(secp_xonly.data[:], xonlyPubkey.data[:])
348
349 // Call the C-translated verification function
350 result := secp256k1_schnorrsig_verify(ctx, sig64, msg32, len(msg32), &secp_xonly)
351 return result != 0
352 }
353