secp256k1 from Scratch — Learning Plan
Building Schnorr signatures on secp256k1 in Go, from first principles.
Goal: Understand the math deeply, not just copy formulas. End with a working (non-production) implementation compatible with Bitcoin Taproot and Nostr.
⚠️ Not for production use.
This implementation prioritizes clarity over security and performance:
Not constant-time. Uses Go's
big.Int, which has variable-time operations (branches and memory access patterns that depend on secret values). An attacker measuring timing over many signatures could potentially recover private keys. Production libraries use fixed-size arithmetic with no secret-dependent branches.~10x slower than btcec.
big.Intmeans heap allocations on every operation, generic modular reduction, and no assembly. Production libraries use stack-allocated fixed-width limbs, exploit secp256k1's special prime for fast reduction, and hand-tuned assembly.For hobby projects on your own hardware? Fine. For anything with real value at stake, use btcec or dcrd/secp256k1.
Progress
Part 1: Foundations
- [x] Modular arithmetic — clock math, remainders, finite sets
- [x] Modular division/inverses — finding multiplicative inverses
- [x] What is an elliptic curve — y² = x³ + 7, points as (x, y) pairs
Part 2: Curve Operations
- [x] Point addition — adding two points geometrically and algebraically
- [x] Point doubling — special case when adding a point to itself
- [x] The point at infinity — identity element (like zero for addition)
- [x] Scalar multiplication — multiplying a point by an integer (repeated addition)
Part 3: Key Pairs
- [x] Generator point G — the "starting point" everyone uses
- [x] Private key — just a random big number
- [x] Public key — private key × G (scalar multiplication)
- [x] Why it's hard to reverse — the discrete log problem
Part 4: Schnorr Signatures (BIP-340)
- [x] X-only public keys — 32 bytes, implicit even y
- [x] The signing algorithm — nonce, challenge, response
- [x] Why random nonce matters — reuse = leaked private key
- [x] The verification equation — checking without knowing the private key
- [x] Tagged hashes — domain separation for security
Part 5: Serialization
- [x] Bech32 encoding — human-readable format (npub, nsec)
- [x] Hex and bytes — raw formats
Files
| File | Description |
|---|---|
field.go |
Modular arithmetic (mod p) |
point.go |
Curve points and operations |
keys.go |
Private/public key generation |
schnorr.go |
Schnorr signing and verification |
bech32.go |
Bech32 encoding (npub/nsec) |
Compatibility
This implementation targets: - Bitcoin Taproot (BIP-340 Schnorr) - Nostr (NIP-01 uses BIP-340)
Compatibility Tests
Verified against btcec/v2 (the library used by most Go Bitcoin/Nostr projects):
go get github.com/btcsuite/btcd/btcec/v2
go test -tags=compat ./...
Tests confirm: - Key derivation produces identical public keys - Signatures created here verify with btcec - Signatures from btcec verify with our code - All 14 official BIP-340 test vectors pass
Resources
- BIP-340: Schnorr Signatures
- secp256k1 parameters: p, n, G coordinates
- Test vectors from BIP-340 for verification
Notes
Learning project — do not use for real money or keys.
