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