use base64::prelude::{BASE64_STANDARD_NO_PAD, Engine as _};
use thiserror::Error;
#[cfg(feature = "wasm-js")]
pub mod async_storage;
#[cfg(feature = "native")]
pub mod sync_storage;
pub const KEY_STORAGE_KEY: &str = "identity";
pub const KEY_STORAGE_DELEGATION: &str = "delegation";
pub(crate) const KEY_VECTOR: &str = "iv";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StoredKey {
String(String),
Raw([u8; 32]),
}
impl StoredKey {
pub fn decode(&self) -> Result<[u8; 32], DecodeError> {
match self {
StoredKey::String(s) => {
let bytes = BASE64_STANDARD_NO_PAD
.decode(s)
.map_err(DecodeError::Base64)?;
let bytes: [u8; 32] = bytes
.try_into()
.map_err(|_| DecodeError::Ed25519("Invalid slice length".to_string()))?;
Ok(bytes)
}
StoredKey::Raw(bytes) => Ok(*bytes),
}
}
pub fn encode(&self) -> String {
match self {
StoredKey::String(s) => s.clone(),
StoredKey::Raw(bytes) => BASE64_STANDARD_NO_PAD.encode(bytes),
}
}
}
impl From<[u8; 32]> for StoredKey {
fn from(value: [u8; 32]) -> Self {
StoredKey::Raw(value)
}
}
impl TryFrom<Vec<u8>> for StoredKey {
type Error = DecodeError;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
let bytes: [u8; 32] = value
.try_into()
.map_err(|_| DecodeError::Ed25519("Invalid slice length".to_string()))?;
Ok(StoredKey::Raw(bytes))
}
}
impl From<String> for StoredKey {
fn from(value: String) -> Self {
StoredKey::String(value)
}
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum DecodeError {
#[error("Ed25519 error: {0}")]
Ed25519(String),
#[error("Base64 error: {0}")]
Base64(base64::DecodeError),
}
#[derive(Error, Debug)]
pub enum StorageError {
#[error("Keyring error: {0}")]
Keyring(String),
#[error("Web Sys error: {0}")]
WebSys(String),
#[error("File storage error: {0}")]
File(String),
#[error("Decode error: {0}")]
Decode(#[from] DecodeError),
}
#[cfg(feature = "keyring")]
impl From<keyring::Error> for StorageError {
fn from(err: keyring::Error) -> Self {
StorageError::Keyring(err.to_string())
}
}
#[cfg(feature = "wasm-js")]
impl From<web_sys::wasm_bindgen::JsValue> for StorageError {
fn from(err: web_sys::wasm_bindgen::JsValue) -> Self {
StorageError::WebSys(
err.as_string()
.unwrap_or_else(|| "unknown websys error".to_string()),
)
}
}
impl From<std::io::Error> for StorageError {
fn from(err: std::io::Error) -> Self {
StorageError::File(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use ed25519_dalek::SigningKey;
#[test]
fn test_stored_key_encode_decode() {
let mut rng = rand::thread_rng();
let signing_key = SigningKey::generate(&mut rng);
let raw_key = signing_key.to_bytes();
let encoded = StoredKey::Raw(raw_key).encode();
let key = StoredKey::String(encoded);
let decoded = key.decode().unwrap();
assert_eq!(raw_key, decoded);
}
}