mirror of
https://github.com/tlsnotary/PageSigner.git
synced 2026-01-08 22:27:57 -05:00
- implement garbled row reduction GRR3 for 25% bandwidth saving - implement KOS15 OT extension - linting
153 lines
6.2 KiB
JavaScript
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);
|
|
}
|
|
} |