ts-mls
Version:
[](https://github.com/LukaJCB/ts-mls/actions/workflows/ci.yml) [](https://badge.fury.io/js/ts-mls) [) {
test(`Large Group, Full Lifecycle ${cs}`, async () => {
await largeGroupFullLifecycle(cs, 5, 8);
}, 60000);
}
async function largeGroupFullLifecycle(cipherSuite, initialSize, targetSize) {
const impl = await getCiphersuiteImpl(getCiphersuiteFromName(cipherSuite));
const groupId = new TextEncoder().encode("dynamic-group");
const makeCredential = (name) => ({
credentialType: "basic",
identity: new TextEncoder().encode(name),
});
const memberStates = [];
const initialCreatorName = "member-0";
const creatorCred = makeCredential(initialCreatorName);
const creatorKP = await generateKeyPackage(creatorCred, defaultCapabilities(), defaultLifetime, [], impl);
const creatorGroup = await createGroup(groupId, creatorKP.publicPackage, creatorKP.privatePackage, [], impl);
memberStates.push({
id: initialCreatorName,
state: creatorGroup,
public: creatorKP.publicPackage,
private: creatorKP.privatePackage,
});
// Add first M members
for (let i = 1; i < initialSize; i++) {
await addMember(memberStates, i, impl);
}
for (const index of shuffledIndices(memberStates)) {
await update(memberStates, index, impl);
}
// Until group size is N
for (let i = memberStates.length; i < targetSize; i++) {
const adderIndex = randomInt(memberStates.length);
await addMember(memberStates, i, impl, adderIndex);
}
await testEveryoneCanMessageEveryone(memberStates.map((ms) => ms.state), impl);
const shuffled = shuffledIndices(memberStates);
for (const index of shuffled) {
await update(memberStates, index, impl);
}
await testEveryoneCanMessageEveryone(memberStates.map((ms) => ms.state), impl);
// While group size > 1, randomly remove someone
while (memberStates.length > 1) {
const removerIndex = randomInt(memberStates.length);
let removedIndex = randomInt(memberStates.length);
while (removedIndex === removerIndex) {
removedIndex = randomInt(memberStates.length);
}
const remover = memberStates[removerIndex];
const removed = memberStates[removedIndex];
const removeProposal = {
proposalType: "remove",
remove: {
removed: removed.state.privatePath.leafIndex,
},
};
const commitResult = await createCommit(remover.state, emptyPskIndex, false, [removeProposal], impl);
if (commitResult.commit.wireformat !== "mls_private_message")
throw new Error("Expected private message");
remover.state = commitResult.newState;
// Apply the commit to all members (except removed and remover)
for (let i = 0; i < memberStates.length; i++) {
if (i === removerIndex)
continue;
const m = memberStates[i];
const result = await processPrivateMessage(m.state, commitResult.commit.privateMessage, makePskIndex(m.state, {}), impl);
m.state = result.newState;
}
// Remove the member from the group
memberStates.splice(removedIndex, 1);
await testEveryoneCanMessageEveryone(memberStates.map((ms) => ms.state), impl);
}
}
async function addMember(memberStates, index, impl, adderIndex = 0) {
const newName = `member-${index}`;
const newCred = {
credentialType: "basic",
identity: new TextEncoder().encode(newName),
};
const newKP = await generateKeyPackage(newCred, defaultCapabilities(), defaultLifetime, [], impl);
const adder = memberStates[adderIndex];
const addProposal = {
proposalType: "add",
add: { keyPackage: newKP.publicPackage },
};
const commitResult = await createCommit(adder.state, emptyPskIndex, false, [addProposal], impl);
if (commitResult.commit.wireformat !== "mls_private_message")
throw new Error("Expected private message");
adder.state = commitResult.newState;
const newState = await joinGroup(commitResult.welcome, newKP.publicPackage, newKP.privatePackage, emptyPskIndex, impl, adder.state.ratchetTree);
// Update all existing members (excluding adder)
for (let i = 0; i < memberStates.length; i++) {
if (i === adderIndex)
continue;
const m = memberStates[i];
const result = await processPrivateMessage(m.state, commitResult.commit.privateMessage, makePskIndex(m.state, {}), impl);
m.state = result.newState;
}
// Add new member
memberStates.push({ id: newName, state: newState, public: newKP.publicPackage, private: newKP.privatePackage });
}
async function update(memberStates, updateIndex, impl) {
const updater = memberStates[updateIndex];
const emptyCommitResult = await createCommit(updater.state, emptyPskIndex, false, [], impl);
updater.state = emptyCommitResult.newState;
if (emptyCommitResult.commit.wireformat !== "mls_private_message")
throw new Error("Expected private message");
// Update all existing members (including adder)
for (let i = 0; i < memberStates.length; i++) {
if (i === updateIndex)
continue;
const m = memberStates[i];
const result = await processPrivateMessage(m.state, emptyCommitResult.commit.privateMessage, makePskIndex(m.state, {}), impl);
m.state = result.newState;
}
}
//# sourceMappingURL=largeGroupFullLifecycle.test.js.map