11use async_trait:: async_trait;
2- use bcr_ebill_core:: contact:: IdentityPublicData ;
2+ use bcr_ebill_core:: { contact:: IdentityPublicData , util :: crypto } ;
33use bcr_ebill_transport:: event:: EventEnvelope ;
44use bcr_ebill_transport:: handler:: NotificationHandlerApi ;
5- use log:: { error, trace, warn} ;
6- use nostr_sdk:: nips:: nip59:: UnwrappedGift ;
5+ use log:: { error, info, trace, warn} ;
76use nostr_sdk:: {
8- Client , EventId , Filter , Kind , Metadata , Options , PublicKey , RelayPoolNotification , Timestamp ,
9- ToBech32 , UnsignedEvent ,
7+ Client , EventBuilder , EventId , Filter , Kind , Metadata , Options , PublicKey ,
8+ RelayPoolNotification , SecretKey , Tag , Timestamp , ToBech32 , UnsignedEvent ,
9+ nips:: { nip04, nip59:: UnwrappedGift } ,
1010} ;
1111use std:: collections:: HashMap ;
1212use std:: str:: FromStr ;
1313use std:: sync:: Arc ;
1414
15- use crate :: service :: contact_service :: ContactServiceApi ;
16- use crate :: util :: { BcrKeys , crypto } ;
15+ use crate :: util :: BcrKeys ;
16+ use crate :: { constants :: NOSTR_EVENT_TIME_SLACK , service :: contact_service :: ContactServiceApi } ;
1717use bcr_ebill_core:: ServiceTraitBounds ;
1818use bcr_ebill_persistence:: { NostrEventOffset , NostrEventOffsetStoreApi } ;
1919use bcr_ebill_transport:: { Error , NotificationJsonTransportApi , Result } ;
@@ -47,7 +47,7 @@ impl NostrConfig {
4747/// A wrapper around nostr_sdk that implements the NotificationJsonTransportApi.
4848///
4949/// # Example:
50- /// ```ignore
50+ /// ```no_run
5151/// let config = NostrConfig {
5252/// keys: BcrKeys::new(),
5353/// relays: vec!["wss://relay.example.com".to_string()],
@@ -95,6 +95,14 @@ impl NostrClient {
9595 self . keys . get_public_key ( )
9696 }
9797
98+ pub fn get_nostr_keys ( & self ) -> nostr_sdk:: Keys {
99+ self . keys . get_nostr_keys ( )
100+ }
101+
102+ fn use_nip04 ( & self ) -> bool {
103+ true
104+ }
105+
98106 /// Subscribe to some nostr events with a filter
99107 pub async fn subscribe ( & self , subscription : Filter ) -> Result < ( ) > {
100108 self . client
@@ -107,10 +115,21 @@ impl NostrClient {
107115 Ok ( ( ) )
108116 }
109117
110- /// Unwrap envelope from private direct message
111118 pub async fn unwrap_envelope (
112119 & self ,
113120 note : RelayPoolNotification ,
121+ ) -> Option < ( EventEnvelope , PublicKey , EventId , Timestamp ) > {
122+ if self . use_nip04 ( ) {
123+ self . unwrap_nip04_envelope ( note) . await
124+ } else {
125+ self . unwrap_nip17_envelope ( note) . await
126+ }
127+ }
128+
129+ /// Unwrap envelope from private direct message
130+ async fn unwrap_nip17_envelope (
131+ & self ,
132+ note : RelayPoolNotification ,
114133 ) -> Option < ( EventEnvelope , PublicKey , EventId , Timestamp ) > {
115134 let mut result: Option < ( EventEnvelope , PublicKey , EventId , Timestamp ) > = None ;
116135 if let RelayPoolNotification :: Event { event, .. } = note {
@@ -127,17 +146,68 @@ impl NostrClient {
127146 }
128147 result
129148 }
130- }
131149
132- impl ServiceTraitBounds for NostrClient { }
150+ /// Unwrap envelope from private direct message
151+ async fn unwrap_nip04_envelope (
152+ & self ,
153+ note : RelayPoolNotification ,
154+ ) -> Option < ( EventEnvelope , PublicKey , EventId , Timestamp ) > {
155+ let mut result: Option < ( EventEnvelope , PublicKey , EventId , Timestamp ) > = None ;
156+ if let RelayPoolNotification :: Event { event, .. } = note {
157+ if event. kind == Kind :: EncryptedDirectMessage {
158+ match nip04:: decrypt (
159+ self . keys . get_nostr_keys ( ) . secret_key ( ) ,
160+ & event. pubkey ,
161+ & event. content ,
162+ ) {
163+ Ok ( decrypted) => {
164+ result = extract_text_envelope ( & decrypted)
165+ . map ( |e| ( e, event. pubkey , event. id , event. created_at ) ) ;
166+ }
167+ Err ( e) => {
168+ error ! ( "Decrypting event failed: {e}" ) ;
169+ }
170+ }
171+ } else {
172+ info ! (
173+ "Received event with kind {} but expected GiftWrap" ,
174+ event. kind
175+ ) ;
176+ }
177+ }
178+ result
179+ }
133180
134- #[ cfg_attr( target_arch = "wasm32" , async_trait( ?Send ) ) ]
135- #[ cfg_attr( not( target_arch = "wasm32" ) , async_trait) ]
136- impl NotificationJsonTransportApi for NostrClient {
137- fn get_sender_key ( & self ) -> String {
138- self . get_node_id ( )
181+ pub async fn send_nip04_message (
182+ & self ,
183+ recipient : & IdentityPublicData ,
184+ event : EventEnvelope ,
185+ ) -> bcr_ebill_transport:: Result < ( ) > {
186+ if let Ok ( npub) = crypto:: get_nostr_npub_as_hex_from_node_id ( & recipient. node_id ) {
187+ let public_key = PublicKey :: from_str ( & npub) . map_err ( |e| {
188+ error ! ( "Failed to parse Nostr npub when sending a notification: {e}" ) ;
189+ Error :: Crypto ( "Failed to parse Nostr npub" . to_string ( ) )
190+ } ) ?;
191+ let message = serde_json:: to_string ( & event) ?;
192+ let event =
193+ create_nip04_event ( self . get_nostr_keys ( ) . secret_key ( ) , & public_key, & message) ?;
194+ if let Some ( relay) = & recipient. nostr_relay {
195+ if let Err ( e) = self . client . send_event_builder_to ( vec ! [ relay] , event) . await {
196+ error ! ( "Error sending Nostr message: {e}" )
197+ } ;
198+ } else if let Err ( e) = self . client . send_event_builder ( event) . await {
199+ error ! ( "Error sending Nostr message: {e}" )
200+ }
201+ } else {
202+ error ! (
203+ "Try to send Nostr message but Nostr npub not found in contact {}" ,
204+ recipient. name
205+ ) ;
206+ }
207+ Ok ( ( ) )
139208 }
140- async fn send (
209+
210+ async fn send_nip17_message (
141211 & self ,
142212 recipient : & IdentityPublicData ,
143213 event : EventEnvelope ,
@@ -173,6 +243,28 @@ impl NotificationJsonTransportApi for NostrClient {
173243 }
174244}
175245
246+ impl ServiceTraitBounds for NostrClient { }
247+
248+ #[ cfg_attr( target_arch = "wasm32" , async_trait( ?Send ) ) ]
249+ #[ cfg_attr( not( target_arch = "wasm32" ) , async_trait) ]
250+ impl NotificationJsonTransportApi for NostrClient {
251+ fn get_sender_key ( & self ) -> String {
252+ self . get_node_id ( )
253+ }
254+ async fn send (
255+ & self ,
256+ recipient : & IdentityPublicData ,
257+ event : EventEnvelope ,
258+ ) -> bcr_ebill_transport:: Result < ( ) > {
259+ if self . use_nip04 ( ) {
260+ self . send_nip04_message ( recipient, event) . await ?;
261+ } else {
262+ self . send_nip17_message ( recipient, event) . await ?;
263+ }
264+ Ok ( ( ) )
265+ }
266+ }
267+
176268#[ derive( Clone ) ]
177269pub struct NostrConsumer {
178270 clients : HashMap < String , Arc < NostrClient > > ,
@@ -226,15 +318,14 @@ impl NostrConsumer {
226318 // continue where we left off
227319 let offset_ts = get_offset ( & offset_store, & node_id) . await ;
228320 let public_key = current_client. keys . get_nostr_keys ( ) . public_key ( ) ;
321+ let filter = Filter :: new ( )
322+ . pubkey ( public_key)
323+ . kind ( Kind :: EncryptedDirectMessage )
324+ . since ( offset_ts) ;
229325
230326 // subscribe only to private messages sent to our pubkey
231327 current_client
232- . subscribe (
233- Filter :: new ( )
234- . pubkey ( public_key)
235- . kind ( Kind :: GiftWrap )
236- . since ( offset_ts) ,
237- )
328+ . subscribe ( filter)
238329 . await
239330 . expect ( "Failed to subscribe to Nostr events" ) ;
240331
@@ -306,13 +397,18 @@ async fn valid_sender(
306397}
307398
308399async fn get_offset ( db : & Arc < dyn NostrEventOffsetStoreApi > , node_id : & str ) -> Timestamp {
309- Timestamp :: from_secs (
310- db. current_offset ( node_id)
311- . await
312- . map_err ( |e| error ! ( "Could not get event offset: {e}" ) )
313- . ok ( )
314- . unwrap_or ( 0 ) ,
315- )
400+ let current = db
401+ . current_offset ( node_id)
402+ . await
403+ . map_err ( |e| error ! ( "Could not get event offset: {e}" ) )
404+ . ok ( )
405+ . unwrap_or ( 0 ) ;
406+ let ts = if current <= NOSTR_EVENT_TIME_SLACK {
407+ current
408+ } else {
409+ current - NOSTR_EVENT_TIME_SLACK
410+ } ;
411+ Timestamp :: from_secs ( ts)
316412}
317413
318414async fn add_offset (
@@ -333,6 +429,16 @@ async fn add_offset(
333429 . ok ( ) ;
334430}
335431
432+ fn extract_text_envelope ( message : & str ) -> Option < EventEnvelope > {
433+ match serde_json:: from_str :: < EventEnvelope > ( message) {
434+ Ok ( envelope) => Some ( envelope) ,
435+ Err ( e) => {
436+ error ! ( "Json deserializing event envelope failed: {e}" ) ;
437+ None
438+ }
439+ }
440+ }
441+
336442fn extract_event_envelope ( rumor : UnsignedEvent ) -> Option < EventEnvelope > {
337443 if rumor. kind == Kind :: PrivateDirectMessage {
338444 match serde_json:: from_str :: < EventEnvelope > ( rumor. content . as_str ( ) ) {
@@ -347,6 +453,21 @@ fn extract_event_envelope(rumor: UnsignedEvent) -> Option<EventEnvelope> {
347453 }
348454}
349455
456+ fn create_nip04_event (
457+ secret_key : & SecretKey ,
458+ public_key : & PublicKey ,
459+ message : & str ,
460+ ) -> Result < EventBuilder > {
461+ Ok ( EventBuilder :: new (
462+ Kind :: EncryptedDirectMessage ,
463+ nip04:: encrypt ( secret_key, public_key, message) . map_err ( |e| {
464+ error ! ( "Failed to encrypt direct private message: {e}" ) ;
465+ Error :: Crypto ( "Failed to encrypt direct private message" . to_string ( ) )
466+ } ) ?,
467+ )
468+ . tag ( Tag :: public_key ( * public_key) ) )
469+ }
470+
350471/// Handle extracted event with given handlers.
351472async fn handle_event (
352473 event : EventEnvelope ,
0 commit comments