v0.2.5-pre.007
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-wallet-lib/src/payload.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Plaintext payload codecs protected inside native `.kspwallet` V1 compartments.
|
||||
|
||||
@@ -17,6 +17,99 @@ impl MetadataPayloadV1 {
|
||||
return &self.pubkey;
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
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 {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
self.alias = alias;
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
let validation_result = validate_note_text(text.as_str());
|
||||
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 }>() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let note_id = encode_base64url(note_id_bytes.as_slice());
|
||||
for note in &self.notes {
|
||||
if note.id() == note_id.as_str() {
|
||||
return std::result::Result::Err(random_note_identifier_error());
|
||||
}
|
||||
}
|
||||
self.notes.push(crate::WalletNote::new(note_id.clone(), text));
|
||||
return std::result::Result::Ok(note_id);
|
||||
}
|
||||
|
||||
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 {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let mut index = 0_usize;
|
||||
while index < self.notes.len() {
|
||||
if self.notes[index].id() == note_id {
|
||||
self.notes[index] = crate::WalletNote::new(std::string::String::from(note_id), text);
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
return std::result::Result::Err(note_not_found_error());
|
||||
}
|
||||
|
||||
pub(crate) fn delete_note(&mut self, note_id: &str) -> ksp_core_lib::Result<()> {
|
||||
let mut index = 0_usize;
|
||||
while index < self.notes.len() {
|
||||
if self.notes[index].id() == note_id {
|
||||
self.notes.remove(index);
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
return std::result::Result::Err(note_not_found_error());
|
||||
}
|
||||
|
||||
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 {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
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"));
|
||||
}
|
||||
let mut raw_notes = std::vec::Vec::with_capacity(self.notes.len());
|
||||
for note in &self.notes {
|
||||
let validation_result = validate_note_text(note.text());
|
||||
if let std::result::Result::Err(error) = validation_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
raw_notes.push(RawMetadataNoteV1 { id: std::string::String::from(note.id()), text: std::string::String::from(note.text()) });
|
||||
}
|
||||
let raw = RawMetadataPayloadV1 { pubkey: self.pubkey.to_string(), alias: self.alias.clone(), notes: raw_notes };
|
||||
let serialized_result = serde_json::to_vec(&raw);
|
||||
let serialized = match serialized_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(metadata_error("Wallet metadata payload cannot be serialized", "metadata")),
|
||||
};
|
||||
if serialized.len() > crate::KSPWALLET_V1_MAX_METADATA_PLAINTEXT_BYTES {
|
||||
return std::result::Result::Err(metadata_error("Wallet metadata payload exceeds the V1 plaintext limit", "metadata"));
|
||||
}
|
||||
return std::result::Result::Ok(serialized);
|
||||
}
|
||||
|
||||
pub(crate) fn into_info(self, capability: crate::WalletCapability) -> crate::WalletInfo {
|
||||
return crate::WalletInfo::new(capability, self.pubkey, self.alias, self.notes);
|
||||
}
|
||||
@@ -64,21 +157,20 @@ pub(crate) fn encode_initial_metadata_payload(
|
||||
metadata: crate::WalletCreateMetadataV1,
|
||||
) -> ksp_core_lib::Result<(std::vec::Vec<u8>, MetadataPayloadV1)> {
|
||||
let (alias, note_texts) = metadata.into_parts();
|
||||
if let std::option::Option::Some(alias_value) = alias.as_ref()
|
||||
&& alias_value.len() > crate::KSPWALLET_V1_MAX_ALIAS_BYTES
|
||||
{
|
||||
return std::result::Result::Err(metadata_error("Wallet alias exceeds the V1 UTF-8 byte limit", "metadata.alias"));
|
||||
let validation_result = validate_alias(alias.as_deref());
|
||||
if let std::result::Result::Err(error) = validation_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
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 raw_notes = std::vec::Vec::with_capacity(note_texts.len());
|
||||
let mut notes = std::vec::Vec::with_capacity(note_texts.len());
|
||||
let mut note_ids = std::collections::BTreeSet::new();
|
||||
for text in note_texts {
|
||||
if text.len() > crate::KSPWALLET_V1_MAX_NOTE_TEXT_BYTES {
|
||||
return std::result::Result::Err(metadata_error("Wallet note text exceeds the V1 UTF-8 byte limit", "metadata.notes.text"));
|
||||
let validation_result = validate_note_text(text.as_str());
|
||||
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 }>() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -88,20 +180,15 @@ pub(crate) fn encode_initial_metadata_payload(
|
||||
if !note_ids.insert(note_id.clone()) {
|
||||
return std::result::Result::Err(random_note_identifier_error());
|
||||
}
|
||||
raw_notes.push(RawMetadataNoteV1 { id: note_id.clone(), text: text.clone() });
|
||||
notes.push(crate::WalletNote::new(note_id, text));
|
||||
}
|
||||
|
||||
let raw = RawMetadataPayloadV1 { pubkey: pubkey.to_string(), alias: alias.clone(), notes: raw_notes };
|
||||
let serialized_result = serde_json::to_vec(&raw);
|
||||
let serialized = match serialized_result {
|
||||
let payload = MetadataPayloadV1 { pubkey, alias, notes };
|
||||
let serialized = match payload.encode() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(metadata_error("Wallet metadata payload cannot be serialized", "metadata")),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if serialized.len() > crate::KSPWALLET_V1_MAX_METADATA_PLAINTEXT_BYTES {
|
||||
return std::result::Result::Err(metadata_error("Wallet metadata payload exceeds the V1 plaintext limit", "metadata"));
|
||||
}
|
||||
return std::result::Result::Ok((serialized, MetadataPayloadV1 { pubkey, alias, notes }));
|
||||
return std::result::Result::Ok((serialized, payload));
|
||||
}
|
||||
|
||||
pub(crate) fn decode_metadata_payload(source: &[u8]) -> ksp_core_lib::Result<MetadataPayloadV1> {
|
||||
@@ -113,10 +200,9 @@ pub(crate) fn decode_metadata_payload(source: &[u8]) -> ksp_core_lib::Result<Met
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(metadata_error("Wallet metadata payload is invalid", "metadata")),
|
||||
};
|
||||
if let std::option::Option::Some(alias) = raw.alias.as_ref()
|
||||
&& alias.len() > crate::KSPWALLET_V1_MAX_ALIAS_BYTES
|
||||
{
|
||||
return std::result::Result::Err(metadata_error("Wallet alias exceeds the V1 UTF-8 byte limit", "metadata.alias"));
|
||||
let validation_result = validate_alias(raw.alias.as_deref());
|
||||
if let std::result::Result::Err(error) = validation_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
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"));
|
||||
@@ -134,8 +220,9 @@ pub(crate) fn decode_metadata_payload(source: &[u8]) -> ksp_core_lib::Result<Met
|
||||
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 {
|
||||
if raw_note.text.len() > crate::KSPWALLET_V1_MAX_NOTE_TEXT_BYTES {
|
||||
return std::result::Result::Err(metadata_error("Wallet note text exceeds the V1 UTF-8 byte limit", "metadata.notes.text"));
|
||||
let validation_result = validate_note_text(raw_note.text.as_str());
|
||||
if let std::result::Result::Err(error) = validation_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let id_bytes = match decode_base64url(raw_note.id.as_str()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -181,6 +268,22 @@ pub(crate) fn decode_owner_control_payload(source: &[u8]) -> ksp_core_lib::Resul
|
||||
});
|
||||
}
|
||||
|
||||
fn validate_alias(alias: std::option::Option<&str>) -> ksp_core_lib::Result<()> {
|
||||
if let std::option::Option::Some(alias_value) = alias
|
||||
&& alias_value.len() > crate::KSPWALLET_V1_MAX_ALIAS_BYTES
|
||||
{
|
||||
return std::result::Result::Err(metadata_error("Wallet alias exceeds the V1 UTF-8 byte limit", "metadata.alias"));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn validate_note_text(text: &str) -> ksp_core_lib::Result<()> {
|
||||
if text.len() > crate::KSPWALLET_V1_MAX_NOTE_TEXT_BYTES {
|
||||
return std::result::Result::Err(metadata_error("Wallet note text exceeds the V1 UTF-8 byte limit", "metadata.notes.text"));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn encode_base64url(bytes: &[u8]) -> std::string::String {
|
||||
return base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes);
|
||||
}
|
||||
@@ -198,6 +301,10 @@ fn random_note_identifier_error() -> ksp_core_lib::Error {
|
||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_RANDOMNESS_FAILED, "Wallet generated a duplicate protected note identifier");
|
||||
}
|
||||
|
||||
fn note_not_found_error() -> ksp_core_lib::Error {
|
||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_NOTE_NOT_FOUND, "Wallet note identifier was not found");
|
||||
}
|
||||
|
||||
fn metadata_error(message: &'static str, field: &'static str) -> ksp_core_lib::Error {
|
||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_FORMAT_INVALID, message).with_context("field", field);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user