From 72ff86cf7abb8cc411c1b595186c4873ce7a8b46 Mon Sep 17 00:00:00 2001 From: Stanislav Tkach Date: Fri, 14 Feb 2025 01:48:39 +0100 Subject: [PATCH 1/5] Use new types in rbac-registration --- rust/rbac-registration/Cargo.toml | 6 +- .../src/cardano/cip509/cip509.rs | 37 ++++++--- .../src/cardano/cip509/validation.rs | 80 +++++++++++++++++-- .../src/registration/cardano/mod.rs | 7 +- rust/rbac-registration/src/utils/test.rs | 8 +- 5 files changed, 108 insertions(+), 30 deletions(-) diff --git a/rust/rbac-registration/Cargo.toml b/rust/rbac-registration/Cargo.toml index 7140aa3e2a..c818427c5f 100644 --- a/rust/rbac-registration/Cargo.toml +++ b/rust/rbac-registration/Cargo.toml @@ -32,6 +32,6 @@ uuid = "1.11.0" c509-certificate = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "v0.0.3" } pallas = { version = "0.30.1", git = "https://github.com/input-output-hk/catalyst-pallas.git", rev = "9b5183c8b90b90fe2cc319d986e933e9518957b3" } -cbork-utils = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250127-00" } -cardano-blockchain-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250127-00" } -catalyst-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250127-00" } +cbork-utils = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250212-00" } +cardano-blockchain-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250214-00" } +catalyst-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250212-00" } diff --git a/rust/rbac-registration/src/cardano/cip509/cip509.rs b/rust/rbac-registration/src/cardano/cip509/cip509.rs index 086ca39ea3..b04ec7925e 100644 --- a/rust/rbac-registration/src/cardano/cip509/cip509.rs +++ b/rust/rbac-registration/src/cardano/cip509/cip509.rs @@ -5,10 +5,11 @@ use std::{borrow::Cow, collections::HashMap}; use anyhow::{anyhow, Context}; -use cardano_blockchain_types::{MetadatumLabel, MultiEraBlock, TxnIndex}; +use cardano_blockchain_types::{MetadatumLabel, MultiEraBlock, TransactionHash, TxnIndex}; use catalyst_types::{ cbor_utils::{report_duplicated_key, report_missing_keys}, hashes::{Blake2b256Hash, BLAKE_2B256_SIZE}, + id_uri::IdUri, problem_report::ProblemReport, uuid::UuidV4, }; @@ -59,7 +60,7 @@ pub struct Cip509 { /// An optional hash of the previous transaction. /// /// The hash must always be present except for the first registration transaction. - prv_tx_id: Option, + prv_tx_id: Option, /// Metadata. /// /// This field encoded in chunks. See [`X509Chunks`] for more details. @@ -72,10 +73,14 @@ pub struct Cip509 { /// constructors. payment_history: PaymentHistory, /// A hash of the transaction from which this registration is extracted. - txn_hash: Blake2b256Hash, + txn_hash: TransactionHash, /// A point (slot) and a transaction index identifying the block and the transaction /// that this `Cip509` was extracted from. origin: PointTxnIdx, + /// A catalyst ID. + /// + /// This field is only present in role 0 registrations. + catalyst_id: Option, /// A report potentially containing all the issues occurred during `Cip509` decoding /// and validation. /// @@ -139,7 +144,7 @@ impl Cip509 { payment_history, report: &mut report, }; - let cip509 = + let mut cip509 = Cip509::decode(&mut decoder, &mut decode_context).context("Failed to decode Cip509")?; // Perform the validation. @@ -156,7 +161,7 @@ impl Cip509 { validate_stake_public_key(txn, cip509.certificate_uris(), &cip509.report); } if let Some(metadata) = &cip509.metadata { - validate_role_data(metadata, &cip509.report); + cip509.catalyst_id = validate_role_data(metadata, &cip509.report); } Ok(Some(cip509)) @@ -210,7 +215,7 @@ impl Cip509 { /// Returns a hash of the previous transaction. #[must_use] - pub fn previous_transaction(&self) -> Option { + pub fn previous_transaction(&self) -> Option { self.prv_tx_id } @@ -228,7 +233,7 @@ impl Cip509 { /// Returns a hash of the transaction where this data is originating from. #[must_use] - pub fn txn_hash(&self) -> Blake2b256Hash { + pub fn txn_hash(&self) -> TransactionHash { self.txn_hash } @@ -244,6 +249,12 @@ impl Cip509 { self.txn_inputs_hash.as_ref() } + /// Returns a Catalyst ID of this registration if role 0 is present. + #[must_use] + pub fn catalyst_id(&self) -> Option<&IdUri> { + self.catalyst_id.as_ref() + } + /// Returns `Cip509` fields consuming the structure if it was successfully decoded and /// validated otherwise return the problem report that contains all the encountered /// issues. @@ -370,9 +381,10 @@ impl Decode<'_, DecodeContext<'_, '_>> for Cip509 { .missing_field("metadata (10, 11 or 12 chunks)", context); } - let txn_hash = MultiEraTx::Conway(Box::new(Cow::Borrowed(decode_context.txn))) - .hash() - .into(); + let txn_hash = Blake2b256Hash::from( + MultiEraTx::Conway(Box::new(Cow::Borrowed(decode_context.txn))).hash(), + ) + .into(); Ok(Self { purpose, txn_inputs_hash, @@ -382,6 +394,7 @@ impl Decode<'_, DecodeContext<'_, '_>> for Cip509 { payment_history: HashMap::new(), txn_hash, origin: decode_context.origin.clone(), + catalyst_id: None, report: decode_context.report.clone(), }) } @@ -501,7 +514,7 @@ fn decode_input_hash( /// Decodes previous transaction id. fn decode_previous_transaction_id( d: &mut Decoder, context: &str, report: &ProblemReport, -) -> Result, ()> { +) -> Result, ()> { let bytes = match decode_bytes(d, "Cip509 previous transaction id") { Ok(v) => v, Err(e) => { @@ -515,7 +528,7 @@ fn decode_previous_transaction_id( let len = bytes.len(); if let Ok(v) = Blake2b256Hash::try_from(bytes) { - Ok(Some(v)) + Ok(Some(v.into())) } else { report.invalid_value( "previous transaction hash", diff --git a/rust/rbac-registration/src/cardano/cip509/validation.rs b/rust/rbac-registration/src/cardano/cip509/validation.rs index aec646aba2..88cfa098f3 100644 --- a/rust/rbac-registration/src/cardano/cip509/validation.rs +++ b/rust/rbac-registration/src/cardano/cip509/validation.rs @@ -6,11 +6,14 @@ use std::borrow::Cow; +use c509_certificate::c509::C509; use cardano_blockchain_types::{TxnWitness, VKeyHash}; use catalyst_types::{ hashes::{Blake2b128Hash, Blake2b256Hash}, + id_uri::IdUri, problem_report::ProblemReport, }; +use ed25519_dalek::{VerifyingKey, PUBLIC_KEY_LENGTH}; use pallas::{ codec::{ minicbor::{Encode, Encoder}, @@ -18,6 +21,7 @@ use pallas::{ }, ledger::{addresses::Address, primitives::conway, traverse::MultiEraTx}, }; +use x509_cert::Certificate; use super::utils::cip19::compare_key_hash; use crate::cardano::cip509::{ @@ -158,9 +162,9 @@ fn extract_stake_addresses(uris: Option<&Cip0134UriSet>) -> Vec { .collect() } -/// Checks that only role 0 uses certificates with zero index. +/// Checks the role data. #[allow(clippy::similar_names)] -pub fn validate_role_data(metadata: &Cip509RbacMetadata, report: &ProblemReport) { +pub fn validate_role_data(metadata: &Cip509RbacMetadata, report: &ProblemReport) -> Option { let context = "Role data validation"; if metadata.role_data.contains_key(&RoleNumber::ROLE_0) { @@ -226,9 +230,10 @@ pub fn validate_role_data(metadata: &Cip509RbacMetadata, report: &ProblemReport) ); } + let mut catalyst_id = None; for (number, data) in &metadata.role_data { if number == &RoleNumber::ROLE_0 { - validate_role_0(data, metadata, context, report); + catalyst_id = validate_role_0(data, metadata, context, report); } else { if let Some(signing_key) = data.signing_key() { if signing_key.key_offset == 0 { @@ -252,12 +257,13 @@ pub fn validate_role_data(metadata: &Cip509RbacMetadata, report: &ProblemReport) } } } + catalyst_id } /// Checks that the role 0 data is correct. fn validate_role_0( role: &RoleData, metadata: &Cip509RbacMetadata, context: &str, report: &ProblemReport, -) { +) -> Option { if let Some(key) = role.encryption_key() { report.invalid_value( "Role 0 encryption key", @@ -269,7 +275,7 @@ fn validate_role_0( let Some(signing_key) = role.signing_key() else { report.missing_field("(Role 0) RoleData::signing_key", context); - return; + return None; }; if signing_key.key_offset != 0 { @@ -277,14 +283,18 @@ fn validate_role_0( &format!("The role 0 must reference a certificate with 0 index ({role:?})"), context, ); - return; + return None; } + let mut catalyst_id = None; + let network = "cardano"; + match signing_key.local_ref { LocalRefInt::X509Certs => { match metadata.x509_certs.first() { - Some(X509DerCert::X509Cert(_)) => { + Some(X509DerCert::X509Cert(cert)) => { // All good: role 0 references a valid X509 certificate. + catalyst_id = x509_cert_key(cert, context, report).map(|k| IdUri::new(network, None, k)); } Some(c) => report.other(&format!("Invalid X509 certificate value ({c:?}) for role 0 ({role:?})"), context), None => report.other("Role 0 reference X509 certificate at index 0, but there is no such certificate", context), @@ -292,8 +302,9 @@ fn validate_role_0( }, LocalRefInt::C509Certs => { match metadata.c509_certs.first() { - Some(C509Cert::C509Certificate(_)) => { + Some(C509Cert::C509Certificate(cert)) => { // All good: role 0 references a valid C509 certificate. + catalyst_id = c509_cert_key(cert, context, report).map(|k| IdUri::new(network, None, k)); } Some(c) => report.other(&format!("Invalid C509 certificate value ({c:?}) for role 0 ({role:?})"), context), None => report.other("Role 0 reference C509 certificate at index 0, but there is no such certificate", context), @@ -308,6 +319,59 @@ fn validate_role_0( ); }, } + catalyst_id +} + +/// Extracts `VerifyingKey` from the given `X509` certificate. +fn x509_cert_key( + cert: &Certificate, context: &str, report: &ProblemReport, +) -> Option { + let Some(bytes) = cert + .tbs_certificate + .subject_public_key_info + .subject_public_key + .as_bytes() + else { + report.invalid_value( + "subject_public_key", + "is not octet aligned", + "Must not have unused bits", + context, + ); + return None; + }; + verifying_key(bytes, context, report) +} + +/// Extracts `VerifyingKey` from the given `C509` certificate. +fn c509_cert_key(cert: &C509, context: &str, report: &ProblemReport) -> Option { + verifying_key(cert.tbs_cert().subject_public_key(), context, report) +} + +/// Creates `VerifyingKey` from the given byte slice. +fn verifying_key(bytes: &[u8], context: &str, report: &ProblemReport) -> Option { + println!("FIXME: bytes len = {}", bytes.len()); + println!("FIXME: PUBLIC_KEY_LENGTH len = {PUBLIC_KEY_LENGTH}"); + let bytes: &[u8; PUBLIC_KEY_LENGTH] = match bytes.try_into() { + Ok(v) => v, + Err(e) => { + report.other( + &format!("Invalid public key length in X509 certificate: {e:?}"), + context, + ); + return None; + }, + }; + match VerifyingKey::from_bytes(bytes) { + Ok(k) => Some(k), + Err(e) => { + report.other( + &format!("Invalid public key in C509 certificate: {e:?}"), + context, + ); + None + }, + } } #[cfg(test)] diff --git a/rust/rbac-registration/src/registration/cardano/mod.rs b/rust/rbac-registration/src/registration/cardano/mod.rs index 8d562944a4..0ac95fb4a1 100644 --- a/rust/rbac-registration/src/registration/cardano/mod.rs +++ b/rust/rbac-registration/src/registration/cardano/mod.rs @@ -4,7 +4,8 @@ use std::{collections::HashMap, sync::Arc}; use anyhow::bail; use c509_certificate::c509::C509; -use catalyst_types::{hashes::Blake2b256Hash, uuid::UuidV4}; +use cardano_blockchain_types::TransactionHash; +use catalyst_types::uuid::UuidV4; use ed25519_dalek::VerifyingKey; use tracing::{error, warn}; use x509_cert::certificate::Certificate as X509Certificate; @@ -57,7 +58,7 @@ impl RegistrationChain { /// Get the current transaction ID hash. #[must_use] - pub fn current_tx_id_hash(&self) -> Blake2b256Hash { + pub fn current_tx_id_hash(&self) -> TransactionHash { self.inner.current_tx_id_hash } @@ -108,7 +109,7 @@ impl RegistrationChain { #[derive(Debug, Clone)] struct RegistrationChainInner { /// The current transaction ID hash (32 bytes) - current_tx_id_hash: Blake2b256Hash, + current_tx_id_hash: TransactionHash, /// List of purpose for this registration chain purpose: Vec, diff --git a/rust/rbac-registration/src/utils/test.rs b/rust/rbac-registration/src/utils/test.rs index 4d8b141da5..4a89c9ce94 100644 --- a/rust/rbac-registration/src/utils/test.rs +++ b/rust/rbac-registration/src/utils/test.rs @@ -2,8 +2,8 @@ // cspell: words stake_test1urs8t0ssa3w9wh90ld5tprp3gurxd487rth2qlqk6ernjqcef4ugr -use cardano_blockchain_types::{MultiEraBlock, Network, Point, Slot, TxnIndex}; -use catalyst_types::{hashes::Blake2b256Hash, uuid::UuidV4}; +use cardano_blockchain_types::{MultiEraBlock, Network, Point, Slot, TransactionHash, TxnIndex}; +use catalyst_types::uuid::UuidV4; use uuid::Uuid; use crate::cardano::cip509::{Cip509, RoleNumber}; @@ -20,9 +20,9 @@ pub struct BlockTestData { /// Transaction index. pub txn_index: TxnIndex, /// Transaction hash. - pub txn_hash: Blake2b256Hash, + pub txn_hash: TransactionHash, /// Previous hash. - pub prv_hash: Option, + pub prv_hash: Option, /// Purpose. pub purpose: UuidV4, /// Stake address. From 62f3742a40cd6e932d6a1bf76a0a59932145d3c1 Mon Sep 17 00:00:00 2001 From: Stanislav Tkach Date: Fri, 14 Feb 2025 11:23:20 +0100 Subject: [PATCH 2/5] Fix verifying key extraction --- .../src/cardano/cip509/validation.rs | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/rust/rbac-registration/src/cardano/cip509/validation.rs b/rust/rbac-registration/src/cardano/cip509/validation.rs index 88cfa098f3..4ad2516602 100644 --- a/rust/rbac-registration/src/cardano/cip509/validation.rs +++ b/rust/rbac-registration/src/cardano/cip509/validation.rs @@ -326,7 +326,7 @@ fn validate_role_0( fn x509_cert_key( cert: &Certificate, context: &str, report: &ProblemReport, ) -> Option { - let Some(bytes) = cert + let Some(extended_public_key) = cert .tbs_certificate .subject_public_key_info .subject_public_key @@ -340,7 +340,7 @@ fn x509_cert_key( ); return None; }; - verifying_key(bytes, context, report) + verifying_key(extended_public_key, context, report) } /// Extracts `VerifyingKey` from the given `C509` certificate. @@ -348,11 +348,28 @@ fn c509_cert_key(cert: &C509, context: &str, report: &ProblemReport) -> Option Option { - println!("FIXME: bytes len = {}", bytes.len()); - println!("FIXME: PUBLIC_KEY_LENGTH len = {PUBLIC_KEY_LENGTH}"); - let bytes: &[u8; PUBLIC_KEY_LENGTH] = match bytes.try_into() { +/// Creates `VerifyingKey` from the given extended public key. +fn verifying_key( + extended_public_key: &[u8], context: &str, report: &ProblemReport, +) -> Option { + const EXTENDED_PUBLIC_KEY_LENGTH: usize = 64; + + if extended_public_key.len() != EXTENDED_PUBLIC_KEY_LENGTH { + report.other( + &format!("Unexpected extended public key length in certificate: {}, expected {EXTENDED_PUBLIC_KEY_LENGTH}", + extended_public_key.len()), + context, + ); + return None; + } + + // This should never fail because of the check above. + let Some(public_key) = extended_public_key.get(0..PUBLIC_KEY_LENGTH) else { + report.other("Unable to get public key part", context); + return None; + }; + + let bytes: &[u8; PUBLIC_KEY_LENGTH] = match public_key.try_into() { Ok(v) => v, Err(e) => { report.other( From f881d0a921cfe66b38630abe49472a8ab6d8d91c Mon Sep 17 00:00:00 2001 From: Stanislav Tkach Date: Fri, 14 Feb 2025 11:48:21 +0100 Subject: [PATCH 3/5] Add a documentation --- rust/rbac-registration/src/cardano/cip509/validation.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/rbac-registration/src/cardano/cip509/validation.rs b/rust/rbac-registration/src/cardano/cip509/validation.rs index 4ad2516602..885e23f846 100644 --- a/rust/rbac-registration/src/cardano/cip509/validation.rs +++ b/rust/rbac-registration/src/cardano/cip509/validation.rs @@ -352,6 +352,7 @@ fn c509_cert_key(cert: &C509, context: &str, report: &ProblemReport) -> Option Option { + /// An extender public key length in bytes. const EXTENDED_PUBLIC_KEY_LENGTH: usize = 64; if extended_public_key.len() != EXTENDED_PUBLIC_KEY_LENGTH { From 1fe775f45ae102d59a3e91124b99f81a174e6209 Mon Sep 17 00:00:00 2001 From: Stanislav Tkach Date: Fri, 14 Feb 2025 19:47:23 +0100 Subject: [PATCH 4/5] Try to fix open-api errors --- rust/catalyst-types/src/id_uri/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rust/catalyst-types/src/id_uri/mod.rs b/rust/catalyst-types/src/id_uri/mod.rs index eb48425ebd..bf03a19e50 100644 --- a/rust/catalyst-types/src/id_uri/mod.rs +++ b/rust/catalyst-types/src/id_uri/mod.rs @@ -714,4 +714,14 @@ mod tests { let encoded_vk = base64_url::encode(vk.as_bytes()); assert_eq!(encoded_vk, "1234"); } + + #[test] + fn fixme_remove() { + // example: Some("cardano/FftxFnOrj2qmTuB2oZG2v0YEWJfKvQ9Gg8AgNAhDsKE".into()), + let id: IdUri = "cardano/FftxFnOrj2qmTuB2oZG2v0YEWJfKvQ9Gg8AgNAhDsKE" + .parse() + .unwrap(); + println!("{:?}", id.role0_pk.as_bytes()); + todo!(); + } } From 6c05706cf235844dd3e0bf4720e71d671bc3806e Mon Sep 17 00:00:00 2001 From: Stanislav Tkach Date: Sat, 15 Feb 2025 10:47:33 +0100 Subject: [PATCH 5/5] Remove wrong commit --- rust/catalyst-types/src/id_uri/mod.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/rust/catalyst-types/src/id_uri/mod.rs b/rust/catalyst-types/src/id_uri/mod.rs index bf03a19e50..eb48425ebd 100644 --- a/rust/catalyst-types/src/id_uri/mod.rs +++ b/rust/catalyst-types/src/id_uri/mod.rs @@ -714,14 +714,4 @@ mod tests { let encoded_vk = base64_url::encode(vk.as_bytes()); assert_eq!(encoded_vk, "1234"); } - - #[test] - fn fixme_remove() { - // example: Some("cardano/FftxFnOrj2qmTuB2oZG2v0YEWJfKvQ9Gg8AgNAhDsKE".into()), - let id: IdUri = "cardano/FftxFnOrj2qmTuB2oZG2v0YEWJfKvQ9Gg8AgNAhDsKE" - .parse() - .unwrap(); - println!("{:?}", id.role0_pk.as_bytes()); - todo!(); - } }