nip07.mx raw
1 package main
2
3 import (
4 "git.smesh.lol/smesh/web/common/crypto/nip44"
5 "git.smesh.lol/smesh/web/common/helpers"
6 "git.smesh.lol/smesh/web/common/jsbridge/schnorr"
7 "git.smesh.lol/smesh/web/common/jsbridge/subtle"
8 "git.smesh.lol/smesh/web/common/nostr"
9 nosig "git.smesh.lol/smesh/web/common/nostr/sig"
10 )
11
12 // NIP-07 method handlers. Secret keys never leave this process.
13
14 var errNoActiveIdentity = func() string {
15 return jsonErr(string(append([]byte(nil), 'n', 'o', ' ', 'a', 'c', 't', 'i', 'v', 'e', ' ', 'i', 'd', 'e', 'n', 't', 'i', 't', 'y')))
16 }
17
18 func nip07GetPublicKey() (s string) {
19 id := activeIdentity()
20 if id == nil {
21 return errNoActiveIdentity()
22 }
23 return jsonResult(helpers.JsonString(id.Pubkey))
24 }
25
26 func nip07SignEvent(paramsJSON string) (s string) {
27 id := activeIdentity()
28 if id == nil {
29 return errNoActiveIdentity()
30 }
31 kEvent := string(append([]byte(nil), 'e', 'v', 'e', 'n', 't'))
32 eventRaw := helpers.JsonGetValue(paramsJSON, kEvent)
33 if eventRaw == "" {
34 eventRaw = paramsJSON
35 }
36 ev := nostr.ParseEvent(eventRaw)
37 if ev == nil {
38 return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'e', 'v', 'e', 'n', 't')))
39 }
40 skBytes := helpers.HexDecode(id.Seckey)
41 if skBytes == nil {
42 return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 's', 'e', 'c', 'k', 'e', 'y')))
43 }
44 pkBytes, ok := schnorr.PubKeyFromSecKey(skBytes)
45 if !ok {
46 return jsonErr(string(append([]byte(nil), 'k', 'e', 'y', ' ', 'd', 'e', 'r', 'i', 'v', 'a', 't', 'i', 'o', 'n', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
47 }
48 ev.PubKey = helpers.HexEncode(pkBytes)
49 (*nosig.Event)(ev).ComputeID()
50 idBytes := helpers.HexDecode(ev.ID)
51 if idBytes == nil {
52 return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'i', 'd')))
53 }
54 aux := []byte{:32}
55 subtle.RandomBytes(aux)
56 sig, ok := schnorr.SignSchnorr(skBytes, idBytes, aux)
57 if !ok {
58 return jsonErr(string(append([]byte(nil), 's', 'i', 'g', 'n', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
59 }
60 ev.Sig = helpers.HexEncode(sig)
61 return jsonResult(ev.ToJSON())
62 }
63
64 func nip07Nip04Encrypt(paramsJSON string) (s string) {
65 id := activeIdentity()
66 if id == nil {
67 return errNoActiveIdentity()
68 }
69 kPubkey := string(append([]byte(nil), 'p', 'u', 'b', 'k', 'e', 'y'))
70 kPlaintext := string(append([]byte(nil), 'p', 'l', 'a', 'i', 'n', 't', 'e', 'x', 't'))
71 pk := helpers.JsonGetString(paramsJSON, kPubkey)
72 pt := helpers.JsonGetString(paramsJSON, kPlaintext)
73 if pk == "" || pt == "" {
74 return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 'p', 'a', 'r', 'a', 'm', 's')))
75 }
76 skBytes := helpers.HexDecode(id.Seckey)
77 pkBytes := helpers.HexDecode(pk)
78 shared, ok := schnorr.ECDH(skBytes, pkBytes)
79 if !ok {
80 return jsonErr(string(append([]byte(nil), 'e', 'c', 'd', 'h', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
81 }
82 iv := []byte{:16}
83 subtle.RandomBytes(iv)
84 ct := subtle.AESCBCEncrypt(shared, iv, []byte(pt))
85 if len(ct) == 0 {
86 return jsonErr(string(append([]byte(nil), 'e', 'n', 'c', 'r', 'y', 'p', 't', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
87 }
88 ctB64 := helpers.Base64Encode(ct)
89 ivB64 := helpers.Base64Encode(iv)
90 result := []byte(nil) | ctB64
91 result = append(result, '?', 'i', 'v', '=')
92 result = result | ivB64
93 return jsonResult(helpers.JsonString(string(result)))
94 }
95
96 func nip07Nip04Decrypt(paramsJSON string) (s string) {
97 id := activeIdentity()
98 if id == nil {
99 return errNoActiveIdentity()
100 }
101 kPubkey2 := string(append([]byte(nil), 'p', 'u', 'b', 'k', 'e', 'y'))
102 kCiphertext := string(append([]byte(nil), 'c', 'i', 'p', 'h', 'e', 'r', 't', 'e', 'x', 't'))
103 pk := helpers.JsonGetString(paramsJSON, kPubkey2)
104 ct := helpers.JsonGetString(paramsJSON, kCiphertext)
105 if pk == "" || ct == "" {
106 return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 'p', 'a', 'r', 'a', 'm', 's')))
107 }
108 skBytes := helpers.HexDecode(id.Seckey)
109 pkBytes := helpers.HexDecode(pk)
110 shared, ok := schnorr.ECDH(skBytes, pkBytes)
111 if !ok {
112 return jsonErr(string(append([]byte(nil), 'e', 'c', 'd', 'h', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
113 }
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 jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'c', 'i', 'p', 'h', 'e', 'r', 't', 'e', 'x', 't', ' ', 'f', 'o', 'r', 'm', 'a', 't')))
123 }
124 ctBytes := helpers.Base64Decode(ct[:ivIdx])
125 ivBytes := helpers.Base64Decode(ct[ivIdx+4:])
126 if ctBytes == nil || ivBytes == nil {
127 return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'b', 'a', 's', 'e', '6', '4')))
128 }
129 pt := subtle.AESCBCDecrypt(shared, ivBytes, ctBytes)
130 if pt == nil {
131 return jsonErr(string(append([]byte(nil), 'd', 'e', 'c', 'r', 'y', 'p', 't', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
132 }
133 return jsonResult(helpers.JsonString(string(pt)))
134 }
135
136 // nip07Nip44Encrypt is synchronous: nip44.ConversationKey uses a sync ECDH.
137 func nip07Nip44Encrypt(paramsJSON string) (s string) {
138 id := activeIdentity()
139 if id == nil {
140 return errNoActiveIdentity()
141 }
142 kPubkeyN := string(append([]byte(nil), 'p', 'u', 'b', 'k', 'e', 'y'))
143 kPlaintextN := string(append([]byte(nil), 'p', 'l', 'a', 'i', 'n', 't', 'e', 'x', 't'))
144 pk := helpers.JsonGetString(paramsJSON, kPubkeyN)
145 pt := helpers.JsonGetString(paramsJSON, kPlaintextN)
146 if pk == "" || pt == "" {
147 return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 'p', 'a', 'r', 'a', 'm', 's')))
148 }
149 sk32, ok := helpers.HexDecode32(id.Seckey)
150 if !ok {
151 return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 's', 'e', 'c', 'k', 'e', 'y')))
152 }
153 pk32, ok := helpers.HexDecode32(pk)
154 if !ok {
155 return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'p', 'u', 'b', 'k', 'e', 'y')))
156 }
157 convKey, ok := nip44.ConversationKey(sk32, pk32)
158 if !ok {
159 return jsonErr(string(append([]byte(nil), 'k', 'e', 'y', ' ', 'd', 'e', 'r', 'i', 'v', 'a', 't', 'i', 'o', 'n', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
160 }
161 var nonce [32]byte
162 subtle.RandomBytes(nonce[:])
163 return jsonResult(helpers.JsonString(nip44.Encrypt(pt, convKey, nonce)))
164 }
165
166 // nip07Nip44Decrypt is synchronous.
167 func nip07Nip44Decrypt(paramsJSON string) (s string) {
168 id := activeIdentity()
169 if id == nil {
170 return errNoActiveIdentity()
171 }
172 kPubkeyD := string(append([]byte(nil), 'p', 'u', 'b', 'k', 'e', 'y'))
173 kCiphertextD := string(append([]byte(nil), 'c', 'i', 'p', 'h', 'e', 'r', 't', 'e', 'x', 't'))
174 pk := helpers.JsonGetString(paramsJSON, kPubkeyD)
175 ct := helpers.JsonGetString(paramsJSON, kCiphertextD)
176 if pk == "" || ct == "" {
177 return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 'p', 'a', 'r', 'a', 'm', 's')))
178 }
179 sk32, ok := helpers.HexDecode32(id.Seckey)
180 if !ok {
181 return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 's', 'e', 'c', 'k', 'e', 'y')))
182 }
183 pk32, ok := helpers.HexDecode32(pk)
184 if !ok {
185 return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'p', 'u', 'b', 'k', 'e', 'y')))
186 }
187 convKey, ok := nip44.ConversationKey(sk32, pk32)
188 if !ok {
189 return jsonErr(string(append([]byte(nil), 'k', 'e', 'y', ' ', 'd', 'e', 'r', 'i', 'v', 'a', 't', 'i', 'o', 'n', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
190 }
191 pt, ok := nip44.Decrypt(ct, convKey)
192 if !ok {
193 return jsonErr(string(append([]byte(nil), 'd', 'e', 'c', 'r', 'y', 'p', 't', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
194 }
195 return jsonResult(helpers.JsonString(pt))
196 }
197
198 // nip07GetSharedSecret is synchronous: schnorr.ECDH is a sync wasmimport.
199 func nip07GetSharedSecret(paramsJSON string) (s string) {
200 id := activeIdentity()
201 if id == nil {
202 return errNoActiveIdentity()
203 }
204 kPubkeyS := string(append([]byte(nil), 'p', 'u', 'b', 'k', 'e', 'y'))
205 pk := helpers.JsonGetString(paramsJSON, kPubkeyS)
206 if pk == "" {
207 return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 'p', 'u', 'b', 'k', 'e', 'y')))
208 }
209 shared, ok := schnorr.ECDH(helpers.HexDecode(id.Seckey), helpers.HexDecode(pk))
210 if !ok {
211 return jsonErr(string(append([]byte(nil), 'e', 'c', 'd', 'h', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
212 }
213 return jsonResult(helpers.JsonString(helpers.HexEncode(shared)))
214 }
215
216 // cryptoEcdhWithSecret: ECDH with caller-provided secret key.
217 // params: {"secret":"<hex>","pubkey":"<hex>"}
218 // returns: {"result":"<shared hex>"}
219 func cryptoEcdhWithSecret(paramsJSON string) (s string) {
220 kSecret := string(append([]byte(nil), 's', 'e', 'c', 'r', 'e', 't'))
221 kPubkey := string(append([]byte(nil), 'p', 'u', 'b', 'k', 'e', 'y'))
222 sk := helpers.JsonGetString(paramsJSON, kSecret)
223 pk := helpers.JsonGetString(paramsJSON, kPubkey)
224 if sk == "" || pk == "" {
225 return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 'p', 'a', 'r', 'a', 'm', 's')))
226 }
227 skBytes := helpers.HexDecode(sk)
228 pkBytes := helpers.HexDecode(pk)
229 if len(skBytes) != 32 || len(pkBytes) != 32 {
230 return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'k', 'e', 'y', ' ', 'l', 'e', 'n', 'g', 't', 'h')))
231 }
232 shared, ok := schnorr.ECDH(skBytes, pkBytes)
233 if !ok {
234 return jsonErr(string(append([]byte(nil), 'e', 'c', 'd', 'h', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
235 }
236 return jsonResult(helpers.JsonString(helpers.HexEncode(shared)))
237 }
238
239 // cryptoSignWithSecret: sign event with caller-provided secret key.
240 // params: {"secret":"<hex>","event":"<eventJSON>"}
241 // returns: {"result":"<signed eventJSON>"}
242 func cryptoSignWithSecret(paramsJSON string) (s string) {
243 kSecret := string(append([]byte(nil), 's', 'e', 'c', 'r', 'e', 't'))
244 kEvent := string(append([]byte(nil), 'e', 'v', 'e', 'n', 't'))
245 sk := helpers.JsonGetString(paramsJSON, kSecret)
246 eventRaw := helpers.JsonGetValue(paramsJSON, kEvent)
247 if sk == "" || eventRaw == "" {
248 return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 'p', 'a', 'r', 'a', 'm', 's')))
249 }
250 skBytes := helpers.HexDecode(sk)
251 if len(skBytes) != 32 {
252 return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 's', 'e', 'c', 'r', 'e', 't')))
253 }
254 ev := nostr.ParseEvent(eventRaw)
255 if ev == nil {
256 return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'e', 'v', 'e', 'n', 't')))
257 }
258 pkBytes, ok := schnorr.PubKeyFromSecKey(skBytes)
259 if !ok {
260 return jsonErr(string(append([]byte(nil), 'k', 'e', 'y', ' ', 'd', 'e', 'r', 'i', 'v', 'a', 't', 'i', 'o', 'n', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
261 }
262 ev.PubKey = helpers.HexEncode(pkBytes)
263 (*nosig.Event)(ev).ComputeID()
264 idBytes := helpers.HexDecode(ev.ID)
265 if len(idBytes) != 32 {
266 return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'i', 'd')))
267 }
268 aux := []byte{:32}
269 subtle.RandomBytes(aux)
270 sig, ok := schnorr.SignSchnorr(skBytes, idBytes, aux)
271 if !ok {
272 return jsonErr(string(append([]byte(nil), 's', 'i', 'g', 'n', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
273 }
274 ev.Sig = helpers.HexEncode(sig)
275 return jsonResult(ev.ToJSON())
276 }
277
278 // cryptoPubkeyFromSecret: derive x-only pubkey from caller-provided secret.
279 // params: {"secret":"<hex>"}
280 // returns: {"result":"<pubkey hex>"}
281 func cryptoPubkeyFromSecret(paramsJSON string) (s string) {
282 kSecret := string(append([]byte(nil), 's', 'e', 'c', 'r', 'e', 't'))
283 sk := helpers.JsonGetString(paramsJSON, kSecret)
284 if sk == "" {
285 return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 's', 'e', 'c', 'r', 'e', 't')))
286 }
287 skBytes := helpers.HexDecode(sk)
288 if len(skBytes) != 32 {
289 return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 's', 'e', 'c', 'r', 'e', 't')))
290 }
291 pkBytes, ok := schnorr.PubKeyFromSecKey(skBytes)
292 if !ok {
293 return jsonErr(string(append([]byte(nil), 'k', 'e', 'y', ' ', 'd', 'e', 'r', 'i', 'v', 'a', 't', 'i', 'o', 'n', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
294 }
295 return jsonResult(helpers.JsonString(helpers.HexEncode(pkBytes)))
296 }
297