Skip to main content

nostro2_nips/
nip_46.rs

1use rand::Rng;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
4#[serde(rename_all = "snake_case")]
5pub enum Nip46Method {
6    Connect,
7    SignEvent,
8    Ping,
9    GetPublicKey,
10    Nip04Encrypt,
11    Nip04Decrypt,
12    Nip44Encrypt,
13    Nip44Decrypt,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
17pub struct Nip46Request {
18    id: String,
19    method: Nip46Method,
20    params: Vec<String>,
21}
22impl std::fmt::Display for Nip46Request {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        let value = serde_json::to_string(self).unwrap_or_default();
25        write!(f, "{value}")
26    }
27}
28impl std::str::FromStr for Nip46Request {
29    type Err = serde_json::Error;
30
31    fn from_str(s: &str) -> Result<Self, Self::Err> {
32        serde_json::from_str(s)
33    }
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
37pub struct Nip46Response {
38    id: String,
39    result: String,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    error: Option<String>,
42}
43impl std::fmt::Display for Nip46Response {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        let value = serde_json::to_string(self).unwrap_or_default();
46        write!(f, "{value}")
47    }
48}
49impl std::str::FromStr for Nip46Response {
50    type Err = serde_json::Error;
51
52    fn from_str(s: &str) -> Result<Self, Self::Err> {
53        serde_json::from_str(s)
54    }
55}
56
57#[derive(Debug, thiserror::Error)]
58pub enum Nip46Error {
59    #[error("Nostr note error {0}")]
60    NostrNoteError(#[from] nostro2::errors::NostrErrors),
61    #[error("Failed to encrypt message {0}")]
62    Nip44Error(#[from] crate::nip_44::Nip44Error),
63}
64
65pub trait Nip46: nostro2::NostrSigner + crate::Nip44 {
66    /// Creates a NIP-46 request note.
67    ///
68    /// # Errors
69    ///
70    /// Returns an error if the note fails to sign or encrypt.
71    fn nip46_request(
72        &self,
73        method: Nip46Method,
74        params: Vec<String>,
75        signer_pk: &str,
76    ) -> Result<nostro2::NostrNote, Nip46Error> {
77        let mut note = nostro2::NostrNote {
78            kind: 24133,
79            content: self
80                .nip_44_encrypt(
81                    &Nip46Request {
82                        id: rand::thread_rng()
83                            .gen_range(0..=u8::MAX)
84                            .to_string(),
85                        method,
86                        params,
87                    }
88                    .to_string(),
89                    signer_pk,
90                )?
91                .to_string(),
92            pubkey: self.public_key(),
93            ..Default::default()
94        };
95        note.tags.add_pubkey_tag(signer_pk, None);
96        self.sign_nostr_note(&mut note)?;
97        Ok(note)
98    }
99    /// Creates a NIP-46 response note.
100    ///
101    /// # Errors
102    ///
103    /// Returns an error if the note fails to sign or encrypt.
104    fn nip46_response(
105        &self,
106        request_id: &str,
107        result: String,
108        error: Option<String>,
109        signer_pk: &str,
110    ) -> Result<nostro2::NostrNote, Nip46Error> {
111        let response = Nip46Response {
112            id: request_id.to_string(),
113            result,
114            error,
115        };
116        let mut note = nostro2::NostrNote {
117            kind: 24133,
118            content: self
119                .nip_44_encrypt(&response.to_string(), signer_pk)?
120                .to_string(),
121            pubkey: self.public_key(),
122            ..Default::default()
123        };
124        note.tags.add_pubkey_tag(signer_pk, None);
125        self.sign_nostr_note(&mut note)?;
126        Ok(note)
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use crate::{tests::NipTester, Nip44};
134    use nostro2::NostrSigner;
135
136    #[test]
137    fn nip46_request() {
138        let nip_tester = NipTester::generate(false);
139        let remote_key = NipTester::generate(false);
140        let request = nip_tester.nip46_request(
141            Nip46Method::Connect,
142            vec!["test".to_string()],
143            &remote_key.public_key(),
144        );
145        assert!(request.is_ok());
146        let request = request.unwrap();
147        assert_eq!(request.kind, 24133);
148        assert_eq!(
149            request.tags.first_tagged_pubkey(),
150            Some(remote_key.public_key())
151        );
152
153        let pubkey = nip_tester.public_key();
154        let content = remote_key.nip44_decrypt_note(&request, &pubkey).unwrap();
155        let nip46_request: Nip46Request = content.parse().unwrap();
156        assert_eq!(nip46_request.method, Nip46Method::Connect);
157        assert_eq!(nip46_request.params.len(), 1);
158    }
159    #[test]
160    fn nip46_response() {
161        let nip_tester = NipTester::generate(false);
162        let remote_key = NipTester::generate(false);
163        let request = nip_tester.nip46_request(
164            Nip46Method::Connect,
165            vec!["test".to_string()],
166            &remote_key.public_key(),
167        );
168        assert!(request.is_ok());
169        let request = request.unwrap();
170        assert_eq!(request.kind, 24133);
171        assert_eq!(
172            request.tags.first_tagged_pubkey(),
173            Some(remote_key.public_key())
174        );
175
176        let pubkey = nip_tester.public_key();
177        let content = remote_key.nip44_decrypt_note(&request, &pubkey).unwrap();
178        let nip46_request: Nip46Request = content.parse().unwrap();
179        assert_eq!(nip46_request.method, Nip46Method::Connect);
180        assert_eq!(nip46_request.params.len(), 1);
181
182        let response =
183            nip_tester.nip46_response(&nip46_request.id, "test".to_string(), None, &request.pubkey);
184        assert!(response.is_ok());
185        let response = response.unwrap();
186        assert_eq!(response.kind, 24133);
187        assert_eq!(
188            response.tags.first_tagged_pubkey(),
189            Some(request.pubkey.clone())
190        );
191        let content = nip_tester
192            .nip44_decrypt_note(&response, &request.pubkey)
193            .unwrap();
194        let nip46_response: Nip46Response = content.parse().unwrap();
195        assert_eq!(nip46_response.id, nip46_request.id);
196    }
197}