From 6c376aa679ffed32f281c6e51ce393abe0d21ac4 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Tue, 26 Mar 2024 12:41:25 -0500 Subject: [PATCH] Fixes for blind tokens --- mutiny-core/src/blindauth.rs | 88 +++++++++++++++++++++++++++++++++-- mutiny-core/src/hermes.rs | 9 +++- mutiny-core/src/lib.rs | 2 +- mutiny-wasm/src/indexed_db.rs | 18 +++++++ 4 files changed, 110 insertions(+), 7 deletions(-) diff --git a/mutiny-core/src/blindauth.rs b/mutiny-core/src/blindauth.rs index efaf1842b..61e2f470a 100644 --- a/mutiny-core/src/blindauth.rs +++ b/mutiny-core/src/blindauth.rs @@ -31,7 +31,7 @@ const SPEND_KEY_CHILD_ID: ChildId = ChildId(0); /// Child ID used to derive the blinding key from a service plan's DerivableSecret const BLINDING_KEY_CHILD_ID: ChildId = ChildId(1); -#[derive(Debug, Serialize, Deserialize, Clone, Default)] +#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)] pub struct TokenStorage { // (service_id, plan_id): number of times used pub map: HashMap, @@ -64,13 +64,44 @@ impl TokenStorage { } } -#[derive(Debug, Serialize, Deserialize, Clone, Default, Eq, PartialEq, Hash)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)] pub struct ServicePlanIndex { pub service_id: u32, pub plan_id: u32, } -#[derive(Debug, Serialize, Deserialize, Clone)] +impl Serialize for ServicePlanIndex { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let string = format!("{}-{}", self.service_id, self.plan_id); + serializer.serialize_str(&string) + } +} + +impl<'a> Deserialize<'a> for ServicePlanIndex { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + let uri = String::deserialize(deserializer)?; + + let parts: Vec<&str> = uri.split('-').collect(); + if parts.len() != 2 { + return Err(serde::de::Error::custom("Invalid ServicePlanIndex")); + } + + let service_id = parts[0].parse::().map_err(serde::de::Error::custom)?; + let plan_id = parts[1].parse::().map_err(serde::de::Error::custom)?; + Ok(ServicePlanIndex { + service_id, + plan_id, + }) + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct UnsignedToken { pub counter: u32, pub service_id: u32, @@ -78,7 +109,7 @@ pub struct UnsignedToken { pub blinded_message: BlindedMessage, } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct SignedToken { pub counter: u32, pub service_id: u32, @@ -219,7 +250,11 @@ impl BlindAuthClient { // Maybe have an "issued" tokens call so we can see if we're caught up with the server? self.storage .insert_token_storage(token_storage_guard.clone()) - .await?; + .await + .map_err(|e| { + log_error!(self.logger, "could not save token storage: {e:?}"); + e + })?; Ok(signed_token) } @@ -370,3 +405,46 @@ fn create_blind_auth_secret( BLINDAUTH_CLIENT_NONCE, )) } + +#[cfg(test)] +mod test { + use crate::blindauth::{ServicePlanIndex, SignedToken, TokenStorage}; + use tbs::{BlindedMessage, BlindedSignature}; + + #[test] + fn test_token_storage_serialization() { + let mut map = std::collections::HashMap::new(); + map.insert( + ServicePlanIndex { + service_id: 1, + plan_id: 1, + }, + 1, + ); + + let token = SignedToken { + counter: 1, + service_id: 1, + plan_id: 1, + blinded_message: BlindedMessage(Default::default()), + blind_sig: BlindedSignature(Default::default()), + spent: false, + }; + + let storage = TokenStorage { + map, + tokens: vec![token], + version: 0, + }; + + let serialized = serde_json::to_string(&storage).unwrap(); + let deserialized: TokenStorage = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(storage, deserialized); + + // test backwards compatibility + let string = "{\"map\":{\"1-1\":1},\"tokens\":[{\"counter\":1,\"service_id\":1,\"plan_id\":1,\"blinded_message\":\"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"blind_sig\":\"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"spent\":false}],\"version\":0}"; + let deserialized: TokenStorage = serde_json::from_str(string).unwrap(); + assert_eq!(storage, deserialized); + } +} diff --git a/mutiny-core/src/hermes.rs b/mutiny-core/src/hermes.rs index 1e1497641..743380dce 100644 --- a/mutiny-core/src/hermes.rs +++ b/mutiny-core/src/hermes.rs @@ -209,7 +209,14 @@ impl HermesClient { let available_paid_token = match find_hermes_token(&available_tokens, HERMES_SERVICE_ID, HERMES_PAID_PLAN_ID) { Some(t) => t, - None => return Err(MutinyError::NotFound), + None => { + log_error!( + self.logger, + "No available paid token for Hermes, current tokens: {}", + available_tokens.len() + ); + return Err(MutinyError::NotFound); + } }; // check that we have a federation added and get it's id/invite code diff --git a/mutiny-core/src/lib.rs b/mutiny-core/src/lib.rs index 1de938644..871690607 100644 --- a/mutiny-core/src/lib.rs +++ b/mutiny-core/src/lib.rs @@ -10,7 +10,7 @@ extern crate core; pub mod auth; -mod blindauth; +pub mod blindauth; mod cashu; mod chain; pub mod encrypt; diff --git a/mutiny-wasm/src/indexed_db.rs b/mutiny-wasm/src/indexed_db.rs index 9ea74d8b3..793d5cf37 100644 --- a/mutiny-wasm/src/indexed_db.rs +++ b/mutiny-wasm/src/indexed_db.rs @@ -6,6 +6,7 @@ use gloo_utils::format::JsValueSerdeExt; use lightning::util::logger::Logger; use lightning::{log_debug, log_error, log_trace}; use log::error; +use mutiny_core::blindauth::TokenStorage; use mutiny_core::logging::LOGGING_KEY; use mutiny_core::storage::*; use mutiny_core::vss::*; @@ -445,6 +446,23 @@ impl IndexedDbStorage { } } } + SERVICE_TOKENS => { + // we can get version from TokenStorage, so we should compare + match current.get_data::(&kv.key)? { + Some(token_storage) => { + if token_storage.version < kv.version { + let obj = vss.get_object(&kv.key).await?; + if serde_json::from_value::(obj.value.clone()).is_ok() { + return Ok(Some((kv.key, obj.value))); + } + } + } + None => { + let obj = vss.get_object(&kv.key).await?; + return Ok(Some((kv.key, obj.value))); + } + } + } key => { if key.starts_with(MONITORS_PREFIX_KEY) { // we can get versions from monitors, so we should compare