Skip to content

Commit 7ac4e5b

Browse files
authored
fix(rust/rbac-registration): Validate witness for stake address and payment address URIs in certificate (#507)
* fix: validate all uris in cert that they should be witness Signed-off-by: bkioshn <[email protected]> * chore: fix typo Signed-off-by: bkioshn <[email protected]> * fix: get addresses Signed-off-by: bkioshn <[email protected]> * test: add get role address test Signed-off-by: bkioshn <[email protected]> --------- Signed-off-by: bkioshn <[email protected]>
1 parent 031b613 commit 7ac4e5b

File tree

4 files changed

+113
-50
lines changed

4 files changed

+113
-50
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 extracted from certificate URIs of a specific role.
280279
#[must_use]
281-
pub fn role_0_stake_addresses(&self) -> HashSet<StakeAddress> {
280+
pub fn certificate_addresses(
281+
&self,
282+
role: 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.role_addresses(role))
285287
.unwrap_or_default()
286288
}
287289

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

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -72,24 +72,57 @@ 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 role.
7676
#[must_use]
77-
pub fn stake_addresses(
77+
pub fn role_addresses(
7878
&self,
79-
index: usize,
80-
) -> HashSet<StakeAddress> {
79+
role: usize,
80+
) -> HashSet<Address> {
8181
let mut result = HashSet::new();
8282

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

9090
result
9191
}
9292

93+
/// Returns a list of stake addresses by the given role.
94+
#[must_use]
95+
pub fn role_stake_addresses(
96+
&self,
97+
role: usize,
98+
) -> HashSet<StakeAddress> {
99+
self.role_addresses(role)
100+
.iter()
101+
.filter_map(|address| {
102+
match address {
103+
Address::Stake(a) => Some(a.clone().into()),
104+
_ => None,
105+
}
106+
})
107+
.collect()
108+
}
109+
110+
/// Returns a list of all stake addresses.
111+
#[must_use]
112+
pub fn stake_addresses(&self) -> HashSet<StakeAddress> {
113+
self.x_uris()
114+
.values()
115+
.chain(self.c_uris().values())
116+
.flat_map(|uris| uris.iter())
117+
.filter_map(|uri| {
118+
match uri.address() {
119+
Address::Stake(a) => Some(a.clone().into()),
120+
_ => None,
121+
}
122+
})
123+
.collect()
124+
}
125+
93126
/// Return the updated URIs set.
94127
///
95128
/// The resulting set includes all the data from both the original and a new one. In
@@ -289,18 +322,6 @@ fn extract_c509_uris(
289322
result
290323
}
291324

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-
304325
#[cfg(test)]
305326
mod tests {
306327

@@ -323,6 +344,9 @@ mod tests {
323344
let set = cip509.certificate_uris().unwrap();
324345
assert!(!set.is_empty());
325346
assert!(set.c_uris().is_empty());
347+
assert_eq!(set.role_addresses(0).len(), 1);
348+
assert_eq!(set.role_stake_addresses(0).len(), 1);
349+
assert_eq!(set.stake_addresses().len(), 1);
326350

327351
let x_uris = set.x_uris();
328352
assert_eq!(x_uris.len(), 1);

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 and spendable"
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.role_stake_addresses(0).is_empty() {
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: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,10 @@ impl RegistrationChain {
249249
.and_then(|rdr| rdr.encryption_key_from_rotation(rotation))
250250
}
251251

252-
/// Returns a set of role 0 stake addresses.
252+
/// Returns all stake addresses associated to this registration.
253253
#[must_use]
254-
pub fn role_0_stake_addresses(&self) -> HashSet<StakeAddress> {
255-
self.inner.certificate_uris.stake_addresses(0)
254+
pub fn stake_addresses(&self) -> HashSet<StakeAddress> {
255+
self.inner.certificate_uris.stake_addresses()
256256
}
257257
}
258258

0 commit comments

Comments
 (0)