@@ -17,6 +17,7 @@ use curve25519_dalek::{
1717 edwards:: { CompressedEdwardsY , EdwardsPoint } ,
1818 montgomery:: MontgomeryPoint ,
1919 scalar:: Scalar ,
20+ traits:: IsIdentity ,
2021} ;
2122
2223use 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
0 commit comments