v0.2.5-pre.009

This commit is contained in:
2026-08-20 09:24:10 +02:00
parent 1125ade4c1
commit e91bf36e9e
83 changed files with 2467 additions and 1801 deletions

View File

@@ -1,16 +1,16 @@
// file: crates/ksp-wallet-lib/src/wallet.rs
// version: 5
// version: 7
//! In-memory native Wallet V1 create/open orchestration.
use ed25519_dalek::Signer as _;
use zeroize::Zeroize as _;
use ed25519_dalek::Signer; // rust-rules: derive-import
use zeroize::Zeroize; // rust-rules: derive-import
pub(crate) struct OwnerStateV1 {
envelope: crate::KspWalletEnvelopeV1,
owner_root: std::option::Option<crate::crypto::SecretKeyV1>,
metadata_key: std::option::Option<crate::crypto::SecretKeyV1>,
secret_key: std::option::Option<crate::crypto::SecretKeyV1>,
owner_root: std::option::Option<crate::SecretKeyV1>,
metadata_key: std::option::Option<crate::SecretKeyV1>,
secret_key: std::option::Option<crate::SecretKeyV1>,
admin_signing_key: std::option::Option<ed25519_dalek::SigningKey>,
solana_keypair: std::option::Option<solana_keypair::Keypair>,
}
@@ -18,9 +18,9 @@ pub(crate) struct OwnerStateV1 {
impl OwnerStateV1 {
pub(crate) fn new(
envelope: crate::KspWalletEnvelopeV1,
owner_root: crate::crypto::SecretKeyV1,
metadata_key: crate::crypto::SecretKeyV1,
secret_key: crate::crypto::SecretKeyV1,
owner_root: crate::SecretKeyV1,
metadata_key: crate::SecretKeyV1,
secret_key: crate::SecretKeyV1,
admin_signing_key: ed25519_dalek::SigningKey,
solana_keypair: solana_keypair::Keypair,
) -> Self {
@@ -43,7 +43,7 @@ impl OwnerStateV1 {
return;
}
pub(crate) fn apply_strong_view_state(&mut self, envelope: crate::KspWalletEnvelopeV1, metadata_key: crate::crypto::SecretKeyV1) {
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);
drop(previous);
@@ -80,8 +80,8 @@ impl OwnerStateV1 {
std::option::Option::None => return std::result::Result::Err(signing_error()),
};
let mut keypair_bytes = keypair.to_bytes();
let mut seed = [0_u8; crate::crypto::SECRET_KEY_BYTES];
seed.copy_from_slice(&keypair_bytes[0..crate::crypto::SECRET_KEY_BYTES]);
let mut seed = [0_u8; crate::SECRET_KEY_BYTES];
seed.copy_from_slice(&keypair_bytes[0..crate::SECRET_KEY_BYTES]);
let signing_key = ed25519_dalek::SigningKey::from_bytes(&seed);
let signature = signing_key.sign(message).to_bytes();
seed.zeroize();
@@ -89,10 +89,7 @@ impl OwnerStateV1 {
return std::result::Result::Ok(signature);
}
pub(crate) fn stage_metadata_payload(
&self,
payload: crate::payload::MetadataPayloadV1,
) -> ksp_core_lib::Result<(crate::KspWalletEnvelopeV1, crate::WalletInfo)> {
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,
std::option::Option::None => return std::result::Result::Err(crypto_operation_error()),
@@ -105,14 +102,14 @@ impl OwnerStateV1 {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let nonce = match crate::crypto::random_nonce() {
let nonce = match crate::random_nonce() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
plaintext.zeroize();
return std::result::Result::Err(error);
},
};
let encrypted = crate::crypto::encrypt_bytes(
let encrypted = crate::encrypt_bytes(
metadata_key,
&nonce,
self.envelope.compartment_aad(crate::WalletCompartmentKindV1::Metadata).as_slice(),
@@ -204,7 +201,7 @@ impl OwnerStateV1 {
return verify_and_return(envelope);
}
pub(crate) fn stage_disable_view(&self) -> ksp_core_lib::Result<(crate::KspWalletEnvelopeV1, crate::crypto::SecretKeyV1)> {
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"));
}
@@ -214,12 +211,12 @@ impl OwnerStateV1 {
pub(crate) async fn stage_recreate_view(
&self,
new_password: crate::ViewPassword,
) -> ksp_core_lib::Result<(crate::KspWalletEnvelopeV1, crate::crypto::SecretKeyV1)> {
let slot_id = match crate::crypto::random_bytes::<{ crate::KSPWALLET_V1_SLOT_ID_BYTES }>() {
) -> ksp_core_lib::Result<(crate::KspWalletEnvelopeV1, crate::SecretKeyV1)> {
let slot_id = match crate::random_bytes::<{ crate::KSPWALLET_V1_SLOT_ID_BYTES }>() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let new_metadata_key = match crate::crypto::SecretKeyV1::random() {
let new_metadata_key = match crate::SecretKeyV1::random() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -234,8 +231,8 @@ impl OwnerStateV1 {
fn stage_strong_view_change(
&self,
view_slot: std::option::Option<crate::WalletKeySlotV1>,
) -> ksp_core_lib::Result<(crate::KspWalletEnvelopeV1, crate::crypto::SecretKeyV1)> {
let new_metadata_key = match crate::crypto::SecretKeyV1::random() {
) -> ksp_core_lib::Result<(crate::KspWalletEnvelopeV1, crate::SecretKeyV1)> {
let new_metadata_key = match crate::SecretKeyV1::random() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -245,8 +242,8 @@ impl OwnerStateV1 {
fn stage_strong_view_change_with_key(
&self,
view_slot: std::option::Option<crate::WalletKeySlotV1>,
new_metadata_key: crate::crypto::SecretKeyV1,
) -> ksp_core_lib::Result<(crate::KspWalletEnvelopeV1, crate::crypto::SecretKeyV1)> {
new_metadata_key: crate::SecretKeyV1,
) -> ksp_core_lib::Result<(crate::KspWalletEnvelopeV1, crate::SecretKeyV1)> {
let current_metadata_key = match self.metadata_key.as_ref() {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Err(crypto_operation_error()),
@@ -264,7 +261,7 @@ impl OwnerStateV1 {
std::option::Option::None => return std::result::Result::Err(crypto_operation_error()),
};
let current_plaintext_result = crate::crypto::decrypt_bytes(
let current_plaintext_result = crate::decrypt_bytes(
current_metadata_key,
self.envelope.metadata().nonce(),
self.envelope.compartment_aad(crate::WalletCompartmentKindV1::Metadata).as_slice(),
@@ -274,14 +271,14 @@ impl OwnerStateV1 {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(authentication_error()),
};
let metadata_nonce = match crate::crypto::random_nonce() {
let metadata_nonce = match crate::random_nonce() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
metadata_plaintext.zeroize();
return std::result::Result::Err(error);
},
};
let metadata_ciphertext_result = crate::crypto::encrypt_bytes(
let metadata_ciphertext_result = crate::encrypt_bytes(
&new_metadata_key,
&metadata_nonce,
self.envelope.compartment_aad(crate::WalletCompartmentKindV1::Metadata).as_slice(),
@@ -297,14 +294,14 @@ impl OwnerStateV1 {
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);
admin_secret.zeroize();
let owner_control_nonce = match crate::crypto::random_nonce() {
let owner_control_nonce = match crate::random_nonce() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
owner_control_plaintext.zeroize();
return std::result::Result::Err(error);
},
};
let owner_control_ciphertext_result = crate::crypto::encrypt_bytes(
let owner_control_ciphertext_result = crate::encrypt_bytes(
owner_root,
&owner_control_nonce,
self.envelope.compartment_aad(crate::WalletCompartmentKindV1::OwnerControl).as_slice(),
@@ -357,11 +354,11 @@ impl std::ops::Drop for OwnerStateV1 {
pub(crate) struct ViewStateV1 {
envelope: crate::KspWalletEnvelopeV1,
metadata_key: std::option::Option<crate::crypto::SecretKeyV1>,
metadata_key: std::option::Option<crate::SecretKeyV1>,
}
impl ViewStateV1 {
pub(crate) fn new(envelope: crate::KspWalletEnvelopeV1, metadata_key: crate::crypto::SecretKeyV1) -> Self {
pub(crate) fn new(envelope: crate::KspWalletEnvelopeV1, metadata_key: crate::SecretKeyV1) -> Self {
return Self { envelope, metadata_key: std::option::Option::Some(metadata_key) };
}
@@ -411,6 +408,18 @@ impl std::ops::Drop for ViewStateV1 {
}
}
enum PasswordRotationV1 {
Owner(crate::OwnerPassword),
View(crate::ViewPassword),
}
struct ViewCreationMaterialV1 {
slot_id: [u8; crate::KSPWALLET_V1_SLOT_ID_BYTES],
kdf: crate::WalletKdfParametersV1,
wrap_nonce: [u8; crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES],
password: crate::ViewPassword,
}
/// Creates a new in-memory native `.kspwallet` V1 with a fresh Solana keypair.
///
/// The optional VIEW password creates an independent VIEW slot. This function performs no filesystem I/O; use [`crate::create_wallet_file_v1`] when
@@ -420,7 +429,7 @@ pub async fn create_wallet_v1(
view_password: std::option::Option<crate::ViewPassword>,
metadata: crate::WalletCreateMetadataV1,
) -> ksp_core_lib::Result<crate::WalletOwner> {
let mut solana_secret = match crate::crypto::random_bytes::<32>() {
let mut solana_secret = match crate::random_bytes::<32>() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -429,26 +438,204 @@ pub async fn create_wallet_v1(
return crate::create_wallet_v1_from_keypair(owner_password, view_password, metadata, solana_keypair).await;
}
/// Opens the VIEW capability from a native `.kspwallet` V1 JSON document.
pub async fn open_wallet_view_v1(source: &[u8], password: crate::ViewPassword) -> ksp_core_lib::Result<crate::WalletView> {
let envelope = match crate::KspWalletEnvelopeV1::parse_json(source) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let verify_result = verify_state_signature(&envelope);
if let std::result::Result::Err(error) = verify_result {
return std::result::Result::Err(error);
}
let view_slot = match envelope.view_slot() {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Err(capability_error("Wallet VIEW capability is disabled")),
};
let derived_result = derive_view_password_key_async(password, view_slot.kdf().clone()).await;
let derived = match derived_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let aad = envelope.view_slot_aad();
let view_aad = match aad {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Err(capability_error("Wallet VIEW capability is disabled")),
};
let metadata_key_result = crate::unwrap_key(&derived, view_slot.wrap().nonce(), view_aad.as_slice(), view_slot.wrap().ciphertext());
let metadata_key = match metadata_key_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(view_unlock_error()),
};
let metadata_plaintext_result = crate::decrypt_bytes(
&metadata_key,
envelope.metadata().nonce(),
envelope.compartment_aad(crate::WalletCompartmentKindV1::Metadata).as_slice(),
envelope.metadata().ciphertext(),
);
let mut metadata_plaintext = match metadata_plaintext_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(view_unlock_error()),
};
let metadata_payload_result = crate::decode_metadata_payload(metadata_plaintext.as_slice());
metadata_plaintext.zeroize();
let metadata_payload = match metadata_payload_result {
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 state = ViewStateV1::new(envelope, metadata_key);
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
operation = "wallet_open_view",
format_version = crate::KSPWALLET_FORMAT_VERSION_V1,
capability = "view",
"native wallet VIEW capability opened"
);
return std::result::Result::Ok(crate::WalletView::from_unlocked(info, state));
}
/// Opens the OWNER capability from a native `.kspwallet` V1 JSON document.
pub async fn open_wallet_owner_v1(source: &[u8], password: crate::OwnerPassword) -> ksp_core_lib::Result<crate::WalletOwner> {
let envelope = match crate::KspWalletEnvelopeV1::parse_json(source) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let verify_result = verify_state_signature(&envelope);
if let std::result::Result::Err(error) = verify_result {
return std::result::Result::Err(error);
}
let owner_slot = envelope.owner_slot();
let derived_result = derive_owner_password_key_async(password, owner_slot.kdf().clone()).await;
let derived = match derived_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let owner_root_result = crate::unwrap_key(&derived, owner_slot.wrap().nonce(), envelope.owner_slot_aad().as_slice(), owner_slot.wrap().ciphertext());
let owner_root = match owner_root_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(owner_unlock_error()),
};
let owner_control_plaintext_result = crate::decrypt_bytes(
&owner_root,
envelope.owner_control().nonce(),
envelope.compartment_aad(crate::WalletCompartmentKindV1::OwnerControl).as_slice(),
envelope.owner_control().ciphertext(),
);
let mut owner_control_plaintext = match owner_control_plaintext_result {
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());
owner_control_plaintext.zeroize();
let control = match control_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let (mut admin_secret, metadata_key, secret_key) = control.into_parts();
let admin_signing_key = ed25519_dalek::SigningKey::from_bytes(&admin_secret);
admin_secret.zeroize();
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(),
envelope.compartment_aad(crate::WalletCompartmentKindV1::Metadata).as_slice(),
envelope.metadata().ciphertext(),
);
let mut metadata_plaintext = match metadata_plaintext_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(authentication_error()),
};
let metadata_payload_result = crate::decode_metadata_payload(metadata_plaintext.as_slice());
metadata_plaintext.zeroize();
let metadata_payload = match metadata_payload_result {
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(),
envelope.compartment_aad(crate::WalletCompartmentKindV1::Secret).as_slice(),
envelope.secret().ciphertext(),
);
let mut secret_plaintext = match secret_plaintext_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(authentication_error()),
};
if secret_plaintext.len() != crate::KSPWALLET_V1_SECRET_PLAINTEXT_BYTES {
secret_plaintext.zeroize();
return std::result::Result::Err(key_material_error());
}
let keypair_result = solana_keypair::Keypair::try_from(secret_plaintext.as_slice());
let solana_keypair = match keypair_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
secret_plaintext.zeroize();
return std::result::Result::Err(key_material_error());
},
};
let secret_pubkey_result = pubkey_from_keypair_bytes(secret_plaintext.as_slice());
let secret_pubkey = match secret_pubkey_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
secret_plaintext.zeroize();
return std::result::Result::Err(error);
},
};
secret_plaintext.zeroize();
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!(
target: crate::TRACING_TARGET,
operation = "wallet_open_owner",
format_version = crate::KSPWALLET_FORMAT_VERSION_V1,
capability = "owner",
"native wallet OWNER capability opened"
);
return std::result::Result::Ok(crate::WalletOwner::from_unlocked(info, state));
}
/// Parses and verifies the OWNER-authenticated locked state without unlocking metadata or secret material.
pub fn inspect_locked_wallet_v1(source: &[u8]) -> ksp_core_lib::Result<crate::LockedWalletInfo> {
let envelope = match crate::KspWalletEnvelopeV1::parse_json(source) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let verify_result = verify_state_signature(&envelope);
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()));
}
pub(crate) async fn create_wallet_v1_from_keypair(
owner_password: crate::OwnerPassword,
view_password: std::option::Option<crate::ViewPassword>,
metadata: crate::WalletCreateMetadataV1,
solana_keypair: solana_keypair::Keypair,
) -> ksp_core_lib::Result<crate::WalletOwner> {
let owner_root = match crate::crypto::SecretKeyV1::random() {
let owner_root = match crate::SecretKeyV1::random() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let metadata_key = match crate::crypto::SecretKeyV1::random() {
let metadata_key = match crate::SecretKeyV1::random() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let secret_key = match crate::crypto::SecretKeyV1::random() {
let secret_key = match crate::SecretKeyV1::random() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut admin_secret = match crate::crypto::random_bytes::<{ crate::crypto::SECRET_KEY_BYTES }>() {
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),
};
@@ -478,20 +665,20 @@ pub(crate) async fn create_wallet_v1_from_keypair(
let mut owner_control_plaintext = crate::payload::encode_owner_control_payload(&admin_secret, &metadata_key, &secret_key);
admin_secret.zeroize();
let owner_slot_id = match crate::crypto::random_bytes::<{ crate::KSPWALLET_V1_SLOT_ID_BYTES }>() {
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) => {
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_bytes);
},
};
let owner_salt = match crate::crypto::random_bytes::<{ crate::KSPWALLET_V1_DEFAULT_KDF_SALT_BYTES }>() {
let owner_salt = match crate::random_bytes::<{ crate::KSPWALLET_V1_DEFAULT_KDF_SALT_BYTES }>() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_bytes);
},
};
let owner_kdf = crate::WalletKdfParametersV1::new_creation(owner_salt.to_vec());
let owner_wrap_nonce = match crate::crypto::random_nonce() {
let owner_wrap_nonce = match crate::random_nonce() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_bytes);
@@ -506,19 +693,19 @@ pub(crate) async fn create_wallet_v1_from_keypair(
},
};
let owner_control_nonce = match crate::crypto::random_nonce() {
let owner_control_nonce = match crate::random_nonce() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_bytes);
},
};
let metadata_nonce = match crate::crypto::random_nonce() {
let metadata_nonce = match crate::random_nonce() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_bytes);
},
};
let secret_nonce = match crate::crypto::random_nonce() {
let secret_nonce = match crate::random_nonce() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_bytes);
@@ -543,7 +730,7 @@ 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_wrapped_result = crate::crypto::wrap_key(&owner_derived, &owner_root, &owner_wrap_nonce, provisional.owner_slot_aad().as_slice());
let owner_wrapped_result = crate::wrap_key(&owner_derived, &owner_root, &owner_wrap_nonce, provisional.owner_slot_aad().as_slice());
let owner_wrapped = match owner_wrapped_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
@@ -559,7 +746,7 @@ pub(crate) async fn create_wallet_v1_from_keypair(
},
};
let owner_control_ciphertext_result = crate::crypto::encrypt_bytes(
let owner_control_ciphertext_result = crate::encrypt_bytes(
&owner_root,
&owner_control_nonce,
provisional.compartment_aad(crate::WalletCompartmentKindV1::OwnerControl).as_slice(),
@@ -573,7 +760,7 @@ pub(crate) async fn create_wallet_v1_from_keypair(
};
owner_control_plaintext.zeroize();
let metadata_ciphertext_result = crate::crypto::encrypt_bytes(
let metadata_ciphertext_result = crate::encrypt_bytes(
&metadata_key,
&metadata_nonce,
provisional.compartment_aad(crate::WalletCompartmentKindV1::Metadata).as_slice(),
@@ -587,7 +774,7 @@ pub(crate) async fn create_wallet_v1_from_keypair(
};
metadata_plaintext.zeroize();
let secret_ciphertext_result = crate::crypto::encrypt_bytes(
let secret_ciphertext_result = crate::encrypt_bytes(
&secret_key,
&secret_nonce,
provisional.compartment_aad(crate::WalletCompartmentKindV1::Secret).as_slice(),
@@ -648,185 +835,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
return std::result::Result::Ok(crate::WalletOwner::from_unlocked(info, state));
}
/// Opens the VIEW capability from a native `.kspwallet` V1 JSON document.
pub async fn open_wallet_view_v1(source: &[u8], password: crate::ViewPassword) -> ksp_core_lib::Result<crate::WalletView> {
let envelope = match crate::KspWalletEnvelopeV1::parse_json(source) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let verify_result = verify_state_signature(&envelope);
if let std::result::Result::Err(error) = verify_result {
return std::result::Result::Err(error);
}
let view_slot = match envelope.view_slot() {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Err(capability_error("Wallet VIEW capability is disabled")),
};
let derived_result = derive_view_password_key_async(password, view_slot.kdf().clone()).await;
let derived = match derived_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let aad = envelope.view_slot_aad();
let view_aad = match aad {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Err(capability_error("Wallet VIEW capability is disabled")),
};
let metadata_key_result = crate::crypto::unwrap_key(&derived, view_slot.wrap().nonce(), view_aad.as_slice(), view_slot.wrap().ciphertext());
let metadata_key = match metadata_key_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(view_unlock_error()),
};
let metadata_plaintext_result = crate::crypto::decrypt_bytes(
&metadata_key,
envelope.metadata().nonce(),
envelope.compartment_aad(crate::WalletCompartmentKindV1::Metadata).as_slice(),
envelope.metadata().ciphertext(),
);
let mut metadata_plaintext = match metadata_plaintext_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(view_unlock_error()),
};
let metadata_payload_result = crate::payload::decode_metadata_payload(metadata_plaintext.as_slice());
metadata_plaintext.zeroize();
let metadata_payload = match metadata_payload_result {
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 state = ViewStateV1::new(envelope, metadata_key);
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
operation = "wallet_open_view",
format_version = crate::KSPWALLET_FORMAT_VERSION_V1,
capability = "view",
"native wallet VIEW capability opened"
);
return std::result::Result::Ok(crate::WalletView::from_unlocked(info, state));
}
/// Opens the OWNER capability from a native `.kspwallet` V1 JSON document.
pub async fn open_wallet_owner_v1(source: &[u8], password: crate::OwnerPassword) -> ksp_core_lib::Result<crate::WalletOwner> {
let envelope = match crate::KspWalletEnvelopeV1::parse_json(source) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let verify_result = verify_state_signature(&envelope);
if let std::result::Result::Err(error) = verify_result {
return std::result::Result::Err(error);
}
let owner_slot = envelope.owner_slot();
let derived_result = derive_owner_password_key_async(password, owner_slot.kdf().clone()).await;
let derived = match derived_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let owner_root_result =
crate::crypto::unwrap_key(&derived, owner_slot.wrap().nonce(), envelope.owner_slot_aad().as_slice(), owner_slot.wrap().ciphertext());
let owner_root = match owner_root_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(owner_unlock_error()),
};
let owner_control_plaintext_result = crate::crypto::decrypt_bytes(
&owner_root,
envelope.owner_control().nonce(),
envelope.compartment_aad(crate::WalletCompartmentKindV1::OwnerControl).as_slice(),
envelope.owner_control().ciphertext(),
);
let mut owner_control_plaintext = match owner_control_plaintext_result {
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());
owner_control_plaintext.zeroize();
let control = match control_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let (mut admin_secret, metadata_key, secret_key) = control.into_parts();
let admin_signing_key = ed25519_dalek::SigningKey::from_bytes(&admin_secret);
admin_secret.zeroize();
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::crypto::decrypt_bytes(
&metadata_key,
envelope.metadata().nonce(),
envelope.compartment_aad(crate::WalletCompartmentKindV1::Metadata).as_slice(),
envelope.metadata().ciphertext(),
);
let mut metadata_plaintext = match metadata_plaintext_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(authentication_error()),
};
let metadata_payload_result = crate::payload::decode_metadata_payload(metadata_plaintext.as_slice());
metadata_plaintext.zeroize();
let metadata_payload = match metadata_payload_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let secret_plaintext_result = crate::crypto::decrypt_bytes(
&secret_key,
envelope.secret().nonce(),
envelope.compartment_aad(crate::WalletCompartmentKindV1::Secret).as_slice(),
envelope.secret().ciphertext(),
);
let mut secret_plaintext = match secret_plaintext_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(authentication_error()),
};
if secret_plaintext.len() != crate::KSPWALLET_V1_SECRET_PLAINTEXT_BYTES {
secret_plaintext.zeroize();
return std::result::Result::Err(key_material_error());
}
let keypair_result = solana_keypair::Keypair::try_from(secret_plaintext.as_slice());
let solana_keypair = match keypair_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
secret_plaintext.zeroize();
return std::result::Result::Err(key_material_error());
},
};
let secret_pubkey_result = pubkey_from_keypair_bytes(secret_plaintext.as_slice());
let secret_pubkey = match secret_pubkey_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
secret_plaintext.zeroize();
return std::result::Result::Err(error);
},
};
secret_plaintext.zeroize();
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!(
target: crate::TRACING_TARGET,
operation = "wallet_open_owner",
format_version = crate::KSPWALLET_FORMAT_VERSION_V1,
capability = "owner",
"native wallet OWNER capability opened"
);
return std::result::Result::Ok(crate::WalletOwner::from_unlocked(info, state));
}
/// Parses and verifies the OWNER-authenticated locked state without unlocking metadata or secret material.
pub fn inspect_locked_wallet_v1(source: &[u8]) -> ksp_core_lib::Result<crate::LockedWalletInfo> {
let envelope = match crate::KspWalletEnvelopeV1::parse_json(source) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let verify_result = verify_state_signature(&envelope);
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()));
}
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 {
@@ -841,24 +849,19 @@ pub(crate) fn verify_state_signature(envelope: &crate::KspWalletEnvelopeV1) -> k
};
}
enum PasswordRotationV1 {
Owner(crate::OwnerPassword),
View(crate::ViewPassword),
}
async fn rewrap_slot(
envelope: &crate::KspWalletEnvelopeV1,
slot_id: [u8; crate::KSPWALLET_V1_SLOT_ID_BYTES],
role: crate::WalletKeySlotRoleV1,
capability_key: &crate::crypto::SecretKeyV1,
capability_key: &crate::SecretKeyV1,
password: PasswordRotationV1,
) -> ksp_core_lib::Result<crate::WalletKeySlotV1> {
let salt = match crate::crypto::random_bytes::<{ crate::KSPWALLET_V1_DEFAULT_KDF_SALT_BYTES }>() {
let salt = match crate::random_bytes::<{ crate::KSPWALLET_V1_DEFAULT_KDF_SALT_BYTES }>() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let kdf = crate::WalletKdfParametersV1::new_creation(salt.to_vec());
let nonce = match crate::crypto::random_nonce() {
let nonce = match crate::random_nonce() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -874,10 +877,10 @@ async fn rewrap_slot(
slot_id,
role,
kdf.clone(),
crate::WalletKeyWrapV1::new(nonce, std::vec![0_u8; crate::crypto::SECRET_KEY_BYTES + crate::KSPWALLET_V1_AEAD_TAG_BYTES]),
crate::WalletKeyWrapV1::new(nonce, std::vec![0_u8; crate::SECRET_KEY_BYTES + crate::KSPWALLET_V1_AEAD_TAG_BYTES]),
);
let aad = crate::slot_aad(envelope, &provisional);
let wrapped = match crate::crypto::wrap_key(&derived, capability_key, &nonce, aad.as_slice()) {
let wrapped = match crate::wrap_key(&derived, capability_key, &nonce, aad.as_slice()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -925,27 +928,20 @@ fn verify_and_return(envelope: crate::KspWalletEnvelopeV1) -> ksp_core_lib::Resu
return std::result::Result::Ok(envelope);
}
struct ViewCreationMaterialV1 {
slot_id: [u8; crate::KSPWALLET_V1_SLOT_ID_BYTES],
kdf: crate::WalletKdfParametersV1,
wrap_nonce: [u8; crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES],
password: crate::ViewPassword,
}
fn prepare_view_creation(password: std::option::Option<crate::ViewPassword>) -> ksp_core_lib::Result<std::option::Option<ViewCreationMaterialV1>> {
let password = match password {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
};
let slot_id = match crate::crypto::random_bytes::<{ crate::KSPWALLET_V1_SLOT_ID_BYTES }>() {
let slot_id = match crate::random_bytes::<{ crate::KSPWALLET_V1_SLOT_ID_BYTES }>() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let salt = match crate::crypto::random_bytes::<{ crate::KSPWALLET_V1_DEFAULT_KDF_SALT_BYTES }>() {
let salt = match crate::random_bytes::<{ crate::KSPWALLET_V1_DEFAULT_KDF_SALT_BYTES }>() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wrap_nonce = match crate::crypto::random_nonce() {
let wrap_nonce = match crate::random_nonce() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -960,7 +956,7 @@ fn prepare_view_creation(password: std::option::Option<crate::ViewPassword>) ->
async fn seal_view_slot(
envelope: &crate::KspWalletEnvelopeV1,
material: std::option::Option<ViewCreationMaterialV1>,
metadata_key: &crate::crypto::SecretKeyV1,
metadata_key: &crate::SecretKeyV1,
) -> ksp_core_lib::Result<std::option::Option<crate::WalletKeySlotV1>> {
let material = match material {
std::option::Option::Some(value) => value,
@@ -975,10 +971,10 @@ async fn seal_view_slot(
material.slot_id,
crate::WalletKeySlotRoleV1::View,
material.kdf.clone(),
crate::WalletKeyWrapV1::new(material.wrap_nonce, std::vec![0_u8; crate::crypto::SECRET_KEY_BYTES + crate::KSPWALLET_V1_AEAD_TAG_BYTES]),
crate::WalletKeyWrapV1::new(material.wrap_nonce, std::vec![0_u8; crate::SECRET_KEY_BYTES + crate::KSPWALLET_V1_AEAD_TAG_BYTES]),
);
let aad = crate::slot_aad(envelope, &provisional_slot);
let wrapped_result = crate::crypto::wrap_key(&derived, metadata_key, &material.wrap_nonce, aad.as_slice());
let wrapped_result = crate::wrap_key(&derived, metadata_key, &material.wrap_nonce, aad.as_slice());
let wrapped = match wrapped_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -1005,7 +1001,7 @@ fn provisional_envelope(
owner_slot_id,
crate::WalletKeySlotRoleV1::Owner,
owner_kdf,
crate::WalletKeyWrapV1::new(owner_wrap_nonce, std::vec![0_u8; crate::crypto::SECRET_KEY_BYTES + crate::KSPWALLET_V1_AEAD_TAG_BYTES]),
crate::WalletKeyWrapV1::new(owner_wrap_nonce, std::vec![0_u8; crate::SECRET_KEY_BYTES + crate::KSPWALLET_V1_AEAD_TAG_BYTES]),
);
let (view_descriptor, view_slot) = match view_material {
std::option::Option::Some(material) => (
@@ -1014,7 +1010,7 @@ fn provisional_envelope(
material.slot_id,
crate::WalletKeySlotRoleV1::View,
material.kdf.clone(),
crate::WalletKeyWrapV1::new(material.wrap_nonce, std::vec![0_u8; crate::crypto::SECRET_KEY_BYTES + crate::KSPWALLET_V1_AEAD_TAG_BYTES]),
crate::WalletKeyWrapV1::new(material.wrap_nonce, std::vec![0_u8; crate::SECRET_KEY_BYTES + crate::KSPWALLET_V1_AEAD_TAG_BYTES]),
)),
),
std::option::Option::None => (crate::WalletViewDescriptorV1::disabled(), std::option::Option::None),
@@ -1035,19 +1031,16 @@ fn provisional_envelope(
);
}
async fn derive_owner_password_key_async(
password: crate::OwnerPassword,
kdf: crate::WalletKdfParametersV1,
) -> ksp_core_lib::Result<crate::crypto::SecretKeyV1> {
let task = tokio::task::spawn_blocking(move || return crate::crypto::derive_password_key(password.as_bytes(), &kdf));
async fn derive_owner_password_key_async(password: crate::OwnerPassword, kdf: crate::WalletKdfParametersV1) -> ksp_core_lib::Result<crate::SecretKeyV1> {
let task = tokio::task::spawn_blocking(move || return crate::derive_password_key(password.as_bytes(), &kdf));
return match task.await {
std::result::Result::Ok(result) => result,
std::result::Result::Err(_) => std::result::Result::Err(crypto_operation_error()),
};
}
async fn derive_view_password_key_async(password: crate::ViewPassword, kdf: crate::WalletKdfParametersV1) -> ksp_core_lib::Result<crate::crypto::SecretKeyV1> {
let task = tokio::task::spawn_blocking(move || return crate::crypto::derive_password_key(password.as_bytes(), &kdf));
async fn derive_view_password_key_async(password: crate::ViewPassword, kdf: crate::WalletKdfParametersV1) -> ksp_core_lib::Result<crate::SecretKeyV1> {
let task = tokio::task::spawn_blocking(move || return crate::derive_password_key(password.as_bytes(), &kdf));
return match task.await {
std::result::Result::Ok(result) => result,
std::result::Result::Err(_) => std::result::Result::Err(crypto_operation_error()),