moksha_core/
blind.rs

1//! This module defines the `BlindedMessage` and `BlindedSignature` structs, which are used for representing blinded messages and signatures in Cashu as described in [Nut-00](https://github.com/cashubtc/nuts/blob/main/00.md)
2//!
3//! The `BlindedMessage` struct represents a blinded message, with an `amount` field for the amount in satoshis and a `b_` field for the public key of the blinding factor.
4//!
5//! The `BlindedSignature` struct represents a blinded signature, with an `amount` field for the amount in satoshis, a `c_` field for the public key of the blinding factor, and an optional `id` field for the ID of the signature.
6//!
7//! Both the `BlindedMessage` and `BlindedSignature` structs are serializable and deserializable using serde.
8//!
9//! The `TotalAmount` trait is also defined in this module, which provides a `total_amount` method for calculating the total amount of a vector of `BlindedMessage` or `BlindedSignature` structs. The trait is implemented for both `Vec<BlindedMessage>` and `Vec<BlindedSignature>`.
10
11use secp256k1::{PublicKey, SecretKey};
12use serde::{Deserialize, Serialize};
13use utoipa::ToSchema;
14
15use crate::{
16    amount::{generate_random_string, Amount},
17    dhke::Dhke,
18    error::MokshaCoreError,
19};
20
21#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
22pub struct BlindedSignature {
23    pub amount: u64,
24    #[serde(rename = "C_")]
25    #[schema(value_type=String)]
26    pub c_: PublicKey,
27    pub id: Option<String>,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
31pub struct BlindedMessage {
32    pub amount: u64,
33    #[serde(rename = "B_")]
34    #[schema(value_type=String)]
35    pub b_: PublicKey,
36    pub id: String,
37}
38
39impl BlindedMessage {
40    pub fn blank(
41        fee_reserve: Amount,
42        keyset_id: String,
43    ) -> Result<Vec<(Self, SecretKey, String)>, MokshaCoreError> {
44        if fee_reserve.0 == 0 {
45            return Ok(vec![]);
46        }
47
48        let fee_reserve_float = fee_reserve.0 as f64;
49        let count = (fee_reserve_float.log2().ceil() as u64).max(1);
50        let dhke = Dhke::new();
51
52        let blinded_messages = (0..count)
53            .map(|_| {
54                let secret = generate_random_string();
55                let (b_, alice_secret_key) = dhke.step1_alice(secret.clone(), None).unwrap(); // FIXME
56                (
57                    Self {
58                        amount: 1,
59                        b_,
60                        id: keyset_id.clone(),
61                    },
62                    alice_secret_key,
63                    secret,
64                )
65            })
66            .collect::<Vec<(Self, SecretKey, String)>>();
67
68        Ok(blinded_messages)
69    }
70}
71
72pub trait TotalAmount {
73    fn total_amount(&self) -> u64;
74}
75
76impl TotalAmount for Vec<BlindedSignature> {
77    fn total_amount(&self) -> u64 {
78        self.iter().fold(0, |acc, x| acc + x.amount)
79    }
80}
81
82impl TotalAmount for Vec<BlindedMessage> {
83    fn total_amount(&self) -> u64 {
84        self.iter().fold(0, |acc, x| acc + x.amount)
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_1000_sats() {
94        let result = BlindedMessage::blank(1000.into(), "00ffd48b8f5ecf80".to_owned());
95        println!("{:?}", result);
96        assert!(result.is_ok());
97        let result = result.unwrap();
98        assert!(result.len() == 10);
99        assert!(result.first().unwrap().0.amount == 1);
100    }
101
102    #[test]
103    fn test_zero_sats() {
104        let result = BlindedMessage::blank(0.into(), "00ffd48b8f5ecf80".to_owned());
105        println!("{:?}", result);
106        assert!(result.is_ok());
107        assert!(result.unwrap().is_empty());
108    }
109
110    #[test]
111    fn test_serialize() -> anyhow::Result<()> {
112        let result = BlindedMessage::blank(4000.into(), "00ffd48b8f5ecf80".to_owned())?;
113        for (blinded_message, _, _) in result {
114            let out = serde_json::to_string(&blinded_message)?;
115            assert!(!out.is_empty());
116        }
117        Ok(())
118    }
119}