Add methods to wait for random session to complete
This commit is contained in:
parent
0a1011f0e5
commit
b37f273b05
@ -12,6 +12,7 @@ const POST_GAME = 3;
|
||||
let game_state = WAITING;
|
||||
|
||||
let socket;
|
||||
let random;
|
||||
|
||||
// Not totally reliable but better than nothing.
|
||||
window.addEventListener("beforeunload", () => {
|
||||
@ -25,6 +26,7 @@ window.addEventListener("beforeunload", () => {
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
socket = io();
|
||||
random = new Random();
|
||||
|
||||
socket.on("connect", () => {
|
||||
console.log("Connected!");
|
||||
@ -38,7 +40,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
players[ID] = new Player(ID, name);
|
||||
});
|
||||
|
||||
socket.on("message", (data) => {
|
||||
socket.on("message", async (data) => {
|
||||
// Ignore any messages that originate from us.
|
||||
if (data.author === ID) {
|
||||
return;
|
||||
@ -58,11 +60,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
break;
|
||||
|
||||
case "SYNC":
|
||||
sync(data);
|
||||
await sync(data);
|
||||
break;
|
||||
|
||||
case "RANDOM":
|
||||
processCooperativeRandom(data);
|
||||
random.processCooperativeRandom(data);
|
||||
break;
|
||||
}
|
||||
});
|
||||
@ -124,15 +126,21 @@ function keepAlive(data) {
|
||||
*
|
||||
* @param data Packet received
|
||||
*/
|
||||
function sync(data) {
|
||||
async function sync(data) {
|
||||
players[data.author].name = data.name;
|
||||
players[data.author].ready = data.ready;
|
||||
|
||||
if (allPlayersReady()) {
|
||||
game_state = PRE_GAME;
|
||||
// Decide turn order. "Master" begins a cooperative rng process.
|
||||
if (ID === Array.min(...Object.keys(players))) {
|
||||
random.coopRandom(Object.keys(players).length, "first-player");
|
||||
}
|
||||
|
||||
//
|
||||
// Wait for value to populate
|
||||
let player1 = await random.get("first-player");
|
||||
|
||||
console.log(player1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,106 +153,3 @@ function allPlayersReady() {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Track ongoing random sessions.
|
||||
const randomSessions = {};
|
||||
|
||||
/**
|
||||
* Start a cooperative random session.
|
||||
*/
|
||||
function coopRandom(n) {
|
||||
const sessionId = window.crypto.randomUUID();
|
||||
|
||||
const noise = CryptoJS.lib.WordArray.random(8).toString();
|
||||
const key = CryptoJS.lib.WordArray.random(32).toString();
|
||||
const cipherText = CryptoJS.AES.encrypt(noise, key).toString();
|
||||
|
||||
randomSessions[sessionId] = {
|
||||
range: n,
|
||||
cipherTexts: {},
|
||||
cipherKeys: {},
|
||||
ourKey: key,
|
||||
ourNoise: noise,
|
||||
finalValue: -1,
|
||||
};
|
||||
|
||||
socket.emit("message", {
|
||||
type: "RANDOM",
|
||||
author: ID,
|
||||
session: sessionId,
|
||||
range: n,
|
||||
stage: "CIPHERTEXT",
|
||||
cipherText: cipherText,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Process cooperative random protocol.
|
||||
*
|
||||
* @param data Packet received
|
||||
*/
|
||||
function processCooperativeRandom(data) {
|
||||
// Step 0: extract relevant information from data
|
||||
let session = randomSessions[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}`);
|
||||
|
||||
const key = CryptoJS.lib.WordArray.random(32).toString();
|
||||
const cipherText = CryptoJS.AES.encrypt(noise, key).toString();
|
||||
|
||||
randomSessions[data.session] = {
|
||||
range: data.range,
|
||||
cipherTexts: {},
|
||||
cipherKeys: {},
|
||||
ourKey: key,
|
||||
ourNoise: noise,
|
||||
finalValue: -1,
|
||||
};
|
||||
session = randomSessions[data.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: 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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
151
static/js/random.js
Normal file
151
static/js/random.js
Normal file
@ -0,0 +1,151 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
<script src="{{ url_for('static', filename='js/index.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/player.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/dom.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/random.js') }}"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user