diff --git a/circuits/aes-gcm/ghash.circom b/circuits/aes-gcm/ghash.circom index e497d27..39546d2 100644 --- a/circuits/aes-gcm/ghash.circom +++ b/circuits/aes-gcm/ghash.circom @@ -1,6 +1,6 @@ pragma circom 2.1.9; -include "gfmul_int.circom"; +include "ghash_gfmul_int.circom"; include "helper_functions.circom"; // GHASH computes the authentication tag for AES-GCM. diff --git a/circuits/aes-gcm/gfmul_int.circom b/circuits/aes-gcm/ghash_gfmul_int.circom similarity index 100% rename from circuits/aes-gcm/gfmul_int.circom rename to circuits/aes-gcm/ghash_gfmul_int.circom diff --git a/circuits/aes-gcm/helper_functions.circom b/circuits/aes-gcm/helper_functions.circom index 7424b5c..c6efb16 100644 --- a/circuits/aes-gcm/helper_functions.circom +++ b/circuits/aes-gcm/helper_functions.circom @@ -257,3 +257,75 @@ template ReverseBitsArray(n) { out[i] <== in[n-i-1]; } } + +// todo: check bit settings +// compute x * n over polyval polynomial +// if the msb of in is 1, +// compute in << 1 and xor with 127, 126, 121, 1 +template mulX_ghash() { + signal input in[128]; + signal output out[128]; + + // v = in left-shifted by 1 + signal v[128]; + // v_xor = 0 if in[0] is 0, or the irreducible poly if in[0] is 1 + signal v_xor[128]; + + // initialize v and v_xor. + v[127] <== 0; + v_xor[127] <== in[0]; + + for (var i=126; i>=0; i--) { + v[i] <== in[i+1]; + + // XOR with polynomial if MSB is 1 + // v_xor has 1s at positions 127, 126, 121, 1 + if (i==0 || i == 1 || i == 6) { + v_xor[i] <== in[0]; + } else { + v_xor[i] <== 0; + } + } + + // compute out + component xor = BitwiseXor(128); + xor.a <== v; + xor.b <== v_xor; + out <== xor.out; +} + +// todo: check bit settings +// compute x * n over polyval polynomial +// if the msb of in is 1, +// compute in << 1 and xor with 127, 126, 121, 1 +template mulX_polyval() { + signal input in[128]; + signal output out[128]; + + // v = in left-shifted by 1 + signal v[128]; + // v_xor = 0 if in[0] is 0, or the irreducible poly if in[0] is 1 + signal v_xor[128]; + + // initialize v and v_xor. + v[127] <== 0; + v_xor[127] <== in[0]; + + for (var i=126; i>=0; i--) { + v[i] <== in[i+1]; + + // polyval: v_xor at positions 127, 7, 2, 1 + if (i == 0 || i == 1 || i == 7) { + // ghash: v_xor at positions 127, 126, 121, 1 + // if (i==121 || i == 126 || i == 127) { + v_xor[i] <== in[0]; + } else { + v_xor[i] <== 0; + } + } + + component xor = BitwiseXor(128); + xor.a <== v; + xor.b <== v_xor; + out <== xor.out; +} \ No newline at end of file diff --git a/circuits/aes-gcm/polyval.circom b/circuits/aes-gcm/polyval.circom index cb63743..6277c8f 100644 --- a/circuits/aes-gcm/polyval.circom +++ b/circuits/aes-gcm/polyval.circom @@ -1,6 +1,6 @@ pragma circom 2.1.9; -include "gfmul_int.circom"; +include "polyval_gfmul_int.circom"; include "helper_functions.circom"; template POLYVAL(n_bits) diff --git a/circuits/aes-gcm/polyval_gfmul_int.circom b/circuits/aes-gcm/polyval_gfmul_int.circom new file mode 100644 index 0000000..bea885c --- /dev/null +++ b/circuits/aes-gcm/polyval_gfmul_int.circom @@ -0,0 +1,192 @@ +pragma circom 2.1.9; + +include "vclmul_emulator.circom"; +include "helper_functions.circom"; + +// GFMULInt multiplies two 128-bit numbers in GF(2^128) and returns the result. +// +// Inputs: +// - `a` the first 128-bit number, encoded as a polynomial over GF(2^128) +// - `b` the second 128-bit number, encoded as a polynomial over GF(2^128) +// +// Outputs: +// - `res` the result of the multiplication +// +// Computes: +// res = a * b mod p +// where p = x^128 + x^7 + x^2 + x + 1 +template GFMULInt() +{ + signal input a[2][64]; + signal input b[2][64]; + signal output res[2][64]; + + + // TODO(TK 2024-08-10): note about magic nuhmebr + var tmp[5][2][64]; + + // XMMMask is x^128 + x^7 + x^2 + x + 1 + // GHASH is big endian, polyval is little endian + // bitwise: 11100001 .... 00000001 + // + // 0x87 = 0b1000_0111 => encode x^7 + x^2 + x + 1 + var XMMMASK[2] = [0x87, 0x0]; + + var i, j, k; + + log("GFMULInt"); + log(a[0][0]); + // log(b); + // log("res", res); + + component num2bits_1[2]; + var XMMMASK_bits[2][64]; + for(i=0; i<2; i++) + { + num2bits_1[i] = Num2Bits(64); + num2bits_1[i].in <== XMMMASK[i]; + for(j=0; j<8; j++) + { + for(k=0; k<8; k++) XMMMASK_bits[i][j*8+k] = num2bits_1[i].out[j*8+7-k]; + } + } + + component vclmul_emulator_1[4]; + + for(i=0; i<4; i++) + { + vclmul_emulator_1[i] = VCLMULEmulator(i); + for(j=0; j<2; j++) + { + for(k=0; k<64; k++) + { + vclmul_emulator_1[i].src1[j][k] <== a[j][k]; + vclmul_emulator_1[i].src2[j][k] <== b[j][k]; + } + } + + tmp[i+1] = vclmul_emulator_1[i].destination; + } + + component xor_1[2][64]; + for(i=0; i<2; i++) + { + for(j=0; j<64; j++) + { + xor_1[i][j] = XOR(); + xor_1[i][j].a <== tmp[2][i][j]; + xor_1[i][j].b <== tmp[3][i][j]; + + tmp[2][i][j] = xor_1[i][j].out; + } + } + + for(i=0; i<64; i++) tmp[3][0][i] = 0; + + for(i=0; i<64; i++) tmp[3][1][i] = tmp[2][0][i]; + + for(i=0; i<64; i++) tmp[2][0][i] = tmp[2][1][i]; + + for(i=0; i<64; i++) tmp[2][1][i] = 0; + + component xor_2[2][64]; + for(i=0; i<2; i++) + { + for(j=0; j<64; j++) + { + xor_2[i][j] = XOR(); + xor_2[i][j].a <== tmp[1][i][j]; + xor_2[i][j].b <== tmp[3][i][j]; + + tmp[1][i][j] = xor_2[i][j].out; + } + } + + component xor_3[2][64]; + for(i=0; i<2; i++) + { + for(j=0; j<64; j++) + { + xor_3[i][j] = XOR(); + xor_3[i][j].a <== tmp[4][i][j]; + xor_3[i][j].b <== tmp[2][i][j]; + + tmp[4][i][j] = xor_3[i][j].out; + } + } + + component vclmul_emulator_2 = VCLMULEmulator(1); + for(i=0; i<2; i++) + { + for(j=0; j<64; j++) + { + vclmul_emulator_2.src1[i][j] <== XMMMASK_bits[i][j]; + vclmul_emulator_2.src2[i][j] <== tmp[1][i][j]; + } + } + + tmp[2] = vclmul_emulator_2.destination; + + for(i=0; i<64; i++) + { + tmp[3][0][i] = tmp[1][1][i]; + tmp[3][1][i] = tmp[1][0][i]; + } + + component xor_4[2][64]; + for(i=0; i<2; i++) + { + for(j=0; j<64; j++) + { + xor_4[i][j] = XOR(); + xor_4[i][j].a <== tmp[2][i][j]; + xor_4[i][j].b <== tmp[3][i][j]; + + tmp[1][i][j] = xor_4[i][j].out; + } + } + + component vclmul_emulator_3 = VCLMULEmulator(1); + for(i=0; i<2; i++) + { + for(j=0; j<64; j++) + { + vclmul_emulator_3.src1[i][j] <== XMMMASK_bits[i][j]; + vclmul_emulator_3.src2[i][j] <== tmp[1][i][j]; + } + } + + tmp[2] = vclmul_emulator_3.destination; + + for(i=0; i<64; i++) + { + tmp[3][0][i] = tmp[1][1][i]; + tmp[3][1][i] = tmp[1][0][i]; + } + + component xor_5[2][64]; + for(i=0; i<2; i++) + { + for(j=0; j<64; j++) + { + xor_5[i][j] = XOR(); + xor_5[i][j].a <== tmp[2][i][j]; + xor_5[i][j].b <== tmp[3][i][j]; + + tmp[1][i][j] = xor_5[i][j].out; + } + } + + component xor_6[2][64]; + for(i=0; i<2; i++) + { + for(j=0; j<64; j++) + { + xor_6[i][j] = XOR(); + xor_6[i][j].a <== tmp[1][i][j]; + xor_6[i][j].b <== tmp[4][i][j]; + + res[i][j] <== xor_6[i][j].out; + } + } +} \ No newline at end of file diff --git a/circuits/test/gfmulint/ghash_gfmul_int.test.ts b/circuits/test/gfmulint/ghash_gfmul_int.test.ts new file mode 100644 index 0000000..d6b8c90 --- /dev/null +++ b/circuits/test/gfmulint/ghash_gfmul_int.test.ts @@ -0,0 +1,55 @@ +import { assert } from "chai"; +import { WitnessTester } from "circomkit"; +import { circomkit } from "../common"; + +// input and output type of GFMULInt +type Arr128 = number[][]; +type _Arr128 = number[]; + +describe("mulX_polyval", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("mulX_polyval", { + file: "aes-gcm/helper_functions", + template: "mulX_polyval", + }); + }); + + it("it should compute leftshift of one", async () => { + let _res = await circuit.compute({ in: _pad_num_to_arr128(1) }, ["out"]); + // console.log(`${_res.out}`); + let res = _parse_arr128_to_number(_res.out as _Arr128); + assert.equal(res, 2); + }); + + it("it should compute leftshift of two", async () => { + let _res = await circuit.compute({ in: _pad_num_to_arr128(2) }, ["out"]); + // console.log(`${_res.out}`); + let res = _parse_arr128_to_number(_res.out as _Arr128); + assert.equal(res, 4); + }); + + it("it should compute leftshift of 127", async () => { + let input = [1, ...Array(127).fill(0)]; + let _res = await circuit.compute({ in: input }, ["out"]); + console.log(`${_res.out}`); + + // recall + // x^127 + x^126 + x^121 + 1 + let expected = [1, 1, 0, 0, 0, 0, 0, 1, ...Array(119).fill(0), 1]; + assert.equal(_res.out, expected); + }); +}); + +function _pad_num_to_arr128(value: number): _Arr128 { + return value + .toString(2) + .padStart(128, "0") + .split("") + .map((bit) => parseInt(bit, 10)); +} + +function _parse_arr128_to_number(res: _Arr128): number { + return parseInt(res.join(""), 2); +} diff --git a/circuits/test/gfmulint/gfmulint.test.ts b/circuits/test/gfmulint/polyval_gfmul_int.test.ts similarity index 97% rename from circuits/test/gfmulint/gfmulint.test.ts rename to circuits/test/gfmulint/polyval_gfmul_int.test.ts index 256986c..03c8e42 100644 --- a/circuits/test/gfmulint/gfmulint.test.ts +++ b/circuits/test/gfmulint/polyval_gfmul_int.test.ts @@ -10,7 +10,7 @@ describe("gfmulint", () => { before(async () => { circuit = await circomkit.WitnessTester("gfmulint", { - file: "aes-gcm/gfmul_int", + file: "aes-gcm/polyval_gfmul_int", template: "GFMULInt", }); console.log("#constraints:", await circuit.getConstraintCount()); diff --git a/circuits/test/ghash/ghash.test.ts b/circuits/test/hashes/ghash.test.ts similarity index 100% rename from circuits/test/ghash/ghash.test.ts rename to circuits/test/hashes/ghash.test.ts diff --git a/circuits/test/ghash/polyval.test.ts b/circuits/test/hashes/polyval.test.ts similarity index 100% rename from circuits/test/ghash/polyval.test.ts rename to circuits/test/hashes/polyval.test.ts diff --git a/package-lock.json b/package-lock.json index b9dc24e..feaa306 100644 --- a/package-lock.json +++ b/package-lock.json @@ -107,7 +107,8 @@ "version": "4.3.17", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.17.tgz", "integrity": "sha512-zmZ21EWzR71B4Sscphjief5djsLre50M6lI622OSySTmn9DB3j+C3kWroHfBQWXbOBwbgg/M8CG/hUxDLIloow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/mocha": { "version": "10.0.7",