Skip to main content
digital payment systems cryptography banking protocols and blockchain internals

Elliptic Curve Cryptography in Payment Networks

7 min read Chapter 2 of 21

Elliptic Curve Cryptography in Payment Networks

RSA has served payment systems for decades, but it’s hitting a wall. The key sizes required for adequate security keep growing: 1024-bit RSA was deprecated in 2013, 2048-bit is the current minimum, and post-quantum recommendations push toward 4096-bit or beyond. A 4096-bit RSA operation on a payment terminal’s embedded processor takes over 200ms — an eternity when the entire transaction must complete in under a second.

Elliptic Curve Cryptography (ECC) delivers equivalent security with dramatically smaller keys. A 256-bit ECC key provides the same security as a 3072-bit RSA key. This matters in payment contexts where keys are stored on constrained devices (chip cards, mobile secure elements) and operations must complete in milliseconds.

The Mathematics You Need (and Nothing More)

An elliptic curve over a finite field $\mathbb{F}_p$ is the set of points $(x, y)$ satisfying:

$$y^2 \equiv x^3 + ax + b \pmod{p}$$

plus a special “point at infinity” $\mathcal{O}$ that serves as the identity element. The security of ECC rests on the Elliptic Curve Discrete Logarithm Problem (ECDLP): given points $G$ (generator) and $Q = kG$ (where $k$ is a scalar), finding $k$ is computationally infeasible for sufficiently large curves.

In payment systems, this translates directly:

  • Private key: a random integer $k$ in range $[1, n-1]$ where $n$ is the curve order
  • Public key: the point $Q = kG$
  • Signing: prove knowledge of $k$ without revealing it
  • Verification: confirm the signature was created by someone who knows $k$

secp256k1 vs P-256: A Tale of Two Curves

Two curves dominate payment cryptography, and they were chosen for very different reasons:

P-256 (secp256r1) — The Banking Standard

P-256 is specified by NIST in FIPS 186-4 and used throughout traditional finance:

  • EMV contactless (newer specifications)
  • TLS connections between payment processors
  • FIDO2/WebAuthn for payment authentication
  • Apple Pay and Google Pay secure element operations

Its parameters were generated using a seed value through a process NIST described as “verifiably random.” The curve equation uses $a = -3$ (which enables a computational optimization) and a specific prime $p$ chosen for efficient modular arithmetic on common processors.

secp256k1 — The Blockchain Standard

Bitcoin’s secp256k1 uses $a = 0$ and $b = 7$, making the curve equation:

$$y^2 \equiv x^3 + 7 \pmod{p}$$

The parameters are not random — they were chosen from the Koblitz family of curves where the coefficients are small integers. This provides two advantages:

  1. No trust assumption: You don’t need to trust that NIST didn’t backdoor the seed
  2. Endomorphism speedup: The special structure enables a ~33% faster verification using the GLV (Gallant-Lambert-Vanstone) method
# Curve parameters side by side
SECP256K1 = {
    "p": 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F,
    "a": 0,
    "b": 7,
    "n": 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,
    "Gx": 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
    "Gy": 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8,
    "use": "Bitcoin, Ethereum, all EVM chains"
}

P256 = {
    "p": 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF,
    "a": -3,  # a = p - 3
    "b": 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B,
    "n": 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551,
    "Gx": 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296,
    "Gy": 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5,
    "use": "TLS, EMV contactless, Apple Pay, FIDO2"
}

ECDSA: Signing Payment Transactions

The Elliptic Curve Digital Signature Algorithm follows a specific sequence. Understanding each step reveals where things can go catastrophically wrong.

Signing Process

Given private key $d$, message hash $z = \text{SHA-256}(m)$:

  1. Generate a random nonce $k \in [1, n-1]$ (or derive deterministically per RFC 6979)
  2. Compute point $R = kG$, set $r = R_x \bmod n$
  3. Compute $s = k^{-1}(z + r \cdot d) \bmod n$
  4. Signature is $(r, s)$
from ecdsa import SigningKey, VerifyingKey, SECP256k1
from ecdsa.util import sigencode_der, sigdecode_der
import hashlib

class PaymentSigner:
    """
    ECDSA signer for payment transactions.
    Uses deterministic nonces (RFC 6979) to prevent the k-reuse attack.
    """
    
    def __init__(self, private_key_hex: str):
        self._sk = SigningKey.from_string(
            bytes.fromhex(private_key_hex),
            curve=SECP256k1
        )
        self._vk = self._sk.get_verifying_key()
    
    def sign_transaction(self, transaction_bytes: bytes) -> bytes:
        """
        Sign a serialized transaction.
        
        Uses SHA-256 as the hash function and deterministic k-value
        generation. The returned signature is DER-encoded, which is
        the format Bitcoin uses in scriptSig.
        """
        return self._sk.sign_deterministic(
            transaction_bytes,
            hashfunc=hashlib.sha256,
            sigencode=sigencode_der
        )
    
    def get_public_key_compressed(self) -> bytes:
        """
        Return the compressed public key (33 bytes).
        
        Compression stores only the x-coordinate plus a parity byte
        (0x02 or 0x03), since y can be recovered from the curve equation.
        This saves 32 bytes per public key — significant when every
        Bitcoin transaction includes a public key.
        """
        point = self._vk.pubkey.point
        prefix = b'\x02' if point.y() % 2 == 0 else b'\x03'
        return prefix + point.x().to_bytes(32, 'big')

def verify_payment_signature(
    public_key_compressed: bytes,
    signature_der: bytes,
    transaction_bytes: bytes
) -> bool:
    """
    Verify an ECDSA signature on a payment transaction.
    
    This operation is performed by every validating node in a
    blockchain network, and by the issuer's HSM in traditional
    card payment authentication.
    """
    # Decompress the public key
    prefix = public_key_compressed[0]
    x = int.from_bytes(public_key_compressed[1:], 'big')
    
    # Recover y from curve equation: y² = x³ + 7 (mod p)
    p = SECP256k1.curve.p()
    y_squared = (pow(x, 3, p) + 7) % p
    y = pow(y_squared, (p + 1) // 4, p)
    
    if (y % 2 == 0) != (prefix == 0x02):
        y = p - y
    
    from ecdsa import ellipticcurve, VerifyingKey
    point = ellipticcurve.Point(SECP256k1.curve, x, y)
    vk = VerifyingKey.from_public_point(point, curve=SECP256k1)
    
    try:
        return vk.verify(
            signature_der,
            transaction_bytes,
            hashfunc=hashlib.sha256,
            sigdecode=sigdecode_der
        )
    except Exception:
        return False

The k-Reuse Catastrophe

If two signatures $(r_1, s_1)$ and $(r_2, s_2)$ are produced with the same nonce $k$ but different messages, an attacker can recover the private key:

$$k = \frac{z_1 - z_2}{s_1 - s_2} \bmod n$$ $$d = \frac{s_1 \cdot k - z_1}{r_1} \bmod n$$

This isn’t theoretical. In 2013, Android’s SecureRandom had a bug that caused nonce reuse in Bitcoin wallet signing. Attackers drained wallets within hours. The lesson: always use RFC 6979 deterministic nonces in production payment systems. The nonce is derived from the private key and message, making reuse mathematically impossible.

ECDH Key Agreement in Payment Channels

Elliptic Curve Diffie-Hellman (ECDH) lets two parties derive a shared secret without transmitting it. In payment systems, this is used for:

  • Establishing encrypted channels between payment terminals and acquirers
  • Deriving per-session encryption keys in EMV contactless (protocol mode)
  • Creating shared secrets in Lightning Network payment channels
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF

def establish_payment_channel_keys(
    local_private_key: ec.EllipticCurvePrivateKey,
    remote_public_key: ec.EllipticCurvePublicKey
) -> dict:
    """
    Derive symmetric encryption keys from an ECDH shared secret.
    
    Uses HKDF (HMAC-based Key Derivation Function) to derive
    separate keys for encryption and MAC, preventing related-key
    attacks.
    """
    # ECDH: shared_secret = local_private * remote_public
    shared_secret = local_private_key.exchange(
        ec.ECDH(),
        remote_public_key
    )
    
    # Derive encryption key
    encryption_key = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        salt=None,
        info=b"payment-channel-encryption"
    ).derive(shared_secret)
    
    # Derive MAC key (separate derivation prevents related-key attacks)
    mac_key = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        salt=None,
        info=b"payment-channel-mac"
    ).derive(shared_secret)
    
    return {
        "encryption_key": encryption_key,
        "mac_key": mac_key,
    }

The security of ECDH in payment contexts depends on both parties properly validating that the received public key is a valid point on the curve. Without this check, an attacker can send a point on a different (weaker) curve — a small subgroup attack — and recover the private key through a series of carefully crafted exchanges. Production HSMs perform this validation automatically; if you’re implementing in software, validate the point explicitly.

Performance: Why Curve Choice Matters for Payment Latency

On a typical payment terminal processor (ARM Cortex-A53, 1.2 GHz):

OperationRSA-2048RSA-4096ECDSA P-256ECDSA secp256k1
Sign15ms100ms2ms2ms
Verify0.5ms1.5ms3ms2.2ms*
Key size256 bytes512 bytes32 bytes32 bytes

*secp256k1 verification benefits from the GLV endomorphism optimization.

RSA has faster verification than signing (because the public exponent is small), but ECC’s smaller key size and faster signing make it the clear winner for constrained payment devices. A chip card generating an ARQC signature needs fast signing; a terminal verifying a certificate chain needs fast verification. ECC delivers both within the sub-second latency budget that real-time payments demand.