#[cfg(feature = "file-storage")]
use std::fs::{self, File};
#[cfg(feature = "file-storage")]
use std::io::{Read, Write};
#[cfg(feature = "file-storage")]
use std::path::{Path, PathBuf};
use std::str::FromStr;
#[cfg(feature = "file-storage")]
use async_trait::async_trait;
#[cfg(feature = "file-storage")]
use ed25519_dalek::SigningKey as Keypair;
#[cfg(feature = "file-storage")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "file-storage")]
use crate::constants::{BASE_DERIVATION_PATH, DEFAULT_NUM_ACCOUNTS};
#[cfg(feature = "file-storage")]
use crate::error::{Result, RialoError};
#[cfg(all(feature = "file-storage", feature = "encryption"))]
use crate::keyring::encryption;
#[cfg(all(feature = "file-storage", feature = "mnemonic"))]
use crate::keyring::mnemonic;
#[cfg(feature = "file-storage")]
use crate::keyring::provider_base::BaseKeyringProvider;
#[cfg(feature = "file-storage")]
use crate::keyring::traits::{Keyring, KeyringProvider};
use crate::rpc::types::Pubkey;
#[cfg(feature = "file-storage")]
pub struct FileKeyringProvider {
keyring_dir: PathBuf,
}
#[cfg(feature = "file-storage")]
#[derive(Serialize, Deserialize)]
struct StoredKeypair {
pubkey: Pubkey,
encrypted_keypair: Vec<u8>,
derivation_path: Option<String>,
index: u32,
}
#[cfg(feature = "file-storage")]
#[derive(Serialize, Deserialize)]
struct KeyringFile {
name: String,
mnemonic: Option<String>,
keypairs: Vec<StoredKeypair>,
}
#[cfg(feature = "file-storage")]
impl FileKeyringProvider {
pub fn new(keyring_dir: impl AsRef<Path>) -> Self {
Self {
keyring_dir: keyring_dir.as_ref().to_path_buf(),
}
}
pub fn default_path() -> Result<PathBuf> {
let mut path = dirs::config_dir()
.ok_or_else(|| RialoError::Keyring("Could not find config directory".to_string()))?;
path.push("rialo");
path.push("keyrings");
std::fs::create_dir_all(&path)?;
Ok(path)
}
fn keyring_path(&self, name: &str) -> PathBuf {
let mut path = self.keyring_dir.clone();
path.push(format!("{name}.keyring"));
path
}
fn read_keyring_file(&self, name: &str) -> Result<KeyringFile> {
let mut file = File::open(self.keyring_path(name))
.map_err(|e| RialoError::Keyring(format!("Failed to open keyring file: {e}")))?;
let mut contents = String::new();
file.read_to_string(&mut contents)
.map_err(|e| RialoError::Keyring(format!("Failed to read keyring file: {e}")))?;
serde_json::from_str(&contents)
.map_err(|e| RialoError::Keyring(format!("Failed to parse keyring file: {e}")))
}
fn save_keyring_file(&self, name: &str, keyring_file: &KeyringFile) -> Result<()> {
let json = serde_json::to_string_pretty(keyring_file)
.map_err(|e| RialoError::Keyring(format!("Failed to serialize keyring: {e}")))?;
let mut file = File::create(self.keyring_path(name))
.map_err(|e| RialoError::Keyring(format!("Failed to create keyring file: {e}")))?;
file.write_all(json.as_bytes())
.map_err(|e| RialoError::Keyring(format!("Failed to write keyring file: {e}")))?;
Ok(())
}
}
#[cfg(feature = "file-storage")]
#[async_trait]
impl BaseKeyringProvider for FileKeyringProvider {}
#[cfg(feature = "file-storage")]
#[async_trait]
impl KeyringProvider for FileKeyringProvider {
async fn create(&self, name: &str, password: &str) -> Result<Keyring> {
if self.exists(name).await? {
return Err(RialoError::Keyring(format!(
"Keyring already exists: {name}"
)));
}
fs::create_dir_all(&self.keyring_dir)?;
let keypair = Keypair::generate(&mut rand::rngs::OsRng);
#[cfg(feature = "encryption")]
let encrypted_keypair = encryption::encrypt_keypair(&keypair, password)?;
#[cfg(not(feature = "encryption"))]
let encrypted_keypair = {
let _ = password;
keypair.as_bytes().to_vec()
};
let keyring = Keyring::new(name.to_string(), keypair, None, None);
let stored = StoredKeypair {
pubkey: Pubkey::from_str(&keyring.pubkey_string()).unwrap(),
encrypted_keypair,
derivation_path: None,
index: 0,
};
let keyring_file = KeyringFile {
name: name.to_string(),
mnemonic: None,
keypairs: vec![stored],
};
self.save_keyring_file(name, &keyring_file)?;
Ok(keyring)
}
#[cfg(feature = "mnemonic")]
async fn create_with_mnemonic(&self, name: &str, password: &str) -> Result<Keyring> {
if self.exists(name).await? {
return Err(RialoError::Keyring(format!(
"Keyring already exists: {name}"
)));
}
fs::create_dir_all(&self.keyring_dir)?;
let mnemonic_phrase = mnemonic::generate_mnemonic()?;
let mut keypairs = Vec::new();
let mut primary_keypair = None;
for i in 0..DEFAULT_NUM_ACCOUNTS {
let path = format!("{}{}'/{}'", BASE_DERIVATION_PATH, i, 0);
let keypair = mnemonic::keypair_from_mnemonic(&mnemonic_phrase, Some(&path))?;
#[cfg(feature = "encryption")]
let encrypted_keypair = encryption::encrypt_keypair(&keypair, password)?;
#[cfg(not(feature = "encryption"))]
let encrypted_keypair = {
let _ = password;
keypair.as_bytes().to_vec()
};
let pubkey =
Pubkey::from_str(&bs58::encode(keypair.verifying_key().as_bytes()).into_string())
.unwrap();
if i == 0 {
primary_keypair = Some(keypair);
}
let stored = StoredKeypair {
pubkey,
encrypted_keypair,
derivation_path: Some(path),
index: i,
};
keypairs.push(stored);
}
let primary_path = format!("{}{}'/{}'", BASE_DERIVATION_PATH, 0, 0);
let keyring = Keyring::new(
name.to_string(),
primary_keypair.unwrap(),
Some(mnemonic_phrase.clone()),
Some(primary_path),
);
let keyring_file = KeyringFile {
name: name.to_string(),
mnemonic: Some(mnemonic_phrase),
keypairs,
};
self.save_keyring_file(name, &keyring_file)?;
Ok(keyring)
}
#[cfg(feature = "mnemonic")]
async fn recover_from_mnemonic(
&self,
name: &str,
mnemonic: &str,
password: &str,
) -> Result<Keyring> {
if self.exists(name).await? {
return Err(RialoError::Keyring(format!(
"Keyring already exists: {name}"
)));
}
fs::create_dir_all(&self.keyring_dir)?;
let mut keypairs = Vec::new();
let mut primary_keypair = None;
for i in 0..DEFAULT_NUM_ACCOUNTS {
let path = format!("{}{}'/{}'", BASE_DERIVATION_PATH, i, 0);
let keypair = mnemonic::keypair_from_mnemonic(mnemonic, Some(&path))?;
let pubkey =
Pubkey::from_str(&bs58::encode(keypair.verifying_key().as_bytes()).into_string())
.unwrap();
#[cfg(feature = "encryption")]
let encrypted_keypair = encryption::encrypt_keypair(&keypair, password)?;
#[cfg(not(feature = "encryption"))]
let encrypted_keypair = {
let _ = password;
keypair.as_bytes().to_vec()
};
if i == 0 {
primary_keypair = Some(keypair);
}
let stored = StoredKeypair {
pubkey,
encrypted_keypair,
derivation_path: Some(path),
index: i,
};
keypairs.push(stored);
}
let primary_path = format!("{}{}'/{}'", BASE_DERIVATION_PATH, 0, 0);
let keyring = Keyring::new(
name.to_string(),
primary_keypair.unwrap(),
Some(mnemonic.to_string()),
Some(primary_path),
);
let keyring_file = KeyringFile {
name: name.to_string(),
mnemonic: Some(mnemonic.to_string()),
keypairs,
};
self.save_keyring_file(name, &keyring_file)?;
Ok(keyring)
}
async fn load(&self, name: &str, password: &str) -> Result<Keyring> {
if !self.exists(name).await? {
return Err(RialoError::Keyring(format!("Keyring not found: {name}")));
}
let keyring_file = self.read_keyring_file(name)?;
if keyring_file.keypairs.is_empty() {
return Err(RialoError::Keyring("Keyring has no keypairs".to_string()));
}
let primary = &keyring_file.keypairs[0];
#[cfg(feature = "encryption")]
let keypair = encryption::decrypt_keypair(&primary.encrypted_keypair, password)?;
#[cfg(not(feature = "encryption"))]
let keypair = {
let _ = password;
let keypair_bytes: [u8; 32] = primary.encrypted_keypair[..32]
.try_into()
.map_err(|_| RialoError::Keyring("Invalid keypair size".to_string()))?;
Keypair::from_bytes(&keypair_bytes)
};
Ok(Keyring::new(
name.to_string(),
keypair,
keyring_file.mnemonic,
primary.derivation_path.clone(),
))
}
async fn list(&self) -> Result<Vec<String>> {
if !self.keyring_dir.exists() {
fs::create_dir_all(&self.keyring_dir)?;
return Ok(Vec::new());
}
let entries = fs::read_dir(&self.keyring_dir)
.map_err(|e| RialoError::Keyring(format!("Failed to read keyring directory: {e}")))?;
let mut names = Vec::new();
for entry in entries {
let entry = entry
.map_err(|e| RialoError::Keyring(format!("Failed to read directory entry: {e}")))?;
let path = entry.path();
if let Some(ext) = path.extension() {
if ext == "keyring" {
if let Some(stem) = path.file_stem() {
if let Some(name) = stem.to_str() {
names.push(name.to_string());
}
}
}
}
}
Ok(names)
}
async fn list_keypairs(&self, name: &str) -> Result<Vec<(u32, Pubkey)>> {
if !self.exists(name).await? {
return Err(RialoError::Keyring(format!("Keyring not found: {name}")));
}
let keyring_file = self.read_keyring_file(name)?;
let keypairs: Vec<(u32, Pubkey)> = keyring_file
.keypairs
.iter()
.map(|kp| (kp.index, kp.pubkey))
.collect();
Ok(keypairs)
}
async fn exists(&self, name: &str) -> Result<bool> {
Ok(self.keyring_path(name).exists())
}
#[cfg(feature = "hd-wallet")]
async fn derive_keyring(
&self,
source_keyring_name: &str,
new_keyring_name: &str,
keypair_index: u32,
password: &str,
) -> Result<Keyring> {
self.validate_mnemonic_operation(source_keyring_name, new_keyring_name)
.await?;
let source_keyring = self.load(source_keyring_name, password).await?;
let mnemonic = source_keyring.mnemonic().ok_or_else(|| {
RialoError::Keyring("Source keyring does not have a mnemonic phrase".to_string())
})?;
let path = format!("{}{}'/{}'", BASE_DERIVATION_PATH, keypair_index, 0);
let keypair = mnemonic::keypair_from_mnemonic(mnemonic, Some(&path))?;
#[cfg(feature = "encryption")]
let encrypted_keypair = encryption::encrypt_keypair(&keypair, password)?;
#[cfg(not(feature = "encryption"))]
let encrypted_keypair = keypair.as_bytes().to_vec();
let keyring = Keyring::new(
new_keyring_name.to_string(),
keypair,
Some(mnemonic.to_string()),
Some(path.clone()),
);
let stored = StoredKeypair {
pubkey: Pubkey::from_str(&keyring.pubkey_string()).unwrap(),
encrypted_keypair,
derivation_path: Some(path),
index: keypair_index,
};
let keyring_file = KeyringFile {
name: new_keyring_name.to_string(),
mnemonic: Some(mnemonic.to_string()),
keypairs: vec![stored],
};
self.save_keyring_file(new_keyring_name, &keyring_file)?;
Ok(keyring)
}
#[cfg(feature = "hd-wallet")]
async fn derive_keypair(
&self,
keyring_name: &str,
keypair_index: u32,
password: &str,
) -> Result<(u32, Pubkey)> {
if !self.exists(keyring_name).await? {
return Err(RialoError::Keyring(format!(
"Keyring not found: {keyring_name}"
)));
}
let mut keyring_file = self.read_keyring_file(keyring_name)?;
if keyring_file
.keypairs
.iter()
.any(|kp| kp.index == keypair_index)
{
return Err(RialoError::Keyring(format!(
"Keypair with index {keypair_index} already exists"
)));
}
let mnemonic = keyring_file.mnemonic.as_ref().ok_or_else(|| {
RialoError::Keyring("Keyring does not have a mnemonic phrase".to_string())
})?;
let path = format!("{}{}'/{}'", BASE_DERIVATION_PATH, keypair_index, 0);
let keypair = mnemonic::keypair_from_mnemonic(mnemonic, Some(&path))?;
#[cfg(feature = "encryption")]
let encrypted_keypair = encryption::encrypt_keypair(&keypair, password)?;
#[cfg(not(feature = "encryption"))]
let encrypted_keypair = {
let _ = password;
keypair.as_bytes().to_vec()
};
let pubkey =
Pubkey::from_str(&bs58::encode(keypair.verifying_key().as_bytes()).into_string())
.unwrap();
let stored = StoredKeypair {
pubkey,
encrypted_keypair,
derivation_path: Some(path),
index: keypair_index,
};
keyring_file.keypairs.push(stored);
self.save_keyring_file(keyring_name, &keyring_file)?;
Ok((keypair_index, pubkey))
}
async fn get_keypair_balance(&self, name: &str, _keypair_index: u32) -> Result<u64> {
if !self.exists(name).await? {
return Err(RialoError::Keyring(format!("Keyring not found: {name}")));
}
Ok(0) }
async fn list_public_keys(&self) -> Result<Vec<(String, Pubkey)>> {
let mut results = Vec::new();
let keyrings = self.list().await?;
for name in keyrings {
let path = self.keyring_path(&name);
if let Ok(mut file) = File::open(&path) {
let mut contents = String::new();
if file.read_to_string(&mut contents).is_ok() {
if let Ok(keyring_file) = serde_json::from_str::<KeyringFile>(&contents) {
if let Some(kp) = keyring_file.keypairs.first() {
results.push((name, kp.pubkey));
}
}
}
}
}
Ok(results)
}
async fn get_public_key(&self, name: &str) -> Result<Pubkey> {
if !self.exists(name).await? {
return Err(RialoError::Keyring(format!("Keyring not found: {name}")));
}
let keyring_file = self.read_keyring_file(name)?;
if let Some(kp) = keyring_file.keypairs.first() {
Ok(kp.pubkey)
} else {
Err(RialoError::Keyring("Keyring has no keypairs".to_string()))
}
}
async fn get_keypairs_info(&self, name: &str) -> Result<Vec<(u32, Pubkey)>> {
self.list_keypairs(name).await
}
async fn get_keypair_public_key(&self, name: &str, keypair_index: u32) -> Result<Pubkey> {
if !self.exists(name).await? {
return Err(RialoError::Keyring(format!("Keyring not found: {name}")));
}
let keyring_file = self.read_keyring_file(name)?;
for kp in keyring_file.keypairs {
if kp.index == keypair_index {
return Ok(kp.pubkey);
}
}
Err(RialoError::Keyring(format!(
"Keypair with index {keypair_index} not found in keyring '{name}'"
)))
}
async fn next_keypair_index(&self, name: &str) -> Result<u32> {
if !self.exists(name).await? {
return Err(RialoError::Keyring(format!("Keyring not found: {name}")));
}
let keyring_file = self.read_keyring_file(name)?;
if keyring_file.keypairs.is_empty() {
return Ok(0);
}
let max_index = keyring_file
.keypairs
.iter()
.map(|kp| kp.index)
.max()
.unwrap_or(0);
Ok(max_index + 1)
}
}
#[cfg(feature = "file-storage")]
#[deprecated(since = "0.2.0", note = "Use FileKeyringProvider instead")]
pub type FileWalletProvider = FileKeyringProvider;