refactor Paillier2PC to use the same notation as the how_it_works writeup.

This commit is contained in:
themighty1
2022-01-16 19:06:22 +03:00
parent 45cead96da
commit 16c5e6bbbd
7 changed files with 261 additions and 321 deletions

View File

@@ -540,7 +540,7 @@ export class Main{
break;
case 'export':
this.sendToManager({'pgsg': JSON.stringify(await this.getPGSG(data.args.dir)),
'name': await getSession(data.args.dir).sessionName}, 'export');
'name': (await getSession(data.args.dir)).sessionName}, 'export');
break;
case 'notarize':
this.prepareNotarization(false);

View File

@@ -2,8 +2,8 @@ export const globals = {
// defaultNotaryIP/Port is the default IP address/port of the notary server.
// If this IP address becomes unavailable, Pagesigner will query backupUrl for
// a new notary's IP address and will save the new IP address in the preferences.
defaultNotaryIP: '127.0.0.1',
//defaultNotaryIP: '3.236.244.77',
//defaultNotaryIP: '127.0.0.1',
defaultNotaryIP: '18.207.130.193',
defaultNotaryPort: 10011,
// backupUrl is the URL to query to get the IP address of another notary
// server in case if defaultNotaryIP is unreachable
@@ -26,6 +26,6 @@ export const globals = {
// if useNotaryNoSandbox is set to true, then we fetch notary's pubkey by
// querying /getPubKey and trust it. This is only useful when notary runs
// in a non-sandbox environment.
//useNotaryNoSandbox: false
useNotaryNoSandbox: true
useNotaryNoSandbox: false
//useNotaryNoSandbox: true
};

View File

@@ -4,7 +4,10 @@ import {ba2str, b64decode, assert, ba2int, verifyAttestationDoc, ba2hex, eq,
// rootsOfTrust contains an array of trusted EBS snapshots
// essentially the whole oracle verification procedure boils down to proving that an EC2 instance was
// launched from an AMI which was created from one of the "rootsOfTrust" snapshot ids.
const rootsOfTrust = ['snap-0ccb00d0e0fb4d4da', 'snap-07eda3ed4836f82fb'];
const rootsOfTrust = [
'snap-0ccb00d0e0fb4d4da',
'snap-07eda3ed4836f82fb',
'snap-023d50ee97873a1f0'];
// URLFetcher trusted enclave measurements, see
// https://github.com/tlsnotary/URLFetcher
const URLFetcherPCR0 = 'f70217239e8a1cb0f3c010b842a279e2b8d30d3700d7e4722fef22291763479a13783dc76d5219fabbd7e5aa92a7b255';

View File

@@ -1,14 +1,15 @@
/* eslint-disable no-console */
/* eslint-disable max-classes-per-file */
/* global bcuNode, ECSimple */
import {ba2str, concatTA, int2ba, str2ba, ba2int, assert} from './../utils.js';
var bcu
var bcu;
if (typeof(window) !== 'undefined'){
bcu = await import('./../third-party/math.js');
import('./../third-party/math.js').then((module) => {
bcu = module;
});
} else {
// we are in node. bcuNode must have been made global
bcu = bcuNode
bcu = bcuNode;
}
import {ba2str, concatTA, int2ba, str2ba, ba2int} from './../utils.js';
function pad(str) {
if (str.length % 2 === 1) {
@@ -17,7 +18,8 @@ function pad(str) {
return str;
}
// eslint-disable-next-line no-unused-vars
// class PaillierPubkey implements encryption and homomorphic operations
class PaillierPubkey {
constructor(n, g) {
this.n = n;
@@ -46,36 +48,15 @@ class PaillierPubkey {
}
}
// Protocol to compute EC point addition in Paillier
//
// we need to find xr = lambda**2 - xp - xq, where
// lambda = (yq - yp)(xq - xp)**-1
// (when p is prime then) a**-1 mod p == a**p-2 mod p
// Simplifying:
// xr == (yq**2 - 2yqyp + yp**2) (xq - xp)**2p-4 - xp - xq
// we have 3 terms
// A = (yq**2 - 2yqyp + yp**2)
// B = (xq - xp)**2p-4
// C = - xp - xq
//
// class Paillier2PC is the client's side part of 2PC
// you should launch the notary side of 2PC with:
// go build -o notary2pc && ./notary2pc
// Protocol to compute EC point addition in Paillier as described here:
// https://tlsnotary.org/how_it_works#section1
// The code uses the same notation as in the link above.
// TODO rename Paillier2PC into ECDH2PC
// eslint-disable-next-line no-unused-vars
// class Paillier2PC implements the client's side of computing an EC point
// addition in 2PC
export class Paillier2PC {
// x and y are 32-byte long byte arrays: coordinates of server pubkey
constructor(parent, x_, y_, ) {
this.send = parent.send;
this.notary = parent.notary;
this.clientKey = parent.clientKey;
this.notaryKey = parent.notaryKey;
this.encryptToNotary = parent.encryptToNotary;
this.decryptFromNotary = parent.decryptFromNotary;
this.uid = parent.uid;
// x and y are 32-byte Uint8Arrays: coordinates of server pubkey
constructor(x, y) {
// define secp256r1 curve
this.secp256r1 = new ECSimple.Curve(
0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFCn,
@@ -88,216 +69,162 @@ export class Paillier2PC {
),
);
const x = ba2int(x_);
const y = ba2int(y_);
this.serverPubkey = new ECSimple.ModPoint(x, y);
this.pPriv = bcu.randBetween(this.secp256r1.n - 1n, 1n);
// debug only delete priv by 2 so that the final client priv doesnt
// overflow 32 bytes - to make openssl happy
// remove in production
this.pPriv = this.pPriv / 2n;
this.pPrivG = this.secp256r1.multiply(this.secp256r1.g, this.pPriv);
this.share1 = this.secp256r1.multiply(this.serverPubkey, this.pPriv);
// var ecPubKey = secp256r1.add(qPrivG, pPrivG)
// var pms = secp256r1.add(share1, share2)
// console.log('pms is', pms)
this.Q_b = new ECSimple.ModPoint(ba2int(x), ba2int(y));
// C picks a random private key share d_c
this.d_c = bcu.randBetween(this.secp256r1.n - 1n, 1n);
// and computes a public key share Q_c
this.Q_c = this.secp256r1.multiply(this.secp256r1.g, this.d_c);
// C computes an EC point d_c * Q_b
const ECpoint_c = this.secp256r1.multiply(this.Q_b, this.d_c);
this.x_p = ECpoint_c.x;
this.y_p = ECpoint_c.y;
this.p = this.secp256r1.p;
// all the fields below are intermediate values which we save after each
// communication round with the Notary
// Enx_q is enrypted negative x_q
this.Enx_q = null;
this.Q_nx = null;
this.Q_ny = null;
this.Ey_q2 = null;
// enrypted negative 2*y_q
this.En2y_q = null;
// we call E(A*M_A+N_A) "E114"
this.E114 = null;
this.N_Amodp = null;
this.M_b = null;
this.M_A = null;
this.M_B = null;
this.s_q = null;
}
// run 2PC computation with the notary
async run() {
step1() {
// S sends its public key Q_b to C and C passes it to N
const payload1 = str2ba(JSON.stringify({
serverX: pad(this.serverPubkey.x.toString(16)),
serverY: pad(this.serverPubkey.y.toString(16)),
share1X: pad(this.share1.x.toString(16)),
share1Y: pad(this.share1.y.toString(16)),
Q_bx: pad(this.Q_b.x.toString(16)),
Q_by: pad(this.Q_b.y.toString(16))
}));
const step1 = JSON.parse(ba2str(await this.send('step1', payload1)));
const p = this.secp256r1.p;
return payload1;
}
// (party1 is the notary, party2 is the client)
step2(step1Resp){
const step1 = JSON.parse(ba2str(step1Resp));
// -------- PHASE 1: computing term B
// party1 provides P(xq)
// var Pxq = publicKey.encrypt(qPrivG.x)
// // negative xq
// // 5 - 3 mod 6 == 5 + (6 -3) mod 6
// var Pnxq = publicKey.encrypt(secp256r1.p - qPrivG.x)
// party2:
// 1) compute P(A) == P(xq - xp)
// 2) blind with B: P(AB) == P(A)^B
// 3) (to prevent factorizing AB) blind with C: P(AB+C) == P(AB)*C
// 4) send 1) P(AB+C) and 2) C mod p
// if xp > xq then PA below is negative and we'd have to apply the mask C the size of
// n = 2048bits we don't want that. If we keep PA positive then the mask C size will be
// 256+256=512 bits for this reason we increase xp by prime (mod prime)
const n = BigInt(`0x${step1.n}`, 16);
const g = BigInt(`0x${step1.g}`, 16);
const Pxq = BigInt(`0x${step1.Pxq}`, 16);
const Pnxq = BigInt(`0x${step1.Pnxq}`, 16);
const Ex_q = BigInt(`0x${step1.Ex_q}`, 16);
this.Enx_q = BigInt(`0x${step1.Enx_q}`, 16);
this.Q_nx = BigInt(`0x${step1.Q_nx}`, 16);
this.Q_ny = BigInt(`0x${step1.Q_ny}`, 16);
this.Ey_q2 = BigInt(`0x${step1.Ey_q2}`, 16);
this.En2y_q = BigInt(`0x${step1.Pn2yq}`, 16);
// we expect n to be 1536 bits = 192 bytes
assert(int2ba(n).length == 192);
this.pubkey = new PaillierPubkey(n, g);
const PA = this.pubkey.addCleartext(Pxq, p - this.share1.x);
const maskB = bcu.randBetween(2n ** 256n);
const PAB = this.pubkey.multiply(PA, maskB);
const maskC = bcu.randBetween(2n ** 512n);
const PABC = this.pubkey.addCleartext(PAB, maskC);
const Cmodp = maskC % p;
// 1.2.4
// if x_p > x_q then b will be negative and the size of N_b would have to be
// n = 2048bits in order to mask a negative b. But if we keep b positive then
// the size of N_b will be 256+256=512 bits. In order to keep b positive, we
// increase x_q by prime p and only then add -x_p, which is the same as doing:
// x_q + (p - x_p)
const Eb = this.pubkey.addCleartext(Ex_q, this.p - this.x_p);
// 1.2.5
// picks random mask M_b
this.M_b = bcu.randBetween(2n ** 256n);
// picks random mask N_b
const N_b = bcu.randBetween(2n ** 512n);
// we call E(b*M_b+N_b) E125
const E125 = this.pubkey.addCleartext(this.pubkey.multiply(Eb, this.M_b), N_b);
const N_bmodp = N_b % this.p;
// 1.2.6
const payload2 = str2ba(JSON.stringify({
PABC: pad(PABC.toString(16)),
Cmodp: pad(Cmodp.toString(16)),
E125: pad(E125.toString(16)),
N_bmodp: pad(N_bmodp.toString(16)),
}));
const fetch2 = this.send('step2', payload2, true);
return payload2;
}
// while fetching we can compute
// -------- PHASE 2: computing term A
// party1 provides: P(yq**2), P(-2yq) (was done in step1)
// var Pyq2 = publicKey.encrypt(bcu.modPow(qPrivG.y, 2n, secp256r1.p))
// var Pn2yq = publicKey.encrypt(2n * (secp256r1.p - qPrivG.y) % secp256r1.p)
// party2
// 1) computes P(term A) = P(yq**2) + P(-2yq)**yp + P(yp**2)
// 2) blind with c1 and c2 : d == P(termA * c1 mod c2)
// 3) send d and c2 mod p
const Pyq2 = BigInt(`0x${step1.Pyq2}`, 16);
const Pn2yq = BigInt(`0x${step1.Pn2yq}`, 16);
const PtermA = this.pubkey.addCleartext(
this.pubkey.addCiphertext(Pyq2, this.pubkey.multiply(Pn2yq, this.share1.y)),
bcu.modPow(this.share1.y, 2n, p),
async step2async(){
// while fetching we can compute PHASE 2: computing term A
// N provides: E(y_q**2), E(-2y_q) (both were received in step1 response above)
// 1.1.3
const EA = this.pubkey.addCleartext(
this.pubkey.addCiphertext(this.Ey_q2, this.pubkey.multiply(this.En2y_q, this.y_p)),
bcu.modPow(this.y_p, 2n, this.p),
);
// 1.1.4
this.M_A = bcu.randBetween(2n ** 512n);
const N_A = bcu.randBetween(2n ** 1024n);
this.E114 = this.pubkey.addCleartext(this.pubkey.multiply(EA, this.M_A), N_A);
this.N_Amodp = N_A % this.p;
// we don't send step 1.1.5 now, because a response from N is pending
}
const c1 = bcu.randBetween(2n ** 512n);
const c2 = bcu.randBetween(2n ** 1024n);
const d = this.pubkey.addCleartext(this.pubkey.multiply(PtermA, c1), c2);
const c2modp = c2 % p;
const step2 = JSON.parse(ba2str(await fetch2));
// party1
// computing term B continued
// 1) decrypt to get AB+C
// 2) reduce AB+C mod p
// 3) subtract C to get AB mod p
// 4) compute ABraised = (AB)**2p-4 mod p
// 5) send P(ABraised)
// var DABC = privateKey.decrypt(PABC)
// var ABC = DABC % secp256r1.p
// var AB = (ABC + secp256r1.p - Cmodp) % secp256r1.p
// var ABraised = bcu.modPow(AB, 2n*secp256r1.p - 4n, secp256r1.p)
// var PABraised = publicKey.encrypt(ABraised)
// party2
// (remember that ABraised == (A**2p-4)(B**2p-4)
// in order to get (A**2p-4), we need to divide by (B**2p-4) or multiply by (B**2p-4)**-1)
// 1) compute P(term B) = P(ABraised) ** (B**2p-4)**-1 mod p
// 2) blind with a1 and a2 : b == P(termB * a1 + a2)
// 3) send b and a2 mod p
const PABraised = BigInt(`0x${step2.PABraised}`, 16);
const Braised = bcu.modPow(maskB, p - 3n, p);
const Binv = bcu.modInv(Braised, p);
const PtermB = this.pubkey.multiply(PABraised, Binv);
const a1 = bcu.randBetween(2n ** 512n);
const a2 = bcu.randBetween(2n ** 1024n);
const b = this.pubkey.addCleartext(this.pubkey.multiply(PtermB, a1), a2);
const a2modp = a2 % p;
step3(step2Resp){
const step2 = JSON.parse(ba2str(step2Resp));
// get what N sent in 1.2.10
const E1210 = BigInt(`0x${step2.E1210}`, 16);
// 1.2.11
const Binv = bcu.modInv(bcu.modPow(this.M_b, this.p - 3n, this.p), this.p);
// 1.2.12
const EB = this.pubkey.multiply(E1210, Binv);
// 1.2.13
this.M_B = bcu.randBetween(2n ** 512n);
const N_B = bcu.randBetween(2n ** 1024n);
const E1213 = this.pubkey.addCleartext(this.pubkey.multiply(EB, this.M_B), N_B);
const N_Bmodp = N_B % this.p;
// 1.2.14 + 1.1.5
const payload3 = str2ba(JSON.stringify({
b: pad(b.toString(16)),
a2modp: pad(a2modp.toString(16)),
d: pad(d.toString(16)),
c2modp: pad(c2modp.toString(16)),
E1213: pad(E1213.toString(16)),
N_Bmodp: pad(N_Bmodp.toString(16)),
E114: pad(this.E114.toString(16)),
N_Amodp: pad(this.N_Amodp.toString(16)),
}));
const step3 = JSON.parse(ba2str(await this.send('step3', payload3)));
return payload3;
}
step4(step3Resp){
const step3 = JSON.parse(ba2str(step3Resp));
// -------- PHASE 3: computing termA*termB and term C
// party1
// 1) compute termB*a1*termA*c2
// 2) send P()
// var termBa1 = (privateKey.decrypt(b) - a2modp) % secp256r1.p
// var termAc2 = (privateKey.decrypt(d) - c2modp) % secp256r1.p
// var PtermABmasked = publicKey.encrypt(termBa1 * termAc2 % secp256r1.p)
// party2
// 1) compute P(termAB) = P(termABmasked) * (a1*c2)**-1
// 2) compute P(X) = P(termAB) + P(-xp) + P (-xq)
// 3) blind P(X) with x1: P(x2) = P(X+x1)
// 4) send P(x2) (unreduced)
const PtermABmasked = BigInt(`0x${step3.PtermABmasked}`, 16);
const a1c1inv = bcu.modInv((a1 * c1) % p, p);
const PtermAB = this.pubkey.multiply(PtermABmasked, a1c1inv);
// negative xp
const nxp = p - this.share1.x;
const PX = this.pubkey.addCleartext(
this.pubkey.addCiphertext(PtermAB, Pnxq),
nxp,
);
// PX may be up to 1027 bits long
const x1unreduced = bcu.randBetween(2n ** 1027n);
const Px2unreduced = this.pubkey.addCleartext(PX, x1unreduced);
// negative x1
const nx1 = p - (x1unreduced % p);
// get what N sent in 1.3.1 (note that E(-x_q) was already sent to us earlier
// in step1)
const E131 = BigInt(`0x${step3.E131}`, 16);
// 1.3.2
const EAB = this.pubkey.multiply(E131, bcu.modInv((this.M_B * this.M_A) % this.p, this.p));
// 1.3.3.
// nx_p is negative x_p
const nx_p = this.p - this.x_p;
// Note that the reason why 1.3.3 says "computes E(-x_p)" is to simplify the
// explanation. In reality, Paillier crypto allows to add a cleartext to a
// ciphertext without needing to first encrypt the cleartext. For this reason
// we don't encrypt -x_p but we add it homomorphically.
// 1.3.4
const EABC = this.pubkey.addCleartext(this.pubkey.addCiphertext(EAB, this.Enx_q),
nx_p);
// 1.3.5
// EABC may be up to 1027 bits long, the mask must be same length
const S_q = bcu.randBetween(2n ** 1027n);
const E135 = this.pubkey.addCleartext(EABC, S_q);
// 1.3.6
// Since N has (PMS + S_q) and we have S_q, in order to compute PMS, we will
// later compute the following in 2PC circuit: PMS = (PMS + S_q) - S_q,
// thus C's s_q must be negative
this.s_q = this.p - (S_q % this.p);
const payload4 = str2ba(JSON.stringify({
Px2unreduced: pad(Px2unreduced.toString(16)),
E135: pad(E135.toString(16)),
}));
const step4 = JSON.parse(ba2str(await this.send('step4', payload4)));
return payload4;
}
// party1
// var x2 = privateKey.decrypt(Px2unreduced) % secp256r1.p
// // now both parties have additive shares of X: x1 and nx2
// // x2 + nx1 == x2 - x1 == X
const x2 = BigInt(`0x${step4.x2}`, 16);
const qPriv = BigInt(`0x${step4.qPriv}`, 16);
const qPrivG = this.secp256r1.multiply(this.secp256r1.g, qPriv);
const share2 = this.secp256r1.multiply(this.serverPubkey, qPriv);
// TODO this must not be done here because qPriv (from which qPrivG is computed)
// is here only for debugging. notary should pass qPrivG after step1
const clientPubkey = this.secp256r1.add(qPrivG, this.pPrivG);
const cpubBytes = concatTA(
int2ba(clientPubkey.x, 32),
int2ba(clientPubkey.y, 32));
const clientPrivkey = this.pPriv + qPriv;
console.log('priv sum len is ', int2ba(clientPrivkey).length);
// if privkey > 32 bytes, the result will still be correct
// the problem may arise when (during debugging) we feed >32 bytes
// into openssl. It expects 32 bytes
// assert(bigint2ba(clientPrivkey).length <= 32)
const x = (((
bcu.modPow(share2.y + p - this.share1.y, 2n, p) *
bcu.modPow(share2.x + p - this.share1.x, p - 3n, p)) % p) +
(p - this.share1.x) + (p - share2.x)) % p;
const pms = (x2 + nx1) % p;
console.log('is sum larger than prime', (x2 + nx1) > p);
const nx1Hex = int2ba(nx1, 32); // negative share x1
return [nx1Hex, cpubBytes];
final(){
// N sends Q_n to C who computes Q_a = Q_c + Q_n and sends Q_a to S
const Q_n = new ECSimple.ModPoint(this.Q_nx, this.Q_ny);
const Q_a = this.secp256r1.add(Q_n, this.Q_c);
// cpubBytes is TLS session's client's ephemeral pubkey for ECDHE
const cpubBytes = concatTA(int2ba(Q_a.x, 32), int2ba(Q_a.y, 32));
return [int2ba(this.s_q, 32), cpubBytes];
}
}

View File

@@ -114,8 +114,15 @@ export class TWOPC {
}
async getECDHShare(x, y){
const paillier = new Paillier2PC(this, x, y);
return await paillier.run();
const paillier = new Paillier2PC(x, y);
const step1Resp = await this.send('step1', paillier.step1());
const step2Promise = this.send('step2', paillier.step2(step1Resp), true);
// while Notary is responding to step2 we can do some more computations
paillier.step2async();
const step2Resp = await step2Promise;
const step3Resp = await this.send('step3', paillier.step3(step2Resp));
await this.send('step4', paillier.step4(step3Resp));
return paillier.final();
}
// pass client/server random, handshake (to derive verify_data), pms share

View File

@@ -1,3 +1,5 @@
/* global process, global */
// gcworker.js is a WebWorker which performs garbling and evaluation
// of garbled circuits.
// This is a fixed-key-cipher garbling method from BHKR13 https://eprint.iacr.org/2013/426.pdf
@@ -9,14 +11,15 @@ let truthTable = null;
// fixedKey is used by randomOracle(). We need a 32-byte key because we use Salsa20. The last 4
// bytes will be filled with the index of the circuit's wire.
const fixedKey = new Uint8Array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,
25,26,27,28,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]);
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;
let randomPool;
// randomPoolOffset will be moved after data was read from randomPool
let randomPoolOffset = 0;
let randomPoolOffset = 0;
let garbledAssigment;
var crypto_;
@@ -32,19 +35,19 @@ 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_ = parentPort
const { Crypto } = require("@peculiar/webcrypto");
parentPort_ = parentPort;
const { Crypto } = require('@peculiar/webcrypto');
crypto_ = new Crypto();
const perf = {'now':function(){return 0;}}
const perf = {'now':function(){return 0;}};
global.performance = perf;
parentPort.on('message', msg => {
processMessage(msg);
})
})
}
});
});
}
function processMessage(obj){
const msg = obj.msg;
@@ -96,7 +99,7 @@ function processMessage(obj){
function postMsg(value, transferList){
if (typeof importScripts !== 'function'){
parentPort_.postMessage({data:value}, transferList)
parentPort_.postMessage({data:value}, transferList);
} else {
postMessage(value, transferList);
}
@@ -122,12 +125,12 @@ function generateInputLabels(count, R){
return newLabels;
}
function garble(circuit, ga, reuseLabels = new Uint8Array(0) , reuseIndexes = [], R){
function garble(circuit, ga, reuseLabels = new Uint8Array(0), reuseIndexes = [], R){
const inputCount = circuit.notaryInputSize + circuit.clientInputSize;
fillRandom((inputCount+1+circuit.andGateCount)*16);
R = R || newR();
// generate new labels
const newLabels = generateInputLabels(inputCount - reuseIndexes.length, R);
@@ -145,7 +148,7 @@ function garble(circuit, ga, reuseLabels = new Uint8Array(0) , reuseIndexes = []
newInputsCount += 1;
}
}
const truthTable = new Uint8Array(circuit.andGateCount*48);
let andGateIdx = 0;
@@ -164,50 +167,50 @@ function garble(circuit, ga, reuseLabels = new Uint8Array(0) , reuseIndexes = []
throw new Error('Unrecognized gate: ' + op);
}
}
return [truthTable, ga.slice(0, inputCount*32), ga.slice(-circuit.outputSize*32), R];
}
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 in1 = threeBytesToInt(gateBlob.subarray(1, 4));
const in2 = threeBytesToInt(gateBlob.subarray(4, 7));
const out = threeBytesToInt(gateBlob.subarray(7, 10));
// 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);
// rows is a truthtable if wire labels in a canonical order, the third
// 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]
]
];
// 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
// 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)
let outLabels
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]
outLabels = [xor(outWire, R), outWire];
} else {
outLabels = [outWire, xor(outWire, R)]
outLabels = [outWire, xor(outWire, R)];
}
idxToReduce = i;
break;
@@ -215,25 +218,25 @@ const garbleAnd = function (gateBlob, R, ga, tt, andGateIdx, id) {
}
gaSetIndexG(ga, out, 0, outLabels[0]);
gaSetIndexG(ga, out, 1, outLabels[1]);
assert(idxToReduce != -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;
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])
const point = 2 * getPoint(rows[i][0]) + getPoint(rows[i][1]);
tt.set(value, andGateIdx*48+16*point);
}
};
const garbleXor = function (gateBlob, R, ga) {
const in1 = threeBytesToInt(gateBlob.subarray(1,4));
const in2 = threeBytesToInt(gateBlob.subarray(4,7));
const out = threeBytesToInt(gateBlob.subarray(7,10));
const in1 = threeBytesToInt(gateBlob.subarray(1, 4));
const in2 = threeBytesToInt(gateBlob.subarray(4, 7));
const out = threeBytesToInt(gateBlob.subarray(7, 10));
const in1_0 = gaGetIndexG(ga, in1, 0);
const in1_1 = gaGetIndexG(ga, in1, 1);
@@ -243,11 +246,11 @@ const garbleXor = function (gateBlob, R, ga) {
gaSetIndexG(ga, out, 0, xor(in1_0, in2_0));
gaSetIndexG(ga, out, 1, xor(xor(in1_1, in2_1), R, true));
};
const garbleNot = function (gateBlob, ga) {
const in1 = threeBytesToInt(gateBlob.subarray(1,4));
const out = threeBytesToInt(gateBlob.subarray(7,10));
const in1 = threeBytesToInt(gateBlob.subarray(1, 4));
const out = threeBytesToInt(gateBlob.subarray(7, 10));
const in1_0 = gaGetIndexG(ga, in1, 0);
const in1_1 = gaGetIndexG(ga, in1, 1);
@@ -262,7 +265,6 @@ function evaluate (circuit, ga, tt, inputLabels) {
// evaluate one gate at a time
let numberOfANDGates = 0;
const t0 = performance.now();
console.time('worker_evaluate');
for (let i = 0; i < circuit.gatesCount; i++) {
const gateBlob = circuit.gatesBlob.subarray(i*10, i*10+10);
@@ -279,24 +281,23 @@ function evaluate (circuit, ga, tt, inputLabels) {
}
}
console.timeEnd('worker_evaluate');
const t1 = performance.now();
return ga.slice((circuit.wiresCount-circuit.outputSize)*16, circuit.wiresCount*16);
}
const evaluateAnd = function (ga, tt, andGateIdx, gateBlob, id) {
const in1 = threeBytesToInt(gateBlob.subarray(1,4));
const in2 = threeBytesToInt(gateBlob.subarray(4,7));
const out = threeBytesToInt(gateBlob.subarray(7,10));
const in1 = threeBytesToInt(gateBlob.subarray(1, 4));
const in2 = threeBytesToInt(gateBlob.subarray(4, 7));
const out = threeBytesToInt(gateBlob.subarray(7, 10));
const label1 = gaGetIndexE(ga, in1); // ga[in1];
const label2 = gaGetIndexE(ga, in2); // ga[in2];
let cipher
let cipher;
const point = 2 * getPoint(label1) + getPoint(label2);
if (point == 3){
// GRR3: all rows with point sum of 3 have been reduced
// their encryption is an all-zero bytestring
// their encryption is an all-zero bytestring
cipher = new Uint8Array(16).fill(0);
} else {
const offset = andGateIdx*48+16*point;
@@ -307,18 +308,18 @@ const evaluateAnd = function (ga, tt, andGateIdx, gateBlob, id) {
const evaluateXor = function (ga, gateBlob) {
const in1 = threeBytesToInt(gateBlob.subarray(1,4));
const in2 = threeBytesToInt(gateBlob.subarray(4,7));
const out = threeBytesToInt(gateBlob.subarray(7,10));
const in1 = threeBytesToInt(gateBlob.subarray(1, 4));
const in2 = threeBytesToInt(gateBlob.subarray(4, 7));
const out = threeBytesToInt(gateBlob.subarray(7, 10));
const v1 = gaGetIndexE(ga, in1);
const v2 = gaGetIndexE(ga, in2);
gaSetIndexE(ga, out, xor(v1, v2));
};
const evaluateNot = function (ga, gateBlob) {
const in1 = threeBytesToInt(gateBlob.subarray(1,4));
const out = threeBytesToInt(gateBlob.subarray(7,10));
const in1 = threeBytesToInt(gateBlob.subarray(1, 4));
const out = threeBytesToInt(gateBlob.subarray(7, 10));
gaSetIndexE(ga, out, gaGetIndexE(ga, in1));
};
@@ -346,7 +347,7 @@ function gaSetIndexG(ga, idx, pos, value){
function xor(a, b, reuse) {
assert(a.length == b.length, '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"
@@ -372,19 +373,19 @@ const decrypt = encrypt;
// 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.
// so we choose a circular byte shift instead of a circular bitshift as in Fig6.
function encrypt(a, b, t, m) {
// double a
// double a
const a2 = a.slice();
const leastbyte = a2[0];
a2.copyWithin(0,1,15); // Logical left shift by 1 byte
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
const b4 = b.slice();
const leastbytes = [b4[0], b4[1]];
b4.copyWithin(0,2,15); // Logical left shift by 2 byte
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
const k = xor(a2, b4, true);
const ro = randomOracle(k, t);
const mXorK = xor(k, m, true);
@@ -415,7 +416,7 @@ function getRandom(count) {
return rand;
}
// to save time we fill the randomPool in one call and then take
// to save time we fill the randomPool in one call and then take
// randomness from that pool. Instead of making 1000s of calls to getRandomValues()
function fillRandom(count){
// 65536 is the max that API supports
@@ -423,7 +424,7 @@ function fillRandom(count){
const chunkCount = Math.ceil(count/65536);
for (let i=0; i < chunkCount; i++){
randomChunks.push(crypto_.getRandomValues(new Uint8Array(65536)));
}
}
randomPool = concatTA(...randomChunks);
randomPoolOffset = 0;
}
@@ -456,7 +457,7 @@ function concatTA (...arr){
// to be permuted.
function Salsa20(key, data){
const out = new Uint8Array(16);
core_salsa20(out, data, key, sigma)
core_salsa20(out, data, key, sigma);
return out;
}
@@ -464,25 +465,25 @@ function Salsa20(key, data){
// 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;
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;
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;
@@ -557,16 +558,16 @@ function core_salsa20(o, p, k, c) {
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;
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;

View File

@@ -1,3 +1,5 @@
/* global process*/
// Serializes the circuit into a compact representation
if (typeof(importScripts) === 'undefined'){