# p8k.mleku.dev Go bindings for libsecp256k1 without CGO. This package uses dynamic library loading via [purego](https://github.com/ebitengine/purego) to call C functions directly, eliminating the need for CGO. ## Features - **No CGO Required**: Uses dynamic library loading instead of CGO - **Complete API Coverage**: Bindings for all major libsecp256k1 functions - **Module Support**: ECDSA, Schnorr (BIP-340), ECDH, and Recovery modules - **Cross-Platform**: Works on Linux, macOS, and Windows - **Performance**: Direct C calls with minimal overhead ## Requirements **Linux AMD64**: A bundled libsecp256k1 is included for Linux AMD64 systems. No installation required! See [LIBRARY.md](LIBRARY.md) for details. **Other Platforms**: You must have libsecp256k1 installed on your system. The library can be built from source or installed via package managers. ### Installation **Ubuntu/Debian:** ```bash sudo apt-get install libsecp256k1-dev ``` **macOS (Homebrew):** ```bash brew install libsecp256k1 ``` **From Source:** ```bash git clone https://github.com/bitcoin-core/secp256k1 cd secp256k1 ./autogen.sh ./configure --enable-module-recovery --enable-module-schnorrsig --enable-module-ecdh make sudo make install ``` ## Installation ```bash go get p8k.mleku.dev ``` ## Benchmarks A comprehensive benchmark suite is included in the `/bench` directory that compares this implementation against: - **BTCEC** - The btcsuite implementation ([github.com/btcsuite/btcd/btcec](https://github.com/btcsuite/btcd/tree/master/btcec)) - **P256K1** - Pure Go implementation ([github.com/mleku/p256k1](https://github.com/mleku/p256k1)) The benchmarks cover: - Public key derivation - Schnorr signature creation (BIP-340) - Schnorr signature verification (BIP-340) - ECDH shared secret computation ### Running Benchmarks ```bash cd bench # Run all comparative benchmarks make bench-quick # Run comprehensive benchmarks with statistical analysis make bench-all # Run individual operation benchmarks make bench-pubkey # Public key derivation make bench-sign # Schnorr signing make bench-verify # Schnorr verification make bench-ecdh # ECDH key exchange ``` See the [bench/README.md](bench/README.md) for more details. ### Performance Results **P8K consistently outperforms pure Go implementations:** | Operation | BTCEC | P256K1 | **P8K** | Speedup | |-----------|-------|--------|---------|---------| | Schnorr Sign | 225,536 ns | 28,855 ns | **19,982 ns** | **11.3x faster** 🚀 | | Schnorr Verify | 153,205 ns | 133,235 ns | **36,541 ns** | **4.2x faster** ⚡ | | ECDH | 125,679 ns | 97,435 ns | **41,087 ns** | **3.1x faster** 💨 | | Pubkey Derivation | 32,226 ns | 28,098 ns | **19,329 ns** | **1.7x faster** ✨ | See [bench/BENCHMARK_RESULTS.md](bench/BENCHMARK_RESULTS.md) for detailed analysis. ## Usage ### Basic ECDSA Operations ```go package main import ( "crypto/rand" "crypto/sha256" "fmt" "log" "p8k.mleku.dev" ) func main() { // Create a context for signing and verification ctx, err := secp.NewContext(secp.ContextSign | secp.ContextVerify) if err != nil { log.Fatal(err) } defer ctx.Destroy() // Generate a private key (32 random bytes) privKey := make([]byte, 32) if _, err := rand.Read(privKey); err != nil { log.Fatal(err) } // Create public key from private key pubKey, err := ctx.CreatePublicKey(privKey) if err != nil { log.Fatal(err) } // Serialize public key (compressed) pubKeyBytes, err := ctx.SerializePublicKey(pubKey, true) if err != nil { log.Fatal(err) } fmt.Printf("Public key: %x\n", pubKeyBytes) // Sign a message message := []byte("Hello, libsecp256k1!") msgHash := sha256.Sum256(message) sig, err := ctx.Sign(msgHash[:], privKey) if err != nil { log.Fatal(err) } fmt.Printf("Signature: %x\n", sig) // Verify the signature valid, err := ctx.Verify(msgHash[:], sig, pubKey) if err != nil { log.Fatal(err) } fmt.Printf("Signature valid: %v\n", valid) } ``` ### Schnorr Signatures (BIP-340) ```go package main import ( "crypto/rand" "crypto/sha256" "fmt" "log" "p8k.mleku.dev" ) func main() { ctx, err := secp.NewContext(secp.ContextSign | secp.ContextVerify) if err != nil { log.Fatal(err) } defer ctx.Destroy() // Generate private key privKey := make([]byte, 32) if _, err := rand.Read(privKey); err != nil { log.Fatal(err) } // Create keypair for Schnorr keypair, err := ctx.CreateKeypair(privKey) if err != nil { log.Fatal(err) } // Extract x-only public key xonly, _, err := ctx.KeypairXOnlyPub(keypair) if err != nil { log.Fatal(err) } fmt.Printf("X-only public key: %x\n", xonly) // Sign with Schnorr message := []byte("Hello, Schnorr!") msgHash := sha256.Sum256(message) auxRand := make([]byte, 32) rand.Read(auxRand) sig, err := ctx.SchnorrSign(msgHash[:], keypair, auxRand) if err != nil { log.Fatal(err) } fmt.Printf("Schnorr signature: %x\n", sig) // Verify Schnorr signature valid, err := ctx.SchnorrVerify(sig, msgHash[:], xonly[:]) if err != nil { log.Fatal(err) } fmt.Printf("Schnorr signature valid: %v\n", valid) } ``` ### ECDH Key Exchange ```go package main import ( "crypto/rand" "fmt" "log" "p8k.mleku.dev" ) func main() { ctx, err := secp.NewContext(secp.ContextSign) if err != nil { log.Fatal(err) } defer ctx.Destroy() // Alice's keys alicePriv := make([]byte, 32) rand.Read(alicePriv) alicePub, _ := ctx.CreatePublicKey(alicePriv) // Bob's keys bobPriv := make([]byte, 32) rand.Read(bobPriv) bobPub, _ := ctx.CreatePublicKey(bobPriv) // Alice computes shared secret with Bob's public key aliceShared, err := ctx.ECDH(bobPub, alicePriv) if err != nil { log.Fatal(err) } // Bob computes shared secret with Alice's public key bobShared, err := ctx.ECDH(alicePub, bobPriv) if err != nil { log.Fatal(err) } fmt.Printf("Alice's shared secret: %x\n", aliceShared) fmt.Printf("Bob's shared secret: %x\n", bobShared) fmt.Printf("Secrets match: %v\n", string(aliceShared) == string(bobShared)) } ``` ### Public Key Recovery ```go package main import ( "crypto/rand" "crypto/sha256" "fmt" "log" "p8k.mleku.dev" ) func main() { ctx, err := secp.NewContext(secp.ContextSign | secp.ContextVerify) if err != nil { log.Fatal(err) } defer ctx.Destroy() // Generate keys privKey := make([]byte, 32) rand.Read(privKey) originalPubKey, _ := ctx.CreatePublicKey(privKey) // Sign with recovery message := []byte("Recover me!") msgHash := sha256.Sum256(message) recSig, err := ctx.SignRecoverable(msgHash[:], privKey) if err != nil { log.Fatal(err) } // Serialize to get recovery ID sigBytes, recID, err := ctx.SerializeRecoverableSignatureCompact(recSig) if err != nil { log.Fatal(err) } fmt.Printf("Signature: %x\n", sigBytes) fmt.Printf("Recovery ID: %d\n", recID) // Parse back parsedSig, err := ctx.ParseRecoverableSignatureCompact(sigBytes, recID) if err != nil { log.Fatal(err) } // Recover public key recoveredPubKey, err := ctx.Recover(parsedSig, msgHash[:]) if err != nil { log.Fatal(err) } // Serialize both for comparison origSer, _ := ctx.SerializePublicKey(originalPubKey, true) recSer, _ := ctx.SerializePublicKey(recoveredPubKey, true) fmt.Printf("Original public key: %x\n", origSer) fmt.Printf("Recovered public key: %x\n", recSer) fmt.Printf("Keys match: %v\n", string(origSer) == string(recSer)) } ``` ## API Reference ### Context Management - `NewContext(flags uint32) (*Context, error)` - Create a new secp256k1 context - `Context.Destroy()` - Destroy the context - `Context.Randomize(seed32 []byte) error` - Randomize the context with entropy ### Public Key Operations - `Context.CreatePublicKey(seckey []byte) ([]byte, error)` - Generate public key from private key - `Context.SerializePublicKey(pubkey []byte, compressed bool) ([]byte, error)` - Serialize public key - `Context.ParsePublicKey(input []byte) ([]byte, error)` - Parse serialized public key ### ECDSA Signatures - `Context.Sign(msg32 []byte, seckey []byte) ([]byte, error)` - Create ECDSA signature - `Context.Verify(msg32 []byte, sig []byte, pubkey []byte) (bool, error)` - Verify ECDSA signature - `Context.SerializeSignatureDER(sig []byte) ([]byte, error)` - Serialize signature to DER - `Context.ParseSignatureDER(input []byte) ([]byte, error)` - Parse DER signature - `Context.SerializeSignatureCompact(sig []byte) ([]byte, error)` - Serialize to compact format - `Context.ParseSignatureCompact(input64 []byte) ([]byte, error)` - Parse compact signature - `Context.NormalizeSignature(sig []byte) ([]byte, bool, error)` - Normalize to lower-S form ### Schnorr Signatures (BIP-340) - `Context.CreateKeypair(seckey []byte) (Keypair, error)` - Create keypair for Schnorr - `Context.KeypairXOnlyPub(keypair Keypair) (XOnlyPublicKey, int32, error)` - Extract x-only pubkey - `Context.SchnorrSign(msg32 []byte, keypair Keypair, auxRand32 []byte) ([]byte, error)` - Sign with Schnorr - `Context.SchnorrVerify(sig64 []byte, msg []byte, xonlyPubkey []byte) (bool, error)` - Verify Schnorr signature - `Context.ParseXOnlyPublicKey(input32 []byte) ([]byte, error)` - Parse x-only public key - `Context.SerializeXOnlyPublicKey(xonly []byte) ([]byte, error)` - Serialize x-only public key - `Context.XOnlyPublicKeyFromPublicKey(pubkey []byte) ([]byte, int32, error)` - Convert to x-only ### ECDH - `Context.ECDH(pubkey []byte, seckey []byte) ([]byte, error)` - Compute ECDH shared secret ### Recovery - `Context.SignRecoverable(msg32 []byte, seckey []byte) ([]byte, error)` - Create recoverable signature - `Context.SerializeRecoverableSignatureCompact(sig []byte) ([]byte, int32, error)` - Serialize recoverable sig - `Context.ParseRecoverableSignatureCompact(input64 []byte, recid int32) ([]byte, error)` - Parse recoverable sig - `Context.Recover(sig []byte, msg32 []byte) ([]byte, error)` - Recover public key from signature ## Constants ### Context Flags - `ContextNone` - No context flags - `ContextVerify` - Context for signature verification - `ContextSign` - Context for signature creation - `ContextDeclassify` - Context for declassification ### EC Flags - `ECCompressed` - Compressed public key format (33 bytes) - `ECUncompressed` - Uncompressed public key format (65 bytes) ### Size Constants - `PublicKeySize` - 64 bytes (internal format) - `CompressedPublicKeySize` - 33 bytes - `UncompressedPublicKeySize` - 65 bytes - `SignatureSize` - 64 bytes (internal format) - `CompactSignatureSize` - 64 bytes - `PrivateKeySize` - 32 bytes - `SharedSecretSize` - 32 bytes - `SchnorrSignatureSize` - 64 bytes - `RecoverableSignatureSize` - 65 bytes ## Performance Since this library uses direct C calls via dynamic loading, performance is nearly identical to using CGO, with the added benefit of not requiring a C compiler during builds. ## License MIT License - See LICENSE file for details ## Credits This package provides Go bindings to [libsecp256k1](https://github.com/bitcoin-core/secp256k1) by the Bitcoin Core developers. ## Contributing Contributions are welcome! Please open an issue or submit a pull request.