@@ -7,14 +7,16 @@ use std::{
7
7
sync:: Arc ,
8
8
} ;
9
9
10
- use anyhow:: bail;
10
+ use anyhow:: { bail, Context } ;
11
11
use c509_certificate:: c509:: C509 ;
12
12
use cardano_blockchain_types:: { StakeAddress , TransactionId } ;
13
13
use catalyst_types:: {
14
14
catalyst_id:: { key_rotation:: KeyRotation , role_index:: RoleId , CatalystId } ,
15
+ conversion:: zero_out_last_n_bytes,
16
+ problem_report:: ProblemReport ,
15
17
uuid:: UuidV4 ,
16
18
} ;
17
- use ed25519_dalek:: VerifyingKey ;
19
+ use ed25519_dalek:: { Signature , VerifyingKey } ;
18
20
use tracing:: error;
19
21
use update_rbac:: {
20
22
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;
23
25
24
26
use crate :: cardano:: cip509:: {
25
27
CertKeyHash , CertOrPk , Cip0134UriSet , Cip509 , PaymentHistory , PointData , RoleData ,
26
- RoleDataRecord ,
28
+ RoleDataRecord , ValidationSignature ,
27
29
} ;
28
30
29
31
/// Registration chains.
@@ -43,7 +45,7 @@ impl RegistrationChain {
43
45
/// # Errors
44
46
///
45
47
/// 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 > {
47
49
let inner = RegistrationChainInner :: new ( cip509) ?;
48
50
49
51
Ok ( Self {
@@ -59,9 +61,17 @@ impl RegistrationChain {
59
61
/// # Errors
60
62
///
61
63
/// 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
+ } ;
65
75
Ok ( Self {
66
76
inner : Arc :: new ( new_inner) ,
67
77
} )
@@ -264,18 +274,25 @@ impl RegistrationChainInner {
264
274
/// # Errors
265
275
///
266
276
/// 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" ;
268
279
// Should be chain root, return immediately if not
269
280
if cip509. previous_transaction ( ) . is_some ( ) {
281
+ cip509
282
+ . report ( )
283
+ . invalid_value ( "previous transaction ID" , "None" , "Some" , context) ;
270
284
bail ! ( "Invalid chain root, previous transaction ID should be None." ) ;
271
285
}
272
286
let Some ( catalyst_id) = cip509. catalyst_id ( ) . cloned ( ) else {
287
+ cip509. report ( ) . missing_field ( "catalyst id" , context) ;
273
288
bail ! ( "Invalid chain root, catalyst id should be present." ) ;
274
289
} ;
275
290
276
291
let point_tx_idx = cip509. origin ( ) . clone ( ) ;
277
292
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 ( ) {
279
296
Ok ( v) => v,
280
297
Err ( e) => {
281
298
let error = format ! ( "Invalid Cip509: {e:?}" ) ;
@@ -284,6 +301,42 @@ impl RegistrationChainInner {
284
301
} ,
285
302
} ;
286
303
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
+
287
340
let purpose = vec ! [ purpose] ;
288
341
let certificate_uris = registration. certificate_uris . clone ( ) ;
289
342
let mut x509_certs = HashMap :: new ( ) ;
@@ -306,17 +359,6 @@ impl RegistrationChainInner {
306
359
) ;
307
360
let revocations = revocations_list ( registration. revocation_list . clone ( ) , & point_tx_idx) ;
308
361
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
-
320
362
Ok ( Self {
321
363
catalyst_id,
322
364
current_tx_id_hash,
@@ -340,23 +382,43 @@ impl RegistrationChainInner {
340
382
/// # Errors
341
383
///
342
384
/// 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" ;
344
387
let mut new_inner = self . clone ( ) ;
345
388
346
389
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" ) ;
348
394
} ;
395
+
349
396
// Previous transaction ID in the CIP509 should equal to the current transaction ID
350
- // or else it is not a part of the chain
351
397
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
353
409
new_inner. current_tx_id_hash = cip509. txn_hash ( ) ;
354
410
} 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
+ ) ;
355
417
bail ! ( "Invalid previous transaction ID, not a part of this registration chain" ) ;
356
418
}
357
419
358
420
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 ( ) {
360
422
Ok ( v) => v,
361
423
Err ( e) => {
362
424
let error = format ! ( "Invalid Cip509: {e:?}" ) ;
@@ -403,6 +465,41 @@ impl RegistrationChainInner {
403
465
}
404
466
}
405
467
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
+
406
503
#[ cfg( test) ]
407
504
mod test {
408
505
use catalyst_types:: catalyst_id:: role_index:: RoleId ;
@@ -419,7 +516,7 @@ mod test {
419
516
data. assert_valid ( & registration) ;
420
517
421
518
// Create a chain with the first registration.
422
- let chain = RegistrationChain :: new ( registration) . unwrap ( ) ;
519
+ let chain = RegistrationChain :: new ( & registration) . unwrap ( ) ;
423
520
assert_eq ! ( chain. purpose( ) , & [ data. purpose] ) ;
424
521
assert_eq ! ( 1 , chain. x509_certs( ) . len( ) ) ;
425
522
let origin = & chain. x509_certs ( ) . get ( & 0 ) . unwrap ( ) . first ( ) . unwrap ( ) ;
@@ -445,7 +542,7 @@ mod test {
445
542
. unwrap ( ) ;
446
543
assert ! ( registration. report( ) . is_problematic( ) ) ;
447
544
448
- let error = chain. update ( registration) . unwrap_err ( ) ;
545
+ let error = chain. update ( & registration) . unwrap_err ( ) ;
449
546
let error = format ! ( "{error:?}" ) ;
450
547
assert ! (
451
548
error. contains( "Invalid previous transaction ID" ) ,
@@ -459,7 +556,7 @@ mod test {
459
556
. unwrap ( )
460
557
. unwrap ( ) ;
461
558
data. assert_valid ( & registration) ;
462
- let update = chain. update ( registration) . unwrap ( ) ;
559
+ let update = chain. update ( & registration) . unwrap ( ) ;
463
560
// Current tx hash should be equal to the hash from block 4.
464
561
assert_eq ! ( update. current_tx_id_hash( ) , data. txn_hash) ;
465
562
assert ! ( update. role_data_record( ) . contains_key( & data. role) ) ;
0 commit comments