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