ECDSA over the short-Weierstrass curves y² = x³ + ax + b (mod p) that
Ethereum uses, in pure Elixir:
:secp256k1for ECRECOVER (0x01) and for transaction and EIP-7702 authorization signatures: public-key recovery (recover/5) and deterministic signing (sign/3).:p256(NIST P-256, secp256r1) for P256VERIFY (0x100, EIP-7951):verify/5.
Both curves share one implementation of
SEC 1 v2 §4.1. Points are computed in
Jacobian coordinates {X, Y, Z}, which stand for the affine point
(X / Z², Y / Z³), so adding and doubling need no field inversion; the
point at infinity is :infinity. Scalar multiplication recodes scalars in
width-w NAF, so about one bit in six or nine costs an addition. Each base
point gets a table of its odd multiples; the base point G's table is wide
and built at compile time. u₁·G + u₂·Q shares one chain of doublings
between both products (Shamir's trick). On secp256k1 the GLV endomorphism
splits each scalar into two halves of about 128 bits, which halves the
doublings.
Hashes and private keys are 32-byte big-endian binaries; r, s and
coordinates are integers.
This code is not constant-time. eevm verifies and recovers public
signatures; sign/3 serves tests, tooling and examples, not keys that
must resist timing side channels.
Summary
Functions
Returns the order n of curve's base point.
Returns the public key d·G of the 32-byte private key d, or
{:error, :invalid_private_key} unless 0 < d < n.
Recovers the public key that produced the signature (r, s) over the
32-byte hash (SEC 1 §4.1.6).
Signs the 32-byte hash with the 32-byte private_key.
Verifies the signature (r, s) over the 32-byte hash against
public_key (SEC 1 §4.1.4).
Types
@type curve() :: :secp256k1 | :p256
A supported curve.
@type point() :: {non_neg_integer(), non_neg_integer()}
An affine point {x, y}.
Functions
@spec order(curve()) :: pos_integer()
Returns the order n of curve's base point.
Example
iex> EEVM.Crypto.ECDSA.order(:secp256k1)
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
Returns the public key d·G of the 32-byte private key d, or
{:error, :invalid_private_key} unless 0 < d < n.
Example
iex> {:ok, {x, _y}} = EEVM.Crypto.ECDSA.public_key(:secp256k1, <<1::256>>)
iex> x == 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
true
@spec recover(curve(), <<_::256>>, non_neg_integer(), non_neg_integer(), 0..3) :: {:ok, point()} | {:error, :invalid_signature}
Recovers the public key that produced the signature (r, s) over the
32-byte hash (SEC 1 §4.1.6).
recovery_id picks the ephemeral point R: bit 0 is the parity of its
y, and bit 1 says its x is r + n rather than r. Any s in
[1, n) is accepted; enforcing EIP-2's low s is the caller's job.
Returns {:error, :invalid_signature} when r or s is out of range,
no curve point has that x, or the result is the point at infinity.
@spec sign(curve(), <<_::256>>, <<_::256>>) :: {:ok, {pos_integer(), pos_integer(), 0..3}} | {:error, :invalid_private_key}
Signs the 32-byte hash with the 32-byte private_key.
The nonce is deterministic (RFC 6979 with HMAC-SHA256), and s is
normalised to the low half of [1, n) (EIP-2), flipping the recovery id
to match, as libsecp256k1 does. Returns {:ok, {r, s, recovery_id}}, or
{:error, :invalid_private_key} unless 0 < d < n.
@spec verify(curve(), <<_::256>>, non_neg_integer(), non_neg_integer(), point()) :: boolean()
Verifies the signature (r, s) over the 32-byte hash against
public_key (SEC 1 §4.1.4).
False when r or s is outside (0, n), public_key is not on the
curve (which rules out (0, 0)), or R' = u₁·G + u₂·Q is the point at
infinity; otherwise true when R'.x mod n == r. This matches EIP-7951.