Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5b233fc
crypto: lazy_static removed, light parser added
michalkucharczyk Nov 9, 2023
3ab3733
fuzzer added
michalkucharczyk Nov 9, 2023
7e6bdb3
fix
michalkucharczyk Nov 9, 2023
f92f50f
cleanup
michalkucharczyk Nov 9, 2023
1a52c98
license added
michalkucharczyk Nov 9, 2023
b0167c3
std gate added
michalkucharczyk Nov 9, 2023
b6dbf0d
address uri moved to src/
michalkucharczyk Nov 9, 2023
eaf5de0
Apply suggestions from code review
michalkucharczyk Nov 13, 2023
047eb83
parsing error added
michalkucharczyk Nov 13, 2023
b33de54
fix
michalkucharczyk Nov 13, 2023
ee7f179
Merge remote-tracking branch 'origin/master' into mku-crypto-address-…
michalkucharczyk Nov 13, 2023
a3b9ded
Update substrate/primitives/core/src/address_uri.rs
michalkucharczyk Nov 13, 2023
3aad2c5
Update substrate/primitives/core/src/address_uri.rs
michalkucharczyk Nov 13, 2023
d556091
review suggestions
michalkucharczyk Nov 13, 2023
40486a6
fuzzer fixed
michalkucharczyk Nov 13, 2023
77ad7f1
review suggestion
michalkucharczyk Nov 13, 2023
93bc851
naming
michalkucharczyk Nov 13, 2023
33dd1f3
missed review suggesions + fixes
michalkucharczyk Nov 13, 2023
0476638
Update substrate/primitives/core/src/address_uri.rs
michalkucharczyk Nov 13, 2023
0dddd34
fix
michalkucharczyk Nov 13, 2023
2500fe0
Merge remote-tracking branch 'origin/master' into mku-crypto-address-…
michalkucharczyk Nov 13, 2023
52f8aa2
Merge branch 'master' into mku-crypto-address-uri-parser
michalkucharczyk Nov 13, 2023
abff740
".git/.scripts/commands/update-ui/update-ui.sh"
Nov 13, 2023
b2bb315
test-pallet-ui: trybuild-overwrite
michalkucharczyk Nov 13, 2023
13582d3
Merge branch 'master' into mku-crypto-address-uri-parser
michalkucharczyk Nov 13, 2023
9fa5ff8
better error communication
michalkucharczyk Nov 16, 2023
80532e7
fuzzer added to workspace
michalkucharczyk Nov 16, 2023
69d7c7f
fix
michalkucharczyk Nov 16, 2023
ad71461
fix
michalkucharczyk Nov 16, 2023
ab059c3
fix indexing to be 0-based
michalkucharczyk Nov 16, 2023
f6c7bfd
Merge branch 'master' into mku-crypto-address-uri-parser
michalkucharczyk Nov 16, 2023
06dd2fd
Merge branch 'master' into mku-crypto-address-uri-parser
michalkucharczyk Nov 17, 2023
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
Prev Previous commit
Next Next commit
parsing error added
  • Loading branch information
michalkucharczyk committed Nov 13, 2023
commit 047eb835f1ec872c141e7d7bcf735a770c17f565
120 changes: 82 additions & 38 deletions substrate/primitives/core/src/address_uri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,45 @@
//! Little util for parsing an address URI. Replaces regular expressions.

/// A container for results of parsing the address uri string.
///
/// Intended to be equivalent of:
/// `Regex::new(r"^(?P<phrase>[a-zA-Z0-9 ]+)?(?P<path>(//?[^/]+)*)(///(?P<password>.*))?$")`
/// which also handles soft and hard derivation paths:
/// `Regex::new(r"/(/?[^/]+)")`
///
/// Example:
/// ```
/// use sp_core::crypto::AddressUri;
/// let manual_result = AddressUri::parse("hello world/s//h///pass");
/// assert_eq!(
/// manual_result.unwrap(),
/// AddressUri { phrase: Some("hello world"), paths: vec!["s", "/h"], pass: Some("pass") }
/// );
/// ```
#[derive(Debug, PartialEq)]
pub struct AddressUri<'a> {
pub ss58: Option<&'a str>,
/// Phrase, hexadecimal string, or ss58-compatible string.
pub phrase: Option<&'a str>,
/// Key derivation paths, ordered as in input string,
pub paths: Vec<&'a str>,
/// Password.
pub pass: Option<&'a str>,
}

/// Errors that are possible during parsing the address URI.
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone, Copy)]
pub enum Error {
#[error("Invalid character in phrase")]
InvalidCharacterInPhrase,
#[error("Invalid character in password")]
InvalidCharacterInPass,
#[error("Invalid character in hard path")]
InvalidCharacterInHardPath,
#[error("Invalid character in soft path")]
InvalidCharacterInSoftPath,
}

impl<'a> AddressUri<'a> {
fn extract_prefix(input: &mut &'a str, is_allowed: &dyn Fn(char) -> bool) -> Option<&'a str> {
let output = input.trim_start_matches(is_allowed);
Expand All @@ -35,11 +67,8 @@ impl<'a> AddressUri<'a> {
}

/// Parses the given string.
///
/// Intended to be equivalent of:
/// Regex::new(r"^(?P<phrase>[a-zA-Z0-9 ]+)?(?P<path>(//?[^/]+)*)(///(?P<password>.*))?$")
pub fn parse(mut input: &'a str) -> Option<Self> {
let ss58 = Self::extract_prefix(&mut input, &|ch: char| {
pub fn parse(mut input: &'a str) -> Result<Self, Error> {
let phrase = Self::extract_prefix(&mut input, &|ch: char| {
ch.is_ascii_digit() || ch.is_ascii_alphabetic() || ch == ' '
});

Expand All @@ -55,7 +84,7 @@ impl<'a> AddressUri<'a> {
} else if let Some(mut maybe_hard) = input.strip_prefix("//") {
let Some(mut path) = Self::extract_prefix(&mut maybe_hard, &|ch: char| ch != '/')
else {
return None;
return Err(Error::InvalidCharacterInHardPath);
};
assert!(path.len() > 0);
// hard path shall contain leading '/', so take it from input.
Expand All @@ -65,16 +94,20 @@ impl<'a> AddressUri<'a> {
} else if let Some(mut maybe_soft) = input.strip_prefix("/") {
let Some(path) = Self::extract_prefix(&mut maybe_soft, &|ch: char| ch != '/')
else {
return None;
return Err(Error::InvalidCharacterInSoftPath);
};
paths.push(path);
maybe_soft
} else {
return None;
return if pass.is_some() {
Err(Error::InvalidCharacterInPass)
} else {
Err(Error::InvalidCharacterInPhrase)
};
}
}

Some(Self { ss58, paths, pass })
Ok(Self { phrase, paths, pass })
}
}

Expand All @@ -91,9 +124,12 @@ mod tests {
fn check_with_regex(input: &str) {
let regex_result = SECRET_PHRASE_REGEX.captures(input);
let manual_result = AddressUri::parse(input);
assert_eq!(regex_result.is_some(), manual_result.is_some());
if let (Some(regex_result), Some(manual_result)) = (regex_result, manual_result) {
assert_eq!(regex_result.name("phrase").map(|ss58| ss58.as_str()), manual_result.ss58);
assert_eq!(regex_result.is_some(), manual_result.is_ok());
if let (Some(regex_result), Ok(manual_result)) = (regex_result, manual_result) {
assert_eq!(
regex_result.name("phrase").map(|phrase| phrase.as_str()),
manual_result.phrase
);

let manual_paths = manual_result
.paths
Expand All @@ -103,31 +139,34 @@ mod tests {
.join("");

assert_eq!(regex_result.name("path").unwrap().as_str().to_string(), manual_paths);
assert_eq!(regex_result.name("password").map(|ss58| ss58.as_str()), manual_result.pass);
assert_eq!(
regex_result.name("password").map(|phrase| phrase.as_str()),
manual_result.pass
);
}
}

fn check(input: &str, result: Option<AddressUri>) {
fn check(input: &str, result: Result<AddressUri, Error>) {
let manual_result = AddressUri::parse(input);
assert_eq!(manual_result, result);
check_with_regex(input);
}

#[test]
fn test00() {
check("///", Some(AddressUri { ss58: None, pass: Some(""), paths: vec![] }));
check("///", Ok(AddressUri { phrase: None, pass: Some(""), paths: vec![] }));
}

#[test]
fn test01() {
check("////////", Some(AddressUri { ss58: None, pass: Some("/////"), paths: vec![] }))
check("////////", Ok(AddressUri { phrase: None, pass: Some("/////"), paths: vec![] }))
}

#[test]
fn test02() {
check(
"sdasd///asda",
Some(AddressUri { ss58: Some("sdasd"), pass: Some("asda"), paths: vec![] }),
Ok(AddressUri { phrase: Some("sdasd"), pass: Some("asda"), paths: vec![] }),
);
//
}
Expand All @@ -136,35 +175,35 @@ mod tests {
fn test03() {
check(
"sdasd//asda",
Some(AddressUri { ss58: Some("sdasd"), pass: None, paths: vec!["/asda"] }),
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["/asda"] }),
);
}

#[test]
fn test04() {
check("sdasd//a", Some(AddressUri { ss58: Some("sdasd"), pass: None, paths: vec!["/a"] }));
check("sdasd//a", Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["/a"] }));
}

#[test]
fn test05() {
check("sdasd//", None);
check("sdasd//", Err(Error::InvalidCharacterInHardPath));
//
}

#[test]
fn test06() {
check(
"sdasd/xx//asda",
Some(AddressUri { ss58: Some("sdasd"), pass: None, paths: vec!["xx", "/asda"] }),
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["xx", "/asda"] }),
);
}

#[test]
fn test07() {
check(
"sdasd/xx//a/b//c///pass",
Some(AddressUri {
ss58: Some("sdasd"),
Ok(AddressUri {
phrase: Some("sdasd"),
pass: Some("pass"),
paths: vec!["xx", "/a", "b", "/c"],
}),
Expand All @@ -175,84 +214,89 @@ mod tests {
fn test08() {
check(
"sdasd/xx//a",
Some(AddressUri { ss58: Some("sdasd"), pass: None, paths: vec!["xx", "/a"] }),
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["xx", "/a"] }),
);
}

#[test]
fn test09() {
check("sdasd/xx//", None);
check("sdasd/xx//", Err(Error::InvalidCharacterInHardPath));
}

#[test]
fn test10() {
check(
"sdasd/asda",
Some(AddressUri { ss58: Some("sdasd"), pass: None, paths: vec!["asda"] }),
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["asda"] }),
);
}

#[test]
fn test11() {
check(
"sdasd/asda//x",
Some(AddressUri { ss58: Some("sdasd"), pass: None, paths: vec!["asda", "/x"] }),
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["asda", "/x"] }),
);
}

#[test]
fn test12() {
check("sdasd/a", Some(AddressUri { ss58: Some("sdasd"), pass: None, paths: vec!["a"] }));
check("sdasd/a", Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["a"] }));
}

#[test]
fn test13() {
check("sdasd/", None);
check("sdasd/", Err(Error::InvalidCharacterInSoftPath));
}

#[test]
fn test14() {
check("sdasd", Some(AddressUri { ss58: Some("sdasd"), pass: None, paths: vec![] }));
check("sdasd", Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec![] }));
}

#[test]
fn test15() {
check("sd.asd", None);
check("sd.asd", Err(Error::InvalidCharacterInPhrase));
}

#[test]
fn test16() {
check("sd.asd/asd.a", None);
check("sd.asd/asd.a", Err(Error::InvalidCharacterInPhrase));
}

#[test]
fn test17() {
check("sd.asd//asd.a", None);
check("sd.asd//asd.a", Err(Error::InvalidCharacterInPhrase));
}

#[test]
fn test18() {
check(
"sdasd/asd.a",
Some(AddressUri { ss58: Some("sdasd"), pass: None, paths: vec!["asd.a"] }),
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["asd.a"] }),
);
}

#[test]
fn test19() {
check(
"sdasd//asd.a",
Some(AddressUri { ss58: Some("sdasd"), pass: None, paths: vec!["/asd.a"] }),
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["/asd.a"] }),
);
}

#[test]
fn test20() {
check("///\n", None);
check("///\n", Err(Error::InvalidCharacterInPass));
}

#[test]
fn test21() {
check("///a\n", None);
check("///a\n", Err(Error::InvalidCharacterInPass));
}

#[test]
fn test22() {
check("sd.asd///asd.a\n", Err(Error::InvalidCharacterInPhrase));
}
}
26 changes: 15 additions & 11 deletions substrate/primitives/core/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub use ss58_registry::{from_known_address_format, Ss58AddressFormat, Ss58Addres
pub use zeroize::Zeroize;

#[cfg(feature = "std")]
use crate::address_uri::AddressUri;
pub use crate::address_uri::{AddressUri, Error as AddressUriError};

/// The root phrase for our publicly known keys.
pub const DEV_PHRASE: &str =
Expand Down Expand Up @@ -83,8 +83,8 @@ impl<S, T: UncheckedFrom<S>> UncheckedInto<T> for S {
#[cfg(feature = "full_crypto")]
pub enum SecretStringError {
/// The overall format was invalid (e.g. the seed phrase contained symbols).
#[cfg_attr(feature = "std", error("Invalid format"))]
InvalidFormat,
#[cfg_attr(feature = "std", error("Invalid format {0}"))]
InvalidFormat(#[from] AddressUriError),
/// The seed phrase provided is not a valid BIP39 phrase.
#[cfg_attr(feature = "std", error("Invalid phrase"))]
InvalidPhrase,
Expand Down Expand Up @@ -236,6 +236,10 @@ pub enum PublicError {
InvalidPath,
#[cfg_attr(feature = "std", error("Disallowed SS58 Address Format for this datatype."))]
FormatNotAllowed,
#[cfg_attr(feature = "std", error("Password not allowed."))]
PasswordNotAllowed,
#[cfg_attr(feature = "std", error("Incorrect URI syntax {0}."))]
MalformedUri(#[from] AddressUriError),
}

#[cfg(feature = "std")]
Expand Down Expand Up @@ -418,11 +422,11 @@ pub fn set_default_ss58_version(new_default: Ss58AddressFormat) {
#[cfg(feature = "std")]
impl<T: Sized + AsMut<[u8]> + AsRef<[u8]> + Public + Derive> Ss58Codec for T {
fn from_string(s: &str) -> Result<Self, PublicError> {
let cap = AddressUri::parse(s).ok_or(PublicError::InvalidFormat)?;
let cap = AddressUri::parse(s)?;
if cap.pass.is_some() {
return Err(PublicError::InvalidFormat);
return Err(PublicError::PasswordNotAllowed);
}
let s = cap.ss58.unwrap_or(DEV_ADDRESS);
let s = cap.phrase.unwrap_or(DEV_ADDRESS);
let addr = if let Some(stripped) = s.strip_prefix("0x") {
let d = array_bytes::hex2bytes(stripped).map_err(|_| PublicError::InvalidFormat)?;
Self::from_slice(&d).map_err(|()| PublicError::BadLength)?
Expand All @@ -438,11 +442,11 @@ impl<T: Sized + AsMut<[u8]> + AsRef<[u8]> + Public + Derive> Ss58Codec for T {
}

fn from_string_with_version(s: &str) -> Result<(Self, Ss58AddressFormat), PublicError> {
let cap = AddressUri::parse(s).ok_or(PublicError::InvalidFormat)?;
let cap = AddressUri::parse(s)?;
if cap.pass.is_some() {
return Err(PublicError::InvalidFormat);
return Err(PublicError::PasswordNotAllowed);
}
let (addr, v) = Self::from_ss58check_with_version(cap.ss58.unwrap_or(DEV_ADDRESS))?;
let (addr, v) = Self::from_ss58check_with_version(cap.phrase.unwrap_or(DEV_ADDRESS))?;
if cap.paths.is_empty() {
Ok((addr, v))
} else {
Expand Down Expand Up @@ -811,8 +815,8 @@ impl sp_std::str::FromStr for SecretUri {
type Err = SecretStringError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let cap = AddressUri::parse(s).ok_or(SecretStringError::InvalidFormat)?;
let phrase = cap.ss58.unwrap_or(DEV_PHRASE);
let cap = AddressUri::parse(s)?;
let phrase = cap.phrase.unwrap_or(DEV_PHRASE);

Ok(Self {
phrase: SecretString::from_str(phrase).expect("Returns infallible error; qed"),
Expand Down