2023-03-04 00:25:54 +00:00
|
|
|
import { socket, ID, game } from "./main.js";
|
2023-03-03 17:34:15 +00:00
|
|
|
|
2023-01-31 14:19:56 +00:00
|
|
|
class RandomSession {
|
|
|
|
constructor(range) {
|
|
|
|
this.range = range;
|
|
|
|
this.cipherTexts = {};
|
|
|
|
this.cipherKeys = {};
|
|
|
|
this.ourKey = CryptoJS.lib.WordArray.random(32).toString();
|
2023-02-11 14:59:24 +00:00
|
|
|
// 32-bit as JavaScript does funny stuff at 53-bit levels.
|
2023-02-01 18:09:32 +00:00
|
|
|
this.ourNoise = CryptoJS.lib.WordArray.random(4);
|
2023-01-31 14:19:56 +00:00
|
|
|
this.finalValue = null;
|
|
|
|
this.resolvers = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
cipherText() {
|
|
|
|
return CryptoJS.AES.encrypt(this.ourNoise, this.ourKey).toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-03 17:34:15 +00:00
|
|
|
export class Random {
|
2023-01-31 14:19:56 +00:00
|
|
|
constructor() {
|
|
|
|
this.sessions = {};
|
|
|
|
}
|
|
|
|
|
2023-02-01 18:09:32 +00:00
|
|
|
async get(n, sessionId) {
|
|
|
|
if (this.sessions[sessionId] === undefined) {
|
|
|
|
this.initialiseSession(n, sessionId);
|
2023-01-31 14:19:56 +00:00
|
|
|
}
|
2023-02-01 18:09:32 +00:00
|
|
|
|
|
|
|
let promise;
|
|
|
|
await navigator.locks.request(`random-${sessionId}`, () => {
|
|
|
|
if (this.sessions[sessionId].finalValue === null) {
|
|
|
|
let session = this.sessions[sessionId];
|
|
|
|
let resolver;
|
|
|
|
promise = new Promise((resolve) => {
|
|
|
|
resolver = resolve;
|
|
|
|
});
|
|
|
|
session.resolvers.push(resolver);
|
|
|
|
} else {
|
|
|
|
promise = new Promise((resolve) => {
|
|
|
|
resolve(this.sessions[sessionId].finalValue);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return promise;
|
2023-01-31 14:19:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Start a cooperative random session.
|
|
|
|
*/
|
2023-02-01 18:09:32 +00:00
|
|
|
initialiseSession(n, sessionId) {
|
2023-01-31 14:19:56 +00:00
|
|
|
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(),
|
|
|
|
});
|
|
|
|
|
2023-02-01 18:09:32 +00:00
|
|
|
return session;
|
2023-01-31 14:19:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process cooperative random protocol.
|
|
|
|
*
|
|
|
|
* @param data Packet received
|
|
|
|
*/
|
2023-02-01 18:09:32 +00:00
|
|
|
async processCooperativeRandom(data) {
|
2023-01-31 14:19:56 +00:00
|
|
|
// Step 0: extract relevant information from data
|
|
|
|
let session = this.sessions[data.session];
|
|
|
|
const stage = data.stage;
|
|
|
|
|
|
|
|
if (session === undefined) {
|
2023-02-01 18:09:32 +00:00
|
|
|
session = this.initialiseSession(data.range, data.session);
|
2023-01-31 14:19:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (stage === "CIPHERTEXT") {
|
|
|
|
session.cipherTexts[data.author] = data.cipherText;
|
|
|
|
|
|
|
|
if (
|
|
|
|
Object.keys(session.cipherTexts).length ===
|
2023-03-04 00:25:54 +00:00
|
|
|
Object.keys(game.players).length - 1
|
2023-01-31 14:19:56 +00:00
|
|
|
) {
|
|
|
|
// 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 ===
|
2023-03-04 00:25:54 +00:00
|
|
|
Object.keys(game.players).length - 1
|
2023-01-31 14:19:56 +00:00
|
|
|
) {
|
|
|
|
// Lock out wait calls as they may resolve to never-ending promises.
|
2023-02-01 18:09:32 +00:00
|
|
|
await navigator.locks.request(`random-${data.session}`, () => {
|
|
|
|
let total = parseInt(session.ourNoise, 16);
|
2023-01-31 14:19:56 +00:00
|
|
|
|
2023-02-01 18:09:32 +00:00
|
|
|
for (let participant of Object.keys(session.cipherKeys)) {
|
|
|
|
let decrypted = CryptoJS.AES.decrypt(
|
|
|
|
session.cipherTexts[participant],
|
|
|
|
session.cipherKeys[participant]
|
|
|
|
).toString();
|
2023-01-31 14:19:56 +00:00
|
|
|
|
2023-02-01 18:09:32 +00:00
|
|
|
total += parseInt(decrypted, 16);
|
|
|
|
}
|
2023-01-31 14:19:56 +00:00
|
|
|
|
2023-02-01 18:09:32 +00:00
|
|
|
session.finalValue = total % session.range;
|
2023-01-31 14:19:56 +00:00
|
|
|
|
2023-02-01 18:09:32 +00:00
|
|
|
this.resolve(data.session);
|
|
|
|
});
|
2023-01-31 14:19:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|