diff --git a/core/FirstTimeSetup.js b/core/FirstTimeSetup.js index 65022fd..1df4ec2 100644 --- a/core/FirstTimeSetup.js +++ b/core/FirstTimeSetup.js @@ -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'); diff --git a/core/Socket.js b/core/Socket.js index f5ca783..f21fe5b 100644 --- a/core/Socket.js +++ b/core/Socket.js @@ -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) { diff --git a/core/globals.js b/core/globals.js index 8cdc1b2..08995f5 100644 --- a/core/globals.js +++ b/core/globals.js @@ -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 }; \ No newline at end of file diff --git a/core/oracles.js b/core/oracles.js index 0b9cea0..708833a 100644 --- a/core/oracles.js +++ b/core/oracles.js @@ -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'; diff --git a/core/twopc/GCWorker.js b/core/twopc/GCWorker.js index c2069d5..52b56b5 100644 --- a/core/twopc/GCWorker.js +++ b/core/twopc/GCWorker.js @@ -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); }); } diff --git a/core/twopc/GHASH.js b/core/twopc/GHASH.js index 46fdd16..e2df3a1 100644 --- a/core/twopc/GHASH.js +++ b/core/twopc/GHASH.js @@ -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]]; diff --git a/core/twopc/Garbler.js b/core/twopc/Garbler.js index 7c4bcd9..ba26fd5 100644 --- a/core/twopc/Garbler.js +++ b/core/twopc/Garbler.js @@ -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 = []; diff --git a/core/twopc/OTReceiver.js b/core/twopc/OTReceiver.js index 94f59e5..1e702b6 100644 --- a/core/twopc/OTReceiver.js +++ b/core/twopc/OTReceiver.js @@ -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); } } \ No newline at end of file diff --git a/core/twopc/TWOPC.js b/core/twopc/TWOPC.js index 83fa69a..598b09b 100644 --- a/core/twopc/TWOPC.js +++ b/core/twopc/TWOPC.js @@ -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; diff --git a/core/twopc/circuits b/core/twopc/circuits index cb87bc3..d1bfca1 160000 --- a/core/twopc/circuits +++ b/core/twopc/circuits @@ -1 +1 @@ -Subproject commit cb87bc30e7ee2b1913856e1eef4acd0fee9bf664 +Subproject commit d1bfca11971053385c236fd47e42f4e2012c7929 diff --git a/core/twopc/webWorkers/README b/core/twopc/webWorkers/README new file mode 100644 index 0000000..260bf71 --- /dev/null +++ b/core/twopc/webWorkers/README @@ -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 \ No newline at end of file diff --git a/core/twopc/webWorkers/garbler.wasm b/core/twopc/webWorkers/garbler.wasm new file mode 100755 index 0000000..dc6c894 Binary files /dev/null and b/core/twopc/webWorkers/garbler.wasm differ diff --git a/core/twopc/webWorkers/gcworker.js b/core/twopc/webWorkers/gcworker_purejs.js similarity index 82% rename from core/twopc/webWorkers/gcworker.js rename to core/twopc/webWorkers/gcworker_purejs.js index 46ce0a6..860d3f1 100644 --- a/core/twopc/webWorkers/gcworker.js +++ b/core/twopc/webWorkers/gcworker_purejs.js @@ -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){ diff --git a/core/twopc/webWorkers/gcworker_wasm.js b/core/twopc/webWorkers/gcworker_wasm.js new file mode 100644 index 0000000..2d40c42 --- /dev/null +++ b/core/twopc/webWorkers/gcworker_wasm.js @@ -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; +} diff --git a/core/twopc/webWorkers/src/Cargo.toml b/core/twopc/webWorkers/src/Cargo.toml new file mode 100644 index 0000000..86650c7 --- /dev/null +++ b/core/twopc/webWorkers/src/Cargo.toml @@ -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'] } \ No newline at end of file diff --git a/core/twopc/webWorkers/src/src/lib.rs b/core/twopc/webWorkers/src/src/lib.rs new file mode 100644 index 0000000..7612f22 --- /dev/null +++ b/core/twopc/webWorkers/src/src/lib.rs @@ -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 = 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 = 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 = 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) -> Vec { + let mut outBytes: Vec = 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 = 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 { + let mut c: Vec = 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 = 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 = 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; +} diff --git a/core/utils.js b/core/utils.js index 659db2c..d77c674 100644 --- a/core/utils.js +++ b/core/utils.js @@ -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);