1
1
use async_trait:: async_trait;
2
- use bcr_ebill_core:: contact:: IdentityPublicData ;
2
+ use bcr_ebill_core:: { contact:: IdentityPublicData , util :: crypto } ;
3
3
use bcr_ebill_transport:: event:: EventEnvelope ;
4
4
use 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} ;
7
6
use 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 } ,
10
10
} ;
11
11
use std:: collections:: HashMap ;
12
12
use std:: str:: FromStr ;
13
13
use std:: sync:: Arc ;
14
14
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 } ;
17
17
use bcr_ebill_core:: ServiceTraitBounds ;
18
18
use bcr_ebill_persistence:: { NostrEventOffset , NostrEventOffsetStoreApi } ;
19
19
use bcr_ebill_transport:: { Error , NotificationJsonTransportApi , Result } ;
@@ -47,7 +47,7 @@ impl NostrConfig {
47
47
/// A wrapper around nostr_sdk that implements the NotificationJsonTransportApi.
48
48
///
49
49
/// # Example:
50
- /// ```ignore
50
+ /// ```no_run
51
51
/// let config = NostrConfig {
52
52
/// keys: BcrKeys::new(),
53
53
/// relays: vec!["wss://relay.example.com".to_string()],
@@ -95,6 +95,14 @@ impl NostrClient {
95
95
self . keys . get_public_key ( )
96
96
}
97
97
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
+
98
106
/// Subscribe to some nostr events with a filter
99
107
pub async fn subscribe ( & self , subscription : Filter ) -> Result < ( ) > {
100
108
self . client
@@ -107,10 +115,21 @@ impl NostrClient {
107
115
Ok ( ( ) )
108
116
}
109
117
110
- /// Unwrap envelope from private direct message
111
118
pub async fn unwrap_envelope (
112
119
& self ,
113
120
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 ,
114
133
) -> Option < ( EventEnvelope , PublicKey , EventId , Timestamp ) > {
115
134
let mut result: Option < ( EventEnvelope , PublicKey , EventId , Timestamp ) > = None ;
116
135
if let RelayPoolNotification :: Event { event, .. } = note {
@@ -127,17 +146,68 @@ impl NostrClient {
127
146
}
128
147
result
129
148
}
130
- }
131
149
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
+ }
133
180
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 ( ( ) )
139
208
}
140
- async fn send (
209
+
210
+ async fn send_nip17_message (
141
211
& self ,
142
212
recipient : & IdentityPublicData ,
143
213
event : EventEnvelope ,
@@ -173,6 +243,28 @@ impl NotificationJsonTransportApi for NostrClient {
173
243
}
174
244
}
175
245
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
+
176
268
#[ derive( Clone ) ]
177
269
pub struct NostrConsumer {
178
270
clients : HashMap < String , Arc < NostrClient > > ,
@@ -226,15 +318,14 @@ impl NostrConsumer {
226
318
// continue where we left off
227
319
let offset_ts = get_offset ( & offset_store, & node_id) . await ;
228
320
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) ;
229
325
230
326
// subscribe only to private messages sent to our pubkey
231
327
current_client
232
- . subscribe (
233
- Filter :: new ( )
234
- . pubkey ( public_key)
235
- . kind ( Kind :: GiftWrap )
236
- . since ( offset_ts) ,
237
- )
328
+ . subscribe ( filter)
238
329
. await
239
330
. expect ( "Failed to subscribe to Nostr events" ) ;
240
331
@@ -306,13 +397,18 @@ async fn valid_sender(
306
397
}
307
398
308
399
async 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)
316
412
}
317
413
318
414
async fn add_offset (
@@ -333,6 +429,16 @@ async fn add_offset(
333
429
. ok ( ) ;
334
430
}
335
431
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
+
336
442
fn extract_event_envelope ( rumor : UnsignedEvent ) -> Option < EventEnvelope > {
337
443
if rumor. kind == Kind :: PrivateDirectMessage {
338
444
match serde_json:: from_str :: < EventEnvelope > ( rumor. content . as_str ( ) ) {
@@ -347,6 +453,21 @@ fn extract_event_envelope(rumor: UnsignedEvent) -> Option<EventEnvelope> {
347
453
}
348
454
}
349
455
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
+
350
471
/// Handle extracted event with given handlers.
351
472
async fn handle_event (
352
473
event : EventEnvelope ,
0 commit comments