Skip to content

Commit

Permalink
Add raw_sign_byupdate and raw_verify_byupdate
Browse files Browse the repository at this point in the history
These allow signing/verifying a non-prehashed message
but don't require the whole message to be provided at once.
  • Loading branch information
mkj committed Jul 31, 2023
1 parent 9968fd4 commit f981763
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 3 deletions.
43 changes: 43 additions & 0 deletions ed25519-dalek/src/hazmat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,30 @@ where
esk.raw_sign_prehashed::<CtxDigest, MsgDigest>(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<CtxDigest, F>(
esk: &ExpandedSecretKey,
msg_update: F,
verifying_key: &VerifyingKey,
) -> Result<Signature, SignatureError>
where
CtxDigest: Digest<OutputSize = U64>,
F: Fn(&mut CtxDigest) -> Result<(), SignatureError>,
{
esk.raw_sign_byupdate::<CtxDigest, F>(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`.
Expand Down Expand Up @@ -202,6 +226,25 @@ where
vk.raw_verify_prehashed::<CtxDigest, MsgDigest>(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<CtxDigest, F>(
vk: &VerifyingKey,
msg_update: F,
signature: &ed25519::Signature,
) -> Result<(), SignatureError>
where
CtxDigest: Digest<OutputSize = U64>,
F: Fn(&mut CtxDigest) -> Result<(), SignatureError>,
{
vk.raw_verify_byupdate::<CtxDigest, F>(msg_update, signature)
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
31 changes: 28 additions & 3 deletions ed25519-dalek/src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,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<CtxDigest>(
&self,
Expand All @@ -751,24 +752,48 @@ impl ExpandedSecretKey {
) -> Signature
where
CtxDigest: Digest<OutputSize = U64>,
{
// 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<CtxDigest, F>(
&self,
msg_update: F,
verifying_key: &VerifyingKey,
) -> Result<Signature, SignatureError>
where
CtxDigest: Digest<OutputSize = U64>,
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();

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
Expand Down
23 changes: 23 additions & 0 deletions ed25519-dalek/src/verifying.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,29 @@ impl VerifyingKey {
}
}

#[allow(non_snake_case)]
pub(crate) fn raw_verify_byupdate<CtxDigest, F>(
&self,
msg_update: F,
signature: &ed25519::Signature,
) -> Result<(), SignatureError>
where
CtxDigest: Digest<OutputSize = U64>,
F: Fn(&mut CtxDigest) -> Result<(), SignatureError>,
{
let signature = InternalSignature::try_from(signature)?;

let mut c = RCompute::<CtxDigest>::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
Expand Down

0 comments on commit f981763

Please sign in to comment.