Skip to content
Merged
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
24 changes: 23 additions & 1 deletion Cargo.lock

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

7 changes: 4 additions & 3 deletions ecdsa/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@ keywords = ["crypto", "ecc", "nist", "secp256k1", "signature"]

[dependencies]
elliptic-curve = { version = "0.5", default-features = false, features = ["weierstrass"] }
hmac = { version = "0.9", optional = true, default-features = false }
signature = { version = ">= 1.2.2, < 1.3.0", default-features = false }

[features]
default = ["digest", "std"]
default = ["digest"]
dev = []
digest = ["signature/digest-preview"]
digest = ["elliptic-curve/digest", "signature/digest-preview"]
hazmat = []
rand = ["elliptic-curve/rand", "signature/rand-preview"]
signer = ["digest", "hazmat", "rand", "zeroize"] # TODO(tarcieri): deterministic signing
signer = ["digest", "hazmat", "hmac", "zeroize"]
std = ["elliptic-curve/std", "signature/std"]
verifier = ["digest", "hazmat"]
zeroize = ["elliptic-curve/zeroize"]
Expand Down
95 changes: 73 additions & 22 deletions ecdsa/src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,27 @@
//! Requires an [`elliptic_curve::Arithmetic`] impl on the curve, and a
//! [`SignPrimitive`] impl on its associated `Scalar` type.

// TODO(tarcieri): RFC 6979; support for hardware crypto accelerators
// TODO(tarcieri): support for hardware crypto accelerators

mod rfc6979;

use crate::{
hazmat::{DigestPrimitive, SignPrimitive},
Error, Signature, SignatureSize,
};
use elliptic_curve::{
generic_array::ArrayLength,
ops::Invert,
weierstrass::Curve,
zeroize::{Zeroize, Zeroizing},
Arithmetic, FromBytes, SecretKey,
generic_array::ArrayLength, ops::Invert, scalar::NonZeroScalar, weierstrass::Curve,
zeroize::Zeroize, Arithmetic, ElementBytes, FromBytes, FromDigest, SecretKey,
};
use signature::{
digest::{BlockInput, Digest, FixedOutput, Reset, Update},
DigestSigner,
};

#[cfg(feature = "rand")]
use {
elliptic_curve::Generate,
signature::{
digest::Digest,
rand_core::{CryptoRng, RngCore},
RandomizedDigestSigner, RandomizedSigner,
},
use signature::{
rand_core::{CryptoRng, RngCore},
RandomizedDigestSigner, RandomizedSigner,
};

/// ECDSA signer
Expand All @@ -34,7 +33,7 @@ where
C::Scalar: Invert<Output = C::Scalar> + SignPrimitive<C> + Zeroize,
SignatureSize<C>: ArrayLength<u8>,
{
secret_scalar: C::Scalar,
secret_scalar: NonZeroScalar<C>,
}

impl<C> Signer<C>
Expand All @@ -45,8 +44,9 @@ where
{
/// Create a new signer
pub fn new(secret_key: &SecretKey<C>) -> Result<Self, Error> {
let scalar = C::Scalar::from_bytes(secret_key.as_bytes());
let scalar = NonZeroScalar::from_bytes(secret_key.as_bytes());

// TODO(tarcieri): replace with into conversion when available (see subtle#73)
if scalar.is_some().into() {
Ok(Self {
secret_scalar: scalar.unwrap(),
Expand All @@ -57,24 +57,75 @@ where
}
}

impl<C, D> DigestSigner<D, Signature<C>> for Signer<C>
where
C: Curve + Arithmetic,
C::Scalar: FromDigest<C> + Invert<Output = C::Scalar> + SignPrimitive<C> + Zeroize,
D: BlockInput<BlockSize = C::ElementSize>
+ FixedOutput<OutputSize = C::ElementSize>
+ Clone
+ Default
+ Reset
+ Update,
ElementBytes<C>: Zeroize,
SignatureSize<C>: ArrayLength<u8>,
{
/// Sign message prehash using a deterministic ephemeral scalar (`k`)
/// computed using the algorithm described in RFC 6979 (Section 3.2):
/// <https://tools.ietf.org/html/rfc6979#section-3>
fn try_sign_digest(&self, digest: D) -> Result<Signature<C>, Error> {
let ephemeral_scalar = rfc6979::generate_k(&self.secret_scalar, digest.clone(), &[]);

self.secret_scalar
.as_ref()
.try_sign_prehashed(ephemeral_scalar.as_ref(), &digest.finalize())
}
}

impl<C> signature::Signer<Signature<C>> for Signer<C>
where
C: Curve + Arithmetic + DigestPrimitive,
C::Scalar: Invert<Output = C::Scalar> + SignPrimitive<C> + Zeroize,
SignatureSize<C>: ArrayLength<u8>,
Self: DigestSigner<C::Digest, Signature<C>>,
{
fn try_sign(&self, msg: &[u8]) -> Result<Signature<C>, signature::Error> {
self.try_sign_digest(C::Digest::new().chain(msg))
}
}

#[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
impl<C, D> RandomizedDigestSigner<D, Signature<C>> for Signer<C>
where
C: Curve + Arithmetic,
D: Digest<OutputSize = C::ElementSize>,
C::Scalar: Invert<Output = C::Scalar> + Generate + SignPrimitive<C> + Zeroize,
C::Scalar: FromDigest<C> + Invert<Output = C::Scalar> + SignPrimitive<C> + Zeroize,
D: BlockInput<BlockSize = C::ElementSize>
+ FixedOutput<OutputSize = C::ElementSize>
+ Clone
+ Default
+ Reset
+ Update,
ElementBytes<C>: Zeroize,
SignatureSize<C>: ArrayLength<u8>,
{
/// Sign message prehash using an ephemeral scalar (`k`) derived according
/// to a variant of RFC 6979 (Section 3.6) which supplies additional
/// entropy from an RNG.
fn try_sign_digest_with_rng(
&self,
rng: impl CryptoRng + RngCore,
mut rng: impl CryptoRng + RngCore,
digest: D,
) -> Result<Signature<C>, Error> {
let ephemeral_scalar = Zeroizing::new(C::Scalar::generate(rng));
let mut added_entropy = ElementBytes::<C>::default();
rng.fill_bytes(&mut added_entropy);

let ephemeral_scalar =
rfc6979::generate_k(&self.secret_scalar, digest.clone(), &added_entropy);

self.secret_scalar
.try_sign_prehashed(&*ephemeral_scalar, &digest.finalize())
.as_ref()
.try_sign_prehashed(ephemeral_scalar.as_ref(), &digest.finalize())
}
}

Expand All @@ -83,9 +134,9 @@ where
impl<C> RandomizedSigner<Signature<C>> for Signer<C>
where
C: Curve + Arithmetic + DigestPrimitive,
C::Digest: Digest<OutputSize = C::ElementSize>,
C::Scalar: Invert<Output = C::Scalar> + Generate + SignPrimitive<C> + Zeroize,
C::Scalar: Invert<Output = C::Scalar> + SignPrimitive<C> + Zeroize,
SignatureSize<C>: ArrayLength<u8>,
Self: RandomizedDigestSigner<C::Digest, Signature<C>>,
{
fn try_sign_with_rng(
&self,
Expand Down
115 changes: 115 additions & 0 deletions ecdsa/src/signer/rfc6979.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//! Support for computing deterministic ECDSA ephemeral scalar (`k`) using
//! the method described in RFC 6979 rules (Section 3.2):
//! <https://tools.ietf.org/html/rfc6979#section-3>

use elliptic_curve::{
digest::{BlockInput, FixedOutput, Reset, Update},
generic_array::GenericArray,
ops::Invert,
scalar::NonZeroScalar,
zeroize::{Zeroize, Zeroizing},
Arithmetic, ElementBytes, FromBytes, FromDigest,
};
use hmac::{Hmac, Mac, NewMac};

/// Generate ephemeral scalar `k` from the secret scalar and a digest of the
/// input message.
pub(super) fn generate_k<C, D>(
secret_scalar: &NonZeroScalar<C>,
msg_digest: D,
additional_data: &[u8],
) -> Zeroizing<NonZeroScalar<C>>
where
C: Arithmetic,
C::Scalar: FromDigest<C> + Invert<Output = C::Scalar> + Zeroize,
D: BlockInput<BlockSize = C::ElementSize>
+ FixedOutput<OutputSize = C::ElementSize>
+ Clone
+ Default
+ Reset
+ Update,
ElementBytes<C>: Zeroize,
{
let x = Zeroizing::new(secret_scalar.to_bytes());
let h1: ElementBytes<C> = C::Scalar::from_digest(msg_digest).into();
let mut hmac_drbg = HmacDrbg::<D>::new(&*x, &h1, additional_data);

loop {
let k = NonZeroScalar::from_bytes(&hmac_drbg.next());

if k.is_some().into() {
return Zeroizing::new(k.unwrap());
}
}
}

/// Internal implementation of `HMAC_DRBG` as described in NIST SP800-90A:
/// <https://csrc.nist.gov/publications/detail/sp/800-90a/rev-1/final>
///
/// This is a HMAC-based deterministic random bit generator used internally
/// to compute a deterministic ECDSA ephemeral scalar `k`.
// TODO(tarcieri): use `hmac-drbg` crate when sorpaas/rust-hmac-drbg#3 is merged
struct HmacDrbg<D>
where
D: BlockInput<BlockSize = <D as FixedOutput>::OutputSize>
+ FixedOutput
+ Clone
+ Default
+ Reset
+ Update,
{
/// HMAC key `K` (see RFC 6979 Section 3.2.c)
k: Hmac<D>,

/// Chaining value `V` (see RFC 6979 Section 3.2.c)
v: GenericArray<u8, D::OutputSize>,
}

impl<D> HmacDrbg<D>
where
D: BlockInput<BlockSize = <D as FixedOutput>::OutputSize>
+ FixedOutput
+ Clone
+ Default
+ Reset
+ Update,
{
/// Initialize `HMAC_DRBG`
pub fn new(entropy_input: &[u8], nonce: &[u8], additional_data: &[u8]) -> Self {
let mut k = Hmac::new(&Default::default());
let mut v = GenericArray::default();

for b in &mut v {
*b = 0x01;
}

for i in 0..=1 {
k.update(&v);
k.update(&[i]);
k.update(entropy_input);
k.update(nonce);
k.update(additional_data);
k = Hmac::new(&k.finalize().into_bytes());

// Steps 3.2.e,g: v = HMAC_k(v)
k.update(&v);
v = k.finalize_reset().into_bytes();
}

Self { k, v }
}

/// Get the next `HMAC_DRBG` output
pub fn next(&mut self) -> GenericArray<u8, D::OutputSize> {
self.k.update(&self.v);
let t = self.k.finalize_reset().into_bytes();

self.k.update(&t);
self.k.update(&[0x00]);
self.k = Hmac::new(&self.k.finalize_reset().into_bytes());
self.k.update(&t);
self.v = self.k.finalize_reset().into_bytes();

t
}
}