Encrypted Payloads (Versioned)
NIP-44 provides secure end-to-end encryption for Nostr messages using modern cryptographic primitives. It addresses the security weaknesses of NIP-04 and introduces versioning for future upgrades.
NIP-44: Encrypted Payloads (Versioned)
Status: Final Author: paulmillr Category: Encryption
Overview
NIP-44 is the modern encryption standard for Nostr, designed to replace the original NIP-04 encrypted direct messages. It provides:
- Stronger encryption using ChaCha20-Poly1305
- Proper key derivation with HKDF
- Padding to hide message length
- Version field for future upgrades
- Authentication preventing message tampering
If you’re building a new Nostr application, use NIP-44 instead of NIP-04 for all encrypted content.
Why NIP-44 Matters
Problems with NIP-04
NIP-04 had several security weaknesses:
- No message padding - Message length was visible
- Weak nonce generation - Using AES-CBC with random IVs
- No authentication - Messages could be modified
- No versioning - Impossible to upgrade encryption
NIP-44 Improvements
- ChaCha20-Poly1305 - Modern authenticated encryption
- HKDF key derivation - Cryptographically sound key generation
- Message padding - Hides exact message length
- Version byte - Allows future algorithm upgrades
- Conversation keys - Efficient multi-message encryption
How It Works
Encryption Flow
- Generate conversation key from sender private key and recipient public key using ECDH + HKDF
- Generate nonce - 32 bytes of random data
- Derive message keys using HKDF with nonce
- Pad message to hide length
- Encrypt with ChaCha20-Poly1305
- Concatenate version + nonce + ciphertext
Encrypted Payload Structure
version (1 byte) | nonce (32 bytes) | ciphertext (variable) | auth tag (16 bytes)
Current version is 0x02.
Key Derivation
// Generate shared secret using ECDH
const sharedX = getSharedSecret(senderPrivKey, recipientPubKey).x;
// Derive conversation key using HKDF
const conversationKey = hkdf(sha256, sharedX, salt, info, 32);
Example Implementation
Encrypting a Message
import { nip44 } from 'nostr-tools';
const senderPrivateKey = '...'; // Your nsec (hex)
const recipientPublicKey = '...'; // Their npub (hex)
const message = "Hello, this is a secret message!";
// Encrypt the message
const ciphertext = nip44.encrypt(senderPrivateKey, recipientPublicKey, message);
// Result is base64-encoded
console.log(ciphertext);
Decrypting a Message
const recipientPrivateKey = '...'; // Your nsec (hex)
const senderPublicKey = '...'; // Their npub (hex)
// Decrypt the message
const plaintext = nip44.decrypt(recipientPrivateKey, senderPublicKey, ciphertext);
console.log(plaintext); // "Hello, this is a secret message!"
Event Format
Encrypted content using NIP-44 is typically stored in events with:
- kind: 4 (for DMs) or other encrypted event kinds
- content: Base64-encoded NIP-44 payload
- tags: [“p”, recipientPubkey] for DMs
{
"kind": 4,
"pubkey": "sender_pubkey",
"tags": [["p", "recipient_pubkey"]],
"content": "AhZIThqMbm..."
}
Security Properties
What NIP-44 Protects
- Confidentiality - Only sender and recipient can read
- Integrity - Tampering is detected
- Authenticity - Message provably from sender
- Length hiding - Padding obscures message size
What NIP-44 Does NOT Protect
- Metadata - Who’s messaging whom is visible
- Timing - When messages are sent is visible
- Forward secrecy - Compromised key reveals past messages
For stronger privacy, consider additional layers like Tor for network anonymity.
Migrating from NIP-04
For Developers
- Check version byte - NIP-44 starts with
0x02 - Support both - Decrypt NIP-04 for old messages
- Encrypt with NIP-44 - Use NIP-44 for new messages
- Update UI - Show encryption strength indicators
For Users
- Old NIP-04 messages remain readable
- New clients will automatically use NIP-44
- No action required from users
Client Support
NIP-44 is supported by modern clients:
| Client | NIP-44 Support |
|---|---|
| Damus | Full |
| Amethyst | Full |
| Primal | Full |
| Snort | Full |
| Coracle | Full |
| noStrudel | Full |
| 0xchat | Full |
Related NIPs
- NIP-04 - Original encrypted DMs (deprecated)
- NIP-17 - Private direct messages (gift wrapping)
- NIP-46 - Nostr Connect (remote signing)
- NIP-59 - Gift Wrap (metadata hiding)
Technical Details
Cryptographic Primitives
- ECDH - secp256k1 key exchange
- HKDF-SHA256 - Key derivation
- ChaCha20-Poly1305 - Authenticated encryption
- XChaCha20 - Extended nonce variant
Padding Scheme
Messages are padded to the next power of 2 (minimum 32 bytes) to hide exact length. This prevents length analysis attacks.
Version History
| Version | Description |
|---|---|
| 0x02 | Current - ChaCha20-Poly1305 |
| 0x01 | Reserved |
| 0x00 | Reserved |
For Developers
Libraries
- nostr-tools (JavaScript) -
nip44.encrypt()/nip44.decrypt() - rust-nostr (Rust) - Full NIP-44 support
- python-nostr (Python) - NIP-44 implementation
Testing
The NIP-44 specification includes test vectors for validating implementations. Ensure your implementation passes all test cases.
Summary
NIP-44 is the recommended encryption standard for Nostr:
- Use NIP-44 for all new encrypted content
- Support NIP-04 for backward compatibility
- Modern cryptography ensures strong security
- Versioning allows future improvements
Last updated: January 2026 Official specification: GitHub
Client Support
This NIP is supported by the following clients: