NAME

Net::Nostr::Encryption - NIP-44 versioned encrypted payloads

SYNOPSIS

use Net::Nostr::Encryption;
use Net::Nostr::Key;

my $alice = Net::Nostr::Key->new;
my $bob   = Net::Nostr::Key->new;

# Calculate shared conversation key
my $conv_key = Net::Nostr::Encryption->get_conversation_key(
    $alice->privkey_hex, $bob->pubkey_hex,
);

# Encrypt a message
my $payload = Net::Nostr::Encryption->encrypt('Hello, Bob!', $conv_key);

# Bob decrypts using the same conversation key
my $conv_key2 = Net::Nostr::Encryption->get_conversation_key(
    $bob->privkey_hex, $alice->pubkey_hex,
);
my $plaintext = Net::Nostr::Encryption->decrypt($payload, $conv_key2);
# $plaintext is 'Hello, Bob!'

DESCRIPTION

Implements NIP-44 version 2 encrypted payloads using secp256k1 ECDH, HKDF-SHA256, ChaCha20, and HMAC-SHA256. This module provides the encryption primitives - it does not define any event kinds.

The encryption is symmetric: get_conversation_key(a_priv, B_pub) produces the same key as get_conversation_key(b_priv, A_pub).

METHODS

get_conversation_key

my $key = Net::Nostr::Encryption->get_conversation_key($privkey_hex, $pubkey_hex);

Computes the shared conversation key between two users via ECDH and HKDF-extract. Both keys are 64-character hex strings. The private key is a secp256k1 scalar, the public key is a 32-byte x-only coordinate. Returns 32 raw bytes.

my $conv = Net::Nostr::Encryption->get_conversation_key(
    $my_key->privkey_hex, $their_pubkey_hex,
);

get_message_keys

my ($chacha_key, $chacha_nonce, $hmac_key) =
    Net::Nostr::Encryption->get_message_keys($conversation_key, $nonce);

Derives per-message keys from a conversation key and nonce using HKDF-expand. Both arguments are 32 raw bytes. Returns three raw byte strings: ChaCha20 key (32 bytes), ChaCha20 nonce (12 bytes), and HMAC key (32 bytes).

calc_padded_len

my $padded = Net::Nostr::Encryption->calc_padded_len($unpadded_len);

Calculates the padded length for a given plaintext length. The padding scheme uses power-of-two-based chunking with a minimum padded size of 32.

Net::Nostr::Encryption->calc_padded_len(1);    # 32
Net::Nostr::Encryption->calc_padded_len(32);   # 32
Net::Nostr::Encryption->calc_padded_len(33);   # 64
Net::Nostr::Encryption->calc_padded_len(257);  # 320

encrypt

my $payload = Net::Nostr::Encryption->encrypt($plaintext, $conversation_key);
my $payload = Net::Nostr::Encryption->encrypt($plaintext, $conversation_key, $nonce);

Encrypts a plaintext string using the NIP-44 v2 scheme. The conversation key is 32 raw bytes (from get_conversation_key). An optional 32-byte nonce can be provided for deterministic encryption (useful for testing); otherwise a cryptographically random nonce is generated.

Returns a base64-encoded payload string. The plaintext is UTF-8 encoded before encryption and UTF-8 decoded after decryption. Croaks if the plaintext is empty or exceeds 65535 bytes (after UTF-8 encoding).

my $payload = Net::Nostr::Encryption->encrypt('secret message', $conv_key);

decrypt

my $plaintext = Net::Nostr::Encryption->decrypt($payload, $conversation_key);

Decrypts a NIP-44 payload. The payload is the base64 string from encrypt. Croaks on invalid version, bad MAC, invalid padding, or malformed payload.

my $msg = Net::Nostr::Encryption->decrypt($payload, $conv_key);

SEE ALSO

Net::Nostr, Net::Nostr::Key