diff --git a/README.md b/README.md index 3ffc81a..503a4b3 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ just install ## Usage -### Generate AES witnsess values +### Generate AES witness values Generate json witnesses and an AES proof to populate the `inputs` dir: `just witness`. ### Testing diff --git a/circuits/aes-gcm/ghash.circom b/circuits/aes-gcm/ghash.circom index cb8b21b..7be2382 100644 --- a/circuits/aes-gcm/ghash.circom +++ b/circuits/aes-gcm/ghash.circom @@ -1,6 +1,6 @@ pragma circom 2.1.9; -include "polyval_gfmul.circom"; -include "gfmulx.circom"; +include "helper_functions.circom"; +include "nistgmul.circom"; // GHASH computes the authentication tag for AES-GCM. // Inputs: @@ -34,97 +34,44 @@ include "gfmulx.circom"; // └─────────┘ └─────────┘ └─────────┘ // template GHASH(NUM_BLOCKS) { - signal input HashKey[4][4]; // Hash subkey (128 bits) - signal input msg[NUM_BLOCKS][4][4]; // Input blocks (each 128 bits) - signal output tag[128]; // Output tag (128 bits) - // signal output tag[2][64]; // Output tag (128 bits) - - // Janky convert [4][4] block into [2][64] bit lists - // TODO: Double check the endianness of this conversion. - signal hashBits[2][64]; - for(var i = 0; i < 4; i++) { - for(var j = 0; j < 4; j++) { - var bit = 1; - var lc = 0; - for(var k = 0; k < 8; k++) { - var bitIndex = (i*4*8)+(j*8)+k; - var bitValue = (HashKey[i][j] >> k) & 1; - var rowIndex = bitIndex\64; - var colIndex = bitIndex%64; - hashBits[rowIndex][colIndex] <-- bitValue; - hashBits[rowIndex][colIndex] * (hashBits[rowIndex][colIndex] - 1) === 0; - lc += hashBits[rowIndex][colIndex] * bit; - bit = bit+bit; - } - HashKey[i][j] === lc; - } - } - - signal msgBits[NUM_BLOCKS][2][64]; - for(var i = 0; i < NUM_BLOCKS; i++) { - for(var j = 0; j < 4; j++) { - for(var k=0; k < 4; k++) { - var bit = 1; - var lc = 0; - for(var l = 0; l < 8; l++) { - var bitIndex = (j*4*8)+(k*8)+l; - var bitValue = (msg[i][j][k] >> l) & 1; - var rowIndex = bitIndex\64; - var colIndex = bitIndex%64; - msgBits[i][rowIndex][colIndex] <-- bitValue; - msgBits[i][rowIndex][colIndex] * (msgBits[i][rowIndex][colIndex] - 1) === 0; - lc += msgBits[i][rowIndex][colIndex] * bit; - bit = bit+bit; - } - msg[i][j][k] === lc; - } - } - } + signal input HashKey[16]; // Hash subkey (128 bits) + signal input msg[NUM_BLOCKS][16]; // Input blocks (each 128 bits) + signal output tag[16]; // Output tag (128 bits) // Intermediate tags - signal intermediate[NUM_BLOCKS][2][64]; + signal intermediate[NUM_BLOCKS+1][16]; - // Initialize first intermediate to zero - for (var j = 0; j < 64; j++) { - intermediate[0][0][j] <== 0; - intermediate[0][1][j] <== 0; + // Initialize first intermediate block to zero + for (var j = 0; j < 16; j++) { + intermediate[0][j] <== 0; } // Initialize components // two 64bit xor components for each block - component xor[NUM_BLOCKS][2]; + component xor[NUM_BLOCKS]; // one gfmul component for each block component gfmul[NUM_BLOCKS]; // Accumulate each block using GHASH multiplication - for (var i = 1; i < NUM_BLOCKS; i++) { - xor[i][0] = BitwiseXor(64); - xor[i][1] = BitwiseXor(64); - gfmul[i] = POLYVAL_GFMUL(); // TODO(TK 2024-09-18): Update this for ghash + for (var i = 0; i < NUM_BLOCKS; i++) { + xor[i] = XORBLOCK(16); + gfmul[i] = NistGMulByte(); // XOR current block with the previous intermediate result - // note: intermediate[0] is initialized to zero, so all rounds are valid - xor[i][0].a <== intermediate[i-1][0]; - xor[i][1].a <== intermediate[i-1][1]; - xor[i][0].b <== msgBits[i][0]; - xor[i][1].b <== msgBits[i][1]; + xor[i].a <== intermediate[i]; + xor[i].b <== msg[i]; // Multiply the XOR result with the hash subkey H - gfmul[i].a[0] <== xor[i][0].out; - gfmul[i].a[1] <== xor[i][1].out; - gfmul[i].b <== hashBits; + gfmul[i].X <== HashKey; + gfmul[i].Y <== xor[i].out; // Store the result in the next intermediate tag - intermediate[i][0] <== gfmul[i].out[0]; - intermediate[i][1] <== gfmul[i].out[1]; + intermediate[i+1] <== gfmul[i].out; } - // Assign the final tag - for (var j = 0; j < 64; j++) { - tag[j] <== intermediate[NUM_BLOCKS-1][0][j]; - tag[j+64] <== intermediate[NUM_BLOCKS-1][1][j]; - } - // tag[0] <== intermediate[NUM_BLOCKS-1][0]; - // tag[1] <== intermediate[NUM_BLOCKS-1][1]; + + // Final tag is the last intermediate block + tag <== intermediate[NUM_BLOCKS]; + } diff --git a/circuits/aes-gcm/nistgmul.circom b/circuits/aes-gcm/nistgmul.circom new file mode 100644 index 0000000..1bb4b8b --- /dev/null +++ b/circuits/aes-gcm/nistgmul.circom @@ -0,0 +1,268 @@ +pragma circom 2.1.9; + +include "utils.circom"; // xor +include "circomlib/circuits/comparators.circom"; // isZero +include "helper_functions.circom"; // bitwise right shift +include "circomlib/circuits/mux1.circom"; // multiplexer +include "../aes-ctr/utils.circom"; // xorbyte + +// Algorithm 1: X •Y +// Input: +// blocks X, Y. +// Output: +// block X •Y. +// multiplication of two blocks in the binary extension field defined by the irreducible polynomial +// 1 + X + X^2 + X^7 + X^128 +// computes a “product” block, denoted X •Y +template NistGMulByte() { + + signal input X[16]; + signal input Y[16]; + signal output out[16]; + + // Let R be the bit string 11100001 || 0120. Given two blocks X and Y + // byte 0xE1 is 11100001 in binary + // var R[16] = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + // 1. Let x0, x1...x127 denote the sequence of bits in X. + // 2. Let Z0 = 0128 and V0 = Y. + signal Z[129][16]; + Z[0] <== [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + /// State accumulator. ie. V[i] is V0 holding 16 bytes + signal V[129][16]; + V[0] <== Y; + // ⎧ Zi if xi = 0; + // Zi+1 ⎨ + // ⎩ Zi ⊕Vi if xi =1. + // + // The V update is actually just gmulx (multiply binary polynomial by x) + // + // ⎧ Vi >>1 if LSB1(Vi) = 0; + // Vi+1 ⎨ + // ⎩ (Vi >>1) ⊕ R if LSB1(Vi) =1. + // + component bit[16]; + component z_i_update[128]; + component mulx[128]; + component bytesToBits = BytesToBits(16); + bytesToBits.in <== X; + signal bitsX[16*8]; + bitsX <== bytesToBits.out; + for (var i = 0; i < 128; i++) { + // log("i*8 + j", i*8 + j); + // z_i_update + z_i_update[i] = Z_I_UPDATE(16); + z_i_update[i].Z <== Z[i]; + z_i_update[i].V <== V[i]; + z_i_update[i].bit_val <== bitsX[i]; + Z[i + 1] <== z_i_update[i].Z_new; + + // mulx to update V + mulx[i] = Mulx(16); + mulx[i].in <== V[i]; + V[i + 1] <== mulx[i].out; + } + // 4. Return Z128. + out <== Z[128]; +} + +// mul on 1 byte to debug by hand +template debug_1_byte() { + signal input X[1]; + signal input Y[1]; + signal output out[1]; + + signal bitsX[8]; + signal Z[9][1]; + signal V[9][1]; + Z[0] <== [0x00]; + V[0] <== Y; + + component z_i_update[8]; + component mulx[8]; + component bytesToBits = BytesToBits(1); + bytesToBits.in <== X; + bitsX <== bytesToBits.out; + for (var i = 0; i < 8; i++) { + log("i", i); + log("bitsX[i]", bitsX[i]); + z_i_update[i] = Z_I_UPDATE(1); + z_i_update[i].Z <== Z[i]; + z_i_update[i].V <== V[i]; + z_i_update[i].bit_val <== bitsX[i]; + Z[i + 1] <== z_i_update[i].Z_new; + + // mulx to update V + mulx[i] = Mulx(1); + mulx[i].in <== V[i]; + V[i + 1] <== mulx[i].out; + + log("V[i]", V[i][0]); + } + out <== Z[8]; +} + +// mul on 2 bytes to debug by hand +template debug_2_bytes() { + signal input X[2]; + signal input Y[2]; + signal output out[2]; + + signal bitsX[16]; + signal Z[17][2]; + signal V[17][2]; + Z[0] <== [0x00, 0x00]; + V[0] <== Y; + + component z_i_update[16]; + component mulx[16]; + component bytesToBits = BytesToBits(2); + bytesToBits.in <== X; + bitsX <== bytesToBits.out; + for (var i = 0; i < 16; i++) { + log("i", i); + log("bitsX[i]", bitsX[i]); + z_i_update[i] = Z_I_UPDATE(2); + z_i_update[i].Z <== Z[i]; + z_i_update[i].V <== V[i]; + z_i_update[i].bit_val <== bitsX[i]; + Z[i + 1] <== z_i_update[i].Z_new; + + // mulx to update V + mulx[i] = Mulx(2); + mulx[i].in <== V[i]; + V[i + 1] <== mulx[i].out; + + log("V[i][0]", V[i][0]); + log("V[i][1]", V[i][1]); + } + + out <== Z[16]; +} + +// if bit value is 0, then Z_new = Z +// if bit value is 1, then Z_new = Z xor V +template Z_I_UPDATE(n_bytes) { + signal input Z[n_bytes]; // this is Zero block in first itteration + signal input V[n_bytes]; // this is Y in first itteration + signal input bit_val; + signal output Z_new[n_bytes]; + + component mux = ArrayMux(n_bytes); + mux.sel <== bit_val; + mux.a <== Z; + component xorBlock = XORBLOCK(n_bytes); + xorBlock.a <== Z; + xorBlock.b <== V; + mux.b <== xorBlock.out; + Z_new <== mux.out; +} + +// multiplexer for arrays of length n +template ArrayMux(n) { + signal input a[n]; // First input array + signal input b[n]; // Second input array + signal input sel; // Selector signal (0 or 1) + signal output out[n]; // Output array + + for (var i = 0; i < n; i++) { + // If sel = 0, out[i] = a[i] + // If sel = 1, out[i] = b[i] + out[i] <== (b[i] - a[i]) * sel + a[i]; + } +} + +// XOR 16 bytes +template XORBLOCK(n_bytes){ + signal input a[n_bytes]; + signal input b[n_bytes]; + signal output out[n_bytes]; + + component xorByte[n_bytes]; + for (var i = 0; i < n_bytes; i++) { + xorByte[i] = XorByte(); + xorByte[i].a <== a[i]; + xorByte[i].b <== b[i]; + out[i] <== xorByte[i].out; + } +} + +// right shift by one bit. If msb is 1: +// then we xor the first byte with 0xE1 (11100001: 1 + X + X^2 + X^7) +// this is the irreducible polynomial used in AES-GCM +template Mulx(n_bytes) { + signal input in[n_bytes]; + signal output out[n_bytes]; + + signal intermediate[n_bytes]; + + component blockRightShift = BlockRightShift(n_bytes); + blockRightShift.in <== in; + intermediate <== blockRightShift.out; + + component xorByte = XorByte(); + xorByte.a <== intermediate[0]; + xorByte.b <== 0xE1; // 11100001 + + // if msb is 1, then we xor the first byte with R[0] + component mux = Mux1(); + mux.s <== blockRightShift.msb; + mux.c[0] <== intermediate[0]; + mux.c[1] <== xorByte.out; + + for (var i = 1; i < n_bytes; i++) { + out[i] <== intermediate[i]; + } + out[0] <== mux.out; +} + +// right shifts 16 bytes by one bit and returns the msb before the shift +template BlockRightShift(n_bytes) { + signal input in[n_bytes]; + signal output out[n_bytes]; + signal output msb; + + signal shiftedbits[n_bytes*8]; + component bytesToBits = BytesToBits(n_bytes); + for (var i = 0; i < n_bytes; i++) { + bytesToBits.in[i] <== in[i]; + } + msb <== bytesToBits.out[n_bytes*8 - 1]; + + component BitwiseRightShift = BitwiseRightShift(n_bytes*8, 1); + BitwiseRightShift.in <== bytesToBits.out; + shiftedbits <== BitwiseRightShift.out; + + component bitsToBytes = BitsToBytes(n_bytes); + bitsToBytes.in <== shiftedbits; + out <== bitsToBytes.out; +} + +// n is the number of bytes to convert to bits +template BytesToBits(n_bytes) { + signal input in[n_bytes]; + signal output out[n_bytes*8]; + component num2bits[n_bytes]; + for (var i = 0; i < n_bytes; i++) { + num2bits[i] = Num2Bits(8); + num2bits[i].in <== in[i]; + for (var j = 7; j >=0; j--) { + out[i*8 + j] <== num2bits[i].out[7 -j]; + } + } +} + +// n is the number of bytes we want +template BitsToBytes(n) { + signal input in[n*8]; + signal output out[n]; + component bits2num[n]; + for (var i = 0; i < n; i++) { + bits2num[i] = Bits2Num(8); + for (var j = 0; j < 8; j++) { + bits2num[i].in[7 - j] <== in[i*8 + j]; + } + out[i] <== bits2num[i].out; + } +} + diff --git a/circuits/test/common/index.ts b/circuits/test/common/index.ts index 800bd39..3be5ea8 100644 --- a/circuits/test/common/index.ts +++ b/circuits/test/common/index.ts @@ -85,6 +85,27 @@ export function padArrayTo64Bits(array: number[]): number[] { return new Array(64 - array.length).fill(0).concat(array); } +export function hexByteToBigInt(hexByte: string): bigint { + if (typeof hexByte !== 'string') { + throw new TypeError('Input must be a string'); + } + + // Remove '0x' prefix if present and ensure lowercase + hexByte = hexByte.replace(/^0x/i, "").toLowerCase(); + + if (hexByte.length !== 2) { + throw new Error('Input must be exactly one byte (2 hex characters)'); + } + + const byte = parseInt(hexByte, 16); + + if (isNaN(byte)) { + throw new Error('Invalid hex byte'); + } + + return BigInt(byte); + } + export function numberTo16Hex(num: number): string { // Convert the number to a hexadecimal string let hexString = num.toString(16); @@ -127,3 +148,28 @@ it("tests bitArrayToHexString", async () => { result = bitArrayToHex(bits); assert.equal(result, expectedHex); }); + +export function hexStringToByteArray(hexString: string): number[] { + // Ensure the string has an even number of characters + if (hexString.length % 2 !== 0) { + throw new Error('Hex string must have an even number of characters'); + } + + const byteArray: number[] = []; + + for (let i = 0; i < hexString.length; i += 2) { + const byteValue = parseInt(hexString.substr(i, 2), 16); + if (isNaN(byteValue)) { + throw new Error('Invalid hex string'); + } + byteArray.push(byteValue); + } + + return byteArray; +} + +export function byteArrayToHex(byteArray: any) { + return Array.from(byteArray, (byte: any) => { + return ('0' + (byte & 0xFF).toString(16)).slice(-2); + }).join(''); +} \ No newline at end of file diff --git a/circuits/test/gfmulint/nistgfmul.test.ts b/circuits/test/gfmulint/nistgfmul.test.ts new file mode 100644 index 0000000..f0a7ea4 --- /dev/null +++ b/circuits/test/gfmulint/nistgfmul.test.ts @@ -0,0 +1,336 @@ +import { assert } from "chai"; +import { WitnessTester } from "circomkit"; +import { circomkit, hexToBitArray, hexByteToBigInt, hexStringToByteArray, byteArrayToHex } from "../common"; + + +describe("NistGMulByte", () => { + let circuit: WitnessTester<["X", "Y"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("nistgfmul", { + file: "aes-gcm/nistgmul", + template: "NistGMulByte", + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("Should Compute NistGMulByte Correctly", async () => { + + let X = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let Y = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + const expected = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + await circuit.expectPass({ X: X, Y: Y }, { out: expected }); + }); + + it("Should Compute NistGMulByte of LSB=1 Correctly", async () => { + + let X = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + let Y = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + + const expected = [0xe6, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03]; + await circuit.expectPass({ X: X, Y: Y }, { out: expected }); + }); + it("Should Compute NistGMulByte of LSB=1 Correctly", async () => { + + // x = "aae06992acbf52a3e8f4a96ec9300bd7" + // y = "98e7247c07f0fe411c267e4384b0f600" + // expected = "90e87315fb7d4e1b4092ec0cbfda5d7d" + let X = [0xaa, 0xe0, 0x69, 0x92, 0xac, 0xbf, 0x52, 0xa3, 0xe8, 0xf4, 0xa9, 0x6e, 0xc9, 0x30, 0x0b, 0xd7]; + let Y = [0x98, 0xe7, 0x24, 0x7c, 0x07, 0xf0, 0xfe, 0x41, 0x1c, 0x26, 0x7e, 0x43, 0x84, 0xb0, 0xf6, 0x00]; + + const expected = [0x90, 0xe8, 0x73, 0x15, 0xfb, 0x7d, 0x4e, 0x1b, 0x40, 0x92, 0xec, 0x0c, 0xbf, 0xda, 0x5d, 0x7d]; + await circuit.expectPass({ X: X, Y: Y }, { out: expected }); + }); + +}); + +describe("debug1", () => { + let circuit: WitnessTester<["X", "Y"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("debug1", { + file: "aes-gcm/nistgmul", + template: "debug_1_byte", + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("Should Compute Correctly", async () => { + let inputX = [0x80]; + let inputY = [0x80]; + const expected = [0x80]; + console.log("expected", expected); + const _res = await circuit.expectPass({ X: inputX, Y: inputY }, { out: expected }); + }); + it("Should Compute Correctly", async () => { + let inputX = [0x01]; + let inputY = [0x01]; + + const expected = [0x5E]; + console.log("expected", expected); + await circuit.expectPass({ X: inputX, Y: inputY }, { out: expected }); + }); +}); + +describe("debug2", () => { + let circuit: WitnessTester<["X", "Y"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("debug1", { + file: "aes-gcm/nistgmul", + template: "debug_2_bytes", + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("Should Compute Correctly", async () => { + let inputX = [0x80, 0x00]; + let inputY = [0x80, 0x00]; + const expected = [0x80, 0x00]; + console.log("expected", expected); + const _res = await circuit.expectPass({ X: inputX, Y: inputY }, { out: expected }); + }); + it("Should Compute Correctly", async () => { + let inputX = [0x00, 0x01]; + let inputY = [0x00, 0x01]; + + const expected = [0xE6, 0x0B]; + console.log("expected", expected); + await circuit.expectPass({ X: inputX, Y: inputY }, { out: expected }); + }); +}); + +describe("ToBits", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("bytesToBits", { + file: "aes-gcm/nistgmul", + template: "BytesToBits", + params: [1] + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("Should Compute bytesToBits Correctly", async () => { + let input = 0x01; + const expected = hexToBitArray("0x01"); + console.log("expected", expected); + const _res = await circuit.expectPass({ in: input }, { out: expected }); + }); + it("Should Compute bytesToBits Correctly", async () => { + let input = 0xFF; + const expected = hexToBitArray("0xFF"); + console.log("expected", expected); + const _res = await circuit.expectPass({ in: input }, { out: expected }); + }); +}); +describe("ToBits", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("bytesToBits", { + file: "aes-gcm/nistgmul", + template: "BytesToBits", + params: [2] + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("Should Compute bytesToBits Correctly", async () => { + let input = [0x01, 0x00]; + const expected = hexToBitArray("0x0100"); + console.log("expected", expected); + const _res = await circuit.expectPass({ in: input }, { out: expected }); + }); + it("Should Compute bytesToBits Correctly", async () => { + let input = [0xFF, 0x00]; + const expected = hexToBitArray("0xFF00"); + console.log("expected", expected); + const _res = await circuit.expectPass({ in: input }, { out: expected }); + }); +}); + +describe("ToBytes", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("bytesToBits", { + file: "aes-gcm/nistgmul", + template: "BitsToBytes", + params: [1] + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("Should Compute bytesToBits Correctly", async () => { + let input = hexToBitArray("0x01"); + const expected = hexByteToBigInt("0x01"); + console.log("expected", expected); + const _res = await circuit.compute({ in: input }, ["out"]); + console.log("res:", _res.out); + assert.deepEqual(_res.out, expected); + }); + it("Should Compute bytesToBits Correctly", async () => { + let input = hexToBitArray("0xFF"); + const expected = hexByteToBigInt("0xFF"); + console.log("expected", expected); + const _res = await circuit.compute({ in: input }, ["out"]); + console.log("res:", _res.out); + assert.deepEqual(_res.out, expected); + }); +}); + + +describe("intrightshift", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("intrightshift", { + file: "aes-gcm/helper_functions", + template: "IntRightShift", + params: [8, 1] + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("Should Compute IntRightShift Correctly", async () => { + let input = 0x02; // little endian hex vectors + const expected = hexByteToBigInt("0x01"); + const _res = await circuit.compute({ in: input }, ["out"]); + console.log("res:", _res.out); + assert.deepEqual(_res.out, expected); + }); + + it("Should Compute IntRightShift Correctly", async () => { + let input = 0x04; // little endian hex vectors + const expected = hexByteToBigInt("0x02"); + const _res = await circuit.compute({ in: input }, ["out"]); + console.log("res:", _res.out); + assert.deepEqual(_res.out, expected); + }); + }); + + +describe("BlockRightShift", () => { + let circuit: WitnessTester<["in"], ["out", "msb"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("BlockRightShift", { + file: "aes-gcm/nistgmul", + template: "BlockRightShift", + params: [16] + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("Should Compute BlockRightShift Correctly", async () => { + let input = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + const expected = [0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + await circuit.expectPass({ in: input }, { out: expected, msb: 0 }); + }); + it("Should Compute BlockRightShift Correctly", async () => { + let input = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + const expected = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + await circuit.expectPass({ in: input }, { out: expected, msb: 1 }); + }); +}); + +describe("Mulx", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("Mulx", { + file: "aes-gcm/nistgmul", + template: "Mulx", + params: [16] + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + // msb is 1 so we xor the first byte with 0xE1 + it("Should Compute Mulx Correctly", async () => { + let input = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + const expected = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + await circuit.expectPass({ in: input }, { out: expected }); + }); +}); + +describe("XORBLOCK", () => { + let circuit: WitnessTester<["a", "b"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("XORBLOCK", { + file: "aes-gcm/nistgmul", + template: "XORBLOCK", + params: [16] + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + // msb is 1 so we xor the first byte with 0xE1 + it("Should Compute block XOR Correctly", async () => { + let inputa = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let inputb = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + const expected = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + await circuit.expectPass({ a: inputa, b: inputb }, { out: expected }); + }); +}); +describe("ArrayMux", () => { + let circuit: WitnessTester<["a", "b", "sel"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("XORBLOCK", { + file: "aes-gcm/nistgmul", + template: "ArrayMux", + params: [16] + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + // msb is 1 so we xor the first byte with 0xE1 + it("Should Compute selector mux Correctly", async () => { + let a= [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let b = [0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + let sel = 0x00; + let expected= [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + await circuit.expectPass({ a: a, b: b, sel: sel }, { out: expected }); + }); + + it("Should Compute block XOR Correctly", async () => { + let a= [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let b = [0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + let sel = 0x01; + let expected= [0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + await circuit.expectPass({ a: a, b: b, sel: sel }, { out: expected }); + }); + +}); + + +describe("Z_I_UPDATE", () => { + let circuit: WitnessTester<["Z", "V", "bit_val"], ["Z_new"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("XORBLOCK", { + file: "aes-gcm/nistgmul", + template: "Z_I_UPDATE", + params: [16] + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + // msb is 1 so we xor the first byte with 0xE1 + it("Should Compute block XOR Correctly", async () => { + let inputZ= [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let inputV = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + let inputc = 0x00; + let expected= [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + await circuit.expectPass({ Z: inputZ, V: inputV, bit_val: inputc }, { Z_new: expected }); + }); + + it("Should Compute block XOR Correctly", async () => { + let inputa = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let inputb = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + let inputc = 0x01; + const expected = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + await circuit.expectPass({ Z: inputa, V: inputb, bit_val: inputc }, { Z_new: expected }); + }); +}); \ No newline at end of file diff --git a/circuits/test/hashes/ghash.test.ts b/circuits/test/hashes/ghash.test.ts index c723aa0..eb29984 100644 --- a/circuits/test/hashes/ghash.test.ts +++ b/circuits/test/hashes/ghash.test.ts @@ -2,12 +2,7 @@ import { WitnessTester } from "circomkit"; import { bitArrayToHex, circomkit, hexToBitArray } from "../common"; import { assert } from "chai"; -// https://datatracker.ietf.org/doc/html/rfc8452#appendix-A -const H = hexToBitArray("25629347589242761d31f826ba4b757b"); -const X1 = "4f4f95668c83dfb6401762bb2d01a262"; -const X2 = "d1a24ddd2721d006bbe45f20d3c9f362"; -const M = hexToBitArray(X1.concat(X2)); -const EXPECT = hexToBitArray("bd9b3997046731fb96251b91f9c99d7a"); + describe("GHASH_HASH", () => { let circuit: WitnessTester<["HashKey", "msg"], ["tag"]>; @@ -22,11 +17,13 @@ describe("GHASH_HASH", () => { }); it("test ghash", async () => { - const input = { HashKey: H, msg: M }; - console.log("input message length: ", input.msg.length); - console.log("input hash key length: ", input.HashKey.length); - console.log("input message: ", EXPECT); - const _res = await circuit.expectPass(input, { tag: EXPECT }); + // https://datatracker.ietf.org/doc/html/rfc8452#appendix-A + const H = [0x25, 0x62, 0x93, 0x47, 0x58, 0x92, 0x42, 0x76, 0x1d, 0x31, 0xf8, 0x26, 0xba, 0x4b, 0x75, 0x7b]; + const X1 = [0x4f, 0x4f, 0x95, 0x66, 0x8c, 0x83, 0xdf, 0xb6, 0x40, 0x17, 0x62, 0xbb, 0x2d, 0x01, 0xa2, 0x62]; + const X2 = [0xd1, 0xa2, 0x4d, 0xdd, 0x27, 0x21, 0xd0, 0x06, 0xbb, 0xe4, 0x5f, 0x20, 0xd3, 0xc9, 0xf3, 0x62]; + const M = X1.concat(X2); + const EXPECT = [0xbd, 0x9b, 0x39, 0x97, 0x04, 0x67, 0x31, 0xfb, 0x96, 0x25, 0x1b, 0x91, 0xf9, 0xc9, 0x9d, 0x7a]; + const _res = await circuit.expectPass({ HashKey: H, msg: M }, { tag: EXPECT }); }); }); diff --git a/nistgmul.circom b/nistgmul.circom new file mode 100644 index 0000000..8e49be9 --- /dev/null +++ b/nistgmul.circom @@ -0,0 +1,12 @@ +template BytesToBits(n) { + signal input in[n]; + signal output out[n*8]; + component num2bits[n]; + for (var i = 0; i < n; i++) { + // ... existing code ... + for (var j = 0; j < 8; j++) { + // Reverse the bit order within each byte + out[i*8 + (7 - j)] <== num2bits[i].out[j]; + } + } +} \ No newline at end of file