1use nostr_sdk::prelude::*;
12use zeroize::Zeroize;
13
14use crate::signer::{BoxedFuture, SignerError};
15
16#[derive(Debug, Clone)]
21pub struct GuardedSigner {
22 public_key: PublicKey,
23}
24
25impl GuardedSigner {
26 pub fn new(public_key: PublicKey) -> Self {
27 Self { public_key }
28 }
29
30 fn temp_keys(&self) -> Result<Keys, SignerError> {
31 crate::state::MY_SECRET_KEY
32 .to_keys()
33 .ok_or_else(|| SignerError::from("Secret key not available"))
34 }
35
36 fn temp_secret_key() -> Result<SecretKey, SignerError> {
39 let mut bytes = crate::state::MY_SECRET_KEY
40 .get()
41 .ok_or_else(|| SignerError::from("Secret key not available"))?;
42 let key = SecretKey::from_slice(&bytes).map_err(SignerError::backend);
43 bytes.zeroize();
44 key
45 }
46
47 pub fn unwrap_gift_wrap(gift_wrap: &Event) -> Result<UnwrappedGift, SignerError> {
49 Self::unwrap_batch(&[gift_wrap]).pop().unwrap_or_else(|| Err(SignerError::from("empty batch")))
50 }
51
52 pub fn unwrap_batch(wraps: &[&Event]) -> Vec<Result<UnwrappedGift, SignerError>> {
65 let checked: Vec<Result<(), SignerError>> = wraps
66 .iter()
67 .map(|wrap| {
68 if wrap.kind != Kind::GiftWrap {
69 return Err(SignerError::from("not a gift wrap"));
70 }
71 if !wrap.verify_id() {
74 return Err(SignerError::from("gift wrap id does not match its content"));
75 }
76 Ok(())
77 })
78 .collect();
79
80 let key = if checked.iter().any(Result::is_ok) { Some(Self::temp_secret_key()) } else { None };
81 let mut senders: Vec<(PublicKey, nip44::v2::ConversationKey)> = Vec::new();
82 let opened: Vec<Result<(Event, String), SignerError>> = wraps
83 .iter()
84 .zip(checked)
85 .map(|(wrap, check)| {
86 check?;
87 match &key {
88 Some(Ok(key)) => open_layers(key, wrap, &mut senders),
89 Some(Err(e)) => Err(SignerError::from(e.to_string())),
90 None => Err(SignerError::from("unreachable")),
91 }
92 })
93 .collect();
94 for (_, ck) in senders.iter_mut() {
95 wipe_conversation_key(ck);
96 }
97 drop(key);
98
99 opened
100 .into_iter()
101 .map(|slot| {
102 let (seal, rumor) = slot?;
103 seal.verify().map_err(SignerError::backend)?;
104 let rumor = UnsignedEvent::from_json(rumor).map_err(SignerError::backend)?;
105 if rumor.pubkey != seal.pubkey {
106 return Err(SignerError::from("sender mismatch"));
107 }
108 Ok(UnwrappedGift { sender: seal.pubkey, rumor })
109 })
110 .collect()
111 }
112}
113
114fn open_layers(
116 key: &SecretKey,
117 wrap: &Event,
118 senders: &mut Vec<(PublicKey, nip44::v2::ConversationKey)>,
119) -> Result<(Event, String), SignerError> {
120 let mut wrap_key = nip44::v2::ConversationKey::derive(key, &wrap.pubkey).map_err(SignerError::backend)?;
121 let seal = nip44_open(&wrap_key, &wrap.content);
122 wipe_conversation_key(&mut wrap_key);
123 let seal = Event::from_json(seal?).map_err(SignerError::backend)?;
124
125 let at = match senders.iter().position(|(pk, _)| *pk == seal.pubkey) {
126 Some(at) => at,
127 None => {
128 let ck = nip44::v2::ConversationKey::derive(key, &seal.pubkey).map_err(SignerError::backend)?;
129 senders.push((seal.pubkey, ck));
130 senders.len() - 1
131 }
132 };
133 let rumor = nip44_open(&senders[at].1, &seal.content)?;
134 Ok((seal, rumor))
135}
136
137const NIP44_MAX_ENCODED: usize = 87_472;
139
140fn nip44_open(ck: &nip44::v2::ConversationKey, payload: &str) -> Result<String, SignerError> {
142 if payload.len() > NIP44_MAX_ENCODED {
143 return Err(SignerError::from("nip44 payload too long"));
144 }
145 let bytes = base64_simd::STANDARD.decode_to_vec(payload.as_bytes()).map_err(SignerError::backend)?;
146 if bytes.first() != Some(&nip44::Version::V2.as_u8()) {
147 return Err(SignerError::from("unsupported nip44 version"));
148 }
149 let plain = nip44::v2::decrypt_to_bytes(ck, &bytes).map_err(SignerError::backend)?;
150 String::from_utf8(plain).map_err(SignerError::backend)
151}
152
153fn wipe_conversation_key(ck: &mut nip44::v2::ConversationKey) {
154 unsafe { core::ptr::write_volatile(ck, nip44::v2::ConversationKey::new([0u8; 32])) };
156}
157
158impl GetPublicKey for GuardedSigner {
163 type Error = SignerError;
164
165 #[inline]
166 fn get_public_key(&self) -> Result<PublicKey, Self::Error> {
167 Ok(self.public_key)
168 }
169}
170
171impl SignEvent for GuardedSigner {
172 type Error = SignerError;
173
174 fn sign_event(&self, unsigned: UnsignedEvent) -> Result<Event, Self::Error> {
175 let keys = self.temp_keys()?;
176 SignEvent::sign_event(&keys, unsigned).map_err(SignerError::backend)
177 }
178}
179
180impl Nip04 for GuardedSigner {
181 type Error = SignerError;
182
183 fn nip04_encrypt(&self, public_key: &PublicKey, content: &str) -> Result<String, Self::Error> {
184 nip04::encrypt(&Self::temp_secret_key()?, public_key, content).map_err(SignerError::backend)
185 }
186
187 fn nip04_decrypt(&self, public_key: &PublicKey, payload: &str) -> Result<String, Self::Error> {
188 nip04::decrypt(&Self::temp_secret_key()?, public_key, payload).map_err(SignerError::backend)
189 }
190}
191
192impl Nip44 for GuardedSigner {
193 type Error = SignerError;
194
195 fn nip44_encrypt(&self, public_key: &PublicKey, content: &str) -> Result<String, Self::Error> {
196 nip44::encrypt(&Self::temp_secret_key()?, public_key, content, nip44::Version::default())
197 .map_err(SignerError::backend)
198 }
199
200 fn nip44_decrypt(&self, public_key: &PublicKey, payload: &str) -> Result<String, Self::Error> {
201 nip44::decrypt(&Self::temp_secret_key()?, public_key, payload).map_err(SignerError::backend)
202 }
203}
204
205impl AsyncGetPublicKey for GuardedSigner {
210 type Error = SignerError;
211
212 #[inline]
213 fn get_public_key_async(&self) -> BoxedFuture<'_, Result<PublicKey, Self::Error>> {
214 let res = GetPublicKey::get_public_key(self);
215 Box::pin(async move { res })
216 }
217}
218
219impl AsyncSignEvent for GuardedSigner {
220 type Error = SignerError;
221
222 #[inline]
223 fn sign_event_async(&self, unsigned: UnsignedEvent) -> BoxedFuture<'_, Result<Event, Self::Error>> {
224 let res = SignEvent::sign_event(self, unsigned);
225 Box::pin(async move { res })
226 }
227}
228
229impl AsyncNip04 for GuardedSigner {
230 type Error = SignerError;
231
232 #[inline]
233 fn nip04_encrypt_async<'a>(
234 &'a self,
235 public_key: &'a PublicKey,
236 content: &'a str,
237 ) -> BoxedFuture<'a, Result<String, Self::Error>> {
238 let res = Nip04::nip04_encrypt(self, public_key, content);
239 Box::pin(async move { res })
240 }
241
242 #[inline]
243 fn nip04_decrypt_async<'a>(
244 &'a self,
245 public_key: &'a PublicKey,
246 encrypted_content: &'a str,
247 ) -> BoxedFuture<'a, Result<String, Self::Error>> {
248 let res = Nip04::nip04_decrypt(self, public_key, encrypted_content);
249 Box::pin(async move { res })
250 }
251}
252
253impl AsyncNip44 for GuardedSigner {
254 type Error = SignerError;
255
256 #[inline]
257 fn nip44_encrypt_async<'a>(
258 &'a self,
259 public_key: &'a PublicKey,
260 content: &'a str,
261 ) -> BoxedFuture<'a, Result<String, Self::Error>> {
262 let res = Nip44::nip44_encrypt(self, public_key, content);
263 Box::pin(async move { res })
264 }
265
266 #[inline]
267 fn nip44_decrypt_async<'a>(
268 &'a self,
269 public_key: &'a PublicKey,
270 payload: &'a str,
271 ) -> BoxedFuture<'a, Result<String, Self::Error>> {
272 let res = Nip44::nip44_decrypt(self, public_key, payload);
273 Box::pin(async move { res })
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 fn rumor(from: &PublicKey, to: &PublicKey) -> UnsignedEvent {
282 EventBuilder::new(Kind::PrivateDirectMessage, "see you at nine")
283 .tag(Tag::public_key(*to))
284 .finalize_unsigned(*from)
285 }
286
287 fn seal(author: &Keys, rumor: &UnsignedEvent, to: &PublicKey) -> Event {
288 let content = nip44::encrypt(author.secret_key(), to, rumor.as_json(), nip44::Version::default()).unwrap();
289 EventBuilder::new(Kind::Seal, content).finalize(author).unwrap()
290 }
291
292 fn wrap(seal: &Event, to: &PublicKey) -> Event {
293 let ephemeral = Keys::generate();
294 let content = nip44::encrypt(ephemeral.secret_key(), to, seal.as_json(), nip44::Version::default()).unwrap();
295 EventBuilder::new(Kind::GiftWrap, content).tag(Tag::public_key(*to)).finalize(&ephemeral).unwrap()
296 }
297
298 fn flip_sig(mut e: Event) -> Event {
299 let mut sig = e.sig.to_bytes();
300 sig[10] ^= 1;
301 e.sig = Signature::from_slice(&sig).unwrap();
302 e
303 }
304
305 fn retime(mut e: Event) -> Event {
307 e.created_at = Timestamp::from_secs(e.created_at.as_secs() - 1);
308 e
309 }
310
311 fn with_me<R>(me: &Keys, f: impl FnOnce() -> R) -> R {
314 let _guard = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
315 crate::state::MY_SECRET_KEY.store_from_keys(me, &[]);
316 let out = f();
317 crate::state::MY_SECRET_KEY.clear(&[]);
318 out
319 }
320
321 #[test]
322 fn unwrap_matches_nostr_on_a_valid_wrap() {
323 let (me, sender) = (Keys::generate(), Keys::generate());
324 let gift = wrap(&seal(&sender, &rumor(&sender.public_key(), &me.public_key()), &me.public_key()), &me.public_key());
325 let reference = UnwrappedGift::from_gift_wrap(&me, &gift).unwrap();
326 let ours = with_me(&me, || GuardedSigner::unwrap_gift_wrap(&gift)).unwrap();
327 assert_eq!(ours, reference);
328 assert_eq!(ours.sender, sender.public_key());
329 }
330
331 #[test]
332 fn unwrap_rejects_everything_nostr_rejects() {
333 let (me, sender, stranger) = (Keys::generate(), Keys::generate(), Keys::generate());
334 let good_rumor = rumor(&sender.public_key(), &me.public_key());
335 let good_seal = seal(&sender, &good_rumor, &me.public_key());
336
337 let cases: Vec<(&str, Event)> = vec![
338 ("wrap id does not match its fields", retime(wrap(&good_seal, &me.public_key()))),
339 ("forged seal signature", wrap(&flip_sig(good_seal.clone()), &me.public_key())),
341 ("rumor author is not the seal author",
342 wrap(&seal(&sender, &rumor(&stranger.public_key(), &me.public_key()), &me.public_key()), &me.public_key())),
343 ("wrap sealed to someone else", wrap(&good_seal, &stranger.public_key())),
344 ("seal sealed to someone else",
345 wrap(&seal(&sender, &good_rumor, &stranger.public_key()), &me.public_key())),
346 ("not a gift wrap", good_seal.clone()),
347 ];
348 for (what, gift) in cases {
349 assert!(UnwrappedGift::from_gift_wrap(&me, &gift).is_err(), "nostr accepts: {what}");
350 assert!(with_me(&me, || GuardedSigner::unwrap_gift_wrap(&gift)).is_err(), "we accept: {what}");
351 }
352 }
353
354 #[test]
355 fn a_wrap_is_checked_for_its_id_but_not_its_signature() {
356 let (me, sender) = (Keys::generate(), Keys::generate());
357 let good = wrap(&seal(&sender, &rumor(&sender.public_key(), &me.public_key()), &me.public_key()), &me.public_key());
358 let forged_sig = flip_sig(good.clone());
359 assert!(forged_sig.verify().is_err() && forged_sig.verify_id());
360 assert!(with_me(&me, || GuardedSigner::unwrap_gift_wrap(&forged_sig)).is_ok());
361 assert!(with_me(&me, || GuardedSigner::unwrap_gift_wrap(&retime(good))).is_err());
362 }
363
364 #[test]
365 fn a_batch_matches_unwrapping_one_by_one() {
366 let (me, stranger) = (Keys::generate(), Keys::generate());
367 let senders: Vec<Keys> = (0..3).map(|_| Keys::generate()).collect();
368 let mut gifts = Vec::new();
369 for i in 0..12 {
370 let from = &senders[i % 3];
371 let r = rumor(&from.public_key(), &me.public_key());
372 gifts.push(match i {
373 4 => wrap(&flip_sig(seal(from, &r, &me.public_key())), &me.public_key()),
374 7 => wrap(&seal(from, &r, &stranger.public_key()), &me.public_key()),
375 9 => retime(wrap(&seal(from, &r, &me.public_key()), &me.public_key())),
376 10 => seal(from, &r, &me.public_key()),
377 _ => wrap(&seal(from, &r, &me.public_key()), &me.public_key()),
378 });
379 }
380 let refs: Vec<&Event> = gifts.iter().collect();
381 let (batch, single) = with_me(&me, || {
382 let batch = GuardedSigner::unwrap_batch(&refs);
383 let single: Vec<_> = gifts.iter().map(GuardedSigner::unwrap_gift_wrap).collect();
384 (batch, single)
385 });
386 assert_eq!(batch.len(), gifts.len());
387 for (i, ((b, s), gift)) in batch.iter().zip(&single).zip(&gifts).enumerate() {
388 match (b, s) {
389 (Ok(b), Ok(s)) => {
390 assert_eq!(b, s, "wrap {i}");
391 assert_eq!(*b, UnwrappedGift::from_gift_wrap(&me, gift).unwrap(), "wrap {i}");
392 assert_eq!(b.sender, senders[i % 3].public_key());
393 }
394 (Err(_), Err(_)) => assert!([4, 7, 9, 10].contains(&i), "wrap {i} refused"),
395 _ => panic!("wrap {i}: batch and single disagree"),
396 }
397 }
398 }
399
400 #[test]
401 fn a_batch_with_an_empty_vault_fails_every_wrap() {
402 let (me, sender) = (Keys::generate(), Keys::generate());
403 let gift = wrap(&seal(&sender, &rumor(&sender.public_key(), &me.public_key()), &me.public_key()), &me.public_key());
404 let _guard = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
405 crate::state::MY_SECRET_KEY.clear(&[]);
406 assert!(GuardedSigner::unwrap_batch(&[&gift, &gift]).iter().all(Result::is_err));
407 assert!(GuardedSigner::unwrap_batch(&[]).is_empty());
408 }
409
410 #[test]
411 fn nip44_open_accepts_and_refuses_exactly_what_nostr_does() {
412 let (a, b) = (Keys::generate(), Keys::generate());
413 let ck = nip44::v2::ConversationKey::derive(b.secret_key(), &a.public_key()).unwrap();
414 let same = |p: &str| {
415 let theirs = nip44::decrypt(b.secret_key(), &a.public_key(), p).ok();
416 let ours = nip44_open(&ck, p).ok();
417 assert_eq!(ours, theirs, "payload {p:?}");
418 };
419 let mut seed = 0x2545_f491_4f6c_dd1du64;
420 for len in [1usize, 2, 31, 32, 33, 100, 255, 256, 257, 1000, 5000, 65_000] {
421 let text: String = (0..len).map(|i| char::from(b'a' + (i % 26) as u8)).collect();
422 let p = nip44::encrypt(a.secret_key(), &b.public_key(), &text, nip44::Version::V2).unwrap();
423 same(&p);
424 for cut in 1..5 {
425 same(&p[..p.len() - cut]);
426 }
427 for extra in ["=", "A", "AAAA", " ", "\n"] {
428 same(&format!("{p}{extra}"));
429 }
430 let tail = p.trim_end_matches('=').len();
431 let spots: Vec<usize> = (1..=3).map(|k| tail - k).chain((0..40).map(|_| {
432 seed ^= seed << 13;
433 seed ^= seed >> 7;
434 seed ^= seed << 17;
435 (seed as usize) % p.len()
436 })).collect();
437 for at in spots {
438 for c in ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'Q', 'g', 'w', '/', '+', '=', '-', '_', ' '] {
439 let mut q = p.clone().into_bytes();
440 q[at] = c as u8;
441 same(&String::from_utf8(q).unwrap());
442 }
443 }
444 }
445 same("");
446 same("Ag==");
447 same(&"A".repeat(NIP44_MAX_ENCODED + 4));
448 }
449
450 #[test]
451 fn unwrap_with_an_empty_vault_fails() {
452 let (me, sender) = (Keys::generate(), Keys::generate());
453 let gift = wrap(&seal(&sender, &rumor(&sender.public_key(), &me.public_key()), &me.public_key()), &me.public_key());
454 let _guard = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
455 crate::state::MY_SECRET_KEY.clear(&[]);
456 assert!(GuardedSigner::unwrap_gift_wrap(&gift).is_err());
457 }
458
459 #[test]
460 fn nip44_and_nip04_match_keys_both_ways() {
461 let (me, peer) = (Keys::generate(), Keys::generate());
462 let signer = GuardedSigner::new(me.public_key());
463 with_me(&me, || {
464 let sealed = Nip44::nip44_encrypt(&signer, &peer.public_key(), "hello").unwrap();
465 assert_eq!(peer.nip44_decrypt(&me.public_key(), &sealed).unwrap(), "hello");
466 let from_peer = peer.nip44_encrypt(&me.public_key(), "back").unwrap();
467 assert_eq!(Nip44::nip44_decrypt(&signer, &peer.public_key(), &from_peer).unwrap(), "back");
468
469 let sealed = Nip04::nip04_encrypt(&signer, &peer.public_key(), "old").unwrap();
470 assert_eq!(peer.nip04_decrypt(&me.public_key(), &sealed).unwrap(), "old");
471 let from_peer = peer.nip04_encrypt(&me.public_key(), "school").unwrap();
472 assert_eq!(Nip04::nip04_decrypt(&signer, &peer.public_key(), &from_peer).unwrap(), "school");
473 });
474 }
475}