diff --git a/ed25519-dalek/src/hazmat.rs b/ed25519-dalek/src/hazmat.rs index 7849613..4be9f0d 100644 --- a/ed25519-dalek/src/hazmat.rs +++ b/ed25519-dalek/src/hazmat.rs @@ -106,7 +106,7 @@ impl TryFrom<&[u8]> for ExpandedSecretKey { /// calculate the pseudorandomness needed for signing. According to the Ed25519 spec, `CtxDigest = /// Sha512`. /// -/// # ⚠️ Unsafe +/// # ⚠️ Cryptographically Unsafe /// /// Do NOT use this function unless you absolutely must. Using the wrong values in /// `ExpandedSecretKey` can leak your signing key. See @@ -127,7 +127,7 @@ where /// `CtxDigest` is the digest function used to calculate the pseudorandomness needed for signing. /// According to the Ed25519 spec, `MsgDigest = CtxDigest = Sha512`. /// -/// # ⚠️ Unsafe +/// # ⚠️ Cryptographically Unsafe // /// Do NOT use this function unless you absolutely must. Using the wrong values in /// `ExpandedSecretKey` can leak your signing key. See @@ -169,6 +169,33 @@ where esk.raw_sign_prehashed::(prehashed_message, verifying_key, context) } +/// Compute an ordinary Ed25519 signature, with the message contents provided incrementally by +/// updating a digest instance. +/// +/// The `msg_update` closure provides the message content, updating a hasher argument. It will be +/// called twice. This closure MUST leave its hasher in the same state (i.e., must hash the same +/// values) after both calls. Otherwise it will produce an invalid signature. +/// +/// `CtxDigest` is the digest used to calculate the pseudorandomness needed for signing. According +/// to the Ed25519 spec, `CtxDigest = Sha512`. +/// +/// # ⚠️ Cryptographically 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`. @@ -263,4 +290,47 @@ mod test { .unwrap(); raw_verify_prehashed::(&vk, h, Some(ctx_str), &sig).unwrap(); } + + #[test] + fn sign_byupdate() { + // Generate the keypair + let mut rng = OsRng; + let esk = ExpandedSecretKey::random(&mut rng); + let vk = VerifyingKey::from(&esk); + + let msg = b"realistic"; + // signatures are deterministic so we can compare with a good one + let good_sig = raw_sign::(&esk, msg, &vk); + + let sig = raw_sign_byupdate::( + &esk, + |h| { + h.update(msg); + Ok(()) + }, + &vk, + ); + assert!(sig.unwrap() == good_sig, "sign byupdate matches"); + + let sig = raw_sign_byupdate::( + &esk, + |h| { + h.update(msg); + Err(SignatureError::new()) + }, + &vk, + ); + assert!(sig.is_err(), "sign byupdate failure propagates"); + + let sig = raw_sign_byupdate::( + &esk, + |h| { + h.update(&msg[..1]); + h.update(&msg[1..]); + Ok(()) + }, + &vk, + ); + assert!(sig.unwrap() == good_sig, "sign byupdate two part"); + } } diff --git a/ed25519-dalek/src/signing.rs b/ed25519-dalek/src/signing.rs index 144569e..5416274 100644 --- a/ed25519-dalek/src/signing.rs +++ b/ed25519-dalek/src/signing.rs @@ -833,6 +833,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, @@ -841,11 +842,36 @@ 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. This closure MUST leave its hasher in the same state (i.e., must hash the + /// same values) after both calls. Otherwise it will produce an invalid signature. + #[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(); @@ -853,12 +879,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