Skip to content
This repository was archived by the owner on Feb 3, 2025. It is now read-only.

Commit 1708d95

Browse files
Merge pull request #1085 from MutinyWallet/blind-auth-followup
Fixes for blind tokens
2 parents 5ca7123 + 6c376aa commit 1708d95

4 files changed

Lines changed: 110 additions & 7 deletions

File tree

mutiny-core/src/blindauth.rs

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const SPEND_KEY_CHILD_ID: ChildId = ChildId(0);
3131
/// Child ID used to derive the blinding key from a service plan's DerivableSecret
3232
const BLINDING_KEY_CHILD_ID: ChildId = ChildId(1);
3333

34-
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
34+
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
3535
pub struct TokenStorage {
3636
// (service_id, plan_id): number of times used
3737
pub map: HashMap<ServicePlanIndex, u32>,
@@ -64,21 +64,52 @@ impl TokenStorage {
6464
}
6565
}
6666

67-
#[derive(Debug, Serialize, Deserialize, Clone, Default, Eq, PartialEq, Hash)]
67+
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
6868
pub struct ServicePlanIndex {
6969
pub service_id: u32,
7070
pub plan_id: u32,
7171
}
7272

73-
#[derive(Debug, Serialize, Deserialize, Clone)]
73+
impl Serialize for ServicePlanIndex {
74+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
75+
where
76+
S: serde::Serializer,
77+
{
78+
let string = format!("{}-{}", self.service_id, self.plan_id);
79+
serializer.serialize_str(&string)
80+
}
81+
}
82+
83+
impl<'a> Deserialize<'a> for ServicePlanIndex {
84+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
85+
where
86+
D: serde::Deserializer<'a>,
87+
{
88+
let uri = String::deserialize(deserializer)?;
89+
90+
let parts: Vec<&str> = uri.split('-').collect();
91+
if parts.len() != 2 {
92+
return Err(serde::de::Error::custom("Invalid ServicePlanIndex"));
93+
}
94+
95+
let service_id = parts[0].parse::<u32>().map_err(serde::de::Error::custom)?;
96+
let plan_id = parts[1].parse::<u32>().map_err(serde::de::Error::custom)?;
97+
Ok(ServicePlanIndex {
98+
service_id,
99+
plan_id,
100+
})
101+
}
102+
}
103+
104+
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
74105
pub struct UnsignedToken {
75106
pub counter: u32,
76107
pub service_id: u32,
77108
pub plan_id: u32,
78109
pub blinded_message: BlindedMessage,
79110
}
80111

81-
#[derive(Debug, Serialize, Deserialize, Clone)]
112+
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
82113
pub struct SignedToken {
83114
pub counter: u32,
84115
pub service_id: u32,
@@ -219,7 +250,11 @@ impl<S: MutinyStorage> BlindAuthClient<S> {
219250
// Maybe have an "issued" tokens call so we can see if we're caught up with the server?
220251
self.storage
221252
.insert_token_storage(token_storage_guard.clone())
222-
.await?;
253+
.await
254+
.map_err(|e| {
255+
log_error!(self.logger, "could not save token storage: {e:?}");
256+
e
257+
})?;
223258

224259
Ok(signed_token)
225260
}
@@ -370,3 +405,46 @@ fn create_blind_auth_secret(
370405
BLINDAUTH_CLIENT_NONCE,
371406
))
372407
}
408+
409+
#[cfg(test)]
410+
mod test {
411+
use crate::blindauth::{ServicePlanIndex, SignedToken, TokenStorage};
412+
use tbs::{BlindedMessage, BlindedSignature};
413+
414+
#[test]
415+
fn test_token_storage_serialization() {
416+
let mut map = std::collections::HashMap::new();
417+
map.insert(
418+
ServicePlanIndex {
419+
service_id: 1,
420+
plan_id: 1,
421+
},
422+
1,
423+
);
424+
425+
let token = SignedToken {
426+
counter: 1,
427+
service_id: 1,
428+
plan_id: 1,
429+
blinded_message: BlindedMessage(Default::default()),
430+
blind_sig: BlindedSignature(Default::default()),
431+
spent: false,
432+
};
433+
434+
let storage = TokenStorage {
435+
map,
436+
tokens: vec![token],
437+
version: 0,
438+
};
439+
440+
let serialized = serde_json::to_string(&storage).unwrap();
441+
let deserialized: TokenStorage = serde_json::from_str(&serialized).unwrap();
442+
443+
assert_eq!(storage, deserialized);
444+
445+
// test backwards compatibility
446+
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}";
447+
let deserialized: TokenStorage = serde_json::from_str(string).unwrap();
448+
assert_eq!(storage, deserialized);
449+
}
450+
}

mutiny-core/src/hermes.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,14 @@ impl<S: MutinyStorage> HermesClient<S> {
209209
let available_paid_token =
210210
match find_hermes_token(&available_tokens, HERMES_SERVICE_ID, HERMES_PAID_PLAN_ID) {
211211
Some(t) => t,
212-
None => return Err(MutinyError::NotFound),
212+
None => {
213+
log_error!(
214+
self.logger,
215+
"No available paid token for Hermes, current tokens: {}",
216+
available_tokens.len()
217+
);
218+
return Err(MutinyError::NotFound);
219+
}
213220
};
214221

215222
// check that we have a federation added and get it's id/invite code

mutiny-core/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
extern crate core;
1111

1212
pub mod auth;
13-
mod blindauth;
13+
pub mod blindauth;
1414
mod cashu;
1515
mod chain;
1616
pub mod encrypt;

mutiny-wasm/src/indexed_db.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use gloo_utils::format::JsValueSerdeExt;
66
use lightning::util::logger::Logger;
77
use lightning::{log_debug, log_error, log_trace};
88
use log::error;
9+
use mutiny_core::blindauth::TokenStorage;
910
use mutiny_core::logging::LOGGING_KEY;
1011
use mutiny_core::storage::*;
1112
use mutiny_core::vss::*;
@@ -445,6 +446,23 @@ impl IndexedDbStorage {
445446
}
446447
}
447448
}
449+
SERVICE_TOKENS => {
450+
// we can get version from TokenStorage, so we should compare
451+
match current.get_data::<TokenStorage>(&kv.key)? {
452+
Some(token_storage) => {
453+
if token_storage.version < kv.version {
454+
let obj = vss.get_object(&kv.key).await?;
455+
if serde_json::from_value::<TokenStorage>(obj.value.clone()).is_ok() {
456+
return Ok(Some((kv.key, obj.value)));
457+
}
458+
}
459+
}
460+
None => {
461+
let obj = vss.get_object(&kv.key).await?;
462+
return Ok(Some((kv.key, obj.value)));
463+
}
464+
}
465+
}
448466
key => {
449467
if key.starts_with(MONITORS_PREFIX_KEY) {
450468
// we can get versions from monitors, so we should compare

0 commit comments

Comments
 (0)