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:
- C bindings to
libsecp256k1
, via thesecp256k1
crate, maintained by the Bitcoin Core team. - A pure-rust implementation via the
k256
crate, maintained by the RustCrypto team.
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.
§CLI
This crate also offers a CLI tool for computing secp256k1 curve operations in your shell. Build it with make cli
. A binary will be built at target/release/secp
.
Usage:
-- Scalar operations --
secp scalar gen Generate a random scalar.
secp scalar add <scalar> [<scalar>...] Sum two or more scalars.
secp scalar mul <scalar> [<scalar>...] Multiply two or more scalars.
secp scalar inv <scalar> Multiplicative inverse of a scalar mod n.
-- Point operations --
secp scalar gen Generate a random point.
secp point add <point> [<point>...] Sum two or more points.
secp point mul <point> [<scalar>...] Multiply a point by one or more scalars.
-- Formats --
Points are represented in 65-byte compressed hex format. Example:
02eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
Scalars are represented in 32-byte hex format. Example:
e8c23ee3c98e040adea5dc92c5c381d6be93615f289ec2d505909657368a0c8f
Prepending a minus sign '-' in front of a point or scalar will negate it. Example:
-02eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
-- Special values --
- The values '0', '1', or '-1' may be substituted for any scalar.
- The value 'G' may be substituted for any point to represent the secp256k1 base point.
- The value '0' may be substituted for any point to represent the additive identity point (infinity).
Example usage:
s1=`secp scalar gen`
s2=`secp scalar gen`
p1=`secp point mul G $s1`
p2=`secp point mul G $s2`
p3=`secp point add $p1 $p2`
p4=`secp point add $p1 -$p2`
§Features
Feature | Description | Dependencies | Enabled by Default |
---|---|---|---|
secp256k1 | Use 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 | ✅ |
k256 | Use 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 | ❌ |
serde | Implement serialization and deserialization for types in this crate. | serde | ❌ |
rand | Enable support for random scalar sampling with a CSPRNG, via the rand crate | rand | ❌ |
secp256k1-invert | libsecp256k1 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-traits | Enable support for numeric identity traits via the num-traits crate. | num_traits | ❌ |
cli-rng | Enable RNG features needed to compile the secp CLI program. Not for public use. | rand | ❌ |
§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).
Scalar
for non-zero scalar values.Point
for non-infinity curve pointsMaybeScalar
for possibly-zero scalars.MaybePoint
for possibly-infinity curve points.
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, Scalar
s 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
ork256::PublicKey
depending on which feature set is enabled. - Represents a non-zero scalar in the range
[1, n)
wheren
is the order of the secp256k1 curve. AScalar
can be:
Enums§
- This type is effectively the same as
Point
, except it can also represent the point at infinity, exposed asMaybePoint::Infinity
. This is the special ‘zero-point’, or ‘identity element’ on the curve for whichMaybePoint::Infinity + X = X
andMaybePoint::Infinity * X = MaybePoint::Infinity
for any other pointX
. - Represents an elliptic curve scalar value which might be zero. Supports all the same constant-time arithmetic operators supported by
Scalar
.