mirror of
https://github.com/nbd-wtf/go-nostr.git
synced 2025-03-18 13:53:03 +01:00
94 lines
2.7 KiB
Go
94 lines
2.7 KiB
Go
package nip44
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"fmt"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"golang.org/x/crypto/chacha20"
|
|
)
|
|
|
|
// ComputeSharedSecret returns a shared secret key used to encrypt messages.
|
|
// The private and public keys should be hex encoded.
|
|
// Uses the Diffie-Hellman key exchange (ECDH) (RFC 4753).
|
|
func ComputeSharedSecret(pub string, sk string) ([]byte, error) {
|
|
privKeyBytes, err := hex.DecodeString(sk)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error decoding sender private key: %w", err)
|
|
}
|
|
privKey, _ := btcec.PrivKeyFromBytes(privKeyBytes)
|
|
|
|
// adding 02 to signal that this is a compressed public key (33 bytes)
|
|
pubKeyBytes, err := hex.DecodeString("02" + pub)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error decoding hex string of receiver public key '%s': %w", "02"+pub, err)
|
|
}
|
|
pubKey, err := btcec.ParsePubKey(pubKeyBytes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing receiver public key '%s': %w", "02"+pub, err)
|
|
}
|
|
|
|
sharedSecret := btcec.GenerateSharedSecret(privKey, pubKey)
|
|
hash := sha256.Sum256(sharedSecret)
|
|
return hash[:], nil
|
|
}
|
|
|
|
// Encrypt encrypts message with key using xchacha20.
|
|
// key should be the shared secret generated by ComputeSharedSecret.
|
|
// Returns: base64(1 + nonce + encrypted_bytes).
|
|
func Encrypt(message string, key []byte) (string, error) {
|
|
nonce := make([]byte, 24)
|
|
if _, err := rand.Read(nonce); err != nil {
|
|
return "", fmt.Errorf("error creating nonce: %w", err)
|
|
}
|
|
|
|
return encryptWithNonce(message, key, nonce)
|
|
}
|
|
|
|
func encryptWithNonce(message string, key []byte, nonce []byte) (string, error) {
|
|
cipher, err := chacha20.NewUnauthenticatedCipher(key, nonce)
|
|
if err != nil {
|
|
return "", fmt.Errorf("error creating cipher: %w", err)
|
|
}
|
|
|
|
msg := []byte(message)
|
|
cipher.XORKeyStream(msg, msg)
|
|
|
|
payload := make([]byte, 1+24+len(msg))
|
|
payload[0] = 1
|
|
copy(payload[1:25], nonce)
|
|
copy(payload[25:], msg)
|
|
|
|
return base64.StdEncoding.EncodeToString(payload), nil
|
|
}
|
|
|
|
// Decrypt decrypts a content string using the shared secret key.
|
|
// The inverse operation to message -> Encrypt(message, key).
|
|
func Decrypt(payload string, key []byte) (string, error) {
|
|
data, err := base64.StdEncoding.DecodeString(payload)
|
|
if err != nil {
|
|
return "", fmt.Errorf("invalid base64: %w", err)
|
|
}
|
|
|
|
if data[0] != 1 {
|
|
return "", fmt.Errorf("unknown version: %d", data[0])
|
|
}
|
|
if len(data) <= 25 {
|
|
return "", fmt.Errorf("invalid payload, too small: %d", len(data))
|
|
}
|
|
|
|
nonce := data[1:25]
|
|
msg := data[25:]
|
|
cipher, err := chacha20.NewUnauthenticatedCipher(key, nonce)
|
|
if err != nil {
|
|
return "", fmt.Errorf("error creating cipher: %w", err)
|
|
}
|
|
|
|
cipher.XORKeyStream(msg, msg)
|
|
|
|
return string(msg), nil
|
|
}
|