Merge pull request #15 from privacy-scaling-explorations/improve-buffering-performance

Improve buffering performance
This commit is contained in:
Andrew Morris
2025-06-12 11:27:04 +10:00
committed by GitHub
11 changed files with 704 additions and 1026 deletions

1012
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "emp-wasm",
"version": "0.2.5",
"version": "0.3.0",
"description": "Wasm build of authenticated garbling from [emp-toolkit/emp-ag2pc](https://github.com/emp-toolkit/emp-ag2pc).",
"type": "module",
"main": "dist/src/ts/index.js",
@@ -23,7 +23,7 @@
"peerjs": "^1.5.4",
"tsx": "^4.19.1",
"typescript": "^5.6.3",
"vite": "^5.4.8",
"vite": "^6.3.5",
"ws": "^8.18.0"
},
"dependencies": {

View File

@@ -3,6 +3,7 @@
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <map>
#include "emp-tool/io/i_raw_io.h"
#include "emp-ag2pc/2pc.h"
@@ -24,45 +25,121 @@ EM_JS(void, send_js, (int to_party, char channel_label, const void* data, size_t
});
// Implement recv_js function to receive data from JavaScript to C++
EM_ASYNC_JS(void, recv_js, (int from_party, char channel_label, void* data, size_t len), {
EM_ASYNC_JS(size_t, recv_js, (int from_party, char channel_label, void* data, size_t min_len, size_t max_len), {
if (!Module.emp?.io?.recv) {
reject(new Error("Module.emp.io.recv is not defined in JavaScript."));
return;
}
// Wait for data from JavaScript
const dataArray = await Module.emp.io.recv(from_party - 1, String.fromCharCode(channel_label), len);
const dataArray = await Module.emp.io.recv(from_party - 1, String.fromCharCode(channel_label), min_len, max_len);
// Copy data from JavaScript Uint8Array to WebAssembly memory
HEAPU8.set(dataArray, data);
// Return the length of the received data
return dataArray.length;
});
class RawIOJS;
std::map<int, RawIOJS*> raw_io_map;
int next_raw_io_id = 0;
size_t MAX_SEND_BUFFER_SIZE = 64 * 1024;
void actual_flush_all();
class RawIOJS : public IRawIO {
public:
int other_party;
char channel_label;
std::vector<uint8_t> send_buffer; // TODO: Max buffer size?
std::vector<uint8_t> recv_buffer;
size_t recv_start = 0;
size_t recv_end = 0;
int id;
RawIOJS(
int other_party,
char channel_label
):
other_party(other_party),
channel_label(channel_label)
{}
channel_label(channel_label),
recv_buffer(64 * 1024)
{
id = next_raw_io_id++;
raw_io_map[id] = this;
}
~RawIOJS() {
raw_io_map.erase(id);
}
void send(const void* data, size_t len) override {
send_js(other_party, channel_label, data, len);
if (send_buffer.size() + len > MAX_SEND_BUFFER_SIZE) {
actual_flush();
}
// This will still exceed max size if len > MAX_SEND_BUFFER_SIZE, that's ok
send_buffer.resize(send_buffer.size() + len);
std::memcpy(send_buffer.data() + send_buffer.size() - len, data, len);
}
void recv(void* data, size_t len) override {
recv_js(other_party, channel_label, data, len);
if (recv_start + len > recv_end) {
if (recv_start + len > recv_buffer.size()) {
// copy within
size_t recv_len = recv_end - recv_start;
std::memmove(recv_buffer.data(), recv_buffer.data() + recv_start, recv_len);
recv_start = 0;
recv_end = recv_len;
}
size_t bytes_needed = recv_start + len - recv_end;
size_t room = recv_buffer.size() - recv_end;
if (bytes_needed > room) {
size_t size = recv_buffer.size();
while (bytes_needed > room) {
size *= 2;
room = size - recv_end;
}
recv_buffer.resize(size);
}
actual_flush_all();
size_t bytes_received = recv_js(other_party, channel_label, recv_buffer.data() + recv_end, bytes_needed, room);
recv_end += bytes_received;
if (bytes_received < bytes_needed) {
throw std::runtime_error("recv failed");
}
}
std::memcpy(data, recv_buffer.data() + recv_start, len);
recv_start += len;
}
void flush() override {
// Ignored for now
// ignored for now
}
void actual_flush() {
if (send_buffer.size() > 0) {
send_js(other_party, channel_label, send_buffer.data(), send_buffer.size());
send_buffer.clear();
}
}
};
void actual_flush_all() {
for (auto& [key, raw_io] : raw_io_map) {
raw_io->actual_flush();
}
}
class MultiIOJS : public IMultiIO {
public:
int mParty;
@@ -284,6 +361,9 @@ void run_2pc_impl(int party, int nP) {
twopc.function_dependent();
std::vector<bool> output_bits = twopc.online(input_bits, true);
actual_flush_all();
handle_output_bits(output_bits);
} catch (const std::exception& e) {
handle_error(e.what());
@@ -339,6 +419,8 @@ void run_mpc_impl(int party, int nP) {
output_bits.push_back(output.get_plaintext_bit(i));
}
actual_flush_all();
handle_output_bits(output_bits);
} catch (const std::exception& e) {
handle_error(e.what());

View File

@@ -5,7 +5,7 @@ export default class BufferQueue {
private buffer: Uint8Array;
private bufferStart: number;
private bufferEnd: number;
private pendingPops: number[];
private pendingPops: { min_len: number, max_len: number }[];
private pendingPopsResolvers: {
resolve: ((value: Uint8Array) => void),
reject: (e: Error) => void,
@@ -65,19 +65,21 @@ export default class BufferQueue {
* @param len - The number of bytes to pop from the buffer.
* @returns A promise resolving with the popped data as a Uint8Array.
*/
pop(len: number): Promise<Uint8Array> {
if (typeof len !== 'number' || len < 0) {
return Promise.reject(new Error('Length must be non-negative integer'));
pop(min_len: number, max_len: number): Promise<Uint8Array> {
if (typeof min_len !== 'number' || typeof max_len !== 'number' || min_len < 0 || max_len < 0 || min_len > max_len) {
return Promise.reject(new Error('Invalid min/max lengths'));
}
if (this.bufferEnd - this.bufferStart >= len) {
const result = this.buffer.slice(this.bufferStart, this.bufferStart + len);
this.bufferStart += len;
if (this.bufferEnd - this.bufferStart >= min_len) {
const available_len = this.bufferEnd - this.bufferStart;
const provide_len = Math.min(max_len, available_len);
const result = this.buffer.slice(this.bufferStart, this.bufferStart + provide_len);
this.bufferStart += result.length;
this._compactBuffer();
return Promise.resolve(result);
} else if (!this.closed) {
return new Promise((resolve, reject) => {
this.pendingPops.push(len);
this.pendingPops.push({ min_len, max_len });
this.pendingPopsResolvers.push({ resolve, reject });
});
} else {
@@ -109,10 +111,12 @@ export default class BufferQueue {
*/
private _resolvePendingPops(): void {
while (this.pendingPops.length > 0) {
const len = this.pendingPops[0];
if (this.bufferEnd - this.bufferStart >= len) {
const data = this.buffer.slice(this.bufferStart, this.bufferStart + len);
this.bufferStart += len;
const { min_len, max_len } = this.pendingPops[0];
if (this.bufferEnd - this.bufferStart >= min_len) {
const available_len = this.bufferEnd - this.bufferStart;
const provide_len = Math.min(max_len, available_len);
const data = this.buffer.slice(this.bufferStart, this.bufferStart + provide_len);
this.bufferStart += data.length;
this.pendingPops.shift();
const { resolve } = this.pendingPopsResolvers.shift()!;
resolve(data);

View File

@@ -27,9 +27,9 @@ export default class BufferedIO
this.closeOther?.();
}
async recv(fromParty: number, channel: 'a' | 'b', len: number): Promise<Uint8Array> {
async recv(fromParty: number, channel: 'a' | 'b', min_len: number, max_len: number): Promise<Uint8Array> {
assert(fromParty === this.otherParty, 'fromParty !== this.otherParty');
return await this.bq[channel].pop(len);
return await this.bq[channel].pop(min_len, max_len);
}
accept(channel: 'a' | 'b', data: Uint8Array) {

View File

@@ -61,15 +61,25 @@ async function secureMPC({
emp.circuit = circuit;
emp.inputBits = inputBits;
emp.inputBitsPerParty = inputBitsPerParty;
emp.io = io;
let reject: undefined | ((error: unknown) => void) = undefined;
const callbackRejector = new Promise((_resolve, rej) => {
reject = rej;
});
reject = reject!;
emp.io = {
send: useRejector(io.send.bind(io), reject),
recv: useRejector(io.recv.bind(io), reject),
};
const method = calculateMethod(mode, size, circuit);
const result = new Promise<Uint8Array>((resolve, reject) => {
const result = new Promise<Uint8Array>(async (resolve, reject) => {
try {
emp.handleOutput = resolve;
emp.handleError = reject;
callbackRejector.catch(reject);
module[method](party, size);
} catch (error) {
reject(error);
@@ -97,7 +107,12 @@ function calculateMethod(
case 'mpc':
return '_run_mpc';
case 'auto':
return size === 2 ? '_run_2pc' : '_run_mpc';
// Advantage of 2PC specialization is small and contains "FEQ error" bug
// for the large circuits, so the performance currently cannot be realized
// where it matters.
// Therefore, we default to the general N-party mpc mode, even when there
// are only 2 parties.
return '_run_mpc';
default:
const _never: never = mode;
@@ -125,11 +140,11 @@ onmessage = async (event) => {
send: (toParty, channel, data) => {
postMessage({ type: 'io_send', toParty, channel, data });
},
recv: (fromParty, channel, len) => {
recv: (fromParty, channel, min_len, max_len) => {
return new Promise((resolve, reject) => {
const id = requestId++;
pendingRequests[id] = { resolve, reject };
postMessage({ type: 'io_recv', fromParty, channel, len, id });
postMessage({ type: 'io_recv', fromParty, channel, min_len, max_len, id });
});
},
};
@@ -147,7 +162,7 @@ onmessage = async (event) => {
postMessage({ type: 'result', result });
} catch (error) {
postMessage({ type: 'error', error: (error as Error).stack });
postMessage({ type: 'error', error: (error as Error).message });
}
} else if (message.type === 'io_recv_response') {
const { id, data } = message;
@@ -163,3 +178,17 @@ onmessage = async (event) => {
}
}
};
function useRejector<F extends (...args: any[]) => any>(
fn: F,
reject: (error: unknown) => void,
): F {
return ((...args: Parameters<F>) => {
try {
return fn(...args);
} catch (error) {
reject(error);
throw error;
}
}) as F;
}

View File

@@ -28,7 +28,7 @@ windowAny.internalDemo = async function(
inputBitsPerParty: [32, 32],
io: {
send: (toParty, channel, data) => bobBq[channel].push(data),
recv: (fromParty, channel, len) => aliceBq[channel].pop(len),
recv: (fromParty, channel, min_len, max_len) => aliceBq[channel].pop(min_len, max_len),
},
mode,
}),
@@ -40,7 +40,7 @@ windowAny.internalDemo = async function(
inputBitsPerParty: [32, 32],
io: {
send: (toParty, channel, data) => aliceBq[channel].push(data),
recv: (fromParty, channel, len) => bobBq[channel].pop(len),
recv: (fromParty, channel, min_len, max_len) => bobBq[channel].pop(min_len, max_len),
},
mode,
}),
@@ -69,7 +69,7 @@ windowAny.internalDemo3 = async function(
inputBitsPerParty: [32, 32, 32],
io: {
send: (toParty, channel, data) => bqs.get(party, toParty, channel).push(data),
recv: (fromParty, channel, len) => bqs.get(fromParty, party, channel).pop(len),
recv: (fromParty, channel, min_len, max_len) => bqs.get(fromParty, party, channel).pop(min_len, max_len),
},
mode,
})));
@@ -342,9 +342,9 @@ function makeCopyPasteIO(otherParty: number): IO {
return {
send: makeConsoleSend(otherParty),
recv: (fromParty, channel, len) => {
recv: (fromParty, channel, min_len, max_len) => {
assert(fromParty === otherParty, 'Unexpected party');
return bq[channel].pop(len);
return bq[channel].pop(min_len, max_len);
},
};
}

View File

@@ -42,7 +42,17 @@ export default async function nodeSecureMPC({
emp.circuit = circuit;
emp.inputBits = inputBits;
emp.inputBitsPerParty = inputBitsPerParty;
emp.io = io;
let reject: undefined | ((error: unknown) => void) = undefined;
const callbackRejector = new Promise((_resolve, rej) => {
reject = rej;
});
reject = reject!;
emp.io = {
send: useRejector(io.send.bind(io), reject),
recv: useRejector(io.recv.bind(io), reject),
};
const method = calculateMethod(mode, size, circuit);
@@ -50,6 +60,7 @@ export default async function nodeSecureMPC({
try {
emp.handleOutput = resolve;
emp.handleError = reject;
callbackRejector.catch(reject);
module[method](party, size);
} catch (error) {
@@ -74,10 +85,29 @@ function calculateMethod(
case 'mpc':
return '_run_mpc';
case 'auto':
return size === 2 ? '_run_2pc' : '_run_mpc';
// Advantage of 2PC specialization is small and contains "FEQ error" bug
// for the large circuits, so the performance currently cannot be realized
// where it matters.
// Therefore, we default to the general N-party mpc mode, even when there
// are only 2 parties.
return '_run_mpc';
default:
const _never: never = mode;
throw new Error('Unexpected mode: ' + mode);
}
}
function useRejector<F extends (...args: any[]) => any>(
fn: F,
reject: (error: unknown) => void,
): F {
return ((...args: Parameters<F>) => {
try {
return fn(...args);
} catch (error) {
reject(error);
throw error;
}
}) as F;
}

View File

@@ -62,10 +62,10 @@ export default function secureMPC({
const { toParty, channel, data } = message;
io.send(toParty, channel, data);
} else if (message.type === 'io_recv') {
const { fromParty, channel, len } = message;
const { fromParty, channel, min_len, max_len } = message;
// Handle the recv request from the worker
try {
const data = await io.recv(fromParty, channel, len);
const data = await io.recv(fromParty, channel, min_len, max_len);
worker.postMessage({ type: 'io_recv_response', id: message.id, data });
} catch (error) {
worker.postMessage({
@@ -80,6 +80,10 @@ export default function secureMPC({
} else if (message.type === 'error') {
// Reject the promise if an error occurred
reject(new Error(message.error));
} else if (message.type === 'log') {
console.log('Worker log:', message.msg);
} else {
console.error('Unexpected message from worker:', message);
}
};

View File

@@ -1,6 +1,6 @@
export type IO = {
send: (toParty: number, channel: 'a' | 'b', data: Uint8Array) => void;
recv: (fromParty: number, channel: 'a' | 'b', len: number) => Promise<Uint8Array>;
recv: (fromParty: number, channel: 'a' | 'b', min_len: number, max_len: number) => Promise<Uint8Array>;
on?: (event: 'error', listener: (error: Error) => void) => void;
off?: (event: 'error', listener: (error: Error) => void) => void;
close?: () => void;

View File

@@ -1,5 +1,7 @@
import fs from 'fs/promises';
import { expect } from 'chai';
import { BufferQueue, secureMPC } from "../src/ts"
import { BufferQueue, secureMPC } from "../src/ts";
describe('Secure MPC', () => {
it('3 + 5 == 8 (2pc)', async function () {
@@ -20,6 +22,36 @@ describe('Secure MPC', () => {
this.timeout(20_000);
expect(await internalDemoN(3, 5, 5)).to.deep.equal([8, 8, 8, 8, 8]);
});
it('sha1("") == "da..09" (2 parties)', async function () {
this.timeout(60_000);
const outputs = await internalDemoSha1N(2);
for (const output of outputs) {
expect(output).to.equal('da39a3ee5e6b4b0d3255bfef95601890afd80709');
}
});
it('sha1("") == "da..09" (3 parties)', async function () {
this.timeout(60_000);
const outputs = await internalDemoSha1N(3);
for (const output of outputs) {
expect(output).to.equal('da39a3ee5e6b4b0d3255bfef95601890afd80709');
}
});
it('sha1("") == "da..09" (4 parties)', async function () {
this.timeout(60_000);
const outputs = await internalDemoSha1N(4);
for (const output of outputs) {
expect(output).to.equal('da39a3ee5e6b4b0d3255bfef95601890afd80709');
}
});
});
class BufferQueueStore {
@@ -42,6 +74,7 @@ async function internalDemo(
mode: '2pc' | 'mpc' | 'auto' = 'auto',
): Promise<{ alice: number, bob: number }> {
const bqs = new BufferQueueStore();
const add32BitCircuit = await getCircuit('adder_32bit.txt');
const [aliceBits, bobBits] = await Promise.all([
secureMPC({
@@ -55,9 +88,9 @@ async function internalDemo(
expect(toParty).to.equal(1);
bqs.get('alice', 'bob', channel).push(data);
},
recv: async (fromParty, channel, len) => {
recv: async (fromParty, channel, min_len, max_len) => {
expect(fromParty).to.equal(1);
return bqs.get('bob', 'alice', channel).pop(len);
return bqs.get('bob', 'alice', channel).pop(min_len, max_len);
},
},
mode,
@@ -73,9 +106,9 @@ async function internalDemo(
expect(toParty).to.equal(0);
bqs.get('bob', 'alice', channel).push(data);
},
recv: async (fromParty, channel, len) => {
recv: async (fromParty, channel, min_len, max_len) => {
expect(fromParty).to.equal(0);
return bqs.get('alice', 'bob', channel).pop(len);
return bqs.get('alice', 'bob', channel).pop(min_len, max_len);
},
},
mode,
@@ -94,6 +127,7 @@ async function internalDemoN(
size: number
): Promise<number[]> {
const bqs = new BufferQueueStore();
const add32BitCircuit = await getCircuit('adder_32bit.txt');
const inputBitsPerParty = new Array(size).fill(0);
inputBitsPerParty[0] = 32;
@@ -119,8 +153,8 @@ async function internalDemoN(
send: (toParty, channel, data) => {
bqs.get(party, toParty, channel).push(data);
},
recv: async (fromParty, channel, len) => {
return bqs.get(fromParty, party, channel).pop(len);
recv: async (fromParty, channel, min_len, max_len) => {
return bqs.get(fromParty, party, channel).pop(min_len, max_len);
},
}
})));
@@ -160,382 +194,67 @@ function numberFrom32Bits(arr: Uint8Array): number {
return result;
}
const add32BitCircuit = `375 439
32 32 33
async function internalDemoSha1N(
size: number,
): Promise<string[]> {
const bqs = new BufferQueueStore();
const sha1Circuit = await getCircuit('sha-1.txt');
2 1 0 32 406 XOR
2 1 5 37 373 AND
2 1 4 36 336 AND
2 1 10 42 340 AND
2 1 14 46 366 AND
2 1 24 56 341 AND
2 1 8 40 342 AND
2 1 1 33 343 AND
2 1 7 39 348 AND
2 1 28 60 349 AND
2 1 19 51 350 AND
2 1 2 34 351 AND
2 1 30 62 364 AND
2 1 13 45 352 AND
2 1 18 50 353 AND
2 1 11 43 355 AND
2 1 3 35 356 AND
2 1 16 48 359 AND
2 1 31 63 357 AND
2 1 27 59 358 AND
2 1 15 47 360 AND
2 1 17 49 361 AND
2 1 9 41 363 AND
2 1 32 0 278 AND
2 1 29 61 362 AND
2 1 6 38 365 AND
2 1 25 57 354 AND
2 1 20 52 367 AND
2 1 22 54 331 AND
2 1 21 53 371 AND
2 1 12 44 372 AND
2 1 23 55 339 AND
2 1 26 58 368 AND
1 1 56 398 INV
1 1 3 314 INV
1 1 40 346 INV
1 1 62 378 INV
1 1 6 389 INV
1 1 28 401 INV
1 1 10 377 INV
1 1 13 391 INV
1 1 27 335 INV
1 1 7 387 INV
1 1 24 399 INV
1 1 54 327 INV
1 1 36 315 INV
1 1 52 332 INV
1 1 50 380 INV
1 1 57 404 INV
1 1 31 323 INV
1 1 55 317 INV
1 1 18 381 INV
1 1 60 400 INV
1 1 5 322 INV
1 1 14 395 INV
1 1 47 402 INV
1 1 8 347 INV
1 1 19 385 INV
1 1 53 374 INV
1 1 29 330 INV
1 1 1 382 INV
1 1 34 344 INV
1 1 20 333 INV
1 1 37 321 INV
1 1 45 390 INV
1 1 11 338 INV
1 1 42 376 INV
1 1 12 370 INV
1 1 38 388 INV
1 1 23 318 INV
1 1 41 392 INV
1 1 61 329 INV
1 1 15 403 INV
1 1 48 396 INV
1 1 26 320 INV
1 1 43 337 INV
1 1 59 334 INV
1 1 9 393 INV
1 1 58 319 INV
1 1 17 326 INV
1 1 44 369 INV
1 1 21 375 INV
1 1 49 325 INV
1 1 16 397 INV
1 1 25 405 INV
1 1 51 384 INV
1 1 4 316 INV
1 1 2 345 INV
1 1 39 386 INV
1 1 46 394 INV
1 1 35 313 INV
1 1 22 328 INV
1 1 63 324 INV
1 1 33 383 INV
1 1 30 379 INV
2 1 313 314 282 AND
2 1 315 316 283 AND
2 1 317 318 284 AND
2 1 319 320 299 AND
2 1 321 322 285 AND
2 1 323 324 286 AND
2 1 325 326 288 AND
2 1 327 328 289 AND
2 1 329 330 290 AND
1 1 331 130 INV
2 1 332 333 287 AND
2 1 334 335 292 AND
1 1 336 256 INV
2 1 337 338 293 AND
1 1 339 123 INV
1 1 340 214 INV
1 1 341 116 INV
1 1 342 228 INV
1 1 343 276 INV
2 1 344 345 310 AND
2 1 346 347 300 AND
1 1 348 235 INV
1 1 349 88 INV
1 1 350 151 INV
1 1 351 270 INV
1 1 352 193 INV
1 1 353 158 INV
1 1 354 109 INV
1 1 355 207 INV
1 1 356 263 INV
1 1 357 66 INV
1 1 358 95 INV
1 1 359 172 INV
1 1 360 179 INV
1 1 361 165 INV
1 1 362 81 INV
1 1 363 221 INV
1 1 364 74 INV
1 1 365 242 INV
1 1 366 186 INV
1 1 367 144 INV
1 1 368 102 INV
2 1 369 370 301 AND
1 1 371 137 INV
1 1 372 200 INV
1 1 373 249 INV
2 1 374 375 298 AND
2 1 376 377 296 AND
2 1 378 379 291 AND
2 1 380 381 297 AND
2 1 382 383 306 AND
2 1 384 385 294 AND
2 1 386 387 295 AND
2 1 388 389 302 AND
2 1 390 391 303 AND
2 1 392 393 304 AND
2 1 394 395 305 AND
2 1 396 397 307 AND
2 1 398 399 308 AND
2 1 400 401 309 AND
2 1 402 403 311 AND
2 1 404 405 312 AND
1 1 282 266 INV
1 1 283 259 INV
1 1 284 126 INV
1 1 285 252 INV
1 1 286 69 INV
1 1 287 147 INV
1 1 288 168 INV
1 1 289 133 INV
1 1 290 84 INV
1 1 291 77 INV
1 1 292 98 INV
1 1 293 210 INV
1 1 294 154 INV
1 1 295 238 INV
1 1 296 217 INV
1 1 297 161 INV
1 1 298 140 INV
1 1 299 105 INV
1 1 300 231 INV
1 1 301 203 INV
1 1 302 245 INV
1 1 303 196 INV
1 1 304 224 INV
1 1 305 189 INV
1 1 306 281 INV
1 1 307 175 INV
1 1 308 119 INV
1 1 309 91 INV
1 1 310 273 INV
1 1 311 182 INV
1 1 312 112 INV
2 1 281 276 277 AND
2 1 69 66 279 AND
2 1 281 278 280 AND
2 1 277 278 407 XOR
1 1 279 71 INV
1 1 280 275 INV
2 1 275 276 274 AND
1 1 274 271 INV
2 1 2 271 268 XOR
2 1 271 273 272 AND
2 1 34 268 408 XOR
1 1 272 269 INV
2 1 269 270 267 AND
1 1 267 264 INV
2 1 3 264 261 XOR
2 1 264 266 265 AND
2 1 35 261 409 XOR
1 1 265 262 INV
2 1 262 263 260 AND
1 1 260 257 INV
2 1 4 257 253 XOR
2 1 257 259 258 AND
2 1 36 253 410 XOR
1 1 258 255 INV
2 1 255 256 254 AND
1 1 254 250 INV
2 1 5 250 247 XOR
2 1 250 252 251 AND
2 1 37 247 411 XOR
1 1 251 248 INV
2 1 248 249 246 AND
1 1 246 243 INV
2 1 6 243 239 XOR
2 1 243 245 244 AND
2 1 38 239 412 XOR
1 1 244 241 INV
2 1 241 242 240 AND
1 1 240 236 INV
2 1 7 236 233 XOR
2 1 236 238 237 AND
2 1 39 233 413 XOR
1 1 237 234 INV
2 1 234 235 232 AND
1 1 232 229 INV
2 1 8 229 226 XOR
2 1 229 231 230 AND
2 1 40 226 414 XOR
1 1 230 227 INV
2 1 227 228 225 AND
1 1 225 222 INV
2 1 9 222 219 XOR
2 1 222 224 223 AND
2 1 41 219 415 XOR
1 1 223 220 INV
2 1 220 221 218 AND
1 1 218 215 INV
2 1 10 215 212 XOR
2 1 215 217 216 AND
2 1 42 212 416 XOR
1 1 216 213 INV
2 1 213 214 211 AND
1 1 211 208 INV
2 1 11 208 205 XOR
2 1 208 210 209 AND
2 1 43 205 417 XOR
1 1 209 206 INV
2 1 206 207 204 AND
1 1 204 201 INV
2 1 12 201 198 XOR
2 1 201 203 202 AND
2 1 44 198 418 XOR
1 1 202 199 INV
2 1 199 200 197 AND
1 1 197 195 INV
2 1 13 195 190 XOR
2 1 195 196 194 AND
2 1 45 190 419 XOR
1 1 194 192 INV
2 1 192 193 191 AND
1 1 191 187 INV
2 1 14 187 183 XOR
2 1 187 189 188 AND
2 1 46 183 420 XOR
1 1 188 185 INV
2 1 185 186 184 AND
1 1 184 180 INV
2 1 15 180 177 XOR
2 1 180 182 181 AND
2 1 47 177 421 XOR
1 1 181 178 INV
2 1 178 179 176 AND
1 1 176 173 INV
2 1 48 173 170 XOR
2 1 173 175 174 AND
2 1 16 170 422 XOR
1 1 174 171 INV
2 1 171 172 169 AND
1 1 169 166 INV
2 1 17 166 163 XOR
2 1 166 168 167 AND
2 1 49 163 423 XOR
1 1 167 164 INV
2 1 164 165 162 AND
1 1 162 159 INV
2 1 18 159 156 XOR
2 1 159 161 160 AND
2 1 50 156 424 XOR
1 1 160 157 INV
2 1 157 158 155 AND
1 1 155 152 INV
2 1 19 152 149 XOR
2 1 152 154 153 AND
2 1 51 149 425 XOR
1 1 153 150 INV
2 1 150 151 148 AND
1 1 148 145 INV
2 1 20 145 141 XOR
2 1 145 147 146 AND
2 1 52 141 426 XOR
1 1 146 143 INV
2 1 143 144 142 AND
1 1 142 138 INV
2 1 53 138 135 XOR
2 1 138 140 139 AND
2 1 21 135 427 XOR
1 1 139 136 INV
2 1 136 137 134 AND
1 1 134 132 INV
2 1 22 132 127 XOR
2 1 132 133 131 AND
2 1 54 127 428 XOR
1 1 131 129 INV
2 1 129 130 128 AND
1 1 128 124 INV
2 1 23 124 121 XOR
2 1 124 126 125 AND
2 1 55 121 429 XOR
1 1 125 122 INV
2 1 122 123 120 AND
1 1 120 117 INV
2 1 24 117 114 XOR
2 1 117 119 118 AND
2 1 56 114 430 XOR
1 1 118 115 INV
2 1 115 116 113 AND
1 1 113 110 INV
2 1 25 110 107 XOR
2 1 110 112 111 AND
2 1 57 107 431 XOR
1 1 111 108 INV
2 1 108 109 106 AND
1 1 106 103 INV
2 1 26 103 100 XOR
2 1 103 105 104 AND
2 1 58 100 432 XOR
1 1 104 101 INV
2 1 101 102 99 AND
1 1 99 96 INV
2 1 59 96 93 XOR
2 1 96 98 97 AND
2 1 27 93 433 XOR
1 1 97 94 INV
2 1 94 95 92 AND
1 1 92 89 INV
2 1 28 89 86 XOR
2 1 89 91 90 AND
2 1 60 86 434 XOR
1 1 90 87 INV
2 1 87 88 85 AND
1 1 85 83 INV
2 1 61 83 79 XOR
2 1 83 84 82 AND
2 1 29 79 435 XOR
1 1 82 80 INV
2 1 80 81 78 AND
1 1 78 76 INV
2 1 30 76 72 XOR
2 1 76 77 75 AND
2 1 62 72 436 XOR
1 1 75 73 INV
2 1 73 74 70 AND
2 1 70 71 437 XOR
1 1 70 68 INV
2 1 68 69 67 AND
1 1 67 65 INV
2 1 65 66 64 AND
1 1 64 438 INV
`;
const inputBitsPerParty = new Array(size).fill(0);
inputBitsPerParty[0] = 512;
const outputBits = await Promise.all(new Array(size).fill(0).map((_0, party) => secureMPC({
party,
size,
circuit: sha1Circuit,
inputBits: (() => {
if (party === 0) {
const bits = new Uint8Array(512);
bits[0] = 1; // A single leading 1 to make a valid sha1 block.
return bits;
}
return new Uint8Array(0);
})(),
inputBitsPerParty,
io: {
send: (toParty, channel, data) => {
bqs.get(party, toParty, channel).push(data);
},
recv: async (fromParty, channel, min_len, max_len) => {
return bqs.get(fromParty, party, channel).pop(min_len, max_len);
},
}
})));
return outputBits.map(bits => bitsToHex(bits));
}
function bitsToHex(bits: Uint8Array): string {
if (bits.length % 8 !== 0) {
throw new Error('Invalid number of bits.');
}
let hexParts: string[] = [];
for (let i = 0; i < bits.length; i += 4) {
let nibble = 0;
for (let j = 0; j < 4; j++) {
nibble |= bits[i + j] << (3 - j);
}
hexParts.push(nibble.toString(16));
}
return hexParts.join('');
}
async function getCircuit(name: string) {
const txt = await fs.readFile(
import.meta.resolve(`../circuits/${name}`).slice(7),
'utf-8',
);
return txt;
}