• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

input-output-hk / catalyst-libs / 17376694348

01 Sep 2025 11:48AM UTC coverage: 67.994% (+0.1%) from 67.899%
17376694348

Pull #507

github

web-flow
Merge c49eaa49b into c20638587
Pull Request #507: fix(rust/rbac-registration): Validate witness for stake address and payment address URIs in certificate

59 of 81 new or added lines in 4 files covered. (72.84%)

3 existing lines in 2 files now uncovered.

13197 of 19409 relevant lines covered (67.99%)

3038.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.0
/rust/rbac-registration/src/registration/cardano/mod.rs
1
//! Chain of Cardano registration data
2

3
mod update_rbac;
4

5
use std::{
6
    collections::{HashMap, HashSet},
7
    sync::Arc,
8
};
9

10
use anyhow::Context;
11
use c509_certificate::c509::C509;
12
use cardano_blockchain_types::{hashes::TransactionId, Point, StakeAddress, TxnIndex};
13
use catalyst_types::{
14
    catalyst_id::{key_rotation::KeyRotation, role_index::RoleId, CatalystId},
15
    conversion::zero_out_last_n_bytes,
16
    problem_report::ProblemReport,
17
    uuid::UuidV4,
18
};
19
use ed25519_dalek::{Signature, VerifyingKey};
20
use update_rbac::{
21
    revocations_list, update_c509_certs, update_public_keys, update_role_data, update_x509_certs,
22
};
23
use x509_cert::certificate::Certificate as X509Certificate;
24

25
use crate::cardano::cip509::{
26
    CertKeyHash, CertOrPk, Cip0134UriSet, Cip509, PaymentHistory, PointData, RoleData,
27
    RoleDataRecord, ValidationSignature,
28
};
29

30
/// Registration chains.
31
///
32
/// This structure uses [`Arc`] internally, so it is cheap to clone.
33
#[derive(Debug, Clone)]
34
pub struct RegistrationChain {
35
    /// Inner part of the registration chain.
36
    inner: Arc<RegistrationChainInner>,
37
}
38

39
impl RegistrationChain {
40
    /// Create a new instance of registration chain.
41
    /// The first new value should be the chain root.
42
    ///
43
    /// # Arguments
44
    /// - `cip509` - The CIP509.
45
    ///
46
    /// # Errors
47
    ///
48
    /// Returns an error if data is invalid
49
    #[must_use]
50
    pub fn new(cip509: Cip509) -> Option<Self> {
1✔
51
        let inner = RegistrationChainInner::new(cip509)?;
1✔
52

53
        Some(Self {
1✔
54
            inner: Arc::new(inner),
1✔
55
        })
1✔
56
    }
1✔
57

58
    /// Update the registration chain.
59
    ///
60
    /// # Arguments
61
    /// - `cip509` - The CIP509.
62
    ///
63
    /// # Errors
64
    ///
65
    /// Returns an error if data is invalid
66
    #[must_use]
67
    pub fn update(
2✔
68
        &self,
2✔
69
        cip509: Cip509,
2✔
70
    ) -> Option<Self> {
2✔
71
        let latest_signing_pk = self.get_latest_signing_pk_for_role(&RoleId::Role0);
2✔
72
        let new_inner = if let Some((signing_pk, _)) = latest_signing_pk {
2✔
73
            self.inner.update(cip509, signing_pk)?
2✔
74
        } else {
75
            cip509.report().missing_field(
×
76
                "latest signing key for role 0",
×
77
                "cannot perform signature validation during Registration Chain update",
×
78
            );
79
            return None;
×
80
        };
81
        Some(Self {
1✔
82
            inner: Arc::new(new_inner),
1✔
83
        })
1✔
84
    }
2✔
85

86
    /// Returns a Catalyst ID.
87
    #[must_use]
88
    pub fn catalyst_id(&self) -> &CatalystId {
×
89
        &self.inner.catalyst_id
×
90
    }
×
91

92
    /// Get the current transaction ID hash.
93
    #[must_use]
94
    pub fn current_tx_id_hash(&self) -> TransactionId {
1✔
95
        *self.inner.current_tx_id_hash.data()
1✔
96
    }
1✔
97

98
    /// Returns a point (slot) of the latest transaction in the registration chain.
99
    #[must_use]
100
    pub fn current_point(&self) -> &Point {
×
101
        self.inner.current_tx_id_hash.point()
×
102
    }
×
103

104
    /// Returns an index of the latest transaction in the registration chain.
105
    #[must_use]
106
    pub fn current_txn_index(&self) -> TxnIndex {
×
107
        self.inner.current_tx_id_hash.txn_index()
×
108
    }
×
109

110
    /// Get a list of purpose for this registration chain.
111
    #[must_use]
112
    pub fn purpose(&self) -> &[UuidV4] {
1✔
113
        &self.inner.purpose
1✔
114
    }
1✔
115

116
    /// Get the map of index in array to list of point + transaction index, and x509
117
    /// certificate.
118
    #[must_use]
119
    pub fn x509_certs(&self) -> &HashMap<usize, Vec<PointData<Option<X509Certificate>>>> {
2✔
120
        &self.inner.x509_certs
2✔
121
    }
2✔
122

123
    /// Get the map of index in array to list of point + transaction index, and c509
124
    /// certificate.
125
    #[must_use]
126
    pub fn c509_certs(&self) -> &HashMap<usize, Vec<PointData<Option<C509>>>> {
×
127
        &self.inner.c509_certs
×
128
    }
×
129

130
    /// Get the map of index in array to list of point + transaction index, and public
131
    /// key.
132
    #[must_use]
133
    pub fn simple_keys(&self) -> &HashMap<usize, Vec<PointData<Option<VerifyingKey>>>> {
×
134
        &self.inner.simple_keys
×
135
    }
×
136

137
    /// Get a list of point + transaction index and revocation.
138
    #[must_use]
139
    pub fn revocations(&self) -> &[PointData<CertKeyHash>] {
×
140
        &self.inner.revocations
×
141
    }
×
142

143
    /// Get the map of role number to role data record where each field in role data
144
    /// record has it own record of its value and its associated point and transaction
145
    /// index.
146
    #[must_use]
147
    pub fn role_data_record(&self) -> &HashMap<RoleId, RoleDataRecord> {
3✔
148
        &self.inner.role_data_record
3✔
149
    }
3✔
150

151
    /// Get the map of role number to list of history data of point + transaction index,
152
    /// and role data.
153
    #[must_use]
154
    pub fn role_data_history(&self) -> &HashMap<RoleId, Vec<PointData<RoleData>>> {
1✔
155
        &self.inner.role_data_history
1✔
156
    }
1✔
157

158
    /// Get the map of tracked payment keys to its history.
159
    #[must_use]
160
    pub fn tracking_payment_history(&self) -> &PaymentHistory {
×
161
        &self.inner.payment_history
×
162
    }
×
163

164
    /// Get the latest signing public key for a role.
165
    /// Returns the public key and the rotation,`None` if not found.
166
    #[must_use]
167
    pub fn get_latest_signing_pk_for_role(
3✔
168
        &self,
3✔
169
        role: &RoleId,
3✔
170
    ) -> Option<(VerifyingKey, KeyRotation)> {
3✔
171
        self.inner.role_data_record.get(role).and_then(|rdr| {
3✔
172
            rdr.signing_keys().last().and_then(|key| {
3✔
173
                let rotation = KeyRotation::from_latest_rotation(rdr.signing_keys());
3✔
174

175
                key.data().extract_pk().map(|pk| (pk, rotation))
3✔
176
            })
3✔
177
        })
3✔
178
    }
3✔
179

180
    /// Get the latest encryption public key for a role.
181
    /// Returns the public key and the rotation, `None` if not found.
182
    #[must_use]
183
    pub fn get_latest_encryption_pk_for_role(
×
184
        &self,
×
185
        role: &RoleId,
×
186
    ) -> Option<(VerifyingKey, KeyRotation)> {
×
187
        self.inner.role_data_record.get(role).and_then(|rdr| {
×
188
            rdr.encryption_keys().last().and_then(|key| {
×
189
                let rotation = KeyRotation::from_latest_rotation(rdr.encryption_keys());
×
190

191
                key.data().extract_pk().map(|pk| (pk, rotation))
×
192
            })
×
193
        })
×
194
    }
×
195

196
    /// Get signing public key for a role with given rotation.
197
    /// Returns the public key, `None` if not found.
198
    #[must_use]
199
    pub fn get_signing_pk_for_role_at_rotation(
1✔
200
        &self,
1✔
201
        role: &RoleId,
1✔
202
        rotation: &KeyRotation,
1✔
203
    ) -> Option<VerifyingKey> {
1✔
204
        self.inner.role_data_record.get(role).and_then(|rdr| {
1✔
205
            rdr.signing_key_from_rotation(rotation)
1✔
206
                .and_then(CertOrPk::extract_pk)
1✔
207
        })
1✔
208
    }
1✔
209

210
    /// Get encryption public key for a role with given rotation.
211
    /// Returns the public key, `None` if not found.
212
    #[must_use]
213
    pub fn get_encryption_pk_for_role_at_rotation(
1✔
214
        &self,
1✔
215
        role: &RoleId,
1✔
216
        rotation: &KeyRotation,
1✔
217
    ) -> Option<VerifyingKey> {
1✔
218
        self.inner.role_data_record.get(role).and_then(|rdr| {
1✔
219
            rdr.encryption_key_from_rotation(rotation)
1✔
220
                .and_then(CertOrPk::extract_pk)
1✔
221
        })
1✔
222
    }
1✔
223

224
    /// Get signing key X509 certificate, C509 certificate or public key for a role with
225
    /// given rotation.
226
    #[must_use]
227
    pub fn get_singing_key_cert_or_key_for_role_at_rotation(
1✔
228
        &self,
1✔
229
        role: &RoleId,
1✔
230
        rotation: &KeyRotation,
1✔
231
    ) -> Option<&CertOrPk> {
1✔
232
        self.inner
1✔
233
            .role_data_record
1✔
234
            .get(role)
1✔
235
            .and_then(|rdr| rdr.signing_key_from_rotation(rotation))
1✔
236
    }
1✔
237

238
    /// Get encryption key X509 certificate, C509 certificate or public key for a role
239
    /// with given rotation.
240
    #[must_use]
241
    pub fn get_encryption_key_cert_or_key_for_role_at_rotation(
1✔
242
        &self,
1✔
243
        role: &RoleId,
1✔
244
        rotation: &KeyRotation,
1✔
245
    ) -> Option<&CertOrPk> {
1✔
246
        self.inner
1✔
247
            .role_data_record
1✔
248
            .get(role)
1✔
249
            .and_then(|rdr| rdr.encryption_key_from_rotation(rotation))
1✔
250
    }
1✔
251

252
    /// Returns all stake addresses associated to this registration.
253
    #[must_use]
NEW
254
    pub fn stake_addresses(&self) -> HashSet<StakeAddress> {
×
NEW
255
        self.inner.certificate_uris.stake_addresses()
×
UNCOV
256
    }
×
257
}
258

259
/// Inner structure of registration chain.
260
#[derive(Debug, Clone)]
261
struct RegistrationChainInner {
262
    /// A Catalyst ID.
263
    catalyst_id: CatalystId,
264
    /// The current transaction ID hash (32 bytes)
265
    current_tx_id_hash: PointData<TransactionId>,
266
    /// List of purpose for this registration chain
267
    purpose: Vec<UuidV4>,
268

269
    // RBAC
270
    /// Map of index in array to list of point + transaction index, and optional x509
271
    /// certificate. If X509 is None, it means the certificate is deleted.
272
    x509_certs: HashMap<usize, Vec<PointData<Option<X509Certificate>>>>,
273
    /// Map of index in array to list of point + transaction index, and optional c509
274
    /// certificate. If C509 is None, it means the certificate is deleted.
275
    c509_certs: HashMap<usize, Vec<PointData<Option<C509>>>>,
276
    /// A set of URIs contained in both x509 and c509 certificates.
277
    certificate_uris: Cip0134UriSet,
278
    /// Map of index in array to list of point + transaction index, and public key.
279
    /// If key is None, it means the key is deleted.
280
    simple_keys: HashMap<usize, Vec<PointData<Option<VerifyingKey>>>>,
281
    /// List of point + transaction index, and certificate key hash.
282
    revocations: Vec<PointData<CertKeyHash>>,
283

284
    // Role
285
    /// Map of role number to list point + transaction index, and role data.
286
    /// Record history of the whole role data in point in time.
287
    role_data_history: HashMap<RoleId, Vec<PointData<RoleData>>>,
288
    /// Map of role number role data record where each field in role data record
289
    /// has it own record of its value and its associated point and transaction index.
290
    role_data_record: HashMap<RoleId, RoleDataRecord>,
291
    /// Map of tracked payment key to its history.
292
    payment_history: PaymentHistory,
293
}
294

295
impl RegistrationChainInner {
296
    /// Create a new instance of registration chain.
297
    /// The first new value should be the chain root.
298
    ///
299
    /// # Arguments
300
    /// - `cip509` - The CIP509.
301
    ///
302
    /// # Errors
303
    ///
304
    /// Returns an error if data is invalid
305
    #[must_use]
306
    fn new(cip509: Cip509) -> Option<Self> {
1✔
307
        let context = "Registration Chain new";
1✔
308
        // Should be chain root, return immediately if not
309
        if cip509.previous_transaction().is_some() {
1✔
310
            cip509
×
311
                .report()
×
312
                .invalid_value("previous transaction ID", "None", "Some", context);
×
313
        }
1✔
314
        let Some(catalyst_id) = cip509.catalyst_id().cloned() else {
1✔
315
            cip509.report().missing_field("catalyst id", context);
×
316
            return None;
×
317
        };
318

319
        let point_tx_idx = cip509.origin().clone();
1✔
320
        let current_tx_id_hash = PointData::new(point_tx_idx.clone(), cip509.txn_hash());
1✔
321
        let validation_signature = cip509.validation_signature().cloned();
1✔
322
        let raw_aux_data = cip509.raw_aux_data().to_vec();
1✔
323

324
        // Role data
325
        let mut role_data_history = HashMap::new();
1✔
326
        let mut role_data_record = HashMap::new();
1✔
327

328
        if let Some(registration) = cip509.metadata() {
1✔
329
            update_role_data(
1✔
330
                registration,
1✔
331
                &mut role_data_history,
1✔
332
                &mut role_data_record,
1✔
333
                &point_tx_idx,
1✔
334
            );
1✔
335
        }
1✔
336

337
        // There should be role 0 since we already check that the chain root (no previous tx id)
338
        // must contain role 0
339
        let Some(role0_data) = role_data_record.get(&RoleId::Role0) else {
1✔
340
            cip509.report().missing_field("Role 0", context);
×
341
            return None;
×
342
        };
343
        let Some(signing_pk) = role0_data
1✔
344
            .signing_keys()
1✔
345
            .last()
1✔
346
            .and_then(|key| key.data().extract_pk())
1✔
347
        else {
348
            cip509
×
349
                .report()
×
350
                .missing_field("Signing pk for role 0 not found", context);
×
351
            return None;
×
352
        };
353

354
        check_validation_signature(
1✔
355
            validation_signature,
1✔
356
            &raw_aux_data,
1✔
357
            signing_pk,
1✔
358
            cip509.report(),
1✔
359
            context,
1✔
360
        );
361

362
        let Ok((purpose, registration, payment_history)) = cip509.consume() else {
1✔
363
            return None;
×
364
        };
365

366
        let purpose = vec![purpose];
1✔
367
        let certificate_uris = registration.certificate_uris.clone();
1✔
368
        let mut x509_certs = HashMap::new();
1✔
369
        update_x509_certs(
1✔
370
            &mut x509_certs,
1✔
371
            registration.x509_certs.clone(),
1✔
372
            &point_tx_idx,
1✔
373
        );
374
        let mut c509_certs = HashMap::new();
1✔
375
        update_c509_certs(
1✔
376
            &mut c509_certs,
1✔
377
            registration.c509_certs.clone(),
1✔
378
            &point_tx_idx,
1✔
379
        );
380
        let mut simple_keys = HashMap::new();
1✔
381
        update_public_keys(
1✔
382
            &mut simple_keys,
1✔
383
            registration.pub_keys.clone(),
1✔
384
            &point_tx_idx,
1✔
385
        );
386
        let revocations = revocations_list(registration.revocation_list.clone(), &point_tx_idx);
1✔
387

388
        Some(Self {
1✔
389
            catalyst_id,
1✔
390
            current_tx_id_hash,
1✔
391
            purpose,
1✔
392
            x509_certs,
1✔
393
            c509_certs,
1✔
394
            certificate_uris,
1✔
395
            simple_keys,
1✔
396
            revocations,
1✔
397
            role_data_history,
1✔
398
            role_data_record,
1✔
399
            payment_history,
1✔
400
        })
1✔
401
    }
1✔
402

403
    /// Update the registration chain.
404
    ///
405
    /// # Arguments
406
    /// - `cip509` - The CIP509.
407
    ///
408
    /// # Errors
409
    ///
410
    /// Returns an error if data is invalid
411
    #[must_use]
412
    fn update(
2✔
413
        &self,
2✔
414
        cip509: Cip509,
2✔
415
        signing_pk: VerifyingKey,
2✔
416
    ) -> Option<Self> {
2✔
417
        let context = "Registration Chain update";
2✔
418
        let mut new_inner = self.clone();
2✔
419

420
        let Some(prv_tx_id) = cip509.previous_transaction() else {
2✔
421
            cip509
×
422
                .report()
×
423
                .missing_field("previous transaction ID", context);
×
424
            return None;
×
425
        };
426

427
        // Previous transaction ID in the CIP509 should equal to the current transaction ID
428
        if &prv_tx_id == self.current_tx_id_hash.data() {
2✔
429
            // Perform signature validation
1✔
430
            // This should be done before updating the signing key
1✔
431
            check_validation_signature(
1✔
432
                cip509.validation_signature().cloned(),
1✔
433
                cip509.raw_aux_data(),
1✔
434
                signing_pk,
1✔
435
                cip509.report(),
1✔
436
                context,
1✔
437
            );
1✔
438

1✔
439
            // If successful, update the chain current transaction ID hash
1✔
440
            new_inner.current_tx_id_hash =
1✔
441
                PointData::new(cip509.origin().clone(), cip509.txn_hash());
1✔
442
        } else {
1✔
443
            cip509.report().invalid_value(
1✔
444
                "previous transaction ID",
1✔
445
                &format!("{prv_tx_id:?}"),
1✔
446
                &format!("{:?}", self.current_tx_id_hash),
1✔
447
                context,
1✔
448
            );
449
            return None;
1✔
450
        }
451

452
        let point_tx_idx = cip509.origin().clone();
1✔
453
        let Ok((purpose, registration, payment_history)) = cip509.consume() else {
1✔
454
            return None;
×
455
        };
456

457
        // Add purpose to the chain, if not already exist
458
        if !self.purpose.contains(&purpose) {
1✔
459
            new_inner.purpose.push(purpose);
×
460
        }
1✔
461

462
        new_inner.certificate_uris = new_inner.certificate_uris.update(&registration);
1✔
463
        new_inner.payment_history.extend(payment_history);
1✔
464
        update_x509_certs(
1✔
465
            &mut new_inner.x509_certs,
1✔
466
            registration.x509_certs.clone(),
1✔
467
            &point_tx_idx,
1✔
468
        );
469
        update_c509_certs(
1✔
470
            &mut new_inner.c509_certs,
1✔
471
            registration.c509_certs.clone(),
1✔
472
            &point_tx_idx,
1✔
473
        );
474
        update_public_keys(
1✔
475
            &mut new_inner.simple_keys,
1✔
476
            registration.pub_keys.clone(),
1✔
477
            &point_tx_idx,
1✔
478
        );
479

480
        let revocations = revocations_list(registration.revocation_list.clone(), &point_tx_idx);
1✔
481
        // Revocation list should be appended
482
        new_inner.revocations.extend(revocations);
1✔
483

484
        update_role_data(
1✔
485
            &registration,
1✔
486
            &mut new_inner.role_data_history,
1✔
487
            &mut new_inner.role_data_record,
1✔
488
            &point_tx_idx,
1✔
489
        );
490

491
        Some(new_inner)
1✔
492
    }
2✔
493
}
494

495
/// Perform a check on the validation signature.
496
/// The auxiliary data should be sign with the latest signing public key.
497
fn check_validation_signature(
2✔
498
    validation_signature: Option<ValidationSignature>,
2✔
499
    raw_aux_data: &[u8],
2✔
500
    signing_pk: VerifyingKey,
2✔
501
    report: &ProblemReport,
2✔
502
    context: &str,
2✔
503
) {
2✔
504
    let context = &format!("Check Validation Signature in {context}");
2✔
505
    // Note that the validation signature can be in the range of 1 - 64 bytes
506
    // But since we allow only Ed25519, it should be 64 bytes
507
    let unsigned_aux = zero_out_last_n_bytes(raw_aux_data, Signature::BYTE_SIZE);
2✔
508

509
    let Some(validation_sig) = validation_signature else {
2✔
510
        report.missing_field("validation signature", context);
×
511
        return;
×
512
    };
513

514
    let Ok(sig) = validation_sig.clone().try_into() else {
2✔
515
        report.conversion_error(
×
516
            "validation signature",
×
517
            &format!("{validation_sig:?}"),
×
518
            "Ed25519 signature",
×
519
            context,
×
520
        );
521
        return;
×
522
    };
523

524
    // Verify the signature using the latest signing public key
525
    if let Err(e) = signing_pk
2✔
526
        .verify_strict(&unsigned_aux, &sig)
2✔
527
        .with_context(|| {
2✔
528
            report.other("Signature validation failed", context);
×
529
            "Signature verification failed"
×
530
        })
×
531
    {
×
532
        report.functional_validation(&format!("Signature validation failed: {e}"), context);
×
533
    }
2✔
534
}
2✔
535

536
#[cfg(test)]
537
mod test {
538
    use catalyst_types::catalyst_id::role_index::RoleId;
539

540
    use super::*;
541
    use crate::utils::test;
542

543
    #[test]
544
    fn multiple_registrations() {
1✔
545
        let data = test::block_5();
1✔
546
        let registration = Cip509::new(&data.block, data.txn_index, &[])
1✔
547
            .unwrap()
1✔
548
            .unwrap();
1✔
549
        data.assert_valid(&registration);
1✔
550

551
        // Create a chain with the first registration.
552
        let chain = RegistrationChain::new(registration).unwrap();
1✔
553
        assert_eq!(chain.purpose(), &[data.purpose]);
1✔
554
        assert_eq!(1, chain.x509_certs().len());
1✔
555
        let origin = &chain.x509_certs().get(&0).unwrap().first().unwrap();
1✔
556
        assert_eq!(origin.point().slot_or_default(), data.slot);
1✔
557
        assert_eq!(origin.txn_index(), data.txn_index);
1✔
558

559
        // no encryption key is included for the role
560
        assert!(chain
1✔
561
            .get_encryption_pk_for_role_at_rotation(&RoleId::Role0, &KeyRotation::default())
1✔
562
            .is_none());
1✔
563

564
        assert!(chain
1✔
565
            .get_encryption_key_cert_or_key_for_role_at_rotation(
1✔
566
                &RoleId::Role0,
1✔
567
                &KeyRotation::default()
1✔
568
            )
1✔
569
            .is_none());
1✔
570

571
        // Try to add an invalid registration.
572
        let data = test::block_2();
1✔
573
        let registration = Cip509::new(&data.block, data.txn_index, &[])
1✔
574
            .unwrap()
1✔
575
            .unwrap();
1✔
576
        assert!(registration.report().is_problematic());
1✔
577

578
        let report = registration.report().to_owned();
1✔
579
        assert!(chain.update(registration).is_none());
1✔
580
        let report = format!("{report:?}");
1✔
581
        assert!(
1✔
582
            report.contains("kind: InvalidValue { field: \"previous transaction ID\""),
1✔
583
            "{}",
584
            report
585
        );
586

587
        // Add the second registration.
588
        let data = test::block_6();
1✔
589
        let registration = Cip509::new(&data.block, data.txn_index, &[])
1✔
590
            .unwrap()
1✔
591
            .unwrap();
1✔
592
        data.assert_valid(&registration);
1✔
593
        let update = chain.update(registration).unwrap();
1✔
594
        // Current tx hash should be equal to the hash from block 4.
595
        assert_eq!(update.current_tx_id_hash(), data.txn_hash);
1✔
596
        assert!(update.role_data_record().contains_key(&data.role));
1✔
597
        // Update contains changes to role 0 without adding more roles.
598
        assert_eq!(update.role_data_record().len(), 1);
1✔
599

600
        // There are 2 updates to role 0 data.
601
        assert_eq!(
1✔
602
            update
1✔
603
                .role_data_history()
1✔
604
                .get(&RoleId::Role0)
1✔
605
                .unwrap()
1✔
606
                .len(),
1✔
607
            2
608
        );
609

610
        let role_0_data = update.role_data_record().get(&RoleId::Role0).unwrap();
1✔
611
        assert_eq!(role_0_data.signing_keys().len(), 2);
1✔
612
        assert_eq!(role_0_data.encryption_keys().len(), 0);
1✔
613
        assert_eq!(role_0_data.payment_keys().len(), 2);
1✔
614
        assert_eq!(role_0_data.extended_data().len(), 2);
1✔
615

616
        let (_k, r) = update
1✔
617
            .get_latest_signing_pk_for_role(&RoleId::Role0)
1✔
618
            .unwrap();
1✔
619
        assert_eq!(r, KeyRotation::from(1));
1✔
620
        assert!(update
1✔
621
            .get_signing_pk_for_role_at_rotation(&RoleId::Role0, &KeyRotation::from(2))
1✔
622
            .is_none());
1✔
623
        assert!(update
1✔
624
            .get_singing_key_cert_or_key_for_role_at_rotation(&RoleId::Role0, &KeyRotation::from(0))
1✔
625
            .is_some());
1✔
626
    }
1✔
627
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc