mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 10:01:17 +00:00
inspect-key: Adds support for expect-public (#10430)
* Introduce `SecretUri` * `inspect-key`: Adds support for `expect-public` `expect-public` can be used to check that a given secret uri corresponds to the given public key. This is mainly useful when the secret uri is protected by a password and a new derived account should be generated. With `--expect-public` the user can pass the public key/account-id of the "base" secret uri aka the one without any derivation to ensure the correct password was inserted. * Fixes * 🤦 * Apply suggestions from code review Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> * Review feedback * FMT * Bump the versions Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
This commit is contained in:
@@ -460,6 +460,7 @@ pub trait Public:
|
||||
fn as_slice(&self) -> &[u8] {
|
||||
self.as_ref()
|
||||
}
|
||||
|
||||
/// Return `CryptoTypePublicPair` from public key.
|
||||
fn to_public_crypto_pair(&self) -> CryptoTypePublicPair;
|
||||
}
|
||||
@@ -709,6 +710,104 @@ mod dummy {
|
||||
}
|
||||
}
|
||||
|
||||
/// A secret uri (`SURI`) that can be used to generate a key pair.
|
||||
///
|
||||
/// The `SURI` can be parsed from a string. The string is interpreted in the following way:
|
||||
///
|
||||
/// - If `string` is a possibly `0x` prefixed 64-digit hex string, then it will be interpreted
|
||||
/// directly as a `MiniSecretKey` (aka "seed" in `subkey`).
|
||||
/// - If `string` is a valid BIP-39 key phrase of 12, 15, 18, 21 or 24 words, then the key will
|
||||
/// be derived from it. In this case:
|
||||
/// - the phrase may be followed by one or more items delimited by `/` characters.
|
||||
/// - the path may be followed by `///`, in which case everything after the `///` is treated
|
||||
/// as a password.
|
||||
/// - If `string` begins with a `/` character it is prefixed with the Substrate public `DEV_PHRASE`
|
||||
/// and interpreted as above.
|
||||
///
|
||||
/// In this case they are interpreted as HDKD junctions; purely numeric items are interpreted as
|
||||
/// integers, non-numeric items as strings. Junctions prefixed with `/` are interpreted as soft
|
||||
/// junctions, and with `//` as hard junctions.
|
||||
///
|
||||
/// There is no correspondence mapping between `SURI` strings and the keys they represent.
|
||||
/// Two different non-identical strings can actually lead to the same secret being derived.
|
||||
/// Notably, integer junction indices may be legally prefixed with arbitrary number of zeros.
|
||||
/// Similarly an empty password (ending the `SURI` with `///`) is perfectly valid and will
|
||||
/// generally be equivalent to no password at all.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Parse [`DEV_PHRASE`] secret uri with junction:
|
||||
///
|
||||
/// ```
|
||||
/// # use sp_core::crypto::{SecretUri, DeriveJunction, DEV_PHRASE, ExposeSecret};
|
||||
/// # use std::str::FromStr;
|
||||
/// let suri = SecretUri::from_str("//Alice").expect("Parse SURI");
|
||||
///
|
||||
/// assert_eq!(vec![DeriveJunction::from("Alice").harden()], suri.junctions);
|
||||
/// assert_eq!(DEV_PHRASE, suri.phrase.expose_secret());
|
||||
/// assert!(suri.password.is_none());
|
||||
/// ```
|
||||
///
|
||||
/// Parse [`DEV_PHRASE`] secret ui with junction and password:
|
||||
///
|
||||
/// ```
|
||||
/// # use sp_core::crypto::{SecretUri, DeriveJunction, DEV_PHRASE, ExposeSecret};
|
||||
/// # use std::str::FromStr;
|
||||
/// let suri = SecretUri::from_str("//Alice///SECRET_PASSWORD").expect("Parse SURI");
|
||||
///
|
||||
/// assert_eq!(vec![DeriveJunction::from("Alice").harden()], suri.junctions);
|
||||
/// assert_eq!(DEV_PHRASE, suri.phrase.expose_secret());
|
||||
/// assert_eq!("SECRET_PASSWORD", suri.password.unwrap().expose_secret());
|
||||
/// ```
|
||||
///
|
||||
/// Parse [`DEV_PHRASE`] secret ui with hex phrase and junction:
|
||||
///
|
||||
/// ```
|
||||
/// # use sp_core::crypto::{SecretUri, DeriveJunction, DEV_PHRASE, ExposeSecret};
|
||||
/// # use std::str::FromStr;
|
||||
/// let suri = SecretUri::from_str("0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a//Alice").expect("Parse SURI");
|
||||
///
|
||||
/// assert_eq!(vec![DeriveJunction::from("Alice").harden()], suri.junctions);
|
||||
/// assert_eq!("0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a", suri.phrase.expose_secret());
|
||||
/// assert!(suri.password.is_none());
|
||||
/// ```
|
||||
#[cfg(feature = "std")]
|
||||
pub struct SecretUri {
|
||||
/// The phrase to derive the private key.
|
||||
///
|
||||
/// This can either be a 64-bit hex string or a BIP-39 key phrase.
|
||||
pub phrase: SecretString,
|
||||
/// Optional password as given as part of the uri.
|
||||
pub password: Option<SecretString>,
|
||||
/// The junctions as part of the uri.
|
||||
pub junctions: Vec<DeriveJunction>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
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");
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait suitable for typical cryptographic PKI key pair type.
|
||||
///
|
||||
/// For now it just specifies how to create a key from a phrase and derivation path.
|
||||
@@ -821,14 +920,12 @@ pub trait Pair: CryptoType + Sized + Clone + Send + Sync + 'static {
|
||||
s: &str,
|
||||
password_override: Option<&str>,
|
||||
) -> Result<(Self, Option<Self::Seed>), SecretStringError> {
|
||||
let cap = SECRET_PHRASE_REGEX.captures(s).ok_or(SecretStringError::InvalidFormat)?;
|
||||
use sp_std::str::FromStr;
|
||||
let SecretUri { junctions, phrase, password } = SecretUri::from_str(s)?;
|
||||
let password =
|
||||
password_override.or_else(|| password.as_ref().map(|p| p.expose_secret().as_str()));
|
||||
|
||||
let path = JUNCTION_REGEX.captures_iter(&cap["path"]).map(|f| DeriveJunction::from(&f[1]));
|
||||
|
||||
let phrase = cap.name("phrase").map(|r| r.as_str()).unwrap_or(DEV_PHRASE);
|
||||
let password = password_override.or_else(|| cap.name("password").map(|m| m.as_str()));
|
||||
|
||||
let (root, seed) = if let Some(stripped) = phrase.strip_prefix("0x") {
|
||||
let (root, seed) = if let Some(stripped) = phrase.expose_secret().strip_prefix("0x") {
|
||||
hex::decode(stripped)
|
||||
.ok()
|
||||
.and_then(|seed_vec| {
|
||||
@@ -842,9 +939,11 @@ pub trait Pair: CryptoType + Sized + Clone + Send + Sync + 'static {
|
||||
})
|
||||
.ok_or(SecretStringError::InvalidSeed)?
|
||||
} else {
|
||||
Self::from_phrase(phrase, password).map_err(|_| SecretStringError::InvalidPhrase)?
|
||||
Self::from_phrase(phrase.expose_secret().as_str(), password)
|
||||
.map_err(|_| SecretStringError::InvalidPhrase)?
|
||||
};
|
||||
root.derive(path, Some(seed)).map_err(|_| SecretStringError::InvalidPath)
|
||||
root.derive(junctions.into_iter(), Some(seed))
|
||||
.map_err(|_| SecretStringError::InvalidPath)
|
||||
}
|
||||
|
||||
/// Interprets the string `s` in order to generate a key pair.
|
||||
|
||||
Reference in New Issue
Block a user