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 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 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}