Files
PageSigner/core/twopc/OTReceiver.js
themighty1 79173c511e - 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
2022-01-17 10:18:04 +03:00

153 lines
6.2 KiB
JavaScript

/* 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);
}
}