use codec::Encode;
use crate::crypto::{seed_from_entropy, DeriveJunction, SecretUri};
use core::{fmt::Display, str::FromStr};
use hex::FromHex;
use secp256k1::{ecdsa::RecoverableSignature, Message, Secp256k1, SecretKey};
use secrecy::ExposeSecret;
const SECRET_KEY_LENGTH: usize = 32;
pub type SecretKeyBytes = [u8; SECRET_KEY_LENGTH];
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Signature(pub [u8; 65]);
impl AsRef<[u8]> for Signature {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
#[derive(Debug, Clone)]
pub struct PublicKey(pub [u8; 33]);
impl AsRef<[u8]> for PublicKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Keypair(pub secp256k1::Keypair);
impl Keypair {
pub fn from_uri(uri: &SecretUri) -> Result<Self, Error> {
let SecretUri {
junctions,
phrase,
password,
} = uri;
let key = if let Some(hex_str) = phrase.expose_secret().strip_prefix("0x") {
let seed = SecretKeyBytes::from_hex(hex_str)?;
Self::from_secret_key(seed)?
} else {
let phrase = bip39::Mnemonic::from_str(phrase.expose_secret().as_str())?;
let pass_str = password.as_ref().map(|p| p.expose_secret().as_str());
Self::from_phrase(&phrase, pass_str)?
};
key.derive(junctions.iter().copied())
}
pub fn from_phrase(mnemonic: &bip39::Mnemonic, password: Option<&str>) -> Result<Self, Error> {
let (arr, len) = mnemonic.to_entropy_array();
let big_seed =
seed_from_entropy(&arr[0..len], password.unwrap_or("")).ok_or(Error::InvalidSeed)?;
let secret_key_bytes: SecretKeyBytes = big_seed[..SECRET_KEY_LENGTH]
.try_into()
.expect("should be valid Seed");
Self::from_secret_key(secret_key_bytes)
}
pub fn from_secret_key(secret_key: SecretKeyBytes) -> Result<Self, Error> {
let secret = SecretKey::from_slice(&secret_key).map_err(|_| Error::InvalidSeed)?;
Ok(Self(secp256k1::Keypair::from_secret_key(
&Secp256k1::signing_only(),
&secret,
)))
}
pub fn derive<Js: IntoIterator<Item = DeriveJunction>>(
&self,
junctions: Js,
) -> Result<Self, Error> {
let mut acc = self.0.secret_key().clone().secret_bytes();
for junction in junctions {
match junction {
DeriveJunction::Soft(_) => return Err(Error::SoftJunction),
DeriveJunction::Hard(junction_bytes) => {
acc = ("Secp256k1HDKD", acc, junction_bytes)
.using_encoded(sp_crypto_hashing::blake2_256)
}
}
}
Self::from_secret_key(acc)
}
pub fn public_key(&self) -> PublicKey {
PublicKey(self.0.public_key().serialize())
}
pub fn sign(&self, message: &[u8]) -> Signature {
self.sign_prehashed(&sp_crypto_hashing::blake2_256(message))
}
pub fn sign_prehashed(&self, message_hash: &[u8; 32]) -> Signature {
let wrapped = Message::from_digest_slice(message_hash).expect("Message is 32 bytes; qed");
Signature(internal::sign(&self.0.secret_key(), &wrapped))
}
}
pub fn verify<M: AsRef<[u8]>>(sig: &Signature, message: M, pubkey: &PublicKey) -> bool {
let message_hash = sp_crypto_hashing::blake2_256(message.as_ref());
let wrapped = Message::from_digest_slice(&message_hash).expect("Message is 32 bytes; qed");
internal::verify(&sig.0, &wrapped, pubkey)
}
pub(crate) mod internal {
use super::*;
pub fn sign(secret_key: &secp256k1::SecretKey, message: &Message) -> [u8; 65] {
let recsig: RecoverableSignature =
Secp256k1::signing_only().sign_ecdsa_recoverable(message, secret_key);
let (recid, sig): (_, [u8; 64]) = recsig.serialize_compact();
let mut signature_bytes: [u8; 65] = [0; 65];
signature_bytes[..64].copy_from_slice(&sig);
signature_bytes[64] = (recid.to_i32() & 0xFF) as u8;
signature_bytes
}
pub fn verify(sig: &[u8; 65], message: &Message, pubkey: &PublicKey) -> bool {
let Ok(signature) = secp256k1::ecdsa::Signature::from_compact(&sig[..64]) else {
return false;
};
let Ok(public) = secp256k1::PublicKey::from_slice(&pubkey.0) else {
return false;
};
Secp256k1::verification_only()
.verify_ecdsa(message, &signature, &public)
.is_ok()
}
}
#[derive(Debug, PartialEq)]
pub enum Error {
InvalidSeed,
SoftJunction,
Phrase(bip39::Error),
Hex(hex::FromHexError),
}
impl Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Error::InvalidSeed => write!(f, "Invalid seed (was it the wrong length?)"),
Error::SoftJunction => write!(f, "Invalid seed for ECDSA, contained soft junction"),
Error::Phrase(e) => write!(f, "Cannot parse phrase: {e}"),
Error::Hex(e) => write!(f, "Cannot parse hex string: {e}"),
}
}
}
impl_from!(bip39::Error => Error::Phrase);
impl_from!(hex::FromHexError => Error::Hex);
#[cfg(feature = "std")]
impl std::error::Error for Error {}
pub mod dev {
use super::*;
once_static_cloned! {
pub fn alice() -> Keypair {
Keypair::from_uri(&SecretUri::from_str("//Alice").unwrap()).unwrap()
}
pub fn bob() -> Keypair {
Keypair::from_uri(&SecretUri::from_str("//Bob").unwrap()).unwrap()
}
pub fn charlie() -> Keypair {
Keypair::from_uri(&SecretUri::from_str("//Charlie").unwrap()).unwrap()
}
pub fn dave() -> Keypair {
Keypair::from_uri(&SecretUri::from_str("//Dave").unwrap()).unwrap()
}
pub fn eve() -> Keypair {
Keypair::from_uri(&SecretUri::from_str("//Eve").unwrap()).unwrap()
}
pub fn ferdie() -> Keypair {
Keypair::from_uri(&SecretUri::from_str("//Ferdie").unwrap()).unwrap()
}
pub fn one() -> Keypair {
Keypair::from_uri(&SecretUri::from_str("//One").unwrap()).unwrap()
}
pub fn two() -> Keypair {
Keypair::from_uri(&SecretUri::from_str("//Two").unwrap()).unwrap()
}
}
}
#[cfg(feature = "subxt")]
mod subxt_compat {
use super::*;
use subxt_core::config::Config;
use subxt_core::tx::signer::Signer as SignerT;
use subxt_core::utils::{AccountId32, MultiAddress, MultiSignature};
impl From<Signature> for MultiSignature {
fn from(value: Signature) -> Self {
MultiSignature::Ecdsa(value.0)
}
}
impl From<PublicKey> for AccountId32 {
fn from(value: PublicKey) -> Self {
value.to_account_id()
}
}
impl<T> From<PublicKey> for MultiAddress<AccountId32, T> {
fn from(value: PublicKey) -> Self {
value.to_address()
}
}
impl PublicKey {
pub fn to_account_id(self) -> AccountId32 {
AccountId32(sp_crypto_hashing::blake2_256(&self.0))
}
pub fn to_address<T>(self) -> MultiAddress<AccountId32, T> {
MultiAddress::Id(self.to_account_id())
}
}
impl<T: Config> SignerT<T> for Keypair
where
T::AccountId: From<PublicKey>,
T::Address: From<PublicKey>,
T::Signature: From<Signature>,
{
fn account_id(&self) -> T::AccountId {
self.public_key().into()
}
fn address(&self) -> T::Address {
self.public_key().into()
}
fn sign(&self, signer_payload: &[u8]) -> T::Signature {
self.sign(signer_payload).into()
}
}
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use super::*;
use sp_core::crypto::Pair as _;
use sp_core::ecdsa::Pair as SpPair;
#[test]
fn check_from_phrase_matches() {
for _ in 0..20 {
let (sp_pair, phrase, _seed) = SpPair::generate_with_phrase(None);
let phrase = bip39::Mnemonic::parse(phrase).expect("valid phrase expected");
let pair = Keypair::from_phrase(&phrase, None).expect("should be valid");
assert_eq!(sp_pair.public().0, pair.public_key().0);
}
}
#[test]
fn check_from_phrase_with_password_matches() {
for _ in 0..20 {
let (sp_pair, phrase, _seed) = SpPair::generate_with_phrase(Some("Testing"));
let phrase = bip39::Mnemonic::parse(phrase).expect("valid phrase expected");
let pair = Keypair::from_phrase(&phrase, Some("Testing")).expect("should be valid");
assert_eq!(sp_pair.public().0, pair.public_key().0);
}
}
#[test]
fn check_from_secret_uri_matches() {
let uri_paths = ["//bar", "//0001", "//1", "//0001", "//foo//bar//wibble"];
for i in 0..2 {
for path in &uri_paths {
let password = format!("Testing{i}");
let (_sp_pair, phrase, _seed) = SpPair::generate_with_phrase(Some(&password));
let uri = format!("{phrase}{path}///{password}");
let sp_pair = SpPair::from_string(&uri, None).expect("should be valid");
let uri = SecretUri::from_str(&uri).expect("should be valid secret URI");
let pair = Keypair::from_uri(&uri).expect("should be valid");
assert_eq!(sp_pair.public().0, pair.public_key().0);
}
}
}
#[test]
fn check_derive_errs_with_soft_junction() {
let uri_paths = ["/bar", "/1", "//foo//bar/wibble"];
for path in &uri_paths {
let (_sp_pair, phrase, _seed) = SpPair::generate_with_phrase(None);
let uri = format!("{phrase}{path}");
let uri = SecretUri::from_str(&uri).expect("should be valid secret URI");
let result = Keypair::from_uri(&uri);
assert_eq!(result.err(), Some(Error::SoftJunction));
}
}
#[test]
fn check_signing_and_verifying_matches() {
use sp_core::ecdsa::Signature as SpSignature;
for _ in 0..20 {
let (sp_pair, phrase, _seed) = SpPair::generate_with_phrase(Some("Testing"));
let phrase = bip39::Mnemonic::parse(phrase).expect("valid phrase expected");
let pair = Keypair::from_phrase(&phrase, Some("Testing")).expect("should be valid");
let message = b"Hello world";
let sp_sig = sp_pair.sign(message).0;
let sig = pair.sign(message).0;
assert!(SpPair::verify(
&SpSignature(sig),
message,
&sp_pair.public(),
));
assert!(verify(&Signature(sp_sig), message, &pair.public_key()));
}
}
#[test]
fn check_hex_uris() {
let uri_str =
"0x1122334455667788112233445566778811223344556677881122334455667788///SomePassword";
let uri = SecretUri::from_str(uri_str).expect("should be valid");
let pair = Keypair::from_uri(&uri).expect("should be valid");
let sp_pair = SpPair::from_string(uri_str, None).expect("should be valid");
assert_eq!(pair.public_key().0, sp_pair.public().0);
}
}