Skip to content

Commit

Permalink
Introduce Cip0134Uri type
Browse files Browse the repository at this point in the history
  • Loading branch information
stanislav-tkach committed Dec 11, 2024
1 parent d911863 commit f768c1e
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 35 deletions.
2 changes: 0 additions & 2 deletions rust/rbac-registration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
121 changes: 97 additions & 24 deletions rust/rbac-registration/src/cardano/cip509/utils/cip134.rs
Original file line number Diff line number Diff line change
@@ -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<Address> {
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<Self> {
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)]
Expand All @@ -35,23 +96,25 @@ 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);
}
}

#[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"));
}

Expand All @@ -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());
Expand All @@ -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());
}
}
2 changes: 1 addition & 1 deletion rust/rbac-registration/src/cardano/cip509/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Utility functions for CIP-509
pub mod cip19;
pub use cip134::parse_cip0134_uri;
pub use cip134::Cip0134Uri;

mod cip134;
19 changes: 11 additions & 8 deletions rust/rbac-registration/src/cardano/cip509/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use super::{
},
utils::{
cip19::{compare_key_hash, extract_key_hash},
parse_cip0134_uri,
Cip0134Uri,
},
Cip509, TxInputHash, TxWitness,
};
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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());
}
}
},
_ => {
Expand Down

0 comments on commit f768c1e

Please sign in to comment.