Skip to content

Add security improvements#45

Open
rcmstark wants to merge 3 commits intomasterfrom
refactor/security
Open

Add security improvements#45
rcmstark wants to merge 3 commits intomasterfrom
refactor/security

Conversation

@rcmstark
Copy link
Copy Markdown
Member

@rcmstark rcmstark commented Apr 10, 2026

Summary

Security

  • Hedged RFC 6979 nonces (replaces pure deterministic k): RFC 6979 §3.6 extra entropy mixes fresh randomness into K-init, so signatures for the same key+message still differ while preserving RFC 6979's protection against RNG failure
  • Low-S signature normalization (BIP-62) and canonical DER enforcement on fromDer
  • Public key on-curve validation in verify and fromCompressed
  • Branch-balanced Montgomery ladder for variable-base scalar multiplication (mitigates simple branch-timing leaks; note: Python bignum ops are not constant-time per-operation, so this is not fully constant-time)
  • Hash truncation per SEC 1 §4.1.3 for hashes wider than the curve order (e.g. SHA-512 on secp256k1)
  • Tonelli–Shanks modular square root (supports all odd primes, not only p ≡ 3 mod 4)
  • Extended-Euclidean Math.inv: pure-Python implementation with a C-level pow(x, -1, n) fast path on CPython 3.8+
  • Range & validation checks: PrivateKey secret range, PEM regex-match error handling, recovery-byte range validation

Performance

  • Mixed affine+Jacobian addition fast path in _jacobianAdd (saves 4 mults per add when the second operand has z = 1)
  • Bit-by-bit NAF scalar multiplication for the generator backed by a precomputed affine [G, 2G, 4G, …, 2ⁿ·G] table — zero doublings on the sign hot path
  • Curve-specific doubling shortcuts (A = 0 for secp256k1, A = -3 for prime256v1)
  • Shamir's trick with Joint Sparse Form for the two-scalar verify path (halves non-zero digit density vs naive binary)
  • GLV endomorphism on secp256k1: splits each 256-bit scalar into two ~128-bit halves and runs a 4-scalar simultaneous multi-exponentiation — halves the verify loop length

Compatibility

  • Python 2.7+ and 3.x preserved via the pure-Python Math.inv fallback
  • Travis matrix: 2.7, 3.4–3.13, PyPy2 and PyPy3

Benchmarks

Apple Silicon, Python 3.14, 500 rounds on secp256k1 with SHA-256 and RFC 6979 k:

Library sign verify
python-ecdsa ~0.6ms ~2.4ms
fast-ecdsa ~1.0ms ~0.9ms
starkbank-ecdsa ~0.7ms ~1.7ms

Matches python-ecdsa on signing and is roughly 30% faster on verification. fast-ecdsa remains faster because it is backed by hand-tuned GMP assembly in C.

Test plan

  • All 74 tests pass under Python 2.7.18 and Python 3.14.4 (`python -m unittest discover -s tests`)
  • New `tests/testSecurity.py` covering hedged RFC 6979, low-S, on-curve validation, DER canonicalization, PEM parsing failures, recovery-byte checks
  • `benchmark.py` compares against python-ecdsa and fast-ecdsa

@rcmstark rcmstark force-pushed the refactor/security branch from 168cfb4 to 1cb23ed Compare April 17, 2026 19:58
Comment thread tests/testSecurity.py Fixed
Comment thread tests/testSecurity.py Fixed
Comment thread tests/testSecurity.py Fixed
Comment thread tests/testSecurity.py Fixed
Comment thread benchmark.py Fixed
- Hedged RFC 6979 nonce derivation with fresh entropy mixed into K-init
- Low-S signature normalization (BIP-62) and canonical DER enforcement
- Public key on-curve validation in verify and fromCompressed
- Branch-balanced Montgomery ladder for scalar multiplication
- Hash truncation per SEC 1 §4.1.3 for hashes wider than the curve order
- Tonelli-Shanks modular square root (supports all odd primes)
- Extended-Euclidean Math.inv with CPython 3.8+ pow(x,-1,n) fast path,
  falling back to pure-Python GCD so Python 2.7+ keeps working
- PrivateKey secret range check, PEM regex-match error handling,
  recovery-byte range validation
- New tests/testSecurity.py covering the above
- Pin Python version matrix via python_requires, keep 2.7/3.4-3.13/PyPy
  in the Travis matrix
- Mixed affine+Jacobian addition fast path in _jacobianAdd
- Bit-by-bit NAF scalar multiplication for the generator, backed by a
  precomputed affine [G, 2G, 4G, ..., 2^n*G] table — zero doublings on
  the sign hot path
- Curve-specific doubling shortcuts (A=0 for secp256k1, A=-3 for P-256)
- Shamir's trick with Joint Sparse Form for the two-scalar verify path
- GLV endomorphism on secp256k1: split each 256-bit scalar into two
  ~128-bit halves and run a 4-scalar multi-exponentiation, halving
  verify loop length
- New benchmark.py comparing against python-ecdsa and fast-ecdsa
- README: updated speed table, Python-version support line, and prose
  describing the new algorithmic stack
@rcmstark rcmstark force-pushed the refactor/security branch from 18aed58 to fc97ef0 Compare April 19, 2026 08:20
- Replace assertTrue(a <= b) with assertLessEqual(a, b) in testSecurity.py
  so low-s failure messages show both operands
- Document python2 invocation alongside python3 in README "Run unit tests"
  and "Run benchmark" sections to match the declared 2.7+ compat
@rcmstark rcmstark force-pushed the refactor/security branch from 45c71e8 to 6b37c09 Compare April 19, 2026 15:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant