152 lines
4.4 KiB
JavaScript
152 lines
4.4 KiB
JavaScript
|
class RandomSession {
|
||
|
constructor(range) {
|
||
|
this.range = range;
|
||
|
this.cipherTexts = {};
|
||
|
this.cipherKeys = {};
|
||
|
this.ourKey = CryptoJS.lib.WordArray.random(32).toString();
|
||
|
// 32-bit as JavaScript does funny stuff at 64-bit levels.
|
||
|
this.ourNoise = CryptoJS.lib.WordArray.random(4).toString();
|
||
|
this.finalValue = null;
|
||
|
this.resolvers = [];
|
||
|
}
|
||
|
|
||
|
cipherText() {
|
||
|
return CryptoJS.AES.encrypt(this.ourNoise, this.ourKey).toString();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class Random {
|
||
|
constructor() {
|
||
|
this.locked = false;
|
||
|
this.sessions = {};
|
||
|
}
|
||
|
|
||
|
get(sessionId) {
|
||
|
// Spin until lock frees.
|
||
|
while (this.locked);
|
||
|
|
||
|
if (this.sessions[sessionId].finalValue === null) {
|
||
|
let session = this.sessions[sessionId];
|
||
|
let resolver;
|
||
|
let promise = new Promise((resolve) => {
|
||
|
resolver = resolve;
|
||
|
});
|
||
|
session.resolvers.push(resolver);
|
||
|
return promise;
|
||
|
} else {
|
||
|
return new Promise((resolve) => {
|
||
|
resolve(this.sessions[sessionId].finalValue);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Start a cooperative random session.
|
||
|
*/
|
||
|
coopRandom(n, sessionId) {
|
||
|
let session = new RandomSession(n);
|
||
|
if (sessionId === undefined) {
|
||
|
sessionId = window.crypto.randomUUID();
|
||
|
}
|
||
|
|
||
|
this.sessions[sessionId] = session;
|
||
|
|
||
|
socket.emit("message", {
|
||
|
type: "RANDOM",
|
||
|
author: ID,
|
||
|
session: sessionId,
|
||
|
range: n,
|
||
|
stage: "CIPHERTEXT",
|
||
|
cipherText: session.cipherText(),
|
||
|
});
|
||
|
|
||
|
return sessionId;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Process cooperative random protocol.
|
||
|
*
|
||
|
* @param data Packet received
|
||
|
*/
|
||
|
processCooperativeRandom(data) {
|
||
|
// Step 0: extract relevant information from data
|
||
|
let session = this.sessions[data.session];
|
||
|
const stage = data.stage;
|
||
|
|
||
|
if (session === undefined) {
|
||
|
// Step 1: generate and encrypt our random value. 8 bytes = 64 bit integer
|
||
|
const noise = CryptoJS.lib.WordArray.random(8).toString();
|
||
|
console.log(`our noise: ${noise}`);
|
||
|
|
||
|
session = new RandomSession(data.range);
|
||
|
this.sessions[data.session] = session;
|
||
|
|
||
|
// Step 2: send our random value and wait for all responses
|
||
|
socket.emit("message", {
|
||
|
type: "RANDOM",
|
||
|
author: ID,
|
||
|
session: data.session,
|
||
|
stage: "CIPHERTEXT",
|
||
|
range: data.range,
|
||
|
cipherText: session.cipherText(),
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (stage === "CIPHERTEXT") {
|
||
|
session.cipherTexts[data.author] = data.cipherText;
|
||
|
|
||
|
if (
|
||
|
Object.keys(session.cipherTexts).length ===
|
||
|
Object.keys(players).length - 1
|
||
|
) {
|
||
|
// Step 3: release our key once all players have sent a ciphertext
|
||
|
socket.emit("message", {
|
||
|
type: "RANDOM",
|
||
|
author: ID,
|
||
|
session: data.session,
|
||
|
stage: "DECRYPT",
|
||
|
cipherKey: session.ourKey,
|
||
|
});
|
||
|
}
|
||
|
} else if (stage === "DECRYPT") {
|
||
|
session.cipherKeys[data.author] = data.cipherKey;
|
||
|
|
||
|
// Step 4: get final random value
|
||
|
if (
|
||
|
Object.keys(session.cipherKeys).length ===
|
||
|
Object.keys(players).length - 1
|
||
|
) {
|
||
|
// Lock out wait calls as they may resolve to never-ending promises.
|
||
|
this.locked = true;
|
||
|
|
||
|
let total = 0;
|
||
|
|
||
|
for (let participant of Object.keys(session.cipherKeys)) {
|
||
|
total += CryptoJS.AES.decrypt(
|
||
|
session.cipherTexts[participant],
|
||
|
session.cipherKeys[participant]
|
||
|
);
|
||
|
}
|
||
|
|
||
|
session.finalValue = total % session.range;
|
||
|
|
||
|
this.resolve(data.session);
|
||
|
|
||
|
this.locked = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Resolve a session by calling any callbacks associated with the session and then deleting it.
|
||
|
*
|
||
|
* @param sessionId
|
||
|
*/
|
||
|
resolve(sessionId) {
|
||
|
const session = this.sessions[sessionId];
|
||
|
for (let resolve of session.resolvers) {
|
||
|
resolve(session.finalValue);
|
||
|
}
|
||
|
}
|
||
|
}
|