@@ -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