Skip to main content

Tiny ECDH in Python

⚠️ Educational only — do not use in production

This package exists to demonstrate how Elliptic-Curve Diffie–Hellman is put together. It is not production cryptography and must never be shipped as such:

  • ~80-bit curve strength. The curve is sect163r2 (NIST B-163), far below the 112-bit floor any current guidance requires.
  • Deprecated curve. NIST SP 800-186 keeps the binary-field curves in the specification but marks them deprecated for new use; sect163r2 is still specified in SEC 2 v2.0 (§3.2.3).
  • Timing side channels. The pure-Python field arithmetic cannot be made timing-uniform; secret-dependent timing is present and documented, not fixed.
  • No zeroisation. Secret material is never wiped from memory.
  • No independent review. No audit, no formal analysis.

For real key agreement use an audited library: cryptography (cryptography.hazmat.primitives.asymmetric.x25519) or PyNaCl.

What this is

A Python port of kokke/tiny-ECDH-c, a small implementation of the Elliptic-Curve Diffie–Hellman key agreement algorithm. The code follows the C original closely rather than being idiomatic Python, and it is much slower than the C version.

Elliptic-curve Diffie–Hellman (ECDH) is an anonymous key agreement protocol that allows two parties, each having an elliptic-curve public–private key pair, to establish a shared secret over an insecure channel.

The curve

The implemented curve is sect163r2, also published as NIST B-163: a pseudo-random (not Koblitz) curve over the binary field GF(2^163). Its parameters are taken from SEC 2: Recommended Elliptic Curve Domain Parameters, Version 1.0 (§3.4.3), consistent with FIPS 186-4 Appendix D. They are pinned by tests/test_curve_parameters.py. SEC 2 v2.0 (§3.2.3) still specifies the same curve.

The curve offers roughly 80 bits of security, below the 112-bit minimum of current guidance. NIST SP 800-186 still specifies the binary-field curves but marks them deprecated for new use.

Attribution and licence

This is a derivative work of kokke/tiny-ECDH-c, which is released into the public domain under the Unlicense. Public-domain dedication imposes no conditions, so redistributing this port under the MIT LICENSE is compatible. Curve parameters are from SEC 2 / FIPS 186-4 as cited above.

Suggested reading order

The package is meant to be read. A useful path through it:

  1. tiny_ecdh/utils.py — the curve definition and GF(2^m) field and point arithmetic (gf2field_*, gf2point_*), including the branch-free point operations.
  2. tiny_ecdh/keys.py — the PrivateKey, PublicKey and SharedSecret types and the validation a peer key must pass.
  3. tiny_ecdh/entropy.py — how the private scalar is drawn from OS entropy.
  4. tiny_ecdh/ecdh.py — the two entry points, ecdh_generate_keys and ecdh_shared_secret, tied together from the pieces above.
  5. tiny_ecdh/kdf.py — turning the shared point's x-coordinate into a usable key.
  6. examples/ecdh_example.py — a full exchange end to end.
  7. tests/ — known-answer vectors and property tests, sourced independently of this implementation.

Usage

Install with pip install tiny-ecdh-python and import from tiny_ecdh. Releases are pre-1.0 and pre-release: the interface and the shared-secret format are not stable and may change without a deprecation period. See CHANGELOG.md for what changed in each version and RELEASING.md for how releases are built and published.

from tiny_ecdh import ecdh_generate_keys, ecdh_shared_secret

The point that comes out of the exchange is not a key: its bits carry the algebraic structure of the curve equation, not uniform randomness. Always derive a key from it, as below.

  1. Alice and Bob each generate a key pair. The private scalar is drawn from the operating system's entropy source; the result is typed PrivateKey / PublicKey.
alice_priv, alice_pub = ecdh_generate_keys()
bob_priv, bob_pub = ecdh_generate_keys()
  1. They exchange public keys over the insecure channel.

  2. Each side computes the shared point and derives a key from it, bound to a context string that names what the key is for:

from tiny_ecdh import constant_time_compare

context = b"my-app: session key v1"

# raises InvalidPublicKeyError if the peer key fails validation
alice_key = ecdh_shared_secret(alice_priv, bob_pub).derive_key(context)
bob_key = ecdh_shared_secret(bob_priv, alice_pub).derive_key(context)
  1. Both sides now hold the same fixed-length derived key. Compare derived keys in constant time, never with ==:
assert constant_time_compare(alice_key, bob_key)

A different context on the same key pair yields a completely different key, so one pair can serve independent purposes without them sharing key material. The shared point's x-coordinate is available as SharedSecret.raw_x for comparison against the C original; it is deliberately named so that reading it instead of calling derive_key is a visible decision, not the normal path.

Deriving the key does not authenticate who you share it with: ECDH here is unauthenticated, so a peer key could belong to an impersonator. Verify the peer's public key and the handshake transcript through some other channel (a signature, a certificate, an out-of-band fingerprint) before trusting a derived key.

Timing limitations

Scalar multiplication always runs the same fixed number of double-and-add-always iterations, and each iteration performs the same sequence of point operations regardless of the scalar: point doubling and addition use branch-free variants that always compute every case and select the result arithmetically. So the implementation no longer leaks the scalar's bit length through the iteration count, or the position of coincidences between the running total and the added point through which case is taken. This is a fixed schedule of point operations, not constant-time execution, and the package must not be described that way — several point operations are still data-dependent underneath:

  • Field inversion (gf2field_inv) is variable-time extended-Euclid; its running time depends on the element being inverted, and it runs three times per iteration.
  • Field multiplication (gf2field_mul) loops once per bit of one operand and branches on each bit, so both its iteration count and branch choice depend on an operand derived from the secret scalar.
  • Python's arbitrary-precision integers do not run in fixed time for a fixed bit width. This cannot be closed from pure Python.

See SECURITY.md for the full threat model and the list of known, accepted weaknesses.

Development checks

Run the complete local gate with:

python -m pip install tox
python -m tox

This runs the test suite on Python 3.9–3.12 and, in the quality environment, checks linting, formatting, the source and wheel builds, and artifact metadata.

Metadata

Release files for tiny-ecdh-python 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tiny-ecdh-python 0.1.0
File Size Uploaded
tiny_ecdh_python-0.1.0.tar.gz 28.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tiny-ecdh-python 0.1.0
File Interpreter ABI Platform
tiny_ecdh_python-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 44.3 kB

Release files / tiny_ecdh_python-0.1.0.tar.gz

Download URL tiny_ecdh_python-0.1.0.tar.gz
Size 28.3 kB
Tags Source
SHA-256 checksum
How to use checksums
7ecdd4ef66907b43c849d473eb85e32b5fe5c705ae998cf9a08308d59db1b786
BLAKE2b-256 checksum
How to use checksums
606c841a9a37f0a11f57a051aeea4fbcb4b812c4295871af7a804012de40bece
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release files / tiny_ecdh_python-0.1.0-py3-none-any.whl

Download URL tiny_ecdh_python-0.1.0-py3-none-any.whl
Size 16.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ef043e8322b40fff35ec44e7695d4dc218d113403dca613506171f35371d7ebc
BLAKE2b-256 checksum
How to use checksums
083f302b9655c12c7689c63d41189aaf1d2726f637cfcaceb9f0a0ac957c2e55
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page