pub(crate) mod ed25519;
pub(crate) mod sr25519;
use crate::errors::{Bip39EntropyConversionError, Result, SubstrateSs58ConversionError};
use regex::Regex;
use secrecy::{ExposeSecret, Secret};
use snafu::ResultExt;
lazy_static! {
static ref SECRET_RE: Regex = Regex::new(r"^(?P<phrase>[\d\w ]+)(?P<path>(//?[^/]+)*)$")
.expect("Constructed from known-good static regex value.");
static ref DERIVE_PATHS_RE: Regex =
Regex::new(r"/(/?[^/]+)").expect("Constructed from known-good static regex value.");
}
#[derive(derive_more::From, Debug, Eq, PartialEq, Hash)]
pub struct Address {
pub value: String,
pub key_type: KeyType,
}
impl AddressValidation for Address {
fn validate(&self) -> Result<()> {
match self.key_type {
KeyType::SubstrateSr25519 => {
sr25519::Public::from_string(&self.value).context(
SubstrateSs58ConversionError {
address: Some(self.value.clone()),
},
)?;
Ok(())
}
KeyType::SubstrateEd25519 => {
ed25519::Public::from_string(&self.value).context(
SubstrateSs58ConversionError {
address: Some(self.value.clone()),
},
)?;
Ok(())
}
KeyType::SharedEncryptionX25519 => Ok(()),
}
}
}
macro_rules! decl_fixed_size_methods {
($modname: ident, $size: literal) => {
pub(crate) mod $modname {
use super::*;
type EntropySize = [u8; $size];
type DeconstructedTuple = (Secret<EntropySize>, Vec<Secret<String>>);
pub(crate) fn deconstruct_secret_string(
secret: Secret<String>,
) -> Result<DeconstructedTuple> {
let cap = SECRET_RE
.captures(secret.expose_secret())
.ok_or(sp_core::crypto::SecretStringError::InvalidFormat)?;
let phrase = cap
.name("phrase")
.map(|r| Secret::new(r.as_str().to_string()))
.ok_or(sp_core::crypto::SecretStringError::InvalidFormat)?;
let paths = DERIVE_PATHS_RE
.captures_iter(&cap["path"])
.map(|path| Secret::new(path[1].to_string()))
.collect();
Ok((to_entropy(phrase)?, paths))
}
fn to_mnemonic_string(entropy: &Secret<EntropySize>) -> Result<Secret<String>> {
let mnemonic = bip39::Mnemonic::from_entropy(
&entropy.expose_secret()[..],
bip39::Language::English,
)
.map_err(|err| err.compat())
.context(Bip39EntropyConversionError {
message: "bytes weren't able to recreate the mnemonic for key.",
})?;
Ok(Secret::new(String::from(mnemonic.phrase())))
}
pub(crate) fn to_secret_string(
entropy: &Secret<EntropySize>,
derive_paths: &[Secret<String>],
) -> Result<Secret<String>> {
let phrase = to_mnemonic_string(&entropy)?;
let paths: Vec<&str> = derive_paths
.iter()
.map(|path| path.expose_secret().as_str())
.collect();
let secret = if !paths.is_empty() {
Secret::new(format!("{}/{}", phrase.expose_secret(), paths.join("/")))
} else {
phrase
};
Ok(secret)
}
fn to_entropy(private_string: Secret<String>) -> Result<Secret<EntropySize>> {
let mnemonic = bip39::Mnemonic::from_phrase(
private_string.expose_secret(),
bip39::Language::English,
)
.map_err(|err| err.compat())
.context(Bip39EntropyConversionError {
message: "Mnemonic wasn't able to load for private key from bip39 library.",
})?;
let entropy = mnemonic.entropy();
let mut secret = [0u8; $size];
secret.copy_from_slice(&entropy[..$size]);
Ok(Secret::new(secret))
}
}
};
}
decl_fixed_size_methods!(siz_sixteen, 16);
use crate::{AddressValidation, KeyType};
pub(crate) use siz_sixteen::*;
use sp_core::crypto::Ss58Codec;
decl_fixed_size_methods!(siz_thirytwo, 32);