Skip to content

Commit 8c44385

Browse files
authored
feat(rust/signed-doc): Add validation rule to check authors of new doc version match previous those in version. (#519)
* feat(rust/signed-doc): add original author validation rule * refactor(rust/signed-doc): cleanup original validation rule test cases * fix(rust/signed-doc): add original author problem report * chore(rust/signed-doc): cleanup macro code
1 parent 24bb4d5 commit 8c44385

File tree

4 files changed

+147
-2
lines changed

4 files changed

+147
-2
lines changed

rust/catalyst-signed-doc-macro/src/rules/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub(crate) fn catalyst_signed_documents_rules_impl() -> anyhow::Result<TokenStre
3636
signature: crate::validator::rules::SignatureRule {
3737
mutlisig: false
3838
},
39+
original_author: crate::validator::rules::OriginalAuthorRule,
3940
}
4041
};
4142

rust/signed_doc/src/validator/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use std::{collections::HashMap, sync::LazyLock};
88
use catalyst_signed_doc_macro;
99
use catalyst_types::catalyst_id::role_index::RoleId;
1010
use rules::{
11-
ContentEncodingRule, ContentRule, ContentSchema, ContentTypeRule, IdRule, ParametersRule,
12-
RefRule, ReplyRule, Rules, SectionRule, SignatureKidRule, VerRule,
11+
ContentEncodingRule, ContentRule, ContentSchema, ContentTypeRule, IdRule, OriginalAuthorRule,
12+
ParametersRule, RefRule, ReplyRule, Rules, SectionRule, SignatureKidRule, VerRule,
1313
};
1414

1515
use crate::{
@@ -62,6 +62,7 @@ fn proposal_rule() -> Rules {
6262
exp: &[RoleId::Proposer],
6363
},
6464
signature: SignatureRule { mutlisig: false },
65+
original_author: OriginalAuthorRule,
6566
}
6667
}
6768

@@ -105,6 +106,7 @@ fn proposal_comment_rule() -> Rules {
105106
exp: &[RoleId::Role0],
106107
},
107108
signature: SignatureRule { mutlisig: false },
109+
original_author: OriginalAuthorRule,
108110
}
109111
}
110112

@@ -154,6 +156,7 @@ fn proposal_submission_action_rule() -> Rules {
154156
exp: &[RoleId::Proposer],
155157
},
156158
signature: SignatureRule { mutlisig: false },
159+
original_author: OriginalAuthorRule,
157160
}
158161
}
159162

rust/signed_doc/src/validator/rules/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod content_encoding;
1313
mod content_type;
1414
mod doc_ref;
1515
mod id;
16+
mod original_author;
1617
mod parameters;
1718
mod reply;
1819
mod section;
@@ -25,6 +26,7 @@ pub(crate) use content_encoding::ContentEncodingRule;
2526
pub(crate) use content_type::ContentTypeRule;
2627
pub(crate) use doc_ref::RefRule;
2728
pub(crate) use id::IdRule;
29+
pub(crate) use original_author::OriginalAuthorRule;
2830
pub(crate) use parameters::ParametersRule;
2931
pub(crate) use reply::ReplyRule;
3032
pub(crate) use section::SectionRule;
@@ -57,6 +59,8 @@ pub(crate) struct Rules {
5759
pub(crate) kid: SignatureKidRule,
5860
/// document's signatures validation rule
5961
pub(crate) signature: SignatureRule,
62+
/// Original Author validation rule.
63+
pub(crate) original_author: OriginalAuthorRule,
6064
}
6165

6266
impl Rules {
@@ -81,6 +85,7 @@ impl Rules {
8185
self.parameters.check(doc, provider).boxed(),
8286
self.kid.check(doc).boxed(),
8387
self.signature.check(doc, provider).boxed(),
88+
self.original_author.check(doc, provider).boxed(),
8489
];
8590

8691
let res = futures::future::join_all(rules)
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
//! Original Author Validation Rule
2+
3+
use crate::{providers::CatalystSignedDocumentProvider, CatalystSignedDocument};
4+
5+
/// Original Author Validation Rule
6+
#[derive(Debug)]
7+
pub(crate) struct OriginalAuthorRule;
8+
9+
impl OriginalAuthorRule {
10+
/// Field validation rule
11+
pub(crate) async fn check<Provider>(
12+
&self,
13+
doc: &CatalystSignedDocument,
14+
provider: &Provider,
15+
) -> anyhow::Result<bool>
16+
where
17+
Provider: CatalystSignedDocumentProvider,
18+
{
19+
let doc_id = doc.doc_id()?;
20+
let Some(original_doc) = provider.try_get_last_doc(doc_id).await? else {
21+
return Ok(true);
22+
};
23+
let is_valid = original_doc.authors() == doc.authors();
24+
if !is_valid {
25+
doc.report().functional_validation(
26+
&format!("New document authors must match the authors from the previous version for Document ID {doc_id}"),
27+
&format!(
28+
"Document's signatures must be identical to previous version for Document ID {doc_id}"
29+
),
30+
);
31+
}
32+
Ok(is_valid)
33+
}
34+
}
35+
36+
#[cfg(test)]
37+
mod tests {
38+
use catalyst_types::{
39+
catalyst_id::{role_index::RoleId, CatalystId},
40+
uuid::{UuidV4, UuidV7},
41+
};
42+
use ed25519_dalek::ed25519::signature::Signer;
43+
use test_case::test_case;
44+
45+
use super::*;
46+
use crate::{
47+
builder::tests::Builder, metadata::SupportedField, providers::tests::TestCatalystProvider,
48+
ContentType,
49+
};
50+
51+
#[derive(Clone)]
52+
struct CatalystAuthorId {
53+
sk: ed25519_dalek::SigningKey,
54+
kid: CatalystId,
55+
}
56+
57+
impl CatalystAuthorId {
58+
fn new() -> Self {
59+
let sk = ed25519_dalek::SigningKey::generate(&mut rand::rngs::OsRng);
60+
let pk = sk.verifying_key();
61+
let kid = CatalystId::new("cardano", None, pk).with_role(RoleId::Role0);
62+
Self { sk, kid }
63+
}
64+
}
65+
66+
fn doc_builder(
67+
doc_id: UuidV7,
68+
doc_ver: UuidV7,
69+
authors: [CatalystAuthorId; 3],
70+
) -> (UuidV7, [CatalystAuthorId; 3], CatalystSignedDocument) {
71+
let mut doc_builder = Builder::new()
72+
.with_metadata_field(SupportedField::Id(doc_id))
73+
.with_metadata_field(SupportedField::Ver(doc_ver))
74+
.with_metadata_field(SupportedField::Type(UuidV4::new().into()))
75+
.with_metadata_field(SupportedField::ContentType(ContentType::Json))
76+
.with_content(vec![1, 2, 3]);
77+
for author in &authors {
78+
doc_builder = doc_builder
79+
.add_signature(|m| author.sk.sign(&m).to_vec(), author.kid.clone())
80+
.unwrap();
81+
}
82+
(doc_id, authors, doc_builder.build())
83+
}
84+
85+
fn gen_authors() -> [CatalystAuthorId; 3] {
86+
[
87+
CatalystAuthorId::new(),
88+
CatalystAuthorId::new(),
89+
CatalystAuthorId::new(),
90+
]
91+
}
92+
93+
fn gen_next_ver_doc(
94+
doc_id: UuidV7,
95+
authors: [CatalystAuthorId; 3],
96+
) -> CatalystSignedDocument {
97+
let (_, _, new_doc) = doc_builder(doc_id, UuidV7::new(), authors);
98+
new_doc
99+
}
100+
101+
fn gen_original_doc_and_provider() -> (UuidV7, [CatalystAuthorId; 3], TestCatalystProvider) {
102+
let authors = gen_authors();
103+
let doc_id = UuidV7::new();
104+
let doc_ver_1 = UuidV7::new();
105+
let (_, _, doc_1) = doc_builder(doc_id, doc_ver_1, authors.clone());
106+
let mut provider = TestCatalystProvider::default();
107+
provider.add_document(None, &doc_1).unwrap();
108+
(doc_id, authors, provider)
109+
}
110+
111+
#[test_case(
112+
|| {
113+
let (doc_id, authors, provider) = gen_original_doc_and_provider();
114+
let doc_2 = gen_next_ver_doc(doc_id, authors);
115+
(doc_2, provider)
116+
} => true ;
117+
"Catalyst Signed Document has the same authors as the previous version"
118+
)]
119+
#[test_case(
120+
|| {
121+
let (doc_id, _, provider) = gen_original_doc_and_provider();
122+
let other_authors = gen_authors();
123+
let doc_2 = gen_next_ver_doc(doc_id, other_authors);
124+
(doc_2, provider)
125+
} => false ;
126+
"Catalyst Signed Document has the different authors from the previous version"
127+
)]
128+
#[tokio::test]
129+
async fn original_author_rule_test(
130+
doc_gen: impl FnOnce() -> (CatalystSignedDocument, TestCatalystProvider)
131+
) -> bool {
132+
let (doc_v2, provider) = doc_gen();
133+
134+
OriginalAuthorRule.check(&doc_v2, &provider).await.unwrap()
135+
}
136+
}

0 commit comments

Comments
 (0)