v0.2.5-pre.010.fix.001
This commit is contained in:
@@ -1,90 +1,89 @@
|
||||
// file: crates/ksp-wallet-lib/src/constants.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
//! Wallet-owned constants.
|
||||
|
||||
/// Exact magic string required by every native `.kspwallet` document.
|
||||
pub const KSPWALLET_MAGIC: &str = "KSPWALLET";
|
||||
/// Native Wallet format version implemented by the V1 codec.
|
||||
pub const KSPWALLET_FORMAT_VERSION_V1: u32 = 1;
|
||||
/// Exact magic string required by every native `.kspwallet` document.
|
||||
pub const KSPWALLET_MAGIC: &str = "KSPWALLET";
|
||||
/// Maximum accepted `.kspwallet` document size before JSON parsing.
|
||||
pub const KSPWALLET_MAX_FILE_BYTES: usize = 1024 * 1024;
|
||||
/// Maximum accepted Solana CLI keypair JSON transfer size before parsing.
|
||||
pub const KSPWALLET_TRANSFER_MAX_SOLANA_CLI_JSON_BYTES: usize = 1024;
|
||||
/// Maximum accepted canonical Base58 keypair transfer size before decoding.
|
||||
pub const KSPWALLET_TRANSFER_MAX_BASE58_BYTES: usize = 128;
|
||||
/// Maximum protected alias size in UTF-8 bytes for metadata V1.
|
||||
pub const KSPWALLET_V1_MAX_ALIAS_BYTES: usize = 256;
|
||||
/// Maximum number of protected notes in metadata V1.
|
||||
pub const KSPWALLET_V1_MAX_NOTES: usize = 64;
|
||||
/// Maximum protected note text size in UTF-8 bytes for metadata V1.
|
||||
pub const KSPWALLET_V1_MAX_NOTE_TEXT_BYTES: usize = 8 * 1024;
|
||||
/// Maximum metadata plaintext size before V1 encryption.
|
||||
pub const KSPWALLET_V1_MAX_METADATA_PLAINTEXT_BYTES: usize = 64 * 1024;
|
||||
/// Maximum password size in exact UTF-8 input bytes.
|
||||
pub const KSPWALLET_V1_MAX_PASSWORD_BYTES: usize = 1024;
|
||||
/// Byte length of every V1 key-slot identifier.
|
||||
pub const KSPWALLET_V1_SLOT_ID_BYTES: usize = 16;
|
||||
/// Byte length of every protected metadata note identifier.
|
||||
pub const KSPWALLET_V1_NOTE_ID_BYTES: usize = 16;
|
||||
/// Byte length of an Ed25519 public key used as Wallet format authority.
|
||||
pub const KSPWALLET_V1_ED25519_PUBLIC_KEY_BYTES: usize = 32;
|
||||
/// Byte length of an Ed25519 detached state signature.
|
||||
pub const KSPWALLET_V1_ED25519_SIGNATURE_BYTES: usize = 64;
|
||||
/// Byte length of every Solana Ed25519 message signature returned by Wallet OWNER.
|
||||
pub const KSPWALLET_SOLANA_SIGNATURE_BYTES: usize = 64;
|
||||
/// Byte length of an XChaCha20-Poly1305 nonce.
|
||||
pub const KSPWALLET_V1_XCHACHA_NONCE_BYTES: usize = 24;
|
||||
/// Maximum accepted canonical Base58 keypair transfer size before decoding.
|
||||
pub const KSPWALLET_TRANSFER_MAX_BASE58_BYTES: usize = 128;
|
||||
/// Maximum accepted Solana CLI keypair JSON transfer size before parsing.
|
||||
pub const KSPWALLET_TRANSFER_MAX_SOLANA_CLI_JSON_BYTES: usize = 1024;
|
||||
/// Byte length of the Poly1305 authentication tag appended to each ciphertext.
|
||||
pub const KSPWALLET_V1_AEAD_TAG_BYTES: usize = 16;
|
||||
/// Argon2 version serialized by V1 key slots.
|
||||
pub const KSPWALLET_V1_ARGON2_VERSION: u32 = 19;
|
||||
/// Default Argon2id memory cost for newly created V1 key slots, calibrated on the 2026-08-19 operator benchmark.
|
||||
pub const KSPWALLET_V1_DEFAULT_ARGON2_MEMORY_KIB: u32 = 65_536;
|
||||
/// Default Argon2id iteration count for newly created V1 key slots.
|
||||
pub const KSPWALLET_V1_DEFAULT_ARGON2_ITERATIONS: u32 = 3;
|
||||
/// Default Argon2id memory cost for newly created V1 key slots, calibrated on the 2026-08-19 operator benchmark.
|
||||
pub const KSPWALLET_V1_DEFAULT_ARGON2_MEMORY_KIB: u32 = 65_536;
|
||||
/// Default Argon2id parallelism for newly created V1 key slots.
|
||||
pub const KSPWALLET_V1_DEFAULT_ARGON2_PARALLELISM: u32 = 1;
|
||||
/// Salt size generated independently for every newly created V1 key slot.
|
||||
pub const KSPWALLET_V1_DEFAULT_KDF_SALT_BYTES: usize = 32;
|
||||
/// Exact plaintext size of the V1 OWNER-control compartment.
|
||||
pub const KSPWALLET_V1_OWNER_CONTROL_PLAINTEXT_BYTES: usize = 96;
|
||||
/// Exact plaintext size of the V1 Solana secret compartment.
|
||||
pub const KSPWALLET_V1_SECRET_PLAINTEXT_BYTES: usize = 64;
|
||||
/// Minimum accepted Argon2 salt size in bytes.
|
||||
pub const KSPWALLET_V1_MIN_KDF_SALT_BYTES: usize = 16;
|
||||
/// Maximum accepted Argon2 salt size in bytes.
|
||||
pub const KSPWALLET_V1_MAX_KDF_SALT_BYTES: usize = 64;
|
||||
/// Structural V1 ceiling for serialized Argon2 memory cost; creation defaults are benchmarked separately.
|
||||
pub const KSPWALLET_V1_MAX_ARGON2_MEMORY_KIB: u32 = 1024 * 1024;
|
||||
/// Byte length of an Ed25519 public key used as Wallet format authority.
|
||||
pub const KSPWALLET_V1_ED25519_PUBLIC_KEY_BYTES: usize = 32;
|
||||
/// Byte length of an Ed25519 detached state signature.
|
||||
pub const KSPWALLET_V1_ED25519_SIGNATURE_BYTES: usize = 64;
|
||||
/// Initial protected payload version used independently by control, metadata and secret compartments.
|
||||
pub const KSPWALLET_V1_INITIAL_PAYLOAD_VERSION: u32 = 1;
|
||||
/// Maximum protected alias size in UTF-8 bytes for metadata V1.
|
||||
pub const KSPWALLET_V1_MAX_ALIAS_BYTES: usize = 256;
|
||||
/// Structural V1 ceiling for serialized Argon2 iteration cost; creation defaults are benchmarked separately.
|
||||
pub const KSPWALLET_V1_MAX_ARGON2_ITERATIONS: u32 = 64;
|
||||
/// Structural V1 ceiling for serialized Argon2 memory cost; creation defaults are benchmarked separately.
|
||||
pub const KSPWALLET_V1_MAX_ARGON2_MEMORY_KIB: u32 = 1024 * 1024;
|
||||
/// Structural V1 ceiling for serialized Argon2 parallelism; creation defaults are benchmarked separately.
|
||||
pub const KSPWALLET_V1_MAX_ARGON2_PARALLELISM: u32 = 64;
|
||||
/// Maximum accepted Argon2 salt size in bytes.
|
||||
pub const KSPWALLET_V1_MAX_KDF_SALT_BYTES: usize = 64;
|
||||
/// Maximum number of key slots understood by format V1: exactly one OWNER plus optional VIEW.
|
||||
pub const KSPWALLET_V1_MAX_KEY_SLOTS: usize = 2;
|
||||
/// Maximum ciphertext size of one wrapped capability payload.
|
||||
pub const KSPWALLET_V1_MAX_KEY_WRAP_CIPHERTEXT_BYTES: usize = 4096;
|
||||
/// Maximum OWNER-control ciphertext size accepted by envelope V1.
|
||||
pub const KSPWALLET_V1_MAX_OWNER_CONTROL_CIPHERTEXT_BYTES: usize = 4096;
|
||||
/// Maximum metadata ciphertext size, including the AEAD tag over the bounded 64-KiB plaintext.
|
||||
pub const KSPWALLET_V1_MAX_METADATA_CIPHERTEXT_BYTES: usize = KSPWALLET_V1_MAX_METADATA_PLAINTEXT_BYTES + KSPWALLET_V1_AEAD_TAG_BYTES;
|
||||
/// Maximum metadata plaintext size before V1 encryption.
|
||||
pub const KSPWALLET_V1_MAX_METADATA_PLAINTEXT_BYTES: usize = 64 * 1024;
|
||||
/// Maximum number of protected notes in metadata V1.
|
||||
pub const KSPWALLET_V1_MAX_NOTES: usize = 64;
|
||||
/// Maximum protected note text size in UTF-8 bytes for metadata V1.
|
||||
pub const KSPWALLET_V1_MAX_NOTE_TEXT_BYTES: usize = 8 * 1024;
|
||||
/// Maximum OWNER-control ciphertext size accepted by envelope V1.
|
||||
pub const KSPWALLET_V1_MAX_OWNER_CONTROL_CIPHERTEXT_BYTES: usize = 4096;
|
||||
/// Maximum password size in exact UTF-8 input bytes.
|
||||
pub const KSPWALLET_V1_MAX_PASSWORD_BYTES: usize = 1024;
|
||||
/// Maximum OWNER-only secret ciphertext size accepted by envelope V1.
|
||||
pub const KSPWALLET_V1_MAX_SECRET_CIPHERTEXT_BYTES: usize = 4096;
|
||||
/// Initial protected payload version used independently by control, metadata and secret compartments.
|
||||
pub const KSPWALLET_V1_INITIAL_PAYLOAD_VERSION: u32 = 1;
|
||||
/// Domain separator for the OWNER state-signature transcript.
|
||||
pub const KSPWALLET_V1_STATE_TRANSCRIPT_DOMAIN: &[u8] = b"KSPWALLET-V1-STATE";
|
||||
/// Domain separator for OWNER key-slot wrapping AAD.
|
||||
pub const KSPWALLET_V1_OWNER_SLOT_AAD_DOMAIN: &[u8] = b"KSPWALLET-V1-AAD-OWNER-SLOT";
|
||||
/// Domain separator for VIEW key-slot wrapping AAD.
|
||||
pub const KSPWALLET_V1_VIEW_SLOT_AAD_DOMAIN: &[u8] = b"KSPWALLET-V1-AAD-VIEW-SLOT";
|
||||
/// Domain separator for OWNER-control compartment AAD.
|
||||
pub const KSPWALLET_V1_OWNER_CONTROL_AAD_DOMAIN: &[u8] = b"KSPWALLET-V1-AAD-OWNER-CONTROL";
|
||||
/// Domain separator for metadata compartment AAD.
|
||||
pub const KSPWALLET_V1_METADATA_AAD_DOMAIN: &[u8] = b"KSPWALLET-V1-AAD-METADATA";
|
||||
/// Minimum accepted Argon2 salt size in bytes.
|
||||
pub const KSPWALLET_V1_MIN_KDF_SALT_BYTES: usize = 16;
|
||||
/// Byte length of every protected metadata note identifier.
|
||||
pub const KSPWALLET_V1_NOTE_ID_BYTES: usize = 16;
|
||||
/// Domain separator for OWNER-control compartment AAD.
|
||||
pub const KSPWALLET_V1_OWNER_CONTROL_AAD_DOMAIN: &[u8] = b"KSPWALLET-V1-AAD-OWNER-CONTROL";
|
||||
/// Exact plaintext size of the V1 OWNER-control compartment.
|
||||
pub const KSPWALLET_V1_OWNER_CONTROL_PLAINTEXT_BYTES: usize = 96;
|
||||
/// Domain separator for OWNER key-slot wrapping AAD.
|
||||
pub const KSPWALLET_V1_OWNER_SLOT_AAD_DOMAIN: &[u8] = b"KSPWALLET-V1-AAD-OWNER-SLOT";
|
||||
/// Domain separator for OWNER-only secret compartment AAD.
|
||||
pub const KSPWALLET_V1_SECRET_AAD_DOMAIN: &[u8] = b"KSPWALLET-V1-AAD-SECRET";
|
||||
|
||||
/// Exact plaintext size of the V1 Solana secret compartment.
|
||||
pub const KSPWALLET_V1_SECRET_PLAINTEXT_BYTES: usize = 64;
|
||||
/// Byte length of every V1 key-slot identifier.
|
||||
pub const KSPWALLET_V1_SLOT_ID_BYTES: usize = 16;
|
||||
/// Domain separator for the OWNER state-signature transcript.
|
||||
pub const KSPWALLET_V1_STATE_TRANSCRIPT_DOMAIN: &[u8] = b"KSPWALLET-V1-STATE";
|
||||
/// Domain separator for VIEW key-slot wrapping AAD.
|
||||
pub const KSPWALLET_V1_VIEW_SLOT_AAD_DOMAIN: &[u8] = b"KSPWALLET-V1-AAD-VIEW-SLOT";
|
||||
/// Byte length of an XChaCha20-Poly1305 nonce.
|
||||
pub const KSPWALLET_V1_XCHACHA_NONCE_BYTES: usize = 24;
|
||||
/// Owning tracing target for events emitted by the Wallet crate.
|
||||
pub(crate) const TRACING_TARGET: &str = "ksp-wallet-lib";
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// file: crates/ksp-wallet-lib/src/crypto.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! In-memory cryptographic primitives for native `.kspwallet` V1.
|
||||
|
||||
use chacha20poly1305::KeyInit; // rust-rules: derive-import
|
||||
use chacha20poly1305::aead::Aead; // rust-rules: derive-import
|
||||
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.
|
||||
pub(crate) const SECRET_KEY_BYTES: usize = 32;
|
||||
@@ -165,7 +165,6 @@ fn derive_argon2id(password: &[u8], salt: &[u8], memory_kib: u32, iterations: u3
|
||||
{
|
||||
return std::result::Result::Err(crypto_parameter_error());
|
||||
}
|
||||
|
||||
let params_result = argon2::Params::new(memory_kib, iterations, parallelism, std::option::Option::Some(SECRET_KEY_BYTES));
|
||||
let params = match params_result {
|
||||
std::result::Result::Ok(params) => params,
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
// file: crates/ksp-wallet-lib/src/error.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
/// Error code used when an atomic Wallet persistence operation cannot publish a valid replacement.
|
||||
pub const ERROR_CODE_ATOMIC_PERSISTENCE_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "atomic_persistence_failed");
|
||||
/// Error code used when an authenticated Wallet structure cannot be verified.
|
||||
pub const ERROR_CODE_AUTHENTICATION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "authentication_failed");
|
||||
/// Error code used when an operation requires a capability that the caller does not own.
|
||||
pub const ERROR_CODE_CAPABILITY_INSUFFICIENT: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "capability_insufficient");
|
||||
/// Error code used when an internal blocking cryptographic operation cannot complete.
|
||||
pub const ERROR_CODE_CRYPTO_OPERATION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "crypto_operation_failed");
|
||||
/// Error code used when serialized cryptographic parameters are invalid or unsupported.
|
||||
pub const ERROR_CODE_CRYPTO_PARAMETERS_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "crypto_parameters_invalid");
|
||||
/// Error code used when a no-clobber create or import destination already exists.
|
||||
pub const ERROR_CODE_DESTINATION_EXISTS: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "destination_exists");
|
||||
/// Error code used when a native Wallet structure is invalid.
|
||||
pub const ERROR_CODE_FORMAT_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "format_invalid");
|
||||
/// Error code used when a native Wallet format version is unsupported.
|
||||
pub const ERROR_CODE_FORMAT_VERSION_UNSUPPORTED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "format_version_unsupported");
|
||||
/// Error code used when serialized cryptographic parameters are invalid or unsupported.
|
||||
pub const ERROR_CODE_CRYPTO_PARAMETERS_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "crypto_parameters_invalid");
|
||||
/// Error code used when the operating-system cryptographic random source cannot provide bytes.
|
||||
pub const ERROR_CODE_RANDOMNESS_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "randomness_failed");
|
||||
/// Error code used when an authenticated Wallet structure cannot be verified.
|
||||
pub const ERROR_CODE_AUTHENTICATION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "authentication_failed");
|
||||
/// Error code used when an internal blocking cryptographic operation cannot complete.
|
||||
pub const ERROR_CODE_CRYPTO_OPERATION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "crypto_operation_failed");
|
||||
/// Error code used when a VIEW unlock attempt fails without exposing a finer cryptographic oracle.
|
||||
pub const ERROR_CODE_VIEW_UNLOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "view_unlock_failed");
|
||||
/// Error code used when an OWNER unlock attempt fails without exposing a finer cryptographic oracle.
|
||||
pub const ERROR_CODE_OWNER_UNLOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "owner_unlock_failed");
|
||||
/// Error code used when an operation requires a capability that the caller does not own.
|
||||
pub const ERROR_CODE_CAPABILITY_INSUFFICIENT: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "capability_insufficient");
|
||||
/// Error code used when Wallet filesystem I/O fails.
|
||||
pub const ERROR_CODE_IO_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "io_failed");
|
||||
/// Error code used when a no-clobber create or import destination already exists.
|
||||
pub const ERROR_CODE_DESTINATION_EXISTS: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "destination_exists");
|
||||
/// Error code used when an atomic Wallet persistence operation cannot publish a valid replacement.
|
||||
pub const ERROR_CODE_ATOMIC_PERSISTENCE_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "atomic_persistence_failed");
|
||||
/// Error code used when an import/export transfer format is unsupported.
|
||||
pub const ERROR_CODE_TRANSFER_FORMAT_UNSUPPORTED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "transfer_format_unsupported");
|
||||
/// Error code used when imported or decoded key material is invalid.
|
||||
pub const ERROR_CODE_KEY_MATERIAL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "key_material_invalid");
|
||||
/// Error code used when a Wallet signing operation fails.
|
||||
pub const ERROR_CODE_SIGNATURE_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "signature_failed");
|
||||
/// Error code used when a protected Wallet note identifier is not present.
|
||||
pub const ERROR_CODE_NOTE_NOT_FOUND: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "note_not_found");
|
||||
/// Error code used when an OWNER unlock attempt fails without exposing a finer cryptographic oracle.
|
||||
pub const ERROR_CODE_OWNER_UNLOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "owner_unlock_failed");
|
||||
/// Error code used when the operating-system cryptographic random source cannot provide bytes.
|
||||
pub const ERROR_CODE_RANDOMNESS_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "randomness_failed");
|
||||
/// Error code used when a Wallet signing operation fails.
|
||||
pub const ERROR_CODE_SIGNATURE_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "signature_failed");
|
||||
/// Error code used when an administrative replacement targets a different or stale Wallet state.
|
||||
pub const ERROR_CODE_STATE_CONFLICT: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "state_conflict");
|
||||
/// Error code used when an import/export transfer format is unsupported.
|
||||
pub const ERROR_CODE_TRANSFER_FORMAT_UNSUPPORTED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "transfer_format_unsupported");
|
||||
/// Error code used when a VIEW unlock attempt fails without exposing a finer cryptographic oracle.
|
||||
pub const ERROR_CODE_VIEW_UNLOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "view_unlock_failed");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-wallet-lib/src/lib.rs
|
||||
// version: 10
|
||||
// version: 11
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -245,10 +245,32 @@ pub(crate) use self::crypto::random_nonce;
|
||||
pub(crate) use self::crypto::unwrap_key;
|
||||
/// Wraps one 32-byte content key with XChaCha20-Poly1305 and caller-provided domain-separated AAD.
|
||||
pub(crate) use self::crypto::wrap_key;
|
||||
/// Crate-internal `MetadataPayloadV1` state shared across the owning crate.
|
||||
pub(crate) use self::payload::MetadataPayloadV1;
|
||||
/// Decodes metadata payload.
|
||||
pub(crate) use self::payload::decode_metadata_payload;
|
||||
/// Decodes owner control payload.
|
||||
pub(crate) use self::payload::decode_owner_control_payload;
|
||||
/// Encodes initial metadata 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.
|
||||
pub(crate) use self::persistence::persist_new_wallet_content_v1;
|
||||
/// Persists new wallet fault before publish.
|
||||
#[cfg(test)]
|
||||
pub(crate) use self::persistence::persist_new_wallet_fault_before_publish;
|
||||
/// Persists new wallet for test.
|
||||
#[cfg(test)]
|
||||
pub(crate) use self::persistence::persist_new_wallet_for_test;
|
||||
/// Replaces wallet fault before publish.
|
||||
#[cfg(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 for test.
|
||||
#[cfg(test)]
|
||||
pub(crate) use self::persistence::replace_wallet_for_test;
|
||||
/// 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.
|
||||
@@ -257,5 +279,11 @@ pub(crate) use self::transcript::slot_aad;
|
||||
pub(crate) use self::transcript::state_transcript;
|
||||
/// Internal no-clobber transfer-file writer used only by OWNER export.
|
||||
pub(crate) use self::transfer::write_wallet_transfer_file_v1;
|
||||
/// 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.
|
||||
pub(crate) use self::wallet::create_wallet_v1_from_keypair;
|
||||
/// Verifies state signature.
|
||||
pub(crate) use self::wallet::verify_state_signature;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-wallet-lib/src/metadata.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
/// Protected metadata requested when creating a new native Wallet.
|
||||
///
|
||||
@@ -17,6 +17,7 @@ impl WalletCreateMetadataV1 {
|
||||
return Self { alias, note_texts };
|
||||
}
|
||||
|
||||
/// Consumes this value and returns parts.
|
||||
pub(crate) fn into_parts(self) -> (std::option::Option<std::string::String>, std::vec::Vec<std::string::String>) {
|
||||
return (self.alias, self.note_texts);
|
||||
}
|
||||
@@ -40,6 +41,7 @@ pub struct WalletNote {
|
||||
}
|
||||
|
||||
impl WalletNote {
|
||||
/// Creates a new `WalletNote` value.
|
||||
pub(crate) fn new(id: std::string::String, text: std::string::String) -> Self {
|
||||
return Self { id, text };
|
||||
}
|
||||
@@ -74,6 +76,7 @@ pub struct WalletInfo {
|
||||
}
|
||||
|
||||
impl WalletInfo {
|
||||
/// Creates a new `WalletInfo` value.
|
||||
pub(crate) fn new(
|
||||
capability: crate::WalletCapability,
|
||||
pubkey: ksp_core_lib::Pubkey,
|
||||
@@ -135,6 +138,7 @@ 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 };
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-wallet-lib/src/owner.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
/// Authorized OWNER capability handle.
|
||||
///
|
||||
@@ -7,11 +7,12 @@
|
||||
/// material remains encapsulated and is never exposed through a general-purpose getter.
|
||||
pub struct WalletOwner {
|
||||
info: crate::WalletInfo,
|
||||
state: crate::wallet::OwnerStateV1,
|
||||
state: crate::OwnerStateV1,
|
||||
}
|
||||
|
||||
impl WalletOwner {
|
||||
pub(crate) fn from_unlocked(info: crate::WalletInfo, state: crate::wallet::OwnerStateV1) -> Self {
|
||||
/// Builds `WalletOwner` from unlocked.
|
||||
pub(crate) fn from_unlocked(info: crate::WalletInfo, state: crate::OwnerStateV1) -> Self {
|
||||
return Self { info, state };
|
||||
}
|
||||
|
||||
@@ -307,7 +308,7 @@ async fn persist_staged(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return crate::persistence::replace_wallet_file_v1(destination, expected_current.clone(), serialized).await;
|
||||
return crate::replace_wallet_file_v1(destination, expected_current.clone(), serialized).await;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// file: crates/ksp-wallet-lib/src/payload.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! Plaintext payload codecs protected inside native `.kspwallet` V1 compartments.
|
||||
|
||||
use base64::Engine; // rust-rules: derive-import
|
||||
use std::str::FromStr; // rust-rules: derive-import
|
||||
use base64::Engine; // rust-rules: trait-import
|
||||
use std::str::FromStr; // rust-rules: trait-import
|
||||
|
||||
/// Crate-internal `MetadataPayloadV1` state shared across the owning crate.
|
||||
pub(crate) struct MetadataPayloadV1 {
|
||||
pubkey: ksp_core_lib::Pubkey,
|
||||
alias: std::option::Option<std::string::String>,
|
||||
@@ -13,16 +14,19 @@ pub(crate) struct MetadataPayloadV1 {
|
||||
}
|
||||
|
||||
impl crate::MetadataPayloadV1 {
|
||||
/// Returns the current pubkey.
|
||||
pub(crate) const fn pubkey(&self) -> &ksp_core_lib::Pubkey {
|
||||
return &self.pubkey;
|
||||
}
|
||||
|
||||
/// Builds `MetadataPayloadV1` from info.
|
||||
pub(crate) fn from_info(info: &crate::WalletInfo) -> Self {
|
||||
let alias = info.alias().map(std::string::String::from);
|
||||
let notes = info.notes().to_vec();
|
||||
return Self { pubkey: *info.pubkey(), alias, notes };
|
||||
}
|
||||
|
||||
/// Updates alias.
|
||||
pub(crate) fn set_alias(&mut self, alias: std::option::Option<std::string::String>) -> ksp_core_lib::Result<()> {
|
||||
let validation_result = validate_alias(alias.as_deref());
|
||||
if let std::result::Result::Err(error) = validation_result {
|
||||
@@ -32,6 +36,7 @@ impl crate::MetadataPayloadV1 {
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
/// Adds note.
|
||||
pub(crate) fn add_note(&mut self, text: std::string::String) -> ksp_core_lib::Result<std::string::String> {
|
||||
if self.notes.len() >= crate::KSPWALLET_V1_MAX_NOTES {
|
||||
return std::result::Result::Err(metadata_error("Wallet note count exceeds the V1 limit", "metadata.notes"));
|
||||
@@ -40,7 +45,7 @@ impl crate::MetadataPayloadV1 {
|
||||
if let std::result::Result::Err(error) = validation_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let note_id_bytes = match crate::crypto::random_bytes::<{ crate::KSPWALLET_V1_NOTE_ID_BYTES }>() {
|
||||
let note_id_bytes = match crate::random_bytes::<{ crate::KSPWALLET_V1_NOTE_ID_BYTES }>() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -54,6 +59,7 @@ impl crate::MetadataPayloadV1 {
|
||||
return std::result::Result::Ok(note_id);
|
||||
}
|
||||
|
||||
/// Updates note.
|
||||
pub(crate) fn update_note(&mut self, note_id: &str, text: std::string::String) -> ksp_core_lib::Result<()> {
|
||||
let validation_result = validate_note_text(text.as_str());
|
||||
if let std::result::Result::Err(error) = validation_result {
|
||||
@@ -70,6 +76,7 @@ impl crate::MetadataPayloadV1 {
|
||||
return std::result::Result::Err(note_not_found_error());
|
||||
}
|
||||
|
||||
/// Deletes note.
|
||||
pub(crate) fn delete_note(&mut self, note_id: &str) -> ksp_core_lib::Result<()> {
|
||||
let mut index = 0_usize;
|
||||
while index < self.notes.len() {
|
||||
@@ -82,6 +89,7 @@ impl crate::MetadataPayloadV1 {
|
||||
return std::result::Result::Err(note_not_found_error());
|
||||
}
|
||||
|
||||
/// Executes the crate-internal encode operation for `MetadataPayloadV1`.
|
||||
pub(crate) fn encode(&self) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
|
||||
let validation_result = validate_alias(self.alias.as_deref());
|
||||
if let std::result::Result::Err(error) = validation_result {
|
||||
@@ -110,23 +118,26 @@ impl crate::MetadataPayloadV1 {
|
||||
return std::result::Result::Ok(serialized);
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// Crate-internal `OwnerControlMaterialV1` state shared across the owning crate.
|
||||
pub(crate) struct OwnerControlMaterialV1 {
|
||||
admin_signing_secret: [u8; crate::crypto::SECRET_KEY_BYTES],
|
||||
admin_signing_secret: [u8; crate::SECRET_KEY_BYTES],
|
||||
metadata_key: crate::SecretKeyV1,
|
||||
secret_key: crate::SecretKeyV1,
|
||||
}
|
||||
|
||||
impl OwnerControlMaterialV1 {
|
||||
pub(crate) fn into_parts(mut self) -> ([u8; crate::crypto::SECRET_KEY_BYTES], crate::SecretKeyV1, crate::SecretKeyV1) {
|
||||
let mut admin_signing_secret = [0_u8; crate::crypto::SECRET_KEY_BYTES];
|
||||
/// Consumes this value and returns parts.
|
||||
pub(crate) fn into_parts(mut self) -> ([u8; crate::SECRET_KEY_BYTES], crate::SecretKeyV1, crate::SecretKeyV1) {
|
||||
let mut admin_signing_secret = [0_u8; crate::SECRET_KEY_BYTES];
|
||||
std::mem::swap(&mut admin_signing_secret, &mut self.admin_signing_secret);
|
||||
let metadata_key = std::mem::replace(&mut self.metadata_key, crate::SecretKeyV1::from_bytes([0_u8; crate::crypto::SECRET_KEY_BYTES]));
|
||||
let secret_key = std::mem::replace(&mut self.secret_key, crate::SecretKeyV1::from_bytes([0_u8; crate::crypto::SECRET_KEY_BYTES]));
|
||||
let metadata_key = std::mem::replace(&mut self.metadata_key, crate::SecretKeyV1::from_bytes([0_u8; crate::SECRET_KEY_BYTES]));
|
||||
let secret_key = std::mem::replace(&mut self.secret_key, crate::SecretKeyV1::from_bytes([0_u8; crate::SECRET_KEY_BYTES]));
|
||||
return (admin_signing_secret, metadata_key, secret_key);
|
||||
}
|
||||
}
|
||||
@@ -152,6 +163,7 @@ struct RawMetadataNoteV1 {
|
||||
text: std::string::String,
|
||||
}
|
||||
|
||||
/// Encodes initial metadata payload.
|
||||
pub(crate) fn encode_initial_metadata_payload(
|
||||
pubkey: ksp_core_lib::Pubkey,
|
||||
metadata: crate::WalletCreateMetadataV1,
|
||||
@@ -164,7 +176,6 @@ pub(crate) fn encode_initial_metadata_payload(
|
||||
if note_texts.len() > crate::KSPWALLET_V1_MAX_NOTES {
|
||||
return std::result::Result::Err(metadata_error("Wallet note count exceeds the V1 limit", "metadata.notes"));
|
||||
}
|
||||
|
||||
let mut notes = std::vec::Vec::with_capacity(note_texts.len());
|
||||
let mut note_ids = std::collections::BTreeSet::new();
|
||||
for text in note_texts {
|
||||
@@ -172,7 +183,7 @@ pub(crate) fn encode_initial_metadata_payload(
|
||||
if let std::result::Result::Err(error) = validation_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let note_id_bytes = match crate::crypto::random_bytes::<{ crate::KSPWALLET_V1_NOTE_ID_BYTES }>() {
|
||||
let note_id_bytes = match crate::random_bytes::<{ crate::KSPWALLET_V1_NOTE_ID_BYTES }>() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -182,7 +193,6 @@ pub(crate) fn encode_initial_metadata_payload(
|
||||
}
|
||||
notes.push(crate::WalletNote::new(note_id, text));
|
||||
}
|
||||
|
||||
let payload = crate::MetadataPayloadV1 { pubkey, alias, notes };
|
||||
let serialized = match payload.encode() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -191,6 +201,7 @@ pub(crate) fn encode_initial_metadata_payload(
|
||||
return std::result::Result::Ok((serialized, payload));
|
||||
}
|
||||
|
||||
/// Decodes metadata payload.
|
||||
pub(crate) fn decode_metadata_payload(source: &[u8]) -> ksp_core_lib::Result<crate::MetadataPayloadV1> {
|
||||
if source.len() > crate::KSPWALLET_V1_MAX_METADATA_PLAINTEXT_BYTES {
|
||||
return std::result::Result::Err(metadata_error("Wallet metadata payload exceeds the V1 plaintext limit", "metadata"));
|
||||
@@ -207,7 +218,6 @@ pub(crate) fn decode_metadata_payload(source: &[u8]) -> ksp_core_lib::Result<cra
|
||||
if raw.notes.len() > crate::KSPWALLET_V1_MAX_NOTES {
|
||||
return std::result::Result::Err(metadata_error("Wallet note count exceeds the V1 limit", "metadata.notes"));
|
||||
}
|
||||
|
||||
let pubkey_result = ksp_core_lib::Pubkey::from_str(raw.pubkey.as_str());
|
||||
let pubkey = match pubkey_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -216,7 +226,6 @@ pub(crate) fn decode_metadata_payload(source: &[u8]) -> ksp_core_lib::Result<cra
|
||||
if pubkey.to_string() != raw.pubkey {
|
||||
return std::result::Result::Err(metadata_error("Wallet metadata Pubkey is not canonical Base58", "metadata.pubkey"));
|
||||
}
|
||||
|
||||
let mut notes = std::vec::Vec::with_capacity(raw.notes.len());
|
||||
let mut note_ids = std::collections::BTreeSet::new();
|
||||
for raw_note in raw.notes {
|
||||
@@ -239,8 +248,9 @@ pub(crate) fn decode_metadata_payload(source: &[u8]) -> ksp_core_lib::Result<cra
|
||||
return std::result::Result::Ok(crate::MetadataPayloadV1 { pubkey, alias: raw.alias, notes });
|
||||
}
|
||||
|
||||
/// Encodes owner control payload.
|
||||
pub(crate) fn encode_owner_control_payload(
|
||||
admin_signing_secret: &[u8; crate::crypto::SECRET_KEY_BYTES],
|
||||
admin_signing_secret: &[u8; crate::SECRET_KEY_BYTES],
|
||||
metadata_key: &crate::SecretKeyV1,
|
||||
secret_key: &crate::SecretKeyV1,
|
||||
) -> [u8; crate::KSPWALLET_V1_OWNER_CONTROL_PLAINTEXT_BYTES] {
|
||||
@@ -251,15 +261,16 @@ pub(crate) fn encode_owner_control_payload(
|
||||
return output;
|
||||
}
|
||||
|
||||
/// Decodes owner control payload.
|
||||
pub(crate) fn decode_owner_control_payload(source: &[u8]) -> ksp_core_lib::Result<OwnerControlMaterialV1> {
|
||||
if source.len() != crate::KSPWALLET_V1_OWNER_CONTROL_PLAINTEXT_BYTES {
|
||||
return std::result::Result::Err(metadata_error("Wallet OWNER-control payload length is invalid", "owner_control"));
|
||||
}
|
||||
let mut admin_signing_secret = [0_u8; crate::crypto::SECRET_KEY_BYTES];
|
||||
let mut admin_signing_secret = [0_u8; crate::SECRET_KEY_BYTES];
|
||||
admin_signing_secret.copy_from_slice(&source[0..32]);
|
||||
let mut metadata_key = [0_u8; crate::crypto::SECRET_KEY_BYTES];
|
||||
let mut metadata_key = [0_u8; crate::SECRET_KEY_BYTES];
|
||||
metadata_key.copy_from_slice(&source[32..64]);
|
||||
let mut secret_key = [0_u8; crate::crypto::SECRET_KEY_BYTES];
|
||||
let mut secret_key = [0_u8; crate::SECRET_KEY_BYTES];
|
||||
secret_key.copy_from_slice(&source[64..96]);
|
||||
return std::result::Result::Ok(OwnerControlMaterialV1 {
|
||||
admin_signing_secret,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// file: crates/ksp-wallet-lib/src/persistence.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
//! Async-first native Wallet V1 filesystem persistence.
|
||||
|
||||
use std::io::Read; // rust-rules: derive-import
|
||||
use std::io::Write; // rust-rules: derive-import
|
||||
use std::io::Read; // rust-rules: trait-import
|
||||
use std::io::Write; // rust-rules: trait-import
|
||||
|
||||
/// Creates a new native `.kspwallet` V1 at `destination` without overwriting an existing path.
|
||||
///
|
||||
@@ -83,10 +83,12 @@ pub async fn inspect_locked_wallet_file_v1(source: impl std::convert::AsRef<std:
|
||||
return crate::inspect_locked_wallet_v1(bytes.as_slice());
|
||||
}
|
||||
|
||||
/// Persists new wallet content v1.
|
||||
pub(crate) async fn persist_new_wallet_content_v1(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,
|
||||
expected_current: crate::KspWalletEnvelopeV1,
|
||||
@@ -131,7 +133,6 @@ fn read_wallet_file_blocking(source: &std::path::Path) -> ksp_core_lib::Result<s
|
||||
if metadata.len() > crate::KSPWALLET_MAX_FILE_BYTES as u64 {
|
||||
return std::result::Result::Err(oversized_document_error());
|
||||
}
|
||||
|
||||
let capacity = std::cmp::min(metadata.len(), crate::KSPWALLET_MAX_FILE_BYTES as u64) as usize;
|
||||
let mut bytes = std::vec::Vec::with_capacity(capacity);
|
||||
let mut bounded = file.take((crate::KSPWALLET_MAX_FILE_BYTES + 1) as u64);
|
||||
@@ -175,7 +176,7 @@ fn verify_expected_wallet_state(destination: &std::path::Path, expected_current:
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let verify_result = crate::wallet::verify_state_signature(¤t);
|
||||
let verify_result = crate::verify_state_signature(¤t);
|
||||
if let std::result::Result::Err(error) = verify_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
@@ -207,7 +208,6 @@ where
|
||||
if !existing_metadata.is_file() {
|
||||
return std::result::Result::Err(atomic_error("replace_destination", "Wallet replacement destination is not a regular file"));
|
||||
}
|
||||
|
||||
let temporary_result = tempfile::Builder::new().prefix(".kspwallet-replace-").suffix(".tmp").tempfile_in(parent);
|
||||
let mut temporary = match temporary_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -225,7 +225,6 @@ where
|
||||
if let std::result::Result::Err(error) = hook_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
|
||||
let persist_result = temporary.persist(destination);
|
||||
let persisted = match persist_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -254,7 +253,6 @@ where
|
||||
if destination.file_name().is_none() {
|
||||
return std::result::Result::Err(atomic_error("destination", "Wallet destination has no file name"));
|
||||
}
|
||||
|
||||
let temporary_result = tempfile::Builder::new().prefix(".kspwallet-write-").suffix(".tmp").tempfile_in(parent);
|
||||
let mut temporary = match temporary_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -268,12 +266,10 @@ where
|
||||
if let std::result::Result::Err(error) = sync_result {
|
||||
return std::result::Result::Err(atomic_io_error("temporary_sync", error));
|
||||
}
|
||||
|
||||
let hook_result = before_publish();
|
||||
if let std::result::Result::Err(error) = hook_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
|
||||
let persist_result = temporary.persist_noclobber(destination);
|
||||
let persisted = match persist_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -371,6 +367,7 @@ fn blocking_atomic_error(operation: &'static str, source: tokio::task::JoinError
|
||||
.with_source(source);
|
||||
}
|
||||
|
||||
/// Persists new wallet fault before publish.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn persist_new_wallet_fault_before_publish(destination: &std::path::Path, content: &[u8]) -> ksp_core_lib::Result<()> {
|
||||
return persist_new_wallet_with_hook(destination, content, || {
|
||||
@@ -378,11 +375,13 @@ pub(crate) fn persist_new_wallet_fault_before_publish(destination: &std::path::P
|
||||
});
|
||||
}
|
||||
|
||||
/// Persists new wallet for test.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn persist_new_wallet_for_test(destination: &std::path::Path, content: &[u8]) -> ksp_core_lib::Result<()> {
|
||||
return persist_new_wallet_blocking(destination, content);
|
||||
}
|
||||
|
||||
/// Replaces wallet fault before publish.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn replace_wallet_fault_before_publish(destination: &std::path::Path, content: &[u8]) -> ksp_core_lib::Result<()> {
|
||||
return replace_wallet_file_with_hook(destination, content, || {
|
||||
@@ -390,6 +389,7 @@ pub(crate) fn replace_wallet_fault_before_publish(destination: &std::path::Path,
|
||||
});
|
||||
}
|
||||
|
||||
/// Replaces wallet for test.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn replace_wallet_for_test(destination: &std::path::Path, content: &[u8]) -> ksp_core_lib::Result<()> {
|
||||
return replace_wallet_file_blocking(destination, content);
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
// file: crates/ksp-wallet-lib/src/transcript.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Deterministic `.kspwallet` V1 state-transcript and AEAD-AAD encoding.
|
||||
|
||||
const TAG_MAGIC: u16 = 0x0001;
|
||||
const TAG_COMPARTMENT_ALGORITHM: u16 = 0x0202;
|
||||
const TAG_COMPARTMENT_CIPHERTEXT: u16 = 0x0204;
|
||||
const TAG_COMPARTMENT_KIND: u16 = 0x0200;
|
||||
const TAG_COMPARTMENT_NONCE: u16 = 0x0203;
|
||||
const TAG_COMPARTMENT_VERSION: u16 = 0x0201;
|
||||
const TAG_FORMAT_VERSION: u16 = 0x0002;
|
||||
const TAG_KDF_ALGORITHM: u16 = 0x0102;
|
||||
const TAG_KDF_ITERATIONS: u16 = 0x0105;
|
||||
const TAG_KDF_MEMORY_KIB: u16 = 0x0104;
|
||||
const TAG_KDF_PARALLELISM: u16 = 0x0106;
|
||||
const TAG_KDF_SALT: u16 = 0x0107;
|
||||
const TAG_KDF_VERSION: u16 = 0x0103;
|
||||
const TAG_MAGIC: u16 = 0x0001;
|
||||
const TAG_OWNER_AUTH_PUBLIC_KEY: u16 = 0x0003;
|
||||
const TAG_SLOT_ID: u16 = 0x0100;
|
||||
const TAG_SLOT_ROLE: u16 = 0x0101;
|
||||
const TAG_STATE_SIGNATURE_ALGORITHM: u16 = 0x0500;
|
||||
const TAG_VIEW_ENABLED: u16 = 0x0010;
|
||||
const TAG_VIEW_ROLE: u16 = 0x0011;
|
||||
const TAG_VIEW_SLOT_ID: u16 = 0x0012;
|
||||
const TAG_SLOT_ID: u16 = 0x0100;
|
||||
const TAG_SLOT_ROLE: u16 = 0x0101;
|
||||
const TAG_KDF_ALGORITHM: u16 = 0x0102;
|
||||
const TAG_KDF_VERSION: u16 = 0x0103;
|
||||
const TAG_KDF_MEMORY_KIB: u16 = 0x0104;
|
||||
const TAG_KDF_ITERATIONS: u16 = 0x0105;
|
||||
const TAG_KDF_PARALLELISM: u16 = 0x0106;
|
||||
const TAG_KDF_SALT: u16 = 0x0107;
|
||||
const TAG_WRAP_ALGORITHM: u16 = 0x0108;
|
||||
const TAG_WRAP_NONCE: u16 = 0x0109;
|
||||
const TAG_WRAP_CIPHERTEXT: u16 = 0x010A;
|
||||
const TAG_COMPARTMENT_KIND: u16 = 0x0200;
|
||||
const TAG_COMPARTMENT_VERSION: u16 = 0x0201;
|
||||
const TAG_COMPARTMENT_ALGORITHM: u16 = 0x0202;
|
||||
const TAG_COMPARTMENT_NONCE: u16 = 0x0203;
|
||||
const TAG_COMPARTMENT_CIPHERTEXT: u16 = 0x0204;
|
||||
const TAG_STATE_SIGNATURE_ALGORITHM: u16 = 0x0500;
|
||||
const TAG_WRAP_NONCE: u16 = 0x0109;
|
||||
|
||||
/// Builds the normative OWNER state-signature transcript for one validated V1 envelope.
|
||||
pub(crate) fn state_transcript(envelope: &crate::KspWalletEnvelopeV1) -> std::vec::Vec<u8> {
|
||||
@@ -37,7 +37,6 @@ pub(crate) fn state_transcript(envelope: &crate::KspWalletEnvelopeV1) -> std::ve
|
||||
std::option::Option::Some(slot_id) => push_bytes(&mut output, TAG_VIEW_SLOT_ID, slot_id),
|
||||
std::option::Option::None => push_bytes(&mut output, TAG_VIEW_SLOT_ID, &[]),
|
||||
}
|
||||
|
||||
push_slot(&mut output, envelope.owner_slot(), true);
|
||||
push_compartment(&mut output, envelope.owner_control(), true);
|
||||
push_compartment(&mut output, envelope.metadata(), true);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// file: crates/ksp-wallet-lib/src/transfer.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Explicit OWNER-only Solana keypair import/export adapters.
|
||||
|
||||
use std::io::Read; // rust-rules: derive-import
|
||||
use std::io::Write; // rust-rules: derive-import
|
||||
use std::io::Read; // rust-rules: trait-import
|
||||
use std::io::Write; // rust-rules: trait-import
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt; // rust-rules: derive-import
|
||||
use zeroize::Zeroize; // rust-rules: derive-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`.
|
||||
#[non_exhaustive]
|
||||
@@ -161,6 +161,7 @@ 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(
|
||||
destination: std::path::PathBuf,
|
||||
content: std::vec::Vec<u8>,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-wallet-lib/src/view.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
/// Authorized VIEW capability handle.
|
||||
///
|
||||
@@ -7,11 +7,12 @@
|
||||
/// Solana secret, OWNER administration material or metadata-write authority.
|
||||
pub struct WalletView {
|
||||
info: crate::WalletInfo,
|
||||
state: crate::wallet::ViewStateV1,
|
||||
state: crate::ViewStateV1,
|
||||
}
|
||||
|
||||
impl WalletView {
|
||||
pub(crate) fn from_unlocked(info: crate::WalletInfo, state: crate::wallet::ViewStateV1) -> Self {
|
||||
/// Builds `WalletView` from unlocked.
|
||||
pub(crate) fn from_unlocked(info: crate::WalletInfo, state: crate::ViewStateV1) -> Self {
|
||||
return Self { info, state };
|
||||
}
|
||||
|
||||
@@ -68,7 +69,7 @@ impl WalletView {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let persist_result = crate::persistence::replace_wallet_file_v1(destination.as_ref().to_path_buf(), self.state.envelope().clone(), serialized).await;
|
||||
let persist_result = crate::replace_wallet_file_v1(destination.as_ref().to_path_buf(), self.state.envelope().clone(), serialized).await;
|
||||
if let std::result::Result::Err(error) = persist_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// file: crates/ksp-wallet-lib/src/wallet.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
//! In-memory native Wallet V1 create/open orchestration.
|
||||
|
||||
use ed25519_dalek::Signer; // rust-rules: derive-import
|
||||
use zeroize::Zeroize; // rust-rules: derive-import
|
||||
use ed25519_dalek::Signer; // rust-rules: trait-import
|
||||
use zeroize::Zeroize; // rust-rules: trait-import
|
||||
|
||||
/// Crate-internal `OwnerStateV1` state shared across the owning crate.
|
||||
pub(crate) struct OwnerStateV1 {
|
||||
envelope: crate::KspWalletEnvelopeV1,
|
||||
owner_root: std::option::Option<crate::SecretKeyV1>,
|
||||
@@ -16,6 +17,7 @@ pub(crate) struct OwnerStateV1 {
|
||||
}
|
||||
|
||||
impl OwnerStateV1 {
|
||||
/// Creates a new `OwnerStateV1` value.
|
||||
pub(crate) fn new(
|
||||
envelope: crate::KspWalletEnvelopeV1,
|
||||
owner_root: crate::SecretKeyV1,
|
||||
@@ -34,15 +36,18 @@ impl OwnerStateV1 {
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the current envelope.
|
||||
pub(crate) const fn envelope(&self) -> &crate::KspWalletEnvelopeV1 {
|
||||
return &self.envelope;
|
||||
}
|
||||
|
||||
/// Applies envelope.
|
||||
pub(crate) fn apply_envelope(&mut self, envelope: crate::KspWalletEnvelopeV1) {
|
||||
self.envelope = envelope;
|
||||
return;
|
||||
}
|
||||
|
||||
/// Applies strong view state.
|
||||
pub(crate) fn apply_strong_view_state(&mut self, envelope: crate::KspWalletEnvelopeV1, metadata_key: crate::SecretKeyV1) {
|
||||
self.envelope = envelope;
|
||||
let previous = self.metadata_key.replace(metadata_key);
|
||||
@@ -50,6 +55,7 @@ impl OwnerStateV1 {
|
||||
return;
|
||||
}
|
||||
|
||||
/// Executes the crate-internal export transfer operation for `OwnerStateV1`.
|
||||
pub(crate) fn export_transfer(&self, format: crate::WalletTransferFormat) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
|
||||
let keypair = match self.solana_keypair.as_ref() {
|
||||
std::option::Option::Some(value) => value,
|
||||
@@ -74,6 +80,7 @@ impl OwnerStateV1 {
|
||||
};
|
||||
}
|
||||
|
||||
/// Executes the crate-internal sign message operation for `OwnerStateV1`.
|
||||
pub(crate) fn sign_message(&self, message: &[u8]) -> ksp_core_lib::Result<[u8; crate::KSPWALLET_SOLANA_SIGNATURE_BYTES]> {
|
||||
let keypair = match self.solana_keypair.as_ref() {
|
||||
std::option::Option::Some(value) => value,
|
||||
@@ -89,6 +96,7 @@ impl OwnerStateV1 {
|
||||
return std::result::Result::Ok(signature);
|
||||
}
|
||||
|
||||
/// Executes the crate-internal stage metadata payload operation for `OwnerStateV1`.
|
||||
pub(crate) fn stage_metadata_payload(&self, payload: crate::MetadataPayloadV1) -> ksp_core_lib::Result<(crate::KspWalletEnvelopeV1, crate::WalletInfo)> {
|
||||
let metadata_key = match self.metadata_key.as_ref() {
|
||||
std::option::Option::Some(value) => value,
|
||||
@@ -138,6 +146,7 @@ impl OwnerStateV1 {
|
||||
return std::result::Result::Ok((envelope, payload.into_info(crate::WalletCapability::Owner)));
|
||||
}
|
||||
|
||||
/// Executes the crate-internal stage owner password rotation operation for `OwnerStateV1`.
|
||||
pub(crate) async fn stage_owner_password_rotation(&self, new_password: crate::OwnerPassword) -> ksp_core_lib::Result<crate::KspWalletEnvelopeV1> {
|
||||
let owner_root = match self.owner_root.as_ref() {
|
||||
std::option::Option::Some(value) => value,
|
||||
@@ -172,6 +181,7 @@ impl OwnerStateV1 {
|
||||
return verify_and_return(envelope);
|
||||
}
|
||||
|
||||
/// Executes the crate-internal stage view password rotation operation for `OwnerStateV1`.
|
||||
pub(crate) async fn stage_view_password_rotation(&self, new_password: crate::ViewPassword) -> ksp_core_lib::Result<crate::KspWalletEnvelopeV1> {
|
||||
let metadata_key = match self.metadata_key.as_ref() {
|
||||
std::option::Option::Some(value) => value,
|
||||
@@ -201,6 +211,7 @@ impl OwnerStateV1 {
|
||||
return verify_and_return(envelope);
|
||||
}
|
||||
|
||||
/// Executes the crate-internal stage disable view operation for `OwnerStateV1`.
|
||||
pub(crate) fn stage_disable_view(&self) -> ksp_core_lib::Result<(crate::KspWalletEnvelopeV1, crate::SecretKeyV1)> {
|
||||
if !self.envelope.view_descriptor().enabled() {
|
||||
return std::result::Result::Err(capability_error("Wallet VIEW capability is already disabled"));
|
||||
@@ -208,6 +219,7 @@ impl OwnerStateV1 {
|
||||
return self.stage_strong_view_change(std::option::Option::None);
|
||||
}
|
||||
|
||||
/// Executes the crate-internal stage recreate view operation for `OwnerStateV1`.
|
||||
pub(crate) async fn stage_recreate_view(
|
||||
&self,
|
||||
new_password: crate::ViewPassword,
|
||||
@@ -260,7 +272,6 @@ impl OwnerStateV1 {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::result::Result::Err(crypto_operation_error()),
|
||||
};
|
||||
|
||||
let current_plaintext_result = crate::decrypt_bytes(
|
||||
current_metadata_key,
|
||||
self.envelope.metadata().nonce(),
|
||||
@@ -290,9 +301,8 @@ impl OwnerStateV1 {
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let metadata = crate::WalletEncryptedCompartmentV1::new(crate::WalletCompartmentKindV1::Metadata, metadata_nonce, metadata_ciphertext);
|
||||
|
||||
let mut admin_secret = admin_signing_key.to_bytes();
|
||||
let mut owner_control_plaintext = crate::payload::encode_owner_control_payload(&admin_secret, &new_metadata_key, secret_key);
|
||||
let mut owner_control_plaintext = crate::encode_owner_control_payload(&admin_secret, &new_metadata_key, secret_key);
|
||||
admin_secret.zeroize();
|
||||
let owner_control_nonce = match crate::random_nonce() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -314,7 +324,6 @@ impl OwnerStateV1 {
|
||||
};
|
||||
let owner_control =
|
||||
crate::WalletEncryptedCompartmentV1::new(crate::WalletCompartmentKindV1::OwnerControl, owner_control_nonce, owner_control_ciphertext);
|
||||
|
||||
let view_descriptor = match view_slot.as_ref() {
|
||||
std::option::Option::Some(slot) => crate::WalletViewDescriptorV1::enabled_for_slot(*slot.slot_id()),
|
||||
std::option::Option::None => crate::WalletViewDescriptorV1::disabled(),
|
||||
@@ -352,25 +361,30 @@ impl std::ops::Drop for OwnerStateV1 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Crate-internal `ViewStateV1` state shared across the owning crate.
|
||||
pub(crate) struct ViewStateV1 {
|
||||
envelope: crate::KspWalletEnvelopeV1,
|
||||
metadata_key: std::option::Option<crate::SecretKeyV1>,
|
||||
}
|
||||
|
||||
impl ViewStateV1 {
|
||||
/// Creates a new `ViewStateV1` value.
|
||||
pub(crate) fn new(envelope: crate::KspWalletEnvelopeV1, metadata_key: crate::SecretKeyV1) -> Self {
|
||||
return Self { envelope, metadata_key: std::option::Option::Some(metadata_key) };
|
||||
}
|
||||
|
||||
/// Returns the current envelope.
|
||||
pub(crate) const fn envelope(&self) -> &crate::KspWalletEnvelopeV1 {
|
||||
return &self.envelope;
|
||||
}
|
||||
|
||||
/// Applies envelope.
|
||||
pub(crate) fn apply_envelope(&mut self, envelope: crate::KspWalletEnvelopeV1) {
|
||||
self.envelope = envelope;
|
||||
return;
|
||||
}
|
||||
|
||||
/// Executes the crate-internal stage view password rotation operation for `ViewStateV1`.
|
||||
pub(crate) async fn stage_view_password_rotation(&self, new_password: crate::ViewPassword) -> ksp_core_lib::Result<crate::KspWalletEnvelopeV1> {
|
||||
let metadata_key = match self.metadata_key.as_ref() {
|
||||
std::option::Option::Some(value) => value,
|
||||
@@ -526,7 +540,7 @@ pub async fn open_wallet_owner_v1(source: &[u8], password: crate::OwnerPassword)
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(owner_unlock_error()),
|
||||
};
|
||||
let control_result = crate::payload::decode_owner_control_payload(owner_control_plaintext.as_slice());
|
||||
let control_result = crate::decode_owner_control_payload(owner_control_plaintext.as_slice());
|
||||
owner_control_plaintext.zeroize();
|
||||
let control = match control_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -538,7 +552,6 @@ pub async fn open_wallet_owner_v1(source: &[u8], password: crate::OwnerPassword)
|
||||
if admin_signing_key.verifying_key().to_bytes() != *envelope.owner_auth_public_key() {
|
||||
return std::result::Result::Err(authentication_error());
|
||||
}
|
||||
|
||||
let metadata_plaintext_result = crate::decrypt_bytes(
|
||||
&metadata_key,
|
||||
envelope.metadata().nonce(),
|
||||
@@ -555,7 +568,6 @@ pub async fn open_wallet_owner_v1(source: &[u8], password: crate::OwnerPassword)
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
|
||||
let secret_plaintext_result = crate::decrypt_bytes(
|
||||
&secret_key,
|
||||
envelope.secret().nonce(),
|
||||
@@ -590,7 +602,6 @@ 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 state = OwnerStateV1::new(envelope, owner_root, metadata_key, secret_key, admin_signing_key, solana_keypair);
|
||||
ksp_logging_lib::debug!(
|
||||
@@ -616,6 +627,7 @@ pub fn inspect_locked_wallet_v1(source: &[u8]) -> ksp_core_lib::Result<crate::Lo
|
||||
return std::result::Result::Ok(crate::LockedWalletInfo::new(envelope.view_descriptor().enabled()));
|
||||
}
|
||||
|
||||
/// Executes the crate-internal create wallet v1 from keypair operation for the owning module.
|
||||
pub(crate) async fn create_wallet_v1_from_keypair(
|
||||
owner_password: crate::OwnerPassword,
|
||||
view_password: std::option::Option<crate::ViewPassword>,
|
||||
@@ -634,14 +646,12 @@ pub(crate) async fn create_wallet_v1_from_keypair(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
|
||||
let mut admin_secret = match crate::random_bytes::<{ crate::SECRET_KEY_BYTES }>() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let admin_signing_key = ed25519_dalek::SigningKey::from_bytes(&admin_secret);
|
||||
let owner_auth_public_key = admin_signing_key.verifying_key().to_bytes();
|
||||
|
||||
let mut solana_keypair_bytes = solana_keypair.to_bytes();
|
||||
let pubkey_result = pubkey_from_keypair_bytes(&solana_keypair_bytes);
|
||||
let pubkey = match pubkey_result {
|
||||
@@ -652,8 +662,7 @@ pub(crate) async fn create_wallet_v1_from_keypair(
|
||||
return std::result::Result::Err(error);
|
||||
},
|
||||
};
|
||||
|
||||
let metadata_encoded_result = crate::payload::encode_initial_metadata_payload(pubkey, metadata);
|
||||
let metadata_encoded_result = crate::encode_initial_metadata_payload(pubkey, metadata);
|
||||
let (mut metadata_plaintext, metadata_payload) = match metadata_encoded_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
@@ -662,9 +671,8 @@ pub(crate) async fn create_wallet_v1_from_keypair(
|
||||
return std::result::Result::Err(error);
|
||||
},
|
||||
};
|
||||
let mut owner_control_plaintext = crate::payload::encode_owner_control_payload(&admin_secret, &metadata_key, &secret_key);
|
||||
let mut owner_control_plaintext = crate::encode_owner_control_payload(&admin_secret, &metadata_key, &secret_key);
|
||||
admin_secret.zeroize();
|
||||
|
||||
let owner_slot_id = match crate::random_bytes::<{ crate::KSPWALLET_V1_SLOT_ID_BYTES }>() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
@@ -684,7 +692,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
|
||||
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_bytes);
|
||||
},
|
||||
};
|
||||
|
||||
let view_material_result = prepare_view_creation(view_password);
|
||||
let view_material = match view_material_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -692,7 +699,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
|
||||
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_bytes);
|
||||
},
|
||||
};
|
||||
|
||||
let owner_control_nonce = match crate::random_nonce() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
@@ -711,7 +717,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
|
||||
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_bytes);
|
||||
},
|
||||
};
|
||||
|
||||
let provisional = provisional_envelope(
|
||||
owner_auth_public_key,
|
||||
owner_slot_id,
|
||||
@@ -722,7 +727,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
|
||||
metadata_nonce,
|
||||
secret_nonce,
|
||||
);
|
||||
|
||||
let owner_derived_result = derive_owner_password_key_async(owner_password, owner_kdf.clone()).await;
|
||||
let owner_derived = match owner_derived_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -737,7 +741,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
|
||||
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_bytes);
|
||||
},
|
||||
};
|
||||
|
||||
let view_slot_result = seal_view_slot(&provisional, view_material, &metadata_key).await;
|
||||
let view_slot = match view_slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -745,7 +748,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
|
||||
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_bytes);
|
||||
},
|
||||
};
|
||||
|
||||
let owner_control_ciphertext_result = crate::encrypt_bytes(
|
||||
&owner_root,
|
||||
&owner_control_nonce,
|
||||
@@ -759,7 +761,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
|
||||
},
|
||||
};
|
||||
owner_control_plaintext.zeroize();
|
||||
|
||||
let metadata_ciphertext_result = crate::encrypt_bytes(
|
||||
&metadata_key,
|
||||
&metadata_nonce,
|
||||
@@ -773,7 +774,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
|
||||
},
|
||||
};
|
||||
metadata_plaintext.zeroize();
|
||||
|
||||
let secret_ciphertext_result = crate::encrypt_bytes(
|
||||
&secret_key,
|
||||
&secret_nonce,
|
||||
@@ -787,7 +787,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
|
||||
},
|
||||
};
|
||||
solana_keypair_bytes.zeroize();
|
||||
|
||||
let owner_slot =
|
||||
crate::WalletKeySlotV1::new(owner_slot_id, crate::WalletKeySlotRoleV1::Owner, owner_kdf, crate::WalletKeyWrapV1::new(owner_wrap_nonce, owner_wrapped));
|
||||
let view_descriptor = match view_slot.as_ref() {
|
||||
@@ -822,7 +821,6 @@ 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 state = OwnerStateV1::new(envelope, owner_root, metadata_key, secret_key, admin_signing_key, solana_keypair);
|
||||
ksp_logging_lib::debug!(
|
||||
@@ -835,6 +833,7 @@ pub(crate) async fn create_wallet_v1_from_keypair(
|
||||
return std::result::Result::Ok(crate::WalletOwner::from_unlocked(info, state));
|
||||
}
|
||||
|
||||
/// Verifies state signature.
|
||||
pub(crate) fn verify_state_signature(envelope: &crate::KspWalletEnvelopeV1) -> ksp_core_lib::Result<()> {
|
||||
let verifying_key_result = ed25519_dalek::VerifyingKey::from_bytes(envelope.owner_auth_public_key());
|
||||
let verifying_key = match verifying_key_result {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// file: crates/ksp-wallet-lib/src/wire.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
//! Strict native `.kspwallet` V1 wire envelope.
|
||||
|
||||
use base64::Engine; // rust-rules: derive-import
|
||||
use base64::Engine; // rust-rules: trait-import
|
||||
|
||||
/// Password KDF supported by `.kspwallet` V1 key slots.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
@@ -105,6 +105,7 @@ pub struct WalletKdfParametersV1 {
|
||||
}
|
||||
|
||||
impl WalletKdfParametersV1 {
|
||||
/// Creates a new creation value for `WalletKdfParametersV1`.
|
||||
pub(crate) fn new_creation(salt: std::vec::Vec<u8>) -> Self {
|
||||
return Self {
|
||||
algorithm: WalletKdfAlgorithmV1::Argon2id,
|
||||
@@ -176,6 +177,7 @@ pub struct WalletKeyWrapV1 {
|
||||
}
|
||||
|
||||
impl WalletKeyWrapV1 {
|
||||
/// Creates a new `WalletKeyWrapV1` value.
|
||||
pub(crate) fn new(nonce: [u8; crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES], ciphertext: std::vec::Vec<u8>) -> Self {
|
||||
return Self { algorithm: WalletAeadAlgorithmV1::XChaCha20Poly1305, nonce, ciphertext };
|
||||
}
|
||||
@@ -220,6 +222,7 @@ pub struct WalletKeySlotV1 {
|
||||
}
|
||||
|
||||
impl WalletKeySlotV1 {
|
||||
/// Creates a new `WalletKeySlotV1` value.
|
||||
pub(crate) fn new(slot_id: [u8; crate::KSPWALLET_V1_SLOT_ID_BYTES], role: WalletKeySlotRoleV1, kdf: WalletKdfParametersV1, wrap: WalletKeyWrapV1) -> Self {
|
||||
return Self { slot_id, role, kdf, wrap };
|
||||
}
|
||||
@@ -269,10 +272,12 @@ pub struct WalletViewDescriptorV1 {
|
||||
}
|
||||
|
||||
impl WalletViewDescriptorV1 {
|
||||
/// Executes the crate-internal disabled operation for `WalletViewDescriptorV1`.
|
||||
pub(crate) const fn disabled() -> Self {
|
||||
return Self { enabled: false, slot_id: std::option::Option::None };
|
||||
}
|
||||
|
||||
/// Executes the crate-internal enabled for slot operation for `WalletViewDescriptorV1`.
|
||||
pub(crate) const fn enabled_for_slot(slot_id: [u8; crate::KSPWALLET_V1_SLOT_ID_BYTES]) -> Self {
|
||||
return Self { enabled: true, slot_id: std::option::Option::Some(slot_id) };
|
||||
}
|
||||
@@ -301,6 +306,7 @@ pub struct WalletEncryptedCompartmentV1 {
|
||||
}
|
||||
|
||||
impl WalletEncryptedCompartmentV1 {
|
||||
/// Creates a new `WalletEncryptedCompartmentV1` value.
|
||||
pub(crate) fn new(kind: WalletCompartmentKindV1, nonce: [u8; crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES], ciphertext: std::vec::Vec<u8>) -> Self {
|
||||
return Self {
|
||||
kind,
|
||||
@@ -363,6 +369,7 @@ pub struct WalletStateSignatureV1 {
|
||||
}
|
||||
|
||||
impl WalletStateSignatureV1 {
|
||||
/// Creates a new `WalletStateSignatureV1` value.
|
||||
pub(crate) const fn new(signature: [u8; crate::KSPWALLET_V1_ED25519_SIGNATURE_BYTES]) -> Self {
|
||||
return Self { algorithm: WalletStateSignatureAlgorithmV1::Ed25519, signature };
|
||||
}
|
||||
@@ -404,6 +411,7 @@ pub struct KspWalletEnvelopeV1 {
|
||||
}
|
||||
|
||||
impl KspWalletEnvelopeV1 {
|
||||
/// Creates a new internal value for `KspWalletEnvelopeV1`.
|
||||
pub(crate) fn new_internal(
|
||||
owner_auth_public_key: [u8; crate::KSPWALLET_V1_ED25519_PUBLIC_KEY_BYTES],
|
||||
view_descriptor: WalletViewDescriptorV1,
|
||||
@@ -434,7 +442,6 @@ impl KspWalletEnvelopeV1 {
|
||||
if source.len() > crate::KSPWALLET_MAX_FILE_BYTES {
|
||||
return std::result::Result::Err(format_error("Wallet document exceeds the V1 maximum size", "document"));
|
||||
}
|
||||
|
||||
let probe_result = serde_json::from_slice::<RawVersionProbe>(source);
|
||||
let probe = match probe_result {
|
||||
std::result::Result::Ok(probe) => probe,
|
||||
@@ -449,7 +456,6 @@ impl KspWalletEnvelopeV1 {
|
||||
.with_context("format_version", probe.format_version.to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
let raw_result = serde_json::from_slice::<RawEnvelopeV1>(source);
|
||||
let raw = match raw_result {
|
||||
std::result::Result::Ok(raw) => raw,
|
||||
@@ -793,7 +799,6 @@ fn parse_raw_envelope(raw: RawEnvelopeV1) -> ksp_core_lib::Result<KspWalletEnvel
|
||||
.with_context("format_version", raw.format_version.to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
let owner_auth_public_key =
|
||||
match decode_fixed::<{ crate::KSPWALLET_V1_ED25519_PUBLIC_KEY_BYTES }>(raw.owner_auth_public_key.as_str(), "owner_auth_public_key") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -803,7 +808,6 @@ fn parse_raw_envelope(raw: RawEnvelopeV1) -> ksp_core_lib::Result<KspWalletEnvel
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
|
||||
if raw.key_slots.is_empty() || raw.key_slots.len() > crate::KSPWALLET_V1_MAX_KEY_SLOTS {
|
||||
return std::result::Result::Err(format_error("Wallet key_slots count is invalid for V1", "key_slots"));
|
||||
}
|
||||
@@ -853,7 +857,6 @@ fn parse_raw_envelope(raw: RawEnvelopeV1) -> ksp_core_lib::Result<KspWalletEnvel
|
||||
} else if view_slot.is_some() {
|
||||
return std::result::Result::Err(format_error("Disabled VIEW descriptor forbids a VIEW key slot", "key_slots"));
|
||||
}
|
||||
|
||||
let owner_control = match parse_compartment(
|
||||
WalletCompartmentKindV1::OwnerControl,
|
||||
raw.owner_control.control_version,
|
||||
@@ -891,7 +894,6 @@ fn parse_raw_envelope(raw: RawEnvelopeV1) -> ksp_core_lib::Result<KspWalletEnvel
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
|
||||
return std::result::Result::Ok(KspWalletEnvelopeV1 {
|
||||
owner_auth_public_key,
|
||||
view_descriptor,
|
||||
|
||||
Reference in New Issue
Block a user