v0.2.6-pre.016
This commit is contained in:
111
crates/ksp-wallet-lib/src/format.rs
Normal file
111
crates/ksp-wallet-lib/src/format.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
// 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;
|
||||
Reference in New Issue
Block a user