nip04.mx raw
1 package nip04
2
3 import (
4 "smesh.lol/web/common/helpers"
5 "smesh.lol/web/common/jsbridge/schnorr"
6 "smesh.lol/web/common/jsbridge/subtle"
7 )
8
9 // SharedKey derives the NIP-04 shared secret from ECDH (x-coordinate only).
10 func SharedKey(seckey, pubkey [32]byte) ([32]byte, bool) {
11 shared, ok := schnorr.ECDH(seckey[:], pubkey[:])
12 if !ok {
13 return [32]byte{}, false
14 }
15 var out [32]byte
16 copy(out[:], shared)
17 return out, true
18 }
19
20 // Encrypt encrypts plaintext using NIP-04 (AES-256-CBC).
21 // sharedKey: 32 bytes from SharedKey.
22 // Calls fn with the encoded string "base64(ciphertext)?iv=base64(iv)".
23 func Encrypt(sharedKey [32]byte, plaintext string, fn func(string)) {
24 iv := []byte{:16}
25 subtle.RandomBytes(iv)
26 subtle.AESCBCEncrypt(sharedKey[:], iv, []byte(plaintext), func(ct []byte) {
27 fn(helpers.Base64Encode(ct) + "?iv=" + helpers.Base64Encode(iv))
28 })
29 }
30
31 // Decrypt decrypts a NIP-04 message "base64(ciphertext)?iv=base64(iv)".
32 // Calls fn with the plaintext.
33 func Decrypt(sharedKey [32]byte, message string, fn func(string)) {
34 // Split on "?iv=".
35 ivIdx := -1
36 for i := 0; i < len(message)-3; i++ {
37 if message[i] == '?' && message[i+1] == 'i' && message[i+2] == 'v' && message[i+3] == '=' {
38 ivIdx = i
39 break
40 }
41 }
42 if ivIdx < 0 {
43 fn("")
44 return
45 }
46 ct := helpers.Base64Decode(message[:ivIdx])
47 iv := helpers.Base64Decode(message[ivIdx+4:])
48 if ct == nil || iv == nil {
49 fn("")
50 return
51 }
52 subtle.AESCBCDecrypt(sharedKey[:], iv, ct, func(pt []byte) {
53 fn(string(pt))
54 })
55 }
56