Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
6 changes: 3 additions & 3 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" }
cardano-blockchain-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 = "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 = "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