secp.go raw
1 // Package secp provides Go bindings to libsecp256k1 without CGO.
2 // It uses dynamic library loading via purego to call C functions directly.
3 package secp
4
5 import (
6 "fmt"
7 "runtime"
8 "sync"
9 "unsafe"
10
11 "github.com/ebitengine/purego"
12 )
13
14 // Constants for context flags
15 const (
16 ContextNone = 1
17 ContextVerify = 257 // 1 | (1 << 8)
18 ContextSign = 513 // 1 | (1 << 9)
19 ContextDeclassify = 1025 // 1 | (1 << 10)
20 )
21
22 // EC flags
23 const (
24 ECCompressed = 258 // SECP256K1_EC_COMPRESSED
25 ECUncompressed = 2 // SECP256K1_EC_UNCOMPRESSED
26 )
27
28 // Size constants
29 const (
30 PublicKeySize = 64
31 CompressedPublicKeySize = 33
32 UncompressedPublicKeySize = 65
33 SignatureSize = 64
34 CompactSignatureSize = 64
35 PrivateKeySize = 32
36 SharedSecretSize = 32
37 SchnorrSignatureSize = 64
38 RecoverableSignatureSize = 65
39 )
40
41 var (
42 libHandle uintptr
43 loadLibOnce sync.Once
44 loadLibErr error
45 )
46
47 // Function pointers
48 var (
49 contextCreate func(flags uint32) uintptr
50 contextDestroy func(ctx uintptr)
51 contextRandomize func(ctx uintptr, seed32 *byte) int32
52 ecPubkeyCreate func(ctx uintptr, pubkey *byte, seckey *byte) int32
53 ecPubkeySerialize func(ctx uintptr, output *byte, outputlen *uint64, pubkey *byte, flags uint32) int32
54 ecPubkeyParse func(ctx uintptr, pubkey *byte, input *byte, inputlen uint64) int32
55 ecdsaSign func(ctx uintptr, sig *byte, msg32 *byte, seckey *byte, noncefp uintptr, ndata uintptr) int32
56 ecdsaVerify func(ctx uintptr, sig *byte, msg32 *byte, pubkey *byte) int32
57 ecdsaSignatureSerializeDer func(ctx uintptr, output *byte, outputlen *uint64, sig *byte) int32
58 ecdsaSignatureParseDer func(ctx uintptr, sig *byte, input *byte, inputlen uint64) int32
59 ecdsaSignatureSerializeCompact func(ctx uintptr, output64 *byte, sig *byte) int32
60 ecdsaSignatureParseCompact func(ctx uintptr, sig *byte, input64 *byte) int32
61 ecdsaSignatureNormalize func(ctx uintptr, sigout *byte, sigin *byte) int32
62
63 // Schnorr functions
64 schnorrsigSign32 func(ctx uintptr, sig64 *byte, msg32 *byte, keypair *byte, auxrand32 *byte) int32
65 schnorrsigVerify func(ctx uintptr, sig64 *byte, msg32 *byte, msglen uint64, pubkey *byte) int32
66 keypairCreate func(ctx uintptr, keypair *byte, seckey *byte) int32
67 xonlyPubkeyParse func(ctx uintptr, pubkey *byte, input32 *byte) int32
68 xonlyPubkeySerialize func(ctx uintptr, output32 *byte, pubkey *byte) int32
69 keypairXonlyPub func(ctx uintptr, pubkey *byte, pkParity *int32, keypair *byte) int32
70
71 // ECDH functions
72 ecdh func(ctx uintptr, output *byte, pubkey *byte, seckey *byte, hashfp uintptr, data uintptr) int32
73
74 // Recovery functions
75 ecdsaRecoverableSignatureSerializeCompact func(ctx uintptr, output64 *byte, recid *int32, sig *byte) int32
76 ecdsaRecoverableSignatureParseCompact func(ctx uintptr, sig *byte, input64 *byte, recid int32) int32
77 ecdsaSignRecoverable func(ctx uintptr, sig *byte, msg32 *byte, seckey *byte, noncefp uintptr, ndata uintptr) int32
78 ecdsaRecover func(ctx uintptr, pubkey *byte, sig *byte, msg32 *byte) int32
79
80 // Extrakeys
81 xonlyPubkeyFromPubkey func(ctx uintptr, xonlyPubkey *byte, pkParity *int32, pubkey *byte) int32
82 )
83
84 // LoadLibrary loads the libsecp256k1 shared library
85 func LoadLibrary() (err error) {
86 loadLibOnce.Do(func() {
87 var libPath string
88
89 // Try to find the library
90 switch runtime.GOOS {
91 case "linux":
92 // Try common library paths
93 // For linux/amd64, try the bundled library first
94 paths := []string{
95 "./libsecp256k1.so", // Bundled in repo for linux amd64
96 "libsecp256k1.so.5",
97 "libsecp256k1.so.2",
98 "libsecp256k1.so.1",
99 "libsecp256k1.so.0",
100 "libsecp256k1.so",
101 "/usr/lib/libsecp256k1.so",
102 "/usr/local/lib/libsecp256k1.so",
103 "/usr/lib/x86_64-linux-gnu/libsecp256k1.so",
104 }
105 for _, p := range paths {
106 libHandle, err = purego.Dlopen(p, purego.RTLD_NOW|purego.RTLD_GLOBAL)
107 if err == nil {
108 libPath = p
109 break
110 }
111 }
112 case "darwin":
113 paths := []string{
114 "libsecp256k1.2.dylib",
115 "libsecp256k1.1.dylib",
116 "libsecp256k1.0.dylib",
117 "libsecp256k1.dylib",
118 "/usr/local/lib/libsecp256k1.dylib",
119 "/opt/homebrew/lib/libsecp256k1.dylib",
120 }
121 for _, p := range paths {
122 libHandle, err = purego.Dlopen(p, purego.RTLD_NOW|purego.RTLD_GLOBAL)
123 if err == nil {
124 libPath = p
125 break
126 }
127 }
128 case "windows":
129 paths := []string{
130 "libsecp256k1-2.dll",
131 "libsecp256k1-1.dll",
132 "libsecp256k1-0.dll",
133 "libsecp256k1.dll",
134 "secp256k1.dll",
135 }
136 for _, p := range paths {
137 libHandle, err = purego.Dlopen(p, purego.RTLD_NOW|purego.RTLD_GLOBAL)
138 if err == nil {
139 libPath = p
140 break
141 }
142 }
143 default:
144 err = fmt.Errorf("unsupported platform: %s", runtime.GOOS)
145 loadLibErr = err
146 return
147 }
148
149 if err != nil {
150 loadLibErr = fmt.Errorf("failed to load libsecp256k1: %w", err)
151 return
152 }
153
154 // Register symbols
155 if err = registerSymbols(); err != nil {
156 loadLibErr = fmt.Errorf("failed to register symbols from %s: %w", libPath, err)
157 return
158 }
159
160 loadLibErr = nil
161 })
162
163 return loadLibErr
164 }
165
166 // registerSymbols registers all C function symbols
167 func registerSymbols() (err error) {
168 // Core context functions
169 purego.RegisterLibFunc(&contextCreate, libHandle, "secp256k1_context_create")
170 purego.RegisterLibFunc(&contextDestroy, libHandle, "secp256k1_context_destroy")
171 purego.RegisterLibFunc(&contextRandomize, libHandle, "secp256k1_context_randomize")
172
173 // Public key functions
174 purego.RegisterLibFunc(&ecPubkeyCreate, libHandle, "secp256k1_ec_pubkey_create")
175 purego.RegisterLibFunc(&ecPubkeySerialize, libHandle, "secp256k1_ec_pubkey_serialize")
176 purego.RegisterLibFunc(&ecPubkeyParse, libHandle, "secp256k1_ec_pubkey_parse")
177
178 // ECDSA functions
179 purego.RegisterLibFunc(&ecdsaSign, libHandle, "secp256k1_ecdsa_sign")
180 purego.RegisterLibFunc(&ecdsaVerify, libHandle, "secp256k1_ecdsa_verify")
181 purego.RegisterLibFunc(&ecdsaSignatureSerializeDer, libHandle, "secp256k1_ecdsa_signature_serialize_der")
182 purego.RegisterLibFunc(&ecdsaSignatureParseDer, libHandle, "secp256k1_ecdsa_signature_parse_der")
183 purego.RegisterLibFunc(&ecdsaSignatureSerializeCompact, libHandle, "secp256k1_ecdsa_signature_serialize_compact")
184 purego.RegisterLibFunc(&ecdsaSignatureParseCompact, libHandle, "secp256k1_ecdsa_signature_parse_compact")
185 purego.RegisterLibFunc(&ecdsaSignatureNormalize, libHandle, "secp256k1_ecdsa_signature_normalize")
186
187 // Try to load optional modules - don't fail if they're not available
188
189 // Schnorr module
190 tryRegister(&schnorrsigSign32, "secp256k1_schnorrsig_sign32")
191 tryRegister(&schnorrsigVerify, "secp256k1_schnorrsig_verify")
192 tryRegister(&keypairCreate, "secp256k1_keypair_create")
193 tryRegister(&xonlyPubkeyParse, "secp256k1_xonly_pubkey_parse")
194 tryRegister(&xonlyPubkeySerialize, "secp256k1_xonly_pubkey_serialize")
195 tryRegister(&keypairXonlyPub, "secp256k1_keypair_xonly_pub")
196 tryRegister(&xonlyPubkeyFromPubkey, "secp256k1_xonly_pubkey_from_pubkey")
197
198 // ECDH module
199 tryRegister(&ecdh, "secp256k1_ecdh")
200
201 // Recovery module
202 tryRegister(&ecdsaRecoverableSignatureSerializeCompact, "secp256k1_ecdsa_recoverable_signature_serialize_compact")
203 tryRegister(&ecdsaRecoverableSignatureParseCompact, "secp256k1_ecdsa_recoverable_signature_parse_compact")
204 tryRegister(&ecdsaSignRecoverable, "secp256k1_ecdsa_sign_recoverable")
205 tryRegister(&ecdsaRecover, "secp256k1_ecdsa_recover")
206
207 return nil
208 }
209
210 // tryRegister attempts to register a symbol without failing if it doesn't exist
211 func tryRegister(fptr interface{}, symbol string) {
212 defer func() {
213 if r := recover(); r != nil {
214 // Symbol not found, ignore
215 }
216 }()
217 purego.RegisterLibFunc(fptr, libHandle, symbol)
218 }
219
220 // Context represents a secp256k1 context
221 type Context struct {
222 ctx uintptr
223 }
224
225 // NewContext creates a new secp256k1 context
226 func NewContext(flags uint32) (c *Context, err error) {
227 if err = LoadLibrary(); err != nil {
228 return
229 }
230
231 ctx := contextCreate(flags)
232 if ctx == 0 {
233 err = fmt.Errorf("failed to create context")
234 return
235 }
236
237 c = &Context{ctx: ctx}
238 runtime.SetFinalizer(c, (*Context).Destroy)
239
240 return
241 }
242
243 // Destroy destroys the context
244 func (c *Context) Destroy() {
245 if c.ctx != 0 {
246 contextDestroy(c.ctx)
247 c.ctx = 0
248 }
249 }
250
251 // Randomize randomizes the context with entropy
252 func (c *Context) Randomize(seed32 []byte) (err error) {
253 if len(seed32) != 32 {
254 err = fmt.Errorf("seed must be 32 bytes")
255 return
256 }
257
258 ret := contextRandomize(c.ctx, &seed32[0])
259 if ret != 1 {
260 err = fmt.Errorf("failed to randomize context")
261 return
262 }
263
264 return
265 }
266
267 // CreatePublicKey creates a public key from a private key
268 func (c *Context) CreatePublicKey(seckey []byte) (pubkey []byte, err error) {
269 if len(seckey) != PrivateKeySize {
270 err = fmt.Errorf("private key must be %d bytes", PrivateKeySize)
271 return
272 }
273
274 pubkey = make([]byte, PublicKeySize)
275 ret := ecPubkeyCreate(c.ctx, &pubkey[0], &seckey[0])
276 if ret != 1 {
277 err = fmt.Errorf("failed to create public key")
278 return
279 }
280
281 return
282 }
283
284 // SerializePublicKey serializes a public key
285 func (c *Context) SerializePublicKey(pubkey []byte, compressed bool) (output []byte, err error) {
286 if len(pubkey) != PublicKeySize {
287 err = fmt.Errorf("public key must be %d bytes", PublicKeySize)
288 return
289 }
290
291 var flags uint32
292 if compressed {
293 output = make([]byte, CompressedPublicKeySize)
294 flags = ECCompressed
295 } else {
296 output = make([]byte, UncompressedPublicKeySize)
297 flags = ECUncompressed
298 }
299
300 outputLen := uint64(len(output))
301 ret := ecPubkeySerialize(c.ctx, &output[0], &outputLen, &pubkey[0], flags)
302 if ret != 1 {
303 err = fmt.Errorf("failed to serialize public key")
304 return
305 }
306
307 output = output[:outputLen]
308 return
309 }
310
311 // ParsePublicKey parses a serialized public key
312 func (c *Context) ParsePublicKey(input []byte) (pubkey []byte, err error) {
313 pubkey = make([]byte, PublicKeySize)
314 ret := ecPubkeyParse(c.ctx, &pubkey[0], &input[0], uint64(len(input)))
315 if ret != 1 {
316 err = fmt.Errorf("failed to parse public key")
317 return
318 }
319
320 return
321 }
322
323 // Sign creates an ECDSA signature
324 func (c *Context) Sign(msg32 []byte, seckey []byte) (sig []byte, err error) {
325 if len(msg32) != 32 {
326 err = fmt.Errorf("message must be 32 bytes")
327 return
328 }
329 if len(seckey) != PrivateKeySize {
330 err = fmt.Errorf("private key must be %d bytes", PrivateKeySize)
331 return
332 }
333
334 sig = make([]byte, SignatureSize)
335 ret := ecdsaSign(c.ctx, &sig[0], &msg32[0], &seckey[0], 0, 0)
336 if ret != 1 {
337 err = fmt.Errorf("failed to sign message")
338 return
339 }
340
341 return
342 }
343
344 // Verify verifies an ECDSA signature
345 func (c *Context) Verify(msg32 []byte, sig []byte, pubkey []byte) (valid bool, err error) {
346 if len(msg32) != 32 {
347 err = fmt.Errorf("message must be 32 bytes")
348 return
349 }
350 if len(sig) != SignatureSize {
351 err = fmt.Errorf("signature must be %d bytes", SignatureSize)
352 return
353 }
354 if len(pubkey) != PublicKeySize {
355 err = fmt.Errorf("public key must be %d bytes", PublicKeySize)
356 return
357 }
358
359 ret := ecdsaVerify(c.ctx, &sig[0], &msg32[0], &pubkey[0])
360 valid = ret == 1
361
362 return
363 }
364
365 // SerializeSignatureDER serializes a signature in DER format
366 func (c *Context) SerializeSignatureDER(sig []byte) (output []byte, err error) {
367 if len(sig) != SignatureSize {
368 err = fmt.Errorf("signature must be %d bytes", SignatureSize)
369 return
370 }
371
372 output = make([]byte, 72) // Max DER signature size
373 outputLen := uint64(len(output))
374
375 ret := ecdsaSignatureSerializeDer(c.ctx, &output[0], &outputLen, &sig[0])
376 if ret != 1 {
377 err = fmt.Errorf("failed to serialize signature")
378 return
379 }
380
381 output = output[:outputLen]
382 return
383 }
384
385 // ParseSignatureDER parses a DER-encoded signature
386 func (c *Context) ParseSignatureDER(input []byte) (sig []byte, err error) {
387 sig = make([]byte, SignatureSize)
388 ret := ecdsaSignatureParseDer(c.ctx, &sig[0], &input[0], uint64(len(input)))
389 if ret != 1 {
390 err = fmt.Errorf("failed to parse DER signature")
391 return
392 }
393
394 return
395 }
396
397 // SerializeSignatureCompact serializes a signature in compact format (64 bytes)
398 func (c *Context) SerializeSignatureCompact(sig []byte) (output []byte, err error) {
399 if len(sig) != SignatureSize {
400 err = fmt.Errorf("signature must be %d bytes", SignatureSize)
401 return
402 }
403
404 output = make([]byte, CompactSignatureSize)
405 ret := ecdsaSignatureSerializeCompact(c.ctx, &output[0], &sig[0])
406 if ret != 1 {
407 err = fmt.Errorf("failed to serialize signature")
408 return
409 }
410
411 return
412 }
413
414 // ParseSignatureCompact parses a compact (64-byte) signature
415 func (c *Context) ParseSignatureCompact(input64 []byte) (sig []byte, err error) {
416 if len(input64) != CompactSignatureSize {
417 err = fmt.Errorf("compact signature must be %d bytes", CompactSignatureSize)
418 return
419 }
420
421 sig = make([]byte, SignatureSize)
422 ret := ecdsaSignatureParseCompact(c.ctx, &sig[0], &input64[0])
423 if ret != 1 {
424 err = fmt.Errorf("failed to parse compact signature")
425 return
426 }
427
428 return
429 }
430
431 // NormalizeSignature normalizes a signature to lower-S form
432 func (c *Context) NormalizeSignature(sig []byte) (normalized []byte, wasNormalized bool, err error) {
433 if len(sig) != SignatureSize {
434 err = fmt.Errorf("signature must be %d bytes", SignatureSize)
435 return
436 }
437
438 normalized = make([]byte, SignatureSize)
439 ret := ecdsaSignatureNormalize(c.ctx, &normalized[0], &sig[0])
440 wasNormalized = ret == 1
441
442 return
443 }
444
445 // Utility function to convert *byte to unsafe.Pointer
446 func bytesToPtr(b []byte) unsafe.Pointer {
447 if len(b) == 0 {
448 return nil
449 }
450 return unsafe.Pointer(&b[0])
451 }
452