nostr_types/nip46/
mod.rs

1use crate::{
2    client, ContentEncryptionAlgorithm, EncryptedPrivateKey, Error, Event, EventKind, Filter,
3    KeySecurity, KeySigner, LockableSigner, PreEvent, PublicKey, RelayUrl, Signer,
4};
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7use std::sync::Arc;
8use std::time::Duration;
9
10mod request;
11pub use request::Nip46Request;
12
13mod response;
14pub use response::Nip46Response;
15
16mod params;
17pub use params::Nip46ConnectionParameters;
18
19mod prebunk;
20pub use prebunk::PreBunkerClient;
21
22/// This is a NIP-46 Bunker client
23#[derive(Debug, Serialize)]
24pub struct BunkerClient {
25    /// The pubkey of the bunker
26    pub remote_signer_pubkey: PublicKey,
27
28    /// The relay the bunker is listening at
29    pub relay_url: RelayUrl,
30
31    /// Our local identity
32    pub local_signer: Arc<KeySigner>,
33
34    /// User Public Key
35    pub public_key: PublicKey,
36
37    /// Timeout
38    pub timeout: Duration,
39
40    /// Client
41    #[serde(skip_serializing)]
42    pub client: client::Client,
43}
44
45impl BunkerClient {
46    /// Create a new BunkerClient from stored data. This will be in the locked state.
47    pub async fn from_stored_data(
48        remote_signer_pubkey: PublicKey,
49        relay_url: RelayUrl,
50        keysigner: KeySigner,
51        public_key: PublicKey,
52        timeout: Duration,
53    ) -> BunkerClient {
54        let client = client::Client::new(relay_url.as_str());
55        BunkerClient {
56            remote_signer_pubkey,
57            relay_url,
58            local_signer: Arc::new(keysigner),
59            public_key,
60            timeout,
61            client,
62        }
63    }
64
65    /// Is the signer locked?
66    pub fn is_locked(&self) -> bool {
67        self.local_signer.is_locked()
68    }
69
70    /// Unlock (if locked)
71    pub fn unlock(&self, password: &str) -> Result<(), Error> {
72        self.local_signer.unlock(password)
73    }
74
75    /// Lock
76    pub fn lock(&self) {
77        self.local_signer.lock()
78    }
79
80    /// Change passphrase
81    pub fn change_passphrase(&self, old: &str, new: &str, log_n: u8) -> Result<(), Error> {
82        self.local_signer.change_passphrase(old, new, log_n)
83    }
84
85    /// Send a `Nip46Request` and wait for a `Nip46Response` (up to our timeout)
86    pub async fn call(&self, request: Nip46Request) -> Result<Nip46Response, Error> {
87        // Subscribe first
88        let mut filter = Filter::new();
89        filter.add_author(self.remote_signer_pubkey);
90        filter.add_event_kind(EventKind::NostrConnect);
91        filter.add_tag_value('p', self.local_signer.public_key().as_hex_string());
92        filter.limit = Some(1);
93        let sub_id = self.client.subscribe(filter.clone(), self.timeout).await?;
94        let event = request
95            .to_event(self.remote_signer_pubkey, self.local_signer.clone())
96            .await?;
97
98        // Post event to server and wait for OK
99        let event_id = event.id;
100        self.client.post_event(event, self.timeout).await?;
101        let (ok, msg) = self.client.wait_for_ok(event_id, self.timeout).await?;
102        if !ok {
103            return Err(Error::Nip46FailedToPost(msg));
104        }
105
106        // Wait for a response
107        let event = self
108            .client
109            .wait_for_subscribed_event(sub_id.clone(), self.timeout)
110            .await?;
111
112        let contents = self.local_signer.decrypt_event_contents(&event).await?;
113
114        // Convert into a response
115        let response: Nip46Response = serde_json::from_str(&contents)?;
116
117        // Close the subscription
118        self.client.close_subscription(sub_id).await?;
119
120        Ok(response)
121    }
122
123    /// Disconnect from the relay
124    pub async fn disconnect(&self) -> Result<(), Error> {
125        self.client.disconnect().await
126    }
127
128    /// Disconnect from the relay and lock
129    pub async fn disconnect_and_lock(&self) -> Result<(), Error> {
130        self.client.disconnect().await?;
131        self.local_signer.lock();
132        Ok(())
133    }
134}
135
136#[async_trait]
137impl Signer for BunkerClient {
138    fn public_key(&self) -> PublicKey {
139        self.public_key
140    }
141
142    fn encrypted_private_key(&self) -> Option<EncryptedPrivateKey> {
143        // NIP-46 does not offer an export function (yet)
144        None
145    }
146
147    async fn sign_event(&self, pre_event: PreEvent) -> Result<Event, Error> {
148        if self.is_locked() {
149            return Err(Error::SignerIsLocked);
150        }
151
152        let pre_event_string = serde_json::to_string(&pre_event)?;
153        let request = Nip46Request::new("sign_event".to_owned(), vec![pre_event_string]);
154        let response = self.call(request).await?;
155        if let Some(error) = response.error {
156            if !error.is_empty() {
157                return Err(Error::Nip46Error(error));
158            }
159        }
160        let event: Event = serde_json::from_str(&response.result)?;
161        Ok(event)
162    }
163
164    async fn encrypt(
165        &self,
166        other: &PublicKey,
167        plaintext: &str,
168        algo: ContentEncryptionAlgorithm,
169    ) -> Result<String, Error> {
170        if self.is_locked() {
171            return Err(Error::SignerIsLocked);
172        }
173
174        let cmd = match algo {
175            ContentEncryptionAlgorithm::Nip04 => "nip04_encrypt",
176            ContentEncryptionAlgorithm::Nip44v1Unpadded => return Err(Error::UnsupportedAlgorithm),
177            ContentEncryptionAlgorithm::Nip44v1Padded => return Err(Error::UnsupportedAlgorithm),
178            ContentEncryptionAlgorithm::Nip44v2 => "nip44_encrypt",
179        };
180
181        let request = Nip46Request::new(
182            cmd.to_owned(),
183            vec![other.as_hex_string(), plaintext.to_owned()],
184        );
185
186        let response = self.call(request).await?;
187        if let Some(error) = response.error {
188            if !error.is_empty() {
189                return Err(Error::Nip46Error(error));
190            }
191        }
192
193        let ciphertext: String = serde_json::from_str(&response.result)?;
194
195        Ok(ciphertext)
196    }
197
198    async fn decrypt(&self, other: &PublicKey, ciphertext: &str) -> Result<String, Error> {
199        if self.is_locked() {
200            return Err(Error::SignerIsLocked);
201        }
202
203        let cmd = if ciphertext.contains("?iv=") {
204            "nip04_decrypt"
205        } else {
206            "nip44_decrypt"
207        };
208
209        let request = Nip46Request::new(
210            cmd.to_owned(),
211            vec![other.as_hex_string(), ciphertext.to_owned()],
212        );
213
214        let response = self.call(request).await?;
215        if let Some(error) = response.error {
216            if !error.is_empty() {
217                return Err(Error::Nip46Error(error));
218            }
219        }
220
221        let plaintext: String = serde_json::from_str(&response.result)?;
222
223        Ok(plaintext)
224    }
225
226    fn key_security(&self) -> Result<KeySecurity, Error> {
227        Ok(KeySecurity::NotTracked)
228    }
229}
230
231use serde::de::Error as DeError;
232use serde::de::{Deserializer, SeqAccess, Visitor};
233use std::fmt;
234
235impl<'de> Deserialize<'de> for BunkerClient {
236    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
237    where
238        D: Deserializer<'de>,
239    {
240        deserializer.deserialize_seq(BunkerClientVisitor)
241    }
242}
243
244struct BunkerClientVisitor;
245
246impl<'de> Visitor<'de> for BunkerClientVisitor {
247    type Value = BunkerClient;
248
249    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
250        write!(f, "a serialized BunkerClient as a sequence")
251    }
252
253    fn visit_seq<A>(self, mut access: A) -> Result<BunkerClient, A::Error>
254    where
255        A: SeqAccess<'de>,
256    {
257        let remote_signer_pubkey = access
258            .next_element::<PublicKey>()?
259            .ok_or_else(|| DeError::custom("Missing remote_signer_pubkey"))?;
260        let relay_url = access
261            .next_element::<RelayUrl>()?
262            .ok_or_else(|| DeError::custom("Missing relay_url"))?;
263        let local_signer = access
264            .next_element::<Arc<KeySigner>>()?
265            .ok_or_else(|| DeError::custom("Missing local_signer"))?;
266        let public_key = access
267            .next_element::<PublicKey>()?
268            .ok_or_else(|| DeError::custom("Missing public_key"))?;
269        let timeout = access
270            .next_element::<Duration>()?
271            .ok_or_else(|| DeError::custom("Missing timeout"))?;
272        let client = client::Client::new(relay_url.as_str());
273        Ok(BunkerClient {
274            remote_signer_pubkey,
275            relay_url,
276            local_signer,
277            public_key,
278            timeout,
279            client,
280        })
281    }
282}
283
284#[cfg(test)]
285mod test {
286    use super::*;
287    use crate::PrivateKey;
288
289    #[test]
290    fn test_bunker_client_serde() {
291        let prebunk = PreBunkerClient::new(
292            PrivateKey::generate().public_key(),
293            RelayUrl::try_from_str("wss://relay.example/").unwrap(),
294            None,
295            "password",
296        )
297        .unwrap();
298
299        let s = serde_json::to_string(&prebunk).unwrap();
300        println!("{s}");
301        let prebunk2: PreBunkerClient = serde_json::from_str(&*s).unwrap();
302        assert_eq!(prebunk.remote_signer_pubkey, prebunk2.remote_signer_pubkey);
303        assert_eq!(prebunk.relay_url, prebunk2.relay_url);
304        assert_eq!(prebunk.connect_secret, prebunk2.connect_secret);
305        assert_eq!(
306            prebunk.local_signer.encrypted_private_key(),
307            prebunk2.local_signer.encrypted_private_key()
308        );
309    }
310}