diff --git a/rust/rbac-registration/Cargo.toml b/rust/rbac-registration/Cargo.toml
index 9b1936d685..153c0a5392 100644
--- a/rust/rbac-registration/Cargo.toml
+++ b/rust/rbac-registration/Cargo.toml
@@ -21,13 +21,11 @@ workspace = true
hex = "0.4.3"
anyhow = "1.0.89"
strum_macros = "0.26.4"
-regex = "1.11.0"
minicbor = { version = "0.25.1", features = ["alloc", "derive", "half"] }
brotli = "7.0.0"
zstd = "0.13.2"
x509-cert = "0.2.5"
der-parser = "9.0.0"
-bech32 = "0.11.0"
dashmap = "6.1.0"
blake2b_simd = "1.0.2"
tracing = "0.1.40"
diff --git a/rust/rbac-registration/src/cardano/cip509/utils/cip134.rs b/rust/rbac-registration/src/cardano/cip509/utils/cip134.rs
index 714c62b142..9b346b8d60 100644
--- a/rust/rbac-registration/src/cardano/cip509/utils/cip134.rs
+++ b/rust/rbac-registration/src/cardano/cip509/utils/cip134.rs
@@ -1,30 +1,91 @@
//! Utility functions for CIP-0134 address.
+// Ignore URIs that are used in tests and doc-examples.
+// cSpell:ignoreRegExp web\+cardano:.+
+
+use std::fmt::{Display, Formatter};
+
use anyhow::{anyhow, Context, Result};
use pallas::ledger::addresses::Address;
-/// Parses CIP-0134 URI and returns an address.
-///
-/// # Errors
-/// - Invalid URI.
+/// An URI in the CIP-0134 format.
///
-/// # Examples
+/// See the [proposal] for more details.
///
-/// ```
-/// use pallas::ledger::addresses::{Address, Network};
-/// use rbac_registration::cardano::cip509::utils::parse_cip0134_uri;
-///
-/// let uri = "web+cardano://addr/stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw";
-/// let Address::Stake(address) = parse_cip0134_uri(uri).unwrap() else {
-/// panic!("Unexpected address type");
-/// };
-/// assert_eq!(address.network(), Network::Mainnet);
-/// ```
-pub fn parse_cip0134_uri(uri: &str) -> Result
{
- let bech32 = uri
- .strip_prefix("web+cardano://addr/")
- .ok_or_else(|| anyhow!("Missing schema part of URI"))?;
- Address::from_bech32(bech32).context("Unable to parse bech32 part of URI")
+/// [proposal]: https://github.com/cardano-foundation/CIPs/pull/888
+#[derive(Debug)]
+pub struct Cip0134Uri {
+ /// A URI string.
+ uri: String,
+ /// An address parsed from the URI.
+ address: Address,
+}
+
+impl Cip0134Uri {
+ /// Creates a new `Cip0134Uri` instance by parsing the given URI.
+ ///
+ /// # Errors
+ /// - Invalid URI.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use rbac_registration::cardano::cip509::utils::Cip0134Uri;
+ ///
+ /// let uri = "web+cardano://addr/stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw";
+ /// let cip0134_uri = Cip0134Uri::parse(uri).unwrap();
+ /// ```
+ pub fn parse(uri: &str) -> Result {
+ let bech32 = uri
+ .strip_prefix("web+cardano://addr/")
+ .ok_or_else(|| anyhow!("Missing schema part of URI"))?;
+ let address = Address::from_bech32(bech32).context("Unable to parse bech32 part of URI")?;
+
+ Ok(Self {
+ uri: uri.to_owned(),
+ address,
+ })
+ }
+
+ /// Returns a URI string.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use rbac_registration::cardano::cip509::utils::Cip0134Uri;
+ ///
+ /// let uri = "web+cardano://addr/stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw";
+ /// let cip0134_uri = Cip0134Uri::parse(uri).unwrap();
+ /// assert_eq!(cip0134_uri.uri(), uri);
+ #[must_use]
+ pub fn uri(&self) -> &str {
+ &self.uri
+ }
+
+ /// Returns a URI string.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use pallas::ledger::addresses::{Address, Network};
+ /// use rbac_registration::cardano::cip509::utils::Cip0134Uri;
+ ///
+ /// let uri = "web+cardano://addr/stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw";
+ /// let cip0134_uri = Cip0134Uri::parse(uri).unwrap();
+ /// let Address::Stake(address) = cip0134_uri.address() else {
+ /// panic!("Unexpected address type");
+ /// };
+ /// assert_eq!(address.network(), Network::Mainnet);
+ #[must_use]
+ pub fn address(&self) -> &Address {
+ &self.address
+ }
+}
+
+impl Display for Cip0134Uri {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.uri())
+ }
}
#[cfg(test)]
@@ -35,15 +96,17 @@ mod tests {
#[test]
fn invalid_prefix() {
+ // cSpell:disable
let test_uris = [
"addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x",
"//addr/addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x",
"web+cardano:/addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x",
"somthing+unexpected://addr/addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x",
];
+ // cSpell:enable
for uri in test_uris {
- let err = format!("{:?}", parse_cip0134_uri(uri).expect_err(&format!("{uri}")));
+ let err = format!("{:?}", Cip0134Uri::parse(uri).expect_err(&format!("{uri}")));
assert_eq!("Missing schema part of URI", err);
}
}
@@ -51,7 +114,7 @@ mod tests {
#[test]
fn invalid_bech32() {
let uri = "web+cardano://addr/adr1qx2fxv2umyh";
- let err = format!("{:?}", parse_cip0134_uri(uri).unwrap_err());
+ let err = format!("{:?}", Cip0134Uri::parse(uri).unwrap_err());
assert!(err.starts_with("Unable to parse bech32 part of URI"));
}
@@ -76,7 +139,8 @@ mod tests {
];
for (uri, network, payload) in test_data {
- let Address::Stake(address) = parse_cip0134_uri(uri).unwrap() else {
+ let cip0134_uri = Cip0134Uri::parse(uri).expect(&format!("{uri}"));
+ let Address::Stake(address) = cip0134_uri.address else {
panic!("Unexpected address type ({uri})");
};
assert_eq!(network, address.network());
@@ -102,10 +166,19 @@ mod tests {
];
for (uri, network) in test_data {
- let Address::Shelley(address) = parse_cip0134_uri(uri).unwrap() else {
+ let cip0134_uri = Cip0134Uri::parse(uri).expect(&format!("{uri}"));
+ let Address::Shelley(address) = cip0134_uri.address else {
panic!("Unexpected address type ({uri})");
};
assert_eq!(network, address.network());
}
}
+
+ // The Display should return the original URI.
+ #[test]
+ fn display() {
+ let uri = "web+cardano://addr/stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw";
+ let cip0134_uri = Cip0134Uri::parse(uri).expect(&format!("{uri}"));
+ assert_eq!(uri, cip0134_uri.to_string());
+ }
}
diff --git a/rust/rbac-registration/src/cardano/cip509/utils/mod.rs b/rust/rbac-registration/src/cardano/cip509/utils/mod.rs
index d9e71ab2e4..ab7c3954d6 100644
--- a/rust/rbac-registration/src/cardano/cip509/utils/mod.rs
+++ b/rust/rbac-registration/src/cardano/cip509/utils/mod.rs
@@ -1,6 +1,6 @@
//! Utility functions for CIP-509
pub mod cip19;
-pub use cip134::parse_cip0134_uri;
+pub use cip134::Cip0134Uri;
mod cip134;
diff --git a/rust/rbac-registration/src/cardano/cip509/validation.rs b/rust/rbac-registration/src/cardano/cip509/validation.rs
index a49f635b61..205957f546 100644
--- a/rust/rbac-registration/src/cardano/cip509/validation.rs
+++ b/rust/rbac-registration/src/cardano/cip509/validation.rs
@@ -40,7 +40,7 @@ use super::{
},
utils::{
cip19::{compare_key_hash, extract_key_hash},
- parse_cip0134_uri,
+ Cip0134Uri,
},
Cip509, TxInputHash, TxWitness,
};
@@ -169,11 +169,12 @@ pub(crate) fn validate_stake_public_key(
// Extract the CIP19 hash and push into
// array
- if let Ok(Address::Stake(a)) =
- parse_cip0134_uri(&addr)
- {
- pk_addrs
- .push(a.payload().as_hash().to_vec());
+ if let Ok(uri) = Cip0134Uri::parse(&addr) {
+ if let Address::Stake(a) = uri.address() {
+ pk_addrs.push(
+ a.payload().as_hash().to_vec(),
+ );
+ }
}
},
Err(e) => {
@@ -222,8 +223,10 @@ pub(crate) fn validate_stake_public_key(
if name.gn_type() == &c509_certificate::general_names::general_name::GeneralNameTypeRegistry::UniformResourceIdentifier {
match name.gn_value() {
GeneralNameValue::Text(s) => {
- if let Ok(Address::Stake(a)) = parse_cip0134_uri(s) {
- pk_addrs.push(a.payload().as_hash().to_vec());
+ if let Ok(uri) = Cip0134Uri::parse(s) {
+ if let Address::Stake(a) = uri.address() {
+ pk_addrs.push(a.payload().as_hash().to_vec());
+ }
}
},
_ => {