// 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, metadata: crate::WalletCreateMetadata, ) -> ksp_core_lib::Result { 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 { 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 { 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 { 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 { 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;