nip_70/
lib.rs

1use async_trait::async_trait;
2use nip_55::json_rpc::{
3    JsonRpcError, JsonRpcErrorCode, JsonRpcId, JsonRpcRequest, JsonRpcResponseData,
4    JsonRpcServerHandler, JsonRpcStructuredValue,
5};
6pub use nip_55::UdsClientError;
7use nip_55::{Nip55Client, Nip55Server};
8use nostr_sdk::{Event, Keys, Kind, PublicKey, UnsignedEvent};
9use serde::{Deserialize, Serialize};
10use serde_json::json;
11use std::sync::Arc;
12
13const NIP70_UDS_ADDRESS: &str = "/tmp/nip-70.sock";
14
15const METHOD_NAME_SIGN_EVENT: &str = "signEvent";
16
17/// Errors that can be returned from [`Nip70`] trait functions.
18#[derive(Clone, Debug, PartialEq)]
19pub enum Nip70ServerError {
20    /// The server rejected the request. This most likely means that the user
21    /// declined to perform the operation for the app that requested it.
22    Rejected,
23
24    /// The server encountered an internal error while processing the request.
25    InternalError,
26
27    /// The server does not support the requested method.
28    MethodNotFound,
29}
30
31impl Nip70ServerError {
32    fn to_json_rpc_error(&self) -> JsonRpcError {
33        match self {
34            Nip70ServerError::Rejected => {
35                JsonRpcError::new(JsonRpcErrorCode::Custom(1), "Rejected".to_string(), None)
36            }
37            Nip70ServerError::InternalError => JsonRpcError::new(
38                JsonRpcErrorCode::InternalError,
39                "Internal error".to_string(),
40                None,
41            ),
42            Nip70ServerError::MethodNotFound => JsonRpcError::new(
43                JsonRpcErrorCode::MethodNotFound,
44                "Method not found".to_string(),
45                None,
46            ),
47        }
48    }
49}
50
51/// Defines the server-side functionality for the NIP-70 protocol.
52/// Implement this trait and pass it to [`Nip70Server::start()`] to run a NIP-70 server.
53#[async_trait]
54pub trait Nip70: Send + Sync {
55    /// Signs a Nostr event on behalf of the signed-in user.
56    async fn sign_event(&self, event: UnsignedEvent) -> Result<Event, Nip70ServerError>;
57}
58
59/// A server for the NIP-70 protocol.
60pub struct Nip70Server {
61    nip55_server: Nip55Server,
62}
63
64impl Nip70Server {
65    /// Creates and starts a NIP-70 compliant Unix domain socket server.
66    pub fn start(nip70: Arc<dyn Nip70>, server_keypair: Keys) -> std::io::Result<Self> {
67        Self::start_internal(nip70, NIP70_UDS_ADDRESS.to_string(), server_keypair)
68    }
69
70    fn start_internal(
71        nip70: Arc<dyn Nip70>,
72        uds_address: String,
73        server_keypair: Keys,
74    ) -> std::io::Result<Self> {
75        Ok(Self {
76            nip55_server: Nip55Server::start(
77                uds_address,
78                server_keypair,
79                Box::from(Nip70ServerHandler { nip70 }),
80            )?,
81        })
82    }
83
84    /// Stops the NIP-70 server.
85    pub fn stop(self) {
86        self.nip55_server.stop();
87    }
88}
89
90struct Nip70ServerHandler {
91    nip70: Arc<dyn Nip70>,
92}
93
94#[async_trait::async_trait]
95impl JsonRpcServerHandler for Nip70ServerHandler {
96    async fn handle_batch_request(
97        &self,
98        requests: Vec<JsonRpcRequest>,
99    ) -> Vec<JsonRpcResponseData> {
100        let mut responses = Vec::new();
101
102        for request in requests {
103            let parsed_request = match Nip70Request::from_json_rpc_request(&request) {
104                Ok(request) => request,
105                Err(error) => {
106                    responses.push(JsonRpcResponseData::Error {
107                        error: error.to_json_rpc_error(),
108                    });
109                    continue;
110                }
111            };
112
113            let response_or = match parsed_request {
114                // TODO: Let's get the pubkey and check it against the unsigned event before signing.
115                Nip70Request::SignEvent(event) => match self.nip70.sign_event(event).await {
116                    Ok(event) => Ok(Nip70Response::SignEvent(event)),
117                    Err(err) => Err(err),
118                },
119            };
120
121            responses.push(match response_or {
122                Ok(response) => response.to_json_rpc_response_data(),
123                Err(err) => JsonRpcResponseData::Error {
124                    error: err.to_json_rpc_error(),
125                },
126            });
127        }
128
129        responses
130    }
131}
132
133/// Errors that can be returned from [`Nip70Client`] functions.
134#[derive(Clone, Debug, PartialEq)]
135pub enum Nip70ClientError {
136    UdsClientError(UdsClientError),
137    ProtocolError,
138    ServerError(Nip70ServerError),
139}
140
141impl Nip70ClientError {
142    fn from_json_rpc_error(error: &JsonRpcError) -> Self {
143        match error.code() {
144            JsonRpcErrorCode::Custom(1) => Self::ServerError(Nip70ServerError::Rejected),
145            JsonRpcErrorCode::InternalError => Self::ServerError(Nip70ServerError::InternalError),
146            JsonRpcErrorCode::MethodNotFound => Self::ServerError(Nip70ServerError::MethodNotFound),
147            _ => Self::ProtocolError,
148        }
149    }
150}
151
152/// A client for the NIP-70 protocol.
153#[derive(Clone)]
154pub struct Nip70Client {
155    transport: Nip55Client,
156}
157
158impl Default for Nip70Client {
159    fn default() -> Self {
160        Self::new()
161    }
162}
163
164impl Nip70Client {
165    pub fn new() -> Self {
166        Self::new_internal(NIP70_UDS_ADDRESS.to_string())
167    }
168
169    fn new_internal(uds_address: String) -> Self {
170        Self {
171            transport: Nip55Client::new(uds_address),
172        }
173    }
174
175    /// Signs a Nostr event on behalf of the signed-in user using the NIP-70 server.
176    pub async fn sign_event(
177        &self,
178        event: UnsignedEvent,
179        server_pubkey: PublicKey,
180    ) -> Result<Event, Nip70ClientError> {
181        self.send_request(Nip70Request::SignEvent(event), server_pubkey)
182            .await
183            .map(|response| match response {
184                Nip70Response::SignEvent(event) => Ok(event),
185            })?
186    }
187
188    async fn send_request(
189        &self,
190        request: Nip70Request,
191        server_pubkey: PublicKey,
192    ) -> Result<Nip70Response, Nip70ClientError> {
193        // TODO: Use a real request id.
194        let json_rpc_request = request.to_json_rpc_request(JsonRpcId::Null);
195        let json_rpc_response = self
196            .transport
197            .send_request(Kind::NostrConnect, &json_rpc_request, server_pubkey)
198            .await
199            .map_err(Nip70ClientError::UdsClientError)?;
200        Nip70Response::from_json_rpc_response_data(json_rpc_response.data())
201    }
202}
203
204enum Nip70Request {
205    SignEvent(UnsignedEvent),
206}
207
208impl Nip70Request {
209    fn get_method_name(&self) -> &str {
210        match self {
211            Nip70Request::SignEvent(_) => METHOD_NAME_SIGN_EVENT,
212        }
213    }
214
215    fn get_params(&self) -> Option<JsonRpcStructuredValue> {
216        match self {
217            Nip70Request::SignEvent(event) => Some(JsonRpcStructuredValue::Object(
218                // This should never panic, since we're converting an `UnsignedEvent`
219                // struct, which should always serialize to a JSON object.
220                json!(event)
221                    .as_object()
222                    .expect("Failed to convert event to object")
223                    .clone(),
224            )),
225        }
226    }
227
228    fn to_json_rpc_request(&self, request_id: JsonRpcId) -> JsonRpcRequest {
229        JsonRpcRequest::new(
230            self.get_method_name().to_string(),
231            self.get_params(),
232            request_id,
233        )
234    }
235
236    fn from_json_rpc_request(request: &JsonRpcRequest) -> Result<Self, Nip70ServerError> {
237        match request.method() {
238            METHOD_NAME_SIGN_EVENT => Ok(Nip70Request::SignEvent(
239                if let Ok(value) =
240                    serde_json::from_value(match request.params().map(|v| v.clone().into_value()) {
241                        Some(value) => value,
242                        None => return Err(Nip70ServerError::InternalError),
243                    })
244                {
245                    value
246                } else {
247                    return Err(Nip70ServerError::InternalError);
248                },
249            )),
250            _ => Err(Nip70ServerError::MethodNotFound),
251        }
252    }
253}
254
255#[derive(Serialize, Deserialize)]
256#[serde(untagged)]
257enum Nip70Response {
258    SignEvent(Event),
259}
260
261impl Nip70Response {
262    fn to_json_rpc_response_data(&self) -> JsonRpcResponseData {
263        JsonRpcResponseData::Success {
264            result: serde_json::to_value(self).unwrap(),
265        }
266    }
267
268    fn from_json_rpc_response_data(
269        response: &JsonRpcResponseData,
270    ) -> Result<Self, Nip70ClientError> {
271        let result = match response {
272            JsonRpcResponseData::Success { result } => result,
273            JsonRpcResponseData::Error { error } => {
274                return Err(Nip70ClientError::from_json_rpc_error(error))
275            }
276        };
277
278        if let Ok(value) = serde_json::from_value(result.clone()) {
279            Ok(value)
280        } else {
281            Err(Nip70ClientError::ProtocolError)
282        }
283    }
284}
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289
290    use nostr_sdk::{EventId, Keys, Kind, Timestamp};
291    use std::{sync::Mutex, time::Duration};
292
293    struct TestNip70Implementation {
294        keys: Keys,
295        reject_all_requests: bool,
296    }
297
298    impl TestNip70Implementation {
299        fn new_with_generated_keys() -> (Self, Keys) {
300            let keys = Keys::generate();
301
302            (
303                Self {
304                    keys: keys.clone(),
305                    reject_all_requests: false,
306                },
307                keys,
308            )
309        }
310
311        fn new_rejecting_all_requests() -> (Self, Keys) {
312            let keys = Keys::generate();
313
314            (
315                Self {
316                    keys: keys.clone(),
317                    reject_all_requests: true,
318                },
319                keys,
320            )
321        }
322    }
323
324    #[async_trait]
325    impl Nip70 for TestNip70Implementation {
326        async fn sign_event(&self, event: UnsignedEvent) -> Result<Event, Nip70ServerError> {
327            if self.reject_all_requests {
328                return Err(Nip70ServerError::Rejected);
329            }
330
331            event
332                .sign(&self.keys)
333                .map_err(|_| Nip70ServerError::InternalError)
334        }
335    }
336
337    lazy_static::lazy_static! {
338        static ref UDS_ADDRESS_COUNTER: Arc<Mutex<i32>> = Arc::new(Mutex::new(0));
339    }
340
341    fn get_free_uds_address() -> String {
342        let mut counter = UDS_ADDRESS_COUNTER.lock().unwrap();
343        let uds_address = format!("/tmp/nip70-{}.sock", *counter);
344        *counter += 1;
345        uds_address
346    }
347
348    fn get_nip70_server_and_client_test_pair(
349        nip70: Arc<dyn Nip70>,
350    ) -> (Nip70Server, Nip70Client, Keys) {
351        let uds_address = get_free_uds_address();
352        let server_keypair = Keys::generate();
353        let server =
354            Nip70Server::start_internal(nip70, uds_address.clone(), server_keypair.clone())
355                .unwrap();
356        let client = Nip70Client::new_internal(uds_address);
357        (server, client, server_keypair)
358    }
359
360    #[tokio::test]
361    async fn sign_event_over_uds() {
362        let (nip70, keys) = TestNip70Implementation::new_with_generated_keys();
363        let (server, client, server_keypair) =
364            get_nip70_server_and_client_test_pair(Arc::from(nip70));
365
366        let pubkey = keys.public_key();
367        let created_at = Timestamp::now();
368        let kind = Kind::TextNote;
369        let tags = vec![];
370        let content = String::from("Hello, world!");
371        let unsigned_event = UnsignedEvent {
372            id: Some(EventId::new(&pubkey, created_at, &kind, &tags, &content)),
373            pubkey,
374            created_at,
375            kind,
376            tags,
377            content,
378        };
379
380        let event = client
381            .sign_event(unsigned_event, server_keypair.public_key())
382            .await
383            .unwrap();
384
385        assert!(event.verify().is_ok());
386
387        server.stop();
388    }
389
390    #[tokio::test]
391    async fn sign_large_event_over_uds() {
392        let (nip70, keys) = TestNip70Implementation::new_with_generated_keys();
393        let (server, client, server_keypair) =
394            get_nip70_server_and_client_test_pair(Arc::from(nip70));
395
396        let pubkey = keys.public_key();
397        let created_at = Timestamp::now();
398        let kind = Kind::TextNote;
399        let tags = vec![];
400        let content: String = std::iter::repeat('a').take((2 as usize).pow(20)).collect();
401        let unsigned_event = UnsignedEvent {
402            id: Some(EventId::new(&pubkey, created_at, &kind, &tags, &content)),
403            pubkey,
404            created_at,
405            kind,
406            tags,
407            content,
408        };
409
410        let event = client
411            .sign_event(unsigned_event, server_keypair.public_key())
412            .await
413            .unwrap();
414
415        assert!(event.verify().is_ok());
416
417        server.stop();
418    }
419
420    #[test]
421    #[should_panic(expected = "must be called from the context of a Tokio 1.x runtime")]
422    fn run_server_without_async_runtime() {
423        let uds_address = get_free_uds_address();
424        let (nip70, keys) = TestNip70Implementation::new_with_generated_keys();
425        Nip70Server::start_internal(Arc::from(nip70), uds_address.clone(), keys).unwrap();
426    }
427
428    #[tokio::test]
429    async fn sign_event_over_uds_load() {
430        let (nip70, keys) = TestNip70Implementation::new_with_generated_keys();
431        let (server, client, server_keypair) =
432            get_nip70_server_and_client_test_pair(Arc::from(nip70));
433
434        let pubkey = keys.public_key();
435
436        let mut client_handles = Vec::new();
437        for i in 0..128 {
438            let client = client.clone();
439            let server_keypair = server_keypair.clone();
440            let handle = tokio::spawn(async move {
441                for j in 0..20 {
442                    let created_at = Timestamp::now();
443                    let kind = Kind::TextNote;
444                    let tags = vec![];
445                    let content = format!("Message {} from thread {}.", j, i);
446                    let unsigned_event = UnsignedEvent {
447                        id: Some(EventId::new(&pubkey, created_at, &kind, &tags, &content)),
448                        pubkey,
449                        created_at,
450                        kind,
451                        tags,
452                        content,
453                    };
454
455                    let event = client
456                        .sign_event(unsigned_event.clone(), server_keypair.public_key())
457                        .await
458                        .unwrap();
459
460                    assert!(event.verify().is_ok());
461                    assert_eq!(Some(event.id), unsigned_event.id);
462
463                    // Give other client tasks a chance to send requests.
464                    tokio::time::sleep(Duration::from_millis(50)).await;
465                }
466            });
467            client_handles.push(handle);
468        }
469
470        for handle in client_handles {
471            handle.await.unwrap();
472        }
473
474        server.stop();
475    }
476
477    #[tokio::test]
478    async fn make_rpc_with_no_server() {
479        let client = Nip70Client::new_internal(get_free_uds_address());
480        let keys = Keys::generate();
481
482        let pubkey = keys.public_key();
483        let created_at = Timestamp::now();
484        let kind = Kind::TextNote;
485        let tags = vec![];
486        let content = String::from("Hello, world!");
487        let unsigned_event = UnsignedEvent {
488            id: Some(EventId::new(&pubkey, created_at, &kind, &tags, &content)),
489            pubkey,
490            created_at,
491            kind,
492            tags,
493            content,
494        };
495
496        assert_eq!(
497            client.sign_event(unsigned_event, pubkey).await,
498            Err(Nip70ClientError::UdsClientError(
499                UdsClientError::ServerNotRunning
500            ))
501        );
502    }
503
504    #[tokio::test]
505    async fn make_rpc_with_rejected_request() {
506        let (nip70, keys) = TestNip70Implementation::new_rejecting_all_requests();
507        let (server, client, server_keypair) =
508            get_nip70_server_and_client_test_pair(Arc::from(nip70));
509
510        let pubkey = keys.public_key();
511        let created_at = Timestamp::now();
512        let kind = Kind::TextNote;
513        let tags = vec![];
514        let content = String::from("Hello, world!");
515        let unsigned_event = UnsignedEvent {
516            id: Some(EventId::new(&pubkey, created_at, &kind, &tags, &content)),
517            pubkey,
518            created_at,
519            kind,
520            tags,
521            content,
522        };
523
524        assert_eq!(
525            client
526                .sign_event(unsigned_event, server_keypair.public_key())
527                .await,
528            Err(Nip70ClientError::ServerError(Nip70ServerError::Rejected))
529        );
530
531        server.stop();
532    }
533}