use std::cell::RefCell;
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
use cashu::nuts::SecretKey;
use super::bindings::{compute_channel_secret_from_hex, sign_with_tweaked_key_util};
use super::client_bridge::SpilmanClientHost;
use super::client_storage::{
ClientChannelFunding, ClientChannelState, ClientPaymentState, ClientStorage,
MemoryClientStorage,
};
#[derive(Debug)]
pub struct ConfigurableClientHost<S: ClientStorage> {
keys: HashMap<String, SecretKey>,
storage: RefCell<S>,
}
impl<S: ClientStorage> ConfigurableClientHost<S> {
pub fn new(storage: S) -> Self {
Self {
keys: HashMap::new(),
storage: RefCell::new(storage),
}
}
pub fn add_key(&mut self, secret: SecretKey) {
let pubkey = secret.public_key();
let pubkey_hex = pubkey.to_hex();
self.keys.insert(pubkey_hex, secret);
}
pub fn add_key_from_hex(&mut self, secret_hex: &str) -> Result<String, String> {
let secret =
SecretKey::from_hex(secret_hex).map_err(|e| format!("Invalid secret key: {}", e))?;
let pubkey_hex = secret.public_key().to_hex();
self.keys.insert(pubkey_hex.clone(), secret);
Ok(pubkey_hex)
}
pub fn get_pubkeys(&self) -> Vec<String> {
self.keys.keys().cloned().collect()
}
pub fn has_key(&self, pubkey_hex: &str) -> bool {
self.keys.contains_key(pubkey_hex)
}
pub fn remove_key(&mut self, pubkey_hex: &str) -> bool {
self.keys.remove(pubkey_hex).is_some()
}
pub fn storage_mut(&mut self) -> &mut S {
self.storage.get_mut()
}
pub fn channel_count(&self) -> usize {
self.storage.borrow().list_channel_ids().len()
}
fn get_secret(&self, pubkey_hex: &str) -> Result<&SecretKey, String> {
self.keys
.get(pubkey_hex)
.ok_or_else(|| format!("No key found for pubkey: {}", pubkey_hex))
}
}
impl ConfigurableClientHost<MemoryClientStorage> {
pub fn new_in_memory() -> Self {
Self::new(MemoryClientStorage::new())
}
}
impl<S: ClientStorage> SpilmanClientHost for ConfigurableClientHost<S> {
fn save_channel_funding(&self, channel_id: &str, funding: ClientChannelFunding) {
self.storage.borrow_mut().save_funding(channel_id, funding);
}
fn get_channel_funding(&self, channel_id: &str) -> Option<ClientChannelFunding> {
self.storage.borrow().get_funding(channel_id).cloned()
}
fn get_payment_state(&self, channel_id: &str) -> Option<ClientPaymentState> {
self.storage.borrow().get_payment_state(channel_id).cloned()
}
fn record_payment(&self, channel_id: &str, state: ClientPaymentState) {
self.storage
.borrow_mut()
.save_payment_state(channel_id, state);
}
fn get_channel_state(&self, channel_id: &str) -> ClientChannelState {
self.storage.borrow().get_state(channel_id)
}
fn mark_channel_closed(&self, channel_id: &str) {
self.storage.borrow_mut().set_closed(channel_id);
}
fn list_channel_ids(&self) -> Vec<String> {
self.storage.borrow().list_channel_ids()
}
fn delete_channel(&self, channel_id: &str) {
self.storage.borrow_mut().delete(channel_id);
}
fn now_seconds(&self) -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}
fn compute_channel_secret(
&self,
sender_pubkey_hex: &str,
receiver_pubkey_hex: &str,
) -> Result<String, String> {
let secret = self.get_secret(sender_pubkey_hex)?;
let secret_hex = secret.to_secret_hex();
compute_channel_secret_from_hex(&secret_hex, receiver_pubkey_hex)
}
fn sign_with_tweaked_key(
&self,
signer_pubkey_hex: &str,
message_hex: &str,
tweak_scalar_hex: &str,
) -> Result<String, String> {
let secret = self.get_secret(signer_pubkey_hex)?;
let secret_hex = secret.to_secret_hex();
sign_with_tweaked_key_util(&secret_hex, message_hex, tweak_scalar_hex)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_key_management() {
let mut host = ConfigurableClientHost::new_in_memory();
assert!(host.get_pubkeys().is_empty());
let secret = SecretKey::generate();
let pubkey = secret.public_key();
let pubkey_hex = pubkey.to_hex();
host.add_key(secret);
assert_eq!(host.get_pubkeys().len(), 1);
assert!(host.has_key(&pubkey_hex));
assert!(host.remove_key(&pubkey_hex));
assert!(!host.has_key(&pubkey_hex));
assert!(host.get_pubkeys().is_empty());
}
#[test]
fn test_add_key_from_hex() {
let mut host = ConfigurableClientHost::new_in_memory();
let secret = SecretKey::generate();
let secret_hex = secret.to_secret_hex();
let expected_pubkey = secret.public_key().to_hex();
let pubkey_hex = host.add_key_from_hex(&secret_hex).unwrap();
assert_eq!(pubkey_hex, expected_pubkey);
assert!(host.has_key(&pubkey_hex));
}
#[test]
fn test_storage_delegation() {
let host = ConfigurableClientHost::new_in_memory();
let channel_id = "test_channel";
assert!(host.get_channel_funding(channel_id).is_none());
assert_eq!(
host.get_channel_state(channel_id),
ClientChannelState::Closed
);
let funding = ClientChannelFunding {
params_json: "{}".to_string(),
funding_proofs_json: "[]".to_string(),
channel_secret_hex: "aa".repeat(32),
keyset_info_json: "{}".to_string(),
sender_pubkey_hex: "02".to_string() + &"bb".repeat(32),
capacity: 1000,
funding_token_amount: 1100,
mint_url: "https://mint.example.com".to_string(),
created_at: 12345,
};
host.save_channel_funding(channel_id, funding.clone());
let retrieved = host.get_channel_funding(channel_id).unwrap();
assert_eq!(retrieved.capacity, 1000);
assert_eq!(host.get_channel_state(channel_id), ClientChannelState::Open);
assert!(host.get_payment_state(channel_id).is_none());
let payment = ClientPaymentState {
balance: 100,
signature: "sig".to_string(),
payment_count: 1,
last_payment_at: 12346,
};
host.record_payment(channel_id, payment);
let state = host.get_payment_state(channel_id).unwrap();
assert_eq!(state.balance, 100);
host.mark_channel_closed(channel_id);
assert_eq!(
host.get_channel_state(channel_id),
ClientChannelState::Closed
);
assert_eq!(host.list_channel_ids(), vec![channel_id]);
host.delete_channel(channel_id);
assert!(host.get_channel_funding(channel_id).is_none());
}
#[test]
fn test_now_seconds() {
let host = ConfigurableClientHost::new_in_memory();
let now = host.now_seconds();
assert!(now > 1577836800);
let now2 = host.now_seconds();
assert!(now2 >= now);
assert!(now2 - now < 2); }
#[test]
fn test_compute_channel_secret() {
let mut host = ConfigurableClientHost::new_in_memory();
let sender_secret = SecretKey::generate();
let sender_pubkey_hex = sender_secret.public_key().to_hex();
host.add_key(sender_secret);
let receiver_secret = SecretKey::generate();
let receiver_pubkey_hex = receiver_secret.public_key().to_hex();
let secret = host
.compute_channel_secret(&sender_pubkey_hex, &receiver_pubkey_hex)
.unwrap();
assert_eq!(secret.len(), 64);
let secret2 = host
.compute_channel_secret(&sender_pubkey_hex, &receiver_pubkey_hex)
.unwrap();
assert_eq!(secret, secret2);
}
#[test]
fn test_sign_with_tweaked_key() {
let mut host = ConfigurableClientHost::new_in_memory();
let secret = SecretKey::generate();
let pubkey_hex = secret.public_key().to_hex();
host.add_key(secret);
let message_hex = "aa".repeat(32); let tweak_hex = "bb".repeat(32);
let signature = host
.sign_with_tweaked_key(&pubkey_hex, &message_hex, &tweak_hex)
.unwrap();
assert_eq!(signature.len(), 128);
}
#[test]
fn test_missing_key_error() {
let host = ConfigurableClientHost::new_in_memory();
let fake_pubkey = "02".to_string() + &"cc".repeat(32);
let message_hex = "aa".repeat(32);
let tweak_hex = "bb".repeat(32);
let result = host.sign_with_tweaked_key(&fake_pubkey, &message_hex, &tweak_hex);
assert!(result.is_err());
assert!(result.unwrap_err().contains("No key found"));
}
}