mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-28 17:57:56 +00:00
crypto: lazy_static removed, light parser for address URI added (#2250)
The `lazy_static` package does not work well in `no-std`: it requires `spin_no_std` feature, which also will propagate into `std` if enabled. This is not what we want. This PR provides simple address uri parser which allows to get rid of _regex_ which was used to parse the address uri, what in turns allows to remove lazy_static. Three regular expressions (`SS58_REGEX`,`SECRET_PHRASE_REGEX`,`JUNCTION_REGEX`) were replaced with the parser which unifies all of them. The new parser does not support Unicode, it is ASCII only. Related to: #2044 --------- Co-authored-by: Bastian Köcher <git@kchr.de> Co-authored-by: Koute <koute@users.noreply.github.com> Co-authored-by: command-bot <>
This commit is contained in:
committed by
GitHub
parent
3ab2bc9ff3
commit
5007e2dd5c
Generated
+36
-9
@@ -7408,6 +7408,17 @@ dependencies = [
|
||||
"rle-decode-fast",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.4"
|
||||
@@ -8182,9 +8193,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
version = "2.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
|
||||
[[package]]
|
||||
name = "memfd"
|
||||
@@ -14297,14 +14308,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.3"
|
||||
version = "1.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a"
|
||||
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.3.6",
|
||||
"regex-syntax 0.7.4",
|
||||
"regex-automata 0.4.3",
|
||||
"regex-syntax 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -14321,10 +14332,16 @@ name = "regex-automata"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69"
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.7.4",
|
||||
"regex-syntax 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -14335,9 +14352,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.4"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "remote-ext-tests-bags-list"
|
||||
@@ -17535,6 +17552,16 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sp-core-fuzz"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libfuzzer-sys",
|
||||
"regex",
|
||||
"sp-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sp-core-hashing"
|
||||
version = "9.0.0"
|
||||
|
||||
@@ -405,6 +405,7 @@ members = [
|
||||
"substrate/primitives/consensus/sassafras",
|
||||
"substrate/primitives/consensus/slots",
|
||||
"substrate/primitives/core",
|
||||
"substrate/primitives/core/fuzz",
|
||||
"substrate/primitives/core/hashing",
|
||||
"substrate/primitives/core/hashing/proc-macro",
|
||||
"substrate/primitives/crypto/ec-utils",
|
||||
|
||||
+6
-6
@@ -6,8 +6,8 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied
|
||||
|
|
||||
= help: the following other types implement trait `WrapperTypeDecode`:
|
||||
Box<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Rc<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Arc<T>
|
||||
= note: required for `Bar` to implement `Decode`
|
||||
= note: required for `Bar` to implement `FullCodec`
|
||||
@@ -44,8 +44,8 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied
|
||||
bytes::bytes::Bytes
|
||||
Cow<'a, T>
|
||||
parity_scale_codec::Ref<'a, T, U>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Rc<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Arc<T>
|
||||
Vec<T>
|
||||
and $N others
|
||||
@@ -81,8 +81,8 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied
|
||||
|
|
||||
= help: the following other types implement trait `WrapperTypeDecode`:
|
||||
Box<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Rc<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Arc<T>
|
||||
= note: required for `Bar` to implement `Decode`
|
||||
= note: required for `Bar` to implement `FullCodec`
|
||||
@@ -119,8 +119,8 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied
|
||||
bytes::bytes::Bytes
|
||||
Cow<'a, T>
|
||||
parity_scale_codec::Ref<'a, T, U>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Rc<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Arc<T>
|
||||
Vec<T>
|
||||
and $N others
|
||||
@@ -137,8 +137,8 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied
|
||||
|
|
||||
= help: the following other types implement trait `WrapperTypeDecode`:
|
||||
Box<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Rc<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Arc<T>
|
||||
= note: required for `Bar` to implement `Decode`
|
||||
= note: required for `Bar` to implement `FullCodec`
|
||||
@@ -177,8 +177,8 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied
|
||||
bytes::bytes::Bytes
|
||||
Cow<'a, T>
|
||||
parity_scale_codec::Ref<'a, T, U>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Rc<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Arc<T>
|
||||
Vec<T>
|
||||
and $N others
|
||||
|
||||
+6
-6
@@ -6,8 +6,8 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied
|
||||
|
|
||||
= help: the following other types implement trait `WrapperTypeDecode`:
|
||||
Box<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Rc<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Arc<T>
|
||||
= note: required for `Bar` to implement `Decode`
|
||||
= note: required for `Bar` to implement `FullCodec`
|
||||
@@ -44,8 +44,8 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied
|
||||
bytes::bytes::Bytes
|
||||
Cow<'a, T>
|
||||
parity_scale_codec::Ref<'a, T, U>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Rc<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Arc<T>
|
||||
Vec<T>
|
||||
and $N others
|
||||
@@ -81,8 +81,8 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied
|
||||
|
|
||||
= help: the following other types implement trait `WrapperTypeDecode`:
|
||||
Box<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Rc<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Arc<T>
|
||||
= note: required for `Bar` to implement `Decode`
|
||||
= note: required for `Bar` to implement `FullCodec`
|
||||
@@ -119,8 +119,8 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied
|
||||
bytes::bytes::Bytes
|
||||
Cow<'a, T>
|
||||
parity_scale_codec::Ref<'a, T, U>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Rc<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Arc<T>
|
||||
Vec<T>
|
||||
and $N others
|
||||
@@ -137,8 +137,8 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied
|
||||
|
|
||||
= help: the following other types implement trait `WrapperTypeDecode`:
|
||||
Box<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Rc<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Arc<T>
|
||||
= note: required for `Bar` to implement `Decode`
|
||||
= note: required for `Bar` to implement `FullCodec`
|
||||
@@ -177,8 +177,8 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied
|
||||
bytes::bytes::Bytes
|
||||
Cow<'a, T>
|
||||
parity_scale_codec::Ref<'a, T, U>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Rc<T>
|
||||
frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes
|
||||
Arc<T>
|
||||
Vec<T>
|
||||
and $N others
|
||||
|
||||
@@ -26,10 +26,8 @@ bs58 = { version = "0.5.0", default-features = false, optional = true }
|
||||
rand = { version = "0.8.5", features = ["small_rng"], optional = true }
|
||||
substrate-bip39 = { version = "0.4.4", optional = true }
|
||||
bip39 = { version = "2.0.0", default-features = false }
|
||||
regex = { version = "1.6.0", optional = true }
|
||||
zeroize = { version = "1.4.3", default-features = false }
|
||||
secrecy = { version = "0.8.0", default-features = false }
|
||||
lazy_static = { version = "1.4.0", default-features = false, optional = true }
|
||||
parking_lot = { version = "0.12.1", optional = true }
|
||||
ss58-registry = { version = "1.34.0", default-features = false }
|
||||
sp-std = { path = "../std", default-features = false}
|
||||
@@ -63,6 +61,8 @@ bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", rev = "cbc342e",
|
||||
[dev-dependencies]
|
||||
criterion = "0.4.0"
|
||||
serde_json = "1.0.108"
|
||||
lazy_static = "1.4.0"
|
||||
regex = "1.6.0"
|
||||
sp-core-hashing-proc-macro = { path = "hashing/proc-macro" }
|
||||
|
||||
[[bench]]
|
||||
@@ -92,7 +92,6 @@ std = [
|
||||
"hash256-std-hasher/std",
|
||||
"impl-serde/std",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"libsecp256k1/std",
|
||||
"log/std",
|
||||
"merlin/std",
|
||||
@@ -102,7 +101,6 @@ std = [
|
||||
"primitive-types/serde",
|
||||
"primitive-types/std",
|
||||
"rand",
|
||||
"regex",
|
||||
"scale-info/std",
|
||||
"schnorrkel/std",
|
||||
"secp256k1/global-context",
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "sp-core-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1.4.0"
|
||||
libfuzzer-sys = "0.4"
|
||||
regex = "1.10.2"
|
||||
|
||||
sp-core = { path = ".." }
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_address_uri"
|
||||
path = "fuzz_targets/fuzz_address_uri.rs"
|
||||
test = false
|
||||
doc = false
|
||||
@@ -0,0 +1,53 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_main]
|
||||
|
||||
extern crate libfuzzer_sys;
|
||||
extern crate regex;
|
||||
extern crate sp_core;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use regex::Regex;
|
||||
use sp_core::crypto::AddressUri;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref SECRET_PHRASE_REGEX: Regex = Regex::new(r"^(?P<phrase>[a-zA-Z0-9 ]+)?(?P<path>(//?[^/]+)*)(///(?P<password>.*))?$")
|
||||
.expect("constructed from known-good static value; qed");
|
||||
}
|
||||
|
||||
fuzz_target!(|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_ok());
|
||||
if manual_result.is_err() {
|
||||
let _ = format!("{}", manual_result.as_ref().err().unwrap());
|
||||
}
|
||||
if let (Some(regex_result), Ok(manual_result)) = (regex_result, manual_result) {
|
||||
assert_eq!(regex_result.name("phrase").map(|p| p.as_str()), manual_result.phrase);
|
||||
|
||||
let manual_paths = manual_result
|
||||
.paths
|
||||
.iter()
|
||||
.map(|s| "/".to_string() + s)
|
||||
.collect::<Vec<_>>()
|
||||
.join("");
|
||||
|
||||
assert_eq!(regex_result.name("path").unwrap().as_str().to_string(), manual_paths);
|
||||
assert_eq!(regex_result.name("password").map(|pass| pass.as_str()), manual_result.pass);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,432 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Little util for parsing an address URI. Replaces regular expressions.
|
||||
|
||||
#[cfg(all(not(feature = "std"), any(feature = "serde", feature = "full_crypto")))]
|
||||
use sp_std::{
|
||||
alloc::string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
/// 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> {
|
||||
/// 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)]
|
||||
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Error {
|
||||
#[cfg_attr(feature = "std", error("Invalid character in phrase:\n{0}"))]
|
||||
InvalidCharacterInPhrase(InvalidCharacterInfo),
|
||||
#[cfg_attr(feature = "std", error("Invalid character in password:\n{0}"))]
|
||||
InvalidCharacterInPass(InvalidCharacterInfo),
|
||||
#[cfg_attr(feature = "std", error("Missing character in hard path:\n{0}"))]
|
||||
MissingCharacterInHardPath(InvalidCharacterInfo),
|
||||
#[cfg_attr(feature = "std", error("Missing character in soft path:\n{0}"))]
|
||||
MissingCharacterInSoftPath(InvalidCharacterInfo),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Creates an instance of `Error::InvalidCharacterInPhrase` using given parameters.
|
||||
pub fn in_phrase(input: &str, pos: usize) -> Self {
|
||||
Self::InvalidCharacterInPhrase(InvalidCharacterInfo::new(input, pos))
|
||||
}
|
||||
/// Creates an instance of `Error::InvalidCharacterInPass` using given parameters.
|
||||
pub fn in_pass(input: &str, pos: usize) -> Self {
|
||||
Self::InvalidCharacterInPass(InvalidCharacterInfo::new(input, pos))
|
||||
}
|
||||
/// Creates an instance of `Error::MissingCharacterInHardPath` using given parameters.
|
||||
pub fn in_hard_path(input: &str, pos: usize) -> Self {
|
||||
Self::MissingCharacterInHardPath(InvalidCharacterInfo::new(input, pos))
|
||||
}
|
||||
/// Creates an instance of `Error::MissingCharacterInSoftPath` using given parameters.
|
||||
pub fn in_soft_path(input: &str, pos: usize) -> Self {
|
||||
Self::MissingCharacterInSoftPath(InvalidCharacterInfo::new(input, pos))
|
||||
}
|
||||
}
|
||||
|
||||
/// Complementary error information.
|
||||
///
|
||||
/// Strucutre contains complementary information about parsing address URI string.
|
||||
/// String contains a copy of an original URI string, 0-based integer indicates position of invalid
|
||||
/// character.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct InvalidCharacterInfo(String, usize);
|
||||
|
||||
impl InvalidCharacterInfo {
|
||||
fn new(info: &str, pos: usize) -> Self {
|
||||
Self(info.to_string(), pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_std::fmt::Display for InvalidCharacterInfo {
|
||||
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
|
||||
let (s, pos) = escape_string(&self.0, self.1);
|
||||
write!(f, "{s}\n{i}^", i = sp_std::iter::repeat(" ").take(pos).collect::<String>())
|
||||
}
|
||||
}
|
||||
|
||||
/// Escapes the control characters in given string, and recomputes the position if some characters
|
||||
/// were actually escaped.
|
||||
fn escape_string(input: &str, pos: usize) -> (String, usize) {
|
||||
let mut out = String::with_capacity(2 * input.len());
|
||||
let mut out_pos = 0;
|
||||
input
|
||||
.chars()
|
||||
.enumerate()
|
||||
.map(|(i, c)| {
|
||||
let esc = |c| (i, Some('\\'), c, 2);
|
||||
match c {
|
||||
'\t' => esc('t'),
|
||||
'\n' => esc('n'),
|
||||
'\r' => esc('r'),
|
||||
'\x07' => esc('a'),
|
||||
'\x08' => esc('b'),
|
||||
'\x0b' => esc('v'),
|
||||
'\x0c' => esc('f'),
|
||||
_ => (i, None, c, 1),
|
||||
}
|
||||
})
|
||||
.for_each(|(i, maybe_escape, c, increment)| {
|
||||
maybe_escape.map(|e| out.push(e));
|
||||
out.push(c);
|
||||
if i < pos {
|
||||
out_pos += increment;
|
||||
}
|
||||
});
|
||||
(out, out_pos)
|
||||
}
|
||||
|
||||
fn extract_prefix<'a>(input: &mut &'a str, is_allowed: &dyn Fn(char) -> bool) -> Option<&'a str> {
|
||||
let output = input.trim_start_matches(is_allowed);
|
||||
let prefix_len = input.len() - output.len();
|
||||
let prefix = if prefix_len > 0 { Some(&input[..prefix_len]) } else { None };
|
||||
*input = output;
|
||||
prefix
|
||||
}
|
||||
|
||||
fn strip_prefix(input: &mut &str, prefix: &str) -> bool {
|
||||
if let Some(stripped_input) = input.strip_prefix(prefix) {
|
||||
*input = stripped_input;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AddressUri<'a> {
|
||||
/// Parses the given string.
|
||||
pub fn parse(mut input: &'a str) -> Result<Self, Error> {
|
||||
let initial_input = input;
|
||||
let initial_input_len = input.len();
|
||||
let phrase = extract_prefix(&mut input, &|ch: char| {
|
||||
ch.is_ascii_digit() || ch.is_ascii_alphabetic() || ch == ' '
|
||||
});
|
||||
|
||||
let mut pass = None;
|
||||
let mut paths = Vec::new();
|
||||
while !input.is_empty() {
|
||||
let unstripped_input = input;
|
||||
if strip_prefix(&mut input, "///") {
|
||||
pass = Some(extract_prefix(&mut input, &|ch: char| ch != '\n').unwrap_or(""));
|
||||
} else if strip_prefix(&mut input, "//") {
|
||||
let path = extract_prefix(&mut input, &|ch: char| ch != '/')
|
||||
.ok_or(Error::in_hard_path(initial_input, initial_input_len - input.len()))?;
|
||||
assert!(path.len() > 0);
|
||||
// hard path shall contain leading '/', so take it from unstripped input.
|
||||
paths.push(&unstripped_input[1..path.len() + 2]);
|
||||
} else if strip_prefix(&mut input, "/") {
|
||||
paths.push(
|
||||
extract_prefix(&mut input, &|ch: char| ch != '/').ok_or(
|
||||
Error::in_soft_path(initial_input, initial_input_len - input.len()),
|
||||
)?,
|
||||
);
|
||||
} else {
|
||||
return Err(if pass.is_some() {
|
||||
Error::in_pass(initial_input, initial_input_len - input.len())
|
||||
} else {
|
||||
Error::in_phrase(initial_input, initial_input_len - input.len())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { phrase, paths, pass })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use regex::Regex;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref SECRET_PHRASE_REGEX: Regex = Regex::new(r"^(?P<phrase>[a-zA-Z0-9 ]+)?(?P<path>(//?[^/]+)*)(///(?P<password>.*))?$")
|
||||
.expect("constructed from known-good static value; qed");
|
||||
}
|
||||
|
||||
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_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
|
||||
.iter()
|
||||
.map(|s| "/".to_string() + s)
|
||||
.collect::<Vec<_>>()
|
||||
.join("");
|
||||
|
||||
assert_eq!(regex_result.name("path").unwrap().as_str().to_string(), manual_paths);
|
||||
assert_eq!(
|
||||
regex_result.name("password").map(|phrase| phrase.as_str()),
|
||||
manual_result.pass
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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("///", Ok(AddressUri { phrase: None, pass: Some(""), paths: vec![] }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test01() {
|
||||
check("////////", Ok(AddressUri { phrase: None, pass: Some("/////"), paths: vec![] }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test02() {
|
||||
check(
|
||||
"sdasd///asda",
|
||||
Ok(AddressUri { phrase: Some("sdasd"), pass: Some("asda"), paths: vec![] }),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test03() {
|
||||
check(
|
||||
"sdasd//asda",
|
||||
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["/asda"] }),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test04() {
|
||||
check("sdasd//a", Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["/a"] }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test05() {
|
||||
let input = "sdasd//";
|
||||
check(input, Err(Error::in_hard_path(input, 7)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test06() {
|
||||
check(
|
||||
"sdasd/xx//asda",
|
||||
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["xx", "/asda"] }),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test07() {
|
||||
check(
|
||||
"sdasd/xx//a/b//c///pass",
|
||||
Ok(AddressUri {
|
||||
phrase: Some("sdasd"),
|
||||
pass: Some("pass"),
|
||||
paths: vec!["xx", "/a", "b", "/c"],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test08() {
|
||||
check(
|
||||
"sdasd/xx//a",
|
||||
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["xx", "/a"] }),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test09() {
|
||||
let input = "sdasd/xx//";
|
||||
check(input, Err(Error::in_hard_path(input, 10)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test10() {
|
||||
check(
|
||||
"sdasd/asda",
|
||||
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["asda"] }),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test11() {
|
||||
check(
|
||||
"sdasd/asda//x",
|
||||
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["asda", "/x"] }),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test12() {
|
||||
check("sdasd/a", Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["a"] }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test13() {
|
||||
let input = "sdasd/";
|
||||
check(input, Err(Error::in_soft_path(input, 6)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test14() {
|
||||
check("sdasd", Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec![] }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test15() {
|
||||
let input = "sdasd.";
|
||||
check(input, Err(Error::in_phrase(input, 5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test16() {
|
||||
let input = "sd.asd/asd.a";
|
||||
check(input, Err(Error::in_phrase(input, 2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test17() {
|
||||
let input = "sd.asd//asd.a";
|
||||
check(input, Err(Error::in_phrase(input, 2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test18() {
|
||||
check(
|
||||
"sdasd/asd.a",
|
||||
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["asd.a"] }),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test19() {
|
||||
check(
|
||||
"sdasd//asd.a",
|
||||
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["/asd.a"] }),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test20() {
|
||||
let input = "///\n";
|
||||
check(input, Err(Error::in_pass(input, 3)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test21() {
|
||||
let input = "///a\n";
|
||||
check(input, Err(Error::in_pass(input, 4)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test22() {
|
||||
let input = "sd asd///asd.a\n";
|
||||
check(input, Err(Error::in_pass(input, 14)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_char_info_1() {
|
||||
let expected = "01234\n^";
|
||||
let f = format!("{}", InvalidCharacterInfo::new("01234", 0));
|
||||
assert_eq!(expected, f);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_char_info_2() {
|
||||
let expected = "01\n ^";
|
||||
let f = format!("{}", InvalidCharacterInfo::new("01", 1));
|
||||
assert_eq!(expected, f);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_char_info_3() {
|
||||
let expected = "01234\n ^";
|
||||
let f = format!("{}", InvalidCharacterInfo::new("01234", 2));
|
||||
assert_eq!(expected, f);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_char_info_4() {
|
||||
let expected = "012\\n456\n ^";
|
||||
let f = format!("{}", InvalidCharacterInfo::new("012\n456", 3));
|
||||
assert_eq!(expected, f);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_char_info_5() {
|
||||
let expected = "012\\n456\n ^";
|
||||
let f = format!("{}", InvalidCharacterInfo::new("012\n456", 5));
|
||||
assert_eq!(expected, f);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_char_info_6() {
|
||||
let expected = "012\\f456\\t89\n ^";
|
||||
let f = format!("{}", InvalidCharacterInfo::new("012\x0c456\t89", 9));
|
||||
assert_eq!(expected, f);
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,6 @@ use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use itertools::Itertools;
|
||||
#[cfg(feature = "std")]
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
#[cfg(feature = "std")]
|
||||
use regex::Regex;
|
||||
use scale_info::TypeInfo;
|
||||
#[cfg(feature = "std")]
|
||||
pub use secrecy::{ExposeSecret, SecretString};
|
||||
@@ -43,6 +41,11 @@ pub use ss58_registry::{from_known_address_format, Ss58AddressFormat, Ss58Addres
|
||||
/// Trait to zeroize a memory buffer.
|
||||
pub use zeroize::Zeroize;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub use crate::address_uri::AddressUri;
|
||||
#[cfg(any(feature = "std", feature = "full_crypto"))]
|
||||
pub use crate::address_uri::Error as AddressUriError;
|
||||
|
||||
/// The root phrase for our publicly known keys.
|
||||
pub const DEV_PHRASE: &str =
|
||||
"bottom drive obey lake curtain smoke basket hold race lonely fit walk";
|
||||
@@ -82,8 +85,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(AddressUriError),
|
||||
/// The seed phrase provided is not a valid BIP39 phrase.
|
||||
#[cfg_attr(feature = "std", error("Invalid phrase"))]
|
||||
InvalidPhrase,
|
||||
@@ -101,6 +104,13 @@ pub enum SecretStringError {
|
||||
InvalidPath,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "std", feature = "full_crypto"))]
|
||||
impl From<AddressUriError> for SecretStringError {
|
||||
fn from(e: AddressUriError) -> Self {
|
||||
Self::InvalidFormat(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// An error when deriving a key.
|
||||
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -208,7 +218,7 @@ impl<T: AsRef<str>> From<T> for DeriveJunction {
|
||||
/// An error type for SS58 decoding.
|
||||
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||
#[cfg_attr(not(feature = "std"), derive(Debug))]
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
#[allow(missing_docs)]
|
||||
#[cfg(any(feature = "full_crypto", feature = "serde"))]
|
||||
pub enum PublicError {
|
||||
@@ -235,6 +245,11 @@ 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(feature = "std")]
|
||||
#[cfg_attr(feature = "std", error("Incorrect URI syntax {0}."))]
|
||||
MalformedUri(#[from] AddressUriError),
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
@@ -414,47 +429,40 @@ pub fn set_default_ss58_version(new_default: Ss58AddressFormat) {
|
||||
DEFAULT_VERSION.store(new_default.into(), core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
lazy_static::lazy_static! {
|
||||
static ref SS58_REGEX: Regex = Regex::new(r"^(?P<ss58>[\w\d ]+)?(?P<path>(//?[^/]+)*)$")
|
||||
.expect("constructed from known-good static value; qed");
|
||||
static ref SECRET_PHRASE_REGEX: Regex = Regex::new(r"^(?P<phrase>[\d\w ]+)?(?P<path>(//?[^/]+)*)(///(?P<password>.*))?$")
|
||||
.expect("constructed from known-good static value; qed");
|
||||
static ref JUNCTION_REGEX: Regex = Regex::new(r"/(/?[^/]+)")
|
||||
.expect("constructed from known-good static value; qed");
|
||||
}
|
||||
|
||||
#[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 = SS58_REGEX.captures(s).ok_or(PublicError::InvalidFormat)?;
|
||||
let s = cap.name("ss58").map(|r| r.as_str()).unwrap_or(DEV_ADDRESS);
|
||||
let cap = AddressUri::parse(s)?;
|
||||
if cap.pass.is_some() {
|
||||
return Err(PublicError::PasswordNotAllowed);
|
||||
}
|
||||
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)?
|
||||
} else {
|
||||
Self::from_ss58check(s)?
|
||||
};
|
||||
if cap["path"].is_empty() {
|
||||
if cap.paths.is_empty() {
|
||||
Ok(addr)
|
||||
} else {
|
||||
let path =
|
||||
JUNCTION_REGEX.captures_iter(&cap["path"]).map(|f| DeriveJunction::from(&f[1]));
|
||||
addr.derive(path).ok_or(PublicError::InvalidPath)
|
||||
addr.derive(cap.paths.iter().map(DeriveJunction::from))
|
||||
.ok_or(PublicError::InvalidPath)
|
||||
}
|
||||
}
|
||||
|
||||
fn from_string_with_version(s: &str) -> Result<(Self, Ss58AddressFormat), PublicError> {
|
||||
let cap = SS58_REGEX.captures(s).ok_or(PublicError::InvalidFormat)?;
|
||||
let (addr, v) = Self::from_ss58check_with_version(
|
||||
cap.name("ss58").map(|r| r.as_str()).unwrap_or(DEV_ADDRESS),
|
||||
)?;
|
||||
if cap["path"].is_empty() {
|
||||
let cap = AddressUri::parse(s)?;
|
||||
if cap.pass.is_some() {
|
||||
return Err(PublicError::PasswordNotAllowed);
|
||||
}
|
||||
let (addr, v) = Self::from_ss58check_with_version(cap.phrase.unwrap_or(DEV_ADDRESS))?;
|
||||
if cap.paths.is_empty() {
|
||||
Ok((addr, v))
|
||||
} else {
|
||||
let path =
|
||||
JUNCTION_REGEX.captures_iter(&cap["path"]).map(|f| DeriveJunction::from(&f[1]));
|
||||
addr.derive(path).ok_or(PublicError::InvalidPath).map(|a| (a, v))
|
||||
addr.derive(cap.paths.iter().map(DeriveJunction::from))
|
||||
.ok_or(PublicError::InvalidPath)
|
||||
.map(|a| (a, v))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -817,22 +825,15 @@ impl sp_std::str::FromStr for SecretUri {
|
||||
type Err = SecretStringError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let cap = SECRET_PHRASE_REGEX.captures(s).ok_or(SecretStringError::InvalidFormat)?;
|
||||
|
||||
let junctions = JUNCTION_REGEX
|
||||
.captures_iter(&cap["path"])
|
||||
.map(|f| DeriveJunction::from(&f[1]))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let phrase = cap.name("phrase").map(|r| r.as_str()).unwrap_or(DEV_PHRASE);
|
||||
let password = cap.name("password");
|
||||
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"),
|
||||
password: password.map(|v| {
|
||||
SecretString::from_str(v.as_str()).expect("Returns infallible error; qed")
|
||||
}),
|
||||
junctions,
|
||||
password: cap
|
||||
.pass
|
||||
.map(|v| SecretString::from_str(v).expect("Returns infallible error; qed")),
|
||||
junctions: cap.paths.iter().map(DeriveJunction::from).collect::<Vec<_>>(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ pub mod crypto;
|
||||
pub mod hexdisplay;
|
||||
pub use paste;
|
||||
|
||||
#[cfg(any(feature = "full_crypto", feature = "std"))]
|
||||
mod address_uri;
|
||||
#[cfg(feature = "bandersnatch-experimental")]
|
||||
pub mod bandersnatch;
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
|
||||
Reference in New Issue
Block a user