@@ -24,13 +24,7 @@ var gift = &cli.Command{
2424
2525a decoupled key (if it has been created or received with "nak dekey" previously) will be used by default.` ,
2626 DisableSliceFlagSeparator : true ,
27- Flags : append (
28- defaultKeyFlags ,
29- & cli.BoolFlag {
30- Name : "use-direct" ,
31- Usage : "Use the key given to --sec directly even when a decoupled key exists." ,
32- },
33- ),
27+ Flags : defaultKeyFlags ,
3428 Commands : []* cli.Command {
3529 {
3630 Name : "wrap" ,
@@ -40,6 +34,14 @@ a decoupled key (if it has been created or received with "nak dekey" previously)
4034 Aliases : []string {"p" , "tgt" , "target" , "pubkey" , "to" },
4135 Required : true ,
4236 },
37+ & cli.BoolFlag {
38+ Name : "use-our-identity-key" ,
39+ Usage : "Encrypt with the key given to --sec directly even when a decoupled key exists for the sender." ,
40+ },
41+ & cli.BoolFlag {
42+ Name : "use-their-identity-key" ,
43+ Usage : "Encrypt to the public key given as --recipient-pubkey directly even when a decoupled key exists for the receiver." ,
44+ },
4345 },
4446 Usage : "turns an event into a rumor (unsigned) then gift-wraps it to the recipient" ,
4547 Description : `example:
@@ -56,18 +58,39 @@ a decoupled key (if it has been created or received with "nak dekey" previously)
5658 return fmt .Errorf ("failed to get sender pubkey: %w" , err )
5759 }
5860
61+ var using bool
62+
5963 var cipher nostr.Cipher = kr
6064 // use decoupled key if it exists
61- configPath := c .String ("config-path" )
62- eSec , has , err := getDecoupledEncryptionKey (ctx , configPath , sender )
63- if has {
64- if err != nil {
65- return fmt .Errorf ("decoupled encryption key exists, but we failed to get it: %w; call `nak dekey` to attempt a fix or call this again with --use-direct to bypass" , err )
65+ using = false
66+ if ! c .Bool ("use-our-identity-key" ) {
67+ configPath := c .String ("config-path" )
68+ eSec , has , err := getDecoupledEncryptionSecretKey (ctx , configPath , sender )
69+ if has {
70+ if err != nil {
71+ return fmt .Errorf ("our decoupled encryption key exists, but we failed to get it: %w; call `nak dekey` to attempt a fix or call this again with --encrypt-with-our-identity-key to bypass" , err )
72+ }
73+ cipher = keyer .NewPlainKeySigner (eSec )
74+ log ("- using our decoupled encryption key %s\n " , color .CyanString (eSec .Public ().Hex ()))
75+ using = true
6676 }
67- cipher = keyer .NewPlainKeySigner (eSec )
77+ }
78+ if ! using {
79+ log ("- using our identity key %s\n " , color .CyanString (sender .Hex ()))
6880 }
6981
7082 recipient := getPubKey (c , "recipient-pubkey" )
83+ using = false
84+ if ! c .Bool ("use-their-identity-key" ) {
85+ if theirEPub , exists := getDecoupledEncryptionPublicKey (ctx , recipient ); exists {
86+ recipient = theirEPub
87+ using = true
88+ log ("- using their decoupled encryption public key %s\n " , color .CyanString (theirEPub .Hex ()))
89+ }
90+ }
91+ if ! using {
92+ log ("- using their identity public key %s\n " , color .CyanString (recipient .Hex ()))
93+ }
7194
7295 // read event from stdin
7396 for eventJSON := range getJsonsOrBlank () {
@@ -137,14 +160,7 @@ a decoupled key (if it has been created or received with "nak dekey" previously)
137160 Name : "unwrap" ,
138161 Usage : "decrypts a gift-wrap event sent by the sender to us and exposes its internal rumor (unsigned event)." ,
139162 Description : `example:
140- nak req -p <my-public-key> -k 1059 dmrelay.com | nak gift unwrap --sec <my-secret-key> --from <sender-public-key>` ,
141- Flags : []cli.Flag {
142- & PubKeyFlag {
143- Name : "sender-pubkey" ,
144- Aliases : []string {"p" , "src" , "source" , "pubkey" , "from" },
145- Required : true ,
146- },
147- },
163+ nak req -p <my-public-key> -k 1059 dmrelay.com | nak gift unwrap --sec <my-secret-key>` ,
148164 Action : func (ctx context.Context , c * cli.Command ) error {
149165 kr , _ , err := gatherKeyerFromArguments (ctx , c )
150166 if err != nil {
@@ -157,19 +173,18 @@ a decoupled key (if it has been created or received with "nak dekey" previously)
157173 return err
158174 }
159175
160- var cipher nostr.Cipher = kr
176+ ciphers := [] nostr.Cipher { kr }
161177 // use decoupled key if it exists
162178 configPath := c .String ("config-path" )
163- eSec , has , err := getDecoupledEncryptionKey (ctx , configPath , receiver )
179+ eSec , has , err := getDecoupledEncryptionSecretKey (ctx , configPath , receiver )
164180 if has {
165181 if err != nil {
166- return fmt .Errorf ("decoupled encryption key exists, but we failed to get it: %w; call `nak dekey` to attempt a fix or call this again with --use-direct to bypass" , err )
182+ return fmt .Errorf ("our decoupled encryption key exists, but we failed to get it: %w; call `nak dekey` to attempt a fix or call this again with --use-direct to bypass" , err )
167183 }
168- cipher = keyer .NewPlainKeySigner (eSec )
184+ ciphers = append (ciphers , kr )
185+ ciphers [0 ] = keyer .NewPlainKeySigner (eSec ) // pub decoupled key first
169186 }
170187
171- sender := getPubKey (c , "sender-pubkey" )
172-
173188 // read gift-wrapped event from stdin
174189 for wrapJSON := range getJsonsOrBlank () {
175190 if wrapJSON == "{}" {
@@ -185,36 +200,79 @@ a decoupled key (if it has been created or received with "nak dekey" previously)
185200 return fmt .Errorf ("not a gift wrap event (kind %d)" , wrap .Kind )
186201 }
187202
188- ephemeralPubkey := wrap .PubKey
203+ // decrypt seal (in the process also find out if they encrypted it to our identity key or to our decoupled key)
204+ var cipher nostr.Cipher
205+ var seal nostr.Event
189206
190- // decrypt seal
191- sealJSON , err := cipher .Decrypt (ctx , wrap .Content , ephemeralPubkey )
192- if err != nil {
193- return fmt .Errorf ("failed to decrypt seal: %w" , err )
207+ // try both the receiver identity key and decoupled key
208+ err = nil
209+ for c , potentialCipher := range ciphers {
210+ switch c {
211+ case 0 :
212+ log ("- trying the receiver's decoupled encryption key %s\n " , color .CyanString (eSec .Public ().Hex ()))
213+ case 1 :
214+ log ("- trying the receiver's identity key %s\n " , color .CyanString (receiver .Hex ()))
215+ }
216+
217+ sealj , thisErr := potentialCipher .Decrypt (ctx , wrap .Content , wrap .PubKey )
218+ if thisErr != nil {
219+ err = thisErr
220+ continue
221+ }
222+ if thisErr := easyjson .Unmarshal ([]byte (sealj ), & seal ); thisErr != nil {
223+ err = fmt .Errorf ("invalid seal JSON: %w" , thisErr )
224+ continue
225+ }
226+
227+ cipher = potentialCipher
228+ break
194229 }
195-
196- var seal nostr.Event
197- if err := easyjson .Unmarshal ([]byte (sealJSON ), & seal ); err != nil {
198- return fmt .Errorf ("invalid seal JSON: %w" , err )
230+ if seal .ID == nostr .ZeroID {
231+ // if both ciphers failed above we'll reach here
232+ return fmt .Errorf ("failed to decrypt seal: %w" , err )
199233 }
200234
201235 if seal .Kind != 13 {
202236 return fmt .Errorf ("not a seal event (kind %d)" , seal .Kind )
203237 }
204238
205- // decrypt rumor
206- rumorJSON , err := cipher . Decrypt (ctx , seal .Content , sender )
207- if err != nil {
208- return fmt . Errorf ( "failed to decrypt rumor: %w" , err )
239+ senderEncryptionPublicKeys := []nostr. PubKey { seal . PubKey }
240+ if theirEPub , exists := getDecoupledEncryptionPublicKey (ctx , seal .PubKey ); exists {
241+ senderEncryptionPublicKeys = append ( senderEncryptionPublicKeys , seal . PubKey )
242+ senderEncryptionPublicKeys [ 0 ] = theirEPub // put decoupled key first
209243 }
210244
245+ // decrypt rumor (at this point we know what cipher is the one they encrypted to)
246+ // (but we don't know if they have encrypted with their identity key or their decoupled key, so try both)
211247 var rumor nostr.Event
212- if err := easyjson .Unmarshal ([]byte (rumorJSON ), & rumor ); err != nil {
213- return fmt .Errorf ("invalid rumor JSON: %w" , err )
248+ err = nil
249+ for s , senderEncryptionPublicKey := range senderEncryptionPublicKeys {
250+ switch s {
251+ case 0 :
252+ log ("- trying the sender's decoupled encryption public key %s\n " , color .CyanString (senderEncryptionPublicKey .Hex ()))
253+ case 1 :
254+ log ("- trying the sender's identity public key %s\n " , color .CyanString (senderEncryptionPublicKey .Hex ()))
255+ }
256+
257+ rumorj , thisErr := cipher .Decrypt (ctx , seal .Content , senderEncryptionPublicKey )
258+ if thisErr != nil {
259+ err = fmt .Errorf ("failed to decrypt rumor: %w" , thisErr )
260+ continue
261+ }
262+ if thisErr := easyjson .Unmarshal ([]byte (rumorj ), & rumor ); thisErr != nil {
263+ err = fmt .Errorf ("invalid rumor JSON: %w" , thisErr )
264+ continue
265+ }
266+
267+ break
268+ }
269+
270+ if rumor .ID == nostr .ZeroID {
271+ return fmt .Errorf ("failed to decrypt rumor: %w" , err )
214272 }
215273
216274 // output the unwrapped event (rumor)
217- stdout (rumorJSON )
275+ stdout (rumor . String () )
218276 }
219277
220278 return nil
@@ -230,18 +288,18 @@ func randomNow() nostr.Timestamp {
230288 return nostr .Timestamp (now - randomOffset )
231289}
232290
233- func getDecoupledEncryptionKey (ctx context.Context , configPath string , pubkey nostr.PubKey ) (nostr.SecretKey , bool , error ) {
291+ func getDecoupledEncryptionSecretKey (ctx context.Context , configPath string , pubkey nostr.PubKey ) (nostr.SecretKey , bool , error ) {
234292 relays := sys .FetchWriteRelays (ctx , pubkey )
235293
236294 keyAnnouncementResult := sys .Pool .FetchManyReplaceable (ctx , relays , nostr.Filter {
237295 Kinds : []nostr.Kind {10044 },
238296 Authors : []nostr.PubKey {pubkey },
239297 }, nostr.SubscriptionOptions {Label : "nak-nip4e-gift" })
240- var eSec nostr.SecretKey
241- var ePub nostr.PubKey
242298
243299 keyAnnouncementEvent , ok := keyAnnouncementResult .Load (nostr.ReplaceableKey {PubKey : pubkey , D : "" })
244300 if ok {
301+ var ePub nostr.PubKey
302+
245303 // get the pub from the tag
246304 for _ , tag := range keyAnnouncementEvent .Tags {
247305 if len (tag ) >= 2 && tag [0 ] == "n" {
@@ -256,8 +314,7 @@ func getDecoupledEncryptionKey(ctx context.Context, configPath string, pubkey no
256314 // check if we have the key
257315 eKeyPath := filepath .Join (configPath , "dekey" , "p" , pubkey .Hex (), "e" , ePub .Hex ())
258316 if data , err := os .ReadFile (eKeyPath ); err == nil {
259- log (color .GreenString ("- and we have it locally already\n " ))
260- eSec , err = nostr .SecretKeyFromHex (string (data ))
317+ eSec , err := nostr .SecretKeyFromHex (string (data ))
261318 if err != nil {
262319 return [32 ]byte {}, true , fmt .Errorf ("invalid main key: %w" , err )
263320 }
@@ -271,3 +328,32 @@ func getDecoupledEncryptionKey(ctx context.Context, configPath string, pubkey no
271328
272329 return [32 ]byte {}, false , nil
273330}
331+
332+ func getDecoupledEncryptionPublicKey (ctx context.Context , pubkey nostr.PubKey ) (nostr.PubKey , bool ) {
333+ relays := sys .FetchWriteRelays (ctx , pubkey )
334+
335+ keyAnnouncementResult := sys .Pool .FetchManyReplaceable (ctx , relays , nostr.Filter {
336+ Kinds : []nostr.Kind {10044 },
337+ Authors : []nostr.PubKey {pubkey },
338+ }, nostr.SubscriptionOptions {Label : "nak-nip4e-gift" })
339+
340+ keyAnnouncementEvent , ok := keyAnnouncementResult .Load (nostr.ReplaceableKey {PubKey : pubkey , D : "" })
341+ if ok {
342+ var ePub nostr.PubKey
343+
344+ // get the pub from the tag
345+ for _ , tag := range keyAnnouncementEvent .Tags {
346+ if len (tag ) >= 2 && tag [0 ] == "n" {
347+ ePub , _ = nostr .PubKeyFromHex (tag [1 ])
348+ break
349+ }
350+ }
351+ if ePub == nostr .ZeroPK {
352+ return nostr .ZeroPK , false
353+ }
354+
355+ return ePub , true
356+ }
357+
358+ return nostr .ZeroPK , false
359+ }
0 commit comments