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