Skip to content
Merged
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 rust/catalyst-signed-doc-macro/src/rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub(crate) fn catalyst_signed_documents_rules_impl() -> anyhow::Result<TokenStre
signature: crate::validator::rules::SignatureRule {
mutlisig: false
},
original_author: crate::validator::rules::OriginalAuthorRule,
}
};

Expand Down
7 changes: 5 additions & 2 deletions rust/signed_doc/src/validator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use std::{collections::HashMap, sync::LazyLock};
use catalyst_signed_doc_macro;
use catalyst_types::catalyst_id::role_index::RoleId;
use rules::{
ContentEncodingRule, ContentRule, ContentSchema, ContentTypeRule, IdRule, ParametersRule,
RefRule, ReplyRule, Rules, SectionRule, SignatureKidRule, VerRule,
ContentEncodingRule, ContentRule, ContentSchema, ContentTypeRule, IdRule, OriginalAuthorRule,
ParametersRule, RefRule, ReplyRule, Rules, SectionRule, SignatureKidRule, VerRule,
};

use crate::{
Expand Down Expand Up @@ -62,6 +62,7 @@ fn proposal_rule() -> Rules {
exp: &[RoleId::Proposer],
},
signature: SignatureRule { mutlisig: false },
original_author: OriginalAuthorRule,
}
}

Expand Down Expand Up @@ -105,6 +106,7 @@ fn proposal_comment_rule() -> Rules {
exp: &[RoleId::Role0],
},
signature: SignatureRule { mutlisig: false },
original_author: OriginalAuthorRule,
}
}

Expand Down Expand Up @@ -154,6 +156,7 @@ fn proposal_submission_action_rule() -> Rules {
exp: &[RoleId::Proposer],
},
signature: SignatureRule { mutlisig: false },
original_author: OriginalAuthorRule,
}
}

Expand Down
5 changes: 5 additions & 0 deletions rust/signed_doc/src/validator/rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod content_encoding;
mod content_type;
mod doc_ref;
mod id;
mod original_author;
mod parameters;
mod reply;
mod section;
Expand All @@ -25,6 +26,7 @@ pub(crate) use content_encoding::ContentEncodingRule;
pub(crate) use content_type::ContentTypeRule;
pub(crate) use doc_ref::RefRule;
pub(crate) use id::IdRule;
pub(crate) use original_author::OriginalAuthorRule;
pub(crate) use parameters::ParametersRule;
pub(crate) use reply::ReplyRule;
pub(crate) use section::SectionRule;
Expand Down Expand Up @@ -57,6 +59,8 @@ pub(crate) struct Rules {
pub(crate) kid: SignatureKidRule,
/// document's signatures validation rule
pub(crate) signature: SignatureRule,
/// Original Author validation rule.
pub(crate) original_author: OriginalAuthorRule,
}

impl Rules {
Expand All @@ -81,6 +85,7 @@ impl Rules {
self.parameters.check(doc, provider).boxed(),
self.kid.check(doc).boxed(),
self.signature.check(doc, provider).boxed(),
self.original_author.check(doc, provider).boxed(),
];

let res = futures::future::join_all(rules)
Expand Down
136 changes: 136 additions & 0 deletions rust/signed_doc/src/validator/rules/original_author.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//! Original Author Validation Rule

use crate::{providers::CatalystSignedDocumentProvider, CatalystSignedDocument};

/// Original Author Validation Rule
#[derive(Debug)]
pub(crate) struct OriginalAuthorRule;

impl OriginalAuthorRule {
/// Field validation rule
pub(crate) async fn check<Provider>(
&self,
doc: &CatalystSignedDocument,
provider: &Provider,
) -> anyhow::Result<bool>
where
Provider: CatalystSignedDocumentProvider,
{
let doc_id = doc.doc_id()?;
let Some(original_doc) = provider.try_get_last_doc(doc_id).await? else {
return Ok(true);
};
let is_valid = original_doc.authors() == doc.authors();
if !is_valid {
doc.report().functional_validation(
&format!("New document authors must match the authors from the previous version for Document ID {doc_id}"),
&format!(
"Document's signatures must be identical to previous version for Document ID {doc_id}"
),
);
}
Ok(is_valid)
}
}

#[cfg(test)]
mod tests {
use catalyst_types::{
catalyst_id::{role_index::RoleId, CatalystId},
uuid::{UuidV4, UuidV7},
};
use ed25519_dalek::ed25519::signature::Signer;
use test_case::test_case;

use super::*;
use crate::{
builder::tests::Builder, metadata::SupportedField, providers::tests::TestCatalystProvider,
ContentType,
};

#[derive(Clone)]
struct CatalystAuthorId {
sk: ed25519_dalek::SigningKey,
kid: CatalystId,
}

impl CatalystAuthorId {
fn new() -> Self {
let sk = ed25519_dalek::SigningKey::generate(&mut rand::rngs::OsRng);
let pk = sk.verifying_key();
let kid = CatalystId::new("cardano", None, pk).with_role(RoleId::Role0);
Self { sk, kid }
}
}

fn doc_builder(
doc_id: UuidV7,
doc_ver: UuidV7,
authors: [CatalystAuthorId; 3],
) -> (UuidV7, [CatalystAuthorId; 3], CatalystSignedDocument) {
let mut doc_builder = Builder::new()
.with_metadata_field(SupportedField::Id(doc_id))
.with_metadata_field(SupportedField::Ver(doc_ver))
.with_metadata_field(SupportedField::Type(UuidV4::new().into()))
.with_metadata_field(SupportedField::ContentType(ContentType::Json))
.with_content(vec![1, 2, 3]);
for author in &authors {
doc_builder = doc_builder
.add_signature(|m| author.sk.sign(&m).to_vec(), author.kid.clone())
.unwrap();
}
(doc_id, authors, doc_builder.build())
}

fn gen_authors() -> [CatalystAuthorId; 3] {
[
CatalystAuthorId::new(),
CatalystAuthorId::new(),
CatalystAuthorId::new(),
]
}

fn gen_next_ver_doc(
doc_id: UuidV7,
authors: [CatalystAuthorId; 3],
) -> CatalystSignedDocument {
let (_, _, new_doc) = doc_builder(doc_id, UuidV7::new(), authors);
new_doc
}

fn gen_original_doc_and_provider() -> (UuidV7, [CatalystAuthorId; 3], TestCatalystProvider) {
let authors = gen_authors();
let doc_id = UuidV7::new();
let doc_ver_1 = UuidV7::new();
let (_, _, doc_1) = doc_builder(doc_id, doc_ver_1, authors.clone());
let mut provider = TestCatalystProvider::default();
provider.add_document(None, &doc_1).unwrap();
(doc_id, authors, provider)
}

#[test_case(
|| {
let (doc_id, authors, provider) = gen_original_doc_and_provider();
let doc_2 = gen_next_ver_doc(doc_id, authors);
(doc_2, provider)
} => true ;
"Catalyst Signed Document has the same authors as the previous version"
)]
#[test_case(
|| {
let (doc_id, _, provider) = gen_original_doc_and_provider();
let other_authors = gen_authors();
let doc_2 = gen_next_ver_doc(doc_id, other_authors);
(doc_2, provider)
} => false ;
"Catalyst Signed Document has the different authors from the previous version"
)]
#[tokio::test]
async fn original_author_rule_test(
doc_gen: impl FnOnce() -> (CatalystSignedDocument, TestCatalystProvider)
) -> bool {
let (doc_v2, provider) = doc_gen();

OriginalAuthorRule.check(&doc_v2, &provider).await.unwrap()
}
}
Loading