Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions rust/rbac-registration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ uuid = "1.11.0"

c509-certificate = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "v0.0.3" }
pallas = { version = "0.30.1", git = "https://github.com/input-output-hk/catalyst-pallas.git", rev = "9b5183c8b90b90fe2cc319d986e933e9518957b3" }
cbork-utils = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250127-00" }
cbork-utils = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250212-00" }
cardano-blockchain-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250214-00" }
catalyst-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250127-00" }
catalyst-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250212-00" }
37 changes: 25 additions & 12 deletions rust/rbac-registration/src/cardano/cip509/cip509.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
use std::{borrow::Cow, collections::HashMap};

use anyhow::{anyhow, Context};
use cardano_blockchain_types::{MetadatumLabel, MultiEraBlock, TxnIndex};
use cardano_blockchain_types::{MetadatumLabel, MultiEraBlock, TransactionHash, TxnIndex};
use catalyst_types::{
cbor_utils::{report_duplicated_key, report_missing_keys},
hashes::{Blake2b256Hash, BLAKE_2B256_SIZE},
id_uri::IdUri,
problem_report::ProblemReport,
uuid::UuidV4,
};
Expand Down Expand Up @@ -59,7 +60,7 @@ pub struct Cip509 {
/// An optional hash of the previous transaction.
///
/// The hash must always be present except for the first registration transaction.
prv_tx_id: Option<Blake2b256Hash>,
prv_tx_id: Option<TransactionHash>,
/// Metadata.
///
/// This field encoded in chunks. See [`X509Chunks`] for more details.
Expand All @@ -72,10 +73,14 @@ pub struct Cip509 {
/// constructors.
payment_history: PaymentHistory,
/// A hash of the transaction from which this registration is extracted.
txn_hash: Blake2b256Hash,
txn_hash: TransactionHash,
/// A point (slot) and a transaction index identifying the block and the transaction
/// that this `Cip509` was extracted from.
origin: PointTxnIdx,
/// A catalyst ID.
///
/// This field is only present in role 0 registrations.
catalyst_id: Option<IdUri>,
/// A report potentially containing all the issues occurred during `Cip509` decoding
/// and validation.
///
Expand Down Expand Up @@ -139,7 +144,7 @@ impl Cip509 {
payment_history,
report: &mut report,
};
let cip509 =
let mut cip509 =
Cip509::decode(&mut decoder, &mut decode_context).context("Failed to decode Cip509")?;

// Perform the validation.
Expand All @@ -156,7 +161,7 @@ impl Cip509 {
validate_stake_public_key(txn, cip509.certificate_uris(), &cip509.report);
}
if let Some(metadata) = &cip509.metadata {
validate_role_data(metadata, &cip509.report);
cip509.catalyst_id = validate_role_data(metadata, &cip509.report);
}

Ok(Some(cip509))
Expand Down Expand Up @@ -210,7 +215,7 @@ impl Cip509 {

/// Returns a hash of the previous transaction.
#[must_use]
pub fn previous_transaction(&self) -> Option<Blake2b256Hash> {
pub fn previous_transaction(&self) -> Option<TransactionHash> {
self.prv_tx_id
}

Expand All @@ -228,7 +233,7 @@ impl Cip509 {

/// Returns a hash of the transaction where this data is originating from.
#[must_use]
pub fn txn_hash(&self) -> Blake2b256Hash {
pub fn txn_hash(&self) -> TransactionHash {
self.txn_hash
}

Expand All @@ -244,6 +249,12 @@ impl Cip509 {
self.txn_inputs_hash.as_ref()
}

/// Returns a Catalyst ID of this registration if role 0 is present.
#[must_use]
pub fn catalyst_id(&self) -> Option<&IdUri> {
self.catalyst_id.as_ref()
}

/// Returns `Cip509` fields consuming the structure if it was successfully decoded and
/// validated otherwise return the problem report that contains all the encountered
/// issues.
Expand Down Expand Up @@ -370,9 +381,10 @@ impl Decode<'_, DecodeContext<'_, '_>> for Cip509 {
.missing_field("metadata (10, 11 or 12 chunks)", context);
}

let txn_hash = MultiEraTx::Conway(Box::new(Cow::Borrowed(decode_context.txn)))
.hash()
.into();
let txn_hash = Blake2b256Hash::from(
MultiEraTx::Conway(Box::new(Cow::Borrowed(decode_context.txn))).hash(),
)
.into();
Ok(Self {
purpose,
txn_inputs_hash,
Expand All @@ -382,6 +394,7 @@ impl Decode<'_, DecodeContext<'_, '_>> for Cip509 {
payment_history: HashMap::new(),
txn_hash,
origin: decode_context.origin.clone(),
catalyst_id: None,
report: decode_context.report.clone(),
})
}
Expand Down Expand Up @@ -501,7 +514,7 @@ fn decode_input_hash(
/// Decodes previous transaction id.
fn decode_previous_transaction_id(
d: &mut Decoder, context: &str, report: &ProblemReport,
) -> Result<Option<Blake2b256Hash>, ()> {
) -> Result<Option<TransactionHash>, ()> {
let bytes = match decode_bytes(d, "Cip509 previous transaction id") {
Ok(v) => v,
Err(e) => {
Expand All @@ -515,7 +528,7 @@ fn decode_previous_transaction_id(

let len = bytes.len();
if let Ok(v) = Blake2b256Hash::try_from(bytes) {
Ok(Some(v))
Ok(Some(v.into()))
} else {
report.invalid_value(
"previous transaction hash",
Expand Down
98 changes: 90 additions & 8 deletions rust/rbac-registration/src/cardano/cip509/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,22 @@

use std::borrow::Cow;

use c509_certificate::c509::C509;
use cardano_blockchain_types::{TxnWitness, VKeyHash};
use catalyst_types::{
hashes::{Blake2b128Hash, Blake2b256Hash},
id_uri::IdUri,
problem_report::ProblemReport,
};
use ed25519_dalek::{VerifyingKey, PUBLIC_KEY_LENGTH};
use pallas::{
codec::{
minicbor::{Encode, Encoder},
utils::Bytes,
},
ledger::{addresses::Address, primitives::conway, traverse::MultiEraTx},
};
use x509_cert::Certificate;

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

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

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

let mut catalyst_id = None;
for (number, data) in &metadata.role_data {
if number == &RoleNumber::ROLE_0 {
validate_role_0(data, metadata, context, report);
catalyst_id = validate_role_0(data, metadata, context, report);
} else {
if let Some(signing_key) = data.signing_key() {
if signing_key.key_offset == 0 {
Expand All @@ -252,12 +257,13 @@ pub fn validate_role_data(metadata: &Cip509RbacMetadata, report: &ProblemReport)
}
}
}
catalyst_id
}

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

let Some(signing_key) = role.signing_key() else {
report.missing_field("(Role 0) RoleData::signing_key", context);
return;
return None;
};

if signing_key.key_offset != 0 {
report.other(
&format!("The role 0 must reference a certificate with 0 index ({role:?})"),
context,
);
return;
return None;
}

let mut catalyst_id = None;
let network = "cardano";

match signing_key.local_ref {
LocalRefInt::X509Certs => {
match metadata.x509_certs.first() {
Some(X509DerCert::X509Cert(_)) => {
Some(X509DerCert::X509Cert(cert)) => {
// All good: role 0 references a valid X509 certificate.
catalyst_id = x509_cert_key(cert, context, report).map(|k| IdUri::new(network, None, k));
}
Some(c) => report.other(&format!("Invalid X509 certificate value ({c:?}) for role 0 ({role:?})"), context),
None => report.other("Role 0 reference X509 certificate at index 0, but there is no such certificate", context),
}
},
LocalRefInt::C509Certs => {
match metadata.c509_certs.first() {
Some(C509Cert::C509Certificate(_)) => {
Some(C509Cert::C509Certificate(cert)) => {
// All good: role 0 references a valid C509 certificate.
catalyst_id = c509_cert_key(cert, context, report).map(|k| IdUri::new(network, None, k));
}
Some(c) => report.other(&format!("Invalid C509 certificate value ({c:?}) for role 0 ({role:?})"), context),
None => report.other("Role 0 reference C509 certificate at index 0, but there is no such certificate", context),
Expand All @@ -308,6 +319,77 @@ fn validate_role_0(
);
},
}
catalyst_id
}

/// Extracts `VerifyingKey` from the given `X509` certificate.
fn x509_cert_key(
cert: &Certificate, context: &str, report: &ProblemReport,
) -> Option<VerifyingKey> {
let Some(extended_public_key) = cert
.tbs_certificate
.subject_public_key_info
.subject_public_key
.as_bytes()
else {
report.invalid_value(
"subject_public_key",
"is not octet aligned",
"Must not have unused bits",
context,
);
return None;
};
verifying_key(extended_public_key, context, report)
}

/// Extracts `VerifyingKey` from the given `C509` certificate.
fn c509_cert_key(cert: &C509, context: &str, report: &ProblemReport) -> Option<VerifyingKey> {
verifying_key(cert.tbs_cert().subject_public_key(), context, report)
}

/// Creates `VerifyingKey` from the given extended public key.
fn verifying_key(
extended_public_key: &[u8], context: &str, report: &ProblemReport,
) -> Option<VerifyingKey> {
/// An extender public key length in bytes.
const EXTENDED_PUBLIC_KEY_LENGTH: usize = 64;

if extended_public_key.len() != EXTENDED_PUBLIC_KEY_LENGTH {
report.other(
&format!("Unexpected extended public key length in certificate: {}, expected {EXTENDED_PUBLIC_KEY_LENGTH}",
extended_public_key.len()),
context,
);
return None;
}

// This should never fail because of the check above.
let Some(public_key) = extended_public_key.get(0..PUBLIC_KEY_LENGTH) else {
report.other("Unable to get public key part", context);
return None;
};

let bytes: &[u8; PUBLIC_KEY_LENGTH] = match public_key.try_into() {
Ok(v) => v,
Err(e) => {
report.other(
&format!("Invalid public key length in X509 certificate: {e:?}"),
context,
);
return None;
},
};
match VerifyingKey::from_bytes(bytes) {
Ok(k) => Some(k),
Err(e) => {
report.other(
&format!("Invalid public key in C509 certificate: {e:?}"),
context,
);
None
},
}
}

#[cfg(test)]
Expand Down
7 changes: 4 additions & 3 deletions rust/rbac-registration/src/registration/cardano/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use std::{collections::HashMap, sync::Arc};

use anyhow::bail;
use c509_certificate::c509::C509;
use catalyst_types::{hashes::Blake2b256Hash, uuid::UuidV4};
use cardano_blockchain_types::TransactionHash;
use catalyst_types::uuid::UuidV4;
use ed25519_dalek::VerifyingKey;
use tracing::{error, warn};
use x509_cert::certificate::Certificate as X509Certificate;
Expand Down Expand Up @@ -57,7 +58,7 @@ impl RegistrationChain {

/// Get the current transaction ID hash.
#[must_use]
pub fn current_tx_id_hash(&self) -> Blake2b256Hash {
pub fn current_tx_id_hash(&self) -> TransactionHash {
self.inner.current_tx_id_hash
}

Expand Down Expand Up @@ -108,7 +109,7 @@ impl RegistrationChain {
#[derive(Debug, Clone)]
struct RegistrationChainInner {
/// The current transaction ID hash (32 bytes)
current_tx_id_hash: Blake2b256Hash,
current_tx_id_hash: TransactionHash,
/// List of purpose for this registration chain
purpose: Vec<UuidV4>,

Expand Down
8 changes: 4 additions & 4 deletions rust/rbac-registration/src/utils/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

// cspell: words stake_test1urs8t0ssa3w9wh90ld5tprp3gurxd487rth2qlqk6ernjqcef4ugr

use cardano_blockchain_types::{MultiEraBlock, Network, Point, Slot, TxnIndex};
use catalyst_types::{hashes::Blake2b256Hash, uuid::UuidV4};
use cardano_blockchain_types::{MultiEraBlock, Network, Point, Slot, TransactionHash, TxnIndex};
use catalyst_types::uuid::UuidV4;
use uuid::Uuid;

use crate::cardano::cip509::{Cip509, RoleNumber};
Expand All @@ -20,9 +20,9 @@ pub struct BlockTestData {
/// Transaction index.
pub txn_index: TxnIndex,
/// Transaction hash.
pub txn_hash: Blake2b256Hash,
pub txn_hash: TransactionHash,
/// Previous hash.
pub prv_hash: Option<Blake2b256Hash>,
pub prv_hash: Option<TransactionHash>,
/// Purpose.
pub purpose: UuidV4,
/// Stake address.
Expand Down