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