From 1247160a54559987781f1cdfd60564e3fd96813f Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Wed, 20 May 2026 11:52:03 -0700 Subject: [PATCH 1/2] ecdsa: relax digest requirement for sign_digest_recoverable --- Cargo.lock | 16 +++++++++++++-- ecdsa/src/hazmat.rs | 8 ++++---- ecdsa/src/recovery.rs | 17 ++++++++-------- ecdsa/src/signing.rs | 16 +++++++-------- ecdsa/src/verifying.rs | 30 +++++++++++++++------------- rfc6979/Cargo.toml | 1 + rfc6979/src/lib.rs | 45 ++++++++++++++++++++++++++++-------------- 7 files changed, 82 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fecb9630..5f1465fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1139,6 +1139,7 @@ dependencies = [ "hex-literal", "hmac", "sha2", + "sha3 0.12.0", ] [[package]] @@ -1333,6 +1334,17 @@ dependencies = [ "keccak", ] +[[package]] +name = "sha3" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9bad02c26382724b2d2692c6f179285e4b54eeecd7968f52a50059c3c11759" +dependencies = [ + "digest", + "keccak", + "sponge-cursor", +] + [[package]] name = "shake" version = "0.1.0" @@ -1377,7 +1389,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "sha3", + "sha3 0.11.0", "signature", "typenum", "zerocopy", @@ -1757,7 +1769,7 @@ dependencies = [ "serde_json", "serdect", "sha2", - "sha3", + "sha3 0.11.0", "signature", "spki", "subtle", diff --git a/ecdsa/src/hazmat.rs b/ecdsa/src/hazmat.rs index 74c13a8b..9a34d9b4 100644 --- a/ecdsa/src/hazmat.rs +++ b/ecdsa/src/hazmat.rs @@ -31,9 +31,9 @@ use { }; #[cfg(feature = "digest")] -use digest::block_api::EagerHash; +use digest::{Digest, FixedOutput, FixedOutputReset, common::BlockSizeUser}; -/// Bind a preferred [`EagerHash`] algorithm to an elliptic curve type. +/// Bind a preferred [`Digest`] algorithm to an elliptic curve type. /// /// Generally there is a preferred variety of the SHA-2 family used with ECDSA /// for a particular elliptic curve. @@ -41,7 +41,7 @@ use digest::block_api::EagerHash; pub trait DigestAlgorithm: EcdsaCurve { /// Preferred digest to use when computing ECDSA signatures for this /// elliptic curve. This is typically a member of the SHA-2 family. - type Digest: EagerHash + digest::Update; + type Digest: BlockSizeUser + Digest + FixedOutput + FixedOutputReset; } /// Partial implementation of the `bits2int` function as defined in @@ -166,7 +166,7 @@ pub fn sign_prehashed_rfc6979( ) -> Result<(Signature, RecoveryId)> where C: EcdsaCurve + CurveArithmetic, - D: EagerHash, + D: Digest + BlockSizeUser + FixedOutput + FixedOutputReset, SignatureSize: ArraySize, { // From RFC6979 ยง 2.4: diff --git a/ecdsa/src/recovery.rs b/ecdsa/src/recovery.rs index fe644c27..9d79c558 100644 --- a/ecdsa/src/recovery.rs +++ b/ecdsa/src/recovery.rs @@ -8,7 +8,7 @@ use { EcdsaCurve, Signature, SignatureSize, SigningKey, VerifyingKey, hazmat::{DigestAlgorithm, bits2field, sign_prehashed_rfc6979, verify_prehashed}, }, - digest::{Digest, block_api::EagerHash}, + digest::{Digest, FixedOutputReset, Update}, elliptic_curve::{ AffinePoint, FieldBytesEncoding, FieldBytesSize, Group, PrimeField, ProjectivePoint, bigint::CheckedAdd, @@ -108,7 +108,7 @@ impl RecoveryId { ) -> Result where C: EcdsaCurve + CurveArithmetic, - D: EagerHash, + D: Digest, AffinePoint: DecompressPoint + FromSec1Point + ToSec1Point, FieldBytesSize: sec1::ModulusSize, SignatureSize: ArraySize, @@ -203,7 +203,7 @@ where /// Sign the given message digest, returning a signature and recovery ID. pub fn sign_digest_recoverable(&self, msg_digest: D) -> Result<(Signature, RecoveryId)> where - D: EagerHash, + D: Digest, { self.sign_prehash_recoverable(&msg_digest.finalize()) } @@ -219,7 +219,7 @@ where impl DigestSigner, RecoveryId)> for SigningKey where C: EcdsaCurve + CurveArithmetic + DigestAlgorithm, - D: EagerHash + digest::Update, + D: Digest + Update, Scalar: Invert>>, SignatureSize: ArraySize, { @@ -253,7 +253,7 @@ where impl RandomizedDigestSigner, RecoveryId)> for SigningKey where C: EcdsaCurve + CurveArithmetic + DigestAlgorithm, - D: EagerHash + digest::Update, + D: Digest + Update + FixedOutputReset, Scalar: Invert>>, SignatureSize: ArraySize, { @@ -264,7 +264,7 @@ where ) -> Result<(Signature, RecoveryId)> { let mut digest = D::new(); f(&mut digest)?; - self.sign_prehash_with_rng(rng, &digest.finalize()) + self.sign_prehash_with_rng(rng, &digest.finalize_reset()) } } @@ -301,7 +301,8 @@ where { fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result<(Signature, RecoveryId)> { let mut digest = C::Digest::new(); - msg.iter().for_each(|slice| digest.update(slice)); + msg.iter() + .for_each(|slice| Update::update(&mut digest, slice)); self.sign_digest_recoverable(digest) } } @@ -337,7 +338,7 @@ where recovery_id: RecoveryId, ) -> Result where - D: EagerHash, + D: Digest, { Self::recover_from_prehash(&msg_digest.finalize(), signature, recovery_id) } diff --git a/ecdsa/src/signing.rs b/ecdsa/src/signing.rs index 922b092d..a8f409b8 100644 --- a/ecdsa/src/signing.rs +++ b/ecdsa/src/signing.rs @@ -5,7 +5,7 @@ use crate::{ hazmat::{DigestAlgorithm, bits2field, sign_prehashed_rfc6979}, }; use core::fmt::{self, Debug}; -use digest::{Update, block_api::EagerHash, const_oid::AssociatedOid}; +use digest::{Digest, FixedOutput, const_oid::AssociatedOid}; use elliptic_curve::{ CurveArithmetic, FieldBytes, Generate, NonZeroScalar, Scalar, SecretKey, array::ArraySize, @@ -146,14 +146,14 @@ where impl DigestSigner> for SigningKey where C: EcdsaCurve + CurveArithmetic + DigestAlgorithm, - D: EagerHash + Update, + D: Digest + FixedOutput, Scalar: Invert>>, SignatureSize: ArraySize, { fn try_sign_digest Result<()>>(&self, f: F) -> Result> { let mut digest = D::new(); f(&mut digest)?; - self.sign_prehash(&digest.finalize()) + self.sign_prehash(&digest.finalize_fixed()) } } @@ -205,7 +205,7 @@ where impl RandomizedDigestSigner> for SigningKey where C: EcdsaCurve + CurveArithmetic + DigestAlgorithm, - D: EagerHash + Update, + D: Digest + FixedOutput, Scalar: Invert>>, SignatureSize: ArraySize, { @@ -216,7 +216,7 @@ where ) -> Result> { let mut digest = D::new(); f(&mut digest)?; - self.sign_prehash_with_rng(rng, &digest.finalize()) + self.sign_prehash_with_rng(rng, &digest.finalize_fixed()) } } @@ -284,7 +284,7 @@ where impl DigestSigner> for SigningKey where C: EcdsaCurve + CurveArithmetic + DigestAlgorithm, - D: AssociatedOid + EagerHash + Update, + D: AssociatedOid + Digest + FixedOutput, Scalar: Invert>>, SignatureSize: ArraySize, { @@ -354,7 +354,7 @@ where impl RandomizedDigestSigner> for SigningKey where C: EcdsaCurve + CurveArithmetic + DigestAlgorithm, - D: EagerHash + Update, + D: Digest + FixedOutput, Scalar: Invert>>, SignatureSize: ArraySize, der::MaxSize: ArraySize, @@ -393,7 +393,7 @@ where impl DigestSigner> for SigningKey where C: EcdsaCurve + CurveArithmetic + DigestAlgorithm, - D: EagerHash + Update, + D: Digest + FixedOutput, Scalar: Invert>>, SignatureSize: ArraySize, der::MaxSize: ArraySize, diff --git a/ecdsa/src/verifying.rs b/ecdsa/src/verifying.rs index 4b0a5903..4bac50e2 100644 --- a/ecdsa/src/verifying.rs +++ b/ecdsa/src/verifying.rs @@ -5,7 +5,7 @@ use crate::{ hazmat::{self, DigestAlgorithm, bits2field}, }; use core::{cmp::Ordering, fmt::Debug}; -use digest::{Update, block_api::EagerHash}; +use digest::{Digest, Update}; use elliptic_curve::{ AffinePoint, CurveArithmetic, FieldBytesSize, ProjectivePoint, PublicKey, array::ArraySize, @@ -144,7 +144,7 @@ where impl DigestVerifier> for VerifyingKey where C: EcdsaCurve + CurveArithmetic, - D: EagerHash + Update, + D: Digest + Update, SignatureSize: ArraySize, { fn verify_digest Result<()>>( @@ -194,7 +194,7 @@ where fn multipart_verify(&self, msg: &[&[u8]], signature: &Signature) -> Result<()> { self.verify_digest( |digest: &mut C::Digest| { - msg.iter().for_each(|slice| digest.update(slice)); + msg.iter().for_each(|slice| Update::update(digest, slice)); Ok(()) }, signature, @@ -220,28 +220,30 @@ where SignatureSize: ArraySize, { fn multipart_verify(&self, msg: &[&[u8]], sig: &SignatureWithOid) -> Result<()> { - use digest::FixedOutput; - match sig.oid() { ECDSA_SHA224_OID => { let mut digest = Sha224::default(); - msg.iter().for_each(|slice| digest.update(slice)); - self.verify_prehash(&digest.finalize_fixed(), sig.signature()) + msg.iter() + .for_each(|slice| Update::update(&mut digest, slice)); + self.verify_prehash(&digest.finalize(), sig.signature()) } ECDSA_SHA256_OID => { let mut digest = Sha256::default(); - msg.iter().for_each(|slice| digest.update(slice)); - self.verify_prehash(&digest.finalize_fixed(), sig.signature()) + msg.iter() + .for_each(|slice| Update::update(&mut digest, slice)); + self.verify_prehash(&digest.finalize(), sig.signature()) } ECDSA_SHA384_OID => { let mut digest = Sha384::default(); - msg.iter().for_each(|slice| digest.update(slice)); - self.verify_prehash(&digest.finalize_fixed(), sig.signature()) + msg.iter() + .for_each(|slice| Update::update(&mut digest, slice)); + self.verify_prehash(&digest.finalize(), sig.signature()) } ECDSA_SHA512_OID => { let mut digest = Sha512::default(); - msg.iter().for_each(|slice| digest.update(slice)); - self.verify_prehash(&digest.finalize_fixed(), sig.signature()) + msg.iter() + .for_each(|slice| Update::update(&mut digest, slice)); + self.verify_prehash(&digest.finalize(), sig.signature()) } _ => Err(Error::new()), } @@ -252,7 +254,7 @@ where impl DigestVerifier> for VerifyingKey where C: EcdsaCurve + CurveArithmetic, - D: EagerHash + Update, + D: Digest + Update, SignatureSize: ArraySize, der::MaxSize: ArraySize, as Add>::Output: Add + ArraySize, diff --git a/rfc6979/Cargo.toml b/rfc6979/Cargo.toml index dda354a1..ce15e982 100644 --- a/rfc6979/Cargo.toml +++ b/rfc6979/Cargo.toml @@ -22,6 +22,7 @@ hmac = { version = "0.13", default-features = false } [dev-dependencies] hex-literal = "1" sha2 = "0.11" +sha3 = "0.12" [lints] workspace = true diff --git a/rfc6979/src/lib.rs b/rfc6979/src/lib.rs index 78ae5310..a20f262e 100644 --- a/rfc6979/src/lib.rs +++ b/rfc6979/src/lib.rs @@ -42,11 +42,11 @@ pub use hmac::digest::array::typenum::consts; use core::fmt::{self, Debug}; use hmac::{ - HmacReset, + SimpleHmacReset, digest::{ - KeyInit, Mac, OutputSizeUser, + Digest, FixedOutput, FixedOutputReset, KeyInit, Mac, array::{Array, ArraySize}, - block_api::EagerHash, + common::BlockSizeUser, }, }; @@ -66,7 +66,7 @@ pub fn generate_k( data: &[u8], ) -> Array where - D: EagerHash, + D: Digest + BlockSizeUser + FixedOutput + FixedOutputReset, N: ArraySize, { let mut k = Array::default(); @@ -91,7 +91,7 @@ where #[inline] pub fn generate_k_mut(x: &[u8], q: &[u8], h: &[u8], data: &[u8], k: &mut [u8]) where - D: EagerHash, + D: Digest + BlockSizeUser + FixedOutput + FixedOutputReset, { let k_len = k.len(); assert_eq!(k_len, x.len()); @@ -124,24 +124,24 @@ where /// deterministic ephemeral scalar `k`. pub struct HmacDrbg where - D: EagerHash, + D: Digest + BlockSizeUser + FixedOutputReset, { /// HMAC key `K` (see RFC 6979 Section 3.2.c) - k: HmacReset, + k: SimpleHmacReset, /// Chaining value `V` (see RFC 6979 Section 3.2.c) - v: Array::OutputSize>, + v: Array, } impl HmacDrbg where - D: EagerHash, + D: Digest + BlockSizeUser + FixedOutputReset, { /// Initialize `HMAC_DRBG`. #[must_use] #[allow(clippy::missing_panics_doc, reason = "should not panic")] pub fn new(entropy_input: &[u8], nonce: &[u8], personalization_string: &[u8]) -> Self { - let mut k = HmacReset::new(&Default::default()); + let mut k = SimpleHmacReset::new(&Default::default()); let mut v = Array::default(); v.fill(0x01); @@ -152,7 +152,7 @@ where k.update(entropy_input); k.update(nonce); k.update(personalization_string); - k = HmacReset::new_from_slice(&k.finalize().into_bytes()).expect("should work"); + k = SimpleHmacReset::new_from_slice(&k.finalize().into_bytes()).expect("should work"); // Steps 3.2.e,g: v = HMAC_k(v) k.update(&v); @@ -182,8 +182,8 @@ where self.k.update(&self.v); self.k.update(&[0x00]); - self.k = - HmacReset::new_from_slice(&self.k.finalize_reset().into_bytes()).expect("should work"); + self.k = SimpleHmacReset::new_from_slice(&self.k.finalize_reset().into_bytes()) + .expect("should work"); self.k.update(&self.v); self.v = self.k.finalize_reset().into_bytes(); } @@ -191,7 +191,7 @@ where impl Debug for HmacDrbg where - D: EagerHash, + D: Digest + BlockSizeUser + FixedOutputReset, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("HmacDrbg").finish_non_exhaustive() @@ -202,11 +202,12 @@ where mod tests { use crate::{ Array, - consts::{U21, U66}, + consts::{U12, U21, U66}, generate_k, }; use hex_literal::hex; use sha2::{Digest, Sha256, Sha512}; + use sha3::Sha3_256; /// "Detailed Example" from RFC6979 Appendix A.1. /// @@ -250,4 +251,18 @@ mod tests { assert_eq!(k, expected_k); } + + /// This test showcase the use with a non-block_api hash + /// This is used for ethereum which uses ecdsa-sha3 + #[test] + fn non_block_api() { + let x = hex!("000000000000000000000000"); + let q = hex!("ffffffffffffffffffffffff"); + + let h = hex!("080808080808080808080808"); + + let data = b""; + let out = generate_k::(&x.into(), &q.into(), &h.into(), data); + assert_eq!(out, hex!("d460ac6531e9743d3829850f")); + } } From bdd748ccc073f9ef4431e1b92aded55d996d6fff Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Fri, 22 May 2026 00:13:46 -0700 Subject: [PATCH 2/2] dsa: relax digest requirement for sign_prehashed_rfc6979 --- dsa/src/generate/secret_number.rs | 4 ++-- dsa/src/signing_key.rs | 8 ++++---- dsa/tests/deterministic.rs | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/dsa/src/generate/secret_number.rs b/dsa/src/generate/secret_number.rs index d62e3956..51e521e1 100644 --- a/dsa/src/generate/secret_number.rs +++ b/dsa/src/generate/secret_number.rs @@ -6,7 +6,7 @@ use crate::{Components, signing_key::SigningKey}; use alloc::vec; use core::cmp::min; use crypto_bigint::{BoxedUint, NonZero, RandomBits, Resize}; -use digest::block_api::EagerHash; +use digest::{Digest, FixedOutputReset, common::BlockSizeUser}; use signature::rand_core::TryCryptoRng; use zeroize::Zeroizing; @@ -25,7 +25,7 @@ pub(crate) fn secret_number_rfc6979( hash: &[u8], ) -> Result<(BoxedUint, BoxedUint), signature::Error> where - D: EagerHash, + D: Digest + BlockSizeUser + FixedOutputReset, { let q = signing_key.verifying_key().components().q(); let size = (q.bits() / 8) as usize; diff --git a/dsa/src/signing_key.rs b/dsa/src/signing_key.rs index 14a71030..5875bbda 100644 --- a/dsa/src/signing_key.rs +++ b/dsa/src/signing_key.rs @@ -13,7 +13,7 @@ use crypto_bigint::{ BoxedUint, ConcatenatingMul, NonZero, Resize, modular::{BoxedMontyForm, BoxedMontyParams}, }; -use digest::{Update, block_api::EagerHash}; +use digest::{Digest, FixedOutputReset, common::BlockSizeUser}; use signature::{ DigestSigner, MultipartSigner, RandomizedDigestSigner, Signer, hazmat::{PrehashSigner, RandomizedPrehashSigner}, @@ -94,7 +94,7 @@ impl SigningKey { #[cfg(feature = "hazmat")] pub fn sign_prehashed_rfc6979(&self, prehash: &[u8]) -> Result where - D: EagerHash, + D: Digest + BlockSizeUser + FixedOutputReset, { let k_kinv = crate::generate::secret_number_rfc6979::(self, prehash)?; self.sign_prehashed(k_kinv, prehash) @@ -193,7 +193,7 @@ impl RandomizedPrehashSigner for SigningKey { impl DigestSigner for SigningKey where - D: EagerHash + Update, + D: Digest + FixedOutputReset + BlockSizeUser, { fn try_sign_digest Result<(), signature::Error>>( &self, @@ -210,7 +210,7 @@ where impl RandomizedDigestSigner for SigningKey where - D: EagerHash + Update, + D: Digest + FixedOutputReset + BlockSizeUser, { fn try_sign_digest_with_rng< R: TryCryptoRng + ?Sized, diff --git a/dsa/tests/deterministic.rs b/dsa/tests/deterministic.rs index cc8610b7..53c88b38 100644 --- a/dsa/tests/deterministic.rs +++ b/dsa/tests/deterministic.rs @@ -1,6 +1,6 @@ #![cfg(feature = "hazmat")] use crypto_bigint::BoxedUint; -use digest::{Update, block_api::EagerHash}; +use digest::{Digest, FixedOutputReset, common::BlockSizeUser}; use dsa::{Components, Signature, SigningKey, VerifyingKey}; use sha1::Sha1; use sha2::{Sha224, Sha256, Sha384, Sha512}; @@ -100,15 +100,15 @@ fn dsa_2048_signing_key() -> SigningKey { /// Generate a signature given the unhashed message and a private key fn generate_signature(signing_key: SigningKey, data: &[u8]) -> Signature where - D: EagerHash + Update, + D: Digest + FixedOutputReset + BlockSizeUser, { - signing_key.sign_digest(|digest: &mut D| Update::update(digest, data)) + signing_key.sign_digest(|digest: &mut D| Digest::update(digest, data)) } /// Generate a signature using the 1024-bit DSA key fn generate_1024_signature(data: &[u8]) -> Signature where - D: EagerHash + Update, + D: Digest + FixedOutputReset + BlockSizeUser, { generate_signature::(dsa_1024_signing_key(), data) } @@ -116,7 +116,7 @@ where /// Generate a signature using the 2048-bit DSA key fn generate_2048_signature(data: &[u8]) -> Signature where - D: EagerHash + Update, + D: Digest + FixedOutputReset + BlockSizeUser, { generate_signature::(dsa_2048_signing_key(), data) }