diff --git a/rust/cardano-chain-follower/Cargo.toml b/rust/cardano-chain-follower/Cargo.toml index f9171998c..fb1c3a1e6 100644 --- a/rust/cardano-chain-follower/Cargo.toml +++ b/rust/cardano-chain-follower/Cargo.toml @@ -21,7 +21,7 @@ mithril-client = { version = "0.8.18", default-features = false, features = [ "num-integer-backend", ] } -c509-certificate = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git" , tag = "v0.0.3" } +rbac-registration = { version = "0.0.2", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "v0.0.8" } thiserror = "1.0.64" tokio = { version = "1.40.0", features = [ @@ -53,9 +53,7 @@ mimalloc = { version = "0.1.43", optional = true } memx = "0.1.32" fmmap = { version = "0.3.3", features = ["sync", "tokio-async"] } minicbor = { version = "0.25.1", features = ["alloc", "derive", "half"] } -brotli = "7.0.0" zstd = "0.13.2" -x509-cert = "0.2.5" ed25519-dalek = "2.1.1" blake2b_simd = "1.0.2" num-traits = "0.2.19" @@ -65,9 +63,6 @@ ureq = { version = "2.10.1", features = ["native-certs"] } http = "1.1.0" hickory-resolver = { version = "0.24.1", features = ["dns-over-rustls"] } moka = { version = "0.12.8", features = ["sync"] } -der-parser = "9.0.0" -regex = "1.11.0" -bech32 = "0.11.0" [dev-dependencies] hex = "0.4.3" diff --git a/rust/cardano-chain-follower/examples/follow_chains.rs b/rust/cardano-chain-follower/examples/follow_chains.rs index bc60dc5ec..ec389b7f4 100644 --- a/rust/cardano-chain-follower/examples/follow_chains.rs +++ b/rust/cardano-chain-follower/examples/follow_chains.rs @@ -7,6 +7,7 @@ #[cfg(feature = "mimalloc")] use mimalloc::MiMalloc; +use rbac_registration::cardano::cip509; /// Use Mimalloc for the global allocator. #[cfg(feature = "mimalloc")] @@ -329,10 +330,7 @@ fn update_biggest_aux_data( None => 0, }; - let raw_size_cip509 = match chain_update - .data - .txn_raw_metadata(tx_idx, Metadata::cip509::LABEL) - { + let raw_size_cip509 = match chain_update.data.txn_raw_metadata(tx_idx, cip509::LABEL) { Some(raw) => raw.len(), None => 0, }; @@ -368,10 +366,7 @@ fn log_bad_cip36_info(chain_update: &ChainUpdate, network: Network, tx_idx: usiz /// Helper function for logging CIP509 validation. fn log_cip509_info(chain_update: &ChainUpdate, block_num: u64, network: Network, tx_idx: usize) { - if let Some(decoded_metadata) = chain_update - .data - .txn_metadata(tx_idx, Metadata::cip509::LABEL) - { + if let Some(decoded_metadata) = chain_update.data.txn_metadata(tx_idx, cip509::LABEL) { info!("Block Number {}", block_num); if let Metadata::DecodedMetadataValues::Cip509(cip509) = &decoded_metadata.value { diff --git a/rust/cardano-chain-follower/src/metadata/cip509.rs b/rust/cardano-chain-follower/src/metadata/cip509.rs new file mode 100644 index 000000000..a100887df --- /dev/null +++ b/rust/cardano-chain-follower/src/metadata/cip509.rs @@ -0,0 +1,80 @@ +//! Cardano Improvement Proposal 509 (CIP-509) metadata module. +//! Doc Reference: +//! CDDL Reference: + +use std::sync::Arc; + +use minicbor::{Decode, Decoder}; +use pallas::ledger::traverse::MultiEraTx; +use rbac_registration::cardano::cip509::{Cip509 as RbacRegCip509, Cip509Validation, LABEL}; + +use super::{ + DecodedMetadata, DecodedMetadataItem, DecodedMetadataValues, RawAuxData, ValidationReport, +}; + +/// CIP509 metadatum. +#[derive(Debug, PartialEq, Clone, Default)] +pub struct Cip509 { + /// CIP509 data. + pub cip509: RbacRegCip509, + /// Validation value, not a part of CIP509, justs storing validity of the data. + pub validation: Cip509Validation, +} + +impl Cip509 { + /// Decode and validate CIP509 Metadata + /// + /// # Returns + /// + /// Nothing. IF CIP509 Metadata is found it will be updated in `decoded_metadata`. + pub(crate) fn decode_and_validate( + decoded_metadata: &DecodedMetadata, txn: &MultiEraTx, raw_aux_data: &RawAuxData, + ) { + // Get the CIP509 metadata if possible + let Some(k509) = raw_aux_data.get_metadata(LABEL) else { + return; + }; + + let mut validation_report = ValidationReport::new(); + let mut decoder = Decoder::new(k509.as_slice()); + + let cip509 = match RbacRegCip509::decode(&mut decoder, &mut ()) { + Ok(metadata) => metadata, + Err(e) => { + Cip509::default().validation_failure( + &format!("Failed to decode CIP509 metadata: {e}"), + &mut validation_report, + decoded_metadata, + ); + return; + }, + }; + + // Validate the decoded metadata + let validation = cip509.validate(txn, &mut validation_report); + + // Create a Cip509 struct and insert it into decoded_metadata + decoded_metadata.0.insert( + LABEL, + Arc::new(DecodedMetadataItem { + value: DecodedMetadataValues::Cip509(Arc::new(Cip509 { cip509, validation })), + report: validation_report.clone(), + }), + ); + } + + /// Handle validation failure. + fn validation_failure( + &self, reason: &str, validation_report: &mut ValidationReport, + decoded_metadata: &DecodedMetadata, + ) { + validation_report.push(reason.into()); + decoded_metadata.0.insert( + LABEL, + Arc::new(DecodedMetadataItem { + value: DecodedMetadataValues::Cip509(Arc::new(self.clone()).clone()), + report: validation_report.clone(), + }), + ); + } +} diff --git a/rust/cardano-chain-follower/src/metadata/cip509/decode_helper.rs b/rust/cardano-chain-follower/src/metadata/cip509/decode_helper.rs deleted file mode 100644 index a75ac3eaa..000000000 --- a/rust/cardano-chain-follower/src/metadata/cip509/decode_helper.rs +++ /dev/null @@ -1,215 +0,0 @@ -//! Cbor decoding helper functions for CIP-509 metadata. - -use minicbor::{data::Tag, decode, Decoder}; - -/// Generic helper function for decoding different types. -pub(crate) fn decode_helper<'a, T, C>( - d: &mut Decoder<'a>, from: &str, context: &mut C, -) -> Result -where T: minicbor::Decode<'a, C> { - T::decode(d, context).map_err(|e| { - decode::Error::message(format!( - "Failed to decode {:?} in {from}: {e}", - std::any::type_name::() - )) - }) -} - -/// Helper function for decoding bytes. -pub(crate) fn decode_bytes(d: &mut Decoder, from: &str) -> Result, decode::Error> { - d.bytes().map(<[u8]>::to_vec).map_err(|e| { - decode::Error::message(format!( - "Failed to decode bytes in {from}: - {e}" - )) - }) -} - -/// Helper function for decoding array. -pub(crate) fn decode_array_len(d: &mut Decoder, from: &str) -> Result { - d.array() - .map_err(|e| { - decode::Error::message(format!( - "Failed to decode array in {from}: - {e}" - )) - })? - .ok_or(decode::Error::message(format!( - "Failed to decode array in {from}, unexpected indefinite length", - ))) -} - -/// Helper function for decoding map. -pub(crate) fn decode_map_len(d: &mut Decoder, from: &str) -> Result { - d.map() - .map_err(|e| decode::Error::message(format!("Failed to decode map in {from}: {e}")))? - .ok_or(decode::Error::message(format!( - "Failed to decode map in {from}, unexpected indefinite length", - ))) -} - -/// Helper function for decoding tag. -pub(crate) fn decode_tag(d: &mut Decoder, from: &str) -> Result { - d.tag() - .map_err(|e| decode::Error::message(format!("Failed to decode tag in {from}: {e}"))) -} - -/// Decode any in CDDL, only support basic datatype -pub(crate) fn decode_any(d: &mut Decoder, from: &str) -> Result, decode::Error> { - match d.datatype()? { - minicbor::data::Type::String => { - match decode_helper::(d, &format!("{from} Any"), &mut ()) { - Ok(i) => Ok(i.as_bytes().to_vec()), - Err(e) => Err(e), - } - }, - minicbor::data::Type::U8 => { - match decode_helper::(d, &format!("{from} Any"), &mut ()) { - Ok(i) => Ok(i.to_be_bytes().to_vec()), - Err(e) => Err(e), - } - }, - minicbor::data::Type::U16 => { - match decode_helper::(d, &format!("{from} Any"), &mut ()) { - Ok(i) => Ok(i.to_be_bytes().to_vec()), - Err(e) => Err(e), - } - }, - minicbor::data::Type::U32 => { - match decode_helper::(d, &format!("{from} Any"), &mut ()) { - Ok(i) => Ok(i.to_be_bytes().to_vec()), - Err(e) => Err(e), - } - }, - minicbor::data::Type::U64 => { - match decode_helper::(d, &format!("{from} Any"), &mut ()) { - Ok(i) => Ok(i.to_be_bytes().to_vec()), - Err(e) => Err(e), - } - }, - minicbor::data::Type::I8 => { - match decode_helper::(d, &format!("{from} Any"), &mut ()) { - Ok(i) => Ok(i.to_be_bytes().to_vec()), - Err(e) => Err(e), - } - }, - minicbor::data::Type::I16 => { - match decode_helper::(d, &format!("{from} Any"), &mut ()) { - Ok(i) => Ok(i.to_be_bytes().to_vec()), - Err(e) => Err(e), - } - }, - minicbor::data::Type::I32 => { - match decode_helper::(d, &format!("{from} Any"), &mut ()) { - Ok(i) => Ok(i.to_be_bytes().to_vec()), - Err(e) => Err(e), - } - }, - minicbor::data::Type::I64 => { - match decode_helper::(d, &format!("{from} Any"), &mut ()) { - Ok(i) => Ok(i.to_be_bytes().to_vec()), - Err(e) => Err(e), - } - }, - minicbor::data::Type::Bytes => Ok(decode_bytes(d, &format!("{from} Any"))?), - minicbor::data::Type::Array => { - Ok(decode_array_len(d, &format!("{from} Any"))? - .to_be_bytes() - .to_vec()) - }, - _ => { - Err(decode::Error::message(format!( - "{from} Any, Data type not supported" - ))) - }, - } -} - -#[cfg(test)] -mod tests { - - use minicbor::Encoder; - - use super::*; - - #[test] - fn test_decode_any_bytes() { - let mut buf = Vec::new(); - let mut e = Encoder::new(&mut buf); - e.bytes(&[1, 2, 3, 4]).expect("Error encoding bytes"); - - let mut d = Decoder::new(&buf); - let result = decode_any(&mut d, "test").expect("Error decoding bytes"); - assert_eq!(result, vec![1, 2, 3, 4]); - } - - #[test] - fn test_decode_any_string() { - let mut buf = Vec::new(); - let mut e = Encoder::new(&mut buf); - e.str("hello").expect("Error encoding string"); - - let mut d = Decoder::new(&buf); - let result = decode_any(&mut d, "test").expect("Error decoding string"); - assert_eq!(result, b"hello".to_vec()); - } - - #[test] - fn test_decode_any_array() { - // The array should contain a supported type - let mut buf = Vec::new(); - let mut e = Encoder::new(&mut buf); - e.array(2).expect("Error encoding array"); - e.u8(1).expect("Error encoding u8"); - e.u8(2).expect("Error encoding u8"); - let mut d = Decoder::new(&buf); - let result = decode_any(&mut d, "test").expect("Error decoding array"); - // The decode of array is just a length of the array - assert_eq!( - u64::from_be_bytes(result.try_into().expect("Error converting bytes to u64")), - 2 - ); - } - - #[test] - fn test_decode_any_u32() { - let mut buf = Vec::new(); - let mut e = Encoder::new(&mut buf); - let num: u32 = 123_456_789; - e.u32(num).expect("Error encoding u32"); - - let mut d = Decoder::new(&buf); - let result = decode_any(&mut d, "test").expect("Error decoding u32"); - assert_eq!( - u32::from_be_bytes(result.try_into().expect("Error converting bytes to u32")), - num - ); - } - - #[test] - fn test_decode_any_i32() { - let mut buf = Vec::new(); - let mut e = Encoder::new(&mut buf); - let num: i32 = -123_456_789; - e.i32(num).expect("Error encoding i32"); - let mut d = Decoder::new(&buf); - let result = decode_any(&mut d, "test").expect("Error decoding i32"); - assert_eq!( - i32::from_be_bytes(result.try_into().expect("Error converting bytes to i32")), - num - ); - } - - #[test] - fn test_decode_any_unsupported_type() { - let mut buf = Vec::new(); - let mut e = Encoder::new(&mut buf); - e.null().expect("Error encoding null"); // Encode a null type which is unsupported - - let mut d = Decoder::new(&buf); - let result = decode_any(&mut d, "test"); - // Should print out the error message with the location of the error - println!("{result:?}"); - assert!(result.is_err()); - } -} diff --git a/rust/cardano-chain-follower/src/metadata/cip509/mod.rs b/rust/cardano-chain-follower/src/metadata/cip509/mod.rs deleted file mode 100644 index 6a622e8cd..000000000 --- a/rust/cardano-chain-follower/src/metadata/cip509/mod.rs +++ /dev/null @@ -1,1240 +0,0 @@ -//! Cardano Improvement Proposal 509 (CIP-509) metadata module. -//! Doc Reference: -//! CDDL Reference: - -// cspell: words pkix - -pub mod rbac; -pub mod utils; -pub mod x509_chunks; - -mod decode_helper; - -use std::sync::Arc; - -use c509_certificate::{general_names::general_name::GeneralNameValue, C509ExtensionType}; -use decode_helper::{decode_bytes, decode_helper, decode_map_len}; -use der_parser::der::parse_der_sequence; -use minicbor::{ - decode::{self}, - Decode, Decoder, -}; -use pallas::{ - codec::{ - minicbor::{Encode, Encoder}, - utils::Bytes, - }, - ledger::traverse::MultiEraTx, -}; -use rbac::{certs::C509Cert, role_data::RoleData}; -use strum::FromRepr; -use utils::{ - compare_key_hash, decremented_index, extract_cip19_hash, extract_key_hash, - zero_out_last_n_bytes, -}; -use x509_cert::{der::Decode as _, ext::pkix::ID_CE_SUBJECT_ALT_NAME}; -use x509_chunks::X509Chunks; - -use super::{ - raw_aux_data::RawAuxData, DecodedMetadata, DecodedMetadataItem, DecodedMetadataValues, - ValidationReport, -}; -use crate::{ - utils::{blake2b_128, blake2b_256, decode_utf8}, - witness::TxWitness, -}; - -/// CIP509 label. -pub const LABEL: u64 = 509; - -/// Context-specific primitive type with tag number 6 (`raw_tag` 134) for -/// uniform resource identifier (URI) in the subject alternative name extension. -pub(crate) const URI: u8 = 134; - -/// CIP509 metadatum. -#[derive(Debug, PartialEq, Clone, Default)] -pub struct Cip509 { - /// `UUIDv4` Purpose . - pub purpose: [u8; 16], // (bytes .size 16) - /// Transaction inputs hash. - pub txn_inputs_hash: [u8; 16], // bytes .size 16 - /// Optional previous transaction ID. - pub prv_tx_id: Option<[u8; 32]>, // bytes .size 32 - /// x509 chunks. - pub x509_chunks: X509Chunks, // chunk_type => [ + x509_chunk ] - /// Validation signature. - pub validation_signature: Vec, // bytes size (1..64) - /// Validation value, not a part of CIP509, justs storing validity of the data. - pub validation: Cip509Validation, -} - -/// Validation value for CIP509 metadatum. -#[allow(clippy::struct_excessive_bools, clippy::module_name_repetitions)] -#[derive(Debug, PartialEq, Clone, Default)] -pub struct Cip509Validation { - /// Boolean value for the validity of the transaction inputs hash. - pub valid_txn_inputs_hash: bool, - /// Boolean value for the validity of the auxiliary data. - pub valid_aux: bool, - /// Bytes of precomputed auxiliary data. - pub precomputed_aux: Vec, - /// Boolean value for the validity of the public key. - pub valid_public_key: bool, - /// Boolean value for the validity of the payment key. - pub valid_payment_key: bool, -} - -/// Enum of CIP509 metadatum with its associated unsigned integer value. -#[allow(clippy::module_name_repetitions)] -#[derive(FromRepr, Debug, PartialEq)] -#[repr(u8)] -pub(crate) enum Cip509IntIdentifier { - /// Purpose. - Purpose = 0, - /// Transaction inputs hash. - TxInputsHash = 1, - /// Previous transaction ID. - PreviousTxId = 2, - /// Validation signature. - ValidationSignature = 99, -} - -impl Decode<'_, ()> for Cip509 { - fn decode(d: &mut Decoder, ctx: &mut ()) -> Result { - let map_len = decode_map_len(d, "CIP509")?; - let mut cip509_metadatum = Cip509::default(); - for _ in 0..map_len { - // Use probe to peak - let key = d.probe().u8()?; - if let Some(key) = Cip509IntIdentifier::from_repr(key) { - // Consuming the int - let _: u8 = decode_helper(d, "CIP509", ctx)?; - match key { - Cip509IntIdentifier::Purpose => { - cip509_metadatum.purpose = decode_bytes(d, "CIP509 purpose")? - .try_into() - .map_err(|_| decode::Error::message("Invalid data size of Purpose"))?; - }, - Cip509IntIdentifier::TxInputsHash => { - cip509_metadatum.txn_inputs_hash = - decode_bytes(d, "CIP509 txn inputs hash")? - .try_into() - .map_err(|_| { - decode::Error::message("Invalid data size of TxInputsHash") - })?; - }, - Cip509IntIdentifier::PreviousTxId => { - cip509_metadatum.prv_tx_id = Some( - decode_bytes(d, "CIP509 previous tx ID")? - .try_into() - .map_err(|_| { - decode::Error::message("Invalid data size of PreviousTxId") - })?, - ); - }, - Cip509IntIdentifier::ValidationSignature => { - let validation_signature = decode_bytes(d, "CIP509 validation signature")?; - if validation_signature.is_empty() || validation_signature.len() > 64 { - return Err(decode::Error::message( - "Invalid data size of ValidationSignature", - )); - } - cip509_metadatum.validation_signature = validation_signature; - }, - } - } else { - // Handle the x509 chunks 10 11 12 - let x509_chunks = X509Chunks::decode(d, ctx)?; - cip509_metadatum.x509_chunks = x509_chunks; - } - } - Ok(cip509_metadatum) - } -} - -#[allow(clippy::module_name_repetitions)] -impl Cip509 { - /// Decode and validate CIP509 Metadata - /// - /// The validation include the following: - /// * Hashing the transaction inputs within the transaction should match the - /// txn-inputs-hash - /// * Auxiliary data hash within the transaction should the hash of the auxiliary data - /// itself. This also includes logging the pre-computed hash where the last 64 bytes - /// are set to zero. - /// * Public key validation for role 0 where public key extracted from x509 and c509 - /// subject alternative name should match one of the witness in witness set within - /// the transaction. - /// * Payment key reference validation for role 0 where the reference should be either - /// 1. Negative index reference - reference to transaction output in transaction: - /// should match some of the key within witness set. - /// 2. Positive index reference - reference to the transaction input in - /// transaction: only check whether the index exist within the transaction - /// inputs. - /// - /// See: - /// * - /// * - /// - /// Note: This CIP509 is still under development and is subject to change. - /// - /// # Parameters - /// * `decoded_metadata` - Decoded Metadata - Will be updated only if CIP509 Metadata - /// is found. - /// * `txn` - Transaction data was attached to and to be validated/decoded against. - /// * `raw_aux_data` - Raw Auxiliary Data for the transaction. - /// * `txn_idx` - Transaction Index - /// - /// # Returns - /// - /// Nothing. IF CIP509 Metadata is found it will be updated in `decoded_metadata`. - pub(crate) fn decode_and_validate( - decoded_metadata: &DecodedMetadata, txn: &MultiEraTx, raw_aux_data: &RawAuxData, - txn_idx: usize, - ) { - // Get the CIP509 metadata if possible - let Some(k509) = raw_aux_data.get_metadata(LABEL) else { - return; - }; - - let mut validation_report = ValidationReport::new(); - let mut decoder = Decoder::new(k509.as_slice()); - - let mut cip509 = match Cip509::decode(&mut decoder, &mut ()) { - Ok(metadata) => metadata, - Err(e) => { - Cip509::default().validation_failure( - &format!("Failed to decode CIP509 metadata: {e}"), - &mut validation_report, - decoded_metadata, - ); - return; - }, - }; - - // Validate transaction inputs hash - match cip509.validate_txn_inputs_hash(txn, &mut validation_report, decoded_metadata) { - Some(b) => cip509.validation.valid_txn_inputs_hash = b, - None => { - cip509.validation_failure( - "Failed to validate transaction inputs hash", - &mut validation_report, - decoded_metadata, - ); - }, - } - - // Validate the auxiliary data - match cip509.validate_aux(txn, &mut validation_report, decoded_metadata) { - Some(b) => cip509.validation.valid_aux = b, - None => { - cip509.validation_failure( - "Failed to validate auxiliary data", - &mut validation_report, - decoded_metadata, - ); - }, - } - - // Validate the role 0 - if let Some(role_set) = &cip509.x509_chunks.0.role_set { - // Validate only role 0 - for role in role_set { - if role.role_number == 0 { - // Validate stake public key to in certificate to the witness set in transaction - match cip509.validate_stake_public_key( - txn, - &mut validation_report, - decoded_metadata, - txn_idx, - ) { - Some(b) => cip509.validation.valid_public_key = b, - None => { - cip509.validation_failure( - &format!("Failed to validate stake public key in tx id {txn_idx}"), - &mut validation_report, - decoded_metadata, - ); - }, - } - // Validate payment key reference - match cip509.validate_payment_key( - txn, - &mut validation_report, - decoded_metadata, - txn_idx, - role, - ) { - Some(b) => cip509.validation.valid_payment_key = b, - None => { - cip509.validation_failure( - &format!("Failed to validate payment key in tx id {txn_idx}"), - &mut validation_report, - decoded_metadata, - ); - }, - } - } - } - } - } - - /// Handle validation failure. - fn validation_failure( - &self, reason: &str, validation_report: &mut ValidationReport, - decoded_metadata: &DecodedMetadata, - ) { - validation_report.push(reason.into()); - decoded_metadata.0.insert( - LABEL, - Arc::new(DecodedMetadataItem { - value: DecodedMetadataValues::Cip509(Arc::new(self.clone()).clone()), - report: validation_report.clone(), - }), - ); - } - - /// Transaction inputs hash validation. - /// Must exist and match the hash of the transaction inputs. - fn validate_txn_inputs_hash( - &self, txn: &MultiEraTx, validation_report: &mut ValidationReport, - decoded_metadata: &DecodedMetadata, - ) -> Option { - let mut buffer = Vec::new(); - let mut e = Encoder::new(&mut buffer); - match txn { - MultiEraTx::AlonzoCompatible(tx, _) => { - let inputs = tx.transaction_body.inputs.clone(); - if let Err(e) = e.array(inputs.len() as u64) { - self.validation_failure( - &format!("Failed to encode array of transaction input in validate_txn_inputs_hash: {e}"), - validation_report, - decoded_metadata, - ); - return None; - } - for input in &inputs { - if let Err(e) = input.encode(&mut e, &mut ()) { - self.validation_failure( - &format!("Failed to encode transaction input in validate_txn_inputs_hash: {e}"), - validation_report, - decoded_metadata, - ); - return None; - } - } - }, - MultiEraTx::Babbage(tx) => { - let inputs = tx.transaction_body.inputs.clone(); - if let Err(e) = e.array(inputs.len() as u64) { - self.validation_failure( - &format!("Failed to encode array of transaction input in validate_txn_inputs_hash: {e}"), - validation_report, - decoded_metadata, - ); - return None; - } - for input in &inputs { - if let Err(e) = input.encode(&mut e, &mut ()) { - self.validation_failure( - &format!("Failed to encode transaction input in validate_txn_inputs_hash: {e}"), - validation_report, - decoded_metadata, - ); - return None; - } - } - }, - MultiEraTx::Conway(tx) => { - let inputs = tx.transaction_body.inputs.clone(); - if let Err(e) = e.array(inputs.len() as u64) { - self.validation_failure( - &format!("Failed to encode array of transaction input in validate_txn_inputs_hash: {e}"), - validation_report, - decoded_metadata, - ); - return None; - } - for input in &inputs { - match input.encode(&mut e, &mut ()) { - Ok(()) => {}, - Err(e) => { - self.validation_failure( - &format!( - "Failed to encode transaction input in validate_txn_inputs_hash {e}" - ), - validation_report, - decoded_metadata, - ); - return None; - }, - } - } - }, - _ => { - self.validation_failure( - "Unsupported transaction era for transaction inputs hash validation", - validation_report, - decoded_metadata, - ); - return None; - }, - } - let inputs_hash = match blake2b_128(&buffer) { - Ok(hash) => hash, - Err(e) => { - self.validation_failure( - &format!("Failed to hash transaction inputs in validate_txn_inputs_hash {e}"), - validation_report, - decoded_metadata, - ); - return None; - }, - }; - Some(inputs_hash == self.txn_inputs_hash) - } - - /// Validate the auxiliary data with the auxiliary data hash in the transaction. - /// Also log out the pre-computed hash where the validation signature (99) set to - /// zero. - fn validate_aux( - &mut self, txn: &MultiEraTx, validation_report: &mut ValidationReport, - decoded_metadata: &DecodedMetadata, - ) -> Option { - match txn { - MultiEraTx::AlonzoCompatible(tx, _) => { - if let pallas::codec::utils::Nullable::Some(a) = &tx.auxiliary_data { - let original_aux = a.raw_cbor(); - let aux_data_hash = - tx.transaction_body - .auxiliary_data_hash - .as_ref() - .or_else(|| { - self.validation_failure( - "Auxiliary data hash not found in transaction", - validation_report, - decoded_metadata, - ); - None - })?; - self.validate_aux_helper( - original_aux, - aux_data_hash, - validation_report, - decoded_metadata, - ) - } else { - self.validation_failure( - "Auxiliary data not found in transaction", - validation_report, - decoded_metadata, - ); - None - } - }, - MultiEraTx::Babbage(tx) => { - if let pallas::codec::utils::Nullable::Some(a) = &tx.auxiliary_data { - let original_aux = a.raw_cbor(); - let aux_data_hash = - tx.transaction_body - .auxiliary_data_hash - .as_ref() - .or_else(|| { - self.validation_failure( - "Auxiliary data hash not found in transaction", - validation_report, - decoded_metadata, - ); - None - })?; - self.validate_aux_helper( - original_aux, - aux_data_hash, - validation_report, - decoded_metadata, - ) - } else { - self.validation_failure( - "Auxiliary data not found in transaction", - validation_report, - decoded_metadata, - ); - None - } - }, - MultiEraTx::Conway(tx) => { - if let pallas::codec::utils::Nullable::Some(a) = &tx.auxiliary_data { - let original_aux = a.raw_cbor(); - let aux_data_hash = - tx.transaction_body - .auxiliary_data_hash - .as_ref() - .or_else(|| { - self.validation_failure( - "Auxiliary data hash not found in transaction", - validation_report, - decoded_metadata, - ); - None - })?; - self.validate_aux_helper( - original_aux, - aux_data_hash, - validation_report, - decoded_metadata, - ) - } else { - self.validation_failure( - "Auxiliary data not found in transaction", - validation_report, - decoded_metadata, - ); - None - } - }, - _ => { - self.validation_failure( - "Unsupported transaction era for auxiliary data validation", - validation_report, - decoded_metadata, - ); - None - }, - } - } - - /// Helper function for auxiliary data validation. - fn validate_aux_helper( - &mut self, original_aux: &[u8], aux_data_hash: &Bytes, - validation_report: &mut ValidationReport, decoded_metadata: &DecodedMetadata, - ) -> Option { - let mut vec_aux = original_aux.to_vec(); - - // Zero out the last 64 bytes - zero_out_last_n_bytes(&mut vec_aux, 64); - - // Pre-computed aux with the last 64 bytes set to zero - self.validation.precomputed_aux = vec_aux; - - // Compare the hash - match blake2b_256(original_aux) { - Ok(original_hash) => { - return Some(aux_data_hash.as_ref() == original_hash); - }, - Err(e) => { - self.validation_failure( - &format!("Cannot hash auxiliary data {e}"), - validation_report, - decoded_metadata, - ); - None - }, - } - } - - /// Validate the stake public key in the certificate with witness set in transaction. - #[allow(clippy::too_many_lines)] - fn validate_stake_public_key( - &self, txn: &MultiEraTx, validation_report: &mut ValidationReport, - decoded_metadata: &DecodedMetadata, txn_idx: usize, - ) -> Option { - let mut pk_addrs = Vec::new(); - match txn { - MultiEraTx::AlonzoCompatible(..) | MultiEraTx::Babbage(_) | MultiEraTx::Conway(_) => { - // X509 certificate - if let Some(x509_certs) = &self.x509_chunks.0.x509_certs { - for cert in x509_certs { - // Attempt to decode the DER certificate - let der_cert = match x509_cert::Certificate::from_der(&cert.0) { - Ok(cert) => cert, - Err(e) => { - self.validation_failure( - &format!("Failed to decode x509 certificate DER: {e}"), - validation_report, - decoded_metadata, - ); - return None; - }, - }; - - // Find the Subject Alternative Name extension - let san_ext = - der_cert - .tbs_certificate - .extensions - .as_ref() - .and_then(|exts| { - exts.iter() - .find(|ext| ext.extn_id == ID_CE_SUBJECT_ALT_NAME) - }); - - // Subject Alternative Name extension if it exists - if let Some(san_ext) = san_ext { - match parse_der_sequence(san_ext.extn_value.as_bytes()) { - Ok((_, parsed_seq)) => { - for data in parsed_seq.ref_iter() { - // Check for context-specific primitive type with tag number - // 6 (raw_tag 134) - if data.header.raw_tag() == Some(&[URI]) { - match data.content.as_slice() { - Ok(content) => { - // Decode the UTF-8 string - let addr: String = match decode_utf8(content) { - Ok(addr) => addr, - Err(e) => { - self.validation_failure( - &format!( - "Failed to decode UTF-8 string for context-specific primitive type with raw tag 134: {e}", - ), - validation_report, - decoded_metadata, - ); - return None; - }, - }; - - // Extract the CIP19 hash and push into - // array - if let Some(h) = - extract_cip19_hash(&addr, Some("stake")) - { - pk_addrs.push(h); - } - }, - Err(e) => { - self.validation_failure( - &format!("Failed to process content for context-specific primitive type with raw tag 134: {e}"), - validation_report, - decoded_metadata, - ); - return None; - }, - } - } - } - }, - Err(e) => { - self.validation_failure( - &format!( - "Failed to parse DER sequence for Subject Alternative Name extension: {e}", - ), - validation_report, - decoded_metadata, - ); - return None; - }, - } - } - } - } - // C509 Certificate - if let Some(c509_certs) = &self.x509_chunks.0.c509_certs { - for cert in c509_certs { - match cert { - C509Cert::C509CertInMetadatumReference(_) => { - self.validation_failure( - "C509 metadatum reference is currently not supported", - validation_report, - decoded_metadata, - ); - }, - C509Cert::C509Certificate(c509) => { - for exts in c509.tbs_cert().extensions().extensions() { - if *exts.registered_oid().c509_oid().oid() - == C509ExtensionType::SubjectAlternativeName.oid() - { - match exts.value() { - c509_certificate::extensions::extension::ExtensionValue::AlternativeName(alt_name) => { - match alt_name.general_name() { - c509_certificate::extensions::alt_name::GeneralNamesOrText::GeneralNames(gn) => { - for name in gn.general_names() { - if name.gn_type() == &c509_certificate::general_names::general_name::GeneralNameTypeRegistry::UniformResourceIdentifier { - match name.gn_value() { - GeneralNameValue::Text(s) => { - if let Some(h) = extract_cip19_hash(s, Some("stake")) { - pk_addrs.push(h); - } - }, - _ => { - self.validation_failure( - "Failed to get the value of subject alternative name", - validation_report, - decoded_metadata, - ); - } - } - } - } - }, - c509_certificate::extensions::alt_name::GeneralNamesOrText::Text(_) => { - self.validation_failure( - "Failed to find C509 general names in subject alternative name", - validation_report, - decoded_metadata, - ); - } - } - }, - _ => { - self.validation_failure( - "Failed to get C509 subject alternative name", - validation_report, - decoded_metadata, - ); - } - } - } - } - }, - } - } - } - }, - _ => { - self.validation_failure( - "Unsupported transaction era for public key validation", - validation_report, - decoded_metadata, - ); - return None; - }, - } - - // Create TxWitness - let witnesses = match TxWitness::new(&[txn.clone()]) { - Ok(witnesses) => witnesses, - Err(e) => { - self.validation_failure( - &format!("Failed to create TxWitness: {e}"), - validation_report, - decoded_metadata, - ); - return None; - }, - }; - - let index = match u16::try_from(txn_idx) { - Ok(value) => value, - Err(e) => { - self.validation_failure( - &format!("Failed to convert transaction index to usize: {e}"), - validation_report, - decoded_metadata, - ); - return None; - }, - }; - Some( - compare_key_hash(&pk_addrs, &witnesses, index) - .map_err(|e| { - self.validation_failure( - &format!("Failed to compare public keys with witnesses {e}"), - validation_report, - decoded_metadata, - ); - }) - .is_ok(), - ) - } - - /// Validate the payment key - #[allow(clippy::too_many_lines)] - fn validate_payment_key( - &self, txn: &MultiEraTx, validation_report: &mut ValidationReport, - decoded_metadata: &DecodedMetadata, txn_idx: usize, role_data: &RoleData, - ) -> Option { - if let Some(payment_key) = role_data.payment_key { - // 0 reference key is not allowed - if payment_key == 0 { - self.validation_failure( - "Invalid payment reference key, 0 is not allowed", - validation_report, - decoded_metadata, - ); - return None; - } - match txn { - MultiEraTx::AlonzoCompatible(tx, _) => { - // Handle negative payment keys (reference to tx output) - if payment_key < 0 { - let witness = match TxWitness::new(&[txn.clone()]) { - Ok(witness) => witness, - Err(e) => { - self.validation_failure( - &format!("Failed to create TxWitness: {e}"), - validation_report, - decoded_metadata, - ); - return None; - }, - }; - let index = match decremented_index(payment_key.abs()) { - Ok(value) => value, - Err(e) => { - self.validation_failure( - &format!("Failed to get index of payment key: {e}"), - validation_report, - decoded_metadata, - ); - return None; - }, - }; - let outputs = tx.transaction_body.outputs.clone(); - if let Some(output) = outputs.get(index) { - return self.validate_payment_output_key_helper( - &output.address.to_vec(), - validation_report, - decoded_metadata, - &witness, - txn_idx, - ); - } - self.validation_failure( - "Role payment key reference index is not found in transaction outputs", - validation_report, - decoded_metadata, - ); - return None; - } - // Handle positive payment keys (reference to tx input) - let inputs = &tx.transaction_body.inputs; - let index = match decremented_index(payment_key) { - Ok(value) => value, - Err(e) => { - self.validation_failure( - &format!("Failed to get index of payment key: {e}"), - validation_report, - decoded_metadata, - ); - return None; - }, - }; - if inputs.get(index).is_none() { - self.validation_failure( - "Role payment key reference index is not found in transaction inputs", - validation_report, - decoded_metadata, - ); - return None; - } - return Some(true); - }, - MultiEraTx::Babbage(tx) => { - // Negative indicates reference to tx output - if payment_key < 0 { - let index = match decremented_index(payment_key.abs()) { - Ok(value) => value, - Err(e) => { - self.validation_failure( - &format!("Failed to get index of payment key: {e}"), - validation_report, - decoded_metadata, - ); - return None; - }, - }; - let outputs = tx.transaction_body.outputs.clone(); - let witness = match TxWitness::new(&[txn.clone()]) { - Ok(witnesses) => witnesses, - Err(e) => { - self.validation_failure( - &format!("Failed to create TxWitness: {e}"), - validation_report, - decoded_metadata, - ); - return None; - }, - }; - if let Some(output) = outputs.get(index) { - match output { - pallas::ledger::primitives::babbage::PseudoTransactionOutput::Legacy(o) => { - return self.validate_payment_output_key_helper(&o.address.to_vec(), validation_report, decoded_metadata, &witness, txn_idx); - } - , - pallas::ledger::primitives::babbage::PseudoTransactionOutput::PostAlonzo(o) => { - return self.validate_payment_output_key_helper(&o.address.to_vec(), validation_report, decoded_metadata, &witness, txn_idx) - } - , - }; - } - self.validation_failure( - "Role payment key reference index is not found in transaction outputs", - validation_report, - decoded_metadata, - ); - return None; - } - // Positive indicates reference to tx input - let inputs = &tx.transaction_body.inputs; - let index = match decremented_index(payment_key) { - Ok(value) => value, - Err(e) => { - self.validation_failure( - &format!("Failed to get index of payment key: {e}"), - validation_report, - decoded_metadata, - ); - return None; - }, - }; - if inputs.get(index).is_none() { - self.validation_failure( - "Role payment key reference index is not found in transaction inputs", - validation_report, - decoded_metadata, - ); - return None; - } - return Some(true); - }, - MultiEraTx::Conway(tx) => { - // Negative indicates reference to tx output - if payment_key < 0 { - let index = match decremented_index(payment_key.abs()) { - Ok(value) => value, - Err(e) => { - self.validation_failure( - &format!("Failed to get index of payment key: {e}"), - validation_report, - decoded_metadata, - ); - return None; - }, - }; - let outputs = tx.transaction_body.outputs.clone(); - let witness = match TxWitness::new(&[txn.clone()]) { - Ok(witnesses) => witnesses, - Err(e) => { - self.validation_failure( - &format!("Failed to create TxWitness: {e}"), - validation_report, - decoded_metadata, - ); - return None; - }, - }; - - if let Some(output) = outputs.get(index) { - match output { - pallas::ledger::primitives::conway::PseudoTransactionOutput::Legacy(o) => { - return self.validate_payment_output_key_helper(&o.address.to_vec(), validation_report, decoded_metadata, &witness, txn_idx); - }, - pallas::ledger::primitives::conway::PseudoTransactionOutput::PostAlonzo(o) => { - return self.validate_payment_output_key_helper(&o.address.to_vec(), validation_report, decoded_metadata, &witness, txn_idx); - }, - }; - } - self.validation_failure( - "Role payment key reference index is not found in transaction outputs", - validation_report, - decoded_metadata, - ); - return None; - } - // Positive indicates reference to tx input - let inputs = &tx.transaction_body.inputs; - let index = match decremented_index(payment_key) { - Ok(value) => value, - Err(e) => { - self.validation_failure( - &format!("Failed to get index of payment key: {e}"), - validation_report, - decoded_metadata, - ); - return None; - }, - }; - // Check whether the index exists in transaction inputs - if inputs.get(index).is_none() { - self.validation_failure( - "Role payment key reference index is not found in transaction inputs", - validation_report, - decoded_metadata, - ); - return None; - } - return Some(true); - }, - _ => { - self.validation_failure( - "Unsupported transaction era for payment key validation", - validation_report, - decoded_metadata, - ); - return None; - }, - } - } - Some(false) - } - - /// Helper function for validating payment output key. - fn validate_payment_output_key_helper( - &self, output_address: &[u8], validation_report: &mut ValidationReport, - decoded_metadata: &DecodedMetadata, witness: &TxWitness, txn_idx: usize, - ) -> Option { - let idx = match u16::try_from(txn_idx) { - Ok(value) => value, - Err(e) => { - self.validation_failure( - &format!("Transaction index conversion failed: {e}"), - validation_report, - decoded_metadata, - ); - return None; - }, - }; - // Extract the key hash from the output address - if let Some(key) = extract_key_hash(output_address) { - // Compare the key hash and return the result - return Some(compare_key_hash(&[key], witness, idx).is_ok()); - } - self.validation_failure( - "Failed to extract payment key hash from address", - validation_report, - decoded_metadata, - ); - None - } -} - -#[cfg(test)] -mod tests { - - use dashmap::DashMap; - - use super::*; - fn conway_1() -> Vec { - hex::decode(include_str!( - "../../../test_data/conway_tx_rbac/conway_1.block" - )) - .expect("Failed to decode hex block.") - } - - fn conway_2() -> Vec { - hex::decode(include_str!( - "../../../test_data/conway_tx_rbac/conway_2.block" - )) - .expect("Failed to decode hex block.") - } - - fn conway_3() -> Vec { - hex::decode(include_str!( - "../../../test_data/conway_tx_rbac/conway_3.block" - )) - .expect("Failed to decode hex block.") - } - - fn cip_509_aux_data(tx: &MultiEraTx<'_>) -> Vec { - let raw_auxiliary_data = tx - .as_conway() - .unwrap() - .clone() - .auxiliary_data - .map(|aux| aux.raw_cbor()); - - let raw_cbor_data = match raw_auxiliary_data { - pallas::codec::utils::Nullable::Some(data) => Ok(data), - _ => Err("Auxiliary data not found"), - }; - - let auxiliary_data = RawAuxData::new(raw_cbor_data.expect("Failed to get raw cbor data")); - auxiliary_data - .get_metadata(LABEL) - .expect("Failed to get metadata") - .to_vec() - } - - #[test] - fn test_decode_cip509() { - // This data is from conway_1.block - let cip_509 = "a50050ca7a1457ef9f4c7f9c747f8c4a4cfa6c0150226d126819472b7afad7d0b8c7b89aa20258204d3f576f26db29139981a69443c2325daa812cc353a31b5a4db794a5bcbb06c20b9458401b03060066006fd5b67002167882eac0b5f2b11da40788a39bfa0324c494f7003a6b4c1c4bac378e322cb280230a4002f5b2754e863806f7e524afc99996aa28584032f02b600cbf04c6a09e05100880a09ee59b6627dc78d68175469b8c5b1fac141a6da5c6c2ea446597b6f0b6efea00a04ac0c1756455589908a5e089ba604a1258405917d6ee2b2535959d806c00eb2958929ababb40d681b5245751538e915d3d90f561ddcaa9aaa9cd78a30882a22a99c742c4f7610b43750a0d6651e8640a8d4c58402167427cfa933d6430c026640888210cd0c4e93e7015100300dcaef47b9c155ea4ccb27773c27f5d6a44fbf98065a14e5f0eca530e57082a971cbf22fa9065585840ae72e2a061eb558d3fd7727e87a8f07b5faf0d3cedf8d99ab6e0c845f5dd3ce78d31d7365c523b5a4dfe5d35bfafaefb2f60dd7473cbe8d6aa6bf557b1fbdf775840bf96bcd3ffdbfc7d20b65be7f5c7dba1cf635e3b449bb644bdfc73e0e49a5db73edddc7ed331220ba732f62f3aee8503f5c6f9bd5f7fedb37dc6580196052e50584027fdd7e8bfe9146561ad1ebc79ecef0ee1df7081cf9cd1fd929569ef3d55972d5b7ff882ce2213f789fc08787164f14aa86d55e98e332b220a07fa464aaa7c335840ce4bcfb268ed577f72e87fdea4442107bf2da93fe05121d5befa7ae5aecc5f3f9c732e82108003166380198c0146b0e214114a31d7c62b0ec18afd5834034c2b58402b2c515b350d8980a16932071b6d8d125ea1eb53dc28a8aee1787a1670b9e8c4c8cb00c726f3515a39ca1689f870295752820a64721e05e1a234710583416316584031d80291ac9a2b66a04cba844b85f9928a4a04a9928b2805124a25b3aaa4422e45e5d422a9b88a028ba4a5123ac244b8b472164b86085ac21357c3aae7696be25840f1104878009b03813d9e6c53255722402090206058a009d2b808aff772fb712d75f1dea09507fd83838e045dd9ce3eb59e4554f5ed02b8aeb60700f4b39dd9fe584064e1d5a137de0fa4c6cccfd71f831bee372756d72990b357a44e2f9eaf3854db65379db466cfcb55517ba71550acade564f4b7efd1fd95fa57228cee6fa9ae3458405ce1ae79b77f7cd5bdecfcb800fbdb7eaf720eae5995176d94a07c326c71aaf5e6f8439e577edb2d1ed64959324b5a7476e9159bf37bdf226edb747787b79b9e5840bc6ab5b84714eefa4a8c2df4aba37a36757d8b39dd79ec41b4a2f3ee96eabdc0e1f65b37264bdbfdf79eebbc820a7deab4e39f7e1cbf6610402fd8fb55fbef3d584038226e4d37c42970c830184b2e1c5026eadb9677ae8f6d300975ca6ceec5c8920382e827c1f636f7dd9f8d492737f4520a944bfeebba5ca2d5efa80ad453a43f584004c357ecccfc4dab75ce560b0300db9092ced52625d0c8df6fc89da9a45b6dc9c2461f21e6ee7b7afd877fbd8c1a1fa7ff38fa506e14749ebb68e24571c6220c584004208c284d628c2148b252f91b8b50014b080b040554095b52ca862bb974218222d412112ae5d2584c54584ae157f22b183cb4ba9c5fc42ba6894ad074ffe0875840c69ee921211d0ce4cd0f89b7e708163b3ab9286fe26a8c68ed85930cabc5dbfed7f9681c535dbdbfeb56f7a2b32d1f43de1dbcc934676edefacb3df7c1210067584064a1b8d94448b7f22a77dc736edb12f7c2c52b2eb8d4a80b78147d89f9a3a0659c03e10bbb336e391b3961f1afbfa08af3de2a817fceddea0cb57f438b0f8947581e9782ee92e890df65636d835d2d465cc5521c0ec05470e002800015eecf5818635840e0427f23196c17cf13f030595335343030c11d914bc7a84b56af7040930af4110fd4ca29b0bc0e83789adb8668ea2ef28c1dd10dc1fd35ea6ae8c06ee769540d"; - let binding = hex::decode(cip_509).unwrap(); - let mut decoder = Decoder::new(binding.as_slice()); - let decoded_cip509 = Cip509::decode(&mut decoder, &mut ()).unwrap(); - - let purpose: [u8; 16] = hex::decode("ca7a1457ef9f4c7f9c747f8c4a4cfa6c") - .unwrap() - .try_into() - .unwrap(); - let txn_inputs_hash: [u8; 16] = hex::decode("226d126819472b7afad7d0b8c7b89aa2") - .unwrap() - .try_into() - .unwrap(); - let prv_tx_id: [u8; 32] = - hex::decode("4d3f576f26db29139981a69443c2325daa812cc353a31b5a4db794a5bcbb06c2") - .unwrap() - .try_into() - .unwrap(); - let validation_signature = hex::decode("e0427f23196c17cf13f030595335343030c11d914bc7a84b56af7040930af4110fd4ca29b0bc0e83789adb8668ea2ef28c1dd10dc1fd35ea6ae8c06ee769540d").unwrap(); - - assert_eq!(decoded_cip509.purpose, purpose); - assert_eq!(decoded_cip509.txn_inputs_hash, txn_inputs_hash); - assert_eq!(decoded_cip509.prv_tx_id, Some(prv_tx_id)); - assert_eq!(decoded_cip509.validation_signature, validation_signature); - } - - #[test] - fn test_validate_txn_inputs_hash() { - let decoded_metadata = DecodedMetadata(DashMap::new()); - let mut validation_report = ValidationReport::new(); - let conway_block_data = conway_1(); - let multi_era_block = pallas::ledger::traverse::MultiEraBlock::decode(&conway_block_data) - .expect("Failed to decode MultiEraBlock"); - - let transactions = multi_era_block.txs(); - // Forth transaction of this test data contains the CIP509 auxiliary data - let tx = transactions - .get(3) - .expect("Failed to get transaction index"); - let aux_data = cip_509_aux_data(tx); - - let mut decoder = Decoder::new(aux_data.as_slice()); - let cip509 = Cip509::decode(&mut decoder, &mut ()).expect("Failed to decode Cip509"); - assert!(cip509 - .validate_txn_inputs_hash(tx, &mut validation_report, &decoded_metadata) - .unwrap()); - } - - #[test] - fn test_validate_aux() { - let decoded_metadata = DecodedMetadata(DashMap::new()); - let mut validation_report = ValidationReport::new(); - let conway_block_data = conway_1(); - let multi_era_block = pallas::ledger::traverse::MultiEraBlock::decode(&conway_block_data) - .expect("Failed to decode MultiEraBlock"); - - let transactions = multi_era_block.txs(); - // Forth transaction of this test data contains the CIP509 auxiliary data - let tx = transactions - .get(3) - .expect("Failed to get transaction index"); - - let aux_data = cip_509_aux_data(tx); - - let mut decoder = Decoder::new(aux_data.as_slice()); - let mut cip509 = Cip509::decode(&mut decoder, &mut ()).expect("Failed to decode Cip509"); - assert!(cip509 - .validate_aux(tx, &mut validation_report, &decoded_metadata) - .unwrap()); - } - - #[test] - fn test_validate_public_key_success() { - let decoded_metadata = DecodedMetadata(DashMap::new()); - let mut validation_report = ValidationReport::new(); - let conway_block_data = conway_1(); - let multi_era_block = pallas::ledger::traverse::MultiEraBlock::decode(&conway_block_data) - .expect("Failed to decode MultiEraBlock"); - - let transactions = multi_era_block.txs(); - // Forth transaction of this test data contains the CIP509 auxiliary data - let tx = transactions - .get(3) - .expect("Failed to get transaction index"); - - let aux_data = cip_509_aux_data(tx); - - let mut decoder = Decoder::new(aux_data.as_slice()); - let cip509 = Cip509::decode(&mut decoder, &mut ()).expect("Failed to decode Cip509"); - assert!(cip509 - .validate_stake_public_key(tx, &mut validation_report, &decoded_metadata, 0) - .unwrap()); - } - - #[test] - fn test_validate_payment_key_success_positive_ref() { - let decoded_metadata = DecodedMetadata(DashMap::new()); - let mut validation_report = ValidationReport::new(); - let conway_block_data = conway_1(); - let multi_era_block = pallas::ledger::traverse::MultiEraBlock::decode(&conway_block_data) - .expect("Failed to decode MultiEraBlock"); - - let transactions = multi_era_block.txs(); - // Forth transaction of this test data contains the CIP509 auxiliary data - let tx = transactions - .get(3) - .expect("Failed to get transaction index"); - - let aux_data = cip_509_aux_data(tx); - - let mut decoder = Decoder::new(aux_data.as_slice()); - let cip509 = Cip509::decode(&mut decoder, &mut ()).expect("Failed to decode Cip509"); - - if let Some(role_set) = &cip509.x509_chunks.0.role_set { - for role in role_set { - if role.role_number == 0 { - assert!(cip509 - .validate_payment_key( - tx, - &mut validation_report, - &decoded_metadata, - 0, - role - ) - .unwrap()); - } - } - } - } - - #[test] - fn test_validate_payment_key_success_negative_ref() { - let decoded_metadata = DecodedMetadata(DashMap::new()); - let mut validation_report = ValidationReport::new(); - let conway_block_data = conway_3(); - let multi_era_block = pallas::ledger::traverse::MultiEraBlock::decode(&conway_block_data) - .expect("Failed to decode MultiEraBlock"); - - let transactions = multi_era_block.txs(); - // First transaction of this test data contains the CIP509 auxiliary data - let tx = transactions - .first() - .expect("Failed to get transaction index"); - - let aux_data = cip_509_aux_data(tx); - - let mut decoder = Decoder::new(aux_data.as_slice()); - let cip509 = Cip509::decode(&mut decoder, &mut ()).expect("Failed to decode Cip509"); - - if let Some(role_set) = &cip509.x509_chunks.0.role_set { - for role in role_set { - if role.role_number == 0 { - println!( - "{:?}", - cip509.validate_payment_key( - tx, - &mut validation_report, - &decoded_metadata, - 0, - role - ) - ); - } - } - } - } - - #[test] - fn test_validate_public_key_fail() { - let decoded_metadata = DecodedMetadata(DashMap::new()); - let mut validation_report = ValidationReport::new(); - let conway_block_data = conway_2(); - let multi_era_block = pallas::ledger::traverse::MultiEraBlock::decode(&conway_block_data) - .expect("Failed to decode MultiEraBlock"); - - let transactions = multi_era_block.txs(); - // Forth transaction of this test data contains the CIP509 auxiliary data - let tx = transactions - .get(3) - .expect("Failed to get transaction index"); - - let aux_data = cip_509_aux_data(tx); - - let mut decoder = Decoder::new(aux_data.as_slice()); - let cip509 = Cip509::decode(&mut decoder, &mut ()).expect("Failed to decode Cip509"); - assert!(!cip509 - .validate_stake_public_key(tx, &mut validation_report, &decoded_metadata, 0) - .unwrap()); - } -} diff --git a/rust/cardano-chain-follower/src/metadata/cip509/rbac/certs.rs b/rust/cardano-chain-follower/src/metadata/cip509/rbac/certs.rs deleted file mode 100644 index df27f4fa4..000000000 --- a/rust/cardano-chain-follower/src/metadata/cip509/rbac/certs.rs +++ /dev/null @@ -1,99 +0,0 @@ -//! Certificates for the RBAC metadata. - -use c509_certificate::c509::C509; -use minicbor::{decode, Decode, Decoder}; -use x509_cert::{der::Decode as x509Decode, Certificate}; - -use crate::metadata::cip509::decode_helper::{decode_array_len, decode_bytes, decode_helper}; - -// ------------------x509------------------------ - -/// A struct of X509 certificate. -#[derive(Debug, PartialEq, Clone)] -pub struct X509DerCert(pub Vec); - -impl Decode<'_, ()> for X509DerCert { - fn decode(d: &mut Decoder, _ctx: &mut ()) -> Result { - let data = decode_bytes(d, "X509DerCert")?; - Certificate::from_der(&data) - .map_err(|_| decode::Error::message("Invalid x509 certificate"))?; - Ok(Self(data.clone())) - } -} - -// ------------------c509----------------------- - -/// Enum of possible c509 certificate. -#[derive(Debug, PartialEq, Clone)] -pub enum C509Cert { - /// A c509 certificate in metadatum reference. - C509CertInMetadatumReference(C509CertInMetadatumReference), - /// A c509 certificate. - C509Certificate(Box), -} - -impl Decode<'_, ()> for C509Cert { - fn decode(d: &mut Decoder, ctx: &mut ()) -> Result { - if d.datatype()? == minicbor::data::Type::Array { - let arr_len = decode_array_len(d, "C509Cert")?; - // C509CertInMetadatumReference must have 3 items - if arr_len == 3 { - Ok(Self::C509CertInMetadatumReference( - C509CertInMetadatumReference::decode(d, ctx)?, - )) - } else { - Err(decode::Error::message( - "Invalid length C509CertInMetadatumReference, expected 3", - )) - } - } else { - // Consuming the c509 bytes - let c509 = decode_bytes(d, "C509Cert")?; - let mut c509_d = Decoder::new(&c509); - Ok(Self::C509Certificate(Box::new(C509::decode( - &mut c509_d, - ctx, - )?))) - } - } -} - -/// A struct of c509 certificate in metadatum reference. -#[derive(Debug, PartialEq, Clone)] -pub struct C509CertInMetadatumReference { - /// Transaction output field. - txn_output_field: u8, - /// Transaction output index. - txn_output_index: u64, - /// Optional certificate reference. - cert_ref: Option>, -} - -impl Decode<'_, ()> for C509CertInMetadatumReference { - fn decode(d: &mut Decoder, ctx: &mut ()) -> Result { - let txn_output_field: u8 = - decode_helper(d, "txn output field in C509CertInMetadatumReference", ctx)?; - let txn_output_index: u64 = - decode_helper(d, "txn output index in C509CertInMetadatumReference", ctx)?; - let cert_ref = match d.datatype()? { - minicbor::data::Type::Array => { - let len = decode_array_len(d, "cert ref in C509CertInMetadatumReference")?; - let arr: Result, _> = (0..len).map(|_| d.u64()).collect(); - arr.map(Some) - }, - minicbor::data::Type::Null => Ok(None), - _ => { - Ok(Some(vec![decode_helper( - d, - "C509CertInMetadatumReference", - ctx, - )?])) - }, - }?; - Ok(Self { - txn_output_field, - txn_output_index, - cert_ref, - }) - } -} diff --git a/rust/cardano-chain-follower/src/metadata/cip509/rbac/mod.rs b/rust/cardano-chain-follower/src/metadata/cip509/rbac/mod.rs deleted file mode 100644 index 3dbf5da10..000000000 --- a/rust/cardano-chain-follower/src/metadata/cip509/rbac/mod.rs +++ /dev/null @@ -1,167 +0,0 @@ -//! Role Based Access Control (RBAC) metadata for CIP509. -//! Doc Reference: -//! CDDL Reference: - -pub mod certs; -pub mod pub_key; -pub mod role_data; - -use std::collections::HashMap; - -use certs::{C509Cert, X509DerCert}; -use minicbor::{decode, Decode, Decoder}; -use pub_key::SimplePublicKeyType; -use role_data::RoleData; -use strum::FromRepr; - -use super::decode_helper::{ - decode_any, decode_array_len, decode_bytes, decode_helper, decode_map_len, -}; - -/// Struct of Cip509 RBAC metadata. -#[derive(Debug, PartialEq, Clone, Default)] -pub struct Cip509RbacMetadata { - /// Optional list of x509 certificates. - pub x509_certs: Option>, - /// Optional list of c509 certificates. - /// The value can be either the c509 certificate or c509 metadatum reference. - pub c509_certs: Option>, - /// Optional list of Public keys. - pub pub_keys: Option>, - /// Optional list of revocation list. - pub revocation_list: Option>, - /// Optional list of role data. - pub role_set: Option>, - /// Optional map of purpose key data. - /// Empty map if no purpose key data is present. - pub purpose_key_data: HashMap>, -} - -/// The first valid purpose key. -const FIRST_PURPOSE_KEY: u16 = 200; -/// The last valid purpose key. -const LAST_PURPOSE_KEY: u16 = 299; - -/// Enum of CIP509 RBAC metadata with its associated unsigned integer value. -#[derive(FromRepr, Debug, PartialEq)] -#[repr(u16)] -pub enum Cip509RbacMetadataInt { - /// x509 certificates. - X509Certs = 10, - /// c509 certificates. - C509Certs = 20, - /// Public keys. - PubKeys = 30, - /// Revocation list. - RevocationList = 40, - /// Role data set. - 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) { - self.x509_certs = Some(x509_certs); - } - - /// Set the c509 certificates. - fn set_c509_certs(&mut self, c509_certs: Vec) { - self.c509_certs = Some(c509_certs); - } - - /// Set the public keys. - fn set_pub_keys(&mut self, pub_keys: Vec) { - self.pub_keys = Some(pub_keys); - } - - /// Set the revocation list. - fn set_revocation_list(&mut self, revocation_list: Vec<[u8; 16]>) { - self.revocation_list = Some(revocation_list); - } - - /// Set the role data set. - fn set_role_set(&mut self, role_set: Vec) { - self.role_set = Some(role_set); - } -} - -impl Decode<'_, ()> for Cip509RbacMetadata { - fn decode(d: &mut Decoder, ctx: &mut ()) -> Result { - let map_len = decode_map_len(d, "Cip509RbacMetadata")?; - - let mut x509_rbac_metadata = Cip509RbacMetadata::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); - }, - Cip509RbacMetadataInt::C509Certs => { - let c509_certs = decode_array_rbac(d, "c509 certificate")?; - x509_rbac_metadata.set_c509_certs(c509_certs); - }, - Cip509RbacMetadataInt::PubKeys => { - let pub_keys = decode_array_rbac(d, "public keys")?; - x509_rbac_metadata.set_pub_keys(pub_keys); - }, - Cip509RbacMetadataInt::RevocationList => { - let revocation_list = decode_revocation_list(d)?; - x509_rbac_metadata.set_revocation_list(revocation_list); - }, - Cip509RbacMetadataInt::RoleSet => { - let role_set = decode_array_rbac(d, "role set")?; - x509_rbac_metadata.set_role_set(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")?); - } - } - Ok(x509_rbac_metadata) - } -} - -/// Decode an array of type T. -fn decode_array_rbac<'b, T>(d: &mut Decoder<'b>, from: &str) -> Result, decode::Error> -where T: Decode<'b, ()> { - let len = decode_array_len(d, &format!("{from} Cip509RbacMetadata"))?; - let mut vec = Vec::with_capacity(usize::try_from(len).map_err(decode::Error::message)?); - for _ in 0..len { - vec.push(T::decode(d, &mut ())?); - } - Ok(vec) -} - -/// Decode an array of revocation list. -fn decode_revocation_list(d: &mut Decoder) -> Result, decode::Error> { - let len = decode_array_len(d, "revocation list Cip509RbacMetadata")?; - let mut revocation_list = - Vec::with_capacity(usize::try_from(len).map_err(decode::Error::message)?); - for _ in 0..len { - let arr: [u8; 16] = decode_bytes(d, "revocation list Cip509RbacMetadata")? - .try_into() - .map_err(|_| decode::Error::message("Invalid revocation list size"))?; - revocation_list.push(arr); - } - Ok(revocation_list) -} diff --git a/rust/cardano-chain-follower/src/metadata/cip509/rbac/pub_key.rs b/rust/cardano-chain-follower/src/metadata/cip509/rbac/pub_key.rs deleted file mode 100644 index 120a4b8a2..000000000 --- a/rust/cardano-chain-follower/src/metadata/cip509/rbac/pub_key.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! Public key type for RBAC metadata - -use minicbor::{data::Tag, decode, Decode, Decoder}; - -use crate::metadata::cip509::decode_helper::{decode_bytes, decode_tag}; - -/// Enum of possible public key type. -#[derive(Debug, PartialEq, Clone, Default)] -pub enum SimplePublicKeyType { - /// Undefined indicates skipped element. - #[default] - Undefined, - /// Deleted indicates the key is deleted. - Deleted, - /// Ed25519 key. - Ed25519([u8; 32]), -} - -/// Enum of possible public key tag. -enum PublicKeyTag { - /// Deleted Key tag 31. - Deleted, - /// Ed25519 Key tag 32773. - Ed25519, -} - -impl PublicKeyTag { - /// Get the tag value. - fn tag(self) -> Tag { - match self { - PublicKeyTag::Deleted => Tag::new(0x31), - PublicKeyTag::Ed25519 => Tag::new(0x8005), - } - } -} - -impl Decode<'_, ()> for SimplePublicKeyType { - fn decode(d: &mut Decoder, _ctx: &mut ()) -> Result { - match d.datatype()? { - minicbor::data::Type::Tag => { - let tag = decode_tag(d, "SimplePublicKeyType")?; - match tag { - t if t == PublicKeyTag::Deleted.tag() => Ok(SimplePublicKeyType::Deleted), - t if t == PublicKeyTag::Ed25519.tag() => { - let bytes = decode_bytes(d, "Ed25519 SimplePublicKeyType")?; - let mut ed25519 = [0u8; 32]; - if bytes.len() == 32 { - ed25519.copy_from_slice(&bytes); - Ok(SimplePublicKeyType::Ed25519(ed25519)) - } else { - Err(decode::Error::message("Invalid length for Ed25519 key")) - } - }, - _ => { - Err(decode::Error::message( - "Unknown tag for SimplePublicKeyType", - )) - }, - } - }, - minicbor::data::Type::Undefined => Ok(SimplePublicKeyType::Undefined), - _ => { - Err(decode::Error::message( - "Invalid datatype for SimplePublicKeyType", - )) - }, - } - } -} diff --git a/rust/cardano-chain-follower/src/metadata/cip509/rbac/role_data.rs b/rust/cardano-chain-follower/src/metadata/cip509/rbac/role_data.rs deleted file mode 100644 index 21733a953..000000000 --- a/rust/cardano-chain-follower/src/metadata/cip509/rbac/role_data.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! Role data for RBAC metadata. - -use std::collections::HashMap; - -use minicbor::{decode, Decode, Decoder}; -use strum::FromRepr; - -use super::Cip509RbacMetadataInt; -use crate::metadata::cip509::decode_helper::{ - decode_any, decode_array_len, decode_bytes, decode_helper, decode_map_len, -}; - -/// Struct of role data. -#[derive(Debug, PartialEq, Clone, Default)] -pub struct RoleData { - /// Role number. - pub role_number: u8, - /// Optional role signing key. - pub role_signing_key: Option, - /// Optional role encryption key. - pub role_encryption_key: Option, - /// Optional payment key. - pub payment_key: Option, - /// Optional role extended data keys. - /// Empty map if no role extended data keys. - pub role_extended_data_keys: HashMap>, -} - -/// The first valid role extended data key. -const FIRST_ROLE_EXT_KEY: u8 = 10; -/// The last valid role extended data key. -const LAST_ROLE_EXT_KEY: u8 = 99; - -/// Enum of role data with its associated unsigned integer value. -#[allow(clippy::module_name_repetitions)] -#[derive(FromRepr, Debug, PartialEq)] -#[repr(u8)] -pub enum RoleDataInt { - /// Role number. - RoleNumber = 0, - /// Role signing key. - RoleSigningKey = 1, - /// Role encryption key. - RoleEncryptionKey = 2, - /// Payment key. - PaymentKey = 3, -} - -impl Decode<'_, ()> for RoleData { - fn decode(d: &mut Decoder, ctx: &mut ()) -> Result { - let map_len = decode_map_len(d, "RoleData")?; - let mut role_data = RoleData::default(); - for _ in 0..map_len { - let key: u8 = decode_helper(d, "key in RoleData", ctx)?; - if let Some(key) = RoleDataInt::from_repr(key) { - match key { - RoleDataInt::RoleNumber => { - role_data.role_number = decode_helper(d, "RoleNumber in RoleData", ctx)?; - }, - RoleDataInt::RoleSigningKey => { - role_data.role_signing_key = Some(KeyReference::decode(d, ctx)?); - }, - RoleDataInt::RoleEncryptionKey => { - role_data.role_encryption_key = Some(KeyReference::decode(d, ctx)?); - }, - RoleDataInt::PaymentKey => { - role_data.payment_key = - Some(decode_helper(d, "PaymentKey in RoleData", ctx)?); - }, - } - } else { - if !(FIRST_ROLE_EXT_KEY..=LAST_ROLE_EXT_KEY).contains(&key) { - return Err(decode::Error::message(format!("Invalid role extended data key, should be with the range {FIRST_ROLE_EXT_KEY} - {LAST_ROLE_EXT_KEY}"))); - } - role_data - .role_extended_data_keys - .insert(key, decode_any(d, "Role extended data keys")?); - } - } - Ok(role_data) - } -} - -/// Enum of key reference. -#[derive(Debug, PartialEq, Clone)] -pub enum KeyReference { - /// Key local reference. - KeyLocalRef(KeyLocalRef), - /// Key hash. - KeyHash(Vec), -} - -impl Default for KeyReference { - fn default() -> Self { - KeyReference::KeyHash(Vec::new()) - } -} - -impl Decode<'_, ()> for KeyReference { - fn decode(d: &mut Decoder, ctx: &mut ()) -> Result { - match d.datatype()? { - minicbor::data::Type::Array => Ok(Self::KeyLocalRef(KeyLocalRef::decode(d, ctx)?)), - minicbor::data::Type::Bytes => { - Ok(Self::KeyHash(decode_bytes(d, "KeyHash in KeyReference")?)) - }, - _ => Err(decode::Error::message("Invalid data type for KeyReference")), - } - } -} - -/// Struct of key local reference. -#[derive(Debug, PartialEq, Clone)] -pub struct KeyLocalRef { - /// Local reference. - pub local_ref: LocalRefInt, - /// Key offset. - pub key_offset: u64, -} - -/// Enum of local reference with its associated unsigned integer value. -#[derive(FromRepr, Debug, PartialEq, Clone)] -#[repr(u8)] -pub enum LocalRefInt { - /// x509 certificates. - X509Certs = Cip509RbacMetadataInt::X509Certs as u8, // 10 - /// c509 certificates. - C509Certs = Cip509RbacMetadataInt::C509Certs as u8, // 20 - /// Public keys. - PubKeys = Cip509RbacMetadataInt::PubKeys as u8, // 30 -} - -impl Decode<'_, ()> for KeyLocalRef { - fn decode(d: &mut Decoder, ctx: &mut ()) -> Result { - decode_array_len(d, "KeyLocalRef")?; - let local_ref = LocalRefInt::from_repr(decode_helper(d, "LocalRef in KeyLocalRef", ctx)?) - .ok_or(decode::Error::message("Invalid local reference"))?; - let key_offset: u64 = decode_helper(d, "KeyOffset in KeyLocalRef", ctx)?; - Ok(Self { - local_ref, - key_offset, - }) - } -} diff --git a/rust/cardano-chain-follower/src/metadata/cip509/utils.rs b/rust/cardano-chain-follower/src/metadata/cip509/utils.rs deleted file mode 100644 index 47cb0c434..000000000 --- a/rust/cardano-chain-follower/src/metadata/cip509/utils.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! Utility functions use in CIP-509 -use regex::Regex; - -use crate::witness::TxWitness; - -/// Extracts the CIP-19 bytes from a URI. -/// Example input: `web+cardano://addr/` -/// -/// URI = scheme ":" ["//" authority] path ["?" query] ["#" fragment] -#[must_use] -pub fn extract_cip19_hash(uri: &str, prefix: Option<&str>) -> Option> { - // Regex pattern to match the expected URI format - let r = Regex::new("^.+://addr/(.+)$").ok()?; - - // Apply the regex pattern to capture the CIP-19 address string - let address = r - .captures(uri) - .and_then(|cap| cap.get(1).map(|m| m.as_str().to_string())); - - match address { - Some(addr) => { - if let Some(prefix) = prefix { - if !addr.starts_with(prefix) { - return None; - } - } - let addr = bech32::decode(&addr).ok()?.1; - // As in CIP19, the first byte is the header, so extract only the payload - extract_key_hash(&addr) - }, - None => None, - } -} - -/// Extract the first 28 bytes from the given key -/// Refer to for more information. -pub(crate) fn extract_key_hash(key: &[u8]) -> Option> { - key.get(1..29).map(<[u8]>::to_vec) -} - -/// Compare the given public key bytes with the transaction witness set. -pub(crate) fn compare_key_hash( - pk_addrs: &[Vec], witness: &TxWitness, txn_idx: u16, -) -> anyhow::Result<()> { - if pk_addrs.is_empty() { - return Err(anyhow::anyhow!("No public key addresses provided")); - } - - pk_addrs.iter().try_for_each(|pk_addr| { - let pk_addr: [u8; 28] = pk_addr.as_slice().try_into().map_err(|_| { - anyhow::anyhow!( - "Invalid length for vkey, expected 28 bytes but got {}", - pk_addr.len() - ) - })?; - - // Key hash not found in the transaction witness set - if !witness.check_witness_in_tx(&pk_addr, txn_idx) { - return Err(anyhow::anyhow!( - "Public key hash not found in transaction witness set given {:?}", - pk_addr - )); - } - - Ok(()) - }) -} - -/// Zero out the last n bytes -pub(crate) fn zero_out_last_n_bytes(vec: &mut [u8], n: usize) { - if let Some(slice) = vec.get_mut(vec.len().saturating_sub(n)..) { - slice.fill(0); - } -} - -/// Getting the index by decrementing by 1. -/// e.g. 1 should refers to index 0 -pub(crate) fn decremented_index(int: i16) -> anyhow::Result { - match usize::try_from(int) { - Ok(value) => Ok(value - 1), - Err(e) => { - Err(anyhow::Error::msg(format!( - "Failed to convert to usize: {e}" - ))) - }, - } -} - -#[cfg(test)] -mod tests { - use super::*; - - // Test data from https://cips.cardano.org/cip/CIP-19 - // cSpell:disable - const STAKE_ADDR: &str = "stake_test1uqehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gssrtvn"; - const PAYMENT_ADDR: &str = "addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs68faae"; - // cSpell:enable - - #[test] - fn test_extract_cip19_hash_with_stake() { - // Additional tools to check for bech32 https://slowli.github.io/bech32-buffer/ - let uri = &format!("web+cardano://addr/{STAKE_ADDR}"); - // Given: - // e0337b62cfff6403a06a3acbc34f8c46003c69fe79a3628cefa9c47251 - // The first byte is the header, so extract only the payload - let bytes = hex::decode("337b62cfff6403a06a3acbc34f8c46003c69fe79a3628cefa9c47251") - .expect("Failed to decode bytes"); - assert_eq!( - extract_cip19_hash(uri, Some("stake")).expect("Failed to extract CIP-19 hash"), - bytes - ); - } - - #[test] - fn test_extract_cip19_hash_with_addr_with_prefix_set() { - let uri = &format!("web+cardano://addr/{PAYMENT_ADDR}"); - let result = extract_cip19_hash(uri, Some("stake")); - assert_eq!(result, None); - } - - #[test] - fn test_extract_cip19_hash_with_addr_without_prefix_set() { - let uri = &format!("web+cardano://addr/{PAYMENT_ADDR}"); - let result = extract_cip19_hash(uri, None); - assert!(result.is_some()); - } - - #[test] - fn test_extract_cip19_hash_invalid_uri() { - let uri = "invalid_uri"; - let result = extract_cip19_hash(uri, None); - assert_eq!(result, None); - } - - #[test] - fn test_extract_cip19_hash_non_bech32_address() { - let uri = "example://addr/not_bech32"; - let result = extract_cip19_hash(uri, None); - assert_eq!(result, None); - } - - #[test] - fn test_extract_cip19_hash_empty_uri() { - let uri = ""; - let result = extract_cip19_hash(uri, None); - assert_eq!(result, None); - } - - #[test] - fn test_extract_cip19_hash_no_address() { - let uri = "example://addr/"; - let result = extract_cip19_hash(uri, None); - assert_eq!(result, None); - } -} diff --git a/rust/cardano-chain-follower/src/metadata/cip509/x509_chunks.rs b/rust/cardano-chain-follower/src/metadata/cip509/x509_chunks.rs deleted file mode 100644 index a666e03c4..000000000 --- a/rust/cardano-chain-follower/src/metadata/cip509/x509_chunks.rs +++ /dev/null @@ -1,122 +0,0 @@ -//! X509 chunks handler where compressed chunks are decompressed and decoded. - -use std::io::Read; - -use minicbor::{decode, Decode, Decoder}; -use strum::FromRepr; - -use super::{decode_helper::decode_helper, rbac::Cip509RbacMetadata}; -use crate::metadata::cip509::decode_helper::{decode_array_len, decode_bytes}; - -/// Enum of compression algorithms used to compress chunks. -#[derive(FromRepr, Debug, PartialEq, Clone, Default)] -#[repr(u8)] -pub enum CompressionAlgorithm { - /// Raw data, no compression. - #[default] - Raw = 10, - /// Brotli compression. - Brotli = 11, - /// Zstd compression. - Zstd = 12, -} - -/// Struct of x509 chunks. -#[derive(Debug, PartialEq, Clone, Default)] -pub struct X509Chunks(pub Cip509RbacMetadata); - -#[allow(dead_code)] -impl X509Chunks { - /// Create new instance of `X509Chunks`. - fn new(chunk_data: Cip509RbacMetadata) -> Self { - Self(chunk_data) - } -} - -impl Decode<'_, ()> for X509Chunks { - fn decode(d: &mut Decoder, ctx: &mut ()) -> Result { - // Determine the algorithm - let algo: u8 = decode_helper(d, "algorithm in X509Chunks", ctx)?; - let algorithm = CompressionAlgorithm::from_repr(algo) - .ok_or(decode::Error::message("Invalid chunk data type"))?; - - // Decompress the data - let decompressed = decompress(d, &algorithm) - .map_err(|e| decode::Error::message(format!("Failed to decompress {e}")))?; - - // Decode the decompressed data. - let mut decoder = Decoder::new(&decompressed); - let chunk_data = Cip509RbacMetadata::decode(&mut decoder, &mut ()) - .map_err(|e| decode::Error::message(format!("Failed to decode {e}")))?; - - Ok(X509Chunks(chunk_data)) - } -} - -/// Decompress the data using the given algorithm. -fn decompress(d: &mut Decoder, algorithm: &CompressionAlgorithm) -> anyhow::Result> { - let chunk_len = decode_array_len(d, "decompression in X509Chunks")?; - // Vector containing the concatenated chunks - let mut concat_chunk = vec![]; - for _ in 0..chunk_len { - let chunk_data = decode_bytes(d, "decompression in X509Chunks")?; - concat_chunk.extend_from_slice(&chunk_data); - } - - let mut buffer = vec![]; - - match algorithm { - CompressionAlgorithm::Raw => { - buffer.extend_from_slice(concat_chunk.as_slice()); - }, - CompressionAlgorithm::Zstd => { - zstd::stream::copy_decode(concat_chunk.as_slice(), &mut buffer)?; - }, - CompressionAlgorithm::Brotli => { - let mut decoder = brotli::Decompressor::new(concat_chunk.as_slice(), 4096); - decoder - .read_to_end(&mut buffer) - .map_err(|_| anyhow::anyhow!("Failed to decompress using Brotli algorithm"))?; - }, - } - Ok(buffer) -} - -#[cfg(test)] -mod tests { - use super::*; - - // RAW data: 10 - const RAW: &str = "0a98195840a50a815904a8308204a43082038ca00302010202141fec832800975bc0ae18b21985e0a97418b5334e300d06092a864886f70d01010b0500307c310b300906035840550406130255533113301106035504080c0a43616c69666f726e69613116301406035504070c0d53616e204672616e636973636f31123010060355040a0c094d584079436f6d70616e7931153013060355040b0c0c4d794465706172746d656e743115301306035504030c0c6d79646f6d61696e2e636f6d301e170d32343038323558403132343132315a170d3235303832353132343132315a307c310b30090603550406130255533113301106035504080c0a43616c69666f726e696131163014060358405504070c0d53616e204672616e636973636f31123010060355040a0c094d79436f6d70616e7931153013060355040b0c0c4d794465706172746d656e7431153058401306035504030c0c6d79646f6d61696e2e636f6d30820122300d06092a864886f70d01010105000382010f003082010a0282010100ba58de3aa83b075a69ee9e584074fe8a0c06259f41a64628f4c17a55c36b736ca4c7d475f05b176a0ac8c11785d50efcd65dfb450a718debad42cb27f32134a0c4d10dbb6529b14dd6b0d20cc558406e31fb6d9fbe7c8bfd012180d9b495b56dd67dd582dff7bbeb77ee15fd83f7904e8b12ebf1db46e74deb264c1b844be9fcc0c9acbadaf2d9b7d927495b000a965840fe4c4fe82633d8cfbdf5fabe23e636a8026942a00d07ff0e8fd9284b1e210d44b396fdb874fd48733d9e18df67a957ef2f0ba6a6afc3d012a21a2d98604930a358404a66a90386aa68b8cca84962908e94676d3e59cf78cd383487d5f93a35fcec554b70c1f6a47dc02e6b7811d5eba9ba5afaf8295dd10203010001a382011c3082584001183081ca0603551d110481c23081bf820c6d79646f6d61696e2e636f6d82107777772e6d79646f6d61696e2e636f6d820b6578616d706c652e636f6d820f77584077772e6578616d706c652e636f6d867f7765622b63617264616e6f3a2f2f616464722f616464725f74657374317172346a723975757130686477677273797a6558403270647234766b363634376336397375796e6476367237357764333071776b6c70706d7a75326177326c376d676b7a78727a336378766d32307578687735703758407064346a383879707332307565766e300b0603551d0f0404030205e0301d0603551d250416301406082b0601050507030106082b06010505070302301d06035558401d0e04160414e9b7c7100d76bd43a9c1404443c443d4b739caaf300d06092a864886f70d01010b050003820101008a86e00103f9873787e4a1d592ec3ce07819584037f3d7e302b0df83526e7c8170cdd47828e8e7a8c27b8a249b53acbce9aa4bbd9b81034e231f994cf6f74c8f6c15d44918dfb46e6c17a82d0fdd8f97aade5c6058404f4125db3fea5dc1acaa5f4c185109c947a35621b350c22fd4fb24a3931255a49d07a2cda5d422cfbded702cb613bb48871bc9d520a38c75482bea7682a13f7c5840332f808af64dcb89c6e04c8189ddb46ebd01c387dad899688740bdce89d472743d7c1150e5f197d616413561bdf511fdde30494fac8b033a61265916965462905840090615236a89af4e7e1a46285a838d823177c0734ce4442522df2de6967899b08d32610967ff132ad2347db86d42148158e10248722b0278f30067890c470101584023456789ab1a6703a2a0f647010123456789ab0a5820b45b8295e2701d2dbc8d4093fae94b979735f8c5e17a1843cf64a298e86659a2820201038206785377655840622b63617264616e6f3a2f2f616464722f7374616b655f7465737431757165686b636b306c616a713867723238743975786e757667637172633630373078336b58403972383034387a3879356773737274766e584004441e56b190be1238456c493c5ac1adbcaad398ba28e82dd4d63b250ff3436fc3a2f2c9f243992102beefaec45840d750bf51f84025c7fb4b8431dc77a7c2d0a907181e81d9800558203b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da2918288250eb5840f47281cf3c0a42fcceda894625ec34508cb997d509e0937f945813355e26a700186481a5000001820a000250ebf47281cf3c0a42fcceda894625ec3403200a644454657374"; - // Brotli data: 11 - const BROTLI: &str = "0b9458401b0306006678baf9873001af26aafd1674e5b11bf549d4bea0a39bfa0324c494f7003a6b4c1c4bac378e322cb280230a4002f5b2754e863806f7e524afc9995658406829cb08ab014e580e707468318ac0086262ffa6d3039a557178a3c556cbee076b44163b02b2812e49d3ab27b5e7bb2e0c6b1321d042d1069a24b4884032468a58400a28c6c0b0af3474b476028a193051b0641d5d778162040c24d7d2d1cc925f3d504c8171e5b4757466bc5104a11147eeb110f17d6067ae4b0d04a1d4200a3590584042a59443fca0277bc8e0b053c8101ca338ac0fe93e702d048761686fe9bdf0d511e472c6bb85a27f033a24d745712b933dbe1c51141d6309d9cb4fa9c41f2acc5840995aa78f9877abf5fe4c55fd4ed2120cbfde107fcefdb363e0e2e35774f7357a6ec99ada7c55e70497f29bb3e860cbe05fd8b1e3e6f6193b3853ad6afcfef77d5840afa5ef4cff767e1fcb1e347afdf176f2f3acd76e99d65d192fff1c3eb37eefad4f3777dd744fab80b466fecbcc79e11670e3d281afbf0e3a3f0b5e8d30e217eb584092ffeb8ddef4c8b073d44ddc36f3ef6ed1df5461d4428bfbf43525effdb557aedc74ecb2d1522bdfd9b5696059fab43568cfdaa6dde757a7d58f8d4ca773a2cb58402ec92e8406f6aa7f8607fd795394c13ff26d79eb613f96cc50fd7acddef25f3f3cabae20280cc1cb70d806e0b005509e25a145b68604e551a03c844733c00db25840c212b5b5d3900818fa2673b0d1d32e6dacf77e8a1b755c5eb8bf7f1d8d864d8c9a0c706c8a000b64626162b10034d1a4744c28573452f9342c50c20a0e0e0c695840080e138ae55c9a24180b0992d2028040ca62f3f91c85985a27a5b243387496428629021a64120e1588654dd2207e089f16c80c0d95f3852d69947081f679dafa584004028a101f005b207025906daae14d828944320a0f0b31417a17114c5eee3a65a02b3990b0e6486c62c2f104d5aeb0b39b5844b5df2e0c0df43c80d19fbd21bd58404f96a827de443e9059867cbefe18d97cbf339fdba2e45f50c93c5e3c5f7db479c0655ec1fafd2fd7661c98a744b39dede7647efb9e39ca3655a559dcdfce659b5840aff6d5bf3b3a6bedbdcada9c38d7db31afaa8eac5f5b936991a779266559b1e3b6dca3feaadf2ecb268d8a962f202fbdb042e574e9c05bbecf4ee37da9bdd6675840d40ecb86c4a9deaf24f892989600ff8e816f59e7fa4f3ec854f6dfddce3d001febbd75634e536fec818bfd2a4c14d56298fbf4e3ac29b3b8a0ba035f0dffde0358406939eb07d1f03ab732b39985f5639a24536766ffa6ec36ab648ff2ce619c223d2ccc7c92e8ea74dff7d94cd99ccdc3d43a4dfa7f63afab81adbb39f126cad2875840482ae68dc83e43f47e9d1418764ea2f7afb3a2a34b177f43ee4cabd4617b053ee311dfd677ff70ece4af9719b36605fd38f15061917089b674f68b69654b71045840467192ac80318630c94ae56f2c163436b11a58805dc71484d2316aa8284c2ce38a25f40601d6100c42802c8015868582c05045a83c882e14622209b734969068584057bc65eca05168123b2db2fcc886fd6bafcddeebf1c2573515e1aaff3981776ce9a7339f12e6382207df6f3c7e3df750de8f58d753bf33ba2877a4ab8e5e5e435840b6b053deec20963a4430ddf75fdcb9dcb77ed9eacb5e3c5d6123d5546afb286165f8e1d4258365b73c2d3cf0dcd75f30e5a548adf83f176ff527bbbe09cc1dda5820334badf960b27d7aa97150b5db2ac882a65c014130ae05210b0075d0a2fd8c05"; - // Zstd data: 12 - const ZSTD: &str= "0c95584028b52ffd601605ad2800b44ca50a815904a8308204a43082038ca00302010202141fec832800975bc0ae18b21985e0a97418b5334e300d06092a864886f70d015840010b0500307c310b300906035504061302555331133011080c0a43616c69666f726e696131163014070c0d53616e204672616e636973636f311230100a0c094d584079436f6d70616e79311530130b0c0c4d794465706172746d656e74030c0c6d79646f6d61696e2e636f6d301e170d3234303832353132343132315a170d323582584001220105000382010f003082010a0282010100ba58de3aa83b075a69ee9e74fe8a0c06259f41a64628f4c17a55c36b736ca4c7d475f05b176a0ac8c11785d50e5840fcd65dfb450a718debad42cb27f32134a0c4d10dbb6529b14dd6b0d20cc56e31fb6d9fbe7c8bfd012180d9b495b56dd67dd582dff7bbeb77ee15fd83f7904e8b584012ebf1db46e74deb264c1b844be9fcc0c9acbadaf2d9b7d927495b000a96fe4c4fe82633d8cfbdf5fabe23e636a8026942a00d07ff0e8fd9284b1e210d44b3965840fdb874fd48733d9e18df67a957ef2f0ba6a6afc3d012a21a2d98604930a34a66a90386aa68b8cca84962908e94676d3e59cf78cd383487d5f93a35fcec554b705840c1f6a47dc02e6b7811d5eba9ba5afaf8295dd10203010001a382011c308201183081ca0603551d110481c23081bf8282107777772e0b6578616d706c65820f8658407f7765622b63617264616e6f3a2f2f616464725f74657374317172346a723975757130686477677273797a653270647234766b363634376336397375796e64765840367237357764333071776b6c70706d7a75326177326c376d676b7a78727a336378766d3230757868773570377064346a383879707332307565766e300b06035558401d0f0404030205e0301d2504082b06010505070301020e04160414e9b7c7100d76bd43a9c1404443c443d4b739caaf0b01008a86e00103f9873787e4a1d592ec58403ce0781937f3d7e302b0df83526e7c8170cdd47828e8e7a8c27b8a249b53acbce9aa4bbd9b81034e231f994cf6f74c8f6c15d44918dfb46e6c17a82d0fdd8f975840aade5c604f4125db3fea5dc1acaa5f4c185109c947a35621b350c22fd4fb24a3931255a49d07a2cda5d422cfbded702cb613bb48871bc9d520a38c75482bea76584082a13f7c332f808af64dcb89c6e04c8189ddb46ebd01c387dad899688740bdce89d472743d7c1150e5f197d616413561bdf511fdde30494fac8b033a61265916584096546290090615236a89af4e7e1a46285a838d823177c0734ce4442522df2de6967899b08d32610967ff132ad2347db86d42148158e10248722b0278f300678958400c47010123456789ab1a6703a2a0f60a5820b45b8295e2701d2dbc8d4093fae94b979735f8c5e17a1843cf64a298e86659a282020103820678537374616b657558407165686b636b306c616a713867723238743975786e757667637172633630373078336b3972383034387a3879356773737274766e584004441e56b190be12384558406c493c5ac1adbcaad398ba28e82dd4d63b250ff3436fc3a2f2c9f243992102beefaec4d750bf51f84025c7fb4b8431dc77a7c2d0a907181e81d9800558203b6a584027bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da2918288250ebf47281cf3c0a42fcceda894625ec34508cb997d509e0937f945813355e584026a700186481a5000001820a000203200a6454657374001900b8f93ca19a5286c0f46288547ec4a40117ab88e058a8215aea28148137cc3850eed1b425543405581f22883507dcb24ec794216e78e8254099f1d1f9d5cd0576ee8d73c102eb8c02"; - - #[test] - fn test_decode_x509_chunks_raw() { - let raw_bytes = hex::decode(RAW).unwrap(); - let mut decoder = Decoder::new(raw_bytes.as_slice()); - let x509_chunks = X509Chunks::decode(&mut decoder, &mut ()); - // Decode the decompressed data should success. - assert!(x509_chunks.is_ok()); - } - - #[test] - fn test_decode_x509_chunks_brotli() { - let brotli_bytes = hex::decode(BROTLI).unwrap(); - let mut decoder = Decoder::new(brotli_bytes.as_slice()); - let x509_chunks = X509Chunks::decode(&mut decoder, &mut ()); - // Decode the decompressed data should success. - assert!(x509_chunks.is_ok()); - } - - #[test] - fn test_decode_x509_chunks_zstd() { - let zstd_bytes = hex::decode(ZSTD).unwrap(); - let mut decoder = Decoder::new(zstd_bytes.as_slice()); - let x509_chunks = X509Chunks::decode(&mut decoder, &mut ()); - // Decode the decompressed data should success. - assert!(x509_chunks.is_ok()); - } -} diff --git a/rust/cardano-chain-follower/src/metadata/mod.rs b/rust/cardano-chain-follower/src/metadata/mod.rs index 1f624f398..1ed77c300 100644 --- a/rust/cardano-chain-follower/src/metadata/mod.rs +++ b/rust/cardano-chain-follower/src/metadata/mod.rs @@ -49,14 +49,12 @@ pub(crate) struct DecodedMetadata(DashMap>); impl DecodedMetadata { /// Create new decoded metadata for a transaction. - fn new( - chain: Network, slot: u64, txn: &MultiEraTx, raw_aux_data: &RawAuxData, txn_idx: usize, - ) -> Self { + fn new(chain: Network, slot: u64, txn: &MultiEraTx, raw_aux_data: &RawAuxData) -> Self { let decoded_metadata = Self(DashMap::new()); // Process each known type of metadata here, and record the decoded result. Cip36::decode_and_validate(&decoded_metadata, slot, txn, raw_aux_data, true, chain); - Cip509::decode_and_validate(&decoded_metadata, txn, raw_aux_data, txn_idx); + Cip509::decode_and_validate(&decoded_metadata, txn, raw_aux_data); // if !decoded_metadata.0.is_empty() { // debug!("Decoded Metadata final: {decoded_metadata:?}"); @@ -108,7 +106,7 @@ impl DecodedTransaction { }; let txn_raw_aux_data = RawAuxData::new(cbor_data); - let txn_metadata = DecodedMetadata::new(chain, slot, txn, &txn_raw_aux_data, txn_idx); + let txn_metadata = DecodedMetadata::new(chain, slot, txn, &txn_raw_aux_data); self.raw.insert(txn_idx, txn_raw_aux_data); self.decoded.insert(txn_idx, txn_metadata); diff --git a/rust/cardano-chain-follower/src/utils.rs b/rust/cardano-chain-follower/src/utils.rs index a8e6325b5..cc8d43647 100644 --- a/rust/cardano-chain-follower/src/utils.rs +++ b/rust/cardano-chain-follower/src/utils.rs @@ -93,22 +93,10 @@ pub(crate) fn blake2b_256(value: &[u8]) -> anyhow::Result<[u8; 32]> { } /// Convert the given value to `blake2b_128` array. +#[allow(dead_code)] // Its OK if we don't use this general utility function. pub(crate) fn blake2b_128(value: &[u8]) -> anyhow::Result<[u8; 16]> { let h = Params::new().hash_length(16).hash(value); let b = h.as_bytes(); b.try_into() .map_err(|_| anyhow::anyhow!("Invalid length of blake2b_128, expected 16 got {}", b.len())) } - -/// Decode the given UTF-8 content. -pub(crate) fn decode_utf8(content: &[u8]) -> anyhow::Result { - // Decode the UTF-8 string - std::str::from_utf8(content) - .map(std::string::ToString::to_string) - .map_err(|_| { - anyhow::anyhow!( - "Invalid UTF-8 string, expected valid UTF-8 string but got {:?}", - content - ) - }) -}