Skip to content

Commit 82be435

Browse files
committed
feat: verify epoch chaining in certificate chain
1 parent 8fa4b49 commit 82be435

File tree

1 file changed

+105
-1
lines changed

1 file changed

+105
-1
lines changed

mithril-common/src/certificate_chain/certificate_verifier.rs

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ pub enum CertificateVerifierError {
5858
#[error("certificate epoch unmatch error")]
5959
CertificateEpochUnmatch,
6060

61+
/// Error raised when validating the certificate chain if the current [Certificate]
62+
/// `epoch` is neither equal to the previous certificate `epoch` nor to the previous certificate `epoch - 1`
63+
#[error("certificate chain missing epoch error")]
64+
CertificateChainMissingEpoch,
65+
6166
/// Error raised when validating the certificate chain if the chain loops.
6267
#[error("certificate chain infinite loop error")]
6368
CertificateChainInfiniteLoop,
@@ -224,6 +229,21 @@ impl MithrilCertificateVerifier {
224229

225230
Err(anyhow!(CertificateVerifierError::CertificateEpochUnmatch))
226231
}
232+
233+
fn verify_epoch_chaining(
234+
&self,
235+
certificate: &Certificate,
236+
previous_certificate: &Certificate,
237+
) -> StdResult<()> {
238+
if certificate.epoch.has_gap_with(&previous_certificate.epoch) {
239+
return Err(anyhow!(
240+
CertificateVerifierError::CertificateChainMissingEpoch
241+
));
242+
}
243+
244+
Ok(())
245+
}
246+
227247
fn verify_aggregate_verification_key_chaining(
228248
&self,
229249
certificate: &Certificate,
@@ -312,6 +332,7 @@ impl CertificateVerifier for MithrilCertificateVerifier {
312332
&certificate.metadata.protocol_parameters,
313333
)?;
314334
self.verify_epoch_matches_protocol_message(certificate)?;
335+
self.verify_epoch_chaining(certificate, previous_certificate)?;
315336
self.verify_previous_hash_matches_previous_certificate_hash(
316337
certificate,
317338
previous_certificate,
@@ -366,7 +387,9 @@ mod tests {
366387

367388
use crate::certificate_chain::{CertificateRetrieverError, FakeCertificaterRetriever};
368389
use crate::crypto_helper::{tests_setup::*, ProtocolClerk};
369-
use crate::test_utils::{MithrilFixtureBuilder, TestLogger};
390+
use crate::test_utils::{
391+
CertificateChainBuilder, CertificateChainBuilderContext, MithrilFixtureBuilder, TestLogger,
392+
};
370393

371394
macro_rules! assert_error_matches {
372395
( $expected_error:path, $error:expr ) => {{
@@ -693,6 +716,87 @@ mod tests {
693716
)
694717
}
695718

719+
#[tokio::test]
720+
async fn verify_standard_certificate_fails_if_epoch_unmatch() {
721+
let (total_certificates, certificates_per_epoch) = (5, 1);
722+
let (fake_certificates, _) =
723+
setup_certificate_chain(total_certificates, certificates_per_epoch);
724+
let verifier = MockDependencyInjector::new().build_certificate_verifier();
725+
let mut certificate = fake_certificates[0].clone();
726+
certificate.epoch -= 1;
727+
certificate.hash = certificate.compute_hash();
728+
let previous_certificate = fake_certificates[1].clone();
729+
730+
let error = verifier
731+
.verify_standard_certificate(&certificate, &previous_certificate)
732+
.await
733+
.expect_err("verify_standard_certificate should fail");
734+
735+
assert_error_matches!(CertificateVerifierError::CertificateEpochUnmatch, error)
736+
}
737+
738+
#[tokio::test]
739+
async fn verify_standard_certificate_fails_if_has_missing_epoch() {
740+
fn create_epoch_gap_certificate(
741+
certificate: Certificate,
742+
context: &CertificateChainBuilderContext,
743+
) -> Certificate {
744+
let fixture = context.fixture;
745+
let modified_epoch = certificate.epoch + 1;
746+
let mut protocol_message = certificate.protocol_message.to_owned();
747+
protocol_message.set_message_part(
748+
ProtocolMessagePartKey::CurrentEpoch,
749+
modified_epoch.to_string(),
750+
);
751+
let signed_message = protocol_message.compute_hash();
752+
let mut modified_certificate = certificate;
753+
modified_certificate.epoch = modified_epoch;
754+
modified_certificate.protocol_message = protocol_message;
755+
modified_certificate.signed_message = signed_message.clone();
756+
let single_signatures = fixture
757+
.signers_fixture()
758+
.iter()
759+
.filter_map(|s| s.protocol_signer.sign(signed_message.as_bytes()))
760+
.collect::<Vec<_>>();
761+
let clerk = ProtocolClerk::from_signer(&fixture.signers_fixture()[0].protocol_signer);
762+
let modified_multi_signature = clerk
763+
.aggregate(&single_signatures, signed_message.as_bytes())
764+
.unwrap();
765+
modified_certificate.signature = CertificateSignature::MultiSignature(
766+
modified_certificate.signed_entity_type(),
767+
modified_multi_signature.into(),
768+
);
769+
770+
modified_certificate
771+
}
772+
773+
let (total_certificates, certificates_per_epoch) = (5, 1);
774+
let (fake_certificates, _) = CertificateChainBuilder::new()
775+
.with_total_certificates(total_certificates)
776+
.with_certificates_per_epoch(certificates_per_epoch)
777+
.with_standard_certificate_processor(&|certificate, context| {
778+
if context.is_last_certificate() {
779+
create_epoch_gap_certificate(certificate, context)
780+
} else {
781+
certificate
782+
}
783+
})
784+
.build();
785+
let verifier = MockDependencyInjector::new().build_certificate_verifier();
786+
let certificate = fake_certificates[0].clone();
787+
let previous_certificate = fake_certificates[1].clone();
788+
789+
let error = verifier
790+
.verify_standard_certificate(&certificate, &previous_certificate)
791+
.await
792+
.expect_err("verify_standard_certificate should fail");
793+
794+
assert_error_matches!(
795+
CertificateVerifierError::CertificateChainMissingEpoch,
796+
error
797+
)
798+
}
799+
696800
#[tokio::test]
697801
async fn verify_standard_certificate_fails_if_certificate_previous_hash_unmatch() {
698802
let (total_certificates, certificates_per_epoch) = (5, 1);

0 commit comments

Comments
 (0)