414 lines
11 KiB
Markdown
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.
|
|
|