Files
p8k/README.md

414 lines
11 KiB
Markdown

# 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.