112 lines
5.2 KiB
Rust
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;
|