From ee0a60e40c65d654abc5fe0dcce143df36868428 Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Fri, 19 Jan 2024 21:38:15 +0000 Subject: [PATCH] feat: nut-10 --- crates/cashu/Cargo.toml | 3 +- crates/cashu/src/nuts/mod.rs | 2 + crates/cashu/src/nuts/nut10.rs | 111 +++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 crates/cashu/src/nuts/nut10.rs diff --git a/crates/cashu/Cargo.toml b/crates/cashu/Cargo.toml index f1454c23..df7b2f1e 100644 --- a/crates/cashu/Cargo.toml +++ b/crates/cashu/Cargo.toml @@ -15,9 +15,10 @@ description = "Cashu rust wallet and mint library" default = ["mint", "wallet", "all-nuts"] mint = [] wallet = [] -all-nuts = ["nut07", "nut08"] +all-nuts = ["nut07", "nut08", "nut10"] nut07 = [] nut08 = [] +nut10 = [] [dependencies] diff --git a/crates/cashu/src/nuts/mod.rs b/crates/cashu/src/nuts/mod.rs index 660d8d76..db426359 100644 --- a/crates/cashu/src/nuts/mod.rs +++ b/crates/cashu/src/nuts/mod.rs @@ -9,6 +9,8 @@ pub mod nut06; pub mod nut07; #[cfg(feature = "nut08")] pub mod nut08; +#[cfg(feature = "nut10")] +pub mod nut10; #[cfg(feature = "wallet")] pub use nut00::wallet::{PreMint, PreMintSecrets, Token}; diff --git a/crates/cashu/src/nuts/nut10.rs b/crates/cashu/src/nuts/nut10.rs new file mode 100644 index 00000000..54c1d2dc --- /dev/null +++ b/crates/cashu/src/nuts/nut10.rs @@ -0,0 +1,111 @@ +use serde::ser::SerializeTuple; +use serde::{Deserialize, Serialize, Serializer}; + +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] +pub enum Kind { + /// NUT-11 P2PK + #[default] + P2PK, +} + +#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)] +pub struct SecretData { + /// Unique random string + pub nonce: String, + /// Expresses the spending condition specific to each kind + pub data: String, + /// Additional data committed to and can be used for feature extensions + #[serde(skip_serializing_if = "Option::is_none")] + pub tags: Option>>, +} + +#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)] +pub struct Secret { + /// Kind of the spending condition + pub kind: Kind, + pub secret_data: SecretData, +} + +impl Serialize for Secret { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // Create a tuple representing the struct fields + let secret_tuple = (&self.kind, &self.secret_data); + + // Serialize the tuple as a JSON array + let mut s = serializer.serialize_tuple(2)?; + + s.serialize_element(&secret_tuple.0)?; + s.serialize_element(&secret_tuple.1)?; + s.end() + } +} + +#[cfg(test)] +mod tests { + use std::assert_eq; + + use super::*; + + #[test] + fn test_secret_deserialize() { + let secret_str = r#"[ + "P2PK", + { + "nonce": "5d11913ee0f92fefdc82a6764fd2457a", + "data": "026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198", + "tags": [["key", "value1", "value2"]] + } +]"# + .to_string(); + + let secret_ser: Secret = serde_json::from_str(&secret_str).unwrap(); + let secret = Secret { + kind: Kind::P2PK, + secret_data: SecretData { + nonce: "5d11913ee0f92fefdc82a6764fd2457a".to_string(), + data: "026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198" + .to_string(), + tags: Some(vec![vec![ + "key".to_string(), + "value1".to_string(), + "value2".to_string(), + ]]), + }, + }; + + assert_eq!(secret, secret_ser); + } + + #[test] + fn test_secret_serialize() { + let secret = Secret { + kind: Kind::P2PK, + secret_data: SecretData { + nonce: "5d11913ee0f92fefdc82a6764fd2457a".to_string(), + data: "026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198" + .to_string(), + tags: Some(vec![vec![ + "key".to_string(), + "value1".to_string(), + "value2".to_string(), + ]]), + }, + }; + + let secret_str = r#"["P2PK",{"nonce":"5d11913ee0f92fefdc82a6764fd2457a","data":"026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198","tags":[["key","value1","value2"]]}]"#; + + assert_eq!(serde_json::to_string(&secret).unwrap(), secret_str); + } + + #[test] + fn test_secret_roundtrip() { + let secret_str = r#"["P2PK",{"nonce":"5d11913ee0f92fefdc82a6764fd2457a","data":"026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198","tags":[["key","value1","value2"]]}]"#; + + let secret_ser: Secret = serde_json::from_str(secret_str).unwrap(); + + assert_eq!(serde_json::to_string(&secret_ser).unwrap(), secret_str) + } +}