#![cfg(feature = "std")]
use crate::{identity::seed_phrase, util::SipHasherBuild};
use async_lock::Mutex;
use rand_chacha::rand_core::{RngCore as _, SeedableRng as _};
use std::{borrow::Cow, fs, io, path, str};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum KeyNamespace {
Aura,
AuthorityDiscovery,
Babe,
Grandpa,
ImOnline,
}
impl KeyNamespace {
pub fn all() -> impl ExactSizeIterator<Item = KeyNamespace> {
[
KeyNamespace::Aura,
KeyNamespace::AuthorityDiscovery,
KeyNamespace::Babe,
KeyNamespace::Grandpa,
KeyNamespace::ImOnline,
]
.into_iter()
}
fn from_string(str: &str) -> Option<Self> {
match str {
"aura" => Some(KeyNamespace::Aura),
"audi" => Some(KeyNamespace::AuthorityDiscovery),
"babe" => Some(KeyNamespace::Babe),
"gran" => Some(KeyNamespace::Grandpa),
"imon" => Some(KeyNamespace::ImOnline),
_ => None,
}
}
fn as_string(&self) -> &'static str {
match self {
KeyNamespace::Aura => "aura",
KeyNamespace::AuthorityDiscovery => "audi",
KeyNamespace::Babe => "babe",
KeyNamespace::Grandpa => "gran",
KeyNamespace::ImOnline => "imon",
}
}
}
pub struct Keystore {
keys_directory: Option<path::PathBuf>,
guarded: Mutex<Guarded>,
sr25519_signing_context: schnorrkel::context::SigningContext,
}
impl Keystore {
pub async fn new(
keys_directory: Option<path::PathBuf>,
randomness_seed: [u8; 32],
) -> Result<Self, io::Error> {
let mut gen_rng = rand_chacha::ChaCha20Rng::from_seed(randomness_seed);
let mut keys = hashbrown::HashMap::with_capacity_and_hasher(32, {
SipHasherBuild::new({
let mut seed = [0; 16];
gen_rng.fill_bytes(&mut seed);
seed
})
});
if let Some(keys_directory) = &keys_directory {
if !keys_directory.try_exists()? {
fs::create_dir_all(keys_directory)?;
}
for entry in fs::read_dir(keys_directory)? {
let entry = entry?;
if entry.file_type()?.is_dir() {
continue;
}
let file_name = match entry.file_name().into_string() {
Ok(n) => n,
Err(_) => continue,
};
let mut parser = nom::combinator::all_consuming::<
_,
(&str, nom::error::ErrorKind),
_,
>(nom::combinator::complete((
nom::combinator::map_opt(
nom::bytes::streaming::take(4u32),
KeyNamespace::from_string,
),
nom::bytes::streaming::tag("-"),
nom::combinator::map_opt(nom::bytes::streaming::take(7u32), |b| match b {
"ed25519" => Some(PrivateKey::FileEd25519),
"sr25519" => Some(PrivateKey::FileSr25519),
_ => None,
}),
nom::bytes::streaming::tag("-"),
nom::combinator::map_opt(
nom::bytes::complete::take_while(|c: char| {
c.is_ascii_digit() || ('a'..='f').contains(&c)
}),
|k: &str| {
if k.len() == 64 {
Some(<[u8; 32]>::try_from(hex::decode(k).unwrap()).unwrap())
} else {
None
}
},
),
)));
let (namespace, _, algorithm, _, public_key) =
match nom::Parser::parse(&mut parser, &file_name) {
Ok((_, v)) => v,
Err(_) => continue,
};
match algorithm {
PrivateKey::FileEd25519 => {
match Self::load_ed25519_from_file(keys_directory.join(entry.path())).await
{
Ok(kp) => {
if ed25519_zebra::VerificationKey::from(&*kp).as_ref() != public_key
{
continue;
}
}
Err(_) => continue,
}
}
PrivateKey::FileSr25519 => {
match Self::load_sr25519_from_file(keys_directory.join(entry.path())).await
{
Ok(kp) => {
if kp.public.to_bytes() != public_key {
continue;
}
}
Err(err) => panic!("{err:?}"),
}
}
_ => unreachable!(),
}
keys.insert((namespace, public_key), algorithm);
}
}
Ok(Keystore {
keys_directory,
guarded: Mutex::new(Guarded { gen_rng, keys }),
sr25519_signing_context: schnorrkel::signing_context(b"substrate"),
})
}
pub fn insert_sr25519_memory(
&mut self,
namespaces: impl Iterator<Item = KeyNamespace>,
private_key: &[u8; 64],
) -> [u8; 32] {
let private_key = schnorrkel::SecretKey::from_bytes(&private_key[..]).unwrap();
let keypair = zeroize::Zeroizing::new(private_key.to_keypair());
let public_key = keypair.public.to_bytes();
for namespace in namespaces {
self.guarded.get_mut().keys.insert(
(namespace, public_key),
PrivateKey::MemorySr25519(keypair.clone()),
);
}
public_key
}
pub async fn generate_ed25519(
&self,
namespace: KeyNamespace,
save: bool,
) -> Result<[u8; 32], io::Error> {
let mut guarded = self.guarded.lock().await;
let private_key =
zeroize::Zeroizing::new(ed25519_zebra::SigningKey::new(&mut guarded.gen_rng));
let public_key: [u8; 32] = ed25519_zebra::VerificationKey::from(&*private_key).into();
let save_path = if save {
self.path_of_key_ed25519(namespace, &public_key)
} else {
None
};
if let Some(save_path) = save_path {
Self::write_to_file_ed25519(&save_path, &private_key).await?;
guarded
.keys
.insert((namespace, public_key), PrivateKey::FileEd25519);
} else {
guarded.keys.insert(
(namespace, public_key),
PrivateKey::MemoryEd25519(private_key),
);
}
Ok(public_key)
}
pub async fn keys(&self) -> impl Iterator<Item = (KeyNamespace, [u8; 32])> {
let guarded = self.guarded.lock().await;
guarded.keys.keys().cloned().collect::<Vec<_>>().into_iter()
}
pub async fn generate_sr25519(
&self,
namespace: KeyNamespace,
save: bool,
) -> Result<[u8; 32], io::Error> {
let mut guarded = self.guarded.lock().await;
let mini_secret = zeroize::Zeroizing::new(schnorrkel::MiniSecretKey::generate_with(
&mut guarded.gen_rng,
));
let keypair = zeroize::Zeroizing::new(
mini_secret.expand_to_keypair(schnorrkel::ExpansionMode::Ed25519),
);
let public_key = keypair.public.to_bytes();
let save_path = if save {
self.path_of_key_sr25519(namespace, &public_key)
} else {
None
};
if let Some(save_path) = save_path {
Self::write_to_file_sr25519(&save_path, &mini_secret).await?;
guarded
.keys
.insert((namespace, public_key), PrivateKey::FileSr25519);
} else {
guarded
.keys
.insert((namespace, public_key), PrivateKey::MemorySr25519(keypair));
}
Ok(public_key)
}
pub async fn sign(
&self,
key_namespace: KeyNamespace,
public_key: &[u8; 32],
payload: &[u8],
) -> Result<[u8; 64], SignError> {
let mut guarded = self.guarded.lock().await;
let key = guarded
.keys
.get(&(key_namespace, *public_key))
.ok_or(SignError::UnknownPublicKey)?;
match key {
PrivateKey::MemoryEd25519(key) => Ok(key.sign(payload).into()),
PrivateKey::FileEd25519 => {
match Self::load_ed25519_from_file(
self.path_of_key_ed25519(key_namespace, public_key).unwrap(),
)
.await
{
Ok(key) => {
drop(guarded);
Ok(key.sign(payload).into())
}
Err(err) => {
guarded.keys.remove(&(key_namespace, *public_key));
Err(err.into())
}
}
}
PrivateKey::MemorySr25519(key) => Ok(key
.sign(self.sr25519_signing_context.bytes(payload))
.to_bytes()),
PrivateKey::FileSr25519 => {
match Self::load_sr25519_from_file(
self.path_of_key_sr25519(key_namespace, public_key).unwrap(),
)
.await
{
Ok(key) => {
drop(guarded);
Ok(key
.sign(self.sr25519_signing_context.bytes(payload))
.to_bytes())
}
Err(err) => {
guarded.keys.remove(&(key_namespace, *public_key));
Err(err.into())
}
}
}
}
}
pub fn sign_sr25519_vrf<'a>(
&'a self,
key_namespace: KeyNamespace,
public_key: &'a [u8; 32],
label: &'static [u8],
transcript_items: impl Iterator<Item = (&'static [u8], either::Either<&'a [u8], u64>)> + 'a,
) -> impl Future<Output = Result<VrfSignature, SignVrfError>> {
async move {
let mut guarded = self.guarded.lock().await;
let key = guarded
.keys
.get(&(key_namespace, *public_key))
.ok_or(SignVrfError::Sign(SignError::UnknownPublicKey))?;
match key {
PrivateKey::MemoryEd25519(_) | PrivateKey::FileEd25519 => {
Err(SignVrfError::WrongKeyAlgorithm)
}
PrivateKey::MemorySr25519(_) | PrivateKey::FileSr25519 => {
let key = match key {
PrivateKey::MemorySr25519(key) => Cow::Borrowed(key),
PrivateKey::FileSr25519 => {
match Self::load_sr25519_from_file(
self.path_of_key_sr25519(key_namespace, public_key).unwrap(),
)
.await
{
Ok(key) => {
drop(guarded);
Cow::Owned(key)
}
Err(err) => {
guarded.keys.remove(&(key_namespace, *public_key));
return Err(err.into());
}
}
}
_ => unreachable!(),
};
let mut transcript = merlin::Transcript::new(label);
for (label, value) in transcript_items {
match value {
either::Left(bytes) => {
transcript.append_message(label, bytes);
}
either::Right(value) => {
transcript.append_u64(label, value);
}
}
}
let (_in_out, proof, _) = key.vrf_sign(transcript);
Ok(VrfSignature {
proof: proof.to_bytes(),
})
}
}
}
}
async fn load_ed25519_from_file(
path: impl AsRef<path::Path>,
) -> Result<zeroize::Zeroizing<ed25519_zebra::SigningKey>, KeyLoadError> {
let bytes = fs::read(path).map_err(KeyLoadError::Io)?;
let phrase =
str::from_utf8(&bytes).map_err(|err| KeyLoadError::BadFormat(err.to_string()))?;
let mut private_key = seed_phrase::decode_ed25519_private_key(phrase)
.map_err(|err| KeyLoadError::BadFormat(err.to_string()))?;
let zebra_key = zeroize::Zeroizing::new(ed25519_zebra::SigningKey::from(*private_key));
zeroize::Zeroize::zeroize(&mut *private_key);
Ok(zebra_key)
}
async fn load_sr25519_from_file(
path: impl AsRef<path::Path>,
) -> Result<zeroize::Zeroizing<schnorrkel::Keypair>, KeyLoadError> {
let bytes = fs::read(path).map_err(KeyLoadError::Io)?;
let phrase =
str::from_utf8(&bytes).map_err(|err| KeyLoadError::BadFormat(err.to_string()))?;
let mut private_key = seed_phrase::decode_sr25519_private_key(phrase)
.map_err(|err| KeyLoadError::BadFormat(err.to_string()))?;
let schnorrkel_key = zeroize::Zeroizing::new(
schnorrkel::SecretKey::from_bytes(&*private_key)
.unwrap()
.into(),
);
zeroize::Zeroize::zeroize(&mut *private_key);
Ok(schnorrkel_key)
}
async fn write_to_file_ed25519(
path: impl AsRef<path::Path>,
key: &ed25519_zebra::SigningKey,
) -> Result<(), io::Error> {
let mut phrase = zeroize::Zeroizing::new(vec![0; key.as_ref().len() * 2]);
hex::encode_to_slice(key.as_ref(), &mut phrase).unwrap();
Self::write_to_file(path, &phrase).await
}
async fn write_to_file_sr25519(
path: impl AsRef<path::Path>,
key: &schnorrkel::MiniSecretKey,
) -> Result<(), io::Error> {
let bytes = key.to_bytes();
let mut phrase = zeroize::Zeroizing::new(vec![0; bytes.len() * 2]);
hex::encode_to_slice(bytes, &mut phrase).unwrap();
Self::write_to_file(path, &phrase).await
}
async fn write_to_file(
path: impl AsRef<path::Path>,
key_phrase: &[u8],
) -> Result<(), io::Error> {
let mut file = fs::File::create(path)?;
#[cfg(target_family = "unix")]
file.set_permissions(std::os::unix::fs::PermissionsExt::from_mode(0o400))?;
io::Write::write_all(&mut file, b"0x")?;
io::Write::write_all(&mut file, key_phrase)?;
io::Write::flush(&mut file)?; file.sync_all()?;
Ok(())
}
fn path_of_key_ed25519(
&self,
key_namespace: KeyNamespace,
public_key: &[u8; 32],
) -> Option<path::PathBuf> {
self.path_of_key(key_namespace, "ed25519", public_key)
}
fn path_of_key_sr25519(
&self,
key_namespace: KeyNamespace,
public_key: &[u8; 32],
) -> Option<path::PathBuf> {
self.path_of_key(key_namespace, "sr25519", public_key)
}
fn path_of_key(
&self,
key_namespace: KeyNamespace,
key_algorithm: &str,
public_key: &[u8; 32],
) -> Option<path::PathBuf> {
let keys_directory = match &self.keys_directory {
Some(k) => k,
None => return None,
};
let mut file_name = String::with_capacity(256); file_name.push_str(key_namespace.as_string());
file_name.push('-');
file_name.push_str(key_algorithm);
file_name.push('-');
file_name.push_str(&hex::encode(public_key));
let mut path =
path::PathBuf::with_capacity(keys_directory.as_os_str().len() + file_name.len() + 16);
path.push(keys_directory);
path.push(file_name);
Some(path)
}
}
struct Guarded {
gen_rng: rand_chacha::ChaCha20Rng,
keys: hashbrown::HashMap<(KeyNamespace, [u8; 32]), PrivateKey, SipHasherBuild>,
}
pub struct VrfSignature {
pub proof: [u8; 64],
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum SignError {
UnknownPublicKey,
#[display("Error loading the secret key; {_0}")]
KeyLoad(KeyLoadError),
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum KeyLoadError {
#[display("{_0}")]
Io(io::Error),
#[display("{_0}")]
BadFormat(#[error(not(source))] String),
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum SignVrfError {
#[display("{_0}")]
Sign(SignError),
WrongKeyAlgorithm,
}
enum PrivateKey {
MemoryEd25519(zeroize::Zeroizing<ed25519_zebra::SigningKey>),
MemorySr25519(zeroize::Zeroizing<schnorrkel::Keypair>),
FileEd25519,
FileSr25519,
}
impl From<KeyLoadError> for SignError {
fn from(err: KeyLoadError) -> SignError {
SignError::KeyLoad(err)
}
}
impl From<KeyLoadError> for SignVrfError {
fn from(err: KeyLoadError) -> SignVrfError {
SignVrfError::Sign(SignError::KeyLoad(err))
}
}
#[cfg(test)]
mod tests {
use super::{KeyNamespace, Keystore};
#[test]
fn disk_storage_works_ed25519() {
futures_executor::block_on(async move {
let path = tempfile::tempdir().unwrap();
let keystore1 = Keystore::new(Some(path.path().to_owned()), rand::random())
.await
.unwrap();
let public_key = keystore1
.generate_ed25519(KeyNamespace::Babe, true)
.await
.unwrap();
drop(keystore1);
let keystore2 = Keystore::new(Some(path.path().to_owned()), rand::random())
.await
.unwrap();
assert_eq!(
keystore2.keys().await.next(),
Some((KeyNamespace::Babe, public_key))
);
let signature = keystore2
.sign(KeyNamespace::Babe, &public_key, b"hello world")
.await
.unwrap();
assert!(
ed25519_zebra::VerificationKey::try_from(public_key)
.unwrap()
.verify(&ed25519_zebra::Signature::from(signature), b"hello world")
.is_ok()
);
});
}
#[test]
fn disk_storage_works_sr25519() {
futures_executor::block_on(async move {
let path = tempfile::tempdir().unwrap();
let keystore1 = Keystore::new(Some(path.path().to_owned()), rand::random())
.await
.unwrap();
let public_key = keystore1
.generate_sr25519(KeyNamespace::Aura, true)
.await
.unwrap();
drop(keystore1);
let keystore2 = Keystore::new(Some(path.path().to_owned()), rand::random())
.await
.unwrap();
assert_eq!(
keystore2.keys().await.next(),
Some((KeyNamespace::Aura, public_key))
);
let signature = keystore2
.sign(KeyNamespace::Aura, &public_key, b"hello world")
.await
.unwrap();
assert!(
schnorrkel::PublicKey::from_bytes(&public_key)
.unwrap()
.verify_simple(
b"substrate",
b"hello world",
&schnorrkel::Signature::from_bytes(&signature).unwrap()
)
.is_ok()
);
});
}
}