clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/tinyagent
tinyrelay / scripts / blossom-encryption.test.mjs
blossom-encryption.test.mjs 3.1K
1import assert from "node:assert/strict";2import {webcrypto} from "node:crypto";3import {readFile} from "node:fs/promises";4import test from "node:test";5import vm from "node:vm";67const source = await readFile(new URL("../tinyclient/blossom-encryption.js", import.meta.url), "utf8");8const shared = await readFile(new URL("../tinyclient/tiny.js", import.meta.url), "utf8");9const sandbox = {crypto: webcrypto, TextEncoder, URL, URLSearchParams, Uint8Array, ArrayBuffer};10sandbox.globalThis = sandbox;11vm.runInNewContext(shared + source, sandbox);12const api = sandbox.tiny.blossom.encryption;13const utf8 = value => new TextEncoder().encode(value);14const hex = bytes => Buffer.from(bytes).toString("hex");1516test("matches BUD-15 empty plaintext vector", async () => {17 const result = await api.encryptCHK(new Uint8Array());18 assert.equal(result.key, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");19 assert.equal(hex(result.ciphertext), "7cd161ae8406d82cdf553c1100d012db");20 assert.equal(result.hash, "346c46e7cc6722c99efe7f7bc316d8f3ff5f025f1031bf94418ef4db891e04cd");21 assert.deepEqual(await api.decryptCHK(result.ciphertext, result.key, result.hash), new Uint8Array());22});2324test("matches BUD-15 hello vector", async () => {25 const result = await api.encryptCHK(utf8("hello"));26 assert.equal(result.key, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824");27 assert.equal(hex(result.ciphertext), "c65308d9c8649ff1c59820d0b3a030db34ad00f92d");28 assert.equal(result.hash, "70b977414934faa6270f323f117d05dbcc412e9d7ba2354b4d0f88f60aad2461");29 assert.deepEqual(await api.decryptCHK(result.ciphertext, result.key, result.hash), utf8("hello"));30});3132test("rejects tampered ciphertext, hash, and key", async () => {33 const result = await api.encryptCHK(utf8("private"));34 const tampered = new Uint8Array(result.ciphertext);35 tampered[0] ^= 1;36 await assert.rejects(api.decryptCHK(tampered, result.key, result.hash), /ciphertext hash mismatch/);37 await assert.rejects(api.decryptCHK(result.ciphertext, result.key, "0".repeat(64)), /ciphertext hash mismatch/);38 const wrongKey = "f".repeat(64);39 await assert.rejects(api.decryptCHK(result.ciphertext, wrongKey, result.hash), /CHK decryption failed|plaintext hash mismatch/);40});4142test("creates and parses a CHK URI without altering the key", async () => {43 const result = await api.encryptCHK(utf8("hello"));44 const uri = api.createURI(result.hash, "txt", result.key);45 assert.equal(uri, "blossom:" + result.hash + ".txt?enc=chk-v1&k=" + result.key);46 const parsed = api.parseURI(uri);47 assert.equal(parsed.hash, result.hash);48 assert.equal(parsed.extension, "txt");49 assert.equal(parsed.key, result.key);50 assert.throws(() => api.parseURI("blossom:" + result.hash + "?enc=chk-v1&k=" + result.key.toUpperCase()), /lowercase hex key/);51});5253test("derives a distinct key and zero-nonce ciphertext for each content", async () => {54 const first = await api.encryptCHK(utf8("first"));55 const second = await api.encryptCHK(utf8("second"));56 assert.notEqual(first.key, second.key);57 assert.notEqual(first.hash, second.hash);58 assert.notDeepEqual(first.ciphertext, second.ciphertext);59});