Skip to content

Commit 38f0e8a

Browse files
committed
[WIP] ed25519: add verify_consensus implementing ZIP-215
Adds a new verification method implementing the ZIP-215[1] verification criteria for Ed25519 signatures. TODO: test vectors [1]: https://zips.z.cash/zip-0215
1 parent b76b924 commit 38f0e8a

File tree

2 files changed

+47
-9
lines changed

2 files changed

+47
-9
lines changed

ed25519-dalek/src/verifying.rs

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use curve25519_dalek::{
1717
edwards::{CompressedEdwardsY, EdwardsPoint},
1818
montgomery::MontgomeryPoint,
1919
scalar::Scalar,
20+
traits::IsIdentity,
2021
};
2122

2223
use ed25519::signature::{MultipartVerifier, Verifier};
@@ -363,10 +364,7 @@ impl VerifyingKey {
363364
) -> Result<(), SignatureError> {
364365
let signature = InternalSignature::try_from(signature)?;
365366

366-
let signature_R = signature
367-
.R
368-
.decompress()
369-
.ok_or_else(|| SignatureError::from(InternalError::Verify))?;
367+
let signature_R = signature.R.decompress().ok_or(InternalError::Verify)?;
370368

371369
// Logical OR is fine here as we're not trying to be constant time.
372370
if signature_R.is_small_order() || self.point.is_small_order() {
@@ -381,6 +379,45 @@ impl VerifyingKey {
381379
}
382380
}
383381

382+
/// Verify signature using the [ZIP-215] rules for consensus-critical contexts.
383+
///
384+
/// Consensus-critical contexts require validation criteria that work consistently across
385+
/// implementations. Unfortunately, these criteria were inconsistent in-the-wild, and despite
386+
/// [RFC 8032] enumerating certain criteria, few implementations actually followed that
387+
/// specification.
388+
///
389+
/// ZIP-215 specifically amends the verification criteria in the following way:
390+
///
391+
/// - [`VerifyingKey`] and the `R` component of the signature MUST be valid encodings of
392+
/// compressed Edwards y-coordinates for Curve25519, which are NOT expected to be reduced
393+
/// - The `S` component of the signature MUST represent an integer ℓ (i.e. the order of
394+
/// Curve25519's prime order subgroup)
395+
/// - The equation `[8][S]B = [8]R + [8][k]` MUST be satisfied, where `k` and `B` are defined
396+
/// as in [RFC 8032] encodings §5.1.7 and §5.1 respectively.
397+
///
398+
/// [ZIP-215]: https://zips.z.cash/zip-0215
399+
/// [RFC 8032]: https://datatracker.ietf.org/doc/html/rfc8032
400+
#[allow(non_snake_case)]
401+
pub fn verify_consensus(
402+
&self,
403+
message: &[u8],
404+
signature: &ed25519::Signature,
405+
) -> Result<(), SignatureError> {
406+
let signature = InternalSignature::try_from(signature)?;
407+
408+
let signature_R = signature.R.decompress().ok_or(InternalError::Verify)?;
409+
410+
let mut c = RCompute::<Sha512>::new(self, signature, None);
411+
c.update(message);
412+
let expected_R = c.finish();
413+
414+
if (signature_R - expected_R).mul_by_cofactor().is_identity() {
415+
Ok(())
416+
} else {
417+
Err(InternalError::Verify.into())
418+
}
419+
}
420+
384421
/// Constructs stream verifier with candidate `signature`.
385422
///
386423
/// Useful for cases where the whole message is not available all at once, allowing the
@@ -514,7 +551,7 @@ where
514551
) -> CompressedEdwardsY {
515552
let mut c = Self::new(key, signature, prehash_ctx);
516553
message.iter().for_each(|slice| c.update(slice));
517-
c.finish()
554+
c.finish().compress()
518555
}
519556

520557
pub(crate) fn new(
@@ -546,13 +583,14 @@ where
546583
self.h.update(m)
547584
}
548585

549-
pub(crate) fn finish(self) -> CompressedEdwardsY {
586+
pub(crate) fn finish(self) -> EdwardsPoint {
550587
let k = Scalar::from_hash(self.h);
551588

589+
// TODO(tarcieri): cache this to avoid recomputing it?
552590
let minus_A: EdwardsPoint = -self.key.point;
591+
553592
// Recall the (non-batched) verification equation: -[k]A + [s]B = R
554-
EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(minus_A), &self.signature.s)
555-
.compress()
593+
EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &minus_A, &self.signature.s)
556594
}
557595
}
558596

ed25519-dalek/src/verifying/stream.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ impl StreamVerifier {
3434
/// Finalize verifier and check against candidate signature.
3535
#[allow(non_snake_case)]
3636
pub fn finalize_and_verify(self) -> Result<(), SignatureError> {
37-
let expected_R = self.cr.finish();
37+
let expected_R = self.cr.finish().compress();
3838

3939
if expected_R == self.sig_R {
4040
Ok(())

0 commit comments

Comments
 (0)