From ac4093cae04f23f5299db71c6317a32708a3c38a Mon Sep 17 00:00:00 2001 From: Apisit Ritruengroj <38898766+apskhem@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:07:55 +0700 Subject: [PATCH 01/16] feat(rust/rbac-registration): Add provider trait for `RegistrationChain` (#586) * initial * feat: provider * feat: update chain * feat: start_new_chain * feat: merge validation result struct into cip509 * feat: exports * feat: return new chain * chore: return success object * fix: older version * feat: cat id in payload * fix: new version * chore: lintfix * chore: remove persistent arguments * tmp * chore: complete moving to central module * feat: ref fn * chore: merge methods * chore: minor * feat: export modified chains * chore: minor comment * chore: rbac update logic * chore: isolation * chore: validation and lintfix * docs: remove error doc * chore: minor refactor * fix: comments * chore: validation function return * Update rust/rbac-registration/src/providers.rs Co-authored-by: Alex Pozhylenkov --------- Co-authored-by: Alex Pozhylenkov --- .../src/cardano/cip509/cip509.rs | 10 +- rust/rbac-registration/src/lib.rs | 1 + rust/rbac-registration/src/providers.rs | 47 +++ .../src/registration/cardano/mod.rs | 293 +++++++++++++++--- 4 files changed, 304 insertions(+), 47 deletions(-) create mode 100644 rust/rbac-registration/src/providers.rs diff --git a/rust/rbac-registration/src/cardano/cip509/cip509.rs b/rust/rbac-registration/src/cardano/cip509/cip509.rs index 75551ac5d1..18d21213ff 100644 --- a/rust/rbac-registration/src/cardano/cip509/cip509.rs +++ b/rust/rbac-registration/src/cardano/cip509/cip509.rs @@ -13,7 +13,7 @@ use cardano_blockchain_types::{ pallas_addresses::{Address, ShelleyAddress}, pallas_primitives::{conway, Nullable}, pallas_traverse::MultiEraTx, - MetadatumLabel, MultiEraBlock, TxnIndex, + MetadatumLabel, MultiEraBlock, StakeAddress, TxnIndex, }; use catalyst_types::{ catalyst_id::{role_index::RoleId, CatalystId}, @@ -305,6 +305,14 @@ impl Cip509 { self.metadata.as_ref() } + /// Returns a set of stake addresses. + #[must_use] + pub fn stake_addresses(&self) -> HashSet { + self.certificate_uris() + .map(Cip0134UriSet::stake_addresses) + .unwrap_or_default() + } + /// Returns `Cip509` fields consuming the structure if it was successfully decoded and /// validated otherwise return the problem report that contains all the encountered /// issues. diff --git a/rust/rbac-registration/src/lib.rs b/rust/rbac-registration/src/lib.rs index 6e35f51656..b140584362 100644 --- a/rust/rbac-registration/src/lib.rs +++ b/rust/rbac-registration/src/lib.rs @@ -1,6 +1,7 @@ //! This crate provides functionalities for RBAC registration. pub mod cardano; +pub mod providers; pub mod registration; mod utils; diff --git a/rust/rbac-registration/src/providers.rs b/rust/rbac-registration/src/providers.rs new file mode 100644 index 0000000000..271f7ccd75 --- /dev/null +++ b/rust/rbac-registration/src/providers.rs @@ -0,0 +1,47 @@ +//! Providers traits, which are used during different validation procedures. + +use std::future::Future; + +use cardano_blockchain_types::{hashes::TransactionId, StakeAddress}; +use catalyst_types::catalyst_id::CatalystId; +use ed25519_dalek::VerifyingKey; + +use crate::registration::cardano::RegistrationChain; + +/// `RegistrationChain` Provider trait +pub trait RbacRegistrationProvider { + /// Returns registration chain + /// for the given Catalyst ID. + fn chain( + &self, + id: CatalystId, + ) -> impl Future>> + Send; + + /// Returns `true` if a chain with the given Catalyst ID already exists. + /// + /// This function behaves in the same way as `latest_rbac_chain(...).is_some()` but + /// the implementation is more optimized because we don't need to build the whole + /// chain. + fn is_chain_known( + &self, + id: CatalystId, + ) -> impl Future> + Send; + + /// Returns a Catalyst ID corresponding to the given stake address. + fn catalyst_id_from_stake_address( + &self, + address: &StakeAddress, + ) -> impl Future>> + Send; + + /// Returns a Catalyst ID corresponding to the given public key. + fn catalyst_id_from_public_key( + &self, + key: VerifyingKey, + ) -> impl Future>> + Send; + + /// Returns a Catalyst ID corresponding to the given transaction hash. + fn catalyst_id_from_txn_id( + &self, + txn_id: TransactionId, + ) -> impl Future>> + Send; +} diff --git a/rust/rbac-registration/src/registration/cardano/mod.rs b/rust/rbac-registration/src/registration/cardano/mod.rs index 07d9bc7f77..4c9d1df172 100644 --- a/rust/rbac-registration/src/registration/cardano/mod.rs +++ b/rust/rbac-registration/src/registration/cardano/mod.rs @@ -22,9 +22,12 @@ use update_rbac::{ }; use x509_cert::certificate::Certificate as X509Certificate; -use crate::cardano::cip509::{ - CertKeyHash, CertOrPk, Cip0134UriSet, Cip509, PaymentHistory, PointData, RoleData, - RoleDataRecord, ValidationSignature, +use crate::{ + cardano::cip509::{ + CertKeyHash, CertOrPk, Cip0134UriSet, Cip509, PaymentHistory, PointData, RoleData, + RoleDataRecord, ValidationSignature, + }, + providers::RbacRegistrationProvider, }; /// Registration chains. @@ -42,13 +45,9 @@ impl RegistrationChain { /// /// # Arguments /// - `cip509` - The CIP509. - /// - /// # Errors - /// - /// Returns an error if data is invalid #[must_use] - pub fn new(cip509: Cip509) -> Option { - let inner = RegistrationChainInner::new(cip509)?; + pub fn new_stateless(cip509: Cip509) -> Option { + let inner = RegistrationChainInner::new_stateless(cip509)?; Some(Self { inner: Arc::new(inner), @@ -59,18 +58,14 @@ impl RegistrationChain { /// /// # Arguments /// - `cip509` - The CIP509. - /// - /// # Errors - /// - /// Returns an error if data is invalid #[must_use] - pub fn update( + pub fn update_stateless( &self, cip509: Cip509, ) -> Option { - let latest_signing_pk = self.get_latest_signing_pk_for_role(&RoleId::Role0); + let latest_signing_pk = self.get_latest_signing_pk_for_role(RoleId::Role0); let new_inner = if let Some((signing_pk, _)) = latest_signing_pk { - self.inner.update(cip509, signing_pk)? + self.inner.update_stateless(cip509, signing_pk)? } else { cip509.report().missing_field( "latest signing key for role 0", @@ -83,6 +78,29 @@ impl RegistrationChain { }) } + /// Creates or updates an RBAC registration chain from a CIP-509 registration. + /// + /// If the given registration references a previous transaction, it attempts + /// to update the existing chain using that previous transaction. + /// Otherwise, it starts a new chain from the provided registration. + pub async fn update( + &self, + reg: Cip509, + provider: &Provider, + ) -> Option + where + Provider: RbacRegistrationProvider, + { + let new_inner = if reg.previous_transaction().is_some() { + self.inner.update(reg, provider).await? + } else { + RegistrationChainInner::new(reg, provider).await? + }; + Some(Self { + inner: Arc::new(new_inner), + }) + } + /// Returns a Catalyst ID. #[must_use] pub fn catalyst_id(&self) -> &CatalystId { @@ -166,15 +184,9 @@ impl RegistrationChain { #[must_use] pub fn get_latest_signing_pk_for_role( &self, - role: &RoleId, + role: RoleId, ) -> Option<(VerifyingKey, KeyRotation)> { - self.inner.role_data_record.get(role).and_then(|rdr| { - rdr.signing_keys().last().and_then(|key| { - let rotation = KeyRotation::from_latest_rotation(rdr.signing_keys()); - - key.data().extract_pk().map(|pk| (pk, rotation)) - }) - }) + self.inner.get_latest_signing_pk_for_role(role) } /// Get the latest encryption public key for a role. @@ -182,15 +194,9 @@ impl RegistrationChain { #[must_use] pub fn get_latest_encryption_pk_for_role( &self, - role: &RoleId, + role: RoleId, ) -> Option<(VerifyingKey, KeyRotation)> { - self.inner.role_data_record.get(role).and_then(|rdr| { - rdr.encryption_keys().last().and_then(|key| { - let rotation = KeyRotation::from_latest_rotation(rdr.encryption_keys()); - - key.data().extract_pk().map(|pk| (pk, rotation)) - }) - }) + self.inner.get_latest_encryption_pk_for_role(role) } /// Get signing public key for a role with given rotation. @@ -298,12 +304,8 @@ impl RegistrationChainInner { /// /// # Arguments /// - `cip509` - The CIP509. - /// - /// # Errors - /// - /// Returns an error if data is invalid #[must_use] - fn new(cip509: Cip509) -> Option { + fn new_stateless(cip509: Cip509) -> Option { let context = "Registration Chain new"; // Should be chain root, return immediately if not if cip509.previous_transaction().is_some() { @@ -404,12 +406,8 @@ impl RegistrationChainInner { /// /// # Arguments /// - `cip509` - The CIP509. - /// - /// # Errors - /// - /// Returns an error if data is invalid #[must_use] - fn update( + fn update_stateless( &self, cip509: Cip509, signing_pk: VerifyingKey, @@ -490,6 +488,209 @@ impl RegistrationChainInner { Some(new_inner) } + + /// Attempts to initialize a new RBAC registration chain + /// from a given CIP-509 registration, ensuring uniqueness of Catalyst ID, stake + /// addresses, and associated public keys. + pub async fn new( + reg: Cip509, + provider: &Provider, + ) -> Option + where + Provider: RbacRegistrationProvider, + { + let report = reg.report().to_owned(); + + // Try to start a new chain. + let new_chain = Self::new_stateless(reg)?; + // Verify that a Catalyst ID of this chain is unique. + let catalyst_id = new_chain.catalyst_id.as_short_id(); + if provider.is_chain_known(catalyst_id.clone()).await.ok()? { + report.functional_validation( + &format!("{catalyst_id} is already used"), + "It isn't allowed to use same Catalyst ID (certificate subject public key) in multiple registration chains", + ); + return None; + } + + // Validate stake addresses. + let new_addresses = new_chain.certificate_uris.stake_addresses(); + let mut updated_chains: HashMap<_, HashSet> = HashMap::new(); + for address in &new_addresses { + if let Some(id) = provider + .catalyst_id_from_stake_address(address) + .await + .ok()? + { + // If an address is used in existing chain then a new chain must have different role + // 0 signing key. + let previous_chain = provider.chain(id.clone()).await.ok()??; + if previous_chain.get_latest_signing_pk_for_role(RoleId::Role0) + == new_chain.get_latest_signing_pk_for_role(RoleId::Role0) + { + report.functional_validation( + &format!("A new registration ({catalyst_id}) uses the same public key as the previous one ({})", + previous_chain.catalyst_id().as_short_id() + ), + "It is only allowed to override the existing chain by using different public key", + ); + } else { + // The new root registration "takes" an address(es) from the existing chain, so + // that chain needs to be updated. + updated_chains + .entry(id) + .and_modify(|e| { + e.insert(address.clone()); + }) + .or_insert([address.clone()].into_iter().collect()); + } + } + } + + // Check that new public keys aren't used by other chains. + new_chain + .validate_public_keys(&report, provider) + .await + .ok()?; + + if report.is_problematic() { + return None; + } + + Some(new_chain) + } + + /// Attempts to update an existing RBAC registration chain + /// with a new CIP-509 registration, validating address and key usage consistency. + pub async fn update( + &self, + reg: Cip509, + provider: &Provider, + ) -> Option + where + Provider: RbacRegistrationProvider, + { + let previous_txn = reg.previous_transaction()?; + let report = reg.report().to_owned(); + + // Find a chain this registration belongs to. + let Some(catalyst_id) = provider.catalyst_id_from_txn_id(previous_txn).await.ok()? else { + // We are unable to determine a Catalyst ID, so there is no sense to update the problem + // report because we would be unable to store this registration anyway. + return None; + }; + let chain = provider.chain(catalyst_id.clone()).await.ok()??; + + // Check that addresses from the new registration aren't used in other chains. + let previous_addresses = chain.stake_addresses(); + let reg_addresses = reg.stake_addresses(); + let new_addresses: Vec<_> = reg_addresses.difference(&previous_addresses).collect(); + for address in &new_addresses { + match provider + .catalyst_id_from_stake_address(address) + .await + .ok()? + { + None => { + // All good: the address wasn't used before. + }, + Some(_) => { + report.functional_validation( + &format!("{address} stake addresses is already used"), + "It isn't allowed to use same stake address in multiple registration chains", + ); + }, + } + } + + // Try to add a new registration to the chain. + let (signing_pk, _) = self.get_latest_signing_pk_for_role(RoleId::Role0)?; + let new_chain = chain.inner.update_stateless(reg.clone(), signing_pk)?; + + // Check that new public keys aren't used by other chains. + new_chain + .validate_public_keys(&report, provider) + .await + .ok()?; + + // Return an error if any issues were recorded in the report. + if report.is_problematic() { + return None; + } + + Some(new_chain) + } + + /// Validates that none of the signing keys in a given RBAC registration chain + /// have been used by any other existing chain, ensuring global key uniqueness + /// across all Catalyst registrations. + /// + /// # Returns + /// Returns `Ok(true)` if all signing keys are unique and validation passes + /// successfully. Returns `Ok(false)` if any key conflict is detected, with the + /// issue recorded in the provided [`ProblemReport`]. + /// + /// # Errors + /// - Propagates any I/O or provider-level errors encountered while checking key + /// ownership (e.g., database lookup failures). + async fn validate_public_keys( + &self, + report: &ProblemReport, + provider: &Provider, + ) -> anyhow::Result<()> + where + Provider: RbacRegistrationProvider, + { + let roles: Vec<_> = self.role_data_history.keys().collect(); + let catalyst_id = self.catalyst_id.as_short_id(); + + for role in roles { + if let Some((key, _)) = self.get_latest_signing_pk_for_role(*role) { + if let Some(previous) = provider.catalyst_id_from_public_key(key).await? { + if previous != catalyst_id { + report.functional_validation( + &format!("An update to {catalyst_id} registration chain uses the same public key ({key:?}) as {previous} chain"), + "It isn't allowed to use role 0 signing (certificate subject public) key in different chains", + ); + } + } + } + } + + Ok(()) + } + + /// Get the latest signing public key for a role. + /// Returns the public key and the rotation,`None` if not found. + #[must_use] + pub fn get_latest_signing_pk_for_role( + &self, + role: RoleId, + ) -> Option<(VerifyingKey, KeyRotation)> { + self.role_data_record.get(&role).and_then(|rdr| { + rdr.signing_keys().last().and_then(|key| { + let rotation = KeyRotation::from_latest_rotation(rdr.signing_keys()); + + key.data().extract_pk().map(|pk| (pk, rotation)) + }) + }) + } + + /// Get the latest encryption public key for a role. + /// Returns the public key and the rotation, `None` if not found. + #[must_use] + pub fn get_latest_encryption_pk_for_role( + &self, + role: RoleId, + ) -> Option<(VerifyingKey, KeyRotation)> { + self.role_data_record.get(&role).and_then(|rdr| { + rdr.encryption_keys().last().and_then(|key| { + let rotation = KeyRotation::from_latest_rotation(rdr.encryption_keys()); + + key.data().extract_pk().map(|pk| (pk, rotation)) + }) + }) + } } /// Perform a check on the validation signature. @@ -549,7 +750,7 @@ mod test { data.assert_valid(®istration); // Create a chain with the first registration. - let chain = RegistrationChain::new(registration).unwrap(); + let chain = RegistrationChain::new_stateless(registration).unwrap(); assert_eq!(chain.purpose(), &[data.purpose]); assert_eq!(1, chain.x509_certs().len()); let origin = &chain.x509_certs().get(&0).unwrap().first().unwrap(); @@ -576,7 +777,7 @@ mod test { assert!(registration.report().is_problematic()); let report = registration.report().to_owned(); - assert!(chain.update(registration).is_none()); + assert!(chain.update_stateless(registration).is_none()); let report = format!("{report:?}"); assert!( report.contains("kind: InvalidValue { field: \"previous transaction ID\""), @@ -590,7 +791,7 @@ mod test { .unwrap() .unwrap(); data.assert_valid(®istration); - let update = chain.update(registration).unwrap(); + let update = chain.update_stateless(registration).unwrap(); // Current tx hash should be equal to the hash from block 4. assert_eq!(update.current_tx_id_hash(), data.txn_hash); assert!(update.role_data_record().contains_key(&data.role)); @@ -614,7 +815,7 @@ mod test { assert_eq!(role_0_data.extended_data().len(), 2); let (_k, r) = update - .get_latest_signing_pk_for_role(&RoleId::Role0) + .get_latest_signing_pk_for_role(RoleId::Role0) .unwrap(); assert_eq!(r, KeyRotation::from(1)); assert!(update From 5c0b665c0c6889719081c06121446809ae152028 Mon Sep 17 00:00:00 2001 From: Alex Pozhylenkov Date: Wed, 19 Nov 2025 19:51:07 +0700 Subject: [PATCH 02/16] feat(rust/rbac-registration): RBAC stolen `StakeAddress` handling. (#631) * wip * wip * wip * wip * revert * wip * add stolen_uris field * wip * wip * wip * fix * wip * wip * fix spelling * fix * wip * wip * wip * wip * cleanup * fix test * fix clippy * wip * wip * fix * wip * fix clippy * fix * wip * wip --- rust/rbac-registration/Cargo.toml | 2 +- .../src/cardano/cip509/cip509.rs | 96 ++-- .../cardano/cip509/utils/cip134_uri_set.rs | 107 +++- .../src/cardano/cip509/validation.rs | 18 +- rust/rbac-registration/src/cardano/mod.rs | 1 + rust/rbac-registration/src/cardano/state.rs | 45 ++ rust/rbac-registration/src/lib.rs | 1 - rust/rbac-registration/src/providers.rs | 47 -- .../src/registration/cardano/mod.rs | 484 +++++++++--------- rust/rbac-registration/src/utils/test.rs | 3 +- 10 files changed, 442 insertions(+), 362 deletions(-) create mode 100644 rust/rbac-registration/src/cardano/state.rs delete mode 100644 rust/rbac-registration/src/providers.rs diff --git a/rust/rbac-registration/Cargo.toml b/rust/rbac-registration/Cargo.toml index c238f89a4f..33b0464035 100644 --- a/rust/rbac-registration/Cargo.toml +++ b/rust/rbac-registration/Cargo.toml @@ -34,5 +34,5 @@ thiserror = "2.0.11" c509-certificate = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "c509-certificate-v0.0.3" } cbork-utils = { version = "0.0.2", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "cbork-utils-v0.0.2" } -cardano-blockchain-types = { version = "0.0.8", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "cardano-blockchain-types/v0.0.8" } +cardano-blockchain-types = { version = "0.0.9", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "cardano-blockchain-types/v0.0.9" } catalyst-types = { version = "0.0.10", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "catalyst-types/v0.0.10" } diff --git a/rust/rbac-registration/src/cardano/cip509/cip509.rs b/rust/rbac-registration/src/cardano/cip509/cip509.rs index 18d21213ff..f85defa526 100644 --- a/rust/rbac-registration/src/cardano/cip509/cip509.rs +++ b/rust/rbac-registration/src/cardano/cip509/cip509.rs @@ -22,6 +22,7 @@ use catalyst_types::{ uuid::UuidV4, }; use cbork_utils::decode_helper::{decode_bytes, decode_helper, decode_map_len}; +use ed25519_dalek::VerifyingKey; use minicbor::{ decode::{self}, Decode, Decoder, @@ -32,6 +33,7 @@ use uuid::Uuid; use crate::cardano::cip509::{ decode_context::DecodeContext, + extract_key, rbac::Cip509RbacMetadata, types::{PaymentHistory, TxInputHash, ValidationSignature}, utils::Cip0134UriSet, @@ -40,7 +42,7 @@ use crate::cardano::cip509::{ validate_txn_inputs_hash, }, x509_chunks::X509Chunks, - Payment, PointTxnIdx, RoleData, + C509Cert, LocalRefInt, Payment, PointTxnIdx, RoleData, SimplePublicKeyType, X509DerCert, }; /// A x509 metadata envelope. @@ -80,7 +82,8 @@ pub struct Cip509 { origin: PointTxnIdx, /// A catalyst ID. /// - /// This field is only present in role 0 registrations. + /// This field is only present in role 0 registrations and only for the first + /// registration, which defines a `CatalystId` for the chain. catalyst_id: Option, /// Raw aux data associated with the transaction that CIP509 is attached to, raw_aux_data: Vec, @@ -180,6 +183,12 @@ impl Cip509 { validate_self_sign_cert(metadata, &report); } + // We want to keep `catalyst_id` field only for the first registration, + // which starts a new chain + if cip509.prv_tx_id.is_some() { + cip509.catalyst_id = None; + } + Ok(Some(cip509)) } @@ -227,6 +236,48 @@ impl Cip509 { self.metadata.as_ref().and_then(|m| m.role_data.get(&role)) } + /// Returns signing public key for a role. + /// Would return only signing public keys for the present certificates, + /// if certificate marked as deleted or undefined it would be skipped. + #[must_use] + pub fn signing_pk_for_role( + &self, + role: RoleId, + ) -> Option { + self.metadata.as_ref().and_then(|m| { + let key_ref = m.role_data.get(&role).and_then(|d| d.signing_key())?; + match key_ref.local_ref { + LocalRefInt::X509Certs => { + m.x509_certs.get(key_ref.key_offset).and_then(|c| { + if let X509DerCert::X509Cert(c) = c { + extract_key::x509_key(c).ok() + } else { + None + } + }) + }, + LocalRefInt::C509Certs => { + m.c509_certs.get(key_ref.key_offset).and_then(|c| { + if let C509Cert::C509Certificate(c) = c { + extract_key::c509_key(c).ok() + } else { + None + } + }) + }, + LocalRefInt::PubKeys => { + m.pub_keys.get(key_ref.key_offset).and_then(|c| { + if let SimplePublicKeyType::Ed25519(c) = c { + Some(*c) + } else { + None + } + }) + }, + } + }) + } + /// Returns a purpose of this registration. #[must_use] pub fn purpose(&self) -> Option { @@ -259,7 +310,7 @@ impl Cip509 { /// Returns URIs contained in both x509 and c509 certificates of `Cip509` metadata. #[must_use] - pub fn certificate_uris(&self) -> Option<&Cip0134UriSet> { + pub(crate) fn certificate_uris(&self) -> Option<&Cip0134UriSet> { self.metadata.as_ref().map(|m| &m.certificate_uris) } @@ -269,24 +320,13 @@ impl Cip509 { self.txn_inputs_hash.as_ref() } - /// Returns a Catalyst ID of this registration if role 0 is present. + /// Returns a Catalyst ID of this registration if role 0 is present and if its a first + /// registration, which defines a `CatalystId` for the chain. #[must_use] pub fn catalyst_id(&self) -> Option<&CatalystId> { self.catalyst_id.as_ref() } - /// Returns a list of addresses extracted from certificate URIs of a specific role. - #[must_use] - pub fn certificate_addresses( - &self, - role: usize, - ) -> HashSet
{ - self.metadata - .as_ref() - .map(|m| m.certificate_uris.role_addresses(role)) - .unwrap_or_default() - } - /// Return validation signature. #[must_use] pub fn validation_signature(&self) -> Option<&ValidationSignature> { @@ -313,26 +353,10 @@ impl Cip509 { .unwrap_or_default() } - /// Returns `Cip509` fields consuming the structure if it was successfully decoded and - /// validated otherwise return the problem report that contains all the encountered - /// issues. - /// - /// # Errors - /// - /// - `Err(ProblemReport)` - pub fn consume(self) -> Result<(UuidV4, Cip509RbacMetadata, PaymentHistory), ProblemReport> { - match ( - self.purpose, - self.txn_inputs_hash, - self.metadata, - self.validation_signature, - ) { - (Some(purpose), Some(_), Some(metadata), Some(_)) if !self.report.is_problematic() => { - Ok((purpose, metadata, self.payment_history)) - }, - - _ => Err(self.report), - } + /// Returns a payment history map. + #[must_use] + pub fn payment_history(&self) -> &PaymentHistory { + &self.payment_history } } diff --git a/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs b/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs index 44ba89af91..b71c04f6c3 100644 --- a/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs +++ b/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs @@ -39,67 +39,82 @@ struct Cip0134UriSetInner { x_uris: UrisMap, /// URIs from c509 certificates. c_uris: UrisMap, + /// `StakeAddress` which are taken by another chains. + taken_stake_addresses: HashSet, } impl Cip0134UriSet { /// Creates a new `Cip0134UriSet` instance from the given certificates. #[must_use] - pub fn new( + pub(crate) fn new( x509_certs: &[X509DerCert], c509_certs: &[C509Cert], report: &ProblemReport, ) -> Self { let x_uris = extract_x509_uris(x509_certs, report); let c_uris = extract_c509_uris(c509_certs, report); - Self(Arc::new(Cip0134UriSetInner { x_uris, c_uris })) + let taken_stake_addresses = HashSet::new(); + Self(Arc::new(Cip0134UriSetInner { + x_uris, + c_uris, + taken_stake_addresses, + })) } /// Returns a mapping from the x509 certificate index to URIs contained within. #[must_use] - pub fn x_uris(&self) -> &UrisMap { + pub(crate) fn x_uris(&self) -> &UrisMap { &self.0.x_uris } /// Returns a mapping from the c509 certificate index to URIs contained within. #[must_use] - pub fn c_uris(&self) -> &UrisMap { + pub(crate) fn c_uris(&self) -> &UrisMap { &self.0.c_uris } + /// Returns an iterator over of `Cip0134Uri`. + pub(crate) fn values(&self) -> impl Iterator { + self.x_uris() + .values() + .chain(self.c_uris().values()) + .flat_map(|uris| uris.iter()) + } + /// Returns `true` if both x509 and c509 certificate maps are empty. - #[must_use] - pub fn is_empty(&self) -> bool { + #[cfg(test)] + pub(crate) fn is_empty(&self) -> bool { self.x_uris().is_empty() && self.c_uris().is_empty() } - /// Returns a list of addresses by the given role. + /// Returns a list of URIs by the given role. #[must_use] - pub fn role_addresses( + pub(crate) fn role_uris( &self, role: usize, - ) -> HashSet
{ + ) -> HashSet { let mut result = HashSet::new(); if let Some(uris) = self.x_uris().get(&role) { - result.extend(uris.iter().map(|uri| uri.address().clone())); + result.extend(uris.iter().cloned()); } if let Some(uris) = self.c_uris().get(&role) { - result.extend(uris.iter().map(|uri| uri.address().clone())); + result.extend(uris.iter().cloned()); } result } - /// Returns a list of stake addresses by the given role. + /// Returns a set of stake addresses by the given role. #[must_use] - pub fn role_stake_addresses( + pub(crate) fn role_stake_addresses( &self, role: usize, ) -> HashSet { - self.role_addresses(role) + self.role_uris(role) .iter() - .filter_map(|address| { - match address { + .filter_map(|uri| { + match uri.address() { Address::Stake(a) => Some(a.clone().into()), _ => None, } @@ -107,19 +122,17 @@ impl Cip0134UriSet { .collect() } - /// Returns a list of all stake addresses. + /// Returns a set of all active (without taken) stake addresses. #[must_use] - pub fn stake_addresses(&self) -> HashSet { - self.x_uris() - .values() - .chain(self.c_uris().values()) - .flat_map(|uris| uris.iter()) + pub(crate) fn stake_addresses(&self) -> HashSet { + self.values() .filter_map(|uri| { match uri.address() { Address::Stake(a) => Some(a.clone().into()), _ => None, } }) + .filter(|v| !self.0.taken_stake_addresses.contains(v)) .collect() } @@ -142,7 +155,7 @@ impl Cip0134UriSet { /// 2: [uri_4] /// ``` #[must_use] - pub fn update( + pub(crate) fn update( self, metadata: &Cip509RbacMetadata, ) -> Self { @@ -154,6 +167,7 @@ impl Cip0134UriSet { let Cip0134UriSetInner { mut x_uris, mut c_uris, + mut taken_stake_addresses, } = Arc::unwrap_or_clone(self.0); for (index, cert) in metadata.x509_certs.iter().enumerate() { @@ -191,7 +205,50 @@ impl Cip0134UriSet { } } - Self(Arc::new(Cip0134UriSetInner { x_uris, c_uris })) + metadata + .certificate_uris + .stake_addresses() + .iter() + .for_each(|v| { + taken_stake_addresses.remove(v); + }); + + Self(Arc::new(Cip0134UriSetInner { + x_uris, + c_uris, + taken_stake_addresses, + })) + } + + /// Return the updated URIs set where the provided URIs were taken by other + /// registration chains. + /// + /// Updates the current URI set by marking URIs as taken. + #[must_use] + pub(crate) fn update_taken_uris( + self, + reg: &Cip509RbacMetadata, + ) -> Self { + let current_stake_addresses = self.stake_addresses(); + let latest_taken_stake_addresses = reg + .certificate_uris + .stake_addresses() + .into_iter() + .filter(|v| current_stake_addresses.contains(v)); + + let Cip0134UriSetInner { + x_uris, + c_uris, + mut taken_stake_addresses, + } = Arc::unwrap_or_clone(self.0); + + taken_stake_addresses.extend(latest_taken_stake_addresses); + + Self(Arc::new(Cip0134UriSetInner { + x_uris, + c_uris, + taken_stake_addresses, + })) } } @@ -344,7 +401,7 @@ mod tests { let set = cip509.certificate_uris().unwrap(); assert!(!set.is_empty()); assert!(set.c_uris().is_empty()); - assert_eq!(set.role_addresses(0).len(), 1); + assert_eq!(set.role_uris(0).len(), 1); assert_eq!(set.role_stake_addresses(0).len(), 1); assert_eq!(set.stake_addresses().len(), 1); diff --git a/rust/rbac-registration/src/cardano/cip509/validation.rs b/rust/rbac-registration/src/cardano/cip509/validation.rs index 158a889fef..8484f5bf51 100644 --- a/rust/rbac-registration/src/cardano/cip509/validation.rs +++ b/rust/rbac-registration/src/cardano/cip509/validation.rs @@ -157,10 +157,7 @@ fn extract_stake_addresses(uris: Option<&Cip0134UriSet>) -> Vec<(VKeyHash, Strin return Vec::new(); }; - uris.x_uris() - .iter() - .chain(uris.c_uris()) - .flat_map(|(_index, uris)| uris.iter()) + uris.values() .filter_map(|uri| { if let Address::Stake(a) = uri.address() { let bech32 = uri.address().to_string(); @@ -185,10 +182,7 @@ fn extract_payment_addresses(uris: Option<&Cip0134UriSet>) -> Vec<(VKeyHash, Str return Vec::new(); }; - uris.x_uris() - .iter() - .chain(uris.c_uris()) - .flat_map(|(_index, uris)| uris.iter()) + uris.values() .filter_map(|uri| { if let Address::Shelley(a) = uri.address() { match a.payment() { @@ -593,8 +587,7 @@ mod tests { assert_eq!(origin.txn_index(), data.txn_index); assert_eq!(origin.point().slot_or_default(), data.slot); - // The consume function must return the problem report contained within the registration. - let report = registration.consume().unwrap_err(); + let report = registration.report(); assert!(report.is_problematic()); let report = format!("{report:?}"); assert!(report.contains("is not present in the transaction witness set, and can not be verified as owned and spendable")); @@ -616,7 +609,7 @@ mod tests { assert_eq!(origin.txn_index(), data.txn_index); assert_eq!(origin.point().slot_or_default(), data.slot); - let report = registration.consume().unwrap_err(); + let report = registration.report(); assert!(report.is_problematic()); let report = format!("{report:?}"); assert!(report @@ -637,8 +630,7 @@ mod tests { assert_eq!(origin.txn_index(), data.txn_index); assert_eq!(origin.point().slot_or_default(), data.slot); - // The consume function must return the problem report contained within the registration. - let report = registration.consume().unwrap_err(); + let report = registration.report(); assert!(report.is_problematic()); let report = format!("{report:?}"); assert!(report.contains("Unknown role found: 4")); diff --git a/rust/rbac-registration/src/cardano/mod.rs b/rust/rbac-registration/src/cardano/mod.rs index 8af2182b83..11784792f4 100644 --- a/rust/rbac-registration/src/cardano/mod.rs +++ b/rust/rbac-registration/src/cardano/mod.rs @@ -1,3 +1,4 @@ //! Cardano module pub mod cip509; +pub mod state; diff --git a/rust/rbac-registration/src/cardano/state.rs b/rust/rbac-registration/src/cardano/state.rs new file mode 100644 index 0000000000..c5007bff42 --- /dev/null +++ b/rust/rbac-registration/src/cardano/state.rs @@ -0,0 +1,45 @@ +//! Cardano RBAC state traits, which are used during different stateful validation +//! procedures. + +use std::future::Future; + +use cardano_blockchain_types::StakeAddress; +use catalyst_types::catalyst_id::CatalystId; +use ed25519_dalek::VerifyingKey; + +use crate::registration::cardano::RegistrationChain; + +/// RBAC chains state trait +pub trait RbacChainsState { + /// Returns RBAC chain for the given Catalyst ID. + fn chain( + &self, + id: &CatalystId, + ) -> impl Future>> + Send; + + /// Returns `true` if a RBAC chain with the given Catalyst ID already exists. + fn is_chain_known( + &self, + id: &CatalystId, + ) -> impl Future> + Send; + + /// Returns `true` if a provided address already used by any RBAC chain. + fn is_stake_address_used( + &self, + addr: &StakeAddress, + ) -> impl Future> + Send; + + /// Returns a corresponding to the RBAC chain's Catalyst ID corresponding by the given + /// signing public key. + fn chain_catalyst_id_from_signing_pk( + &self, + key: &VerifyingKey, + ) -> impl Future>> + Send; + + /// Update the chain by "taking" the given `StakeAddress` for the corresponding + /// RBAC chain's by the given `CatalystId`. + fn take_stake_address_from_chains( + &mut self, + addresses: impl Iterator + Send, + ) -> impl Future> + Send; +} diff --git a/rust/rbac-registration/src/lib.rs b/rust/rbac-registration/src/lib.rs index b140584362..6e35f51656 100644 --- a/rust/rbac-registration/src/lib.rs +++ b/rust/rbac-registration/src/lib.rs @@ -1,7 +1,6 @@ //! This crate provides functionalities for RBAC registration. pub mod cardano; -pub mod providers; pub mod registration; mod utils; diff --git a/rust/rbac-registration/src/providers.rs b/rust/rbac-registration/src/providers.rs deleted file mode 100644 index 271f7ccd75..0000000000 --- a/rust/rbac-registration/src/providers.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Providers traits, which are used during different validation procedures. - -use std::future::Future; - -use cardano_blockchain_types::{hashes::TransactionId, StakeAddress}; -use catalyst_types::catalyst_id::CatalystId; -use ed25519_dalek::VerifyingKey; - -use crate::registration::cardano::RegistrationChain; - -/// `RegistrationChain` Provider trait -pub trait RbacRegistrationProvider { - /// Returns registration chain - /// for the given Catalyst ID. - fn chain( - &self, - id: CatalystId, - ) -> impl Future>> + Send; - - /// Returns `true` if a chain with the given Catalyst ID already exists. - /// - /// This function behaves in the same way as `latest_rbac_chain(...).is_some()` but - /// the implementation is more optimized because we don't need to build the whole - /// chain. - fn is_chain_known( - &self, - id: CatalystId, - ) -> impl Future> + Send; - - /// Returns a Catalyst ID corresponding to the given stake address. - fn catalyst_id_from_stake_address( - &self, - address: &StakeAddress, - ) -> impl Future>> + Send; - - /// Returns a Catalyst ID corresponding to the given public key. - fn catalyst_id_from_public_key( - &self, - key: VerifyingKey, - ) -> impl Future>> + Send; - - /// Returns a Catalyst ID corresponding to the given transaction hash. - fn catalyst_id_from_txn_id( - &self, - txn_id: TransactionId, - ) -> impl Future>> + Send; -} diff --git a/rust/rbac-registration/src/registration/cardano/mod.rs b/rust/rbac-registration/src/registration/cardano/mod.rs index 4c9d1df172..927cbac0c1 100644 --- a/rust/rbac-registration/src/registration/cardano/mod.rs +++ b/rust/rbac-registration/src/registration/cardano/mod.rs @@ -22,12 +22,12 @@ use update_rbac::{ }; use x509_cert::certificate::Certificate as X509Certificate; -use crate::{ - cardano::cip509::{ - CertKeyHash, CertOrPk, Cip0134UriSet, Cip509, PaymentHistory, PointData, RoleData, - RoleDataRecord, ValidationSignature, +use crate::cardano::{ + cip509::{ + CertKeyHash, CertOrPk, Cip0134UriSet, Cip509, PaymentHistory, PointData, PointTxnIdx, + RoleData, RoleDataRecord, ValidationSignature, }, - providers::RbacRegistrationProvider, + state::RbacChainsState, }; /// Registration chains. @@ -40,32 +40,120 @@ pub struct RegistrationChain { } impl RegistrationChain { + /// Attempts to initialize a new RBAC registration chain + /// from a given CIP-509 registration, ensuring uniqueness of Catalyst ID, stake + /// addresses, and associated public keys. + /// + /// Returns `None` if the `cip509` is invalid by any reason, properly updating + /// `cip509.report()`. + /// + /// # Errors + /// - Propagates any I/O or provider-level errors encountered while checking key + /// ownership (e.g., database lookup failures). + pub async fn new( + cip509: &Cip509, + state: &mut State, + ) -> anyhow::Result> + where + State: RbacChainsState, + { + let Some(new_chain) = Self::new_stateless(cip509) else { + return Ok(None); + }; + + // Verify that a Catalyst ID of this chain is unique. + { + let cat_id = new_chain.catalyst_id(); + if state.is_chain_known(cat_id).await? { + cip509.report().functional_validation( + &format!("{} is already used", cat_id.as_short_id()), + "It isn't allowed to use same Catalyst ID (certificate subject public key) in multiple registration chains", + ); + } + + check_signing_pk(cat_id, cip509, state).await?; + } + + if cip509.report().is_problematic() { + return Ok(None); + } + + state + .take_stake_address_from_chains(cip509.stake_addresses().into_iter()) + .await?; + + Ok(Some(new_chain)) + } + /// Create a new instance of registration chain. /// The first new value should be the chain root. /// - /// # Arguments - /// - `cip509` - The CIP509. + /// Returns `None` if the `cip509` is invalid by any reason, properly updating + /// `cip509.report()`. #[must_use] - pub fn new_stateless(cip509: Cip509) -> Option { - let inner = RegistrationChainInner::new_stateless(cip509)?; + pub fn new_stateless(cip509: &Cip509) -> Option { + let inner = RegistrationChainInner::new(cip509)?; Some(Self { inner: Arc::new(inner), }) } + /// Attempts to update an existing RBAC registration chain + /// with a new CIP-509 registration, validating address and key usage consistency. + /// + /// Returns `None` if the `cip509` is invalid by any reason, properly updating + /// `cip509.report()`. + /// + /// # Errors + /// - Propagates any I/O or provider-level errors encountered while checking key + /// ownership (e.g., database lookup failures). + pub async fn update( + &self, + cip509: &Cip509, + state: &State, + ) -> anyhow::Result> + where + State: RbacChainsState, + { + let Some(new_chain) = self.update_stateless(cip509) else { + return Ok(None); + }; + + // Check that addresses from the new registration aren't used in other chains. + let previous_addresses = self.stake_addresses(); + let reg_addresses = cip509.stake_addresses(); + let new_addresses: Vec<_> = reg_addresses.difference(&previous_addresses).collect(); + for address in &new_addresses { + if state.is_stake_address_used(address).await? { + cip509.report().functional_validation( + &format!("{address} stake address is already used"), + "It isn't allowed to use same stake address in multiple registration chains, if its not a new chain", + ); + } + } + + check_signing_pk(self.catalyst_id(), cip509, state).await?; + + if cip509.report().is_problematic() { + Ok(None) + } else { + Ok(Some(new_chain)) + } + } + /// Update the registration chain. /// - /// # Arguments - /// - `cip509` - The CIP509. + /// Returns `None` if the `cip509` is invalid by any reason, properly updating + /// `cip509.report()`. #[must_use] pub fn update_stateless( &self, - cip509: Cip509, + cip509: &Cip509, ) -> Option { let latest_signing_pk = self.get_latest_signing_pk_for_role(RoleId::Role0); let new_inner = if let Some((signing_pk, _)) = latest_signing_pk { - self.inner.update_stateless(cip509, signing_pk)? + self.inner.update(cip509, signing_pk)? } else { cip509.report().missing_field( "latest signing key for role 0", @@ -78,29 +166,6 @@ impl RegistrationChain { }) } - /// Creates or updates an RBAC registration chain from a CIP-509 registration. - /// - /// If the given registration references a previous transaction, it attempts - /// to update the existing chain using that previous transaction. - /// Otherwise, it starts a new chain from the provided registration. - pub async fn update( - &self, - reg: Cip509, - provider: &Provider, - ) -> Option - where - Provider: RbacRegistrationProvider, - { - let new_inner = if reg.previous_transaction().is_some() { - self.inner.update(reg, provider).await? - } else { - RegistrationChainInner::new(reg, provider).await? - }; - Some(Self { - inner: Arc::new(new_inner), - }) - } - /// Returns a Catalyst ID. #[must_use] pub fn catalyst_id(&self) -> &CatalystId { @@ -255,11 +320,17 @@ impl RegistrationChain { .and_then(|rdr| rdr.encryption_key_from_rotation(rotation)) } - /// Returns all stake addresses associated to this registration. + /// Returns all stake addresses associated to this chain. #[must_use] pub fn stake_addresses(&self) -> HashSet { self.inner.certificate_uris.stake_addresses() } + + /// Returns the latest know applied registration's `PointTxnIdx`. + #[must_use] + pub fn latest_applied(&self) -> PointTxnIdx { + self.inner.latest_applied() + } } /// Inner structure of registration chain. @@ -269,6 +340,9 @@ struct RegistrationChainInner { catalyst_id: CatalystId, /// The current transaction ID hash (32 bytes) current_tx_id_hash: PointData, + /// The latest `PointTxnIdx` of the stolen taken URIs by another registration chains. + latest_taken_uris_point: Option, + /// List of purpose for this registration chain purpose: Vec, @@ -305,7 +379,7 @@ impl RegistrationChainInner { /// # Arguments /// - `cip509` - The CIP509. #[must_use] - fn new_stateless(cip509: Cip509) -> Option { + fn new(cip509: &Cip509) -> Option { let context = "Registration Chain new"; // Should be chain root, return immediately if not if cip509.previous_transaction().is_some() { @@ -318,23 +392,21 @@ impl RegistrationChainInner { return None; }; - let point_tx_idx = cip509.origin().clone(); - let current_tx_id_hash = PointData::new(point_tx_idx.clone(), cip509.txn_hash()); - let validation_signature = cip509.validation_signature().cloned(); - let raw_aux_data = cip509.raw_aux_data().to_vec(); + let Some(registration) = cip509.metadata().cloned() else { + cip509.report().missing_field("metadata", context); + return None; + }; // Role data let mut role_data_history = HashMap::new(); let mut role_data_record = HashMap::new(); - - if let Some(registration) = cip509.metadata() { - update_role_data( - registration, - &mut role_data_history, - &mut role_data_record, - &point_tx_idx, - ); - } + let point_tx_idx = cip509.origin().clone(); + update_role_data( + ®istration, + &mut role_data_history, + &mut role_data_record, + &point_tx_idx, + ); // There should be role 0 since we already check that the chain root (no previous tx id) // must contain role 0 @@ -354,17 +426,26 @@ impl RegistrationChainInner { }; check_validation_signature( - validation_signature, - &raw_aux_data, + cip509.validation_signature(), + cip509.raw_aux_data(), signing_pk, cip509.report(), context, ); - let Ok((purpose, registration, payment_history)) = cip509.consume() else { + if cip509.txn_inputs_hash().is_none() { + cip509.report().missing_field("txn inputs hash", context); + } + + let Some(purpose) = cip509.purpose() else { + cip509.report().missing_field("purpose", context); return None; }; + if cip509.report().is_problematic() { + return None; + } + let purpose = vec![purpose]; let certificate_uris = registration.certificate_uris.clone(); let mut x509_certs = HashMap::new(); @@ -386,6 +467,8 @@ impl RegistrationChainInner { &point_tx_idx, ); let revocations = revocations_list(registration.revocation_list.clone(), &point_tx_idx); + let current_tx_id_hash = PointData::new(point_tx_idx, cip509.txn_hash()); + let payment_history = cip509.payment_history().clone(); Some(Self { catalyst_id, @@ -394,6 +477,7 @@ impl RegistrationChainInner { x509_certs, c509_certs, certificate_uris, + latest_taken_uris_point: None, simple_keys, revocations, role_data_history, @@ -407,15 +491,41 @@ impl RegistrationChainInner { /// # Arguments /// - `cip509` - The CIP509. #[must_use] - fn update_stateless( + fn update( &self, - cip509: Cip509, + cip509: &Cip509, signing_pk: VerifyingKey, ) -> Option { let context = "Registration Chain update"; + if self.latest_applied().point() >= cip509.origin().point() { + cip509.report().functional_validation( + &format!( + "The provided registration is earlier {} than the latest applied one {}", + cip509.origin().point(), + self.current_tx_id_hash.point() + ), + "Registrations must be applied in the correct ascending order.", + ); + return None; + } + let mut new_inner = self.clone(); let Some(prv_tx_id) = cip509.previous_transaction() else { + if let Some(cat_id) = cip509.catalyst_id() { + if cat_id == &self.catalyst_id { + cip509.report().functional_validation( + &format!( + "Trying to apply the first registration to the associated {} again", + cat_id.as_short_id() + ), + "It isn't allowed to submit first registration twice", + ); + return None; + } + + return Some(new_inner.update_cause_another_chain(cip509)); + } cip509 .report() .missing_field("previous transaction ID", context); @@ -427,7 +537,7 @@ impl RegistrationChainInner { // Perform signature validation // This should be done before updating the signing key check_validation_signature( - cip509.validation_signature().cloned(), + cip509.validation_signature(), cip509.raw_aux_data(), signing_pk, cip509.report(), @@ -447,18 +557,34 @@ impl RegistrationChainInner { return None; } - let point_tx_idx = cip509.origin().clone(); - let Ok((purpose, registration, payment_history)) = cip509.consume() else { + if cip509.txn_inputs_hash().is_none() { + cip509.report().missing_field("txn inputs hash", context); + } + + let Some(purpose) = cip509.purpose() else { + cip509.report().missing_field("purpose", context); + return None; + }; + let Some(registration) = cip509.metadata().cloned() else { + cip509.report().missing_field("metadata", context); return None; }; + if cip509.report().is_problematic() { + return None; + } + // Add purpose to the chain, if not already exist if !self.purpose.contains(&purpose) { new_inner.purpose.push(purpose); } + let point_tx_idx = cip509.origin().clone(); + new_inner.certificate_uris = new_inner.certificate_uris.update(®istration); - new_inner.payment_history.extend(payment_history); + new_inner + .payment_history + .extend(cip509.payment_history().clone()); update_x509_certs( &mut new_inner.x509_certs, registration.x509_certs.clone(), @@ -489,175 +615,23 @@ impl RegistrationChainInner { Some(new_inner) } - /// Attempts to initialize a new RBAC registration chain - /// from a given CIP-509 registration, ensuring uniqueness of Catalyst ID, stake - /// addresses, and associated public keys. - pub async fn new( - reg: Cip509, - provider: &Provider, - ) -> Option - where - Provider: RbacRegistrationProvider, - { - let report = reg.report().to_owned(); - - // Try to start a new chain. - let new_chain = Self::new_stateless(reg)?; - // Verify that a Catalyst ID of this chain is unique. - let catalyst_id = new_chain.catalyst_id.as_short_id(); - if provider.is_chain_known(catalyst_id.clone()).await.ok()? { - report.functional_validation( - &format!("{catalyst_id} is already used"), - "It isn't allowed to use same Catalyst ID (certificate subject public key) in multiple registration chains", - ); - return None; - } - - // Validate stake addresses. - let new_addresses = new_chain.certificate_uris.stake_addresses(); - let mut updated_chains: HashMap<_, HashSet> = HashMap::new(); - for address in &new_addresses { - if let Some(id) = provider - .catalyst_id_from_stake_address(address) - .await - .ok()? - { - // If an address is used in existing chain then a new chain must have different role - // 0 signing key. - let previous_chain = provider.chain(id.clone()).await.ok()??; - if previous_chain.get_latest_signing_pk_for_role(RoleId::Role0) - == new_chain.get_latest_signing_pk_for_role(RoleId::Role0) - { - report.functional_validation( - &format!("A new registration ({catalyst_id}) uses the same public key as the previous one ({})", - previous_chain.catalyst_id().as_short_id() - ), - "It is only allowed to override the existing chain by using different public key", - ); - } else { - // The new root registration "takes" an address(es) from the existing chain, so - // that chain needs to be updated. - updated_chains - .entry(id) - .and_modify(|e| { - e.insert(address.clone()); - }) - .or_insert([address.clone()].into_iter().collect()); - } - } - } - - // Check that new public keys aren't used by other chains. - new_chain - .validate_public_keys(&report, provider) - .await - .ok()?; - - if report.is_problematic() { - return None; - } - - Some(new_chain) - } - - /// Attempts to update an existing RBAC registration chain - /// with a new CIP-509 registration, validating address and key usage consistency. - pub async fn update( - &self, - reg: Cip509, - provider: &Provider, - ) -> Option - where - Provider: RbacRegistrationProvider, - { - let previous_txn = reg.previous_transaction()?; - let report = reg.report().to_owned(); - - // Find a chain this registration belongs to. - let Some(catalyst_id) = provider.catalyst_id_from_txn_id(previous_txn).await.ok()? else { - // We are unable to determine a Catalyst ID, so there is no sense to update the problem - // report because we would be unable to store this registration anyway. - return None; - }; - let chain = provider.chain(catalyst_id.clone()).await.ok()??; - - // Check that addresses from the new registration aren't used in other chains. - let previous_addresses = chain.stake_addresses(); - let reg_addresses = reg.stake_addresses(); - let new_addresses: Vec<_> = reg_addresses.difference(&previous_addresses).collect(); - for address in &new_addresses { - match provider - .catalyst_id_from_stake_address(address) - .await - .ok()? - { - None => { - // All good: the address wasn't used before. - }, - Some(_) => { - report.functional_validation( - &format!("{address} stake addresses is already used"), - "It isn't allowed to use same stake address in multiple registration chains", - ); - }, - } - } - - // Try to add a new registration to the chain. - let (signing_pk, _) = self.get_latest_signing_pk_for_role(RoleId::Role0)?; - let new_chain = chain.inner.update_stateless(reg.clone(), signing_pk)?; - - // Check that new public keys aren't used by other chains. - new_chain - .validate_public_keys(&report, provider) - .await - .ok()?; - - // Return an error if any issues were recorded in the report. - if report.is_problematic() { - return None; - } - - Some(new_chain) - } - - /// Validates that none of the signing keys in a given RBAC registration chain - /// have been used by any other existing chain, ensuring global key uniqueness - /// across all Catalyst registrations. + /// Update the registration chain with the `cip509` associated to another chain. + /// This is the case when registration for different chain affecting the current one, + /// by invalidating some data for the current registration chain (stealing stake + /// addresses etc.). /// - /// # Returns - /// Returns `Ok(true)` if all signing keys are unique and validation passes - /// successfully. Returns `Ok(false)` if any key conflict is detected, with the - /// issue recorded in the provided [`ProblemReport`]. - /// - /// # Errors - /// - Propagates any I/O or provider-level errors encountered while checking key - /// ownership (e.g., database lookup failures). - async fn validate_public_keys( - &self, - report: &ProblemReport, - provider: &Provider, - ) -> anyhow::Result<()> - where - Provider: RbacRegistrationProvider, - { - let roles: Vec<_> = self.role_data_history.keys().collect(); - let catalyst_id = self.catalyst_id.as_short_id(); - - for role in roles { - if let Some((key, _)) = self.get_latest_signing_pk_for_role(*role) { - if let Some(previous) = provider.catalyst_id_from_public_key(key).await? { - if previous != catalyst_id { - report.functional_validation( - &format!("An update to {catalyst_id} registration chain uses the same public key ({key:?}) as {previous} chain"), - "It isn't allowed to use role 0 signing (certificate subject public) key in different chains", - ); - } - } - } + /// The provided `cip509` should be fully validated by another chain before trying to + /// submit to the current one. + #[must_use] + fn update_cause_another_chain( + mut self, + cip509: &Cip509, + ) -> Self { + if let Some(reg) = cip509.metadata() { + self.certificate_uris = self.certificate_uris.update_taken_uris(reg); } - - Ok(()) + self.latest_taken_uris_point = Some(cip509.origin().clone()); + self } /// Get the latest signing public key for a role. @@ -691,12 +665,26 @@ impl RegistrationChainInner { }) }) } + + /// Returns the latest know applied registration's `PointTxnIdx`. + #[must_use] + fn latest_applied(&self) -> PointTxnIdx { + if let Some(latest_taken_uris_point) = &self.latest_taken_uris_point { + if latest_taken_uris_point.point() > self.current_tx_id_hash.point() { + return latest_taken_uris_point.clone(); + } + } + PointTxnIdx::new( + self.current_tx_id_hash.point().clone(), + self.current_tx_id_hash.txn_index(), + ) + } } /// Perform a check on the validation signature. /// The auxiliary data should be sign with the latest signing public key. fn check_validation_signature( - validation_signature: Option, + validation_signature: Option<&ValidationSignature>, raw_aux_data: &[u8], signing_pk: VerifyingKey, report: &ProblemReport, @@ -734,6 +722,32 @@ fn check_validation_signature( } } +/// Checks that a new registration doesn't contain a signing key that was used by any +/// other chain. +async fn check_signing_pk( + cat_id: &CatalystId, + cip509: &Cip509, + state: &State, +) -> anyhow::Result<()> +where + State: RbacChainsState, +{ + for role in cip509.all_roles() { + if let Some(key) = cip509.signing_pk_for_role(role) { + if let Some(previous) = state.chain_catalyst_id_from_signing_pk(&key).await? { + if &previous != cat_id { + cip509.report().functional_validation( + &format!("An update to {cat_id} registration chain uses the same public key ({key:?}) as {previous} chain"), + "It isn't allowed to use role 0 signing (certificate subject public) key in different chains", + ); + } + } + } + } + + Ok(()) +} + #[cfg(test)] mod test { use catalyst_types::catalyst_id::role_index::RoleId; @@ -749,8 +763,9 @@ mod test { .unwrap(); data.assert_valid(®istration); + // Performs only stateless validations // Create a chain with the first registration. - let chain = RegistrationChain::new_stateless(registration).unwrap(); + let chain = RegistrationChain::new_stateless(®istration).unwrap(); assert_eq!(chain.purpose(), &[data.purpose]); assert_eq!(1, chain.x509_certs().len()); let origin = &chain.x509_certs().get(&0).unwrap().first().unwrap(); @@ -777,13 +792,8 @@ mod test { assert!(registration.report().is_problematic()); let report = registration.report().to_owned(); - assert!(chain.update_stateless(registration).is_none()); - let report = format!("{report:?}"); - assert!( - report.contains("kind: InvalidValue { field: \"previous transaction ID\""), - "{}", - report - ); + assert!(chain.update_stateless(®istration).is_none()); + assert!(report.is_problematic(), "{report:?}"); // Add the second registration. let data = test::block_6(); @@ -791,7 +801,7 @@ mod test { .unwrap() .unwrap(); data.assert_valid(®istration); - let update = chain.update_stateless(registration).unwrap(); + let update = chain.update_stateless(®istration).unwrap(); // Current tx hash should be equal to the hash from block 4. assert_eq!(update.current_tx_id_hash(), data.txn_hash); assert!(update.role_data_record().contains_key(&data.role)); diff --git a/rust/rbac-registration/src/utils/test.rs b/rust/rbac-registration/src/utils/test.rs index 3d8f0e8611..345a019b55 100644 --- a/rust/rbac-registration/src/utils/test.rs +++ b/rust/rbac-registration/src/utils/test.rs @@ -48,8 +48,7 @@ impl BlockTestData { assert!(cip509.role_data(self.role).is_some()); assert_eq!(cip509.txn_hash(), self.txn_hash); assert_eq!(cip509.previous_transaction(), self.prv_hash); - let (purpose, ..) = cip509.clone().consume().unwrap(); - assert_eq!(purpose, self.purpose); + assert_eq!(cip509.purpose().unwrap(), self.purpose); } } From 3dc1ba4e11e1f8a9306e606b9570c0904b16a5da Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Wed, 19 Nov 2025 20:40:21 +0700 Subject: [PATCH 03/16] fix clippy --- .../src/registration/cardano/mod.rs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/rust/rbac-registration/src/registration/cardano/mod.rs b/rust/rbac-registration/src/registration/cardano/mod.rs index 1b7a255ef4..cdbc547860 100644 --- a/rust/rbac-registration/src/registration/cardano/mod.rs +++ b/rust/rbac-registration/src/registration/cardano/mod.rs @@ -669,11 +669,12 @@ impl RegistrationChainInner { /// Returns the latest know applied registration's `PointTxnIdx`. #[must_use] fn latest_applied(&self) -> PointTxnIdx { - if let Some(latest_taken_uris_point) = &self.latest_taken_uris_point { - if latest_taken_uris_point.point() > self.current_tx_id_hash.point() { - return latest_taken_uris_point.clone(); - } + if let Some(latest_taken_uris_point) = &self.latest_taken_uris_point + && latest_taken_uris_point.point() > self.current_tx_id_hash.point() + { + return latest_taken_uris_point.clone(); } + PointTxnIdx::new( self.current_tx_id_hash.point().clone(), self.current_tx_id_hash.txn_index(), @@ -733,15 +734,14 @@ where State: RbacChainsState, { for role in cip509.all_roles() { - if let Some(key) = cip509.signing_pk_for_role(role) { - if let Some(previous) = state.chain_catalyst_id_from_signing_pk(&key).await? { - if &previous != cat_id { - cip509.report().functional_validation( - &format!("An update to {cat_id} registration chain uses the same public key ({key:?}) as {previous} chain"), - "It isn't allowed to use role 0 signing (certificate subject public) key in different chains", - ); - } - } + if let Some(key) = cip509.signing_pk_for_role(role) + && let Some(previous) = state.chain_catalyst_id_from_signing_pk(&key).await? + && &previous != cat_id + { + cip509.report().functional_validation( + &format!("An update to {cat_id} registration chain uses the same public key ({key:?}) as {previous} chain"), + "It isn't allowed to use role 0 signing (certificate subject public) key in different chains", + ); } } From b9d316e8ee70d7c01d86c7a63cc517957c262a94 Mon Sep 17 00:00:00 2001 From: Alex Pozhylenkov Date: Thu, 20 Nov 2025 12:14:59 +0700 Subject: [PATCH 04/16] make Cip0134UriSet private (#655) --- rust/rbac-registration/src/cardano/cip509/mod.rs | 2 +- .../src/cardano/cip509/rbac/metadata.rs | 14 +++++++------- .../src/cardano/cip509/utils/cip134_uri_set.rs | 2 +- .../src/cardano/cip509/utils/mod.rs | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/rust/rbac-registration/src/cardano/cip509/mod.rs b/rust/rbac-registration/src/cardano/cip509/mod.rs index caaa74c653..d4f009769e 100644 --- a/rust/rbac-registration/src/cardano/cip509/mod.rs +++ b/rust/rbac-registration/src/cardano/cip509/mod.rs @@ -9,7 +9,7 @@ pub use types::{ CertKeyHash, CertOrPk, KeyLocalRef, LocalRefInt, Payment, PaymentHistory, PointData, PointTxnIdx, RoleData, RoleDataRecord, TxInputHash, ValidationSignature, }; -pub use utils::{Cip0134UriSet, extract_key}; +pub(crate) use utils::{Cip0134UriSet, extract_key}; #[allow(clippy::module_inception)] mod cip509; diff --git a/rust/rbac-registration/src/cardano/cip509/rbac/metadata.rs b/rust/rbac-registration/src/cardano/cip509/rbac/metadata.rs index cd49c11974..3ce70c6a10 100644 --- a/rust/rbac-registration/src/cardano/cip509/rbac/metadata.rs +++ b/rust/rbac-registration/src/cardano/cip509/rbac/metadata.rs @@ -28,9 +28,9 @@ use crate::cardano::cip509::{ #[allow(clippy::module_name_repetitions)] pub struct Cip509RbacMetadata { /// A potentially empty list of x509 certificates. - pub x509_certs: Vec, + pub(crate) x509_certs: Vec, /// A potentially empty list of c509 certificates. - pub c509_certs: Vec, + pub(crate) c509_certs: Vec, /// A set of URIs contained in both x509 and c509 certificates. /// /// URIs from different certificate types are stored separately and certificate @@ -38,21 +38,21 @@ pub struct Cip509RbacMetadata { /// /// This field isn't present in the encoded format and is populated by processing both /// `x509_certs` and `c509_certs` fields. - pub certificate_uris: Cip0134UriSet, + pub(crate) certificate_uris: Cip0134UriSet, /// A list of public keys that can be used instead of storing full certificates. /// /// Check [this section] to understand how certificates and the public keys list are /// related. /// /// [this section]: https://github.com/input-output-hk/catalyst-CIPs/tree/x509-role-registration-metadata/CIP-XXXX#storing-certificates-and-public-key - pub pub_keys: Vec, + pub(crate) pub_keys: Vec, /// A potentially empty list of revoked certificates. - pub revocation_list: Vec, + pub(crate) revocation_list: Vec, /// A potentially empty role data. - pub role_data: HashMap, + pub(crate) role_data: HashMap, /// Optional map of purpose key data. /// Empty map if no purpose key data is present. - pub purpose_key_data: HashMap>, + pub(crate) purpose_key_data: HashMap>, } /// The first valid purpose key. diff --git a/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs b/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs index 6e35215883..d8a13c2c24 100644 --- a/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs +++ b/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs @@ -30,7 +30,7 @@ type UrisMap = HashMap>; /// This structure uses [`Arc`] internally, so it is cheap to clone. #[derive(Debug, Clone, Eq, PartialEq)] #[allow(clippy::module_name_repetitions)] -pub struct Cip0134UriSet(Arc); +pub(crate) struct Cip0134UriSet(Arc); /// Internal `Cip0134UriSet` data. #[derive(Debug, Clone, Eq, PartialEq)] diff --git a/rust/rbac-registration/src/cardano/cip509/utils/mod.rs b/rust/rbac-registration/src/cardano/cip509/utils/mod.rs index 0584cc801c..b1dfcce984 100644 --- a/rust/rbac-registration/src/cardano/cip509/utils/mod.rs +++ b/rust/rbac-registration/src/cardano/cip509/utils/mod.rs @@ -2,6 +2,6 @@ pub mod cip19; pub mod extract_key; -pub use cip134_uri_set::Cip0134UriSet; +pub(crate) use cip134_uri_set::Cip0134UriSet; mod cip134_uri_set; From e4e5a50e7e2139be29133863a3ce258305f01f6a Mon Sep 17 00:00:00 2001 From: Alex Pozhylenkov Date: Thu, 20 Nov 2025 17:32:57 +0700 Subject: [PATCH 05/16] wip (#658) --- rust/rbac-registration/src/cardano/cip509/cip509.rs | 2 +- rust/rbac-registration/src/cardano/cip509/mod.rs | 2 +- .../src/cardano/cip509/utils/cip134_uri_set.rs | 10 +++++----- rust/rbac-registration/src/cardano/cip509/utils/mod.rs | 2 +- rust/rbac-registration/src/registration/cardano/mod.rs | 6 ++++++ 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/rust/rbac-registration/src/cardano/cip509/cip509.rs b/rust/rbac-registration/src/cardano/cip509/cip509.rs index c99a94de95..592e558672 100644 --- a/rust/rbac-registration/src/cardano/cip509/cip509.rs +++ b/rust/rbac-registration/src/cardano/cip509/cip509.rs @@ -310,7 +310,7 @@ impl Cip509 { /// Returns URIs contained in both x509 and c509 certificates of `Cip509` metadata. #[must_use] - pub(crate) fn certificate_uris(&self) -> Option<&Cip0134UriSet> { + pub fn certificate_uris(&self) -> Option<&Cip0134UriSet> { self.metadata.as_ref().map(|m| &m.certificate_uris) } diff --git a/rust/rbac-registration/src/cardano/cip509/mod.rs b/rust/rbac-registration/src/cardano/cip509/mod.rs index d4f009769e..caaa74c653 100644 --- a/rust/rbac-registration/src/cardano/cip509/mod.rs +++ b/rust/rbac-registration/src/cardano/cip509/mod.rs @@ -9,7 +9,7 @@ pub use types::{ CertKeyHash, CertOrPk, KeyLocalRef, LocalRefInt, Payment, PaymentHistory, PointData, PointTxnIdx, RoleData, RoleDataRecord, TxInputHash, ValidationSignature, }; -pub(crate) use utils::{Cip0134UriSet, extract_key}; +pub use utils::{Cip0134UriSet, extract_key}; #[allow(clippy::module_inception)] mod cip509; diff --git a/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs b/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs index d8a13c2c24..8868ea9a53 100644 --- a/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs +++ b/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs @@ -30,7 +30,7 @@ type UrisMap = HashMap>; /// This structure uses [`Arc`] internally, so it is cheap to clone. #[derive(Debug, Clone, Eq, PartialEq)] #[allow(clippy::module_name_repetitions)] -pub(crate) struct Cip0134UriSet(Arc); +pub struct Cip0134UriSet(Arc); /// Internal `Cip0134UriSet` data. #[derive(Debug, Clone, Eq, PartialEq)] @@ -74,7 +74,7 @@ impl Cip0134UriSet { } /// Returns an iterator over of `Cip0134Uri`. - pub(crate) fn values(&self) -> impl Iterator { + pub fn values(&self) -> impl Iterator { self.x_uris() .values() .chain(self.c_uris().values()) @@ -82,8 +82,8 @@ impl Cip0134UriSet { } /// Returns `true` if both x509 and c509 certificate maps are empty. - #[cfg(test)] - pub(crate) fn is_empty(&self) -> bool { + #[must_use] + pub fn is_empty(&self) -> bool { self.x_uris().is_empty() && self.c_uris().is_empty() } @@ -124,7 +124,7 @@ impl Cip0134UriSet { /// Returns a set of all active (without taken) stake addresses. #[must_use] - pub(crate) fn stake_addresses(&self) -> HashSet { + pub fn stake_addresses(&self) -> HashSet { self.values() .filter_map(|uri| { match uri.address() { diff --git a/rust/rbac-registration/src/cardano/cip509/utils/mod.rs b/rust/rbac-registration/src/cardano/cip509/utils/mod.rs index b1dfcce984..0584cc801c 100644 --- a/rust/rbac-registration/src/cardano/cip509/utils/mod.rs +++ b/rust/rbac-registration/src/cardano/cip509/utils/mod.rs @@ -2,6 +2,6 @@ pub mod cip19; pub mod extract_key; -pub(crate) use cip134_uri_set::Cip0134UriSet; +pub use cip134_uri_set::Cip0134UriSet; mod cip134_uri_set; diff --git a/rust/rbac-registration/src/registration/cardano/mod.rs b/rust/rbac-registration/src/registration/cardano/mod.rs index cdbc547860..ead266c971 100644 --- a/rust/rbac-registration/src/registration/cardano/mod.rs +++ b/rust/rbac-registration/src/registration/cardano/mod.rs @@ -320,6 +320,12 @@ impl RegistrationChain { .and_then(|rdr| rdr.encryption_key_from_rotation(rotation)) } + /// Returns most recent URIs contained from both x509 and c509 certificates. + #[must_use] + pub fn certificate_uris(&self) -> &Cip0134UriSet { + &self.inner.certificate_uris + } + /// Returns all stake addresses associated to this chain. #[must_use] pub fn stake_addresses(&self) -> HashSet { From f930f44527beb69faf896dc988cc801ebe09b65f Mon Sep 17 00:00:00 2001 From: Alex Pozhylenkov Date: Thu, 20 Nov 2025 18:00:14 +0700 Subject: [PATCH 06/16] Update rust/rbac-registration/src/registration/cardano/mod.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: RafaƂ Chabowski --- rust/rbac-registration/src/registration/cardano/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/rbac-registration/src/registration/cardano/mod.rs b/rust/rbac-registration/src/registration/cardano/mod.rs index ead266c971..5a5a616d2f 100644 --- a/rust/rbac-registration/src/registration/cardano/mod.rs +++ b/rust/rbac-registration/src/registration/cardano/mod.rs @@ -346,7 +346,7 @@ struct RegistrationChainInner { catalyst_id: CatalystId, /// The current transaction ID hash (32 bytes) current_tx_id_hash: PointData, - /// The latest `PointTxnIdx` of the stolen taken URIs by another registration chains. + /// The latest `PointTxnIdx` of the taken URIs by another registration chains. latest_taken_uris_point: Option, /// List of purpose for this registration chain From 99aebda9756033f703af987784efc1f6eec04b34 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 20 Nov 2025 20:12:31 +0700 Subject: [PATCH 07/16] fix suggestions --- .../src/cardano/cip509/cip509.rs | 2 +- .../src/cardano/cip509/types/cert_or_pk.rs | 2 +- .../cardano/cip509/utils/cip134_uri_set.rs | 24 +++++------ rust/rbac-registration/src/cardano/state.rs | 4 +- .../src/registration/cardano/mod.rs | 42 ++++++++++--------- 5 files changed, 37 insertions(+), 37 deletions(-) diff --git a/rust/rbac-registration/src/cardano/cip509/cip509.rs b/rust/rbac-registration/src/cardano/cip509/cip509.rs index 592e558672..783123884b 100644 --- a/rust/rbac-registration/src/cardano/cip509/cip509.rs +++ b/rust/rbac-registration/src/cardano/cip509/cip509.rs @@ -240,7 +240,7 @@ impl Cip509 { /// Would return only signing public keys for the present certificates, /// if certificate marked as deleted or undefined it would be skipped. #[must_use] - pub fn signing_pk_for_role( + pub fn signing_public_key_for_role( &self, role: RoleId, ) -> Option { diff --git a/rust/rbac-registration/src/cardano/cip509/types/cert_or_pk.rs b/rust/rbac-registration/src/cardano/cip509/types/cert_or_pk.rs index d697755ecb..b75dfe805c 100644 --- a/rust/rbac-registration/src/cardano/cip509/types/cert_or_pk.rs +++ b/rust/rbac-registration/src/cardano/cip509/types/cert_or_pk.rs @@ -21,7 +21,7 @@ pub enum CertOrPk { impl CertOrPk { /// Extract public key from the given certificate or public key. - pub(crate) fn extract_pk(&self) -> Option { + pub(crate) fn extract_public_key(&self) -> Option { match self { CertOrPk::X509(Some(x509)) => extract_key::x509_key(x509).ok(), CertOrPk::C509(Some(c509)) => extract_key::c509_key(c509).ok(), diff --git a/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs b/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs index 8868ea9a53..0be7abe67e 100644 --- a/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs +++ b/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs @@ -92,17 +92,16 @@ impl Cip0134UriSet { pub(crate) fn role_uris( &self, role: usize, - ) -> HashSet { - let mut result = HashSet::new(); - - if let Some(uris) = self.x_uris().get(&role) { - result.extend(uris.iter().cloned()); - } - if let Some(uris) = self.c_uris().get(&role) { - result.extend(uris.iter().cloned()); - } - - result + ) -> impl Iterator { + let x_iter = self + .x_uris() + .get(&role) + .map_or_else(|| [].iter(), |uris| uris.iter()); + let c_iter = self + .c_uris() + .get(&role) + .map_or_else(|| [].iter(), |uris| uris.iter()); + x_iter.chain(c_iter) } /// Returns a set of stake addresses by the given role. @@ -112,7 +111,6 @@ impl Cip0134UriSet { role: usize, ) -> HashSet { self.role_uris(role) - .iter() .filter_map(|uri| { match uri.address() { Address::Stake(a) => Some(a.clone().into()), @@ -401,7 +399,7 @@ mod tests { let set = cip509.certificate_uris().unwrap(); assert!(!set.is_empty()); assert!(set.c_uris().is_empty()); - assert_eq!(set.role_uris(0).len(), 1); + assert_eq!(set.role_uris(0).count(), 1); assert_eq!(set.role_stake_addresses(0).len(), 1); assert_eq!(set.stake_addresses().len(), 1); diff --git a/rust/rbac-registration/src/cardano/state.rs b/rust/rbac-registration/src/cardano/state.rs index c5007bff42..1537ac44f3 100644 --- a/rust/rbac-registration/src/cardano/state.rs +++ b/rust/rbac-registration/src/cardano/state.rs @@ -31,7 +31,7 @@ pub trait RbacChainsState { /// Returns a corresponding to the RBAC chain's Catalyst ID corresponding by the given /// signing public key. - fn chain_catalyst_id_from_signing_pk( + fn chain_catalyst_id_from_signing_public_key( &self, key: &VerifyingKey, ) -> impl Future>> + Send; @@ -40,6 +40,6 @@ pub trait RbacChainsState { /// RBAC chain's by the given `CatalystId`. fn take_stake_address_from_chains( &mut self, - addresses: impl Iterator + Send, + addresses: impl IntoIterator + Send, ) -> impl Future> + Send; } diff --git a/rust/rbac-registration/src/registration/cardano/mod.rs b/rust/rbac-registration/src/registration/cardano/mod.rs index 5a5a616d2f..774724d6d8 100644 --- a/rust/rbac-registration/src/registration/cardano/mod.rs +++ b/rust/rbac-registration/src/registration/cardano/mod.rs @@ -71,7 +71,7 @@ impl RegistrationChain { ); } - check_signing_pk(cat_id, cip509, state).await?; + check_signing_public_key(cat_id, cip509, state).await?; } if cip509.report().is_problematic() { @@ -79,7 +79,7 @@ impl RegistrationChain { } state - .take_stake_address_from_chains(cip509.stake_addresses().into_iter()) + .take_stake_address_from_chains(cip509.stake_addresses()) .await?; Ok(Some(new_chain)) @@ -133,7 +133,7 @@ impl RegistrationChain { } } - check_signing_pk(self.catalyst_id(), cip509, state).await?; + check_signing_public_key(self.catalyst_id(), cip509, state).await?; if cip509.report().is_problematic() { Ok(None) @@ -151,7 +151,7 @@ impl RegistrationChain { &self, cip509: &Cip509, ) -> Option { - let latest_signing_pk = self.get_latest_signing_pk_for_role(RoleId::Role0); + let latest_signing_pk = self.get_latest_signing_public_key_for_role(RoleId::Role0); let new_inner = if let Some((signing_pk, _)) = latest_signing_pk { self.inner.update(cip509, signing_pk)? } else { @@ -247,11 +247,11 @@ impl RegistrationChain { /// Get the latest signing public key for a role. /// Returns the public key and the rotation,`None` if not found. #[must_use] - pub fn get_latest_signing_pk_for_role( + pub fn get_latest_signing_public_key_for_role( &self, role: RoleId, ) -> Option<(VerifyingKey, KeyRotation)> { - self.inner.get_latest_signing_pk_for_role(role) + self.inner.get_latest_signing_public_key_for_role(role) } /// Get the latest encryption public key for a role. @@ -261,7 +261,7 @@ impl RegistrationChain { &self, role: RoleId, ) -> Option<(VerifyingKey, KeyRotation)> { - self.inner.get_latest_encryption_pk_for_role(role) + self.inner.get_latest_encryption_public_key_for_role(role) } /// Get signing public key for a role with given rotation. @@ -274,7 +274,7 @@ impl RegistrationChain { ) -> Option { self.inner.role_data_record.get(role).and_then(|rdr| { rdr.signing_key_from_rotation(rotation) - .and_then(CertOrPk::extract_pk) + .and_then(CertOrPk::extract_public_key) }) } @@ -288,7 +288,7 @@ impl RegistrationChain { ) -> Option { self.inner.role_data_record.get(role).and_then(|rdr| { rdr.encryption_key_from_rotation(rotation) - .and_then(CertOrPk::extract_pk) + .and_then(CertOrPk::extract_public_key) }) } @@ -398,7 +398,7 @@ impl RegistrationChainInner { return None; }; - let Some(registration) = cip509.metadata().cloned() else { + let Some(registration) = cip509.metadata() else { cip509.report().missing_field("metadata", context); return None; }; @@ -408,7 +408,7 @@ impl RegistrationChainInner { let mut role_data_record = HashMap::new(); let point_tx_idx = cip509.origin().clone(); update_role_data( - ®istration, + registration, &mut role_data_history, &mut role_data_record, &point_tx_idx, @@ -423,7 +423,7 @@ impl RegistrationChainInner { let Some(signing_pk) = role0_data .signing_keys() .last() - .and_then(|key| key.data().extract_pk()) + .and_then(|key| key.data().extract_public_key()) else { cip509 .report() @@ -643,7 +643,7 @@ impl RegistrationChainInner { /// Get the latest signing public key for a role. /// Returns the public key and the rotation,`None` if not found. #[must_use] - pub fn get_latest_signing_pk_for_role( + pub fn get_latest_signing_public_key_for_role( &self, role: RoleId, ) -> Option<(VerifyingKey, KeyRotation)> { @@ -651,7 +651,7 @@ impl RegistrationChainInner { rdr.signing_keys().last().and_then(|key| { let rotation = KeyRotation::from_latest_rotation(rdr.signing_keys()); - key.data().extract_pk().map(|pk| (pk, rotation)) + key.data().extract_public_key().map(|pk| (pk, rotation)) }) }) } @@ -659,7 +659,7 @@ impl RegistrationChainInner { /// Get the latest encryption public key for a role. /// Returns the public key and the rotation, `None` if not found. #[must_use] - pub fn get_latest_encryption_pk_for_role( + pub fn get_latest_encryption_public_key_for_role( &self, role: RoleId, ) -> Option<(VerifyingKey, KeyRotation)> { @@ -667,7 +667,7 @@ impl RegistrationChainInner { rdr.encryption_keys().last().and_then(|key| { let rotation = KeyRotation::from_latest_rotation(rdr.encryption_keys()); - key.data().extract_pk().map(|pk| (pk, rotation)) + key.data().extract_public_key().map(|pk| (pk, rotation)) }) }) } @@ -731,7 +731,7 @@ fn check_validation_signature( /// Checks that a new registration doesn't contain a signing key that was used by any /// other chain. -async fn check_signing_pk( +async fn check_signing_public_key( cat_id: &CatalystId, cip509: &Cip509, state: &State, @@ -740,8 +740,10 @@ where State: RbacChainsState, { for role in cip509.all_roles() { - if let Some(key) = cip509.signing_pk_for_role(role) - && let Some(previous) = state.chain_catalyst_id_from_signing_pk(&key).await? + if let Some(key) = cip509.signing_public_key_for_role(role) + && let Some(previous) = state + .chain_catalyst_id_from_signing_public_key(&key) + .await? && &previous != cat_id { cip509.report().functional_validation( @@ -835,7 +837,7 @@ mod test { assert_eq!(role_0_data.extended_data().len(), 2); let (_k, r) = update - .get_latest_signing_pk_for_role(RoleId::Role0) + .get_latest_signing_public_key_for_role(RoleId::Role0) .unwrap(); assert_eq!(r, KeyRotation::from(1)); assert!( From c1e1293319f7ec84b0295e274351ce386bf51f93 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Thu, 20 Nov 2025 21:37:59 +0700 Subject: [PATCH 08/16] wip --- rust/rbac-registration/src/cardano/state.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rust/rbac-registration/src/cardano/state.rs b/rust/rbac-registration/src/cardano/state.rs index 1537ac44f3..ac427421a6 100644 --- a/rust/rbac-registration/src/cardano/state.rs +++ b/rust/rbac-registration/src/cardano/state.rs @@ -38,8 +38,11 @@ pub trait RbacChainsState { /// Update the chain by "taking" the given `StakeAddress` for the corresponding /// RBAC chain's by the given `CatalystId`. - fn take_stake_address_from_chains( + fn take_stake_address_from_chains( &mut self, - addresses: impl IntoIterator + Send, - ) -> impl Future> + Send; + addresses: I, + ) -> impl Future> + Send + where + I: IntoIterator + Send, + ::IntoIter: Send; } From 68c16bbc72b83ca1b05928385cb2c876195df0c2 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Fri, 21 Nov 2025 10:36:48 +0700 Subject: [PATCH 09/16] fix clippy --- .../src/cardano/cip509/utils/cip134_uri_set.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs b/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs index 0be7abe67e..a5359d6c9d 100644 --- a/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs +++ b/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs @@ -88,7 +88,6 @@ impl Cip0134UriSet { } /// Returns a list of URIs by the given role. - #[must_use] pub(crate) fn role_uris( &self, role: usize, @@ -105,7 +104,6 @@ impl Cip0134UriSet { } /// Returns a set of stake addresses by the given role. - #[must_use] pub(crate) fn role_stake_addresses( &self, role: usize, From f150a4083428af0e29c08348a8ae935e3112b0c8 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Tue, 25 Nov 2025 13:39:58 +0700 Subject: [PATCH 10/16] comments fixes --- .../rbac-registration/src/cardano/cip509/cip509.rs | 2 +- .../src/cardano/cip509/utils/cip134_uri_set.rs | 10 +++++----- rust/rbac-registration/src/cardano/state.rs | 4 ++-- .../src/registration/cardano/mod.rs | 14 +++++++------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/rust/rbac-registration/src/cardano/cip509/cip509.rs b/rust/rbac-registration/src/cardano/cip509/cip509.rs index 783123884b..080df8d474 100644 --- a/rust/rbac-registration/src/cardano/cip509/cip509.rs +++ b/rust/rbac-registration/src/cardano/cip509/cip509.rs @@ -349,7 +349,7 @@ impl Cip509 { #[must_use] pub fn stake_addresses(&self) -> HashSet { self.certificate_uris() - .map(Cip0134UriSet::stake_addresses) + .map(Cip0134UriSet::active_stake_addresses) .unwrap_or_default() } diff --git a/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs b/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs index a5359d6c9d..4981cebb67 100644 --- a/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs +++ b/rust/rbac-registration/src/cardano/cip509/utils/cip134_uri_set.rs @@ -120,7 +120,7 @@ impl Cip0134UriSet { /// Returns a set of all active (without taken) stake addresses. #[must_use] - pub fn stake_addresses(&self) -> HashSet { + pub fn active_stake_addresses(&self) -> HashSet { self.values() .filter_map(|uri| { match uri.address() { @@ -203,7 +203,7 @@ impl Cip0134UriSet { metadata .certificate_uris - .stake_addresses() + .active_stake_addresses() .iter() .for_each(|v| { taken_stake_addresses.remove(v); @@ -225,10 +225,10 @@ impl Cip0134UriSet { self, reg: &Cip509RbacMetadata, ) -> Self { - let current_stake_addresses = self.stake_addresses(); + let current_stake_addresses = self.active_stake_addresses(); let latest_taken_stake_addresses = reg .certificate_uris - .stake_addresses() + .active_stake_addresses() .into_iter() .filter(|v| current_stake_addresses.contains(v)); @@ -399,7 +399,7 @@ mod tests { assert!(set.c_uris().is_empty()); assert_eq!(set.role_uris(0).count(), 1); assert_eq!(set.role_stake_addresses(0).len(), 1); - assert_eq!(set.stake_addresses().len(), 1); + assert_eq!(set.active_stake_addresses().len(), 1); let x_uris = set.x_uris(); assert_eq!(x_uris.len(), 1); diff --git a/rust/rbac-registration/src/cardano/state.rs b/rust/rbac-registration/src/cardano/state.rs index ac427421a6..a6d99cc9de 100644 --- a/rust/rbac-registration/src/cardano/state.rs +++ b/rust/rbac-registration/src/cardano/state.rs @@ -29,14 +29,14 @@ pub trait RbacChainsState { addr: &StakeAddress, ) -> impl Future> + Send; - /// Returns a corresponding to the RBAC chain's Catalyst ID corresponding by the given + /// Returns the Catalyst ID associated with the RBAC chain for the given signing public key /// signing public key. fn chain_catalyst_id_from_signing_public_key( &self, key: &VerifyingKey, ) -> impl Future>> + Send; - /// Update the chain by "taking" the given `StakeAddress` for the corresponding + /// Update currently assosiated with the stake addresses chains by "taking" the given `StakeAddress` for the corresponding /// RBAC chain's by the given `CatalystId`. fn take_stake_address_from_chains( &mut self, diff --git a/rust/rbac-registration/src/registration/cardano/mod.rs b/rust/rbac-registration/src/registration/cardano/mod.rs index 774724d6d8..02cf6dfa8c 100644 --- a/rust/rbac-registration/src/registration/cardano/mod.rs +++ b/rust/rbac-registration/src/registration/cardano/mod.rs @@ -121,10 +121,10 @@ impl RegistrationChain { }; // Check that addresses from the new registration aren't used in other chains. - let previous_addresses = self.stake_addresses(); - let reg_addresses = cip509.stake_addresses(); - let new_addresses: Vec<_> = reg_addresses.difference(&previous_addresses).collect(); - for address in &new_addresses { + let previous_stake_addresses = self.stake_addresses(); + let reg_stake_addresses = cip509.stake_addresses(); + let new_stake_addresses: Vec<_> = reg_stake_addresses.difference(&previous_stake_addresses).collect(); + for address in &new_stake_addresses { if state.is_stake_address_used(address).await? { cip509.report().functional_validation( &format!("{address} stake address is already used"), @@ -257,7 +257,7 @@ impl RegistrationChain { /// Get the latest encryption public key for a role. /// Returns the public key and the rotation, `None` if not found. #[must_use] - pub fn get_latest_encryption_pk_for_role( + pub fn get_latest_encryption_public_key_for_role( &self, role: RoleId, ) -> Option<(VerifyingKey, KeyRotation)> { @@ -329,7 +329,7 @@ impl RegistrationChain { /// Returns all stake addresses associated to this chain. #[must_use] pub fn stake_addresses(&self) -> HashSet { - self.inner.certificate_uris.stake_addresses() + self.inner.certificate_uris.active_stake_addresses() } /// Returns the latest know applied registration's `PointTxnIdx`. @@ -748,7 +748,7 @@ where { cip509.report().functional_validation( &format!("An update to {cat_id} registration chain uses the same public key ({key:?}) as {previous} chain"), - "It isn't allowed to use role 0 signing (certificate subject public) key in different chains", + "It isn't allowed to use signing (certificate subject public) key in different chains", ); } } From ef23920adc3a6e1b3678bcaeabd342b91d7ed57f Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Tue, 25 Nov 2025 13:57:52 +0700 Subject: [PATCH 11/16] rename state to Provider --- rust/rbac-registration/src/cardano/mod.rs | 2 +- .../src/cardano/{state.rs => provider.rs} | 42 ++++++++++--------- .../src/registration/cardano/mod.rs | 32 ++++++-------- 3 files changed, 37 insertions(+), 39 deletions(-) rename rust/rbac-registration/src/cardano/{state.rs => provider.rs} (53%) diff --git a/rust/rbac-registration/src/cardano/mod.rs b/rust/rbac-registration/src/cardano/mod.rs index 11784792f4..896998fc23 100644 --- a/rust/rbac-registration/src/cardano/mod.rs +++ b/rust/rbac-registration/src/cardano/mod.rs @@ -1,4 +1,4 @@ //! Cardano module pub mod cip509; -pub mod state; +pub mod provider; diff --git a/rust/rbac-registration/src/cardano/state.rs b/rust/rbac-registration/src/cardano/provider.rs similarity index 53% rename from rust/rbac-registration/src/cardano/state.rs rename to rust/rbac-registration/src/cardano/provider.rs index a6d99cc9de..c381fc754f 100644 --- a/rust/rbac-registration/src/cardano/state.rs +++ b/rust/rbac-registration/src/cardano/provider.rs @@ -1,5 +1,4 @@ -//! Cardano RBAC state traits, which are used during different stateful validation -//! procedures. +//! Cardano RBAC provider trait, which are used during different stateful validation procedures. use std::future::Future; @@ -9,8 +8,8 @@ use ed25519_dalek::VerifyingKey; use crate::registration::cardano::RegistrationChain; -/// RBAC chains state trait -pub trait RbacChainsState { +/// RBAC chains provider trait +pub trait RbacChainsProvider: Send + Sync { /// Returns RBAC chain for the given Catalyst ID. fn chain( &self, @@ -21,28 +20,33 @@ pub trait RbacChainsState { fn is_chain_known( &self, id: &CatalystId, - ) -> impl Future> + Send; + ) -> impl Future> + Send { + async { self.chain(id).await.map(|v| v.is_some()) } + } + + /// Returns the Catalyst ID associated with the RBAC chain for the given stake + /// address. + fn chain_catalyst_id_from_stake_address( + &self, + addr: &StakeAddress, + ) -> impl Future>> + Send; /// Returns `true` if a provided address already used by any RBAC chain. fn is_stake_address_used( &self, addr: &StakeAddress, - ) -> impl Future> + Send; - - /// Returns the Catalyst ID associated with the RBAC chain for the given signing public key - /// signing public key. + ) -> impl Future> + Send { + async { + self.chain_catalyst_id_from_stake_address(addr) + .await + .map(|v| v.is_some()) + } + } + + /// Returns the Catalyst ID associated with the RBAC chain for the given signing + /// public key. fn chain_catalyst_id_from_signing_public_key( &self, key: &VerifyingKey, ) -> impl Future>> + Send; - - /// Update currently assosiated with the stake addresses chains by "taking" the given `StakeAddress` for the corresponding - /// RBAC chain's by the given `CatalystId`. - fn take_stake_address_from_chains( - &mut self, - addresses: I, - ) -> impl Future> + Send - where - I: IntoIterator + Send, - ::IntoIter: Send; } diff --git a/rust/rbac-registration/src/registration/cardano/mod.rs b/rust/rbac-registration/src/registration/cardano/mod.rs index 02cf6dfa8c..d5348a62bb 100644 --- a/rust/rbac-registration/src/registration/cardano/mod.rs +++ b/rust/rbac-registration/src/registration/cardano/mod.rs @@ -27,7 +27,7 @@ use crate::cardano::{ CertKeyHash, CertOrPk, Cip0134UriSet, Cip509, PaymentHistory, PointData, PointTxnIdx, RoleData, RoleDataRecord, ValidationSignature, }, - state::RbacChainsState, + provider::RbacChainsProvider, }; /// Registration chains. @@ -50,12 +50,10 @@ impl RegistrationChain { /// # Errors /// - Propagates any I/O or provider-level errors encountered while checking key /// ownership (e.g., database lookup failures). - pub async fn new( + pub async fn new( cip509: &Cip509, - state: &mut State, + provider: &impl RbacChainsProvider, ) -> anyhow::Result> - where - State: RbacChainsState, { let Some(new_chain) = Self::new_stateless(cip509) else { return Ok(None); @@ -64,24 +62,20 @@ impl RegistrationChain { // Verify that a Catalyst ID of this chain is unique. { let cat_id = new_chain.catalyst_id(); - if state.is_chain_known(cat_id).await? { + if provider.is_chain_known(cat_id).await? { cip509.report().functional_validation( &format!("{} is already used", cat_id.as_short_id()), "It isn't allowed to use same Catalyst ID (certificate subject public key) in multiple registration chains", ); } - check_signing_public_key(cat_id, cip509, state).await?; + check_signing_public_key(cat_id, cip509, provider).await?; } if cip509.report().is_problematic() { return Ok(None); } - state - .take_stake_address_from_chains(cip509.stake_addresses()) - .await?; - Ok(Some(new_chain)) } @@ -108,13 +102,11 @@ impl RegistrationChain { /// # Errors /// - Propagates any I/O or provider-level errors encountered while checking key /// ownership (e.g., database lookup failures). - pub async fn update( + pub async fn update( &self, cip509: &Cip509, - state: &State, + provider: &impl RbacChainsProvider, ) -> anyhow::Result> - where - State: RbacChainsState, { let Some(new_chain) = self.update_stateless(cip509) else { return Ok(None); @@ -123,9 +115,11 @@ impl RegistrationChain { // Check that addresses from the new registration aren't used in other chains. let previous_stake_addresses = self.stake_addresses(); let reg_stake_addresses = cip509.stake_addresses(); - let new_stake_addresses: Vec<_> = reg_stake_addresses.difference(&previous_stake_addresses).collect(); + let new_stake_addresses: Vec<_> = reg_stake_addresses + .difference(&previous_stake_addresses) + .collect(); for address in &new_stake_addresses { - if state.is_stake_address_used(address).await? { + if provider.is_stake_address_used(address).await? { cip509.report().functional_validation( &format!("{address} stake address is already used"), "It isn't allowed to use same stake address in multiple registration chains, if its not a new chain", @@ -133,7 +127,7 @@ impl RegistrationChain { } } - check_signing_public_key(self.catalyst_id(), cip509, state).await?; + check_signing_public_key(self.catalyst_id(), cip509, provider).await?; if cip509.report().is_problematic() { Ok(None) @@ -737,7 +731,7 @@ async fn check_signing_public_key( state: &State, ) -> anyhow::Result<()> where - State: RbacChainsState, + State: RbacChainsProvider, { for role in cip509.all_roles() { if let Some(key) = cip509.signing_public_key_for_role(role) From 608c38800dfbc290a9353c04d380a4418cd3fb04 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Tue, 25 Nov 2025 14:07:58 +0700 Subject: [PATCH 12/16] fix fmt --- rust/rbac-registration/src/cardano/provider.rs | 3 ++- rust/rbac-registration/src/registration/cardano/mod.rs | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/rust/rbac-registration/src/cardano/provider.rs b/rust/rbac-registration/src/cardano/provider.rs index c381fc754f..0f043513e6 100644 --- a/rust/rbac-registration/src/cardano/provider.rs +++ b/rust/rbac-registration/src/cardano/provider.rs @@ -1,4 +1,5 @@ -//! Cardano RBAC provider trait, which are used during different stateful validation procedures. +//! Cardano RBAC provider trait, which are used during different stateful validation +//! procedures. use std::future::Future; diff --git a/rust/rbac-registration/src/registration/cardano/mod.rs b/rust/rbac-registration/src/registration/cardano/mod.rs index d5348a62bb..ff4a3e1070 100644 --- a/rust/rbac-registration/src/registration/cardano/mod.rs +++ b/rust/rbac-registration/src/registration/cardano/mod.rs @@ -53,8 +53,7 @@ impl RegistrationChain { pub async fn new( cip509: &Cip509, provider: &impl RbacChainsProvider, - ) -> anyhow::Result> - { + ) -> anyhow::Result> { let Some(new_chain) = Self::new_stateless(cip509) else { return Ok(None); }; @@ -106,8 +105,7 @@ impl RegistrationChain { &self, cip509: &Cip509, provider: &impl RbacChainsProvider, - ) -> anyhow::Result> - { + ) -> anyhow::Result> { let Some(new_chain) = self.update_stateless(cip509) else { return Ok(None); }; From 07d2dae796ca9624f4ed2d96b52782cc2d043fa6 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Tue, 25 Nov 2025 16:32:56 +0700 Subject: [PATCH 13/16] cleanup `check_signing_public_key` --- rust/rbac-registration/src/registration/cardano/mod.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rust/rbac-registration/src/registration/cardano/mod.rs b/rust/rbac-registration/src/registration/cardano/mod.rs index ff4a3e1070..411d0d15cd 100644 --- a/rust/rbac-registration/src/registration/cardano/mod.rs +++ b/rust/rbac-registration/src/registration/cardano/mod.rs @@ -723,13 +723,11 @@ fn check_validation_signature( /// Checks that a new registration doesn't contain a signing key that was used by any /// other chain. -async fn check_signing_public_key( +async fn check_signing_public_key( cat_id: &CatalystId, cip509: &Cip509, - state: &State, + state: &impl RbacChainsProvider, ) -> anyhow::Result<()> -where - State: RbacChainsProvider, { for role in cip509.all_roles() { if let Some(key) = cip509.signing_public_key_for_role(role) From ff26c8a3cfdecfe3ef41b9ec578295e1efb749dc Mon Sep 17 00:00:00 2001 From: Alex Pozhylenkov Date: Tue, 25 Nov 2025 16:39:20 +0700 Subject: [PATCH 14/16] Update rust/rbac-registration/src/cardano/cip509/cip509.rs Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> --- rust/rbac-registration/src/cardano/cip509/cip509.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/rbac-registration/src/cardano/cip509/cip509.rs b/rust/rbac-registration/src/cardano/cip509/cip509.rs index 080df8d474..9a6a56a9ad 100644 --- a/rust/rbac-registration/src/cardano/cip509/cip509.rs +++ b/rust/rbac-registration/src/cardano/cip509/cip509.rs @@ -238,7 +238,7 @@ impl Cip509 { /// Returns signing public key for a role. /// Would return only signing public keys for the present certificates, - /// if certificate marked as deleted or undefined it would be skipped. + /// if certificates or simple public key is marked as deleted or undefined it would be skipped. #[must_use] pub fn signing_public_key_for_role( &self, From 9bcec5777ce54e3c45626408537cac3d1933c88d Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Tue, 25 Nov 2025 21:37:11 +0700 Subject: [PATCH 15/16] fix fmt --- rust/rbac-registration/src/registration/cardano/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rust/rbac-registration/src/registration/cardano/mod.rs b/rust/rbac-registration/src/registration/cardano/mod.rs index 411d0d15cd..fde962a2c4 100644 --- a/rust/rbac-registration/src/registration/cardano/mod.rs +++ b/rust/rbac-registration/src/registration/cardano/mod.rs @@ -727,8 +727,7 @@ async fn check_signing_public_key( cat_id: &CatalystId, cip509: &Cip509, state: &impl RbacChainsProvider, -) -> anyhow::Result<()> -{ +) -> anyhow::Result<()> { for role in cip509.all_roles() { if let Some(key) = cip509.signing_public_key_for_role(role) && let Some(previous) = state From 74f8cd0c5b646200074c323da294f3c74f3450eb Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Tue, 25 Nov 2025 22:11:36 +0700 Subject: [PATCH 16/16] fix fmt --- rust/rbac-registration/src/cardano/cip509/cip509.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/rbac-registration/src/cardano/cip509/cip509.rs b/rust/rbac-registration/src/cardano/cip509/cip509.rs index 9a6a56a9ad..c1ef53a355 100644 --- a/rust/rbac-registration/src/cardano/cip509/cip509.rs +++ b/rust/rbac-registration/src/cardano/cip509/cip509.rs @@ -238,7 +238,8 @@ impl Cip509 { /// Returns signing public key for a role. /// Would return only signing public keys for the present certificates, - /// if certificates or simple public key is marked as deleted or undefined it would be skipped. + /// if certificates or simple public key is marked as deleted or undefined it would be + /// skipped. #[must_use] pub fn signing_public_key_for_role( &self,