diff --git a/src/signing.rs b/src/signing.rs index 500f8b5..5b7f7a8 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -41,7 +41,7 @@ use crate::{ errors::{InternalError, SignatureError}, hazmat::ExpandedSecretKey, signature::InternalSignature, - verifying::VerifyingKey, + verifying::{StreamVerifier, VerifyingKey}, Signature, }; @@ -473,6 +473,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 { + self.verifying_key.verify_stream(signature) + } + /// Convert this signing key into a Curve25519 scalar. /// /// This is useful for e.g. performing X25519 Diffie-Hellman using diff --git a/src/verifying.rs b/src/verifying.rs index e97e203..014daa2 100644 --- a/src/verifying.rs +++ b/src/verifying.rs @@ -43,6 +43,9 @@ use crate::{ signing::SigningKey, }; +mod stream; +pub use self::stream::StreamVerifier; + /// An ed25519 public key. /// /// # Note @@ -436,8 +439,21 @@ impl VerifyingKey { } } + /// Constructs stream verifier with candidate `signature`. + /// + /// Useful for cases where the whole message is not available all at once, allowing the + /// internal signature state to be updated incrementally and verified at the end. In some cases, + /// this will reduce the need for additional allocations. + pub fn verify_stream( + &self, + signature: &ed25519::Signature, + ) -> Result { + let signature = InternalSignature::try_from(signature)?; + Ok(StreamVerifier::new(*self, signature)) + } + /// Verify a `signature` on a `prehashed_message` using the Ed25519ph algorithm, - /// using strict signture checking as defined by [`Self::verify_strict`]. + /// using strict signature checking as defined by [`Self::verify_strict`]. /// /// # Inputs /// diff --git a/src/verifying/stream.rs b/src/verifying/stream.rs new file mode 100644 index 0000000..76f7d3f --- /dev/null +++ b/src/verifying/stream.rs @@ -0,0 +1,58 @@ +use curve25519_dalek::{edwards::EdwardsPoint, scalar::Scalar}; +use sha2::{Digest, Sha512}; + +use crate::{signature::InternalSignature, InternalError, SignatureError, VerifyingKey}; + +/// An IUF verifier for ed25519. +/// +/// Created with [`VerifyingKey::verify_stream()`] or [`SigningKey::verify_stream()`]. +/// +/// [`SigningKey::verify_stream()`]: super::SigningKey::verify_stream() +#[derive(Debug)] +pub struct StreamVerifier { + /// Public key to verify with. + pub(crate) public_key: VerifyingKey, + + /// Candidate signature to verify against. + pub(crate) signature: InternalSignature, + + /// Hash state. + pub(crate) hasher: Sha512, +} + +impl StreamVerifier { + /// Constructs new stream verifier. + /// + /// Seeds hash state with public key and signature components. + pub(crate) fn new(public_key: VerifyingKey, signature: InternalSignature) -> Self { + let mut hasher = Sha512::new(); + hasher.update(signature.R.as_bytes()); + hasher.update(public_key.as_bytes()); + + Self { + public_key, + hasher, + signature, + } + } + + /// Digest message chunk. + pub fn update(&mut self, chunk: impl AsRef<[u8]>) { + self.hasher.update(&chunk); + } + + /// Finalize verifier and check against candidate signature. + #[allow(non_snake_case)] + pub fn finalize_and_verify(self) -> Result<(), SignatureError> { + let minus_A: EdwardsPoint = -self.public_key.point; + let k = Scalar::from_hash(self.hasher); + let R = + EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(minus_A), &self.signature.s); + + if R.compress() == self.signature.R { + Ok(()) + } else { + Err(InternalError::Verify.into()) + } + } +} diff --git a/tests/ed25519.rs b/tests/ed25519.rs index 6632f01..60729cb 100644 --- a/tests/ed25519.rs +++ b/tests/ed25519.rs @@ -338,6 +338,45 @@ mod integrations { ); } + #[cfg(feature = "digest")] + #[test] + fn sign_verify_digest_equivalence() { + // TestSignVerify + let keypair: SigningKey; + let good_sig: Signature; + let bad_sig: Signature; + + let good: &[u8] = "test message".as_bytes(); + let bad: &[u8] = "wrong message".as_bytes(); + + let mut csprng = OsRng {}; + + keypair = SigningKey::generate(&mut csprng); + good_sig = keypair.sign(&good); + bad_sig = keypair.sign(&bad); + + let mut verifier = keypair.verify_stream(&good_sig).unwrap(); + verifier.update(&good); + assert!( + verifier.finalize_and_verify().is_ok(), + "Verification of a valid signature failed!" + ); + + let mut verifier = keypair.verify_stream(&bad_sig).unwrap(); + verifier.update(&good); + assert!( + verifier.finalize_and_verify().is_err(), + "Verification of a signature on a different message passed!" + ); + + let mut verifier = keypair.verify_stream(&good_sig).unwrap(); + verifier.update(&bad); + assert!( + verifier.finalize_and_verify().is_err(), + "Verification of a signature on a different message passed!" + ); + } + #[cfg(feature = "digest")] #[test] fn ed25519ph_sign_verify() {