Skip to content

Commit 51cca94

Browse files
authored
feat(rust/signed-doc): New CollaboratorsRule type, validating collaborators metadata field (#555)
* added new Signers type * wip * add ReplyRule initialisation * wip * wip * fix * cleanup * fix clippy * add CollaboratorsRule type * fix fmt * fix clippy
1 parent 77cd6a8 commit 51cca94

File tree

6 files changed

+201
-39
lines changed

6 files changed

+201
-39
lines changed

rust/signed_doc/src/metadata/collaborators.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ impl Deref for Collaborators {
1717
}
1818
}
1919

20+
impl From<Vec<CatalystId>> for Collaborators {
21+
fn from(value: Vec<CatalystId>) -> Self {
22+
Self(value)
23+
}
24+
}
25+
2026
impl minicbor::Encode<()> for Collaborators {
2127
fn encode<W: minicbor::encode::Write>(
2228
&self,

rust/signed_doc/src/validator/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::{
1818
},
1919
metadata::DocType,
2020
providers::{CatalystSignedDocumentProvider, VerifyingKeyProvider},
21-
validator::rules::{SignatureRule, TemplateRule},
21+
validator::rules::{CollaboratorsRule, SignatureRule, TemplateRule},
2222
CatalystSignedDocument, ContentEncoding, ContentType,
2323
};
2424

@@ -55,6 +55,7 @@ fn proposal_rule() -> Rules {
5555
doc_ref: RefRule::NotSpecified,
5656
reply: ReplyRule::NotSpecified,
5757
section: SectionRule::NotSpecified,
58+
collaborators: CollaboratorsRule::NotSpecified,
5859
content: ContentRule::NotNil,
5960
kid: SignatureKidRule {
6061
allowed_roles: vec![RoleId::Proposer],
@@ -101,6 +102,7 @@ fn proposal_comment_rule() -> Rules {
101102
allowed_type: parameters.clone(),
102103
optional: false,
103104
},
105+
collaborators: CollaboratorsRule::NotSpecified,
104106
content: ContentRule::NotNil,
105107
kid: SignatureKidRule {
106108
allowed_roles: vec![RoleId::Role0],
@@ -153,6 +155,7 @@ fn proposal_submission_action_rule() -> Rules {
153155
},
154156
reply: ReplyRule::NotSpecified,
155157
section: SectionRule::NotSpecified,
158+
collaborators: CollaboratorsRule::NotSpecified,
156159
content: ContentRule::StaticSchema(ContentSchema::Json(proposal_action_json_schema)),
157160
kid: SignatureKidRule {
158161
allowed_roles: vec![RoleId::Proposer],
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
//! `collaborators` rule type impl.
2+
3+
use crate::CatalystSignedDocument;
4+
5+
/// `collaborators` field validation rule
6+
#[derive(Debug)]
7+
pub(crate) enum CollaboratorsRule {
8+
/// Is 'collaborators' specified
9+
#[allow(dead_code)]
10+
Specified {
11+
/// optional flag for the `collaborators` field
12+
optional: bool,
13+
},
14+
/// 'collaborators' is not specified
15+
NotSpecified,
16+
}
17+
18+
impl CollaboratorsRule {
19+
/// Field validation rule
20+
#[allow(clippy::unused_async)]
21+
pub(crate) async fn check(
22+
&self,
23+
doc: &CatalystSignedDocument,
24+
) -> anyhow::Result<bool> {
25+
if let Self::Specified { optional } = self {
26+
if doc.doc_meta().collaborators().is_empty() && !optional {
27+
doc.report().missing_field(
28+
"collaborators",
29+
"Document must have at least one entry in 'collaborators' field",
30+
);
31+
return Ok(false);
32+
}
33+
}
34+
if let Self::NotSpecified = self {
35+
if !doc.doc_meta().collaborators().is_empty() {
36+
doc.report().unknown_field(
37+
"collaborators",
38+
&format!(
39+
"{:#?}",
40+
doc.doc_meta()
41+
.collaborators()
42+
.iter()
43+
.map(ToString::to_string)
44+
.reduce(|a, b| format!("{a}, {b}"))
45+
),
46+
"Document does not expect to have a 'collaborators' field",
47+
);
48+
return Ok(false);
49+
}
50+
}
51+
52+
Ok(true)
53+
}
54+
}
55+
56+
#[cfg(test)]
57+
mod tests {
58+
use catalyst_types::catalyst_id::role_index::RoleId;
59+
use test_case::test_case;
60+
61+
use super::*;
62+
use crate::{
63+
builder::tests::Builder, metadata::SupportedField,
64+
validator::rules::utils::create_dummy_key_pair,
65+
};
66+
67+
#[test_case(
68+
|| {
69+
Builder::new()
70+
.with_metadata_field(SupportedField::Collaborators(
71+
vec![create_dummy_key_pair(RoleId::Role0).2].into()
72+
))
73+
.build()
74+
}
75+
=> true
76+
;
77+
"valid 'collaborators' field present"
78+
)]
79+
#[test_case(
80+
|| {
81+
Builder::new().build()
82+
}
83+
=> true
84+
;
85+
"missing 'collaborators' field"
86+
)]
87+
#[tokio::test]
88+
async fn section_rule_specified_optional_test(
89+
doc_gen: impl FnOnce() -> CatalystSignedDocument
90+
) -> bool {
91+
let rule = CollaboratorsRule::Specified { optional: true };
92+
93+
let doc = doc_gen();
94+
rule.check(&doc).await.unwrap()
95+
}
96+
97+
#[test_case(
98+
|| {
99+
Builder::new()
100+
.with_metadata_field(SupportedField::Collaborators(
101+
vec![create_dummy_key_pair(RoleId::Role0).2].into()
102+
))
103+
.build()
104+
}
105+
=> true
106+
;
107+
"valid 'collaborators' field present"
108+
)]
109+
#[test_case(
110+
|| {
111+
Builder::new().build()
112+
}
113+
=> false
114+
;
115+
"missing 'collaborators' field"
116+
)]
117+
#[tokio::test]
118+
async fn section_rule_specified_not_optional_test(
119+
doc_gen: impl FnOnce() -> CatalystSignedDocument
120+
) -> bool {
121+
let rule = CollaboratorsRule::Specified { optional: false };
122+
123+
let doc = doc_gen();
124+
rule.check(&doc).await.unwrap()
125+
}
126+
127+
#[test_case(
128+
|| {
129+
Builder::new().build()
130+
}
131+
=> true
132+
;
133+
"missing 'collaborators' field"
134+
)]
135+
#[test_case(
136+
|| {
137+
Builder::new()
138+
.with_metadata_field(SupportedField::Collaborators(
139+
vec![create_dummy_key_pair(RoleId::Role0).2].into()
140+
))
141+
.build()
142+
}
143+
=> false
144+
;
145+
"valid 'collaborators' field present"
146+
)]
147+
#[tokio::test]
148+
async fn section_rule_not_specified_test(
149+
doc_gen: impl FnOnce() -> CatalystSignedDocument
150+
) -> bool {
151+
let rule = CollaboratorsRule::NotSpecified;
152+
153+
let doc = doc_gen();
154+
rule.check(&doc).await.unwrap()
155+
}
156+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::{
88
CatalystSignedDocument,
99
};
1010

11+
mod collaborators;
1112
mod content;
1213
mod content_encoding;
1314
mod content_type;
@@ -23,6 +24,7 @@ mod template;
2324
mod utils;
2425
mod ver;
2526

27+
pub(crate) use collaborators::CollaboratorsRule;
2628
pub(crate) use content::{ContentRule, ContentSchema};
2729
pub(crate) use content_encoding::ContentEncodingRule;
2830
pub(crate) use content_type::ContentTypeRule;
@@ -58,6 +60,8 @@ pub(crate) struct Rules {
5860
pub(crate) section: SectionRule,
5961
/// 'parameters' field validation rule
6062
pub(crate) parameters: ParametersRule,
63+
/// 'collaborators' field validation rule
64+
pub(crate) collaborators: CollaboratorsRule,
6165
/// document's content validation rule
6266
pub(crate) content: ContentRule,
6367
/// `kid` field validation rule
@@ -88,6 +92,7 @@ impl Rules {
8892
self.reply.check(doc, provider).boxed(),
8993
self.section.check(doc).boxed(),
9094
self.parameters.check(doc, provider).boxed(),
95+
self.collaborators.check(doc).boxed(),
9196
self.content.check(doc).boxed(),
9297
self.kid.check(doc).boxed(),
9398
self.signature.check(doc, provider).boxed(),
@@ -132,6 +137,7 @@ impl Rules {
132137
doc_ref: RefRule::new(&spec.docs, &doc_spec.metadata.doc_ref)?,
133138
reply: ReplyRule::new(&spec.docs, &doc_spec.metadata.reply)?,
134139
section: SectionRule::NotSpecified,
140+
collaborators: CollaboratorsRule::NotSpecified,
135141
content: ContentRule::new(&doc_spec.payload)?,
136142
kid: SignatureKidRule::new(&doc_spec.signers.roles),
137143
signature: SignatureRule { mutlisig: false },

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

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -118,37 +118,7 @@ mod tests {
118118
use ed25519_dalek::ed25519::signature::Signer;
119119

120120
use super::*;
121-
use crate::{providers::tests::*, *};
122-
123-
mod helper {
124-
use std::str::FromStr;
125-
126-
use catalyst_types::catalyst_id::role_index::RoleId;
127-
128-
use crate::*;
129-
130-
pub(super) fn create_dummy_key_pair(
131-
role_index: RoleId
132-
) -> anyhow::Result<(
133-
ed25519_dalek::SigningKey,
134-
ed25519_dalek::VerifyingKey,
135-
CatalystId,
136-
)> {
137-
let sk = create_signing_key();
138-
let pk = sk.verifying_key();
139-
let kid = CatalystId::from_str(&format!(
140-
"id.catalyst://cardano/{}/{role_index}/0",
141-
base64_url::encode(pk.as_bytes())
142-
))?;
143-
144-
Ok((sk, pk, kid))
145-
}
146-
147-
pub(super) fn create_signing_key() -> ed25519_dalek::SigningKey {
148-
let mut csprng = rand::rngs::OsRng;
149-
ed25519_dalek::SigningKey::generate(&mut csprng)
150-
}
151-
}
121+
use crate::{providers::tests::*, validator::rules::utils::create_dummy_key_pair, *};
152122

153123
fn metadata() -> serde_json::Value {
154124
serde_json::json!({
@@ -177,7 +147,7 @@ mod tests {
177147

178148
#[tokio::test]
179149
async fn single_signature_validation_test() {
180-
let (sk, pk, kid) = helper::create_dummy_key_pair(RoleId::Role0).unwrap();
150+
let (sk, pk, kid) = create_dummy_key_pair(RoleId::Role0);
181151

182152
let signed_doc = Builder::new()
183153
.with_json_metadata(metadata())
@@ -207,7 +177,7 @@ mod tests {
207177
.unwrap());
208178

209179
// case: signed with different key
210-
let (another_sk, ..) = helper::create_dummy_key_pair(RoleId::Role0).unwrap();
180+
let (another_sk, ..) = create_dummy_key_pair(RoleId::Role0);
211181
let invalid_doc = signed_doc
212182
.into_builder()
213183
.unwrap()
@@ -235,10 +205,10 @@ mod tests {
235205

236206
#[tokio::test]
237207
async fn multiple_signatures_validation_test() {
238-
let (sk1, pk1, kid1) = helper::create_dummy_key_pair(RoleId::Role0).unwrap();
239-
let (sk2, pk2, kid2) = helper::create_dummy_key_pair(RoleId::Role0).unwrap();
240-
let (sk3, pk3, kid3) = helper::create_dummy_key_pair(RoleId::Role0).unwrap();
241-
let (_, pk_n, kid_n) = helper::create_dummy_key_pair(RoleId::Role0).unwrap();
208+
let (sk1, pk1, kid1) = create_dummy_key_pair(RoleId::Role0);
209+
let (sk2, pk2, kid2) = create_dummy_key_pair(RoleId::Role0);
210+
let (sk3, pk3, kid3) = create_dummy_key_pair(RoleId::Role0);
211+
let (_, pk_n, kid_n) = create_dummy_key_pair(RoleId::Role0);
242212

243213
let signed_doc = Builder::new()
244214
.with_json_metadata(metadata())
@@ -391,7 +361,7 @@ mod tests {
391361

392362
#[tokio::test]
393363
async fn special_cbor_cases() {
394-
let (sk, pk, kid) = helper::create_dummy_key_pair(RoleId::Role0).unwrap();
364+
let (sk, pk, kid) = create_dummy_key_pair(RoleId::Role0);
395365
let mut provider = TestCatalystProvider::default();
396366
provider.add_pk(kid.clone(), pk);
397367

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,24 @@ pub(crate) fn content_json_schema_check(
4949

5050
true
5151
}
52+
53+
#[cfg(test)]
54+
pub(super) fn create_dummy_key_pair(
55+
role_index: catalyst_types::catalyst_id::role_index::RoleId
56+
) -> (
57+
ed25519_dalek::SigningKey,
58+
ed25519_dalek::VerifyingKey,
59+
catalyst_types::catalyst_id::CatalystId,
60+
) {
61+
let sk = create_signing_key();
62+
let pk = sk.verifying_key();
63+
let kid =
64+
catalyst_types::catalyst_id::CatalystId::new("cardano", None, pk).with_role(role_index);
65+
(sk, pk, kid)
66+
}
67+
68+
#[cfg(test)]
69+
pub(super) fn create_signing_key() -> ed25519_dalek::SigningKey {
70+
let mut csprng = rand::rngs::OsRng;
71+
ed25519_dalek::SigningKey::generate(&mut csprng)
72+
}

0 commit comments

Comments
 (0)