v0.2.5-pre.010.fix.001

This commit is contained in:
2026-08-20 11:17:12 +02:00
parent 5bf9651038
commit c0b131bf6f
97 changed files with 2277 additions and 655 deletions

View File

@@ -1,11 +1,12 @@
// file: crates/ksp-wallet-lib/src/payload.rs
// version: 4
// version: 5
//! Plaintext payload codecs protected inside native `.kspwallet` V1 compartments.
use base64::Engine; // rust-rules: derive-import
use std::str::FromStr; // rust-rules: derive-import
use base64::Engine; // rust-rules: trait-import
use std::str::FromStr; // rust-rules: trait-import
/// Crate-internal `MetadataPayloadV1` state shared across the owning crate.
pub(crate) struct MetadataPayloadV1 {
pubkey: ksp_core_lib::Pubkey,
alias: std::option::Option<std::string::String>,
@@ -13,16 +14,19 @@ pub(crate) struct MetadataPayloadV1 {
}
impl crate::MetadataPayloadV1 {
/// Returns the current pubkey.
pub(crate) const fn pubkey(&self) -> &ksp_core_lib::Pubkey {
return &self.pubkey;
}
/// Builds `MetadataPayloadV1` from info.
pub(crate) fn from_info(info: &crate::WalletInfo) -> Self {
let alias = info.alias().map(std::string::String::from);
let notes = info.notes().to_vec();
return Self { pubkey: *info.pubkey(), alias, notes };
}
/// Updates alias.
pub(crate) fn set_alias(&mut self, alias: std::option::Option<std::string::String>) -> ksp_core_lib::Result<()> {
let validation_result = validate_alias(alias.as_deref());
if let std::result::Result::Err(error) = validation_result {
@@ -32,6 +36,7 @@ impl crate::MetadataPayloadV1 {
return std::result::Result::Ok(());
}
/// Adds note.
pub(crate) fn add_note(&mut self, text: std::string::String) -> ksp_core_lib::Result<std::string::String> {
if self.notes.len() >= crate::KSPWALLET_V1_MAX_NOTES {
return std::result::Result::Err(metadata_error("Wallet note count exceeds the V1 limit", "metadata.notes"));
@@ -40,7 +45,7 @@ impl crate::MetadataPayloadV1 {
if let std::result::Result::Err(error) = validation_result {
return std::result::Result::Err(error);
}
let note_id_bytes = match crate::crypto::random_bytes::<{ crate::KSPWALLET_V1_NOTE_ID_BYTES }>() {
let note_id_bytes = match crate::random_bytes::<{ crate::KSPWALLET_V1_NOTE_ID_BYTES }>() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -54,6 +59,7 @@ impl crate::MetadataPayloadV1 {
return std::result::Result::Ok(note_id);
}
/// Updates note.
pub(crate) fn update_note(&mut self, note_id: &str, text: std::string::String) -> ksp_core_lib::Result<()> {
let validation_result = validate_note_text(text.as_str());
if let std::result::Result::Err(error) = validation_result {
@@ -70,6 +76,7 @@ impl crate::MetadataPayloadV1 {
return std::result::Result::Err(note_not_found_error());
}
/// Deletes note.
pub(crate) fn delete_note(&mut self, note_id: &str) -> ksp_core_lib::Result<()> {
let mut index = 0_usize;
while index < self.notes.len() {
@@ -82,6 +89,7 @@ impl crate::MetadataPayloadV1 {
return std::result::Result::Err(note_not_found_error());
}
/// Executes the crate-internal encode operation for `MetadataPayloadV1`.
pub(crate) fn encode(&self) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
let validation_result = validate_alias(self.alias.as_deref());
if let std::result::Result::Err(error) = validation_result {
@@ -110,23 +118,26 @@ impl crate::MetadataPayloadV1 {
return std::result::Result::Ok(serialized);
}
/// Consumes this value and returns info.
pub(crate) fn into_info(self, capability: crate::WalletCapability) -> crate::WalletInfo {
return crate::WalletInfo::new(capability, self.pubkey, self.alias, self.notes);
}
}
/// Crate-internal `OwnerControlMaterialV1` state shared across the owning crate.
pub(crate) struct OwnerControlMaterialV1 {
admin_signing_secret: [u8; crate::crypto::SECRET_KEY_BYTES],
admin_signing_secret: [u8; crate::SECRET_KEY_BYTES],
metadata_key: crate::SecretKeyV1,
secret_key: crate::SecretKeyV1,
}
impl OwnerControlMaterialV1 {
pub(crate) fn into_parts(mut self) -> ([u8; crate::crypto::SECRET_KEY_BYTES], crate::SecretKeyV1, crate::SecretKeyV1) {
let mut admin_signing_secret = [0_u8; crate::crypto::SECRET_KEY_BYTES];
/// Consumes this value and returns parts.
pub(crate) fn into_parts(mut self) -> ([u8; crate::SECRET_KEY_BYTES], crate::SecretKeyV1, crate::SecretKeyV1) {
let mut admin_signing_secret = [0_u8; crate::SECRET_KEY_BYTES];
std::mem::swap(&mut admin_signing_secret, &mut self.admin_signing_secret);
let metadata_key = std::mem::replace(&mut self.metadata_key, crate::SecretKeyV1::from_bytes([0_u8; crate::crypto::SECRET_KEY_BYTES]));
let secret_key = std::mem::replace(&mut self.secret_key, crate::SecretKeyV1::from_bytes([0_u8; crate::crypto::SECRET_KEY_BYTES]));
let metadata_key = std::mem::replace(&mut self.metadata_key, crate::SecretKeyV1::from_bytes([0_u8; crate::SECRET_KEY_BYTES]));
let secret_key = std::mem::replace(&mut self.secret_key, crate::SecretKeyV1::from_bytes([0_u8; crate::SECRET_KEY_BYTES]));
return (admin_signing_secret, metadata_key, secret_key);
}
}
@@ -152,6 +163,7 @@ struct RawMetadataNoteV1 {
text: std::string::String,
}
/// Encodes initial metadata payload.
pub(crate) fn encode_initial_metadata_payload(
pubkey: ksp_core_lib::Pubkey,
metadata: crate::WalletCreateMetadataV1,
@@ -164,7 +176,6 @@ pub(crate) fn encode_initial_metadata_payload(
if note_texts.len() > crate::KSPWALLET_V1_MAX_NOTES {
return std::result::Result::Err(metadata_error("Wallet note count exceeds the V1 limit", "metadata.notes"));
}
let mut notes = std::vec::Vec::with_capacity(note_texts.len());
let mut note_ids = std::collections::BTreeSet::new();
for text in note_texts {
@@ -172,7 +183,7 @@ pub(crate) fn encode_initial_metadata_payload(
if let std::result::Result::Err(error) = validation_result {
return std::result::Result::Err(error);
}
let note_id_bytes = match crate::crypto::random_bytes::<{ crate::KSPWALLET_V1_NOTE_ID_BYTES }>() {
let note_id_bytes = match crate::random_bytes::<{ crate::KSPWALLET_V1_NOTE_ID_BYTES }>() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -182,7 +193,6 @@ pub(crate) fn encode_initial_metadata_payload(
}
notes.push(crate::WalletNote::new(note_id, text));
}
let payload = crate::MetadataPayloadV1 { pubkey, alias, notes };
let serialized = match payload.encode() {
std::result::Result::Ok(value) => value,
@@ -191,6 +201,7 @@ pub(crate) fn encode_initial_metadata_payload(
return std::result::Result::Ok((serialized, payload));
}
/// Decodes metadata payload.
pub(crate) fn decode_metadata_payload(source: &[u8]) -> ksp_core_lib::Result<crate::MetadataPayloadV1> {
if source.len() > crate::KSPWALLET_V1_MAX_METADATA_PLAINTEXT_BYTES {
return std::result::Result::Err(metadata_error("Wallet metadata payload exceeds the V1 plaintext limit", "metadata"));
@@ -207,7 +218,6 @@ pub(crate) fn decode_metadata_payload(source: &[u8]) -> ksp_core_lib::Result<cra
if raw.notes.len() > crate::KSPWALLET_V1_MAX_NOTES {
return std::result::Result::Err(metadata_error("Wallet note count exceeds the V1 limit", "metadata.notes"));
}
let pubkey_result = ksp_core_lib::Pubkey::from_str(raw.pubkey.as_str());
let pubkey = match pubkey_result {
std::result::Result::Ok(value) => value,
@@ -216,7 +226,6 @@ pub(crate) fn decode_metadata_payload(source: &[u8]) -> ksp_core_lib::Result<cra
if pubkey.to_string() != raw.pubkey {
return std::result::Result::Err(metadata_error("Wallet metadata Pubkey is not canonical Base58", "metadata.pubkey"));
}
let mut notes = std::vec::Vec::with_capacity(raw.notes.len());
let mut note_ids = std::collections::BTreeSet::new();
for raw_note in raw.notes {
@@ -239,8 +248,9 @@ pub(crate) fn decode_metadata_payload(source: &[u8]) -> ksp_core_lib::Result<cra
return std::result::Result::Ok(crate::MetadataPayloadV1 { pubkey, alias: raw.alias, notes });
}
/// Encodes owner control payload.
pub(crate) fn encode_owner_control_payload(
admin_signing_secret: &[u8; crate::crypto::SECRET_KEY_BYTES],
admin_signing_secret: &[u8; crate::SECRET_KEY_BYTES],
metadata_key: &crate::SecretKeyV1,
secret_key: &crate::SecretKeyV1,
) -> [u8; crate::KSPWALLET_V1_OWNER_CONTROL_PLAINTEXT_BYTES] {
@@ -251,15 +261,16 @@ pub(crate) fn encode_owner_control_payload(
return output;
}
/// Decodes owner control payload.
pub(crate) fn decode_owner_control_payload(source: &[u8]) -> ksp_core_lib::Result<OwnerControlMaterialV1> {
if source.len() != crate::KSPWALLET_V1_OWNER_CONTROL_PLAINTEXT_BYTES {
return std::result::Result::Err(metadata_error("Wallet OWNER-control payload length is invalid", "owner_control"));
}
let mut admin_signing_secret = [0_u8; crate::crypto::SECRET_KEY_BYTES];
let mut admin_signing_secret = [0_u8; crate::SECRET_KEY_BYTES];
admin_signing_secret.copy_from_slice(&source[0..32]);
let mut metadata_key = [0_u8; crate::crypto::SECRET_KEY_BYTES];
let mut metadata_key = [0_u8; crate::SECRET_KEY_BYTES];
metadata_key.copy_from_slice(&source[32..64]);
let mut secret_key = [0_u8; crate::crypto::SECRET_KEY_BYTES];
let mut secret_key = [0_u8; crate::SECRET_KEY_BYTES];
secret_key.copy_from_slice(&source[64..96]);
return std::result::Result::Ok(OwnerControlMaterialV1 {
admin_signing_secret,