1use std::error::Error as StdError;
33use std::fmt;
34use std::future::Future;
35use std::pin::Pin;
36use std::sync::Arc;
37
38use thiserror::Error as ThisError;
39
40use crate::event::{Event, UnsignedEvent, UnsignedEventError};
41use crate::key::{Keys, PublicKey};
42
43#[cfg(not(target_arch = "wasm32"))]
52pub type SignerFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
53
54#[cfg(target_arch = "wasm32")]
58pub type SignerFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
59
60#[cfg(not(target_arch = "wasm32"))]
77pub fn boxed_signer_future<'a, F, T>(future: F) -> SignerFuture<'a, T>
78where
79 F: Future<Output = T> + Send + 'a,
80{
81 Box::pin(future)
82}
83
84#[cfg(target_arch = "wasm32")]
87pub fn boxed_signer_future<'a, F, T>(future: F) -> SignerFuture<'a, T>
88where
89 F: Future<Output = T> + 'a,
90{
91 Box::pin(future)
92}
93
94#[derive(Debug, ThisError)]
96#[non_exhaustive]
97pub enum SignerError {
98 #[error(transparent)]
100 AuthorMismatch(#[from] UnsignedEventError),
101 #[error(
108 "signer rejected the request{}: {message}",
109 code.as_ref().map_or_else(String::new, |c| format!(" (code = {c})"))
110 )]
111 Rejected {
112 message: String,
114 code: Option<String>,
116 },
117 #[error("signer backend failure: {0}")]
119 Backend(Box<dyn StdError + Send + Sync>),
120 #[error("signer does not support `{0}`")]
122 Unsupported(&'static str),
123}
124
125impl SignerError {
126 pub fn backend<E>(err: E) -> Self
128 where
129 E: StdError + Send + Sync + 'static,
130 {
131 Self::Backend(Box::new(err))
132 }
133
134 pub fn rejected<S>(message: S) -> Self
137 where
138 S: Into<String>,
139 {
140 Self::Rejected {
141 message: message.into(),
142 code: None,
143 }
144 }
145
146 pub fn rejected_with_code<S, C>(message: S, code: C) -> Self
149 where
150 S: Into<String>,
151 C: Into<String>,
152 {
153 Self::Rejected {
154 message: message.into(),
155 code: Some(code.into()),
156 }
157 }
158}
159
160pub trait NostrSigner: fmt::Debug + Send + Sync {
185 fn get_public_key(&self) -> SignerFuture<'_, Result<PublicKey, SignerError>>;
191
192 fn sign_event(&self, unsigned: UnsignedEvent) -> SignerFuture<'_, Result<Event, SignerError>>;
197
198 fn nip04_encrypt<'a>(
205 &'a self,
206 _peer: &'a PublicKey,
207 _plaintext: &'a str,
208 ) -> SignerFuture<'a, Result<String, SignerError>> {
209 boxed_signer_future(async { Err(SignerError::Unsupported("nip04_encrypt")) })
210 }
211
212 fn nip04_decrypt<'a>(
218 &'a self,
219 _peer: &'a PublicKey,
220 _ciphertext: &'a str,
221 ) -> SignerFuture<'a, Result<String, SignerError>> {
222 boxed_signer_future(async { Err(SignerError::Unsupported("nip04_decrypt")) })
223 }
224
225 fn nip44_encrypt<'a>(
232 &'a self,
233 _peer: &'a PublicKey,
234 _plaintext: &'a str,
235 ) -> SignerFuture<'a, Result<String, SignerError>> {
236 boxed_signer_future(async { Err(SignerError::Unsupported("nip44_encrypt")) })
237 }
238
239 fn nip44_decrypt<'a>(
245 &'a self,
246 _peer: &'a PublicKey,
247 _payload: &'a str,
248 ) -> SignerFuture<'a, Result<String, SignerError>> {
249 boxed_signer_future(async { Err(SignerError::Unsupported("nip44_decrypt")) })
250 }
251}
252
253impl NostrSigner for Keys {
254 fn get_public_key(&self) -> SignerFuture<'_, Result<PublicKey, SignerError>> {
255 let key = *self.public_key();
256 boxed_signer_future(async move { Ok(key) })
257 }
258
259 fn sign_event(&self, unsigned: UnsignedEvent) -> SignerFuture<'_, Result<Event, SignerError>> {
260 boxed_signer_future(async move {
261 let event = unsigned.sign_with_keys(self)?;
262 Ok(event)
263 })
264 }
265
266 #[cfg(feature = "nip04")]
267 fn nip04_encrypt<'a>(
268 &'a self,
269 peer: &'a PublicKey,
270 plaintext: &'a str,
271 ) -> SignerFuture<'a, Result<String, SignerError>> {
272 boxed_signer_future(async move {
273 crate::nips::nip04::encrypt(self.secret_key(), peer, plaintext)
274 .map_err(SignerError::backend)
275 })
276 }
277
278 #[cfg(feature = "nip04")]
279 fn nip04_decrypt<'a>(
280 &'a self,
281 peer: &'a PublicKey,
282 ciphertext: &'a str,
283 ) -> SignerFuture<'a, Result<String, SignerError>> {
284 boxed_signer_future(async move {
285 crate::nips::nip04::decrypt(self.secret_key(), peer, ciphertext)
286 .map_err(SignerError::backend)
287 })
288 }
289
290 #[cfg(feature = "nip44")]
291 fn nip44_encrypt<'a>(
292 &'a self,
293 peer: &'a PublicKey,
294 plaintext: &'a str,
295 ) -> SignerFuture<'a, Result<String, SignerError>> {
296 boxed_signer_future(async move {
297 crate::nips::nip44::encrypt(self.secret_key(), peer, plaintext)
298 .map_err(SignerError::backend)
299 })
300 }
301
302 #[cfg(feature = "nip44")]
303 fn nip44_decrypt<'a>(
304 &'a self,
305 peer: &'a PublicKey,
306 payload: &'a str,
307 ) -> SignerFuture<'a, Result<String, SignerError>> {
308 boxed_signer_future(async move {
309 crate::nips::nip44::decrypt(self.secret_key(), peer, payload)
310 .map_err(SignerError::backend)
311 })
312 }
313}
314
315impl<S> NostrSigner for Arc<S>
316where
317 S: NostrSigner + ?Sized,
318{
319 fn get_public_key(&self) -> SignerFuture<'_, Result<PublicKey, SignerError>> {
320 (**self).get_public_key()
321 }
322
323 fn sign_event(&self, unsigned: UnsignedEvent) -> SignerFuture<'_, Result<Event, SignerError>> {
324 (**self).sign_event(unsigned)
325 }
326
327 fn nip04_encrypt<'a>(
328 &'a self,
329 peer: &'a PublicKey,
330 plaintext: &'a str,
331 ) -> SignerFuture<'a, Result<String, SignerError>> {
332 (**self).nip04_encrypt(peer, plaintext)
333 }
334
335 fn nip04_decrypt<'a>(
336 &'a self,
337 peer: &'a PublicKey,
338 ciphertext: &'a str,
339 ) -> SignerFuture<'a, Result<String, SignerError>> {
340 (**self).nip04_decrypt(peer, ciphertext)
341 }
342
343 fn nip44_encrypt<'a>(
344 &'a self,
345 peer: &'a PublicKey,
346 plaintext: &'a str,
347 ) -> SignerFuture<'a, Result<String, SignerError>> {
348 (**self).nip44_encrypt(peer, plaintext)
349 }
350
351 fn nip44_decrypt<'a>(
352 &'a self,
353 peer: &'a PublicKey,
354 payload: &'a str,
355 ) -> SignerFuture<'a, Result<String, SignerError>> {
356 (**self).nip44_decrypt(peer, payload)
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363 use crate::event::EventBuilder;
364 use crate::types::Timestamp;
365
366 fn fixture_keys() -> Keys {
367 Keys::parse("0000000000000000000000000000000000000000000000000000000000000003").unwrap()
368 }
369
370 fn block_on<F: Future>(f: F) -> F::Output {
371 use std::pin::pin;
373 use std::task::{Context, Poll, Waker};
374
375 let waker = Waker::noop();
376 let mut cx = Context::from_waker(waker);
377 let mut fut = pin!(f);
378 match fut.as_mut().poll(&mut cx) {
379 Poll::Ready(out) => out,
380 Poll::Pending => unreachable!("test futures must be synchronous"),
381 }
382 }
383
384 #[test]
385 fn keys_implement_signer() {
386 let keys = fixture_keys();
387 let pk = block_on(keys.get_public_key()).unwrap();
388 assert_eq!(pk, *keys.public_key());
389 }
390
391 #[test]
392 fn keys_sign_event() {
393 let keys = fixture_keys();
394 let unsigned = EventBuilder::text_note("hi")
395 .created_at(Timestamp::from_secs(1))
396 .build_unsigned(*keys.public_key())
397 .unwrap();
398 let event = block_on(keys.sign_event(unsigned)).unwrap();
399 event.verify().unwrap();
400 }
401
402 #[test]
403 fn arc_signer_dispatches() {
404 let keys: Arc<dyn NostrSigner> = Arc::new(fixture_keys());
405 let pk = block_on(keys.get_public_key()).unwrap();
406 assert_eq!(pk.to_byte_array().len(), 32);
407 }
408
409 #[test]
410 fn rejected_error_carries_reason() {
411 let err = SignerError::rejected("user denied");
412 let s = err.to_string();
413 assert!(s.contains("user denied"));
414 assert!(!s.contains("code"), "no structured code → no code suffix");
415 }
416
417 #[test]
418 fn rejected_with_code_surfaces_machine_readable_code() {
419 let err = SignerError::rejected_with_code("user denied", "user_rejected");
420 let s = err.to_string();
421 assert!(s.contains("user denied"));
422 assert!(
423 s.contains("code = user_rejected"),
424 "expected code suffix in: {s}",
425 );
426 }
427
428 #[test]
429 fn backend_error_round_trip() {
430 let inner = std::io::Error::other("oops");
431 let err = SignerError::backend(inner);
432 assert!(err.to_string().contains("oops"));
433 }
434
435 #[derive(Debug)]
440 struct SignOnlySigner(Keys);
441
442 impl NostrSigner for SignOnlySigner {
443 fn get_public_key(&self) -> SignerFuture<'_, Result<PublicKey, SignerError>> {
444 self.0.get_public_key()
445 }
446
447 fn sign_event(
448 &self,
449 unsigned: UnsignedEvent,
450 ) -> SignerFuture<'_, Result<Event, SignerError>> {
451 self.0.sign_event(unsigned)
452 }
453 }
456
457 #[test]
458 fn default_encryption_methods_return_unsupported() {
459 let alice = SignOnlySigner(fixture_keys());
463 let bob_pk =
464 *Keys::parse("0000000000000000000000000000000000000000000000000000000000000007")
465 .unwrap()
466 .public_key();
467
468 let cases: [(&str, SignerFuture<'_, _>); 4] = [
469 ("nip04_encrypt", alice.nip04_encrypt(&bob_pk, "hi")),
470 ("nip04_decrypt", alice.nip04_decrypt(&bob_pk, "")),
471 ("nip44_encrypt", alice.nip44_encrypt(&bob_pk, "hi")),
472 ("nip44_decrypt", alice.nip44_decrypt(&bob_pk, "")),
473 ];
474 for (label, fut) in cases {
475 let err = block_on(fut).unwrap_err();
476 assert!(
477 matches!(err, SignerError::Unsupported(name) if name == label),
478 "expected Unsupported({label}), got {err:?}",
479 );
480 }
481 }
482
483 #[cfg(feature = "nip04")]
484 #[test]
485 fn keys_nip04_round_trip_through_signer_trait() {
486 let alice = fixture_keys();
487 let bob = Keys::parse("0000000000000000000000000000000000000000000000000000000000000007")
488 .unwrap();
489 let payload = block_on(alice.nip04_encrypt(bob.public_key(), "legacy hi")).unwrap();
490 let recovered = block_on(bob.nip04_decrypt(alice.public_key(), &payload)).unwrap();
491 assert_eq!(recovered, "legacy hi");
492 }
493
494 #[cfg(feature = "nip44")]
495 #[test]
496 fn keys_nip44_round_trip_through_signer_trait() {
497 let alice = fixture_keys();
501 let bob = Keys::parse("0000000000000000000000000000000000000000000000000000000000000007")
502 .unwrap();
503 let payload = block_on(alice.nip44_encrypt(bob.public_key(), "secret")).unwrap();
504 let recovered = block_on(bob.nip44_decrypt(alice.public_key(), &payload)).unwrap();
505 assert_eq!(recovered, "secret");
506 }
507
508 #[cfg(feature = "nip44")]
509 #[test]
510 fn arc_dyn_signer_forwards_nip44_methods() {
511 let alice: Arc<dyn NostrSigner> = Arc::new(fixture_keys());
514 let bob = Keys::parse("0000000000000000000000000000000000000000000000000000000000000007")
515 .unwrap();
516 let payload = block_on(alice.nip44_encrypt(bob.public_key(), "via dyn")).unwrap();
517 let recovered =
518 block_on(bob.nip44_decrypt(&block_on(alice.get_public_key()).unwrap(), &payload))
519 .unwrap();
520 assert_eq!(recovered, "via dyn");
521 }
522}