nostr_signer/
lib.rs

1// Copyright (c) 2022-2023 Yuki Kishimoto
2// Copyright (c) 2023-2024 Rust Nostr Developers
3// Distributed under the MIT software license
4
5//! Nostr Signer
6
7#![forbid(unsafe_code)]
8#![warn(missing_docs)]
9#![warn(rustdoc::bare_urls)]
10#![warn(clippy::large_futures)]
11
12use std::fmt;
13
14use nostr::prelude::*;
15use thiserror::Error;
16
17#[cfg(feature = "nip46")]
18pub mod nip46;
19pub mod prelude;
20
21#[cfg(feature = "nip46")]
22pub use self::nip46::{Nip46Signer, NostrConnectRemoteSigner, NostrConnectSignerActions};
23
24/// Nostr Signer error
25#[derive(Debug, Error)]
26pub enum Error {
27    /// Keys error
28    #[error(transparent)]
29    Keys(#[from] key::Error),
30    /// Unsigned event error
31    #[error(transparent)]
32    Unsigned(#[from] unsigned::Error),
33    /// NIP04 error
34    #[cfg(feature = "nip04")]
35    #[error(transparent)]
36    NIP04(#[from] nip04::Error),
37    /// NIP07 error
38    #[cfg(all(feature = "nip07", target_arch = "wasm32"))]
39    #[error(transparent)]
40    NIP07(#[from] nip07::Error),
41    /// NIP44 error
42    #[cfg(feature = "nip44")]
43    #[error(transparent)]
44    NIP44(#[from] nip44::Error),
45    /// NIP46 error
46    #[cfg(feature = "nip46")]
47    #[error(transparent)]
48    NIP46(#[from] nip46::Error),
49    /// NIP59 error
50    #[cfg(feature = "nip59")]
51    #[error(transparent)]
52    NIP59(#[from] nip59::Error),
53    /// Event error
54    #[cfg(feature = "nip59")]
55    #[error(transparent)]
56    Event(#[from] event::Error),
57}
58
59/// Nostr Signer Type
60#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
61pub enum NostrSignerType {
62    /// Keys
63    Keys,
64    /// NIP07
65    #[cfg(all(feature = "nip07", target_arch = "wasm32"))]
66    NIP07,
67    /// NIP46
68    #[cfg(feature = "nip46")]
69    NIP46,
70}
71
72// TODO: better display
73impl fmt::Display for NostrSignerType {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        match self {
76            Self::Keys => write!(f, "Keys"),
77            #[cfg(all(feature = "nip07", target_arch = "wasm32"))]
78            Self::NIP07 => write!(f, "Nostr Browser Extension"),
79            #[cfg(feature = "nip46")]
80            Self::NIP46 => write!(f, "Nostr Connect"),
81        }
82    }
83}
84
85/// Nostr signer
86#[derive(Debug, Clone)]
87pub enum NostrSigner {
88    /// Private Keys
89    Keys(Keys),
90    /// NIP07 signer
91    #[cfg(all(feature = "nip07", target_arch = "wasm32"))]
92    NIP07(Nip07Signer),
93    /// NIP46 signer
94    #[cfg(feature = "nip46")]
95    NIP46(Box<Nip46Signer>),
96}
97
98impl NostrSigner {
99    /// Create a new [NIP07] instance and compose [NostrSigner]
100    #[cfg(all(feature = "nip07", target_arch = "wasm32"))]
101    pub fn nip07() -> Result<Self, Error> {
102        let instance = Nip07Signer::new()?;
103        Ok(Self::NIP07(instance))
104    }
105
106    /// Compose [NostrSigner] with [Nip46Signer]
107    #[cfg(feature = "nip46")]
108    pub fn nip46(signer: Nip46Signer) -> Self {
109        Self::NIP46(Box::new(signer))
110    }
111
112    /// Get Nostr Signer Type
113    pub fn r#type(&self) -> NostrSignerType {
114        match self {
115            Self::Keys(..) => NostrSignerType::Keys,
116            #[cfg(all(feature = "nip07", target_arch = "wasm32"))]
117            Self::NIP07(..) => NostrSignerType::NIP07,
118            #[cfg(feature = "nip46")]
119            Self::NIP46(..) => NostrSignerType::NIP46,
120        }
121    }
122
123    /// Get signer public key
124    pub async fn public_key(&self) -> Result<PublicKey, Error> {
125        match self {
126            Self::Keys(keys) => Ok(keys.public_key()),
127            #[cfg(all(feature = "nip07", target_arch = "wasm32"))]
128            Self::NIP07(s) => Ok(s.get_public_key().await?),
129            #[cfg(feature = "nip46")]
130            Self::NIP46(s) => Ok(s.signer_public_key()),
131        }
132    }
133
134    /// Sign an [EventBuilder]
135    pub async fn sign_event_builder(&self, builder: EventBuilder) -> Result<Event, Error> {
136        let public_key: PublicKey = self.public_key().await?;
137        let unsigned: UnsignedEvent = builder.to_unsigned_event(public_key);
138        self.sign_event(unsigned).await
139    }
140
141    /// Sign an [UnsignedEvent]
142    pub async fn sign_event(&self, unsigned: UnsignedEvent) -> Result<Event, Error> {
143        match self {
144            Self::Keys(keys) => Ok(unsigned.sign(keys)?),
145            #[cfg(all(feature = "nip07", target_arch = "wasm32"))]
146            Self::NIP07(nip07) => Ok(nip07.sign_event(unsigned).await?),
147            #[cfg(feature = "nip46")]
148            Self::NIP46(nip46) => Ok(nip46.sign_event(unsigned).await?),
149        }
150    }
151
152    /// NIP04 encrypt
153    #[cfg(feature = "nip04")]
154    pub async fn nip04_encrypt<T>(
155        &self,
156        public_key: &PublicKey,
157        content: T,
158    ) -> Result<String, Error>
159    where
160        T: AsRef<[u8]>,
161    {
162        let content: &[u8] = content.as_ref();
163        match self {
164            Self::Keys(keys) => Ok(nip04::encrypt(keys.secret_key(), public_key, content)?),
165            #[cfg(all(feature = "nip07", target_arch = "wasm32"))]
166            Self::NIP07(signer) => Ok(signer.nip04_encrypt(public_key, content).await?),
167            #[cfg(feature = "nip46")]
168            Self::NIP46(signer) => Ok(signer.nip04_encrypt(*public_key, content).await?),
169        }
170    }
171
172    /// NIP04 decrypt
173    #[cfg(feature = "nip04")]
174    pub async fn nip04_decrypt<T>(
175        &self,
176        public_key: &PublicKey,
177        encrypted_content: T,
178    ) -> Result<String, Error>
179    where
180        T: AsRef<str>,
181    {
182        let encrypted_content: &str = encrypted_content.as_ref();
183        match self {
184            Self::Keys(keys) => Ok(nip04::decrypt(
185                keys.secret_key(),
186                public_key,
187                encrypted_content,
188            )?),
189            #[cfg(all(feature = "nip07", target_arch = "wasm32"))]
190            Self::NIP07(signer) => Ok(signer.nip04_decrypt(public_key, encrypted_content).await?),
191            #[cfg(feature = "nip46")]
192            Self::NIP46(signer) => Ok(signer.nip04_decrypt(*public_key, encrypted_content).await?),
193        }
194    }
195
196    /// NIP44 encryption with [NostrSigner]
197    #[cfg(feature = "nip44")]
198    pub async fn nip44_encrypt<T>(
199        &self,
200        public_key: &PublicKey,
201        content: T,
202    ) -> Result<String, Error>
203    where
204        T: AsRef<[u8]>,
205    {
206        let content: &[u8] = content.as_ref();
207        match self {
208            Self::Keys(keys) => Ok(nip44::encrypt(
209                keys.secret_key(),
210                public_key,
211                content,
212                nip44::Version::default(),
213            )?),
214            #[cfg(all(feature = "nip07", target_arch = "wasm32"))]
215            Self::NIP07(signer) => Ok(signer.nip44_encrypt(public_key, content).await?),
216            #[cfg(feature = "nip46")]
217            Self::NIP46(signer) => Ok(signer.nip44_encrypt(*public_key, content).await?),
218        }
219    }
220
221    /// NIP44 decryption with [NostrSigner]
222    #[cfg(feature = "nip44")]
223    pub async fn nip44_decrypt<T>(
224        &self,
225        public_key: &PublicKey,
226        payload: T,
227    ) -> Result<String, Error>
228    where
229        T: AsRef<[u8]>,
230    {
231        let payload: &[u8] = payload.as_ref();
232        match self {
233            Self::Keys(keys) => Ok(nip44::decrypt(keys.secret_key(), public_key, payload)?),
234            #[cfg(all(feature = "nip07", target_arch = "wasm32"))]
235            Self::NIP07(signer) => Ok(signer.nip44_decrypt(public_key, payload).await?),
236            #[cfg(feature = "nip46")]
237            Self::NIP46(signer) => Ok(signer.nip44_decrypt(*public_key, payload).await?),
238        }
239    }
240
241    /// Unwrap Gift Wrap event
242    ///
243    /// Internally verify the `seal` event
244    ///
245    /// <https://github.com/nostr-protocol/nips/blob/master/59.md>
246    // TODO: find a way to merge this with the `Keys` implementation in `nostr` crate
247    #[cfg(feature = "nip59")]
248    pub async fn unwrap_gift_wrap(&self, gift_wrap: &Event) -> Result<UnwrappedGift, Error> {
249        // Check event kind
250        if gift_wrap.kind != Kind::GiftWrap {
251            return Err(Error::NIP59(nip59::Error::NotGiftWrap));
252        }
253
254        // Decrypt and verify seal
255        let seal: String = self
256            .nip44_decrypt(&gift_wrap.pubkey, &gift_wrap.content)
257            .await?;
258        let seal: Event = Event::from_json(seal)?;
259        seal.verify()?;
260
261        // Decrypt rumor
262        let rumor: String = self.nip44_decrypt(&seal.pubkey, &seal.content).await?;
263
264        Ok(UnwrappedGift {
265            sender: seal.pubkey,
266            rumor: UnsignedEvent::from_json(rumor)?,
267        })
268    }
269}
270
271impl From<Keys> for NostrSigner {
272    fn from(keys: Keys) -> Self {
273        Self::Keys(keys)
274    }
275}
276
277impl From<&Keys> for NostrSigner {
278    fn from(keys: &Keys) -> Self {
279        Self::Keys(keys.clone())
280    }
281}
282
283#[cfg(all(feature = "nip07", target_arch = "wasm32"))]
284impl From<Nip07Signer> for NostrSigner {
285    fn from(nip07: Nip07Signer) -> Self {
286        Self::NIP07(nip07)
287    }
288}
289
290#[cfg(feature = "nip46")]
291impl From<Nip46Signer> for NostrSigner {
292    fn from(nip46: Nip46Signer) -> Self {
293        Self::nip46(nip46)
294    }
295}