Back to Number Theory Dsa

Elliptic Curves & Cryptographic Foundations

September 22, 2026 Wasil Zafar 10 min read

Connect finite-field groups to modern cryptographic primitives.

Table of Contents

  1. Intuition, Vocabulary and Foundations
  2. Step-by-Step Derivations and Algorithms
  3. Implementation, Complexity and Engineering
  4. Applications, Case Studies and Exercises

Intuition, Vocabulary and Foundations

An elliptic curve over a prime field is a finite set of points satisfying $y^2 \equiv x^3+ax+b \pmod p$, together with a special identity point $\mathcal O$. Picture the points as stops on a circular route: the group law combines two stops to reach a third, while scalar multiplication repeats that operation efficiently. For example, on $E: y^2 \equiv x^3+2x+3 \pmod{97}$, the point $P=(3,6)$ lies on the curve because both sides are congruent to $36$.

The discriminant condition $4a^3+27b^2 \not\equiv 0 \pmod p$ rules out singular curves, where the addition formulas cease to define the intended group. RSA instead works in arithmetic modulo a composite, finite-field Diffie–Hellman works in a multiplicative subgroup, and Rabin uses modular squaring; their shared lesson is that security claims depend on the exact algebraic domain and threat model, not merely on using large numbers.

ConceptDefinition or notationConcrete exampleNon-example or boundaryQuestion answered
Prime-field curve$E: y^2 \equiv x^3+ax+b \pmod p$ with prime $p$$y^2 \equiv x^3+2x+3 \pmod{97}$Composite modulus without a specified ring or field modelWhich coordinate pairs are curve points?
Nonsingularity$4a^3+27b^2 \not\equiv 0 \pmod p$For $a=2,b=3,p=97$, the value is nonzeroA zero discriminant produces a singular cubicDoes the chord-and-tangent law form the intended group?
Identity and inverse$P+\mathcal O=P$ and $-(x,y)=(x,-y\bmod p)$$-(3,6)=(3,91)$ modulo $97$Treating $(0,0)$ as a universal identityHow do cancellation and zero scalar work?
Scalar multiplication$kP=P+\cdots+P$ for $k\ge 0$$2P=(80,10)$ for $P=(3,6)$Multiplying coordinates by $k$How is a public point derived from a secret scalar?
ECDHParties compare $a(bG)$ and $b(aG)$Both equal $(ab)G$Accepting an unvalidated peer pointHow can two parties derive the same group element?
RSAExponentiation modulo $N=pq$$c\equiv m^e\pmod N$Textbook RSA without paddingWhich trapdoor operation relies on factoring?
RabinSquaring modulo a composite$c\equiv m^2\pmod N$Assuming decryption has one rootWhy must encoding disambiguate multiple square roots?
Threat modelCapabilities and secrets an attacker may possessChosen inputs plus timing observations“The formula is correct, so the system is secure”What failures must an implementation resist?

Step-by-Step Derivations and Algorithms

For distinct points $P=(x_1,y_1)$ and $Q=(x_2,y_2)$ with $x_1\ne x_2$, compute $\lambda=(y_2-y_1)(x_2-x_1)^{-1}\pmod p$; for doubling, use $\lambda=(3x_1^2+a)(2y_1)^{-1}\pmod p$. Then $x_3\equiv\lambda^2-x_1-x_2$ and $y_3\equiv\lambda(x_1-x_3)-y_1\pmod p$. Vertical pairs add to $\mathcal O$, and doubling a point with $y=0$ also yields $\mathcal O$.

Double-and-add scans the bits of $k$, maintaining an accumulated result and a repeatedly doubled point, so it uses $O(\log k)$ additions instead of $k-1$. A ladder maintains two related multiples and performs one addition and one doubling per bit; its regular structure is useful for side-channel-resistant implementations, although constant operation counts alone do not make arbitrary high-level code constant-time.

Elliptic Curves & Cryptographic Foundations algorithm map
flowchart TD
    A["Validate field and curve"] --> B{"Input point valid?"}
    B -->|No| X["Reject input"]
    B -->|Yes| C{"Operation"}
    C -->|Add or double| D["Apply group-law cases"]
    C -->|Scalar multiply| E["Double-and-add or ladder"]
    D --> F["Verify result on curve"]
    E --> F
    F --> G{"Protocol use?"}
    G -->|Key exchange| H["Validate subgroup and derive key"]
    G -->|Signature| I["Use nonce-safe audited scheme"]

After processing any prefix of the scalar bits, scalar multiplication must preserve that its working points are known multiples of the original point; induction over the bit updates proves the result is $[k]P$. Both double-and-add and the ladder take $O(\log k)$ group operations and $O(1)$ auxiliary point storage, assuming field operations are unit cost.

Implementation, Complexity and Engineering

The reference implementation below uses affine coordinates because they expose the mathematics clearly. It validates the prime-field curve and every input and output point, but its modular inversions, branches, and Python integers are not constant-time. Real systems normally use projective coordinates, strict encodings, subgroup checks, secure randomness, approved protocols, and a maintained cryptographic library.

from dataclasses import dataclass
from typing import Optional, Tuple

Point = Optional[Tuple[int, int]]  # None represents the identity point.


@dataclass(frozen=True)
class Curve:
    p: int
    a: int
    b: int

    def __post_init__(self):
        if self.p <= 3:
            raise ValueError("this educational implementation requires p > 3")
        if (4 * self.a**3 + 27 * self.b**2) % self.p == 0:
            raise ValueError("singular curve")


def is_on_curve(curve: Curve, point: Point) -> bool:
    if point is None:
        return True
    x, y = point
    if not (0 <= x < curve.p and 0 <= y < curve.p):
        return False
    return (y * y - (x * x * x + curve.a * x + curve.b)) % curve.p == 0


def negate(curve: Curve, point: Point) -> Point:
    if point is None:
        return None
    if not is_on_curve(curve, point):
        raise ValueError("point is not on the curve")
    x, y = point
    return (x, (-y) % curve.p)


def add(curve: Curve, left: Point, right: Point) -> Point:
    if not is_on_curve(curve, left) or not is_on_curve(curve, right):
        raise ValueError("point is not on the curve")
    if left is None:
        return right
    if right is None:
        return left

    x1, y1 = left
    x2, y2 = right
    if x1 == x2 and (y1 + y2) % curve.p == 0:
        return None

    if left == right:
        numerator = (3 * x1 * x1 + curve.a) % curve.p
        denominator = (2 * y1) % curve.p
    else:
        numerator = (y2 - y1) % curve.p
        denominator = (x2 - x1) % curve.p

    slope = numerator * pow(denominator, -1, curve.p) % curve.p
    x3 = (slope * slope - x1 - x2) % curve.p
    y3 = (slope * (x1 - x3) - y1) % curve.p
    result = (x3, y3)
    assert is_on_curve(curve, result)
    return result


def scalar_multiply(curve: Curve, scalar: int, point: Point) -> Point:
    if scalar < 0:
        return scalar_multiply(curve, -scalar, negate(curve, point))
    if not is_on_curve(curve, point):
        raise ValueError("point is not on the curve")
    result = None
    addend = point
    while scalar:
        if scalar & 1:
            result = add(curve, result, addend)
        addend = add(curve, addend, addend)
        scalar >>= 1
    return result


def ladder(curve: Curve, scalar: int, point: Point) -> Point:
    if scalar < 0:
        raise ValueError("ladder scalar must be nonnegative")
    if not is_on_curve(curve, point):
        raise ValueError("point is not on the curve")
    r0, r1 = None, point
    for bit in bin(scalar)[2:]:
        if bit == "0":
            r1 = add(curve, r0, r1)
            r0 = add(curve, r0, r0)
        else:
            r0 = add(curve, r0, r1)
            r1 = add(curve, r1, r1)
    return r0


def point_order(curve: Curve, point: Point, limit: int) -> int:
    if point is None or limit < 1:
        raise ValueError("use a nonidentity point and positive limit")
    current = None
    for order in range(1, limit + 1):
        current = add(curve, current, point)
        if current is None:
            return order
    raise ValueError("order exceeds search limit")


curve = Curve(97, 2, 3)
P = (3, 6)
assert is_on_curve(curve, P)
assert add(curve, P, P) == (80, 10)
assert scalar_multiply(curve, 3, P) == (80, 87)
assert scalar_multiply(curve, 4, P) == (3, 91)
assert scalar_multiply(curve, 5, P) is None
assert ladder(curve, 4, P) == scalar_multiply(curve, 4, P)
assert point_order(curve, P, 20) == 5
alice_secret, bob_secret = 2, 3
alice_public = scalar_multiply(curve, alice_secret, P)
bob_public = scalar_multiply(curve, bob_secret, P)
assert scalar_multiply(curve, alice_secret, bob_public) == P
assert scalar_multiply(curve, bob_secret, alice_public) == P

try:
    add(curve, P, (1, 1))
    raise AssertionError("invalid point was accepted")
except ValueError:
    pass

print("all tests passed")
ChoiceCost modelNumeric or security boundaryLikely failureMitigation
Affine point additionOne inversion plus field multiplicationsInversion is expensive and input dependentSlow or variable-time behaviorUse it for teaching; use vetted projective formulas in production
Double-and-add$O(\log k)$ group operationsBranch pattern depends on scalar bitsTiming or power leakageUse an audited constant-time scalar-multiplication routine
Montgomery-style ladderOne add and one double per bitRegular control flow is not sufficient by itselfCompiler, memory, or field-operation leakageUse constant-time field arithmetic and verified implementations
RSA exponentiationRoughly $O(\log e)$ modular multiplications for a public exponentSecurity also depends on padding and key generationTextbook-RSA and fault attacksUse standardized authenticated padding and hardened libraries
ECDH key exchangeOne public and one secret scalar multiplication per partyPeer point and subgroup must be validInvalid-curve or small-subgroup attackDecode strictly, validate, clear cofactors as the standard requires, and use a KDF
Random noncesEntropy must match the scheme requirementsReuse or bias can reveal a signing keyCatastrophic key recoveryUse deterministic standardized signing or a secure system RNG
Property testingMany random identities plus fixed vectorsTests do not prove constant-time behaviorArithmetic corner cases survive examplesCompare with a trusted oracle and run specialized side-channel analysis

Applications, Case Studies and Exercises

Begin with the toy curve to see the group law directly, then contrast it with a protocol review where validation, encoding, entropy, side channels, authentication, and key derivation are part of correctness. The same mathematical operation can therefore be correct in a notebook and unsafe in a deployed service.

Practice

Apply Elliptic Curves & Cryptographic Foundations

Case study 1 — trace a tiny group. Use $E: y^2\equiv x^3+2x+3\pmod{97}$ and $P=(3,6)$. Verify membership, calculate $[2]P$, $[3]P$, $[4]P$, and $[5]P$, and compare each result with the code. Checkpoint: the sequence is $(80,10)$, $(80,87)$, $(3,91)$, then $\mathcal O$; success means every nonidentity result satisfies the curve equation.

Case study 2 — review a toy ECDH exchange. Let Alice choose $a=2$ and Bob choose $b=3$ using base point $P$. Compute $A=aP$, $B=bP$, and both shared values $aB$ and $bA$. Because $P$ has order $5$, both parties obtain $6P=P=(3,6)$. Then list why a real protocol must add parameter and point validation, subgroup handling, authenticated public keys, a KDF, secure randomness, transcript binding, and constant-time code.

Hands-on exercise. Add tests for $[0]P=\mathcal O$, $P+(-P)=\mathcal O$, $[m+n]P=[m]P+[n]P$, and ladder agreement for every $0\le m,n\le 20$. Next, deliberately pass an off-curve point and confirm rejection. Success means all identities pass, all produced points validate, and invalid input cannot reach scalar multiplication.

Tiered follow-up. Recall: define nonsingularity and the identity. Derive: prove the vertical-pair case. Implement: add strict point serialization and parsing. Debug: find why reducing coordinates silently can accept a noncanonical encoding. Extend: compare affine operation counts with Jacobian-coordinate counts, without treating the toy code as production cryptography.

Choose the group and algorithm only after defining the domain, validation rules, threat model, and workload; never equate mathematical correctness with protocol security. A useful next step is to connect these finite-field operations to standardized curve encodings, subgroup structure, authenticated key exchange, and formally tested constant-time implementations.

Next in the Series

In Number-Theoretic Data Structures & Problem Patterns, we'll explore Combine arithmetic invariants with query and dynamic-programming structures..