Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ The `fastcrypto` crate contains:
- Ed25519: Backed by [`ed25519-consensus`](https://github.com/penumbra-zone/ed25519-consensus) crate. Compliant to [ZIP-215](https://zips.z.cash/zip-0215) that defines the signature validity that is lacking from RFC8032 but critical for consensus algorithms. [`ed25519-dalek`](https://github.com/dalek-cryptography/ed25519-dalek) is fully deprecated due to the recently discovered [Chalkias double pub-key api vulnerability](https://github.com/MystenLabs/ed25519-unsafe-libs).
- Secp256k1: ECDSA signatures over the secp256k1 curve. Backed by [Secp256k1 FFI](https://crates.io/crates/secp256k1/0.23.1) wrapper that binds to C library and provides performance faster than the native Rust implementation [k256](https://crates.io/crates/k256) library by ~30% on verification. Produces either a standard ECDSA signature or a 65-byte recoverable signature of shape [r, s, v] where v can be 0 or 1 representing the recovery Id. Produces deterministic signatures using the pseudo-random deterministic nonce generation according to [RFC6979](https://www.rfc-editor.org/rfc/rfc6979), without the strong requirement to generate randomness for nonce protection. Uses sha256 as the default hash function for sign and verify. An interface for `verify_hashed` is provided to accept a pre-hashed message and its signature for verification. Supports public key recovery by providing the Secp256k1 recoverable signature with the corresponding pre-hashed message. An accepted signature must have its `s` in the lower half of the curve order. If s is too high, normalize `s` to `order - s` where curve order is `0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141`. See more at [BIP-0062](https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#low-s-values-in-signatures).
- Secp256r1: ECDSA signatures over the secp256r1 curve backed by the [`p256`](https://crates.io/crates/p256) crate which is a pure rust implementation of the Secp256r1 (aka [NIST P-256](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf) and prime256v1) curve. The functionality from `p256` is extended such that, besides standard ECDSA signatures, our implementation can also produce and verify 65 byte recoverable signatures of the form [r, s, v] where v is the recoveryID. Signatures are produced deterministically using the pseudo-random deterministic nonce generation according to [RFC6979](https://www.rfc-editor.org/rfc/rfc6979), without the strong requirement to generate randomness for nonce protection. Uses sha256 as the default hash function for sign and verify. Supports public key recovery by providing the Secp256r1 ECDSA recoverable signature with the corresponding pre-hashed message. An accepted signature must have its `s` in the lower half of the curve order. If s is too high, normalize `s` to `order - s` where curve order is `0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551` defined [here](https://secg.org/SEC2-Ver-1.0.pdf).
- Secp384r1 (experimental, not yet audited): ECDSA signatures over the secp384r1 (aka [NIST P-384](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf)) curve, using the [`p384`](https://crates.io/crates/p384) crate for key and signature encodings and RFC6979 nonce generation, and [Arkworks](https://github.com/arkworks-rs/) (`ark-secp384r1`) with precomputed multiples of the generator for fast elliptic curve arithmetic. Signatures are produced deterministically according to [RFC6979](https://www.rfc-editor.org/rfc/rfc6979) and are byte-identical to those of the `p384` crate. Uses sha384 as the default hash function for sign and verify. Note that contrary to Secp256k1 and Secp256r1, an accepted signature may have its `s` in the upper half of the curve order and `s` is not normalized on signing; this matches the `p384` crate exactly and allows verification of standard ECDSA signatures, e.g. on X.509 certificate chains. DER-encoded signatures can be imported with `from_der`. Does not support public key recovery.
- BLS12-381: Backed by [`blst`](https://github.com/supranational/blst) crate written in Assembly and C that optimizes for performance and security. G1 and G2 points are serialized following [ZCash specification](https://github.com/supranational/blst#serialization-format) in compressed format. Provides functions for verifying signatures in the G1 group against public keys in the G2 group (min-sig) or vice versa (min-pk). Provides functions for aggregating signatures and fast verifying aggregated signatures, where public keys are assumed to be verified for proof of possession.
- RSA: Backed by crate [rsa](https://crates.io/crates/rsa). Provides functions to sign and verify RSA signature according to [RFC 8017](https://www.rfc-editor.org/rfc/rfc8017)

Expand All @@ -54,7 +55,7 @@ The `fastcrypto` crate contains:

- Encoding: Base64 and Hex are defined with an encoding trait with its customized serialization and validations, backed by [base64ct](https://crates.io/crates/base64ct) and [hex]((https://crates.io/crates/base64ct)). Notably, the base64ct crate has been chosen instead of the most popular base64 Rust crate, because (a) it is constant time and (b) mangled encodings are explicitly rejected to prevent malleability attacks when decoding, see [paper](https://dl.acm.org/doi/10.1145/3488932.3527284) on in-depth analysis.

- Hash functions wrappers: [SHA2_256](https://en.wikipedia.org/wiki/SHA-2) with 256 bit digests, [SHA3_256](https://en.wikipedia.org/wiki/SHA-3) with 256 bit digests, [SHA2_512](https://en.wikipedia.org/wiki/SHA-2) with 512 bit digests, [SHA3_512](https://en.wikipedia.org/wiki/SHA-3) with 512 bit digests, [KECCAK](https://keccak.team/files/Keccak-reference-3.0.pdf) with 256 bit digests, [BLAKE2-256](https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2) with 256 bit digests.
- Hash functions wrappers: [SHA2_256](https://en.wikipedia.org/wiki/SHA-2) with 256 bit digests, [SHA3_256](https://en.wikipedia.org/wiki/SHA-3) with 256 bit digests, [SHA2_384](https://en.wikipedia.org/wiki/SHA-2) with 384 bit digests, [SHA2_512](https://en.wikipedia.org/wiki/SHA-2) with 512 bit digests, [SHA3_512](https://en.wikipedia.org/wiki/SHA-3) with 512 bit digests, [KECCAK](https://keccak.team/files/Keccak-reference-3.0.pdf) with 256 bit digests, [BLAKE2-256](https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2) with 256 bit digests.

- Multiset Hash: A hash function where the output of the hash function is a point on the elliptic curve. It also allows for efficient computation for the hash of the union of two multiset.

Expand Down
7 changes: 7 additions & 0 deletions fastcrypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ typenum.workspace = true
auto_ops = "0.3.0"
derive_more = "0.99.16"
p256 = { version = "0.13.2", features = ["ecdsa"] }
p384 = { version = "0.13.1", features = ["ecdsa"] }
ecdsa = { version = "0.16.6", features = ["rfc6979", "verifying"] }
rfc6979 = "0.4.0"
blake2 = "0.10.6"
Expand All @@ -45,6 +46,7 @@ elliptic-curve = { version = "0.13.2", features = ["hash2curve"] }
rsa = { version = "0.9.10", features = ["sha2"] }
static_assertions = "1.1.0"
ark-secp256r1 = "0.4.0"
ark-secp384r1 = "0.4.0"
ark-secp256k1 = "0.4.0"
ark-ec = "0.4.1"
ark-ff = "0.4.1"
Expand Down Expand Up @@ -77,6 +79,11 @@ name = "crypto"
harness = false
required-features = ["experimental"]

[[bench]]
name = "secp384r1"
harness = false
required-features = ["experimental"]

[[bench]]
name = "encoding"
harness = false
Expand Down
7 changes: 7 additions & 0 deletions fastcrypto/benches/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod signature_benches {
use criterion::*;
use fastcrypto::secp256k1::Secp256k1KeyPair;
use fastcrypto::secp256r1::Secp256r1KeyPair;
use fastcrypto::secp384r1::Secp384r1KeyPair;
use fastcrypto::traits::Signer;
use fastcrypto::traits::{RecoverableSignature, RecoverableSigner};
use fastcrypto::{
Expand Down Expand Up @@ -50,6 +51,7 @@ mod signature_benches {
sign_single::<bls12381::min_pk::BLS12381KeyPair, _>("BLS12381MinPk", &mut group);
sign_single::<Secp256k1KeyPair, _>("Secp256k1", &mut group);
sign_single::<Secp256r1KeyPair, _>("Secp256r1", &mut group);
sign_single::<Secp384r1KeyPair, _>("Secp384r1", &mut group);
sign_recoverable_single::<Secp256k1KeyPair, _>("Secp256k1 recoverable", &mut group);
sign_recoverable_single::<Secp256r1KeyPair, _>("Secp256r1 recoverable", &mut group);
}
Expand Down Expand Up @@ -86,6 +88,7 @@ mod signature_benches {
verify_single::<bls12381::min_pk::BLS12381KeyPair, _>("BLS12381MinPk", &mut group);
verify_single::<Secp256k1KeyPair, _>("Secp256k1", &mut group);
verify_single::<Secp256r1KeyPair, _>("Secp256r1", &mut group);
verify_single::<Secp384r1KeyPair, _>("Secp384r1", &mut group);
recover_single::<Secp256k1KeyPair, _>("Secp256k1 recoverable", &mut group);
recover_single::<Secp256r1KeyPair, _>("Secp256r1 recoverable", &mut group);
}
Expand Down Expand Up @@ -457,6 +460,7 @@ mod signature_benches {
let mut csprng3 = csprng.clone();
let mut csprng4 = csprng.clone();
let mut csprng5 = csprng.clone();
let mut csprng6 = csprng.clone();

let mut group: BenchmarkGroup<_> = c.benchmark_group("Key generation");

Expand All @@ -475,6 +479,9 @@ mod signature_benches {
group.bench_function("Secp256r1", move |b| {
b.iter(|| Secp256r1KeyPair::generate(&mut csprng5))
});
group.bench_function("Secp384r1", move |b| {
b.iter(|| Secp384r1KeyPair::generate(&mut csprng6))
});
}

criterion_group! {
Expand Down
69 changes: 69 additions & 0 deletions fastcrypto/benches/secp384r1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! Benchmarks comparing fastcrypto's optimized secp384r1 (NIST P-384) ECDSA implementation with
//! the RustCrypto p384 crate on identical inputs.

#[macro_use]
extern crate criterion;

mod secp384r1_benches {
use super::*;
use criterion::*;
use fastcrypto::secp384r1::Secp384r1KeyPair;
use fastcrypto::traits::{KeyPair, Signer, VerifyingKey};
use p384::ecdsa::signature::{Signer as ExternalSigner, Verifier as ExternalVerifier};
use rand::thread_rng;

const MSG: &[u8] = b"Hello, world!";

/// Generate a fastcrypto key pair and an identical RustCrypto p384 signing key.
fn keys() -> (Secp384r1KeyPair, p384::ecdsa::SigningKey) {
let kp = Secp384r1KeyPair::generate(&mut thread_rng());
let external_sk = p384::ecdsa::SigningKey::from_slice(kp.as_ref()).unwrap();
(kp, external_sk)
}

fn sign(c: &mut Criterion) {
let (kp, external_sk) = keys();

// Sanity check: the two implementations produce identical signatures.
let external_sig: p384::ecdsa::Signature = external_sk.sign(MSG);
assert_eq!(kp.sign(MSG).as_ref(), external_sig.to_bytes().as_slice());

let mut group: BenchmarkGroup<_> = c.benchmark_group("Secp384r1 sign");
group.bench_function("fastcrypto", |b| b.iter(|| kp.sign(MSG)));
group.bench_function("rust_crypto_p384", |b| {
b.iter(|| {
let _: p384::ecdsa::Signature = external_sk.sign(MSG);
})
});
}

fn verify(c: &mut Criterion) {
let (kp, external_sk) = keys();
let signature = kp.sign(MSG);
let external_vk = p384::ecdsa::VerifyingKey::from(&external_sk);
let external_sig = p384::ecdsa::Signature::from_slice(signature.as_ref()).unwrap();

// Sanity check: both implementations accept the signature.
assert!(kp.public().verify(MSG, &signature).is_ok());
assert!(external_vk.verify(MSG, &external_sig).is_ok());

let mut group: BenchmarkGroup<_> = c.benchmark_group("Secp384r1 verify");
group.bench_function("fastcrypto", |b| {
b.iter(|| kp.public().verify(MSG, &signature))
});
group.bench_function("rust_crypto_p384", |b| {
b.iter(|| external_vk.verify(MSG, &external_sig))
});
}

criterion_group! {
name = secp384r1_benches;
config = Criterion::default().sample_size(100);
targets = sign, verify,
}
}

criterion_main!(secp384r1_benches::secp384r1_benches);
1 change: 1 addition & 0 deletions fastcrypto/src/groups/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use std::ops::{AddAssign, SubAssign};
pub mod bls12381;
pub mod ristretto255;
pub mod secp256r1;
pub mod secp384r1;

pub mod multiplier;
pub mod secp256k1;
Expand Down
136 changes: 136 additions & 0 deletions fastcrypto/src/groups/secp384r1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! Implementation of the Secp384r1 (aka P-384) curve. This is a 384-bit Weirstrass curve of prime order.
//! See "SEC 2: Recommended Elliptic Curve Domain Parameters" for details."

use crate::error::{FastCryptoError, FastCryptoResult};
use crate::groups::multiplier::ToLittleEndianBytes;
use crate::groups::{Doubling, GroupElement, Scalar as ScalarTrait};
use crate::serde_helpers::ToFromByteArray;
use crate::serialize_deserialize_with_to_from_byte_array;
use crate::traits::AllowedRng;
use ark_ec::Group;
use ark_ff::{Field, One, UniformRand, Zero};
use ark_secp384r1::{Fr, Projective};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use derive_more::{Add, From, Neg, Sub};
use fastcrypto_derive::GroupOpsExtend;
use std::ops::{Div, Mul};

pub const SCALAR_SIZE_IN_BYTES: usize = 48;

/// A point on the Secp384r1 curve in projective coordinates.
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, From, Add, Sub, Neg, GroupOpsExtend)]
pub struct ProjectivePoint(pub(crate) Projective);

impl GroupElement for ProjectivePoint {
type ScalarType = Scalar;

fn zero() -> Self {
Self(Projective::zero())
}

fn generator() -> Self {
Self(Projective::generator())
}
}

impl Doubling for ProjectivePoint {
fn double(self) -> Self {
ProjectivePoint::from(self.0.double())
}
}

impl Mul<Scalar> for ProjectivePoint {
type Output = ProjectivePoint;

fn mul(self, rhs: Scalar) -> ProjectivePoint {
ProjectivePoint::from(self.0 * rhs.0)
}
}

#[allow(clippy::suspicious_arithmetic_impl)]
impl Div<Scalar> for ProjectivePoint {
type Output = Result<ProjectivePoint, FastCryptoError>;

fn div(self, rhs: Scalar) -> Result<ProjectivePoint, FastCryptoError> {
Ok(self * rhs.inverse()?)
}
}

/// A field element in the prime field of the same order as the curve.
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, From, Add, Sub, Neg, GroupOpsExtend)]
pub struct Scalar(pub(crate) Fr);

impl GroupElement for Scalar {
type ScalarType = Scalar;

fn zero() -> Self {
Scalar(Fr::zero())
}

fn generator() -> Self {
Scalar(Fr::one())
}
}

impl Mul<Scalar> for Scalar {
type Output = Scalar;

fn mul(self, rhs: Scalar) -> Self::Output {
Scalar(self.0 * rhs.0)
}
}

#[allow(clippy::suspicious_arithmetic_impl)]
impl Div<Scalar> for Scalar {
type Output = Result<Scalar, FastCryptoError>;

fn div(self, rhs: Scalar) -> Result<Scalar, FastCryptoError> {
Ok(self * rhs.inverse()?)
}
}

impl From<u128> for Scalar {
fn from(value: u128) -> Self {
Scalar(Fr::from(value))
}
}

impl ScalarTrait for Scalar {
fn rand<R: AllowedRng>(rng: &mut R) -> Self {
Scalar(Fr::rand(rng))
}

fn inverse(&self) -> FastCryptoResult<Self> {
Ok(Scalar(
self.0.inverse().ok_or(FastCryptoError::InvalidInput)?,
))
}
}

impl ToFromByteArray<SCALAR_SIZE_IN_BYTES> for Scalar {
fn from_byte_array(bytes: &[u8; SCALAR_SIZE_IN_BYTES]) -> Result<Self, FastCryptoError> {
Ok(Scalar(
Fr::deserialize_uncompressed(bytes.as_slice())
.map_err(|_| FastCryptoError::InvalidInput)?,
))
}

fn to_byte_array(&self) -> [u8; SCALAR_SIZE_IN_BYTES] {
let mut bytes = [0u8; SCALAR_SIZE_IN_BYTES];
self.0
.serialize_uncompressed(&mut bytes[..])
.expect("Byte array not large enough");
bytes
}
}

impl ToLittleEndianBytes for Scalar {
fn to_le_bytes(&self) -> Vec<u8> {
self.to_byte_array().to_vec()
}
}

serialize_deserialize_with_to_from_byte_array!(Scalar);
3 changes: 3 additions & 0 deletions fastcrypto/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ pub type Sha256 = HashFunctionWrapper<sha2::Sha256, 32>;
/// The [SHA-3](https://en.wikipedia.org/wiki/SHA-3) hash function with 256 bit digests.
pub type Sha3_256 = HashFunctionWrapper<sha3::Sha3_256, 32>;

/// The [SHA-384](https://en.wikipedia.org/wiki/SHA-2) hash function with 384 bit digests.
pub type Sha384 = HashFunctionWrapper<sha2::Sha384, 48>;

/// The [SHA-512](https://en.wikipedia.org/wiki/SHA-2) hash function with 512 bit digests.
pub type Sha512 = HashFunctionWrapper<sha2::Sha512, 64>;

Expand Down
Loading