-
Notifications
You must be signed in to change notification settings - Fork 198
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
p521: initial
ecdsa
support with FIPS 186-4 test vectors
Initial `ecdsa` feature and test vectors to ensure it's working. Due to P-521's unusual modulus size, we can't use the upstream `ecdsa::{SigningKey, VerifyingKey}` types (although we are able to use the generic implementation of ECDSA). The `ecdsa` crate currently includes bounds for the digest size in several places, including the RFC6979 implementation, which are incompatible with P-521, which uses a 66-byte serialized scalar size along with SHA-521, which emits a 64-byte digest. To work around this, newtypes for `SigningKey` and `VerifyingKey` have been added. They largely wrap the inner types, but don't use RFC6979 and instead randomly generate `R` each time signing is performed (using an on-by-default `getrandom` feature which enables `rand_core::OsRng`). FIPS 186-4 test vectors adapted from `SigGen.txt` in `186-4ecdsatestvectors.zip` from: https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/digital-signatures
- Loading branch information
Showing
7 changed files
with
430 additions
and
13 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,249 @@ | ||
//! Elliptic Curve Digital Signature Algorithm (ECDSA) | ||
//! | ||
//! This module contains support for computing and verifying ECDSA signatures. | ||
//! To use it, you will need to enable one of the two following Cargo features: | ||
//! | ||
//! - `ecdsa-core`: provides only the [`Signature`] type (which represents an | ||
//! ECDSA/P-521 signature). Does not require the `arithmetic` feature. This is | ||
//! useful for 3rd-party crates which wish to use the `Signature` type for | ||
//! interoperability purposes (particularly in conjunction with the | ||
//! [`signature::Signer`] trait. Example use cases for this include other | ||
//! software implementations of ECDSA/P-521 and wrappers for cloud KMS | ||
//! services or hardware devices (HSM or crypto hardware wallet). | ||
//! - `ecdsa`: provides `ecdsa-core` features plus the [`SigningKey`] and | ||
//! [`VerifyingKey`] types which natively implement ECDSA/P-521 signing and | ||
//! verification. | ||
//! | ||
//! ## Signing/Verification Example | ||
//! | ||
//! This example requires the `ecdsa` Cargo feature is enabled: | ||
//! | ||
//! ``` | ||
//! # #[cfg(feature = "ecdsa")] | ||
//! # { | ||
//! use p521::ecdsa::{signature::Signer, Signature, SigningKey}; | ||
//! use rand_core::OsRng; // requires 'getrandom' feature | ||
//! | ||
//! // Signing | ||
//! let signing_key = SigningKey::random(&mut OsRng); // Serialize with `::to_bytes()` | ||
//! let message = b"ECDSA proves knowledge of a secret number in the context of a single message"; | ||
//! let signature: Signature = signing_key.sign(message); | ||
//! | ||
//! // Verification | ||
//! use p521::ecdsa::{signature::Verifier, VerifyingKey}; | ||
//! | ||
//! let verifying_key = VerifyingKey::from(&signing_key); // Serialize with `::to_encoded_point()` | ||
//! assert!(verifying_key.verify(message, &signature).is_ok()); | ||
//! # } | ||
//! ``` | ||
// TODO(tarcieri): use RFC6979 + upstream types from the `ecdsa` crate | ||
|
||
pub use ecdsa_core::signature::{self, Error, Result}; | ||
|
||
#[cfg(feature = "ecdsa")] | ||
use { | ||
crate::{AffinePoint, EncodedPoint, FieldBytes, NonZeroScalar, Scalar}, | ||
ecdsa_core::{ | ||
hazmat::{bits2field, sign_prehashed, SignPrimitive, VerifyPrimitive}, | ||
signature::{ | ||
hazmat::{PrehashVerifier, RandomizedPrehashSigner}, | ||
rand_core::CryptoRngCore, | ||
RandomizedSigner, Verifier, | ||
}, | ||
}, | ||
elliptic_curve::Field, | ||
sha2::{Digest, Sha512}, | ||
}; | ||
|
||
#[cfg(all(feature = "ecdsa", feature = "getrandom"))] | ||
use { | ||
ecdsa_core::signature::{hazmat::PrehashSigner, Signer}, | ||
rand_core::OsRng, | ||
}; | ||
|
||
use super::NistP521; | ||
|
||
/// ECDSA/P-521 signature (fixed-size) | ||
pub type Signature = ecdsa_core::Signature<NistP521>; | ||
|
||
/// ECDSA/P-521 signature (ASN.1 DER encoded) | ||
pub type DerSignature = ecdsa_core::der::Signature<NistP521>; | ||
|
||
#[cfg(feature = "ecdsa")] | ||
impl SignPrimitive<NistP521> for Scalar {} | ||
|
||
#[cfg(feature = "ecdsa")] | ||
impl VerifyPrimitive<NistP521> for AffinePoint {} | ||
|
||
/// ECDSA/P-521 signing key | ||
#[cfg(feature = "ecdsa")] | ||
#[derive(Clone)] | ||
pub struct SigningKey(ecdsa_core::SigningKey<NistP521>); | ||
|
||
#[cfg(feature = "ecdsa")] | ||
impl SigningKey { | ||
/// Generate a cryptographically random [`SigningKey`]. | ||
pub fn random(rng: &mut impl CryptoRngCore) -> Self { | ||
ecdsa_core::SigningKey::<NistP521>::random(rng).into() | ||
} | ||
|
||
/// Initialize signing key from a raw scalar serialized as a byte array. | ||
pub fn from_bytes(bytes: &FieldBytes) -> Result<Self> { | ||
ecdsa_core::SigningKey::<NistP521>::from_bytes(bytes).map(Into::into) | ||
} | ||
|
||
/// Initialize signing key from a raw scalar serialized as a byte slice. | ||
pub fn from_slice(bytes: &[u8]) -> Result<Self> { | ||
ecdsa_core::SigningKey::<NistP521>::from_slice(bytes).map(Into::into) | ||
} | ||
|
||
/// Serialize this [`SigningKey`] as bytes | ||
pub fn to_bytes(&self) -> FieldBytes { | ||
self.0.to_bytes() | ||
} | ||
|
||
/// Borrow the secret [`NonZeroScalar`] value for this key. | ||
/// | ||
/// # ⚠️ Warning | ||
/// | ||
/// This value is key material. | ||
/// | ||
/// Please treat it with the care it deserves! | ||
pub fn as_nonzero_scalar(&self) -> &NonZeroScalar { | ||
self.0.as_nonzero_scalar() | ||
} | ||
|
||
/// Get the [`VerifyingKey`] which corresponds to this [`SigningKey`]. | ||
#[cfg(feature = "verifying")] | ||
pub fn verifying_key(&self) -> VerifyingKey { | ||
VerifyingKey::from(self) | ||
} | ||
} | ||
|
||
#[cfg(feature = "ecdsa")] | ||
impl From<ecdsa_core::SigningKey<NistP521>> for SigningKey { | ||
fn from(inner: ecdsa_core::SigningKey<NistP521>) -> SigningKey { | ||
SigningKey(inner) | ||
} | ||
} | ||
|
||
#[cfg(all(feature = "ecdsa", feature = "getrandom"))] | ||
impl PrehashSigner<Signature> for SigningKey { | ||
fn sign_prehash(&self, prehash: &[u8]) -> Result<Signature> { | ||
self.sign_prehash_with_rng(&mut OsRng, prehash) | ||
} | ||
} | ||
|
||
#[cfg(feature = "ecdsa")] | ||
impl RandomizedPrehashSigner<Signature> for SigningKey { | ||
fn sign_prehash_with_rng( | ||
&self, | ||
rng: &mut impl CryptoRngCore, | ||
prehash: &[u8], | ||
) -> Result<Signature> { | ||
let z = bits2field::<NistP521>(prehash)?; | ||
let k = Scalar::random(rng); | ||
sign_prehashed(self.0.as_nonzero_scalar().as_ref(), k, &z).map(|sig| sig.0) | ||
} | ||
} | ||
|
||
#[cfg(feature = "ecdsa")] | ||
impl RandomizedSigner<Signature> for SigningKey { | ||
fn try_sign_with_rng(&self, rng: &mut impl CryptoRngCore, msg: &[u8]) -> Result<Signature> { | ||
self.sign_prehash_with_rng(rng, &Sha512::digest(msg)) | ||
} | ||
} | ||
|
||
#[cfg(all(feature = "ecdsa", feature = "getrandom"))] | ||
impl Signer<Signature> for SigningKey { | ||
fn try_sign(&self, msg: &[u8]) -> Result<Signature> { | ||
self.try_sign_with_rng(&mut OsRng, msg) | ||
} | ||
} | ||
|
||
/// ECDSA/P-521 verification key (i.e. public key) | ||
#[cfg(feature = "ecdsa")] | ||
#[derive(Clone)] | ||
pub struct VerifyingKey(ecdsa_core::VerifyingKey<NistP521>); | ||
|
||
#[cfg(feature = "ecdsa")] | ||
impl VerifyingKey { | ||
/// Initialize [`VerifyingKey`] from a SEC1-encoded public key. | ||
pub fn from_sec1_bytes(bytes: &[u8]) -> Result<Self> { | ||
ecdsa_core::VerifyingKey::<NistP521>::from_sec1_bytes(bytes).map(Into::into) | ||
} | ||
|
||
/// Initialize [`VerifyingKey`] from an affine point. | ||
/// | ||
/// Returns an [`Error`] if the given affine point is the additive identity | ||
/// (a.k.a. point at infinity). | ||
pub fn from_affine(affine: AffinePoint) -> Result<Self> { | ||
ecdsa_core::VerifyingKey::<NistP521>::from_affine(affine).map(Into::into) | ||
} | ||
|
||
/// Initialize [`VerifyingKey`] from an [`EncodedPoint`]. | ||
pub fn from_encoded_point(public_key: &EncodedPoint) -> Result<Self> { | ||
ecdsa_core::VerifyingKey::<NistP521>::from_encoded_point(public_key).map(Into::into) | ||
} | ||
|
||
/// Serialize this [`VerifyingKey`] as a SEC1 [`EncodedPoint`], optionally | ||
/// applying point compression. | ||
pub fn to_encoded_point(&self, compress: bool) -> EncodedPoint { | ||
self.0.to_encoded_point(compress) | ||
} | ||
|
||
/// Borrow the inner [`AffinePoint`] for this public key. | ||
pub fn as_affine(&self) -> &AffinePoint { | ||
self.0.as_affine() | ||
} | ||
} | ||
|
||
#[cfg(feature = "ecdsa")] | ||
impl From<&SigningKey> for VerifyingKey { | ||
fn from(signing_key: &SigningKey) -> VerifyingKey { | ||
Self::from(*signing_key.0.verifying_key()) | ||
} | ||
} | ||
|
||
#[cfg(feature = "ecdsa")] | ||
impl From<ecdsa_core::VerifyingKey<NistP521>> for VerifyingKey { | ||
fn from(inner: ecdsa_core::VerifyingKey<NistP521>) -> VerifyingKey { | ||
VerifyingKey(inner) | ||
} | ||
} | ||
|
||
#[cfg(feature = "ecdsa")] | ||
impl PrehashVerifier<Signature> for VerifyingKey { | ||
fn verify_prehash(&self, prehash: &[u8], signature: &Signature) -> Result<()> { | ||
self.0.verify_prehash(prehash, signature) | ||
} | ||
} | ||
|
||
#[cfg(feature = "ecdsa")] | ||
impl Verifier<Signature> for VerifyingKey { | ||
fn verify(&self, msg: &[u8], signature: &Signature) -> Result<()> { | ||
self.verify_prehash(&Sha512::digest(msg), signature) | ||
} | ||
} | ||
|
||
#[cfg(all(test, feature = "ecdsa", feature = "getrandom"))] | ||
mod tests { | ||
// TODO(tarcieri): RFC6979 support + test vectors | ||
|
||
mod sign { | ||
use crate::{test_vectors::ecdsa::ECDSA_TEST_VECTORS, NistP521}; | ||
ecdsa_core::new_signing_test!(NistP521, ECDSA_TEST_VECTORS); | ||
} | ||
|
||
mod verify { | ||
use crate::{test_vectors::ecdsa::ECDSA_TEST_VECTORS, NistP521}; | ||
ecdsa_core::new_verification_test!(NistP521, ECDSA_TEST_VECTORS); | ||
} | ||
|
||
// TODO(tarcieri): wycheproof test vectors | ||
// mod wycheproof { | ||
// use crate::NistP521; | ||
// ecdsa_core::new_wycheproof_test!(wycheproof, "wycheproof", NistP521); | ||
// } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
//! secp521r1 test vectors. | ||
#[cfg(test)] | ||
pub mod ecdsa; | ||
pub mod group; |
Oops, something went wrong.