From 2e5ec4e25e61397f94d1f7ff9b2869ef17dcbe11 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Wed, 26 Aug 2020 15:27:06 -0700 Subject: [PATCH] ecdsa: implement RFC6979 ephemeral scalar generation Adds a deterministic signing mode based on RFC6979. Modifies the existing `RandomizedDigestSigner` and `RandomizedSigner` impls to also use an RFC6979-style derivation, but supplying added entropy derived from a provided RNG, per an RFC6979 variant described in Section 3.6. --- Cargo.lock | 24 +++++++- ecdsa/Cargo.toml | 7 ++- ecdsa/src/signer.rs | 95 ++++++++++++++++++++++------- ecdsa/src/signer/rfc6979.rs | 115 ++++++++++++++++++++++++++++++++++++ 4 files changed, 215 insertions(+), 26 deletions(-) create mode 100644 ecdsa/src/signer/rfc6979.rs diff --git a/Cargo.lock b/Cargo.lock index f3febdb9..248a4ac5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,6 +16,16 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +[[package]] +name = "crypto-mac" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bcd97a54c7ca5ce2f6eb16f6bede5b0ab5f0055fedc17d2f0b4466e21671ca" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "digest" version = "0.9.0" @@ -30,6 +40,7 @@ name = "ecdsa" version = "0.7.2" dependencies = [ "elliptic-curve", + "hmac", "signature", ] @@ -45,8 +56,9 @@ dependencies = [ [[package]] name = "elliptic-curve" version = "0.5.0" -source = "git+https://github.com/RustCrypto/traits#61464f528779d2231df69039a4d9a954d4091ad3" +source = "git+https://github.com/RustCrypto/traits#fc56c1b4f649f2126b0f42931d2a7d19cbcdbb0e" dependencies = [ + "digest", "generic-array", "rand_core", "subtle", @@ -63,6 +75,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "hmac" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deae6d9dbb35ec2c502d62b8f7b1c000a0822c3b0794ba36b3149c0a1c840dff" +dependencies = [ + "crypto-mac", + "digest", +] + [[package]] name = "rand_core" version = "0.5.1" diff --git a/ecdsa/Cargo.toml b/ecdsa/Cargo.toml index a13d845c..abd853e4 100644 --- a/ecdsa/Cargo.toml +++ b/ecdsa/Cargo.toml @@ -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"] diff --git a/ecdsa/src/signer.rs b/ecdsa/src/signer.rs index 29d76ef1..764c507b 100644 --- a/ecdsa/src/signer.rs +++ b/ecdsa/src/signer.rs @@ -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 @@ -34,7 +33,7 @@ where C::Scalar: Invert + SignPrimitive + Zeroize, SignatureSize: ArrayLength, { - secret_scalar: C::Scalar, + secret_scalar: NonZeroScalar, } impl Signer @@ -45,8 +44,9 @@ where { /// Create a new signer pub fn new(secret_key: &SecretKey) -> Result { - 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(), @@ -57,24 +57,75 @@ where } } +impl DigestSigner> for Signer +where + C: Curve + Arithmetic, + C::Scalar: FromDigest + Invert + SignPrimitive + Zeroize, + D: BlockInput + + FixedOutput + + Clone + + Default + + Reset + + Update, + ElementBytes: Zeroize, + SignatureSize: ArrayLength, +{ + /// Sign message prehash using a deterministic ephemeral scalar (`k`) + /// computed using the algorithm described in RFC 6979 (Section 3.2): + /// + fn try_sign_digest(&self, digest: D) -> Result, 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 signature::Signer> for Signer +where + C: Curve + Arithmetic + DigestPrimitive, + C::Scalar: Invert + SignPrimitive + Zeroize, + SignatureSize: ArrayLength, + Self: DigestSigner>, +{ + fn try_sign(&self, msg: &[u8]) -> Result, signature::Error> { + self.try_sign_digest(C::Digest::new().chain(msg)) + } +} + #[cfg(feature = "rand")] #[cfg_attr(docsrs, doc(cfg(feature = "rand")))] impl RandomizedDigestSigner> for Signer where C: Curve + Arithmetic, - D: Digest, - C::Scalar: Invert + Generate + SignPrimitive + Zeroize, + C::Scalar: FromDigest + Invert + SignPrimitive + Zeroize, + D: BlockInput + + FixedOutput + + Clone + + Default + + Reset + + Update, + ElementBytes: Zeroize, SignatureSize: ArrayLength, { + /// 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, Error> { - let ephemeral_scalar = Zeroizing::new(C::Scalar::generate(rng)); + let mut added_entropy = ElementBytes::::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()) } } @@ -83,9 +134,9 @@ where impl RandomizedSigner> for Signer where C: Curve + Arithmetic + DigestPrimitive, - C::Digest: Digest, - C::Scalar: Invert + Generate + SignPrimitive + Zeroize, + C::Scalar: Invert + SignPrimitive + Zeroize, SignatureSize: ArrayLength, + Self: RandomizedDigestSigner>, { fn try_sign_with_rng( &self, diff --git a/ecdsa/src/signer/rfc6979.rs b/ecdsa/src/signer/rfc6979.rs new file mode 100644 index 00000000..e9a7b163 --- /dev/null +++ b/ecdsa/src/signer/rfc6979.rs @@ -0,0 +1,115 @@ +//! Support for computing deterministic ECDSA ephemeral scalar (`k`) using +//! the method described in RFC 6979 rules (Section 3.2): +//! + +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( + secret_scalar: &NonZeroScalar, + msg_digest: D, + additional_data: &[u8], +) -> Zeroizing> +where + C: Arithmetic, + C::Scalar: FromDigest + Invert + Zeroize, + D: BlockInput + + FixedOutput + + Clone + + Default + + Reset + + Update, + ElementBytes: Zeroize, +{ + let x = Zeroizing::new(secret_scalar.to_bytes()); + let h1: ElementBytes = C::Scalar::from_digest(msg_digest).into(); + let mut hmac_drbg = HmacDrbg::::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: +/// +/// +/// 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 +where + D: BlockInput::OutputSize> + + FixedOutput + + Clone + + Default + + Reset + + Update, +{ + /// HMAC key `K` (see RFC 6979 Section 3.2.c) + k: Hmac, + + /// Chaining value `V` (see RFC 6979 Section 3.2.c) + v: GenericArray, +} + +impl HmacDrbg +where + D: BlockInput::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 { + 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 + } +}