Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sign/verify by digest update, StreamVerifier refactoring #583

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ed25519-dalek/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Entries are listed in reverse chronological order per undeprecated major series.
* Add `pkcs` feature to support PKCS #8 (de)serialization of `SigningKey` and `VerifyingKey`
* Add `fast` feature to include basepoint tables
* Add tests for validation criteria
* Add `SigningKey::verify_stream()`, and `VerifyingKey::verify_stream()`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this entry should probably be moved up out of the 2.0.0 section

* Impl `DigestSigner`/`DigestVerifier` for `SigningKey`/`VerifyingKey`, respectively
* Impl `Hash` for `VerifyingKey`
* Impl `Clone`, `Drop`, and `ZeroizeOnDrop` for `SigningKey`
Expand Down
150 changes: 150 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 {
#![allow(clippy::unwrap_used)]
Expand Down Expand Up @@ -263,4 +306,111 @@ mod test {
.unwrap();
raw_verify_prehashed::<CtxDigest, MsgDigest>(&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::<CtxDigest>(&esk, msg, &vk);

let sig = raw_sign_byupdate::<CtxDigest, _>(
&esk,
|h| {
h.update(msg);
Ok(())
},
&vk,
);
assert!(sig.unwrap() == good_sig, "sign byupdate matches");

let sig = raw_sign_byupdate::<CtxDigest, _>(
&esk,
|h| {
h.update(msg);
Err(SignatureError::new())
},
&vk,
);
assert!(sig.is_err(), "sign byupdate failure propagates");

let sig = raw_sign_byupdate::<CtxDigest, _>(
&esk,
|h| {
h.update(&msg[..1]);
h.update(&msg[1..]);
Ok(())
},
&vk,
);
assert!(sig.unwrap() == good_sig, "sign byupdate two part");
}

#[test]
fn verify_byupdate() {
// Generate the keypair
let mut rng = OsRng;
let esk = ExpandedSecretKey::random(&mut rng);
let vk = VerifyingKey::from(&esk);

let msg = b"Torrens title";
let sig = raw_sign::<CtxDigest>(&esk, msg, &vk);
let wrong_sig = raw_sign::<CtxDigest>(&esk, b"nope", &vk);

let r = raw_verify_byupdate::<CtxDigest, _>(
&vk,
|h| {
h.update(msg);
Ok(())
},
&sig,
);
assert!(r.is_ok(), "verify byupdate success");

let r = raw_verify_byupdate::<CtxDigest, _>(
&vk,
|h| {
h.update(msg);
Ok(())
},
&wrong_sig,
);
assert!(r.is_err(), "verify byupdate wrong fails");

let r = raw_verify_byupdate::<CtxDigest, _>(
&vk,
|h| {
h.update(&msg[..5]);
h.update(&msg[5..]);
Ok(())
},
&sig,
);
assert!(r.is_ok(), "verify byupdate two-part");

let r = raw_verify_byupdate::<CtxDigest, _>(
&vk,
|h| {
h.update(msg);
h.update(b"X");
Ok(())
},
&sig,
);
assert!(r.is_err(), "verify byupdate extra fails");

let r = raw_verify_byupdate::<CtxDigest, _>(
&vk,
|h| {
h.update(msg);
Err(SignatureError::new())
},
&sig,
);
assert!(r.is_err(), "verify byupdate error propagates");
}
}
43 changes: 39 additions & 4 deletions ed25519-dalek/src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use crate::{
errors::{InternalError, SignatureError},
hazmat::ExpandedSecretKey,
signature::InternalSignature,
verifying::VerifyingKey,
verifying::{StreamVerifier, VerifyingKey},
Signature,
};

Expand Down Expand Up @@ -481,6 +481,16 @@ impl SigningKey {
self.verifying_key.verify_strict(message, signature)
}

/// Constructs stream verifier with candidate `signature`.
///
/// See [`VerifyingKey::verify_stream()`] for more details.
pub fn verify_stream(
&self,
signature: &ed25519::Signature,
) -> Result<StreamVerifier, SignatureError> {
self.verifying_key.verify_stream(signature)
}

/// Convert this signing key into a byte representation of a(n) (unreduced) Curve25519 scalar.
///
/// This can be used for performing X25519 Diffie-Hellman using Ed25519 keys. The bytes output
Expand Down Expand Up @@ -766,6 +776,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 @@ -774,24 +785,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
Loading