aboutsummaryrefslogtreecommitdiffstats

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.Int means 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


Notes

Learning project — do not use for real money or keys.