nip_55/
nip_46.rs

1use crate::{
2    json_rpc::{
3        JsonRpcError, JsonRpcErrorCode, JsonRpcRequest, JsonRpcResponse, JsonRpcResponseData,
4        SingleOrBatch,
5    },
6    stream_helper::map_sender,
7    KeyManager, Nip55Client, Nip55ServerStream, UdsClientError,
8};
9use futures::StreamExt;
10use nostr_sdk::{nips::nip46, Keys, PublicKey};
11use nostr_sdk::{Kind, SecretKey};
12use serde_json::Value;
13use std::{
14    pin::Pin,
15    sync::Arc,
16    task::{Context, Poll},
17};
18
19/// NIP-46 client that can make requests to a NIP-46 server running over NIP-55.
20pub struct Nip46OverNip55Client {
21    client: Nip55Client,
22}
23
24impl Nip46OverNip55Client {
25    /// Create a new NIP-46 client that will communicate with a NIP-46 server over NIP-55 on the specified Unix domain socket address.
26    pub fn new(uds_address: impl Into<String>) -> Self {
27        Self {
28            client: Nip55Client::new(uds_address),
29        }
30    }
31
32    /// Sign an event using the NIP-46 server over NIP-55.
33    pub async fn sign_event(
34        &self,
35        unsigned_event: nostr_sdk::UnsignedEvent,
36        user_pubkey: PublicKey,
37    ) -> Result<nostr_sdk::Event, Nip46OverNip55ClientError> {
38        let request = nip46::Request::SignEvent(unsigned_event);
39        let response = self.send_request(&request, user_pubkey).await?;
40        match response {
41            nip46::ResponseResult::SignEvent(signed_event) => Ok(*signed_event),
42            _ => Err(Nip46OverNip55ClientError::UdsClientError(
43                UdsClientError::MalformedResponse(anyhow::anyhow!(
44                    "Expected SignEvent response, but got {:?}",
45                    response
46                )),
47            )),
48        }
49    }
50
51    async fn send_request(
52        &self,
53        request: &nip46::Request,
54        user_pubkey: PublicKey,
55    ) -> Result<nip46::ResponseResult, Nip46OverNip55ClientError> {
56        let json_rpc_request = request.try_into().map_err(|_| {
57            Nip46OverNip55ClientError::UdsClientError(UdsClientError::RequestSerializationError)
58        })?;
59
60        let SingleOrBatch::Single(response) = self
61            .client
62            .send_request(
63                Kind::NostrConnect,
64                &SingleOrBatch::Single(json_rpc_request),
65                user_pubkey,
66            )
67            .await
68            .map_err(Nip46OverNip55ClientError::UdsClientError)?
69        else {
70            return Err(Nip46OverNip55ClientError::UdsClientError(
71                UdsClientError::MalformedResponse(anyhow::anyhow!(
72                    "Expected single response, but got batch response"
73                )),
74            ));
75        };
76
77        if let JsonRpcResponseData::Error { error } = response.data() {
78            return Err(Nip46OverNip55ClientError::JsonRpcError(error.clone()));
79        }
80
81        (&response).try_into().map_err(|e| {
82            Nip46OverNip55ClientError::UdsClientError(UdsClientError::MalformedResponse(e))
83        })
84    }
85}
86
87/// Error that can occur when communicating with a NIP-46 server over NIP-55.
88#[derive(Debug)]
89pub enum Nip46OverNip55ClientError {
90    /// A transport-level error occurred.
91    UdsClientError(UdsClientError),
92
93    /// The NIP-46 server returned an error response.
94    JsonRpcError(JsonRpcError),
95}
96
97pub struct Nip46OverNip55ServerStream {
98    #[allow(clippy::type_complexity)]
99    stream: Pin<
100        Box<
101            dyn futures::Stream<
102                    Item = (
103                        (Vec<nip46::Request>, PublicKey),
104                        futures::channel::oneshot::Sender<Nip46RequestApproval>,
105                    ),
106                > + Send,
107        >,
108    >,
109}
110
111impl futures::Stream for Nip46OverNip55ServerStream {
112    type Item = (
113        Vec<nip46::Request>,
114        PublicKey,
115        futures::channel::oneshot::Sender<Nip46RequestApproval>,
116    );
117
118    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
119        self.stream.poll_next_unpin(cx).map(|next_item_or| {
120            next_item_or.map(|next_item| (next_item.0 .0, next_item.0 .1, next_item.1))
121        })
122    }
123}
124
125impl Nip46OverNip55ServerStream {
126    /// Start a new NIP-46 server with NIP-55 as the transport that will listen for incoming connections at the specified Unix domain socket address.
127    pub fn start(
128        uds_address: impl Into<String>,
129        key_manager: Arc<dyn KeyManager>,
130    ) -> std::io::Result<Self> {
131        Ok(Self {
132            stream: Box::pin(
133                Nip55ServerStream::start::<(SingleOrBatch<JsonRpcRequest>, SecretKey)>(
134                    uds_address,
135                    key_manager,
136                )?
137                .map(|(request, secret_key, response_sender)| {
138                    (
139                        match request.clone() {
140                            SingleOrBatch::Single(request) =>
141                            // TODO: Ensure there is only one response and return an error if there is more than one.
142                            // Also don't panic if there is no response.
143                            {
144                                handle_request_array((vec![request], secret_key.clone()))
145                            }
146
147                            SingleOrBatch::Batch(requests) =>
148                            // TODO: Ensure the order and number of responses matches the order and number of requests.
149                            {
150                                handle_request_array((requests, secret_key.clone()))
151                            }
152                        },
153                        map_sender(response_sender, move |nip_46_request_approval| {
154                            match nip_46_request_approval {
155                                Nip46RequestApproval::Approve => request.map(|json_rpc_request| {
156                                    let (nip46_request, id): (nip46::Request, String) =
157                                        (&json_rpc_request).try_into().unwrap();
158
159                                    let nip46_response = match nip46_request {
160                                        nip46::Request::SignEvent(unsigned_event) => {
161                                            nip46::ResponseResult::SignEvent(Box::from(
162                                                unsigned_event
163                                                    .sign(&Keys::new(secret_key.clone()))
164                                                    .unwrap(),
165                                            ))
166                                        }
167                                        // TODO: Implement the rest of the NIP-46 methods.
168                                        _ => {
169                                            return JsonRpcResponseData::Error {
170                                                error: JsonRpcError::new(
171                                                    JsonRpcErrorCode::MethodNotFound,
172                                                    "Method not implemented".to_string(),
173                                                    None,
174                                                ),
175                                            };
176                                        }
177                                    };
178
179                                    (&nip46_response, &id).try_into().unwrap()
180                                }),
181                                Nip46RequestApproval::Reject => {
182                                    request.map(|_| JsonRpcResponseData::Error {
183                                        error: JsonRpcError::new(
184                                            JsonRpcErrorCode::InternalError,
185                                            "Batch request rejected".to_string(),
186                                            None,
187                                        ),
188                                    })
189                                }
190                            }
191                        }),
192                    )
193                }),
194            ),
195        })
196    }
197}
198
199/// Approval or rejection of a NIP-46 request. Used in the server to determine whether to handle requests or not.
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201pub enum Nip46RequestApproval {
202    Approve,
203    Reject,
204}
205
206fn handle_request_array(
207    requests: (Vec<JsonRpcRequest>, SecretKey),
208) -> (Vec<nostr_sdk::nips::nip46::Request>, PublicKey) {
209    let nip46_requests: Vec<nostr_sdk::nips::nip46::Request> = requests
210        .0
211        .into_iter()
212        .filter_map(|request| (&request).try_into().ok())
213        .map(|(nip46_request, _nip46_request_id)| nip46_request)
214        .collect();
215
216    let secp = nostr_sdk::secp256k1::Secp256k1::new();
217
218    let public_key: PublicKey = requests.1.x_only_public_key(&secp).0.into();
219
220    (nip46_requests, public_key)
221}
222
223impl TryInto<JsonRpcRequest> for &nip46::Request {
224    type Error = anyhow::Error;
225
226    fn try_into(self) -> Result<JsonRpcRequest, Self::Error> {
227        // TODO: Remove this clone.
228        let object_json = match serde_json::json!(nip46::Message::request(self.clone())) {
229            Value::Object(mut object) => {
230                object.insert("jsonrpc".to_string(), "2.0".into());
231                object
232            }
233            _ => {
234                return Err(anyhow::anyhow!(
235                    "Failed to convert NIP-46 request to JSON-RPC request"
236                ));
237            }
238        };
239
240        Ok(serde_json::from_value(Value::Object(object_json))?)
241    }
242}
243
244impl TryFrom<&JsonRpcRequest> for (nip46::Request, String) {
245    type Error = anyhow::Error;
246
247    fn try_from(value: &JsonRpcRequest) -> Result<Self, Self::Error> {
248        let message: nip46::Message = serde_json::from_value(serde_json::json!(value))?;
249        let request_id = message.id().to_string();
250        Ok((message.to_request()?, request_id))
251    }
252}
253
254impl TryInto<JsonRpcResponseData> for (&nip46::ResponseResult, &String) {
255    type Error = anyhow::Error;
256
257    fn try_into(self) -> Result<JsonRpcResponseData, anyhow::Error> {
258        // TODO: Remove this clone.
259        Ok(serde_json::from_value(serde_json::json!(
260            nip46::Message::response(self.1, Some(self.0.clone()), None)
261        ))?)
262    }
263}
264
265impl TryFrom<&JsonRpcResponse> for nip46::ResponseResult {
266    type Error = anyhow::Error;
267
268    fn try_from(value: &JsonRpcResponse) -> Result<Self, anyhow::Error> {
269        let message: nip46::Message = serde_json::from_value(serde_json::json!(value))?;
270        match message {
271            nip46::Message::Response { result, error, .. } => match (result, error) {
272                (Some(result), None) => Ok(result),
273                (None, Some(error)) => Err(anyhow::anyhow!(error)),
274                _ => Err(anyhow::anyhow!("Invalid NIP-46 response")),
275            },
276            nip46::Message::Request { .. } => Err(anyhow::anyhow!("Invalid NIP-46 response")),
277        }
278    }
279}
280
281// TODO: Currently we're only testing the happy path. Add more tests to cover error/edge cases.
282#[cfg(test)]
283mod tests {
284    use super::*;
285    use crate::KeyManager;
286    use async_trait::async_trait;
287    use nostr_sdk::Keys;
288    use std::collections::HashMap;
289    use std::sync::Arc;
290    use std::sync::Mutex;
291
292    fn get_random_uds_address() -> String {
293        format!("/tmp/test-{}.sock", uuid::Uuid::new_v4())
294    }
295
296    struct MockKeyManager {
297        keys: Arc<Mutex<HashMap<PublicKey, SecretKey>>>,
298    }
299
300    impl Default for MockKeyManager {
301        fn default() -> Self {
302            Self {
303                keys: Arc::new(Mutex::new(HashMap::new())),
304            }
305        }
306    }
307
308    #[async_trait]
309    impl KeyManager for MockKeyManager {
310        fn get_secret_key(&self, public_key: &PublicKey) -> Option<SecretKey> {
311            self.keys.lock().unwrap().get(public_key).cloned()
312        }
313    }
314
315    impl MockKeyManager {
316        fn new() -> Self {
317            Self::default()
318        }
319
320        fn new_with_single_key(secret_key: SecretKey) -> Self {
321            let key_manager = Self::new();
322            key_manager.add_key(secret_key);
323            key_manager
324        }
325
326        fn add_key(&self, secret_key: SecretKey) {
327            self.keys.lock().unwrap().insert(
328                PublicKey::from(
329                    secret_key
330                        .x_only_public_key(&nostr_sdk::secp256k1::Secp256k1::new())
331                        .0,
332                ),
333                secret_key,
334            );
335        }
336    }
337
338    #[tokio::test]
339    async fn test_nip46_over_nip55_registered_key() {
340        let keypair = Keys::generate();
341        let key_manager = MockKeyManager::new_with_single_key(keypair.secret_key().clone());
342
343        // Since we're starting the server in a separate task, we need to wait for it to start.
344        let (server_started_sender, server_started_receiver) = futures::channel::oneshot::channel();
345
346        let uds_address = get_random_uds_address();
347        let uds_address_clone = uds_address.clone();
348
349        let server_handle = tokio::task::spawn(async {
350            let mut foo =
351                Nip46OverNip55ServerStream::start(uds_address_clone, Arc::new(key_manager))
352                    .expect("Failed to start NIP-46 over NIP-55 server");
353
354            server_started_sender.send(()).unwrap();
355
356            while let Some((_request_list, _public_key, response_sender)) = foo.next().await {
357                response_sender.send(Nip46RequestApproval::Approve).unwrap();
358            }
359        });
360
361        server_started_receiver.await.unwrap();
362
363        let client = Nip46OverNip55Client::new(uds_address);
364
365        // Test multiple times to ensure stream doesn't lock up.
366        for _ in 0..10 {
367            let unsigned_event = nostr_sdk::EventBuilder::new(Kind::TextNote, "example text", None)
368                .to_unsigned_event(keypair.public_key());
369
370            let signed_event = client
371                .sign_event(unsigned_event, keypair.public_key())
372                .await
373                .expect("Failed to send NIP-46 request");
374
375            signed_event
376                .verify()
377                .expect("Failed to verify signed event");
378            assert_eq!(signed_event.kind, Kind::TextNote);
379            assert_eq!(signed_event.content, "example text");
380        }
381
382        server_handle.abort();
383    }
384
385    #[tokio::test]
386    async fn test_nip46_over_nip55_unregistered_key() {
387        let key_manager = MockKeyManager::new();
388
389        // Since we're starting the server in a separate task, we need to wait for it to start.
390        let (server_started_sender, server_started_receiver) = futures::channel::oneshot::channel();
391
392        let uds_address = get_random_uds_address();
393        let uds_address_clone = uds_address.clone();
394
395        let server_handle = tokio::task::spawn(async {
396            let mut foo =
397                Nip46OverNip55ServerStream::start(uds_address_clone, Arc::new(key_manager))
398                    .expect("Failed to start NIP-46 over NIP-55 server");
399
400            server_started_sender.send(()).unwrap();
401
402            while let Some((_request_list, _public_key, response_sender)) = foo.next().await {
403                response_sender.send(Nip46RequestApproval::Approve).unwrap();
404            }
405        });
406
407        server_started_receiver.await.unwrap();
408
409        let client = Nip46OverNip55Client::new(uds_address);
410
411        // Test multiple times to ensure stream doesn't lock up.
412        for _ in 0..10 {
413            let unregistered_keypair = Keys::generate();
414
415            let unsigned_event = nostr_sdk::EventBuilder::new(Kind::TextNote, "example text", None)
416                .to_unsigned_event(unregistered_keypair.public_key());
417
418            let signed_event_error = client
419                .sign_event(unsigned_event.clone(), unregistered_keypair.public_key())
420                .await
421                .unwrap_err();
422
423            assert!(matches!(
424                signed_event_error,
425                Nip46OverNip55ClientError::UdsClientError(UdsClientError::MalformedResponse(_))
426            ));
427        }
428
429        server_handle.abort();
430    }
431
432    #[tokio::test]
433    async fn test_nip46_over_nip55_no_server() {
434        let keypair = Keys::generate();
435
436        let client = Nip46OverNip55Client::new(get_random_uds_address());
437
438        let unsigned_event = nostr_sdk::EventBuilder::new(Kind::TextNote, "example text", None)
439            .to_unsigned_event(keypair.public_key());
440
441        assert!(matches!(
442            client
443                .sign_event(unsigned_event, keypair.public_key())
444                .await,
445            Err(Nip46OverNip55ClientError::UdsClientError(
446                UdsClientError::ServerNotRunning
447            ))
448        ));
449    }
450}