Skip to content

Commit 58ba8d1

Browse files
committed
fix: validate all uris in cert that they should be witness
Signed-off-by: bkioshn <[email protected]>
1 parent b35db7c commit 58ba8d1

File tree

4 files changed

+91
-55
lines changed

4 files changed

+91
-55
lines changed

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

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use cardano_blockchain_types::{
1313
pallas_addresses::{Address, ShelleyAddress},
1414
pallas_primitives::{conway, Nullable},
1515
pallas_traverse::MultiEraTx,
16-
MetadatumLabel, MultiEraBlock, StakeAddress, TxnIndex,
16+
MetadatumLabel, MultiEraBlock, TxnIndex,
1717
};
1818
use catalyst_types::{
1919
catalyst_id::{role_index::RoleId, CatalystId},
@@ -36,7 +36,7 @@ use crate::cardano::cip509::{
3636
types::{PaymentHistory, TxInputHash, ValidationSignature},
3737
utils::Cip0134UriSet,
3838
validation::{
39-
validate_aux, validate_role_data, validate_self_sign_cert, validate_stake_public_key,
39+
validate_aux, validate_cert_addrs, validate_role_data, validate_self_sign_cert,
4040
validate_txn_inputs_hash,
4141
},
4242
x509_chunks::X509Chunks,
@@ -172,12 +172,11 @@ impl Cip509 {
172172
txn.transaction_body.auxiliary_data_hash.as_ref(),
173173
&cip509.report,
174174
);
175-
if cip509.role_data(RoleId::Role0).is_some() {
176-
// The following check is only performed for the role 0.
177-
validate_stake_public_key(txn, cip509.certificate_uris(), &cip509.report);
178-
}
179175
if let Some(metadata) = &cip509.metadata {
180176
cip509.catalyst_id = validate_role_data(metadata, block.network(), &cip509.report);
177+
// General check for all roles, check whether the addresses in the certificate URIs are
178+
// witnessed in the transaction.
179+
validate_cert_addrs(txn, cip509.certificate_uris(), &report);
181180
validate_self_sign_cert(metadata, &report);
182181
}
183182

@@ -276,12 +275,15 @@ impl Cip509 {
276275
self.catalyst_id.as_ref()
277276
}
278277

279-
/// Returns a list of role 0 stake addresses.
278+
/// Returns a list of addresses extract from certificate URIs.
280279
#[must_use]
281-
pub fn role_0_stake_addresses(&self) -> HashSet<StakeAddress> {
280+
pub fn certificate_addresses(
281+
&self,
282+
index: usize,
283+
) -> HashSet<Address> {
282284
self.metadata
283285
.as_ref()
284-
.map(|m| m.certificate_uris.stake_addresses(0))
286+
.map(|m| m.certificate_uris.addresses(index))
285287
.unwrap_or_default()
286288
}
287289

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

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use c509_certificate::{
1010
general_names::general_name::{GeneralNameTypeRegistry, GeneralNameValue},
1111
C509ExtensionType,
1212
};
13-
use cardano_blockchain_types::{pallas_addresses::Address, Cip0134Uri, StakeAddress};
13+
use cardano_blockchain_types::{pallas_addresses::Address, Cip0134Uri};
1414
use catalyst_types::problem_report::ProblemReport;
1515
use der_parser::der::parse_der_sequence;
1616
use tracing::debug;
@@ -72,24 +72,35 @@ impl Cip0134UriSet {
7272
self.x_uris().is_empty() && self.c_uris().is_empty()
7373
}
7474

75-
/// Returns a list of stake addresses by the given index.
75+
/// Returns a list of addresses by the given index.
7676
#[must_use]
77-
pub fn stake_addresses(
77+
pub fn addresses(
7878
&self,
7979
index: usize,
80-
) -> HashSet<StakeAddress> {
80+
) -> HashSet<Address> {
8181
let mut result = HashSet::new();
8282

8383
if let Some(uris) = self.x_uris().get(&index) {
84-
result.extend(convert_stake_addresses(uris));
84+
result.extend(uris.iter().map(|uri| uri.address().clone()));
8585
}
8686
if let Some(uris) = self.c_uris().get(&index) {
87-
result.extend(convert_stake_addresses(uris));
87+
result.extend(uris.iter().map(|uri| uri.address().clone()));
8888
}
8989

9090
result
9191
}
9292

93+
/// Return true if the given index contains at least one stake address.
94+
#[must_use]
95+
pub fn contain_stake_address(
96+
&self,
97+
index: usize,
98+
) -> bool {
99+
self.addresses(index)
100+
.iter()
101+
.any(|address| matches!(address, Address::Stake(_)))
102+
}
103+
93104
/// Return the updated URIs set.
94105
///
95106
/// The resulting set includes all the data from both the original and a new one. In
@@ -289,18 +300,6 @@ fn extract_c509_uris(
289300
result
290301
}
291302

292-
/// Converts a list of `Cip0134Uri` to a list of stake addresses.
293-
fn convert_stake_addresses(uris: &[Cip0134Uri]) -> Vec<StakeAddress> {
294-
uris.iter()
295-
.filter_map(|uri| {
296-
match uri.address() {
297-
Address::Stake(a) => Some(a.clone().into()),
298-
_ => None,
299-
}
300-
})
301-
.collect()
302-
}
303-
304303
#[cfg(test)]
305304
mod tests {
306305

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

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,13 @@ pub fn validate_aux(
117117
}
118118
}
119119

120-
/// Checks that all public keys extracted from x509 and c509 certificates are present in
121-
/// the witness set of the transaction.
122-
pub fn validate_stake_public_key(
120+
/// Check certificate URI addresses, if any, must be witnessed in the transaction.
121+
pub fn validate_cert_addrs(
123122
transaction: &conway::Tx,
124123
uris: Option<&Cip0134UriSet>,
125124
report: &ProblemReport,
126125
) {
127-
let context = "Cip509 stake public key validation";
126+
let context = "Cip509 certificate URI address validation";
128127

129128
let transaction = MultiEraTx::Conway(Box::new(Cow::Borrowed(transaction)));
130129
let witness = match TxnWitness::new(std::slice::from_ref(&transaction)) {
@@ -135,23 +134,17 @@ pub fn validate_stake_public_key(
135134
},
136135
};
137136

138-
let pk_addrs = extract_stake_addresses(uris);
139-
if pk_addrs.is_empty() {
140-
report.other(
141-
"Unable to find stake addresses in Cip509 certificates",
142-
context,
143-
);
144-
return;
145-
}
137+
let stake_addrs = extract_stake_addresses(uris);
138+
let payment_addrs = extract_payment_addresses(uris);
146139

147-
for (hash, address) in pk_addrs {
140+
for (hash, address) in stake_addrs.into_iter().chain(payment_addrs) {
148141
if !witness.check_witness_in_tx(&hash, 0.into()) {
149142
report.other(
150-
&format!(
151-
"Payment Key '{address}' (0x{hash}) is not present in the transaction witness set, and can not be verified as owned and spendable."
152-
),
153-
context,
154-
);
143+
&format!(
144+
"Address '{address}', key hash (0x{hash}) is not present in the transaction witness set, and can not be verified as owned"
145+
),
146+
context,
147+
);
155148
}
156149
}
157150
}
@@ -184,6 +177,41 @@ fn extract_stake_addresses(uris: Option<&Cip0134UriSet>) -> Vec<(VKeyHash, Strin
184177
.collect()
185178
}
186179

180+
/// Extracts all payment addresses from both X509 and C509 certificates containing in the
181+
/// given `Cip509`. Returns a list of pairs containing verifying public key hash (only the
182+
/// payment part) and `bech32` string representation of address.
183+
fn extract_payment_addresses(uris: Option<&Cip0134UriSet>) -> Vec<(VKeyHash, String)> {
184+
let Some(uris) = uris else {
185+
return Vec::new();
186+
};
187+
188+
uris.x_uris()
189+
.iter()
190+
.chain(uris.c_uris())
191+
.flat_map(|(_index, uris)| uris.iter())
192+
.filter_map(|uri| {
193+
if let Address::Shelley(a) = uri.address() {
194+
match a.payment() {
195+
// Shelley payment part is used to sign the transaction
196+
cardano_blockchain_types::pallas_addresses::ShelleyPaymentPart::Key(hash) => {
197+
match a.to_bech32() {
198+
Ok(bech32) => {
199+
hash.as_slice().try_into().ok().map(|hash| (hash, bech32))
200+
},
201+
Err(_) => None,
202+
}
203+
},
204+
cardano_blockchain_types::pallas_addresses::ShelleyPaymentPart::Script(_) => {
205+
None
206+
},
207+
}
208+
} else {
209+
None
210+
}
211+
})
212+
.collect()
213+
}
214+
187215
/// Validate self-signed certificates.
188216
/// All certificates should be self-signed and support only ED25519 signature.
189217
pub fn validate_self_sign_cert(
@@ -358,6 +386,15 @@ pub fn validate_role_data(
358386
{
359387
report.other("The role 0 certificate must be present", context);
360388
}
389+
390+
// Can contain different kind of address URIs, but for role 0
391+
// there should be at least 1 stake address
392+
if !metadata.certificate_uris.contain_stake_address(0) {
393+
report.missing_field(
394+
"The role 0 certificate must have at least one stake address",
395+
context,
396+
);
397+
}
361398
} else {
362399
// For other roles there still must be exactly one certificate at 0 index, but it must
363400
// have the `undefined` value.

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

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,11 @@
22
33
mod update_rbac;
44

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

107
use anyhow::Context;
118
use c509_certificate::c509::C509;
12-
use cardano_blockchain_types::{hashes::TransactionId, Point, StakeAddress, TxnIndex};
9+
use cardano_blockchain_types::{hashes::TransactionId, Point, TxnIndex};
1310
use catalyst_types::{
1411
catalyst_id::{key_rotation::KeyRotation, role_index::RoleId, CatalystId},
1512
conversion::zero_out_last_n_bytes,
@@ -249,11 +246,12 @@ impl RegistrationChain {
249246
.and_then(|rdr| rdr.encryption_key_from_rotation(rotation))
250247
}
251248

252-
/// Returns a set of role 0 stake addresses.
253-
#[must_use]
254-
pub fn role_0_stake_addresses(&self) -> HashSet<StakeAddress> {
255-
self.inner.certificate_uris.stake_addresses(0)
256-
}
249+
// FIXME
250+
// /// Returns a set of role 0 stake addresses.
251+
// #[must_use]
252+
// pub fn role_0_stake_addresses(&self) -> HashSet<StakeAddress> {
253+
// self.inner.certificate_uris.stake_addresses(0)
254+
// }
257255
}
258256

259257
/// Inner structure of registration chain.

0 commit comments

Comments
 (0)