Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement multiplication for polyval #54

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
287839e
passing test for gfmulx
0xJepsen Aug 14, 2024
3a4f781
RFC test vector fails
0xJepsen Aug 15, 2024
ef54fc8
comments
0xJepsen Aug 15, 2024
6dd3513
refactor test code paths: create dir for gfmulx for polyval and ghash
thor314 Aug 15, 2024
d2d72c1
implement tests for ghash + format file
thor314 Aug 15, 2024
b9bc6f6
implement polyval tests
thor314 Aug 15, 2024
51b35c6
implement more thorough polyval testing to inspect LE encoding bug
thor314 Aug 15, 2024
9af7124
leftshift LE circom written and tested
thor314 Aug 15, 2024
4538c2b
incorporate LE shift changes into polyval_GFMULX
thor314 Aug 16, 2024
ddd4858
gf_mul polyval passing tests
thor314 Aug 16, 2024
bbebe4e
note conflict in tests
thor314 Aug 16, 2024
91090b6
implement ghash mulx
thor314 Aug 16, 2024
ddef808
clean
thor314 Aug 16, 2024
2f9adea
setup to implement ghash
thor314 Aug 16, 2024
9ea4ede
impl reverse_byte array
thor314 Aug 16, 2024
eefa067
circomkit bug? can't declare input to GHASH
thor314 Aug 16, 2024
f4f8020
ghash tests implemented
thor314 Aug 16, 2024
af3fafa
bitreversal test compares output instead of expectPass
KaiGeffen Aug 19, 2024
0f27e05
Corrected ghash_gfmul comment
KaiGeffen Aug 19, 2024
5f0cd96
Merging main
KaiGeffen Aug 19, 2024
b21c3ce
Added polyval test consistent with ghash
KaiGeffen Aug 19, 2024
bab3274
lint comments in circuit
thor314 Aug 19, 2024
8304241
impl bmul multiplication subroutine
thor314 Aug 20, 2024
f71c3c9
impl gfmul (mostly)
thor314 Aug 21, 2024
6ff0729
Merge branch 'main' into polymul
thor314 Aug 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 231 additions & 0 deletions circuits/aes-gcm/gfmul.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
pragma circom 2.1.9;

include "mul.circom";

// Computes carryless POLYVAL multiplication over GF(2^128) in constant time.
//
// Method described at:
// <https://www.bearssl.org/constanttime.html#ghash-for-gcm>
//
// POLYVAL multiplication is effectively the little endian equivalent of
// GHASH multiplication, aside from one small detail described here:
//
// <https://crypto.stackexchange.com/questions/66448/how-does-bearssls-gcm-modular-reduction-work/66462#66462>
//
// > The product of two bit-reversed 128-bit polynomials yields the
// > bit-reversed result over 255 bits, not 256. The BearSSL code ends up
// > with a 256-bit result in zw[], and that value is shifted by one bit,
// > because of that reversed convention issue. Thus, the code must
// > include a shifting step to put it back where it should
//
// This shift is unnecessary for POLYVAL and has been removed.
//
// ref: https://github.com/RustCrypto/universal-hashes/blob/master/polyval/src/backend/soft64.rs#L151
template MUL() {
signal input a[2][64];
signal input b[2][64];
signal output out[2][64];

// variable aliases to make indexing logic easier to state
signal h[3][64];
signal y[3][64];
signal h_r[3][64];
signal y_r[3][64];

h[0] <== a[0];
h[1] <== a[1];
y[0] <== b[0];
y[1] <== b[1];
component Revs[4];
for (var i = 0; i < 2; i++) {
Revs[i] = REV64();
Revs[i].in <== h[i];
h_r[i] <== Revs[i].out;
}
for (var i = 0; i < 2; i++) {
Revs[i+2] = REV64();
Revs[i+2].in <== y[i];
y_r[i] <== Revs[i+2].out;
}

// h2 = h0^h1; y2 = y0^y1
component Xors[4];
for (var i = 0; i < 4; i++) Xors[i] = BitwiseXor(64);
Xors[0].a <== h[0];
Xors[0].b <== h[1];
h[2] <== Xors[0].out;
Xors[1].a <== y[0];
Xors[1].b <== y[1];
y[2] <== Xors[1].out;

// h2_r = h0_r^h1_r; y2_r = y0_r^y1_r
Xors[2].a <== h_r[0];
Xors[2].b <== h_r[1];
h_r[2] <== Xors[2].out;
Xors[3].a <== y_r[0];
Xors[3].b <== y_r[1];
y_r[2] <== Xors[3].out;

// z0 = bmul64(y0, h0); z1 = bmul64(y1, h1); z2 = bmul64(y2, h2);
// z0_h = bmul64(y0_r, h0_r); z1_h = bmul64(y1_r, h1_r); z2_h = bmul64(y2_r, h2_r);
component BMUL64_z[6];
signal z[3][64];
signal zh[3][64];
for (var i = 0; i < 3; i++) {
BMUL64_z[i] = BMUL64();
BMUL64_z[i].x <== y[i];
BMUL64_z[i].y <== h[i];
z[i] <== BMUL64_z[i].out;

BMUL64_z[i+3] = BMUL64();
BMUL64_z[i+3].x <== y_r[i];
BMUL64_z[i+3].y <== h_r[i];
zh[i] <== BMUL64_z[i+3].out;
}

// _z2 = z0 ^ z1 ^ z2;
// _z2h = z0h ^ z1h ^ z2h;
signal _z2[64];
signal _zh[3][64];
component XorMultiples[2];
XorMultiples[0] = XorMultiple(3, 64);
XorMultiples[0].inputs <== z;
_z2 <== XorMultiples[0].out;

XorMultiples[1] = XorMultiple(3, 64);
XorMultiples[1].inputs <== zh;
_zh[0] <== XorMultiples[1].out;
_zh[1] <== zh[1];
_zh[2] <== zh[2];

// z0h = rev64(z0h) >> 1;
// z1h = rev64(z1h) >> 1;
// _z2h = rev64(_z2h) >> 1;
// signal _zh[3][64];
signal __zh[3][64];
component Revs_zh[3];
component RightShifts_zh[3];
for (var i = 0; i < 3; i++) {
Revs_zh[i] = REV64();
RightShifts_zh[i] = BitwiseRightShift(64, 1);
Revs_zh[i].in <== zh[i];
RightShifts_zh[i].in <== Revs_zh[i].out;
__zh[i] <== RightShifts_zh[i].out;
}

// let v0 = z0;
// let mut v1 = z0h ^ z2;
// let mut v2 = z1 ^ z2h;
// let mut v3 = z1h;
signal v[4][64];
component Xors_v[2];
v[0] <== z[0];
v[3] <== __zh[1];
Xors_v[0] = BitwiseXor(64);
Xors_v[0].a <== __zh[0];
Xors_v[0].b <== _z2;
v[1] <== Xors_v[0].out;
Xors_v[1] = BitwiseXor(64);
Xors_v[1].a <== z[1];
Xors_v[1].b <== __zh[2];
v[2] <== Xors_v[1].out;


// _v2 = v2 ^ v0 ^ (v0 >> 1) ^ (v0 >> 2) ^ (v0 >> 7);
// _v1 = v1 ^ (v0 << 63) ^ (v0 << 62) ^ (v0 << 57);
// _v3 = v3 ^ _v1 ^ (_v1 >> 1) ^ (_v1 >> 2) ^ (_v1 >> 7);
// __v2 = _v2 ^ (_v1 << 63) ^ (_v1 << 62) ^ (_v1 << 57);
signal _v2[64];
signal _v1[64];
signal _v3[64];
signal __v2[64];
component XorMultiples_v[4];
component LeftShifts_v[6];
component RightShifts_v[6];

out <== [__v2, _v3];

}

// Multiplication in GF(2)[X], truncated to the low 64-bits, with “holes”
// (sequences of zeroes) to avoid carry spilling.
//
// When carries do occur, they wind up in a "hole" and are subsequently masked
// out of the result.
//
// ref: https://github.com/RustCrypto/universal-hashes/blob/master/polyval/src/backend/soft64.rs#L206
template BMUL64() {
signal input x[64];
signal input y[64];
signal output out[64];

signal xs[4][64];
signal ys[4][64];
// var masks[4] = [0x1111111111111111, 0x2222222222222222, 0x4444444444444444, 0x8888888888888888];
var masks[4][64] = [
[0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,
0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1],
[0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,
0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0],
[0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,
0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0],
[1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,
1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0]];

component ands[3][4];
for (var i = 0; i < 4; i++) {
ands[0][i] = BitwiseAnd(64);
ands[0][i].a <== masks[i];
ands[0][i].b <== x;
xs[i] <== ands[0][i].out;

ands[1][i] = BitwiseAnd(64);
ands[1][i].a <== masks[i];
ands[1][i].b <== y;
ys[i] <== ands[1][i].out;
}

// w_{i,j} = x_j * y_{i-j%4}
component muls[4][4];
// z_i = XOR(w_{i,0}, w_{i,1}, w_{i,2}, w_{i,3})
component xor_multiples[4];
signal z_mid[4][4][64];
signal z[4][64];
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
var Y_INDEX = (i - j) % 4;
muls[i][j] = Mul64();
muls[i][j].src1 <== xs[j];
muls[i][j].src2 <== ys[Y_INDEX];
z_mid[i][j] <== muls[i][j].out;
}

xor_multiples[i] = XorMultiple(4, 64);
xor_multiples[i].inputs <== z_mid[i];
z[i] <== xor_multiples[i].out;
}

// z_masked[i] = z[i] & masks[i]
signal z_masked[4][64];
for (var i = 0; i < 4; i++) {
ands[2][i] = BitwiseAnd(64);
ands[2][i].a <== masks[i];
ands[2][i].b <== z[i];
z_masked[i] <== ands[2][i].out;
}

// out = z_masked[0] | z_masked[1] | z_masked[2] | z_masked[3]
component or_multiple = OrMultiple(4, 64);
or_multiple.inputs <== z_masked;
out <== or_multiple.out;
}

// todo: verify this is what was actually meant
template REV64(){
signal input in[64];
signal output out[64];

for (var i = 0; i < 64; i++) {
out[i] <== in[63 - i];
}
}
5 changes: 2 additions & 3 deletions circuits/aes-gcm/hashes.circom
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,10 @@ template GHASH(n_msg_bits) {
// }


template POLYVAL(n_msg_bits)
{
template POLYVAL(n_msg_bits) {
signal input msg[n_msg_bits];
// Hash Key
signal input H[128];
// signal input T[2][64]; // TODO
signal output out[128];

for (var i = 0; i < 128; i++) {
Expand Down
52 changes: 52 additions & 0 deletions circuits/aes-gcm/helper_functions.circom
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ template BitwiseAnd(n) {
}
}

template BitwiseOr(n) {
signal input a[n];
signal input b[n];
signal output out[n];

for (var i=0; i<n; i++) {
out[i] <== a[i] + b[i] - a[i]*b[i];
}
}

template IntAnd(n)
{
signal input a;
Expand Down Expand Up @@ -229,6 +239,47 @@ template SumMultiple(n) {
sum <== sums[n-1];
}

// compute the OR of n inputs, each m bits wide
template OrMultiple(n, m) {
signal input inputs[n][m];
signal output out[m];

signal mids[n][m];
mids[0] <== inputs[0];

component ors[n-1];
for(var i=0; i<n-1; i++) {
ors[i] = BitwiseOr(m);
ors[i].a <== mids[i];
ors[i].b <== inputs[i+1];
mids[i+1] <== ors[i].out;
}

out <== mids[n-1];
}

// compute the XOR of n inputs, each m bits wide
template XorMultiple(n, m) {
signal input inputs[n][m];
signal output out[m];

signal mids[n][m];
mids[0] <== inputs[0];

component xors[n-1];
for(var i=0; i<n-1; i++) {
xors[i] = BitwiseXor(m);
xors[i].a <== mids[i];
xors[i].b <== inputs[i+1];
mids[i+1] <== xors[i].out;
}

out <== mids[n-1];
}

// select the index-th element from an array of total elements
// via the argument:
// Sum_0^n (IsEqual(index, i) * in[i])
template IndexSelector(total) {
signal input in[total];
signal input index;
Expand All @@ -249,6 +300,7 @@ template IndexSelector(total) {
out <== calcTotal.sum;
}

// reverse the bit order in an n-bit array
template ReverseBitsArray(n) {
signal input in[n];
signal output out[n];
Expand Down
8 changes: 8 additions & 0 deletions circuits/aes-gcm/mul.circom
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ pragma circom 2.1.9;

include "helper_functions.circom";

template Mul64()
{
signal input src1[64];
signal input src2[64];
signal output out[64];

}

template Mul()
{
signal input src1[64];
Expand Down
53 changes: 53 additions & 0 deletions circuits/test/gfmul.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { assert } from "chai";
import { WitnessTester } from "circomkit";
import { bitArrayToHex, circomkit, hexToBitArray } from "./common";

const X = hexToBitArray("0x0100000000000000");
const Y = hexToBitArray("0x0100000000000000");
const EXPECT = "";

describe("BMUL64", () => {
let circuit: WitnessTester<["x", "y"], ["out"]>;

before(async () => {
circuit = await circomkit.WitnessTester(`BMUL64`, {
file: "aes-gcm/gfmul",
template: "BMUL64",
// params: [8],
});
});

// let bit_array = [1,0,0,0,0,0,0,0];
// let expected_output = [0,0,0,0,0,0,0,1].map((x) => BigInt(x));
it("bmul64", async () => {
const _res = await circuit.compute({ x: X, y: Y }, ["out"]);
// const result = bitArrayToHex(
// (_res.out as number[]).map((bit) => Number(bit))
// ).slice(0, 32);
// console.log("expect: ", EXPECT, "\nresult: ", result);
// assert.equal(result, EXPECT);
});
});

describe("MUL", () => {
let circuit: WitnessTester<["h", "rhs"], ["out"]>;

before(async () => {
circuit = await circomkit.WitnessTester(`MUL64`, {
file: "aes-gcm/gfmul",
template: "MUL",
// params: [8],
});
});

// let bit_array = [1,0,0,0,0,0,0,0];
// let expected_output = [0,0,0,0,0,0,0,1].map((x) => BigInt(x));
it("mul", async () => {
const _res = await circuit.compute({ h: X, rhs: Y }, ["out"]);
// const result = bitArrayToHex(
// (_res.out as number[]).map((bit) => Number(bit))
// ).slice(0, 32);
// console.log("expect: ", EXPECT, "\nresult: ", result);
// assert.equal(result, EXPECT);
});
});