Skip to content

Commit

Permalink
Introduce Cip0134UriSet type
Browse files Browse the repository at this point in the history
  • Loading branch information
stanislav-tkach committed Dec 25, 2024
1 parent 433f3cb commit 5d20379
Show file tree
Hide file tree
Showing 11 changed files with 553 additions and 440 deletions.
109 changes: 70 additions & 39 deletions rust/rbac-registration/src/cardano/cip509/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,42 @@ use validation::{
use x509_chunks::X509Chunks;

use super::transaction::witness::TxWitness;
use crate::utils::{
decode_helper::{decode_bytes, decode_helper, decode_map_len},
general::{decode_utf8, decremented_index},
hashing::{blake2b_128, blake2b_256},
use crate::{
cardano::cip509::rbac::Cip509RbacMetadata,
utils::{
decode_helper::{decode_bytes, decode_helper, decode_map_len},
general::decremented_index,
hashing::{blake2b_128, blake2b_256},
},
};

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

/// CIP509.
#[derive(Debug, PartialEq, Clone, Default)]
/// A x509 metadata envelope.
///
/// The envelope is required to prevent replayability attacks. See [this document] for
/// more details.
///
/// [this document]: https://github.com/input-output-hk/catalyst-CIPs/blob/x509-envelope-metadata/CIP-XXXX/README.md
#[derive(Debug, PartialEq, Clone)]
pub struct Cip509 {
/// `UUIDv4` Purpose .
pub purpose: Uuid, // (bytes .size 16)
/// A registration purpose (UUIDv4).
///
/// The purpose is defined by the consuming dApp.
pub purpose: Uuid,
/// Transaction inputs hash.
pub txn_inputs_hash: TxInputHash, // bytes .size 16
/// Optional previous transaction ID.
pub prv_tx_id: Option<Hash<32>>, // bytes .size 32
/// x509 chunks.
pub x509_chunks: X509Chunks, // chunk_type => [ + x509_chunk ]
pub txn_inputs_hash: TxInputHash,
/// An optional BLAKE2b hash of the previous transaction.
///
/// The hash must always be present except for the first registration transaction.
pub prv_tx_id: Option<Hash<32>>,
/// Metadata.
///
/// This field encoded in chunks. See [`X509Chunks`] for more details.
pub metadata: Cip509RbacMetadata,
/// Validation signature.
pub validation_signature: Vec<u8>, // bytes size (1..64)
pub validation_signature: Vec<u8>,
}

/// Validation value for CIP509 metadatum.
Expand Down Expand Up @@ -92,7 +106,13 @@ pub(crate) enum Cip509IntIdentifier {
impl Decode<'_, ()> for Cip509 {
fn decode(d: &mut Decoder, ctx: &mut ()) -> Result<Self, decode::Error> {
let map_len = decode_map_len(d, "CIP509")?;
let mut cip509_metadatum = Cip509::default();

let mut purpose = Uuid::default();
let mut txn_inputs_hash = TxInputHash::default();
let mut prv_tx_id = None;
let mut metadata = None;
let mut validation_signature = Vec::new();

for _ in 0..map_len {
// Use probe to peak
let key = d.probe().u8()?;
Expand All @@ -101,43 +121,56 @@ impl Decode<'_, ()> for Cip509 {
let _: u8 = decode_helper(d, "CIP509", ctx)?;
match key {
Cip509IntIdentifier::Purpose => {
cip509_metadatum.purpose =
Uuid::try_from(decode_bytes(d, "CIP509 purpose")?).map_err(|_| {
decode::Error::message("Invalid data size of Purpose")
})?;
purpose = Uuid::try_from(decode_bytes(d, "CIP509 purpose")?)
.map_err(|_| decode::Error::message("Invalid data size of Purpose"))?;
},
Cip509IntIdentifier::TxInputsHash => {
cip509_metadatum.txn_inputs_hash =
txn_inputs_hash =
TxInputHash::try_from(decode_bytes(d, "CIP509 txn inputs hash")?)
.map_err(|_| {
decode::Error::message("Invalid data size of TxInputsHash")
})?;
},
Cip509IntIdentifier::PreviousTxId => {
let prv_tx_hash: [u8; 32] = decode_bytes(d, "CIP509 previous tx ID")?
let hash: [u8; 32] = decode_bytes(d, "CIP509 previous tx ID")?
.try_into()
.map_err(|_| {
decode::Error::message("Invalid data size of PreviousTxId")
})?;
cip509_metadatum.prv_tx_id = Some(Hash::from(prv_tx_hash));
decode::Error::message("Invalid data size of PreviousTxId")
})?;
prv_tx_id = Some(Hash::from(hash));
},
Cip509IntIdentifier::ValidationSignature => {
let validation_signature = decode_bytes(d, "CIP509 validation signature")?;
if validation_signature.is_empty() || validation_signature.len() > 64 {
let signature = decode_bytes(d, "CIP509 validation signature")?;
if signature.is_empty() || signature.len() > 64 {
return Err(decode::Error::message(
"Invalid data size of ValidationSignature",
));
}
cip509_metadatum.validation_signature = validation_signature;
validation_signature = signature;
},
}
} else {
// Handle the x509 chunks 10 11 12
let x509_chunks = X509Chunks::decode(d, ctx)?;
cip509_metadatum.x509_chunks = x509_chunks;
// Technically it is possible to store multiple copies (or different instances) of
// metadata, but it isn't allowed. See this link for more details:
// https://github.com/input-output-hk/catalyst-CIPs/blob/x509-envelope-metadata/CIP-XXXX/README.md#keys-10-11-or-12---x509-chunked-data
if metadata.is_some() {
return Err(decode::Error::message(
"Only one instance of the chunked metadata should be present",
));
}
metadata = Some(x509_chunks.into());
}
}
Ok(cip509_metadatum)

Ok(Self {
purpose,
txn_inputs_hash,
prv_tx_id,
metadata: metadata.unwrap_or_default(),
validation_signature,
})
}
}

Expand Down Expand Up @@ -179,16 +212,14 @@ impl Cip509 {
let mut is_valid_stake_public_key = true;
let mut is_valid_payment_key = true;
let mut is_valid_signing_key = true;
if let Some(role_set) = &self.x509_chunks.0.role_set {
// Validate only role 0
for role in role_set {
if role.role_number == 0 {
is_valid_stake_public_key =
validate_stake_public_key(self, txn, validation_report).unwrap_or(false);
is_valid_payment_key =
validate_payment_key(txn, role, validation_report).unwrap_or(false);
is_valid_signing_key = validate_role_singing_key(role, validation_report);
}
// Validate only role 0
for role in &self.metadata.role_set {
if role.role_number == 0 {
is_valid_stake_public_key =
validate_stake_public_key(self, txn, validation_report).unwrap_or(false);
is_valid_payment_key =
validate_payment_key(txn, role, validation_report).unwrap_or(false);
is_valid_signing_key = validate_role_singing_key(role, validation_report);
}
}
Cip509Validation {
Expand Down
126 changes: 59 additions & 67 deletions rust/rbac-registration/src/cardano/cip509/rbac/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,43 @@ use role_data::RoleData;
use strum_macros::FromRepr;

use super::types::cert_key_hash::CertKeyHash;
use crate::utils::decode_helper::{
decode_any, decode_array_len, decode_bytes, decode_helper, decode_map_len,
use crate::{
cardano::cip509::utils::Cip0134UriSet,
utils::decode_helper::{
decode_any, decode_array_len, decode_bytes, decode_helper, decode_map_len,
},
};

/// Cip509 RBAC metadata.
///
/// See [this document] for more details.
///
/// [this document]: https://github.com/input-output-hk/catalyst-CIPs/tree/x509-role-registration-metadata/CIP-XXXX
#[derive(Debug, PartialEq, Clone, Default)]
pub struct Cip509RbacMetadata {
/// Optional list of x509 certificates.
pub x509_certs: Option<Vec<X509DerCert>>,
/// Optional list of c509 certificates.
/// The value can be either the c509 certificate or c509 metadatum reference.
pub c509_certs: Option<Vec<C509Cert>>,
/// Optional list of Public keys.
pub pub_keys: Option<Vec<SimplePublicKeyType>>,
/// Optional list of revocation list.
pub revocation_list: Option<Vec<CertKeyHash>>,
/// Optional list of role data.
pub role_set: Option<Vec<RoleData>>,
/// A potentially empty list of x509 certificates.
pub x509_certs: Vec<X509DerCert>,
/// A potentially empty list of c509 certificates.
pub c509_certs: Vec<C509Cert>,
/// A set of URIs contained in both x509 and c509 certificates.
///
/// URIs from different certificate types are stored separately and certificate
/// indexes are preserved too.
///
/// This field isn't present in the encoded format and is populated by processing both
/// `x509_certs` and `c509_certs` fields.
pub certificate_uris: Cip0134UriSet,
/// A list of public keys that can be used instead of storing full certificates.
///
/// Check [this section] to understand the how certificates and the public keys list
/// are related.
///
/// [this section]: https://github.com/input-output-hk/catalyst-CIPs/tree/x509-role-registration-metadata/CIP-XXXX#storing-certificates-and-public-key
pub pub_keys: Vec<SimplePublicKeyType>,
/// A potentially empty list of revoked certificates.
pub revocation_list: Vec<CertKeyHash>,
/// A potentially empty list of role data.
pub role_set: Vec<RoleData>,
/// Optional map of purpose key data.
/// Empty map if no purpose key data is present.
pub purpose_key_data: HashMap<u16, Vec<u8>>,
Expand All @@ -60,86 +79,59 @@ pub enum Cip509RbacMetadataInt {
RoleSet = 100,
}

impl Cip509RbacMetadata {
/// Create a new instance of `Cip509RbacMetadata`.
pub(crate) fn new() -> Self {
Self {
x509_certs: None,
c509_certs: None,
pub_keys: None,
revocation_list: None,
role_set: None,
purpose_key_data: HashMap::new(),
}
}

/// Set the x509 certificates.
fn set_x509_certs(&mut self, x509_certs: Vec<X509DerCert>) {
self.x509_certs = Some(x509_certs);
}

/// Set the c509 certificates.
fn set_c509_certs(&mut self, c509_certs: Vec<C509Cert>) {
self.c509_certs = Some(c509_certs);
}

/// Set the public keys.
fn set_pub_keys(&mut self, pub_keys: Vec<SimplePublicKeyType>) {
self.pub_keys = Some(pub_keys);
}

/// Set the revocation list.
fn set_revocation_list(&mut self, revocation_list: Vec<CertKeyHash>) {
self.revocation_list = Some(revocation_list);
}

/// Set the role data set.
fn set_role_set(&mut self, role_set: Vec<RoleData>) {
self.role_set = Some(role_set);
}
}

impl Decode<'_, ()> for Cip509RbacMetadata {
fn decode(d: &mut Decoder, ctx: &mut ()) -> Result<Self, decode::Error> {
let map_len = decode_map_len(d, "Cip509RbacMetadata")?;

let mut x509_rbac_metadata = Cip509RbacMetadata::new();
let mut x509_certs = Vec::new();
let mut c509_certs = Vec::new();
let mut pub_keys = Vec::new();
let mut revocation_list = Vec::new();
let mut role_set = Vec::new();
let mut purpose_key_data = HashMap::new();

for _ in 0..map_len {
let key: u16 = decode_helper(d, "key in Cip509RbacMetadata", ctx)?;
if let Some(key) = Cip509RbacMetadataInt::from_repr(key) {
match key {
Cip509RbacMetadataInt::X509Certs => {
let x509_certs = decode_array_rbac(d, "x509 certificate")?;
x509_rbac_metadata.set_x509_certs(x509_certs);
x509_certs = decode_array_rbac(d, "x509 certificate")?;
},
Cip509RbacMetadataInt::C509Certs => {
let c509_certs = decode_array_rbac(d, "c509 certificate")?;
x509_rbac_metadata.set_c509_certs(c509_certs);
c509_certs = decode_array_rbac(d, "c509 certificate")?;
},
Cip509RbacMetadataInt::PubKeys => {
let pub_keys = decode_array_rbac(d, "public keys")?;
x509_rbac_metadata.set_pub_keys(pub_keys);
pub_keys = decode_array_rbac(d, "public keys")?;
},
Cip509RbacMetadataInt::RevocationList => {
let revocation_list = decode_revocation_list(d)?;
x509_rbac_metadata.set_revocation_list(revocation_list);
revocation_list = decode_revocation_list(d)?;
},
Cip509RbacMetadataInt::RoleSet => {
let role_set = decode_array_rbac(d, "role set")?;
x509_rbac_metadata.set_role_set(role_set);
role_set = decode_array_rbac(d, "role set")?;
},
}
} else {
if !(FIRST_PURPOSE_KEY..=LAST_PURPOSE_KEY).contains(&key) {
return Err(decode::Error::message(format!("Invalid purpose key set, should be with the range {FIRST_PURPOSE_KEY} - {LAST_PURPOSE_KEY}")));
}
x509_rbac_metadata
.purpose_key_data
.insert(key, decode_any(d, "purpose key")?);

purpose_key_data.insert(key, decode_any(d, "purpose key")?);
}
}
Ok(x509_rbac_metadata)

let certificate_uris = Cip0134UriSet::new(&x509_certs, &c509_certs).map_err(|e| {
decode::Error::message(format!("Unable to parse URIs from certificates: {e:?}"))
})?;

Ok(Self {
x509_certs,
c509_certs,
certificate_uris,
pub_keys,
revocation_list,
role_set,
purpose_key_data,
})
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
//! Transaction input hash type
/// Transaction input hash representing in 16 bytes.
/// A 16-byte hash of the transaction inputs field.
///
/// This type is described [here].
///
/// [here]: https://github.com/input-output-hk/catalyst-CIPs/blob/x509-envelope-metadata/CIP-XXXX/README.md#key-1-txn-inputs-hash
#[derive(Debug, PartialEq, Clone, Default)]
pub struct TxInputHash([u8; 16]);

Expand Down
7 changes: 7 additions & 0 deletions rust/rbac-registration/src/cardano/cip509/utils/cip134/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//! Utilities for [CIP-134] (Cardano URIs - Address Representation).
//!
//! [CIP-134]: https://github.com/cardano-foundation/CIPs/tree/master/CIP-0134
pub use self::{uri::Cip0134Uri, uri_set::Cip0134UriSet};

mod uri;
mod uri_set;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Utility functions for CIP-0134 address.
//! An URI in the CIP-0134 format.
// Ignore URIs that are used in tests and doc-examples.
// cSpell:ignoreRegExp web\+cardano:.+
Expand All @@ -13,7 +13,8 @@ use pallas::ledger::addresses::Address;
/// See the [proposal] for more details.
///
/// [proposal]: https://github.com/cardano-foundation/CIPs/pull/888
#[derive(Debug)]
#[derive(Debug, Eq, PartialEq)]
#[allow(clippy::module_name_repetitions)]
pub struct Cip0134Uri {
/// A URI string.
uri: String,
Expand Down
Loading

0 comments on commit 5d20379

Please sign in to comment.