libsecp256k1_purego.go raw
1 //go:build !js && !wasm && !tinygo && !wasm32
2
3 package p256k1
4
5 import (
6 "errors"
7 "sync"
8
9 "github.com/ebitengine/purego"
10 )
11
12 // LibSecp256k1 wraps the native libsecp256k1.so library using purego for CGO-free operation.
13 // This provides a way to benchmark against the C implementation without CGO.
14 type LibSecp256k1 struct {
15 lib uintptr
16 ctx uintptr
17 loaded bool
18 mu sync.RWMutex
19
20 // Function pointers
21 contextCreate func(uint) uintptr
22 contextDestroy func(uintptr)
23 contextRandomize func(uintptr, *byte) int
24 schnorrsigSign32 func(uintptr, *byte, *byte, *byte, *byte) int
25 schnorrsigVerify func(uintptr, *byte, *byte, uint, *byte) int
26 keypairCreate func(uintptr, *byte, *byte) int
27 keypairXonlyPub func(uintptr, *byte, *int, *byte) int
28 xonlyPubkeyParse func(uintptr, *byte, *byte) int
29 ecPubkeyCreate func(uintptr, *byte, *byte) int
30 ecPubkeyParse func(uintptr, *byte, *byte, uint) int
31 ecPubkeySerialize func(uintptr, *byte, *uint, *byte, uint) int
32 xonlyPubkeySerialize func(uintptr, *byte, *byte) int
33 ecdh func(uintptr, *byte, *byte, *byte, uintptr, uintptr) int
34
35 // ECDSA function pointers
36 ecdsaSign func(uintptr, *byte, *byte, *byte, uintptr, uintptr) int
37 ecdsaVerify func(uintptr, *byte, *byte, *byte) int
38 ecdsaSignatureParseDER func(uintptr, *byte, *byte, uint) int
39 ecdsaSignatureSerializeDER func(uintptr, *byte, *uint, *byte) int
40 ecdsaSignatureParseCompact func(uintptr, *byte, *byte) int
41 ecdsaSignatureSerializeCompact func(uintptr, *byte, *byte) int
42 ecdsaSignatureNormalize func(uintptr, *byte, *byte) int
43 ecdsaRecover func(uintptr, *byte, *byte, *byte, int) int
44 ecdsaRecoverableSignatureParseCompact func(uintptr, *byte, *byte, int) int
45 ecdsaRecoverableSignatureSerializeCompact func(uintptr, *byte, *int, *byte) int
46 ecdsaSignRecoverable func(uintptr, *byte, *byte, *byte, uintptr, uintptr) int
47 }
48
49 // Secp256k1 context flags
50 // In modern libsecp256k1, SECP256K1_CONTEXT_NONE = 1 is the only valid flag.
51 // The old SIGN (256) and VERIFY (257) flags are deprecated.
52 const (
53 libContextNone = 1
54 )
55
56 // Global instance
57 var (
58 libSecp *LibSecp256k1
59 libSecpOnce sync.Once
60 libSecpInitErr error
61 )
62
63 // GetLibSecp256k1 returns the global LibSecp256k1 instance, loading it if necessary.
64 // Returns nil and an error if the library cannot be loaded.
65 func GetLibSecp256k1() (*LibSecp256k1, error) {
66 libSecpOnce.Do(func() {
67 libSecp = &LibSecp256k1{}
68 // Try multiple paths to find the library
69 paths := []string{
70 "./libsecp256k1.so",
71 "../libsecp256k1.so",
72 "/home/mleku/src/p256k1.mleku.dev/libsecp256k1.so",
73 "libsecp256k1.so",
74 }
75 for _, path := range paths {
76 err := libSecp.Load(path)
77 if err == nil {
78 libSecpInitErr = nil
79 return
80 }
81 libSecpInitErr = err
82 }
83 })
84 if libSecpInitErr != nil {
85 return nil, libSecpInitErr
86 }
87 return libSecp, nil
88 }
89
90 // Load loads the libsecp256k1.so library from the given path.
91 func (l *LibSecp256k1) Load(path string) error {
92 l.mu.Lock()
93 defer l.mu.Unlock()
94
95 if l.loaded {
96 return nil
97 }
98
99 lib, err := purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_GLOBAL)
100 if err != nil {
101 return err
102 }
103 l.lib = lib
104
105 // Register function pointers
106 purego.RegisterLibFunc(&l.contextCreate, lib, "secp256k1_context_create")
107 purego.RegisterLibFunc(&l.contextDestroy, lib, "secp256k1_context_destroy")
108 purego.RegisterLibFunc(&l.contextRandomize, lib, "secp256k1_context_randomize")
109 purego.RegisterLibFunc(&l.schnorrsigSign32, lib, "secp256k1_schnorrsig_sign32")
110 purego.RegisterLibFunc(&l.schnorrsigVerify, lib, "secp256k1_schnorrsig_verify")
111 purego.RegisterLibFunc(&l.keypairCreate, lib, "secp256k1_keypair_create")
112 purego.RegisterLibFunc(&l.keypairXonlyPub, lib, "secp256k1_keypair_xonly_pub")
113 purego.RegisterLibFunc(&l.xonlyPubkeyParse, lib, "secp256k1_xonly_pubkey_parse")
114 purego.RegisterLibFunc(&l.ecPubkeyCreate, lib, "secp256k1_ec_pubkey_create")
115 purego.RegisterLibFunc(&l.ecPubkeyParse, lib, "secp256k1_ec_pubkey_parse")
116 purego.RegisterLibFunc(&l.ecPubkeySerialize, lib, "secp256k1_ec_pubkey_serialize")
117 purego.RegisterLibFunc(&l.xonlyPubkeySerialize, lib, "secp256k1_xonly_pubkey_serialize")
118 purego.RegisterLibFunc(&l.ecdh, lib, "secp256k1_ecdh")
119
120 // Register ECDSA function pointers
121 purego.RegisterLibFunc(&l.ecdsaSign, lib, "secp256k1_ecdsa_sign")
122 purego.RegisterLibFunc(&l.ecdsaVerify, lib, "secp256k1_ecdsa_verify")
123 purego.RegisterLibFunc(&l.ecdsaSignatureParseDER, lib, "secp256k1_ecdsa_signature_parse_der")
124 purego.RegisterLibFunc(&l.ecdsaSignatureSerializeDER, lib, "secp256k1_ecdsa_signature_serialize_der")
125 purego.RegisterLibFunc(&l.ecdsaSignatureParseCompact, lib, "secp256k1_ecdsa_signature_parse_compact")
126 purego.RegisterLibFunc(&l.ecdsaSignatureSerializeCompact, lib, "secp256k1_ecdsa_signature_serialize_compact")
127 purego.RegisterLibFunc(&l.ecdsaSignatureNormalize, lib, "secp256k1_ecdsa_signature_normalize")
128
129 // Register ECDSA recovery functions (may not be available in all builds)
130 // These are optional - the library may not have the recovery module enabled
131 // We use a helper that ignores errors for optional functions
132 registerOptional := func(fn interface{}, lib uintptr, name string) {
133 defer func() { recover() }() // Ignore panic from missing symbol
134 purego.RegisterLibFunc(fn, lib, name)
135 }
136 registerOptional(&l.ecdsaRecover, lib, "secp256k1_ecdsa_recover")
137 registerOptional(&l.ecdsaRecoverableSignatureParseCompact, lib, "secp256k1_ecdsa_recoverable_signature_parse_compact")
138 registerOptional(&l.ecdsaRecoverableSignatureSerializeCompact, lib, "secp256k1_ecdsa_recoverable_signature_serialize_compact")
139 registerOptional(&l.ecdsaSignRecoverable, lib, "secp256k1_ecdsa_sign_recoverable")
140
141 // Create context (modern libsecp256k1 uses SECP256K1_CONTEXT_NONE = 1)
142 l.ctx = l.contextCreate(libContextNone)
143 if l.ctx == 0 {
144 return errors.New("failed to create secp256k1 context")
145 }
146
147 // Randomize context for better security
148 var seed [32]byte
149 // Use zero seed for deterministic benchmarks
150 l.contextRandomize(l.ctx, &seed[0])
151
152 l.loaded = true
153 return nil
154 }
155
156 // Close releases the library resources.
157 func (l *LibSecp256k1) Close() {
158 l.mu.Lock()
159 defer l.mu.Unlock()
160
161 if !l.loaded {
162 return
163 }
164
165 if l.ctx != 0 {
166 l.contextDestroy(l.ctx)
167 l.ctx = 0
168 }
169
170 if l.lib != 0 {
171 purego.Dlclose(l.lib)
172 l.lib = 0
173 }
174
175 l.loaded = false
176 }
177
178 // IsLoaded returns true if the library is loaded.
179 func (l *LibSecp256k1) IsLoaded() bool {
180 l.mu.RLock()
181 defer l.mu.RUnlock()
182 return l.loaded
183 }
184
185 // SchnorrSign signs a 32-byte message using a 32-byte secret key.
186 // Returns a 64-byte signature.
187 func (l *LibSecp256k1) SchnorrSign(msg32, seckey32 []byte) ([]byte, error) {
188 l.mu.RLock()
189 defer l.mu.RUnlock()
190
191 if !l.loaded {
192 return nil, errors.New("library not loaded")
193 }
194 if len(msg32) != 32 {
195 return nil, errors.New("message must be 32 bytes")
196 }
197 if len(seckey32) != 32 {
198 return nil, errors.New("secret key must be 32 bytes")
199 }
200
201 // Create keypair from secret key
202 keypair := make([]byte, 96) // secp256k1_keypair is 96 bytes
203 if l.keypairCreate(l.ctx, &keypair[0], &seckey32[0]) != 1 {
204 return nil, errors.New("failed to create keypair")
205 }
206
207 // Sign
208 sig := make([]byte, 64)
209 if l.schnorrsigSign32(l.ctx, &sig[0], &msg32[0], &keypair[0], nil) != 1 {
210 return nil, errors.New("signing failed")
211 }
212
213 return sig, nil
214 }
215
216 // SchnorrVerify verifies a Schnorr signature.
217 func (l *LibSecp256k1) SchnorrVerify(sig64, msg32, pubkey32 []byte) bool {
218 l.mu.RLock()
219 defer l.mu.RUnlock()
220
221 if !l.loaded {
222 return false
223 }
224 if len(sig64) != 64 || len(msg32) != 32 || len(pubkey32) != 32 {
225 return false
226 }
227
228 // Parse x-only pubkey using secp256k1_xonly_pubkey_parse
229 xonlyPubkey := make([]byte, 64) // secp256k1_xonly_pubkey is 64 bytes
230 if l.xonlyPubkeyParse(l.ctx, &xonlyPubkey[0], &pubkey32[0]) != 1 {
231 return false
232 }
233
234 result := l.schnorrsigVerify(l.ctx, &sig64[0], &msg32[0], 32, &xonlyPubkey[0])
235 return result == 1
236 }
237
238 // CreatePubkey derives a public key from a secret key.
239 // Returns the 32-byte x-only public key.
240 func (l *LibSecp256k1) CreatePubkey(seckey32 []byte) ([]byte, error) {
241 l.mu.RLock()
242 defer l.mu.RUnlock()
243
244 if !l.loaded {
245 return nil, errors.New("library not loaded")
246 }
247 if len(seckey32) != 32 {
248 return nil, errors.New("secret key must be 32 bytes")
249 }
250
251 // Create keypair
252 keypair := make([]byte, 96)
253 if l.keypairCreate(l.ctx, &keypair[0], &seckey32[0]) != 1 {
254 return nil, errors.New("failed to create keypair")
255 }
256
257 // Extract x-only pubkey (internal representation is 64 bytes)
258 xonlyPubkey := make([]byte, 64)
259 var parity int
260 if l.keypairXonlyPub(l.ctx, &xonlyPubkey[0], &parity, &keypair[0]) != 1 {
261 return nil, errors.New("failed to extract x-only pubkey")
262 }
263
264 // Serialize to get the 32-byte x-coordinate
265 pubkey32 := make([]byte, 32)
266 if l.xonlyPubkeySerialize(l.ctx, &pubkey32[0], &xonlyPubkey[0]) != 1 {
267 return nil, errors.New("failed to serialize x-only pubkey")
268 }
269
270 return pubkey32, nil
271 }
272
273 // CreatePubkeyCompressed derives a compressed public key (33 bytes) from a secret key.
274 // Returns (compressed_pubkey, parity) where parity is 0 for even Y, 1 for odd Y.
275 func (l *LibSecp256k1) CreatePubkeyCompressed(seckey32 []byte) ([]byte, int, error) {
276 l.mu.RLock()
277 defer l.mu.RUnlock()
278
279 if !l.loaded {
280 return nil, 0, errors.New("library not loaded")
281 }
282 if len(seckey32) != 32 {
283 return nil, 0, errors.New("secret key must be 32 bytes")
284 }
285
286 // Create pubkey using ec_pubkey_create
287 pubkey := make([]byte, 64) // secp256k1_pubkey internal format
288 if l.ecPubkeyCreate(l.ctx, &pubkey[0], &seckey32[0]) != 1 {
289 return nil, 0, errors.New("failed to create pubkey")
290 }
291
292 // Serialize as compressed (33 bytes)
293 compressed := make([]byte, 33)
294 var outputLen uint = 33
295 const SECP256K1_EC_COMPRESSED = 258
296 if l.ecPubkeySerialize(l.ctx, &compressed[0], &outputLen, &pubkey[0], SECP256K1_EC_COMPRESSED) != 1 {
297 return nil, 0, errors.New("failed to serialize pubkey")
298 }
299
300 // Parity from prefix: 0x02 = even (0), 0x03 = odd (1)
301 parity := 0
302 if compressed[0] == 0x03 {
303 parity = 1
304 }
305
306 return compressed, parity, nil
307 }
308
309 // CreatePubkeyUncompressed derives an uncompressed public key (65 bytes) from a secret key.
310 func (l *LibSecp256k1) CreatePubkeyUncompressed(seckey32 []byte) ([]byte, error) {
311 l.mu.RLock()
312 defer l.mu.RUnlock()
313
314 if !l.loaded {
315 return nil, errors.New("library not loaded")
316 }
317 if len(seckey32) != 32 {
318 return nil, errors.New("secret key must be 32 bytes")
319 }
320
321 // Create pubkey using ec_pubkey_create
322 pubkey := make([]byte, 64) // secp256k1_pubkey internal format
323 if l.ecPubkeyCreate(l.ctx, &pubkey[0], &seckey32[0]) != 1 {
324 return nil, errors.New("failed to create pubkey")
325 }
326
327 // Serialize as uncompressed (65 bytes)
328 uncompressed := make([]byte, 65)
329 var outputLen uint = 65
330 const SECP256K1_EC_UNCOMPRESSED = 2
331 if l.ecPubkeySerialize(l.ctx, &uncompressed[0], &outputLen, &pubkey[0], SECP256K1_EC_UNCOMPRESSED) != 1 {
332 return nil, errors.New("failed to serialize pubkey")
333 }
334
335 return uncompressed, nil
336 }
337
338 // ECDH computes the shared secret using ECDH.
339 func (l *LibSecp256k1) ECDH(seckey32, pubkey33 []byte) ([]byte, error) {
340 l.mu.RLock()
341 defer l.mu.RUnlock()
342
343 if !l.loaded {
344 return nil, errors.New("library not loaded")
345 }
346 if len(seckey32) != 32 {
347 return nil, errors.New("secret key must be 32 bytes")
348 }
349 if len(pubkey33) != 33 && len(pubkey33) != 65 {
350 return nil, errors.New("public key must be 33 or 65 bytes")
351 }
352
353 // Parse pubkey
354 pubkey := make([]byte, 64) // secp256k1_pubkey is 64 bytes
355 if l.ecPubkeyParse(l.ctx, &pubkey[0], &pubkey33[0], uint(len(pubkey33))) != 1 {
356 return nil, errors.New("failed to parse public key")
357 }
358
359 // Compute ECDH
360 output := make([]byte, 32)
361 if l.ecdh(l.ctx, &output[0], &pubkey[0], &seckey32[0], 0, 0) != 1 {
362 return nil, errors.New("ECDH failed")
363 }
364
365 return output, nil
366 }
367
368 // ECDSASign signs a 32-byte message hash with a secret key.
369 // Returns a 64-byte compact signature (r || s).
370 func (l *LibSecp256k1) ECDSASign(msghash32, seckey32 []byte) ([]byte, error) {
371 l.mu.RLock()
372 defer l.mu.RUnlock()
373
374 if !l.loaded {
375 return nil, errors.New("library not loaded")
376 }
377 if len(msghash32) != 32 {
378 return nil, errors.New("message hash must be 32 bytes")
379 }
380 if len(seckey32) != 32 {
381 return nil, errors.New("secret key must be 32 bytes")
382 }
383
384 // Sign (internal signature format is 64 bytes)
385 sig := make([]byte, 64)
386 if l.ecdsaSign(l.ctx, &sig[0], &msghash32[0], &seckey32[0], 0, 0) != 1 {
387 return nil, errors.New("ECDSA signing failed")
388 }
389
390 // Normalize to low-S
391 l.ecdsaSignatureNormalize(l.ctx, &sig[0], &sig[0])
392
393 // Serialize to compact format
394 compact := make([]byte, 64)
395 if l.ecdsaSignatureSerializeCompact(l.ctx, &compact[0], &sig[0]) != 1 {
396 return nil, errors.New("failed to serialize signature")
397 }
398
399 return compact, nil
400 }
401
402 // ECDSAVerify verifies an ECDSA signature.
403 // sig64 is a 64-byte compact signature (r || s).
404 // msghash32 is a 32-byte message hash.
405 // pubkey33 is a 33-byte compressed public key (or 65-byte uncompressed).
406 func (l *LibSecp256k1) ECDSAVerify(sig64, msghash32, pubkey33 []byte) bool {
407 l.mu.RLock()
408 defer l.mu.RUnlock()
409
410 if !l.loaded {
411 return false
412 }
413 if len(sig64) != 64 || len(msghash32) != 32 {
414 return false
415 }
416 if len(pubkey33) != 33 && len(pubkey33) != 65 {
417 return false
418 }
419
420 // Parse compact signature
421 sig := make([]byte, 64)
422 if l.ecdsaSignatureParseCompact(l.ctx, &sig[0], &sig64[0]) != 1 {
423 return false
424 }
425
426 // Parse public key
427 pubkey := make([]byte, 64)
428 if l.ecPubkeyParse(l.ctx, &pubkey[0], &pubkey33[0], uint(len(pubkey33))) != 1 {
429 return false
430 }
431
432 return l.ecdsaVerify(l.ctx, &sig[0], &msghash32[0], &pubkey[0]) == 1
433 }
434
435 // ECDSASignDER signs and returns a DER-encoded signature.
436 func (l *LibSecp256k1) ECDSASignDER(msghash32, seckey32 []byte) ([]byte, error) {
437 l.mu.RLock()
438 defer l.mu.RUnlock()
439
440 if !l.loaded {
441 return nil, errors.New("library not loaded")
442 }
443 if len(msghash32) != 32 {
444 return nil, errors.New("message hash must be 32 bytes")
445 }
446 if len(seckey32) != 32 {
447 return nil, errors.New("secret key must be 32 bytes")
448 }
449
450 // Sign
451 sig := make([]byte, 64)
452 if l.ecdsaSign(l.ctx, &sig[0], &msghash32[0], &seckey32[0], 0, 0) != 1 {
453 return nil, errors.New("ECDSA signing failed")
454 }
455
456 // Normalize to low-S
457 l.ecdsaSignatureNormalize(l.ctx, &sig[0], &sig[0])
458
459 // Serialize to DER format (max 72 bytes)
460 der := make([]byte, 72)
461 var derLen uint = 72
462 if l.ecdsaSignatureSerializeDER(l.ctx, &der[0], &derLen, &sig[0]) != 1 {
463 return nil, errors.New("failed to serialize DER signature")
464 }
465
466 return der[:derLen], nil
467 }
468
469 // ECDSAVerifyDER verifies a DER-encoded ECDSA signature.
470 func (l *LibSecp256k1) ECDSAVerifyDER(sigDER, msghash32, pubkey33 []byte) bool {
471 l.mu.RLock()
472 defer l.mu.RUnlock()
473
474 if !l.loaded {
475 return false
476 }
477 if len(msghash32) != 32 {
478 return false
479 }
480 if len(pubkey33) != 33 && len(pubkey33) != 65 {
481 return false
482 }
483
484 // Parse DER signature
485 sig := make([]byte, 64)
486 if l.ecdsaSignatureParseDER(l.ctx, &sig[0], &sigDER[0], uint(len(sigDER))) != 1 {
487 return false
488 }
489
490 // Parse public key
491 pubkey := make([]byte, 64)
492 if l.ecPubkeyParse(l.ctx, &pubkey[0], &pubkey33[0], uint(len(pubkey33))) != 1 {
493 return false
494 }
495
496 return l.ecdsaVerify(l.ctx, &sig[0], &msghash32[0], &pubkey[0]) == 1
497 }
498
499 // ECDSASignRecoverable signs and returns a recoverable signature (65 bytes: 64 + recid).
500 func (l *LibSecp256k1) ECDSASignRecoverable(msghash32, seckey32 []byte) ([]byte, int, error) {
501 l.mu.RLock()
502 defer l.mu.RUnlock()
503
504 if !l.loaded {
505 return nil, 0, errors.New("library not loaded")
506 }
507 if len(msghash32) != 32 {
508 return nil, 0, errors.New("message hash must be 32 bytes")
509 }
510 if len(seckey32) != 32 {
511 return nil, 0, errors.New("secret key must be 32 bytes")
512 }
513
514 // Sign recoverable (internal format is 65 bytes)
515 sig := make([]byte, 65)
516 if l.ecdsaSignRecoverable(l.ctx, &sig[0], &msghash32[0], &seckey32[0], 0, 0) != 1 {
517 return nil, 0, errors.New("ECDSA recoverable signing failed")
518 }
519
520 // Serialize to compact format with recovery id
521 compact := make([]byte, 64)
522 var recid int
523 if l.ecdsaRecoverableSignatureSerializeCompact(l.ctx, &compact[0], &recid, &sig[0]) != 1 {
524 return nil, 0, errors.New("failed to serialize recoverable signature")
525 }
526
527 return compact, recid, nil
528 }
529
530 // ECDSARecover recovers a public key from a signature.
531 // sig64 is a 64-byte compact signature, recid is the recovery id (0-3).
532 // Returns a 33-byte compressed public key.
533 func (l *LibSecp256k1) ECDSARecover(sig64, msghash32 []byte, recid int) ([]byte, error) {
534 l.mu.RLock()
535 defer l.mu.RUnlock()
536
537 if !l.loaded {
538 return nil, errors.New("library not loaded")
539 }
540 if len(sig64) != 64 {
541 return nil, errors.New("signature must be 64 bytes")
542 }
543 if len(msghash32) != 32 {
544 return nil, errors.New("message hash must be 32 bytes")
545 }
546 if recid < 0 || recid > 3 {
547 return nil, errors.New("recovery id must be 0-3")
548 }
549
550 // Parse recoverable signature
551 sig := make([]byte, 65)
552 if l.ecdsaRecoverableSignatureParseCompact(l.ctx, &sig[0], &sig64[0], recid) != 1 {
553 return nil, errors.New("failed to parse recoverable signature")
554 }
555
556 // Recover public key
557 pubkey := make([]byte, 64)
558 if l.ecdsaRecover(l.ctx, &pubkey[0], &sig[0], &msghash32[0], recid) != 1 {
559 return nil, errors.New("public key recovery failed")
560 }
561
562 // Serialize as compressed
563 compressed := make([]byte, 33)
564 var outputLen uint = 33
565 const SECP256K1_EC_COMPRESSED = 258
566 if l.ecPubkeySerialize(l.ctx, &compressed[0], &outputLen, &pubkey[0], SECP256K1_EC_COMPRESSED) != 1 {
567 return nil, errors.New("failed to serialize recovered pubkey")
568 }
569
570 return compressed, nil
571 }
572