Skip to content

Commit 3c9506b

Browse files
authored
Merge branch 'main' into feat/cat-sign-doc-validator
2 parents fc8d5ba + d32efec commit 3c9506b

File tree

7 files changed

+128
-32
lines changed

7 files changed

+128
-32
lines changed

rust/cardano-chain-follower/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ mithril-client = { version = "0.10.4", default-features = false, features = [
1919
"full",
2020
"num-integer-backend",
2121
] }
22-
cardano-blockchain-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250114-00" }
23-
catalyst-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250121-00" }
22+
cardano-blockchain-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250214-00" }
23+
catalyst-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250212-00" }
2424

2525
thiserror = "1.0.69"
2626
tokio = { version = "1.42.0", features = [

rust/rbac-registration/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ uuid = "1.11.0"
3232

3333
c509-certificate = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "v0.0.3" }
3434
pallas = { version = "0.30.1", git = "https://github.com/input-output-hk/catalyst-pallas.git", rev = "9b5183c8b90b90fe2cc319d986e933e9518957b3" }
35-
cbork-utils = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250127-00" }
35+
cbork-utils = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250212-00" }
3636
cardano-blockchain-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250214-00" }
37-
catalyst-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250127-00" }
37+
catalyst-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250212-00" }

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

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
use std::{borrow::Cow, collections::HashMap};
66

77
use anyhow::{anyhow, Context};
8-
use cardano_blockchain_types::{MetadatumLabel, MultiEraBlock, TxnIndex};
8+
use cardano_blockchain_types::{MetadatumLabel, MultiEraBlock, TransactionHash, TxnIndex};
99
use catalyst_types::{
1010
cbor_utils::{report_duplicated_key, report_missing_keys},
1111
hashes::{Blake2b256Hash, BLAKE_2B256_SIZE},
12+
id_uri::IdUri,
1213
problem_report::ProblemReport,
1314
uuid::UuidV4,
1415
};
@@ -59,7 +60,7 @@ pub struct Cip509 {
5960
/// An optional hash of the previous transaction.
6061
///
6162
/// The hash must always be present except for the first registration transaction.
62-
prv_tx_id: Option<Blake2b256Hash>,
63+
prv_tx_id: Option<TransactionHash>,
6364
/// Metadata.
6465
///
6566
/// This field encoded in chunks. See [`X509Chunks`] for more details.
@@ -72,10 +73,14 @@ pub struct Cip509 {
7273
/// constructors.
7374
payment_history: PaymentHistory,
7475
/// A hash of the transaction from which this registration is extracted.
75-
txn_hash: Blake2b256Hash,
76+
txn_hash: TransactionHash,
7677
/// A point (slot) and a transaction index identifying the block and the transaction
7778
/// that this `Cip509` was extracted from.
7879
origin: PointTxnIdx,
80+
/// A catalyst ID.
81+
///
82+
/// This field is only present in role 0 registrations.
83+
catalyst_id: Option<IdUri>,
7984
/// A report potentially containing all the issues occurred during `Cip509` decoding
8085
/// and validation.
8186
///
@@ -139,7 +144,7 @@ impl Cip509 {
139144
payment_history,
140145
report: &mut report,
141146
};
142-
let cip509 =
147+
let mut cip509 =
143148
Cip509::decode(&mut decoder, &mut decode_context).context("Failed to decode Cip509")?;
144149

145150
// Perform the validation.
@@ -156,7 +161,7 @@ impl Cip509 {
156161
validate_stake_public_key(txn, cip509.certificate_uris(), &cip509.report);
157162
}
158163
if let Some(metadata) = &cip509.metadata {
159-
validate_role_data(metadata, &cip509.report);
164+
cip509.catalyst_id = validate_role_data(metadata, &cip509.report);
160165
}
161166

162167
Ok(Some(cip509))
@@ -210,7 +215,7 @@ impl Cip509 {
210215

211216
/// Returns a hash of the previous transaction.
212217
#[must_use]
213-
pub fn previous_transaction(&self) -> Option<Blake2b256Hash> {
218+
pub fn previous_transaction(&self) -> Option<TransactionHash> {
214219
self.prv_tx_id
215220
}
216221

@@ -228,7 +233,7 @@ impl Cip509 {
228233

229234
/// Returns a hash of the transaction where this data is originating from.
230235
#[must_use]
231-
pub fn txn_hash(&self) -> Blake2b256Hash {
236+
pub fn txn_hash(&self) -> TransactionHash {
232237
self.txn_hash
233238
}
234239

@@ -244,6 +249,12 @@ impl Cip509 {
244249
self.txn_inputs_hash.as_ref()
245250
}
246251

252+
/// Returns a Catalyst ID of this registration if role 0 is present.
253+
#[must_use]
254+
pub fn catalyst_id(&self) -> Option<&IdUri> {
255+
self.catalyst_id.as_ref()
256+
}
257+
247258
/// Returns `Cip509` fields consuming the structure if it was successfully decoded and
248259
/// validated otherwise return the problem report that contains all the encountered
249260
/// issues.
@@ -370,9 +381,10 @@ impl Decode<'_, DecodeContext<'_, '_>> for Cip509 {
370381
.missing_field("metadata (10, 11 or 12 chunks)", context);
371382
}
372383

373-
let txn_hash = MultiEraTx::Conway(Box::new(Cow::Borrowed(decode_context.txn)))
374-
.hash()
375-
.into();
384+
let txn_hash = Blake2b256Hash::from(
385+
MultiEraTx::Conway(Box::new(Cow::Borrowed(decode_context.txn))).hash(),
386+
)
387+
.into();
376388
Ok(Self {
377389
purpose,
378390
txn_inputs_hash,
@@ -382,6 +394,7 @@ impl Decode<'_, DecodeContext<'_, '_>> for Cip509 {
382394
payment_history: HashMap::new(),
383395
txn_hash,
384396
origin: decode_context.origin.clone(),
397+
catalyst_id: None,
385398
report: decode_context.report.clone(),
386399
})
387400
}
@@ -501,7 +514,7 @@ fn decode_input_hash(
501514
/// Decodes previous transaction id.
502515
fn decode_previous_transaction_id(
503516
d: &mut Decoder, context: &str, report: &ProblemReport,
504-
) -> Result<Option<Blake2b256Hash>, ()> {
517+
) -> Result<Option<TransactionHash>, ()> {
505518
let bytes = match decode_bytes(d, "Cip509 previous transaction id") {
506519
Ok(v) => v,
507520
Err(e) => {
@@ -515,7 +528,7 @@ fn decode_previous_transaction_id(
515528

516529
let len = bytes.len();
517530
if let Ok(v) = Blake2b256Hash::try_from(bytes) {
518-
Ok(Some(v))
531+
Ok(Some(v.into()))
519532
} else {
520533
report.invalid_value(
521534
"previous transaction hash",

rust/rbac-registration/src/cardano/cip509/validation.rs

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,22 @@
66
77
use std::borrow::Cow;
88

9+
use c509_certificate::c509::C509;
910
use cardano_blockchain_types::{TxnWitness, VKeyHash};
1011
use catalyst_types::{
1112
hashes::{Blake2b128Hash, Blake2b256Hash},
13+
id_uri::IdUri,
1214
problem_report::ProblemReport,
1315
};
16+
use ed25519_dalek::{VerifyingKey, PUBLIC_KEY_LENGTH};
1417
use pallas::{
1518
codec::{
1619
minicbor::{Encode, Encoder},
1720
utils::Bytes,
1821
},
1922
ledger::{addresses::Address, primitives::conway, traverse::MultiEraTx},
2023
};
24+
use x509_cert::Certificate;
2125

2226
use super::utils::cip19::compare_key_hash;
2327
use crate::cardano::cip509::{
@@ -158,9 +162,9 @@ fn extract_stake_addresses(uris: Option<&Cip0134UriSet>) -> Vec<VKeyHash> {
158162
.collect()
159163
}
160164

161-
/// Checks that only role 0 uses certificates with zero index.
165+
/// Checks the role data.
162166
#[allow(clippy::similar_names)]
163-
pub fn validate_role_data(metadata: &Cip509RbacMetadata, report: &ProblemReport) {
167+
pub fn validate_role_data(metadata: &Cip509RbacMetadata, report: &ProblemReport) -> Option<IdUri> {
164168
let context = "Role data validation";
165169

166170
if metadata.role_data.contains_key(&RoleNumber::ROLE_0) {
@@ -226,9 +230,10 @@ pub fn validate_role_data(metadata: &Cip509RbacMetadata, report: &ProblemReport)
226230
);
227231
}
228232

233+
let mut catalyst_id = None;
229234
for (number, data) in &metadata.role_data {
230235
if number == &RoleNumber::ROLE_0 {
231-
validate_role_0(data, metadata, context, report);
236+
catalyst_id = validate_role_0(data, metadata, context, report);
232237
} else {
233238
if let Some(signing_key) = data.signing_key() {
234239
if signing_key.key_offset == 0 {
@@ -252,12 +257,13 @@ pub fn validate_role_data(metadata: &Cip509RbacMetadata, report: &ProblemReport)
252257
}
253258
}
254259
}
260+
catalyst_id
255261
}
256262

257263
/// Checks that the role 0 data is correct.
258264
fn validate_role_0(
259265
role: &RoleData, metadata: &Cip509RbacMetadata, context: &str, report: &ProblemReport,
260-
) {
266+
) -> Option<IdUri> {
261267
if let Some(key) = role.encryption_key() {
262268
report.invalid_value(
263269
"Role 0 encryption key",
@@ -269,31 +275,36 @@ fn validate_role_0(
269275

270276
let Some(signing_key) = role.signing_key() else {
271277
report.missing_field("(Role 0) RoleData::signing_key", context);
272-
return;
278+
return None;
273279
};
274280

275281
if signing_key.key_offset != 0 {
276282
report.other(
277283
&format!("The role 0 must reference a certificate with 0 index ({role:?})"),
278284
context,
279285
);
280-
return;
286+
return None;
281287
}
282288

289+
let mut catalyst_id = None;
290+
let network = "cardano";
291+
283292
match signing_key.local_ref {
284293
LocalRefInt::X509Certs => {
285294
match metadata.x509_certs.first() {
286-
Some(X509DerCert::X509Cert(_)) => {
295+
Some(X509DerCert::X509Cert(cert)) => {
287296
// All good: role 0 references a valid X509 certificate.
297+
catalyst_id = x509_cert_key(cert, context, report).map(|k| IdUri::new(network, None, k));
288298
}
289299
Some(c) => report.other(&format!("Invalid X509 certificate value ({c:?}) for role 0 ({role:?})"), context),
290300
None => report.other("Role 0 reference X509 certificate at index 0, but there is no such certificate", context),
291301
}
292302
},
293303
LocalRefInt::C509Certs => {
294304
match metadata.c509_certs.first() {
295-
Some(C509Cert::C509Certificate(_)) => {
305+
Some(C509Cert::C509Certificate(cert)) => {
296306
// All good: role 0 references a valid C509 certificate.
307+
catalyst_id = c509_cert_key(cert, context, report).map(|k| IdUri::new(network, None, k));
297308
}
298309
Some(c) => report.other(&format!("Invalid C509 certificate value ({c:?}) for role 0 ({role:?})"), context),
299310
None => report.other("Role 0 reference C509 certificate at index 0, but there is no such certificate", context),
@@ -308,6 +319,77 @@ fn validate_role_0(
308319
);
309320
},
310321
}
322+
catalyst_id
323+
}
324+
325+
/// Extracts `VerifyingKey` from the given `X509` certificate.
326+
fn x509_cert_key(
327+
cert: &Certificate, context: &str, report: &ProblemReport,
328+
) -> Option<VerifyingKey> {
329+
let Some(extended_public_key) = cert
330+
.tbs_certificate
331+
.subject_public_key_info
332+
.subject_public_key
333+
.as_bytes()
334+
else {
335+
report.invalid_value(
336+
"subject_public_key",
337+
"is not octet aligned",
338+
"Must not have unused bits",
339+
context,
340+
);
341+
return None;
342+
};
343+
verifying_key(extended_public_key, context, report)
344+
}
345+
346+
/// Extracts `VerifyingKey` from the given `C509` certificate.
347+
fn c509_cert_key(cert: &C509, context: &str, report: &ProblemReport) -> Option<VerifyingKey> {
348+
verifying_key(cert.tbs_cert().subject_public_key(), context, report)
349+
}
350+
351+
/// Creates `VerifyingKey` from the given extended public key.
352+
fn verifying_key(
353+
extended_public_key: &[u8], context: &str, report: &ProblemReport,
354+
) -> Option<VerifyingKey> {
355+
/// An extender public key length in bytes.
356+
const EXTENDED_PUBLIC_KEY_LENGTH: usize = 64;
357+
358+
if extended_public_key.len() != EXTENDED_PUBLIC_KEY_LENGTH {
359+
report.other(
360+
&format!("Unexpected extended public key length in certificate: {}, expected {EXTENDED_PUBLIC_KEY_LENGTH}",
361+
extended_public_key.len()),
362+
context,
363+
);
364+
return None;
365+
}
366+
367+
// This should never fail because of the check above.
368+
let Some(public_key) = extended_public_key.get(0..PUBLIC_KEY_LENGTH) else {
369+
report.other("Unable to get public key part", context);
370+
return None;
371+
};
372+
373+
let bytes: &[u8; PUBLIC_KEY_LENGTH] = match public_key.try_into() {
374+
Ok(v) => v,
375+
Err(e) => {
376+
report.other(
377+
&format!("Invalid public key length in X509 certificate: {e:?}"),
378+
context,
379+
);
380+
return None;
381+
},
382+
};
383+
match VerifyingKey::from_bytes(bytes) {
384+
Ok(k) => Some(k),
385+
Err(e) => {
386+
report.other(
387+
&format!("Invalid public key in C509 certificate: {e:?}"),
388+
context,
389+
);
390+
None
391+
},
392+
}
311393
}
312394

313395
#[cfg(test)]

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use std::{collections::HashMap, sync::Arc};
44

55
use anyhow::bail;
66
use c509_certificate::c509::C509;
7-
use catalyst_types::{hashes::Blake2b256Hash, uuid::UuidV4};
7+
use cardano_blockchain_types::TransactionHash;
8+
use catalyst_types::uuid::UuidV4;
89
use ed25519_dalek::VerifyingKey;
910
use tracing::{error, warn};
1011
use x509_cert::certificate::Certificate as X509Certificate;
@@ -57,7 +58,7 @@ impl RegistrationChain {
5758

5859
/// Get the current transaction ID hash.
5960
#[must_use]
60-
pub fn current_tx_id_hash(&self) -> Blake2b256Hash {
61+
pub fn current_tx_id_hash(&self) -> TransactionHash {
6162
self.inner.current_tx_id_hash
6263
}
6364

@@ -108,7 +109,7 @@ impl RegistrationChain {
108109
#[derive(Debug, Clone)]
109110
struct RegistrationChainInner {
110111
/// The current transaction ID hash (32 bytes)
111-
current_tx_id_hash: Blake2b256Hash,
112+
current_tx_id_hash: TransactionHash,
112113
/// List of purpose for this registration chain
113114
purpose: Vec<UuidV4>,
114115

rust/rbac-registration/src/utils/test.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
33
// cspell: words stake_test1urs8t0ssa3w9wh90ld5tprp3gurxd487rth2qlqk6ernjqcef4ugr
44

5-
use cardano_blockchain_types::{MultiEraBlock, Network, Point, Slot, TxnIndex};
6-
use catalyst_types::{hashes::Blake2b256Hash, uuid::UuidV4};
5+
use cardano_blockchain_types::{MultiEraBlock, Network, Point, Slot, TransactionHash, TxnIndex};
6+
use catalyst_types::uuid::UuidV4;
77
use uuid::Uuid;
88

99
use crate::cardano::cip509::{Cip509, RoleNumber};
@@ -20,9 +20,9 @@ pub struct BlockTestData {
2020
/// Transaction index.
2121
pub txn_index: TxnIndex,
2222
/// Transaction hash.
23-
pub txn_hash: Blake2b256Hash,
23+
pub txn_hash: TransactionHash,
2424
/// Previous hash.
25-
pub prv_hash: Option<Blake2b256Hash>,
25+
pub prv_hash: Option<TransactionHash>,
2626
/// Purpose.
2727
pub purpose: UuidV4,
2828
/// Stake address.

rust/signed_doc/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ license.workspace = true
1111
workspace = true
1212

1313
[dependencies]
14-
catalyst-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250204-00" }
14+
catalyst-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250212-00" }
1515
anyhow = "1.0.95"
1616
serde = { version = "1.0.217", features = ["derive"] }
1717
serde_json = "1.0.134"

0 commit comments

Comments
 (0)