UNPKG

ts-mls

Version:

[![CI](https://github.com/LukaJCB/ts-mls/actions/workflows/ci.yml/badge.svg)](https://github.com/LukaJCB/ts-mls/actions/workflows/ci.yml) [![npm version](https://badge.fury.io/js/ts-mls.svg)](https://badge.fury.io/js/ts-mls) [![Coverage Status](https://co

100 lines 5.65 kB
import { getCiphersuiteFromId, getCiphersuiteImpl, getCiphersuiteNameFromId, } from "../../src/crypto/ciphersuite"; import { decodeRatchetTree, getHpkePublicKey } from "../../src/ratchetTree"; import { hexToBytes } from "@noble/ciphers/utils"; import json from "../../test_vectors/treekem.json"; import { applyUpdatePath, createUpdatePath, decodeUpdatePath } from "../../src/updatePath"; import { treeHashRoot } from "../../src/treeHash"; import { deriveSecret } from "../../src/crypto/kdf"; import { leafToNodeIndex } from "../../src/treemath"; import { applyUpdatePathSecret } from "../../src/createCommit"; import { getCommitSecret } from "../../src/pathSecrets"; import { toPrivateKeyPath } from "../../src/privateKeyPath"; import { hpkeKeysMatch } from "../crypto/keyMatch"; for (const [index, x] of json.entries()) { test(`treekem test vectors ${index}`, async () => { const impl = await getCiphersuiteImpl(getCiphersuiteFromId(x.cipher_suite)); await treekemTest(x, impl); }, 10000); } async function treekemTest(data, impl) { const tree = decodeRatchetTree(hexToBytes(data.ratchet_tree), 0); if (tree === undefined) throw new Error("could not decode tree"); const th = await treeHashRoot(tree[0], impl.hash); const gc = { version: "mls10", cipherSuite: getCiphersuiteNameFromId(data.cipher_suite), groupId: hexToBytes(data.group_id), epoch: BigInt(data.epoch), treeHash: th, confirmedTranscriptHash: hexToBytes(data.confirmed_transcript_hash), extensions: [], }; const privatePaths = await getPrivatePaths(data, impl); await testTreeKeys(data, tree, impl); for (const path of data.update_paths) { const updatePath = decodeUpdatePath(hexToBytes(path.update_path), 0); if (updatePath === undefined) throw new Error("could not decode updatepath"); const updatedTree = await applyUpdatePath(tree[0], path.sender, updatePath[0], impl.hash); const th = await treeHashRoot(updatedTree, impl.hash); expect(th).toStrictEqual(hexToBytes(path.tree_hash_after)); const updatedGroupContext = { ...gc, treeHash: th }; const senderLeafState = data.leaves_private.find((lp) => lp.index === path.sender); if (senderLeafState === undefined) { throw new Error("Could not find leaf for sender"); } const [t, newUpdatePath, newSecrets] = await createUpdatePath(updatedTree, path.sender, updatedGroupContext, hexToBytes(senderLeafState.signature_priv), impl); const rootSecret = newSecrets.slice().pop(); const newCommitSecret = await deriveSecret(rootSecret.secret, "path", impl.kdf); const newGroupContext = { ...gc, treeHash: await treeHashRoot(t, impl.hash), epoch: gc.epoch + 1n }; for (const pp of privatePaths) { if (pp.leafIndex === path.sender) { expect(path.path_secrets[pp.leafIndex]).toBeNull; } else { await testCommitSecret(tree, pp, path, updatedGroupContext, updatePath[0], impl); await testNewUpdatePath(tree, pp, path, newGroupContext, newUpdatePath, impl, newCommitSecret); } } } } async function testNewUpdatePath(tree, pp, path, newGroupContext, newUpdatePath, impl, newCommitSecret) { const secret = await applyUpdatePathSecret(tree[0], pp, path.sender, newGroupContext, newUpdatePath, [], impl); const commitSecret = await getCommitSecret(tree[0], secret.nodeIndex, secret.pathSecret, impl.kdf); expect(commitSecret).toStrictEqual(newCommitSecret); } async function testCommitSecret(tree, pp, path, updatedGroupContext, updatePath, impl) { const privateP = await applyUpdatePathSecret(tree[0], pp, path.sender, updatedGroupContext, updatePath, [], impl); expect(privateP.pathSecret).toStrictEqual(hexToBytes(path.path_secrets[pp.leafIndex])); const commitSecret = await getCommitSecret(tree[0], privateP.nodeIndex, privateP.pathSecret, impl.kdf); expect(commitSecret).toStrictEqual(hexToBytes(path.commit_secret)); } async function getPrivatePaths(data, impl) { return await Promise.all(data.leaves_private.map(async (leaf) => { const nodeSecrets = leaf.path_secrets.reduce((acc, ps) => ({ ...acc, [ps.node]: hexToBytes(ps.path_secret) }), {}); const pks = await toPrivateKeyPath(nodeSecrets, leaf.index, impl); return { ...pks, privateKeys: { ...pks.privateKeys, [leafToNodeIndex(leaf.index)]: hexToBytes(leaf.encryption_priv) }, }; })); } async function testTreeKeys(data, tree, impl) { for (const leaf of data.leaves_private) { const nodeSecrets = leaf.path_secrets.reduce((acc, ps) => ({ ...acc, [ps.node]: hexToBytes(ps.path_secret) }), {}); const node = tree[0][leafToNodeIndex(leaf.index)]; if (node === undefined || node.nodeType === "parent") throw new Error("No leaf found at leaf index"); expect(await hpkeKeysMatch(node.leaf.hpkePublicKey, hexToBytes(leaf.encryption_priv), impl.hpke)).toBe(true); for (const [nodeIndex, pathSecret] of Object.entries(nodeSecrets)) { const s = await deriveSecret(pathSecret, "node", impl.kdf); const { publicKey } = await impl.hpke.deriveKeyPair(s); const node = tree[0][Number(nodeIndex)]; if (node === undefined) throw new Error("No node found at node index"); expect(getHpkePublicKey(node)).toStrictEqual(await impl.hpke.exportPublicKey(publicKey)); } } } //# sourceMappingURL=treekem.test.js.map