Skip to main content

cashu/
secret.rs

1//! Secret
2
3use std::fmt;
4use std::str::FromStr;
5
6use bitcoin::secp256k1::rand::{self, RngCore};
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9use zeroize::Zeroize;
10
11use crate::util::hex;
12
13/// The secret data that allows spending ecash
14#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
15#[serde(transparent)]
16pub struct Secret(String);
17
18/// Secret Errors
19#[derive(Debug, Error)]
20pub enum Error {
21    /// Invalid Length
22    #[error("Invalid secret length: `{0}`")]
23    InvalidLength(u64),
24    /// Hex Error
25    #[error(transparent)]
26    Hex(#[from] hex::Error),
27    /// Serde Json error
28    #[error(transparent)]
29    SerdeJsonError(#[from] serde_json::Error),
30}
31
32impl Default for Secret {
33    fn default() -> Self {
34        Self::generate()
35    }
36}
37
38impl Secret {
39    /// Create new [`Secret`]
40    #[inline]
41    pub fn new<S>(secret: S) -> Self
42    where
43        S: Into<String>,
44    {
45        Self(secret.into())
46    }
47
48    /// Create secret value
49    /// Generate a new random secret as the recommended 32 byte hex
50    pub fn generate() -> Self {
51        let mut rng = rand::thread_rng();
52
53        let mut random_bytes = [0u8; 32];
54
55        // Generate random bytes
56        rng.fill_bytes(&mut random_bytes);
57        // The secret string is hex encoded
58        let secret = hex::encode(random_bytes);
59        Self(secret)
60    }
61
62    /// Length of the secret string in bytes
63    #[inline]
64    pub fn len(&self) -> usize {
65        self.0.len()
66    }
67
68    /// Check if the secret is empty
69    #[inline]
70    pub fn is_empty(&self) -> bool {
71        self.0.is_empty()
72    }
73
74    /// [`Secret`] as bytes
75    #[inline]
76    pub fn as_bytes(&self) -> &[u8] {
77        self.0.as_bytes()
78    }
79
80    /// [`Secret`] to bytes
81    #[inline]
82    pub fn to_bytes(&self) -> Vec<u8> {
83        self.as_bytes().to_vec()
84    }
85
86    /// Check if secret is P2PK secret
87    pub fn is_p2pk(&self) -> bool {
88        use crate::nuts::Kind;
89
90        let secret: Result<crate::nuts::nut10::Secret, serde_json::Error> =
91            serde_json::from_str(&self.0);
92
93        if let Ok(secret) = secret {
94            if secret.kind().eq(&Kind::P2PK) {
95                return true;
96            }
97        }
98
99        false
100    }
101}
102
103impl FromStr for Secret {
104    type Err = Error;
105
106    fn from_str(s: &str) -> Result<Self, Self::Err> {
107        Ok(Self(s.to_string()))
108    }
109}
110
111impl fmt::Display for Secret {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
113        write!(f, "{}", self.0)
114    }
115}
116
117impl From<Secret> for Vec<u8> {
118    fn from(value: Secret) -> Vec<u8> {
119        value.to_bytes()
120    }
121}
122
123impl From<&Secret> for Vec<u8> {
124    fn from(value: &Secret) -> Vec<u8> {
125        value.to_bytes()
126    }
127}
128
129impl TryFrom<Secret> for crate::nuts::nut10::Secret {
130    type Error = serde_json::Error;
131
132    fn try_from(unchecked_secret: Secret) -> Result<crate::nuts::nut10::Secret, Self::Error> {
133        serde_json::from_str(&unchecked_secret.0)
134    }
135}
136
137impl Drop for Secret {
138    fn drop(&mut self) {
139        self.0.zeroize();
140    }
141}
142
143impl TryFrom<&Secret> for crate::nuts::nut10::Secret {
144    type Error = Error;
145
146    fn try_from(unchecked_secret: &Secret) -> Result<crate::nuts::nut10::Secret, Self::Error> {
147        Ok(serde_json::from_str(&unchecked_secret.0)?)
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use std::assert_eq;
154    use std::str::FromStr;
155
156    use super::*;
157
158    #[test]
159    fn test_secret_from_str() {
160        let secret = Secret::generate();
161
162        let secret_str = secret.to_string();
163
164        assert_eq!(hex::decode(secret_str.clone()).unwrap().len(), 32);
165
166        let secret_n = Secret::from_str(&secret_str).unwrap();
167
168        assert_eq!(secret_n, secret)
169    }
170
171    #[test]
172    fn test_is_p2pk_true() {
173        // A valid P2PK secret in JSON format
174        let p2pk_secret_str = r#"["P2PK",{"nonce":"5d11913ee0f92fefdc82a6764fd2457a","data":"026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198"}]"#;
175        let secret = Secret::from_str(p2pk_secret_str).unwrap();
176
177        assert!(secret.is_p2pk());
178    }
179
180    #[test]
181    fn test_is_p2pk_false() {
182        // A random hex string (not a P2PK secret)
183        let secret = Secret::generate();
184
185        assert!(!secret.is_p2pk());
186
187        // Also test with a plain string that's not valid JSON
188        let plain_secret = Secret::from_str("not_a_p2pk_secret").unwrap();
189
190        assert!(!plain_secret.is_p2pk());
191    }
192
193    #[test]
194    fn test_secret_to_vec_u8() {
195        let secret = Secret::from_str("test_secret_value").unwrap();
196
197        // Test From<Secret> for Vec<u8>
198        let bytes: Vec<u8> = secret.clone().into();
199        assert_eq!(bytes, b"test_secret_value".to_vec());
200        assert!(!bytes.is_empty());
201
202        // Test From<&Secret> for Vec<u8>
203        let bytes_ref: Vec<u8> = (&secret).into();
204        assert_eq!(bytes_ref, b"test_secret_value".to_vec());
205        assert!(!bytes_ref.is_empty());
206    }
207}