From 9c803b52371b94802abd8a264026ce96e4bf1a62 Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Mon, 31 Jul 2023 22:25:53 +0800 Subject: [PATCH] Add raw_sign_byupdate and raw_verify_byupdate These allow signing/verifying a non-prehashed message but don't require the whole message to be provided at once. --- ed25519-dalek/src/hazmat.rs | 42 ++++++++++++++++++++++++++++++++++ ed25519-dalek/src/signing.rs | 31 ++++++++++++++++++++++--- ed25519-dalek/src/verifying.rs | 23 +++++++++++++++++++ 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/ed25519-dalek/src/hazmat.rs b/ed25519-dalek/src/hazmat.rs index 784961304..c69d94457 100644 --- a/ed25519-dalek/src/hazmat.rs +++ b/ed25519-dalek/src/hazmat.rs @@ -169,6 +169,30 @@ where esk.raw_sign_prehashed::(prehashed_message, verifying_key, context) } +/// Compute an ordinary Ed25519 signature over the given message. `CtxDigest` is the digest used to +/// calculate the pseudorandomness needed for signing. According to the Ed25519 spec, `CtxDigest = +/// Sha512`. +/// +/// The `msg_update` closure provides the message content, updating a hash argument. +/// It will be called twice. +/// +/// # ⚠️ Unsafe +/// +/// Do NOT use this function unless you absolutely must. Using the wrong values in +/// `ExpandedSecretKey` can leak your signing key. See +/// [here](https://github.com/MystenLabs/ed25519-unsafe-libs) for more details on this attack. +pub fn raw_sign_byupdate( + esk: &ExpandedSecretKey, + msg_update: F, + verifying_key: &VerifyingKey, +) -> Result +where + CtxDigest: Digest, + F: Fn(&mut CtxDigest) -> Result<(), SignatureError>, +{ + esk.raw_sign_byupdate::(msg_update, verifying_key) +} + /// The ordinary non-batched Ed25519 verification check, rejecting non-canonical R /// values.`CtxDigest` is the digest used to calculate the pseudorandomness needed for signing. /// According to the Ed25519 spec, `CtxDigest = Sha512`. @@ -202,6 +226,24 @@ where vk.raw_verify_prehashed::(prehashed_message, context, signature) } +/// The ordinary non-batched Ed25519 verification check, rejecting non-canonical R +/// values.`CtxDigest` is the digest used to calculate the pseudorandomness needed for signing. +/// According to the Ed25519 spec, `CtxDigest = Sha512`. +/// Instead of passing the message directly (`sign()`), the caller +/// provides a `msg_update` closure that will be called to feed the +/// hash of the message being signed. +pub fn raw_verify_byupdate( + vk: &VerifyingKey, + msg_update: F, + signature: &ed25519::Signature, +) -> Result<(), SignatureError> +where + CtxDigest: Digest, + F: Fn(&mut CtxDigest) -> Result<(), SignatureError>, +{ + vk.raw_verify_byupdate::(msg_update, signature) +} + #[cfg(test)] mod test { #![allow(clippy::unwrap_used)] diff --git a/ed25519-dalek/src/signing.rs b/ed25519-dalek/src/signing.rs index e2818fea7..814e69ac2 100644 --- a/ed25519-dalek/src/signing.rs +++ b/ed25519-dalek/src/signing.rs @@ -806,6 +806,7 @@ impl ExpandedSecretKey { /// This definition is loose in its parameters so that end-users of the `hazmat` module can /// change how the `ExpandedSecretKey` is calculated and which hash function to use. #[allow(non_snake_case)] + #[allow(clippy::unwrap_used)] #[inline(always)] pub(crate) fn raw_sign( &self, @@ -814,11 +815,35 @@ impl ExpandedSecretKey { ) -> Signature where CtxDigest: Digest, + { + // OK unwrap, update can't fail. + self.raw_sign_byupdate( + |h: &mut CtxDigest| { + h.update(message); + Ok(()) + }, + verifying_key, + ) + .unwrap() + } + + /// Sign a message provided in parts. The `msg_update` closure + /// will be called twice to hash the message parts. + #[allow(non_snake_case)] + #[inline(always)] + pub(crate) fn raw_sign_byupdate( + &self, + msg_update: F, + verifying_key: &VerifyingKey, + ) -> Result + where + CtxDigest: Digest, + F: Fn(&mut CtxDigest) -> Result<(), SignatureError>, { let mut h = CtxDigest::new(); h.update(self.hash_prefix); - h.update(message); + msg_update(&mut h)?; let r = Scalar::from_hash(h); let R: CompressedEdwardsY = EdwardsPoint::mul_base(&r).compress(); @@ -826,12 +851,12 @@ impl ExpandedSecretKey { h = CtxDigest::new(); h.update(R.as_bytes()); h.update(verifying_key.as_bytes()); - h.update(message); + msg_update(&mut h)?; let k = Scalar::from_hash(h); let s: Scalar = (k * self.scalar) + r; - InternalSignature { R, s }.into() + Ok(InternalSignature { R, s }.into()) } /// The prehashed signing function for Ed25519 (i.e., Ed25519ph). `CtxDigest` is the digest diff --git a/ed25519-dalek/src/verifying.rs b/ed25519-dalek/src/verifying.rs index 01f4eee0f..ce841d03d 100644 --- a/ed25519-dalek/src/verifying.rs +++ b/ed25519-dalek/src/verifying.rs @@ -212,6 +212,29 @@ impl VerifyingKey { } } + #[allow(non_snake_case)] + pub(crate) fn raw_verify_byupdate( + &self, + msg_update: F, + signature: &ed25519::Signature, + ) -> Result<(), SignatureError> + where + CtxDigest: Digest, + F: Fn(&mut CtxDigest) -> Result<(), SignatureError>, + { + let signature = InternalSignature::try_from(signature)?; + + let mut c = RCompute::::new(self, signature, None); + msg_update(&mut c.h)?; + let expected_R = c.finish(); + + if expected_R == signature.R { + Ok(()) + } else { + Err(InternalError::Verify.into()) + } + } + /// The prehashed non-batched Ed25519 verification check, rejecting non-canonical R values. /// (see [`Self::recompute_R`]). `CtxDigest` is the digest used to calculate the /// pseudorandomness needed for signing. `MsgDigest` is the digest used to hash the signed