Files
next.orly.dev/pkg/crypto/p8k/IMPLEMENTATION.md
mleku e0a95ca1cd
Some checks failed
Go / build (push) Has been cancelled
Go / release (push) Has been cancelled
Refactor signer implementation to use p8k package
- Replaced all instances of p256k1signer with the new p8k.Signer across various modules, including event creation, policy handling, and database interactions.
- Updated related test cases and benchmarks to ensure compatibility with the new signer interface.
- Bumped version to v0.25.0 to reflect these significant changes and improvements in cryptographic operations.
2025-11-04 20:05:19 +00:00

6.5 KiB

P8K Signer Package Implementation

Overview

Created a new /p8k package that provides a unified secp256k1 signer interface with granular automatic fallback from C bindings to pure Go implementation.

Key Features

1. Granular Module Detection

The signer automatically detects which libsecp256k1 modules are available at runtime:

  • Core ECDSA: Always uses C if library loads
  • Schnorr (BIP-340): Uses C if Schnorr module available, otherwise pure Go fallback
  • ECDH: Uses C if ECDH module available, otherwise pure Go fallback
  • Recovery: Uses C if Recovery module available, otherwise pure Go fallback

2. Per-Function Fallback

Unlike all-or-nothing approaches, this implementation falls back on a per-function basis:

Library Available + Schnorr Missing:
  ✓ ECDSA operations → C bindings (fast)
  ✓ Public key generation → C bindings (fast)
  ✗ Schnorr operations → Pure Go p256k1 (reliable)
  ✓ ECDH operations → C bindings (fast)

3. Thread-Safe

All operations are protected with RWMutex for safe concurrent access.

4. Zero Configuration

No manual configuration needed - fallback happens automatically during initialization.

Package Structure

/p8k/
├── signer.go        # Main implementation with granular fallback
├── signer_test.go   # Comprehensive test suite
├── go.mod           # Module definition
└── README.md        # Package documentation

API

Initialization

signer, err := p8k.NewSigner()
defer signer.Close()

Status Checking

status := signer.GetModuleStatus()
// Returns: map[string]bool{
//   "library": true/false,
//   "schnorr": true/false,
//   "ecdh": true/false,
//   "recovery": true/false,
// }

isFullFallback := signer.IsUsingFallback()

Cryptographic Operations

// Public key derivation
pubkey, err := signer.GeneratePublicKey(privkey)

// Schnorr signatures (BIP-340)
sig, err := signer.SchnorrSign(msg32, privkey, auxrand)
valid, err := signer.SchnorrVerify(sig, msg32, xonlyPubkey)
xonly, err := signer.GetXOnlyPubkey(privkey)

// ECDSA signatures
sig, err := signer.Sign(msg, privkey)
valid, err := signer.Verify(msg, sig, pubkey)

// ECDH key exchange
secret, err := signer.ECDHSharedSecret(theirPubkey, myPrivkey)

Implementation Details

Module Detection Process

  1. Library Load: Attempts to load libsecp256k1 via purego
  2. Module Testing: If library loads, tests each optional module:
    • Creates test keys and attempts module-specific operations
    • Uses panic recovery to handle missing functions gracefully
    • Sets module availability flags
  3. Runtime Fallback: Each function checks relevant flags before calling C or Go

Fallback Strategy

func (s *Signer) SchnorrSign(...) {
    // Check if Schnorr module is available
    if !s.hasLibrary || !s.hasSchnorr {
        // Use pure Go p256k1
        return p256k1.SchnorrSign(...)
    }
    // Use C bindings
    return s.ctx.SchnorrSign(...)
}

Benchmarks

Extended the benchmark suite in /bench/bench_test.go to include Signer interface benchmarks:

New Benchmarks

  • BenchmarkSigner_PubkeyDerivation
  • BenchmarkSigner_SchnorrSign
  • BenchmarkSigner_SchnorrVerify
  • BenchmarkSigner_ECDH
  • BenchmarkSigner_ECDSASign
  • BenchmarkSigner_ECDSAVerify
  • BenchmarkSigner_ModuleDetection - Measures initialization overhead
  • BenchmarkSigner_GetModuleStatus - Measures status check overhead

Comparative Benchmarks

All comparative benchmarks now include the Signer interface:

  • BenchmarkComparative_PubkeyDerivation - BTCEC vs P256K1 vs P8K vs Signer
  • BenchmarkComparative_SchnorrSign - BTCEC vs P256K1 vs P8K vs Signer
  • BenchmarkComparative_SchnorrVerify - BTCEC vs P256K1 vs P8K vs Signer
  • BenchmarkComparative_ECDH - BTCEC vs P256K1 vs P8K vs Signer

Running Benchmarks

cd bench

# Run all Signer benchmarks
go test -bench=Signer -benchmem

# Run comparative benchmarks
go test -bench=Comparative -benchmem

# Run all benchmarks
go test -bench=. -benchmem

Use Cases

Scenario 1: Full C Performance

Library: ✓, Schnorr: ✓, ECDH: ✓
→ All operations use C bindings (maximum performance)

Scenario 2: Partial Modules (Most Interesting)

Library: ✓, Schnorr: ✗, ECDH: ✓
→ ECDSA and ECDH use C (fast)
→ Schnorr uses pure Go (reliable)
→ Mixed mode operation

Scenario 3: No Library Available

Library: ✗, Schnorr: ✗, ECDH: ✗
→ All operations use pure Go (guaranteed compatibility)

Testing

The test suite includes:

  • Module detection testing
  • Per-function fallback verification
  • Mixed-mode operation tests (C + Go simultaneously)
  • Schnorr sign/verify round-trips
  • ECDH shared secret agreement
  • ECDSA sign/verify round-trips

Run tests:

cd p8k
go test -v

Benefits

  1. Maximum Performance: Uses C when available
  2. Maximum Compatibility: Falls back to pure Go when needed
  3. Granular Control: Per-function fallback, not all-or-nothing
  4. Zero Config: Automatic detection and fallback
  5. Production Ready: Thread-safe, tested, documented

Integration

To use in your project:

import "next.orly.dev/pkg/crypto/p8k/p8k"

func main() {
    signer, err := p8k.NewSigner()
    if err != nil {
        log.Fatal(err)
    }
    defer signer.Close()
    
    // Check what's being used
    status := signer.GetModuleStatus()
    log.Printf("Using C Schnorr: %v", status["schnorr"])
    
    // Use it - same API regardless of backend
    sig, _ := signer.SchnorrSign(msg, privkey, auxrand)
}

Future Enhancements

Potential additions:

  • Metrics/telemetry for fallback usage
  • Configurable fallback behavior
  • Additional module support (MuSig, Taproot, etc.)
  • Benchmark results comparison tool
  • Performance regression testing

Files Modified/Created

Created

  • /p8k/signer.go - Main signer implementation (398 lines)
  • /p8k/signer_test.go - Test suite (187 lines)
  • /p8k/go.mod - Module definition
  • /p8k/README.md - Package documentation
  • /p8k/IMPLEMENTATION.md - This file

Modified

  • /bench/bench_test.go - Added Signer benchmarks and comparative tests
  • /bench/go.mod - Added p8k/p8k dependency

Performance Expectations

When Schnorr module is missing (most interesting case):

  • Public key derivation: C performance (~20μs)
  • ECDSA operations: C performance (~20-40μs)
  • ECDH: C performance (~40μs)
  • Schnorr sign: Pure Go (~30μs)
  • Schnorr verify: Pure Go (~130μs)

This gives you the best of both worlds - C performance where available, Go reliability everywhere.