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