pub mod backends;
use backends::Backend;
use backends::BackendConfig;
cfg_remote! {
use backends::remote::RemoteEntry;
}
mod config;
use blueprint_crypto::KeyType;
use blueprint_crypto::KeyTypeId;
use blueprint_crypto::{BytesEncoding, IntoCryptoError};
pub use config::KeystoreConfig;
use crate::error::{Error, Result};
#[cfg(feature = "std")]
use crate::storage::FileStorage;
use crate::storage::{InMemoryStorage, RawStorage};
use blueprint_std::{boxed::Box, cmp, collections::BTreeMap, vec::Vec};
use serde::de::DeserializeOwned;
pub struct LocalStorageEntry {
storage: Box<dyn RawStorage>,
priority: u8,
}
pub struct Keystore {
storages: BTreeMap<KeyTypeId, Vec<LocalStorageEntry>>,
#[cfg(any(
feature = "aws-signer",
feature = "gcp-signer",
feature = "ledger-browser",
feature = "ledger-node"
))]
remotes: BTreeMap<KeyTypeId, Vec<RemoteEntry>>,
}
impl Keystore {
pub fn new(config: KeystoreConfig) -> Result<Self> {
let config = config.finalize();
let mut keystore = Self {
storages: BTreeMap::new(),
#[cfg(any(
feature = "aws-signer",
feature = "gcp-signer",
feature = "ledger-browser",
feature = "ledger-node"
))]
remotes: BTreeMap::new(),
};
if config.in_memory {
for key_type in KeyTypeId::ENABLED {
keystore.register_storage(
*key_type,
BackendConfig::Local(Box::new(InMemoryStorage::new())),
0,
)?;
}
}
#[cfg(feature = "std")]
if let Some(fs_root) = config.fs_root {
for key_type in KeyTypeId::ENABLED {
keystore.register_storage(
*key_type,
BackendConfig::Local(Box::new(FileStorage::new(fs_root.as_path())?)),
0,
)?;
}
}
#[cfg(any(
feature = "aws-signer",
feature = "gcp-signer",
feature = "ledger-browser",
feature = "ledger-node"
))]
for remote_config in config.remote_configs {
for key_type in KeyTypeId::ENABLED {
keystore.register_storage(
*key_type,
BackendConfig::Remote(remote_config.clone()),
0,
)?;
}
}
Ok(keystore)
}
#[allow(clippy::unnecessary_wraps)]
fn register_storage(
&mut self,
key_type_id: KeyTypeId,
storage: BackendConfig,
priority: u8,
) -> Result<()> {
match storage {
BackendConfig::Local(storage) => {
let entry = LocalStorageEntry { storage, priority };
let backends = self.storages.entry(key_type_id).or_default();
backends.push(entry);
backends.sort_by_key(|e| cmp::Reverse(e.priority));
}
#[cfg(any(
feature = "aws-signer",
feature = "gcp-signer",
feature = "ledger-browser",
feature = "ledger-node"
))]
BackendConfig::Remote(_config) => return Err(Error::StorageNotSupported),
}
Ok(())
}
}
#[cfg_attr(
not(any(
feature = "ecdsa",
feature = "sr25519-schnorrkel",
feature = "zebra",
feature = "bls",
feature = "bn254"
)),
allow(unreachable_code, unused_variables, unused_mut)
)]
impl Backend for Keystore {
fn generate<T: KeyType>(&self, seed: Option<&[u8]>) -> Result<T::Public>
where
T::Public: DeserializeOwned,
T::Secret: DeserializeOwned,
T::Error: IntoCryptoError,
{
let backends = self.get_storage_backends::<T>()?;
let secret = T::generate_with_seed(seed).map_err(IntoCryptoError::into_crypto_error)?;
let public = T::public_from_secret(&secret);
for entry in backends {
entry
.storage
.store_raw(T::key_type_id(), public.to_bytes(), secret.to_bytes())?;
}
Ok(public)
}
fn insert<T: KeyType>(&self, secret: &T::Secret) -> Result<()>
where
T::Public: DeserializeOwned,
T::Secret: DeserializeOwned,
{
let backends = self.get_storage_backends::<T>()?;
for entry in backends {
entry.storage.store_raw(
T::key_type_id(),
T::public_from_secret(secret).to_bytes(),
secret.to_bytes(),
)?;
}
Ok(())
}
fn generate_from_string<T: KeyType>(&self, seed_str: &str) -> Result<T::Public>
where
T::Public: DeserializeOwned,
T::Secret: DeserializeOwned,
T::Error: IntoCryptoError,
{
let seed = blake3::hash(seed_str.as_bytes()).as_bytes().to_vec();
self.generate::<T>(Some(&seed))
}
fn sign_with_local<T: KeyType>(&self, public: &T::Public, msg: &[u8]) -> Result<T::Signature>
where
T::Public: DeserializeOwned,
T::Secret: DeserializeOwned,
T::Error: IntoCryptoError,
{
let secret = self.get_secret::<T>(public)?;
Ok(T::sign_with_secret(&mut secret.clone(), msg)
.map_err(IntoCryptoError::into_crypto_error)?)
}
fn list_local<T: KeyType>(&self) -> Result<Vec<T::Public>>
where
T::Public: DeserializeOwned,
{
let mut keys = Vec::new();
let key_type = T::key_type_id();
if let Some(backends) = self.storages.get(&key_type) {
for entry in backends {
let mut backend_keys: Vec<T::Public> = entry
.storage
.list_raw(T::key_type_id())
.filter_map(|bytes| T::Public::from_bytes(&bytes).ok())
.collect();
keys.append(&mut backend_keys);
}
}
keys.sort_unstable();
keys.dedup();
Ok(keys)
}
fn first_local<T: KeyType>(&self) -> Result<T::Public>
where
T::Public: DeserializeOwned,
{
let list = self.list_local::<T>()?;
let Some(first_key) = list.first() else {
return Err(Error::KeyNotFound);
};
Ok(first_key.clone())
}
fn get_public_key_local<T: KeyType>(&self, key_id: &str) -> Result<T::Public>
where
T::Public: DeserializeOwned,
{
let storages = self
.storages
.get(&T::key_type_id())
.ok_or(Error::KeyTypeNotSupported)?;
for entry in storages {
if let Some(bytes) = entry
.storage
.load_secret_raw(T::key_type_id(), key_id.into())?
{
let public: T::Public = T::Public::from_bytes(&bytes)?;
return Ok(public);
}
}
Err(Error::KeyNotFound)
}
fn contains_local<T: KeyType>(&self, public: &T::Public) -> Result<bool> {
let public_bytes = public.to_bytes();
let storages = self
.storages
.get(&T::key_type_id())
.ok_or(Error::KeyTypeNotSupported)?;
for entry in storages {
if entry
.storage
.contains_raw(T::key_type_id(), public_bytes.clone())
{
return Ok(true);
}
}
Ok(false)
}
fn remove<T: KeyType>(&self, public: &T::Public) -> Result<()>
where
T::Public: DeserializeOwned,
{
let public_bytes = public.to_bytes();
let storages = self
.storages
.get(&T::key_type_id())
.ok_or(Error::KeyTypeNotSupported)?;
for entry in storages {
entry
.storage
.remove_raw(T::key_type_id(), public_bytes.clone())?;
}
Ok(())
}
fn get_secret<T: KeyType>(&self, public: &T::Public) -> Result<T::Secret>
where
T::Public: DeserializeOwned,
T::Secret: DeserializeOwned,
{
let storages = self
.storages
.get(&T::key_type_id())
.ok_or(Error::KeyTypeNotSupported)?;
let public_bytes = public.to_bytes();
for entry in storages {
if let Some(bytes) = entry
.storage
.load_secret_raw(T::key_type_id(), public_bytes.clone())?
{
let secret: T::Secret = T::Secret::from_bytes(&bytes)?;
return Ok(secret);
}
}
Err(Error::KeyNotFound)
}
fn get_storage_backends<T: KeyType>(&self) -> Result<&[LocalStorageEntry]> {
self.storages
.get(&T::key_type_id())
.map(Vec::as_slice)
.ok_or(Error::KeyTypeNotSupported)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "bls")]
use blueprint_crypto::bls::bls377::W3fBls377;
#[cfg(feature = "bls")]
use blueprint_crypto::bls::bls381::W3fBls381;
#[cfg(feature = "zebra")]
use blueprint_crypto::ed25519::Ed25519Zebra;
#[cfg(feature = "ecdsa")]
use blueprint_crypto::k256::K256Ecdsa;
#[cfg(feature = "sr25519-schnorrkel")]
use blueprint_crypto::sr25519::SchnorrkelSr25519;
#[cfg(feature = "ecdsa")]
#[test]
fn test_generate_from_string() -> Result<()> {
let keystore = Keystore::new(KeystoreConfig::new())?;
let seed = "test seed string";
let public1 = keystore.generate_from_string::<K256Ecdsa>(seed)?;
let public2 = keystore.generate_from_string::<K256Ecdsa>(seed)?;
assert_eq!(public1, public2);
let public3 = keystore.generate_from_string::<K256Ecdsa>("different seed")?;
assert_ne!(public1, public3);
Ok(())
}
macro_rules! local_operations {
($($name:ident => $key_ty:ty),+ $(,)?) => {
$(
#[tokio::test]
async fn $name() -> Result<()> {
test_local_operations_inner::<$key_ty>()
}
)+
}
}
#[cfg(feature = "ecdsa")]
local_operations!(
test_local_k256 => K256Ecdsa,
);
#[cfg(feature = "zebra")]
local_operations!(
test_local_ed25519 => Ed25519Zebra,
);
#[cfg(feature = "bls")]
local_operations!(
test_local_bls377 => W3fBls377,
test_local_bls381 => W3fBls381,
);
#[cfg(feature = "sr25519-schnorrkel")]
local_operations!(
test_local_schnorrkel => SchnorrkelSr25519,
);
fn test_local_operations_inner<T: KeyType>() -> Result<()>
where
<T as blueprint_crypto::KeyType>::Error: IntoCryptoError,
{
let keystore = Keystore::new(KeystoreConfig::new())?;
let public = keystore.generate::<T>(None)?;
let message = b"test message";
let signature = keystore.sign_with_local::<T>(&public, message)?;
assert!(T::verify(&public, message, &signature));
let local_keys = keystore.list_local::<T>()?;
assert_eq!(local_keys.len(), 1);
assert_eq!(
local_keys[0], public,
"Expected local key to be the same as generated key"
);
Ok(())
}
}