Files
khadhroony-solana-project/crates/ksp-app-wallet-desk/src/wallet_secrets.rs
2026-08-21 10:01:11 +02:00

97 lines
4.0 KiB
Rust

// file: crates/ksp-app-wallet-desk/src/wallet_secrets.rs
// version: 1
//! Config-owned Wallet password candidate discovery for explicit unlock operations.
const WALLET_SECRET_PREFIX: &str = "KSP_SECRET_WALLET_PASS_";
/// One Config-owned secret candidate retained only inside Rust.
pub(crate) struct WalletSecretCandidate {
variable_name: String,
}
impl WalletSecretCandidate {
/// Explicitly reveals this candidate through Config's privileged management boundary.
pub(crate) fn reveal(self, management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result<std::option::Option<String>> {
return management.reveal_effective_environment_value(self.variable_name.as_str());
}
}
/// Discovers configured Wallet password candidates without revealing values.
pub(crate) fn discover_wallet_secret_candidates(
management: &ksp_config_lib::ConfigManagement,
wallet_id: &str,
) -> ksp_core_lib::Result<std::vec::Vec<WalletSecretCandidate>> {
let reports = management.environment_report();
let reports = match reports {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut names = reports
.into_iter()
.filter(|report| {
return report.sensitivity() == ksp_config_lib::ConfigSensitivity::Secret
&& report.effective_source().is_some()
&& report.variable_name().starts_with(WALLET_SECRET_PREFIX)
&& report.variable_name().len() > WALLET_SECRET_PREFIX.len();
})
.map(|report| return report.variable_name().to_owned())
.collect::<std::vec::Vec<_>>();
order_wallet_secret_candidate_names(wallet_id, names.as_mut_slice());
return std::result::Result::Ok(names.into_iter().map(|variable_name| return WalletSecretCandidate { variable_name }).collect());
}
/// Returns the number of effective configured Wallet password candidates without revealing names or values.
pub(crate) fn wallet_secret_candidate_count(management: &ksp_config_lib::ConfigManagement, wallet_id: &str) -> ksp_core_lib::Result<usize> {
let candidates = discover_wallet_secret_candidates(management, wallet_id);
return candidates.map(|values| return values.len());
}
/// Normalizes a native Wallet filename to the deterministic Config secret label candidate.
#[must_use]
fn normalize_wallet_secret_label(wallet_id: &str) -> String {
let stem = wallet_id.strip_suffix(crate::WALLET_FILE_SUFFIX).unwrap_or(wallet_id);
let mut output = String::new();
let mut previous_separator = false;
for byte in stem.bytes() {
let normalized = if byte.is_ascii_alphanumeric() { byte.to_ascii_uppercase() as char } else { '_' };
if normalized == '_' {
if output.is_empty() || previous_separator {
continue;
}
output.push('_');
previous_separator = true;
} else {
output.push(normalized);
previous_separator = false;
}
}
while output.ends_with('_') {
output.pop();
}
return output;
}
fn order_wallet_secret_candidate_names(wallet_id: &str, names: &mut [String]) {
let normalized = normalize_wallet_secret_label(wallet_id);
names.sort_by(|left, right| {
return wallet_secret_sort_key(left.as_str(), normalized.as_str()).cmp(&wallet_secret_sort_key(right.as_str(), normalized.as_str()));
});
}
fn wallet_secret_sort_key(variable_name: &str, normalized_filename: &str) -> (u8, u64, String) {
let suffix = variable_name.strip_prefix(WALLET_SECRET_PREFIX).unwrap_or(variable_name);
if suffix == normalized_filename && !normalized_filename.is_empty() {
return (0, 0, suffix.to_owned());
}
if !suffix.is_empty() && suffix.bytes().all(|byte| return byte.is_ascii_digit()) {
let parsed = suffix.parse::<u64>().unwrap_or(u64::MAX);
return (1, parsed, suffix.to_owned());
}
return (2, 0, suffix.to_owned());
}
#[cfg(test)]
#[path = "../unit_tests/wallet_secrets.rs"]
mod tests;