v0.2.6-pre.016
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-wallet-lib/src/constants.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! Wallet-owned constants.
|
||||
|
||||
@@ -91,6 +91,14 @@ pub const KSPWALLET_V1_XCHACHA_NONCE_BYTES: usize = 24;
|
||||
pub const KSPWALLET_V2_AEAD_TAG_BYTES: usize = KSPWALLET_V1_AEAD_TAG_BYTES;
|
||||
/// Argon2 version serialized by V2 key slots.
|
||||
pub const KSPWALLET_V2_ARGON2_VERSION: u32 = KSPWALLET_V1_ARGON2_VERSION;
|
||||
/// Default Argon2id iteration count for newly created V2 key slots.
|
||||
pub const KSPWALLET_V2_DEFAULT_ARGON2_ITERATIONS: u32 = KSPWALLET_V1_DEFAULT_ARGON2_ITERATIONS;
|
||||
/// Default Argon2id memory cost for newly created V2 key slots.
|
||||
pub const KSPWALLET_V2_DEFAULT_ARGON2_MEMORY_KIB: u32 = KSPWALLET_V1_DEFAULT_ARGON2_MEMORY_KIB;
|
||||
/// Default Argon2id parallelism for newly created V2 key slots.
|
||||
pub const KSPWALLET_V2_DEFAULT_ARGON2_PARALLELISM: u32 = KSPWALLET_V1_DEFAULT_ARGON2_PARALLELISM;
|
||||
/// Default KDF salt size generated independently for every newly created V2 key slot.
|
||||
pub const KSPWALLET_V2_DEFAULT_KDF_SALT_BYTES: usize = KSPWALLET_V1_DEFAULT_KDF_SALT_BYTES;
|
||||
/// Byte length of an Ed25519 public key used as V2 Wallet format authority.
|
||||
pub const KSPWALLET_V2_ED25519_PUBLIC_KEY_BYTES: usize = KSPWALLET_V1_ED25519_PUBLIC_KEY_BYTES;
|
||||
/// Byte length of an Ed25519 detached V2 state signature.
|
||||
@@ -123,6 +131,8 @@ pub const KSPWALLET_V2_OWNER_CONTROL_AAD_DOMAIN: &[u8] = b"KSPWALLET-V2-AAD-OWNE
|
||||
pub const KSPWALLET_V2_OWNER_SLOT_AAD_DOMAIN: &[u8] = b"KSPWALLET-V2-AAD-OWNER-SLOT";
|
||||
/// Domain separator for V2 OWNER-only secret compartment AEAD AAD.
|
||||
pub const KSPWALLET_V2_SECRET_AAD_DOMAIN: &[u8] = b"KSPWALLET-V2-AAD-SECRET";
|
||||
/// Exact Solana keypair plaintext size retained by the initial V2 secret payload profile.
|
||||
pub const KSPWALLET_V2_SECRET_PLAINTEXT_BYTES: usize = KSPWALLET_V1_SECRET_PLAINTEXT_BYTES;
|
||||
/// Byte length of every V2 key-slot identifier.
|
||||
pub const KSPWALLET_V2_SLOT_ID_BYTES: usize = KSPWALLET_V1_SLOT_ID_BYTES;
|
||||
/// Domain separator for the V2 OWNER state-signature transcript.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// file: crates/ksp-wallet-lib/src/crypto.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! In-memory cryptographic primitives for native `.kspwallet` V1.
|
||||
//! In-memory cryptographic primitives shared by native `.kspwallet` V1/V2 runtime.
|
||||
|
||||
use chacha20poly1305::KeyInit; // rust-rules: trait-import
|
||||
use chacha20poly1305::aead::Aead; // rust-rules: trait-import
|
||||
|
||||
/// Exact V1 content-key and password-derived-key size in bytes.
|
||||
/// Exact content-key and password-derived-key size in bytes for the current V1/V2 profiles.
|
||||
pub(crate) const SECRET_KEY_BYTES: usize = 32;
|
||||
|
||||
/// Owned 32-byte secret key with redacted diagnostics and drop-time zeroization.
|
||||
@@ -68,6 +68,11 @@ pub(crate) fn derive_password_key(password: &[u8], kdf: &crate::WalletKdfParamet
|
||||
return derive_argon2id(password, kdf.salt(), kdf.memory_kib(), kdf.iterations(), kdf.parallelism());
|
||||
}
|
||||
|
||||
/// Derives one V2 password wrapping key from serialized Argon2id parameters.
|
||||
pub(crate) fn derive_password_key_v2(password: &[u8], kdf: &crate::WalletKdfParametersV2) -> ksp_core_lib::Result<crate::SecretKeyV1> {
|
||||
return derive_argon2id(password, kdf.salt(), kdf.memory_kib(), kdf.iterations(), kdf.parallelism());
|
||||
}
|
||||
|
||||
/// Wraps one 32-byte content key with XChaCha20-Poly1305 and caller-provided domain-separated AAD.
|
||||
pub(crate) fn wrap_key(
|
||||
wrapping_key: &crate::SecretKeyV1,
|
||||
|
||||
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;
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-wallet-lib/src/lib.rs
|
||||
// version: 12
|
||||
// version: 13
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -16,7 +16,8 @@
|
||||
//! replacement. `0.2.5-pre.008` adds bounded Solana CLI JSON and canonical full-keypair Base58 import/export adapters with safe inspection and no-clobber
|
||||
//! native import/export publication. `0.2.5-pre.009` adds adversarial security/compliance canaries and records the final dependency/interop audit before
|
||||
//! documentation closure. `0.2.6-pre.015` defines the strict binary `.kspwallet` V2 wire and its bounded canonical codec without yet changing the public
|
||||
//! persistence default or Wallet Desk dispatch. Public keys are consumed exclusively through the [`ksp_core_lib::Pubkey`] re-export owned by KSP Core, and behavioral
|
||||
//! persistence default or Wallet Desk dispatch. `0.2.6-pre.016` adds complete V2 create/open/administration, stable version-neutral dispatch, explicit V1/V2
|
||||
//! APIs and moves the default native creation/import format to V2 without coupling that default to the latest supported version. Public keys are consumed exclusively through the [`ksp_core_lib::Pubkey`] re-export owned by KSP Core, and behavioral
|
||||
//! observability uses only
|
||||
//! `ksp-logging-lib` with the explicit crate target defined in `src/constants.rs`.
|
||||
|
||||
@@ -24,16 +25,19 @@ mod capability;
|
||||
mod constants;
|
||||
mod crypto;
|
||||
mod error;
|
||||
mod format;
|
||||
mod metadata;
|
||||
mod owner;
|
||||
mod password;
|
||||
mod payload;
|
||||
mod persistence;
|
||||
mod runtime;
|
||||
mod transcript;
|
||||
mod transcript_v2;
|
||||
mod transfer;
|
||||
mod view;
|
||||
mod wallet;
|
||||
mod wallet_v2;
|
||||
mod wire;
|
||||
mod wire_v2;
|
||||
|
||||
@@ -131,6 +135,14 @@ pub use self::constants::KSPWALLET_V1_XCHACHA_NONCE_BYTES;
|
||||
pub use self::constants::KSPWALLET_V2_AEAD_TAG_BYTES;
|
||||
/// Argon2 version serialized by `.kspwallet` V2 key slots.
|
||||
pub use self::constants::KSPWALLET_V2_ARGON2_VERSION;
|
||||
/// Default Argon2id iteration count for newly created V2 slots.
|
||||
pub use self::constants::KSPWALLET_V2_DEFAULT_ARGON2_ITERATIONS;
|
||||
/// Default Argon2id memory cost for newly created V2 slots.
|
||||
pub use self::constants::KSPWALLET_V2_DEFAULT_ARGON2_MEMORY_KIB;
|
||||
/// Default Argon2id parallelism for newly created V2 slots.
|
||||
pub use self::constants::KSPWALLET_V2_DEFAULT_ARGON2_PARALLELISM;
|
||||
/// Default KDF salt size generated for newly created V2 slots.
|
||||
pub use self::constants::KSPWALLET_V2_DEFAULT_KDF_SALT_BYTES;
|
||||
/// Byte length of the V2 Ed25519 format-authority public key.
|
||||
pub use self::constants::KSPWALLET_V2_ED25519_PUBLIC_KEY_BYTES;
|
||||
/// Byte length of the V2 Ed25519 detached state signature.
|
||||
@@ -163,6 +175,8 @@ pub use self::constants::KSPWALLET_V2_OWNER_CONTROL_AAD_DOMAIN;
|
||||
pub use self::constants::KSPWALLET_V2_OWNER_SLOT_AAD_DOMAIN;
|
||||
/// Domain separator for V2 OWNER-only secret compartment AEAD AAD.
|
||||
pub use self::constants::KSPWALLET_V2_SECRET_AAD_DOMAIN;
|
||||
/// Exact Solana keypair plaintext size retained by the initial V2 secret payload profile.
|
||||
pub use self::constants::KSPWALLET_V2_SECRET_PLAINTEXT_BYTES;
|
||||
/// Byte length of every V2 key-slot identifier.
|
||||
pub use self::constants::KSPWALLET_V2_SLOT_ID_BYTES;
|
||||
/// Domain separator for the V2 OWNER state-signature transcript.
|
||||
@@ -205,9 +219,27 @@ pub use self::error::ERROR_CODE_STATE_CONFLICT;
|
||||
pub use self::error::ERROR_CODE_TRANSFER_FORMAT_UNSUPPORTED;
|
||||
/// Error code used when a VIEW unlock attempt fails without exposing a finer cryptographic oracle.
|
||||
pub use self::error::ERROR_CODE_VIEW_UNLOCK_FAILED;
|
||||
/// Default native Wallet format selected by non-versioned create/import APIs.
|
||||
pub use self::format::DEFAULT_WALLET_FORMAT;
|
||||
/// Highest native Wallet format supported by this release.
|
||||
pub use self::format::LATEST_SUPPORTED_WALLET_FORMAT;
|
||||
/// Explicit native Wallet wire-format selector.
|
||||
pub use self::format::WalletFormat;
|
||||
/// Creates a new in-memory native Wallet using the explicit default format.
|
||||
pub use self::format::create_wallet;
|
||||
/// Detects V1 JSON versus V2 binary framing before strict parser dispatch.
|
||||
pub use self::format::detect_wallet_format;
|
||||
/// Inspects any supported in-memory native Wallet document.
|
||||
pub use self::format::inspect_locked_wallet;
|
||||
/// Opens OWNER from any supported in-memory native Wallet document.
|
||||
pub use self::format::open_wallet_owner;
|
||||
/// Opens VIEW from any supported in-memory native Wallet document.
|
||||
pub use self::format::open_wallet_view;
|
||||
/// Minimal non-secret information available while a native Wallet remains locked.
|
||||
pub use self::metadata::LockedWalletInfo;
|
||||
/// Protected initial metadata supplied to native Wallet creation.
|
||||
/// Protected initial metadata supplied to version-neutral native Wallet creation.
|
||||
pub type WalletCreateMetadata = self::metadata::WalletCreateMetadataV1;
|
||||
/// Protected initial metadata payload profile retained for explicit V1 compatibility.
|
||||
pub use self::metadata::WalletCreateMetadataV1;
|
||||
/// Safe metadata projection produced after VIEW or OWNER authorization.
|
||||
pub use self::metadata::WalletInfo;
|
||||
@@ -219,22 +251,46 @@ pub use self::owner::WalletOwner;
|
||||
pub use self::password::OwnerPassword;
|
||||
/// Owned VIEW password material with redacted diagnostics and drop-time zeroization.
|
||||
pub use self::password::ViewPassword;
|
||||
/// Creates and no-clobber persists a native Wallet using the explicit default format.
|
||||
pub use self::persistence::create_wallet_file;
|
||||
/// Creates and no-clobber persists a new native `.kspwallet` V1 file.
|
||||
pub use self::persistence::create_wallet_file_v1;
|
||||
/// Creates and no-clobber persists a new native `.kspwallet` V2 binary file.
|
||||
pub use self::persistence::create_wallet_file_v2;
|
||||
/// Reads and verifies a locked native Wallet using V1/V2 auto-detection.
|
||||
pub use self::persistence::inspect_locked_wallet_file;
|
||||
/// Reads and verifies a locked native `.kspwallet` V1 file.
|
||||
pub use self::persistence::inspect_locked_wallet_file_v1;
|
||||
/// Reads and verifies a locked native `.kspwallet` V2 binary file.
|
||||
pub use self::persistence::inspect_locked_wallet_file_v2;
|
||||
/// Opens a native Wallet with OWNER capability using V1/V2 auto-detection.
|
||||
pub use self::persistence::open_wallet_owner_file;
|
||||
/// Opens a native `.kspwallet` V1 file with OWNER capability.
|
||||
pub use self::persistence::open_wallet_owner_file_v1;
|
||||
/// Opens a native `.kspwallet` V2 binary file with OWNER capability.
|
||||
pub use self::persistence::open_wallet_owner_file_v2;
|
||||
/// Opens a native Wallet with VIEW capability using V1/V2 auto-detection.
|
||||
pub use self::persistence::open_wallet_view_file;
|
||||
/// Opens a native `.kspwallet` V1 file with VIEW capability.
|
||||
pub use self::persistence::open_wallet_view_file_v1;
|
||||
/// Opens a native `.kspwallet` V2 binary file with VIEW capability.
|
||||
pub use self::persistence::open_wallet_view_file_v2;
|
||||
/// Explicit secret-transfer format supported by Wallet.
|
||||
pub use self::transfer::WalletTransferFormat;
|
||||
/// Safe public identity projection of one validated secret-transfer source.
|
||||
pub use self::transfer::WalletTransferInspection;
|
||||
/// Imports one in-memory transfer payload into a new no-clobber native Wallet using the explicit default format.
|
||||
pub use self::transfer::import_wallet_transfer;
|
||||
/// Imports one bounded external transfer file into a new no-clobber native Wallet using the explicit default format.
|
||||
pub use self::transfer::import_wallet_transfer_file;
|
||||
/// Imports one bounded external transfer file into a new no-clobber native Wallet V1.
|
||||
pub use self::transfer::import_wallet_transfer_file_v1;
|
||||
/// Imports one bounded external transfer file into a new no-clobber native Wallet V2.
|
||||
pub use self::transfer::import_wallet_transfer_file_v2;
|
||||
/// Imports one in-memory transfer payload into a new no-clobber native Wallet V1.
|
||||
pub use self::transfer::import_wallet_transfer_v1;
|
||||
/// Imports one in-memory transfer payload into a new no-clobber native Wallet V2.
|
||||
pub use self::transfer::import_wallet_transfer_v2;
|
||||
/// Validates one in-memory transfer payload and exposes only its derived public identity.
|
||||
pub use self::transfer::inspect_wallet_transfer;
|
||||
/// Validates one bounded external transfer file and exposes only its derived public identity.
|
||||
@@ -249,6 +305,14 @@ pub use self::wallet::inspect_locked_wallet_v1;
|
||||
pub use self::wallet::open_wallet_owner_v1;
|
||||
/// Opens the VIEW capability from a native Wallet V1 document.
|
||||
pub use self::wallet::open_wallet_view_v1;
|
||||
/// Creates a new in-memory native Wallet V2.
|
||||
pub use self::wallet_v2::create_wallet_v2;
|
||||
/// Parses and verifies locked native Wallet V2 state without unlocking protected metadata.
|
||||
pub use self::wallet_v2::inspect_locked_wallet_v2;
|
||||
/// Opens the OWNER capability from a native Wallet V2 document.
|
||||
pub use self::wallet_v2::open_wallet_owner_v2;
|
||||
/// Opens the VIEW capability from a native Wallet V2 document.
|
||||
pub use self::wallet_v2::open_wallet_view_v2;
|
||||
/// Strict semantic representation of one parsed native `.kspwallet` V1 envelope.
|
||||
pub use self::wire::KspWalletEnvelopeV1;
|
||||
/// Authenticated-encryption algorithm fixed by native Wallet V1.
|
||||
@@ -308,6 +372,8 @@ pub(crate) use self::crypto::SecretKeyV1;
|
||||
pub(crate) use self::crypto::decrypt_bytes;
|
||||
/// Derives one V1 password wrapping key from serialized Argon2id parameters.
|
||||
pub(crate) use self::crypto::derive_password_key;
|
||||
/// Derives one V2 password wrapping key from serialized Argon2id parameters.
|
||||
pub(crate) use self::crypto::derive_password_key_v2;
|
||||
/// Encrypts bounded plaintext bytes with XChaCha20-Poly1305 and caller-provided domain-separated AAD.
|
||||
pub(crate) use self::crypto::encrypt_bytes;
|
||||
/// Generates a fresh fixed-size byte array from the operating-system CSPRNG.
|
||||
@@ -328,7 +394,9 @@ pub(crate) use self::payload::decode_owner_control_payload;
|
||||
pub(crate) use self::payload::encode_initial_metadata_payload;
|
||||
/// Encodes owner control payload.
|
||||
pub(crate) use self::payload::encode_owner_control_payload;
|
||||
/// Internal no-clobber native persistence path shared by transfer adapters.
|
||||
/// Internal no-clobber native persistence path shared by version-neutral transfer adapters.
|
||||
pub(crate) use self::persistence::persist_new_wallet_content;
|
||||
/// Internal V1 no-clobber native persistence path retained for explicit compatibility adapters.
|
||||
pub(crate) use self::persistence::persist_new_wallet_content_v1;
|
||||
/// Persists new wallet fault before publish.
|
||||
#[cfg(test)]
|
||||
@@ -341,9 +409,15 @@ pub(crate) use self::persistence::persist_new_wallet_for_test;
|
||||
pub(crate) use self::persistence::replace_wallet_fault_before_publish;
|
||||
/// Replaces wallet file v1.
|
||||
pub(crate) use self::persistence::replace_wallet_file_v1;
|
||||
/// Replaces wallet file v2.
|
||||
pub(crate) use self::persistence::replace_wallet_file_v2;
|
||||
/// Replaces wallet for test.
|
||||
#[cfg(test)]
|
||||
pub(crate) use self::persistence::replace_wallet_for_test;
|
||||
/// Version-neutral OWNER runtime state shared by public Wallet handles.
|
||||
pub(crate) use self::runtime::OwnerState;
|
||||
/// Version-neutral VIEW runtime state shared by public Wallet handles.
|
||||
pub(crate) use self::runtime::ViewState;
|
||||
/// Internal deterministic compartment-AAD codec shared by Wallet crypto layers.
|
||||
pub(crate) use self::transcript::compartment_aad;
|
||||
/// Internal deterministic key-slot-AAD codec shared by Wallet crypto layers.
|
||||
@@ -357,12 +431,20 @@ pub(crate) use self::transcript_v2::slot_aad_v2;
|
||||
/// Internal deterministic V2 OWNER-state transcript codec shared by Wallet crypto layers.
|
||||
pub(crate) use self::transcript_v2::state_transcript_v2;
|
||||
/// Internal no-clobber transfer-file writer used only by OWNER export.
|
||||
pub(crate) use self::transfer::write_wallet_transfer_file_v1;
|
||||
pub(crate) use self::transfer::write_wallet_transfer_file;
|
||||
/// Crate-internal `OwnerStateV1` state shared across the owning crate.
|
||||
pub(crate) use self::wallet::OwnerStateV1;
|
||||
/// Crate-internal `ViewStateV1` state shared across the owning crate.
|
||||
pub(crate) use self::wallet::ViewStateV1;
|
||||
/// Internal imported-keypair creation path shared by transfer adapters.
|
||||
/// Internal imported-keypair V1 creation path shared by transfer adapters.
|
||||
pub(crate) use self::wallet::create_wallet_v1_from_keypair;
|
||||
/// Verifies state signature.
|
||||
/// Verifies V1 state signature.
|
||||
pub(crate) use self::wallet::verify_state_signature;
|
||||
/// Crate-internal `OwnerStateV2` state shared across the owning crate.
|
||||
pub(crate) use self::wallet_v2::OwnerStateV2;
|
||||
/// Crate-internal `ViewStateV2` state shared across the owning crate.
|
||||
pub(crate) use self::wallet_v2::ViewStateV2;
|
||||
/// Internal imported-keypair V2 creation path shared by transfer adapters.
|
||||
pub(crate) use self::wallet_v2::create_wallet_v2_from_keypair;
|
||||
/// Verifies V2 state signature.
|
||||
pub(crate) use self::wallet_v2::verify_state_signature_v2;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-wallet-lib/src/metadata.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
/// Protected metadata requested when creating a new native Wallet.
|
||||
///
|
||||
@@ -78,12 +78,13 @@ pub struct WalletInfo {
|
||||
impl WalletInfo {
|
||||
/// Creates a new `WalletInfo` value.
|
||||
pub(crate) fn new(
|
||||
format_version: u32,
|
||||
capability: crate::WalletCapability,
|
||||
pubkey: ksp_core_lib::Pubkey,
|
||||
alias: std::option::Option<std::string::String>,
|
||||
notes: std::vec::Vec<crate::WalletNote>,
|
||||
) -> Self {
|
||||
return Self { format_version: crate::KSPWALLET_FORMAT_VERSION_V1, capability, pubkey, alias, notes };
|
||||
return Self { format_version, capability, pubkey, alias, notes };
|
||||
}
|
||||
|
||||
/// Returns the native Wallet format version parsed for this projection.
|
||||
@@ -139,8 +140,8 @@ pub struct LockedWalletInfo {
|
||||
|
||||
impl LockedWalletInfo {
|
||||
/// Creates a new `LockedWalletInfo` value.
|
||||
pub(crate) const fn new(view_enabled: bool) -> Self {
|
||||
return Self { format_version: crate::KSPWALLET_FORMAT_VERSION_V1, view_enabled };
|
||||
pub(crate) const fn new(format_version: u32, view_enabled: bool) -> Self {
|
||||
return Self { format_version, view_enabled };
|
||||
}
|
||||
|
||||
/// Returns the native Wallet format version.
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
// file: crates/ksp-wallet-lib/src/owner.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
/// Authorized OWNER capability handle.
|
||||
///
|
||||
/// OWNER exposes protected metadata, Solana message signing and the authenticated administration operations defined by native Wallet V1. Secret key
|
||||
/// OWNER exposes protected metadata, Solana message signing and the authenticated administration operations defined by the authenticated native Wallet format. Secret key
|
||||
/// material remains encapsulated and is never exposed through a general-purpose getter.
|
||||
pub struct WalletOwner {
|
||||
info: crate::WalletInfo,
|
||||
state: crate::OwnerStateV1,
|
||||
state: crate::OwnerState,
|
||||
}
|
||||
|
||||
impl WalletOwner {
|
||||
/// Builds `WalletOwner` from unlocked.
|
||||
pub(crate) fn from_unlocked(info: crate::WalletInfo, state: crate::OwnerStateV1) -> Self {
|
||||
pub(crate) fn from_unlocked(info: crate::WalletInfo, state: crate::OwnerState) -> Self {
|
||||
return Self { info, state };
|
||||
}
|
||||
|
||||
/// Returns the native Wallet format version backing this authorized handle.
|
||||
#[must_use]
|
||||
pub const fn format_version(&self) -> u32 {
|
||||
return self.info.format_version();
|
||||
}
|
||||
|
||||
/// Returns the authorization capability represented by this handle.
|
||||
#[must_use]
|
||||
pub const fn capability(&self) -> crate::WalletCapability {
|
||||
@@ -84,7 +90,7 @@ impl WalletOwner {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let result = crate::write_wallet_transfer_file_v1(destination.as_ref().to_path_buf(), encoded, format).await;
|
||||
let result = crate::write_wallet_transfer_file(destination.as_ref().to_path_buf(), encoded, format).await;
|
||||
if result.is_ok() {
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
@@ -181,11 +187,13 @@ impl WalletOwner {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let persist_result = persist_staged(destination.as_ref().to_path_buf(), self.state.envelope(), &envelope).await;
|
||||
let persist_result = self.state.persist_staged(destination.as_ref().to_path_buf(), &envelope).await;
|
||||
if let std::result::Result::Err(error) = persist_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
self.state.apply_envelope(envelope);
|
||||
if let std::result::Result::Err(error) = self.state.apply_envelope(envelope) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
operation = "wallet_rotate_owner_password",
|
||||
@@ -207,11 +215,13 @@ impl WalletOwner {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let persist_result = persist_staged(destination.as_ref().to_path_buf(), self.state.envelope(), &envelope).await;
|
||||
let persist_result = self.state.persist_staged(destination.as_ref().to_path_buf(), &envelope).await;
|
||||
if let std::result::Result::Err(error) = persist_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
self.state.apply_envelope(envelope);
|
||||
if let std::result::Result::Err(error) = self.state.apply_envelope(envelope) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
operation = "wallet_rotate_view_password",
|
||||
@@ -229,11 +239,13 @@ impl WalletOwner {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let persist_result = persist_staged(destination.as_ref().to_path_buf(), self.state.envelope(), &envelope).await;
|
||||
let persist_result = self.state.persist_staged(destination.as_ref().to_path_buf(), &envelope).await;
|
||||
if let std::result::Result::Err(error) = persist_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
self.state.apply_strong_view_state(envelope, metadata_key);
|
||||
if let std::result::Result::Err(error) = self.state.apply_strong_view_state(envelope, metadata_key) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
operation = "wallet_disable_view",
|
||||
@@ -253,11 +265,13 @@ impl WalletOwner {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let persist_result = persist_staged(destination.as_ref().to_path_buf(), self.state.envelope(), &envelope).await;
|
||||
let persist_result = self.state.persist_staged(destination.as_ref().to_path_buf(), &envelope).await;
|
||||
if let std::result::Result::Err(error) = persist_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
self.state.apply_strong_view_state(envelope, metadata_key);
|
||||
if let std::result::Result::Err(error) = self.state.apply_strong_view_state(envelope, metadata_key) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
operation = "wallet_recreate_view",
|
||||
@@ -267,9 +281,16 @@ impl WalletOwner {
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
/// Serializes the complete locked `.kspwallet` V1 document without exposing any unlocked secret material.
|
||||
/// Serializes the complete locked native Wallet in its current V1 or V2 wire format.
|
||||
pub fn to_native_bytes(&self) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
|
||||
return self.state.native_bytes();
|
||||
}
|
||||
|
||||
/// Serializes a V1 handle as its historical JSON document.
|
||||
///
|
||||
/// V2 handles return a format error instead of being converted implicitly.
|
||||
pub fn to_json_bytes(&self) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
|
||||
return self.state.envelope().to_json_bytes();
|
||||
return self.state.json_bytes_v1();
|
||||
}
|
||||
|
||||
async fn persist_metadata_payload(
|
||||
@@ -282,11 +303,13 @@ impl WalletOwner {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let persist_result = persist_staged(destination, self.state.envelope(), &envelope).await;
|
||||
let persist_result = self.state.persist_staged(destination, &envelope).await;
|
||||
if let std::result::Result::Err(error) = persist_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
self.state.apply_envelope(envelope);
|
||||
if let std::result::Result::Err(error) = self.state.apply_envelope(envelope) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
self.info = info;
|
||||
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, operation = operation, capability = "owner", "wallet protected metadata updated");
|
||||
return std::result::Result::Ok(());
|
||||
@@ -299,18 +322,6 @@ impl std::fmt::Debug for WalletOwner {
|
||||
}
|
||||
}
|
||||
|
||||
async fn persist_staged(
|
||||
destination: std::path::PathBuf,
|
||||
expected_current: &crate::KspWalletEnvelopeV1,
|
||||
envelope: &crate::KspWalletEnvelopeV1,
|
||||
) -> ksp_core_lib::Result<()> {
|
||||
let serialized = match envelope.to_json_bytes() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return crate::replace_wallet_file_v1(destination, expected_current.clone(), serialized).await;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/administration.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// file: crates/ksp-wallet-lib/src/payload.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
//! Plaintext payload codecs protected inside native `.kspwallet` V1 compartments.
|
||||
//! Plaintext payload codecs shared by the initial native `.kspwallet` V1/V2 protected compartments.
|
||||
|
||||
use base64::Engine; // rust-rules: trait-import
|
||||
use std::str::FromStr; // rust-rules: trait-import
|
||||
@@ -119,8 +119,8 @@ impl crate::MetadataPayloadV1 {
|
||||
}
|
||||
|
||||
/// Consumes this value and returns info.
|
||||
pub(crate) fn into_info(self, capability: crate::WalletCapability) -> crate::WalletInfo {
|
||||
return crate::WalletInfo::new(capability, self.pubkey, self.alias, self.notes);
|
||||
pub(crate) fn into_info(self, format_version: u32, capability: crate::WalletCapability) -> crate::WalletInfo {
|
||||
return crate::WalletInfo::new(format_version, capability, self.pubkey, self.alias, self.notes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,54 @@
|
||||
// file: crates/ksp-wallet-lib/src/persistence.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
//! Async-first native Wallet V1 filesystem persistence.
|
||||
//! Async-first native Wallet V1/V2 filesystem persistence and version-neutral dispatch.
|
||||
|
||||
use std::io::Read; // rust-rules: trait-import
|
||||
use std::io::Write; // rust-rules: trait-import
|
||||
|
||||
/// Creates a new native `.kspwallet` using [`crate::DEFAULT_WALLET_FORMAT`].
|
||||
///
|
||||
/// The default is explicitly V2 in this release and does not track future `LATEST_SUPPORTED_WALLET_FORMAT` values automatically.
|
||||
pub async fn create_wallet_file(
|
||||
destination: impl std::convert::AsRef<std::path::Path>,
|
||||
owner_password: crate::OwnerPassword,
|
||||
view_password: std::option::Option<crate::ViewPassword>,
|
||||
metadata: crate::WalletCreateMetadata,
|
||||
) -> ksp_core_lib::Result<crate::WalletOwner> {
|
||||
return match crate::DEFAULT_WALLET_FORMAT {
|
||||
crate::WalletFormat::V1 => create_wallet_file_v1(destination, owner_password, view_password, metadata).await,
|
||||
crate::WalletFormat::V2 => create_wallet_file_v2(destination, owner_password, view_password, metadata).await,
|
||||
};
|
||||
}
|
||||
|
||||
/// Creates and no-clobber persists a new native `.kspwallet` V2 binary file.
|
||||
pub async fn create_wallet_file_v2(
|
||||
destination: impl std::convert::AsRef<std::path::Path>,
|
||||
owner_password: crate::OwnerPassword,
|
||||
view_password: std::option::Option<crate::ViewPassword>,
|
||||
metadata: crate::WalletCreateMetadata,
|
||||
) -> ksp_core_lib::Result<crate::WalletOwner> {
|
||||
let destination = destination.as_ref().to_path_buf();
|
||||
let owner = match crate::create_wallet_v2(owner_password, view_password, metadata).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let serialized = match owner.to_native_bytes() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if let std::result::Result::Err(error) = persist_new_wallet_async(destination, serialized).await {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
operation = "wallet_create_file",
|
||||
format_version = crate::KSPWALLET_FORMAT_VERSION_V2,
|
||||
"native wallet persisted with no-clobber semantics"
|
||||
);
|
||||
return std::result::Result::Ok(owner);
|
||||
}
|
||||
|
||||
/// Creates a new native `.kspwallet` V1 at `destination` without overwriting an existing path.
|
||||
///
|
||||
/// The complete encrypted document is created in memory first, written and synchronized through a temporary file in the destination directory, then
|
||||
@@ -40,6 +83,30 @@ pub async fn create_wallet_file_v1(
|
||||
return std::result::Result::Ok(owner);
|
||||
}
|
||||
|
||||
/// Opens a supported native `.kspwallet` file with VIEW capability after bounded V1/V2 detection.
|
||||
pub async fn open_wallet_view_file(
|
||||
source: impl std::convert::AsRef<std::path::Path>,
|
||||
password: crate::ViewPassword,
|
||||
) -> ksp_core_lib::Result<crate::WalletView> {
|
||||
let bytes = match read_wallet_file_async(source.as_ref().to_path_buf()).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return crate::open_wallet_view(bytes.as_slice(), password).await;
|
||||
}
|
||||
|
||||
/// Opens a native `.kspwallet` V2 binary file with VIEW capability.
|
||||
pub async fn open_wallet_view_file_v2(
|
||||
source: impl std::convert::AsRef<std::path::Path>,
|
||||
password: crate::ViewPassword,
|
||||
) -> ksp_core_lib::Result<crate::WalletView> {
|
||||
let bytes = match read_wallet_file_async(source.as_ref().to_path_buf()).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return crate::open_wallet_view_v2(bytes.as_slice(), password).await;
|
||||
}
|
||||
|
||||
/// Opens a native `.kspwallet` V1 from `source` with VIEW capability.
|
||||
///
|
||||
/// The file is read through the bounded async persistence boundary before the normal strict parser, OWNER state-signature verification and VIEW KDF flow.
|
||||
@@ -56,6 +123,30 @@ pub async fn open_wallet_view_file_v1(
|
||||
return crate::open_wallet_view_v1(bytes.as_slice(), password).await;
|
||||
}
|
||||
|
||||
/// Opens a supported native `.kspwallet` file with OWNER capability after bounded V1/V2 detection.
|
||||
pub async fn open_wallet_owner_file(
|
||||
source: impl std::convert::AsRef<std::path::Path>,
|
||||
password: crate::OwnerPassword,
|
||||
) -> ksp_core_lib::Result<crate::WalletOwner> {
|
||||
let bytes = match read_wallet_file_async(source.as_ref().to_path_buf()).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return crate::open_wallet_owner(bytes.as_slice(), password).await;
|
||||
}
|
||||
|
||||
/// Opens a native `.kspwallet` V2 binary file with OWNER capability.
|
||||
pub async fn open_wallet_owner_file_v2(
|
||||
source: impl std::convert::AsRef<std::path::Path>,
|
||||
password: crate::OwnerPassword,
|
||||
) -> ksp_core_lib::Result<crate::WalletOwner> {
|
||||
let bytes = match read_wallet_file_async(source.as_ref().to_path_buf()).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return crate::open_wallet_owner_v2(bytes.as_slice(), password).await;
|
||||
}
|
||||
|
||||
/// Opens a native `.kspwallet` V1 from `source` with OWNER capability.
|
||||
///
|
||||
/// The file is read through the bounded async persistence boundary before the normal strict parser, OWNER state-signature verification and OWNER KDF flow.
|
||||
@@ -72,6 +163,24 @@ pub async fn open_wallet_owner_file_v1(
|
||||
return crate::open_wallet_owner_v1(bytes.as_slice(), password).await;
|
||||
}
|
||||
|
||||
/// Reads and verifies the locked projection of a supported native `.kspwallet` file after bounded V1/V2 detection.
|
||||
pub async fn inspect_locked_wallet_file(source: impl std::convert::AsRef<std::path::Path>) -> ksp_core_lib::Result<crate::LockedWalletInfo> {
|
||||
let bytes = match read_wallet_file_async(source.as_ref().to_path_buf()).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return crate::inspect_locked_wallet(bytes.as_slice());
|
||||
}
|
||||
|
||||
/// Reads and verifies the locked projection of a native `.kspwallet` V2 binary file without running a password KDF.
|
||||
pub async fn inspect_locked_wallet_file_v2(source: impl std::convert::AsRef<std::path::Path>) -> ksp_core_lib::Result<crate::LockedWalletInfo> {
|
||||
let bytes = match read_wallet_file_async(source.as_ref().to_path_buf()).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return crate::inspect_locked_wallet_v2(bytes.as_slice());
|
||||
}
|
||||
|
||||
/// Reads and verifies the locked projection of a native `.kspwallet` V1 from `source` without running a password KDF.
|
||||
pub async fn inspect_locked_wallet_file_v1(source: impl std::convert::AsRef<std::path::Path>) -> ksp_core_lib::Result<crate::LockedWalletInfo> {
|
||||
let source = source.as_ref().to_path_buf();
|
||||
@@ -88,6 +197,11 @@ pub(crate) async fn persist_new_wallet_content_v1(destination: std::path::PathBu
|
||||
return persist_new_wallet_async(destination, content).await;
|
||||
}
|
||||
|
||||
/// Persists one already serialized native Wallet document with no-clobber semantics.
|
||||
pub(crate) async fn persist_new_wallet_content(destination: std::path::PathBuf, content: std::vec::Vec<u8>) -> ksp_core_lib::Result<()> {
|
||||
return persist_new_wallet_async(destination, content).await;
|
||||
}
|
||||
|
||||
/// Replaces wallet file v1.
|
||||
pub(crate) async fn replace_wallet_file_v1(
|
||||
destination: std::path::PathBuf,
|
||||
@@ -103,6 +217,21 @@ pub(crate) async fn replace_wallet_file_v1(
|
||||
};
|
||||
}
|
||||
|
||||
/// Replaces one authenticated V2 Wallet file only if the current V2 state still matches the caller's expected state.
|
||||
pub(crate) async fn replace_wallet_file_v2(
|
||||
destination: std::path::PathBuf,
|
||||
expected_current: crate::KspWalletEnvelopeV2,
|
||||
content: std::vec::Vec<u8>,
|
||||
) -> ksp_core_lib::Result<()> {
|
||||
let task = tokio::task::spawn_blocking(move || {
|
||||
return replace_wallet_file_v2_checked_blocking(destination.as_path(), &expected_current, content.as_slice());
|
||||
});
|
||||
return match task.await {
|
||||
std::result::Result::Ok(result) => result,
|
||||
std::result::Result::Err(error) => std::result::Result::Err(blocking_atomic_error("replace_task", error)),
|
||||
};
|
||||
}
|
||||
|
||||
async fn read_wallet_file_async(source: std::path::PathBuf) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
|
||||
let task = tokio::task::spawn_blocking(move || return read_wallet_file_blocking(source.as_path()));
|
||||
return match task.await {
|
||||
@@ -162,6 +291,18 @@ fn replace_wallet_file_checked_blocking(
|
||||
return replace_wallet_file_with_hook(destination, content, || return verify_expected_wallet_state(destination, expected_current));
|
||||
}
|
||||
|
||||
fn replace_wallet_file_v2_checked_blocking(
|
||||
destination: &std::path::Path,
|
||||
expected_current: &crate::KspWalletEnvelopeV2,
|
||||
content: &[u8],
|
||||
) -> ksp_core_lib::Result<()> {
|
||||
let current_check = verify_expected_wallet_state_v2(destination, expected_current);
|
||||
if let std::result::Result::Err(error) = current_check {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
return replace_wallet_file_with_hook(destination, content, || return verify_expected_wallet_state_v2(destination, expected_current));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn replace_wallet_file_blocking(destination: &std::path::Path, content: &[u8]) -> ksp_core_lib::Result<()> {
|
||||
return replace_wallet_file_with_hook(destination, content, || return std::result::Result::Ok(()));
|
||||
@@ -186,6 +327,24 @@ fn verify_expected_wallet_state(destination: &std::path::Path, expected_current:
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn verify_expected_wallet_state_v2(destination: &std::path::Path, expected_current: &crate::KspWalletEnvelopeV2) -> ksp_core_lib::Result<()> {
|
||||
let current_bytes = match read_wallet_file_blocking(destination) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let current = match crate::KspWalletEnvelopeV2::parse_binary(current_bytes.as_slice()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if let std::result::Result::Err(error) = crate::verify_state_signature_v2(¤t) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
if ¤t != expected_current {
|
||||
return std::result::Result::Err(state_conflict_error());
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn replace_wallet_file_with_hook<F>(destination: &std::path::Path, content: &[u8], before_publish: F) -> ksp_core_lib::Result<()>
|
||||
where
|
||||
F: std::ops::FnOnce() -> ksp_core_lib::Result<()>,
|
||||
|
||||
233
crates/ksp-wallet-lib/src/runtime.rs
Normal file
233
crates/ksp-wallet-lib/src/runtime.rs
Normal file
@@ -0,0 +1,233 @@
|
||||
// file: crates/ksp-wallet-lib/src/runtime.rs
|
||||
// version: 1
|
||||
|
||||
//! Version-neutral unlocked Wallet state dispatch used by stable OWNER/VIEW handles.
|
||||
|
||||
/// One staged native envelope produced by an authenticated administration operation.
|
||||
pub(crate) enum StagedEnvelope {
|
||||
V1(crate::KspWalletEnvelopeV1),
|
||||
V2(crate::KspWalletEnvelopeV2),
|
||||
}
|
||||
|
||||
/// Version-neutral OWNER runtime state.
|
||||
pub(crate) enum OwnerState {
|
||||
V1(crate::OwnerStateV1),
|
||||
V2(crate::OwnerStateV2),
|
||||
}
|
||||
|
||||
impl OwnerState {
|
||||
/// Returns the native format version owned by this OWNER state.
|
||||
pub(crate) const fn format_version(&self) -> u32 {
|
||||
return match self {
|
||||
Self::V1(_) => crate::KSPWALLET_FORMAT_VERSION_V1,
|
||||
Self::V2(_) => crate::KSPWALLET_FORMAT_VERSION_V2,
|
||||
};
|
||||
}
|
||||
|
||||
/// Exports the Solana identity through the requested external transfer format.
|
||||
pub(crate) fn export_transfer(&self, format: crate::WalletTransferFormat) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
|
||||
return match self {
|
||||
Self::V1(state) => state.export_transfer(format),
|
||||
Self::V2(state) => state.export_transfer(format),
|
||||
};
|
||||
}
|
||||
|
||||
/// Signs one message with the OWNER-authorized Solana identity.
|
||||
pub(crate) fn sign_message(&self, message: &[u8]) -> ksp_core_lib::Result<[u8; crate::KSPWALLET_SOLANA_SIGNATURE_BYTES]> {
|
||||
return match self {
|
||||
Self::V1(state) => state.sign_message(message),
|
||||
Self::V2(state) => state.sign_message(message),
|
||||
};
|
||||
}
|
||||
|
||||
/// Stages an authenticated metadata replacement without publishing it.
|
||||
pub(crate) fn stage_metadata_payload(&self, payload: crate::MetadataPayloadV1) -> ksp_core_lib::Result<(StagedEnvelope, crate::WalletInfo)> {
|
||||
return match self {
|
||||
Self::V1(state) => state.stage_metadata_payload(payload).map(|(envelope, info)| (StagedEnvelope::V1(envelope), info)),
|
||||
Self::V2(state) => state.stage_metadata_payload(payload).map(|(envelope, info)| (StagedEnvelope::V2(envelope), info)),
|
||||
};
|
||||
}
|
||||
|
||||
/// Stages an OWNER credential rotation in the state native format.
|
||||
pub(crate) async fn stage_owner_password_rotation(&self, password: crate::OwnerPassword) -> ksp_core_lib::Result<StagedEnvelope> {
|
||||
return match self {
|
||||
Self::V1(state) => state.stage_owner_password_rotation(password).await.map(StagedEnvelope::V1),
|
||||
Self::V2(state) => state.stage_owner_password_rotation(password).await.map(StagedEnvelope::V2),
|
||||
};
|
||||
}
|
||||
|
||||
/// Stages a VIEW credential rotation in the state native format.
|
||||
pub(crate) async fn stage_view_password_rotation(&self, password: crate::ViewPassword) -> ksp_core_lib::Result<StagedEnvelope> {
|
||||
return match self {
|
||||
Self::V1(state) => state.stage_view_password_rotation(password).await.map(StagedEnvelope::V1),
|
||||
Self::V2(state) => state.stage_view_password_rotation(password).await.map(StagedEnvelope::V2),
|
||||
};
|
||||
}
|
||||
|
||||
/// Stages strong VIEW disable while retaining OWNER authority.
|
||||
pub(crate) fn stage_disable_view(&self) -> ksp_core_lib::Result<(StagedEnvelope, crate::SecretKeyV1)> {
|
||||
return match self {
|
||||
Self::V1(state) => state.stage_disable_view().map(|(envelope, key)| (StagedEnvelope::V1(envelope), key)),
|
||||
Self::V2(state) => state.stage_disable_view().map(|(envelope, key)| (StagedEnvelope::V2(envelope), key)),
|
||||
};
|
||||
}
|
||||
|
||||
/// Stages strong VIEW recreation under a new credential.
|
||||
pub(crate) async fn stage_recreate_view(&self, password: crate::ViewPassword) -> ksp_core_lib::Result<(StagedEnvelope, crate::SecretKeyV1)> {
|
||||
return match self {
|
||||
Self::V1(state) => state.stage_recreate_view(password).await.map(|(envelope, key)| (StagedEnvelope::V1(envelope), key)),
|
||||
Self::V2(state) => state.stage_recreate_view(password).await.map(|(envelope, key)| (StagedEnvelope::V2(envelope), key)),
|
||||
};
|
||||
}
|
||||
|
||||
/// Atomically publishes a staged envelope after format-specific state-conflict verification.
|
||||
pub(crate) async fn persist_staged(&self, destination: std::path::PathBuf, envelope: &StagedEnvelope) -> ksp_core_lib::Result<()> {
|
||||
return match (self, envelope) {
|
||||
(Self::V1(state), StagedEnvelope::V1(staged)) => {
|
||||
let serialized = match staged.to_json_bytes() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
crate::replace_wallet_file_v1(destination, state.envelope().clone(), serialized).await
|
||||
},
|
||||
(Self::V2(state), StagedEnvelope::V2(staged)) => {
|
||||
let serialized = match staged.to_binary_bytes() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
crate::replace_wallet_file_v2(destination, state.envelope().clone(), serialized).await
|
||||
},
|
||||
_ => std::result::Result::Err(version_state_error()),
|
||||
};
|
||||
}
|
||||
|
||||
/// Replaces the in-memory authenticated envelope after successful persistence.
|
||||
pub(crate) fn apply_envelope(&mut self, envelope: StagedEnvelope) -> ksp_core_lib::Result<()> {
|
||||
return match (self, envelope) {
|
||||
(Self::V1(state), StagedEnvelope::V1(value)) => {
|
||||
state.apply_envelope(value);
|
||||
std::result::Result::Ok(())
|
||||
},
|
||||
(Self::V2(state), StagedEnvelope::V2(value)) => {
|
||||
state.apply_envelope(value);
|
||||
std::result::Result::Ok(())
|
||||
},
|
||||
_ => std::result::Result::Err(version_state_error()),
|
||||
};
|
||||
}
|
||||
|
||||
/// Replaces the envelope and metadata key after a strong VIEW administration operation.
|
||||
pub(crate) fn apply_strong_view_state(&mut self, envelope: StagedEnvelope, metadata_key: crate::SecretKeyV1) -> ksp_core_lib::Result<()> {
|
||||
return match (self, envelope) {
|
||||
(Self::V1(state), StagedEnvelope::V1(value)) => {
|
||||
state.apply_strong_view_state(value, metadata_key);
|
||||
std::result::Result::Ok(())
|
||||
},
|
||||
(Self::V2(state), StagedEnvelope::V2(value)) => {
|
||||
state.apply_strong_view_state(value, metadata_key);
|
||||
std::result::Result::Ok(())
|
||||
},
|
||||
_ => std::result::Result::Err(version_state_error()),
|
||||
};
|
||||
}
|
||||
|
||||
/// Serializes the authenticated state in its native V1 or V2 wire format.
|
||||
pub(crate) fn native_bytes(&self) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
|
||||
return match self {
|
||||
Self::V1(state) => state.envelope().to_json_bytes(),
|
||||
Self::V2(state) => state.envelope().to_binary_bytes(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Serializes only V1 state as JSON and rejects V2 instead of converting formats.
|
||||
pub(crate) fn json_bytes_v1(&self) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
|
||||
return match self {
|
||||
Self::V1(state) => state.envelope().to_json_bytes(),
|
||||
Self::V2(_) => std::result::Result::Err(v1_serialization_error()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Version-neutral VIEW runtime state.
|
||||
pub(crate) enum ViewState {
|
||||
V1(crate::ViewStateV1),
|
||||
V2(crate::ViewStateV2),
|
||||
}
|
||||
|
||||
impl ViewState {
|
||||
/// Returns the native format version owned by this VIEW state.
|
||||
pub(crate) const fn format_version(&self) -> u32 {
|
||||
return match self {
|
||||
Self::V1(_) => crate::KSPWALLET_FORMAT_VERSION_V1,
|
||||
Self::V2(_) => crate::KSPWALLET_FORMAT_VERSION_V2,
|
||||
};
|
||||
}
|
||||
|
||||
/// Stages a VIEW credential rotation in the state native format.
|
||||
pub(crate) async fn stage_view_password_rotation(&self, password: crate::ViewPassword) -> ksp_core_lib::Result<StagedEnvelope> {
|
||||
return match self {
|
||||
Self::V1(state) => state.stage_view_password_rotation(password).await.map(StagedEnvelope::V1),
|
||||
Self::V2(state) => state.stage_view_password_rotation(password).await.map(StagedEnvelope::V2),
|
||||
};
|
||||
}
|
||||
|
||||
/// Atomically publishes a staged envelope after format-specific state-conflict verification.
|
||||
pub(crate) async fn persist_staged(&self, destination: std::path::PathBuf, envelope: &StagedEnvelope) -> ksp_core_lib::Result<()> {
|
||||
return match (self, envelope) {
|
||||
(Self::V1(state), StagedEnvelope::V1(staged)) => {
|
||||
let serialized = match staged.to_json_bytes() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
crate::replace_wallet_file_v1(destination, state.envelope().clone(), serialized).await
|
||||
},
|
||||
(Self::V2(state), StagedEnvelope::V2(staged)) => {
|
||||
let serialized = match staged.to_binary_bytes() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
crate::replace_wallet_file_v2(destination, state.envelope().clone(), serialized).await
|
||||
},
|
||||
_ => std::result::Result::Err(version_state_error()),
|
||||
};
|
||||
}
|
||||
|
||||
/// Replaces the in-memory authenticated envelope after successful persistence.
|
||||
pub(crate) fn apply_envelope(&mut self, envelope: StagedEnvelope) -> ksp_core_lib::Result<()> {
|
||||
return match (self, envelope) {
|
||||
(Self::V1(state), StagedEnvelope::V1(value)) => {
|
||||
state.apply_envelope(value);
|
||||
std::result::Result::Ok(())
|
||||
},
|
||||
(Self::V2(state), StagedEnvelope::V2(value)) => {
|
||||
state.apply_envelope(value);
|
||||
std::result::Result::Ok(())
|
||||
},
|
||||
_ => std::result::Result::Err(version_state_error()),
|
||||
};
|
||||
}
|
||||
|
||||
/// Serializes the authenticated state in its native V1 or V2 wire format.
|
||||
pub(crate) fn native_bytes(&self) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
|
||||
return match self {
|
||||
Self::V1(state) => state.envelope().to_json_bytes(),
|
||||
Self::V2(state) => state.envelope().to_binary_bytes(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Serializes only V1 state as JSON and rejects V2 instead of converting formats.
|
||||
pub(crate) fn json_bytes_v1(&self) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
|
||||
return match self {
|
||||
Self::V1(state) => state.envelope().to_json_bytes(),
|
||||
Self::V2(_) => std::result::Result::Err(v1_serialization_error()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn version_state_error() -> ksp_core_lib::Error {
|
||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_FORMAT_INVALID, "Wallet runtime state and staged format are inconsistent");
|
||||
}
|
||||
|
||||
fn v1_serialization_error() -> ksp_core_lib::Error {
|
||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_FORMAT_INVALID, "Wallet is not a V1 JSON document");
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-wallet-lib/src/transfer.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! Explicit OWNER-only Solana keypair import/export adapters.
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::io::Write; // rust-rules: trait-import
|
||||
use std::os::unix::fs::PermissionsExt; // rust-rules: trait-import
|
||||
use zeroize::Zeroize; // rust-rules: trait-import
|
||||
|
||||
/// Explicit secret-transfer formats supported by Wallet `0.2.5`.
|
||||
/// Explicit secret-transfer formats supported by Wallet.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum WalletTransferFormat {
|
||||
@@ -106,6 +106,55 @@ pub async fn inspect_wallet_transfer_file(
|
||||
return inspect_wallet_transfer(bytes.as_slice(), format);
|
||||
}
|
||||
|
||||
/// Imports one in-memory Solana keypair transfer payload into a new native Wallet using [`crate::DEFAULT_WALLET_FORMAT`].
|
||||
pub async fn import_wallet_transfer(
|
||||
destination: impl std::convert::AsRef<std::path::Path>,
|
||||
source: &[u8],
|
||||
format: WalletTransferFormat,
|
||||
owner_password: crate::OwnerPassword,
|
||||
view_password: std::option::Option<crate::ViewPassword>,
|
||||
metadata: crate::WalletCreateMetadata,
|
||||
) -> ksp_core_lib::Result<crate::WalletOwner> {
|
||||
return match crate::DEFAULT_WALLET_FORMAT {
|
||||
crate::WalletFormat::V1 => import_wallet_transfer_v1(destination, source, format, owner_password, view_password, metadata).await,
|
||||
crate::WalletFormat::V2 => import_wallet_transfer_v2(destination, source, format, owner_password, view_password, metadata).await,
|
||||
};
|
||||
}
|
||||
|
||||
/// Imports one in-memory Solana keypair transfer payload into a new native `.kspwallet` V2 binary file.
|
||||
pub async fn import_wallet_transfer_v2(
|
||||
destination: impl std::convert::AsRef<std::path::Path>,
|
||||
source: &[u8],
|
||||
format: WalletTransferFormat,
|
||||
owner_password: crate::OwnerPassword,
|
||||
view_password: std::option::Option<crate::ViewPassword>,
|
||||
metadata: crate::WalletCreateMetadata,
|
||||
) -> ksp_core_lib::Result<crate::WalletOwner> {
|
||||
let keypair = match decode_transfer_keypair(source, format) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let owner = match crate::create_wallet_v2_from_keypair(owner_password, view_password, metadata, keypair).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let serialized = match owner.to_native_bytes() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if let std::result::Result::Err(error) = crate::persist_new_wallet_content(destination.as_ref().to_path_buf(), serialized).await {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
operation = "wallet_import_transfer",
|
||||
format_version = crate::KSPWALLET_FORMAT_VERSION_V2,
|
||||
transfer_format = format.code(),
|
||||
"external Solana keypair imported into a new native wallet"
|
||||
);
|
||||
return std::result::Result::Ok(owner);
|
||||
}
|
||||
|
||||
/// Imports one in-memory Solana keypair transfer payload into a new native `.kspwallet` V1 file.
|
||||
///
|
||||
/// Import always creates a fresh native Wallet envelope around the validated immutable Solana keypair and publishes with the same no-clobber semantics as
|
||||
@@ -143,6 +192,38 @@ pub async fn import_wallet_transfer_v1(
|
||||
return std::result::Result::Ok(owner);
|
||||
}
|
||||
|
||||
/// Imports one bounded external transfer file into a new native Wallet using [`crate::DEFAULT_WALLET_FORMAT`].
|
||||
pub async fn import_wallet_transfer_file(
|
||||
destination: impl std::convert::AsRef<std::path::Path>,
|
||||
source: impl std::convert::AsRef<std::path::Path>,
|
||||
format: WalletTransferFormat,
|
||||
owner_password: crate::OwnerPassword,
|
||||
view_password: std::option::Option<crate::ViewPassword>,
|
||||
metadata: crate::WalletCreateMetadata,
|
||||
) -> ksp_core_lib::Result<crate::WalletOwner> {
|
||||
let bytes = match read_transfer_file_async(source.as_ref().to_path_buf(), format).await {
|
||||
std::result::Result::Ok(value) => zeroize::Zeroizing::new(value),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return import_wallet_transfer(destination, bytes.as_slice(), format, owner_password, view_password, metadata).await;
|
||||
}
|
||||
|
||||
/// Imports one bounded external transfer file into a new native `.kspwallet` V2 binary file.
|
||||
pub async fn import_wallet_transfer_file_v2(
|
||||
destination: impl std::convert::AsRef<std::path::Path>,
|
||||
source: impl std::convert::AsRef<std::path::Path>,
|
||||
format: WalletTransferFormat,
|
||||
owner_password: crate::OwnerPassword,
|
||||
view_password: std::option::Option<crate::ViewPassword>,
|
||||
metadata: crate::WalletCreateMetadata,
|
||||
) -> ksp_core_lib::Result<crate::WalletOwner> {
|
||||
let bytes = match read_transfer_file_async(source.as_ref().to_path_buf(), format).await {
|
||||
std::result::Result::Ok(value) => zeroize::Zeroizing::new(value),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return import_wallet_transfer_v2(destination, bytes.as_slice(), format, owner_password, view_password, metadata).await;
|
||||
}
|
||||
|
||||
/// Imports one bounded external transfer file into a new native `.kspwallet` V1 file.
|
||||
///
|
||||
/// The source file is never modified and the destination remains no-clobber.
|
||||
@@ -161,8 +242,8 @@ pub async fn import_wallet_transfer_file_v1(
|
||||
return import_wallet_transfer_v1(destination, bytes.as_slice(), format, owner_password, view_password, metadata).await;
|
||||
}
|
||||
|
||||
/// Executes the crate-internal write wallet transfer file v1 operation for the owning module.
|
||||
pub(crate) async fn write_wallet_transfer_file_v1(
|
||||
/// Writes one bounded OWNER export using no-clobber external transfer semantics.
|
||||
pub(crate) async fn write_wallet_transfer_file(
|
||||
destination: std::path::PathBuf,
|
||||
content: std::vec::Vec<u8>,
|
||||
format: WalletTransferFormat,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-wallet-lib/src/view.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
/// Authorized VIEW capability handle.
|
||||
///
|
||||
@@ -7,15 +7,21 @@
|
||||
/// Solana secret, OWNER administration material or metadata-write authority.
|
||||
pub struct WalletView {
|
||||
info: crate::WalletInfo,
|
||||
state: crate::ViewStateV1,
|
||||
state: crate::ViewState,
|
||||
}
|
||||
|
||||
impl WalletView {
|
||||
/// Builds `WalletView` from unlocked.
|
||||
pub(crate) fn from_unlocked(info: crate::WalletInfo, state: crate::ViewStateV1) -> Self {
|
||||
pub(crate) fn from_unlocked(info: crate::WalletInfo, state: crate::ViewState) -> Self {
|
||||
return Self { info, state };
|
||||
}
|
||||
|
||||
/// Returns the native Wallet format version backing this authorized handle.
|
||||
#[must_use]
|
||||
pub const fn format_version(&self) -> u32 {
|
||||
return self.info.format_version();
|
||||
}
|
||||
|
||||
/// Returns the authorization capability represented by this handle.
|
||||
#[must_use]
|
||||
pub const fn capability(&self) -> crate::WalletCapability {
|
||||
@@ -65,15 +71,13 @@ impl WalletView {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let serialized = match envelope.to_json_bytes() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let persist_result = crate::replace_wallet_file_v1(destination.as_ref().to_path_buf(), self.state.envelope().clone(), serialized).await;
|
||||
let persist_result = self.state.persist_staged(destination.as_ref().to_path_buf(), &envelope).await;
|
||||
if let std::result::Result::Err(error) = persist_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
self.state.apply_envelope(envelope);
|
||||
if let std::result::Result::Err(error) = self.state.apply_envelope(envelope) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
operation = "wallet_rotate_view_password",
|
||||
@@ -83,9 +87,16 @@ impl WalletView {
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
/// Serializes the unchanged locked `.kspwallet` V1 document without exposing the metadata content key.
|
||||
/// Serializes the unchanged locked native Wallet in its current V1 or V2 wire format.
|
||||
pub fn to_native_bytes(&self) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
|
||||
return self.state.native_bytes();
|
||||
}
|
||||
|
||||
/// Serializes a V1 handle as its historical JSON document.
|
||||
///
|
||||
/// V2 handles return a format error instead of being converted implicitly.
|
||||
pub fn to_json_bytes(&self) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
|
||||
return self.state.envelope().to_json_bytes();
|
||||
return self.state.json_bytes_v1();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-wallet-lib/src/wallet.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! In-memory native Wallet V1 create/open orchestration.
|
||||
|
||||
@@ -143,7 +143,7 @@ impl OwnerStateV1 {
|
||||
if let std::result::Result::Err(error) = verify_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
return std::result::Result::Ok((envelope, payload.into_info(crate::WalletCapability::Owner)));
|
||||
return std::result::Result::Ok((envelope, payload.into_info(crate::KSPWALLET_FORMAT_VERSION_V1, crate::WalletCapability::Owner)));
|
||||
}
|
||||
|
||||
/// Executes the crate-internal stage owner password rotation operation for `OwnerStateV1`.
|
||||
@@ -497,7 +497,7 @@ pub async fn open_wallet_view_v1(source: &[u8], password: crate::ViewPassword) -
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let info = metadata_payload.into_info(crate::WalletCapability::View);
|
||||
let info = metadata_payload.into_info(crate::KSPWALLET_FORMAT_VERSION_V1, crate::WalletCapability::View);
|
||||
let state = ViewStateV1::new(envelope, metadata_key);
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
@@ -506,7 +506,7 @@ pub async fn open_wallet_view_v1(source: &[u8], password: crate::ViewPassword) -
|
||||
capability = "view",
|
||||
"native wallet VIEW capability opened"
|
||||
);
|
||||
return std::result::Result::Ok(crate::WalletView::from_unlocked(info, state));
|
||||
return std::result::Result::Ok(crate::WalletView::from_unlocked(info, crate::ViewState::V1(state)));
|
||||
}
|
||||
|
||||
/// Opens the OWNER capability from a native `.kspwallet` V1 JSON document.
|
||||
@@ -602,7 +602,7 @@ pub async fn open_wallet_owner_v1(source: &[u8], password: crate::OwnerPassword)
|
||||
if &secret_pubkey != metadata_payload.pubkey() {
|
||||
return std::result::Result::Err(key_material_error());
|
||||
}
|
||||
let info = metadata_payload.into_info(crate::WalletCapability::Owner);
|
||||
let info = metadata_payload.into_info(crate::KSPWALLET_FORMAT_VERSION_V1, crate::WalletCapability::Owner);
|
||||
let state = OwnerStateV1::new(envelope, owner_root, metadata_key, secret_key, admin_signing_key, solana_keypair);
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
@@ -611,7 +611,7 @@ pub async fn open_wallet_owner_v1(source: &[u8], password: crate::OwnerPassword)
|
||||
capability = "owner",
|
||||
"native wallet OWNER capability opened"
|
||||
);
|
||||
return std::result::Result::Ok(crate::WalletOwner::from_unlocked(info, state));
|
||||
return std::result::Result::Ok(crate::WalletOwner::from_unlocked(info, crate::OwnerState::V1(state)));
|
||||
}
|
||||
|
||||
/// Parses and verifies the OWNER-authenticated locked state without unlocking metadata or secret material.
|
||||
@@ -624,7 +624,7 @@ pub fn inspect_locked_wallet_v1(source: &[u8]) -> ksp_core_lib::Result<crate::Lo
|
||||
if let std::result::Result::Err(error) = verify_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
return std::result::Result::Ok(crate::LockedWalletInfo::new(envelope.view_descriptor().enabled()));
|
||||
return std::result::Result::Ok(crate::LockedWalletInfo::new(crate::KSPWALLET_FORMAT_VERSION_V1, envelope.view_descriptor().enabled()));
|
||||
}
|
||||
|
||||
/// Executes the crate-internal create wallet v1 from keypair operation for the owning module.
|
||||
@@ -821,7 +821,7 @@ pub(crate) async fn create_wallet_v1_from_keypair(
|
||||
if let std::result::Result::Err(error) = verify_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let info = metadata_payload.into_info(crate::WalletCapability::Owner);
|
||||
let info = metadata_payload.into_info(crate::KSPWALLET_FORMAT_VERSION_V1, crate::WalletCapability::Owner);
|
||||
let state = OwnerStateV1::new(envelope, owner_root, metadata_key, secret_key, admin_signing_key, solana_keypair);
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
@@ -830,7 +830,7 @@ pub(crate) async fn create_wallet_v1_from_keypair(
|
||||
view_enabled = state.envelope().view_descriptor().enabled(),
|
||||
"native wallet created in memory"
|
||||
);
|
||||
return std::result::Result::Ok(crate::WalletOwner::from_unlocked(info, state));
|
||||
return std::result::Result::Ok(crate::WalletOwner::from_unlocked(info, crate::OwnerState::V1(state)));
|
||||
}
|
||||
|
||||
/// Verifies state signature.
|
||||
|
||||
1147
crates/ksp-wallet-lib/src/wallet_v2.rs
Normal file
1147
crates/ksp-wallet-lib/src/wallet_v2.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-wallet-lib/src/wire_v2.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Strict native `.kspwallet` V2 binary wire envelope.
|
||||
|
||||
@@ -120,6 +120,17 @@ impl WalletKdfParametersV2 {
|
||||
return Self { algorithm: WalletKdfAlgorithmV2::Argon2id, version, memory_kib, iterations, parallelism, salt };
|
||||
}
|
||||
|
||||
/// Creates one V2 KDF value using the release-calibrated default creation profile.
|
||||
pub(crate) fn new_creation(salt: std::vec::Vec<u8>) -> Self {
|
||||
return Self::new_internal(
|
||||
crate::KSPWALLET_V2_ARGON2_VERSION,
|
||||
crate::KSPWALLET_V2_DEFAULT_ARGON2_MEMORY_KIB,
|
||||
crate::KSPWALLET_V2_DEFAULT_ARGON2_ITERATIONS,
|
||||
crate::KSPWALLET_V2_DEFAULT_ARGON2_PARALLELISM,
|
||||
salt,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the KDF algorithm.
|
||||
#[must_use]
|
||||
pub const fn algorithm(&self) -> WalletKdfAlgorithmV2 {
|
||||
|
||||
Reference in New Issue
Block a user