Skip to main content

secp256k1

zisklib functions for secp256k1 (K-256) base/scalar field arithmetic, curve operations, ECDSA verification and recovery, and BIP-340 Schnorr verification.

Overview

secp256k1 is the Koblitz short-Weierstrass curve y² = x³ + 7 defined over the prime field Fp with p = 2²⁵⁶ − 2³² − 977. The group has prime order n (cofactor 1), and is the curve used for ECDSA signing in Bitcoin and Ethereum and for BIP-340 Schnorr signatures in Taproot.

The page is organized bottom-up:

SectionProvidesDepends on
Base field (Fp)Arithmetic for every coordinate computation.
Scalar field (Fn)Arithmetic for signature components and exponents.
CurvePoint operations (add, double, scalar / multi-scalar).Fp
ECDSASignature verification and public-key recovery.Fn, Curve
SchnorrBIP-340 signature verification (single and batched).Fn, Curve

Base field (Fp)

The prime field Fp where curve coordinates live, with p = 2²⁵⁶ − 2³² − 977. Elements are 4 little-endian u64 limbs (256 bits). Every operation in the Curve section ultimately reduces to these Fp primitives, and sqrt_fp_secp256k1 is what makes lift_x_secp256k1 possible.

Reduces a 256-bit integer modulo the secp256k1 base field prime p.

pub fn reduce_fp_secp256k1(x: &[u64; 4]) -> [u64; 4]

Parameters

NameTypeDescription
x&[u64; 4]256-bit integer as 4 LE limbs.

Returns

TypeDescription
[u64; 4]x mod p in the secp256k1 base field.

Example

let reduced = zisklib::reduce_fp_secp256k1(&x_limbs);

Scalar field (Fn)

The prime field Fn of integers modulo the curve order n, where exponents and signature components live. Elements are 4 little-endian u64 limbs (256 bits). Scalar multiplications on the curve consume Fn elements, and ECDSA and Schnorr verification both rely on inv_fn and mul_fn to reconstruct the verification equation.

Reduces a 256-bit integer modulo the secp256k1 curve order n.

pub fn reduce_fn_secp256k1(x: &[u64; 4]) -> [u64; 4]

Parameters

NameTypeDescription
x&[u64; 4]256-bit integer as 4 LE limbs.

Returns

TypeDescription
[u64; 4]x mod n in the secp256k1 scalar field.

Example

let reduced = zisklib::reduce_fn_secp256k1(&scalar);

Curve

The group of Fp-rational points on y² = x³ + 7, including the point at infinity. Affine points are [u64; 8] (x ++ y); Jacobian points are [u64; 12] (X ++ Y ++ Z). Point arithmetic consumes Fp elements for coordinates and Fn elements for scalars; the double_scalar_mul_with_g and multi_scalar_mul primitives are the building blocks reused by the ECDSA and Schnorr sections.

Converts a non-infinity secp256k1 point from Jacobian (X, Y, Z) coordinates to affine (x, y).

warning

The input must not be the point at infinity (Z ≠ 0); the function asserts this in debug builds.

pub fn jacobian_to_affine_secp256k1(p: &[u64; 12]) -> [u64; 8]

Parameters

NameTypeDescription
p&[u64; 12]Jacobian point as 12 LE limbs: X (4) ++ Y (4) ++ Z (4). Must not be infinity.

Returns

TypeDescription
[u64; 8]Affine point (x, y) = (X/Z², Y/Z³) encoded as 4 LE limbs for x followed by 4 LE limbs for y.

Example

let affine = zisklib::jacobian_to_affine_secp256k1(&jacobian_point);

ECDSA

ECDSA signature verification and public-key recovery on secp256k1. Verification computes u₁·G + u₂·Q (handled by double_scalar_mul_with_g_secp256k1) and compares the resulting x-coordinate against r mod n; recovery lifts R from r and reconstructs the public key. Inputs are limbs: public keys are affine points, while r, s and z are Fn-sized 4-limb values.

Verifies a secp256k1 ECDSA signature (r, s) over message hash z using public key pk. The public key must lie on the curve.

pub fn ecdsa_verify_secp256k1(
pk: &[u64; 8],
z: &[u64; 4],
r: &[u64; 4],
s: &[u64; 4],
) -> bool

Parameters

NameTypeDescription
pk&[u64; 8]Public key as affine x ++ y, 4 LE limbs each.
z&[u64; 4]Message hash as 4 LE limbs.
r&[u64; 4]Signature component r as 4 LE limbs.
s&[u64; 4]Signature component s as 4 LE limbs.

Returns

TypeDescription
booltrue if the signature is valid, false if the public key is off-curve or the signature is invalid.

Example

let ok = zisklib::ecdsa_verify_secp256k1(&pk, &z, &r, &s);

Schnorr

BIP-340 Schnorr signature verification on secp256k1, the variant used by Bitcoin Taproot. Unlike the rest of the page, BIP-340 inputs are 32-byte big-endian byte arrays (x-only public keys and signature components) and messages are arbitrary-length byte slices. Internally the verifier still relies on the Curve multi-scalar primitives, and schnorr_batch_verify_secp256k1 collapses a batch into a single Pippenger MSM.

BIP-340 Schnorr signature verification on secp256k1. The message may have arbitrary length; the public key and signature components are 32-byte big-endian values.

pub fn schnorr_verify_secp256k1(
msg: &[u8],
pk_x: &[u8; 32],
sig_r: &[u8; 32],
sig_s: &[u8; 32],
) -> bool

Parameters

NameTypeDescription
msg&[u8]Message bytes (arbitrary length).
pk_x&[u8; 32]x-only public key (32 big-endian bytes).
sig_r&[u8; 32]Signature component r (32 big-endian bytes).
sig_s&[u8; 32]Signature component s (32 big-endian bytes).

Returns

TypeDescription
booltrue if the BIP-340 signature is valid, false otherwise.

Example

let ok = zisklib::schnorr_verify_secp256k1(
msg, &pk_x_bytes, &r_bytes, &s_bytes,
);