ts-mls
Version:
[](https://github.com/LukaJCB/ts-mls/actions/workflows/ci.yml) [](https://badge.fury.io/js/ts-mls) [ {
const tree = new Array(nodeWidth(leafWidth));
const rootIndex = root(leafWidth);
const parentInhabited = updateArray(tree, rootIndex, encryptionSecret);
return deriveChildren(parentInhabited, rootIndex, kdf);
}
export async function createSecretTree(leafWidth, encryptionSecret, kdf) {
const tree = await scaffoldSecretTree(leafWidth, encryptionSecret, kdf);
return await Promise.all(tree.map(async (secret) => {
const application = await createRatchetRoot(secret, "application", kdf);
const handshake = await createRatchetRoot(secret, "handshake", kdf);
return { handshake, application };
}));
}
async function deriveChildren(tree, nodeIndex, kdf) {
if (isLeaf(nodeIndex))
return tree;
const l = left(nodeIndex);
const r = right(nodeIndex);
const parentSecret = tree[nodeIndex];
if (parentSecret === undefined)
throw new InternalError("Bad node index for secret tree");
const leftSecret = await expandWithLabel(parentSecret, "tree", new TextEncoder().encode("left"), kdf.size, kdf);
const rightSecret = await expandWithLabel(parentSecret, "tree", new TextEncoder().encode("right"), kdf.size, kdf);
const currentTree = updateArray(updateArray(tree, l, leftSecret), r, rightSecret);
return deriveChildren(await deriveChildren(currentTree, l, kdf), r, kdf);
}
export async function deriveNonce(secret, generation, cs) {
return await deriveTreeSecret(secret, "nonce", generation, cs.hpke.nonceLength, cs.kdf);
}
export async function deriveKey(secret, generation, cs) {
return await deriveTreeSecret(secret, "key", generation, cs.hpke.keyLength, cs.kdf);
}
export async function ratchetUntil(current, desiredGen, config, kdf) {
const generationDifference = desiredGen - current.generation;
if (generationDifference > config.maximumForwardRatchetSteps)
throw new ValidationError("Desired generation too far in the future");
return await repeatAsync(async (s) => {
const nextSecret = await deriveTreeSecret(s.secret, "secret", s.generation, kdf.size, kdf);
return {
secret: nextSecret,
generation: s.generation + 1,
unusedGenerations: updateUnusedGenerations(s, config.retainKeysForGenerations),
};
}, current, generationDifference);
}
function updateUnusedGenerations(s, retainGenerationsMax) {
const withNew = { ...s.unusedGenerations, [s.generation]: s.secret };
const generations = Object.keys(withNew);
const result = generations.length >= retainGenerationsMax ? removeOldGenerations(withNew, retainGenerationsMax) : withNew;
return result;
}
function removeOldGenerations(historicalReceiverData, max) {
const sortedGenerations = Object.keys(historicalReceiverData)
.map(Number)
.sort((a, b) => (a < b ? -1 : 1));
return Object.fromEntries(sortedGenerations.slice(-max).map((generation) => [generation, historicalReceiverData[generation]]));
}
export async function derivePrivateMessageNonce(secret, generation, reuseGuard, cs) {
const nonce = await deriveNonce(secret, generation, cs);
if (nonce.length >= 4 && reuseGuard.length >= 4) {
for (let i = 0; i < 4; i++) {
nonce[i] ^= reuseGuard[i];
}
}
else
throw new ValidationError("Reuse guard or nonce incorrect length");
return nonce;
}
export async function ratchetToGeneration(tree, senderData, contentType, config, cs) {
const index = leafToNodeIndex(toLeafIndex(senderData.leafIndex));
const node = tree[index];
if (node === undefined)
throw new InternalError("Bad node index for secret tree");
const ratchet = ratchetForContentType(node, contentType);
if (ratchet.generation > senderData.generation) {
const desired = ratchet.unusedGenerations[senderData.generation];
if (desired !== undefined) {
const { [senderData.generation]: _, ...removedDesiredGen } = ratchet.unusedGenerations;
const ratchetState = { ...ratchet, unusedGenerations: removedDesiredGen };
return await createRatchetResultWithSecret(node, index, desired, senderData.generation, senderData.reuseGuard, tree, contentType, cs, ratchetState);
}
throw new ValidationError("Desired gen in the past");
}
const currentSecret = await ratchetUntil(ratchetForContentType(node, contentType), senderData.generation, config, cs.kdf);
return createRatchetResult(node, index, currentSecret, senderData.reuseGuard, tree, contentType, cs);
}
export async function consumeRatchet(tree, index, contentType, cs) {
const node = tree[index];
if (node === undefined)
throw new InternalError("Bad node index for secret tree");
const currentSecret = ratchetForContentType(node, contentType);
const reuseGuard = cs.rng.randomBytes(4);
return createRatchetResult(node, index, currentSecret, reuseGuard, tree, contentType, cs);
}
async function createRatchetResult(node, index, currentSecret, reuseGuard, tree, contentType, cs) {
const nextSecret = await deriveTreeSecret(currentSecret.secret, "secret", currentSecret.generation, cs.kdf.size, cs.kdf);
const ratchetState = { ...currentSecret, secret: nextSecret, generation: currentSecret.generation + 1 };
return await createRatchetResultWithSecret(node, index, currentSecret.secret, currentSecret.generation, reuseGuard, tree, contentType, cs, ratchetState);
}
async function createRatchetResultWithSecret(node, index, secret, generation, reuseGuard, tree, contentType, cs, ratchetState) {
const { nonce, key } = await createKeyAndNonce(secret, generation, reuseGuard, cs);
const newNode = contentType === "application" ? { ...node, application: ratchetState } : { ...node, handshake: ratchetState };
const newTree = updateArray(tree, index, newNode);
return {
generation: generation,
reuseGuard,
nonce,
key,
newTree,
};
}
async function createKeyAndNonce(secret, generation, reuseGuard, cs) {
const key = await deriveKey(secret, generation, cs);
const nonce = await derivePrivateMessageNonce(secret, generation, reuseGuard, cs);
return { nonce, key };
}
function ratchetForContentType(node, contentType) {
switch (contentType) {
case "application":
return node.application;
case "proposal":
return node.handshake;
case "commit":
return node.handshake;
}
}
async function createRatchetRoot(node, label, kdf) {
const secret = await expandWithLabel(node, label, new Uint8Array(), kdf.size, kdf);
return { secret: secret, generation: 0, unusedGenerations: {} };
}
//# sourceMappingURL=secretTree.js.map