Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rust/rbac-registration): Introduce Cip0134UriSet type #119

Open
wants to merge 1 commit into
base: rbac-registration-improvements
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 73 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>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blake2b256Hash

/// Metadata.
///
/// This field encoded in chunks. See [`X509Chunks`] for more details.
pub metadata: Cip509RbacMetadata,
/// Validation signature.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Validation signature.
/// Validation signature. Must be at least 1 byte and at most 64 bytes long (1..64)

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,59 @@ 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)

let metadata =
metadata.ok_or_else(|| decode::Error::message("Missing metadata in CIP509"))?;

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

Expand Down Expand Up @@ -179,16 +215,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
128 changes: 60 additions & 68 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.
#[derive(Debug, PartialEq, Clone, Default)]
///
/// 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)]
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this certificate_uris is too specific for Cip509Metadata (specific to role 0)
Might be useful to add it in RegistrationChain.
What do you think?

/// 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
Loading