Skip to content

Instantly share code, notes, and snippets.

@sondreb
Last active March 23, 2026 17:59
Show Gist options
  • Select an option

  • Save sondreb/d7ac64573554f3773fb586a21109775c to your computer and use it in GitHub Desktop.

Select an option

Save sondreb/d7ac64573554f3773fb586a21109775c to your computer and use it in GitHub Desktop.
Data Encryption
import { bytesToHex } from '@noble/ciphers/utils';
import { randomBytes } from '@noble/ciphers/webcrypto';
import { secp256k1 } from '@noble/curves/secp256k1';
import { xchacha20poly1305 } from '@noble/ciphers/chacha';
import { hkdf } from '@noble/hashes/hkdf';
import { sha256 } from '@noble/hashes/sha2';
import { concatBytes } from '@noble/hashes/utils';
const HKDF_INFO = 'ecies-xchacha20poly1305';
const NONCE_LENGTH = 24;
const COMPRESSED_PUBLIC_KEY_LENGTH = 33;
function deriveKey(privateKey, publicKey) {
const sharedPoint = secp256k1.getSharedSecret(privateKey, publicKey);
// Extract x-coordinate only (skip the 0x02/0x03/0x04 prefix byte), then
// run through HKDF to produce a uniformly random 256-bit encryption key.
return hkdf(sha256, sharedPoint.slice(1, 33), /*salt=*/ undefined, HKDF_INFO, 32);
}
function encrypt(receiverPublicKey, plaintext) {
const ephemeralPrivateKey = secp256k1.utils.randomSecretKey();
const ephemeralPublicKey = secp256k1.getPublicKey(ephemeralPrivateKey, true);
const key = deriveKey(ephemeralPrivateKey, receiverPublicKey);
const nonce = randomBytes(NONCE_LENGTH);
const ciphertext = xchacha20poly1305(key, nonce).encrypt(
new TextEncoder().encode(plaintext)
);
// Wire format: ephemeralPublicKey (33) || nonce (24) || ciphertext (variable)
return concatBytes(ephemeralPublicKey, nonce, ciphertext);
}
function decrypt(privateKey, payload) {
const ephemeralPublicKey = payload.slice(0, COMPRESSED_PUBLIC_KEY_LENGTH);
const nonce = payload.slice(COMPRESSED_PUBLIC_KEY_LENGTH, COMPRESSED_PUBLIC_KEY_LENGTH + NONCE_LENGTH);
const ciphertext = payload.slice(COMPRESSED_PUBLIC_KEY_LENGTH + NONCE_LENGTH);
const key = deriveKey(privateKey, ephemeralPublicKey);
return new TextDecoder().decode(
xchacha20poly1305(key, nonce).decrypt(ciphertext)
);
}
// --- Demo ---
const privateKey = secp256k1.utils.randomSecretKey();
const publicKey = secp256k1.getPublicKey(privateKey, true);
console.log('Private Key:', bytesToHex(privateKey));
console.log('Public Key:', bytesToHex(publicKey));
const plaintext = 'Hello, XChaCha20-Poly1305!';
console.log('Plaintext:', plaintext);
const encrypted = encrypt(publicKey, plaintext);
console.log('Encrypted:', bytesToHex(encrypted));
const decrypted = decrypt(privateKey, encrypted);
console.log('Decrypted:', decrypted);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment