Skip to content

Commit 448899b

Browse files
Introduce Cip0134UriSet type
1 parent 433f3cb commit 448899b

File tree

13 files changed

+599
-445
lines changed

13 files changed

+599
-445
lines changed

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

Lines changed: 76 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -25,28 +25,43 @@ use validation::{
2525
use x509_chunks::X509Chunks;
2626

2727
use super::transaction::witness::TxWitness;
28-
use crate::utils::{
29-
decode_helper::{decode_bytes, decode_helper, decode_map_len},
30-
general::{decode_utf8, decremented_index},
31-
hashing::{blake2b_128, blake2b_256},
28+
use crate::{
29+
cardano::cip509::{rbac::Cip509RbacMetadata, types::ValidationSignature},
30+
utils::{
31+
decode_helper::{decode_bytes, decode_helper, decode_map_len},
32+
general::decremented_index,
33+
hashing::{blake2b_128, blake2b_256},
34+
},
3235
};
3336

3437
/// CIP509 label.
3538
pub const LABEL: u64 = 509;
3639

37-
/// CIP509.
38-
#[derive(Debug, PartialEq, Clone, Default)]
40+
/// A x509 metadata envelope.
41+
///
42+
/// The envelope is required to prevent replayability attacks. See [this document] for
43+
/// more details.
44+
///
45+
/// [this document]: https://github.com/input-output-hk/catalyst-CIPs/blob/x509-envelope-metadata/CIP-XXXX/README.md
46+
#[derive(Debug, PartialEq, Clone)]
3947
pub struct Cip509 {
40-
/// `UUIDv4` Purpose .
41-
pub purpose: Uuid, // (bytes .size 16)
48+
/// A registration purpose (`UUIDv4`).
49+
///
50+
/// The purpose is defined by the consuming dApp.
51+
pub purpose: Uuid,
4252
/// Transaction inputs hash.
43-
pub txn_inputs_hash: TxInputHash, // bytes .size 16
44-
/// Optional previous transaction ID.
45-
pub prv_tx_id: Option<Hash<32>>, // bytes .size 32
46-
/// x509 chunks.
47-
pub x509_chunks: X509Chunks, // chunk_type => [ + x509_chunk ]
53+
pub txn_inputs_hash: TxInputHash,
54+
/// An optional hash of the previous transaction.
55+
///
56+
/// The hash must always be present except for the first registration transaction.
57+
// TODO: Use the `Blake2b256Hash` type from the `cardano-blockchain-types` crate.
58+
pub prv_tx_id: Option<Hash<32>>,
59+
/// Metadata.
60+
///
61+
/// This field encoded in chunks. See [`X509Chunks`] for more details.
62+
pub metadata: Cip509RbacMetadata,
4863
/// Validation signature.
49-
pub validation_signature: Vec<u8>, // bytes size (1..64)
64+
pub validation_signature: ValidationSignature,
5065
}
5166

5267
/// Validation value for CIP509 metadatum.
@@ -92,7 +107,13 @@ pub(crate) enum Cip509IntIdentifier {
92107
impl Decode<'_, ()> for Cip509 {
93108
fn decode(d: &mut Decoder, ctx: &mut ()) -> Result<Self, decode::Error> {
94109
let map_len = decode_map_len(d, "CIP509")?;
95-
let mut cip509_metadatum = Cip509::default();
110+
111+
let mut purpose = Uuid::default();
112+
let mut txn_inputs_hash = TxInputHash::default();
113+
let mut prv_tx_id = None;
114+
let mut metadata = None;
115+
let mut validation_signature = Vec::new();
116+
96117
for _ in 0..map_len {
97118
// Use probe to peak
98119
let key = d.probe().u8()?;
@@ -101,43 +122,57 @@ impl Decode<'_, ()> for Cip509 {
101122
let _: u8 = decode_helper(d, "CIP509", ctx)?;
102123
match key {
103124
Cip509IntIdentifier::Purpose => {
104-
cip509_metadatum.purpose =
105-
Uuid::try_from(decode_bytes(d, "CIP509 purpose")?).map_err(|_| {
106-
decode::Error::message("Invalid data size of Purpose")
107-
})?;
125+
purpose = Uuid::try_from(decode_bytes(d, "CIP509 purpose")?)
126+
.map_err(|_| decode::Error::message("Invalid data size of Purpose"))?;
108127
},
109128
Cip509IntIdentifier::TxInputsHash => {
110-
cip509_metadatum.txn_inputs_hash =
129+
txn_inputs_hash =
111130
TxInputHash::try_from(decode_bytes(d, "CIP509 txn inputs hash")?)
112131
.map_err(|_| {
113132
decode::Error::message("Invalid data size of TxInputsHash")
114133
})?;
115134
},
116135
Cip509IntIdentifier::PreviousTxId => {
117-
let prv_tx_hash: [u8; 32] = decode_bytes(d, "CIP509 previous tx ID")?
136+
let hash: [u8; 32] = decode_bytes(d, "CIP509 previous tx ID")?
118137
.try_into()
119138
.map_err(|_| {
120-
decode::Error::message("Invalid data size of PreviousTxId")
121-
})?;
122-
cip509_metadatum.prv_tx_id = Some(Hash::from(prv_tx_hash));
139+
decode::Error::message("Invalid data size of PreviousTxId")
140+
})?;
141+
prv_tx_id = Some(Hash::from(hash));
123142
},
124143
Cip509IntIdentifier::ValidationSignature => {
125-
let validation_signature = decode_bytes(d, "CIP509 validation signature")?;
126-
if validation_signature.is_empty() || validation_signature.len() > 64 {
127-
return Err(decode::Error::message(
128-
"Invalid data size of ValidationSignature",
129-
));
130-
}
131-
cip509_metadatum.validation_signature = validation_signature;
144+
let signature = decode_bytes(d, "CIP509 validation signature")?;
145+
validation_signature = signature;
132146
},
133147
}
134148
} else {
135149
// Handle the x509 chunks 10 11 12
136150
let x509_chunks = X509Chunks::decode(d, ctx)?;
137-
cip509_metadatum.x509_chunks = x509_chunks;
151+
// Technically it is possible to store multiple copies (or different instances) of
152+
// metadata, but it isn't allowed. See this link for more details:
153+
// https://github.com/input-output-hk/catalyst-CIPs/blob/x509-envelope-metadata/CIP-XXXX/README.md#keys-10-11-or-12---x509-chunked-data
154+
if metadata.is_some() {
155+
return Err(decode::Error::message(
156+
"Only one instance of the chunked metadata should be present",
157+
));
158+
}
159+
metadata = Some(x509_chunks.into());
138160
}
139161
}
140-
Ok(cip509_metadatum)
162+
163+
let metadata =
164+
metadata.ok_or_else(|| decode::Error::message("Missing metadata in CIP509"))?;
165+
let validation_signature = validation_signature
166+
.try_into()
167+
.map_err(|e| decode::Error::message(format!("Invalid validation signature: {e:?}")))?;
168+
169+
Ok(Self {
170+
purpose,
171+
txn_inputs_hash,
172+
prv_tx_id,
173+
metadata,
174+
validation_signature,
175+
})
141176
}
142177
}
143178

@@ -179,16 +214,14 @@ impl Cip509 {
179214
let mut is_valid_stake_public_key = true;
180215
let mut is_valid_payment_key = true;
181216
let mut is_valid_signing_key = true;
182-
if let Some(role_set) = &self.x509_chunks.0.role_set {
183-
// Validate only role 0
184-
for role in role_set {
185-
if role.role_number == 0 {
186-
is_valid_stake_public_key =
187-
validate_stake_public_key(self, txn, validation_report).unwrap_or(false);
188-
is_valid_payment_key =
189-
validate_payment_key(txn, role, validation_report).unwrap_or(false);
190-
is_valid_signing_key = validate_role_singing_key(role, validation_report);
191-
}
217+
// Validate only role 0
218+
for role in &self.metadata.role_set {
219+
if role.role_number == 0 {
220+
is_valid_stake_public_key =
221+
validate_stake_public_key(self, txn, validation_report).unwrap_or(false);
222+
is_valid_payment_key =
223+
validate_payment_key(txn, role, validation_report).unwrap_or(false);
224+
is_valid_signing_key = validate_role_singing_key(role, validation_report);
192225
}
193226
}
194227
Cip509Validation {

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

Lines changed: 60 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,43 @@ use role_data::RoleData;
1616
use strum_macros::FromRepr;
1717

1818
use super::types::cert_key_hash::CertKeyHash;
19-
use crate::utils::decode_helper::{
20-
decode_any, decode_array_len, decode_bytes, decode_helper, decode_map_len,
19+
use crate::{
20+
cardano::cip509::utils::Cip0134UriSet,
21+
utils::decode_helper::{
22+
decode_any, decode_array_len, decode_bytes, decode_helper, decode_map_len,
23+
},
2124
};
2225

2326
/// Cip509 RBAC metadata.
24-
#[derive(Debug, PartialEq, Clone, Default)]
27+
///
28+
/// See [this document] for more details.
29+
///
30+
/// [this document]: https://github.com/input-output-hk/catalyst-CIPs/tree/x509-role-registration-metadata/CIP-XXXX
31+
#[derive(Debug, PartialEq, Clone)]
2532
pub struct Cip509RbacMetadata {
26-
/// Optional list of x509 certificates.
27-
pub x509_certs: Option<Vec<X509DerCert>>,
28-
/// Optional list of c509 certificates.
29-
/// The value can be either the c509 certificate or c509 metadatum reference.
30-
pub c509_certs: Option<Vec<C509Cert>>,
31-
/// Optional list of Public keys.
32-
pub pub_keys: Option<Vec<SimplePublicKeyType>>,
33-
/// Optional list of revocation list.
34-
pub revocation_list: Option<Vec<CertKeyHash>>,
35-
/// Optional list of role data.
36-
pub role_set: Option<Vec<RoleData>>,
33+
/// A potentially empty list of x509 certificates.
34+
pub x509_certs: Vec<X509DerCert>,
35+
/// A potentially empty list of c509 certificates.
36+
pub c509_certs: Vec<C509Cert>,
37+
/// A set of URIs contained in both x509 and c509 certificates.
38+
///
39+
/// URIs from different certificate types are stored separately and certificate
40+
/// indexes are preserved too.
41+
///
42+
/// This field isn't present in the encoded format and is populated by processing both
43+
/// `x509_certs` and `c509_certs` fields.
44+
pub certificate_uris: Cip0134UriSet,
45+
/// A list of public keys that can be used instead of storing full certificates.
46+
///
47+
/// Check [this section] to understand the how certificates and the public keys list
48+
/// are related.
49+
///
50+
/// [this section]: https://github.com/input-output-hk/catalyst-CIPs/tree/x509-role-registration-metadata/CIP-XXXX#storing-certificates-and-public-key
51+
pub pub_keys: Vec<SimplePublicKeyType>,
52+
/// A potentially empty list of revoked certificates.
53+
pub revocation_list: Vec<CertKeyHash>,
54+
/// A potentially empty list of role data.
55+
pub role_set: Vec<RoleData>,
3756
/// Optional map of purpose key data.
3857
/// Empty map if no purpose key data is present.
3958
pub purpose_key_data: HashMap<u16, Vec<u8>>,
@@ -60,86 +79,59 @@ pub enum Cip509RbacMetadataInt {
6079
RoleSet = 100,
6180
}
6281

63-
impl Cip509RbacMetadata {
64-
/// Create a new instance of `Cip509RbacMetadata`.
65-
pub(crate) fn new() -> Self {
66-
Self {
67-
x509_certs: None,
68-
c509_certs: None,
69-
pub_keys: None,
70-
revocation_list: None,
71-
role_set: None,
72-
purpose_key_data: HashMap::new(),
73-
}
74-
}
75-
76-
/// Set the x509 certificates.
77-
fn set_x509_certs(&mut self, x509_certs: Vec<X509DerCert>) {
78-
self.x509_certs = Some(x509_certs);
79-
}
80-
81-
/// Set the c509 certificates.
82-
fn set_c509_certs(&mut self, c509_certs: Vec<C509Cert>) {
83-
self.c509_certs = Some(c509_certs);
84-
}
85-
86-
/// Set the public keys.
87-
fn set_pub_keys(&mut self, pub_keys: Vec<SimplePublicKeyType>) {
88-
self.pub_keys = Some(pub_keys);
89-
}
90-
91-
/// Set the revocation list.
92-
fn set_revocation_list(&mut self, revocation_list: Vec<CertKeyHash>) {
93-
self.revocation_list = Some(revocation_list);
94-
}
95-
96-
/// Set the role data set.
97-
fn set_role_set(&mut self, role_set: Vec<RoleData>) {
98-
self.role_set = Some(role_set);
99-
}
100-
}
101-
10282
impl Decode<'_, ()> for Cip509RbacMetadata {
10383
fn decode(d: &mut Decoder, ctx: &mut ()) -> Result<Self, decode::Error> {
10484
let map_len = decode_map_len(d, "Cip509RbacMetadata")?;
10585

106-
let mut x509_rbac_metadata = Cip509RbacMetadata::new();
86+
let mut x509_certs = Vec::new();
87+
let mut c509_certs = Vec::new();
88+
let mut pub_keys = Vec::new();
89+
let mut revocation_list = Vec::new();
90+
let mut role_set = Vec::new();
91+
let mut purpose_key_data = HashMap::new();
10792

10893
for _ in 0..map_len {
10994
let key: u16 = decode_helper(d, "key in Cip509RbacMetadata", ctx)?;
11095
if let Some(key) = Cip509RbacMetadataInt::from_repr(key) {
11196
match key {
11297
Cip509RbacMetadataInt::X509Certs => {
113-
let x509_certs = decode_array_rbac(d, "x509 certificate")?;
114-
x509_rbac_metadata.set_x509_certs(x509_certs);
98+
x509_certs = decode_array_rbac(d, "x509 certificate")?;
11599
},
116100
Cip509RbacMetadataInt::C509Certs => {
117-
let c509_certs = decode_array_rbac(d, "c509 certificate")?;
118-
x509_rbac_metadata.set_c509_certs(c509_certs);
101+
c509_certs = decode_array_rbac(d, "c509 certificate")?;
119102
},
120103
Cip509RbacMetadataInt::PubKeys => {
121-
let pub_keys = decode_array_rbac(d, "public keys")?;
122-
x509_rbac_metadata.set_pub_keys(pub_keys);
104+
pub_keys = decode_array_rbac(d, "public keys")?;
123105
},
124106
Cip509RbacMetadataInt::RevocationList => {
125-
let revocation_list = decode_revocation_list(d)?;
126-
x509_rbac_metadata.set_revocation_list(revocation_list);
107+
revocation_list = decode_revocation_list(d)?;
127108
},
128109
Cip509RbacMetadataInt::RoleSet => {
129-
let role_set = decode_array_rbac(d, "role set")?;
130-
x509_rbac_metadata.set_role_set(role_set);
110+
role_set = decode_array_rbac(d, "role set")?;
131111
},
132112
}
133113
} else {
134114
if !(FIRST_PURPOSE_KEY..=LAST_PURPOSE_KEY).contains(&key) {
135115
return Err(decode::Error::message(format!("Invalid purpose key set, should be with the range {FIRST_PURPOSE_KEY} - {LAST_PURPOSE_KEY}")));
136116
}
137-
x509_rbac_metadata
138-
.purpose_key_data
139-
.insert(key, decode_any(d, "purpose key")?);
117+
118+
purpose_key_data.insert(key, decode_any(d, "purpose key")?);
140119
}
141120
}
142-
Ok(x509_rbac_metadata)
121+
122+
let certificate_uris = Cip0134UriSet::new(&x509_certs, &c509_certs).map_err(|e| {
123+
decode::Error::message(format!("Unable to parse URIs from certificates: {e:?}"))
124+
})?;
125+
126+
Ok(Self {
127+
x509_certs,
128+
c509_certs,
129+
certificate_uris,
130+
pub_keys,
131+
revocation_list,
132+
role_set,
133+
purpose_key_data,
134+
})
143135
}
144136
}
145137

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@
22
33
pub mod cert_key_hash;
44
pub mod tx_input_hash;
5+
6+
pub use validation_signature::ValidationSignature;
7+
8+
mod validation_signature;

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
//! Transaction input hash type
22
3-
/// Transaction input hash representing in 16 bytes.
3+
/// A 16-byte hash of the transaction inputs field.
4+
///
5+
/// This type is described [here].
6+
///
7+
/// [here]: https://github.com/input-output-hk/catalyst-CIPs/blob/x509-envelope-metadata/CIP-XXXX/README.md#key-1-txn-inputs-hash
48
#[derive(Debug, PartialEq, Clone, Default)]
59
pub struct TxInputHash([u8; 16]);
610

0 commit comments

Comments
 (0)