From 79173c511e1564961d6d55349a674b249345b445 Mon Sep 17 00:00:00 2001 From: themighty1 Date: Thu, 30 Dec 2021 18:05:30 +0300 Subject: [PATCH] - use Salsa20 instead of tweetnacl-js's secretbox for a 30% garbling speedup in the browser. - implement garbled row reduction GRR3 for 25% bandwidth saving - implement KOS15 OT extension - linting --- background.html | 3 +- core/FirstTimeSetup.js | 12 +- core/Main.js | 212 +- core/ProgressMonitor.js | 4 +- core/Socket.js | 26 +- core/TLS.js | 213 +- core/TLSNotarySession.js | 242 +- core/globals.js | 11 +- core/indexeddb.js | 55 +- core/internal.js | 18 + core/oracles.js | 30 +- core/third-party/nacl-fast.js | 2391 -------------------- core/twopc/Evaluator.js | 13 - core/twopc/GCWorker.js | 4 +- core/twopc/GHASH.js | 446 ++++ core/twopc/Garbler.js | 183 +- core/twopc/OT.js | 201 -- core/twopc/OTCommon.js | 169 ++ core/twopc/OTReceiver.js | 153 ++ core/twopc/OTSender.js | 136 ++ core/twopc/OTWorker.js | 147 -- core/twopc/TWOPC.js | 1133 +++------- core/twopc/webWorkers/gcworker.js | 308 ++- core/twopc/webWorkers/otworker.js | 159 -- core/twopc/webWorkers/serializeCircuits.js | 10 +- core/utils.js | 435 ++-- core/verifychain.js | 45 +- ui/FileChooser.js | 5 +- ui/Manager.js | 40 +- ui/NotificationBar.js | 4 +- ui/Popup.js | 16 +- ui/RawViewer.js | 4 +- ui/Viewer.js | 14 +- 33 files changed, 2224 insertions(+), 4618 deletions(-) create mode 100644 core/internal.js delete mode 100644 core/third-party/nacl-fast.js create mode 100644 core/twopc/GHASH.js delete mode 100644 core/twopc/OT.js create mode 100644 core/twopc/OTCommon.js create mode 100644 core/twopc/OTReceiver.js create mode 100644 core/twopc/OTSender.js delete mode 100644 core/twopc/OTWorker.js delete mode 100644 core/twopc/webWorkers/otworker.js diff --git a/background.html b/background.html index 1a7a22f..4be73b3 100644 --- a/background.html +++ b/background.html @@ -4,12 +4,11 @@ - - + \ No newline at end of file diff --git a/core/FirstTimeSetup.js b/core/FirstTimeSetup.js index 2b65c40..65022fd 100644 --- a/core/FirstTimeSetup.js +++ b/core/FirstTimeSetup.js @@ -1,12 +1,14 @@ +/* global chrome, CASM*/ + import {wait} from './utils.js'; // class FakeFS imitates node.js's fs.readFileSync() by // reading the files in advance and outputting their content when readFileSync() is called class FakeFS{ constructor(){ - this.fileList = {}; // {fileName: } + this.fileList = {}; // {fileName: } } - + // on init we read all .casm and .txt files in core/twopc/circuits async init(){ const that = this; @@ -40,10 +42,10 @@ class FakeFS{ // FirstTimeSetup.start() is invoked once on first install. It assembles the circuits, // serializes them into a compact binary format and stores them in the browser cache. -// All future invocations of Pagesigner use these serialized cached circuits. +// All future invocations of Pagesigner use these serialized cached circuits. export class FirstTimeSetup{ async start(pm){ - const url = chrome.extension.getURL('core/twopc/webWorkers/serializeCircuits.js') + const url = chrome.extension.getURL('core/twopc/webWorkers/serializeCircuits.js'); const worker = new Worker(url); console.log('Parsing circuits. This is done only once on first launch and will take ~30 secs'); console.time('time_to_parse'); @@ -67,7 +69,7 @@ export class FirstTimeSetup{ }); obj[i] = newobj; if (pm) pm.update('first_time', {'current': n, 'total': 6}); - await wait(100); // make sure update reaches popup + await wait(100); // make sure update reaches popup } console.timeEnd('time_to_parse'); return obj; diff --git a/core/Main.js b/core/Main.js index bce4588..c3e0942 100644 --- a/core/Main.js +++ b/core/Main.js @@ -1,10 +1,13 @@ -/* eslint-disable no-import-assign */ -/* eslint-disable no-case-declarations */ -import {parse_certs, verifyChain, getCommonName, getAltNames} from './verifychain.js'; -import {ba2str, b64decode, concatTA, int2ba, sha256, b64encode, verifySig, assert, - ba2int, xor, eq, wait, AESECBencrypt, wildcardTest, pubkeyPEM2raw, import_resource} from './utils.js'; -import {getPref, getSessionBlob, getSession, getAllSessions, saveNewSession, init_db, - addNewPreference, setPref, renameSession, deleteSession} from './indexeddb.js'; +/* global chrome, browser */ + +import {parse_certs, verifyChain, getCommonName, getAltNames} from + './verifychain.js'; +import {ba2str, b64decode, concatTA, int2ba, sha256, b64encode, verifySig, + assert, ba2int, xor, eq, wait, AESECBencrypt, wildcardTest, pubkeyPEM2raw, + import_resource} from './utils.js'; +import {getPref, getSessionBlob, getSession, getAllSessions, saveNewSession, + init_db, addNewPreference, setPref, renameSession, deleteSession} + from './indexeddb.js'; import {globals} from './globals.js'; import {Socket} from './Socket.js'; import {TLS, getExpandedKeys, decrypt_tls_responseV6} from './TLS.js'; @@ -18,15 +21,15 @@ export class Main{ this.messageListener; this.notarization_in_progress = false; this.isFirstTimeSetupNeeded = false; - this.waiting_for_click = false; + this.waiting_for_click = false; // popupError will be set to non-null when there is some error message that must be shown // via the popup this.popupError = null; // tabid set to 0 is a sign that this is the main window when querying with .getViews() - this.tabid = 0; + this.tabid = 0; // pendingAction is Firefox only: the action which must be taken as soon as the user allows // access to the website (either notarize or notarizeAfter) - this.pendingAction = null; + this.pendingAction = null; // trustedOracle is an object {'IP':, 'pubkeyPEM':} // describing the oracle server which was verified and can be used for notarization. this.trustedOracle = null; @@ -44,13 +47,13 @@ export class Main{ this.trustedOracleReady = false; } - async main() { - // perform browser-specific init first + async main() { + // perform browser-specific init first if (this.is_edge || this.is_firefox || this.is_opera){ globals.usePythonBackend = true; } if (this.is_firefox){ - // Firefox asks user for permission to access the current website. + // Firefox asks user for permission to access the current website. // Listen when the permission was given and run the pending action. // This way user doesnt have to click notarize->allow->notarize const listener = function(permissions){ @@ -67,7 +70,7 @@ export class Main{ }; browser.permissions.onAdded.addListener(listener); } - + // browser-agnostic init const that = this; this.messageListener = chrome.runtime.onMessage.addListener(function(data) { @@ -145,6 +148,7 @@ export class Main{ mode: 'no-cors' }); const out = await Promise.race([fProm, wait(5000)]) + // eslint-disable-next-line no-unused-vars .catch(err => { // fetch got 404; do nothing, just prevent exception from propagating }); @@ -156,7 +160,7 @@ export class Main{ } // tryBackupNotary tries to use a backup notary. It checks that the backup notary - // is not the same as failedNotaryIP + // is not the same as failedNotaryIP async tryBackupNotary(failedNotaryIP){ const resp = await fetch(globals.backupUrl); const backupIP = await resp.text(); @@ -181,7 +185,7 @@ export class Main{ this.trustedOracleReady = true; } - + openChromeExtensions(){ chrome.tabs.query({url: 'chrome://extensions/*'}, function(tabs) { @@ -192,9 +196,9 @@ export class Main{ chrome.tabs.update(tabs[0].id, {active: true}); }); } - - - // Pagesigner's popup has been clicked + + + // Pagesigner's popup has been clicked async popupProcess(){ if (this.notarization_in_progress) { chrome.runtime.sendMessage({ @@ -242,13 +246,14 @@ export class Main{ }); } } - + // checkIfTabOpened checks if a "tab" containing window[property] has signalled that it has // been loaded and its message listeners are ready // openTabs is an optional array of tab ids to skip when checking because they were already // opened BEFORE we initiated the opening of "tab" // we abort if tab doesn't open after 5 secs. checkIfTabOpened(tab, property, openTabs){ + // eslint-disable-next-line no-unused-vars openTabs = openTabs || []; let isTimeoutTriggered = false; setTimeout(function(){ @@ -260,7 +265,7 @@ export class Main{ function tryAgain(){ console.log('checking if '+property+' is ready...'); const views = chrome.extension.getViews(); - // sometimes the View for the newly opened tab may not yet be available + // sometimes the View for the newly opened tab may not yet be available // so we must wait a little longer for (const win of views){ if (win[property] == undefined) continue; @@ -283,7 +288,7 @@ export class Main{ } }); } - + openFileChooser(){ const myTabs = []; const views = chrome.extension.getViews(); @@ -300,19 +305,19 @@ export class Main{ // create a separate filechooser.html because as soon as a file is chosen, the // same tab will be reused as a viewer. // Otherwise we would have to close filechooser tab and instantly open a - // viewer tab with an unpleasant flicker. + // viewer tab with an unpleasant flicker. const url = chrome.extension.getURL('ui/html/viewer.html#filechooser'); chrome.tabs.create({url: url}, async function(t){ const win = await that.checkIfTabOpened(t, 'isViewer', myTabs); win.viewer.showFileChooser(); }); } - - + + openManager() { const url = chrome.extension.getURL('ui/html/manager.html'); for (const win of chrome.extension.getViews()){ - if (win.isManager){ + if (win.isManager){ // re-focus tab if manager already open console.log('will refocus manger tab', win.tabid); chrome.tabs.update(win.tabid, {active: true}); @@ -324,8 +329,8 @@ export class Main{ that.checkIfTabOpened(t, 'is_manager'); }); } - - + + async prepareNotarization(after_click) { if (!this.trustedOracleReady) { this.sendAlert({ @@ -334,16 +339,16 @@ export class Main{ }); return; } - + let clickTimeout = null; const that = this; - + const active_tab = await new Promise(function(resolve) { chrome.tabs.query({active: true}, function(t) { resolve(t[0]); }); }); - + if (! active_tab.url.startsWith('https://')) { this.sendAlert({ 'title': 'PageSigner error.', @@ -351,7 +356,7 @@ export class Main{ }); return; } - + if (after_click){ const url = chrome.extension.getURL('ui/img/arrow24.png'); chrome.browserAction.setIcon({path: url}); @@ -365,8 +370,8 @@ export class Main{ }); }, 30 * 1000); } - - let oBR_details; + + let oBR_details; const oBR_handler = function(details){ console.log('in onBeforeRequest', details); chrome.webRequest.onBeforeRequest.removeListener(oBR_handler); @@ -377,15 +382,15 @@ export class Main{ urls: [''], tabId: active_tab.id, types: ['main_frame', 'xmlhttprequest'] - // types: ["main_frame", "sub_frame", "stylesheet", "script", + // types: ["main_frame", "sub_frame", "stylesheet", "script", // "image", "font", "object", "xmlhttprequest", "ping", "csp_report", "media", "websocket", "other"] }, ['requestBody']); - + let oBSH_details; const oBSH_handler = function(details){ console.log('in onBeforeSendHeaders', details); chrome.webRequest.onBeforeSendHeaders.removeListener(oBSH_handler); - oBSH_details = details; + oBSH_details = details; console.log(oBR_details, oBSH_details); }; const extraInfoSpec = ['requestHeaders']; @@ -396,10 +401,10 @@ export class Main{ tabId: active_tab.id, types: ['main_frame', 'xmlhttprequest'] }, extraInfoSpec); - - + + // wait for the request to pass oBR and oBHS and reach onSendHeaders - await new Promise(function(resolve) { + await new Promise(function(resolve) { const oSH_handler = function(details){ console.log('in onSendHeaders'); chrome.webRequest.onSendHeaders.removeListener(oSH_handler); @@ -411,18 +416,18 @@ export class Main{ tabId: active_tab.id, types: ['main_frame', 'xmlhttprequest'] }); - + // if not Notarize After Click mode, // reload current tab in order to trigger the HTTP request if (!that.waiting_for_click) chrome.tabs.reload(active_tab.id); // otherwise just wait for the user to click smth and trigger onBeforeRequest }); - + if (this.waiting_for_click) { clearTimeout(clickTimeout); this.waiting_for_click = false; } - + if (oBR_details.url !== oBSH_details.url) return; if (oBR_details.requestId !== oBSH_details.requestId) return; if (oBR_details.method == 'POST') { @@ -451,15 +456,15 @@ export class Main{ this.loadDefaultIcon(); }); } - - + + getHeaders(obj) { console.log('headers are', obj); const x = obj.url.split('/'); const host = x[2].split(':')[0]; x.splice(0, 3); const resource_url = x.join('/'); - + const http_version = globals.useHTTP11 ? ' HTTP/1.1':' HTTP/1.0'; let headers = obj.method + ' /' + resource_url + http_version + '\r\n'; // Chrome doesnt add Host header. Firefox does @@ -476,7 +481,7 @@ export class Main{ } if (obj.method == 'GET') { headers += '\r\n'; - } + } else if (obj.method == 'POST') { let content; if (obj.requestBody.raw != undefined) { @@ -489,7 +494,7 @@ export class Main{ content += key + '=' + obj.requestBody.formData[key] + '&'; } // get rid of the last & - content = content.slice(0,-1); + content = content.slice(0, -1); // Chrome doesn't expose Content-Length which chokes nginx headers += 'Content-Length: ' + parseInt(content.length) + '\r\n\r\n'; headers += content; @@ -506,16 +511,16 @@ export class Main{ 'port': port }; } - - + + async getPGSG(sid){ const blob = await getSessionBlob(sid); return blob.pgsg; } - - - - + + + + // processMessages is the main entrypoint for all extension's logic async processMessages(data) { if (data.destination !== 'extension') return; @@ -534,9 +539,8 @@ export class Main{ this.importPgsgAndShow(new Uint8Array(data.args.data)); break; case 'export': - const pgsg = await this.getPGSG(data.args.dir); - const value = await getSession(data.args.dir); - this.sendToManager({'pgsg': JSON.stringify(pgsg), 'name': value.sessionName}, 'export'); + this.sendToManager({'pgsg': JSON.stringify(await this.getPGSG(data.args.dir)), + 'name': await getSession(data.args.dir).sessionName}, 'export'); break; case 'notarize': this.prepareNotarization(false); @@ -585,9 +589,9 @@ export class Main{ break; case 'removeNotary': await setPref('trustedOracle', {}); - } - } - + } + } + async startNotarization(headers, server, port) { this.notarization_in_progress = true; this.pm.init(); @@ -624,23 +628,23 @@ export class Main{ const url = chrome.extension.getURL('ui/img/icon.png'); chrome.browserAction.setIcon({path: url}); } - + // opens a tab showing the session. sid is a unique session id // creation time is sid. async showSession (sid){ await this.openViewer(sid); this.sendSessions( await getAllSessions()); // refresh manager } - - + + openPythonScript(){ const url = chrome.extension.getURL('pagesigner.py'); chrome.tabs.create({url: url}, function(t){ chrome.tabs.executeScript(t.id, {file: ('ui/python_script_header.js')}); }); } - - + + async verifyPgsg(json){ if (json['version'] == 6){ return await this.verifyPgsgV6(json); @@ -649,8 +653,8 @@ export class Main{ throw ('Unrecognized version of the imported pgsg file.'); } } - - + + // Serialize fields of json object serializePgsg(json){ // b64encode every field @@ -698,7 +702,7 @@ export class Main{ } return newjson; } - + // verifyPgsgV6 verifies a decoded pgsg // obj is pgsg with values b64decoded and certificates deserialized into Certificate class async verifyPgsgV6(obj) { @@ -707,7 +711,7 @@ export class Main{ // Step 1. Verify URLFetcher attestation doc and get notary's pubkey if (! globals.useNotaryNoSandbox){ - // by default we verify that the notary is indeed a properly sandboxed machine + // by default we verify that the notary is indeed a properly sandboxed machine var URLFetcherDoc = obj['URLFetcher attestation']; var notaryPubkey = await verifyNotary(URLFetcherDoc); } @@ -726,7 +730,7 @@ export class Main{ const certPath = vcRV.certificatePath; const commonName = getCommonName(certPath[0]); const altNames = getAltNames(certPath[0]); - + // Step 3. Verify that RSA signature over ephemeral EC key corresponds to the public key // from the leaf certificate const serverEcPubkey = obj['server pubkey for ECDHE']; @@ -735,13 +739,13 @@ export class Main{ const sr = obj['server random']; const vepsRV = await TLS.verifyECParamsSig(certPath[0], serverEcPubkey, rsaSig, cr, sr); assert (vepsRV === true); - + // Step 4. Combine PMS shares and derive expanded keys. const P256prime = 2n**256n - 2n**224n + 2n**192n + 2n**96n - 1n; // we may need to reduce mod prime if the sum overflows the prime const pms = int2ba((ba2int(obj['notary PMS share']) + ba2int(obj['client PMS share'])) % P256prime, 32); const [cwk, swk, civ, siv] = await getExpandedKeys(pms, cr, sr); - + // Step 5. Check that expanded keys match key shares const clientCwkShare = obj['client client_write_key share']; const clientCivShare = obj['client client_write_iv share']; @@ -755,7 +759,7 @@ export class Main{ assert(eq( xor(notaryCivShare, clientCivShare), civ)); assert(eq( xor(notarySwkShare, clientSwkShare), swk)); assert(eq( xor(notarySivShare, clientSivShare), siv)); - + // Step 6. Check session signature const commitHash = await TLSNotarySession.computeCommitHash(obj['server response records']); const keyShareHash = await sha256(concatTA(clientCwkShare, clientCivShare, @@ -775,29 +779,29 @@ export class Main{ obj['notarization time']); assert(await verifySig( - obj['ephemeral pubkey'], - obj['session signature'], + obj['ephemeral pubkey'], + obj['session signature'], tbs1) === true, 'Session signature verification failed.'); // Step 7. Verify ephemeral key const tbs2 = concatTA( - obj['ephemeral valid from'], - obj['ephemeral valid until'], + obj['ephemeral valid from'], + obj['ephemeral valid until'], obj['ephemeral pubkey']); assert(await verifySig( pubkeyPEM2raw(notaryPubkey), - obj['ephemeral signed by master key'], + obj['ephemeral signed by master key'], tbs2) === true, 'Master key signature verification failed.'); // notarization time must be within the time of ephemeral key validity assert( - ba2int(obj['ephemeral valid from']) < - ba2int(obj['notarization time']) < + ba2int(obj['ephemeral valid from']) < + ba2int(obj['notarization time']) < ba2int(obj['ephemeral valid until'])); - // Step 8. Decrypt client request and make sure that "Host" HTTP header corresponds to + // Step 8. Decrypt client request and make sure that "Host" HTTP header corresponds to // Common Name from the leaf certificate. const ghashInputs = []; const blockCount = obj['client request ciphertext'].length/16; @@ -807,10 +811,10 @@ export class Main{ // aad is additional authenticated data const aad = ghashInputs[0]; // TLS record seq number must be 1 - assert(eq(aad.slice(0,8), int2ba(1, 8))); + assert(eq(aad.slice(0, 8), int2ba(1, 8))); // TLS record type must be "application data" - assert(eq(aad.slice(8,11), new Uint8Array([23,3,3]))); - const recordLen = ba2int(aad.slice(11,13)); + assert(eq(aad.slice(8, 11), new Uint8Array([23, 3, 3]))); + const recordLen = ba2int(aad.slice(11, 13)); const plaintextBlocks = []; const ciphertext = ghashInputs.slice(1, ghashInputs.length-1); for (let i=0; i < ciphertext.length; i++){ @@ -841,14 +845,14 @@ export class Main{ } } assert(isFound, 'Host not found in certificate'); - + // Step 9. Check authentication tags of server response and decrypt it. const responseRecords = await decrypt_tls_responseV6( obj['server response records'], swk, siv); const response = ba2str(concatTA(...responseRecords)); - return [host, request, response, date.toGMTString()]; + return [host, request, response, date.toGMTString()]; } - + async importPgsgAndShow(importedData) { console.log('importedData', importedData); try { @@ -879,8 +883,8 @@ export class Main{ await saveNewSession (date, host, request, response, serializedPgsg, 'imported'); this.showSession(date); } - - + + async openViewer(sid) { const data = await getSession(sid); const blob = await getSessionBlob(sid); @@ -889,10 +893,10 @@ export class Main{ const request = blob.request; const response = blob.response; let tabId = null;// the id of the tab that we will be sending to - + const url = chrome.extension.getURL('ui/html/viewer.html'); await chrome.webRequest.handlerBehaviorChanged(); // flush the in-memory cache - + // reuse a tab if viewer was already open because we were importing file // this tab must be still active const active_tab = await new Promise(function(resolve) { @@ -911,7 +915,7 @@ export class Main{ } } tabId = active_tab.id; - + if (!isImportTab){ const myTabs = []; for (const win of views ){ @@ -925,9 +929,9 @@ export class Main{ console.log('checkIfTabOpened resolved'); resolve(); }); - }); + }); } - + console.log('send to viewer'); // the tab is either an already opened import tab or a fully-loaded new viewer tab // We already checked that the new viewer's tab DOM was loaded. Proceed to send the data @@ -943,13 +947,13 @@ export class Main{ } }); } - + async openDetails(sid, isEditor) { const data = await getSession(sid); const blob = await getSessionBlob(sid); const url = chrome.extension.getURL('ui/html/rawviewer.html'); let tabId = null; // id of the tab to which we will send the data - + const myTabs = []; const myViews = chrome.extension.getViews(); for (const win of myViews ){ @@ -963,8 +967,8 @@ export class Main{ await that.checkIfTabOpened(t, 'isRawViewer', myTabs); resolve(); }); - }); - + }); + chrome.runtime.sendMessage({ destination: 'rawviewer', message: isEditor ? 'edit' : 'show', @@ -975,10 +979,10 @@ export class Main{ sessionId: sid, serverName: data.serverName } - }); + }); } - - + + sendSessions(sessions) { const rows = []; for (const session of sessions){ @@ -993,7 +997,7 @@ export class Main{ } this.sendToManager(rows); } - + sendToManager(data, command) { console.log('sending sendToManager ', data); if (!command) command = 'payload'; // commands can be: payload, export @@ -1004,7 +1008,7 @@ export class Main{ payload: data }); } - + // for some pages we cant inject js/css, use the ugly alert uglyAlert(alertData) { const url = chrome.extension.getURL('ui/img/icon_error.png'); @@ -1013,7 +1017,7 @@ export class Main{ }); this.popupError = alertData; } - + sendAlert(alertData) { const that = this; chrome.tabs.query({active: true}, diff --git a/core/ProgressMonitor.js b/core/ProgressMonitor.js index f8585f5..f045339 100644 --- a/core/ProgressMonitor.js +++ b/core/ProgressMonitor.js @@ -1,3 +1,5 @@ +/* global chrome */ + // class ProgressMonitor receives progress information about client's garbling, // evaluation, blob upload, blob download. It dispatches progress status messages // periodically or when queried. @@ -48,7 +50,7 @@ export class ProgressMonitor{ // destroy de-registers listeners // doesn't do anything because of what seems like a Chrome bug. destroy(){ - // TODO it seems like Chrome does not remove onMessage listener + // TODO it seems like Chrome does not remove onMessage listener chrome.runtime.onMessage.removeListener(this.listener); } } \ No newline at end of file diff --git a/core/Socket.js b/core/Socket.js index f8c0a50..f5ca783 100644 --- a/core/Socket.js +++ b/core/Socket.js @@ -1,3 +1,5 @@ +/* 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 @@ -21,12 +23,12 @@ export class Socket { // connect will throw if we couldnt establish a connection to the server for this long this.connectTimeout = 5 * 1000; // recv() will reject if no data was seen for this long - this.noDataTimeout = 5 * 1000; + this.noDataTimeout = 5 * 1000; // close the socket after this time, even if it is in the middle of receiving data - this.lifeTime = 40 * 1000; + this.lifeTime = 40 * 1000; // delay after which we make a final check of the receicing buffer and if there was no data, // from the server, the we consider the data transmission finished - this.delayBeforeFinalIteration = 500; + this.delayBeforeFinalIteration = 500; this.wasClosed = false; this.backendPort = 20022; } @@ -41,7 +43,7 @@ export class Socket { return; }, that.connectTimeout); - const msg = {'command': 'connect','args': {'name': that.name,'port': that.port},'uid': that.uid}; + const msg = {'command': 'connect', 'args': {'name': that.name, 'port': that.port}, 'uid': that.uid}; if (globals.usePythonBackend){ const url = 'http://127.0.0.1:' + that.backendPort; const payload = JSON.stringify(msg); @@ -66,7 +68,7 @@ export class Socket { }); // we need to access runtime.lastError to prevent Chrome from complaining - // about unchecked error + // about unchecked error chrome.runtime.lastError; clearTimeout(timer); if (response == undefined){ @@ -82,7 +84,7 @@ export class Socket { }, that.lifeTime); // endless data fetching loop for the lifetime of this Socket that.fetchLoop(); - return 'ready'; + return 'ready'; } async send(data_in) { @@ -127,7 +129,7 @@ export class Socket { that.fetchLoop(); }, 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 recv (is_handshake = false) { @@ -180,7 +182,7 @@ export class Socket { buf = rv.incomprecs; setTimeout(function() {check();}, 100); return; - } + } else { console.log('got complete records', that.uid); if (is_handshake) { @@ -205,7 +207,7 @@ export class Socket { async close() { this.wasClosed = true; - var msg = {'command': 'close','uid': this.uid}; + var msg = {'command': 'close', 'uid': this.uid}; console.log('closing socket', this.uid); if (globals.usePythonBackend){ await fetch('http://127.0.0.1:20022', {method:'POST', body: JSON.stringify(msg), @@ -250,10 +252,4 @@ export class Socket { } } } -} - - -if (typeof module !== 'undefined'){ // we are in node.js environment - module.exports={ - }; } \ No newline at end of file diff --git a/core/TLS.js b/core/TLS.js index 2fd1936..406bc88 100644 --- a/core/TLS.js +++ b/core/TLS.js @@ -1,37 +1,12 @@ +/* global SocketNode */ + import {concatTA, int2ba, sha256, str2ba, assert, ba2int, getRandom, sigDER2p1363, pubkeyPEM2raw, eq, xor, AESECBencrypt, b64urlencode} from './utils.js'; import {verifyChain, checkCertSubjName} from './verifychain.js'; import {Socket} from './Socket.js'; -export class TLS { - // allHandshakes is a concatenation of all handshake messages up to this point. - // This is only data visible at the handshake layer and does not include record layer headers - allHandshakes; - // certPath is an array of certificates from the server arranged by pkijs in the ascending - // order from leaf to root - certPath; - // cke is TLS handshake's Client Key Exchange message - cke; - clientRandom; - commPrivkey; - commSymmetricKey; - headers; - isMhm; // multiple handshake messages - mustVerifyCert; - notaryWillEncryptRequest; - options; - port; - rsaSig; - serverRandom; - sckt; - serverEcPubkey; - secret; // for debug purposes only - // sid is id for this notarization session - sid; - serverName; - useMaxFragmentLength; - +export class TLS { constructor (serverName, port, headers, options){ this.serverName = serverName; this.port = port; @@ -41,10 +16,38 @@ export class TLS { this.useMaxFragmentLength = options.useMaxFragmentLength; this.notaryWillEncryptRequest = options.notaryWillEncryptRequest; this.mustVerifyCert = options.mustVerifyCert; + // allHandshakes is a concatenation of all handshake messages up to this point. + // This is only data visible at the handshake layer and does not include record layer headers + this.allHandshakes; + // certPath is an array of certificates from the server arranged by pkijs in the ascending + // order from leaf to root + this.certPath; + // cke is TLS handshake's Client Key Exchange message + this.cke; + this.clientRandom; + this.commPrivkey; + this.commSymmetricKey; + this.headers; + this.isMhm; // multiple handshake messages + this.mustVerifyCert; + this.notaryWillEncryptRequest; + this.options; + this.port; + this.rsaSig; + this.serverRandom; + this.sckt; + this.serverEcPubkey; + this.secret; // for debug purposes only + // sid is id for this notarization session + this.sid; + this.serverName; + this.useMaxFragmentLength; + + if (typeof(window) !== 'undefined'){ this.sckt = new Socket(serverName, port); } else { - // in node SocketNode was made global + // in node SocketNode was made global this.sckt = new SocketNode(serverName, port); } } @@ -56,7 +59,7 @@ export class TLS { tmp.push(0x00, 0x02); // Supported Groups List Length tmp.push(0x00, 0x17); // Supported Group: secp256r1 const supported_groups_extension = new Uint8Array(tmp); - + tmp = []; tmp.push(0x00, 0x0d); // Type signature_algorithms tmp.push(0x00, 0x04); // Length @@ -73,7 +76,7 @@ export class TLS { tmp.push(...Array.from(int2ba(server_name.length, 2))); // Server Name Length tmp.push(...Array.from(server_name)); const server_name_extension = new Uint8Array(tmp); - + tmp = []; if (this.useMaxFragmentLength){ tmp.push(0x00, 0x01); // Type: max_fragment_length @@ -83,12 +86,12 @@ export class TLS { tmp.push(0x04); } const max_fragment_length_extension = new Uint8Array(tmp); - + const extlen = supported_groups_extension.length + signature_algorithm_extension.length + server_name_extension.length + max_fragment_length_extension.length; - + tmp = []; - tmp.push(0x01); // Handshake type: Client Hello + tmp.push(0x01); // Handshake type: Client Hello tmp.push(...int2ba(extlen + 43, 3) ); // Length tmp.push(0x03, 0x03); // Version: TLS 1.2 this.clientRandom = getRandom(32); @@ -105,23 +108,23 @@ export class TLS { signature_algorithm_extension, server_name_extension, max_fragment_length_extension); - + this.allHandshakes = ch; - + tmp = []; tmp.push(0x16); // Type: Handshake tmp.push(0x03, 0x03); // Version: TLS 1.2 - tmp.push(...int2ba(ch.length, 2)); // Length + tmp.push(...int2ba(ch.length, 2)); // Length const tls_record_header = new Uint8Array(tmp); return concatTA(tls_record_header, ch); } async verifyNotarySig(sigDER, pubKey, signed_data, options){ - const isRaw = (options == 'raw') ? true : false; + const isRaw = (options == 'raw') ? true : false; const sig_p1363 = sigDER2p1363(sigDER); const notaryPubkey = isRaw ? pubKey : pubkeyPEM2raw(pubKey); - + const pubkeyCryptoKey = await crypto.subtle.importKey( 'raw', notaryPubkey.buffer, {name: 'ECDSA', namedCurve:'P-256'}, true, ['verify']); // eslint-disable-next-line no-unused-vars @@ -130,43 +133,6 @@ export class TLS { return result; } - - async parse_commpk_commpksig_cpk(dataEnc){ - // Notary's EC pubkey (to derive ECDH secret for communication) is not encrypted - const comm_pk = dataEnc.slice(0,65); - // for debug purposes only - this.secret = dataEnc.slice(65,97); - - this.commSymmetricKey = await getECDHSecret(comm_pk, this.commPrivkey); - const data = await this.decryptFromNotary( - this.commSymmetricKey, - dataEnc.slice(65+this.secret.length)); - - let o = 0; // offset - // get signature over communication pubkey - const ssrvLen = ba2int(data.slice(o,o+=1)); - const signingServerRetval = data.slice(o, o+=ssrvLen); - const cpk = data.slice(o,o+=65); // Client's pubkey for ECDH - // parse signing server's return value - o = 0; - const sessionSigLen = ba2int(signingServerRetval.slice(o, o+=1)); - const sessionSig = signingServerRetval.slice(o, o+=sessionSigLen); - const ephemKeySigLen = ba2int(signingServerRetval.slice(o, o+=1)); - const ephemKeySig = signingServerRetval.slice(o, o+=ephemKeySigLen); - const ephemPubKey = signingServerRetval.slice(o, o+=65); - const ephemValidFrom = signingServerRetval.slice(o, o+=4); - const ephemValidUntil = signingServerRetval.slice(o, o+=4); - - // check signature - const to_be_signed = await sha256(comm_pk); - assert(await this.verifyNotarySig(sessionSig, ephemPubKey, to_be_signed, 'raw') == true); - // the ephemeral key with its validity time range is signed by master key - const ephemTBS = await sha256(concatTA(ephemPubKey, ephemValidFrom, ephemValidUntil)); - assert(await this.verifyNotarySig(ephemKeySig, this.notary.pubkeyPEM, ephemTBS) == true); - this.checkEphemKeyExpiration(ephemValidFrom, ephemValidUntil); - return cpk; - } - parseServerHello(s){ let p = 0; assert(eq(s.slice(p, p+=1), [0x02])); // Server Hello @@ -177,8 +143,8 @@ export class TLS { const sidlen = ba2int(s.slice(p, p+=1)); if (sidlen > 0){ p+=sidlen; // 32 bytes of session ID, if any - } - assert(eq(s.slice(p, p+=2), [0xc0, 0x2f])); // Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + } + assert(eq(s.slice(p, p+=2), [0xc0, 0x2f])); // Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 assert(eq(s.slice(p, p+=1), [0x00])); // Compression Method: null (0) // May contain Extensions. We don't need to parse them } @@ -187,7 +153,7 @@ export class TLS { let p = 0; assert(eq(s.slice(p, p+=1), [0x0b])); // Certificate // eslint-disable-next-line no-unused-vars - const clen = ba2int(s.slice(p, p+=3)); + const clen = ba2int(s.slice(p, p+=3)); const certslen = ba2int(s.slice(p, p+=3)); const certs_last_pos = p + certslen; const certs = []; @@ -205,10 +171,10 @@ export class TLS { } async parseServerKeyExchange(s){ - let p = 0; + let p = 0; assert(eq(s.slice(p, p+=1), [0x0c])); // Handshake Type: Server Key Exchange (12) // eslint-disable-next-line no-unused-vars - const skelen = ba2int(s.slice(p, p+=3)); + const skelen = ba2int(s.slice(p, p+=3)); // EC Diffie-Hellman Server Params assert(eq(s.slice(p, p+=1), [0x03])); // Curve Type: named_curve (0x03) assert(eq(s.slice(p, p+=2), [0x00, 0x17])); // Named Curve: secp256r1 (0x0017) @@ -218,7 +184,7 @@ export class TLS { assert(eq(s.slice(p, p+=2), [0x04, 0x01])); // #Signature Algorithm: rsa_pkcs1_sha256 (0x0401) const siglen = ba2int(s.slice(p, p+=2)); this.rsaSig = s.slice(p, p+=siglen); - + const result = await TLS.verifyECParamsSig(this.certPath[0], this.serverEcPubkey, this.rsaSig, this.clientRandom, this.serverRandom); assert (result == true); return p; @@ -235,7 +201,7 @@ export class TLS { if (i == chunks-1){ // last chunk may be smaller const rem = headers.length % chunkSize; - thisChunkSize = (rem == 0) ? chunkSize : rem; + thisChunkSize = (rem == 0) ? chunkSize : rem; } const explicit_nonce = int2ba(2+i, 8); // const explicit_nonce = getRandom(8) @@ -244,20 +210,20 @@ export class TLS { const aad = concatTA( int2ba(seq_num, 8), new Uint8Array([0x17, 0x03, 0x03]), // type 0x17 = Application data , TLS Version 1.2 - int2ba(thisChunkSize, 2)); // unencrypted data length in bytes + int2ba(thisChunkSize, 2)); // unencrypted data length in bytes const cwkCryptoKey = await crypto.subtle.importKey( - 'raw', - client_write_key.buffer, - 'AES-GCM', - true, + 'raw', + client_write_key.buffer, + 'AES-GCM', + true, ['encrypt', 'decrypt']); const ciphertext = await crypto.subtle.encrypt({ - name: 'AES-GCM', + name: 'AES-GCM', iv: nonce.buffer, - additionalData: aad.buffer}, - cwkCryptoKey, + additionalData: aad.buffer}, + cwkCryptoKey, headers.slice(chunkSize*i, chunkSize*(i+1)).buffer); - encReq.push(concatTA(explicit_nonce, new Uint8Array(ciphertext))); + encReq.push(concatTA(explicit_nonce, new Uint8Array(ciphertext))); } return encReq; } @@ -271,7 +237,7 @@ export class TLS { } async buildAndSendClientHello(){ - const ch = this.buildClientHello(); + const ch = this.buildClientHello(); await this.sckt.connect(); this.sckt.send(ch); // Send Client Hello } @@ -287,18 +253,18 @@ export class TLS { // some incompatible websites silently do not respond to ClientHello throw('Failed to receive a response from a webserver. Make sure your internet connection is working and try again. If this error persists, this may mean that the webserver is not compatible with PageSigner. Please contact the PageSigner devs about this issue.'); } - // restore normal timeout value + // restore normal timeout value this.sckt.recv_timeout = 20 * 1000; // Parse Server Hello, Certificate, Server Key Exchange, Server Hello Done - if (eq(s.slice(0,2), [0x15, 0x03])){ + if (eq(s.slice(0, 2), [0x15, 0x03])){ console.log('Server sent Alert instead of Server Hello'); throw ('Unfortunately PageSigner is not yet able to notarize this website. Please contact the PageSigner devs about this issue.'); } let p = 0; // current position in the byte stream assert(eq(s.slice(p, p+=1), [0x16])); // Type: Handshake assert(eq(s.slice(p, p+=2), [0x03, 0x03])); // Version: TLS 1.2 - const handshakelen = ba2int(s.slice(p, p+=2)); + const handshakelen = ba2int(s.slice(p, p+=2)); // This may be the length of multiple handshake messages (MHM) // For MHM there is only 1 TLS Record layer header followed by Handshake layer messages // Without MHM, each handshake message has its own TLS Record header @@ -314,7 +280,7 @@ export class TLS { this.isMhm = true; }// multiple handshake messages let reclenMhm = 0; if (!this.isMhm){ - // read the TLS Record header + // read the TLS Record header assert(eq(s.slice(p, p+=3), [0x16, 0x03, 0x03])); // Type: Handshake # Version: TLS 1.2 reclenMhm = ba2int(s.slice(p, p+=2)); } @@ -327,7 +293,7 @@ export class TLS { this.allHandshakes = concatTA(this.allHandshakes, c); const cParsedByted = await this.parseCertificate(c); p += cParsedByted; - + if (this.isMhm && (handshakelen+5 == p)){ // another MHM header will follow, read its header assert(eq(s.slice(p, p+=1), [0x16])); // Type: Handshake @@ -337,7 +303,7 @@ export class TLS { } reclenMhm = 0; if (!this.isMhm){ - // read the TLS Record header + // read the TLS Record header assert(eq(s.slice(p, p+=3), [0x16, 0x03, 0x03])); // Type: Handshake # Version: TLS 1.2 reclenMhm = ba2int(s.slice(p, p+=2)); } @@ -350,10 +316,10 @@ export class TLS { this.allHandshakes = concatTA(this.allHandshakes, ske); const skeParsedByted = await this.parseServerKeyExchange(ske); p += skeParsedByted; - + // Parse Server Hello Done if (!this.isMhm) { - // read the TLS Record header + // read the TLS Record header assert(eq(s.slice(p, p+=3), [0x16, 0x03, 0x03])); // Type: Handshake # Version: TLS 1.2 // eslint-disable-next-line no-unused-vars const reclen = ba2int(s.slice(p, p+=2)); @@ -368,7 +334,7 @@ export class TLS { // buildClientKeyExchange builds the TLS handshake's Client Key Exchange message // cpubBytes is client's pubkey for the ECDH async buildClientKeyExchange(cpubBytes){ - let tmp = [0x10]; // Handshake type: Client Key Exchange + let tmp = [0x10]; // Handshake type: Client Key Exchange tmp.push(0x00, 0x00, 0x42); // Length tmp.push(0x41); // Pubkey Length: 65 // 0x04 means compressed pubkey format @@ -392,10 +358,11 @@ export class TLS { return this.rsaSig; } + // TODO this description is incorrect // sendClientFinished accepts encrypted Client Finished (CF), auth tag for CF, verify_data for CF. // It then sends Client Key Exchange, Change Cipher Spec and encrypted Client Finished async sendClientFinished(encCF, tagCF){ - const cke_tls_record_header = new Uint8Array([0x16, 0x03, 0x03, 0x00, 0x46]); // Type: Handshake, Version: TLS 1.2, Length + const cke_tls_record_header = new Uint8Array([0x16, 0x03, 0x03, 0x00, 0x46]); // Type: Handshake, Version: TLS 1.2, Length const ccs = new Uint8Array([0x14, 0x03, 0x03, 0x00, 0x01, 0x01]); const client_finished = concatTA(int2ba(1, 8), encCF, tagCF); // Finished message of 40 (0x28) bytes length @@ -413,12 +380,12 @@ export class TLS { async receiveServerFinished(){ const data = await this.sckt.recv(true); - if (eq(data.slice(0,2), [0x15, 0x03])){ + if (eq(data.slice(0, 2), [0x15, 0x03])){ console.log('Server sent Alert instead of Server Finished'); throw('Server sent Alert instead of Server Finished'); } // Parse CCS and Server's Finished - const ccs_server = data.slice(0,6); + const ccs_server = data.slice(0, 6); assert(eq(ccs_server, [0x14, 0x03, 0x03, 0x00, 0x01, 0x01])); let f = null; // server finished @@ -429,8 +396,8 @@ export class TLS { else { f = data.slice(6); } - - assert (eq(f.slice(0,5), [0x16, 0x03, 0x03, 0x00, 0x28])); + + assert (eq(f.slice(0, 5), [0x16, 0x03, 0x03, 0x00, 0x28])); const encSF = f.slice(5, 45); // encrypted Server Finished // There may be some extra data received after the Server Finished. We ignore it. return encSF; @@ -479,7 +446,7 @@ export class TLS { for (var i=0; i -1) isImported = true; + if (options.indexOf('imported') > -1) isImported = true; if (options.indexOf('edited') > -1) isEdited = true; } // sessionName can be changed by the user in the manager window @@ -154,7 +155,7 @@ export async function saveNewSession(date, host, request, response, pgsg, option isImported: isImported, isEdited: isEdited, version: 6}); - tx.oncomplete = function() { + tx.oncomplete = function() { resolve(); }; tx.onerror = function(event) { @@ -164,14 +165,14 @@ export async function saveNewSession(date, host, request, response, pgsg, option }); await new Promise(function(resolve, reject) { const tx2 = db_blobs.transaction(['sessions'], 'readwrite'); - const store2 = tx2.objectStore('sessions'); + const store2 = tx2.objectStore('sessions'); store2.add({ creationTime: date, - serverName:host, + serverName:host, request:request, response:response, - pgsg:pgsg}); - tx2.oncomplete = function() { + pgsg:pgsg}); + tx2.oncomplete = function() { resolve(); }; tx2.onerror = function(event) { @@ -181,15 +182,15 @@ export async function saveNewSession(date, host, request, response, pgsg, option }); } - + export async function getSession(idx){ return await new Promise(function(resolve, reject) { const tx = db.transaction(['sessions'], 'readonly'); - const store = tx.objectStore('sessions'); + const store = tx.objectStore('sessions'); const req = store.get(idx); req.onsuccess = function(event) { - const entry = event.target.result; + const entry = event.target.result; if (entry) { console.log(entry); resolve(entry); @@ -205,7 +206,7 @@ export async function getSession(idx){ }); } - + @@ -215,10 +216,10 @@ export async function getSessionBlob(idx){ return await new Promise(function(resolve, reject) { const tx = db_blobs.transaction(['sessions'], 'readonly'); - const store = tx.objectStore('sessions'); + const store = tx.objectStore('sessions'); const req = store.get(idx); req.onsuccess = function(event) { - const entry = event.target.result; + const entry = event.target.result; if (entry) { resolve(entry); } else { @@ -239,10 +240,10 @@ export async function getPref(pref){ return await new Promise(function(resolve, reject) { let tx = db.transaction(['preferences'], 'readonly'); - let store = tx.objectStore('preferences'); + let store = tx.objectStore('preferences'); let req = store.get(pref); req.onsuccess = function(event) { - let entry = event.target.result; + let entry = event.target.result; if (entry) { console.log(entry); resolve(entry.value); @@ -261,18 +262,18 @@ export async function getPref(pref){ export async function setPref(pref, newvalue) { await new Promise(function(resolve, reject) { - + const tx = db.transaction(['preferences'], 'readwrite'); const store = tx.objectStore('preferences'); const request = store.get(pref); - + request.onsuccess = function(event) { // Get the old value that we want to update const data = event.target.result; - + // update the value(s) in the object that you want to change data.value = newvalue; - + // Put this updated object back into the database. const requestUpdate = store.put(data); requestUpdate.onerror = function(event) { @@ -296,14 +297,14 @@ export async function renameSession(id, newname) { const tx = db.transaction(['sessions'], 'readwrite'); const sessions = tx.objectStore('sessions'); const request = sessions.get(id); - + request.onsuccess = function(event) { // Get the old value that we want to update const data = event.target.result; - + // update the value(s) in the object that you want to change data.sessionName = newname; - + // Put this updated object back into the database. const requestUpdate = sessions.put(data); requestUpdate.onerror = function(event) { @@ -316,4 +317,4 @@ export async function renameSession(id, newname) { }; }; }); -} \ No newline at end of file +} \ No newline at end of file diff --git a/core/internal.js b/core/internal.js new file mode 100644 index 0000000..2a34763 --- /dev/null +++ b/core/internal.js @@ -0,0 +1,18 @@ +// allows to access PageSigner's internal classes by exposing them to the +// extension's window global + +import * as utils from './utils.js'; +import * as indexeddb from './indexeddb.js'; +import * as globals from './globals.js'; +import * as Main from './Main.js'; +import {OTSender} from './twopc/OTSender.js'; +import {OTReceiver} from './twopc/OTReceiver.js'; + + +window.PageSigner = { + Main:Main, + globals:globals, + utils:utils, + indexeddb:indexeddb, + OTSender:OTSender, + OTReceiver:OTReceiver}; diff --git a/core/oracles.js b/core/oracles.js index 0780849..2a57840 100644 --- a/core/oracles.js +++ b/core/oracles.js @@ -1,4 +1,4 @@ -import {ba2str, b64decode, assert, ba2int, verifyAttestationDoc, ba2hex, eq, +import {ba2str, b64decode, assert, ba2int, verifyAttestationDoc, ba2hex, eq, sha256} from './utils.js'; // rootsOfTrust contains an array of trusted EBS snapshots @@ -45,8 +45,8 @@ function checkDescribeInstances(xmlDoc, instanceId, imageId, volumeId) { assert(parent.getElementsByTagName('instanceId')[0].textContent === instanceId); assert(parent.getElementsByTagName('imageId')[0].textContent === imageId); assert(parent.getElementsByTagName('instanceState')[0].getElementsByTagName('name')[0].textContent === 'running'); - // other instance types may use non-nvme disks and thus would bypass the check that - // only one nvme* disk is allowed + // other instance types may use non-nvme disks and thus would bypass the check that + // only one nvme* disk is allowed assert (parent.getElementsByTagName('instanceType')[0].textContent.startsWith('t3')); var launchTime = parent.getElementsByTagName('launchTime')[0].textContent; assert(parent.getElementsByTagName('rootDeviceType')[0].textContent === 'ebs'); @@ -112,8 +112,8 @@ function checkGetConsoleOutput(xmlDoc, instanceId) { // a redundant check, because the patched ramdisk must halt the boot process if // it detects more than one disk device. const allowedSet = ['nvme', 'nvme0', 'nvme0n1', 'nvme0n1p1']; - // match all substrings starting with nvme, folowed by a count of from 0 to 7 symbols from - // the ranges 0-9 and a-z + // match all substrings starting with nvme, folowed by a count of from 0 to 7 symbols from + // the ranges 0-9 and a-z for (const match of [...logstr.matchAll(/nvme[0-9a-z]{0,7}/g)]){ assert(match.length == 1); assert(allowedSet.includes(match[0]), 'disallowed nvme* string present in log'); @@ -192,7 +192,7 @@ function checkGetUser(xmlDoc, ownerId) { function checkDescribeImages(xmlDoc, imageId, snapshotIds){ - try { + try { assert(xmlDoc.getElementsByTagName('DescribeImagesResponse').length == 1); const images = xmlDoc.getElementsByTagName('imagesSet')[0].children; assert(images.length == 1); @@ -224,7 +224,7 @@ async function fetch_and_parse(obj){ } export async function getURLFetcherDoc(IP, port = 10011){ - // get URLFetcher document containing attestation for AWS HTTP API URLs needed to + // get URLFetcher document containing attestation for AWS HTTP API URLs needed to // verify that the oracle was correctly set up. // https://github.com/tlsnotary/URLFetcher const resp = await fetch('http://' + IP + ':' + port + '/getURLFetcherDoc', { @@ -237,8 +237,8 @@ export async function getURLFetcherDoc(IP, port = 10011){ export async function verifyNotary(URLFetcherDoc) { // URLFetcherDoc is a concatenation of 4-byte transcript length | transcript | attestation doc - const transcriptLen = ba2int(URLFetcherDoc.slice(0,4)); - const transcript = URLFetcherDoc.slice(4,4+transcriptLen); + const transcriptLen = ba2int(URLFetcherDoc.slice(0, 4)); + const transcript = URLFetcherDoc.slice(4, 4+transcriptLen); const attestation = URLFetcherDoc.slice(4+transcriptLen); // transcript is a JSON array for each request[ {"request":, "response":} , {...}] @@ -266,7 +266,7 @@ export async function verifyNotary(URLFetcherDoc) { // check that the URLs are formatted in a canonical way - // Note that AWS expects URL params to be sorted alphabetically. If we put them in + // Note that AWS expects URL params to be sorted alphabetically. If we put them in // arbitrary order, the query will be rejected // "AWSAccessKeyId" should be the same in all URLs to prove that the queries are made @@ -316,7 +316,7 @@ export async function verifyNotary(URLFetcherDoc) { const xmlDocGCO = await fetch_and_parse(o.GCO); const pubkeyPEM = checkGetConsoleOutput(xmlDocGCO, instanceId); - + const xmlDocDIAud = await fetch_and_parse(o.DIAud); checkDescribeInstanceAttributeUserdata(xmlDocDIAud, instanceId); @@ -338,12 +338,4 @@ export async function verifyNotary(URLFetcherDoc) { console.log('oracle verification successfully finished'); return pubkeyPEM; -} - - -if (typeof module !== 'undefined'){ // we are in node.js environment - module.exports={ - check_oracle: verify_oracle, - oracle, - }; } \ No newline at end of file diff --git a/core/third-party/nacl-fast.js b/core/third-party/nacl-fast.js deleted file mode 100644 index 7ea5fb5..0000000 --- a/core/third-party/nacl-fast.js +++ /dev/null @@ -1,2391 +0,0 @@ -(function(nacl) { -'use strict'; - -// Ported in 2014 by Dmitry Chestnykh and Devi Mandiri. -// Public domain. -// -// Implementation derived from TweetNaCl version 20140427. -// See for details: http://tweetnacl.cr.yp.to/ - -var gf = function(init) { - var i, r = new Float64Array(16); - if (init) for (i = 0; i < init.length; i++) r[i] = init[i]; - return r; -}; - -// Pluggable, initialized in high-level API below. -var randombytes = function(/* x, n */) { throw new Error('no PRNG'); }; - -var _0 = new Uint8Array(16); -var _9 = new Uint8Array(32); _9[0] = 9; - -var gf0 = gf(), - gf1 = gf([1]), - _121665 = gf([0xdb41, 1]), - D = gf([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]), - D2 = gf([0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406]), - X = gf([0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169]), - Y = gf([0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666]), - I = gf([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]); - -function ts64(x, i, h, l) { - x[i] = (h >> 24) & 0xff; - x[i+1] = (h >> 16) & 0xff; - x[i+2] = (h >> 8) & 0xff; - x[i+3] = h & 0xff; - x[i+4] = (l >> 24) & 0xff; - x[i+5] = (l >> 16) & 0xff; - x[i+6] = (l >> 8) & 0xff; - x[i+7] = l & 0xff; -} - -function vn(x, xi, y, yi, n) { - var i,d = 0; - for (i = 0; i < n; i++) d |= x[xi+i]^y[yi+i]; - return (1 & ((d - 1) >>> 8)) - 1; -} - -function crypto_verify_16(x, xi, y, yi) { - return vn(x,xi,y,yi,16); -} - -function crypto_verify_32(x, xi, y, yi) { - return vn(x,xi,y,yi,32); -} - -function core_salsa20(o, p, k, 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, - 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; - - 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; -} - -function core_hsalsa20(o,p,k,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, - 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); - } - - o[ 0] = x0 >>> 0 & 0xff; - o[ 1] = x0 >>> 8 & 0xff; - o[ 2] = x0 >>> 16 & 0xff; - o[ 3] = x0 >>> 24 & 0xff; - - o[ 4] = x5 >>> 0 & 0xff; - o[ 5] = x5 >>> 8 & 0xff; - o[ 6] = x5 >>> 16 & 0xff; - o[ 7] = x5 >>> 24 & 0xff; - - o[ 8] = x10 >>> 0 & 0xff; - o[ 9] = x10 >>> 8 & 0xff; - o[10] = x10 >>> 16 & 0xff; - o[11] = x10 >>> 24 & 0xff; - - o[12] = x15 >>> 0 & 0xff; - o[13] = x15 >>> 8 & 0xff; - o[14] = x15 >>> 16 & 0xff; - o[15] = x15 >>> 24 & 0xff; - - o[16] = x6 >>> 0 & 0xff; - o[17] = x6 >>> 8 & 0xff; - o[18] = x6 >>> 16 & 0xff; - o[19] = x6 >>> 24 & 0xff; - - o[20] = x7 >>> 0 & 0xff; - o[21] = x7 >>> 8 & 0xff; - o[22] = x7 >>> 16 & 0xff; - o[23] = x7 >>> 24 & 0xff; - - o[24] = x8 >>> 0 & 0xff; - o[25] = x8 >>> 8 & 0xff; - o[26] = x8 >>> 16 & 0xff; - o[27] = x8 >>> 24 & 0xff; - - o[28] = x9 >>> 0 & 0xff; - o[29] = x9 >>> 8 & 0xff; - o[30] = x9 >>> 16 & 0xff; - o[31] = x9 >>> 24 & 0xff; -} - -function crypto_core_salsa20(out,inp,k,c) { - core_salsa20(out,inp,k,c); -} - -function crypto_core_hsalsa20(out,inp,k,c) { - core_hsalsa20(out,inp,k,c); -} - -var sigma = new Uint8Array([101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98, 121, 116, 101, 32, 107]); - // "expand 32-byte k" - -function crypto_stream_salsa20_xor(c,cpos,m,mpos,b,n,k) { - var z = new Uint8Array(16), x = new Uint8Array(64); - var u, i; - for (i = 0; i < 16; i++) z[i] = 0; - for (i = 0; i < 8; i++) z[i] = n[i]; - while (b >= 64) { - crypto_core_salsa20(x,z,k,sigma); - for (i = 0; i < 64; i++) c[cpos+i] = m[mpos+i] ^ x[i]; - u = 1; - for (i = 8; i < 16; i++) { - u = u + (z[i] & 0xff) | 0; - z[i] = u & 0xff; - u >>>= 8; - } - b -= 64; - cpos += 64; - mpos += 64; - } - if (b > 0) { - crypto_core_salsa20(x,z,k,sigma); - for (i = 0; i < b; i++) c[cpos+i] = m[mpos+i] ^ x[i]; - } - return 0; -} - -function crypto_stream_salsa20(c,cpos,b,n,k) { - var z = new Uint8Array(16), x = new Uint8Array(64); - var u, i; - for (i = 0; i < 16; i++) z[i] = 0; - for (i = 0; i < 8; i++) z[i] = n[i]; - while (b >= 64) { - crypto_core_salsa20(x,z,k,sigma); - for (i = 0; i < 64; i++) c[cpos+i] = x[i]; - u = 1; - for (i = 8; i < 16; i++) { - u = u + (z[i] & 0xff) | 0; - z[i] = u & 0xff; - u >>>= 8; - } - b -= 64; - cpos += 64; - } - if (b > 0) { - crypto_core_salsa20(x,z,k,sigma); - for (i = 0; i < b; i++) c[cpos+i] = x[i]; - } - return 0; -} - -function crypto_stream(c,cpos,d,n,k) { - var s = new Uint8Array(32); - crypto_core_hsalsa20(s,n,k,sigma); - var sn = new Uint8Array(8); - for (var i = 0; i < 8; i++) sn[i] = n[i+16]; - return crypto_stream_salsa20(c,cpos,d,sn,s); -} - -function crypto_stream_xor(c,cpos,m,mpos,d,n,k) { - var s = new Uint8Array(32); - crypto_core_hsalsa20(s,n,k,sigma); - var sn = new Uint8Array(8); - for (var i = 0; i < 8; i++) sn[i] = n[i+16]; - return crypto_stream_salsa20_xor(c,cpos,m,mpos,d,sn,s); -} - -/* -* Port of Andrew Moon's Poly1305-donna-16. Public domain. -* https://github.com/floodyberry/poly1305-donna -*/ - -var poly1305 = function(key) { - this.buffer = new Uint8Array(16); - this.r = new Uint16Array(10); - this.h = new Uint16Array(10); - this.pad = new Uint16Array(8); - this.leftover = 0; - this.fin = 0; - - var t0, t1, t2, t3, t4, t5, t6, t7; - - t0 = key[ 0] & 0xff | (key[ 1] & 0xff) << 8; this.r[0] = ( t0 ) & 0x1fff; - t1 = key[ 2] & 0xff | (key[ 3] & 0xff) << 8; this.r[1] = ((t0 >>> 13) | (t1 << 3)) & 0x1fff; - t2 = key[ 4] & 0xff | (key[ 5] & 0xff) << 8; this.r[2] = ((t1 >>> 10) | (t2 << 6)) & 0x1f03; - t3 = key[ 6] & 0xff | (key[ 7] & 0xff) << 8; this.r[3] = ((t2 >>> 7) | (t3 << 9)) & 0x1fff; - t4 = key[ 8] & 0xff | (key[ 9] & 0xff) << 8; this.r[4] = ((t3 >>> 4) | (t4 << 12)) & 0x00ff; - this.r[5] = ((t4 >>> 1)) & 0x1ffe; - t5 = key[10] & 0xff | (key[11] & 0xff) << 8; this.r[6] = ((t4 >>> 14) | (t5 << 2)) & 0x1fff; - t6 = key[12] & 0xff | (key[13] & 0xff) << 8; this.r[7] = ((t5 >>> 11) | (t6 << 5)) & 0x1f81; - t7 = key[14] & 0xff | (key[15] & 0xff) << 8; this.r[8] = ((t6 >>> 8) | (t7 << 8)) & 0x1fff; - this.r[9] = ((t7 >>> 5)) & 0x007f; - - this.pad[0] = key[16] & 0xff | (key[17] & 0xff) << 8; - this.pad[1] = key[18] & 0xff | (key[19] & 0xff) << 8; - this.pad[2] = key[20] & 0xff | (key[21] & 0xff) << 8; - this.pad[3] = key[22] & 0xff | (key[23] & 0xff) << 8; - this.pad[4] = key[24] & 0xff | (key[25] & 0xff) << 8; - this.pad[5] = key[26] & 0xff | (key[27] & 0xff) << 8; - this.pad[6] = key[28] & 0xff | (key[29] & 0xff) << 8; - this.pad[7] = key[30] & 0xff | (key[31] & 0xff) << 8; -}; - -poly1305.prototype.blocks = function(m, mpos, bytes) { - var hibit = this.fin ? 0 : (1 << 11); - var t0, t1, t2, t3, t4, t5, t6, t7, c; - var d0, d1, d2, d3, d4, d5, d6, d7, d8, d9; - - var h0 = this.h[0], - h1 = this.h[1], - h2 = this.h[2], - h3 = this.h[3], - h4 = this.h[4], - h5 = this.h[5], - h6 = this.h[6], - h7 = this.h[7], - h8 = this.h[8], - h9 = this.h[9]; - - var r0 = this.r[0], - r1 = this.r[1], - r2 = this.r[2], - r3 = this.r[3], - r4 = this.r[4], - r5 = this.r[5], - r6 = this.r[6], - r7 = this.r[7], - r8 = this.r[8], - r9 = this.r[9]; - - while (bytes >= 16) { - t0 = m[mpos+ 0] & 0xff | (m[mpos+ 1] & 0xff) << 8; h0 += ( t0 ) & 0x1fff; - t1 = m[mpos+ 2] & 0xff | (m[mpos+ 3] & 0xff) << 8; h1 += ((t0 >>> 13) | (t1 << 3)) & 0x1fff; - t2 = m[mpos+ 4] & 0xff | (m[mpos+ 5] & 0xff) << 8; h2 += ((t1 >>> 10) | (t2 << 6)) & 0x1fff; - t3 = m[mpos+ 6] & 0xff | (m[mpos+ 7] & 0xff) << 8; h3 += ((t2 >>> 7) | (t3 << 9)) & 0x1fff; - t4 = m[mpos+ 8] & 0xff | (m[mpos+ 9] & 0xff) << 8; h4 += ((t3 >>> 4) | (t4 << 12)) & 0x1fff; - h5 += ((t4 >>> 1)) & 0x1fff; - t5 = m[mpos+10] & 0xff | (m[mpos+11] & 0xff) << 8; h6 += ((t4 >>> 14) | (t5 << 2)) & 0x1fff; - t6 = m[mpos+12] & 0xff | (m[mpos+13] & 0xff) << 8; h7 += ((t5 >>> 11) | (t6 << 5)) & 0x1fff; - t7 = m[mpos+14] & 0xff | (m[mpos+15] & 0xff) << 8; h8 += ((t6 >>> 8) | (t7 << 8)) & 0x1fff; - h9 += ((t7 >>> 5)) | hibit; - - c = 0; - - d0 = c; - d0 += h0 * r0; - d0 += h1 * (5 * r9); - d0 += h2 * (5 * r8); - d0 += h3 * (5 * r7); - d0 += h4 * (5 * r6); - c = (d0 >>> 13); d0 &= 0x1fff; - d0 += h5 * (5 * r5); - d0 += h6 * (5 * r4); - d0 += h7 * (5 * r3); - d0 += h8 * (5 * r2); - d0 += h9 * (5 * r1); - c += (d0 >>> 13); d0 &= 0x1fff; - - d1 = c; - d1 += h0 * r1; - d1 += h1 * r0; - d1 += h2 * (5 * r9); - d1 += h3 * (5 * r8); - d1 += h4 * (5 * r7); - c = (d1 >>> 13); d1 &= 0x1fff; - d1 += h5 * (5 * r6); - d1 += h6 * (5 * r5); - d1 += h7 * (5 * r4); - d1 += h8 * (5 * r3); - d1 += h9 * (5 * r2); - c += (d1 >>> 13); d1 &= 0x1fff; - - d2 = c; - d2 += h0 * r2; - d2 += h1 * r1; - d2 += h2 * r0; - d2 += h3 * (5 * r9); - d2 += h4 * (5 * r8); - c = (d2 >>> 13); d2 &= 0x1fff; - d2 += h5 * (5 * r7); - d2 += h6 * (5 * r6); - d2 += h7 * (5 * r5); - d2 += h8 * (5 * r4); - d2 += h9 * (5 * r3); - c += (d2 >>> 13); d2 &= 0x1fff; - - d3 = c; - d3 += h0 * r3; - d3 += h1 * r2; - d3 += h2 * r1; - d3 += h3 * r0; - d3 += h4 * (5 * r9); - c = (d3 >>> 13); d3 &= 0x1fff; - d3 += h5 * (5 * r8); - d3 += h6 * (5 * r7); - d3 += h7 * (5 * r6); - d3 += h8 * (5 * r5); - d3 += h9 * (5 * r4); - c += (d3 >>> 13); d3 &= 0x1fff; - - d4 = c; - d4 += h0 * r4; - d4 += h1 * r3; - d4 += h2 * r2; - d4 += h3 * r1; - d4 += h4 * r0; - c = (d4 >>> 13); d4 &= 0x1fff; - d4 += h5 * (5 * r9); - d4 += h6 * (5 * r8); - d4 += h7 * (5 * r7); - d4 += h8 * (5 * r6); - d4 += h9 * (5 * r5); - c += (d4 >>> 13); d4 &= 0x1fff; - - d5 = c; - d5 += h0 * r5; - d5 += h1 * r4; - d5 += h2 * r3; - d5 += h3 * r2; - d5 += h4 * r1; - c = (d5 >>> 13); d5 &= 0x1fff; - d5 += h5 * r0; - d5 += h6 * (5 * r9); - d5 += h7 * (5 * r8); - d5 += h8 * (5 * r7); - d5 += h9 * (5 * r6); - c += (d5 >>> 13); d5 &= 0x1fff; - - d6 = c; - d6 += h0 * r6; - d6 += h1 * r5; - d6 += h2 * r4; - d6 += h3 * r3; - d6 += h4 * r2; - c = (d6 >>> 13); d6 &= 0x1fff; - d6 += h5 * r1; - d6 += h6 * r0; - d6 += h7 * (5 * r9); - d6 += h8 * (5 * r8); - d6 += h9 * (5 * r7); - c += (d6 >>> 13); d6 &= 0x1fff; - - d7 = c; - d7 += h0 * r7; - d7 += h1 * r6; - d7 += h2 * r5; - d7 += h3 * r4; - d7 += h4 * r3; - c = (d7 >>> 13); d7 &= 0x1fff; - d7 += h5 * r2; - d7 += h6 * r1; - d7 += h7 * r0; - d7 += h8 * (5 * r9); - d7 += h9 * (5 * r8); - c += (d7 >>> 13); d7 &= 0x1fff; - - d8 = c; - d8 += h0 * r8; - d8 += h1 * r7; - d8 += h2 * r6; - d8 += h3 * r5; - d8 += h4 * r4; - c = (d8 >>> 13); d8 &= 0x1fff; - d8 += h5 * r3; - d8 += h6 * r2; - d8 += h7 * r1; - d8 += h8 * r0; - d8 += h9 * (5 * r9); - c += (d8 >>> 13); d8 &= 0x1fff; - - d9 = c; - d9 += h0 * r9; - d9 += h1 * r8; - d9 += h2 * r7; - d9 += h3 * r6; - d9 += h4 * r5; - c = (d9 >>> 13); d9 &= 0x1fff; - d9 += h5 * r4; - d9 += h6 * r3; - d9 += h7 * r2; - d9 += h8 * r1; - d9 += h9 * r0; - c += (d9 >>> 13); d9 &= 0x1fff; - - c = (((c << 2) + c)) | 0; - c = (c + d0) | 0; - d0 = c & 0x1fff; - c = (c >>> 13); - d1 += c; - - h0 = d0; - h1 = d1; - h2 = d2; - h3 = d3; - h4 = d4; - h5 = d5; - h6 = d6; - h7 = d7; - h8 = d8; - h9 = d9; - - mpos += 16; - bytes -= 16; - } - this.h[0] = h0; - this.h[1] = h1; - this.h[2] = h2; - this.h[3] = h3; - this.h[4] = h4; - this.h[5] = h5; - this.h[6] = h6; - this.h[7] = h7; - this.h[8] = h8; - this.h[9] = h9; -}; - -poly1305.prototype.finish = function(mac, macpos) { - var g = new Uint16Array(10); - var c, mask, f, i; - - if (this.leftover) { - i = this.leftover; - this.buffer[i++] = 1; - for (; i < 16; i++) this.buffer[i] = 0; - this.fin = 1; - this.blocks(this.buffer, 0, 16); - } - - c = this.h[1] >>> 13; - this.h[1] &= 0x1fff; - for (i = 2; i < 10; i++) { - this.h[i] += c; - c = this.h[i] >>> 13; - this.h[i] &= 0x1fff; - } - this.h[0] += (c * 5); - c = this.h[0] >>> 13; - this.h[0] &= 0x1fff; - this.h[1] += c; - c = this.h[1] >>> 13; - this.h[1] &= 0x1fff; - this.h[2] += c; - - g[0] = this.h[0] + 5; - c = g[0] >>> 13; - g[0] &= 0x1fff; - for (i = 1; i < 10; i++) { - g[i] = this.h[i] + c; - c = g[i] >>> 13; - g[i] &= 0x1fff; - } - g[9] -= (1 << 13); - - mask = (c ^ 1) - 1; - for (i = 0; i < 10; i++) g[i] &= mask; - mask = ~mask; - for (i = 0; i < 10; i++) this.h[i] = (this.h[i] & mask) | g[i]; - - this.h[0] = ((this.h[0] ) | (this.h[1] << 13) ) & 0xffff; - this.h[1] = ((this.h[1] >>> 3) | (this.h[2] << 10) ) & 0xffff; - this.h[2] = ((this.h[2] >>> 6) | (this.h[3] << 7) ) & 0xffff; - this.h[3] = ((this.h[3] >>> 9) | (this.h[4] << 4) ) & 0xffff; - this.h[4] = ((this.h[4] >>> 12) | (this.h[5] << 1) | (this.h[6] << 14)) & 0xffff; - this.h[5] = ((this.h[6] >>> 2) | (this.h[7] << 11) ) & 0xffff; - this.h[6] = ((this.h[7] >>> 5) | (this.h[8] << 8) ) & 0xffff; - this.h[7] = ((this.h[8] >>> 8) | (this.h[9] << 5) ) & 0xffff; - - f = this.h[0] + this.pad[0]; - this.h[0] = f & 0xffff; - for (i = 1; i < 8; i++) { - f = (((this.h[i] + this.pad[i]) | 0) + (f >>> 16)) | 0; - this.h[i] = f & 0xffff; - } - - mac[macpos+ 0] = (this.h[0] >>> 0) & 0xff; - mac[macpos+ 1] = (this.h[0] >>> 8) & 0xff; - mac[macpos+ 2] = (this.h[1] >>> 0) & 0xff; - mac[macpos+ 3] = (this.h[1] >>> 8) & 0xff; - mac[macpos+ 4] = (this.h[2] >>> 0) & 0xff; - mac[macpos+ 5] = (this.h[2] >>> 8) & 0xff; - mac[macpos+ 6] = (this.h[3] >>> 0) & 0xff; - mac[macpos+ 7] = (this.h[3] >>> 8) & 0xff; - mac[macpos+ 8] = (this.h[4] >>> 0) & 0xff; - mac[macpos+ 9] = (this.h[4] >>> 8) & 0xff; - mac[macpos+10] = (this.h[5] >>> 0) & 0xff; - mac[macpos+11] = (this.h[5] >>> 8) & 0xff; - mac[macpos+12] = (this.h[6] >>> 0) & 0xff; - mac[macpos+13] = (this.h[6] >>> 8) & 0xff; - mac[macpos+14] = (this.h[7] >>> 0) & 0xff; - mac[macpos+15] = (this.h[7] >>> 8) & 0xff; -}; - -poly1305.prototype.update = function(m, mpos, bytes) { - var i, want; - - if (this.leftover) { - want = (16 - this.leftover); - if (want > bytes) - want = bytes; - for (i = 0; i < want; i++) - this.buffer[this.leftover + i] = m[mpos+i]; - bytes -= want; - mpos += want; - this.leftover += want; - if (this.leftover < 16) - return; - this.blocks(this.buffer, 0, 16); - this.leftover = 0; - } - - if (bytes >= 16) { - want = bytes - (bytes % 16); - this.blocks(m, mpos, want); - mpos += want; - bytes -= want; - } - - if (bytes) { - for (i = 0; i < bytes; i++) - this.buffer[this.leftover + i] = m[mpos+i]; - this.leftover += bytes; - } -}; - -function crypto_onetimeauth(out, outpos, m, mpos, n, k) { - var s = new poly1305(k); - s.update(m, mpos, n); - s.finish(out, outpos); - return 0; -} - -function crypto_onetimeauth_verify(h, hpos, m, mpos, n, k) { - var x = new Uint8Array(16); - crypto_onetimeauth(x,0,m,mpos,n,k); - return crypto_verify_16(h,hpos,x,0); -} - -function crypto_secretbox(c,m,d,n,k) { - var i; - if (d < 32) return -1; - crypto_stream_xor(c,0,m,0,d,n,k); - crypto_onetimeauth(c, 16, c, 32, d - 32, c); - for (i = 0; i < 16; i++) c[i] = 0; - return 0; -} - -function crypto_secretbox_open(m,c,d,n,k) { - var i; - var x = new Uint8Array(32); - if (d < 32) return -1; - crypto_stream(x,0,32,n,k); - if (crypto_onetimeauth_verify(c, 16,c, 32,d - 32,x) !== 0) return -1; - crypto_stream_xor(m,0,c,0,d,n,k); - for (i = 0; i < 32; i++) m[i] = 0; - return 0; -} - -function set25519(r, a) { - var i; - for (i = 0; i < 16; i++) r[i] = a[i]|0; -} - -function car25519(o) { - var i, v, c = 1; - for (i = 0; i < 16; i++) { - v = o[i] + c + 65535; - c = Math.floor(v / 65536); - o[i] = v - c * 65536; - } - o[0] += c-1 + 37 * (c-1); -} - -function sel25519(p, q, b) { - var t, c = ~(b-1); - for (var i = 0; i < 16; i++) { - t = c & (p[i] ^ q[i]); - p[i] ^= t; - q[i] ^= t; - } -} - -function pack25519(o, n) { - var i, j, b; - var m = gf(), t = gf(); - for (i = 0; i < 16; i++) t[i] = n[i]; - car25519(t); - car25519(t); - car25519(t); - for (j = 0; j < 2; j++) { - m[0] = t[0] - 0xffed; - for (i = 1; i < 15; i++) { - m[i] = t[i] - 0xffff - ((m[i-1]>>16) & 1); - m[i-1] &= 0xffff; - } - m[15] = t[15] - 0x7fff - ((m[14]>>16) & 1); - b = (m[15]>>16) & 1; - m[14] &= 0xffff; - sel25519(t, m, 1-b); - } - for (i = 0; i < 16; i++) { - o[2*i] = t[i] & 0xff; - o[2*i+1] = t[i]>>8; - } -} - -function neq25519(a, b) { - var c = new Uint8Array(32), d = new Uint8Array(32); - pack25519(c, a); - pack25519(d, b); - return crypto_verify_32(c, 0, d, 0); -} - -function par25519(a) { - var d = new Uint8Array(32); - pack25519(d, a); - return d[0] & 1; -} - -function unpack25519(o, n) { - var i; - for (i = 0; i < 16; i++) o[i] = n[2*i] + (n[2*i+1] << 8); - o[15] &= 0x7fff; -} - -function A(o, a, b) { - for (var i = 0; i < 16; i++) o[i] = a[i] + b[i]; -} - -function Z(o, a, b) { - for (var i = 0; i < 16; i++) o[i] = a[i] - b[i]; -} - -function M(o, a, b) { - var v, c, - t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0, t6 = 0, t7 = 0, - t8 = 0, t9 = 0, t10 = 0, t11 = 0, t12 = 0, t13 = 0, t14 = 0, t15 = 0, - t16 = 0, t17 = 0, t18 = 0, t19 = 0, t20 = 0, t21 = 0, t22 = 0, t23 = 0, - t24 = 0, t25 = 0, t26 = 0, t27 = 0, t28 = 0, t29 = 0, t30 = 0, - b0 = b[0], - b1 = b[1], - b2 = b[2], - b3 = b[3], - b4 = b[4], - b5 = b[5], - b6 = b[6], - b7 = b[7], - b8 = b[8], - b9 = b[9], - b10 = b[10], - b11 = b[11], - b12 = b[12], - b13 = b[13], - b14 = b[14], - b15 = b[15]; - - v = a[0]; - t0 += v * b0; - t1 += v * b1; - t2 += v * b2; - t3 += v * b3; - t4 += v * b4; - t5 += v * b5; - t6 += v * b6; - t7 += v * b7; - t8 += v * b8; - t9 += v * b9; - t10 += v * b10; - t11 += v * b11; - t12 += v * b12; - t13 += v * b13; - t14 += v * b14; - t15 += v * b15; - v = a[1]; - t1 += v * b0; - t2 += v * b1; - t3 += v * b2; - t4 += v * b3; - t5 += v * b4; - t6 += v * b5; - t7 += v * b6; - t8 += v * b7; - t9 += v * b8; - t10 += v * b9; - t11 += v * b10; - t12 += v * b11; - t13 += v * b12; - t14 += v * b13; - t15 += v * b14; - t16 += v * b15; - v = a[2]; - t2 += v * b0; - t3 += v * b1; - t4 += v * b2; - t5 += v * b3; - t6 += v * b4; - t7 += v * b5; - t8 += v * b6; - t9 += v * b7; - t10 += v * b8; - t11 += v * b9; - t12 += v * b10; - t13 += v * b11; - t14 += v * b12; - t15 += v * b13; - t16 += v * b14; - t17 += v * b15; - v = a[3]; - t3 += v * b0; - t4 += v * b1; - t5 += v * b2; - t6 += v * b3; - t7 += v * b4; - t8 += v * b5; - t9 += v * b6; - t10 += v * b7; - t11 += v * b8; - t12 += v * b9; - t13 += v * b10; - t14 += v * b11; - t15 += v * b12; - t16 += v * b13; - t17 += v * b14; - t18 += v * b15; - v = a[4]; - t4 += v * b0; - t5 += v * b1; - t6 += v * b2; - t7 += v * b3; - t8 += v * b4; - t9 += v * b5; - t10 += v * b6; - t11 += v * b7; - t12 += v * b8; - t13 += v * b9; - t14 += v * b10; - t15 += v * b11; - t16 += v * b12; - t17 += v * b13; - t18 += v * b14; - t19 += v * b15; - v = a[5]; - t5 += v * b0; - t6 += v * b1; - t7 += v * b2; - t8 += v * b3; - t9 += v * b4; - t10 += v * b5; - t11 += v * b6; - t12 += v * b7; - t13 += v * b8; - t14 += v * b9; - t15 += v * b10; - t16 += v * b11; - t17 += v * b12; - t18 += v * b13; - t19 += v * b14; - t20 += v * b15; - v = a[6]; - t6 += v * b0; - t7 += v * b1; - t8 += v * b2; - t9 += v * b3; - t10 += v * b4; - t11 += v * b5; - t12 += v * b6; - t13 += v * b7; - t14 += v * b8; - t15 += v * b9; - t16 += v * b10; - t17 += v * b11; - t18 += v * b12; - t19 += v * b13; - t20 += v * b14; - t21 += v * b15; - v = a[7]; - t7 += v * b0; - t8 += v * b1; - t9 += v * b2; - t10 += v * b3; - t11 += v * b4; - t12 += v * b5; - t13 += v * b6; - t14 += v * b7; - t15 += v * b8; - t16 += v * b9; - t17 += v * b10; - t18 += v * b11; - t19 += v * b12; - t20 += v * b13; - t21 += v * b14; - t22 += v * b15; - v = a[8]; - t8 += v * b0; - t9 += v * b1; - t10 += v * b2; - t11 += v * b3; - t12 += v * b4; - t13 += v * b5; - t14 += v * b6; - t15 += v * b7; - t16 += v * b8; - t17 += v * b9; - t18 += v * b10; - t19 += v * b11; - t20 += v * b12; - t21 += v * b13; - t22 += v * b14; - t23 += v * b15; - v = a[9]; - t9 += v * b0; - t10 += v * b1; - t11 += v * b2; - t12 += v * b3; - t13 += v * b4; - t14 += v * b5; - t15 += v * b6; - t16 += v * b7; - t17 += v * b8; - t18 += v * b9; - t19 += v * b10; - t20 += v * b11; - t21 += v * b12; - t22 += v * b13; - t23 += v * b14; - t24 += v * b15; - v = a[10]; - t10 += v * b0; - t11 += v * b1; - t12 += v * b2; - t13 += v * b3; - t14 += v * b4; - t15 += v * b5; - t16 += v * b6; - t17 += v * b7; - t18 += v * b8; - t19 += v * b9; - t20 += v * b10; - t21 += v * b11; - t22 += v * b12; - t23 += v * b13; - t24 += v * b14; - t25 += v * b15; - v = a[11]; - t11 += v * b0; - t12 += v * b1; - t13 += v * b2; - t14 += v * b3; - t15 += v * b4; - t16 += v * b5; - t17 += v * b6; - t18 += v * b7; - t19 += v * b8; - t20 += v * b9; - t21 += v * b10; - t22 += v * b11; - t23 += v * b12; - t24 += v * b13; - t25 += v * b14; - t26 += v * b15; - v = a[12]; - t12 += v * b0; - t13 += v * b1; - t14 += v * b2; - t15 += v * b3; - t16 += v * b4; - t17 += v * b5; - t18 += v * b6; - t19 += v * b7; - t20 += v * b8; - t21 += v * b9; - t22 += v * b10; - t23 += v * b11; - t24 += v * b12; - t25 += v * b13; - t26 += v * b14; - t27 += v * b15; - v = a[13]; - t13 += v * b0; - t14 += v * b1; - t15 += v * b2; - t16 += v * b3; - t17 += v * b4; - t18 += v * b5; - t19 += v * b6; - t20 += v * b7; - t21 += v * b8; - t22 += v * b9; - t23 += v * b10; - t24 += v * b11; - t25 += v * b12; - t26 += v * b13; - t27 += v * b14; - t28 += v * b15; - v = a[14]; - t14 += v * b0; - t15 += v * b1; - t16 += v * b2; - t17 += v * b3; - t18 += v * b4; - t19 += v * b5; - t20 += v * b6; - t21 += v * b7; - t22 += v * b8; - t23 += v * b9; - t24 += v * b10; - t25 += v * b11; - t26 += v * b12; - t27 += v * b13; - t28 += v * b14; - t29 += v * b15; - v = a[15]; - t15 += v * b0; - t16 += v * b1; - t17 += v * b2; - t18 += v * b3; - t19 += v * b4; - t20 += v * b5; - t21 += v * b6; - t22 += v * b7; - t23 += v * b8; - t24 += v * b9; - t25 += v * b10; - t26 += v * b11; - t27 += v * b12; - t28 += v * b13; - t29 += v * b14; - t30 += v * b15; - - t0 += 38 * t16; - t1 += 38 * t17; - t2 += 38 * t18; - t3 += 38 * t19; - t4 += 38 * t20; - t5 += 38 * t21; - t6 += 38 * t22; - t7 += 38 * t23; - t8 += 38 * t24; - t9 += 38 * t25; - t10 += 38 * t26; - t11 += 38 * t27; - t12 += 38 * t28; - t13 += 38 * t29; - t14 += 38 * t30; - // t15 left as is - - // first car - c = 1; - v = t0 + c + 65535; c = Math.floor(v / 65536); t0 = v - c * 65536; - v = t1 + c + 65535; c = Math.floor(v / 65536); t1 = v - c * 65536; - v = t2 + c + 65535; c = Math.floor(v / 65536); t2 = v - c * 65536; - v = t3 + c + 65535; c = Math.floor(v / 65536); t3 = v - c * 65536; - v = t4 + c + 65535; c = Math.floor(v / 65536); t4 = v - c * 65536; - v = t5 + c + 65535; c = Math.floor(v / 65536); t5 = v - c * 65536; - v = t6 + c + 65535; c = Math.floor(v / 65536); t6 = v - c * 65536; - v = t7 + c + 65535; c = Math.floor(v / 65536); t7 = v - c * 65536; - v = t8 + c + 65535; c = Math.floor(v / 65536); t8 = v - c * 65536; - v = t9 + c + 65535; c = Math.floor(v / 65536); t9 = v - c * 65536; - v = t10 + c + 65535; c = Math.floor(v / 65536); t10 = v - c * 65536; - v = t11 + c + 65535; c = Math.floor(v / 65536); t11 = v - c * 65536; - v = t12 + c + 65535; c = Math.floor(v / 65536); t12 = v - c * 65536; - v = t13 + c + 65535; c = Math.floor(v / 65536); t13 = v - c * 65536; - v = t14 + c + 65535; c = Math.floor(v / 65536); t14 = v - c * 65536; - v = t15 + c + 65535; c = Math.floor(v / 65536); t15 = v - c * 65536; - t0 += c-1 + 37 * (c-1); - - // second car - c = 1; - v = t0 + c + 65535; c = Math.floor(v / 65536); t0 = v - c * 65536; - v = t1 + c + 65535; c = Math.floor(v / 65536); t1 = v - c * 65536; - v = t2 + c + 65535; c = Math.floor(v / 65536); t2 = v - c * 65536; - v = t3 + c + 65535; c = Math.floor(v / 65536); t3 = v - c * 65536; - v = t4 + c + 65535; c = Math.floor(v / 65536); t4 = v - c * 65536; - v = t5 + c + 65535; c = Math.floor(v / 65536); t5 = v - c * 65536; - v = t6 + c + 65535; c = Math.floor(v / 65536); t6 = v - c * 65536; - v = t7 + c + 65535; c = Math.floor(v / 65536); t7 = v - c * 65536; - v = t8 + c + 65535; c = Math.floor(v / 65536); t8 = v - c * 65536; - v = t9 + c + 65535; c = Math.floor(v / 65536); t9 = v - c * 65536; - v = t10 + c + 65535; c = Math.floor(v / 65536); t10 = v - c * 65536; - v = t11 + c + 65535; c = Math.floor(v / 65536); t11 = v - c * 65536; - v = t12 + c + 65535; c = Math.floor(v / 65536); t12 = v - c * 65536; - v = t13 + c + 65535; c = Math.floor(v / 65536); t13 = v - c * 65536; - v = t14 + c + 65535; c = Math.floor(v / 65536); t14 = v - c * 65536; - v = t15 + c + 65535; c = Math.floor(v / 65536); t15 = v - c * 65536; - t0 += c-1 + 37 * (c-1); - - o[ 0] = t0; - o[ 1] = t1; - o[ 2] = t2; - o[ 3] = t3; - o[ 4] = t4; - o[ 5] = t5; - o[ 6] = t6; - o[ 7] = t7; - o[ 8] = t8; - o[ 9] = t9; - o[10] = t10; - o[11] = t11; - o[12] = t12; - o[13] = t13; - o[14] = t14; - o[15] = t15; -} - -function S(o, a) { - M(o, a, a); -} - -function inv25519(o, i) { - var c = gf(); - var a; - for (a = 0; a < 16; a++) c[a] = i[a]; - for (a = 253; a >= 0; a--) { - S(c, c); - if(a !== 2 && a !== 4) M(c, c, i); - } - for (a = 0; a < 16; a++) o[a] = c[a]; -} - -function pow2523(o, i) { - var c = gf(); - var a; - for (a = 0; a < 16; a++) c[a] = i[a]; - for (a = 250; a >= 0; a--) { - S(c, c); - if(a !== 1) M(c, c, i); - } - for (a = 0; a < 16; a++) o[a] = c[a]; -} - -function crypto_scalarmult(q, n, p) { - var z = new Uint8Array(32); - var x = new Float64Array(80), r, i; - var a = gf(), b = gf(), c = gf(), - d = gf(), e = gf(), f = gf(); - for (i = 0; i < 31; i++) z[i] = n[i]; - z[31]=(n[31]&127)|64; - z[0]&=248; - unpack25519(x,p); - for (i = 0; i < 16; i++) { - b[i]=x[i]; - d[i]=a[i]=c[i]=0; - } - a[0]=d[0]=1; - for (i=254; i>=0; --i) { - r=(z[i>>>3]>>>(i&7))&1; - sel25519(a,b,r); - sel25519(c,d,r); - A(e,a,c); - Z(a,a,c); - A(c,b,d); - Z(b,b,d); - S(d,e); - S(f,a); - M(a,c,a); - M(c,b,e); - A(e,a,c); - Z(a,a,c); - S(b,a); - Z(c,d,f); - M(a,c,_121665); - A(a,a,d); - M(c,c,a); - M(a,d,f); - M(d,b,x); - S(b,e); - sel25519(a,b,r); - sel25519(c,d,r); - } - for (i = 0; i < 16; i++) { - x[i+16]=a[i]; - x[i+32]=c[i]; - x[i+48]=b[i]; - x[i+64]=d[i]; - } - var x32 = x.subarray(32); - var x16 = x.subarray(16); - inv25519(x32,x32); - M(x16,x16,x32); - pack25519(q,x16); - return 0; -} - -function crypto_scalarmult_base(q, n) { - return crypto_scalarmult(q, n, _9); -} - -function crypto_box_keypair(y, x) { - randombytes(x, 32); - return crypto_scalarmult_base(y, x); -} - -function crypto_box_beforenm(k, y, x) { - var s = new Uint8Array(32); - crypto_scalarmult(s, x, y); - return crypto_core_hsalsa20(k, _0, s, sigma); -} - -var crypto_box_afternm = crypto_secretbox; -var crypto_box_open_afternm = crypto_secretbox_open; - -function crypto_box(c, m, d, n, y, x) { - var k = new Uint8Array(32); - crypto_box_beforenm(k, y, x); - return crypto_box_afternm(c, m, d, n, k); -} - -function crypto_box_open(m, c, d, n, y, x) { - var k = new Uint8Array(32); - crypto_box_beforenm(k, y, x); - return crypto_box_open_afternm(m, c, d, n, k); -} - -var K = [ - 0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, - 0xb5c0fbcf, 0xec4d3b2f, 0xe9b5dba5, 0x8189dbbc, - 0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019, - 0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118, - 0xd807aa98, 0xa3030242, 0x12835b01, 0x45706fbe, - 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2, - 0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1, - 0x9bdc06a7, 0x25c71235, 0xc19bf174, 0xcf692694, - 0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3, - 0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65, - 0x2de92c6f, 0x592b0275, 0x4a7484aa, 0x6ea6e483, - 0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5, - 0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210, - 0xb00327c8, 0x98fb213f, 0xbf597fc7, 0xbeef0ee4, - 0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725, - 0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70, - 0x27b70a85, 0x46d22ffc, 0x2e1b2138, 0x5c26c926, - 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df, - 0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8, - 0x81c2c92e, 0x47edaee6, 0x92722c85, 0x1482353b, - 0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001, - 0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30, - 0xd192e819, 0xd6ef5218, 0xd6990624, 0x5565a910, - 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8, - 0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53, - 0x2748774c, 0xdf8eeb99, 0x34b0bcb5, 0xe19b48a8, - 0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb, - 0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3, - 0x748f82ee, 0x5defb2fc, 0x78a5636f, 0x43172f60, - 0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec, - 0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9, - 0xbef9a3f7, 0xb2c67915, 0xc67178f2, 0xe372532b, - 0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207, - 0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178, - 0x06f067aa, 0x72176fba, 0x0a637dc5, 0xa2c898a6, - 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b, - 0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493, - 0x3c9ebe0a, 0x15c9bebc, 0x431d67c4, 0x9c100d4c, - 0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a, - 0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817 -]; - -function crypto_hashblocks_hl(hh, hl, m, n) { - var wh = new Int32Array(16), wl = new Int32Array(16), - bh0, bh1, bh2, bh3, bh4, bh5, bh6, bh7, - bl0, bl1, bl2, bl3, bl4, bl5, bl6, bl7, - th, tl, i, j, h, l, a, b, c, d; - - var ah0 = hh[0], - ah1 = hh[1], - ah2 = hh[2], - ah3 = hh[3], - ah4 = hh[4], - ah5 = hh[5], - ah6 = hh[6], - ah7 = hh[7], - - al0 = hl[0], - al1 = hl[1], - al2 = hl[2], - al3 = hl[3], - al4 = hl[4], - al5 = hl[5], - al6 = hl[6], - al7 = hl[7]; - - var pos = 0; - while (n >= 128) { - for (i = 0; i < 16; i++) { - j = 8 * i + pos; - wh[i] = (m[j+0] << 24) | (m[j+1] << 16) | (m[j+2] << 8) | m[j+3]; - wl[i] = (m[j+4] << 24) | (m[j+5] << 16) | (m[j+6] << 8) | m[j+7]; - } - for (i = 0; i < 80; i++) { - bh0 = ah0; - bh1 = ah1; - bh2 = ah2; - bh3 = ah3; - bh4 = ah4; - bh5 = ah5; - bh6 = ah6; - bh7 = ah7; - - bl0 = al0; - bl1 = al1; - bl2 = al2; - bl3 = al3; - bl4 = al4; - bl5 = al5; - bl6 = al6; - bl7 = al7; - - // add - h = ah7; - l = al7; - - a = l & 0xffff; b = l >>> 16; - c = h & 0xffff; d = h >>> 16; - - // Sigma1 - h = ((ah4 >>> 14) | (al4 << (32-14))) ^ ((ah4 >>> 18) | (al4 << (32-18))) ^ ((al4 >>> (41-32)) | (ah4 << (32-(41-32)))); - l = ((al4 >>> 14) | (ah4 << (32-14))) ^ ((al4 >>> 18) | (ah4 << (32-18))) ^ ((ah4 >>> (41-32)) | (al4 << (32-(41-32)))); - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - // Ch - h = (ah4 & ah5) ^ (~ah4 & ah6); - l = (al4 & al5) ^ (~al4 & al6); - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - // K - h = K[i*2]; - l = K[i*2+1]; - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - // w - h = wh[i%16]; - l = wl[i%16]; - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - b += a >>> 16; - c += b >>> 16; - d += c >>> 16; - - th = c & 0xffff | d << 16; - tl = a & 0xffff | b << 16; - - // add - h = th; - l = tl; - - a = l & 0xffff; b = l >>> 16; - c = h & 0xffff; d = h >>> 16; - - // Sigma0 - h = ((ah0 >>> 28) | (al0 << (32-28))) ^ ((al0 >>> (34-32)) | (ah0 << (32-(34-32)))) ^ ((al0 >>> (39-32)) | (ah0 << (32-(39-32)))); - l = ((al0 >>> 28) | (ah0 << (32-28))) ^ ((ah0 >>> (34-32)) | (al0 << (32-(34-32)))) ^ ((ah0 >>> (39-32)) | (al0 << (32-(39-32)))); - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - // Maj - h = (ah0 & ah1) ^ (ah0 & ah2) ^ (ah1 & ah2); - l = (al0 & al1) ^ (al0 & al2) ^ (al1 & al2); - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - b += a >>> 16; - c += b >>> 16; - d += c >>> 16; - - bh7 = (c & 0xffff) | (d << 16); - bl7 = (a & 0xffff) | (b << 16); - - // add - h = bh3; - l = bl3; - - a = l & 0xffff; b = l >>> 16; - c = h & 0xffff; d = h >>> 16; - - h = th; - l = tl; - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - b += a >>> 16; - c += b >>> 16; - d += c >>> 16; - - bh3 = (c & 0xffff) | (d << 16); - bl3 = (a & 0xffff) | (b << 16); - - ah1 = bh0; - ah2 = bh1; - ah3 = bh2; - ah4 = bh3; - ah5 = bh4; - ah6 = bh5; - ah7 = bh6; - ah0 = bh7; - - al1 = bl0; - al2 = bl1; - al3 = bl2; - al4 = bl3; - al5 = bl4; - al6 = bl5; - al7 = bl6; - al0 = bl7; - - if (i%16 === 15) { - for (j = 0; j < 16; j++) { - // add - h = wh[j]; - l = wl[j]; - - a = l & 0xffff; b = l >>> 16; - c = h & 0xffff; d = h >>> 16; - - h = wh[(j+9)%16]; - l = wl[(j+9)%16]; - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - // sigma0 - th = wh[(j+1)%16]; - tl = wl[(j+1)%16]; - h = ((th >>> 1) | (tl << (32-1))) ^ ((th >>> 8) | (tl << (32-8))) ^ (th >>> 7); - l = ((tl >>> 1) | (th << (32-1))) ^ ((tl >>> 8) | (th << (32-8))) ^ ((tl >>> 7) | (th << (32-7))); - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - // sigma1 - th = wh[(j+14)%16]; - tl = wl[(j+14)%16]; - h = ((th >>> 19) | (tl << (32-19))) ^ ((tl >>> (61-32)) | (th << (32-(61-32)))) ^ (th >>> 6); - l = ((tl >>> 19) | (th << (32-19))) ^ ((th >>> (61-32)) | (tl << (32-(61-32)))) ^ ((tl >>> 6) | (th << (32-6))); - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - b += a >>> 16; - c += b >>> 16; - d += c >>> 16; - - wh[j] = (c & 0xffff) | (d << 16); - wl[j] = (a & 0xffff) | (b << 16); - } - } - } - - // add - h = ah0; - l = al0; - - a = l & 0xffff; b = l >>> 16; - c = h & 0xffff; d = h >>> 16; - - h = hh[0]; - l = hl[0]; - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - b += a >>> 16; - c += b >>> 16; - d += c >>> 16; - - hh[0] = ah0 = (c & 0xffff) | (d << 16); - hl[0] = al0 = (a & 0xffff) | (b << 16); - - h = ah1; - l = al1; - - a = l & 0xffff; b = l >>> 16; - c = h & 0xffff; d = h >>> 16; - - h = hh[1]; - l = hl[1]; - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - b += a >>> 16; - c += b >>> 16; - d += c >>> 16; - - hh[1] = ah1 = (c & 0xffff) | (d << 16); - hl[1] = al1 = (a & 0xffff) | (b << 16); - - h = ah2; - l = al2; - - a = l & 0xffff; b = l >>> 16; - c = h & 0xffff; d = h >>> 16; - - h = hh[2]; - l = hl[2]; - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - b += a >>> 16; - c += b >>> 16; - d += c >>> 16; - - hh[2] = ah2 = (c & 0xffff) | (d << 16); - hl[2] = al2 = (a & 0xffff) | (b << 16); - - h = ah3; - l = al3; - - a = l & 0xffff; b = l >>> 16; - c = h & 0xffff; d = h >>> 16; - - h = hh[3]; - l = hl[3]; - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - b += a >>> 16; - c += b >>> 16; - d += c >>> 16; - - hh[3] = ah3 = (c & 0xffff) | (d << 16); - hl[3] = al3 = (a & 0xffff) | (b << 16); - - h = ah4; - l = al4; - - a = l & 0xffff; b = l >>> 16; - c = h & 0xffff; d = h >>> 16; - - h = hh[4]; - l = hl[4]; - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - b += a >>> 16; - c += b >>> 16; - d += c >>> 16; - - hh[4] = ah4 = (c & 0xffff) | (d << 16); - hl[4] = al4 = (a & 0xffff) | (b << 16); - - h = ah5; - l = al5; - - a = l & 0xffff; b = l >>> 16; - c = h & 0xffff; d = h >>> 16; - - h = hh[5]; - l = hl[5]; - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - b += a >>> 16; - c += b >>> 16; - d += c >>> 16; - - hh[5] = ah5 = (c & 0xffff) | (d << 16); - hl[5] = al5 = (a & 0xffff) | (b << 16); - - h = ah6; - l = al6; - - a = l & 0xffff; b = l >>> 16; - c = h & 0xffff; d = h >>> 16; - - h = hh[6]; - l = hl[6]; - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - b += a >>> 16; - c += b >>> 16; - d += c >>> 16; - - hh[6] = ah6 = (c & 0xffff) | (d << 16); - hl[6] = al6 = (a & 0xffff) | (b << 16); - - h = ah7; - l = al7; - - a = l & 0xffff; b = l >>> 16; - c = h & 0xffff; d = h >>> 16; - - h = hh[7]; - l = hl[7]; - - a += l & 0xffff; b += l >>> 16; - c += h & 0xffff; d += h >>> 16; - - b += a >>> 16; - c += b >>> 16; - d += c >>> 16; - - hh[7] = ah7 = (c & 0xffff) | (d << 16); - hl[7] = al7 = (a & 0xffff) | (b << 16); - - pos += 128; - n -= 128; - } - - return n; -} - -function crypto_hash(out, m, n) { - var hh = new Int32Array(8), - hl = new Int32Array(8), - x = new Uint8Array(256), - i, b = n; - - hh[0] = 0x6a09e667; - hh[1] = 0xbb67ae85; - hh[2] = 0x3c6ef372; - hh[3] = 0xa54ff53a; - hh[4] = 0x510e527f; - hh[5] = 0x9b05688c; - hh[6] = 0x1f83d9ab; - hh[7] = 0x5be0cd19; - - hl[0] = 0xf3bcc908; - hl[1] = 0x84caa73b; - hl[2] = 0xfe94f82b; - hl[3] = 0x5f1d36f1; - hl[4] = 0xade682d1; - hl[5] = 0x2b3e6c1f; - hl[6] = 0xfb41bd6b; - hl[7] = 0x137e2179; - - crypto_hashblocks_hl(hh, hl, m, n); - n %= 128; - - for (i = 0; i < n; i++) x[i] = m[b-n+i]; - x[n] = 128; - - n = 256-128*(n<112?1:0); - x[n-9] = 0; - ts64(x, n-8, (b / 0x20000000) | 0, b << 3); - crypto_hashblocks_hl(hh, hl, x, n); - - for (i = 0; i < 8; i++) ts64(out, 8*i, hh[i], hl[i]); - - return 0; -} - -function add(p, q) { - var a = gf(), b = gf(), c = gf(), - d = gf(), e = gf(), f = gf(), - g = gf(), h = gf(), t = gf(); - - Z(a, p[1], p[0]); - Z(t, q[1], q[0]); - M(a, a, t); - A(b, p[0], p[1]); - A(t, q[0], q[1]); - M(b, b, t); - M(c, p[3], q[3]); - M(c, c, D2); - M(d, p[2], q[2]); - A(d, d, d); - Z(e, b, a); - Z(f, d, c); - A(g, d, c); - A(h, b, a); - - M(p[0], e, f); - M(p[1], h, g); - M(p[2], g, f); - M(p[3], e, h); -} - -function cswap(p, q, b) { - var i; - for (i = 0; i < 4; i++) { - sel25519(p[i], q[i], b); - } -} - -function pack(r, p) { - var tx = gf(), ty = gf(), zi = gf(); - inv25519(zi, p[2]); - M(tx, p[0], zi); - M(ty, p[1], zi); - pack25519(r, ty); - r[31] ^= par25519(tx) << 7; -} - -function scalarmult(p, q, s) { - var b, i; - set25519(p[0], gf0); - set25519(p[1], gf1); - set25519(p[2], gf1); - set25519(p[3], gf0); - for (i = 255; i >= 0; --i) { - b = (s[(i/8)|0] >> (i&7)) & 1; - cswap(p, q, b); - add(q, p); - add(p, p); - cswap(p, q, b); - } -} - -function scalarbase(p, s) { - var q = [gf(), gf(), gf(), gf()]; - set25519(q[0], X); - set25519(q[1], Y); - set25519(q[2], gf1); - M(q[3], X, Y); - scalarmult(p, q, s); -} - -function crypto_sign_keypair(pk, sk, seeded) { - var d = new Uint8Array(64); - var p = [gf(), gf(), gf(), gf()]; - var i; - - if (!seeded) randombytes(sk, 32); - crypto_hash(d, sk, 32); - d[0] &= 248; - d[31] &= 127; - d[31] |= 64; - - scalarbase(p, d); - pack(pk, p); - - for (i = 0; i < 32; i++) sk[i+32] = pk[i]; - return 0; -} - -var L = new Float64Array([0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10]); - -function modL(r, x) { - var carry, i, j, k; - for (i = 63; i >= 32; --i) { - carry = 0; - for (j = i - 32, k = i - 12; j < k; ++j) { - x[j] += carry - 16 * x[i] * L[j - (i - 32)]; - carry = Math.floor((x[j] + 128) / 256); - x[j] -= carry * 256; - } - x[j] += carry; - x[i] = 0; - } - carry = 0; - for (j = 0; j < 32; j++) { - x[j] += carry - (x[31] >> 4) * L[j]; - carry = x[j] >> 8; - x[j] &= 255; - } - for (j = 0; j < 32; j++) x[j] -= carry * L[j]; - for (i = 0; i < 32; i++) { - x[i+1] += x[i] >> 8; - r[i] = x[i] & 255; - } -} - -function reduce(r) { - var x = new Float64Array(64), i; - for (i = 0; i < 64; i++) x[i] = r[i]; - for (i = 0; i < 64; i++) r[i] = 0; - modL(r, x); -} - -// Note: difference from C - smlen returned, not passed as argument. -function crypto_sign(sm, m, n, sk) { - var d = new Uint8Array(64), h = new Uint8Array(64), r = new Uint8Array(64); - var i, j, x = new Float64Array(64); - var p = [gf(), gf(), gf(), gf()]; - - crypto_hash(d, sk, 32); - d[0] &= 248; - d[31] &= 127; - d[31] |= 64; - - var smlen = n + 64; - for (i = 0; i < n; i++) sm[64 + i] = m[i]; - for (i = 0; i < 32; i++) sm[32 + i] = d[32 + i]; - - crypto_hash(r, sm.subarray(32), n+32); - reduce(r); - scalarbase(p, r); - pack(sm, p); - - for (i = 32; i < 64; i++) sm[i] = sk[i]; - crypto_hash(h, sm, n + 64); - reduce(h); - - for (i = 0; i < 64; i++) x[i] = 0; - for (i = 0; i < 32; i++) x[i] = r[i]; - for (i = 0; i < 32; i++) { - for (j = 0; j < 32; j++) { - x[i+j] += h[i] * d[j]; - } - } - - modL(sm.subarray(32), x); - return smlen; -} - -function unpackneg(r, p) { - var t = gf(), chk = gf(), num = gf(), - den = gf(), den2 = gf(), den4 = gf(), - den6 = gf(); - - set25519(r[2], gf1); - unpack25519(r[1], p); - S(num, r[1]); - M(den, num, D); - Z(num, num, r[2]); - A(den, r[2], den); - - S(den2, den); - S(den4, den2); - M(den6, den4, den2); - M(t, den6, num); - M(t, t, den); - - pow2523(t, t); - M(t, t, num); - M(t, t, den); - M(t, t, den); - M(r[0], t, den); - - S(chk, r[0]); - M(chk, chk, den); - if (neq25519(chk, num)) M(r[0], r[0], I); - - S(chk, r[0]); - M(chk, chk, den); - if (neq25519(chk, num)) return -1; - - if (par25519(r[0]) === (p[31]>>7)) Z(r[0], gf0, r[0]); - - M(r[3], r[0], r[1]); - return 0; -} - -function crypto_sign_open(m, sm, n, pk) { - var i; - var t = new Uint8Array(32), h = new Uint8Array(64); - var p = [gf(), gf(), gf(), gf()], - q = [gf(), gf(), gf(), gf()]; - - if (n < 64) return -1; - - if (unpackneg(q, pk)) return -1; - - for (i = 0; i < n; i++) m[i] = sm[i]; - for (i = 0; i < 32; i++) m[i+32] = pk[i]; - crypto_hash(h, m, n); - reduce(h); - scalarmult(p, q, h); - - scalarbase(q, sm.subarray(32)); - add(p, q); - pack(t, p); - - n -= 64; - if (crypto_verify_32(sm, 0, t, 0)) { - for (i = 0; i < n; i++) m[i] = 0; - return -1; - } - - for (i = 0; i < n; i++) m[i] = sm[i + 64]; - return n; -} - -var crypto_secretbox_KEYBYTES = 32, - crypto_secretbox_NONCEBYTES = 24, - crypto_secretbox_ZEROBYTES = 32, - crypto_secretbox_BOXZEROBYTES = 16, - crypto_scalarmult_BYTES = 32, - crypto_scalarmult_SCALARBYTES = 32, - crypto_box_PUBLICKEYBYTES = 32, - crypto_box_SECRETKEYBYTES = 32, - crypto_box_BEFORENMBYTES = 32, - crypto_box_NONCEBYTES = crypto_secretbox_NONCEBYTES, - crypto_box_ZEROBYTES = crypto_secretbox_ZEROBYTES, - crypto_box_BOXZEROBYTES = crypto_secretbox_BOXZEROBYTES, - crypto_sign_BYTES = 64, - crypto_sign_PUBLICKEYBYTES = 32, - crypto_sign_SECRETKEYBYTES = 64, - crypto_sign_SEEDBYTES = 32, - crypto_hash_BYTES = 64; - -nacl.lowlevel = { - crypto_core_hsalsa20: crypto_core_hsalsa20, - crypto_stream_xor: crypto_stream_xor, - crypto_stream: crypto_stream, - crypto_stream_salsa20_xor: crypto_stream_salsa20_xor, - crypto_stream_salsa20: crypto_stream_salsa20, - crypto_onetimeauth: crypto_onetimeauth, - crypto_onetimeauth_verify: crypto_onetimeauth_verify, - crypto_verify_16: crypto_verify_16, - crypto_verify_32: crypto_verify_32, - crypto_secretbox: crypto_secretbox, - crypto_secretbox_open: crypto_secretbox_open, - crypto_scalarmult: crypto_scalarmult, - crypto_scalarmult_base: crypto_scalarmult_base, - crypto_box_beforenm: crypto_box_beforenm, - crypto_box_afternm: crypto_box_afternm, - crypto_box: crypto_box, - crypto_box_open: crypto_box_open, - crypto_box_keypair: crypto_box_keypair, - crypto_hash: crypto_hash, - crypto_sign: crypto_sign, - crypto_sign_keypair: crypto_sign_keypair, - crypto_sign_open: crypto_sign_open, - - crypto_secretbox_KEYBYTES: crypto_secretbox_KEYBYTES, - crypto_secretbox_NONCEBYTES: crypto_secretbox_NONCEBYTES, - crypto_secretbox_ZEROBYTES: crypto_secretbox_ZEROBYTES, - crypto_secretbox_BOXZEROBYTES: crypto_secretbox_BOXZEROBYTES, - crypto_scalarmult_BYTES: crypto_scalarmult_BYTES, - crypto_scalarmult_SCALARBYTES: crypto_scalarmult_SCALARBYTES, - crypto_box_PUBLICKEYBYTES: crypto_box_PUBLICKEYBYTES, - crypto_box_SECRETKEYBYTES: crypto_box_SECRETKEYBYTES, - crypto_box_BEFORENMBYTES: crypto_box_BEFORENMBYTES, - crypto_box_NONCEBYTES: crypto_box_NONCEBYTES, - crypto_box_ZEROBYTES: crypto_box_ZEROBYTES, - crypto_box_BOXZEROBYTES: crypto_box_BOXZEROBYTES, - crypto_sign_BYTES: crypto_sign_BYTES, - crypto_sign_PUBLICKEYBYTES: crypto_sign_PUBLICKEYBYTES, - crypto_sign_SECRETKEYBYTES: crypto_sign_SECRETKEYBYTES, - crypto_sign_SEEDBYTES: crypto_sign_SEEDBYTES, - crypto_hash_BYTES: crypto_hash_BYTES, - - gf: gf, - D: D, - L: L, - pack25519: pack25519, - unpack25519: unpack25519, - M: M, - A: A, - S: S, - Z: Z, - pow2523: pow2523, - add: add, - set25519: set25519, - modL: modL, - scalarmult: scalarmult, - scalarbase: scalarbase, -}; - -/* High-level API */ - -function checkLengths(k, n) { - if (k.length !== crypto_secretbox_KEYBYTES) throw new Error('bad key size'); - if (n.length !== crypto_secretbox_NONCEBYTES) throw new Error('bad nonce size'); -} - -function checkBoxLengths(pk, sk) { - if (pk.length !== crypto_box_PUBLICKEYBYTES) throw new Error('bad public key size'); - if (sk.length !== crypto_box_SECRETKEYBYTES) throw new Error('bad secret key size'); -} - -function checkArrayTypes() { - for (var i = 0; i < arguments.length; i++) { - if (!(arguments[i] instanceof Uint8Array)) - throw new TypeError('unexpected type, use Uint8Array'); - } -} - -function cleanup(arr) { - for (var i = 0; i < arr.length; i++) arr[i] = 0; -} - -nacl.randomBytes = function(n) { - var b = new Uint8Array(n); - randombytes(b, n); - return b; -}; - -nacl.secretbox = function(msg, nonce, key) { - checkArrayTypes(msg, nonce, key); - checkLengths(key, nonce); - var m = new Uint8Array(crypto_secretbox_ZEROBYTES + msg.length); - var c = new Uint8Array(m.length); - for (var i = 0; i < msg.length; i++) m[i+crypto_secretbox_ZEROBYTES] = msg[i]; - crypto_secretbox(c, m, m.length, nonce, key); - return c.subarray(crypto_secretbox_BOXZEROBYTES); -}; - -nacl.secretbox.open = function(box, nonce, key) { - checkArrayTypes(box, nonce, key); - checkLengths(key, nonce); - var c = new Uint8Array(crypto_secretbox_BOXZEROBYTES + box.length); - var m = new Uint8Array(c.length); - for (var i = 0; i < box.length; i++) c[i+crypto_secretbox_BOXZEROBYTES] = box[i]; - if (c.length < 32) return null; - if (crypto_secretbox_open(m, c, c.length, nonce, key) !== 0) return null; - return m.subarray(crypto_secretbox_ZEROBYTES); -}; - -nacl.secretbox.keyLength = crypto_secretbox_KEYBYTES; -nacl.secretbox.nonceLength = crypto_secretbox_NONCEBYTES; -nacl.secretbox.overheadLength = crypto_secretbox_BOXZEROBYTES; - -nacl.scalarMult = function(n, p) { - checkArrayTypes(n, p); - if (n.length !== crypto_scalarmult_SCALARBYTES) throw new Error('bad n size'); - if (p.length !== crypto_scalarmult_BYTES) throw new Error('bad p size'); - var q = new Uint8Array(crypto_scalarmult_BYTES); - crypto_scalarmult(q, n, p); - return q; -}; - -nacl.scalarMult.base = function(n) { - checkArrayTypes(n); - if (n.length !== crypto_scalarmult_SCALARBYTES) throw new Error('bad n size'); - var q = new Uint8Array(crypto_scalarmult_BYTES); - crypto_scalarmult_base(q, n); - return q; -}; - -nacl.scalarMult.scalarLength = crypto_scalarmult_SCALARBYTES; -nacl.scalarMult.groupElementLength = crypto_scalarmult_BYTES; - -nacl.box = function(msg, nonce, publicKey, secretKey) { - var k = nacl.box.before(publicKey, secretKey); - return nacl.secretbox(msg, nonce, k); -}; - -nacl.box.before = function(publicKey, secretKey) { - checkArrayTypes(publicKey, secretKey); - checkBoxLengths(publicKey, secretKey); - var k = new Uint8Array(crypto_box_BEFORENMBYTES); - crypto_box_beforenm(k, publicKey, secretKey); - return k; -}; - -nacl.box.after = nacl.secretbox; - -nacl.box.open = function(msg, nonce, publicKey, secretKey) { - var k = nacl.box.before(publicKey, secretKey); - return nacl.secretbox.open(msg, nonce, k); -}; - -nacl.box.open.after = nacl.secretbox.open; - -nacl.box.keyPair = function() { - var pk = new Uint8Array(crypto_box_PUBLICKEYBYTES); - var sk = new Uint8Array(crypto_box_SECRETKEYBYTES); - crypto_box_keypair(pk, sk); - return {publicKey: pk, secretKey: sk}; -}; - -nacl.box.keyPair.fromSecretKey = function(secretKey) { - checkArrayTypes(secretKey); - if (secretKey.length !== crypto_box_SECRETKEYBYTES) - throw new Error('bad secret key size'); - var pk = new Uint8Array(crypto_box_PUBLICKEYBYTES); - crypto_scalarmult_base(pk, secretKey); - return {publicKey: pk, secretKey: new Uint8Array(secretKey)}; -}; - -nacl.box.publicKeyLength = crypto_box_PUBLICKEYBYTES; -nacl.box.secretKeyLength = crypto_box_SECRETKEYBYTES; -nacl.box.sharedKeyLength = crypto_box_BEFORENMBYTES; -nacl.box.nonceLength = crypto_box_NONCEBYTES; -nacl.box.overheadLength = nacl.secretbox.overheadLength; - -nacl.sign = function(msg, secretKey) { - checkArrayTypes(msg, secretKey); - if (secretKey.length !== crypto_sign_SECRETKEYBYTES) - throw new Error('bad secret key size'); - var signedMsg = new Uint8Array(crypto_sign_BYTES+msg.length); - crypto_sign(signedMsg, msg, msg.length, secretKey); - return signedMsg; -}; - -nacl.sign.open = function(signedMsg, publicKey) { - checkArrayTypes(signedMsg, publicKey); - if (publicKey.length !== crypto_sign_PUBLICKEYBYTES) - throw new Error('bad public key size'); - var tmp = new Uint8Array(signedMsg.length); - var mlen = crypto_sign_open(tmp, signedMsg, signedMsg.length, publicKey); - if (mlen < 0) return null; - var m = new Uint8Array(mlen); - for (var i = 0; i < m.length; i++) m[i] = tmp[i]; - return m; -}; - -nacl.sign.detached = function(msg, secretKey) { - var signedMsg = nacl.sign(msg, secretKey); - var sig = new Uint8Array(crypto_sign_BYTES); - for (var i = 0; i < sig.length; i++) sig[i] = signedMsg[i]; - return sig; -}; - -nacl.sign.detached.verify = function(msg, sig, publicKey) { - checkArrayTypes(msg, sig, publicKey); - if (sig.length !== crypto_sign_BYTES) - throw new Error('bad signature size'); - if (publicKey.length !== crypto_sign_PUBLICKEYBYTES) - throw new Error('bad public key size'); - var sm = new Uint8Array(crypto_sign_BYTES + msg.length); - var m = new Uint8Array(crypto_sign_BYTES + msg.length); - var i; - for (i = 0; i < crypto_sign_BYTES; i++) sm[i] = sig[i]; - for (i = 0; i < msg.length; i++) sm[i+crypto_sign_BYTES] = msg[i]; - return (crypto_sign_open(m, sm, sm.length, publicKey) >= 0); -}; - -nacl.sign.keyPair = function() { - var pk = new Uint8Array(crypto_sign_PUBLICKEYBYTES); - var sk = new Uint8Array(crypto_sign_SECRETKEYBYTES); - crypto_sign_keypair(pk, sk); - return {publicKey: pk, secretKey: sk}; -}; - -nacl.sign.keyPair.fromSecretKey = function(secretKey) { - checkArrayTypes(secretKey); - if (secretKey.length !== crypto_sign_SECRETKEYBYTES) - throw new Error('bad secret key size'); - var pk = new Uint8Array(crypto_sign_PUBLICKEYBYTES); - for (var i = 0; i < pk.length; i++) pk[i] = secretKey[32+i]; - return {publicKey: pk, secretKey: new Uint8Array(secretKey)}; -}; - -nacl.sign.keyPair.fromSeed = function(seed) { - checkArrayTypes(seed); - if (seed.length !== crypto_sign_SEEDBYTES) - throw new Error('bad seed size'); - var pk = new Uint8Array(crypto_sign_PUBLICKEYBYTES); - var sk = new Uint8Array(crypto_sign_SECRETKEYBYTES); - for (var i = 0; i < 32; i++) sk[i] = seed[i]; - crypto_sign_keypair(pk, sk, true); - return {publicKey: pk, secretKey: sk}; -}; - -nacl.sign.publicKeyLength = crypto_sign_PUBLICKEYBYTES; -nacl.sign.secretKeyLength = crypto_sign_SECRETKEYBYTES; -nacl.sign.seedLength = crypto_sign_SEEDBYTES; -nacl.sign.signatureLength = crypto_sign_BYTES; - -nacl.hash = function(msg) { - checkArrayTypes(msg); - var h = new Uint8Array(crypto_hash_BYTES); - crypto_hash(h, msg, msg.length); - return h; -}; - -nacl.hash.hashLength = crypto_hash_BYTES; - -nacl.verify = function(x, y) { - checkArrayTypes(x, y); - // Zero length arguments are considered not equal. - if (x.length === 0 || y.length === 0) return false; - if (x.length !== y.length) return false; - return (vn(x, 0, y, 0, x.length) === 0) ? true : false; -}; - -nacl.setPRNG = function(fn) { - randombytes = fn; -}; - -(function() { - // Initialize PRNG if environment provides CSPRNG. - // If not, methods calling randombytes will throw. - var crypto = typeof self !== 'undefined' ? (self.crypto || self.msCrypto) : null; - if (crypto && crypto.getRandomValues) { - // Browsers. - var QUOTA = 65536; - nacl.setPRNG(function(x, n) { - var i, v = new Uint8Array(n); - for (i = 0; i < n; i += QUOTA) { - crypto.getRandomValues(v.subarray(i, i + Math.min(n - i, QUOTA))); - } - for (i = 0; i < n; i++) x[i] = v[i]; - cleanup(v); - }); - } else if (typeof require !== 'undefined') { - // Node.js. - crypto = require('crypto'); - if (crypto && crypto.randomBytes) { - nacl.setPRNG(function(x, n) { - var i, v = crypto.randomBytes(n); - for (i = 0; i < n; i++) x[i] = v[i]; - cleanup(v); - }); - } - } -})(); - -})(typeof module !== 'undefined' && module.exports ? module.exports : (self.nacl = self.nacl || {})); diff --git a/core/twopc/Evaluator.js b/core/twopc/Evaluator.js index 163eb06..b013b43 100644 --- a/core/twopc/Evaluator.js +++ b/core/twopc/Evaluator.js @@ -3,18 +3,6 @@ export class Evaluator{ // s is the current session this.s = parent; } - - getNonFixedLabels(encLabels, otKeys, nonFixedBits){ - const nonFixedLabels = []; - for (let i = 0; i < nonFixedBits.length; i += 1) { - const bit = nonFixedBits[i]; - const ct = encLabels.slice(i * 32, (i+1) * 32); - const inputLabel = this.s.ot.decryptWithKey(ct, bit, otKeys[i]); - nonFixedLabels.push(inputLabel); - } - return nonFixedLabels; - } - async evaluateBatch(batch, cNo){ const outputs = await this.s.workers[cNo].evaluateBatch(batch); @@ -24,5 +12,4 @@ export class Evaluator{ } return parsed; } - } \ No newline at end of file diff --git a/core/twopc/GCWorker.js b/core/twopc/GCWorker.js index f7973b9..c2069d5 100644 --- a/core/twopc/GCWorker.js +++ b/core/twopc/GCWorker.js @@ -1,7 +1,9 @@ +/* global chrome */ + import WorkerPool from './WorkerPool.js'; export class GCWorker extends WorkerPool{ - // class CGWorker provides convenience functions to speak to the web worker + // class CGWorker provides convenience functions to speak to the web worker constructor(numWorkers, processMonitor){ super(numWorkers, chrome.extension.getURL('core/twopc/webWorkers/gcworker.js')); // pm is an instance of ProcessMonitor diff --git a/core/twopc/GHASH.js b/core/twopc/GHASH.js new file mode 100644 index 0000000..94fed43 --- /dev/null +++ b/core/twopc/GHASH.js @@ -0,0 +1,446 @@ +import {sortKeys, assert, bytesToBits, concatTA, int2ba, xor, ba2int, + splitIntoChunks} from '../utils.js'; + + +// class GHASH implements a method of computing the AES-GCM's GHASH function +// in a secure two-party computation (2PC) setting using 1-of-2 Oblivious +// Transfer (OT). The parties start with their secret shares of H (GHASH key) end at +// the end each gets their share of the GHASH output. +// The method is decribed here: +// (https://tlsnotary.org/how_it_works#section4). + + +export class GHASH { + constructor(noOfAESBlocks){ + // otR is an instance of OTReceiver + this.otR = null; + // shares is a sparse array of shares of the powers of H. H is also + // known as GHASH key - it is an all-zero bytestring AES-ECB-encrypted + // with TLS session's client_write_key. Array's key is the power n, + // Array's value is the value of the client's xor-share of H^n. + this.shares = []; + // maxPowerNeeded is the maximum power of H that we'll need in order to + // compute the GHASH function. It is the number of AES blocks + + // aad (1 block) + suffix (1 block) + this.maxPowerNeeded = noOfAESBlocks + 2; + // lastChoiceBits is the choice bits in client's most recent OT request. + this.lastChoiceBits = null; + // lastStrategy contains one of the two strategies which was used when + // preparing the most recent OT request. The same strategy will be used + // when processing an OT response. + this.lastStrategy = null; + // res contains an intermediate result during Block Aggregation. We + // save it here before making an OT request and pick it up after + // receiving a response. + this.res = new Uint8Array(16).fill(0); + + // strategy1&2 shows what existing shares we will be multiplying (values + // 0 and 1) to obtain other odd shares (key). + // Max sequential odd share that we can obtain on first round of + // communication is 19 (we already have 1) shares of H^1, H^2, H^3 from + // the Client Finished message and 2) squares of those 3 shares). + // Note that "sequential" is a keyword here. We can't obtain 21 but we + // indeed can obtain 25==24+1, 33==32+1 etc. However with 21 missing, + // even if we have 25,33,etc, there will be a gap and we will not be able + // to obtain all the needed shares by Block Aggregation. + + // We request OT for each share in each pair of the strategy, i.e. for + // shares: 4,1,4,3,8,1, etc. Even though it would be possible to introduce + // optimizations in order to avoid requesting OT for the same share more + // than once, that would only save us ~2000 OT instances at the cost of + // complicating the code. + + this.strategy1 = { + 5: [4, 1], + 7: [4, 3], + 9: [8, 1], + 11: [8, 3], + 13: [12, 1], + 15: [12, 3], + 17: [16, 1], + 19: [16, 3]}; + this.strategy2 = { + 21: [17, 4], + 23: [17, 6], + 25: [17, 8], + 27: [19, 8], + 29: [17, 12], + 31: [19, 12], + 33: [17, 16], + 35: [19, 16]}; + + // maxOddPowerNeeded is the maximum odd share that we need (it is a key + // from one of the strategies) + this.maxOddPowerNeeded = this.findMaxOddPower(this.maxPowerNeeded); + // otCount is how many instances of OT the client (who is the OT receiver) + // will have to execute. The OT count for Client_Finished is 256 and for + // Server_Finished it is also 256. + this.otCount = 256 + 256 + this.calculateOTCount(); + } + + // sets the OTReceiver instance + setOTReceiver(otR){ + this.otR = otR; + } + + // return true if we need another communication roundtrip with the notary + isStep2Needed(){ + return this.maxOddPowerNeeded > 19; + } + + // findMaxOddPower finds the max odd share that we + findMaxOddPower(maxPowerNeeded){ + assert(maxPowerNeeded <= 1026); + + // maxHTable's shows how many GHASH blocks can be processed + // with Block Aggregation if we have all the sequential shares + // starting with 1 up to and including . + // e.g. {5:29} means that if we have shares of H^1,H^2,H^3,H^4,H^5, + // then we can process 29 GHASH blocks. + // max TLS record size of 16KB requires 1026 GHASH blocks + const maxHTable = {0: 0, 3: 19, 5: 29, 7: 71, 9: 89, 11: 107, 13: 125, 15: 271, 17: 305, 19: 339, 21: 373, + 23: 407, 25: 441, 27: 475, 29: 509, 31: 1023, 33: 1025, 35: 1027}; + + let maxOddPowerNeeded = null; + for (const key of sortKeys(maxHTable)){ + const maxH = maxHTable[key]; + if (maxH >= maxPowerNeeded){ + maxOddPowerNeeded = key; + break; + } + } + return maxOddPowerNeeded; + } + + // calculateOTCount calculates the amount of OTs the OT receiver + // will have to execute when sending the request (Client/Server Finished + // are not included) + calculateOTCount(){ + // z is a dummy value which we set to indicate that we have the share + const z = new Uint8Array(16).fill(0); + // all shares of powers which the client has + // add powers 1,2,3 which the client will already have + const allShares = [undefined, z, z, z]; + // powerCount contains powers from strategies. Each power requires + // 128 OTs. + let powerCount = 0; + const strategy = {...this.strategy1, ...this.strategy2}; + for (const k of sortKeys(strategy)){ + if (k > this.maxOddPowerNeeded){ + break; + } + allShares[k] = z; + powerCount += 2; + } + this.freeSquare(allShares, this.maxPowerNeeded); + + // how many auxillary shares needed for Block Aggregation. Each aux + // share requires 128 OTs for the share itself and 128 OTs for + // its aggregated value. + const auxShares = []; + for (let i=1; i <= this.maxPowerNeeded; i++){ + if (allShares[i] != undefined){ + continue; // the client already has this share + } + // a is the smaller power + const [a, b] = this.findSum(allShares, i); + if (! auxShares.includes(a)){ + auxShares.push(a); + } + } + return powerCount * 128 + auxShares.length * 256; + } + + // prepareOTRequest prepares a request for Notary's masked XTables + // corresponding to our shares in the strategies. + async prepareOTRequest(strategyNo){ + assert(this.lastChoiceBits == null); + this.freeSquare(this.shares, this.maxPowerNeeded); + const strategy = strategyNo == 1 ? this.strategy1 : this.strategy2; + const inputBits1Arr = []; + const keys = Object.keys(strategy); + for (const k of keys){ + if (k > this.maxOddPowerNeeded){ + break; + } + const v = strategy[k]; + inputBits1Arr.push(bytesToBits(this.shares[v[0]]).reverse()); + inputBits1Arr.push(bytesToBits(this.shares[v[1]]).reverse()); + } + this.lastChoiceBits = [].concat(...inputBits1Arr); + this.lastStrategy = strategy; + return this.otR.createRequest(this.lastChoiceBits); + } + + processOTResponse(otResp){ + assert(this.lastChoiceBits != null); + const hisXTables = this.otR.parseResponse(this.lastChoiceBits, otResp); + this.getPowerShares(hisXTables, this.shares); + this.freeSquare(this.shares, this.maxPowerNeeded); + this.lastChoiceBits = null; + this.lastStrategy = null; + } + + // buildStep1 starts the process of computing our share of the tag for the + // client request. We already have shares of H^1, H^2, H^3 from the tag for + // the Client_Finished earlier + async buildStep1(){ + console.log('maxPowerNeeded is', this.maxPowerNeeded); + assert(this.maxPowerNeeded <= 1026); + if (this.maxOddPowerNeeded === 3){ + this.freeSquare(this.shares, this.maxPowerNeeded); + return int2ba(this.maxPowerNeeded, 2); + } + return concatTA(int2ba(this.maxPowerNeeded, 2), await this.prepareOTRequest(1)); + } + + // Step2 is needed when the amount of odd powers needed is > 19 + async buildStep2(){ + return await this.prepareOTRequest(2); + } + + // Step3 performs Block Aggregation for GHASH inputs + async buildStep3(ghashInputs){ + this.res = this.multiplyShares(ghashInputs); + return await this.blockAggregationBuildRequest(ghashInputs); + } + + // Add notary's masked XTables to our tag share + processStep3Response(resp){ + assert(this.lastChoiceBits != null); + let o = 0; + const otResp = resp.slice(o, o += this.lastChoiceBits.length * 32); + const hisTagShare = resp.slice(o, o += 16); + assert(resp.length === o); + const res = this.blockAggregationProcessResponse(otResp); + return xor(res, hisTagShare); + } + + // buildFinRequest builds an OT request to compute the tag of either + // Client Finished (CF) or Server Finished (SF) message + // Note that you must use separate instances of the GHASH class: one for + // CF and one for SF + buildFinRequest(H1){ + assert(this.lastChoiceBits == null); + const H2 = blockMult(H1, H1); + const h1Bits = bytesToBits(H1).reverse(); + const h2Bits = bytesToBits(H2).reverse(); + this.shares[1] = H1; + this.shares[2] = H2; + this.lastChoiceBits = [].concat(h1Bits, h2Bits); + return this.otR.createRequest(this.lastChoiceBits); + } + + // processFinResponse processes notary's OT response and multiplies + // each share of H with the corresponding GHASH block + processFinResponse(otResp, ghashInputs){ + assert(this.lastChoiceBits != null); + const twoXTables = this.otR.parseResponse(this.lastChoiceBits, otResp); + let H3 = new Uint8Array(16).fill(0); + // we multiply our H1 share with his H2's masked XTable and + // we multiply our H2 share with his H1's masked XTable + const items = splitIntoChunks(twoXTables, 16); + for (let i = 0; i < 256; i++) { + H3 = xor(H3, items[i]); + } + H3 = xor(H3, blockMult(this.shares[1], this.shares[2])); + this.shares[3] = H3; + const s1 = blockMult(ghashInputs[0], this.shares[3]); + const s2 = blockMult(ghashInputs[1], this.shares[2]); + const s3 = blockMult(ghashInputs[2], this.shares[1]); + this.lastChoiceBits = null; + return xor(xor(s1, s2), s3); + } + + // findSum finds summands from "powers" which add up to "sumNeeded" + // those powers which we don't have are undefined + findSum(powers, sumNeeded){ + for (let i=1; i < powers.length; i++){ + if (powers[i] == undefined){ + continue; + } + for (let j=1; j < powers.length; j++){ + if (powers[j] == undefined){ + continue; + } + if (i+j === sumNeeded){ + return [i, j]; + } + } + } + // this should never happen because we always call + // findSum() knowing that the sum can be found + throw('sum not found'); + } + + // Perform squaring of each odd share up to maxPower. It is "free" because + // it is done locally without OT. + // "powers" is a sparse array where idx is the power (or undefined if not set) and + // item at that idx is client's share of H^power. Modifies "powers" in-place, + // e.g if "powers" contains 1,2,3 and maxPower==19, then upon return "powers" + // will contain 1,2,3,4,6,8,12,16 + freeSquare(powers, maxPower){ + for (let i=0; i < powers.length; i++){ + if (powers[i] == undefined || i % 2 == 0){ + continue; + } + if (i > maxPower){ + return; + } + let power = i; + while (power <= maxPower){ + power = power * 2; + if (powers.includes(power)){ + continue; + } + powers[power] = blockMult(powers[power/2], powers[power/2]); + } + } + } + + // multiply H shares with corresponding ciphertext blocks + // for all H shares which we do not have we wll use the Block Aggregation method + multiplyShares(ghashInputs){ + let res = new Uint8Array(16).fill(0); + // this.powersOfH is a sparse array, its .length is the max index + for (let i=1; i < this.shares.length; i++){ + if (i > ghashInputs.length){ + // we will have more powers than the input blocks + break; + } + if (this.shares[i] == undefined){ + continue; + } + const x = ghashInputs[ghashInputs.length-i]; + res = xor(res, blockMult(this.shares[i], x)); + } + return res; + } + + // for those shares of powers which are not in powersOfH, we do not compute the shares of + // powers, but instead we compute the share of (X_n*H), where X_n is a ciphertext block + + // e.g. if we need H^21*X_1 and we have shares of H^19 and H^2, we compute it as follows: + // (H19_a + H19_b)*(H2_a + H2_b)*X_1 == H19_a*H2_a*X_1 + H19_a*X_1*H2_b + H19_b*X_1*H2_a + + // H19_b*H2_b*X_1 + // only the 2 middle cross-terms need to be computed using OT + // A will receive OT for H19_a*X_1 from B + // B will receive OT for H19_b*X_1 from A + // All other powers where one of the factors is H^2 can be "collapsed" into (i.e xored with) + // the above two cross-terms, eg if parties have shares of H^23 and need to compute + // H^25 == H^23*H^2, then H23_a*X_2 can be collapsed into H19_a*X_1 and + // H23_b*X_2 can be collapsed into H19_b*X_1 + // Thus we would not need any extra OT to compute shares of H^25 + + async blockAggregationBuildRequest(ghashInputs){ + assert(this.lastChoiceBits == null); + // aggregated object's keys are shares and values are the aggregated + // value of shares * GHASH block + let aggregated = {}; + // this.powersOfH is a sparse array. It contains: the powers 1,2,3 + odd powers that we + // computed earlier + squares of all those powers + for (let i=4; i < this.shares.length; i++){ + if (i > ghashInputs.length){ + // we stop iterating shares of powers of H + break; + } + if (this.shares[i] != undefined){ + // we already multiplied the block with this share in multiplyShares() + continue; + } + // found a share which does not exist in our sparse array, + // we need X*H for this missing power + // a is the smaller power, b is the bigger power + const [a, b] = this.findSum(this.shares, i); + const x = ghashInputs[ghashInputs.length-i]; + this.res = xor(this.res, blockMult(blockMult(this.shares[a], this.shares[b]), x)); + if (aggregated[a] == undefined){ + aggregated[a] = new Uint8Array(16).fill(0); + } + aggregated[a] = xor(aggregated[a], blockMult(this.shares[b], x)); + } + + // request Notary's masked X-table for each Notary's small power a. + // We send bits of our aggregated values + // and also + // request Notary's X-table for each Notary's aggregated value. We send bits + // of our small power + const sortedKeys = sortKeys(aggregated); + let allBits = []; + for (const key of sortedKeys){ + // OT starts with the highest bit because ours is the y value of block multiplication + allBits = [].concat(allBits, bytesToBits(aggregated[key]).reverse()); + 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); + } + + // add NOtary's XTable to our share of the tag + blockAggregationProcessResponse(otResp){ + assert(this.lastChoiceBits != null); + const XTableEntries = this.otR.parseResponse(this.lastChoiceBits, otResp); + const items = splitIntoChunks(XTableEntries, 16); + for (let i = 0; i < items.length; i += 1) { + this.res = xor(this.res, items[i]); + } + this.lastChoiceBits = null; + return this.res; + } + + // Compute shares of powers listed in strategies + // modifies PowersOfH in-place + 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); + for (let j=0; j < stratKeys.length; j++){ + const oddPower = stratKeys[j]; + if (oddPower > this.maxOddPowerNeeded){ + assert(chunks.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); + for (let i=0; i < 256; i++){ + xorSum = xor(xorSum, subChunks[i]); + } + const Cx = powersOfH[this.lastStrategy[oddPower][0]]; + const Cy = powersOfH[this.lastStrategy[oddPower][1]]; + const CxCy = blockMult(Cx, Cy); + powersOfH[oddPower] = xor(xorSum, CxCy); + } + } + +} + +// perform GCM Galois Field block multiplication +// x_,y_ are Uint8Arrays +export function blockMult(x_, y_){ + // casting to BigInt just in case if ba2int returns a Number + let x = BigInt(ba2int(x_)); + const y = BigInt(ba2int(y_)); + let res = 0n; + for (let i=127n; i >= 0n; i--){ + res ^= x * ((y >> i) & 1n); + x = (x >> 1n) ^ ((x & 1n) * BigInt(0xE1000000000000000000000000000000)); + } + return int2ba(res, 16); +} + +// return a table of 128 x values needed for block multiplication +// x is Uint8Array +// Not in use because the Client is the OT receiver, he supplies +// the bits of y. The Notary supplies the XTable. +export function getXTable(x_){ + let x = ba2int(x_); + const table = []; + for (let i=0; i < 128; i++){ + table[i] = int2ba(x, 16); + x = (x >> 1n) ^ ((x & 1n) * BigInt(0xE1000000000000000000000000000000)); + } + return table; +} diff --git a/core/twopc/Garbler.js b/core/twopc/Garbler.js index 2c90764..7c4bcd9 100644 --- a/core/twopc/Garbler.js +++ b/core/twopc/Garbler.js @@ -1,182 +1,97 @@ -import {concatTA, assert, ba2int, expandRange} from './../utils.js'; +import {concatTA, assert} from './../utils.js'; +// class Garbler implements the role of the client as the garbler export class Garbler{ - // class Garbler implements the role of the client as the garbler - s; - garbledC; - constructor(parent){ // this.s is the TWOPC class - this.s = parent; + this.s = parent; // this.garbledC will contain truth table, output labels and input labels for each // circuit after it is garbled - this.garbledC = []; + this.garbledC = []; } - async getAllB(allB){ - let fixedCount = 0; - let nonFixedCount = 0; - - for (let i = 1; i < this.s.cs.length; i++) { - if (i !== 6){ - fixedCount += this.s.cs[i].notaryFixedInputSize; - } - else { - fixedCount += 160 + this.s.C6Count * 128; - } - console.log('fixed count for ', i, ' is ', fixedCount); - nonFixedCount += this.s.cs[i].notaryNonFixedInputSize; - } - - console.log('in getAllB fixedCount, nonFixedCount', fixedCount, nonFixedCount); - const OT0PoolSize = Math.ceil(nonFixedCount/2 * 1.2); - const OT1PoolSize = Math.ceil(nonFixedCount/2 * 1.2); - - // 4000 OT for OT-ghash - const expectedLen = (OT0PoolSize + OT1PoolSize + fixedCount + 6000)*32; - assert(allB.length === expectedLen); - - const fixedBlob = allB.slice(0, fixedCount*32); - const nonFixedPoolBlob = allB.slice(fixedCount*32); - - const encLabels = []; - let fixedBlobIdx = 0; - for (let j = 1; j < this.s.cs.length; j++) { - const c = this.s.cs[j]; - let inputCount = c.notaryFixedInputSize; - if (j === 6){ - inputCount = 160 + this.s.C6Count * 128; - } - assert(inputCount*32 === this.garbledC[j].il.notaryFixed.length); - for (let i = 0; i < inputCount; i++) { - const m0 = this.garbledC[j].il.notaryFixed.slice(i*32, i*32+16); - const m1 = this.garbledC[j].il.notaryFixed.slice(i*32+16, i*32+32); - const B = fixedBlob.slice(fixedBlobIdx*32, fixedBlobIdx*32+32); - encLabels.push(this.s.ot.encrypt(m0, m1, B)); - fixedBlobIdx++; - } - } - assert(fixedBlobIdx*32 === fixedBlob.length); - - const arrOfB = []; - for ( let i = 0; i < nonFixedPoolBlob.length/32; i++) { - arrOfB[i] = nonFixedPoolBlob.slice(i*32, i*32+32); - } - await this.s.ot.prepareEncryptionKeys(arrOfB); - return concatTA(...encLabels); - } - async garbleAll(){ - // first garble circuit 5 once, so that future invocations can reuse labels - const rv5 = await this.s.workers[5].garble(); - - // garble the rest of the circuits asyncronously + // we don't parallelize garbling of non-c5 circuits, we garble them + // one by one const allPromises = []; for (let cNo = 1; cNo < this.s.cs.length; cNo++){ if (cNo === 5){ allPromises.push(Promise.resolve('empty')); - continue; + continue; } const worker = this.s.workers[cNo]; allPromises.push(worker.garble()); } - // reuse labels of c5 and garble in batches - const allTt = [new Uint8Array(rv5.tt)]; - const allOl = [new Uint8Array(rv5.ol)]; - const allIl = this.separateLabels(new Uint8Array(rv5.il), 5); - - const outputs = await this.s.workers[5].garbleBatch(this.s.C5Count-1, { - reuseLabels: concatTA(allIl.notaryFixed, allIl.clientNonFixed), - reuseIndexes: expandRange(0, 320), - reuseR: new Uint8Array(rv5.R) - }); - - for (let i=0; i < this.s.C5Count-1; i++){ + 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 out = outputs[i]; allTt.push(new Uint8Array(out.tt)); allOl.push(new Uint8Array(out.ol)); - const labels = this.separateLabels(new Uint8Array(out.il), 5); - allIl.clientFixed = concatTA(allIl.clientFixed, labels.clientFixed); + allIl.push(new Uint8Array(out.il)); } this.garbledC[5] = { tt: concatTA(...allTt), ol: concatTA(...allOl), - il: allIl}; - - // all the other circuits have been garbled by now + il: concatTA(...allIl)}; + + // all non-c5 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){ - // c5 already dealt with - continue; + continue; // c5 already dealt with } this.garbledC[cNo] = { tt: new Uint8Array(rv.tt), ol: new Uint8Array(rv.ol), - il: this.separateLabels(new Uint8Array(rv.il), cNo)}; + il: new Uint8Array(rv.il)}; } } - getNonFixedEncLabels(idxBlob, cNo){ - const nfis = this.s.cs[cNo].notaryNonFixedInputSize; - assert(nfis*2 === idxBlob.length); - - const encLabels = []; - console.time('encryptWithKeyAtIndex'); - for (let i=0; i < nfis; i++){ - const idx = ba2int(idxBlob.subarray(i*2, i*2+2)); - const m0 = this.garbledC[cNo].il.notaryNonFixed.subarray(i*32, i*32+16); - const m1 = this.garbledC[cNo].il.notaryNonFixed.subarray(i*32+16, i*32+32); - const encr = this.s.ot.encryptWithKeyAtIndex(m0, m1, idx); - encLabels.push(encr); + // 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 il = this.garbledC[cNo].il; + const c = this.s.cs[cNo]; + const ilArray = []; + // chunkSize is the bytesize of input labels for one circuit + const chunkSize = (c.notaryInputSize+c.clientInputSize)*32; + assert(chunkSize*exeCount == il.length); + for (let i=0; i < exeCount; i++){ + ilArray.push(il.slice(i*chunkSize, i*chunkSize+c.notaryInputSize*32)); } - console.timeEnd('encryptWithKeyAtIndex'); - console.log('encryptWithKeyAtIndex for count:', nfis); - - return concatTA(...encLabels); + return concatTA(...ilArray); } - getClientLabels(nonFixedBits, cNo){ - const fixedBits = this.s.cs[cNo].fixedInputs; - const clientInputs = [].concat(nonFixedBits, fixedBits); - const inputLabels = []; - const clientLabelBlob = concatTA( - this.garbledC[cNo].il.clientNonFixed, - this.garbledC[cNo].il.clientFixed); + // 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 il = this.garbledC[cNo].il; + const c = this.s.cs[cNo]; + const ilArray = []; + // chunkSize is the bytesize of input labels for one circuit + const chunkSize = (c.notaryInputSize+c.clientInputSize)*32; + assert(chunkSize*repeatCount == il.length); + for (let i=0; i < repeatCount; i++){ + ilArray.push(il.slice(i*chunkSize+c.notaryInputSize*32, (i+1)*chunkSize)); + } + const clientLabelBlob = concatTA(...ilArray); assert(clientInputs.length*32 == clientLabelBlob.length); - + const out = []; for (let i=0; i < clientInputs.length; i++){ const bit = clientInputs[i]; const label = clientLabelBlob.subarray(i*32+bit*16, i*32+bit*16+16); - inputLabels.push(label); + out.push(label); } - return concatTA(...inputLabels); - } - - // separate one continuous blob of input labels into 4 blobs as in Labels struct - separateLabels(blob, cNo) { - const c = this.s.cs[cNo]; - if (blob.length != (c.notaryInputSize+c.clientInputSize)*32) { - throw('in separateLabels'); - } - const labels = {}; - let offset = 0; - labels['notaryNonFixed'] = blob.slice(offset, offset+c.notaryNonFixedInputSize*32); - offset += c.notaryNonFixedInputSize * 32; - - labels['notaryFixed'] = blob.slice(offset, offset+c.notaryFixedInputSize*32); - offset += c.notaryFixedInputSize * 32; - - labels['clientNonFixed'] = blob.slice(offset, offset+c.clientNonFixedInputSize*32); - offset += c.clientNonFixedInputSize * 32; - - labels['clientFixed'] = blob.slice(offset, offset+c.clientFixedInputSize*32); - offset += c.clientFixedInputSize * 32; - assert(offset === blob.length); - return labels; + return concatTA(...out); } } diff --git a/core/twopc/OT.js b/core/twopc/OT.js deleted file mode 100644 index 6f4cfd8..0000000 --- a/core/twopc/OT.js +++ /dev/null @@ -1,201 +0,0 @@ -import {OTWorker} from './OTWorker.js'; -import {concatTA, int2ba, assert, encrypt_generic, decrypt_generic} from './../utils.js'; - -export class OT{ - // class OT implements oblivious transfer protocol based on - // Chou-Orlandi "Simplest OT" - // as much pre-computation as possiblle is done in the offline phase - - constructor(){ - this.decryptionKeys = []; - this.notaryA = null; // A value of notary the sender - this.worker = new OTWorker(4); - - // pools are used to pre-compute in the offline phase the decryption key - // for a receiver's bit in the oblivious transfer - this.poolOf0 = []; // item format {k_R:, B:, idx:<>} - this.poolOf1 = []; // - - // OT for the sender - this.a = sodium.crypto_core_ristretto255_scalar_random(); - this.A = sodium.crypto_scalarmult_ristretto255_base(this.a); - this.encryptionKeys = []; - } - - setA(A){ - this.notaryA = A; - } - - getSenderA(){ - return this.A; - } - - // for each bit in bits pre-compute B and k_R (per [1]) - saveDecryptionKeysOld(bits){ - const receiverBs = []; - // we will reuse the same B to save time - // during release REUSE IS NOT ALLOWED - IT BREAKS SECURITY - const b0 = sodium.crypto_core_ristretto255_scalar_random(); - const B0 = sodium.crypto_scalarmult_ristretto255_base(b0); - const k0 = sodium.crypto_generichash(16, sodium.crypto_scalarmult_ristretto255(b0, this.notaryA)); - - const b1 = sodium.crypto_core_ristretto255_scalar_random(); - const gb1 = sodium.crypto_scalarmult_ristretto255_base(b1); - const B1 = sodium.crypto_core_ristretto255_add(this.notaryA, gb1); - const k1 = sodium.crypto_generichash(16, sodium.crypto_scalarmult_ristretto255(b1, this.notaryA)); - - for (let i=0; i < bits.length; i++){ - const bit = bits[i]; - this.decryptionKeys.push(bit === 0 ? k0 : k1); - receiverBs.push(bit === 0 ? B0 : B1); - } - console.log('saveDecryptionKeys for count:', bits.length); - return receiverBs; - } - - async saveDecryptionKeys(bits){ - const receiverBs = []; - const entries = await this.worker.saveDecryptionKeys(bits, this.notaryA); - for (const e of entries){ - this.decryptionKeys.push(e[0]); - receiverBs.push(e[1]); - } - return receiverBs; - } - - - // decrypts 1-of-2 ciphertexts for a bit "bit" with a key "key" at index "idx" - // returns the plaintext - decryptWithKeyFromIndex(ciphertext, bit, idx){ - assert(ciphertext.length == 32); - assert(bit === 0 || bit === 1); - assert(this.decryptionKeys[idx] != undefined); - const encMessage = ciphertext.slice(16*bit, 16*bit+16); - return decrypt_generic(encMessage, this.decryptionKeys[idx], 0); - } - - // decrypts 1-of-2 ciphertexts for a bit "bit" with a key from obj.k_R - decryptWithKey(ciphertext, bit, key){ - assert(ciphertext.length === 32 && key.length === 16); - assert(bit === 0 || bit === 1); - const encMessage = ciphertext.slice(16*bit, 16*bit+16); - return decrypt_generic(encMessage, key, 0); - } - - // we prepare B and k_R for 120% of the bits. The extra 20% is needed because we don't - // know in advance exactly how many 1s and 0s we'll need during the online phase - precomputePoolOld(numBits){ - // we will reuse the same B to save time - // during release REUSE IS NOT ALLOWED - IT BREAKS SECURITY - const b0 = sodium.crypto_core_ristretto255_scalar_random(); - const B0 = sodium.crypto_scalarmult_ristretto255_base(b0); - const k0 = sodium.crypto_generichash(16, sodium.crypto_scalarmult_ristretto255(b0, this.notaryA)); - - const b1 = sodium.crypto_core_ristretto255_scalar_random(); - const gb1 = sodium.crypto_scalarmult_ristretto255_base(b1); - const B1 = sodium.crypto_core_ristretto255_add(this.notaryA, gb1); - const k1 = sodium.crypto_generichash(16, sodium.crypto_scalarmult_ristretto255(b1, this.notaryA)); - - console.time('precomputePool'); - for (let i = 0; i < Math.ceil(numBits/2 * 1.2) ; i++){ - this.poolOf0.push({k_R:k0, B:B0}); - } - for (let i = 0; i < Math.ceil(numBits/2 * 1.2); i++){ - this.poolOf1.push({k_R:k1, B:B1}); - } - console.timeEnd('precomputePool'); - console.log('total bits', numBits); - } - - // we prepare B and k_R for 120% of the bits. The extra 20% is needed because we don't - // know in advance exactly how many 1s and 0s we'll need during the online phase - async precomputePool(numBits){ - const count = Math.ceil(numBits/2 * 1.2); // we need "count" zeroes and "count" ones - const entries = await this.worker.precomputePool(count, this.notaryA); - assert(entries.length === count*2); - for (const e of entries.slice(0, count)){ - this.poolOf0.push({k_R:e[0], B:e[1]}); - } - for (const e of entries.slice(count, count*2)){ - this.poolOf1.push({k_R:e[0], B:e[1]}); - } - } - - // given an array of bits, return the index for each bit in the pool - // and decryption keys for OT - getIndexesFromPool(bits){ - const idxArray = []; - const otKeys = []; - for (let i=0; i < bits.length; i++){ - const otbit = this.getFromPool(bits[i]); - idxArray.push(int2ba(otbit.idx, 2)); - otKeys.push(otbit.k_R); - } - return [concatTA(...idxArray), otKeys]; - } - - - // return an array of B values from poolOf0 and poolOf1 in random sequence - // and remember each B's index in that sequence. - getRandomizedPool(){ - function getRandomInt(min, max) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min + 1)) + min; - } - - const randomizedB = []; - const fullPool = [].concat(this.poolOf0, this.poolOf1); - const origLen = fullPool.length; - for (let i=0; i < origLen; i++){ - const randIdx = getRandomInt(0, fullPool.length-1); - const ot = fullPool.splice(randIdx, 1)[0]; - // modifying ot will be reflected in this.poolOf0/this.poolOf1 because of pass-by-reference - ot.idx = i; - randomizedB.push(ot.B); - } - return randomizedB; - } - - - // gets either 0 or 1 from pool - getFromPool(bit){ - assert(bit === 0 || bit === 1); - const pool = (bit === 0) ? this.poolOf0 : this.poolOf1; - const item = pool.pop(); - assert(this.poolOf0.length > 0 && this.poolOf0.length > 0); - return item; - } - - // given the receiver's B, encrypt m0 and m1 - // we don't parallelize this function because the amount of encryptions is small - encrypt(m0, m1, B){ - const k0 = sodium.crypto_generichash(16, sodium.crypto_scalarmult_ristretto255(this.a, B)); - const sub = sodium.crypto_core_ristretto255_sub(B, this.A); - const k1 = sodium.crypto_generichash(16, sodium.crypto_scalarmult_ristretto255(this.a, sub)); - const e0 = encrypt_generic(m0, k0, 0); - const e1 = encrypt_generic(m1, k1, 0); - return concatTA(e0, e1); - } - - encryptWithKeyAtIndex(m0, m1, idx){ - const k0 = this.encryptionKeys[idx][0]; - const k1 = this.encryptionKeys[idx][1]; - const e0 = encrypt_generic(m0, k0, 0); - const e1 = encrypt_generic(m1, k1, 0); - return concatTA(e0, e1); - } - - // (client as the sender) for each B in arrOfB save an encryption keypair [k0,k1] - async prepareEncryptionKeys(arrOfB){ - console.time('prepareEncryptionKeys'); - const entries = await this.worker.prepareEncryptionKeys(arrOfB, this.a, this.A); - assert(entries.length === arrOfB.length); - for (const e of entries){ - this.encryptionKeys.push([e[0], e[1]]); - } - console.timeEnd('prepareEncryptionKeys'); - console.log('prepareEncryptionKeys for count:', arrOfB.length); - } -} - \ No newline at end of file diff --git a/core/twopc/OTCommon.js b/core/twopc/OTCommon.js new file mode 100644 index 0000000..35aa32a --- /dev/null +++ b/core/twopc/OTCommon.js @@ -0,0 +1,169 @@ +import {assert, getRandom, bytesToBits, concatTA, int2ba, xor, ba2int, + bitsToBytes, splitIntoChunks, AESCTRencrypt, Salsa20} from '../utils.js'; + +// methods used by both OTSender and OTReceiver classes +export default class OTCommon{ + // extend r (Uint8Array) into a matrix of 128 columns where depending on r's bit, each row + // is either all 0s or all 1s + extend_r(r){ + // 128 bits all set to 1 + const all_1 = new Uint8Array(16).fill(255); + // 128 bits all set to 0 + const all_0 = new Uint8Array(16).fill(0); + const matrix = []; + const bits = bytesToBits(r).reverse(); + for (const bit of bits){ + matrix.push(bit == 0 ? all_0 : all_1); + } + return matrix; + } + + + // given a matrix, output 2 xor shares of it + secretShareMatrix(matrix){ + const T0 = []; + const T1 = []; + for (let i=0; i < matrix.length; i++){ + const rand = getRandom(16); + T0.push(rand); + T1.push(xor(matrix[i], rand)); + } + return [T0, T1]; + } + + + // transpose a matrix of bits. matrix is an array of rows (each row is a Uin8Array) + transposeMatrix(matrix){ + const colCount = matrix[0].length*8; + const rowCount = matrix.length; + assert(colCount == 128 || rowCount == 128); + const newRows = []; + for (let j=0; j < colCount; j++){ + // in which byte of the column is j located + const byteNo = j >> 3; //Math.floor(j / 8); + // what is the index of j inside the byte + const bitIdx = j % 8; + const newRowBits = []; + for (let i=0; i < rowCount; i++){ + newRowBits.push((matrix[i][byteNo] >> (7-bitIdx)) & 1); + } + newRows.push(bitsToBytes(newRowBits.reverse())); + } + return newRows; + } + + + // pseudorandomly expands a 16-byte seed into a bytestring of bytesize "count"*16 + // to benefit from AES-NI, we use browser WebCrypto's AES-CTR: with seed as the key + // we encrypt an all-zero bytestring. + async expandSeed(seed, count){ + assert(seed.length == 16); + return await AESCTRencrypt(seed, new Uint8Array(count*16).fill(0)); + } + + + // encrypt each 16-byte chunk of msg with a fixed-key Salsa20 + async fixedKeyCipher(msg){ + assert(msg.length % 16 == 0); + 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, 29, 30, 31, 32]); + const encryptedArr = []; + const chunks = splitIntoChunks(msg, 16); + for (const chunk of chunks){ + encryptedArr.push(Salsa20(fixedKey, chunk)); + } + return concatTA(...encryptedArr); + } + + + // to break the correlation, KOS15 needs a hash function which has tweakable correlation + // robustness (tcr). GKWY20 shows (Section 7.4) how to achieve tcr using a fixed-key cipher C + // instead of a hash, i.e. instead of Hash(x, i) we can do C(C(x) xor i) xor C(x) + async breakCorrelation(rows){ + assert(rows[0].length == 16); + const AESx = await this.fixedKeyCipher(concatTA(...rows)); + const indexesArray = []; + for (let i=0; i < rows.length; i++){ + indexesArray.push(int2ba(i, 16)); + } + const indexes = concatTA(...indexesArray); + return xor(await this.fixedKeyCipher(xor(AESx, indexes)), AESx); + } + + + // carry-less multiplication (i.e. multiplication in galois field) without reduction. + // Let a's right-most bit have index 0. Then for every bit set in a, b is left-shifted + // by the set bit's index value. All the left-shifted values are then XORed. + // a and b are both UintArray's of 16 bytes. Returns UintArray's of 32 bytes + + // this version is 25% faster than the naive implementation clmul128_unoptimized + clmul128(a, b){ + const aBits = bytesToBits(a); // faster if turned to bits rather than shift each time + const b_bi = ba2int(b); + const shiftedB = []; + // shift only 7 times and then use these 7 shifts to construct all other shifts + for (let i=0n; i < 8n; i++){ + const tmp = new Uint8Array(32).fill(0); + tmp.set(int2ba(b_bi << i, 17), 0); + shiftedB.push(tmp); + } + const res = new Uint8Array(32).fill(0); + for (let i = 0; i < 128; i++){ + if (aBits[i]){ // a's bit is set + const byteNo = i >> 3; // this is faster than Math.floor(i / 8); + const bitIdx = i % 8; + const bLen = 17+byteNo; + for (let i=0; i < bLen; i++){ + res[31-i] = res[31-i] ^ shiftedB[bitIdx][bLen-1-i]; + } + } + } + return res; + } + + + // not in use, just for reference. This is the unoptimized version of clmul128 + clmul128_unoptimized(a, b){ + let res = 0n; + const aBits = bytesToBits(a); // faster if turned to bits rather than shift each time + const b_bi = ba2int(b); + for (let i = 0n; i < 128n; i++){ + if (aBits[i]){ // a's bit is set + res ^= (b_bi << i); + } + } + return int2ba(res, 32); + } +} + + +// To test the protocol end-to-end, copy-paste this function into the extension's console +async function testFullProtocol(){ + let otR = new PageSigner.OTReceiver(8); + let otS = new PageSigner.OTSender(8); + const [A, seedCommit] = await otR.setupStep1(); + const [allBs, senderSeedShare] = otS.setupStep1(A, seedCommit); + const [encryptedColumns, receiverSeedShare, x, t] = await otR.setupStep2(allBs, senderSeedShare); + await otS.setupStep2(encryptedColumns, receiverSeedShare, x, t); + const requestBits = [0, 1, 1, 0]; + const otReq1 = otR.createRequest(requestBits); + const senderMsg = new Uint8Array([].concat( + Array(16).fill(0), + Array(16).fill(1), + Array(16).fill(2), + Array(16).fill(3), + Array(16).fill(4), + Array(16).fill(5), + Array(16).fill(6), + Array(16).fill(7))); + const otResp = otS.processRequest(otReq1, senderMsg); + const decoded = otR.parseResponse(requestBits, otResp); + console.log('the result is: ', decoded); + const expected = new Uint8Array([].concat( + Array(16).fill(0), + Array(16).fill(3), + Array(16).fill(5), + Array(16).fill(6), + )); + console.log('expected result is: ', expected); +} \ No newline at end of file diff --git a/core/twopc/OTReceiver.js b/core/twopc/OTReceiver.js new file mode 100644 index 0000000..94f59e5 --- /dev/null +++ b/core/twopc/OTReceiver.js @@ -0,0 +1,153 @@ +/* global sodium */ + +import {assert, getRandom, bytesToBits, concatTA, sha256, xor, int2ba, + bitsToBytes, AESCTRencrypt} from '../utils.js'; +import OTCommon from './OTCommon.js'; + +// class OTReceiver implements the receiver of the 1-of-2 Oblivious Transfer. +// run the full KOS15 protocol +export class OTReceiver extends OTCommon{ + constructor(otCount){ + super(); //noop but JS requires it to be called + this.otCount = otCount; + this.extraOT = 256; // extended OT which will be sacrificed as part of KOS15 protocol + this.totalOT = Math.ceil(otCount/8)*8 + this.extraOT; + // seedShare is my xor share of a PRG seed + this.seedShare = null; + this.rbits = []; + this.T0 = []; + this.T1 = []; + this.a = null; + this.A = null; + this.RT0 = []; + // receivedSoFar counts how many bits of OT have been used up by the receiver + this.receivedSoFar = 0; + // expectingResponseSize is how many OTs the receiver expects to receive from the sender + // at this point + this.expectingResponseSize = 0; + } + + // run KOS15 to prepare Random OT. Step 1 + async setupStep1(){ + this.seedShare = getRandom(16); + const seedCommit = await sha256(this.seedShare); + const r = getRandom(this.totalOT/8); + const R = this.extend_r(r); + this.rbits = bytesToBits(r).reverse(); + [this.T0, this.T1] = this.secretShareMatrix(R); + // for baseOT Bob is the sender, he chooses a and sends A + this.a = sodium.crypto_core_ristretto255_scalar_random(); + this.A = sodium.crypto_scalarmult_ristretto255_base(this.a); + return [this.A, seedCommit]; + } + + // run KOS15 to prepare Random OT. Step 2 + async setupStep2(allBsBlob, senderSeedShare){ + // compute key_0 and key_1 for each B of the base OT + assert(allBsBlob.length == 128*32); + assert(senderSeedShare.length == 16); + const encrKeys = []; + for (let i=0; i < 128; i++){ + const B = allBsBlob.slice(i*32, (i+1)*32); + const k0 = sodium.crypto_generichash(16, sodium.crypto_scalarmult_ristretto255(this.a, B)); + const sub = sodium.crypto_core_ristretto255_sub(B, this.A); + const k1 = sodium.crypto_generichash(16, sodium.crypto_scalarmult_ristretto255(this.a, sub)); + encrKeys.push([k0, k1]); + } + // Use the i-th k0 to encrypt the i-th column in T0, likewise + // use the i-th k1 to encrypt the i-th column in T1 + const T0columns = this.transposeMatrix(this.T0); + const T1columns = this.transposeMatrix(this.T1); + const encryptedColumns = []; + for (let i=0; i < 128; i++){ + encryptedColumns.push(await AESCTRencrypt(encrKeys[i][0], T0columns[i])); + encryptedColumns.push(await AESCTRencrypt(encrKeys[i][1], T1columns[i])); + } + + // KOS15 kicks in at this point to check if Receiver sent the correct columnsArray + // combine seed shares and expand the seed + const seed = xor(this.seedShare, senderSeedShare); + const expandedSeed = await this.expandSeed(seed, this.totalOT); + // Bob multiplies every 128-bit row of matrix T1 with the corresponding random + // value in expandedSeed and XORs the products. + // Bob multiplies every bit of r with the corresponding random + // value in expandedSeed and XORs the products. + // Bob sends seed,x,t to Alice + let x = new Uint8Array(16).fill(0); + let t = new Uint8Array(32).fill(0); + for (let i=0; i < this.T0.length; i++){ + const rand = expandedSeed.subarray(i*16, (i+1)*16); + if (this.rbits[i] == 1){ + x = xor(x, rand); + } + t = xor(t, this.clmul128(this.T0[i], rand)); + } + + // we need to break correlations between Q0 and Q1 + // The last extraOTs were sacrificed as part of the KOS15 protocol + // and so we don't need them anymore + console.log('start breakCorrelation'); + this.RT0 = await this.breakCorrelation(this.T0.slice(0, -this.extraOT)); + console.log('end breakCorrelation'); + // also drop the unneeded bytes and bits of r + this.rbits = this.rbits.slice(0, -this.extraOT); + + + // now we have instances of Random OT where depending on r's bit, + // each row in RT0 equals to a row either in RQ0 or RQ1 + console.log('done 2'); + // use Beaver Derandomization [Beaver91] to convert randomOT into standardOT + return [concatTA(...encryptedColumns), this.seedShare, x, t]; + } + + // Steps 5, 6 and 7 are repeated for every batch of OT + + + // createRequest takes OT receiver's choice bits and instructs the + // OT sender which random masks need to be flipped (Beaver Derandomization) + createRequest(choiceBits){ + assert(this.receivedSoFar + choiceBits.length <= this.otCount, 'No more OTs left.'); + assert(this.expectingResponseSize == 0, 'The previous request must be processed before requesting more OTs.'); + // for Beaver Derandomization, tell the Sender which masks to flip: 0 means + // no flip needed, 1 means a flip is needed + const bitsToFlip = []; + for (let i=0; i < choiceBits.length; i++){ + bitsToFlip.push(choiceBits[i] ^ this.rbits[this.receivedSoFar+i]); + } + // pad the bitcount to a multiple of 8 + let padCount = 0; + if (choiceBits.length % 8 > 0){ + padCount = 8 - choiceBits.length % 8; + } + for (let i=0; i < padCount; i++){ + bitsToFlip.push(0); + } + + this.expectingResponseSize = choiceBits.length; + // prefix with the amount of bits that Sender needs to drop + // in cases when bitsArr.length is not a multiple of 8 + return concatTA(int2ba(padCount, 1), bitsToBytes(bitsToFlip)); + } + + // parseResponse unmasks the OT sender's masked values based on the choice + // bit and the random mask of the OT receiver + parseResponse(choiceBits, maskedOT){ + assert(this.expectingResponseSize == choiceBits.length); + assert(this.expectingResponseSize*32 == maskedOT.length); + const decodedArr = []; + for (let i=0; i < choiceBits.length; i++){ + const mask = this.RT0.slice((this.receivedSoFar+i)*16, (this.receivedSoFar+i)*16+16); + const m0 = maskedOT.slice(i*32, i*32+16); + const m1 = maskedOT.slice(i*32+16, i*32+32); + if (choiceBits[i] == 0){ + decodedArr.push(xor(m0, mask)); + } else { + decodedArr.push(xor(m1, mask)); + } + } + 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/OTSender.js b/core/twopc/OTSender.js new file mode 100644 index 0000000..89414df --- /dev/null +++ b/core/twopc/OTSender.js @@ -0,0 +1,136 @@ +/* global sodium */ + +import {assert, getRandom, bytesToBits, concatTA, xor, eq, AESCTRdecrypt, + sha256, ba2int} from '../utils.js'; +import OTCommon from './OTCommon.js'; + + +// class OTSender implements the sender of the Oblivious Transfer acc.to. +// the KOS15 protocol +export class OTSender extends OTCommon{ + constructor(otCount){ + super(); //noop but JS requires it to be called + this.otCount = otCount; + this.extraOT = 256; // extended OT which will be sacrificed as part of KOS15 protocol + this.totalOT = Math.ceil(otCount/8)*8 + this.extraOT; + this.S = null; + this.sBits = []; + this.decrKeys = []; + this.RQ0 = []; + this.RQ1 = []; + // sentSoFar is how many OTs the sender has already sent + this.sentSoFar = 0; + // hisCommit is Receiver's commit to the PRG seed + this.hisCommit = null; + // seedShare is my xor share of a PRG seed + this.seedShare = null; + } + + // part of KOS15 + setupStep1(A, hisCommit){ + this.hisCommit = hisCommit; + this.seedShare = getRandom(16); + // Alice computes her Bs and decryption keys based on each bit in S + this.S = getRandom(16); + this.sBits = bytesToBits(this.S).reverse(); + const allBs = []; + this.decrKeys = []; + for (const bit of this.sBits){ + const b = sodium.crypto_core_ristretto255_scalar_random(); + let B = sodium.crypto_scalarmult_ristretto255_base(b); + if (bit == 1){ + B = sodium.crypto_core_ristretto255_add(A, B); + } + const k = sodium.crypto_generichash(16, sodium.crypto_scalarmult_ristretto255(b, A)); + this.decrKeys.push(k); + allBs.push(B); + } + return [concatTA(...allBs), this.seedShare]; + } + + + // part of KOS15 + async setupStep2(encryptedColumnsBlob, receiverSeedShare, x, t){ + assert(receiverSeedShare.length == 16); + assert(encryptedColumnsBlob.length % 256 == 0); + const encryptedColumns = []; + const columnSize = encryptedColumnsBlob.length/256; + for (let i=0; i < 256; i++){ + encryptedColumns.push(encryptedColumnsBlob.slice(i*columnSize, (i+1)*columnSize)); + } + // Decrypt only those columns which correspond to S's bit + const columns = []; + for (let i=0; i < 128; i++){ + const col0 = encryptedColumns[i*2]; + const col1 = encryptedColumns[i*2+1]; + if (this.sBits[i] == 0){ + columns.push(await AESCTRdecrypt(this.decrKeys[i], col0)); + } else { + columns.push(await AESCTRdecrypt(this.decrKeys[i], col1)); + } + } + const Q0 = this.transposeMatrix(columns); + + // KOS15: Alice multiplies every 128-bit row of matrix Q1 with the corresponding random + // value in expandedSeed and XORs the products + assert(eq(await sha256(receiverSeedShare), this.hisCommit), 'Bad seed commit'); + const seed = xor(receiverSeedShare, this.seedShare); + const expandedSeed = await this.expandSeed(seed, this.totalOT); + let q = new Uint8Array(32).fill(0); + for (let i=0; i < Q0.length; i++){ + const rand = expandedSeed.subarray(i*16, (i+1)*16); + q = xor(q, this.clmul128(Q0[i], rand)); + } + // Alice checks that t = q xor x * S + assert(eq(t, xor(q, this.clmul128(x, this.S)))); + + // Alice xors each row of Q0 with S to get Q1 + const Q1 = []; + for (let i=0; i < Q0.length; i++){ + Q1.push(xor(Q0[i], this.S)); + } + + // we need to break correlations between Q0 and Q1 + // The last extraOTs were sacrificed as part of the KOS15 protocol + // and so we don't need them anymore + console.log('start breakCorrelation'); + this.RQ0 = await this.breakCorrelation(Q0.slice(0, -this.extraOT)); + this.RQ1 = await this.breakCorrelation(Q1.slice(0, -this.extraOT)); + console.log('end breakCorrelation'); + // now we have instances of Random OT where depending on r's bit, + // each row in RT0 equals to a row either in RQ0 or RQ1 + console.log('done 2'); + // in Steps 5,6,7 we will use Beaver Derandomization to convert + // randomOT into standardOT + } + + + // processRequest performs Beaver Derandomization: + // for every bit in bitsToFlip, the Sender has two 16-byte messages for 1-of-2 OT and + // two random masks (from the KOS15 protocol) r0 and r1 + // if the bit is 0, the Sender sends (m0 xor r0) and (m1 xor r1), + // if the bit is 1, the Sender sends (m0 xor r1) and (m1 xor r0) + processRequest(bitsBlob, messages){ + const dropCount = ba2int(bitsBlob.slice(0, 1)); + const bitsToFlipWithRem = bytesToBits(bitsBlob.slice(1)); + const bitsToFlip = bitsToFlipWithRem.slice(0, bitsToFlipWithRem.length-dropCount); + assert(this.sentSoFar + bitsToFlip.length <= this.otCount); + assert(bitsToFlip.length*32 == messages.length); + const encodedToSend = []; + for (let i=0; i < bitsToFlip.length; i++){ + const m0 = messages.slice(i*32, i*32+16); + const m1 = messages.slice(i*32+16, i*32+32); + const r0 = this.RQ0.slice((this.sentSoFar+i)*16, (this.sentSoFar+i)*16+16); + const r1 = this.RQ1.slice((this.sentSoFar+i)*16, (this.sentSoFar+i)*16+16); + if (bitsToFlip[i] == 0){ + encodedToSend.push(xor(m0, r0)); + encodedToSend.push(xor(m1, r1)); + } else { + encodedToSend.push(xor(m0, r1)); + encodedToSend.push(xor(m1, r0)); + } + } + this.sentSoFar += bitsToFlip.length; + return concatTA(...encodedToSend); + } +} \ No newline at end of file diff --git a/core/twopc/OTWorker.js b/core/twopc/OTWorker.js deleted file mode 100644 index 0714113..0000000 --- a/core/twopc/OTWorker.js +++ /dev/null @@ -1,147 +0,0 @@ -// note that unlike with GCWorker class, the caller of OTWorker's methods doesn't have -// to manage the worker pool. We manage them internally inside the class. -import {bitsToBytes, concatTA, assert} from './../utils.js'; - -import WorkerPool from './WorkerPool.js'; - -export class OTWorker extends WorkerPool{ - constructor(numWorkers){ - super(numWorkers, chrome.extension.getURL('core/twopc/webWorkers/otworker.js')); - } - - async saveDecryptionKeys(bits, A){ - let padCount = 0; - if (bits.length % 8 > 0){ - // make bits length a multiple of 8 - padCount = 8 - bits.length % 8; - bits.push(...Array(padCount).fill(0)); - } - const bytes = bitsToBytes(bits); - // put chunks of 128 bytes into a batch - const batch = []; - const chunkSize = 128; - for (let i=0; i < Math.ceil(bytes.length/chunkSize); i++){ - batch.push([bytes.slice(i*chunkSize, (i+1)*chunkSize), A]); - } - const outputs = await this.workerPool(batch, this.saveDecryptionKeysDoWork); - const arrOutput = []; - for (let i=0; i < batch.length; i++){ - arrOutput[i] = new Uint8Array(outputs[batch.length-1-i]); - } - const dataBlob = concatTA(...arrOutput); - assert(dataBlob.length === bits.length*48); - // deserialize data - const entries = []; - for (let i=0; i < bits.length; i++){ - const k = dataBlob.slice(i*48, i*48+16); - const B = dataBlob.slice(i*48+16, i*48+48); - entries.push([k,B]); - } - return entries.slice(0, entries.length-padCount); - } - - async precomputePool(count, A){ - // chunk up into 512 items and put chunks into a batch - const batch = []; - const chunkSize = 512; - const chunkCount = Math.ceil(count/chunkSize); - const lastChunkSize = count - (chunkSize * (chunkCount-1)); - for (let i=0; i < chunkCount; i++){ - if (i === chunkCount-1){ - batch.push([lastChunkSize, A]); - } - else{ - batch.push([chunkSize, A]); - } - } - const outputs = await this.workerPool(batch, this.precomputePoolDoWork); - const arrOutput = []; - for (let i=0; i < batch.length; i++){ - arrOutput[i] = new Uint8Array(outputs[i]); - } - // deserialize data - const entries0 = []; - const entries1 = []; - for (let i=0; i < arrOutput.length; i++){ - const size = (i < (chunkCount-1)) ? chunkSize : lastChunkSize; - const batch = arrOutput[i]; - for (let j=0; j < size*2; j++){ - const k = batch.slice(j*48, j*48+16); - const B = batch.slice(j*48+16, j*48+48); - if (j < size){ - entries0.push([k,B]); - } - else { - entries1.push([k,B]); - } - } - } - const allEntries = [].concat(entries0, entries1); - assert(allEntries.length === count*2); - return allEntries; - } - - async prepareEncryptionKeys(arrOfB, a, A){ - const batch = []; - const chunkSize = 1024; - const chunkCount = Math.ceil(arrOfB.length/chunkSize); - for (let i=0; i < chunkCount; i++){ - batch.push([concatTA(...arrOfB.slice(i*chunkSize, (i+1)*chunkSize)), a, A]); - } - const outputs = await this.workerPool(batch, this.prepareEncryptionKeysDoWork); - const arrOutput = []; - for (let i=0; i < batch.length; i++){ - arrOutput[i] = new Uint8Array(outputs[i]); - } - const dataBlob = concatTA(...arrOutput); - assert(dataBlob.length === arrOfB.length*32); - // deserialize data - const entries = []; - for (let i=0; i < arrOfB.length; i++){ - const k0 = dataBlob.slice(i*32, i*32+16); - const k1 = dataBlob.slice(i*32+16, i*32+32); - entries.push([k0,k1]); - } - return entries; - } - - prepareEncryptionKeysDoWork(batchItem, worker){ - const bytes = batchItem[0]; - const a = batchItem[1]; - const A = batchItem[2]; - return new Promise(function(resolve) { - worker.onmessage = function(event) { - worker['isResolved'] = true; - resolve(event.data.blob); - }; - const obj = {msg:'prepareEncryptionKeys', data:{bytes:bytes.buffer, a:a.buffer, A:A.buffer}}; - worker.postMessage(obj); - }); - } - - precomputePoolDoWork(batchItem, worker){ - const count = batchItem[0]; - const A = batchItem[1]; - return new Promise(function(resolve) { - worker.onmessage = function(event) { - worker['isResolved'] = true; - resolve(event.data.blob); - }; - const obj = {msg:'precomputePool', data:{count:count, A:A.buffer}}; - worker.postMessage(obj); - }); - } - - saveDecryptionKeysDoWork(batchItem, worker){ - const bytes = batchItem[0]; - const A = batchItem[1]; - return new Promise(function(resolve) { - worker.onmessage = function(event) { - worker['isResolved'] = true; - resolve(event.data.blob); - }; - const obj = {msg:'saveDecryptionKeys', data:{bytes:bytes.buffer, A:A.buffer}}; - worker.postMessage(obj); - }); - } -} \ No newline at end of file diff --git a/core/twopc/TWOPC.js b/core/twopc/TWOPC.js index 2ab4181..cd79e6f 100644 --- a/core/twopc/TWOPC.js +++ b/core/twopc/TWOPC.js @@ -1,114 +1,63 @@ -/* eslint-disable no-bitwise */ -/* eslint-disable class-methods-use-this */ -/* eslint-disable max-len */ +/* global keepAliveAgent */ -import {ba2hex, sortKeys, assert, getRandom, bytesToBits, concatTA, int2ba, - str2ba, innerHash, xor, blockMult, eq, sha256, getXTable, ba2int, splitIntoChunks, - bitsToBytes, pubkeyPEM2raw, checkExpiration, wait} from '../utils.js'; -import {Garbler} from './Garbler.js'; -import {Evaluator} from './Evaluator.js'; -import {OT} from './OT.js'; +import {ba2hex, assert, getRandom, bytesToBits, concatTA, int2ba, str2ba, + innerHash, xor, eq, sha256, ba2int, splitIntoChunks, bitsToBytes, + pubkeyPEM2raw, checkExpiration, wait} from '../utils.js'; +import {Garbler} from './Garbler.js'; +import {Evaluator} from './Evaluator.js'; import {Paillier2PC} from './Paillier2PC.js'; -import {GCWorker} from './GCWorker.js'; -import {globals} from './../globals.js'; +import {GCWorker} from './GCWorker.js'; +import {globals} from './../globals.js'; +import {OTSender} from './OTSender.js'; +import {OTReceiver} from './OTReceiver.js'; +import {GHASH} from './GHASH.js'; -// class C is initialized once and then it is a read-only struct -// accessed by both garbler and evaluator -class C{ - constructor( notaryInputSize, notaryNonFixedInputSize, - clientInputSize, clientNonFixedInputSize, circuitOutputSize, circuit, masks, fixedInputs){ - - this.notaryInputSize = notaryInputSize; - this.clientInputSize = clientInputSize; - this.circuitOutputSize = circuitOutputSize; - // nonFixedInputs are inputs the value of which is not known in the offline phase - // as opposed to fixed inputs like masks which can be chosen in an offline phase - // the order of inputs is: non-fixed inputs first, then fixed inputs - this.notaryNonFixedInputSize = notaryNonFixedInputSize; - this.clientNonFixedInputSize = clientNonFixedInputSize; - this.notaryFixedInputSize = this.notaryInputSize - this.notaryNonFixedInputSize; - this.clientFixedInputSize = this.clientInputSize - this.clientNonFixedInputSize; - // circuit is a serialized circuit with metadata - this.circuit = circuit; - // what each mask does for each circuit is explained in c*.casm files - this.masks = masks; - // fixedInputs is an array A of arrays B, where - // arrayA's length is equal to the amount of circuits - // arrayB consists of 0 and 1, item at idx==0 is the first input to the circuit - this.fixedInputs = fixedInputs; - } -} - - - -// eslint-disable-next-line no-unused-vars export class TWOPC { - // notary is an object {'IP':, 'pubkeyPEM':} - notary; - // clientKey is a symmetric key used to encrypt messages to the notary - clientKey; - // notaryKey is a symmetric key used to decrypt messages from the notary - notaryKey; constructor(notary, plaintextLen, circuits, progressMonitor) { + // notary is an object {'IP':, 'pubkeyPEM':} 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; - this.cwkMaskedByNotary = null; - this.swkMaskedByNotary = null; - this.sivMaskedByNotary = null; - this.civMaskedByNotary = null; + // shares of TLS session keys + this.cwkShare = null; + this.swkShare = null; + this.sivShare = null; + this.civShare = null; this.innerState_MS = null; // HMAC inner sha256 state for MS this.g = new Garbler(this); this.e = new Evaluator(this); - this.ot = new OT(); this.pm = progressMonitor; // uid is used for communication with the notary this.uid = Math.random().toString(36).slice(-10); - - const [masks, fixedInputs] = this.initInputs(); - this.cs = []; - this.cs[1] = new C(512, 256, 512, 256, 512, circuits[1], masks[1], fixedInputs[1]); - this.cs[2] = new C(512, 256, 640, 384, 512, circuits[2], masks[2], fixedInputs[2]); - this.cs[3] = new C(832, 256, 1568, 768, 800, circuits[3], masks[3], fixedInputs[3]); - this.cs[4] = new C(672, 416, 960, 480, 480, circuits[4], masks[4], fixedInputs[4]); - this.cs[5] = new C(160, 0, 308, 160, 128, circuits[5], masks[5], fixedInputs[5]); - this.cs[6] = new C(288, 0, 304, 160, 128, circuits[6], masks[6], fixedInputs[6]); - - // fixedLabels contains a label for each fixed input bit - this.fixedLabels = Array(this.cs.length); + // cs is an array of circuits, where each circuit is an object with the fields: + // gatesBlob, gatesCount, wiresCount, notaryInputSize, clientInputSize, + // outputSize, andGateCount + this.cs = Object.keys(circuits).map(k => circuits[k]); + // start count of circuits with 1, push an empy element to index 0 + this.cs.splice(0, 0, undefined); // 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 - // if client's and notary's commits match, then both parties can be assured + // if client's and notary's commits match, then both parties can be assured // that no malicious garbling took place. - // hisSaltedCommit is a salted hash of the circuit's output from the notary + // hisSaltedCommit is a salted hash of the circuit's output from the notary this.hisSaltedCommit = Array(this.cs.length); // myCommit is a (unsalted) hash of the circuit's output this.myCommit = Array(this.cs.length); // workers are GCWorker class for each circuit this.workers = Array(this.cs.length); - - console.log('need to evaluate ', this.C5Count, ' c5 circuits'); - this.preComputed = false; // will set to true after preCompute() was run - // powersOfH is an array of client's shares of powers of H, starting with H^1 at index 1 - this.powersOfH = []; - - // maxPowerNeeded is the maximum power of H that we'll need in order to compute the GHASH - // function. This doesn't mean that the parties will compute shares of this power. It just - // shows the upper bound of H. - this.maxPowerNeeded = Math.ceil(plaintextLen /16) + 2; - // maxOddPowerNeeded is the maximum odd power for which the parties must compute their shares - this.maxOddPowerNeeded = this.initMaxOddPower(this.maxPowerNeeded); - // ghashOTNeeded is how many bits the receiver of Oblivious Transfer will have - // in order to compute the GHASH function - this.ghashOTNeeded = this.initGhashOTNeeded(this.maxPowerNeeded, this.maxOddPowerNeeded); - + // clientKey is a symmetric key used to encrypt messages to the notary + this.clientKey = null; + // notaryKey is a symmetric key used to decrypt messages from the notary + this.notaryKey = null; // ephemeralKey is an ephemeral key used by notary to sign this session this.ephemeralKey = null; // eValidFrom and eValidUntil are times from/until which the ephemeralKey is valid @@ -123,11 +72,34 @@ export class TWOPC { this.client_random = null; // server_random is a 32-byte random value from Server Hellp this.server_random = null; - // allHandshakes is a concatenation of all handshake messages up to this point. + // allHandshakes is a concatenation of all handshake messages up to this point. // This is only data visible at the handshake layer and does not include record layer headers this.allHandshakes = null; // hs_hash is a sha256 hash of allHandshakes this.hs_hash = null; + // ghash is used when computing an MAC (tag) on the request + // we need GHASH to first tell OTReceiver how many OTs will be needed, + // then we pass the OTReceiver to GHASH + this.ghash = new GHASH(noOfAESBlocks); + // otCountRecv is how many OTs the receiver needs. The client plays OT + // receiver for each bit of his inputs to the circuits + OTs for GHASH + this.otCountRecv = this.ghash.otCount; + // otCountSend is how many OTs the sender should expect. The client plays + // OT sender for each bit of notary's circuits' inputs + 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]; + 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]; + } + console.log('otCountRecv/otCountSend', this.otCountRecv, this.otCountSend); + // otR is the receiver of Oblivious Transfer + this.otR = new OTReceiver(this.otCountRecv); + // otS is the sender of Oblivious Transfer + this.otS = new OTSender(this.otCountSend); + this.ghash.setOTReceiver(this.otR); } // destroy de-registers listeners, terminates workers @@ -139,169 +111,6 @@ export class TWOPC { gcworker.workers[j].terminate(); } } - for (const w of this.ot.worker.workers){ - w.terminate(); - } - } - - // compute how many bits need OT for ghash-OT method - initGhashOTNeeded(maxPowerNeeded, maxOddPowerNeeded){ - const strategies = { - 5: [4,1], - 7: [4,3], - 9: [8,1], - 11: [8,3], - 13: [12,1], - 15: [12,3], - 17: [16,1], - 19: [16,3], - 21: [17,4], - 23: [17,6], - 25: [17,8], - 27: [19,8], - 29: [17,12], - 31: [19,12], - 33: [17,16], - 35: [19,16]}; - - // add powers 1,2,3 which the client will already have - const allPowers = []; // all shares of powers which the client has - allPowers[1] = true; - allPowers[2] = true; - allPowers[3] = true; - - // will contain unique powers for which we compute shares directly - // each power requires the client to prepare 256 OT bits - const uniquePowers = []; - for (const k of sortKeys(strategies)){ - if (k <= maxOddPowerNeeded){ - allPowers[k] = true; - const v = strategies[k]; - if (! uniquePowers.includes(v[0])){ - uniquePowers.push(v[0]); - } - if (! uniquePowers.includes(v[1])){ - uniquePowers.push(v[1]); - } - } - } - - // and perform "free squaring" on all powers - for (let i=1; i < allPowers.length; i++){ - if (allPowers[i] == undefined){ - continue; - } - let power = i; - while (power <= maxPowerNeeded){ - power = power * 2; - if (allPowers[power] != undefined){ - continue; - } - allPowers[power] = true; - } - } - - // how many auxillary powers will be needed for the block aggregation method - // each aux power requires 128 OT bits from client - const auxPowers = []; - for (let i=1; i <= maxPowerNeeded; i++){ - if (allPowers[i] != undefined){ - // for this power the client has a share of H, no need for the block aggregation method - continue; - } - // a is the smaller power - const [a,b] = this.findSum(allPowers, i); - if (! auxPowers.includes(a)){ - auxPowers.push(a); - } - } - - return uniquePowers.length * 256 + auxPowers.length*128; - } - - initMaxOddPower(maxPowerNeeded){ - assert(maxPowerNeeded <= 1026); - - // maxHTable = {:}. shows (when using the block aggregation method) - // how many powers of H can be obtained if we: - // A) have all the sequential powers starting with 1 up to AND - // B) have performed OT with the notary on all those sequential powers. - // e.g. {5:29} means that if we have powers 1,2,3,4,5 then using the block aggregation method we can obtain - // up to and including H^29. - // max TLS record size of 16KB requires 1026 powers of H - const maxHTable = {0: 0, 3: 19, 5: 29, 7: 71, 9: 89, 11: 107, 13: 125, 15: 271, 17: 305, 19: 339, 21: 373, - 23: 407, 25: 441, 27: 475, 29: 509, 31: 1023, 33: 1025, 35: 1027}; - - let maxOddPowerNeeded = null; - for (const key of sortKeys(maxHTable)){ - const maxH = maxHTable[key]; - if (maxH >= maxPowerNeeded){ - maxOddPowerNeeded = key; - break; - } - } - return maxOddPowerNeeded; - } - - // set all masks and fixed inputs for each circuit - initInputs(){ - const masks = []; - const fixedInputs = []; - - masks[1] = []; - masks[1][1] = getRandom(32); - fixedInputs[1] = bytesToBits(masks[1][1]); - - masks[2] = []; - masks[2][1] = getRandom(32); - fixedInputs[2] = bytesToBits(masks[2][1]); - - masks[3] = []; - const maskSizes3 = [0,16,16,4,4,16,16,16,12]; - for (let i=1; i < maskSizes3.length; i++){ - masks[3][i] = getRandom(maskSizes3[i]); - } - fixedInputs[3] = bytesToBits(concatTA(...masks[3].slice(1).reverse())); - - masks[4] = []; - const maskSizes4 = [0,16,16,16,12]; - for (let i=1; i < maskSizes4.length; i++){ - masks[4][i] = getRandom(maskSizes4[i]); - } - fixedInputs[4] = bytesToBits(concatTA(...masks[4].slice(1).reverse())); - - masks[5] = []; - for (let i = 0; i < this.C5Count; i++) { - masks[5].push(getRandom(16)); - } - const totalBits = []; - // we only send 1 TLS record with a fixed nonce 2 - const nonce = 2; - for (let i = 0; i < this.C5Count; i++) { - const counter = 2 + i; - const counterBits = bytesToBits(int2ba(counter, 2)).slice(0, 10); - const nonceBits = bytesToBits(int2ba(nonce, 2)).slice(0, 10); - const maskBits = bytesToBits(masks[5][i]); - totalBits.push([].concat(maskBits, nonceBits, counterBits)); - } - fixedInputs[5] = [].concat(...totalBits); - - masks[6] = []; - for (let i = 0; i < this.C6Count; i++) { - masks[6].push(getRandom(16)); - } - // for gctr blocks - let totalParsed6 = []; - for (let i = 0; i < this.C6Count; i++) { - // nonce starts with 2 - const nonce = 2 + i; - const parsedNonce = bytesToBits(int2ba(nonce, 2)).slice(0, 16); - const mask = bytesToBits(masks[6][i]); - totalParsed6.push([].concat(mask, parsedNonce)); - } - fixedInputs[6] = [].concat(...totalParsed6); - - return [masks, fixedInputs]; } async getECDHShare(x, y){ @@ -309,9 +118,8 @@ export class TWOPC { return await paillier.run(); } - // pass client/server random, handshake (to derive verify_data), pms share - // returns encrypted Client Finished (CF), authentication tag for CF, verify_data for CF + // returns encrypted Client Finished (CF), authentication tag for CF, verify_data for CF async run(cr, sr, allHandshakes, pmsShare) { this.clientPMSShare = pmsShare; this.client_random = cr; @@ -324,13 +132,27 @@ export class TWOPC { console.log('pre-computation was not done before the session started'); throw('pre-computation was not done before the session started'); } - - const nonFixedBits = bytesToBits(this.clientPMSShare); - const c1Output = await this.runCircuit(nonFixedBits, 1); - const c2_output = await this.phase2(c1Output); - const c3_output = await this.phase3(c2_output); - const [encFinished, tag, verify_data] = await this.phase4(c3_output); + const c1Mask = getRandom(32); + const c1Out = await this.runCircuit([this.clientPMSShare, c1Mask], 1); + // unmask the output + const innerState1Masked = c1Out[0].slice(0, 32); + const innerState1 = xor(innerState1Masked, c1Mask); + + const [c2_p2, c2_p1inner] = await this.phase2(innerState1); + const c2Mask = getRandom(32); + const input2 = [c2_p1inner, c2_p2.subarray(0, 16), c2Mask]; + const c2Out = await this.runCircuit(input2, 2); + // unmask the output + const innerState2Masked = c2Out[0].slice(0, 32); + const innerState2 = xor(innerState2Masked, c2Mask); + + const [c3_p1inner_vd, c3_p2inner, c3_p1inner] = await this.phase3(innerState2); + // for readability, mask indexing starts from 1 + const masks3 = [0, 16, 16, 4, 4, 16, 16, 16, 12].map(x => getRandom(x)); + const input3 = [c3_p1inner, c3_p2inner, c3_p1inner_vd, ...masks3.slice(1)]; + const c3Out = await this.runCircuit(input3, 3); + const [encFinished, tag, verify_data] = await this.phase4(c3Out, masks3); return [encFinished, tag, verify_data]; } @@ -349,427 +171,128 @@ export class TWOPC { const a1 = await this.send('c4_pre1', a1inner); const p1inner = innerHash(this.innerState_MS, concatTA(a1, seed)); - const nonFixedBits = bytesToBits(concatTA( - new Uint8Array(sf_nonce), - new Uint8Array(this.sivMaskedByNotary), - new Uint8Array(this.swkMaskedByNotary), - p1inner)); - - const outArray = await this.runCircuit(nonFixedBits, 4); + // 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 outArray = await this.runCircuit(input4, 4); const c4_output = outArray[0]; console.log('c4Output.length', c4_output.length); - let o = 0; // offset + let o = 0; // offset const verify_dataMasked = c4_output.slice(o, o+=12); - const verify_data = xor(verify_dataMasked, this.cs[4].masks[4]); - const sf = concatTA(new Uint8Array([0x14, 0x00, 0x00, 0x0c]), verify_data); - + const verify_data = xor(verify_dataMasked, masks4[4]); const encCounterMasked = c4_output.slice(o, o+=16); - const encCounter = xor(encCounterMasked, this.cs[4].masks[3]); - + const encCounter = xor(encCounterMasked, masks4[3]); const gctrSFMasked = c4_output.slice(o, o+=16); - const gctrShare = xor(gctrSFMasked, this.cs[4].masks[2]); - + const gctrShare = xor(gctrSFMasked, masks4[2]); const H1MaskedTwice = c4_output.slice(o, o+=16); - const H1 = xor(H1MaskedTwice, this.cs[4].masks[1]); - const H2 = blockMult(H1, H1); - const H1H2 = blockMult(H1, H2); - - // OT starts with the highest bit - const h1Bits = bytesToBits(H1).reverse(); - const [idxArray1, otKeys1] = this.ot.getIndexesFromPool(h1Bits); - const h2Bits = bytesToBits(H2).reverse(); - const [idxArray2, otKeys2] = this.ot.getIndexesFromPool(h2Bits); - + // 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 sf = concatTA(new Uint8Array([0x14, 0x00, 0x00, 0x0c]), verify_data); const encSF = xor(sf, encCounter); assert(eq(sf_pure, encSF)); - const step3resp = await this.send('c4_step3', concatTA(encSF, idxArray1, idxArray2)); - assert(step3resp.length === 16 + 128*32 + 128*32); + 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)); + assert(step3resp.length == 16 + 256*32); o = 0; - const Snotary = step3resp.slice(o, o+=16); - const encEntries1 = step3resp.slice(o, o+=(128*32)); - const encEntries2 = step3resp.slice(o, o+=(128*32)); - - let H3share = H1H2; - for (let i = 0; i < h1Bits.length; i += 1) { - const bit = h1Bits[i]; - const ct = encEntries2.slice(i * 32, (i+1) * 32); - const maskedEntry = this.ot.decryptWithKey(ct, bit, otKeys1[i]); - H3share = xor(H3share, maskedEntry); - } - for (let i = 0; i < h2Bits.length; i += 1) { - const bit = h2Bits[i]; - const ct = encEntries1.slice(i * 32, (i+1) * 32); - const maskedEntry = this.ot.decryptWithKey(ct, bit, otKeys2[i]); - H3share = xor(H3share, maskedEntry); - } + const notaryTagShare = step3resp.slice(o, o+=16); + const otResp = step3resp.slice(o, o+=(256*32)); const aad = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 22, 3, 3, 0, 16, 0, 0, 0]); // lenA (before padding) == 13*8 == 104, lenC == 16*8 == 128 const lenAlenC = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 104, 0, 0, 0, 0, 0, 0, 0, 128]); - - const s1share = blockMult(aad, H3share); - const s2share = blockMult(encSF, H2); - const s3share = blockMult(lenAlenC, H1); - const Sclient = xor(xor(xor(s1share, s2share), s3share), gctrShare); - - const tagFromPowersOfH = xor(Snotary, Sclient); + const tagShare = ghashSF.processFinResponse(otResp, [aad, encSF, lenAlenC]); + // notary's gctr share is already included in notaryTagShare + const tagFromPowersOfH = xor(xor(notaryTagShare, tagShare), gctrShare); assert (eq(sf_tag, tagFromPowersOfH)); } + // runs circuit 5 async getEncryptedCounters() { - const nonFixedBits = bytesToBits(concatTA(this.civMaskedByNotary, this.cwkMaskedByNotary)); - const outArray = await this.runCircuit(nonFixedBits, 5); - const results = []; - for (let i=0; i < this.C5Count; i++){ - const encCounter = xor(outArray[i], this.cs[5].masks[i]); - results.push(encCounter); + const masks5 = []; + let input5 = []; + // we only send 1 TLS record with a fixed nonce 2 + const fixedNonce = int2ba(2, 2); + for (let i = 0; i < this.C5Count; i++) { + const counter = int2ba(2 + i, 2); + masks5[i] = getRandom(16); + input5 = [].concat(input5, [this.cwkShare, this.civShare, masks5[i], + fixedNonce, 10, counter, 10]); } - return results; + const outArray = await this.runCircuit(input5, 5); + const encCounters = []; + for (let i=0; i < this.C5Count; i++){ + encCounters.push(xor(outArray[i], masks5[i])); + } + return encCounters; } // note that getGctrBlocks does not actually output gctr function's output but only // client's gctr share. The notary's gctr share is already xored into the output // of getTagFromPowersOfH async getGctrBlocks(){ - const nonFixedBits = bytesToBits(concatTA(this.civMaskedByNotary, this.cwkMaskedByNotary)); - const outArray = await this.runCircuit(nonFixedBits, 6); - + 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 outArray = 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 results = []; + + const gctrBlocks = []; for (let i=0; i < this.C6Count; i++){ - const gctrBlock = xor(outArray[i], this.cs[6].masks[i]); - results.push(gctrBlock); + gctrBlocks.push(xor(outArray[i], masks6[i])); } - return results; + return gctrBlocks; } - async getTagFromPowersOfH(encRequestBlocks){ + // getTagFromPowersOfH receives an array of encrypted request blocks and computes + // an authentication tag over them + async getTagFromPowersOfH(erb){ // prepare ghash inputs const ghashInputs = []; - // last block may not be 16 bytes - const recordLen = (encRequestBlocks.length-1)*16+encRequestBlocks[encRequestBlocks.length-1].length; + // last block may be less than 16 bytes + const recordLen = (erb.length-1)*16+erb[erb.length-1].length; const seqNum = int2ba(1, 8); const recordLenBytes = int2ba(recordLen, 2); - const aad = concatTA(seqNum, new Uint8Array([23,3,3]), recordLenBytes); + const aad = concatTA(seqNum, new Uint8Array([23, 3, 3]), recordLenBytes); // right-pad with zeroes if needed let aadPadding = []; if (aad.length % 16 > 0) { aadPadding = Array(16-(aad.length%16)).fill(0); } ghashInputs.push(concatTA(aad, new Uint8Array(aadPadding))); - ghashInputs.push(...encRequestBlocks.slice(0,-1)); - let ctPadding = []; - if (encRequestBlocks[encRequestBlocks.length-1].length%16 > 0) { - ctPadding = Array(16-(encRequestBlocks[encRequestBlocks.length-1].length%16)).fill(0); + ghashInputs.push(...erb.slice(0, -1)); + let ctPadding = []; + if (erb[erb.length-1].length%16 > 0) { + ctPadding = Array(16-(erb[erb.length-1].length%16)).fill(0); } - ghashInputs.push(concatTA(encRequestBlocks[encRequestBlocks.length-1], new Uint8Array(ctPadding))); + ghashInputs.push(concatTA(erb[erb.length-1], new Uint8Array(ctPadding))); const lenA = int2ba(aad.length*8, 8); const lenC = int2ba(recordLen*8, 8); ghashInputs.push(concatTA(lenA, lenC)); - // fill this.powersOfH with needed H shares - await this.getOddPowers(this.maxPowerNeeded); - const directSum = this.useDirectHShares(ghashInputs); - const indirectSum = await this.useIndirectHShares(ghashInputs); - return [[xor(directSum, indirectSum)], concatTA(...ghashInputs)]; - + const resp1 = await this.send('ghash_step1', await this.ghash.buildStep1()); + this.ghash.processOTResponse(resp1); + if (this.ghash.isStep2Needed()){ + const resp2 = await this.send('ghash_step2', await this.ghash.buildStep2()); + this.ghash.processOTResponse(resp2); + } + const req3 = concatTA(...ghashInputs, await this.ghash.buildStep3(ghashInputs)); + const resp3 = await this.send('ghash_step3', req3); + const tagShare = this.ghash.processStep3Response(resp3); + return [[tagShare], concatTA(...ghashInputs)]; } - // multiply direct H shares with corresponding ciphertext blocks - useDirectHShares(ghashInputs){ - let res = int2ba(0, 16); - // this.powersOfH is a sparse array, its .length is the max index - for (let i=1; i < this.powersOfH.length; i++){ - if (i > ghashInputs.length){ - // we will have more powers than the input blocks - break; - } - if (this.powersOfH[i] == undefined){ - continue; - } - const h = this.powersOfH[i]; - const x = ghashInputs[ghashInputs.length-i]; - res = xor(res, blockMult(h,x)); - } - return res; - } - - // for those shares of powers which are not in powersOfH, we do not compute the shares of - // powers, but instead we compute the share of X_n*H, where X_n is a ciphertext block - - // e.g. if we need H^21*X_1 and we have shares of H^19 and H^2, we compute it as follows: - // (H19_a + H19_b)*(H2_a + H2_b)*X_1 == H19_a*H2_a*X_1 + H19_a*X_1*H2_b + H19_b*X_1*H2_a + - // H19_b*H2_b*X_1 - // only the 2 middle cross-terms need to be computed using OT - // A will receive OT for H19_a*X_1 from B - // B will receive OT for H19_b*X_1 from A - // All other powers where one of the factors is H^2 can be "collapsed" into (i.e xored with) - // the above two cross-terms, eg if parties have shares of H^23 and need to compute - // H^25 == H^23*H^2, then H23_a*X_2 can be collapsed into H19_a*X_1 and - // H23_b*X_2 can be collapsed into H19_b*X_1 - // Thus we would not need any extra OT to compute shares of H^25 - - async useIndirectHShares(ghashInputs){ - let res = int2ba(0, 16); // this is the xor of all my X*H shares - let sumForPowers = {}; - for (let i=4; i < this.powersOfH.length; i++){ - if (i > ghashInputs.length){ - // we stop iterating shares of powers of H - break; - } - if (this.powersOfH[i] != undefined){ - continue; - } - // found a hole in our sparse array, we need X*H for this missing power - // a is the smaller power - const [a,b] = this.findSum(this.powersOfH, i); - const x = ghashInputs[ghashInputs.length-i]; - const h_small = this.powersOfH[a]; - const h_big = this.powersOfH[b]; - res = xor(res, blockMult(blockMult(h_small, h_big), x)); - const hx = blockMult(h_big, x); - if (sumForPowers.hasOwnProperty(a)){ - sumForPowers[a] = xor(sumForPowers[a], hx); - } - else { - sumForPowers[a] = hx; - } - } - - // send OT for all sums in sumForPowers in ascending order - const sortedKeys = sortKeys(sumForPowers); - let allBits = []; - for (const key of sortedKeys){ - const bytes = sumForPowers[key]; - // OT starts with the highest bit - const bits = bytesToBits(new Uint8Array(bytes)).reverse(); - allBits = [].concat(allBits, bits); - } - const [idxArray, otKeys] = this.ot.getIndexesFromPool(allBits); - - const payload = concatTA(...ghashInputs, idxArray); - const step5resp = await this.send('ghash_step5', payload); - let o = 0; - const encEntries = step5resp.slice(o, o+idxArray.length/2 * 32); - o += idxArray.length/2 * 32; - const hisIndexes = step5resp.slice(o, o+idxArray.length); - o += idxArray.length; - assert(step5resp.length === o); - - for (let i = 0; i < allBits.length; i += 1) { - const bit = allBits[i]; - const ct = encEntries.slice(i * 32, (i+1) * 32); - const maskedEntry = this.ot.decryptWithKey(ct, bit, otKeys[i]); - res = xor(res, maskedEntry); - } - - // get his indexes and send his encrypted masked entries - let maskSum = int2ba(0, 16); - let totalOffset = 0; - const myEncEntries = []; - for (let i=0; i < sortedKeys.length; i++){ - const xTable = getXTable(this.powersOfH[sortedKeys[i]]); - for (let i=0; i < 128; i++){ - const idx = ba2int(hisIndexes.slice(totalOffset, totalOffset+2)); - totalOffset += 2; - const mask = getRandom(16); - maskSum = xor(maskSum, mask); - const m0 = mask; - const m1 = xor(xTable[i], mask); - myEncEntries.push(this.ot.encryptWithKeyAtIndex(m0, m1, idx)); - } - - } - res = xor(res, maskSum); - const hisTagShare = await this.send('ghash_step6', concatTA(...myEncEntries)); - return xor(res, hisTagShare); - } - - - // draw from the OT pool and send to notary OT step1 - getOTIndexArray(powersForOT, powersOfH){ - // prepare OT data for each bit of each power - const idxArrays = []; - for (const power of sortKeys(powersForOT)){ - const bits = bytesToBits(powersOfH[power]).reverse(); - powersForOT[power]['bits'] = bits; - const [idxArray, otKeys] = this.ot.getIndexesFromPool(bits); - powersForOT[power]['otKeys'] = otKeys; - idxArrays.push(idxArray); - } - return idxArrays; - } - - - // decrypt OT from notary and compute shares of powers listed in strategies - // modifies PowersOfH in-place - getPowerShares(strategies, powersForOT, encEntriesBlob, powersOfH){ - const stratKeys = sortKeys(strategies); - assert(encEntriesBlob.length === stratKeys.length*256*32); - - for (let j=0; j < stratKeys.length; j++){ - const oddPower = stratKeys[j]; - let xorSum = int2ba(0, 16); // start with 0 sum - for (let round=0; round < 2; round++){ - // first factor is always notary's, second factor is always client's - // on first round factor at strategies[key][0] is notary's - // on second round factor at strategies[key][1] is notary's - let f2 = round === 0 ? strategies[oddPower][1] : strategies[oddPower][0]; - const encEntries = encEntriesBlob.slice((j*256+round*128)*32, (j*256+(round+1)*128)*32); - for (let i = 0; i < 128; i += 1) { - const bit = powersForOT[f2].bits[i]; - const ct = encEntries.slice(i * 32, (i+1) * 32); - const maskedEntry = this.ot.decryptWithKey(ct, bit, powersForOT[f2].otKeys[i]); - xorSum = xor(xorSum, maskedEntry); - } - } - const Cx = powersOfH[strategies[oddPower][0]]; - const Cy = powersOfH[strategies[oddPower][1]]; - const CxCy = blockMult(Cx,Cy); - powersOfH[oddPower] = xor(xorSum, CxCy); - } - } - - - // Use oblivious transfer with the notary to compute the necessary amount of odd powers - // we will later call powersPlusOne with these odd powers to compute X*H. - // Note: when OT-extension is implemented, we're not gonna need all these optimizations. - // We will be able to compute all powers directly with OT-extenion. But for now, since - // we use base-OT which is expensive, we resort to optimizations. - - async getOddPowers(maxPowerNeeded){ - console.log('maxPowerNeeded is', maxPowerNeeded); - assert(maxPowerNeeded <= 1026); - - // perform free squaring of shares H^2 and H^3 which we have from the Client_Finished - this.freeSquare(this.powersOfH, maxPowerNeeded); - - if (this.maxOddPowerNeeded === 3){ - await this.send('ghash_step1', int2ba(maxPowerNeeded, 2)); - return; // already have power 3 - } - - // strategies shows what existing powers we will be multiplying to obtain other odd powers - // max sequential odd power that we can obtain on first round is 19 - // Note that "sequential" is a keyword here. We can't obtain 21 but we indeed can obtain - // 25==24+1, 33==32+1 etc. However with 21 missing, we will not be able to obtain more powers - // with the X*H method, even if we have 25,33,etc. - const strategies = { - 5: [4,1], - 7: [4,3], - 9: [8,1], - 11: [8,3], - 13: [12,1], - 15: [12,3], - 17: [16,1], - 19: [16,3]}; - const powersForOT1 = {1: {},3: {},4: {},8: {},12: {},16: {}}; - - // TODO send only those powers which we actually need - const idxArrays1 = this.getOTIndexArray(powersForOT1, this.powersOfH); - - const encEntries1 = await this.send('ghash_step1', concatTA(...idxArrays1, int2ba(maxPowerNeeded, 2))); - - this.getPowerShares(strategies, powersForOT1, encEntries1, this.powersOfH); - this.freeSquare(this.powersOfH, maxPowerNeeded); - - // we need to perform OT on all powers [1, maxOddPowerNeeded] which we havent performed OT - // for yet - const powersLeft = [2,5,6,7,9,10,11,13,14,15,17,18,19]; - const powersForOT2 = {}; - for (const power of powersLeft){ - if (power <= this.maxOddPowerNeeded){ - powersForOT2[power] = {}; - } - } - const idxArrays2 = this.getOTIndexArray(powersForOT2, this.powersOfH); - await this.send('ghash_step2', concatTA(...idxArrays2)); - - if (this.maxOddPowerNeeded <= 19){ - return; - } - // else we need more odd powers. The max that we'll ever need is 35 - // strategies2 should be canonical for both client and notary - // Eventually we will send OT for all powers from 1 to 35 - const strategies2 = { - 21: [17,4], - 23: [17,6], - 25: [17,8], - 27: [19,8], - 29: [17,12], - 31: [19,12], - 33: [17,16], - 35: [19,16]}; - - const powersForOT3 = {17: {},19: {}}; - const idxArrays3 = this.getOTIndexArray(powersForOT3, this.powersOfH); - const step3resp = await this.send('ghash_step3', concatTA(...idxArrays3)); - this.getPowerShares(strategies2, powersForOT3, step3resp, this.powersOfH); - this.freeSquare(this.powersOfH, maxPowerNeeded); - // we need to perform OT on all powers [1, maxOddPowerNeeded] which we havent performed OT - // for yet - const powersLeft2 = [20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35]; - const powersForOT4 = {}; - for (let i=0; i < powersLeft2.length; i++){ - if (powersLeft2[i] <= this.maxOddPowerNeeded){ - powersForOT4[i] = {}; - } - } - const idxArrays4 = this.getOTIndexArray(powersForOT4, this.powersOfH); - await this.send('ghash_step4', concatTA(...idxArrays4)); - } - - // those powers which were not set are undefined - findSum(powers, sumNeeded){ - for (let i=1; i < powers.length; i++){ - if (powers[i] == undefined){ - continue; - } - for (let j=1; j < powers.length; j++){ - if (powers[j] == undefined){ - continue; - } - if (i+j === sumNeeded){ - return [i,j]; - } - } - } - // this should never happen because we always call - // findSum() knowing that the sum can be found - throw('sum not found'); - } - - // Perform squaring of each share of odd power up to maxPower. It is "free" because we can - // compute them locally without doing OT with the other party. - // "powers" is an array where idx is the power (or undefined if not set) and item at that idx - // is client's share of H^power - // modifies "powers" in-place - // e.g if "powers" contains 1,2,3 and maxPower==19, then upon return "powers" - // will contain 1,2,3,4,6,8,12,16 - freeSquare(powers, maxPower){ - for (let i=0; i < powers.length; i++){ - if (powers[i] == undefined || i % 2 === 0){ - continue; - } - if (i > maxPower){ - return; - } - let power = i; - while (power <= maxPower){ - power = power * 2; - if (powers.includes(power)){ - continue; - } - powers[power] = blockMult(powers[power/2], powers[power/2]); - } - } - } // send data to the notary async send(cmd, data = new Uint8Array(0), returnPromise = false, willEncrypt = true){ @@ -780,7 +303,7 @@ export class TWOPC { else { to_be_sent = data; } - + function timeout(ms, promise) { return new Promise(function(resolve, reject) { setTimeout(function() { @@ -791,7 +314,7 @@ export class TWOPC { } const fetchPromise = fetch( - 'http://'+this.notary.IP+':'+globals.defaultNotaryPort+'/'+cmd+'?'+this.uid, + 'http://'+this.notary.IP+':'+globals.defaultNotaryPort+'/'+cmd+'?'+this.uid, { method: 'POST', mode: 'cors', @@ -801,7 +324,7 @@ export class TWOPC { 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){ @@ -832,74 +355,34 @@ export class TWOPC { } } - async preComputeOT(){ - - const allFixedInputs = []; - let allNonFixedInputsCount = 0; - let allFixedInputsCount = 0; - for (let i=1; i < this.cs.length; i++){ - allFixedInputs.push(this.cs[i].fixedInputs); - allNonFixedInputsCount += this.cs[i].clientNonFixedInputSize; - } - allNonFixedInputsCount += (256 + 256); // for powers of H OT for ClFin and SerFin - allFixedInputsCount = [].concat(...allFixedInputs).length; - - console.log('allFixedInputs.length', allFixedInputsCount); - console.log('precomputePool size', allNonFixedInputsCount + this.ghashOTNeeded); - - if (this.pm) this.pm.update('last_stage', {'current': 1, 'total': 10}); - const receiverBs = await this.ot.saveDecryptionKeys([].concat(...allFixedInputs)); - await this.ot.precomputePool(allNonFixedInputsCount + this.ghashOTNeeded); - - const OTpayload = concatTA(...receiverBs, ...this.ot.getRandomizedPool()); - console.log('sending payload of size', OTpayload.length); - const resp_ot_AllB = await this.send('ot_AllB', OTpayload); - console.log('returned from ot_AllB'); - const fixedOTBlob = resp_ot_AllB.slice(0, allFixedInputsCount*32); - const OTFromNotaryEval = resp_ot_AllB.slice(allFixedInputsCount*32); - console.time('getAllB'); - const encLabelsBlob = await this.g.getAllB(OTFromNotaryEval); - console.timeEnd('getAllB'); - this.send('ot_encLabelsForEval', encLabelsBlob); - - // get evaluator's fixed labels and keep them - let idx = 0; - for (let i=1; i < this.cs.length; i++){ - this.fixedLabels[i] = []; - for (let j=0; j < this.cs[i].fixedInputs.length; j++){ - const ct = fixedOTBlob.slice(idx*32, (idx+1)*32); - const bit = this.cs[i].fixedInputs[j]; - const inputLabel = this.ot.decryptWithKeyFromIndex(ct, bit, idx); - idx += 1; - this.fixedLabels[i].push(inputLabel); - } - } - assert(fixedOTBlob.length === idx*32); - if (this.pm) this.pm.update('last_stage', {'current': 2, 'total': 10}); - } - - // preCompute() should be called before run() is called + // init() should be called before run() is called // it does all fetching/pre-computation that can be done in 2PC offline phase - async preCompute() { + async init() { if (this.preComputed) { return; } - const [pubBytes, privKey] = await this.generateECKeypair(); + // asyncly garble while the rest of the setup is running + await this.setupWorkers(); + const garbleAllPromise = this.g.garbleAll(); - const preInitBlob = await this.send('preInit', concatTA( + const [A, seedCommit] = await this.otR.setupStep1(); + const [pubBytes, privKey] = await this.generateECKeypair(); + const init1Blob = await this.send('init1', concatTA( pubBytes, int2ba(this.C5Count, 2), int2ba(this.C6Count, 2), - int2ba(this.ghashOTNeeded, 2), - this.ot.getSenderA()), false, false); + int2ba(this.otCountRecv, 4), + int2ba(this.otCountSend, 4), + A, + seedCommit), false, false); let o = 0; - this.eValidFrom = preInitBlob.slice(o, o+=4); - this.eValidUntil = preInitBlob.slice(o, o+=4); - this.ephemeralKey = preInitBlob.slice(o, o+=65); - this.eSigByMasterKey = preInitBlob.slice(o, o+=64); - const encrypted = preInitBlob.slice(o); + this.eValidFrom = init1Blob.slice(o, o+=4); + this.eValidUntil = init1Blob.slice(o, o+=4); + this.ephemeralKey = init1Blob.slice(o, o+=65); + this.eSigByMasterKey = init1Blob.slice(o, o+=64); + const encrypted = init1Blob.slice(o); console.log(this.notary.pubkeyPEM); const notaryKey = pubkeyPEM2raw(this.notary.pubkeyPEM); @@ -912,27 +395,49 @@ export class TWOPC { this.eSigByMasterKey.buffer, tbs.buffer); assert(result === true, 'Error verifying ephemeral keys from notary.'); - assert(checkExpiration(this.eValidFrom, this.eValidUntil) === true, + assert(checkExpiration(this.eValidFrom, this.eValidUntil) === true, 'Error verifying ephemeral key validity.'); // generate symmetric encryption keys for communication with the notary const [ck, nk] = await this.generateSymmetricKeys(privKey, this.ephemeralKey); this.clientKey = ck; this.notaryKey = nk; - const notaryA = await this.decryptFromNotary(nk, encrypted); - this.ot.setA(notaryA); + const init1Resp = await this.decryptFromNotary(nk, encrypted); + o = 0; + const hisReceiverA = init1Resp.slice(o, o+=32); + 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 const that = this; const getBlobPromise = new Promise((resolve) => { - this.getBlobFromNotary() + this.getBlobFromNotary(totalBytes) .then(function(hisBlob){ that.blob = that.processBlob(hisBlob); resolve(); }); }); - - await this.setupWorkers(); - await this.g.garbleAll(); + + const [encryptedColumns, receiverSeedShare, x, t] = + await this.otR.setupStep2(hisSenderallBs, hisSenderSeedShare); + const [allBs, senderSeedShare] = this.otS.setupStep1(hisReceiverA, hisReceiverCommit); + const init2Resp = await this.send('init2', concatTA( + encryptedColumns, receiverSeedShare, x, t, allBs, senderSeedShare + )); + + assert(init2Resp.length == 256*this.otS.totalOT/8 + 16 + 16 + 32); + o = 0; + const hisReceiverEncryptedColumns = init2Resp.slice(o, o+=256*this.otS.totalOT/8); + const hisReceiverSeedShare = init2Resp.slice(o, o+=16); + const hisReceiverX = init2Resp.slice(o, o+=16); + const hisReceiverT = init2Resp.slice(o, o+=32); + 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); @@ -941,9 +446,7 @@ export class TWOPC { // start blob upload as soon as download finishes await getBlobPromise; - const sendBlobPromise = this.sendBlobToNotary(concatTA(...blobOut)); - await this.preComputeOT(); - await sendBlobPromise; + await this.sendBlobToNotary(concatTA(...blobOut)); this.preComputed = true; } @@ -969,9 +472,8 @@ export class TWOPC { // getNotaryBlob downloads a blob from notary in 1 MB chunks publishes the download progress // to the UI. - async getBlobFromNotary (){ + async getBlobFromNotary (totalBytes){ const allChunks = []; - const totalBytes = ba2int(await this.send('init')); console.log('totalBytes is', totalBytes); let soFarBytes = 0; @@ -992,7 +494,7 @@ export class TWOPC { async decryptFromNotary (key, enc){ try{ - const IV = enc.slice(0,12); + const IV = enc.slice(0, 12); const ciphertext = enc.slice(12); return new Uint8Array(await crypto.subtle.decrypt( {name: 'AES-GCM', iv: IV.buffer}, @@ -1018,18 +520,14 @@ export class TWOPC { async setupWorkers(){ const maxWorkerCount = 3; // only needed for c5 - const workerCount = [0,1,1,1,1,maxWorkerCount,1]; + const workerCount = [0, 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].circuit); + this.workers[i].parse(this.cs[i]); } } - async phase2(outArray) { - console.log('reached phase2'); - const c1Output = outArray[0]; - const innerStateMasked = c1Output.slice(0, 32); - const innerStateUint8 = xor(innerStateMasked, this.cs[1].masks[1]); + async phase2(innerStateUint8) { const innerState = new Int32Array(8); for (let i = 0; i < 8; i++) { @@ -1055,19 +553,14 @@ export class TWOPC { const p2 = await this.send('c1_step5', p2inner); const p1inner = innerHash(innerState, concatTA(a1, seed)); - const nonFixedBits = bytesToBits(concatTA(p2.subarray(0, 16), p1inner)); - return await this.runCircuit(nonFixedBits, 2); + return [p2, p1inner]; } - async phase3(outArray) { - const c2_output = outArray[0]; - const innerStateMasked = c2_output.slice(0, 32); - const innerStateUint8 = xor(innerStateMasked, this.cs[2].masks[1]); - + async phase3(innerState) { // hash state for MS this.innerState_MS = new Int32Array(8); for (var i = 0; i < 8; i++) { - var hex = ba2hex(innerStateUint8).slice(i * 8, (i + 1) * 8); + var hex = ba2hex(innerState).slice(i * 8, (i + 1) * 8); this.innerState_MS[i] = `0x${hex}`; } @@ -1097,104 +590,70 @@ export class TWOPC { const p1inner = innerHash(this.innerState_MS, concatTA(a1, seed)); const p2inner = innerHash(this.innerState_MS, concatTA(a2, seed)); - const nonFixedBits = bytesToBits(concatTA(p1inner_vd, p2inner, p1inner)); - return await this.runCircuit(nonFixedBits, 3); + + return [p1inner_vd, p2inner, p1inner]; } - async phase4(outArray) { + async phase4(outArray, masks3) { const c3_output = outArray[0]; - let o = 0; // offset + let o = 0; // offset const verify_dataMasked = c3_output.slice(o, o+=12); - const verify_data = xor(verify_dataMasked, this.cs[3].masks[8]); - const clientFinished = concatTA(new Uint8Array([0x14, 0x00, 0x00, 0x0c]), verify_data); - + const verify_data = xor(verify_dataMasked, masks3[8]); const encCounterMasked = c3_output.slice(o, o+=16); - const encCounter = xor(encCounterMasked, this.cs[3].masks[7]); - + const encCounter = xor(encCounterMasked, masks3[7]); const gctrMaskedTwice = c3_output.slice(o, o+=16); - const myGctrShare = xor(gctrMaskedTwice, this.cs[3].masks[6]); - + const myGctrShare = xor(gctrMaskedTwice, masks3[6]); const H1MaskedTwice = c3_output.slice(o, o+=16); - - const H1 = xor(H1MaskedTwice, this.cs[3].masks[5]); - this.powersOfH[1] = H1; - const H2 = blockMult(H1, H1); - this.powersOfH[2] = H2; - const H1H2 = blockMult(H1, H2); - - // OT starts with the highest bit - const h1Bits = bytesToBits(H1).reverse(); - const [idxArray1, otKeys1] = this.ot.getIndexesFromPool(h1Bits); - const h2Bits = bytesToBits(H2).reverse(); - const [idxArray2, otKeys2] = this.ot.getIndexesFromPool(h2Bits); - const civMaskedTwice = c3_output.slice(o, o+=4); - this.civMaskedByNotary = xor(civMaskedTwice, this.cs[3].masks[4]); + this.civShare = xor(civMaskedTwice, masks3[4]); const sivMaskedTwice = c3_output.slice(o, o+=4); - this.sivMaskedByNotary = xor(sivMaskedTwice, this.cs[3].masks[3]); - + this.sivShare = xor(sivMaskedTwice, masks3[3]); const cwkMaskedTwice = c3_output.slice(o, o+=16); const swkMaskedTwice = c3_output.slice(o, o+=16); - this.cwkMaskedByNotary = xor(cwkMaskedTwice, this.cs[3].masks[2]); - this.swkMaskedByNotary = xor(swkMaskedTwice, this.cs[3].masks[1]); + this.cwkShare = xor(cwkMaskedTwice, masks3[2]); + this.swkShare = xor(swkMaskedTwice, masks3[1]); + // 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 clientFinished = concatTA(new Uint8Array([0x14, 0x00, 0x00, 0x0c]), verify_data); const encCF = xor(clientFinished, encCounter); - const step3resp = await this.send('c3_step3', concatTA(encCF, idxArray1, idxArray2)); - assert(step3resp.length === 16 + 128*32 + 128*32); - + const otReq = await this.ghash.buildFinRequest(H1share); + const step3resp = await this.send('c3_step3', concatTA(encCF, otReq)); + assert(step3resp.length === 16 + 256*32); o = 0; - const Snotary = step3resp.slice(o, o+=16); - const encEntries1 = step3resp.slice(o, o+=(128*32)); - const encEntries2 = step3resp.slice(o, o+=(128*32)); + const notaryTagShare = step3resp.slice(o, o+=16); + const otResp = step3resp.slice(o, o+=(256*32)); - let H3share = H1H2; - for (let i = 0; i < h1Bits.length; i += 1) { - const bit = h1Bits[i]; - const ct = encEntries2.slice(i * 32, (i+1) * 32); - const maskedEntry = this.ot.decryptWithKey(ct, bit, otKeys1[i]); - H3share = xor(H3share, maskedEntry); - } - for (let i = 0; i < h2Bits.length; i += 1) { - const bit = h2Bits[i]; - const ct = encEntries1.slice(i * 32, (i+1) * 32); - const maskedEntry = this.ot.decryptWithKey(ct, bit, otKeys2[i]); - H3share = xor(H3share, maskedEntry); - } - this.powersOfH[3] = H3share; - const aad = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 22, 3, 3, 0, 16, 0, 0, 0]); - // lenA (before padding) == 13*8 == 104, lenC == 16*8 == 128 + // lenA (before padding) == 13*8 == 104, lenC (before padding) == 16*8 == 128 const lenAlenC = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 104, 0, 0, 0, 0, 0, 0, 0, 128]); - - const s1share = blockMult(aad, H3share); - const s2share = blockMult(encCF, H2); - const s3share = blockMult(lenAlenC, H1); - const Sclient = xor(xor(xor(s1share, s2share), s3share), myGctrShare); - const tagFromPowersOfH = xor(Sclient, Snotary); - + const tagShare = this.ghash.processFinResponse(otResp, [aad, encCF, lenAlenC]); + // notary's gctr share is already included in notaryTagShare + const tagFromPowersOfH = xor(xor(notaryTagShare, tagShare), myGctrShare); return [encCF, tagFromPowersOfH, verify_data]; } // getClientFinishedResumed runs a circuit to obtain data needed to construct the - // Client Finished messages for cases when we need TLS session resumption + // 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 mask1 = getRandom(16); + const mask2 = getRandom(12); const nonFixedBits = bytesToBits(concatTA( - mask2, mask2, this.civMaskedByNotary, this.cwkMaskedByNotary, p1inner)); - + mask2, mask2, this.civShare, this.cwkShare, p1inner)); } // getServerKeyShare returns client's xor share of server_write_key - // and server_write_iv + // and server_write_iv getKeyShares(){ - return [this.cwkMaskedByNotary, this.civMaskedByNotary, - this.swkMaskedByNotary, this.sivMaskedByNotary]; + return [this.cwkShare, this.civShare, + this.swkShare, this.sivShare]; } getEphemeralKey(){ @@ -1203,91 +662,78 @@ export class TWOPC { // eslint-disable-next-line class-methods-use-this // optionally send extra data - async runCircuit(nonFixedBits, cNo, extraData) { + async runCircuit(inputs, cNo, extraData = new Uint8Array(0)) { console.log('in runCircuit', cNo); - if (extraData == undefined){ - extraData = new Uint8Array(); + // inputs is an array of inputs 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 = []; + for (let i=0; i < inputs.length; i++){ + let bits = bytesToBits(inputs[i]); + if (typeof(inputs[i+1]) == 'number'){ + // sometimes we need an amount of bits which is not a multiple of 8 + // in such cases the input we be followed by a number of bits to slice + bits = bits.slice(0, inputs[i+1]); + i += 1; + } + inputBits = [].concat(inputBits, bits); } const c = this.cs[cNo]; - const circuit = c.circuit; // how many times to repeat evaluation (> 1 only for circuits 5&7 ) - const repeatCount = [0,1,1,1,1,this.C5Count,1,this.C6Count][cNo]; - const [idxArray, otKeys] = this.ot.getIndexesFromPool(nonFixedBits); + const repeatCount = [0, 1, 1, 1, 1, this.C5Count, 1, this.C6Count][cNo]; // for circuit 1, there was no previous commit - const prevCommit = cNo > 1 ? this.myCommit[cNo-1] : new Uint8Array(); + const prevCommit = cNo > 1 ? this.myCommit[cNo-1] : new Uint8Array(0); + const otReq = this.otR.createRequest(inputBits); + const blob1 = await this.send(`c${cNo}_step1`, concatTA(prevCommit, otReq, extraData)); - const blob1 = await this.send(`c${cNo}_step1`, concatTA(prevCommit, idxArray, extraData)); - - let offset = 0; + let o = 0; if (cNo > 1){ - const prevCommitSalt = blob1.slice(offset, offset + 32); - offset += 32; + const prevCommitSalt = blob1.slice(o, o += 32); // the hash to which the notary committed must be equal to our commit const saltedCommit = await sha256(concatTA(this.myCommit[cNo-1], prevCommitSalt)); assert (eq(saltedCommit, this.hisSaltedCommit[cNo-1])); } - let notaryInputsCount = circuit.notaryInputSize; - if (cNo === 6){ - notaryInputsCount = 160 + 128 * this.C6Count; - } - const notaryInputsBlob = blob1.slice(offset, offset + notaryInputsCount*16); - offset += notaryInputsCount*16; - const encLabels = blob1.slice(offset, offset+nonFixedBits.length*32); - offset += nonFixedBits.length*32; - const nonFixedIndexes = blob1.slice(offset, offset+c.notaryNonFixedInputSize*2); - offset += c.notaryNonFixedInputSize*2; - assert(blob1.length === offset); - const nonFixedEncLabels = this.g.getNonFixedEncLabels(nonFixedIndexes, cNo); - const clientLabels = this.g.getClientLabels(nonFixedBits, cNo); - const sendPromise = this.send(`c${cNo}_step2`, concatTA(nonFixedEncLabels, clientLabels), true); - const nonFixedLabels = this.e.getNonFixedLabels(encLabels, otKeys, nonFixedBits); - const allNotaryLabels = splitIntoChunks(notaryInputsBlob, 16); - + // 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 + const hisOtReq = blob1.slice(o, o += 1+ c.notaryInputSize/8*repeatCount); + assert(blob1.length == o); + + const allClientLabels = this.otR.parseResponse(inputBits, hisOtResp); + const senderMsg = this.g.getNotaryLabels(cNo); + const otResp = this.otS.processRequest(hisOtReq, senderMsg); + const clientLabels = this.g.getClientLabels(inputBits, cNo); + const sendPromise = this.send(`c${cNo}_step2`, concatTA(otResp, clientLabels), true); + // collect batch of 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); for (let r=0; r < repeatCount; r++){ - let fixedLabels = this.fixedLabels[cNo]; - let notaryLabels = allNotaryLabels; - if (cNo === 5){ - fixedLabels = this.fixedLabels[cNo].slice(r*148, r*148+148); - } - else if (cNo === 6){ - assert(this.fixedLabels[cNo].length === 144*repeatCount); - fixedLabels = this.fixedLabels[cNo].slice(r*144, r*144+144); - const commonNotary = allNotaryLabels.slice(0,160); - const uniqueNotary = allNotaryLabels.slice(160+r*128, 160+r*128+128); - notaryLabels = [].concat(commonNotary, uniqueNotary); - } - const garbledAssignment = concatTA(...notaryLabels, ...nonFixedLabels, ...fixedLabels); - const ttSize = circuit.andGateCount * 64; - const tt = this.blob[`c${cNo}_tt`].slice(r*ttSize, r*ttSize+ttSize); - batch.push([garbledAssignment, tt]); + batch.push([concatTA(nlBatch[r], clBatch[r]), ttBatch[r]]); } // perform evaluation console.time('evaluateBatch'); - const batchOutputLabels = await this.e.evaluateBatch(batch, cNo); + const evalOutputLabelsBatch = await this.e.evaluateBatch(batch, cNo); console.timeEnd('evaluateBatch'); - const output = []; // 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 outputLabelsBlob = batchOutputLabels[r]; - const outputLabels = splitIntoChunks(outputLabelsBlob, 16); - - const outputSize = circuit.outputSize * 32; - const allOutputLabelsBlob = this.blob[`c${cNo}_ol`].slice(r*outputSize, (r+1)*outputSize); - assert(allOutputLabelsBlob.length === circuit.outputSize*32); - const allOutputLabels = splitIntoChunks(allOutputLabelsBlob, 16); - + const evalOL = splitIntoChunks(evalOutputLabelsBatch[r], 16); + const garbOL = splitIntoChunks(garbOutputLabelsBatch[r], 16); const bits = []; - for (let i = 0; i < circuit.outputSize; i++) { - const out = outputLabels[i]; - if (eq(out, allOutputLabels[i*2])) { + for (let i = 0; i < c.outputSize; i++) { + if (eq(evalOL[i], garbOL[i*2])) { bits.push(0); - } else if (eq(out, allOutputLabels[i*2+1])) { + } else if (eq(evalOL[i], garbOL[i*2+1])) { bits.push(1); } else { console.log('evaluator output does not match the garbled outputs'); @@ -1297,24 +743,25 @@ export class TWOPC { this.output[cNo] = out; output.push(out); } + console.log('output is', output); this.myCommit[cNo] = await sha256(concatTA(...output)); const cStep2Out = await sendPromise; - this.hisSaltedCommit[cNo] = cStep2Out.subarray(0,32); + this.hisSaltedCommit[cNo] = cStep2Out.subarray(0, 32); const extraDataOut = cStep2Out.subarray(32); output.push(extraDataOut); return output; } - + // eslint-disable-next-line class-methods-use-this 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].circuit.andGateCount *64; - let outputLabelsSize = this.cs[i].circuit.outputSize *32; + 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; @@ -1353,12 +800,12 @@ export class TWOPC { const clientKey = await crypto.subtle.importKey( 'raw', - Secret.slice(0,16), + Secret.slice(0, 16), 'AES-GCM', true, ['encrypt']); const notaryKey = await crypto.subtle.importKey( 'raw', - Secret.slice(16,32), + Secret.slice(16, 32), 'AES-GCM', true, ['decrypt']); return [clientKey, notaryKey]; diff --git a/core/twopc/webWorkers/gcworker.js b/core/twopc/webWorkers/gcworker.js index cba521c..e9c54d4 100644 --- a/core/twopc/webWorkers/gcworker.js +++ b/core/twopc/webWorkers/gcworker.js @@ -1,26 +1,26 @@ // gcworker.js is a WebWorker which performs garbling and evaluation -// of garbled circuits +// 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 var parentPort_; let circuit = null; let truthTable = null; -let timeEvaluating = 0; -// sha0 is used by randomOracle -const sha0 = new Uint8Array( hex2ba('da5698be17b9b46962335799779fbeca8ce5d491c0d26243bafef9ea1837a9d8')); -// byteArray is used by randomOracle -const byteArray = new Uint8Array(24); +// 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,0,0,0,0]); +// 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]); // randomPool will be filled with data from getRandom let randomPool; // randomPoolOffset will be moved after data was read from randomPool let randomPoolOffset = 0; let garbledAssigment; var crypto_; -var nacl; if (typeof(importScripts) !== 'undefined') { - importScripts('./../../third-party/nacl-fast.js'); crypto_ = self.crypto; self.onmessage = function(event) { processMessage(event.data); @@ -34,7 +34,6 @@ if (typeof(importScripts) !== 'undefined') { 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) - nacl = require('tweetnacl') const { parentPort } = require('worker_threads'); parentPort_ = parentPort const { Crypto } = require("@peculiar/webcrypto"); @@ -56,7 +55,7 @@ function processMessage(obj){ // no need to respond to this message } else if (msg === 'setTruthTable'){ - assert(data.byteLength == circuit.andGateCount*64); + assert(data.byteLength == circuit.andGateCount*48); truthTable = new Uint8Array(data); } else if (msg === 'garble'){ @@ -70,7 +69,7 @@ function processMessage(obj){ const reuseR = (data == undefined) ? undefined : data.reuseR; const [truthTable, inputLabels, outputLabels, R] = garble(circuit, garbledAssigment, reuseLabels, reuseIndexes, reuseR); - assert (truthTable.length === circuit.andGateCount*64); + assert (truthTable.length === circuit.andGateCount*48); 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}; @@ -106,6 +105,8 @@ function postMsg(value, transferList){ function newR(){ const R = getRandom(16); + // set the last bit of R to 1 for point-and-permute + // this guarantees that 2 labels of the same wire will have the opposite last bits R[15] = R[15] | 0x01; return R; } @@ -145,7 +146,8 @@ function garble(circuit, ga, reuseLabels = new Uint8Array(0) , reuseIndexes = [] } } - const truthTable = new Uint8Array(circuit.andGateCount*64); + const truthTable = new Uint8Array(circuit.andGateCount*48); + let andGateIdx = 0; // garble gates for (let i = 0; i < circuit.gatesCount; i++) { @@ -167,41 +169,64 @@ function garble(circuit, ga, reuseLabels = new Uint8Array(0) , reuseIndexes = [] } + const garbleAnd = function (gateBlob, R, ga, tt, andGateIdx, id) { + // get wire numbers const in1 = threeBytesToInt(gateBlob.subarray(1,4)); const in2 = threeBytesToInt(gateBlob.subarray(4,7)); const out = threeBytesToInt(gateBlob.subarray(7,10)); - - const randomLabel = getRandom(16); - - gaSetIndexG(ga, out, 0, randomLabel); - gaSetIndexG(ga, out, 1, xor(randomLabel, R, true)); + // get labels of each wire 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); - const out_0 = gaGetIndexG(ga, out, 0); - const out_1 = gaGetIndexG(ga, out, 1); + + // rows is a truthtable if wire labels in a canonical order, the third + // item shows an index of output label + const rows = [ + [in1_0, in2_0, 0], + [in1_0, in2_1, 0], + [in1_1, in2_0, 0], + [in1_1, in2_1, 1] + ] - const values = [ - encrypt(in1_0, in2_0, id, out_0), - encrypt(in1_0, in2_1, id, out_0), - encrypt(in1_1, in2_0, id, out_0), - encrypt(in1_1, in2_1, id, out_1) - ]; - - const points = [ - 2 * getPoint(in1_0) + getPoint(in2_0), - 2 * getPoint(in1_0) + getPoint(in2_1), - 2 * getPoint(in1_1) + getPoint(in2_0), - 2 * getPoint(in1_1) + getPoint(in2_1) - ]; + // GRR3: garbled row reduction + // We want to reduce a row where both labels' points are set to 1. + // We first need to encrypt those labels with a dummy all-zero output label. The + // result X will be the actual value of the output label that we need to set. + // After we set the output label to X and encrypt again, the result will be 0 (but + // we don't actually need to encrypt it again, we just know that the result will be 0) - tt.set(values[0], andGateIdx*64+16*points[0]); - tt.set(values[1], andGateIdx*64+16*points[1]); - tt.set(values[2], andGateIdx*64+16*points[2]); - tt.set(values[3], andGateIdx*64+16*points[3]); + let outLabels + // idxToReduce is the index of the row that will be reduced + let idxToReduce = -1; + for (let i=0; i < rows.length; i++){ + if (getPoint(rows[i][0]) == 1 && getPoint(rows[i][1]) == 1){ + const outWire = encrypt(rows[i][0], rows[i][1], id, new Uint8Array(16).fill(0)); + if (i==3){ + outLabels = [xor(outWire, R), outWire] + } else { + outLabels = [outWire, xor(outWire, R)] + } + idxToReduce = i; + break; + } + } + gaSetIndexG(ga, out, 0, outLabels[0]); + gaSetIndexG(ga, out, 1, outLabels[1]); + assert(idxToReduce != -1) + + for (let i=0; i < rows.length; i++){ + if (i == idxToReduce){ + // not encrypting this row because we already know that its encryption is 0 + // and the sum of its points is 3 + continue; + } + const value = encrypt(rows[i][0], rows[i][1], id, outLabels[rows[i][2]]); + const point = 2 * getPoint(rows[i][0]) + getPoint(rows[i][1]) + tt.set(value, andGateIdx*48+16*point); + } }; @@ -255,7 +280,6 @@ function evaluate (circuit, ga, tt, inputLabels) { } console.timeEnd('worker_evaluate'); const t1 = performance.now(); - timeEvaluating += (t1 - t0); return ga.slice((circuit.wiresCount-circuit.outputSize)*16, circuit.wiresCount*16); } @@ -268,13 +292,20 @@ const evaluateAnd = function (ga, tt, andGateIdx, gateBlob, id) { const label1 = gaGetIndexE(ga, in1); // ga[in1]; const label2 = gaGetIndexE(ga, in2); // ga[in2]; + let cipher const point = 2 * getPoint(label1) + getPoint(label2); - const offset = andGateIdx*64+16*point; - const cipher = tt.subarray(offset, offset+16); - + if (point == 3){ + // GRR3: all rows with point sum of 3 have been reduced + // their encryption is an all-zero bytestring + cipher = new Uint8Array(16).fill(0); + } else { + const offset = andGateIdx*48+16*point; + cipher = tt.subarray(offset, offset+16); + } gaSetIndexE(ga, out, decrypt(label1, label2, id, cipher)); }; + const evaluateXor = function (ga, gateBlob) { const in1 = threeBytesToInt(gateBlob.subarray(1,4)); const in2 = threeBytesToInt(gateBlob.subarray(4,7)); @@ -315,10 +346,7 @@ function gaSetIndexG(ga, idx, pos, value){ function xor(a, b, reuse) { - if (a.length !== b.length){ - console.log('a.length !== b.length'); - throw('a.length !== b.length'); - } + assert(a.length == b.length, 'a.length !== b.length') let bytes; if (reuse === true){ // in some cases the calling function will have no more use of "a" @@ -341,16 +369,18 @@ function getPoint(arr) { const decrypt = encrypt; -let a2; -let b4; +// Based on the the A4 method from Fig.1 and the D4 method in Fig6 of the BHKR13 paper +// (https://eprint.iacr.org/2013/426.pdf) +// Note that the paper doesn't prescribe a specific method to break the symmetry between A and B, +// so we choose a circular byte shift instead of a circular bitshift as in Fig6. function encrypt(a, b, t, m) { // double a - a2 = a.slice(); + const a2 = a.slice(); const leastbyte = a2[0]; a2.copyWithin(0,1,15); // Logical left shift by 1 byte a2[14] = leastbyte; // Restore old least byte as new greatest (non-pointer) byte // quadruple b - b4 = b.slice(); + const b4 = b.slice(); const leastbytes = [b4[0], b4[1]]; b4.copyWithin(0,2,15); // Logical left shift by 2 byte [b4[13], b4[14]] = leastbytes; // Restore old least two bytes as new greatest bytes @@ -361,41 +391,23 @@ function encrypt(a, b, t, m) { return xor(ro, mXorK, true); } + function randomOracle(m, t) { - return nacl.secretbox( - m, - longToByteArray(t), - sha0, - ).subarray(0,16); + // convert the integer t to a 4-byte big-endian array and append + // it to fixedKey in-place + for (let index = 0; index < 4; index++) { + const byte = t & 0xff; + fixedKey[31-index] = byte; + t = (t - byte) / 256; + } + return Salsa20(fixedKey, m); } -function longToByteArray(long) { - // we want to represent the input as a 24-bytes array - for (let index = 0; index < byteArray.length; index++) { - const byte = long & 0xff; - byteArray[index] = byte; - long = (long - byte) / 256; - } - return byteArray; -} function threeBytesToInt(b){ return b[2] + b[1]*256 + b[0]*65536; } -// convert a hex string into byte array -function hex2ba(str) { - var ba = []; - // pad with a leading 0 if necessary - if (str.length % 2) { - str = '0' + str; - } - for (var i = 0; i < str.length; i += 2) { - ba.push(parseInt('0x' + str.substr(i, 2))); - } - return ba; -} - function getRandom(count) { const rand = randomPool.subarray(randomPoolOffset, randomPoolOffset+count); @@ -438,4 +450,148 @@ function concatTA (...arr){ offset += item.length; } return newArray; -} \ No newline at end of file +} + +// use Salsa20 as a random permutator. Instead of the nonce, we feed the data that needs +// to be permuted. +function Salsa20(key, data){ + const out = new Uint8Array(16); + core_salsa20(out, data, key, sigma) + return out; +} + +// copied from https://github.com/dchest/tweetnacl-js/blob/master/nacl-fast.js +// and modified to output only 16 bytes +function core_salsa20(o, p, k, 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, + 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; + + 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; + // we only need 16 bytes of the output +} diff --git a/core/twopc/webWorkers/otworker.js b/core/twopc/webWorkers/otworker.js deleted file mode 100644 index c6ad08e..0000000 --- a/core/twopc/webWorkers/otworker.js +++ /dev/null @@ -1,159 +0,0 @@ -// otworker.js is a WebWorker where most of the heavy computations for -// Oblivious Transfer happen. Based on Chou-Orlandi "Simplest OT" - -var sodium -var parentPort_; - - -if (typeof(importScripts) === 'undefined'){ - // we are in nodejs - import('module').then((module) => { - // 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 - sodium = require('libsodium-wrappers-sumo'); - parentPort.on('message', msg => { - processMessage(msg); - }) - }) -} else { - importScripts('./../../third-party/sodium.js'); - self.onmessage = function(event) { - processMessage(event.data); - }; -} - - -function processMessage(obj){ - const msg = obj.msg; - const data = obj.data; - if (msg === 'saveDecryptionKeys'){ - const bytes = new Uint8Array(data.bytes); - const A = new Uint8Array(data.A); - const rv = saveDecryptionKeys(bytes, A); - postMsg({'blob': rv.buffer}); - - } - else if (msg === 'precomputePool'){ - const count = data.count; - const A = new Uint8Array(data.A); - const rv = precomputePool(count, A); - postMsg({'blob': rv.buffer}); - } - else if (msg === 'prepareEncryptionKeys'){ - const bytes = new Uint8Array(data.bytes); - const a = new Uint8Array(data.a, data.A); - const A = new Uint8Array(data.A); - const rv = prepareEncryptionKeys(bytes, a, A); - postMsg({'blob': rv.buffer}); - } -} - - -function postMsg(value, transferList){ - if (typeof importScripts !== 'function'){ - parentPort_.postMessage({data:value}, transferList) - } else { - postMessage(value, transferList); - } -} - - -function prepareEncryptionKeys(bytes, a, A){ - const blob = []; - const Bcount = bytes.length/32; - for (let i=0; i < Bcount; i++){ - const B = bytes.slice(i*32, (i+1)*32); - const k0 = sodium.crypto_generichash(16, sodium.crypto_scalarmult_ristretto255(a, B)); - const sub = sodium.crypto_core_ristretto255_sub(B, A); - const k1 = sodium.crypto_generichash(16, sodium.crypto_scalarmult_ristretto255(a, sub)); - blob.push(k0); - blob.push(k1); - } - return concatTA(...blob); -} - -function saveDecryptionKeys(bytes, A){ - const bits = bytesToBits(bytes); - const blob = []; - for (const bit of bits){ - if (bit === 0){ - const b0 = sodium.crypto_core_ristretto255_scalar_random(); - const B0 = sodium.crypto_scalarmult_ristretto255_base(b0); - const k0 = sodium.crypto_generichash(16, sodium.crypto_scalarmult_ristretto255(b0, A)); - blob.push(k0); - blob.push(B0); - } - else { - const b1 = sodium.crypto_core_ristretto255_scalar_random(); - const gb1 = sodium.crypto_scalarmult_ristretto255_base(b1); - const B1 = sodium.crypto_core_ristretto255_add(A, gb1); - const k1 = sodium.crypto_generichash(16, sodium.crypto_scalarmult_ristretto255(b1, A)); - blob.push(k1); - blob.push(B1); - } - } - return concatTA(...blob); -} - -function precomputePool(count, A){ - const blob = []; - for (let i = 0; i < count ; i++){ - const b0 = sodium.crypto_core_ristretto255_scalar_random(); - const B0 = sodium.crypto_scalarmult_ristretto255_base(b0); - const k0 = sodium.crypto_generichash(16, sodium.crypto_scalarmult_ristretto255(b0, A)); - blob.push(k0); - blob.push(B0); - } - for (let i = 0; i < count; i++){ - const b1 = sodium.crypto_core_ristretto255_scalar_random(); - const gb1 = sodium.crypto_scalarmult_ristretto255_base(b1); - const B1 = sodium.crypto_core_ristretto255_add(A, gb1); - const k1 = sodium.crypto_generichash(16, sodium.crypto_scalarmult_ristretto255(b1, A)); - blob.push(k1); - blob.push(B1); - } - return concatTA(...blob); -} - -// convert Uint8Array into an array of 0/1 where least bit has index 0 -function bytesToBits (ba){ - assert(ba instanceof Uint8Array); - const bitArr = Array(ba.length*8); - let idx = 0; - for (let i=ba.length-1; i >= 0; i--){ - for (let j=0; j < 8; j++){ - bitArr[idx] = (ba[i] >> j) & 0x01; - idx++; - } - } - return bitArr; -} - -function assert(condition, message) { - if (!condition) { - console.trace(); - throw message || 'Assertion failed'; - } -} - -// concatenate an array of typed arrays (specifically Uint8Array) -function concatTA (...arr){ - let newLen = 0; - for (const item of arr){ - assert(item instanceof Uint8Array); - newLen += item.length; - } - const newArray = new Uint8Array(newLen); - let offset = 0; - for (const item of arr){ - newArray.set(item, offset); - offset += item.length; - } - return newArray; -} \ No newline at end of file diff --git a/core/twopc/webWorkers/serializeCircuits.js b/core/twopc/webWorkers/serializeCircuits.js index 166e8a0..9f07547 100644 --- a/core/twopc/webWorkers/serializeCircuits.js +++ b/core/twopc/webWorkers/serializeCircuits.js @@ -7,14 +7,14 @@ if (typeof(importScripts) === 'undefined'){ // 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) + // 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.on('message', msg => { const text = msg.text; const [obj, blob] = serializeCircuit(text); parentPort.postMessage({data: {'obj': obj, blob: blob.buffer}}); - }) + }); }); } else { @@ -37,7 +37,7 @@ function serializeCircuit(text){ obj['notaryInputSize'] = Number(rows[1].split(' ')[1]); obj['clientInputSize'] = Number(rows[1].split(' ')[2]); obj['outputSize'] = Number(rows[2].split(' ')[1]); - + // each gate is serialized as // 1 byte: gate type XOR==0 AND==1 INV==2 // 3 bytes: 1st input wire number @@ -75,7 +75,7 @@ function serializeCircuit(text){ const out = intToThreeBytes(tokens[tokens.length-2]); blob.set(in1, blobOffset); blobOffset+=3; - blob.set([0,0,0], blobOffset); + blob.set([0, 0, 0], blobOffset); blobOffset+=3; blob.set(out, blobOffset); blobOffset+=3; diff --git a/core/utils.js b/core/utils.js index 0d6c08f..659db2c 100644 --- a/core/utils.js +++ b/core/utils.js @@ -1,14 +1,18 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable no-undef */ +/* global chrome, CBOR, COSE, Buffer, fastsha256 */ + import {verifyChain} from './verifychain.js'; import * as asn1js from './third-party/pkijs/asn1.js'; import Certificate from './third-party/pkijs/Certificate.js'; // returns an array of obj's keys converted to numbers sorted ascendingly export function sortKeys(obj){ - const numArray = Object.keys(obj).map(function(x){return Number(x);}); + const numArray = Object.keys(obj).map(function(x){return Number(x);}); return numArray.sort(function(a, b){return a-b;}); } -// convert a byte array into a hex string +// convert a Uint8Array into a hex string export function ba2hex(ba) { assert(ba instanceof Uint8Array); let hexstring = ''; @@ -22,7 +26,7 @@ export function ba2hex(ba) { return hexstring; } -// convert a hex string into byte array +// convert a hex string into a Uint8Array export function hex2ba(str) { const ba = []; // pad with a leading 0 if necessary @@ -78,7 +82,7 @@ export function ba2int(ba){ export function int2ba(int, size){ assert(typeof(int) == 'bigint' || typeof(int) == 'number', 'Only can convert Number or BigInt'); let hexstr = int.toString(16); - if (hexstr.length % 2) { + if (hexstr.length % 2) { hexstr = '0' + hexstr; } const ba = []; for (let i=0; i < hexstr.length/2; i++){ @@ -116,9 +120,9 @@ export function ba2str(ba) { } // xor 2 byte arrays of equal length -export function xor (a,b){ +export function xor (a, b){ assert(a instanceof Uint8Array && b instanceof Uint8Array); - assert(a.length === b.length); + assert(a.length == b.length); var c = new Uint8Array(a.length); for (var i=0; i< a.length; i++){ c[i] = a[i]^b[i]; @@ -173,12 +177,12 @@ export async function verifyAttestationDoc(doc){ const doc_obj = CBOR.decode(payload.buffer); const leafCertDer = doc_obj.certificate.slice(); const cert_asn1 = asn1js.fromBER(leafCertDer.buffer); - const leafCert = new Certificate({ schema: cert_asn1.result }); + const leafCert = new Certificate({ schema: cert_asn1.result }); const x = new Uint8Array(leafCert.subjectPublicKeyInfo.parsedKey.x); const y = new Uint8Array(leafCert.subjectPublicKeyInfo.parsedKey.y); // verify the signature COSE.verify(x, y, doc.buffer); - + // verify certificate chain // this is a sha256 hash of root cert from https://aws-nitro-enclaves.amazonaws.com/AWS_NitroEnclaves_Root-G1.zip @@ -237,7 +241,7 @@ export function b64decode(str) { return new Uint8Array(dec); } -// conform to base64url format replace +/= with -_ +// conform to base64url format replace +/= with -_ export function b64urlencode (ba){ assert(ba instanceof Uint8Array); let str = b64encode(ba); @@ -250,7 +254,7 @@ export function buildChunkMetadata(plaintextArr){ for (const pt of plaintextArr){ http_data += ba2str(pt); } - + const chunkMetadata = []; // '''Dechunk only if http_data is chunked otherwise return http_data unmodified''' const http_header = http_data.slice(0, http_data.search('\r\n\r\n') + '\r\n\r\n'.length); @@ -307,7 +311,7 @@ export function dechunk_http(decrRecords) { var chunkMetadata = buildChunkMetadata(decrRecords); var dechunkedPlaintexts = []; - var totalOffset = -1; // an offset at which the last byte is found of plaintexts processed so far + var totalOffset = -1; // an offset at which the last byte is found of plaintexts processed so far var shrinkNextRecordBy = 0; // used when chunking metadata spans 2 TLS records var shrinkThisRecordBy = 0; for (var i=0; i < decrRecords.length; i++){ @@ -320,7 +324,7 @@ export function dechunk_http(decrRecords) { var s = true; } } - var metadataInThisRecord = []; + var metadataInThisRecord = []; var tmpArray = [...chunkMetadata]; // every even index contains the start of metadata for (var j=0; j < tmpArray.length; j+=2){ @@ -389,15 +393,15 @@ export function gunzip_http(dechunkedRecords) { return dechunkedRecords; // #nothing to gunzip } throw ('gzip enabled'); - var http_body = http_data.slice(http_header.length); - var ungzipped = http_header; - if (!http_body) { - // HTTP 304 Not Modified has no body - return [ungzipped]; - } - var inflated = pako.inflate(http_body); - ungzipped += ba2str(inflated); - return [ungzipped]; + // var http_body = http_data.slice(http_header.length); + // var ungzipped = http_header; + // if (!http_body) { + // // HTTP 304 Not Modified has no body + // return [ungzipped]; + // } + // var inflated = pako.inflate(http_body); + // ungzipped += ba2str(inflated); + // return [ungzipped]; } export function getTime() { @@ -406,7 +410,7 @@ export function getTime() { ('00' + (today.getMonth() + 1)).slice(-2) + '-' + ('00' + today.getDate()).slice(-2) + '-' + ('00' + today.getHours()).slice(-2) + '-' + - ('00' + today.getMinutes()).slice(-2) + '-' + + ('00' + today.getMinutes()).slice(-2) + '-' + ('00' + today.getSeconds()).slice(-2); return time; } @@ -424,11 +428,11 @@ export function pem2ba(pem) { encoded += line.trim(); } } - return b64decode(encoded); + return b64decode(encoded); } -// compare bytes in 2 arrays. a or b can be either Array or Uint8Array +// compares two Uint8Arrays or Arrays export function eq(a, b) { assert(Array.isArray(a) || a instanceof Uint8Array); assert(Array.isArray(b) || b instanceof Uint8Array); @@ -436,7 +440,7 @@ export function eq(a, b) { a.every((val, index) => val === b[index]); } - + // expand the range [min:max) into array of ints 1,2,3,4... up to but not including max export function expandRange(min, max){ const arr = []; @@ -459,31 +463,6 @@ export function splitIntoChunks(ba, chunkSize) { return newArray; } -// perform GCM Galois Field block multiplication -// x,y are byte arrays -export function blockMult(x_,y_){ - // casting to BigInt just in case if ba2int returns a Number - let x = BigInt(ba2int(x_)); - const y = BigInt(ba2int(y_)); - let res = 0n; - for (let i=127n; i >= 0n; i--){ - res ^= x * ((y >> i) & 1n); - x = (x >> 1n) ^ ((x & 1n) * BigInt(0xE1000000000000000000000000000000)); - } - return int2ba(res, 16); -} - -// x is Uint8Array -export function getXTable(x_){ - let x = ba2int(x_); - const table = []; - for (let i=0; i < 128; i++){ - table[i] = int2ba(x, 16); - x = (x >> 1n) ^ ((x & 1n) * BigInt(0xE1000000000000000000000000000000)); - } - return table; -} - // convert Uint8Array into an array of 0/1 where least bit has index 0 export function bytesToBits (ba){ @@ -517,29 +496,29 @@ export function bitsToBytes(arr){ // convert OpenSSL's signature format (asn1 DER) into WebCrypto's IEEE P1363 format export function sigDER2p1363(sigDER){ var o = 0; - assert(eq(sigDER.slice(o,o+=1), [0x30])); - var total_len = ba2int(sigDER.slice(o,o+=1)); + assert(eq(sigDER.slice(o, o+=1), [0x30])); + var total_len = ba2int(sigDER.slice(o, o+=1)); assert(sigDER.length == total_len+2); - assert(eq(sigDER.slice(o,o+=1), [0x02])); - var r_len = ba2int(sigDER.slice(o,o+=1)); + assert(eq(sigDER.slice(o, o+=1), [0x02])); + var r_len = ba2int(sigDER.slice(o, o+=1)); assert(r_len === 32 || r_len === 33); - var r = sigDER.slice(o,o+=r_len); - assert(eq(sigDER.slice(o,o+=1), [0x02])); - var s_len = ba2int(sigDER.slice(o,o+=1)); + var r = sigDER.slice(o, o+=r_len); + assert(eq(sigDER.slice(o, o+=1), [0x02])); + var s_len = ba2int(sigDER.slice(o, o+=1)); assert(s_len >= 31 && s_len <= 33); - var s = sigDER.slice(o,o+=s_len); + var s = sigDER.slice(o, o+=s_len); if (s.length === 31){ s = concatTA(new Uint8Array([0x00]), s); } if (r_len === 33){ - assert(eq(r.slice(0,1), [0x00])); + assert(eq(r.slice(0, 1), [0x00])); r = r.slice(1); } if (s_len == 33){ - assert(eq(s.slice(0,1), [0x00])); + assert(eq(s.slice(0, 1), [0x00])); s = s.slice(1); } - var sig_p1363 = concatTA(r,s); + var sig_p1363 = concatTA(r, s); return sig_p1363; } @@ -555,7 +534,7 @@ export async function import_resource(filename) { // take PEM EC pubkey and output a "raw" pubkey with all asn1 data stripped export function pubkeyPEM2raw(pkPEM){ // prepended asn1 data for ECpubkey prime256v1 - const preasn1 = [0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06,0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00]; + const preasn1 = [0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00]; const pk = pem2ba(pkPEM); assert(eq(pk.slice(0, preasn1.length), preasn1)); return pk.slice(preasn1.length); @@ -610,29 +589,21 @@ export function encrypt_generic(plaintext, key, nonce) { return xor(tmp, ro); } -const byteArray = new Uint8Array(24); -const sha0 = hex2ba('da5698be17b9b46962335799779fbeca8ce5d491c0d26243bafef9ea1837a9d8'); -// class PRF is initialized once and then it is a read-only +function randomOracle(m, t) { + // 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, 0, 0, 0, 0]); -export function longToByteArray(long) { - // we want to represent the input as a 24-bytes array - for (let index = 0; index < byteArray.length; index++) { - const byte = long & 0xff; - byteArray[index] = byte; - long = (long - byte) / 256; + // convert the integer t to a 4-byte big-endian array and append + // it to fixedKey in-place + for (let index = 0; index < 4; index++) { + const byte = t & 0xff; + fixedKey[31-index] = byte; + t = (t - byte) / 256; } - return byteArray; -} - - -export function randomOracle(m, t) { - const nonce = longToByteArray(t); - return nacl.secretbox( - m, - nonce, // Nonce 24 bytes because this sodium uses 192 bit blocks. - sha0, - ).slice(0,16); + return Salsa20(fixedKey, m); } export const decrypt_generic = encrypt_generic; @@ -671,17 +642,17 @@ export function concatTA(...arr){ async function gcmEncrypt(key, plaintext, IV, aad){ const cryptoKey = await crypto.subtle.importKey( - 'raw', - key.buffer, - 'AES-GCM', - true, + 'raw', + key.buffer, + 'AES-GCM', + true, ['encrypt', 'decrypt']); const ciphertext = await crypto.subtle.encrypt({ - name: 'AES-GCM', + name: 'AES-GCM', iv: IV.buffer, - additionalData: aad.buffer}, - cryptoKey, + additionalData: aad.buffer}, + cryptoKey, plaintext.buffer, ); @@ -709,7 +680,7 @@ async function gcmEncrypt(key, plaintext, IV, aad){ const H2a = times_auth_key(H1a, H1a); const H2b = times_auth_key(H1b, H1b); - const X1 = ba2int(aad); + const X1 = ba2int(aad); const X2 = ba2int(ct); const X3 = ba2int(lenAlenC); @@ -724,25 +695,212 @@ async function gcmEncrypt(key, plaintext, IV, aad){ } // WebCrypto doesn't provide AES-ECB encryption. We achieve it by using -// the CTR mode and setting CTR's counter to what we want to AES-encrypt and setting CTR's -// data to encrypt to zero, because in CTR ciphertext = AES(counter) XOR plaintext -// -export async function AESECBencrypt(key, data){ - const cryptoKey = await crypto.subtle.importKey( - 'raw', - key.buffer, - 'AES-CTR', - true, - ['encrypt', 'decrypt']); +// the CBC mode with a zero IV. This workaraound only works for encrypting +// 16 bytes at a time. - const zeroes = int2ba(0, 16); +export async function AESECBencrypt(key, data){ + assert(data.length == 16, 'can only AES-ECB encrypt 16 bytes at a time'); + const cryptoKey = await crypto.subtle.importKey( + 'raw', key.buffer, 'AES-CBC', false, ['encrypt']); + + // Even if data is a multiple of 16, WebCrypto adds 16 bytes of + // padding. We drop it. return new Uint8Array (await crypto.subtle.encrypt({ - name: 'AES-CTR', - counter: data.buffer, length: 16}, + name: 'AES-CBC', + iv: new Uint8Array(16).fill(0).buffer}, cryptoKey, - zeroes.buffer)); + data.buffer)).slice(0, 16); } + +// AEC-CTR encrypt data, setting initial counter to 0 +export async function AESCTRencrypt(key, data){ + const cryptoKey = await crypto.subtle.importKey( + 'raw', key.buffer, 'AES-CTR', false, ['encrypt']); + + return new Uint8Array (await crypto.subtle.encrypt({ + name: 'AES-CTR', + counter: new Uint8Array(16).fill(0).buffer, + length:64}, + cryptoKey, + data.buffer)); +} + + +// AEC-CTR decrypt ciphertext, setting initial counter to 0 +export async function AESCTRdecrypt(key, ciphertext){ + const cryptoKey = await crypto.subtle.importKey( + 'raw', key.buffer, 'AES-CTR', false, ['decrypt']); + + return new Uint8Array (await crypto.subtle.decrypt({ + name: 'AES-CTR', + counter: new Uint8Array(16).fill(0).buffer, + length:64}, + cryptoKey, + ciphertext.buffer)); +} + + +// use Salsa20 as a random permutator. Instead of the nonce, we feed the data that needs +// to be permuted. +export function Salsa20(key, data){ + // 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]); + const out = new Uint8Array(16); + core_salsa20(out, data, key, sigma); + return out; +} + +// copied from https://github.com/dchest/tweetnacl-js/blob/master/nacl-fast.js +// and modified to output only 16 bytes +function core_salsa20(o, p, k, 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, + 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; + + 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; + // we only need 16 bytes of the output +} + + +// ephemeral key usage time must be within the time of ephemeral key validity +export function checkExpiration(validFrom, validUntil, time){ + time = time || Math.floor(new Date().getTime() / 1000); + if (ba2int(validFrom) > time || time > ba2int(validUntil)){ + return false; + } + return true; +} + + + + +// -----------OBSOLETE functions below this line function bestPathNew(num){ const mainPowers = []; const auxPowers = []; // aux powers @@ -750,7 +908,7 @@ function bestPathNew(num){ for (let i=0; i<10; i++){ mainPowers.push(2**i); } - mainPowers.sort(function(a, b){return a-b;}); + mainPowers.sort(function(a, b){return a-b;}); const primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503]; let paths = []; paths.push([mainPowers, auxPowers, auxIncrements]); @@ -759,7 +917,7 @@ function bestPathNew(num){ const emptyIndexes = []; for (let pathIdx=0; pathIdx < paths.length; pathIdx++ ){ const mainPowers = paths[pathIdx][0]; - const auxPowers = paths[pathIdx][1]; + const auxPowers = paths[pathIdx][1]; if (! isSumFound(mainPowers, auxPowers, i)){ emptyIndexes.push(pathIdx); } @@ -769,7 +927,7 @@ function bestPathNew(num){ // TODO: do we want to discard, or maybe to add primes? for (let i=0; i < emptyIndexes.length; i++){ // console.log('discarding path with index ', i) - paths.splice(i,1); + paths.splice(i, 1); } } else { // sum was not found in any path @@ -778,7 +936,7 @@ function bestPathNew(num){ for (let pathIdx=0; pathIdx < paths.length; pathIdx++ ){ const mainPowers = paths[pathIdx][0]; const auxPowers = paths[pathIdx][1]; - const auxIncrements = paths[pathIdx][2]; + const auxIncrements = paths[pathIdx][2]; for (let p=0; p < primes.length; p++){ const prime = primes[p]; @@ -800,7 +958,7 @@ function bestPathNew(num){ } // add new numbers to auxPowers - // this can be any number - prime or non-prime that is already + // this can be any number - prime or non-prime that is already // available in mainPowers for (let p=0; p < mainPowers.length; p++){ const num = mainPowers[p]; @@ -867,11 +1025,11 @@ function bestPathNewer(num){ // if (paths.length > sampleSize){ // for (let i=0; i < paths.length-sampleSize; i++){ // const randIdx = Math.ceil(Math.random()*paths.length) - // paths.splice(randIdx, 1) + // paths.splice(randIdx, 1) // } // } - // take each path and see if sum is found. + // take each path and see if sum is found. // if found at least in one path, advance to the next number let foundAtLeastOnce = false; for (let pathIdx=0; pathIdx < paths.length; pathIdx++ ){ @@ -900,7 +1058,7 @@ function bestPathNewer(num){ // add the next num to main powers and check if sum is found for (let pathIdx=0; pathIdx < paths.length; pathIdx++ ){ const mainPowers = paths[pathIdx][0]; - const auxIncrements = paths[pathIdx][2]; + const auxIncrements = paths[pathIdx][2]; for (let p=0; p <= i; p++){ if (p%2 === 0 || mainPowers.includes(p)){ @@ -938,9 +1096,9 @@ function bestPathNewer(num){ } paths = newPaths; } - + const numFreq = {}; - let minLen = paths[0][0].length; + let minLen = paths[0][0].length; for (let i=0; i < paths.length; i++){ if (paths[i][3] !== true){ continue; @@ -966,7 +1124,7 @@ function bestPathNewer(num){ } } console.log(nums.sort(function(a, b){return a-b;})); - } + } console.log(numFreq); } @@ -1108,7 +1266,7 @@ function bestPath(num){ mainPowers.push(2**i); auxPowers.push(2**i); } - mainPowers.sort(function(a, b){return a-b;}); + mainPowers.sort(function(a, b){return a-b;}); auxPowers.sort(function(a, b){return a-b;}); const primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503]; let paths = []; @@ -1118,7 +1276,7 @@ function bestPath(num){ const emptyIndexes = []; for (let pathIdx=0; pathIdx < paths.length; pathIdx++ ){ const mainPowers = paths[pathIdx][0]; - const auxPowers = paths[pathIdx][1]; + const auxPowers = paths[pathIdx][1]; if (! isSumFound(mainPowers, auxPowers, i)){ emptyIndexes.push(pathIdx); } @@ -1128,7 +1286,7 @@ function bestPath(num){ // TODO: do we want to discard, or maybe to add primes? for (let i=0; i < emptyIndexes.length; i++){ // console.log('discarding path with index ', i) - paths.splice(i,1); + paths.splice(i, 1); } } else { // sum was not found in any path @@ -1136,8 +1294,8 @@ function bestPath(num){ // add the next prime to main powers and check if sum is found for (let pathIdx=0; pathIdx < paths.length; pathIdx++ ){ const mainPowers = paths[pathIdx][0]; - const auxPowers = paths[pathIdx][1]; - + const auxPowers = paths[pathIdx][1]; + for (let p=0; p < primes.length; p++){ const prime = primes[p]; if (mainPowers.includes(prime)){ @@ -1226,7 +1384,7 @@ function isSumFound(a, b, sum){ if (b[j] + a[i] == sum){ // sums.push([i, j]) return true; - } + } } } return false; @@ -1242,14 +1400,6 @@ function allPrimeMultiples(num){ return arr; } -// ephemeral key usage time must be within the time of ephemeral key validity -export function checkExpiration(validFrom, validUntil, time){ - time = time || Math.floor(new Date().getTime() / 1000); - if (ba2int(validFrom) > time || time > ba2int(validUntil)){ - return false; - } - return true; -} // computes AES GCM authentication tag // all 4 inputs arrays of bytes @@ -1307,31 +1457,6 @@ function getAuthTag(aad, ct, encZero, encIV, precompute){ } return res; } -} +} -if (typeof module !== 'undefined'){ // we are in node.js environment - module.exports={ - assert, - ba2ab, - ba2bigint, - ba2str, - bi2ba, - ab2ba, - ba2int, - buildChunkMetadata, - b64encode, - b64decode, - b64urlencode, - dechunk_http, - gunzip_http, - eq, - getTime, - pem2ba, - pubkeyPEM2raw, - sha256, - sigDER2p1363, - str2ba, - xor - }; -} \ No newline at end of file diff --git a/core/verifychain.js b/core/verifychain.js index f674c85..a06a148 100644 --- a/core/verifychain.js +++ b/core/verifychain.js @@ -1,12 +1,11 @@ -import {pem2ba, eq, import_resource} from './utils.js'; +import {pem2ba, eq} from './utils.js'; import * as asn1js from './third-party/pkijs/asn1.js'; import Certificate from './third-party/pkijs/Certificate.js'; -import CertificateChainValidationEngine from './third-party/pkijs/CertificateChainValidationEngine.js'; - +import CertificateChainValidationEngine from + './third-party/pkijs/CertificateChainValidationEngine.js'; var trustedCertificates = []; - // extract PEMs from Mozilla's CA store and convert into asn1js's Certificate object export async function parse_certs(text){ // wait for pkijs module to load @@ -22,15 +21,10 @@ export async function parse_certs(text){ const lines = text.split('"\n"').slice(1); // discard the first line - headers for (const line of lines){ const fields = line.split('","'); - const pem = fields[32].slice(1,-1); + const pem = fields[32].slice(1, -1); const asn1cert = asn1js.fromBER(pem2ba(pem).buffer); trustedCertificates.push(new Certificate({ schema: asn1cert.result })); - } -} - - -function getPubkey(c){ - return new Uint8Array(c.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHex); + } } @@ -57,7 +51,7 @@ export function checkCertSubjName(cert, serverName){ } for (let nameInCert of allNames){ - if (wildTest(nameInCert, serverName) == true) + if (wildTest(nameInCert, serverName) == true) return true; } throw 'Server name is not the same as the certificate\'s subject name(s)'; @@ -89,10 +83,10 @@ export function getAltNames(cert) { -// verifyChain verifies a certificate chain "chain_der" against the time "date". If "date" is not +// verifyChain verifies a certificate chain "chain_der" against the time "date". If "date" is not // given, verifies againt the current time. // Returns true on success or throws if verification failed. -// Sometimes servers do not put intermediate certs into the chain. In such case we +// Sometimes servers do not put intermediate certs into the chain. In such case we // fetch the missing cert from a URL embedded in the leaf cert. We return the fetched cert. export async function verifyChain(chain_der, date, trustedCerts) { if (trustedCerts == undefined){ @@ -109,17 +103,17 @@ export async function verifyChain(chain_der, date, trustedCerts) { const cert = new Certificate({ schema: cert_asn1.result }); chain.push(cert); } - + async function do_verify(chain, date, trustedCerts){ - // CertificateChainValidationEngine will fail the verification if the root cert is - // included in the chain. To prevent this, we remove the root CA from the chain. + // CertificateChainValidationEngine will fail the verification if the root cert is + // included in the chain. To prevent this, we remove the root CA from the chain. // Check by pubkey if the last cert is a root CA known to us. var pubkeyToFind = new Uint8Array(chain.slice(-1)[0].subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHex); for (let cert of trustedCerts){ if (eq(new Uint8Array(cert.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHex), pubkeyToFind)){ - chain = chain.slice(0,-1); + chain = chain.slice(0, -1); break; } } @@ -127,8 +121,8 @@ export async function verifyChain(chain_der, date, trustedCerts) { // pkijs requires that the leaf cert is last in the array // if not, the verification will still succeed, but the returned certificatePath // will be incomplete - var leafCert = chain.splice(0,1)[0]; - chain.push(leafCert); + var leafCert = chain.splice(0, 1)[0]; + chain.push(leafCert); const ccve = new CertificateChainValidationEngine({ trustedCerts: trustedCerts, certs: chain, @@ -163,15 +157,4 @@ export async function verifyChain(chain_der, date, trustedCerts) { throw ('Could not notarize because the website presented an untrusted certificate'); } return rv; -} - - -if (typeof module !== 'undefined'){ // we are in node.js environment - module.exports={ - checkCertSubjName, - getCommonName, - getModulus, - parse_certs, - verifyChain - }; } \ No newline at end of file diff --git a/ui/FileChooser.js b/ui/FileChooser.js index a219e68..df0b23e 100644 --- a/ui/FileChooser.js +++ b/ui/FileChooser.js @@ -1,6 +1,7 @@ -// FileChooser create a "choose file" button and sends the chosen file -// to the extension +/* global chrome*/ +// class FileChooser create a "choose file" button and sends the chosen file +// to the extension export class FileChooser{ // show is called by extension's Main.openFileChooser() show(){ diff --git a/ui/Manager.js b/ui/Manager.js index 57a93ee..1ec6408 100644 --- a/ui/Manager.js +++ b/ui/Manager.js @@ -1,3 +1,5 @@ +/* global chrome, swal*/ + import {str2ba} from './utils.js'; document.addEventListener('load', onload); @@ -21,7 +23,7 @@ class Manager{ that.processData(data.payload); } else if (data.command == 'export'){ - // .payload contains {pgsg: json, name: session_name} + // .payload contains {pgsg: json, name: session_name} const exportedBlobUrl = URL.createObjectURL(new Blob([str2ba(data.payload.pgsg)]), { type: 'application/octet-stream' }); @@ -38,7 +40,7 @@ class Manager{ 'message': 'refresh' }); } - + processData(rows) { const tb = document.getElementsByTagName('tbody')[0]; const initial_row_length = tb.rows.length; @@ -49,7 +51,7 @@ class Manager{ rows.sort(function(a, b) { return Date.parse(a.creationTime) < Date.parse(b.creationTime) ? 1 : -1; }); - + for (const r of rows) { this.addRow({ 'sessionName': r.sessionName, @@ -61,14 +63,14 @@ class Manager{ }); } } - - + + addRow(args) { const that = this; const session = args.creationTime; const tb = document.getElementById('tableBody'); const row = tb.insertRow(tb.rows.length); - + const td_session = document.createElement('td'); if (args.isImported){ const importedIcon = document.createElement('img'); @@ -91,9 +93,9 @@ class Manager{ td_session.appendChild(editedIcon); } td_session.appendChild(document.createTextNode(args.sessionName)); - + const iconDiv = document.createElement('div'); - + const imgExp = document.createElement('img'); imgExp.classList.add('icon'); imgExp.src = '../img/export.svg'; @@ -120,7 +122,7 @@ class Manager{ }; imgExp.value = 'Export'; iconDiv.appendChild(imgExp); - + const imgRen = document.createElement('img'); imgRen.classList.add('icon'); imgRen.src = '../img/rename.svg'; @@ -132,7 +134,7 @@ class Manager{ that.doRename(event.target, session); }; iconDiv.appendChild(imgRen); - + const imgDel = document.createElement('img'); imgDel.classList.add('icon'); imgDel.src = '../img/delete.svg'; @@ -158,22 +160,22 @@ class Manager{ }; imgDel.value = 'Delete'; iconDiv.appendChild(imgDel); - + iconDiv.style.position = 'absolute'; iconDiv.style.top = 2; iconDiv.style.right = 4; - + td_session.style.position = 'relative'; td_session.appendChild(iconDiv); row.appendChild(td_session); - + const td_time = document.createElement('td'); td_time.style.textAlign = 'center'; td_time.textContent = args.creationTime; row.appendChild(td_time); - + const buttonDiv = document.createElement('div'); - + const input1 = document.createElement('input'); input1.type = 'button'; input1.className = 'btn'; @@ -188,7 +190,7 @@ class Manager{ }; input1.value = 'HTML'; buttonDiv.appendChild(input1); - + const input2 = document.createElement('input'); input2.type = 'button'; input2.className = 'btn'; @@ -203,7 +205,7 @@ class Manager{ }; input2.value = 'Details'; buttonDiv.appendChild(input2); - + const input3 = document.createElement('input'); input3.type = 'button'; input3.className = 'btn'; @@ -218,7 +220,7 @@ class Manager{ }; input3.value = 'Edit'; // Edit button will be used in fututre versions - input3.style.visibility = "hidden"; + input3.style.visibility = 'hidden'; if (args.isImported || args.isEdited || (args.version < 5)){ input3.style.opacity = '0.3'; input3.onclick = null; @@ -228,7 +230,7 @@ class Manager{ } } buttonDiv.appendChild(input3); - + const td3 = document.createElement('td'); td3.style.textAlign = 'center'; td3.appendChild(buttonDiv); diff --git a/ui/NotificationBar.js b/ui/NotificationBar.js index 4b5b374..b4b4b9f 100644 --- a/ui/NotificationBar.js +++ b/ui/NotificationBar.js @@ -1,9 +1,11 @@ +/* global chrome*/ + export class NotificationBar{ constructor(){} show(sessionId, serverName, hideButton) { hideButton = hideButton || false; - + const table = document.createElement('table'); table.style.position = 'fixed'; table.style.top = '0px'; diff --git a/ui/Popup.js b/ui/Popup.js index 2aae2db..0451633 100644 --- a/ui/Popup.js +++ b/ui/Popup.js @@ -1,4 +1,4 @@ -// import {ProgressBar} from './progressbar.min.js'; +/* global chrome, browser*/ class Popup{ constructor(){ @@ -14,7 +14,7 @@ class Popup{ // to pass it to the click event listener so that it could run synchronously. // document.getElementById("notarize").addEventListener("mouseover", // function() { - // chrome.tabs.query({active: true}, async function(t) { + // chrome.tabs.query({active: true}, async function(t) { // currentUrl = t[0].url // hasPermission = await browser.permissions.contains({origins: [currentUrl]}) // }) @@ -56,7 +56,7 @@ class Popup{ }); window.close(); }); - + chrome.runtime.onMessage.addListener(function(data) { that.processMessages(data); }); @@ -70,7 +70,7 @@ class Popup{ // notarizeClicked is triggered when Notarize of Notariza after click was pressed notarizeClicked(isAfterClick){ isAfterClick = isAfterClick || false; - const msg = isAfterClick ? 'notarizeAfter' : 'notarize'; + const msg = isAfterClick ? 'notarizeAfter' : 'notarize'; if (this.is_firefox && ! this.hasPermission && this.currentUrl.startsWith('https://')){ // in Firefox we give a temporary permission just for the current tab's URL // also no async/await/callback here, otherwise Firefox will complain @@ -142,13 +142,13 @@ class Popup{ console.log('popup received unexpected message ' + data.message); } } - + // showInProgressDiv show the
with progress info and listens for // progress updates showInProgressDiv(isFirstTimeSetup){ document.getElementById('menu').setAttribute('hidden', ''); document.getElementById('in_progress').removeAttribute('hidden'); - + const progressBars = {}; const types = ['download', 'upload', 'garbling', 'last_stage']; if (isFirstTimeSetup){ @@ -186,12 +186,12 @@ class Popup{ moveBar(bar, goalWidth) { - const curWidth = Number(bar.style.width.slice(0,-1)); + const curWidth = Number(bar.style.width.slice(0, -1)); if (curWidth === goalWidth){ return; // no update needed } bar.style.width = String(goalWidth) + '%'; - bar.innerHTML = String(goalWidth) + '%'; + bar.innerHTML = String(goalWidth) + '%'; } } diff --git a/ui/RawViewer.js b/ui/RawViewer.js index fea299d..66fcb50 100644 --- a/ui/RawViewer.js +++ b/ui/RawViewer.js @@ -1,3 +1,5 @@ +/* global chrome*/ + import {decode_str} from './utils.js'; class RawViewer{ @@ -7,7 +9,7 @@ class RawViewer{ // isReady will be se to true after message listener is installed window.isReady = false; } - + main(){ chrome.runtime.onMessage.addListener(function(obj) { if (obj.destination !== 'rawviewer') return; diff --git a/ui/Viewer.js b/ui/Viewer.js index 60e34e0..385d977 100644 --- a/ui/Viewer.js +++ b/ui/Viewer.js @@ -1,3 +1,5 @@ +/* global chrome*/ + import {NotificationBar} from './NotificationBar.js'; import {FileChooser} from './FileChooser.js'; import {decode_str, str2ba } from './utils.js'; @@ -7,7 +9,7 @@ class Viewer{ window.tabid = null; // allow the extension to put the id of the tab which opened this page window.isViewer = true; // isFileChooser will be toggled to true if extension calls Main.openFileChooser() - window.isFileChooser = false; + window.isFileChooser = false; // isReady will be se to true after message listener is installed window.isReady = false; } @@ -32,7 +34,7 @@ class Viewer{ throw 'unexpected message'; } console.log('got data in viewer'); - var hideButton = false; + var hideButton = false; var text = msg.data.response; console.log('text size is', text.length); // remove the HTTP headers @@ -43,8 +45,8 @@ class Viewer{ // add CSP to prevent loading any resources from the page const csp = '\r\n'; document.write(csp + decode_str(http_body)); - } - else { + } + else { // a file which cannot be shown but has to be downloaded like e.g. PDF document.getElementById('type').textContent = type; document.getElementById('view file button').onclick = function() { @@ -68,10 +70,10 @@ class Viewer{ if (line.search(/content-type:\s*/i) < 0) continue; if (line.match('application/pdf')){ return 'pdf'; - } + } else if (line.match('image/jpeg')){ return 'jpg'; - } + } return 'html'; } }