v0.5.2-pre.004

This commit is contained in:
2026-08-10 16:43:19 +02:00
parent ef630e3123
commit b8e6751747
15 changed files with 1489 additions and 89 deletions

View File

@@ -1,7 +1,9 @@
// file: ks-wallet/src/manager.rs
// version: 4
// version: 6
//! Multi-wallet discovery and native wallet file references.
//! Multi-wallet discovery, authenticated opening and password rotation.
use solana_signer::Signer; // rust-rules: trait-import
/// Opaque reference to one validated native wallet file.
///
@@ -182,6 +184,204 @@ impl crate::WalletManager {
) -> ks_core::Result<crate::WalletFileHandle> {
return inspect_native_wallet_file(path.as_ref().to_path_buf()).await;
}
/// Creates and atomically persists one new password-protected native wallet.
pub async fn create(
&self,
alias: crate::WalletAlias,
password: crate::WalletPassword,
) -> ks_core::Result<crate::UnlockedWallet> {
let path = self.wallet_path(&alias);
let exists = match tokio::fs::try_exists(&path).await {
std::result::Result::Ok(exists) => exists,
std::result::Result::Err(error) => {
return std::result::Result::Err(ks_core::Error::new(
"wallet_file_exists_check_failed",
error.to_string(),
));
},
};
if exists {
return std::result::Result::Err(ks_core::Error::new(
"wallet_native_already_exists",
"native wallet already exists for this alias",
));
}
let keypair = solana_keypair::Keypair::new();
let public_key = keypair.pubkey();
let keypair_bytes = zeroize::Zeroizing::new(keypair.to_bytes());
let container = match crate::protect_native_wallet_keypair(
alias.clone(),
public_key,
keypair_bytes,
password,
)
.await
{
std::result::Result::Ok(container) => container,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
match crate::write_native_wallet_file_atomic(path, &container).await {
std::result::Result::Ok(()) => {},
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
tracing::info!(
target: crate::TRACING_TARGET,
action = "create_native_wallet",
wallet_alias = alias.as_str(),
public_key = %public_key,
"created password-protected native wallet"
);
return std::result::Result::Ok(crate::UnlockedWallet::new(alias, keypair));
}
/// Authenticates and unlocks one native wallet from the configured directory.
pub async fn unlock(
&self,
alias: &crate::WalletAlias,
password: crate::WalletPassword,
) -> ks_core::Result<crate::UnlockedWallet> {
let path = self.wallet_path(alias);
let exists = match tokio::fs::try_exists(&path).await {
std::result::Result::Ok(exists) => exists,
std::result::Result::Err(error) => {
return std::result::Result::Err(ks_core::Error::new(
"wallet_file_exists_check_failed",
error.to_string(),
));
},
};
if !exists {
return std::result::Result::Err(ks_core::Error::new(
"wallet_native_not_found",
"native wallet was not found for this alias",
));
}
return unlock_native_wallet_at_path(
path,
std::option::Option::Some(alias),
std::option::Option::None,
password,
)
.await;
}
/// Authenticates an explicitly selected native wallet file outside or inside the store.
///
/// The opaque handle is revalidated against the file before any signing
/// capability is returned. The file is never registered in the configured store.
pub async fn unlock_file(
&self,
handle: &crate::WalletFileHandle,
password: crate::WalletPassword,
) -> ks_core::Result<crate::UnlockedWallet> {
return unlock_native_wallet_at_path(
handle.path.clone(),
std::option::Option::Some(&handle.alias),
std::option::Option::Some(handle),
password,
)
.await;
}
/// Changes the protection password while preserving the exact keypair and public key.
pub async fn change_password(
&self,
alias: &crate::WalletAlias,
current_password: crate::WalletPassword,
new_password: crate::WalletPassword,
) -> ks_core::Result<crate::WalletFileHandle> {
let path = self.wallet_path(alias);
let container = match crate::read_native_wallet_container(&path).await {
std::result::Result::Ok(container) => container,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if container.alias() != alias {
return std::result::Result::Err(ks_core::Error::new(
"wallet_native_alias_mismatch",
"native wallet filename alias does not match the container alias",
));
}
let public_key = *container.public_key();
let keypair = match crate::unlock_native_wallet_keypair(container, current_password).await {
std::result::Result::Ok(keypair) => keypair,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let keypair_bytes = zeroize::Zeroizing::new(keypair.to_bytes());
let replacement = match crate::protect_native_wallet_keypair(
alias.clone(),
public_key,
keypair_bytes,
new_password,
)
.await
{
std::result::Result::Ok(container) => container,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
match crate::replace_native_wallet_file_atomic(path.clone(), &replacement).await {
std::result::Result::Ok(()) => {},
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
tracing::info!(
target: crate::TRACING_TARGET,
action = "change_native_wallet_password",
wallet_alias = alias.as_str(),
public_key = %public_key,
"changed native wallet protection password"
);
return std::result::Result::Ok(crate::WalletFileHandle {
alias: alias.clone(),
public_key: public_key.to_string(),
format_version: crate::KSWALLET_FORMAT_VERSION,
path,
});
}
}
async fn unlock_native_wallet_at_path(
path: std::path::PathBuf,
expected_alias: std::option::Option<&crate::WalletAlias>,
expected_handle: std::option::Option<&crate::WalletFileHandle>,
password: crate::WalletPassword,
) -> ks_core::Result<crate::UnlockedWallet> {
let container = match crate::read_native_wallet_container(&path).await {
std::result::Result::Ok(container) => container,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if let std::option::Option::Some(alias) = expected_alias {
if container.alias() != alias {
return std::result::Result::Err(ks_core::Error::new(
"wallet_native_alias_mismatch",
"native wallet alias does not match the requested identity",
));
}
}
if let std::option::Option::Some(handle) = expected_handle {
if handle.format_version != crate::KSWALLET_FORMAT_VERSION
|| container.alias() != &handle.alias
|| container.public_key().to_string() != handle.public_key
{
return std::result::Result::Err(ks_core::Error::new(
"wallet_native_handle_stale",
"native wallet file no longer matches the inspected handle",
));
}
}
let alias = container.alias().clone();
let public_key = *container.public_key();
let keypair = match crate::unlock_native_wallet_keypair(container, password).await {
std::result::Result::Ok(keypair) => keypair,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
tracing::debug!(
target: crate::TRACING_TARGET,
action = "unlock_native_wallet",
wallet_alias = alias.as_str(),
public_key = %public_key,
"authenticated native wallet"
);
return std::result::Result::Ok(crate::UnlockedWallet::new(alias, keypair));
}
fn has_native_wallet_extension(path: &std::path::Path) -> bool {