nip07.mx raw
1 package main
2
3 import (
4 "smesh.lol/web/common/crypto/nip44"
5 "smesh.lol/web/common/helpers"
6 "smesh.lol/web/common/jsbridge/schnorr"
7 "smesh.lol/web/common/jsbridge/subtle"
8 "smesh.lol/web/common/nostr"
9 )
10
11 // NIP-07 method handlers. Secret keys never leave this process.
12
13 func nip07GetPublicKey() string {
14 id := activeIdentity()
15 if id == nil {
16 return "{\"error\":\"no active identity\"}"
17 }
18 return "{\"result\":" | helpers.JsonString(id.Pubkey) | "}"
19 }
20
21 func nip07SignEvent(paramsJSON string, senderTabID int) string {
22 id := activeIdentity()
23 if id == nil {
24 return "{\"error\":\"no active identity\"}"
25 }
26
27 eventRaw := helpers.JsonGetValue(paramsJSON, "event")
28 if eventRaw == "" {
29 eventRaw = paramsJSON
30 }
31 ev := nostr.ParseEvent(eventRaw)
32 if ev == nil {
33 return "{\"error\":\"invalid event\"}"
34 }
35
36 skBytes := helpers.HexDecode(id.Seckey)
37 if skBytes == nil {
38 return "{\"error\":\"invalid seckey\"}"
39 }
40
41 // Derive pubkey via JS BigInt (Go secp256k1 uses uint64 which breaks in JS).
42 pkBytes, ok := schnorr.PubKeyFromSecKey(skBytes)
43 if !ok {
44 return "{\"error\":\"key derivation failed\"}"
45 }
46 ev.PubKey = helpers.HexEncode(pkBytes)
47 ev.ComputeID()
48
49 idBytes := helpers.HexDecode(ev.ID)
50 if idBytes == nil {
51 return "{\"error\":\"invalid id\"}"
52 }
53 aux := []byte{:32}
54 subtle.RandomBytes(aux)
55 sig, ok := schnorr.SignSchnorr(skBytes, idBytes, aux)
56 if !ok {
57 return "{\"error\":\"sign failed\"}"
58 }
59 ev.Sig = helpers.HexEncode(sig)
60 return "{\"result\":" | ev.ToJSON() | "}"
61 }
62
63 func nip07GetRelays() string {
64 return "{\"result\":{}}"
65 }
66
67 func nip07Nip04Encrypt(paramsJSON string) string {
68 id := activeIdentity()
69 if id == nil {
70 return "{\"error\":\"no active identity\"}"
71 }
72 pk := helpers.JsonGetString(paramsJSON, "pubkey")
73 pt := helpers.JsonGetString(paramsJSON, "plaintext")
74 if pk == "" || pt == "" {
75 return "{\"error\":\"missing params\"}"
76 }
77 skBytes := helpers.HexDecode(id.Seckey)
78 pkDec := helpers.HexDecode(pk)
79 shared, ok := schnorr.ECDH(skBytes, pkDec)
80 if !ok {
81 return "{\"error\":\"ecdh failed\"}"
82 }
83 // AES-CBC encrypt.
84 iv := []byte{:16}
85 subtle.RandomBytes(iv)
86 var result string
87 subtle.AESCBCEncrypt(shared, iv, []byte(pt), func(ct []byte) {
88 result = "{\"result\":" | helpers.JsonString(
89 helpers.Base64Encode(ct) | "?iv=" | helpers.Base64Encode(iv)) | "}"
90 })
91 if result == "" {
92 return "{\"error\":\"encrypt failed\"}"
93 }
94 return result
95 }
96
97 func nip07Nip04Decrypt(paramsJSON string) string {
98 id := activeIdentity()
99 if id == nil {
100 return "{\"error\":\"no active identity\"}"
101 }
102 pk := helpers.JsonGetString(paramsJSON, "pubkey")
103 ct := helpers.JsonGetString(paramsJSON, "ciphertext")
104 if pk == "" || ct == "" {
105 return "{\"error\":\"missing params\"}"
106 }
107 skBytes := helpers.HexDecode(id.Seckey)
108 pkDec := helpers.HexDecode(pk)
109 shared, ok := schnorr.ECDH(skBytes, pkDec)
110 if !ok {
111 return "{\"error\":\"ecdh failed\"}"
112 }
113 // Parse "base64(ct)?iv=base64(iv)".
114 ivIdx := -1
115 for i := 0; i < len(ct)-3; i++ {
116 if ct[i] == '?' && ct[i+1] == 'i' && ct[i+2] == 'v' && ct[i+3] == '=' {
117 ivIdx = i
118 break
119 }
120 }
121 if ivIdx < 0 {
122 return "{\"error\":\"invalid ciphertext format\"}"
123 }
124 ctBytes := helpers.Base64Decode(ct[:ivIdx])
125 ivBytes := helpers.Base64Decode(ct[ivIdx+4:])
126 if ctBytes == nil || ivBytes == nil {
127 return "{\"error\":\"invalid base64\"}"
128 }
129 var result string
130 subtle.AESCBCDecrypt(shared, ivBytes, ctBytes, func(pt []byte) {
131 result = "{\"result\":" | helpers.JsonString(string(pt)) | "}"
132 })
133 if result == "" {
134 return "{\"error\":\"decrypt failed\"}"
135 }
136 return result
137 }
138
139 func nip07Nip44Encrypt(paramsJSON string) string {
140 id := activeIdentity()
141 if id == nil {
142 return "{\"error\":\"no active identity\"}"
143 }
144 pk := helpers.JsonGetString(paramsJSON, "pubkey")
145 pt := helpers.JsonGetString(paramsJSON, "plaintext")
146 if pk == "" || pt == "" {
147 return "{\"error\":\"missing params\"}"
148 }
149 var sk, peer [32]byte
150 skBytes := helpers.HexDecode(id.Seckey)
151 pkBytes := helpers.HexDecode(pk)
152 if len(skBytes) != 32 || len(pkBytes) != 32 {
153 return "{\"error\":\"invalid key length\"}"
154 }
155 copy(sk[:], skBytes)
156 copy(peer[:], pkBytes)
157 ck, ok := nip44.ConversationKey(sk, peer)
158 if !ok {
159 return "{\"error\":\"conversation key derivation failed\"}"
160 }
161 var nonce [32]byte
162 subtle.RandomBytes(nonce[:])
163 ct := nip44.Encrypt(pt, ck, nonce)
164 return "{\"result\":" | helpers.JsonString(ct) | "}"
165 }
166
167 func nip07Nip44Decrypt(paramsJSON string) string {
168 id := activeIdentity()
169 if id == nil {
170 return "{\"error\":\"no active identity\"}"
171 }
172 pk := helpers.JsonGetString(paramsJSON, "pubkey")
173 ct := helpers.JsonGetString(paramsJSON, "ciphertext")
174 if pk == "" || ct == "" {
175 return "{\"error\":\"missing params\"}"
176 }
177 var sk, peer [32]byte
178 skBytes := helpers.HexDecode(id.Seckey)
179 pkBytes := helpers.HexDecode(pk)
180 if len(skBytes) != 32 || len(pkBytes) != 32 {
181 return "{\"error\":\"invalid key length\"}"
182 }
183 copy(sk[:], skBytes)
184 copy(peer[:], pkBytes)
185 ck, ok := nip44.ConversationKey(sk, peer)
186 if !ok {
187 return "{\"error\":\"conversation key derivation failed\"}"
188 }
189 plain, ok := nip44.Decrypt(ct, ck)
190 if !ok {
191 return "{\"error\":\"decrypt failed\"}"
192 }
193 return "{\"result\":" | helpers.JsonString(plain) | "}"
194 }
195