Skip to content

Commit 85a96a3

Browse files
committed
feat: initial
1 parent b35db7c commit 85a96a3

File tree

3 files changed

+130
-84
lines changed

3 files changed

+130
-84
lines changed

rust/signed_doc/src/validator/mod.rs

Lines changed: 6 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ use std::{
88
sync::{Arc, LazyLock},
99
};
1010

11-
use anyhow::Context;
12-
use catalyst_types::{catalyst_id::role_index::RoleId, problem_report::ProblemReport};
11+
use catalyst_types::catalyst_id::role_index::RoleId;
1312
use rules::{
1413
ContentEncodingRule, ContentRule, ContentSchema, ContentTypeRule, IdRule, ParametersRule,
1514
RefRule, ReplyRule, Rules, SectionRule, SignatureKidRule, VerRule,
@@ -22,7 +21,7 @@ use crate::{
2221
},
2322
metadata::DocType,
2423
providers::{CatalystSignedDocumentProvider, VerifyingKeyProvider},
25-
signature::{tbs_data, Signature},
24+
validator::rules::SignatureRule,
2625
CatalystSignedDocument, ContentEncoding, ContentType,
2726
};
2827

@@ -62,6 +61,7 @@ fn proposal_rule() -> Rules {
6261
kid: SignatureKidRule {
6362
exp: &[RoleId::Proposer],
6463
},
64+
signature: SignatureRule { mutlisig: true },
6565
}
6666
}
6767

@@ -104,6 +104,7 @@ fn proposal_comment_rule() -> Rules {
104104
kid: SignatureKidRule {
105105
exp: &[RoleId::Role0],
106106
},
107+
signature: SignatureRule { mutlisig: true },
107108
}
108109
}
109110

@@ -152,6 +153,7 @@ fn proposal_submission_action_rule() -> Rules {
152153
kid: SignatureKidRule {
153154
exp: &[RoleId::Proposer],
154155
},
156+
signature: SignatureRule { mutlisig: true },
155157
}
156158
}
157159

@@ -185,7 +187,7 @@ pub async fn validate<Provider>(
185187
provider: &Provider,
186188
) -> anyhow::Result<bool>
187189
where
188-
Provider: CatalystSignedDocumentProvider,
190+
Provider: CatalystSignedDocumentProvider + VerifyingKeyProvider,
189191
{
190192
let Ok(doc_type) = doc.doc_type() else {
191193
doc.report().missing_field(
@@ -207,82 +209,6 @@ where
207209
rules.check(doc, provider).await
208210
}
209211

210-
/// Verify document signatures.
211-
/// Return true if all signatures are valid, otherwise return false.
212-
///
213-
/// # Errors
214-
/// If `provider` returns error, fails fast throwing that error.
215-
pub async fn validate_signatures(
216-
doc: &CatalystSignedDocument,
217-
provider: &impl VerifyingKeyProvider,
218-
) -> anyhow::Result<bool> {
219-
if doc.signatures().is_empty() {
220-
doc.report().other(
221-
"Catalyst Signed Document is unsigned",
222-
"During Catalyst Signed Document signature validation",
223-
);
224-
return Ok(false);
225-
}
226-
227-
let sign_rules = doc
228-
.signatures()
229-
.iter()
230-
.map(|sign| validate_signature(doc, sign, provider, doc.report()));
231-
232-
let res = futures::future::join_all(sign_rules)
233-
.await
234-
.into_iter()
235-
.collect::<anyhow::Result<Vec<_>>>()?
236-
.iter()
237-
.all(|res| *res);
238-
239-
Ok(res)
240-
}
241-
242-
/// A single signature validation function
243-
async fn validate_signature<Provider>(
244-
doc: &CatalystSignedDocument,
245-
sign: &Signature,
246-
provider: &Provider,
247-
report: &ProblemReport,
248-
) -> anyhow::Result<bool>
249-
where
250-
Provider: VerifyingKeyProvider,
251-
{
252-
let kid = sign.kid();
253-
254-
let Some(pk) = provider.try_get_key(kid).await? else {
255-
report.other(
256-
&format!("Missing public key for {kid}."),
257-
"During public key extraction",
258-
);
259-
return Ok(false);
260-
};
261-
262-
let tbs_data = tbs_data(kid, doc.doc_meta(), doc.content()).context("Probably a bug, cannot build CBOR COSE bytes for signature verification from the structurally valid COSE object.")?;
263-
264-
let Ok(signature_bytes) = sign.signature().try_into() else {
265-
report.invalid_value(
266-
"cose signature",
267-
&format!("{}", sign.signature().len()),
268-
&format!("must be {}", ed25519_dalek::Signature::BYTE_SIZE),
269-
"During encoding cose signature to bytes",
270-
);
271-
return Ok(false);
272-
};
273-
274-
let signature = ed25519_dalek::Signature::from_bytes(signature_bytes);
275-
if pk.verify_strict(&tbs_data, &signature).is_err() {
276-
report.functional_validation(
277-
&format!("Verification failed for signature with Key ID {kid}"),
278-
"During signature validation with verifying key",
279-
);
280-
return Ok(false);
281-
}
282-
283-
Ok(true)
284-
}
285-
286212
#[cfg(test)]
287213
mod tests {
288214
use crate::validator::document_rules_init;

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
44
use futures::FutureExt;
55

6-
use crate::{providers::CatalystSignedDocumentProvider, CatalystSignedDocument};
6+
use crate::{
7+
providers::{CatalystSignedDocumentProvider, VerifyingKeyProvider},
8+
CatalystSignedDocument,
9+
};
710

811
mod content_encoding;
912
mod content_type;
@@ -12,6 +15,7 @@ mod id;
1215
mod parameters;
1316
mod reply;
1417
mod section;
18+
mod signature;
1519
mod signature_kid;
1620
mod template;
1721
mod ver;
@@ -23,6 +27,7 @@ pub(crate) use id::IdRule;
2327
pub(crate) use parameters::ParametersRule;
2428
pub(crate) use reply::ReplyRule;
2529
pub(crate) use section::SectionRule;
30+
pub(crate) use signature::SignatureRule;
2631
pub(crate) use signature_kid::SignatureKidRule;
2732
pub(crate) use template::{ContentRule, ContentSchema};
2833
pub(crate) use ver::VerRule;
@@ -49,6 +54,8 @@ pub(crate) struct Rules {
4954
pub(crate) parameters: ParametersRule,
5055
/// `kid` field validation rule
5156
pub(crate) kid: SignatureKidRule,
57+
/// document's signatures validation rule
58+
pub(crate) signature: SignatureRule,
5259
}
5360

5461
impl Rules {
@@ -59,7 +66,7 @@ impl Rules {
5966
provider: &Provider,
6067
) -> anyhow::Result<bool>
6168
where
62-
Provider: CatalystSignedDocumentProvider,
69+
Provider: CatalystSignedDocumentProvider + VerifyingKeyProvider,
6370
{
6471
let rules = [
6572
self.id.check(doc, provider).boxed(),
@@ -74,12 +81,15 @@ impl Rules {
7481
self.kid.check(doc).boxed(),
7582
];
7683

77-
let res = futures::future::join_all(rules)
84+
let rules_res = futures::future::join_all(rules)
7885
.await
7986
.into_iter()
8087
.collect::<anyhow::Result<Vec<_>>>()?
8188
.iter()
8289
.all(|res| *res);
83-
Ok(res)
90+
91+
let sig_res = self.signature.check(doc, provider).await?;
92+
93+
Ok(rules_res && sig_res)
8494
}
8595
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//! Validator for Signatures
2+
3+
use anyhow::Context;
4+
use catalyst_types::problem_report::ProblemReport;
5+
6+
use crate::{
7+
providers::{CatalystSignedDocumentProvider, VerifyingKeyProvider},
8+
signature::{tbs_data, Signature},
9+
CatalystSignedDocument,
10+
};
11+
12+
#[derive(Debug)]
13+
pub(crate) struct SignatureRule {
14+
/// Allows multiple signatures.
15+
pub(crate) mutlisig: bool,
16+
}
17+
18+
impl SignatureRule {
19+
/// Verify document signatures.
20+
/// Return true if all signatures are valid, otherwise return false.
21+
///
22+
/// # Errors
23+
/// If `provider` returns error, fails fast throwing that error.
24+
pub(crate) async fn check<Provider>(
25+
&self,
26+
doc: &CatalystSignedDocument,
27+
provider: &Provider,
28+
) -> anyhow::Result<bool>
29+
where
30+
Provider: CatalystSignedDocumentProvider + VerifyingKeyProvider,
31+
{
32+
if doc.signatures().is_empty() {
33+
doc.report().other(
34+
"Catalyst Signed Document is unsigned",
35+
"During Catalyst Signed Document signature validation",
36+
);
37+
return Ok(false);
38+
}
39+
40+
if !self.mutlisig && doc.signatures().len() > 1 {
41+
doc.report().other(
42+
format!(
43+
"Multi-signature is not allowed, found {} signatures",
44+
doc.signatures().len()
45+
)
46+
.as_str(),
47+
"During Catalyst Signed Document signature validation",
48+
);
49+
return Ok(false);
50+
}
51+
52+
let sign_rules = doc
53+
.signatures()
54+
.iter()
55+
.map(|sign| validate_signature(doc, sign, provider, doc.report()));
56+
57+
let res = futures::future::join_all(sign_rules)
58+
.await
59+
.into_iter()
60+
.collect::<anyhow::Result<Vec<_>>>()?
61+
.iter()
62+
.all(|res| *res);
63+
64+
Ok(res)
65+
}
66+
}
67+
68+
/// A single signature validation function
69+
async fn validate_signature<Provider>(
70+
doc: &CatalystSignedDocument,
71+
sign: &Signature,
72+
provider: &Provider,
73+
report: &ProblemReport,
74+
) -> anyhow::Result<bool>
75+
where
76+
Provider: VerifyingKeyProvider,
77+
{
78+
let kid = sign.kid();
79+
80+
let Some(pk) = provider.try_get_key(kid).await? else {
81+
report.other(
82+
&format!("Missing public key for {kid}."),
83+
"During public key extraction",
84+
);
85+
return Ok(false);
86+
};
87+
88+
let tbs_data = tbs_data(kid, doc.doc_meta(), doc.content()).context("Probably a bug, cannot build CBOR COSE bytes for signature verification from the structurally valid COSE object.")?;
89+
90+
let Ok(signature_bytes) = sign.signature().try_into() else {
91+
report.invalid_value(
92+
"cose signature",
93+
&format!("{}", sign.signature().len()),
94+
&format!("must be {}", ed25519_dalek::Signature::BYTE_SIZE),
95+
"During encoding cose signature to bytes",
96+
);
97+
return Ok(false);
98+
};
99+
100+
let signature = ed25519_dalek::Signature::from_bytes(signature_bytes);
101+
if pk.verify_strict(&tbs_data, &signature).is_err() {
102+
report.functional_validation(
103+
&format!("Verification failed for signature with Key ID {kid}"),
104+
"During signature validation with verifying key",
105+
);
106+
return Ok(false);
107+
}
108+
109+
Ok(true)
110+
}

0 commit comments

Comments
 (0)