1119 lines
53 KiB
Rust
1119 lines
53 KiB
Rust
// file: crates/ksp-wallet-lib/src/wallet.rs
|
|
// version: 10
|
|
|
|
//! In-memory native Wallet V1 create/open orchestration.
|
|
|
|
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>,
|
|
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>,
|
|
}
|
|
|
|
impl OwnerStateV1 {
|
|
/// Creates a new `OwnerStateV1` value.
|
|
pub(crate) fn new(
|
|
envelope: crate::KspWalletEnvelopeV1,
|
|
owner_root: crate::SecretKeyV1,
|
|
metadata_key: crate::SecretKeyV1,
|
|
secret_key: crate::SecretKeyV1,
|
|
admin_signing_key: ed25519_dalek::SigningKey,
|
|
solana_keypair: solana_keypair::Keypair,
|
|
) -> Self {
|
|
return Self {
|
|
envelope,
|
|
owner_root: std::option::Option::Some(owner_root),
|
|
metadata_key: std::option::Option::Some(metadata_key),
|
|
secret_key: std::option::Option::Some(secret_key),
|
|
admin_signing_key: std::option::Option::Some(admin_signing_key),
|
|
solana_keypair: std::option::Option::Some(solana_keypair),
|
|
};
|
|
}
|
|
|
|
/// 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);
|
|
drop(previous);
|
|
return;
|
|
}
|
|
|
|
/// Reconstructs one short-lived Solana keypair copy for authenticated V1 -> V2 migration.
|
|
pub(crate) fn clone_solana_keypair_for_migration(&self) -> ksp_core_lib::Result<solana_keypair::Keypair> {
|
|
let keypair = match self.solana_keypair.as_ref() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::result::Result::Err(key_material_error()),
|
|
};
|
|
let mut bytes = keypair.to_bytes();
|
|
let cloned = solana_keypair::Keypair::try_from(bytes.as_slice());
|
|
bytes.zeroize();
|
|
return match cloned {
|
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
|
std::result::Result::Err(_) => std::result::Result::Err(key_material_error()),
|
|
};
|
|
}
|
|
|
|
/// 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,
|
|
std::option::Option::None => return std::result::Result::Err(key_material_error()),
|
|
};
|
|
return match format {
|
|
crate::WalletTransferFormat::SolanaCliJson => {
|
|
let mut keypair_bytes = keypair.to_bytes();
|
|
let encoded = serde_json::to_vec(keypair_bytes.as_slice());
|
|
keypair_bytes.zeroize();
|
|
match encoded {
|
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
|
std::result::Result::Err(_) => std::result::Result::Err(key_material_error()),
|
|
}
|
|
},
|
|
crate::WalletTransferFormat::SolanaKeypairBase58 => {
|
|
let mut encoded = keypair.to_base58_string();
|
|
let output = encoded.as_bytes().to_vec();
|
|
encoded.zeroize();
|
|
std::result::Result::Ok(output)
|
|
},
|
|
};
|
|
}
|
|
|
|
/// 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,
|
|
std::option::Option::None => return std::result::Result::Err(signing_error()),
|
|
};
|
|
let mut keypair_bytes = keypair.to_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();
|
|
keypair_bytes.zeroize();
|
|
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,
|
|
std::option::Option::None => return std::result::Result::Err(crypto_operation_error()),
|
|
};
|
|
let admin_signing_key = match self.admin_signing_key.as_ref() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::result::Result::Err(crypto_operation_error()),
|
|
};
|
|
let mut plaintext = match payload.encode() {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
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::encrypt_bytes(
|
|
metadata_key,
|
|
&nonce,
|
|
self.envelope.compartment_aad(crate::WalletCompartmentKindV1::Metadata).as_slice(),
|
|
plaintext.as_slice(),
|
|
);
|
|
plaintext.zeroize();
|
|
let ciphertext = match encrypted {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let metadata = crate::WalletEncryptedCompartmentV1::new(crate::WalletCompartmentKindV1::Metadata, nonce, ciphertext);
|
|
let envelope = resign_envelope(
|
|
&self.envelope,
|
|
*self.envelope.view_descriptor(),
|
|
self.envelope.owner_slot().clone(),
|
|
self.envelope.view_slot().cloned(),
|
|
self.envelope.owner_control().clone(),
|
|
metadata,
|
|
self.envelope.secret().clone(),
|
|
admin_signing_key,
|
|
);
|
|
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((envelope, payload.into_info(crate::KSPWALLET_FORMAT_VERSION_V1, 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,
|
|
std::option::Option::None => return std::result::Result::Err(crypto_operation_error()),
|
|
};
|
|
let admin_signing_key = match self.admin_signing_key.as_ref() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::result::Result::Err(crypto_operation_error()),
|
|
};
|
|
let owner_slot = match rewrap_slot(
|
|
&self.envelope,
|
|
*self.envelope.owner_slot().slot_id(),
|
|
crate::WalletKeySlotRoleV1::Owner,
|
|
owner_root,
|
|
PasswordRotationV1::Owner(new_password),
|
|
)
|
|
.await
|
|
{
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let envelope = resign_envelope(
|
|
&self.envelope,
|
|
*self.envelope.view_descriptor(),
|
|
owner_slot,
|
|
self.envelope.view_slot().cloned(),
|
|
self.envelope.owner_control().clone(),
|
|
self.envelope.metadata().clone(),
|
|
self.envelope.secret().clone(),
|
|
admin_signing_key,
|
|
);
|
|
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,
|
|
std::option::Option::None => return std::result::Result::Err(crypto_operation_error()),
|
|
};
|
|
let view_slot = match self.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 new_slot =
|
|
match rewrap_slot(&self.envelope, *view_slot.slot_id(), crate::WalletKeySlotRoleV1::View, metadata_key, PasswordRotationV1::View(new_password))
|
|
.await
|
|
{
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let envelope = crate::KspWalletEnvelopeV1::new_internal(
|
|
*self.envelope.owner_auth_public_key(),
|
|
*self.envelope.view_descriptor(),
|
|
self.envelope.owner_slot().clone(),
|
|
std::option::Option::Some(new_slot),
|
|
self.envelope.owner_control().clone(),
|
|
self.envelope.metadata().clone(),
|
|
self.envelope.secret().clone(),
|
|
self.envelope.state_signature().clone(),
|
|
);
|
|
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"));
|
|
}
|
|
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,
|
|
) -> 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::SecretKeyV1::random() {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let view_slot =
|
|
match rewrap_slot(&self.envelope, slot_id, crate::WalletKeySlotRoleV1::View, &new_metadata_key, PasswordRotationV1::View(new_password)).await {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return self.stage_strong_view_change_with_key(std::option::Option::Some(view_slot), new_metadata_key);
|
|
}
|
|
|
|
fn stage_strong_view_change(
|
|
&self,
|
|
view_slot: std::option::Option<crate::WalletKeySlotV1>,
|
|
) -> 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),
|
|
};
|
|
return self.stage_strong_view_change_with_key(view_slot, new_metadata_key);
|
|
}
|
|
|
|
fn stage_strong_view_change_with_key(
|
|
&self,
|
|
view_slot: std::option::Option<crate::WalletKeySlotV1>,
|
|
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()),
|
|
};
|
|
let owner_root = match self.owner_root.as_ref() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::result::Result::Err(crypto_operation_error()),
|
|
};
|
|
let secret_key = match self.secret_key.as_ref() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::result::Result::Err(crypto_operation_error()),
|
|
};
|
|
let admin_signing_key = match self.admin_signing_key.as_ref() {
|
|
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(),
|
|
self.envelope.compartment_aad(crate::WalletCompartmentKindV1::Metadata).as_slice(),
|
|
self.envelope.metadata().ciphertext(),
|
|
);
|
|
let mut metadata_plaintext = match current_plaintext_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::result::Result::Err(authentication_error()),
|
|
};
|
|
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::encrypt_bytes(
|
|
&new_metadata_key,
|
|
&metadata_nonce,
|
|
self.envelope.compartment_aad(crate::WalletCompartmentKindV1::Metadata).as_slice(),
|
|
metadata_plaintext.as_slice(),
|
|
);
|
|
metadata_plaintext.zeroize();
|
|
let metadata_ciphertext = match metadata_ciphertext_result {
|
|
std::result::Result::Ok(value) => value,
|
|
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::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,
|
|
std::result::Result::Err(error) => {
|
|
owner_control_plaintext.zeroize();
|
|
return std::result::Result::Err(error);
|
|
},
|
|
};
|
|
let owner_control_ciphertext_result = crate::encrypt_bytes(
|
|
owner_root,
|
|
&owner_control_nonce,
|
|
self.envelope.compartment_aad(crate::WalletCompartmentKindV1::OwnerControl).as_slice(),
|
|
owner_control_plaintext.as_slice(),
|
|
);
|
|
owner_control_plaintext.zeroize();
|
|
let owner_control_ciphertext = match owner_control_ciphertext_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
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(),
|
|
};
|
|
let envelope = resign_envelope(
|
|
&self.envelope,
|
|
view_descriptor,
|
|
self.envelope.owner_slot().clone(),
|
|
view_slot,
|
|
owner_control,
|
|
metadata,
|
|
self.envelope.secret().clone(),
|
|
admin_signing_key,
|
|
);
|
|
let envelope = match verify_and_return(envelope) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return std::result::Result::Ok((envelope, new_metadata_key));
|
|
}
|
|
}
|
|
|
|
impl std::ops::Drop for OwnerStateV1 {
|
|
fn drop(&mut self) {
|
|
let owner_root = self.owner_root.take();
|
|
let metadata_key = self.metadata_key.take();
|
|
let secret_key = self.secret_key.take();
|
|
let admin_signing_key = self.admin_signing_key.take();
|
|
let solana_keypair = self.solana_keypair.take();
|
|
drop(owner_root);
|
|
drop(metadata_key);
|
|
drop(secret_key);
|
|
drop(admin_signing_key);
|
|
drop(solana_keypair);
|
|
}
|
|
}
|
|
|
|
/// 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,
|
|
std::option::Option::None => return std::result::Result::Err(crypto_operation_error()),
|
|
};
|
|
let view_slot = match self.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 new_slot =
|
|
match rewrap_slot(&self.envelope, *view_slot.slot_id(), crate::WalletKeySlotRoleV1::View, metadata_key, PasswordRotationV1::View(new_password))
|
|
.await
|
|
{
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let envelope = crate::KspWalletEnvelopeV1::new_internal(
|
|
*self.envelope.owner_auth_public_key(),
|
|
*self.envelope.view_descriptor(),
|
|
self.envelope.owner_slot().clone(),
|
|
std::option::Option::Some(new_slot),
|
|
self.envelope.owner_control().clone(),
|
|
self.envelope.metadata().clone(),
|
|
self.envelope.secret().clone(),
|
|
self.envelope.state_signature().clone(),
|
|
);
|
|
return verify_and_return(envelope);
|
|
}
|
|
}
|
|
|
|
impl std::ops::Drop for ViewStateV1 {
|
|
fn drop(&mut self) {
|
|
let metadata_key = self.metadata_key.take();
|
|
drop(metadata_key);
|
|
}
|
|
}
|
|
|
|
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
|
|
/// the caller also wants no-clobber native filesystem publication.
|
|
pub async fn create_wallet_v1(
|
|
owner_password: crate::OwnerPassword,
|
|
view_password: std::option::Option<crate::ViewPassword>,
|
|
metadata: crate::WalletCreateMetadataV1,
|
|
) -> ksp_core_lib::Result<crate::WalletOwner> {
|
|
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),
|
|
};
|
|
let solana_keypair = solana_keypair::Keypair::new_from_array(solana_secret);
|
|
solana_secret.zeroize();
|
|
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::KSPWALLET_FORMAT_VERSION_V1, 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, crate::ViewState::V1(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::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::KSPWALLET_FORMAT_VERSION_V1, crate::WalletCapability::Owner);
|
|
let state = OwnerStateV1::new(envelope, owner_root, metadata_key, secret_key, admin_signing_key, solana_keypair);
|
|
ksp_logging_lib::debug!(
|
|
target: crate::TRACING_TARGET,
|
|
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, crate::OwnerState::V1(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(crate::KSPWALLET_FORMAT_VERSION_V1, 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>,
|
|
metadata: crate::WalletCreateMetadataV1,
|
|
solana_keypair: solana_keypair::Keypair,
|
|
) -> ksp_core_lib::Result<crate::WalletOwner> {
|
|
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::SecretKeyV1::random() {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
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::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 {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
admin_secret.zeroize();
|
|
solana_keypair_bytes.zeroize();
|
|
return std::result::Result::Err(error);
|
|
},
|
|
};
|
|
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) => {
|
|
admin_secret.zeroize();
|
|
solana_keypair_bytes.zeroize();
|
|
return std::result::Result::Err(error);
|
|
},
|
|
};
|
|
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) => {
|
|
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_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::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 view_material_result = prepare_view_creation(view_password);
|
|
let view_material = match view_material_result {
|
|
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_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::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::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 provisional = provisional_envelope(
|
|
owner_auth_public_key,
|
|
owner_slot_id,
|
|
owner_kdf.clone(),
|
|
owner_wrap_nonce,
|
|
view_material.as_ref(),
|
|
owner_control_nonce,
|
|
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,
|
|
std::result::Result::Err(error) => {
|
|
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_bytes);
|
|
},
|
|
};
|
|
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) => {
|
|
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,
|
|
std::result::Result::Err(error) => {
|
|
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,
|
|
provisional.compartment_aad(crate::WalletCompartmentKindV1::OwnerControl).as_slice(),
|
|
owner_control_plaintext.as_slice(),
|
|
);
|
|
let owner_control_ciphertext = match owner_control_ciphertext_result {
|
|
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);
|
|
},
|
|
};
|
|
owner_control_plaintext.zeroize();
|
|
let metadata_ciphertext_result = crate::encrypt_bytes(
|
|
&metadata_key,
|
|
&metadata_nonce,
|
|
provisional.compartment_aad(crate::WalletCompartmentKindV1::Metadata).as_slice(),
|
|
metadata_plaintext.as_slice(),
|
|
);
|
|
let metadata_ciphertext = match metadata_ciphertext_result {
|
|
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);
|
|
},
|
|
};
|
|
metadata_plaintext.zeroize();
|
|
let secret_ciphertext_result = crate::encrypt_bytes(
|
|
&secret_key,
|
|
&secret_nonce,
|
|
provisional.compartment_aad(crate::WalletCompartmentKindV1::Secret).as_slice(),
|
|
solana_keypair_bytes.as_slice(),
|
|
);
|
|
let secret_ciphertext = match secret_ciphertext_result {
|
|
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);
|
|
},
|
|
};
|
|
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() {
|
|
std::option::Option::Some(slot) => crate::WalletViewDescriptorV1::enabled_for_slot(*slot.slot_id()),
|
|
std::option::Option::None => crate::WalletViewDescriptorV1::disabled(),
|
|
};
|
|
let owner_control = crate::WalletEncryptedCompartmentV1::new(crate::WalletCompartmentKindV1::OwnerControl, owner_control_nonce, owner_control_ciphertext);
|
|
let metadata_compartment = crate::WalletEncryptedCompartmentV1::new(crate::WalletCompartmentKindV1::Metadata, metadata_nonce, metadata_ciphertext);
|
|
let secret_compartment = crate::WalletEncryptedCompartmentV1::new(crate::WalletCompartmentKindV1::Secret, secret_nonce, secret_ciphertext);
|
|
let unsigned = crate::KspWalletEnvelopeV1::new_internal(
|
|
owner_auth_public_key,
|
|
view_descriptor,
|
|
owner_slot.clone(),
|
|
view_slot.clone(),
|
|
owner_control.clone(),
|
|
metadata_compartment.clone(),
|
|
secret_compartment.clone(),
|
|
crate::WalletStateSignatureV1::new([0_u8; crate::KSPWALLET_V1_ED25519_SIGNATURE_BYTES]),
|
|
);
|
|
let signature = admin_signing_key.sign(unsigned.state_transcript().as_slice()).to_bytes();
|
|
let envelope = crate::KspWalletEnvelopeV1::new_internal(
|
|
owner_auth_public_key,
|
|
view_descriptor,
|
|
owner_slot,
|
|
view_slot,
|
|
owner_control,
|
|
metadata_compartment,
|
|
secret_compartment,
|
|
crate::WalletStateSignatureV1::new(signature),
|
|
);
|
|
let verify_result = verify_state_signature(&envelope);
|
|
if let std::result::Result::Err(error) = verify_result {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
let info = metadata_payload.into_info(crate::KSPWALLET_FORMAT_VERSION_V1, crate::WalletCapability::Owner);
|
|
let state = OwnerStateV1::new(envelope, owner_root, metadata_key, secret_key, admin_signing_key, solana_keypair);
|
|
ksp_logging_lib::debug!(
|
|
target: crate::TRACING_TARGET,
|
|
operation = "wallet_create",
|
|
format_version = crate::KSPWALLET_FORMAT_VERSION_V1,
|
|
view_enabled = state.envelope().view_descriptor().enabled(),
|
|
"native wallet created in memory"
|
|
);
|
|
return std::result::Result::Ok(crate::WalletOwner::from_unlocked(info, crate::OwnerState::V1(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 {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::result::Result::Err(authentication_error()),
|
|
};
|
|
let signature = ed25519_dalek::Signature::from_bytes(envelope.state_signature().signature());
|
|
let verify_result = verifying_key.verify_strict(envelope.state_transcript().as_slice(), &signature);
|
|
return match verify_result {
|
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
|
std::result::Result::Err(_) => std::result::Result::Err(authentication_error()),
|
|
};
|
|
}
|
|
|
|
async fn rewrap_slot(
|
|
envelope: &crate::KspWalletEnvelopeV1,
|
|
slot_id: [u8; crate::KSPWALLET_V1_SLOT_ID_BYTES],
|
|
role: crate::WalletKeySlotRoleV1,
|
|
capability_key: &crate::SecretKeyV1,
|
|
password: PasswordRotationV1,
|
|
) -> ksp_core_lib::Result<crate::WalletKeySlotV1> {
|
|
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::random_nonce() {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let derived_result = match password {
|
|
PasswordRotationV1::Owner(value) => derive_owner_password_key_async(value, kdf.clone()).await,
|
|
PasswordRotationV1::View(value) => derive_view_password_key_async(value, 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 provisional = crate::WalletKeySlotV1::new(
|
|
slot_id,
|
|
role,
|
|
kdf.clone(),
|
|
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::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),
|
|
};
|
|
return std::result::Result::Ok(crate::WalletKeySlotV1::new(slot_id, role, kdf, crate::WalletKeyWrapV1::new(nonce, wrapped)));
|
|
}
|
|
|
|
fn resign_envelope(
|
|
base: &crate::KspWalletEnvelopeV1,
|
|
view_descriptor: crate::WalletViewDescriptorV1,
|
|
owner_slot: crate::WalletKeySlotV1,
|
|
view_slot: std::option::Option<crate::WalletKeySlotV1>,
|
|
owner_control: crate::WalletEncryptedCompartmentV1,
|
|
metadata: crate::WalletEncryptedCompartmentV1,
|
|
secret: crate::WalletEncryptedCompartmentV1,
|
|
admin_signing_key: &ed25519_dalek::SigningKey,
|
|
) -> crate::KspWalletEnvelopeV1 {
|
|
let unsigned = crate::KspWalletEnvelopeV1::new_internal(
|
|
*base.owner_auth_public_key(),
|
|
view_descriptor,
|
|
owner_slot.clone(),
|
|
view_slot.clone(),
|
|
owner_control.clone(),
|
|
metadata.clone(),
|
|
secret.clone(),
|
|
crate::WalletStateSignatureV1::new([0_u8; crate::KSPWALLET_V1_ED25519_SIGNATURE_BYTES]),
|
|
);
|
|
let signature = admin_signing_key.sign(unsigned.state_transcript().as_slice()).to_bytes();
|
|
return crate::KspWalletEnvelopeV1::new_internal(
|
|
*base.owner_auth_public_key(),
|
|
view_descriptor,
|
|
owner_slot,
|
|
view_slot,
|
|
owner_control,
|
|
metadata,
|
|
secret,
|
|
crate::WalletStateSignatureV1::new(signature),
|
|
);
|
|
}
|
|
|
|
fn verify_and_return(envelope: crate::KspWalletEnvelopeV1) -> ksp_core_lib::Result<crate::KspWalletEnvelopeV1> {
|
|
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(envelope);
|
|
}
|
|
|
|
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::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::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::random_nonce() {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return std::result::Result::Ok(std::option::Option::Some(ViewCreationMaterialV1 {
|
|
slot_id,
|
|
kdf: crate::WalletKdfParametersV1::new_creation(salt.to_vec()),
|
|
wrap_nonce,
|
|
password,
|
|
}));
|
|
}
|
|
|
|
async fn seal_view_slot(
|
|
envelope: &crate::KspWalletEnvelopeV1,
|
|
material: std::option::Option<ViewCreationMaterialV1>,
|
|
metadata_key: &crate::SecretKeyV1,
|
|
) -> ksp_core_lib::Result<std::option::Option<crate::WalletKeySlotV1>> {
|
|
let material = match material {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
|
|
};
|
|
let derived_result = derive_view_password_key_async(material.password, material.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 provisional_slot = crate::WalletKeySlotV1::new(
|
|
material.slot_id,
|
|
crate::WalletKeySlotRoleV1::View,
|
|
material.kdf.clone(),
|
|
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::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),
|
|
};
|
|
return std::result::Result::Ok(std::option::Option::Some(crate::WalletKeySlotV1::new(
|
|
material.slot_id,
|
|
crate::WalletKeySlotRoleV1::View,
|
|
material.kdf,
|
|
crate::WalletKeyWrapV1::new(material.wrap_nonce, wrapped),
|
|
)));
|
|
}
|
|
|
|
fn provisional_envelope(
|
|
owner_auth_public_key: [u8; crate::KSPWALLET_V1_ED25519_PUBLIC_KEY_BYTES],
|
|
owner_slot_id: [u8; crate::KSPWALLET_V1_SLOT_ID_BYTES],
|
|
owner_kdf: crate::WalletKdfParametersV1,
|
|
owner_wrap_nonce: [u8; crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES],
|
|
view_material: std::option::Option<&ViewCreationMaterialV1>,
|
|
owner_control_nonce: [u8; crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES],
|
|
metadata_nonce: [u8; crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES],
|
|
secret_nonce: [u8; crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES],
|
|
) -> crate::KspWalletEnvelopeV1 {
|
|
let owner_slot = crate::WalletKeySlotV1::new(
|
|
owner_slot_id,
|
|
crate::WalletKeySlotRoleV1::Owner,
|
|
owner_kdf,
|
|
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) => (
|
|
crate::WalletViewDescriptorV1::enabled_for_slot(material.slot_id),
|
|
std::option::Option::Some(crate::WalletKeySlotV1::new(
|
|
material.slot_id,
|
|
crate::WalletKeySlotRoleV1::View,
|
|
material.kdf.clone(),
|
|
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),
|
|
};
|
|
return crate::KspWalletEnvelopeV1::new_internal(
|
|
owner_auth_public_key,
|
|
view_descriptor,
|
|
owner_slot,
|
|
view_slot,
|
|
crate::WalletEncryptedCompartmentV1::new(
|
|
crate::WalletCompartmentKindV1::OwnerControl,
|
|
owner_control_nonce,
|
|
std::vec![0_u8; crate::KSPWALLET_V1_AEAD_TAG_BYTES],
|
|
),
|
|
crate::WalletEncryptedCompartmentV1::new(crate::WalletCompartmentKindV1::Metadata, metadata_nonce, std::vec![0_u8; crate::KSPWALLET_V1_AEAD_TAG_BYTES]),
|
|
crate::WalletEncryptedCompartmentV1::new(crate::WalletCompartmentKindV1::Secret, secret_nonce, std::vec![0_u8; crate::KSPWALLET_V1_AEAD_TAG_BYTES]),
|
|
crate::WalletStateSignatureV1::new([0_u8; crate::KSPWALLET_V1_ED25519_SIGNATURE_BYTES]),
|
|
);
|
|
}
|
|
|
|
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::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()),
|
|
};
|
|
}
|
|
|
|
fn pubkey_from_keypair_bytes(bytes: &[u8]) -> ksp_core_lib::Result<ksp_core_lib::Pubkey> {
|
|
let public_slice = match bytes.get(32..64) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::result::Result::Err(key_material_error()),
|
|
};
|
|
let public_array = match <[u8; 32]>::try_from(public_slice) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::result::Result::Err(key_material_error()),
|
|
};
|
|
return std::result::Result::Ok(ksp_core_lib::Pubkey::new_from_array(public_array));
|
|
}
|
|
|
|
fn cleanup_create_error<T>(
|
|
error: ksp_core_lib::Error,
|
|
owner_control_plaintext: &mut [u8],
|
|
metadata_plaintext: &mut [u8],
|
|
solana_keypair_bytes: &mut [u8],
|
|
) -> ksp_core_lib::Result<T> {
|
|
owner_control_plaintext.zeroize();
|
|
metadata_plaintext.zeroize();
|
|
solana_keypair_bytes.zeroize();
|
|
return std::result::Result::Err(error);
|
|
}
|
|
|
|
fn authentication_error() -> ksp_core_lib::Error {
|
|
return ksp_core_lib::Error::new(crate::ERROR_CODE_AUTHENTICATION_FAILED, "Wallet OWNER-authenticated state verification failed");
|
|
}
|
|
|
|
fn crypto_operation_error() -> ksp_core_lib::Error {
|
|
return ksp_core_lib::Error::new(crate::ERROR_CODE_CRYPTO_OPERATION_FAILED, "Wallet blocking cryptographic operation could not complete");
|
|
}
|
|
|
|
fn view_unlock_error() -> ksp_core_lib::Error {
|
|
return ksp_core_lib::Error::new(crate::ERROR_CODE_VIEW_UNLOCK_FAILED, "Wallet VIEW capability could not be unlocked");
|
|
}
|
|
|
|
fn owner_unlock_error() -> ksp_core_lib::Error {
|
|
return ksp_core_lib::Error::new(crate::ERROR_CODE_OWNER_UNLOCK_FAILED, "Wallet OWNER capability could not be unlocked");
|
|
}
|
|
|
|
fn capability_error(message: &'static str) -> ksp_core_lib::Error {
|
|
return ksp_core_lib::Error::new(crate::ERROR_CODE_CAPABILITY_INSUFFICIENT, message);
|
|
}
|
|
|
|
fn signing_error() -> ksp_core_lib::Error {
|
|
return ksp_core_lib::Error::new(crate::ERROR_CODE_SIGNATURE_FAILED, "Wallet Solana message signing failed");
|
|
}
|
|
|
|
fn key_material_error() -> ksp_core_lib::Error {
|
|
return ksp_core_lib::Error::new(crate::ERROR_CODE_KEY_MATERIAL_INVALID, "Wallet Solana key material is invalid or inconsistent");
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "../unit_tests/wallet.rs"]
|
|
mod tests;
|