Secp256k1 Security
Security considerations, attack vectors, and best practices for elliptic curve cryptography with secp256k1.Critical Warnings
⚠️ Zig Implementation NOT Audited The Zig implementation (src/crypto/secp256k1.zig) is:
- UNAUDITED - No security review
- NOT constant-time - Vulnerable to timing attacks
- Educational only - Do not use in production
- TypeScript implementation (@noble/curves) - audited
- Hardware wallets (Ledger, Trezor)
- EVM precompiles (ecRecover)
Private Key Security
Generation
Use cryptographically secure random: ✅ Correct:- Minimum 256 bits (32 bytes) of cryptographic randomness
- Use OS-provided CSPRNG (crypto.getRandomValues)
- Hardware RNG preferred (TPM, Secure Enclave)
- For offline: dice rolls + hashing (256 rolls * 2.58 bits = 660 bits)
Storage
⚠️ Protect keys at rest: Best practices:- Hardware wallets: Store in Ledger/Trezor, never export
- Encrypted keystores: AES-256-GCM with strong KDF (scrypt/argon2)
- Secure Enclave: iOS/macOS hardware-backed storage
- HSM: Enterprise hardware security modules
- Environment isolation: Air-gapped for high-value keys
- Plain text files
- Environment variables (leak in logs)
- Git repositories (permanent history)
- Clipboard (malware can read)
- Screenshots (OCR readable)
- Cloud storage unencrypted
- Browser localStorage unencrypted
Key Derivation
Use BIP32/BIP39 for backups:Nonce Security
RFC 6979 Deterministic Nonces
Why deterministic? Random nonce generation has catastrophic failure modes: ❌ Nonce reuse leaks private key:- PlayStation 3 hack (2010) - Sony reused k=4
- Bitcoin theft - Bad RNG in Android wallet (2013)
- Blockchain.info bug (2014) - Weak Java SecureRandom
- Same (message, key) always produces same nonce
- No RNG required
- Deterministic = testable
- HMAC_DRBG provides cryptographic strength
Implementation Requirements
All implementations MUST:- Use RFC 6979 deterministic nonces
- NEVER allow custom nonce input
- NEVER reuse nonces across different messages
- Validate nonce is in range [1, n-1]
Signature Malleability
Problem
ECDSA signatures have inherent malleability:- Transaction replay with modified txHash
- Smart contract vulnerabilities (signature-based authentication)
- Blockchain state inconsistency
Solution: Low-s Enforcement
Ethereum enforcess ≤ n/2 (BIP 62, EIP-2):
sign()always produces low-sverify()rejects high-s signaturesrecoverPublicKey()rejects high-s
Side-Channel Attacks
Timing Attacks
Non-constant-time implementations leak secrets via execution time: ❌ Vulnerable:- Use constant-time libraries (@noble/curves ✅)
- Avoid conditional branches on secrets
- Use hardware wallets (constant-time guaranteed)
- Avoid Zig implementation (⚠️ NOT constant-time)
Power Analysis
Differential Power Analysis (DPA):- Measure CPU power consumption during crypto ops
- Correlate power spikes with bit operations
- Recover secret keys after many measurements
- Single power trace reveals operation sequence
- Identify point additions vs doublings
- Reconstruct scalar multiplication pattern
- Hardware security (HSM, Secure Enclave)
- Power randomization
- Constant-time algorithms
- Blinding techniques
Cache Timing Attacks
Memory access patterns leak information via cache hits/misses: ❌ Vulnerable:- Flush+Reload on AES T-tables
- Prime+Probe on RSA/ECC
Message Hashing
Always Hash Before Signing
⚠️ Sign hashes, not raw messages: ❌ Vulnerable:- ECDSA security requires random-looking messages
- Raw messages may have structure attackers exploit
- Hashing provides collision resistance
- Fixed-length input simplifies validation
Hash Function Requirements
Use collision-resistant hash functions: ✅ Approved:- Keccak256 (Ethereum standard)
- SHA-256 (Bitcoin, general use)
- SHA-3 (NIST standard)
- Blake2b (high performance)
- MD5 (broken - collisions trivial)
- SHA-1 (broken - collisions practical)
Public Key Validation
Always Validate Public Keys
⚠️ Verify points are on curve:- Invalid curve attacks (point not on secp256k1)
- Small subgroup attacks
- Twist attacks (point on quadratic twist)
Check for Point at Infinity
Ethereum-Specific Considerations
EIP-155 Replay Protection
Problem: Signatures valid on one chain can be replayed on forks. Solution: Include chainId in v value:ecRecover Gotchas
Precompile behavior:- Returns zero address on invalid signature (NOT error)
- Always check return value != 0x0
EIP-191 Personal Sign
Prefix prevents signing raw transactions:Best Practices Summary
DO
✅ Use hardware wallets for high-value keys ✅ Use @noble/curves (audited) for TypeScript ✅ Generate keys with crypto.getRandomValues() ✅ Store encrypted keystores (AES-256-GCM + scrypt) ✅ Validate all inputs (keys, signatures, hashes) ✅ Use RFC 6979 deterministic nonces ✅ Enforce low-s malleability protection ✅ Hash messages before signing ✅ Include EIP-155 chainId in signatures ✅ Use BIP39/BIP32 for backups ✅ Test with official test vectorsDON’T
❌ Use Math.random() for key generation ❌ Reuse nonces across messages ❌ Store private keys unencrypted ❌ Sign raw messages without hashing ❌ Skip public key validation ❌ Use Zig implementation in production (unaudited) ❌ Implement custom crypto (use audited libraries) ❌ Trust user-provided public keys without validation ❌ Ignore signature malleability ❌ Forget EIP-155 replay protectionSecurity Checklist
- Keys generated with CSPRNG
- Keys stored encrypted or in hardware
- Using audited library (@noble/curves)
- RFC 6979 deterministic nonces
- Low-s enforcement enabled
- Public keys validated (point on curve)
- Messages hashed before signing
- EIP-155 chainId in signatures
- Test vectors passing
- No custom crypto implementation
- Side-channel mitigations in place
- ecRecover zero-address checks
Related
- Signing - ECDSA signing implementation
- Verification - Signature verification
- Test Vectors - Validation test cases
- HD Wallet - BIP32 key derivation
- Bip39 - Mnemonic seed phrases
- RFC 6979 - Deterministic ECDSA

