Files
khadhroony-solana-project/crates/ksp-wallet-lib/src/format.rs
2026-08-22 08:18:38 +02:00

112 lines
5.2 KiB
Rust

// file: crates/ksp-wallet-lib/src/format.rs
// version: 2
//! Native `.kspwallet` version selection and bounded dispatch detection.
/// Explicit native `.kspwallet` wire format understood by this Wallet release.
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WalletFormat {
/// Stable historical JSON/Base64url format.
V1,
/// Canonical binary KSP format.
V2,
}
impl WalletFormat {
/// Returns the serialized native format version.
#[must_use]
pub const fn version(self) -> u32 {
return match self {
Self::V1 => crate::KSPWALLET_FORMAT_VERSION_V1,
Self::V2 => crate::KSPWALLET_FORMAT_VERSION_V2,
};
}
}
/// Default format selected by the non-versioned native Wallet creation/import APIs.
///
/// This is intentionally independent from [`LATEST_SUPPORTED_WALLET_FORMAT`]. Adding a future format does not implicitly move this default.
pub const DEFAULT_WALLET_FORMAT: WalletFormat = WalletFormat::V2;
/// Highest native Wallet format understood by this release.
pub const LATEST_SUPPORTED_WALLET_FORMAT: WalletFormat = WalletFormat::V2;
/// Creates a new in-memory native Wallet using [`DEFAULT_WALLET_FORMAT`].
pub async fn create_wallet(
owner_password: crate::OwnerPassword,
view_password: std::option::Option<crate::ViewPassword>,
metadata: crate::WalletCreateMetadata,
) -> ksp_core_lib::Result<crate::WalletOwner> {
return match DEFAULT_WALLET_FORMAT {
WalletFormat::V1 => crate::create_wallet_v1(owner_password, view_password, metadata).await,
WalletFormat::V2 => crate::create_wallet_v2(owner_password, view_password, metadata).await,
};
}
/// Opens VIEW from any currently supported native Wallet wire format.
pub async fn open_wallet_view(source: &[u8], password: crate::ViewPassword) -> ksp_core_lib::Result<crate::WalletView> {
return match detect_wallet_format(source) {
std::result::Result::Ok(WalletFormat::V1) => crate::open_wallet_view_v1(source, password).await,
std::result::Result::Ok(WalletFormat::V2) => crate::open_wallet_view_v2(source, password).await,
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
/// Opens OWNER from any currently supported native Wallet wire format.
pub async fn open_wallet_owner(source: &[u8], password: crate::OwnerPassword) -> ksp_core_lib::Result<crate::WalletOwner> {
return match detect_wallet_format(source) {
std::result::Result::Ok(WalletFormat::V1) => crate::open_wallet_owner_v1(source, password).await,
std::result::Result::Ok(WalletFormat::V2) => crate::open_wallet_owner_v2(source, password).await,
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
/// Inspects the authenticated locked projection of any currently supported native Wallet wire format.
pub fn inspect_locked_wallet(source: &[u8]) -> ksp_core_lib::Result<crate::LockedWalletInfo> {
return match detect_wallet_format(source) {
std::result::Result::Ok(WalletFormat::V1) => crate::inspect_locked_wallet_v1(source),
std::result::Result::Ok(WalletFormat::V2) => crate::inspect_locked_wallet_v2(source),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
/// Detects the native wire format without decrypting or authenticating the document.
///
/// Detection only selects the strict parser. The selected V1/V2 parser remains authoritative for all structural and cryptographic validation.
pub fn detect_wallet_format(source: &[u8]) -> ksp_core_lib::Result<WalletFormat> {
if source.len() > crate::KSPWALLET_MAX_FILE_BYTES {
return std::result::Result::Err(format_error("Wallet document exceeds the maximum size"));
}
if source.starts_with(crate::KSPWALLET_MAGIC.as_bytes()) {
let version_start = crate::KSPWALLET_MAGIC.len();
let version_end = version_start + 2;
let version_bytes = match source.get(version_start..version_end) {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Err(format_error("Wallet binary header is truncated")),
};
let version = u16::from_be_bytes([version_bytes[0], version_bytes[1]]);
return match u32::from(version) {
crate::KSPWALLET_FORMAT_VERSION_V2 => std::result::Result::Ok(WalletFormat::V2),
other => std::result::Result::Err(version_error(other)),
};
}
let first_non_whitespace = source.iter().copied().find(|byte| return !byte.is_ascii_whitespace());
if first_non_whitespace == std::option::Option::Some(b'{') {
return std::result::Result::Ok(WalletFormat::V1);
}
return std::result::Result::Err(format_error("Wallet document does not match a supported native framing"));
}
fn format_error(message: &'static str) -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(crate::ERROR_CODE_FORMAT_INVALID, message);
}
fn version_error(version: u32) -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(crate::ERROR_CODE_FORMAT_VERSION_UNSUPPORTED, "Wallet format version is not supported")
.with_context("format_version", version.to_string());
}
#[cfg(test)]
#[path = "../unit_tests/format.rs"]
mod tests;