mirror of
https://github.com/tlsnotary/PageSigner.git
synced 2026-01-08 22:27:57 -05:00
enforce same input labels for c4 and c6
dont perform OT on the same inputs of c4 and c6 use fetch() built-in download progress monitor query the notary about fetch() upload progress renumber circuits (we have new circuit 4) traverse circuit faster with wasm
This commit is contained in:
@@ -56,8 +56,8 @@ export class FirstTimeSetup{
|
||||
await window['fs'].init();
|
||||
}
|
||||
// start from the last circuits in order to give the user a quicker initial update
|
||||
for (let n=1; n < 7; n++){
|
||||
const i = 7-n;
|
||||
for (let n=1; n < 8; n++){
|
||||
const i = 8-n;
|
||||
const text = CASM.parseAndAssemble('c'+i+'.casm');
|
||||
const newobj = await new Promise(function(resolve) {
|
||||
worker.onmessage = function(event) {
|
||||
@@ -68,7 +68,7 @@ export class FirstTimeSetup{
|
||||
worker.postMessage({'text': text});
|
||||
});
|
||||
obj[i] = newobj;
|
||||
if (pm) pm.update('first_time', {'current': n, 'total': 6});
|
||||
if (pm) pm.update('first_time', {'current': n, 'total': 7});
|
||||
await wait(100); // make sure update reaches popup
|
||||
}
|
||||
console.timeEnd('time_to_parse');
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
/* global chrome*/
|
||||
|
||||
// The only way to determine if the server is done sending data is to check that out receiving
|
||||
// buffer has nothing but complete TLS records i.e. that there is no incomplete TLS records
|
||||
// However it was observed that in cases when getting e.g. zip files, some servers first send HTTP header as one
|
||||
// TLS record followed by the body as another record(s)
|
||||
// That's why after receiving a complete TLS record we wait to get some more data
|
||||
// This extra waiting must not be done for the handshake messages to avoid adding latency and having the handshake
|
||||
// dropped by the server
|
||||
// We do not communicate directly with the server but we send messages to the helper app
|
||||
// It is the helper app which opens a TCP socket and sends/receives data
|
||||
// The only way to determine if the server is done sending data is to check
|
||||
// that the receiving buffer has nothing but complete TLS records i.e. that
|
||||
// there are no incomplete TLS records. However it was observed that in cases
|
||||
// when getting e.g. zip files, some servers first send HTTP header as one TLS
|
||||
// record followed by the body as another record(s). That's why after
|
||||
// receiving a complete TLS record we wait to get some more data.This extra
|
||||
// waiting must NOT be done for the handshake messages to avoid causing a
|
||||
// handshake timeout by the server.
|
||||
// We do not communicate directly with the server but we send messages to the
|
||||
// helper app. It is the helper app who opens a TCP socket and sends/receives
|
||||
// data.
|
||||
|
||||
import {globals} from './globals.js';
|
||||
import {ba2str, b64decode, concatTA, b64encode, str2ba, ba2int} from './utils.js';
|
||||
@@ -36,6 +38,7 @@ export class Socket {
|
||||
async connect() {
|
||||
const that = this;
|
||||
let timer;
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
const response = await new Promise(async function(resolve, reject) {
|
||||
// dont wait for connect for too long
|
||||
timer = setTimeout(function() {
|
||||
@@ -105,6 +108,7 @@ export class Socket {
|
||||
return;
|
||||
}
|
||||
var that = this;
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
var response = await new Promise(async function(resolve){
|
||||
var msg = {'command': 'recv', 'uid': that.uid};
|
||||
if (globals.usePythonBackend){
|
||||
@@ -130,8 +134,8 @@ export class Socket {
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// fetchLoop has built up the recv buffer
|
||||
// check if there are complete records in the buffer,return them if yes or wait some more if no
|
||||
// fetchLoop has built up the recv buffer. Check if there are complete
|
||||
// records in the buffer, return them if yes or wait some more if no.
|
||||
recv (is_handshake = false) {
|
||||
const that = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
|
||||
@@ -2,8 +2,8 @@ export const globals = {
|
||||
// defaultNotaryIP/Port is the default IP address/port of the notary server.
|
||||
// If this IP address becomes unavailable, Pagesigner will query backupUrl for
|
||||
// a new notary's IP address and will save the new IP address in the preferences.
|
||||
defaultNotaryIP: '127.0.0.1',
|
||||
//defaultNotaryIP: '18.207.130.193',
|
||||
//defaultNotaryIP: '127.0.0.1',
|
||||
defaultNotaryIP: '100.27.49.242',
|
||||
defaultNotaryPort: 10011,
|
||||
// backupUrl is the URL to query to get the IP address of another notary
|
||||
// server in case if defaultNotaryIP is unreachable
|
||||
@@ -26,6 +26,6 @@ export const globals = {
|
||||
// if useNotaryNoSandbox is set to true, then we fetch notary's pubkey by
|
||||
// querying /getPubKey and trust it. This is only useful when notary runs
|
||||
// in a non-sandbox environment.
|
||||
//useNotaryNoSandbox: false
|
||||
useNotaryNoSandbox: true
|
||||
useNotaryNoSandbox: false
|
||||
//useNotaryNoSandbox: true
|
||||
};
|
||||
@@ -7,7 +7,8 @@ import {ba2str, b64decode, assert, ba2int, verifyAttestationDoc, ba2hex, eq,
|
||||
const rootsOfTrust = [
|
||||
'snap-0ccb00d0e0fb4d4da',
|
||||
'snap-07eda3ed4836f82fb',
|
||||
'snap-023d50ee97873a1f0'];
|
||||
'snap-023d50ee97873a1f0',
|
||||
'snap-0e50af508006037dc'];
|
||||
// URLFetcher trusted enclave measurements, see
|
||||
// https://github.com/tlsnotary/URLFetcher
|
||||
const URLFetcherPCR0 = 'f70217239e8a1cb0f3c010b842a279e2b8d30d3700d7e4722fef22291763479a13783dc76d5219fabbd7e5aa92a7b255';
|
||||
|
||||
@@ -4,8 +4,10 @@ import WorkerPool from './WorkerPool.js';
|
||||
|
||||
export class GCWorker extends WorkerPool{
|
||||
// class CGWorker provides convenience functions to speak to the web worker
|
||||
constructor(numWorkers, processMonitor){
|
||||
super(numWorkers, chrome.extension.getURL('core/twopc/webWorkers/gcworker.js'));
|
||||
constructor(numWorkers, processMonitor, ){
|
||||
// you can replace gcworker_wasm.js with gcworker_purejs.js on systems where
|
||||
// WebAssembly is not available
|
||||
super(numWorkers, chrome.extension.getURL('core/twopc/webWorkers/gcworker_wasm.js'));
|
||||
// pm is an instance of ProcessMonitor
|
||||
this.pm = processMonitor;
|
||||
}
|
||||
@@ -21,11 +23,10 @@ export class GCWorker extends WorkerPool{
|
||||
resolve();
|
||||
}
|
||||
else {
|
||||
console.log('unexpected response from worker');
|
||||
throw('unexpected response from worker');
|
||||
}
|
||||
};
|
||||
const obj = {msg: 'parse', data: circuit_ab};
|
||||
const obj = {msg: 'parse', circuit: circuit_ab};
|
||||
worker.postMessage(obj);
|
||||
}));
|
||||
}
|
||||
@@ -44,42 +45,43 @@ export class GCWorker extends WorkerPool{
|
||||
}
|
||||
|
||||
// pm is an instance of ProgressMonitor
|
||||
async garbleBatch(count, obj){
|
||||
async garbleBatch(count){
|
||||
const batch = [];
|
||||
for (let i=0; i < count; i++){
|
||||
batch.push(obj);
|
||||
// we dont pass any args when garbling
|
||||
batch.push({});
|
||||
}
|
||||
const output = await this.workerPool(batch, this.garbleBatchDoWork, this.pm, 'garbling');
|
||||
return output;
|
||||
}
|
||||
|
||||
// batchItem is empty because we dont pass any args
|
||||
garbleBatchDoWork(batchItem, worker){
|
||||
return new Promise(function(resolve) {
|
||||
worker.onmessage = function(event) {
|
||||
worker['isResolved'] = true;
|
||||
resolve(event.data);
|
||||
};
|
||||
worker.postMessage( {msg: 'garble', data: batchItem});
|
||||
worker.postMessage( {msg: 'garble'});
|
||||
});
|
||||
}
|
||||
|
||||
async evaluateBatch(batch){
|
||||
//const output = await this.workerPool(batch, this.evaluateBatchDoWork, this.pm, 'evaluating');
|
||||
const output = await this.workerPool(batch, this.evaluateBatchDoWork);
|
||||
return output;
|
||||
return await this.workerPool(batch, this.evaluateBatchDoWork);
|
||||
}
|
||||
|
||||
evaluateBatchDoWork(batchItem, worker){
|
||||
const ga = batchItem[0];
|
||||
const il = batchItem[0];
|
||||
const tt = batchItem[1];
|
||||
const obj = {msg: 'setTruthTable', data: tt.buffer};
|
||||
const dt = batchItem[2];
|
||||
const obj = {msg: 'setTruthTables', tt: tt.buffer};
|
||||
worker.postMessage(obj);
|
||||
return new Promise(function(resolve) {
|
||||
worker.onmessage = function(event) {
|
||||
worker['isResolved'] = true;
|
||||
resolve(event.data);
|
||||
};
|
||||
const obj = {msg: 'evaluate', data: ga.buffer};
|
||||
const obj = {msg: 'evaluate', il: il.buffer, dt: dt.buffer};
|
||||
worker.postMessage(obj);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -378,9 +378,12 @@ export class GHASH {
|
||||
allBits = [].concat(allBits, bytesToBits(this.shares[key]).reverse());
|
||||
}
|
||||
this.lastChoiceBits = allBits;
|
||||
// TODO rename requestMaskedOT to createRequest and
|
||||
// rename the rest to createResponse, parseResponse
|
||||
return this.otR.createRequest(allBits);
|
||||
if (allBits.length == 0){
|
||||
// no block agregation is needed
|
||||
return new Uint8Array(0);
|
||||
} else {
|
||||
return this.otR.createRequest(allBits);
|
||||
}
|
||||
}
|
||||
|
||||
// add NOtary's XTable to our share of the tag
|
||||
@@ -400,18 +403,17 @@ export class GHASH {
|
||||
getPowerShares(hisXTables, powersOfH){
|
||||
const stratKeys = sortKeys(this.lastStrategy);
|
||||
// for each strategy we have 128 values for 1st factor and 128 values for 2nd factor
|
||||
const chunks = splitIntoChunks(hisXTables, 256*16);
|
||||
const xTablePair = splitIntoChunks(hisXTables, 256*16);
|
||||
for (let j=0; j < stratKeys.length; j++){
|
||||
const oddPower = stratKeys[j];
|
||||
if (oddPower > this.maxOddPowerNeeded){
|
||||
assert(chunks.length === j);
|
||||
assert(xTablePair.length === j);
|
||||
break;
|
||||
}
|
||||
let xorSum = new Uint8Array(16).fill(0); // start with 0 sum
|
||||
// TODO find a better name for subChunks
|
||||
const subChunks = splitIntoChunks(chunks[j], 16);
|
||||
const xTableRow = splitIntoChunks(xTablePair[j], 16);
|
||||
for (let i=0; i < 256; i++){
|
||||
xorSum = xor(xorSum, subChunks[i]);
|
||||
xorSum = xor(xorSum, xTableRow[i]);
|
||||
}
|
||||
const Cx = powersOfH[this.lastStrategy[oddPower][0]];
|
||||
const Cy = powersOfH[this.lastStrategy[oddPower][1]];
|
||||
|
||||
@@ -5,8 +5,8 @@ export class Garbler{
|
||||
constructor(parent){
|
||||
// this.s is the TWOPC class
|
||||
this.s = parent;
|
||||
// this.garbledC will contain truth table, output labels and input labels for each
|
||||
// circuit after it is garbled
|
||||
// this.garbledC will contain input labels, truth tables and decoding table
|
||||
// for each circuit after it is garbled
|
||||
this.garbledC = [];
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export class Garbler{
|
||||
// one by one
|
||||
const allPromises = [];
|
||||
for (let cNo = 1; cNo < this.s.cs.length; cNo++){
|
||||
if (cNo === 5){
|
||||
if (cNo === 6){
|
||||
allPromises.push(Promise.resolve('empty'));
|
||||
continue;
|
||||
}
|
||||
@@ -23,42 +23,42 @@ export class Garbler{
|
||||
allPromises.push(worker.garble());
|
||||
}
|
||||
|
||||
const allTt = [];
|
||||
const allOl = [];
|
||||
const allIl = [];
|
||||
// while non-c5 circuits are being garbled, we saturate the CPU with
|
||||
// parallel garbling of c5
|
||||
const outputs = await this.s.workers[5].garbleBatch(this.s.C5Count, {});
|
||||
for (let i=0; i < this.s.C5Count; i++){
|
||||
const allTt = [];
|
||||
const allDt = [];
|
||||
// while non-c6 circuits are being garbled, we saturate the CPU with
|
||||
// parallel garbling of c6
|
||||
const outputs = await this.s.workers[6].garbleBatch(this.s.C6Count, {});
|
||||
for (let i=0; i < this.s.C6Count; i++){
|
||||
const out = outputs[i];
|
||||
allTt.push(new Uint8Array(out.tt));
|
||||
allOl.push(new Uint8Array(out.ol));
|
||||
allIl.push(new Uint8Array(out.il));
|
||||
allTt.push(new Uint8Array(out.tt));
|
||||
allDt.push(new Uint8Array(out.dt));
|
||||
}
|
||||
this.garbledC[5] = {
|
||||
this.garbledC[6] = {
|
||||
il: concatTA(...allIl),
|
||||
tt: concatTA(...allTt),
|
||||
ol: concatTA(...allOl),
|
||||
il: concatTA(...allIl)};
|
||||
dt: concatTA(...allDt)};
|
||||
|
||||
// all non-c5 circuits have been garbled by now
|
||||
// all non-c6 circuits have been garbled by now
|
||||
const allRv = await Promise.all(allPromises);
|
||||
|
||||
for (let cNo=1; cNo < this.s.cs.length; cNo++){
|
||||
const rv = allRv[cNo-1];
|
||||
if (cNo === 5){
|
||||
continue; // c5 already dealt with
|
||||
if (cNo === 6){
|
||||
continue; // c6 already dealt with
|
||||
}
|
||||
this.garbledC[cNo] = {
|
||||
il: new Uint8Array(rv.il),
|
||||
tt: new Uint8Array(rv.tt),
|
||||
ol: new Uint8Array(rv.ol),
|
||||
il: new Uint8Array(rv.il)};
|
||||
dt: new Uint8Array(rv.dt)};
|
||||
}
|
||||
}
|
||||
|
||||
// Notary's inputs are always the first inputs in the circuit
|
||||
getNotaryLabels(cNo){
|
||||
// exeCount is how many executions of this circuit we need
|
||||
const exeCount = [0, 1, 1, 1, 1, this.s.C5Count, this.s.C6Count][cNo];
|
||||
const exeCount = [0, 1, 1, 1, 1, 1, this.s.C6Count, 1][cNo];
|
||||
const il = this.garbledC[cNo].il;
|
||||
const c = this.s.cs[cNo];
|
||||
const ilArray = [];
|
||||
@@ -73,7 +73,7 @@ export class Garbler{
|
||||
|
||||
// Client's inputs always come after the Notary's inputs in the circuit
|
||||
getClientLabels(clientInputs, cNo){
|
||||
const repeatCount = [0, 1, 1, 1, 1, this.s.C5Count, this.s.C6Count][cNo];
|
||||
const repeatCount = [0, 1, 1, 1, 1, 1, this.s.C6Count, 1][cNo];
|
||||
const il = this.garbledC[cNo].il;
|
||||
const c = this.s.cs[cNo];
|
||||
const ilArray = [];
|
||||
|
||||
@@ -147,7 +147,6 @@ export class OTReceiver extends OTCommon{
|
||||
}
|
||||
this.receivedSoFar += choiceBits.length;
|
||||
this.expectingResponseSize = 0;
|
||||
console.log('this.receivedSoFar', this.receivedSoFar);
|
||||
return concatTA(...decodedArr);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import {ba2hex, assert, getRandom, bytesToBits, concatTA, int2ba, str2ba,
|
||||
innerHash, xor, eq, sha256, ba2int, splitIntoChunks, bitsToBytes,
|
||||
pubkeyPEM2raw, checkExpiration, wait} from '../utils.js';
|
||||
pubkeyPEM2raw, checkExpiration, AESCTRdecrypt} from '../utils.js';
|
||||
import {Garbler} from './Garbler.js';
|
||||
import {Evaluator} from './Evaluator.js';
|
||||
import {Paillier2PC} from './Paillier2PC.js';
|
||||
@@ -25,12 +25,9 @@ export class TWOPC {
|
||||
this.notary = notary;
|
||||
// number of client request encr counter blocks to produce
|
||||
const noOfAESBlocks = Math.ceil(plaintextLen/16);
|
||||
// const noOfGctrBlocks = Math.ceil(noOfAESBlocks /8)
|
||||
const noOfGctrBlocks = 1;
|
||||
// C5Count is the number of c5 circuit executions
|
||||
this.C5Count = noOfAESBlocks;
|
||||
console.log('need to evaluate ', this.C5Count, ' c5 circuits');
|
||||
this.C6Count = noOfGctrBlocks;
|
||||
// C6Count is the number of c6 circuit executions
|
||||
this.C6Count = noOfAESBlocks;
|
||||
console.log('need to evaluate ', this.C6Count, ' c6 circuits');
|
||||
// shares of TLS session keys
|
||||
this.cwkShare = null;
|
||||
this.swkShare = null;
|
||||
@@ -44,7 +41,7 @@ export class TWOPC {
|
||||
this.uid = Math.random().toString(36).slice(-10);
|
||||
// cs is an array of circuits, where each circuit is an object with the fields:
|
||||
// gatesBlob, gatesCount, wiresCount, notaryInputSize, clientInputSize,
|
||||
// outputSize, andGateCount, outputsSizes
|
||||
// outputSize, andGateCount, outputsSizes. Count starts at 1
|
||||
this.cs = Object.keys(circuits).map(k => circuits[k]);
|
||||
// start count of circuits with 1, push an empty element to index 0
|
||||
this.cs.splice(0, 0, undefined);
|
||||
@@ -52,10 +49,11 @@ export class TWOPC {
|
||||
// to know how many bits each output value has in order to parse the output
|
||||
this.cs[1]['outputsSizes'] = [256, 256];
|
||||
this.cs[2]['outputsSizes'] = [256, 256];
|
||||
this.cs[3]['outputsSizes'] = [128, 128, 32, 32, 128, 128, 128];
|
||||
this.cs[4]['outputsSizes'] = [128, 128, 128, 96];
|
||||
this.cs[5]['outputsSizes'] = [128];
|
||||
this.cs[3]['outputsSizes'] = [128, 128, 32, 32];
|
||||
this.cs[4]['outputsSizes'] = [128, 128, 128];
|
||||
this.cs[5]['outputsSizes'] = [128, 128, 128, 96];
|
||||
this.cs[6]['outputsSizes'] = [128];
|
||||
this.cs[7]['outputsSizes'] = [128];
|
||||
// output is the output of the circuit as array of bytes
|
||||
this.output = Array(this.cs.length);
|
||||
// Commits are used to ensure malicious security of garbled circuits
|
||||
@@ -103,7 +101,7 @@ export class TWOPC {
|
||||
this.otCountSend = 0;
|
||||
// exeCount is how many executions of each circuit we need. Circuit numbering
|
||||
// starts from 1
|
||||
const exeCount = [0, 1, 1, 1, 1, this.C5Count, this.C6Count];
|
||||
const exeCount = [0, 1, 1, 1, 1, 1, this.C6Count, 1];
|
||||
for (let i=1; i < this.cs.length; i++){
|
||||
this.otCountRecv += this.cs[i].clientInputSize * exeCount[i];
|
||||
this.otCountSend += this.cs[i].notaryInputSize * exeCount[i];
|
||||
@@ -114,6 +112,14 @@ export class TWOPC {
|
||||
// otS is the sender of Oblivious Transfer
|
||||
this.otS = new OTSender(this.otCountSend);
|
||||
this.ghash.setOTReceiver(this.otR);
|
||||
this.blobSize = this.getBlobSize();
|
||||
// decrLabels are decrypted input labels corresponding to client_write_key
|
||||
// (cwk) and client_write_iv (civ) for circuit 4 (c4) and circuit 6 (c6).
|
||||
// It is a two-dimensional array [A][B], where A is an array of 160 elements
|
||||
// (each element corresponding to the bit of [cwk|civ]) and B contains as
|
||||
// many elements as there are executions of c4 + c6. Each element of B is a
|
||||
// 16-byte input label. B[0] are labels for c4.
|
||||
this.decrLabels = [];
|
||||
}
|
||||
|
||||
// destroy de-registers listeners, terminates workers
|
||||
@@ -174,10 +180,15 @@ export class TWOPC {
|
||||
// [REF 1] Steps 13,15,17,20,22
|
||||
const [verify_data, c3_p2inner, c3_p1inner] = await this.phase3(msInnerHashState);
|
||||
// for readability, indexing of masks starts from 1
|
||||
const masks3 = [0, 16, 16, 4, 4, 16, 16, 16].map(x => getRandom(x));
|
||||
const masks3 = [0, 16, 16, 4, 4].map(x => getRandom(x));
|
||||
const input3 = [c3_p1inner, c3_p2inner, ...masks3.slice(1)];
|
||||
const c3Out = (await this.runCircuit(input3, 3))[0];
|
||||
const [encFinished, tag] = await this.phase4(c3Out, masks3, verify_data);
|
||||
await this.phase4(c3Out, masks3);
|
||||
const masks4 = [0, 16, 16, 16].map(x => getRandom(x));
|
||||
const input4 = [this.swkShare, this.cwkShare, this.sivShare, this.civShare,
|
||||
...masks4.slice(1)];
|
||||
const c4Out = (await this.runCircuit(input4, 4))[0];
|
||||
const [encFinished, tag] = await this.phase5(c4Out, masks4, verify_data);
|
||||
return [encFinished, tag, verify_data];
|
||||
}
|
||||
|
||||
@@ -194,28 +205,28 @@ export class TWOPC {
|
||||
const a0 = seed;
|
||||
// [REF 1] Step 25
|
||||
const a1inner = innerHash(this.innerState_MS, a0);
|
||||
const a1 = await this.send('c4_pre1', a1inner);
|
||||
const a1 = await this.send('c5_pre1', a1inner);
|
||||
// [REF 1] Step 27
|
||||
const p1inner = innerHash(this.innerState_MS, concatTA(a1, seed));
|
||||
|
||||
// for readability, mask indexing starts from 1
|
||||
const masks4 = [0, 16, 16, 16, 12].map(x => getRandom(x));
|
||||
const input4 = [p1inner, this.swkShare, this.sivShare, sf_nonce,
|
||||
...masks4.slice(1)];
|
||||
const masks5 = [0, 16, 16, 16, 12].map(x => getRandom(x));
|
||||
const input5 = [p1inner, this.swkShare, this.sivShare, sf_nonce,
|
||||
...masks5.slice(1)];
|
||||
// [REF 1] Step 28
|
||||
const c4out = (await this.runCircuit(input4, 4))[0];
|
||||
const c5out = (await this.runCircuit(input5, 5))[0];
|
||||
let o = 0; // offset
|
||||
// parse outputs
|
||||
const H1MaskedTwice = c4out.slice(o, o+=16);
|
||||
const gctrSFMasked = c4out.slice(o, o+=16);
|
||||
const encCounterMasked = c4out.slice(o, o+=16);
|
||||
const verify_dataMasked = c4out.slice(o, o+=12);
|
||||
const H1MaskedTwice = c5out.slice(o, o+=16);
|
||||
const gctrSFMasked = c5out.slice(o, o+=16);
|
||||
const encCounterMasked = c5out.slice(o, o+=16);
|
||||
const verify_dataMasked = c5out.slice(o, o+=12);
|
||||
// unmask outputs
|
||||
// H1 xor-masked by notary is our share of H^1, the mask is notary's share of H^1
|
||||
const H1share = xor(H1MaskedTwice, masks4[1]);
|
||||
const gctrShare = xor(gctrSFMasked, masks4[2]);
|
||||
const encCounter = xor(encCounterMasked, masks4[3]);
|
||||
const verify_data = xor(verify_dataMasked, masks4[4]);
|
||||
const H1share = xor(H1MaskedTwice, masks5[1]);
|
||||
const gctrShare = xor(gctrSFMasked, masks5[2]);
|
||||
const encCounter = xor(encCounterMasked, masks5[3]);
|
||||
const verify_data = xor(verify_dataMasked, masks5[4]);
|
||||
|
||||
const sf = concatTA(new Uint8Array([0x14, 0x00, 0x00, 0x0c]), verify_data);
|
||||
const encSF = xor(sf, encCounter);
|
||||
@@ -224,7 +235,7 @@ export class TWOPC {
|
||||
const ghashSF = new GHASH(1);
|
||||
ghashSF.setOTReceiver(this.otR);
|
||||
const otReq = await ghashSF.buildFinRequest(H1share);
|
||||
const step3resp = await this.send('c4_step3', concatTA(encSF, otReq));
|
||||
const step3resp = await this.send('c5_step3', concatTA(encSF, otReq));
|
||||
assert(step3resp.length == 16 + 256*32);
|
||||
o = 0;
|
||||
const notaryTagShare = step3resp.slice(o, o+=16);
|
||||
@@ -239,22 +250,25 @@ export class TWOPC {
|
||||
assert (eq(sf_tag, tagFromPowersOfH));
|
||||
}
|
||||
|
||||
// runs circuit 5
|
||||
// runs circuit 6
|
||||
async getEncryptedCounters() {
|
||||
const masks5 = [];
|
||||
let input5 = [];
|
||||
const masks6 = [];
|
||||
let input6 = [];
|
||||
// we only send 1 TLS record with a fixed nonce 2
|
||||
const fixedNonce = int2ba(2, 2);
|
||||
for (let i = 0; i < this.C5Count; i++) {
|
||||
for (let i = 0; i < this.C6Count; i++) {
|
||||
const counter = int2ba(2 + i, 2);
|
||||
masks5[i] = getRandom(16);
|
||||
input5 = [].concat(input5, [this.cwkShare, this.civShare, masks5[i],
|
||||
masks6[i] = getRandom(16);
|
||||
input6 = [].concat(input6, [
|
||||
this.cwkShare,
|
||||
this.civShare,
|
||||
masks6[i],
|
||||
fixedNonce, 10, counter, 10]);
|
||||
}
|
||||
const c5out = await this.runCircuit(input5, 5);
|
||||
const c6out = await this.runCircuit(input6, 6);
|
||||
const encCounters = [];
|
||||
for (let i=0; i < this.C5Count; i++){
|
||||
encCounters.push(xor(c5out[i], masks5[i]));
|
||||
for (let i=0; i < this.C6Count; i++){
|
||||
encCounters.push(xor(c6out[i], masks6[i]));
|
||||
}
|
||||
return encCounters;
|
||||
}
|
||||
@@ -263,23 +277,18 @@ export class TWOPC {
|
||||
// client's gctr share. The notary's gctr share is already xored into the output
|
||||
// of getTagFromPowersOfH
|
||||
async getGctrBlocks(){
|
||||
const masks6 = [];
|
||||
let input6 = [];
|
||||
for (let i = 0; i < this.C6Count; i++) {
|
||||
const nonce = int2ba(2 + i, 2);
|
||||
masks6.push(getRandom(16));
|
||||
input6 = [].concat(input6, [this.cwkShare, this.civShare, masks6[i], nonce]);
|
||||
}
|
||||
const c6out = await this.runCircuit(input6, 6);
|
||||
const c6CommitSalt = await this.send('checkC6Commit', this.myCommit[6]);
|
||||
// the commit which notary computed on their side must be equal to our commit
|
||||
const saltedCommit = await sha256(concatTA(this.myCommit[6], c6CommitSalt));
|
||||
assert (eq(saltedCommit, this.hisSaltedCommit[6]));
|
||||
const mask7 = getRandom(16);
|
||||
let input7 = [];
|
||||
const nonce = int2ba(2, 2);
|
||||
input7 = [].concat(input7, [this.cwkShare, this.civShare, mask7, nonce]);
|
||||
|
||||
const c7out = await this.runCircuit(input7, 7);
|
||||
const c7CommitSalt = await this.send('checkC7Commit', this.myCommit[7]);
|
||||
// the commit which notary computed on their side must be equal to our commit
|
||||
const saltedCommit = await sha256(concatTA(this.myCommit[7], c7CommitSalt));
|
||||
assert (eq(saltedCommit, this.hisSaltedCommit[7]));
|
||||
const gctrBlocks = [];
|
||||
for (let i=0; i < this.C6Count; i++){
|
||||
gctrBlocks.push(xor(c6out[i], masks6[i]));
|
||||
}
|
||||
gctrBlocks.push(xor(c7out[0], mask7));
|
||||
return gctrBlocks;
|
||||
}
|
||||
|
||||
@@ -324,59 +333,96 @@ export class TWOPC {
|
||||
}
|
||||
|
||||
|
||||
// send data to the notary
|
||||
async send(cmd, data = new Uint8Array(0), returnPromise = false, willEncrypt = true){
|
||||
// send sends a command with data to the notary.
|
||||
// returnPromise if set to true, will return a promise
|
||||
// (instead of data resolved from the promise).
|
||||
// willEncrypt if set to true, will encrypt the request to notary.
|
||||
// willDecrypt if set to true, will decrypt the response from the notary.
|
||||
// downloadProgress if set to true, will update the progress monitor with
|
||||
// the amount of data already downloaded.
|
||||
async send(cmd, data = new Uint8Array(0), returnPromise = false, willEncrypt = true,
|
||||
willDecrypt = true, downloadProgress = false){
|
||||
let to_be_sent;
|
||||
if (willEncrypt){
|
||||
if (willEncrypt && data.length > 0){
|
||||
to_be_sent = await this.encryptToNotary(this.clientKey, data);
|
||||
}
|
||||
else {
|
||||
to_be_sent = data;
|
||||
}
|
||||
|
||||
function timeout(ms, promise) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
setTimeout(function() {
|
||||
reject(new Error('Timed out while while transfering data.'));
|
||||
}, ms);
|
||||
promise.then(resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
const fetchPromise = fetch(
|
||||
'http://'+this.notary.IP+':'+globals.defaultNotaryPort+'/'+cmd+'?'+this.uid,
|
||||
{
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
cache: 'no-store',
|
||||
// agent: is only used when running in nodejs
|
||||
agent: typeof(window) === 'undefined' ? keepAliveAgent : null,
|
||||
//keepalive: true, // <-- trips up chrome
|
||||
//keepalive: true, // <-- trips up Chrome
|
||||
body: to_be_sent.buffer
|
||||
});
|
||||
const that = this;
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
timeout(20000, fetchPromise).then(function(resp){
|
||||
resp.arrayBuffer().then(function(ab){
|
||||
const payload = new Uint8Array(ab);
|
||||
if (payload.length !== 0){
|
||||
if (willEncrypt){
|
||||
that.decryptFromNotary(that.notaryKey, payload).then(function(decrypted){
|
||||
resolve(decrypted);
|
||||
});
|
||||
}
|
||||
else {
|
||||
resolve(payload);
|
||||
}
|
||||
}
|
||||
else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}).catch(err =>{
|
||||
reject(err);
|
||||
async function timeout(ms, promise) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
setTimeout(function() {
|
||||
reject(new Error('Timed out while while transfering data.'));
|
||||
}, ms);
|
||||
promise.then(resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
const that = this;
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
const promise = new Promise(async function(resolve) {
|
||||
const resp = await timeout(20000, fetchPromise);
|
||||
let payload;
|
||||
if (downloadProgress) {
|
||||
const reader = resp.body.getReader();
|
||||
let soFarBytes = 0;
|
||||
let chunks = [];
|
||||
// lastUpdateTime is when we last sent progress update. We don't want
|
||||
// send the update more often than every 1 sec. Set initial value so
|
||||
// that the very first update triggers immediately.
|
||||
let lastUpdateTime = (Date.now()/1000) + 1;
|
||||
let done2;
|
||||
do {
|
||||
const {done, value} = await reader.read();
|
||||
done2 = done;
|
||||
if (done) break;
|
||||
chunks.push(value);
|
||||
soFarBytes += value.length;
|
||||
const now = (Date.now()/1000);
|
||||
if (now - lastUpdateTime > 1){
|
||||
if (that.pm) that.pm.update('download', {'current': soFarBytes, 'total': that.blobSize});
|
||||
lastUpdateTime = now;
|
||||
}
|
||||
} while (!done2);
|
||||
payload = new Uint8Array(soFarBytes);
|
||||
let position = 0;
|
||||
for(let chunk of chunks) {
|
||||
payload.set(chunk, position);
|
||||
position += chunk.length;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const ab = await resp.arrayBuffer();
|
||||
payload = new Uint8Array(ab);
|
||||
}
|
||||
if (payload.length == 0){
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
if (!willDecrypt) {
|
||||
resolve(payload);
|
||||
return;
|
||||
}
|
||||
const decrypted = await that.decryptFromNotary(that.notaryKey, payload);
|
||||
resolve(decrypted);
|
||||
return;
|
||||
}).catch(err =>{
|
||||
throw(err);
|
||||
});
|
||||
|
||||
if (returnPromise === true){
|
||||
return promise;
|
||||
}
|
||||
@@ -400,12 +446,11 @@ export class TWOPC {
|
||||
const [pubBytes, privKey] = await this.generateECKeypair();
|
||||
const init1Blob = await this.send('init1', concatTA(
|
||||
pubBytes,
|
||||
int2ba(this.C5Count, 2),
|
||||
int2ba(this.C6Count, 2),
|
||||
int2ba(this.otCountRecv, 4),
|
||||
int2ba(this.otCountSend, 4),
|
||||
A,
|
||||
seedCommit), false, false);
|
||||
seedCommit), false, false, false);
|
||||
|
||||
let o = 0;
|
||||
this.eValidFrom = init1Blob.slice(o, o+=4);
|
||||
@@ -438,17 +483,24 @@ export class TWOPC {
|
||||
const hisReceiverCommit = init1Resp.slice(o, o+=32);
|
||||
const hisSenderallBs = init1Resp.slice(o, o+=128*32);
|
||||
const hisSenderSeedShare = init1Resp.slice(o, o+=16);
|
||||
const totalBytes = ba2int(init1Resp.slice(o, o+=4));
|
||||
assert(init1Resp.length == o);
|
||||
|
||||
// as soon as communication keys are established, start downloading
|
||||
// and uploading
|
||||
const that = this;
|
||||
const getBlobPromise = new Promise((resolve) => {
|
||||
this.getBlobFromNotary(totalBytes)
|
||||
.then(function(hisBlob){
|
||||
that.blob = that.processBlob(hisBlob);
|
||||
resolve();
|
||||
});
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
const blobTransferPromise = new Promise(async function(resolve){
|
||||
const blob = await that.getBlobFromNotary();
|
||||
that.blob = that.processBlob(blob);
|
||||
await garbleAllPromise;
|
||||
let blobOut = new Uint8Array(0);
|
||||
// proceed to upload
|
||||
for (let i=1; i < that.cs.length; i++){
|
||||
blobOut = concatTA(blobOut, that.g.garbledC[i].tt);
|
||||
blobOut = concatTA(blobOut, that.g.garbledC[i].dt);
|
||||
}
|
||||
await that.sendBlobToNotary(blobOut);
|
||||
resolve();
|
||||
});
|
||||
|
||||
const [encryptedColumns, receiverSeedShare, x, t] =
|
||||
@@ -467,58 +519,55 @@ export class TWOPC {
|
||||
await this.otS.setupStep2(hisReceiverEncryptedColumns, hisReceiverSeedShare,
|
||||
hisReceiverX, hisReceiverT);
|
||||
|
||||
await garbleAllPromise;
|
||||
let blobOut = [];
|
||||
for (let i=1; i < this.cs.length; i++){
|
||||
blobOut.push(this.g.garbledC[i].tt);
|
||||
blobOut.push(this.g.garbledC[i].ol);
|
||||
}
|
||||
|
||||
// start blob upload as soon as download finishes
|
||||
await getBlobPromise;
|
||||
await this.sendBlobToNotary(concatTA(...blobOut));
|
||||
await blobTransferPromise;
|
||||
this.preComputed = true;
|
||||
}
|
||||
|
||||
// sendBlobToNotary uploads a blob to the notary and periodically publishes the download progress
|
||||
// to the UI. Upload is in 1MB chunks.
|
||||
async sendBlobToNotary(blob){
|
||||
const that = this;
|
||||
let sentSoFar = 0;
|
||||
let oneMB = 1024*1024;
|
||||
let chunkCount = Math.ceil(blob.length / oneMB);
|
||||
for (let i=0; i < chunkCount; i++){
|
||||
if (i % 10 == 0){
|
||||
// dont monopolize the upload bandwidth, allow preComputeOT to squeeze in some data
|
||||
await wait(200);
|
||||
// getBlobSize returns the size of all truth tables and encoding tables for
|
||||
// all garbled circuits executions
|
||||
getBlobSize(){
|
||||
let total = 0;
|
||||
for (let i=1; i < this.cs.length; i++){
|
||||
let truthTablesSize = this.cs[i].andGateCount *48;
|
||||
let decryptionTableSize = Math.ceil(this.cs[i].outputSize/8);
|
||||
if (i === 6){
|
||||
truthTablesSize = this.C6Count * truthTablesSize;
|
||||
decryptionTableSize = this.C6Count * decryptionTableSize;
|
||||
}
|
||||
let chunk = blob.slice(sentSoFar, sentSoFar + oneMB);
|
||||
await that.send('setBlobChunk', chunk);
|
||||
sentSoFar += chunk.length;
|
||||
if (that.pm) that.pm.update('upload', {'current': i+1, 'total': chunkCount});
|
||||
total += ( truthTablesSize + decryptionTableSize);
|
||||
}
|
||||
that.send('setBlobChunk', str2ba('magic: no more data'));
|
||||
return total;
|
||||
}
|
||||
|
||||
// getNotaryBlob downloads a blob from notary in 1 MB chunks publishes the download progress
|
||||
// to the UI.
|
||||
async getBlobFromNotary (totalBytes){
|
||||
const allChunks = [];
|
||||
console.log('totalBytes is', totalBytes);
|
||||
async sendBlobToNotary(blob){
|
||||
const uploadPromise = this.send('setBlob', blob, true, false);
|
||||
let uploadFinished = false;
|
||||
const that = this;
|
||||
|
||||
let soFarBytes = 0;
|
||||
while (soFarBytes < totalBytes){
|
||||
let needBytes = 1024*1024;
|
||||
if (needBytes + soFarBytes > totalBytes){
|
||||
needBytes = totalBytes - soFarBytes;
|
||||
// since fetch() does not implement upload progress, we continuously query
|
||||
// the server about upload progress
|
||||
const interval = setInterval(async function(){
|
||||
if (uploadFinished == true) {
|
||||
clearInterval(interval);
|
||||
return;
|
||||
}
|
||||
let chunk = await this.send('getBlobChunk', int2ba(needBytes, 4));
|
||||
allChunks.push(chunk);
|
||||
soFarBytes += chunk.length;
|
||||
if (this.pm) this.pm.update('download', {'current': soFarBytes, 'total': totalBytes});
|
||||
}
|
||||
const rv = concatTA(...allChunks);
|
||||
assert(rv.length === totalBytes);
|
||||
const progress = await that.send('getUploadProgress');
|
||||
const byteCount = ba2int(progress);
|
||||
if (that.pm) that.pm.update('upload', {'current': byteCount, 'total': blob.length});
|
||||
console.log('returned progress ', byteCount, blob.length);
|
||||
}, 1000);
|
||||
|
||||
await uploadPromise;
|
||||
uploadFinished = true;
|
||||
if (this.pm) this.pm.update('upload', {'current': blob.length, 'total': blob.length});
|
||||
}
|
||||
|
||||
// getBlobFromNotary downloads truth tables and decoding table from notary
|
||||
// with a progress monitor
|
||||
async getBlobFromNotary (){
|
||||
const rv = await this.send('getBlob', new Uint8Array(0), false, false, false, true);
|
||||
// set download progress to 100%
|
||||
if (this.pm) this.pm.update('download', {'current': this.blobSize, 'total': this.blobSize});
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -549,11 +598,11 @@ export class TWOPC {
|
||||
}
|
||||
|
||||
async setupWorkers(){
|
||||
const maxWorkerCount = 3; // only needed for c5
|
||||
const workerCount = [0, 1, 1, 1, 1, maxWorkerCount, 1];
|
||||
const maxWorkerCount = 2; // only needed for circuit 6
|
||||
const workerCount = [0, 1, 1, 1, 1, 1, maxWorkerCount, 1];
|
||||
for (let i=1; i < this.cs.length; i++){
|
||||
this.workers[i] = new GCWorker(workerCount[i], this.pm);
|
||||
this.workers[i].parse(this.cs[i]);
|
||||
await this.workers[i].parse(this.cs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,31 +677,37 @@ export class TWOPC {
|
||||
return [verify_data, p2inner, p1inner];
|
||||
}
|
||||
|
||||
async phase4(c3out, masks3, verify_data) {
|
||||
async phase4(c3out, masks3) {
|
||||
let o = 0; // offset
|
||||
// parse all outputs
|
||||
const swkMaskedTwice = c3out.slice(o, o+=16);
|
||||
const cwkMaskedTwice = c3out.slice(o, o+=16);
|
||||
const sivMaskedTwice = c3out.slice(o, o+=4);
|
||||
const civMaskedTwice = c3out.slice(o, o+=4);
|
||||
const H1MaskedTwice = c3out.slice(o, o+=16);
|
||||
const gctrMaskedTwice = c3out.slice(o, o+=16);
|
||||
const encCounterMasked = c3out.slice(o, o+=16);
|
||||
// unmask all outputs
|
||||
this.swkShare = xor(swkMaskedTwice, masks3[1]);
|
||||
this.cwkShare = xor(cwkMaskedTwice, masks3[2]);
|
||||
this.sivShare = xor(sivMaskedTwice, masks3[3]);
|
||||
this.civShare = xor(civMaskedTwice, masks3[4]);
|
||||
const myGctrShare = xor(gctrMaskedTwice, masks3[6]);
|
||||
const encCounter = xor(encCounterMasked, masks3[7]);
|
||||
}
|
||||
|
||||
async phase5(c4out, masks4, verify_data) {
|
||||
let o = 0; // offset
|
||||
// parse all outputs
|
||||
const H1MaskedTwice = c4out.slice(o, o+=16);
|
||||
const gctrMaskedTwice = c4out.slice(o, o+=16);
|
||||
const encCounterMasked = c4out.slice(o, o+=16);
|
||||
// unmask all outputs
|
||||
const myGctrShare = xor(gctrMaskedTwice, masks4[2]);
|
||||
const encCounter = xor(encCounterMasked, masks4[3]);
|
||||
|
||||
// H1 xor-masked by notary is our (i.e. the client's) share of H^1,
|
||||
// notary's mask is his share of H^1.
|
||||
const H1share = xor(H1MaskedTwice, masks3[5]);
|
||||
const H1share = xor(H1MaskedTwice, masks4[1]);
|
||||
const clientFinished = concatTA(new Uint8Array([0x14, 0x00, 0x00, 0x0c]), verify_data);
|
||||
const encCF = xor(clientFinished, encCounter);
|
||||
const otReq = await this.ghash.buildFinRequest(H1share);
|
||||
const step3resp = await this.send('c3_step3', concatTA(encCF, otReq));
|
||||
const step3resp = await this.send('c4_step3', concatTA(encCF, otReq));
|
||||
assert(step3resp.length === 16 + 256*32);
|
||||
o = 0;
|
||||
const notaryTagShare = step3resp.slice(o, o+=16);
|
||||
@@ -670,17 +725,17 @@ export class TWOPC {
|
||||
// getClientFinishedResumed runs a circuit to obtain data needed to construct the
|
||||
// Client Finished messages for cases when we need TLS session resumption
|
||||
// TODO: this is wip, not yet fully implemented
|
||||
async getClientFinishedResumed(hs_hash){
|
||||
const seed = concatTA(str2ba('client finished'), hs_hash);
|
||||
const a1inner = innerHash(this.innerState_MS, seed);
|
||||
const a1 = await this.send('c7_step1', a1inner);
|
||||
const p1inner = innerHash(this.innerState_MS, a1);
|
||||
// p1inner is the client's input to c7
|
||||
const mask1 = getRandom(16);
|
||||
const mask2 = getRandom(12);
|
||||
const nonFixedBits = bytesToBits(concatTA(
|
||||
mask2, mask2, this.civShare, this.cwkShare, p1inner));
|
||||
}
|
||||
// async getClientFinishedResumed(hs_hash){
|
||||
// const seed = concatTA(str2ba('client finished'), hs_hash);
|
||||
// const a1inner = innerHash(this.innerState_MS, seed);
|
||||
// const a1 = await this.send('c7_step1', a1inner);
|
||||
// const p1inner = innerHash(this.innerState_MS, a1);
|
||||
// // p1inner is the client's input to c7
|
||||
// const mask1 = getRandom(16);
|
||||
// const mask2 = getRandom(12);
|
||||
// const nonFixedBits = bytesToBits(concatTA(
|
||||
// mask2, mask2, this.civShare, this.cwkShare, p1inner));
|
||||
// }
|
||||
|
||||
// getServerKeyShare returns client's xor share of server_write_key
|
||||
// and server_write_iv
|
||||
@@ -698,9 +753,10 @@ export class TWOPC {
|
||||
// The notary is also evaluating this circuit on his end.
|
||||
async runCircuit(inputs, cNo) {
|
||||
console.log('in runCircuit', cNo);
|
||||
// inputs is an array of inputs in the order in which the inputs appear in the c*.casm files.
|
||||
// inputBitsE is an array of inputs for Client-the-Evaluator in the order
|
||||
// in which the inputs appear in the c*.casm files.
|
||||
// The circuit expects the least bit of the input to be the first bit
|
||||
let inputBits = [];
|
||||
let inputBitsE = [];
|
||||
for (let i=0; i < inputs.length; i++){
|
||||
let bits = bytesToBits(inputs[i]);
|
||||
if (typeof(inputs[i+1]) == 'number'){
|
||||
@@ -709,15 +765,30 @@ export class TWOPC {
|
||||
bits = bits.slice(0, inputs[i+1]);
|
||||
i += 1;
|
||||
}
|
||||
inputBits = [].concat(inputBits, bits);
|
||||
inputBitsE = [].concat(inputBitsE, bits);
|
||||
}
|
||||
// inputBitsG is an array of inputs for Client-the-Garbler
|
||||
const inputBitsG = inputBitsE.slice();
|
||||
// c is circuit
|
||||
const c = this.cs[cNo];
|
||||
// how many times to repeat evaluation (> 1 only for circuits 5&7 )
|
||||
const repeatCount = [0, 1, 1, 1, 1, this.C5Count, this.C6Count][cNo];
|
||||
// how many times to repeat evaluation (> 1 only for circuit 6 )
|
||||
const repeatCount = [0, 1, 1, 1, 1, 1, this.C6Count, 1][cNo];
|
||||
|
||||
// for circuit 1, there was no previous commit
|
||||
const prevCommit = cNo > 1 ? this.myCommit[cNo-1] : new Uint8Array(0);
|
||||
const otReq = this.otR.createRequest(inputBits);
|
||||
|
||||
if (cNo==6){
|
||||
// here we remove inputs this.cwkShare and this.civShare (first 160 bits)
|
||||
// because we already have active labels for those bits for each circuit 6
|
||||
// execution. We obtained them when we were running circuit 4.
|
||||
let newInputBits = [];
|
||||
const chunkSize = inputBitsE.length/this.C6Count;
|
||||
for (let i=0; i < this.C6Count; i++){
|
||||
newInputBits = [].concat(newInputBits, inputBitsE.slice(i*chunkSize+160, (i+1)*chunkSize));
|
||||
}
|
||||
inputBitsE = newInputBits;
|
||||
}
|
||||
const otReq = this.otR.createRequest(inputBitsE);
|
||||
const blob1 = await this.send(`c${cNo}_step1`, concatTA(prevCommit, otReq));
|
||||
|
||||
let o = 0;
|
||||
@@ -730,56 +801,53 @@ export class TWOPC {
|
||||
|
||||
// all notary's labels
|
||||
const allNotaryLabels = blob1.slice(o, o += c.notaryInputSize*16*repeatCount);
|
||||
const hisOtResp = blob1.slice(o, o += inputBits.length*32);
|
||||
// we may need to drop some bits if their amount is not a multiple of 8
|
||||
let hisOtResp = blob1.slice(o, o += inputBitsE.length*32);
|
||||
const hisOtReq = blob1.slice(o, o += 1+ c.notaryInputSize/8*repeatCount);
|
||||
assert(blob1.length == o);
|
||||
|
||||
const allClientLabels = this.otR.parseResponse(inputBits, hisOtResp);
|
||||
let allClientLabels;
|
||||
if (cNo == 4){
|
||||
const encryptedLabels = blob1.slice(o, o += 160*2*16*(1+this.C6Count));
|
||||
assert(blob1.length == o);
|
||||
allClientLabels = await this.processC4Labels(inputBitsE, hisOtResp, encryptedLabels);
|
||||
}
|
||||
else {
|
||||
assert(blob1.length == o);
|
||||
allClientLabels = this.otR.parseResponse(inputBitsE, hisOtResp);
|
||||
}
|
||||
if (cNo == 6){
|
||||
allClientLabels = this.processC6Labels(allClientLabels);
|
||||
}
|
||||
|
||||
const notaryLabels = this.g.getNotaryLabels(cNo);
|
||||
const otResp = this.otS.processRequest(hisOtReq, notaryLabels);
|
||||
const clientLabels = this.g.getClientLabels(inputBits, cNo);
|
||||
const clientLabels = this.g.getClientLabels(inputBitsG, cNo);
|
||||
const sendPromise = this.send(`c${cNo}_step2`, concatTA(otResp, clientLabels), true);
|
||||
|
||||
// collect batch of evaluation
|
||||
// collect batch for evaluation
|
||||
const batch = [];
|
||||
const clBatch = splitIntoChunks(allClientLabels, c.clientInputSize*16);
|
||||
const nlBatch = splitIntoChunks(allNotaryLabels, c.notaryInputSize*16);
|
||||
const ttBatch = splitIntoChunks(this.blob[`c${cNo}_tt`], c.andGateCount * 48);
|
||||
const dtBatch = splitIntoChunks(this.blob[`c${cNo}_dt`], Math.ceil(c.outputSize/8));
|
||||
for (let r=0; r < repeatCount; r++){
|
||||
batch.push([concatTA(nlBatch[r], clBatch[r]), ttBatch[r]]);
|
||||
batch.push([concatTA(nlBatch[r], clBatch[r]), ttBatch[r], dtBatch[r]]);
|
||||
}
|
||||
|
||||
// perform evaluation
|
||||
console.time('evaluateBatch');
|
||||
const evalOutputLabelsBatch = await this.e.evaluateBatch(batch, cNo);
|
||||
const evalPlaintextBatch = await this.e.evaluateBatch(batch, cNo);
|
||||
console.timeEnd('evaluateBatch');
|
||||
|
||||
// process evaluation outputs
|
||||
const output = [];
|
||||
// garbOutputLabelsBatch is output labels which the garbler sent to us
|
||||
assert(this.blob[`c${cNo}_ol`].length === c.outputSize*32*repeatCount);
|
||||
const garbOutputLabelsBatch = splitIntoChunks(this.blob[`c${cNo}_ol`], c.outputSize*32);
|
||||
for (let r=0; r < repeatCount; r++){
|
||||
const evalOL = splitIntoChunks(evalOutputLabelsBatch[r], 16);
|
||||
const garbOL = splitIntoChunks(garbOutputLabelsBatch[r], 16);
|
||||
const bits = [];
|
||||
for (let i = 0; i < c.outputSize; i++) {
|
||||
if (eq(evalOL[i], garbOL[i*2])) {
|
||||
bits.push(0);
|
||||
} else if (eq(evalOL[i], garbOL[i*2+1])) {
|
||||
bits.push(1);
|
||||
} else {
|
||||
console.log('evaluator output does not match the garbled outputs');
|
||||
}
|
||||
}
|
||||
// reverse output bits so that the values of the output be placed in
|
||||
// the same order as they appear in the *.casm files
|
||||
// plaintext has a padding in MSB to make it a multiple of 8 bits. We
|
||||
// decompose into bits and drop the padding
|
||||
const bits = bytesToBits(evalPlaintextBatch[r]).slice(0, c.outputSize);
|
||||
const out = this.parseOutputBits(cNo, bits);
|
||||
this.output[cNo] = out;
|
||||
output.push(out);
|
||||
}
|
||||
console.log('output is', output);
|
||||
this.myCommit[cNo] = await sha256(concatTA(...output));
|
||||
|
||||
const cStep2Out = await sendPromise;
|
||||
@@ -787,6 +855,68 @@ export class TWOPC {
|
||||
return output;
|
||||
}
|
||||
|
||||
async processC4Labels(inputBits, hisOtResp, encryptedLabels){
|
||||
assert(encryptedLabels.length % 320 == 0);
|
||||
// notary also sends input labels for client_write_key and
|
||||
// client_write_iv for c4 and c6. Each set of labels corresponding
|
||||
// to the same input bit is encrypted with a unique key.
|
||||
|
||||
// replace the labels for cwk/ciw with zeroes. Then the value returned by
|
||||
// parseResponse() in places of the zeroes will be the decryption keys
|
||||
// needed to decrypt each encrypted set of labels.
|
||||
const newOtResp = concatTA(
|
||||
hisOtResp.slice(0, 128*32),
|
||||
new Uint8Array(128*32).fill(0),
|
||||
hisOtResp.slice(256*32, 288*32),
|
||||
new Uint8Array(32*32).fill(0),
|
||||
hisOtResp.slice(320*32));
|
||||
|
||||
const allClientLabels = this.otR.parseResponse(inputBits, newOtResp);
|
||||
|
||||
// now allClientLabels in cwk/civ position have keys needed to decrypt labels
|
||||
const decrKeys = splitIntoChunks(concatTA(
|
||||
allClientLabels.slice(128*16, 256*16),
|
||||
allClientLabels.slice(288*16, 320*16)), 16);
|
||||
|
||||
const encrLabels = splitIntoChunks(encryptedLabels, encryptedLabels.length/320);
|
||||
// choiceBits for cwk/civ
|
||||
const choiceBits = [].concat(inputBits.slice(128, 256), inputBits.slice(288, 320));
|
||||
for (let i=0; i < 160; i++){
|
||||
// the decryption key only decrypts labels corresponding to our choice bit
|
||||
let toDecrypt;
|
||||
if (choiceBits[i] == 0){
|
||||
toDecrypt = encrLabels[i*2];
|
||||
} else {
|
||||
toDecrypt = encrLabels[i*2+1];
|
||||
}
|
||||
this.decrLabels[i] = splitIntoChunks(await AESCTRdecrypt(decrKeys[i], toDecrypt), 16);
|
||||
}
|
||||
|
||||
// insert decrypted labels for c4 (they are at index 0),
|
||||
// the rest of the labels will be used for circuit 6
|
||||
const newLabels = concatTA(
|
||||
allClientLabels.slice(0, 128*16),
|
||||
concatTA(...this.decrLabels.slice(0, 128).map(function(arr){return arr[0];})),
|
||||
allClientLabels.slice(256*16, 288*16),
|
||||
concatTA(...this.decrLabels.slice(128, 160).map(function(arr){return arr[0];})),
|
||||
allClientLabels.slice(320*16));
|
||||
return newLabels;
|
||||
}
|
||||
|
||||
processC6Labels(allClientLabels){
|
||||
// insert the labels for client_write_key and client_write_iv which we got
|
||||
// during circuit 4 execution (see func processC4Labels)
|
||||
const labelsPerExecution = splitIntoChunks(allClientLabels, allClientLabels.length/this.C6Count);
|
||||
const updatedLabels = [];
|
||||
for (let i=0; i < this.C6Count; i++) {
|
||||
updatedLabels.push(concatTA(
|
||||
...this.decrLabels.map(function(arr){return arr[i+1];}),
|
||||
labelsPerExecution[i]));
|
||||
}
|
||||
return concatTA(...updatedLabels);
|
||||
}
|
||||
|
||||
|
||||
// parseOutputBits converts the output bits of the circuit number "cNo" into
|
||||
// a slice of output values in the same order as they appear in the *.casm files
|
||||
parseOutputBits(cNo, outBits){
|
||||
@@ -801,27 +931,26 @@ export class TWOPC {
|
||||
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
// blob contains decryption table for all consecutive circuit execution followed
|
||||
// by truth tables for all consecutive circuit execution
|
||||
processBlob(blob) {
|
||||
// split up blob into [truth table + output labels] for each circuit
|
||||
const obj = {};
|
||||
let offset = 0;
|
||||
for (let i=1; i < this.cs.length; i++){
|
||||
let truthTableSize = this.cs[i].andGateCount *48;
|
||||
let outputLabelsSize = this.cs[i].outputSize *32;
|
||||
if (i === 5){
|
||||
truthTableSize = this.C5Count * truthTableSize;
|
||||
outputLabelsSize = this.C5Count * outputLabelsSize;
|
||||
let decryptionTableSize = Math.ceil(this.cs[i].outputSize/8);
|
||||
if (i === 6){
|
||||
decryptionTableSize = this.C6Count * decryptionTableSize;
|
||||
}
|
||||
else if ( i == 6 ){
|
||||
truthTableSize = this.C6Count * truthTableSize;
|
||||
outputLabelsSize = this.C6Count * outputLabelsSize;
|
||||
obj['c'+String(i)+'_dt'] = blob.subarray(offset, offset+decryptionTableSize);
|
||||
offset += decryptionTableSize;
|
||||
}
|
||||
for (let i=1; i < this.cs.length; i++){
|
||||
let truthTablesSize = this.cs[i].andGateCount *48;
|
||||
if (i === 6){
|
||||
truthTablesSize = this.C6Count * truthTablesSize;
|
||||
}
|
||||
console.log('truthtable for ', i, ' is size: ', truthTableSize);
|
||||
obj['c'+String(i)+'_tt'] = blob.subarray(offset, offset+truthTableSize);
|
||||
offset += truthTableSize;
|
||||
console.log('ol for ', i, ' is size: ', outputLabelsSize);
|
||||
obj['c'+String(i)+'_ol'] = blob.subarray(offset, offset+outputLabelsSize);
|
||||
offset += outputLabelsSize;
|
||||
obj['c'+String(i)+'_tt'] = blob.subarray(offset, offset+truthTablesSize);
|
||||
offset += truthTablesSize;
|
||||
}
|
||||
assert(blob.length === offset);
|
||||
return obj;
|
||||
|
||||
Submodule core/twopc/circuits updated: cb87bc30e7...d1bfca1197
35
core/twopc/webWorkers/README
Normal file
35
core/twopc/webWorkers/README
Normal file
@@ -0,0 +1,35 @@
|
||||
This folder contains files used by WebWorkers.
|
||||
|
||||
serializeCircuits.js:
|
||||
Used on first startup to convert Bristol Fashion circuits into a compact
|
||||
binary format.
|
||||
|
||||
gcworker_wasm.js:
|
||||
Garbler and evaluator which uses garbler.wasm for most operations. We still perform
|
||||
Salsa20 hashing in JS (because it is more performant than in wasm).
|
||||
|
||||
gcworker_purejs.js:
|
||||
Garbler and evaluator written in pure JS (~5-10x slower than the wasm version).
|
||||
Not used by default but can serve as a drop-in replacement in cases if wasm
|
||||
cannot be loaded.
|
||||
|
||||
garbler.wasm:
|
||||
Garbler and evaluator loaded by gcworker_wasm.js. It was compiled from Rust
|
||||
into WebAssembly.
|
||||
|
||||
src/:
|
||||
Rust source code for garbler.wasm. Not actually in use by the extenstion. It
|
||||
is here just for convenience.
|
||||
|
||||
To compile garbler.wasm from source deterministically:
|
||||
|
||||
cd src/
|
||||
sudo apt update && sudo apt install -y build-essential
|
||||
# the official way to install rustup is:
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source $HOME/.cargo/env
|
||||
rustup install 1.58.1
|
||||
rustup +1.58.1 target add wasm32-unknown-unknown --toolchain 1.58.1
|
||||
RUSTFLAGS='-C target-feature=+simd128' cargo +1.58.1 build --target wasm32-unknown-unknown --release
|
||||
The result in target/wasm32-unknown-unknown/release/garbler.wasm will be bit
|
||||
identical to garbler.wasm
|
||||
BIN
core/twopc/webWorkers/garbler.wasm
Executable file
BIN
core/twopc/webWorkers/garbler.wasm
Executable file
Binary file not shown.
@@ -2,15 +2,18 @@
|
||||
|
||||
// gcworker.js is a WebWorker which performs garbling and evaluation
|
||||
// of garbled circuits.
|
||||
// This is a fixed-key-cipher garbling method from BHKR13 https://eprint.iacr.org/2013/426.pdf
|
||||
// This is a fixed-key-cipher garbling method from BHKR13
|
||||
// https://eprint.iacr.org/2013/426.pdf
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
var parentPort_;
|
||||
let circuit = null;
|
||||
let truthTable = null;
|
||||
let truthTables = null;
|
||||
let andGateIdx = null;
|
||||
|
||||
// fixedKey is used by randomOracle(). We need a 32-byte key because we use Salsa20. The last 4
|
||||
// bytes will be filled with the index of the circuit's wire.
|
||||
// fixedKey is used by randomOracle(). We need a 32-byte key because we use
|
||||
// Salsa20. The last 4 bytes will be filled with the tweak, i.e. the index of
|
||||
// the circuit's wire.
|
||||
const fixedKey = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 0, 0, 0, 0]);
|
||||
// sigma is Salsa's constant "expand 32-byte k"
|
||||
@@ -18,9 +21,9 @@ const sigma = new Uint8Array([101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98,
|
||||
121, 116, 101, 32, 107]);
|
||||
// randomPool will be filled with data from getRandom
|
||||
let randomPool;
|
||||
// randomPoolOffset will be moved after data was read from randomPool
|
||||
// randomPoolOffset will be shifted after data was read from randomPool
|
||||
let randomPoolOffset = 0;
|
||||
let garbledAssigment;
|
||||
let garblingAssigment, evaluationAssigment;
|
||||
var crypto_;
|
||||
|
||||
if (typeof(importScripts) !== 'undefined') {
|
||||
@@ -50,46 +53,46 @@ if (typeof(importScripts) !== 'undefined') {
|
||||
}
|
||||
|
||||
function processMessage(obj){
|
||||
const msg = obj.msg;
|
||||
const data = obj.data;
|
||||
if (msg === 'parse'){
|
||||
circuit = data;
|
||||
garbledAssigment = new Uint8Array(32*(circuit.wiresCount));
|
||||
// no need to respond to this message
|
||||
if (obj.msg === 'parse'){
|
||||
circuit = obj.circuit;
|
||||
garblingAssigment = new Uint8Array(32*(circuit.wiresCount));
|
||||
evaluationAssigment = new Uint8Array(16*(circuit.wiresCount));
|
||||
postMsg('DONE');
|
||||
}
|
||||
else if (msg === 'setTruthTable'){
|
||||
assert(data.byteLength == circuit.andGateCount*48);
|
||||
truthTable = new Uint8Array(data);
|
||||
else if (obj.msg === 'setTruthTables'){
|
||||
assert(obj.tt.byteLength == circuit.andGateCount*48);
|
||||
truthTables = new Uint8Array(obj.tt);
|
||||
// no need to respond to this
|
||||
}
|
||||
else if (msg === 'garble'){
|
||||
else if (obj.msg === 'garble'){
|
||||
if (circuit == null){
|
||||
console.log('error: need to parse circuit before garble');
|
||||
return;
|
||||
}
|
||||
console.time('garbling done in');
|
||||
const reuseLabels = (data == undefined) ? undefined : data.reuseLabels;
|
||||
const reuseIndexes = (data == undefined) ? undefined : data.reuseIndexes;
|
||||
const reuseR = (data == undefined) ? undefined : data.reuseR;
|
||||
|
||||
const [truthTable, inputLabels, outputLabels, R] = garble(circuit, garbledAssigment, reuseLabels, reuseIndexes, reuseR);
|
||||
assert (truthTable.length === circuit.andGateCount*48);
|
||||
console.time('worker_garble');
|
||||
const [inputLabels, truthTables, decodingTable] = garble(circuit, garblingAssigment);
|
||||
assert (inputLabels.length === circuit.clientInputSize*32 + circuit.notaryInputSize*32);
|
||||
assert (outputLabels.length === circuit.outputSize*32);
|
||||
const obj = {'tt': truthTable.buffer, 'il': inputLabels.buffer, 'ol': outputLabels.buffer, 'R': R};
|
||||
console.timeEnd('garbling done in');
|
||||
postMsg(obj, [truthTable.buffer, inputLabels.buffer, outputLabels.buffer]);
|
||||
assert (truthTables.length === circuit.andGateCount*48);
|
||||
assert (decodingTable.length === Math.ceil(circuit.outputSize/8));
|
||||
const obj = {'il': inputLabels.buffer, 'tt': truthTables.buffer, 'dt': decodingTable.buffer};
|
||||
postMsg(obj, [inputLabels.buffer, truthTables.buffer, decodingTable.buffer]);
|
||||
console.timeEnd('worker_garble');
|
||||
}
|
||||
else if (msg === 'evaluate'){
|
||||
if (circuit == null || truthTable == null){
|
||||
else if (obj.msg === 'evaluate'){
|
||||
if (circuit == null || truthTables == null){
|
||||
console.log('error: need to parse circuit and set truth table before evaluate');
|
||||
return;
|
||||
}
|
||||
const garbledAssigment = new Uint8Array(16*(circuit.wiresCount));
|
||||
const inputLabels = new Uint8Array(data);
|
||||
assert (inputLabels.length === circuit.clientInputSize*16 + circuit.notaryInputSize*16);
|
||||
const outputLabels = evaluate(circuit, garbledAssigment, truthTable, inputLabels);
|
||||
assert (outputLabels.length === circuit.outputSize*16);
|
||||
postMsg(outputLabels.buffer);
|
||||
console.time('worker_evaluate');
|
||||
const inputSize = circuit.clientInputSize*16 + circuit.notaryInputSize*16;
|
||||
const inputLabels = new Uint8Array(obj.il);
|
||||
const decodingTable = new Uint8Array(obj.dt);
|
||||
assert (inputLabels.length === inputSize);
|
||||
assert (decodingTable.length === Math.ceil(circuit.outputSize/8));
|
||||
const plaintext = evaluate(circuit, evaluationAssigment, inputLabels, truthTables, decodingTable);
|
||||
assert (plaintext.length === Math.ceil(circuit.outputSize/8));
|
||||
postMsg(plaintext.buffer);
|
||||
console.timeEnd('worker_evaluate');
|
||||
}
|
||||
else {
|
||||
console.log('Error: unexpected message in worker');
|
||||
@@ -125,39 +128,23 @@ function generateInputLabels(count, R){
|
||||
return newLabels;
|
||||
}
|
||||
|
||||
function garble(circuit, ga, reuseLabels = new Uint8Array(0), reuseIndexes = [], R){
|
||||
|
||||
function garble(circuit, ga){
|
||||
const inputCount = circuit.notaryInputSize + circuit.clientInputSize;
|
||||
fillRandom((inputCount+1+circuit.andGateCount)*16);
|
||||
R = R || newR();
|
||||
const R = newR();
|
||||
|
||||
// generate new labels
|
||||
const newLabels = generateInputLabels(inputCount - reuseIndexes.length, R);
|
||||
const inputLabels = generateInputLabels(inputCount, R);
|
||||
ga.set(inputLabels);
|
||||
const truthTables = new Uint8Array(circuit.andGateCount*48);
|
||||
|
||||
// set both new and reused labels into ga
|
||||
let reusedCount = 0; // how many reused inputs were already put into ga
|
||||
let newInputsCount = 0; // how many new inputs were already put into ga
|
||||
|
||||
for (let i = 0; i < inputCount; i++) {
|
||||
if (reuseIndexes.includes(i)) {
|
||||
ga.set(reuseLabels.subarray(reusedCount*32, reusedCount*32+32), i*32);
|
||||
reusedCount += 1;
|
||||
}
|
||||
else {
|
||||
ga.set(newLabels.subarray(newInputsCount*32, newInputsCount*32+32), i*32);
|
||||
newInputsCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const truthTable = new Uint8Array(circuit.andGateCount*48);
|
||||
|
||||
let andGateIdx = 0;
|
||||
andGateIdx = 0;
|
||||
// garble gates
|
||||
for (let i = 0; i < circuit.gatesCount; i++) {
|
||||
const gateBlob = circuit.gatesBlob.subarray(i*10, i*10+10);
|
||||
const op = ['XOR', 'AND', 'INV'][gateBlob[0]];
|
||||
if (op === 'AND') {
|
||||
garbleAnd(gateBlob, R, ga, truthTable, andGateIdx, i);
|
||||
garbleAnd(gateBlob, R, ga, truthTables, andGateIdx, i);
|
||||
andGateIdx += 1;
|
||||
} else if (op === 'XOR') {
|
||||
garbleXor(gateBlob, R, ga);
|
||||
@@ -168,8 +155,13 @@ function garble(circuit, ga, reuseLabels = new Uint8Array(0), reuseIndexes = [],
|
||||
}
|
||||
}
|
||||
|
||||
return [truthTable, ga.slice(0, inputCount*32), ga.slice(-circuit.outputSize*32), R];
|
||||
|
||||
// get decoding table: LSB of label0 for each output wire
|
||||
const outLSBs = [];
|
||||
for (let i = 0; i < circuit.outputSize; i++){
|
||||
outLSBs.push(ga[ga.length-circuit.outputSize*32+i*32+15] & 1);
|
||||
}
|
||||
const decodingTable = bitsToBytes(outLSBs);
|
||||
return [inputLabels, truthTables, decodingTable];
|
||||
}
|
||||
|
||||
|
||||
@@ -185,7 +177,7 @@ const garbleAnd = function (gateBlob, R, ga, tt, andGateIdx, id) {
|
||||
const in2_0 = gaGetIndexG(ga, in2, 0);
|
||||
const in2_1 = gaGetIndexG(ga, in2, 1);
|
||||
|
||||
// rows is a truthtable if wire labels in a canonical order, the third
|
||||
// rows is a truthtable in a canonical order, the third
|
||||
// item shows an index of output label
|
||||
const rows = [
|
||||
[in1_0, in2_0, 0],
|
||||
@@ -239,12 +231,11 @@ const garbleXor = function (gateBlob, R, ga) {
|
||||
const out = threeBytesToInt(gateBlob.subarray(7, 10));
|
||||
|
||||
const in1_0 = gaGetIndexG(ga, in1, 0);
|
||||
const in1_1 = gaGetIndexG(ga, in1, 1);
|
||||
const in2_0 = gaGetIndexG(ga, in2, 0);
|
||||
const in2_1 = gaGetIndexG(ga, in2, 1);
|
||||
|
||||
gaSetIndexG(ga, out, 0, xor(in1_0, in2_0));
|
||||
gaSetIndexG(ga, out, 1, xor(xor(in1_1, in2_1), R, true));
|
||||
const label0 = xor(in1_0, in2_0);
|
||||
gaSetIndexG(ga, out, 0, label0);
|
||||
gaSetIndexG(ga, out, 1, xor(label0, R, true));
|
||||
};
|
||||
|
||||
|
||||
@@ -259,18 +250,17 @@ const garbleNot = function (gateBlob, ga) {
|
||||
gaSetIndexG(ga, out, 1, in1_0.slice());
|
||||
};
|
||||
|
||||
function evaluate (circuit, ga, tt, inputLabels) {
|
||||
function evaluate (circuit, ga, inputLabels, truthTables, decodingTable) {
|
||||
// set input labels
|
||||
ga.set(inputLabels);
|
||||
|
||||
// evaluate one gate at a time
|
||||
let numberOfANDGates = 0;
|
||||
console.time('worker_evaluate');
|
||||
for (let i = 0; i < circuit.gatesCount; i++) {
|
||||
const gateBlob = circuit.gatesBlob.subarray(i*10, i*10+10);
|
||||
const op = ['XOR', 'AND', 'INV'][gateBlob[0]];
|
||||
if (op === 'AND') {
|
||||
evaluateAnd(ga, tt, numberOfANDGates, gateBlob, i);
|
||||
evaluateAnd(ga, truthTables, numberOfANDGates, gateBlob, i);
|
||||
numberOfANDGates += 1;
|
||||
} else if (op === 'XOR') {
|
||||
evaluateXor(ga, gateBlob);
|
||||
@@ -280,9 +270,16 @@ function evaluate (circuit, ga, tt, inputLabels) {
|
||||
throw new Error(`Unrecognized gate: ${op}`);
|
||||
}
|
||||
}
|
||||
console.timeEnd('worker_evaluate');
|
||||
|
||||
return ga.slice((circuit.wiresCount-circuit.outputSize)*16, circuit.wiresCount*16);
|
||||
//decode output labels
|
||||
// get decoding table: LSB of label0 for each output wire
|
||||
const outLSBs = [];
|
||||
for (let i = 0; i < circuit.outputSize; i++){
|
||||
outLSBs.push(ga[ga.length-circuit.outputSize*16+i*16+15] & 1);
|
||||
}
|
||||
const encodings = bitsToBytes(outLSBs);
|
||||
const plaintext = xor(decodingTable, encodings);
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
const evaluateAnd = function (ga, tt, andGateIdx, gateBlob, id) {
|
||||
@@ -453,6 +450,20 @@ function concatTA (...arr){
|
||||
return newArray;
|
||||
}
|
||||
|
||||
// convert an array of 0/1 (with least bit at index 0) to Uint8Array
|
||||
function bitsToBytes(arr){
|
||||
assert(arr.length % 8 === 0);
|
||||
const ba = new Uint8Array(arr.length/8);
|
||||
for (let i=0; i < ba.length; i++){
|
||||
let sum = 0;
|
||||
for (let j=0; j < 8; j++){
|
||||
sum += arr[i*8+j] * (2**j);
|
||||
}
|
||||
ba[ba.length-1-i] = sum;
|
||||
}
|
||||
return ba;
|
||||
}
|
||||
|
||||
// use Salsa20 as a random permutator. Instead of the nonce, we feed the data that needs
|
||||
// to be permuted.
|
||||
function Salsa20(key, data){
|
||||
454
core/twopc/webWorkers/gcworker_wasm.js
Normal file
454
core/twopc/webWorkers/gcworker_wasm.js
Normal file
@@ -0,0 +1,454 @@
|
||||
/* global process, global */
|
||||
|
||||
// gcworker_wasm.js is a WebWorker which performs garbling and evaluation
|
||||
// of garbled circuits.
|
||||
// This is a fixed-key-cipher garbling method from BHKR13 https://eprint.iacr.org/2013/426.pdf
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
let parentPort_;
|
||||
let loadFile;
|
||||
let module;
|
||||
let circuit = null;
|
||||
let truthTables = null;
|
||||
|
||||
|
||||
// offsets are set once for the life of the wasm module
|
||||
let malloc_ptr;
|
||||
// cryptographically random data
|
||||
let randOffset, randSize;
|
||||
// decoding table
|
||||
let dtOffset, dtSize;
|
||||
// output (for garbler this is decoding table, tof evaluator this is plaintext)
|
||||
let outOffset, outSize;
|
||||
// truth tables
|
||||
let ttOffset, ttSize;
|
||||
// input labels
|
||||
let ilOffset, ilSize;
|
||||
let wasm = null;
|
||||
|
||||
// fixedKey is used by randomOracle(). We need a 32-byte key because we use Salsa20. The last 4
|
||||
// bytes will be filled with the index of the circuit's wire.
|
||||
const fixedKey = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28]);
|
||||
// sigma is Salsa's constant "expand 32-byte k"
|
||||
const sigma = new Uint8Array([101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98,
|
||||
121, 116, 101, 32, 107]);
|
||||
var crypto_;
|
||||
|
||||
if (typeof(importScripts) !== 'undefined') {
|
||||
crypto_ = self.crypto;
|
||||
crypto_.getRandomValues = function(arr){
|
||||
return new Uint8Array([...Array(arr.length).keys()]);
|
||||
};
|
||||
loadFile = loadFileBrowser;
|
||||
self.onmessage = function(event) {
|
||||
processMessage(event.data);
|
||||
};
|
||||
} else {
|
||||
// we are in nodejs
|
||||
import('module').then((mod) => {
|
||||
module = mod;
|
||||
// we cannot use the "import" keyword here because on first pass the browser unconditionaly
|
||||
// parses this if clause and will error out if "import" is found.
|
||||
// using process.argv instead of import.meta.url to get the name of this script
|
||||
const filePath = 'file://' + process.argv[1];
|
||||
// this workaround allows to require() from ES6 modules, which is not allowed by default
|
||||
const require = module.createRequire(filePath);
|
||||
const { parentPort } = require('worker_threads');
|
||||
parentPort_ = parentPort;
|
||||
const { Crypto } = require('@peculiar/webcrypto');
|
||||
crypto_ = new Crypto();
|
||||
const perf = {'now':function(){return 0;}};
|
||||
global.performance = perf;
|
||||
loadFile = loadFileNode;
|
||||
parentPort.on('message', msg => {
|
||||
processMessage(msg);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function loadFileBrowser(path) {
|
||||
const bytes = await fetch(path);
|
||||
return bytes.arrayBuffer();
|
||||
}
|
||||
|
||||
async function loadFileNode(file) {
|
||||
const filePath = 'file://' + process.argv[1];
|
||||
// this workaround allows to require() from ES6 modules, which is not allowed by default
|
||||
const require = module.createRequire(filePath);
|
||||
const path = require('path');
|
||||
const dir = path.dirname(process.argv[1]);
|
||||
const fs = require('fs');
|
||||
const data = fs.readFileSync(path.join(dir, file));
|
||||
return data;
|
||||
}
|
||||
|
||||
function processMessage(obj){
|
||||
const msg = obj.msg;
|
||||
// parse is called only once for the duration of the wasm module, even if
|
||||
// multiple garbling executions will be made.
|
||||
if (msg === 'parse'){
|
||||
circuit = obj.circuit;
|
||||
|
||||
loadFile('garbler.wasm').then(async function(ab){
|
||||
// we don't need to allocate any heap memory
|
||||
const memory = new WebAssembly.Memory({initial:0, maximum:0});
|
||||
const module = await WebAssembly.instantiate(ab, { js: {memory: memory}});
|
||||
wasm = module.instance.exports;
|
||||
assert(circuit.gatesBlob.length == circuit.gatesCount*10);
|
||||
malloc_ptr = wasm.initAll(
|
||||
circuit.gatesCount,
|
||||
circuit.wiresCount,
|
||||
circuit.notaryInputSize,
|
||||
circuit.clientInputSize,
|
||||
circuit.outputSize,
|
||||
circuit.andGateCount
|
||||
);
|
||||
let offset = malloc_ptr;
|
||||
// ipc1 and ipc2
|
||||
offset += (20+48);
|
||||
|
||||
const gateSize = circuit.gatesCount*10;
|
||||
const gatesBytesArr = new Uint8Array(wasm.memory.buffer, offset, gateSize);
|
||||
offset += gateSize;
|
||||
gatesBytesArr.set(circuit.gatesBlob);
|
||||
console.time('parse blob');
|
||||
wasm.parseGatesBlob();
|
||||
console.timeEnd('parse blob');
|
||||
|
||||
randOffset = offset;
|
||||
randSize = (circuit.notaryInputSize + circuit.clientInputSize + 1)*16;
|
||||
offset += randSize;
|
||||
|
||||
dtOffset = offset;
|
||||
dtSize = Math.ceil(circuit.outputSize/8);
|
||||
offset += dtSize;
|
||||
|
||||
outOffset = offset;
|
||||
outSize = Math.ceil(circuit.outputSize/8);
|
||||
offset += outSize;
|
||||
|
||||
ttOffset = offset;
|
||||
ttSize = circuit.andGateCount*48;
|
||||
offset += ttSize;
|
||||
|
||||
ilOffset = offset;
|
||||
ilSize = (circuit.notaryInputSize + circuit.clientInputSize)*32;
|
||||
offset += ilSize;
|
||||
postMsg('DONE');
|
||||
});
|
||||
}
|
||||
else if (msg === 'setTruthTables'){
|
||||
assert(obj.tt.byteLength == circuit.andGateCount*48);
|
||||
truthTables = new Uint8Array(obj.tt);
|
||||
}
|
||||
else if (msg === 'garble'){
|
||||
if (circuit == null){
|
||||
console.log('error: need to parse circuit before garble');
|
||||
return;
|
||||
}
|
||||
console.time('garbling done in');
|
||||
let randArr = new Uint8Array(wasm.memory.buffer, randOffset, randSize);
|
||||
randArr.set(crypto_.getRandomValues(new Uint8Array(randSize)));
|
||||
const dataBuf = new Uint8Array(wasm.memory.buffer, malloc_ptr, 16);
|
||||
const tweak = new Uint8Array(wasm.memory.buffer, malloc_ptr+16, 4);
|
||||
const in1 = new Uint8Array(wasm.memory.buffer, malloc_ptr+20, 16);
|
||||
const in2 = new Uint8Array(wasm.memory.buffer, malloc_ptr+20+16, 16);
|
||||
const in3 = new Uint8Array(wasm.memory.buffer, malloc_ptr+20+32, 16);
|
||||
wasm.startGarbler();
|
||||
for (let i=0; i < circuit.andGateCount; i++){
|
||||
wasm.resumeGarbler_0();
|
||||
Salsa20(dataBuf, tweak);
|
||||
wasm.resumeGarbler_1();
|
||||
Salsa20(in1, tweak);
|
||||
Salsa20(in2, tweak);
|
||||
Salsa20(in3, tweak);
|
||||
}
|
||||
wasm.finishGarbler();
|
||||
const inputLabels = new Uint8Array(wasm.memory.buffer, ilOffset, ilSize).slice();
|
||||
const truthTables = new Uint8Array(wasm.memory.buffer, ttOffset, ttSize).slice();
|
||||
const decodingTable = new Uint8Array(wasm.memory.buffer, outOffset, outSize).slice();
|
||||
const obj = {'il': inputLabels.buffer, 'tt': truthTables.buffer, 'dt': decodingTable.buffer};
|
||||
console.timeEnd('garbling done in');
|
||||
postMsg(obj, [inputLabels.buffer, truthTables.buffer, decodingTable.buffer]);
|
||||
}
|
||||
else if (msg === 'evaluate'){
|
||||
if (circuit == null || truthTables == null){
|
||||
console.log('error: need to parse circuit and set truth table before evaluate');
|
||||
return;
|
||||
}
|
||||
const inputLabels = new Uint8Array(obj.il);
|
||||
const decodingTable = new Uint8Array(obj.dt);
|
||||
assert (inputLabels.length === circuit.clientInputSize*16 + circuit.notaryInputSize*16);
|
||||
assert (decodingTable.length === Math.ceil(circuit.outputSize/8));
|
||||
const ilArr = new Uint8Array(wasm.memory.buffer, ilOffset, inputLabels.length);
|
||||
ilArr.set(inputLabels);
|
||||
const ttArr = new Uint8Array(wasm.memory.buffer, ttOffset, ttSize);
|
||||
ttArr.set(truthTables);
|
||||
const dtArr = new Uint8Array(wasm.memory.buffer, dtOffset, dtSize);
|
||||
dtArr.set(decodingTable);
|
||||
const dataBuf = new Uint8Array(wasm.memory.buffer, malloc_ptr, 16);
|
||||
const tweak = new Uint8Array(wasm.memory.buffer, malloc_ptr+16, 4);
|
||||
wasm.startEvaluator();
|
||||
for (let i=0; i < circuit.andGateCount; i++){
|
||||
wasm.resumeEvaluator_0();
|
||||
Salsa20(dataBuf, tweak);
|
||||
}
|
||||
wasm.finishEvaluator();
|
||||
const plaintext = new Uint8Array(wasm.memory.buffer, outOffset, outSize).slice();
|
||||
postMsg(plaintext.buffer);
|
||||
truthTables == null;
|
||||
}
|
||||
else {
|
||||
console.log('Error: unexpected message in worker');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function postMsg(value, transferList){
|
||||
if (typeof importScripts !== 'function'){
|
||||
parentPort_.postMessage({data:value}, transferList);
|
||||
} else {
|
||||
postMessage(value, transferList);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
console.trace();
|
||||
throw message || 'Assertion failed';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// use Salsa20 as a random permutator. Instead of the nonce, we feed the data that needs
|
||||
// to be permuted. The output will be placed into data;
|
||||
function Salsa20(data, t){
|
||||
core_salsa20(data, fixedKey, t, sigma);
|
||||
return data;
|
||||
}
|
||||
|
||||
// copied from https://github.com/dchest/tweetnacl-js/blob/master/nacl-fast.js
|
||||
// and modified to reuse input (p) as output and to output only 16 bytes. Also allows
|
||||
// to input a 28-byte key (k) and a 4-byte tweak (t).
|
||||
// modified parts are commented out
|
||||
// function core_salsa20(o, p, k, c) {
|
||||
function core_salsa20(p, k, t, c) {
|
||||
var j0 = c[ 0] & 0xff | (c[ 1] & 0xff)<<8 | (c[ 2] & 0xff)<<16 | (c[ 3] & 0xff)<<24,
|
||||
j1 = k[ 0] & 0xff | (k[ 1] & 0xff)<<8 | (k[ 2] & 0xff)<<16 | (k[ 3] & 0xff)<<24,
|
||||
j2 = k[ 4] & 0xff | (k[ 5] & 0xff)<<8 | (k[ 6] & 0xff)<<16 | (k[ 7] & 0xff)<<24,
|
||||
j3 = k[ 8] & 0xff | (k[ 9] & 0xff)<<8 | (k[10] & 0xff)<<16 | (k[11] & 0xff)<<24,
|
||||
j4 = k[12] & 0xff | (k[13] & 0xff)<<8 | (k[14] & 0xff)<<16 | (k[15] & 0xff)<<24,
|
||||
j5 = c[ 4] & 0xff | (c[ 5] & 0xff)<<8 | (c[ 6] & 0xff)<<16 | (c[ 7] & 0xff)<<24,
|
||||
j6 = p[ 0] & 0xff | (p[ 1] & 0xff)<<8 | (p[ 2] & 0xff)<<16 | (p[ 3] & 0xff)<<24,
|
||||
j7 = p[ 4] & 0xff | (p[ 5] & 0xff)<<8 | (p[ 6] & 0xff)<<16 | (p[ 7] & 0xff)<<24,
|
||||
j8 = p[ 8] & 0xff | (p[ 9] & 0xff)<<8 | (p[10] & 0xff)<<16 | (p[11] & 0xff)<<24,
|
||||
j9 = p[12] & 0xff | (p[13] & 0xff)<<8 | (p[14] & 0xff)<<16 | (p[15] & 0xff)<<24,
|
||||
j10 = c[ 8] & 0xff | (c[ 9] & 0xff)<<8 | (c[10] & 0xff)<<16 | (c[11] & 0xff)<<24,
|
||||
j11 = k[16] & 0xff | (k[17] & 0xff)<<8 | (k[18] & 0xff)<<16 | (k[19] & 0xff)<<24,
|
||||
j12 = k[20] & 0xff | (k[21] & 0xff)<<8 | (k[22] & 0xff)<<16 | (k[23] & 0xff)<<24,
|
||||
j13 = k[24] & 0xff | (k[25] & 0xff)<<8 | (k[26] & 0xff)<<16 | (k[27] & 0xff)<<24,
|
||||
//j14 = k[28] & 0xff | (k[29] & 0xff)<<8 | (k[30] & 0xff)<<16 | (k[31] & 0xff)<<24,
|
||||
// the commented out line was replaced with the line below
|
||||
j14 = t[0] & 0xff | (t[1] & 0xff)<<8 | (t[2] & 0xff)<<16 | (t[3] & 0xff)<<24,
|
||||
j15 = c[12] & 0xff | (c[13] & 0xff)<<8 | (c[14] & 0xff)<<16 | (c[15] & 0xff)<<24;
|
||||
|
||||
var x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7,
|
||||
x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14,
|
||||
x15 = j15, u;
|
||||
|
||||
for (var i = 0; i < 20; i += 2) {
|
||||
u = x0 + x12 | 0;
|
||||
x4 ^= u<<7 | u>>>(32-7);
|
||||
u = x4 + x0 | 0;
|
||||
x8 ^= u<<9 | u>>>(32-9);
|
||||
u = x8 + x4 | 0;
|
||||
x12 ^= u<<13 | u>>>(32-13);
|
||||
u = x12 + x8 | 0;
|
||||
x0 ^= u<<18 | u>>>(32-18);
|
||||
|
||||
u = x5 + x1 | 0;
|
||||
x9 ^= u<<7 | u>>>(32-7);
|
||||
u = x9 + x5 | 0;
|
||||
x13 ^= u<<9 | u>>>(32-9);
|
||||
u = x13 + x9 | 0;
|
||||
x1 ^= u<<13 | u>>>(32-13);
|
||||
u = x1 + x13 | 0;
|
||||
x5 ^= u<<18 | u>>>(32-18);
|
||||
|
||||
u = x10 + x6 | 0;
|
||||
x14 ^= u<<7 | u>>>(32-7);
|
||||
u = x14 + x10 | 0;
|
||||
x2 ^= u<<9 | u>>>(32-9);
|
||||
u = x2 + x14 | 0;
|
||||
x6 ^= u<<13 | u>>>(32-13);
|
||||
u = x6 + x2 | 0;
|
||||
x10 ^= u<<18 | u>>>(32-18);
|
||||
|
||||
u = x15 + x11 | 0;
|
||||
x3 ^= u<<7 | u>>>(32-7);
|
||||
u = x3 + x15 | 0;
|
||||
x7 ^= u<<9 | u>>>(32-9);
|
||||
u = x7 + x3 | 0;
|
||||
x11 ^= u<<13 | u>>>(32-13);
|
||||
u = x11 + x7 | 0;
|
||||
x15 ^= u<<18 | u>>>(32-18);
|
||||
|
||||
u = x0 + x3 | 0;
|
||||
x1 ^= u<<7 | u>>>(32-7);
|
||||
u = x1 + x0 | 0;
|
||||
x2 ^= u<<9 | u>>>(32-9);
|
||||
u = x2 + x1 | 0;
|
||||
x3 ^= u<<13 | u>>>(32-13);
|
||||
u = x3 + x2 | 0;
|
||||
x0 ^= u<<18 | u>>>(32-18);
|
||||
|
||||
u = x5 + x4 | 0;
|
||||
x6 ^= u<<7 | u>>>(32-7);
|
||||
u = x6 + x5 | 0;
|
||||
x7 ^= u<<9 | u>>>(32-9);
|
||||
u = x7 + x6 | 0;
|
||||
x4 ^= u<<13 | u>>>(32-13);
|
||||
u = x4 + x7 | 0;
|
||||
x5 ^= u<<18 | u>>>(32-18);
|
||||
|
||||
u = x10 + x9 | 0;
|
||||
x11 ^= u<<7 | u>>>(32-7);
|
||||
u = x11 + x10 | 0;
|
||||
x8 ^= u<<9 | u>>>(32-9);
|
||||
u = x8 + x11 | 0;
|
||||
x9 ^= u<<13 | u>>>(32-13);
|
||||
u = x9 + x8 | 0;
|
||||
x10 ^= u<<18 | u>>>(32-18);
|
||||
|
||||
u = x15 + x14 | 0;
|
||||
x12 ^= u<<7 | u>>>(32-7);
|
||||
u = x12 + x15 | 0;
|
||||
x13 ^= u<<9 | u>>>(32-9);
|
||||
u = x13 + x12 | 0;
|
||||
x14 ^= u<<13 | u>>>(32-13);
|
||||
u = x14 + x13 | 0;
|
||||
x15 ^= u<<18 | u>>>(32-18);
|
||||
}
|
||||
x0 = x0 + j0 | 0;
|
||||
x1 = x1 + j1 | 0;
|
||||
x2 = x2 + j2 | 0;
|
||||
x3 = x3 + j3 | 0;
|
||||
// x4 = x4 + j4 | 0;
|
||||
// x5 = x5 + j5 | 0;
|
||||
// x6 = x6 + j6 | 0;
|
||||
// x7 = x7 + j7 | 0;
|
||||
// x8 = x8 + j8 | 0;
|
||||
// x9 = x9 + j9 | 0;
|
||||
// x10 = x10 + j10 | 0;
|
||||
// x11 = x11 + j11 | 0;
|
||||
// x12 = x12 + j12 | 0;
|
||||
// x13 = x13 + j13 | 0;
|
||||
// x14 = x14 + j14 | 0;
|
||||
// x15 = x15 + j15 | 0;
|
||||
|
||||
p[ 0] = x0 >>> 0 & 0xff;
|
||||
p[ 1] = x0 >>> 8 & 0xff;
|
||||
p[ 2] = x0 >>> 16 & 0xff;
|
||||
p[ 3] = x0 >>> 24 & 0xff;
|
||||
|
||||
p[ 4] = x1 >>> 0 & 0xff;
|
||||
p[ 5] = x1 >>> 8 & 0xff;
|
||||
p[ 6] = x1 >>> 16 & 0xff;
|
||||
p[ 7] = x1 >>> 24 & 0xff;
|
||||
|
||||
p[ 8] = x2 >>> 0 & 0xff;
|
||||
p[ 9] = x2 >>> 8 & 0xff;
|
||||
p[10] = x2 >>> 16 & 0xff;
|
||||
p[11] = x2 >>> 24 & 0xff;
|
||||
|
||||
p[12] = x3 >>> 0 & 0xff;
|
||||
p[13] = x3 >>> 8 & 0xff;
|
||||
p[14] = x3 >>> 16 & 0xff;
|
||||
p[15] = x3 >>> 24 & 0xff;
|
||||
// we only need 16 bytes of the output
|
||||
|
||||
// o[ 0] = x0 >>> 0 & 0xff;
|
||||
// o[ 1] = x0 >>> 8 & 0xff;
|
||||
// o[ 2] = x0 >>> 16 & 0xff;
|
||||
// o[ 3] = x0 >>> 24 & 0xff;
|
||||
|
||||
// o[ 4] = x1 >>> 0 & 0xff;
|
||||
// o[ 5] = x1 >>> 8 & 0xff;
|
||||
// o[ 6] = x1 >>> 16 & 0xff;
|
||||
// o[ 7] = x1 >>> 24 & 0xff;
|
||||
|
||||
// o[ 8] = x2 >>> 0 & 0xff;
|
||||
// o[ 9] = x2 >>> 8 & 0xff;
|
||||
// o[10] = x2 >>> 16 & 0xff;
|
||||
// o[11] = x2 >>> 24 & 0xff;
|
||||
|
||||
// o[12] = x3 >>> 0 & 0xff;
|
||||
// o[13] = x3 >>> 8 & 0xff;
|
||||
// o[14] = x3 >>> 16 & 0xff;
|
||||
// o[15] = x3 >>> 24 & 0xff;
|
||||
|
||||
// o[16] = x4 >>> 0 & 0xff;
|
||||
// o[17] = x4 >>> 8 & 0xff;
|
||||
// o[18] = x4 >>> 16 & 0xff;
|
||||
// o[19] = x4 >>> 24 & 0xff;
|
||||
|
||||
// o[20] = x5 >>> 0 & 0xff;
|
||||
// o[21] = x5 >>> 8 & 0xff;
|
||||
// o[22] = x5 >>> 16 & 0xff;
|
||||
// o[23] = x5 >>> 24 & 0xff;
|
||||
|
||||
// o[24] = x6 >>> 0 & 0xff;
|
||||
// o[25] = x6 >>> 8 & 0xff;
|
||||
// o[26] = x6 >>> 16 & 0xff;
|
||||
// o[27] = x6 >>> 24 & 0xff;
|
||||
|
||||
// o[28] = x7 >>> 0 & 0xff;
|
||||
// o[29] = x7 >>> 8 & 0xff;
|
||||
// o[30] = x7 >>> 16 & 0xff;
|
||||
// o[31] = x7 >>> 24 & 0xff;
|
||||
|
||||
// o[32] = x8 >>> 0 & 0xff;
|
||||
// o[33] = x8 >>> 8 & 0xff;
|
||||
// o[34] = x8 >>> 16 & 0xff;
|
||||
// o[35] = x8 >>> 24 & 0xff;
|
||||
|
||||
// o[36] = x9 >>> 0 & 0xff;
|
||||
// o[37] = x9 >>> 8 & 0xff;
|
||||
// o[38] = x9 >>> 16 & 0xff;
|
||||
// o[39] = x9 >>> 24 & 0xff;
|
||||
|
||||
// o[40] = x10 >>> 0 & 0xff;
|
||||
// o[41] = x10 >>> 8 & 0xff;
|
||||
// o[42] = x10 >>> 16 & 0xff;
|
||||
// o[43] = x10 >>> 24 & 0xff;
|
||||
|
||||
// o[44] = x11 >>> 0 & 0xff;
|
||||
// o[45] = x11 >>> 8 & 0xff;
|
||||
// o[46] = x11 >>> 16 & 0xff;
|
||||
// o[47] = x11 >>> 24 & 0xff;
|
||||
|
||||
// o[48] = x12 >>> 0 & 0xff;
|
||||
// o[49] = x12 >>> 8 & 0xff;
|
||||
// o[50] = x12 >>> 16 & 0xff;
|
||||
// o[51] = x12 >>> 24 & 0xff;
|
||||
|
||||
// o[52] = x13 >>> 0 & 0xff;
|
||||
// o[53] = x13 >>> 8 & 0xff;
|
||||
// o[54] = x13 >>> 16 & 0xff;
|
||||
// o[55] = x13 >>> 24 & 0xff;
|
||||
|
||||
// o[56] = x14 >>> 0 & 0xff;
|
||||
// o[57] = x14 >>> 8 & 0xff;
|
||||
// o[58] = x14 >>> 16 & 0xff;
|
||||
// o[59] = x14 >>> 24 & 0xff;
|
||||
|
||||
// o[60] = x15 >>> 0 & 0xff;
|
||||
// o[61] = x15 >>> 8 & 0xff;
|
||||
// o[62] = x15 >>> 16 & 0xff;
|
||||
// o[63] = x15 >>> 24 & 0xff;
|
||||
}
|
||||
16
core/twopc/webWorkers/src/Cargo.toml
Normal file
16
core/twopc/webWorkers/src/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "garbler"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
# [dependencies]
|
||||
# uncomment the dependencies below when debugging with wasm-bindgen
|
||||
# wasm-bindgen = "0.2"
|
||||
# console_error_panic_hook = "0.1.7"
|
||||
# web-sys = { version = "0.3.56", features = ['console'] }
|
||||
537
core/twopc/webWorkers/src/src/lib.rs
Normal file
537
core/twopc/webWorkers/src/src/lib.rs
Normal file
@@ -0,0 +1,537 @@
|
||||
#! [allow(non_snake_case)]
|
||||
#! [allow(non_upper_case_globals)]
|
||||
|
||||
use core::slice::{from_raw_parts_mut, from_raw_parts};
|
||||
|
||||
// when debugging, uncomment the imports below,
|
||||
// add #[wasm_bindgen] to the function and insert on 1st line of function:
|
||||
// panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
|
||||
//use wasm_bindgen::prelude::*;
|
||||
//use web_sys::console;
|
||||
//extern crate console_error_panic_hook;
|
||||
//use std::panic;
|
||||
|
||||
// We are trying to make as many vars global as possible. It sacrifices code readability
|
||||
// but the performance gains are worth it (~20% gains).
|
||||
|
||||
static mut gatesCount: u32 = 0;
|
||||
static mut wiresCount: u32 = 0;
|
||||
static mut andGateCount: u32 = 0;
|
||||
static mut notaryInputSize: u32 = 0;
|
||||
static mut clientInputSize: u32 = 0;
|
||||
static mut outputSize: u32 = 0;
|
||||
|
||||
// gates is parsed gates
|
||||
static mut gates: Vec<Gate> = Vec::new();
|
||||
// ga is garbling assignment: all wires with their labels
|
||||
static mut ga: Vec<[[u8; 16]; 2]> = Vec::new();
|
||||
// ea is evaluating assignment: one label per wire
|
||||
static mut ea: Vec<[u8; 16]> = Vec::new();
|
||||
// R is circuit's delta (garbler only)
|
||||
static mut R: [u8; 16] = [0; 16];
|
||||
|
||||
// resumeFrom saves the gate number from which we will resume
|
||||
static mut resumeFrom: usize = 0;
|
||||
// andIdx is index of the AND gate that was processed so far
|
||||
static mut andIdx: usize = 0;
|
||||
|
||||
// m is malloc()ed pointer to shared memory between wasm and JS
|
||||
static mut m: u32 = 0;
|
||||
// ipc1 points to a 20-byte buffer for IPC. wasm puts 16-byte data to be salsaed and a
|
||||
// 4-byte tweak and reads the salsaed result (the first 16 bytes).
|
||||
static mut ipc1: &mut [u8] = &mut [0; 0];
|
||||
// ipc2 points to a 48-byte buffer for IPC. wasm puts data to be salsaed,
|
||||
// JS puts the result of salsaing.
|
||||
static mut ipc2: &mut [u8] = &mut [0; 0];
|
||||
// output points to output (for garbler it is decoding table, for evaluator it
|
||||
// is plaintext output)
|
||||
static mut output: &mut [u8] = &mut [0; 0];
|
||||
// decoding_table point to the decoding table (evaluator's input)
|
||||
static mut decoding_table: & [u8] = & [0; 0];
|
||||
// tt points to truth tables
|
||||
static mut tt: &mut [u8] = &mut [0; 0];
|
||||
// il points to input labels
|
||||
static mut il: &mut [u8] = &mut [0; 0];
|
||||
// gatesBuf points to serialized gates description
|
||||
static mut gatesBuf: &[u8] = &[0; 0];
|
||||
// randBuf points to crypto random data supplied by JS
|
||||
static mut randBuf: &[u8] = &[0; 0];
|
||||
// g points to a gate currently being processed
|
||||
static mut g: &Gate = &Gate{op:0, in1:0, in2:0, out:0};
|
||||
|
||||
|
||||
// xor* are values that need to be xored to randomOracle's output
|
||||
// to get an encrypted row
|
||||
static mut xor0: [u8; 16] = [0; 16];
|
||||
static mut xor1: [u8; 16] = [0; 16];
|
||||
static mut xor2: [u8; 16] = [0; 16];
|
||||
// rows contains info about rows of 1 specific AND gate
|
||||
static mut rows: Vec<Row> = Vec::new();
|
||||
static mut idxToReduce: u8 = 0;
|
||||
// a2 and b4 used in encrypt();
|
||||
static mut a2: [u8; 16] = [0; 16];
|
||||
static mut b4: [u8; 16] = [0; 16];
|
||||
// a and b a re return values of encrypt()
|
||||
static mut a: [u8; 16] = [0; 16];
|
||||
static mut b: [u8; 16] = [0; 16];
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct Gate {
|
||||
// gate operation {'XOR': 0, 'AND': 1, 'INV': 2};
|
||||
op: u8,
|
||||
// first input wire number
|
||||
in1: u32,
|
||||
// second input wire number
|
||||
in2: u32,
|
||||
// output wire number
|
||||
out: u32,
|
||||
}
|
||||
|
||||
struct Row <'a>{
|
||||
in1: &'a[u8; 16],
|
||||
in2: &'a[u8; 16],
|
||||
outNo: u8,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn startEvaluator() {
|
||||
unsafe {
|
||||
resumeFrom = 0;
|
||||
andIdx = 0;
|
||||
// set input labels
|
||||
for i in 0..(notaryInputSize + clientInputSize) as usize {
|
||||
ea[i].copy_from_slice(&il[i*16..i*16+16]);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn startGarbler() {
|
||||
unsafe {
|
||||
resumeFrom = 0;
|
||||
andIdx = 0;
|
||||
idxToReduce = 99;
|
||||
|
||||
R.copy_from_slice(&randBuf[0..16]);
|
||||
R[15] = R[15] | 0x01;
|
||||
|
||||
// generate input labels
|
||||
for i in 0..(notaryInputSize + clientInputSize) as usize {
|
||||
let mut label0: [u8; 16] = [0; 16];
|
||||
label0.copy_from_slice(&randBuf[16 + i * 16..16 + (i + 1) * 16]);
|
||||
let label1 = xor(&label0, &R);
|
||||
ga[i] = [label0, label1];
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
//#[wasm_bindgen]
|
||||
// resume after AND gate was processed. Back to normal gate-by-gate
|
||||
pub fn resumeGarbler_0() {
|
||||
unsafe {
|
||||
if resumeFrom > 0 || andIdx+1 == andGateCount as usize{
|
||||
finishLastANDGate();
|
||||
}
|
||||
|
||||
let mut lastIdx = 0;
|
||||
for i in resumeFrom..gates.len() {
|
||||
lastIdx = i as u32;
|
||||
g = &gates[i];
|
||||
if g.op == 1 {
|
||||
// garbleAndStage1 will implicitly return values in globals a and b
|
||||
garbleAndStage1();
|
||||
ipc1[0..16].copy_from_slice(&a);
|
||||
ipc1[16..20].copy_from_slice(&i.to_be_bytes());
|
||||
xor0 = b;
|
||||
resumeFrom = i;
|
||||
return;
|
||||
} else if g.op == 0 {
|
||||
garbleXor();
|
||||
} else if g.op == 2 {
|
||||
garbleNot();
|
||||
} else {
|
||||
panic!("unknown gate op");
|
||||
}
|
||||
}
|
||||
assert!(gatesCount == lastIdx+1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[no_mangle]
|
||||
//#[wasm_bindgen]
|
||||
// resume after AND gate was processed. Back to normal gate-by-gate
|
||||
pub fn resumeEvaluator_0() {
|
||||
unsafe {
|
||||
// this is the last step of the previous AND gate
|
||||
if resumeFrom > 0 {
|
||||
let out = gates[resumeFrom].out;
|
||||
ea[out as usize] = xor(&ipc1[0..16], &xor0);
|
||||
andIdx += 1;
|
||||
resumeFrom += 1;
|
||||
}
|
||||
|
||||
// proceed gate by gate
|
||||
let mut lastIdx = 0;
|
||||
for i in resumeFrom..gates.len() {
|
||||
lastIdx = i as u32;
|
||||
g = &gates[i];
|
||||
if g.op == 1 {
|
||||
// evaluateAndStage1 will implicitly return values in globals a and b
|
||||
evaluateAndStage1();
|
||||
ipc1[0..16].copy_from_slice(&a);
|
||||
ipc1[16..20].copy_from_slice(&i.to_be_bytes());
|
||||
xor0 = b;
|
||||
resumeFrom = i;
|
||||
return;
|
||||
} else if g.op == 0 {
|
||||
evaluateXor();
|
||||
} else if g.op == 2 {
|
||||
evaluateNot();
|
||||
} else {
|
||||
panic!("unknown gate op");
|
||||
}
|
||||
}
|
||||
assert!(gatesCount == lastIdx+1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[no_mangle]
|
||||
//#[wasm_bindgen]
|
||||
// resume after 1 Salsa invocation
|
||||
pub fn resumeGarbler_1() {
|
||||
unsafe {
|
||||
let outWire = xor(&ipc1[0..16], &xor0);
|
||||
let mut outLabels: [[u8; 16]; 2] = [[0;16];2];
|
||||
if idxToReduce == 3 {
|
||||
outLabels[0] = xor(&outWire, &R);
|
||||
outLabels[1] = outWire;
|
||||
} else {
|
||||
outLabels[0] = outWire;
|
||||
outLabels[1] = xor(&outWire, &R);
|
||||
}
|
||||
|
||||
let out = gates[resumeFrom].out;
|
||||
ga[out as usize][0] = outLabels[0];
|
||||
ga[out as usize][1] = outLabels[1];
|
||||
|
||||
for i in 0..rows.len(){
|
||||
if i == idxToReduce as usize {
|
||||
continue;
|
||||
}
|
||||
encrypt(&rows[i].in1, &rows[i].in2, &outLabels[rows[i].outNo as usize]);
|
||||
let point = (2 * getPoint(&rows[i].in1) + getPoint(&rows[i].in2)) as usize;
|
||||
ipc2[point*16..(point+1)*16].copy_from_slice(&a);
|
||||
if point == 0 {
|
||||
xor0 = b;
|
||||
}
|
||||
else if point == 1 {
|
||||
xor1 = b;
|
||||
}
|
||||
else if point == 2 {
|
||||
xor2 = b;
|
||||
}
|
||||
else {
|
||||
panic!("wrong point");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
// final touches after all salsa invocations:
|
||||
// xor the data from JS with saved data to finish row encryption
|
||||
pub fn finishLastANDGate() {
|
||||
unsafe {
|
||||
tt[andIdx* 48..andIdx*48+16].copy_from_slice(&xor( &xor0, &ipc2[0..16]));
|
||||
tt[andIdx*48+16..andIdx*48+32].copy_from_slice(&xor( &xor1, &ipc2[16..32]));
|
||||
tt[andIdx*48+32..andIdx*48+48].copy_from_slice(&xor( &xor2, &ipc2[32..48]));
|
||||
andIdx += 1;
|
||||
resumeFrom += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
//#[wasm_bindgen]
|
||||
// finishGarbler is called after garbling of all AND gates has been done
|
||||
// to output the output labels and input labels. (the truth tables are already
|
||||
// sitting in the output buffer).
|
||||
pub fn finishGarbler() {
|
||||
unsafe {
|
||||
// finish with the remaining non-AND gates
|
||||
resumeGarbler_0();
|
||||
|
||||
let roundedUp = ((outputSize+7)/8) as usize;
|
||||
let mut bits: Vec<u8> = vec![0; roundedUp*8];
|
||||
for i in 0..outputSize as usize {
|
||||
// get decoding table: LSB of label0 for each output wire
|
||||
bits[i] = ga[ga.len() - outputSize as usize + i][0][15] & 1
|
||||
}
|
||||
output.copy_from_slice(&bitsToBytes(&bits));
|
||||
|
||||
// input label size: how many input label pairs there are
|
||||
let ils = (notaryInputSize + clientInputSize) as usize;
|
||||
for i in 0..ils {
|
||||
il[i* 32..i*32+16].copy_from_slice(&ga[i][0]);
|
||||
il[i*32+16..i*32+32].copy_from_slice(&ga[i][1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// convert a string of bits with LSB at index 0 into big-endian bytes
|
||||
fn bitsToBytes(bits: &Vec<u8>) -> Vec<u8> {
|
||||
let mut outBytes: Vec<u8> = vec![0; bits.len()/8];
|
||||
let byteLen = outBytes.len();
|
||||
for i in 0..byteLen {
|
||||
let eightBits = &bits[i*8..i*8+8];
|
||||
let mut byte: u8 = 0;
|
||||
for j in 0..8 {
|
||||
byte <<= 1;
|
||||
byte ^= eightBits[7-j]
|
||||
}
|
||||
outBytes[byteLen-1 - i] = byte
|
||||
}
|
||||
return outBytes
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
//#[wasm_bindgen]
|
||||
// finishEvaluator is called after garbling of all AND gates has been done
|
||||
// to output the output labels
|
||||
pub fn finishEvaluator() {
|
||||
//panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
|
||||
unsafe {
|
||||
// finish with the remaining non-AND gates
|
||||
resumeEvaluator_0();
|
||||
|
||||
let roundedUp = ((outputSize+7)/8) as usize;
|
||||
let mut bits: Vec<u8> = vec![0; roundedUp*8];
|
||||
for i in 0..outputSize as usize {
|
||||
// get decoding table: LSB of label0 for each output wire
|
||||
bits[i] = ea[ea.len() - outputSize as usize + i][15] & 1
|
||||
}
|
||||
let outputBytes = bitsToBytes(&bits);
|
||||
let plaintext = xorSlices(&outputBytes, &decoding_table);
|
||||
output.copy_from_slice(&plaintext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn garbleXor() {
|
||||
unsafe {
|
||||
ga[g.out as usize][0] = xor(&ga[g.in1 as usize][0], &ga[g.in2 as usize][0]);
|
||||
ga[g.out as usize][1] = xor(&ga[g.out as usize][0], &R);
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluateXor() {
|
||||
unsafe {
|
||||
let in1 = &ea[g.in1 as usize];
|
||||
let in2 = &ea[g.in2 as usize];
|
||||
ea[g.out as usize] = xor(in1, in2);
|
||||
}
|
||||
}
|
||||
|
||||
fn garbleNot() {
|
||||
unsafe {
|
||||
ga[g.out as usize][0] = ga[g.in1 as usize][1];
|
||||
ga[g.out as usize][1] = ga[g.in1 as usize][0];
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluateNot() {
|
||||
unsafe {
|
||||
ea[g.out as usize] = ea[g.in1 as usize];
|
||||
}
|
||||
}
|
||||
|
||||
fn garbleAndStage1() {
|
||||
unsafe {
|
||||
let in1_0 = &ga[g.in1 as usize][0];
|
||||
let in1_1 = &ga[g.in1 as usize][1];
|
||||
let in2_0 = &ga[g.in2 as usize][0];
|
||||
let in2_1 = &ga[g.in2 as usize][1];
|
||||
rows = vec![
|
||||
Row {in1: in1_0, in2: in2_0, outNo: 0,},
|
||||
Row {in1: in1_0, in2: in2_1, outNo: 0,},
|
||||
Row {in1: in1_1, in2: in2_0, outNo: 0,},
|
||||
Row {in1: in1_1, in2: in2_1, outNo: 1,}];
|
||||
|
||||
for i in 0..rows.len() {
|
||||
if getPoint(&rows[i].in1) == 1 && getPoint(&rows[i].in2) == 1 {
|
||||
idxToReduce = i as u8;
|
||||
// encrypt implicitly returns in a and b
|
||||
encrypt(&rows[i].in1, &rows[i].in2, &[0; 16]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert!(idxToReduce != 99);
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluateAndStage1() {
|
||||
unsafe {
|
||||
let label1 = &ea[g.in1 as usize];
|
||||
let label2 = &ea[g.in2 as usize];
|
||||
let mut cipher: [u8; 16] = [0; 16]; // if point == 3, we will keep it zeroed
|
||||
let point = (2 * getPoint(label1) + getPoint(label2)) as usize;
|
||||
if point != 3 {
|
||||
cipher.copy_from_slice(&tt[andIdx*48+16*point..andIdx*48+16*point+16]);
|
||||
}
|
||||
// decrypt implicitly returns in globals a and b
|
||||
decrypt(label1, label2, &cipher);
|
||||
}
|
||||
}
|
||||
|
||||
fn getPoint(label: &[u8; 16]) -> u8 {
|
||||
return label[15] & 0x01;
|
||||
}
|
||||
|
||||
fn decrypt(a_: &[u8; 16], b_: &[u8; 16], m_: &[u8; 16]){
|
||||
encrypt(a_, b_, m_);
|
||||
}
|
||||
|
||||
fn encrypt(a_: &[u8; 16], b_: &[u8; 16], m_: &[u8; 16]) {
|
||||
unsafe {
|
||||
a2.copy_from_slice(a_);
|
||||
let leastbyte = a2[0];
|
||||
a2.copy_within(1..15, 0);
|
||||
a2[14] = leastbyte;
|
||||
|
||||
b4.copy_from_slice(b_);
|
||||
let leastbyte0 = b4[0];
|
||||
let leastbyte1 = b4[1];
|
||||
b4.copy_within(2..15, 0);
|
||||
b4[13] = leastbyte0;
|
||||
b4[14] = leastbyte1;
|
||||
|
||||
// return mid-state of encryption:
|
||||
// a must be hashed with Salsa and the result xored with b
|
||||
a = xor(&a2, &b4);
|
||||
b = xor(&a, m_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn threeBytesToInt(b_: &[u8]) -> u32 {
|
||||
return b_[2] as u32 + b_[1] as u32 * 256 + b_[0] as u32 * 65536;
|
||||
}
|
||||
|
||||
// xors 16-byte slices
|
||||
fn xor(a_: &[u8], b_: &[u8]) -> [u8; 16] {
|
||||
let mut c: [u8; 16] = [0; 16];
|
||||
for i in 0..16 {
|
||||
c[i] = a_[i] ^ b_[i];
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
// xors arbitrary-sized slices
|
||||
fn xorSlices(a_: &[u8], b_: &[u8]) -> Vec<u8> {
|
||||
let mut c: Vec<u8> = vec![0; a_.len()];
|
||||
for i in 0..a_.len() {
|
||||
c[i] = a_[i] ^ b_[i];
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
//#[wasm_bindgen]
|
||||
pub fn parseGatesBlob() {
|
||||
unsafe {
|
||||
gates = vec![Gate::default(); gatesCount as usize];
|
||||
for i in 0..gatesCount as usize {
|
||||
let blob: Vec<u8> = gatesBuf[i * 10..(i + 1) * 10].to_vec();
|
||||
let gate: Gate = Gate {
|
||||
op: (blob[0]),
|
||||
in1: (threeBytesToInt(&blob[1..4])),
|
||||
in2: (threeBytesToInt(&blob[4..7])),
|
||||
out: (threeBytesToInt(&blob[7..10])),
|
||||
};
|
||||
gates[i] = gate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
//#[wasm_bindgen]
|
||||
pub fn initAll(
|
||||
gatesCount_: u32,
|
||||
wiresCount_: u32,
|
||||
notaryInputSize_: u32,
|
||||
clientInputSize_: u32,
|
||||
outputSize_: u32,
|
||||
andGateCount_: u32,
|
||||
) -> u32 {
|
||||
unsafe {
|
||||
gatesCount = gatesCount_;
|
||||
wiresCount = wiresCount_;
|
||||
notaryInputSize = notaryInputSize_;
|
||||
clientInputSize = clientInputSize_;
|
||||
outputSize = outputSize_;
|
||||
andGateCount = andGateCount_;
|
||||
|
||||
// memory needs to be allocated for:
|
||||
let mut total = 0;
|
||||
// IPC buffer 1 of 20 bytes
|
||||
total += 20;
|
||||
// IPC buffer 2 of 48 bytes (garbler only)
|
||||
total += 48;
|
||||
// gates blob
|
||||
total += gatesCount * 10;
|
||||
// random blob (used by garbler only)
|
||||
total += (notaryInputSize + clientInputSize + 1) * 16;
|
||||
// decoding table
|
||||
total += (outputSize+7)/8;
|
||||
// output
|
||||
total += (outputSize+7)/8;
|
||||
// truth tables
|
||||
total += andGateCount * 48;
|
||||
// input lables
|
||||
total += (notaryInputSize + clientInputSize) * 32;
|
||||
m = malloc(total as usize) as u32;
|
||||
|
||||
// o is memory offset
|
||||
let mut o = m;
|
||||
ipc1 = from_raw_parts_mut(o as *mut u8, 20);
|
||||
o += 20;
|
||||
ipc2 = from_raw_parts_mut(o as *mut u8, 48);
|
||||
o += 48;
|
||||
gatesBuf = from_raw_parts(o as *mut u8, (gatesCount * 10)as usize);
|
||||
o += gatesCount * 10;
|
||||
let randSize = (notaryInputSize + clientInputSize + 1) * 16;
|
||||
randBuf = from_raw_parts(o as *mut u8, randSize as usize);
|
||||
o += randSize;
|
||||
decoding_table = from_raw_parts(o as *mut u8, ((outputSize+7)/8) as usize);
|
||||
o += (outputSize+7)/8;
|
||||
output = from_raw_parts_mut(o as *mut u8, ((outputSize+7)/8) as usize);
|
||||
o += (outputSize+7)/8;
|
||||
tt = from_raw_parts_mut(o as *mut u8, (andGateCount * 48) as usize);
|
||||
o += andGateCount * 48;
|
||||
il = from_raw_parts_mut(o as *mut u8, ((notaryInputSize + clientInputSize)*32) as usize);
|
||||
ga = vec![[[0; 16]; 2]; wiresCount as usize];
|
||||
ea = vec![[0; 16]; wiresCount as usize];
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
||||
// allocates memory
|
||||
fn malloc(size: usize) -> *mut u8 {
|
||||
let mut buf: Vec<u8> = vec![0; size];
|
||||
let ptr = buf.as_mut_ptr();
|
||||
// take ownership of the memory block and
|
||||
// ensure that its destructor is not
|
||||
// called when the object goes out of scope
|
||||
// at the end of the function
|
||||
std::mem::forget(buf);
|
||||
return ptr;
|
||||
}
|
||||
@@ -451,7 +451,7 @@ export function expandRange(min, max){
|
||||
}
|
||||
|
||||
|
||||
// split Array or Uint8Array into an array array of chunks
|
||||
// split Array or Uint8Array into an array of chunks
|
||||
export function splitIntoChunks(ba, chunkSize) {
|
||||
assert(ba instanceof Uint8Array);
|
||||
assert(ba.length % chunkSize === 0);
|
||||
|
||||
Reference in New Issue
Block a user