218 lines
10 KiB
Rust
218 lines
10 KiB
Rust
// file: crates/ksp-wallet-lib/src/runtime.rs
|
|
// version: 2
|
|
|
|
//! Version-neutral unlocked Wallet state dispatch used by stable OWNER/VIEW handles.
|
|
|
|
/// One staged native envelope produced by an authenticated administration operation.
|
|
pub(crate) enum StagedEnvelope {
|
|
V1(crate::KspWalletEnvelopeV1),
|
|
V2(crate::KspWalletEnvelopeV2),
|
|
}
|
|
|
|
/// Version-neutral OWNER runtime state.
|
|
pub(crate) enum OwnerState {
|
|
V1(crate::OwnerStateV1),
|
|
V2(crate::OwnerStateV2),
|
|
}
|
|
|
|
impl OwnerState {
|
|
/// Exports the Solana identity through the requested external transfer format.
|
|
pub(crate) fn export_transfer(&self, format: crate::WalletTransferFormat) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
|
|
return match self {
|
|
Self::V1(state) => state.export_transfer(format),
|
|
Self::V2(state) => state.export_transfer(format),
|
|
};
|
|
}
|
|
|
|
/// Signs one message with the OWNER-authorized Solana identity.
|
|
pub(crate) fn sign_message(&self, message: &[u8]) -> ksp_core_lib::Result<[u8; crate::KSPWALLET_SOLANA_SIGNATURE_BYTES]> {
|
|
return match self {
|
|
Self::V1(state) => state.sign_message(message),
|
|
Self::V2(state) => state.sign_message(message),
|
|
};
|
|
}
|
|
|
|
/// Stages an authenticated metadata replacement without publishing it.
|
|
pub(crate) fn stage_metadata_payload(&self, payload: crate::MetadataPayloadV1) -> ksp_core_lib::Result<(StagedEnvelope, crate::WalletInfo)> {
|
|
return match self {
|
|
Self::V1(state) => state.stage_metadata_payload(payload).map(|(envelope, info)| return (StagedEnvelope::V1(envelope), info)),
|
|
Self::V2(state) => state.stage_metadata_payload(payload).map(|(envelope, info)| return (StagedEnvelope::V2(envelope), info)),
|
|
};
|
|
}
|
|
|
|
/// Stages an OWNER credential rotation in the state native format.
|
|
pub(crate) async fn stage_owner_password_rotation(&self, password: crate::OwnerPassword) -> ksp_core_lib::Result<StagedEnvelope> {
|
|
return match self {
|
|
Self::V1(state) => state.stage_owner_password_rotation(password).await.map(StagedEnvelope::V1),
|
|
Self::V2(state) => state.stage_owner_password_rotation(password).await.map(StagedEnvelope::V2),
|
|
};
|
|
}
|
|
|
|
/// Stages a VIEW credential rotation in the state native format.
|
|
pub(crate) async fn stage_view_password_rotation(&self, password: crate::ViewPassword) -> ksp_core_lib::Result<StagedEnvelope> {
|
|
return match self {
|
|
Self::V1(state) => state.stage_view_password_rotation(password).await.map(StagedEnvelope::V1),
|
|
Self::V2(state) => state.stage_view_password_rotation(password).await.map(StagedEnvelope::V2),
|
|
};
|
|
}
|
|
|
|
/// Stages strong VIEW disable while retaining OWNER authority.
|
|
pub(crate) fn stage_disable_view(&self) -> ksp_core_lib::Result<(StagedEnvelope, crate::SecretKeyV1)> {
|
|
return match self {
|
|
Self::V1(state) => state.stage_disable_view().map(|(envelope, key)| return (StagedEnvelope::V1(envelope), key)),
|
|
Self::V2(state) => state.stage_disable_view().map(|(envelope, key)| return (StagedEnvelope::V2(envelope), key)),
|
|
};
|
|
}
|
|
|
|
/// Stages strong VIEW recreation under a new credential.
|
|
pub(crate) async fn stage_recreate_view(&self, password: crate::ViewPassword) -> ksp_core_lib::Result<(StagedEnvelope, crate::SecretKeyV1)> {
|
|
return match self {
|
|
Self::V1(state) => state.stage_recreate_view(password).await.map(|(envelope, key)| return (StagedEnvelope::V1(envelope), key)),
|
|
Self::V2(state) => state.stage_recreate_view(password).await.map(|(envelope, key)| return (StagedEnvelope::V2(envelope), key)),
|
|
};
|
|
}
|
|
|
|
/// Atomically publishes a staged envelope after format-specific state-conflict verification.
|
|
pub(crate) async fn persist_staged(&self, destination: std::path::PathBuf, envelope: &StagedEnvelope) -> ksp_core_lib::Result<()> {
|
|
return match (self, envelope) {
|
|
(Self::V1(state), StagedEnvelope::V1(staged)) => {
|
|
let serialized = match staged.to_json_bytes() {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
crate::replace_wallet_file_v1(destination, state.envelope().clone(), serialized).await
|
|
},
|
|
(Self::V2(state), StagedEnvelope::V2(staged)) => {
|
|
let serialized = match staged.to_binary_bytes() {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
crate::replace_wallet_file_v2(destination, state.envelope().clone(), serialized).await
|
|
},
|
|
_ => std::result::Result::Err(version_state_error()),
|
|
};
|
|
}
|
|
|
|
/// Replaces the in-memory authenticated envelope after successful persistence.
|
|
pub(crate) fn apply_envelope(&mut self, envelope: StagedEnvelope) -> ksp_core_lib::Result<()> {
|
|
return match (self, envelope) {
|
|
(Self::V1(state), StagedEnvelope::V1(value)) => {
|
|
state.apply_envelope(value);
|
|
std::result::Result::Ok(())
|
|
},
|
|
(Self::V2(state), StagedEnvelope::V2(value)) => {
|
|
state.apply_envelope(value);
|
|
std::result::Result::Ok(())
|
|
},
|
|
_ => std::result::Result::Err(version_state_error()),
|
|
};
|
|
}
|
|
|
|
/// Replaces the envelope and metadata key after a strong VIEW administration operation.
|
|
pub(crate) fn apply_strong_view_state(&mut self, envelope: StagedEnvelope, metadata_key: crate::SecretKeyV1) -> ksp_core_lib::Result<()> {
|
|
return match (self, envelope) {
|
|
(Self::V1(state), StagedEnvelope::V1(value)) => {
|
|
state.apply_strong_view_state(value, metadata_key);
|
|
std::result::Result::Ok(())
|
|
},
|
|
(Self::V2(state), StagedEnvelope::V2(value)) => {
|
|
state.apply_strong_view_state(value, metadata_key);
|
|
std::result::Result::Ok(())
|
|
},
|
|
_ => std::result::Result::Err(version_state_error()),
|
|
};
|
|
}
|
|
|
|
/// Serializes the authenticated state in its native V1 or V2 wire format.
|
|
pub(crate) fn native_bytes(&self) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
|
|
return match self {
|
|
Self::V1(state) => state.envelope().to_json_bytes(),
|
|
Self::V2(state) => state.envelope().to_binary_bytes(),
|
|
};
|
|
}
|
|
|
|
/// Serializes only V1 state as JSON and rejects V2 instead of converting formats.
|
|
pub(crate) fn json_bytes_v1(&self) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
|
|
return match self {
|
|
Self::V1(state) => state.envelope().to_json_bytes(),
|
|
Self::V2(_) => std::result::Result::Err(v1_serialization_error()),
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Version-neutral VIEW runtime state.
|
|
pub(crate) enum ViewState {
|
|
V1(crate::ViewStateV1),
|
|
V2(crate::ViewStateV2),
|
|
}
|
|
|
|
impl ViewState {
|
|
/// Stages a VIEW credential rotation in the state native format.
|
|
pub(crate) async fn stage_view_password_rotation(&self, password: crate::ViewPassword) -> ksp_core_lib::Result<StagedEnvelope> {
|
|
return match self {
|
|
Self::V1(state) => state.stage_view_password_rotation(password).await.map(StagedEnvelope::V1),
|
|
Self::V2(state) => state.stage_view_password_rotation(password).await.map(StagedEnvelope::V2),
|
|
};
|
|
}
|
|
|
|
/// Atomically publishes a staged envelope after format-specific state-conflict verification.
|
|
pub(crate) async fn persist_staged(&self, destination: std::path::PathBuf, envelope: &StagedEnvelope) -> ksp_core_lib::Result<()> {
|
|
return match (self, envelope) {
|
|
(Self::V1(state), StagedEnvelope::V1(staged)) => {
|
|
let serialized = match staged.to_json_bytes() {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
crate::replace_wallet_file_v1(destination, state.envelope().clone(), serialized).await
|
|
},
|
|
(Self::V2(state), StagedEnvelope::V2(staged)) => {
|
|
let serialized = match staged.to_binary_bytes() {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
crate::replace_wallet_file_v2(destination, state.envelope().clone(), serialized).await
|
|
},
|
|
_ => std::result::Result::Err(version_state_error()),
|
|
};
|
|
}
|
|
|
|
/// Replaces the in-memory authenticated envelope after successful persistence.
|
|
pub(crate) fn apply_envelope(&mut self, envelope: StagedEnvelope) -> ksp_core_lib::Result<()> {
|
|
return match (self, envelope) {
|
|
(Self::V1(state), StagedEnvelope::V1(value)) => {
|
|
state.apply_envelope(value);
|
|
std::result::Result::Ok(())
|
|
},
|
|
(Self::V2(state), StagedEnvelope::V2(value)) => {
|
|
state.apply_envelope(value);
|
|
std::result::Result::Ok(())
|
|
},
|
|
_ => std::result::Result::Err(version_state_error()),
|
|
};
|
|
}
|
|
|
|
/// Serializes the authenticated state in its native V1 or V2 wire format.
|
|
pub(crate) fn native_bytes(&self) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
|
|
return match self {
|
|
Self::V1(state) => state.envelope().to_json_bytes(),
|
|
Self::V2(state) => state.envelope().to_binary_bytes(),
|
|
};
|
|
}
|
|
|
|
/// Serializes only V1 state as JSON and rejects V2 instead of converting formats.
|
|
pub(crate) fn json_bytes_v1(&self) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
|
|
return match self {
|
|
Self::V1(state) => state.envelope().to_json_bytes(),
|
|
Self::V2(_) => std::result::Result::Err(v1_serialization_error()),
|
|
};
|
|
}
|
|
}
|
|
|
|
fn version_state_error() -> ksp_core_lib::Error {
|
|
return ksp_core_lib::Error::new(crate::ERROR_CODE_FORMAT_INVALID, "Wallet runtime state and staged format are inconsistent");
|
|
}
|
|
|
|
fn v1_serialization_error() -> ksp_core_lib::Error {
|
|
return ksp_core_lib::Error::new(crate::ERROR_CODE_FORMAT_INVALID, "Wallet is not a V1 JSON document");
|
|
}
|