nip04.mx raw

   1  package nip04
   2  
   3  import (
   4  	"smesh.lol/web/common/helpers"
   5  	"smesh.lol/web/common/jsbridge/schnorr"
   6  	"smesh.lol/web/common/jsbridge/subtle"
   7  )
   8  
   9  // SharedKey derives the NIP-04 shared secret from ECDH (x-coordinate only).
  10  func SharedKey(seckey, pubkey [32]byte) ([32]byte, bool) {
  11  	shared, ok := schnorr.ECDH(seckey[:], pubkey[:])
  12  	if !ok {
  13  		return [32]byte{}, false
  14  	}
  15  	var out [32]byte
  16  	copy(out[:], shared)
  17  	return out, true
  18  }
  19  
  20  // Encrypt encrypts plaintext using NIP-04 (AES-256-CBC).
  21  // sharedKey: 32 bytes from SharedKey.
  22  // Calls fn with the encoded string "base64(ciphertext)?iv=base64(iv)".
  23  func Encrypt(sharedKey [32]byte, plaintext string, fn func(string)) {
  24  	iv := []byte{:16}
  25  	subtle.RandomBytes(iv)
  26  	subtle.AESCBCEncrypt(sharedKey[:], iv, []byte(plaintext), func(ct []byte) {
  27  		fn(helpers.Base64Encode(ct) + "?iv=" + helpers.Base64Encode(iv))
  28  	})
  29  }
  30  
  31  // Decrypt decrypts a NIP-04 message "base64(ciphertext)?iv=base64(iv)".
  32  // Calls fn with the plaintext.
  33  func Decrypt(sharedKey [32]byte, message string, fn func(string)) {
  34  	// Split on "?iv=".
  35  	ivIdx := -1
  36  	for i := 0; i < len(message)-3; i++ {
  37  		if message[i] == '?' && message[i+1] == 'i' && message[i+2] == 'v' && message[i+3] == '=' {
  38  			ivIdx = i
  39  			break
  40  		}
  41  	}
  42  	if ivIdx < 0 {
  43  		fn("")
  44  		return
  45  	}
  46  	ct := helpers.Base64Decode(message[:ivIdx])
  47  	iv := helpers.Base64Decode(message[ivIdx+4:])
  48  	if ct == nil || iv == nil {
  49  		fn("")
  50  		return
  51  	}
  52  	subtle.AESCBCDecrypt(sharedKey[:], iv, ct, func(pt []byte) {
  53  		fn(string(pt))
  54  	})
  55  }
  56