@@ -7,14 +7,16 @@ use std::{
77 sync:: Arc ,
88} ;
99
10- use anyhow:: bail;
10+ use anyhow:: { bail, Context } ;
1111use c509_certificate:: c509:: C509 ;
1212use cardano_blockchain_types:: { StakeAddress , TransactionId } ;
1313use catalyst_types:: {
1414 catalyst_id:: { key_rotation:: KeyRotation , role_index:: RoleId , CatalystId } ,
15+ conversion:: zero_out_last_n_bytes,
16+ problem_report:: ProblemReport ,
1517 uuid:: UuidV4 ,
1618} ;
17- use ed25519_dalek:: VerifyingKey ;
19+ use ed25519_dalek:: { Signature , VerifyingKey } ;
1820use tracing:: error;
1921use update_rbac:: {
2022 revocations_list, update_c509_certs, update_public_keys, update_role_data, update_x509_certs,
@@ -23,7 +25,7 @@ use x509_cert::certificate::Certificate as X509Certificate;
2325
2426use crate :: cardano:: cip509:: {
2527 CertKeyHash , CertOrPk , Cip0134UriSet , Cip509 , PaymentHistory , PointData , RoleData ,
26- RoleDataRecord ,
28+ RoleDataRecord , ValidationSignature ,
2729} ;
2830
2931/// Registration chains.
@@ -43,7 +45,7 @@ impl RegistrationChain {
4345 /// # Errors
4446 ///
4547 /// Returns an error if data is invalid
46- pub fn new ( cip509 : Cip509 ) -> anyhow:: Result < Self > {
48+ pub fn new ( cip509 : & Cip509 ) -> anyhow:: Result < Self > {
4749 let inner = RegistrationChainInner :: new ( cip509) ?;
4850
4951 Ok ( Self {
@@ -59,9 +61,17 @@ impl RegistrationChain {
5961 /// # Errors
6062 ///
6163 /// Returns an error if data is invalid
62- pub fn update ( & self , cip509 : Cip509 ) -> anyhow:: Result < Self > {
63- let new_inner = self . inner . update ( cip509) ?;
64-
64+ pub fn update ( & self , cip509 : & Cip509 ) -> anyhow:: Result < Self > {
65+ let latest_signing_pk = self . get_latest_signing_pk_for_role ( & RoleId :: Role0 ) ;
66+ let new_inner = if let Some ( ( signing_pk, _) ) = latest_signing_pk {
67+ self . inner . update ( cip509, signing_pk) ?
68+ } else {
69+ cip509. report ( ) . missing_field (
70+ "latest signing key for role 0" ,
71+ "cannot perform signature validation during Registration Chain update" ,
72+ ) ;
73+ bail ! ( "No latest signing key found for role 0, cannot perform signature validation" )
74+ } ;
6575 Ok ( Self {
6676 inner : Arc :: new ( new_inner) ,
6777 } )
@@ -264,18 +274,25 @@ impl RegistrationChainInner {
264274 /// # Errors
265275 ///
266276 /// Returns an error if data is invalid
267- fn new ( cip509 : Cip509 ) -> anyhow:: Result < Self > {
277+ fn new ( cip509 : & Cip509 ) -> anyhow:: Result < Self > {
278+ let context = "Registration Chain new" ;
268279 // Should be chain root, return immediately if not
269280 if cip509. previous_transaction ( ) . is_some ( ) {
281+ cip509
282+ . report ( )
283+ . invalid_value ( "previous transaction ID" , "None" , "Some" , context) ;
270284 bail ! ( "Invalid chain root, previous transaction ID should be None." ) ;
271285 }
272286 let Some ( catalyst_id) = cip509. catalyst_id ( ) . cloned ( ) else {
287+ cip509. report ( ) . missing_field ( "catalyst id" , context) ;
273288 bail ! ( "Invalid chain root, catalyst id should be present." ) ;
274289 } ;
275290
276291 let point_tx_idx = cip509. origin ( ) . clone ( ) ;
277292 let current_tx_id_hash = cip509. txn_hash ( ) ;
278- let ( purpose, registration, payment_history) = match cip509. consume ( ) {
293+ let validation_signature = cip509. validation_signature ( ) . cloned ( ) ;
294+ let raw_aux_data = cip509. raw_aux_data ( ) . to_vec ( ) ;
295+ let ( purpose, registration, payment_history) = match cip509. clone ( ) . consume ( ) {
279296 Ok ( v) => v,
280297 Err ( e) => {
281298 let error = format ! ( "Invalid Cip509: {e:?}" ) ;
@@ -284,6 +301,42 @@ impl RegistrationChainInner {
284301 } ,
285302 } ;
286303
304+ // Role data
305+ let mut role_data_history = HashMap :: new ( ) ;
306+ let mut role_data_record = HashMap :: new ( ) ;
307+
308+ update_role_data (
309+ & registration,
310+ & mut role_data_history,
311+ & mut role_data_record,
312+ & point_tx_idx,
313+ ) ;
314+
315+ // There should be role 0 since we already check that the chain root (no previous tx id)
316+ // must contain role 0
317+ let Some ( role0_data) = role_data_record. get ( & RoleId :: Role0 ) else {
318+ cip509. report ( ) . missing_field ( "Role 0" , context) ;
319+ bail ! ( "Role 0 not found" ) ;
320+ } ;
321+ let Some ( signing_pk) = role0_data
322+ . signing_keys ( )
323+ . last ( )
324+ . and_then ( |key| key. data ( ) . extract_pk ( ) )
325+ else {
326+ cip509
327+ . report ( )
328+ . missing_field ( "Signing pk for role 0 not found" , context) ;
329+ bail ! ( "No valid signing key found for role 0" ) ;
330+ } ;
331+
332+ check_validation_signature (
333+ validation_signature,
334+ & raw_aux_data,
335+ signing_pk,
336+ cip509. report ( ) ,
337+ context,
338+ ) ?;
339+
287340 let purpose = vec ! [ purpose] ;
288341 let certificate_uris = registration. certificate_uris . clone ( ) ;
289342 let mut x509_certs = HashMap :: new ( ) ;
@@ -306,17 +359,6 @@ impl RegistrationChainInner {
306359 ) ;
307360 let revocations = revocations_list ( registration. revocation_list . clone ( ) , & point_tx_idx) ;
308361
309- // Role data
310- let mut role_data_history = HashMap :: new ( ) ;
311- let mut role_data_record = HashMap :: new ( ) ;
312-
313- update_role_data (
314- & registration,
315- & mut role_data_history,
316- & mut role_data_record,
317- & point_tx_idx,
318- ) ;
319-
320362 Ok ( Self {
321363 catalyst_id,
322364 current_tx_id_hash,
@@ -340,23 +382,43 @@ impl RegistrationChainInner {
340382 /// # Errors
341383 ///
342384 /// Returns an error if data is invalid
343- fn update ( & self , cip509 : Cip509 ) -> anyhow:: Result < Self > {
385+ fn update ( & self , cip509 : & Cip509 , signing_pk : VerifyingKey ) -> anyhow:: Result < Self > {
386+ let context = "Registration Chain update" ;
344387 let mut new_inner = self . clone ( ) ;
345388
346389 let Some ( prv_tx_id) = cip509. previous_transaction ( ) else {
347- bail ! ( "Empty previous transaction ID" ) ;
390+ cip509
391+ . report ( )
392+ . missing_field ( "previous transaction ID" , context) ;
393+ bail ! ( "Missing previous transaction ID" ) ;
348394 } ;
395+
349396 // Previous transaction ID in the CIP509 should equal to the current transaction ID
350- // or else it is not a part of the chain
351397 if prv_tx_id == self . current_tx_id_hash {
352- // Update the current transaction ID hash
398+ // Perform signature validation
399+ // This should be done before updating the signing key
400+ check_validation_signature (
401+ cip509. validation_signature ( ) . cloned ( ) ,
402+ cip509. raw_aux_data ( ) ,
403+ signing_pk,
404+ cip509. report ( ) ,
405+ context,
406+ ) ?;
407+
408+ // If successful, update the chain current transaction ID hash
353409 new_inner. current_tx_id_hash = cip509. txn_hash ( ) ;
354410 } else {
411+ cip509. report ( ) . invalid_value (
412+ "previous transaction ID" ,
413+ & format ! ( "{prv_tx_id:?}" ) ,
414+ & format ! ( "{:?}" , self . current_tx_id_hash) ,
415+ context,
416+ ) ;
355417 bail ! ( "Invalid previous transaction ID, not a part of this registration chain" ) ;
356418 }
357419
358420 let point_tx_idx = cip509. origin ( ) . clone ( ) ;
359- let ( purpose, registration, payment_history) = match cip509. consume ( ) {
421+ let ( purpose, registration, payment_history) = match cip509. clone ( ) . consume ( ) {
360422 Ok ( v) => v,
361423 Err ( e) => {
362424 let error = format ! ( "Invalid Cip509: {e:?}" ) ;
@@ -403,6 +465,41 @@ impl RegistrationChainInner {
403465 }
404466}
405467
468+ /// Perform a check on the validation signature.
469+ /// The auxiliary data should be sign with the latest signing public key.
470+ fn check_validation_signature (
471+ validation_signature : Option < ValidationSignature > , raw_aux_data : & [ u8 ] ,
472+ signing_pk : VerifyingKey , report : & ProblemReport , context : & str ,
473+ ) -> anyhow:: Result < ( ) > {
474+ let context = & format ! ( "Check Validation Signature in {context}" ) ;
475+ // Note that the validation signature can be in the range of 1 - 64 bytes
476+ // But since we allow only Ed25519, it should be 64 bytes
477+ let unsigned_aux = zero_out_last_n_bytes ( raw_aux_data, Signature :: BYTE_SIZE ) ;
478+
479+ let validation_sig = validation_signature. with_context ( || {
480+ report. missing_field ( "validation signature" , context) ;
481+ "Missing validation signature"
482+ } ) ?;
483+
484+ let sig: Signature = validation_sig. clone ( ) . try_into ( ) . with_context ( || {
485+ report. conversion_error (
486+ "validation signature" ,
487+ & format ! ( "{validation_sig:?}" ) ,
488+ "Ed25519 signature" ,
489+ context,
490+ ) ;
491+ "Failed to convert validation signature to Ed25519 Signature"
492+ } ) ?;
493+
494+ // Verify the signature using the latest signing public key
495+ signing_pk
496+ . verify_strict ( & unsigned_aux, & sig)
497+ . with_context ( || {
498+ report. other ( "Signature validation failed" , context) ;
499+ "Signature verification failed"
500+ } )
501+ }
502+
406503#[ cfg( test) ]
407504mod test {
408505 use catalyst_types:: catalyst_id:: role_index:: RoleId ;
@@ -419,7 +516,7 @@ mod test {
419516 data. assert_valid ( & registration) ;
420517
421518 // Create a chain with the first registration.
422- let chain = RegistrationChain :: new ( registration) . unwrap ( ) ;
519+ let chain = RegistrationChain :: new ( & registration) . unwrap ( ) ;
423520 assert_eq ! ( chain. purpose( ) , & [ data. purpose] ) ;
424521 assert_eq ! ( 1 , chain. x509_certs( ) . len( ) ) ;
425522 let origin = & chain. x509_certs ( ) . get ( & 0 ) . unwrap ( ) . first ( ) . unwrap ( ) ;
@@ -445,7 +542,7 @@ mod test {
445542 . unwrap ( ) ;
446543 assert ! ( registration. report( ) . is_problematic( ) ) ;
447544
448- let error = chain. update ( registration) . unwrap_err ( ) ;
545+ let error = chain. update ( & registration) . unwrap_err ( ) ;
449546 let error = format ! ( "{error:?}" ) ;
450547 assert ! (
451548 error. contains( "Invalid previous transaction ID" ) ,
@@ -459,7 +556,7 @@ mod test {
459556 . unwrap ( )
460557 . unwrap ( ) ;
461558 data. assert_valid ( & registration) ;
462- let update = chain. update ( registration) . unwrap ( ) ;
559+ let update = chain. update ( & registration) . unwrap ( ) ;
463560 // Current tx hash should be equal to the hash from block 4.
464561 assert_eq ! ( update. current_tx_id_hash( ) , data. txn_hash) ;
465562 assert ! ( update. role_data_record( ) . contains_key( & data. role) ) ;
0 commit comments