Skip to content

Commit 0f40e96

Browse files
authored
Merge branch 'main' into feat/signed-doc-validation-optional-content-type
2 parents 0978efc + eb5e0d9 commit 0f40e96

File tree

8 files changed

+226
-57
lines changed

8 files changed

+226
-57
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//! `RefRule` generation
2+
3+
use proc_macro2::TokenStream;
4+
use quote::quote;
5+
6+
use crate::signed_doc_spec::{self, IsRequired};
7+
8+
/// Generating `RefRule` instantiation
9+
pub(crate) fn ref_rule(ref_spec: &signed_doc_spec::doc_ref::Ref) -> anyhow::Result<TokenStream> {
10+
let optional = match ref_spec.required {
11+
IsRequired::Yes => false,
12+
IsRequired::Optional => true,
13+
IsRequired::Excluded => {
14+
return Ok(quote! {
15+
crate::validator::rules::RefRule::NotSpecified
16+
});
17+
},
18+
};
19+
20+
anyhow::ensure!(!ref_spec.doc_type.is_empty(), "'type' field should exists and has at least one entry for the required 'ref' metadata definition");
21+
22+
let const_type_name_idents = ref_spec.doc_type.iter().map(|doc_name| {
23+
let const_type_name_ident = doc_name.ident();
24+
quote! {
25+
crate::doc_types::#const_type_name_ident
26+
}
27+
});
28+
let multiple = ref_spec.multiple.ok_or(anyhow::anyhow!(
29+
"'multiple' field should exists for the required 'ref' metadata definition"
30+
))?;
31+
Ok(quote! {
32+
crate::validator::rules::RefRule::Specified {
33+
exp_ref_types: vec![ #(#const_type_name_idents,)* ],
34+
multiple: #multiple,
35+
optional: #optional,
36+
}
37+
})
38+
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! `catalyst_signed_documents_rules!` macro implementation
22
3+
pub(crate) mod doc_ref;
4+
35
use proc_macro2::TokenStream;
46
use quote::quote;
57

@@ -10,9 +12,10 @@ pub(crate) fn catalyst_signed_documents_rules_impl() -> anyhow::Result<TokenStre
1012
let spec = CatalystSignedDocSpec::load_signed_doc_spec()?;
1113

1214
let mut rules_definitions = Vec::new();
13-
for (doc_name, _doc_spec) in spec.docs {
15+
for (doc_name, doc_spec) in spec.docs {
1416
let const_type_name_ident = doc_name.ident();
1517

18+
let ref_rule = doc_ref::ref_rule(&doc_spec.metadata.doc_ref)?;
1619
// TODO: implement a proper initialization for all specific validation rules
1720
let rules = quote! {
1821
crate::validator::rules::Rules {
@@ -25,7 +28,7 @@ pub(crate) fn catalyst_signed_documents_rules_impl() -> anyhow::Result<TokenStre
2528
},
2629
content: crate::validator::rules::ContentRule::NotSpecified,
2730
parameters: crate::validator::rules::ParametersRule::NotSpecified,
28-
doc_ref: crate::validator::rules::RefRule::NotSpecified,
31+
doc_ref: #ref_rule,
2932
reply: crate::validator::rules::ReplyRule::NotSpecified,
3033
section: crate::validator::rules::SectionRule::NotSpecified,
3134
kid: crate::validator::rules::SignatureKidRule {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//! `signed_doc.json` "ref" field JSON definition
2+
3+
use crate::signed_doc_spec::{DocTypes, IsRequired};
4+
5+
/// `signed_doc.json` "ref" field JSON object
6+
#[derive(serde::Deserialize)]
7+
#[allow(clippy::missing_docs_in_private_items)]
8+
pub(crate) struct Ref {
9+
pub(crate) required: IsRequired,
10+
#[serde(rename = "type")]
11+
pub(crate) doc_type: DocTypes,
12+
pub(crate) multiple: Option<bool>,
13+
}

rust/catalyst-signed-doc-macro/src/signed_doc_spec.rs renamed to rust/catalyst-signed-doc-macro/src/signed_doc_spec/mod.rs

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
//! Catalyst Signed Document spec type
22
3-
use std::collections::HashMap;
3+
pub(crate) mod doc_ref;
4+
5+
use std::{collections::HashMap, ops::Deref};
46

5-
use anyhow::Context;
67
use proc_macro2::Ident;
78
use quote::format_ident;
89

@@ -43,15 +44,66 @@ pub(crate) struct DocSpec {
4344
/// Document type UUID v4 value
4445
#[serde(rename = "type")]
4546
pub(crate) doc_type: String,
47+
48+
/// Document type metadata definitions
49+
pub(crate) metadata: Metadata,
50+
}
51+
52+
/// Document's metadata fields definition
53+
#[derive(serde::Deserialize)]
54+
#[allow(clippy::missing_docs_in_private_items)]
55+
pub(crate) struct Metadata {
56+
#[serde(rename = "ref")]
57+
pub(crate) doc_ref: doc_ref::Ref,
58+
}
59+
60+
/// "required" field definition
61+
#[derive(serde::Deserialize)]
62+
#[serde(rename_all = "lowercase")]
63+
#[allow(clippy::missing_docs_in_private_items)]
64+
pub(crate) enum IsRequired {
65+
Yes,
66+
Excluded,
67+
Optional,
68+
}
69+
70+
/// A helper type for deserialization "type" metadata field
71+
pub(crate) struct DocTypes(Vec<DocumentName>);
72+
73+
impl Deref for DocTypes {
74+
type Target = Vec<DocumentName>;
75+
76+
fn deref(&self) -> &Self::Target {
77+
&self.0
78+
}
79+
}
80+
81+
impl<'de> serde::Deserialize<'de> for DocTypes {
82+
#[allow(clippy::missing_docs_in_private_items)]
83+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
84+
where D: serde::Deserializer<'de> {
85+
#[derive(serde::Deserialize)]
86+
#[serde(untagged)]
87+
enum SingleOrVec {
88+
Single(DocumentName),
89+
Multiple(Vec<DocumentName>),
90+
}
91+
let value = Option::<SingleOrVec>::deserialize(deserializer)?;
92+
let result = match value {
93+
Some(SingleOrVec::Single(item)) => vec![item],
94+
Some(SingleOrVec::Multiple(items)) => items,
95+
None => vec![],
96+
};
97+
Ok(Self(result))
98+
}
4699
}
47100

48101
impl CatalystSignedDocSpec {
49102
/// Loading a Catalyst Signed Documents spec from the `signed_doc.json`
50103
// #[allow(dependency_on_unit_never_type_fallback)]
51104
pub(crate) fn load_signed_doc_spec() -> anyhow::Result<CatalystSignedDocSpec> {
52-
let signed_doc_str = include_str!("../../../specs/signed_doc.json");
53-
let signed_doc_spec = serde_json::from_str(signed_doc_str)
54-
.context("Catalyst Signed Documents spec must be a JSON object")?;
105+
let signed_doc_str = include_str!("../../../../specs/signed_doc.json");
106+
let signed_doc_spec = serde_json::from_str(signed_doc_str)?;
55107
Ok(signed_doc_spec)
56108
}
57109
}

rust/rbac-registration/src/cardano/cip509/cip509.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use cardano_blockchain_types::{
1313
pallas_addresses::{Address, ShelleyAddress},
1414
pallas_primitives::{conway, Nullable},
1515
pallas_traverse::MultiEraTx,
16-
MetadatumLabel, MultiEraBlock, StakeAddress, TxnIndex,
16+
MetadatumLabel, MultiEraBlock, TxnIndex,
1717
};
1818
use catalyst_types::{
1919
catalyst_id::{role_index::RoleId, CatalystId},
@@ -36,7 +36,7 @@ use crate::cardano::cip509::{
3636
types::{PaymentHistory, TxInputHash, ValidationSignature},
3737
utils::Cip0134UriSet,
3838
validation::{
39-
validate_aux, validate_role_data, validate_self_sign_cert, validate_stake_public_key,
39+
validate_aux, validate_cert_addrs, validate_role_data, validate_self_sign_cert,
4040
validate_txn_inputs_hash,
4141
},
4242
x509_chunks::X509Chunks,
@@ -172,12 +172,11 @@ impl Cip509 {
172172
txn.transaction_body.auxiliary_data_hash.as_ref(),
173173
&cip509.report,
174174
);
175-
if cip509.role_data(RoleId::Role0).is_some() {
176-
// The following check is only performed for the role 0.
177-
validate_stake_public_key(txn, cip509.certificate_uris(), &cip509.report);
178-
}
179175
if let Some(metadata) = &cip509.metadata {
180176
cip509.catalyst_id = validate_role_data(metadata, block.network(), &cip509.report);
177+
// General check for all roles, check whether the addresses in the certificate URIs are
178+
// witnessed in the transaction.
179+
validate_cert_addrs(txn, cip509.certificate_uris(), &report);
181180
validate_self_sign_cert(metadata, &report);
182181
}
183182

@@ -276,12 +275,15 @@ impl Cip509 {
276275
self.catalyst_id.as_ref()
277276
}
278277

279-
/// Returns a list of role 0 stake addresses.
278+
/// Returns a list of addresses extracted from certificate URIs of a specific role.
280279
#[must_use]
281-
pub fn role_0_stake_addresses(&self) -> HashSet<StakeAddress> {
280+
pub fn certificate_addresses(
281+
&self,
282+
role: usize,
283+
) -> HashSet<Address> {
282284
self.metadata
283285
.as_ref()
284-
.map(|m| m.certificate_uris.stake_addresses(0))
286+
.map(|m| m.certificate_uris.role_addresses(role))
285287
.unwrap_or_default()
286288
}
287289

rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -72,24 +72,57 @@ impl Cip0134UriSet {
7272
self.x_uris().is_empty() && self.c_uris().is_empty()
7373
}
7474

75-
/// Returns a list of stake addresses by the given index.
75+
/// Returns a list of addresses by the given role.
7676
#[must_use]
77-
pub fn stake_addresses(
77+
pub fn role_addresses(
7878
&self,
79-
index: usize,
80-
) -> HashSet<StakeAddress> {
79+
role: usize,
80+
) -> HashSet<Address> {
8181
let mut result = HashSet::new();
8282

83-
if let Some(uris) = self.x_uris().get(&index) {
84-
result.extend(convert_stake_addresses(uris));
83+
if let Some(uris) = self.x_uris().get(&role) {
84+
result.extend(uris.iter().map(|uri| uri.address().clone()));
8585
}
86-
if let Some(uris) = self.c_uris().get(&index) {
87-
result.extend(convert_stake_addresses(uris));
86+
if let Some(uris) = self.c_uris().get(&role) {
87+
result.extend(uris.iter().map(|uri| uri.address().clone()));
8888
}
8989

9090
result
9191
}
9292

93+
/// Returns a list of stake addresses by the given role.
94+
#[must_use]
95+
pub fn role_stake_addresses(
96+
&self,
97+
role: usize,
98+
) -> HashSet<StakeAddress> {
99+
self.role_addresses(role)
100+
.iter()
101+
.filter_map(|address| {
102+
match address {
103+
Address::Stake(a) => Some(a.clone().into()),
104+
_ => None,
105+
}
106+
})
107+
.collect()
108+
}
109+
110+
/// Returns a list of all stake addresses.
111+
#[must_use]
112+
pub fn stake_addresses(&self) -> HashSet<StakeAddress> {
113+
self.x_uris()
114+
.values()
115+
.chain(self.c_uris().values())
116+
.flat_map(|uris| uris.iter())
117+
.filter_map(|uri| {
118+
match uri.address() {
119+
Address::Stake(a) => Some(a.clone().into()),
120+
_ => None,
121+
}
122+
})
123+
.collect()
124+
}
125+
93126
/// Return the updated URIs set.
94127
///
95128
/// The resulting set includes all the data from both the original and a new one. In
@@ -289,18 +322,6 @@ fn extract_c509_uris(
289322
result
290323
}
291324

292-
/// Converts a list of `Cip0134Uri` to a list of stake addresses.
293-
fn convert_stake_addresses(uris: &[Cip0134Uri]) -> Vec<StakeAddress> {
294-
uris.iter()
295-
.filter_map(|uri| {
296-
match uri.address() {
297-
Address::Stake(a) => Some(a.clone().into()),
298-
_ => None,
299-
}
300-
})
301-
.collect()
302-
}
303-
304325
#[cfg(test)]
305326
mod tests {
306327

@@ -323,6 +344,9 @@ mod tests {
323344
let set = cip509.certificate_uris().unwrap();
324345
assert!(!set.is_empty());
325346
assert!(set.c_uris().is_empty());
347+
assert_eq!(set.role_addresses(0).len(), 1);
348+
assert_eq!(set.role_stake_addresses(0).len(), 1);
349+
assert_eq!(set.stake_addresses().len(), 1);
326350

327351
let x_uris = set.x_uris();
328352
assert_eq!(x_uris.len(), 1);

0 commit comments

Comments
 (0)