diff --git a/Cargo.lock b/Cargo.lock index 490568cfeb..845270fd11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -345,6 +345,17 @@ dependencies = [ "ark-std", ] +[[package]] +name = "ark-secp384r1" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4032fd3937de1d629fa50f3004ec82f74e898542782e17a2e6cd3da16ef353d8" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "ark-serialize" version = "0.4.2" @@ -1284,6 +1295,7 @@ dependencies = [ "ff 0.13.1", "generic-array", "group 0.13.0", + "hkdf", "pem-rfc7468", "pkcs8 0.10.2", "rand_core 0.6.4", @@ -1325,6 +1337,7 @@ dependencies = [ "ark-ff", "ark-secp256k1", "ark-secp256r1", + "ark-secp384r1", "ark-serialize", "auto_ops", "base58", @@ -1360,6 +1373,7 @@ dependencies = [ "num-bigint 0.4.6", "once_cell", "p256", + "p384", "proptest", "radix64", "rand 0.8.5", @@ -2466,6 +2480,18 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder", + "sha2 0.10.9", +] + [[package]] name = "pairing" version = "0.23.0" diff --git a/README.md b/README.md index ad4d8ff786..2c53e6dc92 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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. diff --git a/fastcrypto/Cargo.toml b/fastcrypto/Cargo.toml index ee99269cf0..ff5900057d 100644 --- a/fastcrypto/Cargo.toml +++ b/fastcrypto/Cargo.toml @@ -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" @@ -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" @@ -77,6 +79,11 @@ name = "crypto" harness = false required-features = ["experimental"] +[[bench]] +name = "secp384r1" +harness = false +required-features = ["experimental"] + [[bench]] name = "encoding" harness = false diff --git a/fastcrypto/benches/crypto.rs b/fastcrypto/benches/crypto.rs index ded77aa152..5977829fed 100644 --- a/fastcrypto/benches/crypto.rs +++ b/fastcrypto/benches/crypto.rs @@ -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::{ @@ -50,6 +51,7 @@ mod signature_benches { sign_single::("BLS12381MinPk", &mut group); sign_single::("Secp256k1", &mut group); sign_single::("Secp256r1", &mut group); + sign_single::("Secp384r1", &mut group); sign_recoverable_single::("Secp256k1 recoverable", &mut group); sign_recoverable_single::("Secp256r1 recoverable", &mut group); } @@ -86,6 +88,7 @@ mod signature_benches { verify_single::("BLS12381MinPk", &mut group); verify_single::("Secp256k1", &mut group); verify_single::("Secp256r1", &mut group); + verify_single::("Secp384r1", &mut group); recover_single::("Secp256k1 recoverable", &mut group); recover_single::("Secp256r1 recoverable", &mut group); } @@ -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"); @@ -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! { diff --git a/fastcrypto/benches/secp384r1.rs b/fastcrypto/benches/secp384r1.rs new file mode 100644 index 0000000000..356e6e564b --- /dev/null +++ b/fastcrypto/benches/secp384r1.rs @@ -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); diff --git a/fastcrypto/src/groups/mod.rs b/fastcrypto/src/groups/mod.rs index aff97ec4a9..3756353dd9 100644 --- a/fastcrypto/src/groups/mod.rs +++ b/fastcrypto/src/groups/mod.rs @@ -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; diff --git a/fastcrypto/src/groups/secp384r1.rs b/fastcrypto/src/groups/secp384r1.rs new file mode 100644 index 0000000000..bceca8b161 --- /dev/null +++ b/fastcrypto/src/groups/secp384r1.rs @@ -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 for ProjectivePoint { + type Output = ProjectivePoint; + + fn mul(self, rhs: Scalar) -> ProjectivePoint { + ProjectivePoint::from(self.0 * rhs.0) + } +} + +#[allow(clippy::suspicious_arithmetic_impl)] +impl Div for ProjectivePoint { + type Output = Result; + + fn div(self, rhs: Scalar) -> Result { + 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 for Scalar { + type Output = Scalar; + + fn mul(self, rhs: Scalar) -> Self::Output { + Scalar(self.0 * rhs.0) + } +} + +#[allow(clippy::suspicious_arithmetic_impl)] +impl Div for Scalar { + type Output = Result; + + fn div(self, rhs: Scalar) -> Result { + Ok(self * rhs.inverse()?) + } +} + +impl From for Scalar { + fn from(value: u128) -> Self { + Scalar(Fr::from(value)) + } +} + +impl ScalarTrait for Scalar { + fn rand(rng: &mut R) -> Self { + Scalar(Fr::rand(rng)) + } + + fn inverse(&self) -> FastCryptoResult { + Ok(Scalar( + self.0.inverse().ok_or(FastCryptoError::InvalidInput)?, + )) + } +} + +impl ToFromByteArray for Scalar { + fn from_byte_array(bytes: &[u8; SCALAR_SIZE_IN_BYTES]) -> Result { + 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 { + self.to_byte_array().to_vec() + } +} + +serialize_deserialize_with_to_from_byte_array!(Scalar); diff --git a/fastcrypto/src/hash.rs b/fastcrypto/src/hash.rs index 11aee79667..54fd8addcb 100644 --- a/fastcrypto/src/hash.rs +++ b/fastcrypto/src/hash.rs @@ -165,6 +165,9 @@ pub type Sha256 = HashFunctionWrapper; /// The [SHA-3](https://en.wikipedia.org/wiki/SHA-3) hash function with 256 bit digests. pub type Sha3_256 = HashFunctionWrapper; +/// The [SHA-384](https://en.wikipedia.org/wiki/SHA-2) hash function with 384 bit digests. +pub type Sha384 = HashFunctionWrapper; + /// The [SHA-512](https://en.wikipedia.org/wiki/SHA-2) hash function with 512 bit digests. pub type Sha512 = HashFunctionWrapper; diff --git a/fastcrypto/src/lib.rs b/fastcrypto/src/lib.rs index 618031480a..f33518c686 100644 --- a/fastcrypto/src/lib.rs +++ b/fastcrypto/src/lib.rs @@ -28,6 +28,10 @@ pub mod secp256r1_tests; #[path = "tests/secp256r1_recoverable_tests.rs"] pub mod secp256r1_recoverable_tests; +#[cfg(all(test, feature = "experimental"))] +#[path = "tests/secp384r1_tests.rs"] +pub mod secp384r1_tests; + #[cfg(test)] #[path = "tests/bls12381_tests.rs"] pub mod bls12381_tests; @@ -80,6 +84,10 @@ pub mod utils_tests; #[path = "tests/secp256r1_group_tests.rs"] pub mod secp256r1_group_tests; +#[cfg(test)] +#[path = "tests/secp384r1_group_tests.rs"] +pub mod secp384r1_group_tests; + #[cfg(test)] #[path = "tests/secp256k1_group_tests.rs"] pub mod secp256k1_group_tests; @@ -114,6 +122,11 @@ pub mod twisted_elgamal; pub mod utils; pub mod vrf; +/// Experimental: An optimized implementation of the ECDSA signature scheme over the secp384r1 +/// (NIST P-384) curve. This module has not yet been audited. +#[cfg(feature = "experimental")] +pub mod secp384r1; + #[cfg(feature = "experimental")] pub mod sphincs; /// This module contains unsecure cryptographic primitives. The purpose of this library is to allow seamless diff --git a/fastcrypto/src/secp384r1/conversion.rs b/fastcrypto/src/secp384r1/conversion.rs new file mode 100644 index 0000000000..35d585f22b --- /dev/null +++ b/fastcrypto/src/secp384r1/conversion.rs @@ -0,0 +1,243 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! This module contains conversion function between scalars (fr), field elements (fq) and elliptic curve +//! points between the representations used by arkworks in ark-secp384r1 and RustCrypto's p384 crate. + +#[cfg(test)] +use ark_ec::AffineRepr; +use ark_ff::{Field, PrimeField as ArkworksPrimeField, Zero}; +use ark_serialize::{CanonicalSerialize, CanonicalSerializeWithFlags, EmptyFlags}; +use elliptic_curve::bigint::ArrayEncoding; +use elliptic_curve::scalar::FromUintUnchecked; +#[cfg(test)] +use elliptic_curve::sec1::FromEncodedPoint; +use elliptic_curve::sec1::ToEncodedPoint; +use generic_array::GenericArray; +use p384::{FieldBytes, U384}; + +/// The size in bytes of a serialized field element or scalar. +const FIELD_BYTES_SIZE: usize = 48; + +/// Convert a p384 scalar to an arkworks scalar. +pub(crate) fn fr_p384_to_arkworks(scalar: &p384::Scalar) -> ark_secp384r1::Fr { + ark_secp384r1::Fr::from_be_bytes_mod_order(&scalar.to_bytes()) +} + +/// Convert an arkworks scalar to a p384 scalar. +pub(crate) fn fr_arkworks_to_p384(scalar: &ark_secp384r1::Fr) -> p384::Scalar { + // This implementation is taken from bls_fr_to_blst_fr in fastcrypto-zkp. + let mut bytes = [0u8; FIELD_BYTES_SIZE]; + scalar + .serialize_with_flags(&mut bytes[..], EmptyFlags) + .unwrap(); + p384::Scalar::from_uint_unchecked(U384::from_le_byte_array(GenericArray::clone_from_slice( + &bytes, + ))) +} + +/// Convert an arkworks field element to a p384 field element. +#[cfg(test)] +pub(crate) fn fq_arkworks_to_p384(scalar: &ark_secp384r1::Fq) -> FieldBytes { + // This implementation is taken from bls_fq_to_blst_fp in fastcrypto-zkp. + let mut bytes = [0u8; FIELD_BYTES_SIZE]; + scalar.serialize_uncompressed(&mut bytes[..]).unwrap(); + bytes.reverse(); + FieldBytes::clone_from_slice(&bytes) +} + +/// Convert a p384 affine point to an arkworks affine point. +pub(crate) fn affine_pt_p384_to_projective_arkworks( + point: &p384::AffinePoint, +) -> ark_secp384r1::Projective { + if point.is_identity().into() { + return ark_secp384r1::Projective::zero(); + } + let encoded_point = point.to_encoded_point(false); + ark_secp384r1::Projective::from(ark_secp384r1::Affine::new_unchecked( + ark_secp384r1::Fq::from_be_bytes_mod_order(encoded_point.x().unwrap()), + ark_secp384r1::Fq::from_be_bytes_mod_order(encoded_point.y().unwrap()), + )) +} + +/// Convert a message digest to an integer representation as defined in the `bits2int` algorithm in +/// section 2.3.2 in "SEC 1: Elliptic Curve Cryptography": If the digest is longer than 48 bytes +/// (384 bits), only the leftmost 384 bits are used, and if it is shorter it is padded with zeros +/// from the left. This is the same encoding used by the p384 crate. +pub(crate) fn digest_to_field_bytes(digest: &[u8]) -> FieldBytes { + let mut bytes = FieldBytes::default(); + if digest.len() >= FIELD_BYTES_SIZE { + bytes.copy_from_slice(&digest[..FIELD_BYTES_SIZE]); + } else { + bytes[FIELD_BYTES_SIZE - digest.len()..].copy_from_slice(digest); + } + bytes +} + +/// Reduce a big-endian integer representation modulo the subgroup order in arkworks representation. +pub(crate) fn reduce_bytes(bytes: &FieldBytes) -> ark_secp384r1::Fr { + ark_secp384r1::Fr::from_be_bytes_mod_order(bytes) +} + +/// Reduce an arkworks field element (modulo field size) to a scalar (modulo subgroup order). This also +/// returns a boolean indicating whether a modular reduction was performed. +pub(crate) fn arkworks_fq_to_fr(scalar: &ark_secp384r1::Fq) -> (ark_secp384r1::Fr, bool) { + let mut bytes = [0u8; FIELD_BYTES_SIZE]; + scalar.serialize_uncompressed(&mut bytes[..]).unwrap(); + let output = ark_secp384r1::Fr::from_le_bytes_mod_order(&bytes); + (output, output.into_bigint() != scalar.into_bigint()) +} + +/// Converts an arkworks affine point to a p384 affine point. +#[cfg(test)] +pub(crate) fn affine_pt_arkworks_to_p384(point: &ark_secp384r1::Affine) -> p384::AffinePoint { + if point.is_zero() { + return p384::AffinePoint::IDENTITY; + } + let encoded_point = p384::EncodedPoint::from_affine_coordinates( + &fq_arkworks_to_p384(point.x().expect("The point should not be zero")), + &fq_arkworks_to_p384(point.y().expect("The point should not be zero")), + false, + ); + p384::AffinePoint::from_encoded_point(&encoded_point).unwrap() +} + +/// Extract the affine x-coordinate from a projective point. Returns none if the point is the identity. +pub(crate) fn get_affine_x_coordinate( + point: &ark_secp384r1::Projective, +) -> Option { + if point.is_zero() { + return None; + } + let mut z_inv = point + .z + .inverse() + .expect("z is zero. This should never happen."); + z_inv.square_in_place(); + Some(point.x * z_inv) +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_ec::{CurveGroup, Group}; + use ark_ff::UniformRand; + use elliptic_curve::group::prime::PrimeCurveAffine; + use elliptic_curve::ops::Reduce; + use elliptic_curve::Field; + + #[test] + fn test_fr_p384_to_arkworks() { + let arkworks_seven = ark_secp384r1::Fr::from(7u32); + let p384_seven = p384::Scalar::from(7u32); + + let actual_arkworks_seven = fr_p384_to_arkworks(&p384_seven); + assert_eq!(actual_arkworks_seven, arkworks_seven); + + let actual_p384_seven = fr_arkworks_to_p384(&arkworks_seven); + assert_eq!(actual_p384_seven, p384_seven); + } + + #[test] + fn test_pt_arkworks_to_p384() { + // 0 + assert_eq!( + p384::AffinePoint::IDENTITY, + affine_pt_arkworks_to_p384(&ark_secp384r1::Affine::zero()) + ); + + // G + assert_eq!( + p384::AffinePoint::generator(), + affine_pt_arkworks_to_p384(&ark_secp384r1::Affine::generator()) + ); + + // 7G + assert_eq!( + (p384::AffinePoint::generator() * p384::Scalar::from(7u32)).to_affine(), + affine_pt_arkworks_to_p384( + &(ark_secp384r1::Projective::generator() * ark_secp384r1::Fr::from(7u32)) + .into_affine() + ) + ); + + // sG, random s + let random_s = p384::Scalar::random(&mut rand::thread_rng()); + assert_eq!( + (p384::AffinePoint::generator() * random_s).to_affine(), + affine_pt_arkworks_to_p384( + &(ark_secp384r1::Projective::generator() * fr_p384_to_arkworks(&random_s)) + .into_affine() + ) + ); + } + + #[test] + fn test_pt_p384_to_arkworks() { + // 0 + assert_eq!( + ark_secp384r1::Projective::zero(), + affine_pt_p384_to_projective_arkworks(&p384::AffinePoint::IDENTITY) + ); + + // G + assert_eq!( + ark_secp384r1::Projective::generator(), + affine_pt_p384_to_projective_arkworks(&p384::AffinePoint::generator()) + ); + + // 7G + assert_eq!( + ark_secp384r1::Projective::generator() * ark_secp384r1::Fr::from(7u32), + affine_pt_p384_to_projective_arkworks( + &(p384::AffinePoint::generator() * p384::Scalar::from(7u32)).to_affine() + ) + ); + + // sG, random s + let random_s = p384::Scalar::random(&mut rand::thread_rng()); + assert_eq!( + (ark_secp384r1::Projective::generator() * fr_p384_to_arkworks(&random_s)).into_affine(), + affine_pt_p384_to_projective_arkworks( + &(p384::AffinePoint::generator() * random_s).to_affine() + ) + ); + } + + #[test] + fn test_arkworks_fq_to_fr() { + let s = ark_secp384r1::Fq::rand(&mut rand::thread_rng()); + let s_fr = arkworks_fq_to_fr(&s).0; + let p384_s = fq_arkworks_to_p384(&s); + let reduced_s = p384::Scalar::reduce_bytes(&p384_s); + assert_eq!(fr_arkworks_to_p384(&s_fr), reduced_s); + assert_eq!(reduce_bytes(&p384_s), s_fr); + } + + #[test] + fn test_fq_arkworks_to_p384() { + let arkworks_seven = ark_secp384r1::Fq::from(7u32); + let p384_seven = p384::FieldBytes::from(p384::Scalar::from(7u32)); + + let actual_p384_seven = fq_arkworks_to_p384(&arkworks_seven); + assert_eq!(actual_p384_seven, p384_seven); + } + + #[test] + fn test_digest_to_field_bytes() { + // Shorter digests are padded with zeros from the left. + let digest_32 = [1u8; 32]; + let bytes = digest_to_field_bytes(&digest_32); + assert_eq!(bytes[..16], [0u8; 16]); + assert_eq!(bytes[16..], digest_32); + + // 48 byte digests are used as is. + let digest_48 = [2u8; 48]; + assert_eq!(digest_to_field_bytes(&digest_48).as_slice(), &digest_48); + + // Longer digests are truncated to the leftmost 384 bits. + let mut digest_64 = [3u8; 64]; + digest_64[48..].copy_from_slice(&[4u8; 16]); + assert_eq!(digest_to_field_bytes(&digest_64).as_slice(), &[3u8; 48]); + } +} diff --git a/fastcrypto/src/secp384r1/mod.rs b/fastcrypto/src/secp384r1/mod.rs new file mode 100644 index 0000000000..da6ebe33d0 --- /dev/null +++ b/fastcrypto/src/secp384r1/mod.rs @@ -0,0 +1,525 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! This module contains an implementation of the [ECDSA signature scheme](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm) over the +//! [secp384r1 NIST P-384 curve](https://www.secg.org/SEC2-Ver-1.0.pdf). The nonce is generated deterministically according to [RFC6979](https://www.rfc-editor.org/rfc/rfc6979). +//! +//! The implementation accepts and produces the same signatures as the [p384](https://crates.io/crates/p384) +//! crate but uses precomputation and faster elliptic curve arithmetic to speed up signing and verification. +//! +//! Note that unlike the [crate::secp256r1] module, this module does *not* require the `s` value of a +//! signature to be in the lower half of the scalar field. This matches the behaviour of the `p384` +//! crate and allows verification of signatures from standard ECDSA implementations (e.g. on X.509 +//! certificate chains), but it implies that signatures are malleable: if `(r, s)` is a valid +//! signature, then so is `(r, n - s)`. +//! +//! This module is experimental and has not yet been audited. +//! +//! Messages can be signed and the signature can be verified again: +//! # Example +//! ```rust +//! # use fastcrypto::secp384r1::*; +//! # use fastcrypto::traits::{KeyPair, Signer, VerifyingKey}; +//! use rand::thread_rng; +//! let kp = Secp384r1KeyPair::generate(&mut thread_rng()); +//! let message: &[u8] = b"Hello, world!"; +//! let signature = kp.sign(message); +//! assert!(kp.public().verify(message, &signature).is_ok()); +//! ``` + +pub mod conversion; + +use crate::groups::GroupElement; +use crate::serde_helpers::BytesRepresentation; +use crate::{ + generate_bytes_representation, impl_base64_display_fmt, + serialize_deserialize_with_to_from_bytes, +}; +use ark_ec::{AffineRepr, CurveGroup}; +use ark_ff::Field; +use elliptic_curve::{Curve, FieldBytesEncoding, PrimeField as OtherPrimeField}; +use lazy_static::lazy_static; +use once_cell::sync::OnceCell; +use p384::ecdsa::{ + Signature as ExternalSignature, Signature, SigningKey as ExternalSecretKey, + VerifyingKey as ExternalPublicKey, +}; +use p384::elliptic_curve::group::GroupEncoding; +use p384::{NistP384, Scalar}; +use std::fmt::{self, Debug}; +use std::str::FromStr; + +use fastcrypto_derive::{SilentDebug, SilentDisplay}; + +use crate::groups::multiplier::windowed::WindowedScalarMultiplier; +use crate::groups::multiplier::ScalarMultiplier; +use crate::groups::secp384r1; +use crate::groups::secp384r1::ProjectivePoint; +use crate::hash::{HashFunction, Sha384}; +use crate::secp384r1::conversion::{ + affine_pt_p384_to_projective_arkworks, arkworks_fq_to_fr, digest_to_field_bytes, + fr_arkworks_to_p384, fr_p384_to_arkworks, get_affine_x_coordinate, reduce_bytes, +}; +use crate::traits::Signer; +use crate::{ + encoding::{Base64, Encoding}, + error::FastCryptoError, + traits::{ + AllowedRng, Authenticator, EncodeDecodeBase64, KeyPair, SigningKey, ToFromBytes, + VerifyingKey, + }, +}; + +/// The length of a public key in bytes. +pub const SECP384R1_PUBLIC_KEY_LENGTH: usize = 49; + +/// The length of a private key in bytes. +pub const SECP384R1_PRIVATE_KEY_LENGTH: usize = 48; + +/// The length of a signature in bytes. +pub const SECP384R1_SIGNATURE_LENGTH: usize = 96; + +/// The key pair bytes length is the same as the private key length. This enforces deserialization to always derive the public key from the private key. +pub const SECP384R1_KEYPAIR_LENGTH: usize = SECP384R1_PRIVATE_KEY_LENGTH; + +/// The number of precomputed points used for scalar multiplication. +pub const PRECOMPUTED_POINTS: usize = 256; + +/// The size of the sliding window used for scalar multiplication. +pub const SLIDING_WINDOW_WIDTH: usize = 5; + +/// Default hash function used for signing and verifying messages unless another hash function is +/// specified using the `with_hash` functions. +pub type DefaultHash = Sha384; + +/// The length of a digest of [DefaultHash] in bytes. +pub const DEFAULT_HASH_LENGTH: usize = 48; + +/// Secp384r1 public key. +#[readonly::make] +#[derive(Debug, Clone)] +pub struct Secp384r1PublicKey { + pub pubkey: ExternalPublicKey, + pub bytes: OnceCell<[u8; SECP384R1_PUBLIC_KEY_LENGTH]>, +} + +/// Secp384r1 private key. +#[readonly::make] +#[derive(SilentDebug, SilentDisplay)] +pub struct Secp384r1PrivateKey { + pub privkey: ExternalSecretKey, + pub bytes: OnceCell>, +} + +/// Secp384r1 signature. +#[readonly::make] +#[derive(Debug, Clone)] +pub struct Secp384r1Signature { + pub sig: ExternalSignature, + pub bytes: OnceCell<[u8; SECP384R1_SIGNATURE_LENGTH]>, +} + +impl std::hash::Hash for Secp384r1PublicKey { + fn hash(&self, state: &mut H) { + self.as_ref().hash(state); + } +} + +impl PartialOrd for Secp384r1PublicKey { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Secp384r1PublicKey { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.pubkey.cmp(&other.pubkey) + } +} + +impl PartialEq for Secp384r1PublicKey { + fn eq(&self, other: &Self) -> bool { + self.pubkey == other.pubkey + } +} + +impl Eq for Secp384r1PublicKey {} + +impl VerifyingKey for Secp384r1PublicKey { + type PrivKey = Secp384r1PrivateKey; + type Sig = Secp384r1Signature; + const LENGTH: usize = SECP384R1_PUBLIC_KEY_LENGTH; + + fn verify(&self, msg: &[u8], signature: &Secp384r1Signature) -> Result<(), FastCryptoError> { + self.verify_with_hash::(msg, signature) + } +} + +lazy_static! { + static ref MULTIPLIER: WindowedScalarMultiplier< + ProjectivePoint, + ::ScalarType, + PRECOMPUTED_POINTS, + SLIDING_WINDOW_WIDTH, + > = WindowedScalarMultiplier::< + ProjectivePoint, + ::ScalarType, + PRECOMPUTED_POINTS, + SLIDING_WINDOW_WIDTH, + >::new( + secp384r1::ProjectivePoint::generator(), + secp384r1::ProjectivePoint::zero() + ); +} + +serialize_deserialize_with_to_from_bytes!(Secp384r1PublicKey, SECP384R1_PUBLIC_KEY_LENGTH); +generate_bytes_representation!( + Secp384r1PublicKey, + SECP384R1_PUBLIC_KEY_LENGTH, + Secp384r1PublicKeyAsBytes +); +impl Secp384r1PublicKey { + /// Verify the signature using the given hash function to hash the message. + /// + /// Note that contrary to [crate::secp256r1::Secp256r1PublicKey::verify_with_hash], signatures + /// with an `s` value in the upper half of the scalar field are accepted, matching the behaviour + /// of the p384 crate. + pub fn verify_with_hash, const DIGEST_LEN: usize>( + &self, + msg: &[u8], + signature: &Secp384r1Signature, + ) -> Result<(), FastCryptoError> { + // The flow below is identical to verify_prehash from ecdsa-0.16.6/src/hazmat.rs, but using + // arkworks for the finite field and elliptic curve arithmetic. + + // Split signature into scalars. Note that this panics if r or s are zero, which is handled + // in Secp384r1Signature::from_bytes. + let (r, s) = signature.sig.split_scalars(); + let z = reduce_bytes(&digest_to_field_bytes(&H::digest(msg).digest)); + + // Convert scalars to arkworks representation + let r = fr_p384_to_arkworks(&r); + let s = fr_p384_to_arkworks(&s); + let q = affine_pt_p384_to_projective_arkworks(self.pubkey.as_affine()); + + // Compute inverse of s. This fails if s is zero which is checked in deserialization and in + // split_scalars above, but we avoid an unwrap here to be safe. + let s_inv = s.inverse().ok_or(FastCryptoError::InvalidSignature)?; + + // Verify signature + let u1 = z * s_inv; + let u2 = r * s_inv; + + // Do optimised double multiplication + let p = MULTIPLIER + .two_scalar_mul( + &secp384r1::Scalar(u1), + &ProjectivePoint(q), + &secp384r1::Scalar(u2), + ) + .0; + + // Note that x is none if and only if p is zero, in which case the signature is invalid. See + // step 5 in section 4.1.4 in "SEC 1: Elliptic Curve Cryptography". + let x = get_affine_x_coordinate(&p).ok_or(FastCryptoError::InvalidSignature)?; + + let (x_reduced, _) = arkworks_fq_to_fr(&x); + if x_reduced == r { + return Ok(()); + } + Err(FastCryptoError::InvalidSignature) + } +} + +impl AsRef<[u8]> for Secp384r1PublicKey { + fn as_ref(&self) -> &[u8] { + self.bytes.get_or_init::<_>(|| { + <[u8; SECP384R1_PUBLIC_KEY_LENGTH]>::try_from( + self.pubkey.as_ref().to_bytes().as_slice(), + ) + .unwrap() + }) + } +} + +impl ToFromBytes for Secp384r1PublicKey { + fn from_bytes(bytes: &[u8]) -> Result { + match ExternalPublicKey::try_from(bytes) { + Ok(pubkey) => Ok(Secp384r1PublicKey { + pubkey, + // If the given bytes is in the right format (compressed), we keep them for next time to_bytes is called + bytes: match <[u8; SECP384R1_PUBLIC_KEY_LENGTH]>::try_from(bytes) { + Ok(result) => OnceCell::with_value(result), + Err(_) => OnceCell::new(), + }, + }), + Err(_) => Err(FastCryptoError::InvalidInput), + } + } +} + +impl_base64_display_fmt!(Secp384r1PublicKey); + +impl<'a> From<&'a Secp384r1PrivateKey> for Secp384r1PublicKey { + fn from(secret: &'a Secp384r1PrivateKey) -> Self { + Secp384r1PublicKey { + pubkey: ExternalPublicKey::from(&secret.privkey), + bytes: OnceCell::new(), + } + } +} + +impl SigningKey for Secp384r1PrivateKey { + type PubKey = Secp384r1PublicKey; + type Sig = Secp384r1Signature; + const LENGTH: usize = SECP384R1_PRIVATE_KEY_LENGTH; +} + +serialize_deserialize_with_to_from_bytes!(Secp384r1PrivateKey, SECP384R1_PRIVATE_KEY_LENGTH); + +impl ToFromBytes for Secp384r1PrivateKey { + fn from_bytes(bytes: &[u8]) -> Result { + match ExternalSecretKey::try_from(bytes) { + Ok(privkey) => Ok(Secp384r1PrivateKey { + privkey, + bytes: OnceCell::with_value(zeroize::Zeroizing::new( + <[u8; SECP384R1_PRIVATE_KEY_LENGTH]>::try_from(bytes).map_err(|_| { + FastCryptoError::InputLengthWrong(SECP384R1_PRIVATE_KEY_LENGTH) + })?, + )), + }), + Err(_) => Err(FastCryptoError::InvalidInput), + } + } +} + +impl PartialEq for Secp384r1PrivateKey { + fn eq(&self, other: &Self) -> bool { + self.privkey == other.privkey + } +} + +impl Eq for Secp384r1PrivateKey {} + +impl AsRef<[u8]> for Secp384r1PrivateKey { + fn as_ref(&self) -> &[u8] { + self.bytes + .get_or_init::<_>(|| { + zeroize::Zeroizing::new( + <[u8; SECP384R1_PRIVATE_KEY_LENGTH]>::try_from( + self.privkey.to_bytes().as_slice(), + ) + .unwrap(), + ) + }) + .as_ref() + } +} + +// All fields impl zeroize::ZeroizeOnDrop directly or indirectly (OnceCell's drop will call +// ZeroizeOnDrop). +impl zeroize::ZeroizeOnDrop for Secp384r1PrivateKey {} + +serialize_deserialize_with_to_from_bytes!(Secp384r1Signature, SECP384R1_SIGNATURE_LENGTH); +generate_bytes_representation!( + Secp384r1Signature, + SECP384R1_SIGNATURE_LENGTH, + Secp384r1SignatureAsBytes +); + +impl ToFromBytes for Secp384r1Signature { + fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != SECP384R1_SIGNATURE_LENGTH { + return Err(FastCryptoError::InputLengthWrong( + SECP384R1_SIGNATURE_LENGTH, + )); + } + + // This fails if either r or s are zero: https://docs.rs/ecdsa/0.16.6/src/ecdsa/lib.rs.html#209-219. + let sig = ExternalSignature::try_from(bytes).map_err(|_| FastCryptoError::InvalidInput)?; + + Ok(Secp384r1Signature { + sig, + bytes: OnceCell::new(), + }) + } +} + +impl Secp384r1Signature { + /// Parse a signature from its strict DER encoding, as used e.g. in X.509 certificates. + /// + /// This accepts exactly the same encodings as `p384::ecdsa::Signature::from_der`: BER variants + /// and signatures where r or s is zero or not in [1, n-1] are rejected. The parsed signature + /// is held in the fixed-length encoding used by this module, so `as_ref` returns the 96 byte + /// `r || s` representation and not the original DER encoding. + pub fn from_der(bytes: &[u8]) -> Result { + let sig = ExternalSignature::from_der(bytes).map_err(|_| FastCryptoError::InvalidInput)?; + Ok(Secp384r1Signature { + sig, + bytes: OnceCell::new(), + }) + } +} + +impl Authenticator for Secp384r1Signature { + type PubKey = Secp384r1PublicKey; + type PrivKey = Secp384r1PrivateKey; + const LENGTH: usize = SECP384R1_SIGNATURE_LENGTH; +} + +impl AsRef<[u8]> for Secp384r1Signature { + fn as_ref(&self) -> &[u8] { + self.bytes.get_or_init::<_>(|| { + <[u8; SECP384R1_SIGNATURE_LENGTH]>::try_from(self.sig.to_bytes().as_slice()).unwrap() + }) + } +} + +impl std::hash::Hash for Secp384r1Signature { + fn hash(&self, state: &mut H) { + self.as_ref().hash(state); + } +} + +impl PartialEq for Secp384r1Signature { + fn eq(&self, other: &Self) -> bool { + self.sig == other.sig + } +} + +impl Eq for Secp384r1Signature {} + +impl_base64_display_fmt!(Secp384r1Signature); + +/// Secp384r1 public/private key pair. +#[derive(Debug, PartialEq, Eq)] +pub struct Secp384r1KeyPair { + pub public: Secp384r1PublicKey, + pub secret: Secp384r1PrivateKey, +} + +impl Secp384r1KeyPair { + /// Create a new signature using the given hash function to hash the message. + /// + /// The nonce is generated deterministically with RFC6979 using SHA-384 regardless of the choice + /// of `H`, so for `H` = SHA-384 the output is identical to the signatures produced by the p384 + /// crate on the same inputs. The `s` value of the signature is *not* normalized to the lower + /// half of the scalar field (see also the module documentation). + pub fn sign_with_hash, const DIGEST_LEN: usize>( + &self, + msg: &[u8], + ) -> Secp384r1Signature { + // Hash message + let z_bytes = digest_to_field_bytes(&H::digest(msg).digest); + let z = reduce_bytes(&z_bytes); + + // Private key as scalar + let x = self.secret.privkey.as_nonzero_scalar(); + + // Generate nonce according to RFC6979. The unwrap is safe because k is generated smaller + // than the group size. + let k = fr_p384_to_arkworks( + &Scalar::from_repr(rfc6979::generate_k::( + &x.to_bytes(), + &NistP384::ORDER.encode_field_bytes(), + &z_bytes, + &[], + )) + .unwrap(), + ); + + // Convert secret key to an arkworks scalar. + let x = fr_p384_to_arkworks(x); + + // Compute scalar inversion of k + let k_inv = k.inverse().expect("k should not be zero"); + + // Compute R = kG + let big_r = MULTIPLIER.mul(&secp384r1::Scalar(k)).0.into_affine(); + + // Lift x-coordinate of R and reduce it into an element of the scalar field + let (r, _) = arkworks_fq_to_fr(big_r.x().expect("R should not be zero")); + + // Compute s as a signature over r and z. + let s = k_inv * (z + r * x); + + // Convert to p384 format + let s = fr_arkworks_to_p384(&s).to_bytes(); + let r = fr_arkworks_to_p384(&r).to_bytes(); + + // This can only fail if either 𝒓 or 𝒔 are zero (see ecdsa-0.16.6/src/lib.rs) which is negligible. + let signature = Signature::from_scalars(r, s).expect("r or s is zero"); + + Secp384r1Signature { + sig: signature, + bytes: OnceCell::new(), + } + } +} + +/// The bytes form of the keypair always only contain the private key bytes +impl ToFromBytes for Secp384r1KeyPair { + fn from_bytes(bytes: &[u8]) -> Result { + Secp384r1PrivateKey::from_bytes(bytes).map(|secret| secret.into()) + } +} + +serialize_deserialize_with_to_from_bytes!(Secp384r1KeyPair, SECP384R1_KEYPAIR_LENGTH); + +impl AsRef<[u8]> for Secp384r1KeyPair { + fn as_ref(&self) -> &[u8] { + self.secret.as_ref() + } +} + +impl KeyPair for Secp384r1KeyPair { + type PubKey = Secp384r1PublicKey; + type PrivKey = Secp384r1PrivateKey; + type Sig = Secp384r1Signature; + + fn public(&'_ self) -> &'_ Self::PubKey { + &self.public + } + + fn private(self) -> Self::PrivKey { + Secp384r1PrivateKey::from_bytes(self.secret.as_ref()).unwrap() + } + + #[cfg(feature = "copy_key")] + fn copy(&self) -> Self { + Secp384r1KeyPair { + public: self.public.clone(), + secret: Secp384r1PrivateKey::from_bytes(self.secret.as_ref()).unwrap(), + } + } + + fn generate(rng: &mut R) -> Self { + let privkey = ExternalSecretKey::random(rng); + Secp384r1PrivateKey { + privkey, + bytes: OnceCell::new(), + } + .into() + } +} + +impl FromStr for Secp384r1KeyPair { + type Err = FastCryptoError; + + fn from_str(s: &str) -> Result { + Self::decode_base64(s) + } +} + +impl Signer for Secp384r1KeyPair { + fn sign(&self, msg: &[u8]) -> Secp384r1Signature { + self.sign_with_hash::(msg) + } +} + +impl From for Secp384r1KeyPair { + fn from(secret: Secp384r1PrivateKey) -> Self { + let public = Secp384r1PublicKey::from(&secret); + Secp384r1KeyPair { public, secret } + } +} diff --git a/fastcrypto/src/tests/secp384r1_group_tests.rs b/fastcrypto/src/tests/secp384r1_group_tests.rs new file mode 100644 index 0000000000..24fce3a9aa --- /dev/null +++ b/fastcrypto/src/tests/secp384r1_group_tests.rs @@ -0,0 +1,92 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::groups::multiplier::windowed::WindowedScalarMultiplier; +use crate::groups::multiplier::ScalarMultiplier; +use crate::groups::secp384r1::{ProjectivePoint, Scalar}; +use crate::groups::{secp384r1, Doubling, GroupElement, Scalar as ScalarTrait}; +use crate::serde_helpers::ToFromByteArray; +use ark_ff::{BigInteger, PrimeField}; +use ark_secp384r1::Fr; +use rand::thread_rng; + +#[test] +fn test_to_from_byte_array() { + let scalar = secp384r1::Scalar::rand(&mut thread_rng()); + let bytes = scalar.to_byte_array(); + let reconstructed = Scalar::from_byte_array(&bytes).unwrap(); + assert_eq!(scalar, reconstructed); +} + +#[test] +fn test_arithmetic() { + let p = ProjectivePoint::generator(); + let two_p = p + p; + let s = Scalar::from(2); + assert_eq!(two_p, p.double()); + assert_eq!(two_p, p * s); + assert_eq!(p, two_p * (Scalar::generator() / s).unwrap()); + + // Check that u128 is decoded correctly. + let x: u128 = 2 << 66; + let x_scalar = Scalar::from(x); + let res = x_scalar / Scalar::from(8); + assert_eq!(res.unwrap(), Scalar::from(2 << 63)); +} + +#[test] +fn test_scalar_multiplication() { + let mut modulus_minus_one = Fr::MODULUS_MINUS_ONE_DIV_TWO; + modulus_minus_one.mul2(); + let scalars = [ + Scalar::from(0), + Scalar::from(1), + Scalar::from(2), + Scalar::from(1234), + Scalar::from(123456), + Scalar::from(123456789), + Scalar::from(0xffffffffffffffff), + Scalar(Fr::from(modulus_minus_one)), + ]; + + let multiplier = WindowedScalarMultiplier::::new( + ProjectivePoint::generator(), + ProjectivePoint::zero(), + ); + + for scalar in scalars { + let expected = ProjectivePoint::generator() * scalar; + let actual = multiplier.mul(&scalar); + assert_eq!(expected, actual); + } +} + +#[test] +fn test_double_mul() { + let multiplier = WindowedScalarMultiplier::::new( + ProjectivePoint::generator(), + ProjectivePoint::zero(), + ); + + for _ in 0..10 { + let other_point = ProjectivePoint::generator() * Scalar::rand(&mut thread_rng()); + let a = Scalar::rand(&mut thread_rng()); + let b = Scalar::rand(&mut thread_rng()); + let expected = ProjectivePoint::generator() * a + other_point * b; + let actual = multiplier.two_scalar_mul(&a, &other_point, &b); + assert_eq!(expected, actual); + } + + // Edge cases. + let p = ProjectivePoint::generator() * Scalar::rand(&mut thread_rng()); + let s = Scalar::rand(&mut thread_rng()); + assert_eq!( + multiplier.two_scalar_mul(&Scalar::from(0), &p, &Scalar::from(0)), + ProjectivePoint::zero() + ); + assert_eq!( + multiplier.two_scalar_mul(&s, &p, &Scalar::from(0)), + ProjectivePoint::generator() * s + ); + assert_eq!(multiplier.two_scalar_mul(&Scalar::from(0), &p, &s), p * s); +} diff --git a/fastcrypto/src/tests/secp384r1_tests.rs b/fastcrypto/src/tests/secp384r1_tests.rs new file mode 100644 index 0000000000..310be27180 --- /dev/null +++ b/fastcrypto/src/tests/secp384r1_tests.rs @@ -0,0 +1,799 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use p384::ecdsa::signature::hazmat::PrehashVerifier; +use p384::ecdsa::signature::{Signer as ExternalSigner, Verifier as ExternalVerifier}; +use p384::ecdsa::Signature; +use p384::elliptic_curve::scalar::IsHigh; +use proptest::{prelude::*, strategy::Strategy}; +use rand::{rngs::StdRng, SeedableRng as _}; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; +use std::str::FromStr; +use wycheproof::ecdsa::{TestName, TestSet}; +use wycheproof::TestResult; + +use crate::encoding::{Base64, Encoding}; +use crate::hash::{Blake2b256, Keccak256, Sha512}; +use crate::secp384r1::{ + SECP384R1_PRIVATE_KEY_LENGTH, SECP384R1_PUBLIC_KEY_LENGTH, SECP384R1_SIGNATURE_LENGTH, +}; +use crate::test_helpers::verify_serialization; +use crate::traits::Signer; +use crate::{ + hash::{HashFunction, Sha256, Sha384}, + secp384r1::{Secp384r1KeyPair, Secp384r1PrivateKey, Secp384r1PublicKey, Secp384r1Signature}, + test_helpers, + traits::{EncodeDecodeBase64, KeyPair, ToFromBytes, VerifyingKey}, +}; + +const MSG: &[u8] = b"Hello, world!"; + +pub fn keys() -> Vec { + let mut rng = StdRng::from_seed([0; 32]); + (0..4) + .map(|_| Secp384r1KeyPair::generate(&mut rng)) + .collect() +} + +#[test] +fn serialize_deserialize() { + let kp = keys().pop().unwrap(); + let pk = kp.public().clone(); + let sk = kp.private(); + let sig = keys().pop().unwrap().sign(MSG); + + verify_serialization(&pk, Some(pk.as_bytes())); + verify_serialization(&sk, Some(sk.as_bytes())); + verify_serialization(&sig, Some(sig.as_bytes())); + + let kp = keys().pop().unwrap(); + verify_serialization(&kp, Some(kp.as_bytes())); +} + +#[test] +fn import_export_public_key() { + let kpref = keys().pop().unwrap(); + let public_key = kpref.public(); + let export = public_key.encode_base64(); + let import = Secp384r1PublicKey::decode_base64(&export); + assert!(import.is_ok()); + assert_eq!(import.unwrap().as_ref(), public_key.as_ref()); +} + +#[test] +fn public_key_ordering() { + let pk1 = keys().pop().unwrap().public().clone(); + let pk2 = keys().pop().unwrap().public().clone(); + assert_eq!(pk1.as_bytes().cmp(pk2.as_bytes()), pk1.cmp(&pk2)); + assert_eq!( + pk1.as_bytes().cmp(pk2.as_bytes()), + pk1.partial_cmp(&pk2).unwrap() + ); +} + +#[test] +fn hash_public_key() { + let kpref = keys().pop().unwrap(); + let public_key = kpref.public(); + + let mut hasher = DefaultHasher::new(); + public_key.hash(&mut hasher); + let digest = hasher.finish(); + + let mut other_hasher = DefaultHasher::new(); + public_key.as_bytes().hash(&mut other_hasher); + let expected = other_hasher.finish(); + assert_eq!(expected, digest); +} + +#[test] +fn fmt_public_key() { + let kpref = keys().pop().unwrap(); + let public_key = kpref.public(); + assert_eq!( + public_key.to_string(), + Base64::encode(public_key.as_bytes()) + ); +} + +#[test] +fn public_key_from_bytes() { + let kp = keys().pop().unwrap(); + let pk = kp.public().clone(); + let pk_bytes = pk.as_ref(); + let rebuilt_pk = ::from_bytes(pk_bytes).unwrap(); + assert_eq!(rebuilt_pk, pk); + + // check for failure + let mut pk_bytes = pk.as_ref().to_vec(); + pk_bytes.pop(); + assert!(::from_bytes(&pk_bytes).is_err()); +} + +#[test] +fn import_export_secret_key() { + let kpref = keys().pop().unwrap(); + let secret_key = kpref.private(); + let export = secret_key.encode_base64(); + let import = Secp384r1PrivateKey::decode_base64(&export); + assert!(import.is_ok()); + assert_eq!(import.unwrap().as_ref(), secret_key.as_ref()); +} + +#[test] +#[cfg(feature = "copy_key")] +fn test_copy_key_pair() { + let kp = keys().pop().unwrap(); + let kp_copied = kp.copy(); + + assert_eq!(kp.public().as_bytes(), kp_copied.public().as_bytes()); + assert_eq!(kp.private().as_bytes(), kp_copied.private().as_bytes()); +} + +#[test] +#[cfg(feature = "copy_key")] +fn serialize_private_key_only_for_keypair() { + let keypairs = keys(); + keypairs.into_iter().for_each(|kp| { + let sk = kp.copy().private(); + let serialized_kp = bincode::serialize(&kp).unwrap(); + let serialized_sk = bincode::serialize(&sk).unwrap(); + assert_eq!(serialized_sk, serialized_kp); + }); +} + +#[test] +fn key_pair_from_string_roundtrip() { + let kp = keys().pop().unwrap(); + let kp_str = Base64::encode(kp.as_ref()); + let recovered = Secp384r1KeyPair::from_str(&kp_str).unwrap(); + assert_eq!(kp, recovered); +} + +#[test] +fn private_key_from_bytes() { + let kp = keys().pop().unwrap(); + let sk = kp.private(); + let sk_bytes = sk.as_ref(); + let rebuilt_sk = ::from_bytes(sk_bytes).unwrap(); + assert_eq!(rebuilt_sk, sk); + + // check for failure + let mut sk_bytes = sk.as_ref().to_vec(); + sk_bytes.pop(); + assert!(::from_bytes(&sk_bytes).is_err()); +} + +#[test] +fn non_canonical_secret_key() { + // Secret keys should be scalars between 0 and the base point order + + let zero = vec![0u8; SECP384R1_PRIVATE_KEY_LENGTH]; + assert!(Secp384r1PrivateKey::from_bytes(&zero).is_err()); + + let mut one = vec![0u8; SECP384R1_PRIVATE_KEY_LENGTH]; + one[SECP384R1_PRIVATE_KEY_LENGTH - 1] = 1; + assert!(Secp384r1PrivateKey::from_bytes(&one).is_ok()); + + let order_minus_one = hex::decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52972").unwrap(); + assert!(Secp384r1PrivateKey::from_bytes(&order_minus_one).is_ok()); + + let order = hex::decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973").unwrap(); + assert!(Secp384r1PrivateKey::from_bytes(&order).is_err()); +} + +#[test] +fn to_from_bytes_signature() { + let kpref = keys().pop().unwrap(); + let signature = kpref.sign(MSG); + let sig_bytes = signature.as_ref(); + let rebuilt_sig = ::from_bytes(sig_bytes).unwrap(); + assert_eq!(rebuilt_sig.as_ref(), signature.as_ref()); + // check for failure + let mut sig_bytes = signature.as_ref().to_vec(); + sig_bytes.pop(); + assert!(::from_bytes(&sig_bytes).is_err()); +} + +#[test] +fn fail_on_r_or_s_zero() { + // Verification (split_scalars) panics if r or s is zero, so we check that this is caught in deserialization. + + // Build valid signature + let signature = keys().pop().unwrap().sign(MSG); + let sig_bytes = signature.as_ref(); + + // Set r to zero + let mut r_is_zero = [0u8; SECP384R1_SIGNATURE_LENGTH]; + r_is_zero[48..96].copy_from_slice(&sig_bytes[48..96]); + assert!(::from_bytes(&r_is_zero).is_err()); + + // Set s to zero + let mut s_is_zero = [0u8; SECP384R1_SIGNATURE_LENGTH]; + s_is_zero[0..48].copy_from_slice(&sig_bytes[0..48]); + assert!(::from_bytes(&s_is_zero).is_err()); +} + +#[test] +fn hash_signature() { + let sig = keys().pop().unwrap().sign(MSG); + + let mut hasher = DefaultHasher::new(); + sig.hash(&mut hasher); + let digest = hasher.finish(); + + let mut other_hasher = DefaultHasher::new(); + sig.as_bytes().hash(&mut other_hasher); + let expected = other_hasher.finish(); + assert_eq!(expected, digest); +} + +#[test] +fn fmt_signature() { + let sig = keys().pop().unwrap().sign(MSG); + assert_eq!(sig.to_string(), Base64::encode(sig.as_bytes())); +} + +#[test] +fn verify_valid_signature() { + // Get a keypair. + let kp = keys().pop().unwrap(); + + // Sign over raw message, hashed to sha384. + let digest = Sha384::digest(MSG); + + let signature = kp.sign(digest.as_ref()); + + // Verify the signature. + assert!(kp.public().verify(digest.as_ref(), &signature).is_ok()); +} + +#[test] +fn verify_hashed_failed_if_different_hash() { + // Get a keypair. + let kp = keys().pop().unwrap(); + + // Sign over raw message, hashed to keccak256. + let message: &[u8] = &[0u8; 1]; + let signature = kp.sign_with_hash::(message); + + // Verify the signature using other hash functions fails. + assert!(kp + .public() + .verify_with_hash::(message, &signature) + .is_err()); + assert!(kp + .public() + .verify_with_hash::(message, &signature) + .is_err()); + + // Verify the signature using the same hash function succeeds. + assert!(kp + .public() + .verify_with_hash::(message, &signature) + .is_ok()); +} + +#[test] +fn sign_and_verify_with_sha256() { + let kp = keys().pop().unwrap(); + let signature = kp.sign_with_hash::(MSG); + assert!(kp + .public() + .verify_with_hash::(MSG, &signature) + .is_ok()); + + // The signature is also valid for the p384 crate using the same (padded) prehash. + let external_pk = p384::ecdsa::VerifyingKey::from_sec1_bytes(kp.public().as_ref()).unwrap(); + let external_sig = Signature::from_slice(signature.as_ref()).unwrap(); + assert!(external_pk + .verify_prehash(&Sha256::digest(MSG).digest, &external_sig) + .is_ok()); +} + +fn signature_test_inputs() -> (Vec, Vec, Vec) { + // Make signatures. + let digest = Sha384::digest(MSG); + let (pubkeys, signatures): (Vec, Vec) = keys() + .into_iter() + .take(3) + .map(|kp| { + let sig = kp.sign(digest.as_ref()); + (kp.public().clone(), sig) + }) + .unzip(); + + (digest.to_vec(), pubkeys, signatures) +} + +#[test] +fn verify_valid_batch() { + let (digest, pubkeys, signatures) = signature_test_inputs(); + + let res = Secp384r1PublicKey::verify_batch_empty_fail(&digest[..], &pubkeys, &signatures); + assert!(res.is_ok(), "{:?}", res); +} + +#[test] +fn verify_invalid_batch() { + let (digest, pubkeys, mut signatures) = signature_test_inputs(); + // mangle one signature + signatures.swap(0, 1); + + assert!(Secp384r1PublicKey::verify_batch_empty_fail(&digest, &pubkeys, &signatures).is_err()) +} + +#[test] +fn verify_empty_batch() { + let (digest, _, _) = signature_test_inputs(); + + let res = Secp384r1PublicKey::verify_batch_empty_fail(&digest[..], &[], &[]); + assert!(res.is_err(), "{:?}", res); +} + +#[test] +fn verify_batch_missing_public_keys() { + let (digest, pubkeys, signatures) = signature_test_inputs(); + + // missing leading public keys + let res = Secp384r1PublicKey::verify_batch_empty_fail(&digest, &pubkeys[1..], &signatures); + assert!(res.is_err(), "{:?}", res); + + // missing trailing public keys + let res = Secp384r1PublicKey::verify_batch_empty_fail( + &digest, + &pubkeys[..pubkeys.len() - 1], + &signatures, + ); + assert!(res.is_err(), "{:?}", res); +} + +#[test] +fn verify_invalid_signature() { + // Get a keypair. + let kp = keys().pop().unwrap(); + + // Make signature. + let digest = Sha384::digest(MSG); + + // Verify the signature against good digest passes. + let signature = kp.sign(digest.as_ref()); + assert!(kp.public().verify(digest.as_ref(), &signature).is_ok()); + + // Verify the signature against bad digest fails. + let bad_message: &[u8] = b"Bad message!"; + let digest = Sha384::digest(bad_message); + + assert!(kp.public().verify(digest.as_ref(), &signature).is_err()); +} + +#[test] +fn verify_valid_batch_different_msg() { + let inputs = test_helpers::signature_test_inputs_different_msg::(); + let res = Secp384r1PublicKey::verify_batch_empty_fail_different_msg( + &inputs.digests, + &inputs.pubkeys, + &inputs.signatures, + ); + assert!(res.is_ok(), "{:?}", res); +} + +#[test] +fn verify_invalid_batch_different_msg() { + let mut inputs = test_helpers::signature_test_inputs_different_msg::(); + inputs.signatures.swap(0, 1); + let res = Secp384r1PublicKey::verify_batch_empty_fail_different_msg( + &inputs.digests, + &inputs.pubkeys, + &inputs.signatures, + ); + assert!(res.is_err(), "{:?}", res); +} + +#[test] +fn verify_high_s_signature() { + // Unlike secp256r1, high-s signatures are valid (matching the p384 crate and standard ECDSA + // implementations, e.g. for X.509 certificate chains). + let kp = keys().pop().unwrap(); + let signature = kp.sign(MSG); + + // Compute the malleated signature (r, n - s) and pick the high-s variant. + let (r, s) = signature.sig.split_scalars(); + let high_s = if bool::from(s.is_high()) { + *s.as_ref() + } else { + -*s.as_ref() + }; + let high_sig_external = Signature::from_scalars(r.to_bytes(), high_s.to_bytes()).unwrap(); + let high_sig = Secp384r1Signature::from_bytes(high_sig_external.to_bytes().as_slice()).unwrap(); + assert!(bool::from(high_sig.sig.s().is_high())); + + // Both this implementation and the p384 crate accept the high-s signature. + assert!(kp.public().verify(MSG, &high_sig).is_ok()); + let external_pk = p384::ecdsa::VerifyingKey::from_sec1_bytes(kp.public().as_ref()).unwrap(); + assert!(external_pk.verify(MSG, &high_sig_external).is_ok()); +} + +#[test] +fn test_sk_zeroization_on_drop() { + let ptr: *const u8; + let bytes_ptr: *const u8; + + let mut sk_bytes = Vec::new(); + let mut privkey_memory_before_drop = Vec::new(); + + { + let mut rng = StdRng::from_seed([9; 32]); + let kp = Secp384r1KeyPair::generate(&mut rng); + let sk = kp.private(); + sk_bytes.extend_from_slice(sk.as_ref()); + + ptr = std::ptr::addr_of!(sk.privkey) as *const u8; + bytes_ptr = &sk.as_ref()[0] as *const u8; + + // The in-memory representation of the private key before it is dropped. + privkey_memory_before_drop.extend_from_slice(unsafe { + std::slice::from_raw_parts(ptr, SECP384R1_PRIVATE_KEY_LENGTH) + }); + + let sk_memory: &[u8] = + unsafe { std::slice::from_raw_parts(bytes_ptr, SECP384R1_PRIVATE_KEY_LENGTH) }; + // Assert that this is equal to sk_bytes before deletion + assert_eq!(sk_memory, &sk_bytes[..]); + } + + // Check that the memory of self.privkey has been overwritten. Contrary to the corresponding + // test for secp256r1, we do not check for an exact pattern here because the in-memory + // representation of a p384 scalar is an implementation detail of the p384 crate. + let privkey_memory: &[u8] = + unsafe { std::slice::from_raw_parts(ptr, SECP384R1_PRIVATE_KEY_LENGTH) }; + assert_ne!(privkey_memory, &privkey_memory_before_drop[..]); + + // Check that self.bytes is zeroized + let sk_memory: &[u8] = + unsafe { std::slice::from_raw_parts(bytes_ptr, SECP384R1_PRIVATE_KEY_LENGTH) }; + assert_ne!(sk_memory, &sk_bytes[..]); +} + +#[test] +fn wycheproof_test() { + // Wycheproof does not have a test set for secp384r1 with SHA-256, so we use the SHA-384 and + // SHA-512 sets. + run_wycheproof_test(TestName::EcdsaSecp384r1Sha384); + run_wycheproof_test(TestName::EcdsaSecp384r1Sha512); +} + +fn run_wycheproof_test(test_name: TestName) { + let test_set = TestSet::load(test_name).unwrap(); + for test_group in test_set.test_groups { + let pk = Secp384r1PublicKey::from_bytes(&test_group.key.key).unwrap(); + let external_pk = p384::ecdsa::VerifyingKey::from_sec1_bytes(&test_group.key.key).unwrap(); + for test in test_group.tests { + // DER parsing accepts exactly the same encodings as the p384 crate. + assert_eq!( + Secp384r1Signature::from_der(&test.sig).is_ok(), + Signature::from_der(&test.sig).is_ok(), + "{}", + test.comment + ); + let signature = match Secp384r1Signature::from_der(&test.sig) { + Ok(s) => s, + Err(_) => { + assert_eq!(map_result(test.result), TestResult::Invalid); + continue; + } + }; + + let our_result = match test_name { + TestName::EcdsaSecp384r1Sha384 => pk.verify(&test.msg, &signature), + TestName::EcdsaSecp384r1Sha512 => { + pk.verify_with_hash::(&test.msg, &signature) + } + _ => panic!("Unsupported test set"), + }; + + // This implementation accepts exactly the same signatures as the p384 crate. + let external_digest = match test_name { + TestName::EcdsaSecp384r1Sha384 => Sha384::digest(&test.msg).to_vec(), + TestName::EcdsaSecp384r1Sha512 => Sha512::digest(&test.msg).to_vec(), + _ => panic!("Unsupported test set"), + }; + let external_result = external_pk.verify_prehash(&external_digest, &signature.sig); + assert_eq!( + our_result.is_ok(), + external_result.is_ok(), + "{}", + test.comment + ); + + let expected = map_result(test.result); + let actual = if our_result.is_ok() { + TestResult::Valid + } else { + TestResult::Invalid + }; + assert_eq!(expected, actual, "{}", test.comment); + } + } +} + +fn map_result(t: TestResult) -> TestResult { + match t { + TestResult::Valid => TestResult::Valid, + _ => TestResult::Invalid, // Treat Acceptable as Invalid + } +} + +#[test] +fn test_from_der() { + let kp = keys().pop().unwrap(); + let signature = kp.sign(MSG); + + // Round-trip through the DER encoding. + let der = signature.sig.to_der(); + let parsed = Secp384r1Signature::from_der(der.as_bytes()).unwrap(); + assert_eq!(parsed, signature); + assert!(kp.public().verify(MSG, &parsed).is_ok()); + + // as_ref returns the fixed-length encoding, not the DER encoding. + assert_eq!(parsed.as_ref(), signature.as_ref()); + + // Invalid inputs are rejected. + assert!(Secp384r1Signature::from_der(&[]).is_err()); + let truncated = &der.as_bytes()[..der.as_bytes().len() - 1]; + assert!(Secp384r1Signature::from_der(truncated).is_err()); + + // The fixed-length encoding is not valid DER. + assert!(Secp384r1Signature::from_der(signature.as_ref()).is_err()); +} + +#[test] +fn test_rfc6979_vectors() { + // Test vectors from RFC 6979, appendix A.2.6 (ECDSA over P-384 with SHA-384). + let sk = Secp384r1PrivateKey::from_bytes(&hex::decode("6B9D3DAD2E1B8C1C05B19875B6659F4DE23C3B667BF297BA9AA47740787137D896D5724E4C70A825F872C9EA60D2EDF5").unwrap()).unwrap(); + let kp = Secp384r1KeyPair::from(sk); + + let sig = kp.sign(b"sample"); + assert_eq!(hex::encode(sig.as_ref()).to_uppercase(), "94EDBB92A5ECB8AAD4736E56C691916B3F88140666CE9FA73D64C4EA95AD133C81A648152E44ACF96E36DD1E80FABE4699EF4AEB15F178CEA1FE40DB2603138F130E740A19624526203B6351D0A3A94FA329C145786E679E7B82C71A38628AC8"); + + let sig = kp.sign(b"test"); + assert_eq!(hex::encode(sig.as_ref()).to_uppercase(), "8203B63D3C853E8D77227FB377BCF7B7B772E97892A80F36AB775D509D7A5FEB0542A7F0812998DA8F1DD3CA3CF023DBDDD0760448D42D8A43AF45AF836FCE4DE8BE06B485E9B61B827C2F13173923E06A739F040649A667BF3B828246BAA5A5"); +} + +#[test] +fn test_sign_matches_p384_crate() { + // The signatures produced by this implementation are identical to those produced by the p384 + // crate on the same inputs. + let messages: [&[u8]; 4] = [b"Hello, world!", b"", &[0u8; 100], &[0xffu8; 48]]; + for kp in keys() { + let external_sk = p384::ecdsa::SigningKey::from_slice(kp.as_ref()).unwrap(); + for msg in messages { + let signature = kp.sign(msg); + let external_sig: Signature = external_sk.sign(msg); + assert_eq!(signature.as_ref(), external_sig.to_bytes().as_slice()); + } + } +} + +#[test] +fn test_verify_signature_from_p384_crate() { + // Signatures produced by the p384 crate are accepted by this implementation and vice versa. + let kp = keys().pop().unwrap(); + let external_sk = p384::ecdsa::SigningKey::from_slice(kp.as_ref()).unwrap(); + let external_pk = p384::ecdsa::VerifyingKey::from(&external_sk); + + let external_sig: Signature = external_sk.sign(MSG); + let signature = Secp384r1Signature::from_bytes(external_sig.to_bytes().as_slice()).unwrap(); + assert!(kp.public().verify(MSG, &signature).is_ok()); + + let signature = kp.sign(MSG); + let external_sig = Signature::from_slice(signature.as_ref()).unwrap(); + assert!(external_pk.verify(MSG, &external_sig).is_ok()); +} + +#[test] +fn dont_display_secrets() { + let keypairs = keys(); + keypairs.into_iter().for_each(|keypair| { + let sk = keypair.private(); + assert_eq!(format!("{}", sk), ""); + assert_eq!( + format!("{:?}", sk), + "" + ); + }); +} + +#[test] +fn test_public_key_and_signature_lengths() { + let kp = keys().pop().unwrap(); + assert_eq!(kp.public().as_ref().len(), SECP384R1_PUBLIC_KEY_LENGTH); + assert_eq!(kp.as_ref().len(), SECP384R1_PRIVATE_KEY_LENGTH); + let sig = kp.sign(MSG); + assert_eq!(sig.as_ref().len(), SECP384R1_SIGNATURE_LENGTH); +} + +/// Differential fuzzing of this implementation against the p384 crate. Each iteration signs a +/// random message with both implementations, asserting that the signatures are identical, and +/// verifies a signature variant (valid, bit-flipped, edge-case scalars, random bytes, wrong key, +/// or malleated) with both implementations, asserting that they agree on acceptance for SHA-256, +/// SHA-384 and SHA-512 message digests. +/// +/// Runs 100 iterations by default. For a longer session, run e.g.: +/// `SECP384R1_FUZZ_ITERATIONS=100000 cargo test --release --features experimental +/// fuzz_differential_against_p384_crate -- --nocapture` +#[test] +fn fuzz_differential_against_p384_crate() { + use rand::{Rng, RngCore}; + + let iterations: usize = std::env::var("SECP384R1_FUZZ_ITERATIONS") + .ok() + .and_then(|i| i.parse().ok()) + .unwrap_or(100); + + // Use a random seed and print it to allow reproducing a failure (run with --nocapture). + let seed: [u8; 32] = rand::thread_rng().gen(); + println!("Differential fuzzing seed: {}", hex::encode(seed)); + let mut rng = StdRng::from_seed(seed); + + let keypairs: Vec = (0..4) + .map(|_| Secp384r1KeyPair::generate(&mut rng)) + .collect(); + let external_keys: Vec<(p384::ecdsa::SigningKey, p384::ecdsa::VerifyingKey)> = keypairs + .iter() + .map(|kp| { + let sk = p384::ecdsa::SigningKey::from_slice(kp.as_ref()).unwrap(); + let vk = p384::ecdsa::VerifyingKey::from(&sk); + (sk, vk) + }) + .collect(); + + // Edge case scalar encodings: 1, 2, n-1 and a value larger than the group order. + let mut one = [0u8; 48]; + one[47] = 1; + let mut two = [0u8; 48]; + two[47] = 2; + let n_minus_one: [u8; 48] = hex::decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52972").unwrap().try_into().unwrap(); + let too_large = [0xffu8; 48]; + let edge_scalars = [one, two, n_minus_one, too_large]; + + for _ in 0..iterations { + let i = rng.gen_range(0..keypairs.len()); + let kp = &keypairs[i]; + let (external_sk, external_vk) = &external_keys[i]; + + let mut msg = vec![0u8; rng.gen_range(0..100)]; + rng.fill_bytes(&mut msg); + + // Differential signing: both implementations produce identical signatures. + let signature = kp.sign(&msg); + let external_sig: Signature = external_sk.sign(&msg); + assert_eq!(signature.as_ref(), external_sig.to_bytes().as_slice()); + + // Build a signature variant to verify. + let mut sig_bytes = <[u8; 96]>::try_from(signature.as_ref()).unwrap(); + match rng.gen_range(0..6) { + // Valid signature. + 0 => (), + // Single bit flip. + 1 => { + let byte = rng.gen_range(0..96); + sig_bytes[byte] ^= 1 << rng.gen_range(0..8); + } + // Replace r or s with an edge-case scalar. + 2 => { + let scalar = edge_scalars[rng.gen_range(0..edge_scalars.len())]; + let offset = if rng.gen::() { 0 } else { 48 }; + sig_bytes[offset..offset + 48].copy_from_slice(&scalar); + } + // Random bytes. + 3 => rng.fill_bytes(&mut sig_bytes), + // Valid signature by a (potentially) different key. + 4 => { + let other = &keypairs[rng.gen_range(0..keypairs.len())]; + sig_bytes.copy_from_slice(other.sign(&msg).as_ref()); + } + // Malleated signature (r, n - s). + 5 => { + let (r, s) = signature.sig.split_scalars(); + let malleated = + Signature::from_scalars(r.to_bytes(), (-*s.as_ref()).to_bytes()).unwrap(); + sig_bytes.copy_from_slice(&malleated.to_bytes()); + } + _ => unreachable!(), + }; + + // Both implementations agree on whether the bytes parse... + let (sig, external_sig) = match ( + ::from_bytes(&sig_bytes), + Signature::from_slice(&sig_bytes), + ) { + (Ok(sig), Ok(external_sig)) => (sig, external_sig), + (Err(_), Err(_)) => continue, + (ours, theirs) => panic!( + "Parsing disagreement on signature {}: ours: {}, p384: {}", + hex::encode(sig_bytes), + ours.is_ok(), + theirs.is_ok() + ), + }; + + // ...and on whether the signature is valid, for all supported digest lengths. + let pk = kp.public(); + let results = [ + ( + pk.verify_with_hash::(&msg, &sig).is_ok(), + Sha256::digest(&msg).to_vec(), + ), + ( + pk.verify_with_hash::(&msg, &sig).is_ok(), + Sha384::digest(&msg).to_vec(), + ), + ( + pk.verify_with_hash::(&msg, &sig).is_ok(), + Sha512::digest(&msg).to_vec(), + ), + ]; + for (ours, digest) in results { + let theirs = external_vk.verify_prehash(&digest, &external_sig).is_ok(); + assert_eq!( + ours, + theirs, + "Verification disagreement: signature {}, message {}, digest length {}", + hex::encode(sig_bytes), + hex::encode(&msg), + digest.len() + ); + } + } +} + +// Arbitrary implementations for the proptests +fn arb_keypair() -> impl Strategy { + any::<[u8; 32]>() + .prop_map(|seed| { + let mut rng = StdRng::from_seed(seed); + Secp384r1KeyPair::generate(&mut rng) + }) + .no_shrink() +} + +proptest! { + #[test] + fn test_keypair_roundtrip( + kp in arb_keypair(), + ){ + let serialized = bincode::serialize(&kp).unwrap(); + let deserialized: Secp384r1KeyPair = bincode::deserialize(&serialized).unwrap(); + assert_eq!(kp.public(), deserialized.public()); + } +} + +proptest! { + #![proptest_config(ProptestConfig::with_cases(64))] + #[test] + fn test_equivalence_to_p384_crate( + kp in arb_keypair(), + msg in any::>(), + ){ + // Sign and verify with this implementation. + let signature = kp.sign(&msg); + assert!(kp.public().verify(&msg, &signature).is_ok()); + + // The p384 crate produces an identical signature and accepts ours. + let external_sk = p384::ecdsa::SigningKey::from_slice(kp.as_ref()).unwrap(); + let external_pk = p384::ecdsa::VerifyingKey::from(&external_sk); + let external_sig: Signature = external_sk.sign(&msg); + assert_eq!(signature.as_ref(), external_sig.to_bytes().as_slice()); + assert!(external_pk.verify(&msg, &external_sig).is_ok()); + + // A modified signature is rejected by both implementations. + let mut modified = <[u8; 96]>::try_from(signature.as_ref()).unwrap(); + modified[0] ^= 1; + if let Ok(modified_sig) = ::from_bytes(&modified) { + assert!(kp.public().verify(&msg, &modified_sig).is_err()); + assert!(external_pk.verify(&msg, &modified_sig.sig).is_err()); + } + } +}