Skip to content

Commit e5a8e72

Browse files
committed
chore: isolation
1 parent 80bda85 commit e5a8e72

File tree

1 file changed

+190
-77
lines changed
  • rust/rbac-registration/src/registration/cardano

1 file changed

+190
-77
lines changed

rust/rbac-registration/src/registration/cardano/mod.rs

Lines changed: 190 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -92,58 +92,21 @@ impl RegistrationChain {
9292
/// to update the existing chain using that previous transaction.
9393
/// Otherwise, it starts a new chain from the provided registration.
9494
pub async fn update<Provider>(
95+
&self,
9596
reg: Cip509,
9697
provider: &Provider,
9798
) -> Option<Self>
9899
where
99100
Provider: RbacRegistrationProvider,
100101
{
101-
if reg.previous_transaction().is_some() {
102-
RegistrationChainInner::update(reg, provider).await
102+
let new_inner = if reg.previous_transaction().is_some() {
103+
self.inner.update(reg, provider).await?
103104
} else {
104-
RegistrationChainInner::new(reg, provider).await
105-
}
106-
}
107-
108-
/// Validates that none of the signing keys in a given RBAC registration chain
109-
/// have been used by any other existing chain, ensuring global key uniqueness
110-
/// across all Catalyst registrations.
111-
///
112-
/// # Returns
113-
/// Returns a [`Result<HashSet<VerifyingKey>>`] containing all unique public keys
114-
/// extracted from the registration chain if validation passes successfully.
115-
///
116-
/// # Errors
117-
/// - Propagates any I/O or provider-level errors encountered while checking key
118-
/// ownership (e.g., database lookup failures).
119-
pub async fn validate_public_keys<Provider>(
120-
&self,
121-
report: &ProblemReport,
122-
provider: &Provider,
123-
) -> anyhow::Result<bool>
124-
where
125-
Provider: RbacRegistrationProvider,
126-
{
127-
let roles: Vec<_> = self.role_data_history().keys().collect();
128-
let catalyst_id = self.catalyst_id().as_short_id();
129-
130-
let mut result = true;
131-
for role in roles {
132-
if let Some((key, _)) = self.get_latest_signing_pk_for_role(role) {
133-
if let Some(previous) = provider.catalyst_id_from_public_key(key).await? {
134-
if previous != catalyst_id {
135-
report.functional_validation(
136-
&format!("An update to {catalyst_id} registration chain uses the same public key ({key:?}) as {previous} chain"),
137-
"It isn't allowed to use role 0 signing (certificate subject public) key in different chains",
138-
);
139-
140-
result = false
141-
}
142-
}
143-
}
144-
}
145-
146-
Ok(result)
105+
RegistrationChainInner::new(reg, provider).await?
106+
};
107+
Some(Self {
108+
inner: Arc::new(new_inner),
109+
})
147110
}
148111

149112
/// Returns a Catalyst ID.
@@ -231,13 +194,7 @@ impl RegistrationChain {
231194
&self,
232195
role: &RoleId,
233196
) -> Option<(VerifyingKey, KeyRotation)> {
234-
self.inner.role_data_record.get(role).and_then(|rdr| {
235-
rdr.signing_keys().last().and_then(|key| {
236-
let rotation = KeyRotation::from_latest_rotation(rdr.signing_keys());
237-
238-
key.data().extract_pk().map(|pk| (pk, rotation))
239-
})
240-
})
197+
self.inner.get_latest_signing_pk_for_role(role)
241198
}
242199

243200
/// Get the latest encryption public key for a role.
@@ -247,13 +204,7 @@ impl RegistrationChain {
247204
&self,
248205
role: &RoleId,
249206
) -> Option<(VerifyingKey, KeyRotation)> {
250-
self.inner.role_data_record.get(role).and_then(|rdr| {
251-
rdr.encryption_keys().last().and_then(|key| {
252-
let rotation = KeyRotation::from_latest_rotation(rdr.encryption_keys());
253-
254-
key.data().extract_pk().map(|pk| (pk, rotation))
255-
})
256-
})
207+
self.inner.get_latest_encryption_pk_for_role(role)
257208
}
258209

259210
/// Get signing public key for a role with given rotation.
@@ -557,43 +508,205 @@ impl RegistrationChainInner {
557508
/// Attempts to update an existing RBAC registration chain
558509
/// with a new CIP-509 registration, validating address and key usage consistency.
559510
pub async fn update<Provider>(
511+
&self,
560512
reg: Cip509,
561513
provider: &Provider,
562-
) -> Option<RegistrationChain>
514+
) -> Option<Self>
563515
where
564516
Provider: RbacRegistrationProvider,
565517
{
566-
// perform provider lookups and validations here
567-
// then build new RegistrationChain using update_stateless
568518
let previous_txn = reg.previous_transaction()?;
569-
let catalyst_id = provider
570-
.catalyst_id_from_txn_id(previous_txn)
571-
.await
572-
.ok()??;
519+
let report = reg.report().to_owned();
520+
521+
// Find a chain this registration belongs to.
522+
let Some(catalyst_id) = provider.catalyst_id_from_txn_id(previous_txn).await.ok()? else {
523+
// We are unable to determine a Catalyst ID, so there is no sense to update the problem
524+
// report because we would be unable to store this registration anyway.
525+
return None;
526+
};
573527
let chain = provider.chain(catalyst_id.clone()).await.ok()??;
574528

575-
let latest_signing_pk = chain.get_latest_signing_pk_for_role(&RoleId::Role0)?;
576-
let (signing_pk, _) = latest_signing_pk;
529+
// Check that addresses from the new registration aren't used in other chains.
530+
let previous_addresses = chain.stake_addresses();
531+
let reg_addresses = reg.stake_addresses();
532+
let new_addresses: Vec<_> = reg_addresses.difference(&previous_addresses).collect();
533+
for address in &new_addresses {
534+
match provider
535+
.catalyst_id_from_stake_address(address)
536+
.await
537+
.ok()?
538+
{
539+
None => {
540+
// All good: the address wasn't used before.
541+
},
542+
Some(_) => {
543+
report.functional_validation(
544+
&format!("{address} stake addresses is already used"),
545+
"It isn't allowed to use same stake address in multiple registration chains",
546+
);
547+
},
548+
}
549+
}
577550

578-
let new_inner = chain.inner.update_stateless(reg.clone(), signing_pk)?;
579-
Some(RegistrationChain {
580-
inner: Arc::new(new_inner),
581-
})
551+
// Try to add a new registration to the chain.
552+
let (signing_pk, _) = self.get_latest_signing_pk_for_role(&RoleId::Role0)?;
553+
let new_chain = chain.inner.update_stateless(reg.clone(), signing_pk)?;
554+
555+
// Check that new public keys aren't used by other chains.
556+
let valid_pks = new_chain
557+
.validate_public_keys(&report, provider)
558+
.await
559+
.ok()?;
560+
561+
// Return an error if any issues were recorded in the report.
562+
if report.is_problematic() || !valid_pks {
563+
return None;
564+
}
565+
566+
Some(new_chain)
582567
}
583568

584569
/// Attempts to initialize a new RBAC registration chain
585570
/// from a given CIP-509 registration, ensuring uniqueness of Catalyst ID, stake
586571
/// addresses, and associated public keys.
587572
pub async fn new<Provider>(
588573
reg: Cip509,
589-
_provider: &Provider,
590-
) -> Option<RegistrationChain>
574+
provider: &Provider,
575+
) -> Option<Self>
591576
where
592577
Provider: RbacRegistrationProvider,
593578
{
594-
let inner = RegistrationChainInner::new_stateless(reg)?;
595-
Some(RegistrationChain {
596-
inner: Arc::new(inner),
579+
let report = reg.report().to_owned();
580+
581+
// Try to start a new chain.
582+
let new_chain = Self::new_stateless(reg)?;
583+
// Verify that a Catalyst ID of this chain is unique.
584+
let catalyst_id = new_chain.catalyst_id.as_short_id();
585+
if provider.is_chain_known(catalyst_id.clone()).await.ok()? {
586+
report.functional_validation(
587+
&format!("{catalyst_id} is already used"),
588+
"It isn't allowed to use same Catalyst ID (certificate subject public key) in multiple registration chains",
589+
);
590+
return None;
591+
}
592+
593+
// Validate stake addresses.
594+
let new_addresses = new_chain.certificate_uris.stake_addresses();
595+
let mut updated_chains: HashMap<_, HashSet<StakeAddress>> = HashMap::new();
596+
for address in &new_addresses {
597+
if let Some(id) = provider
598+
.catalyst_id_from_stake_address(address)
599+
.await
600+
.ok()?
601+
{
602+
// If an address is used in existing chain then a new chain must have different role
603+
// 0 signing key.
604+
let previous_chain = provider.chain(id.clone()).await.ok()??;
605+
if previous_chain.get_latest_signing_pk_for_role(&RoleId::Role0)
606+
== new_chain.get_latest_signing_pk_for_role(&RoleId::Role0)
607+
{
608+
report.functional_validation(
609+
&format!("A new registration ({catalyst_id}) uses the same public key as the previous one ({})",
610+
previous_chain.catalyst_id().as_short_id()
611+
),
612+
"It is only allowed to override the existing chain by using different public key",
613+
);
614+
} else {
615+
// The new root registration "takes" an address(es) from the existing chain, so
616+
// that chain needs to be updated.
617+
updated_chains
618+
.entry(id)
619+
.and_modify(|e| {
620+
e.insert(address.clone());
621+
})
622+
.or_insert([address.clone()].into_iter().collect());
623+
}
624+
}
625+
}
626+
627+
// Check that new public keys aren't used by other chains.
628+
let valid_pks = new_chain
629+
.validate_public_keys(&report, provider)
630+
.await
631+
.ok()?;
632+
633+
if report.is_problematic() || !valid_pks {
634+
return None;
635+
}
636+
637+
Some(new_chain)
638+
}
639+
640+
/// Validates that none of the signing keys in a given RBAC registration chain
641+
/// have been used by any other existing chain, ensuring global key uniqueness
642+
/// across all Catalyst registrations.
643+
///
644+
/// # Returns
645+
/// Returns a [`Result<HashSet<VerifyingKey>>`] containing all unique public keys
646+
/// extracted from the registration chain if validation passes successfully.
647+
///
648+
/// # Errors
649+
/// - Propagates any I/O or provider-level errors encountered while checking key
650+
/// ownership (e.g., database lookup failures).
651+
pub async fn validate_public_keys<Provider>(
652+
&self,
653+
report: &ProblemReport,
654+
provider: &Provider,
655+
) -> anyhow::Result<bool>
656+
where
657+
Provider: RbacRegistrationProvider,
658+
{
659+
let roles: Vec<_> = self.role_data_history.keys().collect();
660+
let catalyst_id = self.catalyst_id.as_short_id();
661+
662+
let mut result = true;
663+
for role in roles {
664+
if let Some((key, _)) = self.get_latest_signing_pk_for_role(role) {
665+
if let Some(previous) = provider.catalyst_id_from_public_key(key).await? {
666+
if previous != catalyst_id {
667+
report.functional_validation(
668+
&format!("An update to {catalyst_id} registration chain uses the same public key ({key:?}) as {previous} chain"),
669+
"It isn't allowed to use role 0 signing (certificate subject public) key in different chains",
670+
);
671+
672+
result = false
673+
}
674+
}
675+
}
676+
}
677+
678+
Ok(result)
679+
}
680+
681+
/// Get the latest signing public key for a role.
682+
/// Returns the public key and the rotation,`None` if not found.
683+
#[must_use]
684+
pub fn get_latest_signing_pk_for_role(
685+
&self,
686+
role: &RoleId,
687+
) -> Option<(VerifyingKey, KeyRotation)> {
688+
self.role_data_record.get(role).and_then(|rdr| {
689+
rdr.signing_keys().last().and_then(|key| {
690+
let rotation = KeyRotation::from_latest_rotation(rdr.signing_keys());
691+
692+
key.data().extract_pk().map(|pk| (pk, rotation))
693+
})
694+
})
695+
}
696+
697+
/// Get the latest encryption public key for a role.
698+
/// Returns the public key and the rotation, `None` if not found.
699+
#[must_use]
700+
pub fn get_latest_encryption_pk_for_role(
701+
&self,
702+
role: &RoleId,
703+
) -> Option<(VerifyingKey, KeyRotation)> {
704+
self.role_data_record.get(role).and_then(|rdr| {
705+
rdr.encryption_keys().last().and_then(|key| {
706+
let rotation = KeyRotation::from_latest_rotation(rdr.encryption_keys());
707+
708+
key.data().extract_pk().map(|pk| (pk, rotation))
709+
})
597710
})
598711
}
599712
}

0 commit comments

Comments
 (0)