import sodium from "sodium-native"; /** * Derive a shared secret from our X25519 secret key and peer's X25519 public key. * Uses crypto_scalarmult (raw X25519 ECDH) then hashes with crypto_generichash * to produce a 32-byte symmetric key. */ export function deriveSharedKey( ourEncSec: Buffer, peerEncPub: Buffer, ): Buffer { const raw = Buffer.alloc(sodium.crypto_scalarmult_BYTES); sodium.crypto_scalarmult(raw, ourEncSec, peerEncPub); // Hash the raw shared secret to get a uniform key const sharedKey = Buffer.alloc(32); sodium.crypto_generichash(sharedKey, raw); return sharedKey; } /** * Encrypt plaintext using XChaCha20-Poly1305 with the shared key. * Returns { nonce, ciphertext } where both are Buffers. */ export function encrypt( plaintext: Buffer, sharedKey: Buffer, ): { nonce: Buffer; ciphertext: Buffer } { const nonce = Buffer.alloc(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); sodium.randombytes_buf(nonce); const ciphertext = Buffer.alloc( plaintext.length + sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES, ); sodium.crypto_aead_xchacha20poly1305_ietf_encrypt( ciphertext, plaintext, null, // no additional data null, // unused nsec nonce, sharedKey, ); return { nonce, ciphertext }; } /** * Decrypt ciphertext using XChaCha20-Poly1305 with the shared key. * Returns the plaintext Buffer, or throws on authentication failure. */ export function decrypt( ciphertext: Buffer, nonce: Buffer, sharedKey: Buffer, ): Buffer { const plaintext = Buffer.alloc( ciphertext.length - sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES, ); sodium.crypto_aead_xchacha20poly1305_ietf_decrypt( plaintext, null, // unused nsec ciphertext, null, // no additional data nonce, sharedKey, ); return plaintext; }