Crate secp

source ·
Expand description

§secp

A flexible and secure secp256k1 elliptic curve math library, with constant-time support, and superb ergonomics.

secp takes full advantage of Rust’s std::ops traits to make elliptic curve cryptography code easy to read, easy to write, succinct, readable, and secure.

§Example

Here’s an implementation of simple Schnorr signatures using the secp crate.

use secp::{MaybeScalar, Point, Scalar};
use sha2::{Digest, Sha256};

fn compute_challenge(nonce_point: &Point, pubkey: &Point, msg: &[u8]) -> MaybeScalar {
    let hash: [u8; 32] = Sha256::new()
        .chain_update(&nonce_point.serialize())
        .chain_update(&pubkey.serialize())
        .chain_update(msg)
        .finalize()
        .into();
    MaybeScalar::reduce_from(&hash)
}

fn random_scalar() -> Scalar {
    // In an actual implementation this would produce a scalar value
    // sampled from a CSPRNG.
    Scalar::two()
}

fn schnorr_sign(secret_key: Scalar, message: &[u8]) -> (Point, MaybeScalar) {
    let nonce = random_scalar();
    let nonce_point = nonce.base_point_mul();
    let pubkey = secret_key.base_point_mul();

    let e = compute_challenge(&nonce_point, &pubkey, message);
    let s = nonce + secret_key * e;
    (nonce_point, s)
}

fn schnorr_verify(public_key: Point, signature: (Point, MaybeScalar), message: &[u8]) -> bool {
    let (r, s) = signature;
    let e = compute_challenge(&r, &public_key, message);
    s.base_point_mul() == r + e * public_key
}

let secret_key: Scalar = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
    .parse()
    .unwrap();
let public_key = secret_key.base_point_mul();

let message = b"I am the dragon!";

let signature = schnorr_sign(secret_key, message);
assert!(schnorr_verify(public_key, signature, message));

§Choice of Backbone

This crate does not implement elliptic curve point math directly. Instead we depend on one of two reputable elliptic curve cryptography libraries:

One or the other can be used. By default, this crate prefers to rely on libsecp256k1, as this is the most vetted and publicly trusted implementation of secp256k1 curve math available anywhere. However, if you need a pure-rust implementation, you can install this crate without it, and use the pure-rust k256 crate instead.

cargo add secp --no-default-features --features k256

If both k256 and secp256k1 features are enabled, then we default to using libsecp256k1 bindings for the actual math, but still provide trait implementations to make this crate interoperable with k256.

§Documentation

To see the API documentation, head on over to docs.rs.

§Features

FeatureDescriptionDependenciesEnabled by Default
secp256k1Use libsecp256k1 bindings for elliptic curve math. Include trait implementations for converting to and from types in the secp256k1 crate. This feature supercedes the k256 feature if that one is enabled.secp256k1
k256Use the k256 crate for elliptic curve math. This enables a pure-rust build. Include trait implementations for types from k256. If the secp256k1 feature is enabled, then k256 will still be brought in and trait implementations will be included, but the actual curve math will be done by libsecp256k1.k256
serdeImplement serialization and deserialization for types in this crate.serde
randEnable support for random scalar sampling with a CSPRNG, via the rand craterand
secp256k1-invertlibsecp256k1 doesn’t expose any functionality to invert scalars modulo the curve order (i.e. to compute t-1 for some scalar t, so that t(t-1) = 1 mod n). Inversion is useful for certain cryptographic operations, such as ECDSA signing, or OPRFs.

Enable this feature if you need to invert scalars but you only have the secp256k1 feature enabled. This feature is only useful if the secp256k1 feature is enabled but k256 is not, as the k256 crate provides scalar inversion methods. This feature pulls in the crypto-bigint crate to perform the inversion.
crypto_bigint
num-traitsEnable support for numeric identity traits via the num-traits crate.num_traits

§Usage

The secp crate’s primary export is four types which can be used to represent elliptic curve points (e.g. public keys) and scalars (e.g. private keys).

Depending on which features of this crate are enabled, we implement various conversion traits between these types and higher-level types such as secp256k1::PublicKey or k256::SecretKey.

let seckey = secp256k1::SecretKey::new(&mut rand::rngs::OsRng);
let scalar = secp::Scalar::from(seckey);
secp256k1::SecretKey::from(scalar);
secp256k1::Scalar::from(scalar);

let point: secp::Point = scalar.base_point_mul();
secp256k1::PublicKey::from(point);

let seckey = k256::SecretKey::random(&mut rand::rngs::OsRng);
let scalar = secp::Scalar::from(seckey);
k256::SecretKey::from(scalar);
k256::Scalar::from(scalar);
k256::NonZeroScalar::from(scalar);
k256::Scalar::from(secp::MaybeScalar::Valid(scalar));
assert!(k256::NonZeroScalar::try_from(secp::MaybeScalar::Valid(scalar)).is_ok());
assert!(k256::NonZeroScalar::try_from(secp::MaybeScalar::Zero).is_err());

let point: secp::Point = scalar.base_point_mul();
k256::PublicKey::from(point);
k256::AffinePoint::from(point);

§Scalars

A Scalar can represent any integers in the range [1, n), while a MaybeScalar represents any integer in the range [0, n), where n is the secp256k1 elliptic curve order (the number of possible points on the curve). As Scalar is never zero it doesn’t implement Default). MaybeScalar::Zero represents the integer zero.

pub enum MaybeScalar {
    Zero,
    Valid(Scalar),
}

§Arithmetic

Addition, subtract, and multiplication operators are supported by default between the two scalar types. All operations are done in the finite field modulo n.

use secp::{MaybeScalar, Scalar};

assert_eq!(
    (Scalar::one() + Scalar::two()) * Scalar::max(),
    Scalar::max() - Scalar::two()
);

// Addition or subtraction of two non-zero [`Scalar`] instances will
// output a [`MaybeScalar`], since the sum of two non-zero numbers
// could be zero in a finite field.
assert_eq!(Scalar::one() + Scalar::one(), MaybeScalar::two());

// Arithmetic works across commutatively both scalar types.
assert_eq!(
    MaybeScalar::from(20) * Scalar::two() - Scalar::try_from(10).unwrap(),
    MaybeScalar::from(30)
);

// Zero acts like zero.
assert_eq!(MaybeScalar::Zero + Scalar::two(), MaybeScalar::two());
assert_eq!(MaybeScalar::Zero * Scalar::two(), MaybeScalar::Zero);

Division is supported via modular multiplicative inversion. Since libsecp256k1 does not support this out of the box, scalar inversion requires either the k256 feature or the secp256k1-invert feature to be enabled.

let x = "0000000000000000000000000000000000000000000000000000000000000aae"
    .parse::<Scalar>()
    .unwrap();

assert_eq!(
    x / Scalar::two(),
    "0000000000000000000000000000000000000000000000000000000000000557"
        .parse()
        .unwrap()
);

// Since `0xAAF` is an odd number, this would be a fraction if we were
// operating in the real numbers. Since we're operating in a finite field,
// there does exist an integer solution to the equation `x * 2 = 0xAAF`
assert_eq!(
    (x + Scalar::one()) / Scalar::two(),
    "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b25f8"
        .parse()
        .unwrap()
);

Division by a MaybeScalar is not defined, since the divisor might be zero.

Scalar::two() / MaybeScalar::two();

§Formatting

To reduce the risk of accidental exposure of private keys, signatures, or other secret scalar values, Scalar does not implement Display.

println!("{}", Scalar::max());

Instead, Scalars can be formatted as hex strings explicitly by using {:x} or {:X} format directives, via the LowerHex or UpperHex trait implementations on Scalar. Conversion to hex is done in constant-time, but we can’t make any guarantees about side-channel leakage beyond that point.

let hex = "e2df7e885217c19c42a8159fd02633f0dc463fadfafc09a71af20bfa2b9036c6";
let scalar = hex.parse::<Scalar>().unwrap();

assert_eq!(format!("{:x}", scalar), hex);
assert_eq!(format!("{:X}", scalar), hex.to_uppercase());
assert_eq!(format!("{:x}", MaybeScalar::Valid(scalar)), hex);
assert_eq!(format!("{:X}", MaybeScalar::Valid(scalar)), hex.to_uppercase());
assert_eq!(
    format!("{:x}", MaybeScalar::Zero),
    "0000000000000000000000000000000000000000000000000000000000000000"
);

§Points

Valid elliptic curve points are represented by the Point type. There is a special curve point called infinity, or the identity point, or the zero point, which we represent as MaybePoint::Infinity.

pub enum MaybePoint {
    Infinity,
    Valid(Point),
}

§Arithmetic

Points can be added and subtracted from one-another.

use secp::{MaybePoint, Point};

let P1 = "02b435092055e2dc9a1474dac777302c172dde0a40323f0879bff48d002575b685"
    .parse::<Point>()
    .unwrap();
let P2 = "0375663d8ea90563709204f1b1ff4822220cfb257ed5602609282314ba4e7d492c"
    .parse::<Point>()
    .unwrap();

let P3 = "02bc0b73e8233f4fbaa30bcfa540f76d517d385383dd8c9a13ba6dad097f8ea9db"
    .parse::<Point>()
    .unwrap();

// Similar to `Scalar`, adding and subtracting non-infinity points
// results in a `MaybePoint`, because point addition is cyclic just
// like scalar addition.
assert_eq!(P1 + P2, MaybePoint::Valid(P3));
assert_eq!(P3 - P2, MaybePoint::Valid(P1));

// Iterators of points can be summed like any other number-like type.
// Prefer this over manually implementing a summation reducer, as
// we offload most of the work to libsecp256k1.
assert_eq!(
    [P1, P2].into_iter().sum::<MaybePoint>(),
    MaybePoint::Valid(P3)
);

And of course, the most important operation in elliptic curve cryptography, scalar-point multiplication is also supported.

use secp::{MaybePoint, Point, Scalar};

let P = "02b435092055e2dc9a1474dac777302c172dde0a40323f0879bff48d002575b685"
    .parse::<Point>()
    .unwrap();

let d = Scalar::try_from(3).unwrap();

// Multiplying by one is a no-op.
assert_eq!(P * Scalar::one(), P);

// Multiplying by a non-zero scalar guarantees a non-zero
// point is the output.
assert_eq!(
    P * d,
    (P + P + P).unwrap()
);

// Multiplying by the secp256k1 base point `G` is easy.
assert_eq!(
    d.base_point_mul(),
    d * Point::generator()
);

// We provide a static shortcut to the generator point `G`
// which works with arithemtic operators.
use secp::G;
assert_eq!(
    G * d,
    (G + G + G).unwrap()
);
assert_eq!(G - G, MaybePoint::Infinity);

// Point-scalar division works if scalar inversion is enabled
// by the feature set.
assert_eq!(d * G / d, (*G));

§Formatting

Like the scalars, Point and MaybePoint can be formatted compressed form as hex strings explicitly using {:x} and {:X} directives. They also implement Display. The default displayable string value of Point and MaybePoint is the compressed lower-case hex encoding. Uncompressed keys can be formatted by adding the + flag to the directive, i.e. by formatting as {:+} or {:+x}.

// Compressed
let point_hex = "02bc0b73e8233f4fbaa30bcfa540f76d517d385383dd8c9a13ba6dad097f8ea9db";
let point: Point = point_hex.parse().unwrap();
assert_eq!(point.to_string(), point_hex);
assert_eq!(format!("{}", point), point_hex);
assert_eq!(format!("{:x}", point), point_hex);
assert_eq!(format!("{:X}", point), point_hex.to_uppercase());
assert_eq!(format!("{:x}", MaybePoint::Valid(point)), point_hex);
assert_eq!(format!("{:X}", MaybePoint::Valid(point)), point_hex.to_uppercase());
assert_eq!(
    format!("{:x}", MaybePoint::Infinity),
    "000000000000000000000000000000000000000000000000000000000000000000"
);

// Uncompressed
let point_hex_uncompressed =
    "04bc0b73e8233f4fbaa30bcfa540f76d517d385383dd8c9a13ba6dad097f8ea9db\
     6c11d8da7d251e5756c297147a40767bd21d3cd18a830bf79dd4d17ba26fc546";
let point: Point = point_hex_uncompressed.parse().unwrap();
assert_eq!(format!("{:+}", point), point_hex_uncompressed);
assert_eq!(format!("{:+x}", point), point_hex_uncompressed);
assert_eq!(format!("{:+X}", point), point_hex_uncompressed.to_uppercase());
assert_eq!(format!("{:+x}", MaybePoint::Valid(point)), point_hex_uncompressed);
assert_eq!(format!("{:+X}", MaybePoint::Valid(point)), point_hex_uncompressed.to_uppercase());
assert_eq!(
    format!("{:+x}", MaybePoint::Infinity),
    "000000000000000000000000000000000000000000000000000000000000000000\
     0000000000000000000000000000000000000000000000000000000000000000"
);

Modules§

Structs§

  • This struct type represents the secp256k1 generator point, and can be used for scalar-point multiplication.
  • Represents a valid non-infinity point on the secp256k1 curve. Internally this wraps either secp256k1::PublicKey or k256::PublicKey depending on which feature set is enabled.
  • Represents a non-zero scalar in the range [1, n) where n is the order of the secp256k1 curve. A Scalar can be:

Enums§

  • This type is effectively the same as Point, except it can also represent the point at infinity, exposed as MaybePoint::Infinity. This is the special ‘zero-point’, or ‘identity element’ on the curve for which MaybePoint::Infinity + X = X and MaybePoint::Infinity * X = MaybePoint::Infinity for any other point X.
  • Represents an elliptic curve scalar value which might be zero. Supports all the same constant-time arithmetic operators supported by Scalar.