ratchet-pair — dm-ratchet.js
The pairwise confidentiality plugin: a per-message symmetric ratchet for 1:1 threads. An
ECDH-derived per-pair epoch secret seeds a per-sender HKDF ratchet, so every message uses a
fresh key. Suite: enc-xchacha-v1 (XChaCha20-Poly1305, 24-byte nonce). Used by
dm and super. Normative spec:
ratchet-pair.
import {
ratchetMessageKey, dmRatchetEncrypt, dmRatchetDecrypt,
} from '@enc-protocol/core/dm-ratchet.js'Ratchet API
ratchetMessageKey(epochSecret, senderSeq)
ratchetMessageKey(epochSecret: Uint8Array, senderSeq: number) → Uint8Array(32)Derives the message key for a sequence number by chaining HKDF advances from the epoch secret.
Deterministic — the same (epochSecret, senderSeq) always yields the same key.
dmRatchetEncrypt(epochSecret, plaintext, senderSeq, epochN)
dmRatchetEncrypt(epochSecret, plaintext: string, senderSeq: number, epochN: number)
→ { epoch: number, sender_seq: number, ciphertext: string }Encrypts plaintext under the message key for senderSeq, tagging the result with the epoch
number. ciphertext is the base64 XChaCha20-Poly1305 output.
dmRatchetDecrypt(epochSecret, ciphertextB64, senderSeq)
dmRatchetDecrypt(epochSecret, ciphertextB64: string, senderSeq: number) → stringRe-derives the message key for senderSeq and decrypts.
NIP-44 v2 interop
dm-ratchet.js also ships a Nostr NIP-44 v2 code path for bridging to Nostr DMs:
nip44Encrypt(privHex, peerPubHex, plaintext) → string // base64 NIP-44 payload
nip44Decrypt(privHex, peerPubHex, payload) → stringThese are built from the lower-level primitives nip44ConversationKey(privHex, pubHex),
nip44MessageKeys(ck, nonce), nip44Pad / nip44Unpad, nip44HmacAad, and
nip44EncryptWithKey / nip44DecryptWithKey.
Example
import { dmRatchetEncrypt, dmRatchetDecrypt } from '@enc-protocol/core/dm-ratchet.js'
// both sides hold the same 32-byte per-pair epoch secret (ECDH-derived)
const env = dmRatchetEncrypt(epochSecret, 'hello', 0, 0)
// → { epoch: 0, sender_seq: 0, ciphertext: '…base64…' }
const plain = dmRatchetDecrypt(epochSecret, env.ciphertext, env.sender_seq)
console.log(plain) // 'hello'Or the NIP-44 v2 path, for Nostr interop:
import { nip44Encrypt, nip44Decrypt } from '@enc-protocol/core/dm-ratchet.js'
const payload = nip44Encrypt(alicePrivHex, bobPubHex, 'hi bob')
const msg = nip44Decrypt(bobPrivHex, alicePubHex, payload) // 'hi bob'See also
- Normative: ratchet-pair
- Plugin SDKs ·
@enc-protocol/core