1use std::sync::{LazyLock, OnceLock};
13
14use nostr_sdk::prelude::*;
15
16use crate::signer::{BoxedFuture, SignerError};
17
18pub trait Nip07Backend: Send + Sync + 'static {
24 fn request(
25 &self,
26 method: &'static str,
27 params: serde_json::Value,
28 ) -> BoxedFuture<'static, Result<serde_json::Value, String>>;
29}
30
31static NIP07_BACKEND: OnceLock<Box<dyn Nip07Backend>> = OnceLock::new();
32
33pub fn set_nip07_backend(backend: Box<dyn Nip07Backend>) {
35 let _ = NIP07_BACKEND.set(backend);
36}
37
38#[inline]
39pub fn nip07_backend() -> Option<&'static dyn Nip07Backend> {
40 NIP07_BACKEND.get().map(|b| b.as_ref())
41}
42
43const NIP07_MAX_CONCURRENT_OPS: usize = 8;
45
46static NIP07_SEMAPHORE: LazyLock<tokio::sync::Semaphore> =
47 LazyLock::new(|| tokio::sync::Semaphore::new(NIP07_MAX_CONCURRENT_OPS));
48
49async fn call(method: &'static str, params: serde_json::Value) -> Result<serde_json::Value, SignerError> {
50 let backend = nip07_backend().ok_or("no browser signer available")?;
51 let _permit = NIP07_SEMAPHORE
52 .acquire()
53 .await
54 .map_err(|_| SignerError::from("browser signer queue closed"))?;
55 backend.request(method, params).await.map_err(SignerError::from)
56}
57
58async fn call_str(method: &'static str, params: serde_json::Value) -> Result<String, SignerError> {
59 match call(method, params).await? {
60 serde_json::Value::String(s) => Ok(s),
61 other => Err(SignerError::from(format!("browser signer returned {other} for {method}"))),
62 }
63}
64
65pub async fn nip07_get_public_key() -> Result<PublicKey, String> {
67 let pk = call_str("getPublicKey", serde_json::json!([])).await.map_err(|e| e.to_string())?;
68 PublicKey::parse(&pk).map_err(|e| format!("browser signer returned an invalid pubkey: {e}"))
69}
70
71#[derive(Debug, Clone)]
73pub struct Nip07Signer {
74 user_pubkey: PublicKey,
75}
76
77impl Nip07Signer {
78 pub fn new(user_pubkey: PublicKey) -> Self {
79 Self { user_pubkey }
80 }
81}
82
83impl AsyncGetPublicKey for Nip07Signer {
84 type Error = SignerError;
85
86 fn get_public_key_async(&self) -> BoxedFuture<'_, Result<PublicKey, Self::Error>> {
88 Box::pin(async move { Ok(self.user_pubkey) })
89 }
90}
91
92impl AsyncSignEvent for Nip07Signer {
93 type Error = SignerError;
94
95 fn sign_event_async(&self, unsigned: UnsignedEvent) -> BoxedFuture<'_, Result<Event, Self::Error>> {
96 Box::pin(async move {
97 let template = serde_json::json!({
98 "kind": unsigned.kind.as_u16(),
99 "created_at": unsigned.created_at.as_secs(),
100 "tags": unsigned.tags,
101 "content": unsigned.content,
102 "pubkey": self.user_pubkey.to_hex(),
103 });
104 let signed = call("signEvent", serde_json::json!([template])).await?;
105 accept_signed(&unsigned, self.user_pubkey, signed)
106 })
107 }
108}
109
110fn accept_signed(unsigned: &UnsignedEvent, me: PublicKey, signed: serde_json::Value) -> Result<Event, SignerError> {
114 let event: Event = serde_json::from_value(signed).map_err(SignerError::backend)?;
115 if event.pubkey != me {
116 return Err(SignerError::from(format!(
117 "browser signer signed as {} (expected {})",
118 event.pubkey.to_hex(),
119 me.to_hex()
120 )));
121 }
122 if event.kind != unsigned.kind
123 || event.created_at != unsigned.created_at
124 || event.content != unsigned.content
125 || event.tags != unsigned.tags
126 {
127 return Err(SignerError::from("browser signer altered the event it signed"));
128 }
129 event.verify().map_err(SignerError::backend)?;
130 Ok(event)
131}
132
133impl AsyncNip04 for Nip07Signer {
134 type Error = SignerError;
135
136 fn nip04_encrypt_async<'a>(
137 &'a self,
138 public_key: &'a PublicKey,
139 content: &'a str,
140 ) -> BoxedFuture<'a, Result<String, Self::Error>> {
141 Box::pin(async move { call_str("nip04.encrypt", serde_json::json!([public_key.to_hex(), content])).await })
142 }
143
144 fn nip04_decrypt_async<'a>(
145 &'a self,
146 public_key: &'a PublicKey,
147 content: &'a str,
148 ) -> BoxedFuture<'a, Result<String, Self::Error>> {
149 Box::pin(async move { call_str("nip04.decrypt", serde_json::json!([public_key.to_hex(), content])).await })
150 }
151}
152
153impl AsyncNip44 for Nip07Signer {
154 type Error = SignerError;
155
156 fn nip44_encrypt_async<'a>(
157 &'a self,
158 public_key: &'a PublicKey,
159 content: &'a str,
160 ) -> BoxedFuture<'a, Result<String, Self::Error>> {
161 Box::pin(async move { call_str("nip44.encrypt", serde_json::json!([public_key.to_hex(), content])).await })
162 }
163
164 fn nip44_decrypt_async<'a>(
165 &'a self,
166 public_key: &'a PublicKey,
167 payload: &'a str,
168 ) -> BoxedFuture<'a, Result<String, Self::Error>> {
169 Box::pin(async move { call_str("nip44.decrypt", serde_json::json!([public_key.to_hex(), payload])).await })
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176 use crate::event_ext::FinalizeUnsignedWithId;
177
178 fn unsigned(pk: PublicKey, content: &str) -> UnsignedEvent {
179 EventBuilder::new(Kind::TextNote, content)
180 .tag(Tag::hashtag("vector"))
181 .finalize_unsigned_with_id(pk)
182 }
183
184 #[test]
185 fn accepts_exactly_the_event_asked_for() {
186 let keys = Keys::generate();
187 let asked = unsigned(keys.public_key(), "hi");
188 let signed = asked.clone().finalize(&keys).unwrap();
189 let event = accept_signed(&asked, keys.public_key(), serde_json::to_value(&signed).unwrap()).unwrap();
190 assert_eq!(Some(event.id), asked.id);
191 }
192
193 #[test]
194 fn rejects_another_identity_an_edit_or_a_bad_signature() {
195 let me = Keys::generate();
196 let asked = unsigned(me.public_key(), "hi");
197
198 let other = Keys::generate();
199 let foreign = unsigned(other.public_key(), "hi").finalize(&other).unwrap();
200 assert!(accept_signed(&asked, me.public_key(), serde_json::to_value(&foreign).unwrap()).is_err());
201
202 let edited = unsigned(me.public_key(), "hello").finalize(&me).unwrap();
203 assert!(accept_signed(&asked, me.public_key(), serde_json::to_value(&edited).unwrap()).is_err());
204
205 let mut forged = serde_json::to_value(asked.clone().finalize(&me).unwrap()).unwrap();
206 let sig = forged["sig"].as_str().unwrap().to_string();
207 let flipped = if sig.starts_with('0') { format!("1{}", &sig[1..]) } else { format!("0{}", &sig[1..]) };
208 forged["sig"] = serde_json::json!(flipped);
209 assert!(accept_signed(&asked, me.public_key(), forged).is_err());
210 }
211
212 #[tokio::test]
213 async fn no_backend_is_a_clean_error() {
214 if nip07_backend().is_some() {
215 return;
216 }
217 let keys = Keys::generate();
218 let signer = Nip07Signer::new(keys.public_key());
219 assert_eq!(signer.get_public_key_async().await.unwrap(), keys.public_key());
220 assert!(signer.sign_event_async(unsigned(keys.public_key(), "hi")).await.is_err());
221 }
222}