From 287839ec40dc168c135b625c37cca1b14b4b83bf Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Wed, 14 Aug 2024 15:22:20 -0700 Subject: [PATCH 01/21] passing test for gfmulx --- circuits/aes-gcm/gfmulx.circom | 56 +++++++++++++++++++++++++++++++ circuits/test/bitreversal.test.ts | 10 ++---- circuits/test/gfmulx.test.ts | 29 ++++++++++++++++ 3 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 circuits/aes-gcm/gfmulx.circom create mode 100644 circuits/test/gfmulx.test.ts diff --git a/circuits/aes-gcm/gfmulx.circom b/circuits/aes-gcm/gfmulx.circom new file mode 100644 index 0000000..15f96b2 --- /dev/null +++ b/circuits/aes-gcm/gfmulx.circom @@ -0,0 +1,56 @@ +pragma circom 2.1.9; + +include "circomlib/circuits/gates.circom"; + +/// Multiplies a by x in GF(2^128) defined by the irreducible polynomial x^128 + x^7 + x^2 + x + 1 + +/// Small testing polynomial: x^8 + x^4 + x^3 + x + 1 +template GFMULX() { + var size = 128; + + signal input in[size]; + signal output out[size]; + signal temp[size]; + + + // Get the most significant bit of the input signal + var msb; + msb = in[0]; /// [>1<,0,0,0,0,0,0,0] + + // Left shift input by 1 into temp + for (var i = 0; i < size - 1; i++) { + temp[i] <== in[i+1]; + } + temp[size - 1] <== 0; + + component xor1 = XOR(); + component xor2 = XOR(); + component xor3 = XOR(); + component xor4 = XOR(); + + // x^128 = x^7 + x^2 + x + 1 + // XOR the input with msb * (x^7 + x^2 + x + 1) + for (var i = 0; i < size; i++) { + if (i == size - 1) { + // x^0 term + xor1.a <== temp[i]; + xor1.b <== msb; + out[i] <== xor1.out; + } else if (i == size - 2) { + // x^1 term + xor2.a <== temp[i]; + xor2.b <== msb; + out[i] <== xor2.out; + } else if (i == size - 3) { + // x^2 term + xor3.a <== temp[i]; + xor3.b <== msb; + out[i] <== xor3.out; + } else if (i == size - 8) { + // x^7 term + xor4.a <== temp[i]; + xor4.b <== msb; + out[i] <== xor4.out; + } + } +} \ No newline at end of file diff --git a/circuits/test/bitreversal.test.ts b/circuits/test/bitreversal.test.ts index f1012f7..f6a89f4 100644 --- a/circuits/test/bitreversal.test.ts +++ b/circuits/test/bitreversal.test.ts @@ -13,19 +13,13 @@ describe("bitreversal", () => { }); let bit_array = [1,0,0,0,0,0,0,0]; - let bit_array_1 = [1,0,0,0,0,0,0,1]; + let expected_output = [0,0,0,0,0,0,0,1]; it("should have correct output", async () => { - const witness = await circuit.calculateWitness({ in: bit_array }); + const witness = await circuit.expectPass({ in: bit_array}, { out: expected_output }); circuit.expectPass({in: bit_array}); // I was initially not sure what the first bit was doing in the witness, but it's just the success flag console.log("witness: from input: [1,0,0,0,0,0,0,0]", witness); }); - it("should fail with wrong input", async () => { - const witness = await circuit.calculateWitness({ in: bit_array_1 }); - circuit.expectPass({in: bit_array_1}); - - console.log("witness: from input: [1,0,0,0,0,0,0,1]", witness); - }); }); \ No newline at end of file diff --git a/circuits/test/gfmulx.test.ts b/circuits/test/gfmulx.test.ts new file mode 100644 index 0000000..6ce7f68 --- /dev/null +++ b/circuits/test/gfmulx.test.ts @@ -0,0 +1,29 @@ +import chai from "chai"; +import { WitnessTester } from "circomkit"; +import { circomkit } from "./common"; + +// Disable truncation of arrays in error messages +chai.config.truncateThreshold = 0; + +describe("GFMulX", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + describe("GF Mul X test", () => { + before(async () => { + circuit = await circomkit.WitnessTester(`gfmulx`, { + file: "aes-gcm/gfmulx", + template: "GFMULX", + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + // x^8 + x^4 + x^3 + x + 1 + it("should compute correctly 1", async () => { + // x^128 = x^7 + x^2 + x + 1 + let test = [1].concat(Array(127).fill(0)) // 128-bit array with MSB = 1 and everything else 0 + /// 10000111 + let expected_output: number[] = Array(120).fill(0).concat([1,0,0,0,0,1,1,1]) + await circuit.expectPass({in: test}, {out: expected_output}) + }); + }); +}); \ No newline at end of file From 3a4f7818a5c0a600295a35df5d674b8ac744a3a9 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Thu, 15 Aug 2024 08:38:19 -0700 Subject: [PATCH 02/21] RFC test vector fails --- circuits/test/gfmulx.test.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/circuits/test/gfmulx.test.ts b/circuits/test/gfmulx.test.ts index 6ce7f68..827ba64 100644 --- a/circuits/test/gfmulx.test.ts +++ b/circuits/test/gfmulx.test.ts @@ -25,5 +25,27 @@ describe("GFMulX", () => { let expected_output: number[] = Array(120).fill(0).concat([1,0,0,0,0,1,1,1]) await circuit.expectPass({in: test}, {out: expected_output}) }); + + it("should compute correctly 2", async () => { + /// test vector rfc 8452 appendix A.1 https://datatracker.ietf.org/doc/html/rfc8452#appendix-A + let bits = hexToBits("01000000000000000000000000000000") + let expected_output = hexToBits("00800000000000000000000000000000") + console.log(bits); + console.log(expected_output); + await circuit.expectPass({in: bits}, {out: expected_output}) + }); }); -}); \ No newline at end of file +}); + +function hexToBits(hex: string): number[] { + // Remove the '0x' prefix if present + if (hex.startsWith('0x')) { + hex = hex.slice(2); + } + + // Convert hex to binary string + const binaryString = BigInt(`0x${hex}`).toString(2).padStart(hex.length * 4, '0'); + + // Convert binary string to array of bits + return binaryString.split('').map(bit => parseInt(bit, 10)); + } \ No newline at end of file From ef54fc838e88ea9618e60d7b069102302269726f Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Thu, 15 Aug 2024 08:39:27 -0700 Subject: [PATCH 03/21] comments --- circuits/test/gfmulx.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/circuits/test/gfmulx.test.ts b/circuits/test/gfmulx.test.ts index 827ba64..dbd09b7 100644 --- a/circuits/test/gfmulx.test.ts +++ b/circuits/test/gfmulx.test.ts @@ -17,11 +17,10 @@ describe("GFMulX", () => { console.log("#constraints:", await circuit.getConstraintCount()); }); - // x^8 + x^4 + x^3 + x + 1 it("should compute correctly 1", async () => { // x^128 = x^7 + x^2 + x + 1 let test = [1].concat(Array(127).fill(0)) // 128-bit array with MSB = 1 and everything else 0 - /// 10000111 + /// 10000111 : x^7 + x^2 + x + 1 let expected_output: number[] = Array(120).fill(0).concat([1,0,0,0,0,1,1,1]) await circuit.expectPass({in: test}, {out: expected_output}) }); From 6dd3513a05d7efd9cf88ee142af1bee5b88dd9bb Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 15 Aug 2024 11:53:48 -0700 Subject: [PATCH 04/21] refactor test code paths: create dir for gfmulx for polyval and ghash --- circuits/test/{gfmulx.test.ts => gfmulx/ghash_mulx.test.ts} | 2 +- circuits/test/gfmulx/polyval_mulx.test.ts | 0 circuits/test/{ghash => hashes}/ghash.test.ts | 0 circuits/test/{ghash => hashes}/polyval.test.ts | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename circuits/test/{gfmulx.test.ts => gfmulx/ghash_mulx.test.ts} (97%) create mode 100644 circuits/test/gfmulx/polyval_mulx.test.ts rename circuits/test/{ghash => hashes}/ghash.test.ts (100%) rename circuits/test/{ghash => hashes}/polyval.test.ts (100%) diff --git a/circuits/test/gfmulx.test.ts b/circuits/test/gfmulx/ghash_mulx.test.ts similarity index 97% rename from circuits/test/gfmulx.test.ts rename to circuits/test/gfmulx/ghash_mulx.test.ts index dbd09b7..a5b2d93 100644 --- a/circuits/test/gfmulx.test.ts +++ b/circuits/test/gfmulx/ghash_mulx.test.ts @@ -1,6 +1,6 @@ import chai from "chai"; import { WitnessTester } from "circomkit"; -import { circomkit } from "./common"; +import { circomkit } from "../common"; // Disable truncation of arrays in error messages chai.config.truncateThreshold = 0; diff --git a/circuits/test/gfmulx/polyval_mulx.test.ts b/circuits/test/gfmulx/polyval_mulx.test.ts new file mode 100644 index 0000000..e69de29 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 From d2d72c1f072148aa63c9ca6ca109536e0a35b05b Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 15 Aug 2024 12:57:21 -0700 Subject: [PATCH 05/21] implement tests for ghash + format file --- circuits/aes-gcm/gfmulx.circom | 2 +- circuits/test/gfmulx/ghash_mulx.test.ts | 99 +++++++++++++++++-------- 2 files changed, 71 insertions(+), 30 deletions(-) diff --git a/circuits/aes-gcm/gfmulx.circom b/circuits/aes-gcm/gfmulx.circom index 15f96b2..187ee23 100644 --- a/circuits/aes-gcm/gfmulx.circom +++ b/circuits/aes-gcm/gfmulx.circom @@ -5,7 +5,7 @@ include "circomlib/circuits/gates.circom"; /// Multiplies a by x in GF(2^128) defined by the irreducible polynomial x^128 + x^7 + x^2 + x + 1 /// Small testing polynomial: x^8 + x^4 + x^3 + x + 1 -template GFMULX() { +template ghash_GFMULX() { var size = 128; signal input in[size]; diff --git a/circuits/test/gfmulx/ghash_mulx.test.ts b/circuits/test/gfmulx/ghash_mulx.test.ts index a5b2d93..887df3a 100644 --- a/circuits/test/gfmulx/ghash_mulx.test.ts +++ b/circuits/test/gfmulx/ghash_mulx.test.ts @@ -1,50 +1,91 @@ -import chai from "chai"; +import chai, { assert, expect } from "chai"; import { WitnessTester } from "circomkit"; import { circomkit } from "../common"; // Disable truncation of arrays in error messages chai.config.truncateThreshold = 0; -describe("GFMulX", () => { +// ghash irreducible polynomial: x^128 = x^7 + x^2 + x + 1 +const ghashIrreduciblePolynomial: number[] = Array(120) + .fill(0) + .concat([1, 0, 0, 0, 0, 1, 1, 1]); + +describe("ghash_GFMulX", () => { let circuit: WitnessTester<["in"], ["out"]>; - describe("GF Mul X test", () => { + describe("ghash GF Mul X test", () => { before(async () => { circuit = await circomkit.WitnessTester(`gfmulx`, { file: "aes-gcm/gfmulx", - template: "GFMULX", + template: "ghash_GFMULX", }); console.log("#constraints:", await circuit.getConstraintCount()); }); - it("should compute correctly 1", async () => { - // x^128 = x^7 + x^2 + x + 1 - let test = [1].concat(Array(127).fill(0)) // 128-bit array with MSB = 1 and everything else 0 - /// 10000111 : x^7 + x^2 + x + 1 - let expected_output: number[] = Array(120).fill(0).concat([1,0,0,0,0,1,1,1]) - await circuit.expectPass({in: test}, {out: expected_output}) + it("send 1000... to the irreducible polynomial", async () => { + const test = [1].concat(Array(127).fill(0)); + await circuit.expectPass( + { in: test }, + { out: ghashIrreduciblePolynomial } + ); + }); + + // ref: https://datatracker.ietf.org/doc/html/rfc8452#appendix-A + // todo + it("compute IETF test 1: expect 2", async () => { + let bits = hexToBitArray("01000000000000000000000000000000"); + let expected = hexToBitArray("00800000000000000000000000000000"); + // let expected = [0,0,8].concat(Array(29).fill(0)) + // console.log(bits); + // console.log(expected); + await circuit.expectPass({ in: bits }, { out: expected }); + }); + + // ref: https://datatracker.ietf.org/doc/html/rfc8452#appendix-A + it("compute IETF test 2", async () => { + let bits = hexToBitArray("9c98c04df9387ded828175a92ba652d8"); + let expected_output = hexToBitArray("4e4c6026fc9c3ef6c140bad495d3296c"); + // console.log(bits); + // console.log(expected_output); + await circuit.expectPass({ in: bits }, { out: expected_output }); }); - it("should compute correctly 2", async () => { - /// test vector rfc 8452 appendix A.1 https://datatracker.ietf.org/doc/html/rfc8452#appendix-A - let bits = hexToBits("01000000000000000000000000000000") - let expected_output = hexToBits("00800000000000000000000000000000") - console.log(bits); - console.log(expected_output); - await circuit.expectPass({in: bits}, {out: expected_output}) + it("tests hexToBitArray", async () => { + let hex = "0F"; + let expectedBits = [0, 0, 0, 0, 1, 1, 1, 1]; + let result = hexToBitArray(hex); + result.forEach((bit, index) => { + expect(bit).equals(expectedBits[index]); + }); + + hex = "1248"; + expectedBits = [0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0]; + result = hexToBitArray(hex); + result.forEach((bit, index) => { + expect(bit).equals(expectedBits[index]); + }); }); }); }); -function hexToBits(hex: string): number[] { - // Remove the '0x' prefix if present - if (hex.startsWith('0x')) { - hex = hex.slice(2); - } - - // Convert hex to binary string - const binaryString = BigInt(`0x${hex}`).toString(2).padStart(hex.length * 4, '0'); - - // Convert binary string to array of bits - return binaryString.split('').map(bit => parseInt(bit, 10)); - } \ No newline at end of file +function hexToBitArray(hex: string): number[] { + // Remove '0x' prefix if present and ensure lowercase + hex = hex.replace(/^0x/i, "").toLowerCase(); + + // Ensure even number of characters + if (hex.length % 2 !== 0) { + hex = "0" + hex; + } + + return ( + hex + // Split into pairs of characters + .match(/.{2}/g)! + .flatMap((pair) => { + const byte = parseInt(pair, 16); + // map byte to 8-bits. Apologies for the obtuse mapping; + // which cycles through the bits in byte and extracts them one by one. + return Array.from({ length: 8 }, (_, i) => (byte >> (7 - i)) & 1); + }) + ); +} From b9bc6f632f86b1a08c886eee124b80289923496b Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 15 Aug 2024 13:17:08 -0700 Subject: [PATCH 06/21] implement polyval tests --- circuits/aes-gcm/gfmulx.circom | 5 +- circuits/test/gfmulx/polyval_mulx.test.ts | 92 +++++++++++++++++++++++ 2 files changed, 94 insertions(+), 3 deletions(-) diff --git a/circuits/aes-gcm/gfmulx.circom b/circuits/aes-gcm/gfmulx.circom index 187ee23..18d1d1e 100644 --- a/circuits/aes-gcm/gfmulx.circom +++ b/circuits/aes-gcm/gfmulx.circom @@ -2,9 +2,8 @@ pragma circom 2.1.9; include "circomlib/circuits/gates.circom"; -/// Multiplies a by x in GF(2^128) defined by the irreducible polynomial x^128 + x^7 + x^2 + x + 1 - -/// Small testing polynomial: x^8 + x^4 + x^3 + x + 1 +// Multiplies `in` by x in GF(2^128) defined by the +// ghash irreducible polynomial x^128 + x^7 + x^2 + x + 1 template ghash_GFMULX() { var size = 128; diff --git a/circuits/test/gfmulx/polyval_mulx.test.ts b/circuits/test/gfmulx/polyval_mulx.test.ts index e69de29..6d03d28 100644 --- a/circuits/test/gfmulx/polyval_mulx.test.ts +++ b/circuits/test/gfmulx/polyval_mulx.test.ts @@ -0,0 +1,92 @@ +import chai, { assert, expect } from "chai"; +import { WitnessTester } from "circomkit"; +import { circomkit } from "../common"; + +// Disable truncation of arrays in error messages +chai.config.truncateThreshold = 0; + +// polyval irreducible polynomial: x^128 + x^127 + x^126 + x^121 + 1 +const polyvalIrreduciblePolynomial: number[] = Array(120) + .fill(0) + .concat([1, 0, 0, 0, 0, 1, 1, 1]) + .reverse(); + +describe("polyval_GFMulX", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + describe("polyval GF Mul X test", () => { + before(async () => { + circuit = await circomkit.WitnessTester(`gfmulx`, { + file: "aes-gcm/gfmulx", + template: "polyval_GFMULX", + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("send 1000... to the irreducible polynomial", async () => { + const test = [1].concat(Array(127).fill(0)); + await circuit.expectPass( + { in: test }, + { out: polyvalIrreduciblePolynomial } + ); + }); + + // ref: https://datatracker.ietf.org/doc/html/rfc8452#appendix-A + // todo + it("compute IETF test 1: expect 2", async () => { + let bits = hexToBitArray("01000000000000000000000000000000"); + let expected = hexToBitArray("020000000000000000000000000000"); + // let expected = [0,0,8].concat(Array(29).fill(0)) + // console.log(bits); + // console.log(expected); + await circuit.expectPass({ in: bits }, { out: expected }); + }); + + // ref: https://datatracker.ietf.org/doc/html/rfc8452#appendix-A + it("compute IETF test 2", async () => { + let bits = hexToBitArray("9c98c04df9387ded828175a92ba652d8"); + let expected_output = hexToBitArray("3931819bf271fada0503eb52574ca5f2"); + // console.log(bits); + // console.log(expected_output); + await circuit.expectPass({ in: bits }, { out: expected_output }); + }); + + it("tests hexToBitArray", async () => { + let hex = "0F"; + let expectedBits = [0, 0, 0, 0, 1, 1, 1, 1]; + let result = hexToBitArray(hex); + result.forEach((bit, index) => { + expect(bit).equals(expectedBits[index]); + }); + + hex = "1248"; + expectedBits = [0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0]; + result = hexToBitArray(hex); + result.forEach((bit, index) => { + expect(bit).equals(expectedBits[index]); + }); + }); + }); +}); + +function hexToBitArray(hex: string): number[] { + // Remove '0x' prefix if present and ensure lowercase + hex = hex.replace(/^0x/i, "").toLowerCase(); + + // Ensure even number of characters + if (hex.length % 2 !== 0) { + hex = "0" + hex; + } + + return ( + hex + // Split into pairs of characters + .match(/.{2}/g)! + .flatMap((pair) => { + const byte = parseInt(pair, 16); + // map byte to 8-bits. Apologies for the obtuse mapping; + // which cycles through the bits in byte and extracts them one by one. + return Array.from({ length: 8 }, (_, i) => (byte >> (7 - i)) & 1); + }) + ); +} From 51b35c64ce5d1792c85fe8a4a84a9b736a7453f1 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 15 Aug 2024 15:04:16 -0700 Subject: [PATCH 07/21] implement more thorough polyval testing to inspect LE encoding bug --- circuits/aes-gcm/gfmulx.circom | 171 +++++++++++----- circuits/test/gfmulx/polyval_mulx.test.ts | 228 +++++++++++++++++++--- 2 files changed, 325 insertions(+), 74 deletions(-) diff --git a/circuits/aes-gcm/gfmulx.circom b/circuits/aes-gcm/gfmulx.circom index 18d1d1e..b25baa6 100644 --- a/circuits/aes-gcm/gfmulx.circom +++ b/circuits/aes-gcm/gfmulx.circom @@ -1,55 +1,138 @@ pragma circom 2.1.9; -include "circomlib/circuits/gates.circom"; +// include "circomlib/circuits/gates.circom"; +include "helper_functions.circom"; -// Multiplies `in` by x in GF(2^128) defined by the + +// // Multiplies `in` by x in GF(2^128) defined by the +// // ghash irreducible polynomial x^128 + x^7 + x^2 + x + 1 +// template ghash_GFMULX() { +// var size = 128; + +// signal input in[size]; +// signal output out[size]; +// signal temp[size]; + + +// // Get the most significant bit of the input signal +// var msb; +// msb = in[0]; /// [>1<,0,0,0,0,0,0,0] + +// // Left shift input by 1 into temp +// for (var i = 0; i < size - 1; i++) { +// temp[i] <== in[i+1]; +// } +// temp[size - 1] <== 0; + +// component xor1 = XOR(); +// component xor2 = XOR(); +// component xor3 = XOR(); +// component xor4 = XOR(); + +// // x^128 = x^7 + x^2 + x + 1 +// // XOR the input with msb * (x^7 + x^2 + x + 1) +// for (var i = 0; i < size; i++) { +// if (i == size - 1) { +// // x^0 term +// xor1.a <== temp[i]; +// xor1.b <== msb; +// out[i] <== xor1.out; +// } else if (i == size - 2) { +// // x^1 term +// xor2.a <== temp[i]; +// xor2.b <== msb; +// out[i] <== xor2.out; +// } else if (i == size - 3) { +// // x^2 term +// xor3.a <== temp[i]; +// xor3.b <== msb; +// out[i] <== xor3.out; +// } else if (i == size - 8) { +// // x^7 term +// xor4.a <== temp[i]; +// xor4.b <== msb; +// out[i] <== xor4.out; +// } +// } +// } + +// compute x * `in` over ghash polynomial // ghash irreducible polynomial x^128 + x^7 + x^2 + x + 1 +// +// spec: +// https://tools.ietf.org/html/rfc8452#appendix-A +// +// rust-crypto reference implementation: todo template ghash_GFMULX() { - var size = 128; + var block = 128; + signal input in[block]; + signal output out[block]; - signal input in[size]; - signal output out[size]; - signal temp[size]; + // v = in left-shifted by 1 + signal v[block]; + // v_xor = 0 if in[0] is 0, or the irreducible poly if in[0] is 1 + signal v_xor[block]; + // initialize v and v_xor. + v[block - 1] <== 0; + v_xor[block - 1] <== in[0]; - // Get the most significant bit of the input signal - var msb; - msb = in[0]; /// [>1<,0,0,0,0,0,0,0] + for (var i=126; i>=0; i--) { + v[i] <== in[i+1]; - // Left shift input by 1 into temp - for (var i = 0; i < size - 1; i++) { - temp[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 == 121 || i == 126) { + v_xor[i] <== in[0]; + } else { + v_xor[i] <== 0; + } } - temp[size - 1] <== 0; - - component xor1 = XOR(); - component xor2 = XOR(); - component xor3 = XOR(); - component xor4 = XOR(); - - // x^128 = x^7 + x^2 + x + 1 - // XOR the input with msb * (x^7 + x^2 + x + 1) - for (var i = 0; i < size; i++) { - if (i == size - 1) { - // x^0 term - xor1.a <== temp[i]; - xor1.b <== msb; - out[i] <== xor1.out; - } else if (i == size - 2) { - // x^1 term - xor2.a <== temp[i]; - xor2.b <== msb; - out[i] <== xor2.out; - } else if (i == size - 3) { - // x^2 term - xor3.a <== temp[i]; - xor3.b <== msb; - out[i] <== xor3.out; - } else if (i == size - 8) { - // x^7 term - xor4.a <== temp[i]; - xor4.b <== msb; - out[i] <== xor4.out; - } - } + + // compute out + component xor = BitwiseXor(block); + xor.a <== v; + xor.b <== v_xor; + out <== xor.out; +} + +// compute x * `in` over polyval polynomial +// polyval irreducible polynomial x^128 + x^127 + x^126 + x^121 + 1 +// +// spec: +// https://tools.ietf.org/html/rfc8452#appendix-A +// +// rust-crypto reference implementation: +// https://github.com/RustCrypto/universal-hashes/blob/master/polyval/src/mulx.rs#L11 +template polyval_GFMULX() { + var block = 128; + signal input in[block]; + signal output out[block]; + + // v = in << 1; + signal v[block]; + // if `in` MSB set, assign irreducible poly bits, otherwise zero + signal irreducible_poly[block]; + var msb = in[0]; // endianness: 0 in polyval, 127(?) in ghash + + v[block - 1] <== 0; + irreducible_poly[block - 1] <== msb; + + for (var i=126; i>=0; i--) { + v[i] <== in[i+1]; + + // XOR with polynomial if MSB is 1 + // irreducible_poly has 1s at positions 127, 126, 121, 1 + if (i==0 || i == 121 || i == 126) { + irreducible_poly[i] <== in[0]; + } else { + irreducible_poly[i] <== 0; + } + } + + // compute out + component xor = BitwiseXor(block); + xor.a <== v; + xor.b <== irreducible_poly; + out <== xor.out; } \ No newline at end of file diff --git a/circuits/test/gfmulx/polyval_mulx.test.ts b/circuits/test/gfmulx/polyval_mulx.test.ts index 6d03d28..409ed8a 100644 --- a/circuits/test/gfmulx/polyval_mulx.test.ts +++ b/circuits/test/gfmulx/polyval_mulx.test.ts @@ -5,7 +5,139 @@ import { circomkit } from "../common"; // Disable truncation of arrays in error messages chai.config.truncateThreshold = 0; +const mulXTestVectors = [ + "02000000000000000000000000000000", + "04000000000000000000000000000000", + "08000000000000000000000000000000", + "10000000000000000000000000000000", + "20000000000000000000000000000000", + "40000000000000000000000000000000", + "80000000000000000000000000000000", + "00010000000000000000000000000000", + // "00020000000000000000000000000000", + // "00040000000000000000000000000000", + // "00080000000000000000000000000000", + // "00100000000000000000000000000000", + // "00200000000000000000000000000000", + // "00400000000000000000000000000000", + // "00800000000000000000000000000000", + // "00000100000000000000000000000000", + // "00000200000000000000000000000000", + // "00000400000000000000000000000000", + // "00000800000000000000000000000000", + // "00001000000000000000000000000000", + // "00002000000000000000000000000000", + // "00004000000000000000000000000000", + // "00008000000000000000000000000000", + // "00000001000000000000000000000000", + // "00000002000000000000000000000000", + // "00000004000000000000000000000000", + // "00000008000000000000000000000000", + // "00000010000000000000000000000000", + // "00000020000000000000000000000000", + // "00000040000000000000000000000000", + // "00000080000000000000000000000000", + // "00000000010000000000000000000000", + // "00000000020000000000000000000000", + // "00000000040000000000000000000000", + // "00000000080000000000000000000000", + // "00000000100000000000000000000000", + // "00000000200000000000000000000000", + // "00000000400000000000000000000000", + // "00000000800000000000000000000000", + // "00000000000100000000000000000000", + // "00000000000200000000000000000000", + // "00000000000400000000000000000000", + // "00000000000800000000000000000000", + // "00000000001000000000000000000000", + // "00000000002000000000000000000000", + // "00000000004000000000000000000000", + // "00000000008000000000000000000000", + // "00000000000001000000000000000000", + // "00000000000002000000000000000000", + // "00000000000004000000000000000000", + // "00000000000008000000000000000000", + // "00000000000010000000000000000000", + // "00000000000020000000000000000000", + // "00000000000040000000000000000000", + // "00000000000080000000000000000000", + // "00000000000000010000000000000000", + // "00000000000000020000000000000000", + // "00000000000000040000000000000000", + // "00000000000000080000000000000000", + // "00000000000000100000000000000000", + // "00000000000000200000000000000000", + // "00000000000000400000000000000000", + // "00000000000000800000000000000000", + // "00000000000000000100000000000000", + // "00000000000000000200000000000000", + // "00000000000000000400000000000000", + // "00000000000000000800000000000000", + // "00000000000000001000000000000000", + // "00000000000000002000000000000000", + // "00000000000000004000000000000000", + // "00000000000000008000000000000000", + // "00000000000000000001000000000000", + // "00000000000000000002000000000000", + // "00000000000000000004000000000000", + // "00000000000000000008000000000000", + // "00000000000000000010000000000000", + // "00000000000000000020000000000000", + // "00000000000000000040000000000000", + // "00000000000000000080000000000000", + // "00000000000000000000010000000000", + // "00000000000000000000020000000000", + // "00000000000000000000040000000000", + // "00000000000000000000080000000000", + // "00000000000000000000100000000000", + // "00000000000000000000200000000000", + // "00000000000000000000400000000000", + // "00000000000000000000800000000000", + // "00000000000000000000000100000000", + // "00000000000000000000000200000000", + // "00000000000000000000000400000000", + // "00000000000000000000000800000000", + // "00000000000000000000001000000000", + // "00000000000000000000002000000000", + // "00000000000000000000004000000000", + // "00000000000000000000008000000000", + // "00000000000000000000000001000000", + // "00000000000000000000000002000000", + // "00000000000000000000000004000000", + // "00000000000000000000000008000000", + // "00000000000000000000000010000000", + // "00000000000000000000000020000000", + // "00000000000000000000000040000000", + // "00000000000000000000000080000000", + // "00000000000000000000000000010000", + // "00000000000000000000000000020000", + // "00000000000000000000000000040000", + // "00000000000000000000000000080000", + // "00000000000000000000000000100000", + // "00000000000000000000000000200000", + // "00000000000000000000000000400000", + // "00000000000000000000000000800000", + // "00000000000000000000000000000100", + // "00000000000000000000000000000200", + // "00000000000000000000000000000400", + // "00000000000000000000000000000800", + // "00000000000000000000000000001000", + // "00000000000000000000000000002000", + // "00000000000000000000000000004000", + // "00000000000000000000000000008000", + // "00000000000000000000000000000001", + // "00000000000000000000000000000002", + // "00000000000000000000000000000004", + // "00000000000000000000000000000008", + // "00000000000000000000000000000010", + // "00000000000000000000000000000020", + // "00000000000000000000000000000040", + // "00000000000000000000000000000080", + // "010000000000000000000000000000c2", +]; + // polyval irreducible polynomial: x^128 + x^127 + x^126 + x^121 + 1 +// note that polyval uses LE encoding. const polyvalIrreduciblePolynomial: number[] = Array(120) .fill(0) .concat([1, 0, 0, 0, 0, 1, 1, 1]) @@ -23,48 +155,52 @@ describe("polyval_GFMulX", () => { console.log("#constraints:", await circuit.getConstraintCount()); }); - it("send 1000... to the irreducible polynomial", async () => { - const test = [1].concat(Array(127).fill(0)); - await circuit.expectPass( - { in: test }, - { out: polyvalIrreduciblePolynomial } - ); - }); - - // ref: https://datatracker.ietf.org/doc/html/rfc8452#appendix-A - // todo - it("compute IETF test 1: expect 2", async () => { + it("test all values", async () => { let bits = hexToBitArray("01000000000000000000000000000000"); - let expected = hexToBitArray("020000000000000000000000000000"); - // let expected = [0,0,8].concat(Array(29).fill(0)) - // console.log(bits); - // console.log(expected); - await circuit.expectPass({ in: bits }, { out: expected }); + // for (vector in mulXTestVectors) { + for (let i = 0; i < mulXTestVectors.length; i++) { + const expect = mulXTestVectors[i]; + const _res = await circuit.compute({ in: bits }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + console.log("expect: ", expect, "\nresult: ", result); + assert.equal(expect, result); + bits = hexToBitArray(result); + } }); - // ref: https://datatracker.ietf.org/doc/html/rfc8452#appendix-A - it("compute IETF test 2", async () => { - let bits = hexToBitArray("9c98c04df9387ded828175a92ba652d8"); - let expected_output = hexToBitArray("3931819bf271fada0503eb52574ca5f2"); - // console.log(bits); - // console.log(expected_output); - await circuit.expectPass({ in: bits }, { out: expected_output }); - }); + // // ref: https://datatracker.ietf.org/doc/html/rfc8452#appendix-A + // it("compute IETF test 2", async () => { + // let bits = hexToBitArray("9c98c04df9387ded828175a92ba652d8"); + // let expected_output = hexToBitArray("3931819bf271fada0503eb52574ca5f2"); + // // console.log(bits); + // // console.log(expected_output); + // await circuit.expectPass({ in: bits }, { out: expected_output }); + // }); it("tests hexToBitArray", async () => { let hex = "0F"; let expectedBits = [0, 0, 0, 0, 1, 1, 1, 1]; let result = hexToBitArray(hex); - result.forEach((bit, index) => { - expect(bit).equals(expectedBits[index]); - }); + assert.deepEqual(result, expectedBits); hex = "1248"; expectedBits = [0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0]; result = hexToBitArray(hex); - result.forEach((bit, index) => { - expect(bit).equals(expectedBits[index]); - }); + assert.deepEqual(result, expectedBits); + }); + + it("tests bitArrayToHexString", async () => { + let bits = [0, 0, 0, 0, 1, 1, 1, 1]; + let expectedHex = "0f"; + let result = bitArrayToHex(bits); + assert.equal(result, expectedHex); + + bits = [1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1]; + expectedHex = "8b09"; + result = bitArrayToHex(bits); + assert.equal(result, expectedHex); }); }); }); @@ -90,3 +226,35 @@ function hexToBitArray(hex: string): number[] { }) ); } + +function bitArrayToHex(bits: number[]): string { + // console.log(bits); + if (bits.length % 8 !== 0) { + throw new Error("Input length must be a multiple of 8 bits"); + } + + return bits + .reduce((acc, bit, index) => { + const byteIndex = Math.floor(index / 8); + const bitPosition = 7 - (index % 8); + acc[byteIndex] = (acc[byteIndex] || 0) | (bit << bitPosition); + return acc; + }, new Array(bits.length / 8).fill(0)) + .map((byte) => byte.toString(16).padStart(2, "0")) + .join(""); +} + +// function bitArrayToHex(bits: (number | bigint)[]): string { +// if (bits.length % 8 !== 0) { +// throw new Error("Input length must be a multiple of 8 bits"); +// } +// return bits +// .reduce((acc, bit, index) => { +// const byteIndex = Math.floor(index / 8); +// const bitPosition = 7 - (index % 8); +// acc[byteIndex] = (acc[byteIndex] || BigInt(0)) | (BigInt(bit) << BigInt(bitPosition)); +// return acc; +// }, new Array(bits.length / 8).fill(BigInt(0))) +// .map((byte) => byte.toString(16).padStart(2, "0")) +// .join(""); +// } From 9af71243eb8c79776a93fa5749762a17f686a658 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 15 Aug 2024 16:49:06 -0700 Subject: [PATCH 08/21] leftshift LE circom written and tested --- circuits/aes-gcm/gfmulx.circom | 87 +++++++++++++++++++---- circuits/test/gfmulx/polyval_mulx.test.ts | 63 ++++++++++++---- 2 files changed, 123 insertions(+), 27 deletions(-) diff --git a/circuits/aes-gcm/gfmulx.circom b/circuits/aes-gcm/gfmulx.circom index b25baa6..da7824c 100644 --- a/circuits/aes-gcm/gfmulx.circom +++ b/circuits/aes-gcm/gfmulx.circom @@ -110,29 +110,92 @@ template polyval_GFMULX() { signal output out[block]; // v = in << 1; + // observe that LE makes this less straightforward + // since 0x8000 (2^7) + // => 0x0001 (2^8) signal v[block]; // if `in` MSB set, assign irreducible poly bits, otherwise zero signal irreducible_poly[block]; var msb = in[0]; // endianness: 0 in polyval, 127(?) in ghash - v[block - 1] <== 0; + v[7] <== 0; irreducible_poly[block - 1] <== msb; - for (var i=126; i>=0; i--) { - v[i] <== in[i+1]; + // for (var i=(block-2); i>=0; i--) { + // // not so simple!: + // v[i] <== in[i+1]; - // XOR with polynomial if MSB is 1 - // irreducible_poly has 1s at positions 127, 126, 121, 1 - if (i==0 || i == 121 || i == 126) { - irreducible_poly[i] <== in[0]; - } else { - irreducible_poly[i] <== 0; - } - } + // // XOR with polynomial if MSB is 1 + // // irreducible_poly has 1s at positions 127, 126, 121, 1 + // if (i==0 || i == 121 || i == 126) { + // irreducible_poly[i] <== in[0]; + // } else { + // irreducible_poly[i] <== 0; + // } + // } // compute out component xor = BitwiseXor(block); xor.a <== v; xor.b <== irreducible_poly; out <== xor.out; -} \ No newline at end of file +} + + +// Left shift a `n`-bit little-endian array by `shift` bits +// +// example for 16 bit-array shifted by 1 bit: +// in = [h g f e d c b a, p o n m l k j i] +// mid1= [a b c d e f g h, i j k l m n o p] // swap order of bits in each byte +// mid2= [0 a b c d e f g, h i j k l m n o] // shift bits right by 1 +// out = [g f e d c b a 0, o n m l k j i h] // swap order of bits in each byte +template LeftShiftLE(shift) { + signal input in[128]; + signal output out[128]; + signal mid_1[128]; + signal mid_2[128]; + + for (var i = 0; i < 16; i++) { + for (var j = 0; j < 8; j++) { + mid_1[j + 8*i] <== in[7-j + 8*i]; + } + } + + for (var i = 0; i < shift; i++) { + mid_2[i] <== 0; + } + for (var i = shift; i < 128; i++) { + mid_2[i] <== mid_1[i - shift]; + } + + for (var i = 0; i < 16; i++) { + for (var j = 0; j < 8; j++) { + out[j + 8*i] <== mid_2[7-j + 8*i]; + } + } +} +// template LeftShiftLE(shift) { +// signal input in[128]; +// signal output out[128]; +// signal mid_1[128]; +// signal mid_2[128]; + +// for (var i = 0; i < 16; i++) { +// for (var j = 0; j < 8; j++) { +// mid_1[j + 8*i] <== in[7-j + 8*i]; +// } +// } + +// for (var i = 0; i < shift; i++) { +// mid_2[i] <== 0; +// } +// for (var i = shift; i < 128; i++) { +// mid_2[i] <== mid_1[i - shift]; +// } + +// for (var i = 0; i < 16; i++) { +// for (var j = 0; j < 8; j++) { +// out[j + 8*i] <== mid_2[7-j + 8*i]; +// } +// } +// } \ No newline at end of file diff --git a/circuits/test/gfmulx/polyval_mulx.test.ts b/circuits/test/gfmulx/polyval_mulx.test.ts index 409ed8a..443c395 100644 --- a/circuits/test/gfmulx/polyval_mulx.test.ts +++ b/circuits/test/gfmulx/polyval_mulx.test.ts @@ -155,7 +155,7 @@ describe("polyval_GFMulX", () => { console.log("#constraints:", await circuit.getConstraintCount()); }); - it("test all values", async () => { + it("test polyval at all bits set", async () => { let bits = hexToBitArray("01000000000000000000000000000000"); // for (vector in mulXTestVectors) { for (let i = 0; i < mulXTestVectors.length; i++) { @@ -244,17 +244,50 @@ function bitArrayToHex(bits: number[]): string { .join(""); } -// function bitArrayToHex(bits: (number | bigint)[]): string { -// if (bits.length % 8 !== 0) { -// throw new Error("Input length must be a multiple of 8 bits"); -// } -// return bits -// .reduce((acc, bit, index) => { -// const byteIndex = Math.floor(index / 8); -// const bitPosition = 7 - (index % 8); -// acc[byteIndex] = (acc[byteIndex] || BigInt(0)) | (BigInt(bit) << BigInt(bitPosition)); -// return acc; -// }, new Array(bits.length / 8).fill(BigInt(0))) -// .map((byte) => byte.toString(16).padStart(2, "0")) -// .join(""); -// } +describe("LeftShiftLE", () => { + const shift_1 = 1; + const shift_2 = 2; + let circuit: WitnessTester<["in"], ["out"]>; + + describe("leftshiftLE", () => { + before(async () => { + circuit = await circomkit.WitnessTester(`LeftShiftLE`, { + file: "aes-gcm/gfmulx", + template: "LeftShiftLE", + params: [shift_1], + }); + }); + + it("tests leftshiftLE", async () => { + let bits = [1].concat(Array(127).fill(0)); + let expect = Array(15).fill(0).concat([1]).concat(Array(112).fill(0)); + let _res = await circuit.compute({ in: bits }, ["out"]); + let result = (_res.out as (number | bigint)[]).map((bit) => Number(bit)); + assert.deepEqual(result, expect); + + bits = [0, 1].concat(Array(126).fill(0)); + expect = [1].concat(Array(127).fill(0)); + _res = await circuit.compute({ in: bits }, ["out"]); + result = (_res.out as (number | bigint)[]).map((bit) => Number(bit)); + assert.deepEqual(result, expect); + }); + }); + + describe("leftshiftLE shift 2", () => { + before(async () => { + circuit = await circomkit.WitnessTester(`LeftShiftLE`, { + file: "aes-gcm/gfmulx", + template: "LeftShiftLE", + params: [shift_2], + }); + }); + + it("tests leftshiftLE", async () => { + let bits = [1].concat(Array(127).fill(0)); + let expect = Array(14).fill(0).concat([1]).concat(Array(113).fill(0)); + let _res = await circuit.compute({ in: bits }, ["out"]); + let result = (_res.out as (number | bigint)[]).map((bit) => Number(bit)); + assert.deepEqual(result, expect); + }); + }); +}); From 4538c2b26a82165f3bc302dbbbc170ad13cc73b1 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 15 Aug 2024 18:07:08 -0700 Subject: [PATCH 09/21] incorporate LE shift changes into polyval_GFMULX --- circuits/aes-gcm/gfmulx.circom | 64 ++++++++++------------------------ 1 file changed, 18 insertions(+), 46 deletions(-) diff --git a/circuits/aes-gcm/gfmulx.circom b/circuits/aes-gcm/gfmulx.circom index da7824c..b96a445 100644 --- a/circuits/aes-gcm/gfmulx.circom +++ b/circuits/aes-gcm/gfmulx.circom @@ -108,31 +108,28 @@ template polyval_GFMULX() { var block = 128; signal input in[block]; signal output out[block]; - - // v = in << 1; - // observe that LE makes this less straightforward - // since 0x8000 (2^7) - // => 0x0001 (2^8) + // v = in << 1; observe that LE makes this less straightforward signal v[block]; // if `in` MSB set, assign irreducible poly bits, otherwise zero signal irreducible_poly[block]; var msb = in[0]; // endianness: 0 in polyval, 127(?) in ghash - v[7] <== 0; - irreducible_poly[block - 1] <== msb; - - // for (var i=(block-2); i>=0; i--) { - // // not so simple!: - // v[i] <== in[i+1]; + component left_shift = LeftShiftLE(1); + for (var i = 0; i < block; i++) { + left_shift.in[i] <== in[i]; + } + for (var i = 0; i < block; i++) { + v[i] <== left_shift.out[i]; + } - // // XOR with polynomial if MSB is 1 - // // irreducible_poly has 1s at positions 127, 126, 121, 1 - // if (i==0 || i == 121 || i == 126) { - // irreducible_poly[i] <== in[0]; - // } else { - // irreducible_poly[i] <== 0; - // } - // } + for (var i = 0; i < 128; i++) { + // irreducible_poly has 1s at positions 127, 126, 121, 1 + if (i==0 || i == 121 || i == 126 || i==127) { + irreducible_poly[i] <== msb; + } else { + irreducible_poly[i] <== 0; + } + } // compute out component xor = BitwiseXor(block); @@ -142,7 +139,7 @@ template polyval_GFMULX() { } -// Left shift a `n`-bit little-endian array by `shift` bits +// Left shift a 128-bit little-endian array by `shift` bits // // example for 16 bit-array shifted by 1 bit: // in = [h g f e d c b a, p o n m l k j i] @@ -173,29 +170,4 @@ template LeftShiftLE(shift) { out[j + 8*i] <== mid_2[7-j + 8*i]; } } -} -// template LeftShiftLE(shift) { -// signal input in[128]; -// signal output out[128]; -// signal mid_1[128]; -// signal mid_2[128]; - -// for (var i = 0; i < 16; i++) { -// for (var j = 0; j < 8; j++) { -// mid_1[j + 8*i] <== in[7-j + 8*i]; -// } -// } - -// for (var i = 0; i < shift; i++) { -// mid_2[i] <== 0; -// } -// for (var i = shift; i < 128; i++) { -// mid_2[i] <== mid_1[i - shift]; -// } - -// for (var i = 0; i < 16; i++) { -// for (var j = 0; j < 8; j++) { -// out[j + 8*i] <== mid_2[7-j + 8*i]; -// } -// } -// } \ No newline at end of file +} \ No newline at end of file From ddd485876119c8a1b5edecc00c6b9bbd08907c2f Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 15 Aug 2024 20:07:58 -0700 Subject: [PATCH 10/21] gf_mul polyval passing tests --- circuits/aes-gcm/gfmulx.circom | 9 +- circuits/test/gfmulx/polyval_mulx.test.ts | 240 +++++++++++----------- 2 files changed, 126 insertions(+), 123 deletions(-) diff --git a/circuits/aes-gcm/gfmulx.circom b/circuits/aes-gcm/gfmulx.circom index b96a445..8c496ae 100644 --- a/circuits/aes-gcm/gfmulx.circom +++ b/circuits/aes-gcm/gfmulx.circom @@ -112,7 +112,7 @@ template polyval_GFMULX() { signal v[block]; // if `in` MSB set, assign irreducible poly bits, otherwise zero signal irreducible_poly[block]; - var msb = in[0]; // endianness: 0 in polyval, 127(?) in ghash + var msb = in[block - 8]; // endianness: 0 in polyval, 127(?) in ghash component left_shift = LeftShiftLE(1); for (var i = 0; i < block; i++) { @@ -123,8 +123,10 @@ template polyval_GFMULX() { } for (var i = 0; i < 128; i++) { - // irreducible_poly has 1s at positions 127, 126, 121, 1 - if (i==0 || i == 121 || i == 126 || i==127) { + // irreducible_poly has 1s at positions 1, 121, 126, 127 + // 0000 0001... <== encodes 1 + // ...1100 0010 <== encodes 121, 126, 127 + if (i==7 || i == 120 || i==121 || i==126) { irreducible_poly[i] <== msb; } else { irreducible_poly[i] <== 0; @@ -146,6 +148,7 @@ template polyval_GFMULX() { // mid1= [a b c d e f g h, i j k l m n o p] // swap order of bits in each byte // mid2= [0 a b c d e f g, h i j k l m n o] // shift bits right by 1 // out = [g f e d c b a 0, o n m l k j i h] // swap order of bits in each byte +// TODO(TK 2024-08-15): optimize template LeftShiftLE(shift) { signal input in[128]; signal output out[128]; diff --git a/circuits/test/gfmulx/polyval_mulx.test.ts b/circuits/test/gfmulx/polyval_mulx.test.ts index 443c395..dd671b7 100644 --- a/circuits/test/gfmulx/polyval_mulx.test.ts +++ b/circuits/test/gfmulx/polyval_mulx.test.ts @@ -14,126 +14,126 @@ const mulXTestVectors = [ "40000000000000000000000000000000", "80000000000000000000000000000000", "00010000000000000000000000000000", - // "00020000000000000000000000000000", - // "00040000000000000000000000000000", - // "00080000000000000000000000000000", - // "00100000000000000000000000000000", - // "00200000000000000000000000000000", - // "00400000000000000000000000000000", - // "00800000000000000000000000000000", - // "00000100000000000000000000000000", - // "00000200000000000000000000000000", - // "00000400000000000000000000000000", - // "00000800000000000000000000000000", - // "00001000000000000000000000000000", - // "00002000000000000000000000000000", - // "00004000000000000000000000000000", - // "00008000000000000000000000000000", - // "00000001000000000000000000000000", - // "00000002000000000000000000000000", - // "00000004000000000000000000000000", - // "00000008000000000000000000000000", - // "00000010000000000000000000000000", - // "00000020000000000000000000000000", - // "00000040000000000000000000000000", - // "00000080000000000000000000000000", - // "00000000010000000000000000000000", - // "00000000020000000000000000000000", - // "00000000040000000000000000000000", - // "00000000080000000000000000000000", - // "00000000100000000000000000000000", - // "00000000200000000000000000000000", - // "00000000400000000000000000000000", - // "00000000800000000000000000000000", - // "00000000000100000000000000000000", - // "00000000000200000000000000000000", - // "00000000000400000000000000000000", - // "00000000000800000000000000000000", - // "00000000001000000000000000000000", - // "00000000002000000000000000000000", - // "00000000004000000000000000000000", - // "00000000008000000000000000000000", - // "00000000000001000000000000000000", - // "00000000000002000000000000000000", - // "00000000000004000000000000000000", - // "00000000000008000000000000000000", - // "00000000000010000000000000000000", - // "00000000000020000000000000000000", - // "00000000000040000000000000000000", - // "00000000000080000000000000000000", - // "00000000000000010000000000000000", - // "00000000000000020000000000000000", - // "00000000000000040000000000000000", - // "00000000000000080000000000000000", - // "00000000000000100000000000000000", - // "00000000000000200000000000000000", - // "00000000000000400000000000000000", - // "00000000000000800000000000000000", - // "00000000000000000100000000000000", - // "00000000000000000200000000000000", - // "00000000000000000400000000000000", - // "00000000000000000800000000000000", - // "00000000000000001000000000000000", - // "00000000000000002000000000000000", - // "00000000000000004000000000000000", - // "00000000000000008000000000000000", - // "00000000000000000001000000000000", - // "00000000000000000002000000000000", - // "00000000000000000004000000000000", - // "00000000000000000008000000000000", - // "00000000000000000010000000000000", - // "00000000000000000020000000000000", - // "00000000000000000040000000000000", - // "00000000000000000080000000000000", - // "00000000000000000000010000000000", - // "00000000000000000000020000000000", - // "00000000000000000000040000000000", - // "00000000000000000000080000000000", - // "00000000000000000000100000000000", - // "00000000000000000000200000000000", - // "00000000000000000000400000000000", - // "00000000000000000000800000000000", - // "00000000000000000000000100000000", - // "00000000000000000000000200000000", - // "00000000000000000000000400000000", - // "00000000000000000000000800000000", - // "00000000000000000000001000000000", - // "00000000000000000000002000000000", - // "00000000000000000000004000000000", - // "00000000000000000000008000000000", - // "00000000000000000000000001000000", - // "00000000000000000000000002000000", - // "00000000000000000000000004000000", - // "00000000000000000000000008000000", - // "00000000000000000000000010000000", - // "00000000000000000000000020000000", - // "00000000000000000000000040000000", - // "00000000000000000000000080000000", - // "00000000000000000000000000010000", - // "00000000000000000000000000020000", - // "00000000000000000000000000040000", - // "00000000000000000000000000080000", - // "00000000000000000000000000100000", - // "00000000000000000000000000200000", - // "00000000000000000000000000400000", - // "00000000000000000000000000800000", - // "00000000000000000000000000000100", - // "00000000000000000000000000000200", - // "00000000000000000000000000000400", - // "00000000000000000000000000000800", - // "00000000000000000000000000001000", - // "00000000000000000000000000002000", - // "00000000000000000000000000004000", - // "00000000000000000000000000008000", - // "00000000000000000000000000000001", - // "00000000000000000000000000000002", - // "00000000000000000000000000000004", - // "00000000000000000000000000000008", - // "00000000000000000000000000000010", - // "00000000000000000000000000000020", - // "00000000000000000000000000000040", - // "00000000000000000000000000000080", - // "010000000000000000000000000000c2", + "00020000000000000000000000000000", + "00040000000000000000000000000000", + "00080000000000000000000000000000", + "00100000000000000000000000000000", + "00200000000000000000000000000000", + "00400000000000000000000000000000", + "00800000000000000000000000000000", + "00000100000000000000000000000000", + "00000200000000000000000000000000", + "00000400000000000000000000000000", + "00000800000000000000000000000000", + "00001000000000000000000000000000", + "00002000000000000000000000000000", + "00004000000000000000000000000000", + "00008000000000000000000000000000", + "00000001000000000000000000000000", + "00000002000000000000000000000000", + "00000004000000000000000000000000", + "00000008000000000000000000000000", + "00000010000000000000000000000000", + "00000020000000000000000000000000", + "00000040000000000000000000000000", + "00000080000000000000000000000000", + "00000000010000000000000000000000", + "00000000020000000000000000000000", + "00000000040000000000000000000000", + "00000000080000000000000000000000", + "00000000100000000000000000000000", + "00000000200000000000000000000000", + "00000000400000000000000000000000", + "00000000800000000000000000000000", + "00000000000100000000000000000000", + "00000000000200000000000000000000", + "00000000000400000000000000000000", + "00000000000800000000000000000000", + "00000000001000000000000000000000", + "00000000002000000000000000000000", + "00000000004000000000000000000000", + "00000000008000000000000000000000", + "00000000000001000000000000000000", + "00000000000002000000000000000000", + "00000000000004000000000000000000", + "00000000000008000000000000000000", + "00000000000010000000000000000000", + "00000000000020000000000000000000", + "00000000000040000000000000000000", + "00000000000080000000000000000000", + "00000000000000010000000000000000", + "00000000000000020000000000000000", + "00000000000000040000000000000000", + "00000000000000080000000000000000", + "00000000000000100000000000000000", + "00000000000000200000000000000000", + "00000000000000400000000000000000", + "00000000000000800000000000000000", + "00000000000000000100000000000000", + "00000000000000000200000000000000", + "00000000000000000400000000000000", + "00000000000000000800000000000000", + "00000000000000001000000000000000", + "00000000000000002000000000000000", + "00000000000000004000000000000000", + "00000000000000008000000000000000", + "00000000000000000001000000000000", + "00000000000000000002000000000000", + "00000000000000000004000000000000", + "00000000000000000008000000000000", + "00000000000000000010000000000000", + "00000000000000000020000000000000", + "00000000000000000040000000000000", + "00000000000000000080000000000000", + "00000000000000000000010000000000", + "00000000000000000000020000000000", + "00000000000000000000040000000000", + "00000000000000000000080000000000", + "00000000000000000000100000000000", + "00000000000000000000200000000000", + "00000000000000000000400000000000", + "00000000000000000000800000000000", + "00000000000000000000000100000000", + "00000000000000000000000200000000", + "00000000000000000000000400000000", + "00000000000000000000000800000000", + "00000000000000000000001000000000", + "00000000000000000000002000000000", + "00000000000000000000004000000000", + "00000000000000000000008000000000", + "00000000000000000000000001000000", + "00000000000000000000000002000000", + "00000000000000000000000004000000", + "00000000000000000000000008000000", + "00000000000000000000000010000000", + "00000000000000000000000020000000", + "00000000000000000000000040000000", + "00000000000000000000000080000000", + "00000000000000000000000000010000", + "00000000000000000000000000020000", + "00000000000000000000000000040000", + "00000000000000000000000000080000", + "00000000000000000000000000100000", + "00000000000000000000000000200000", + "00000000000000000000000000400000", + "00000000000000000000000000800000", + "00000000000000000000000000000100", + "00000000000000000000000000000200", + "00000000000000000000000000000400", + "00000000000000000000000000000800", + "00000000000000000000000000001000", + "00000000000000000000000000002000", + "00000000000000000000000000004000", + "00000000000000000000000000008000", + "00000000000000000000000000000001", + "00000000000000000000000000000002", + "00000000000000000000000000000004", + "00000000000000000000000000000008", + "00000000000000000000000000000010", + "00000000000000000000000000000020", + "00000000000000000000000000000040", + "00000000000000000000000000000080", + "010000000000000000000000000000c2", ]; // polyval irreducible polynomial: x^128 + x^127 + x^126 + x^121 + 1 From bbebe4ee1dd998a54c10fe461032e36e85916861 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 15 Aug 2024 20:31:08 -0700 Subject: [PATCH 11/21] note conflict in tests --- circuits/aes-gcm/gfmulx.circom | 87 +++++------------------ circuits/test/gfmulx/polyval_mulx.test.ts | 28 ++++---- 2 files changed, 29 insertions(+), 86 deletions(-) diff --git a/circuits/aes-gcm/gfmulx.circom b/circuits/aes-gcm/gfmulx.circom index 8c496ae..0e0522d 100644 --- a/circuits/aes-gcm/gfmulx.circom +++ b/circuits/aes-gcm/gfmulx.circom @@ -3,61 +3,8 @@ pragma circom 2.1.9; // include "circomlib/circuits/gates.circom"; include "helper_functions.circom"; - -// // Multiplies `in` by x in GF(2^128) defined by the -// // ghash irreducible polynomial x^128 + x^7 + x^2 + x + 1 -// template ghash_GFMULX() { -// var size = 128; - -// signal input in[size]; -// signal output out[size]; -// signal temp[size]; - - -// // Get the most significant bit of the input signal -// var msb; -// msb = in[0]; /// [>1<,0,0,0,0,0,0,0] - -// // Left shift input by 1 into temp -// for (var i = 0; i < size - 1; i++) { -// temp[i] <== in[i+1]; -// } -// temp[size - 1] <== 0; - -// component xor1 = XOR(); -// component xor2 = XOR(); -// component xor3 = XOR(); -// component xor4 = XOR(); - -// // x^128 = x^7 + x^2 + x + 1 -// // XOR the input with msb * (x^7 + x^2 + x + 1) -// for (var i = 0; i < size; i++) { -// if (i == size - 1) { -// // x^0 term -// xor1.a <== temp[i]; -// xor1.b <== msb; -// out[i] <== xor1.out; -// } else if (i == size - 2) { -// // x^1 term -// xor2.a <== temp[i]; -// xor2.b <== msb; -// out[i] <== xor2.out; -// } else if (i == size - 3) { -// // x^2 term -// xor3.a <== temp[i]; -// xor3.b <== msb; -// out[i] <== xor3.out; -// } else if (i == size - 8) { -// // x^7 term -// xor4.a <== temp[i]; -// xor4.b <== msb; -// out[i] <== xor4.out; -// } -// } -// } - // compute x * `in` over ghash polynomial -// ghash irreducible polynomial x^128 + x^7 + x^2 + x + 1 +// ghash irreducible polynomial x^128 = x^7 + x^2 + x + 1 // // spec: // https://tools.ietf.org/html/rfc8452#appendix-A @@ -97,7 +44,7 @@ template ghash_GFMULX() { } // compute x * `in` over polyval polynomial -// polyval irreducible polynomial x^128 + x^127 + x^126 + x^121 + 1 +// polyval irreducible polynomial x^128 = x^127 + x^126 + x^121 + 1 // // spec: // https://tools.ietf.org/html/rfc8452#appendix-A @@ -105,36 +52,36 @@ template ghash_GFMULX() { // rust-crypto reference implementation: // https://github.com/RustCrypto/universal-hashes/blob/master/polyval/src/mulx.rs#L11 template polyval_GFMULX() { - var block = 128; - signal input in[block]; - signal output out[block]; + signal input in[128]; + signal output out[128]; // v = in << 1; observe that LE makes this less straightforward - signal v[block]; - // if `in` MSB set, assign irreducible poly bits, otherwise zero - signal irreducible_poly[block]; - var msb = in[block - 8]; // endianness: 0 in polyval, 127(?) in ghash + signal v[128]; + // if MSB set, assign irreducible poly bits, otherwise zero + signal irreducible_poly[128]; + var msb = in[128 - 8]; component left_shift = LeftShiftLE(1); - for (var i = 0; i < block; i++) { + for (var i = 0; i < 128; i++) { left_shift.in[i] <== in[i]; } - for (var i = 0; i < block; i++) { + for (var i = 0; i < 128; i++) { v[i] <== left_shift.out[i]; } + // irreducible_poly has 1s at positions 1, 121, 126, 127 + // 0000 0001... <== encodes 1 + // ...1100 0010 <== encodes 121, 126, 127 + // ...0100 0010 <== encodes 121, 126 for (var i = 0; i < 128; i++) { - // irreducible_poly has 1s at positions 1, 121, 126, 127 - // 0000 0001... <== encodes 1 - // ...1100 0010 <== encodes 121, 126, 127 - if (i==7 || i == 120 || i==121 || i==126) { + // if (i==7 || i == 120 || i==121 || i==126) { // passes rust-crypto + if (i==7 || i==121 || i==126) { // passes ietf spec? irreducible_poly[i] <== msb; } else { irreducible_poly[i] <== 0; } } - // compute out - component xor = BitwiseXor(block); + component xor = BitwiseXor(128); xor.a <== v; xor.b <== irreducible_poly; out <== xor.out; diff --git a/circuits/test/gfmulx/polyval_mulx.test.ts b/circuits/test/gfmulx/polyval_mulx.test.ts index dd671b7..f202a5d 100644 --- a/circuits/test/gfmulx/polyval_mulx.test.ts +++ b/circuits/test/gfmulx/polyval_mulx.test.ts @@ -136,13 +136,6 @@ const mulXTestVectors = [ "010000000000000000000000000000c2", ]; -// polyval irreducible polynomial: x^128 + x^127 + x^126 + x^121 + 1 -// note that polyval uses LE encoding. -const polyvalIrreduciblePolynomial: number[] = Array(120) - .fill(0) - .concat([1, 0, 0, 0, 0, 1, 1, 1]) - .reverse(); - describe("polyval_GFMulX", () => { let circuit: WitnessTester<["in"], ["out"]>; @@ -157,7 +150,6 @@ describe("polyval_GFMulX", () => { it("test polyval at all bits set", async () => { let bits = hexToBitArray("01000000000000000000000000000000"); - // for (vector in mulXTestVectors) { for (let i = 0; i < mulXTestVectors.length; i++) { const expect = mulXTestVectors[i]; const _res = await circuit.compute({ in: bits }, ["out"]); @@ -170,14 +162,18 @@ describe("polyval_GFMulX", () => { } }); - // // ref: https://datatracker.ietf.org/doc/html/rfc8452#appendix-A - // it("compute IETF test 2", async () => { - // let bits = hexToBitArray("9c98c04df9387ded828175a92ba652d8"); - // let expected_output = hexToBitArray("3931819bf271fada0503eb52574ca5f2"); - // // console.log(bits); - // // console.log(expected_output); - // await circuit.expectPass({ in: bits }, { out: expected_output }); - // }); + // ref: https://datatracker.ietf.org/doc/html/rfc8452#appendix-A + it("compute IETF test 2", async () => { + let bits = hexToBitArray("9c98c04df9387ded828175a92ba652d8"); + let expect = "3931819bf271fada0503eb52574ca5f2"; + const _res = await circuit.compute({ in: bits }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + console.log("2"); + console.log("expect: ", expect, "\nresult: ", result); + assert.equal(expect, result); + }); it("tests hexToBitArray", async () => { let hex = "0F"; From 91090b6359ac8337bf3df47a57cd8999cbb577b1 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 15 Aug 2024 20:48:05 -0700 Subject: [PATCH 12/21] implement ghash mulx --- circuits/aes-gcm/gfmulx.circom | 38 ++- circuits/test/gfmulx/ghash_mulx.test.ts | 332 +++++++++++++++++++--- circuits/test/gfmulx/polyval_mulx.test.ts | 3 +- 3 files changed, 308 insertions(+), 65 deletions(-) diff --git a/circuits/aes-gcm/gfmulx.circom b/circuits/aes-gcm/gfmulx.circom index 0e0522d..4efef85 100644 --- a/circuits/aes-gcm/gfmulx.circom +++ b/circuits/aes-gcm/gfmulx.circom @@ -11,35 +11,29 @@ include "helper_functions.circom"; // // rust-crypto reference implementation: todo template ghash_GFMULX() { - var block = 128; - signal input in[block]; - signal output out[block]; - - // v = in left-shifted by 1 - signal v[block]; - // v_xor = 0 if in[0] is 0, or the irreducible poly if in[0] is 1 - signal v_xor[block]; - - // initialize v and v_xor. - v[block - 1] <== 0; - v_xor[block - 1] <== in[0]; + signal input in[128]; + signal output out[128]; + var msb = in[127]; - for (var i=126; i>=0; i--) { - v[i] <== in[i+1]; + // v = in right-shifted by 1 + signal v[128]; + v[0] <== 0; + for (var i = 1; i < 128; 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 == 121 || i == 126) { - v_xor[i] <== in[0]; + // irreducible_poly has 1s at positions 1, 2, 7, 127 + signal irreducible_poly[128]; + for (var i = 0; i < 128; i++) { + if (i==0 || i == 1 || i==6 || i==127) { // passes rust-crypto + // // if (i==7 || i==121 || i==126) { // passes ietf spec? + irreducible_poly[i] <== msb; } else { - v_xor[i] <== 0; + irreducible_poly[i] <== 0; } } - // compute out - component xor = BitwiseXor(block); + component xor = BitwiseXor(128); xor.a <== v; - xor.b <== v_xor; + xor.b <== irreducible_poly; out <== xor.out; } diff --git a/circuits/test/gfmulx/ghash_mulx.test.ts b/circuits/test/gfmulx/ghash_mulx.test.ts index 887df3a..98437ba 100644 --- a/circuits/test/gfmulx/ghash_mulx.test.ts +++ b/circuits/test/gfmulx/ghash_mulx.test.ts @@ -5,10 +5,128 @@ import { circomkit } from "../common"; // Disable truncation of arrays in error messages chai.config.truncateThreshold = 0; -// ghash irreducible polynomial: x^128 = x^7 + x^2 + x + 1 -const ghashIrreduciblePolynomial: number[] = Array(120) - .fill(0) - .concat([1, 0, 0, 0, 0, 1, 1, 1]); +const mulXTestVectors = [ + "00800000000000000000000000000000", + "00400000000000000000000000000000", + "00200000000000000000000000000000", + "00100000000000000000000000000000", + "00080000000000000000000000000000", + "00040000000000000000000000000000", + "00020000000000000000000000000000", + "00010000000000000000000000000000", + "00008000000000000000000000000000", + "00004000000000000000000000000000", + "00002000000000000000000000000000", + "00001000000000000000000000000000", + "00000800000000000000000000000000", + "00000400000000000000000000000000", + "00000200000000000000000000000000", + "00000100000000000000000000000000", + "00000080000000000000000000000000", + "00000040000000000000000000000000", + "00000020000000000000000000000000", + "00000010000000000000000000000000", + "00000008000000000000000000000000", + "00000004000000000000000000000000", + "00000002000000000000000000000000", + "00000001000000000000000000000000", + "00000000800000000000000000000000", + "00000000400000000000000000000000", + "00000000200000000000000000000000", + "00000000100000000000000000000000", + "00000000080000000000000000000000", + "00000000040000000000000000000000", + "00000000020000000000000000000000", + "00000000010000000000000000000000", + "00000000008000000000000000000000", + "00000000004000000000000000000000", + "00000000002000000000000000000000", + "00000000001000000000000000000000", + "00000000000800000000000000000000", + "00000000000400000000000000000000", + "00000000000200000000000000000000", + "00000000000100000000000000000000", + "00000000000080000000000000000000", + "00000000000040000000000000000000", + "00000000000020000000000000000000", + "00000000000010000000000000000000", + "00000000000008000000000000000000", + "00000000000004000000000000000000", + "00000000000002000000000000000000", + "00000000000001000000000000000000", + "00000000000000800000000000000000", + "00000000000000400000000000000000", + "00000000000000200000000000000000", + "00000000000000100000000000000000", + "00000000000000080000000000000000", + "00000000000000040000000000000000", + "00000000000000020000000000000000", + "00000000000000010000000000000000", + "00000000000000008000000000000000", + "00000000000000004000000000000000", + "00000000000000002000000000000000", + "00000000000000001000000000000000", + "00000000000000000800000000000000", + "00000000000000000400000000000000", + "00000000000000000200000000000000", + "00000000000000000100000000000000", + "00000000000000000080000000000000", + "00000000000000000040000000000000", + "00000000000000000020000000000000", + "00000000000000000010000000000000", + "00000000000000000008000000000000", + "00000000000000000004000000000000", + "00000000000000000002000000000000", + "00000000000000000001000000000000", + "00000000000000000000800000000000", + "00000000000000000000400000000000", + "00000000000000000000200000000000", + "00000000000000000000100000000000", + "00000000000000000000080000000000", + "00000000000000000000040000000000", + "00000000000000000000020000000000", + "00000000000000000000010000000000", + "00000000000000000000008000000000", + "00000000000000000000004000000000", + "00000000000000000000002000000000", + "00000000000000000000001000000000", + "00000000000000000000000800000000", + "00000000000000000000000400000000", + "00000000000000000000000200000000", + "00000000000000000000000100000000", + "00000000000000000000000080000000", + "00000000000000000000000040000000", + "00000000000000000000000020000000", + "00000000000000000000000010000000", + "00000000000000000000000008000000", + "00000000000000000000000004000000", + "00000000000000000000000002000000", + "00000000000000000000000001000000", + "00000000000000000000000000800000", + "00000000000000000000000000400000", + "00000000000000000000000000200000", + "00000000000000000000000000100000", + "00000000000000000000000000080000", + "00000000000000000000000000040000", + "00000000000000000000000000020000", + "00000000000000000000000000010000", + "00000000000000000000000000008000", + "00000000000000000000000000004000", + "00000000000000000000000000002000", + "00000000000000000000000000001000", + "00000000000000000000000000000800", + "00000000000000000000000000000400", + "00000000000000000000000000000200", + "00000000000000000000000000000100", + "00000000000000000000000000000080", + "00000000000000000000000000000040", + "00000000000000000000000000000020", + "00000000000000000000000000000010", + "00000000000000000000000000000008", + "00000000000000000000000000000004", + "00000000000000000000000000000002", + "00000000000000000000000000000001", +]; describe("ghash_GFMulX", () => { let circuit: WitnessTester<["in"], ["out"]>; @@ -19,51 +137,33 @@ describe("ghash_GFMulX", () => { file: "aes-gcm/gfmulx", template: "ghash_GFMULX", }); - console.log("#constraints:", await circuit.getConstraintCount()); + // console.log("#constraints:", await circuit.getConstraintCount()); }); - it("send 1000... to the irreducible polynomial", async () => { - const test = [1].concat(Array(127).fill(0)); - await circuit.expectPass( - { in: test }, - { out: ghashIrreduciblePolynomial } - ); - }); - - // ref: https://datatracker.ietf.org/doc/html/rfc8452#appendix-A - // todo - it("compute IETF test 1: expect 2", async () => { + it("test ghash at all bits set", async () => { let bits = hexToBitArray("01000000000000000000000000000000"); - let expected = hexToBitArray("00800000000000000000000000000000"); - // let expected = [0,0,8].concat(Array(29).fill(0)) - // console.log(bits); - // console.log(expected); - await circuit.expectPass({ in: bits }, { out: expected }); + for (let i = 0; i < mulXTestVectors.length; i++) { + const expect = mulXTestVectors[i]; + const _res = await circuit.compute({ in: bits }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + console.log("expect: ", expect, "\nresult: ", result); + assert.equal(expect, result); + bits = hexToBitArray(result); + } }); // ref: https://datatracker.ietf.org/doc/html/rfc8452#appendix-A it("compute IETF test 2", async () => { let bits = hexToBitArray("9c98c04df9387ded828175a92ba652d8"); - let expected_output = hexToBitArray("4e4c6026fc9c3ef6c140bad495d3296c"); - // console.log(bits); - // console.log(expected_output); - await circuit.expectPass({ in: bits }, { out: expected_output }); - }); - - it("tests hexToBitArray", async () => { - let hex = "0F"; - let expectedBits = [0, 0, 0, 0, 1, 1, 1, 1]; - let result = hexToBitArray(hex); - result.forEach((bit, index) => { - expect(bit).equals(expectedBits[index]); - }); - - hex = "1248"; - expectedBits = [0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0]; - result = hexToBitArray(hex); - result.forEach((bit, index) => { - expect(bit).equals(expectedBits[index]); - }); + let expect = "4e4c6026fc9c3ef6c140bad495d3296c"; + const _res = await circuit.compute({ in: bits }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + console.log("expect: ", expect, "\nresult: ", result); + assert.equal(expect, result); }); }); }); @@ -89,3 +189,153 @@ function hexToBitArray(hex: string): number[] { }) ); } + +function bitArrayToHex(bits: number[]): string { + // console.log(bits); + if (bits.length % 8 !== 0) { + throw new Error("Input length must be a multiple of 8 bits"); + } + + return bits + .reduce((acc, bit, index) => { + const byteIndex = Math.floor(index / 8); + const bitPosition = 7 - (index % 8); + acc[byteIndex] = (acc[byteIndex] || 0) | (bit << bitPosition); + return acc; + }, new Array(bits.length / 8).fill(0)) + .map((byte) => byte.toString(16).padStart(2, "0")) + .join(""); +} + +describe("LeftShiftLE", () => { + const shift_1 = 1; + const shift_2 = 2; + let circuit: WitnessTester<["in"], ["out"]>; + + describe("leftshiftLE", () => { + before(async () => { + circuit = await circomkit.WitnessTester(`LeftShiftLE`, { + file: "aes-gcm/gfmulx", + template: "LeftShiftLE", + params: [shift_1], + }); + }); + + it("tests leftshiftLE", async () => { + let bits = [1].concat(Array(127).fill(0)); + let expect = Array(15).fill(0).concat([1]).concat(Array(112).fill(0)); + let _res = await circuit.compute({ in: bits }, ["out"]); + let result = (_res.out as (number | bigint)[]).map((bit) => Number(bit)); + assert.deepEqual(result, expect); + + bits = [0, 1].concat(Array(126).fill(0)); + expect = [1].concat(Array(127).fill(0)); + _res = await circuit.compute({ in: bits }, ["out"]); + result = (_res.out as (number | bigint)[]).map((bit) => Number(bit)); + assert.deepEqual(result, expect); + }); + }); + + describe("leftshiftLE shift 2", () => { + before(async () => { + circuit = await circomkit.WitnessTester(`LeftShiftLE`, { + file: "aes-gcm/gfmulx", + template: "LeftShiftLE", + params: [shift_2], + }); + }); + + it("tests leftshiftLE", async () => { + let bits = [1].concat(Array(127).fill(0)); + let expect = Array(14).fill(0).concat([1]).concat(Array(113).fill(0)); + let _res = await circuit.compute({ in: bits }, ["out"]); + let result = (_res.out as (number | bigint)[]).map((bit) => Number(bit)); + assert.deepEqual(result, expect); + }); + }); +}); + +// // ghash irreducible polynomial: x^128 = x^7 + x^2 + x + 1 +// const ghashIrreduciblePolynomial: number[] = Array(120) +// .fill(0) +// .concat([1, 0, 0, 0, 0, 1, 1, 1]); + +// describe("ghash_GFMulX", () => { +// let circuit: WitnessTester<["in"], ["out"]>; + +// describe("ghash GF Mul X test", () => { +// before(async () => { +// circuit = await circomkit.WitnessTester(`gfmulx`, { +// file: "aes-gcm/gfmulx", +// template: "ghash_GFMULX", +// }); +// console.log("#constraints:", await circuit.getConstraintCount()); +// }); + +// it("send 1000... to the irreducible polynomial", async () => { +// const test = [1].concat(Array(127).fill(0)); +// await circuit.expectPass( +// { in: test }, +// { out: ghashIrreduciblePolynomial } +// ); +// }); + +// // ref: https://datatracker.ietf.org/doc/html/rfc8452#appendix-A +// // todo +// it("compute IETF test 1: expect 2", async () => { +// let bits = hexToBitArray("01000000000000000000000000000000"); +// let expected = hexToBitArray("00800000000000000000000000000000"); +// // let expected = [0,0,8].concat(Array(29).fill(0)) +// // console.log(bits); +// // console.log(expected); +// await circuit.expectPass({ in: bits }, { out: expected }); +// }); + +// // ref: https://datatracker.ietf.org/doc/html/rfc8452#appendix-A +// it("compute IETF test 2", async () => { +// let bits = hexToBitArray("9c98c04df9387ded828175a92ba652d8"); +// let expected_output = hexToBitArray("4e4c6026fc9c3ef6c140bad495d3296c"); +// // console.log(bits); +// // console.log(expected_output); +// await circuit.expectPass({ in: bits }, { out: expected_output }); +// }); + +// it("tests hexToBitArray", async () => { +// let hex = "0F"; +// let expectedBits = [0, 0, 0, 0, 1, 1, 1, 1]; +// let result = hexToBitArray(hex); +// result.forEach((bit, index) => { +// expect(bit).equals(expectedBits[index]); +// }); + +// hex = "1248"; +// expectedBits = [0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0]; +// result = hexToBitArray(hex); +// result.forEach((bit, index) => { +// expect(bit).equals(expectedBits[index]); +// }); +// }); +// }); +// }); + +// function hexToBitArray(hex: string): number[] { +// // Remove '0x' prefix if present and ensure lowercase +// hex = hex.replace(/^0x/i, "").toLowerCase(); + +// // Ensure even number of characters +// if (hex.length % 2 !== 0) { +// hex = "0" + hex; +// } + +// return ( +// hex +// // Split into pairs of characters +// .match(/.{2}/g)! +// .flatMap((pair) => { +// const byte = parseInt(pair, 16); +// // map byte to 8-bits. Apologies for the obtuse mapping; +// // which cycles through the bits in byte and extracts them one by one. +// return Array.from({ length: 8 }, (_, i) => (byte >> (7 - i)) & 1); +// }) +// ); +// } diff --git a/circuits/test/gfmulx/polyval_mulx.test.ts b/circuits/test/gfmulx/polyval_mulx.test.ts index f202a5d..8633cf0 100644 --- a/circuits/test/gfmulx/polyval_mulx.test.ts +++ b/circuits/test/gfmulx/polyval_mulx.test.ts @@ -145,7 +145,7 @@ describe("polyval_GFMulX", () => { file: "aes-gcm/gfmulx", template: "polyval_GFMULX", }); - console.log("#constraints:", await circuit.getConstraintCount()); + // console.log("#constraints:", await circuit.getConstraintCount()); }); it("test polyval at all bits set", async () => { @@ -170,7 +170,6 @@ describe("polyval_GFMulX", () => { const result = bitArrayToHex( (_res.out as (number | bigint)[]).map((bit) => Number(bit)) ); - console.log("2"); console.log("expect: ", expect, "\nresult: ", result); assert.equal(expect, result); }); From ddef808264d4beae508b80cdbb862030dee0c8c8 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 15 Aug 2024 20:54:33 -0700 Subject: [PATCH 13/21] clean --- circuits/aes-gcm/gfmulx.circom | 6 +- circuits/test/common/index.ts | 68 ++++++++- circuits/test/gfmulx/ghash_mulx.test.ts | 178 +--------------------- circuits/test/gfmulx/polyval_mulx.test.ts | 69 +-------- 4 files changed, 74 insertions(+), 247 deletions(-) diff --git a/circuits/aes-gcm/gfmulx.circom b/circuits/aes-gcm/gfmulx.circom index 4efef85..36e40dc 100644 --- a/circuits/aes-gcm/gfmulx.circom +++ b/circuits/aes-gcm/gfmulx.circom @@ -8,8 +8,6 @@ include "helper_functions.circom"; // // spec: // https://tools.ietf.org/html/rfc8452#appendix-A -// -// rust-crypto reference implementation: todo template ghash_GFMULX() { signal input in[128]; signal output out[128]; @@ -20,11 +18,11 @@ template ghash_GFMULX() { v[0] <== 0; for (var i = 1; i < 128; i++) { v[i] <== in[i-1]; } + // if MSB set, assign irreducible poly bits, otherwise zero // irreducible_poly has 1s at positions 1, 2, 7, 127 signal irreducible_poly[128]; for (var i = 0; i < 128; i++) { - if (i==0 || i == 1 || i==6 || i==127) { // passes rust-crypto - // // if (i==7 || i==121 || i==126) { // passes ietf spec? + if (i==0 || i == 1 || i==6 || i==127) { irreducible_poly[i] <== msb; } else { irreducible_poly[i] <== 0; diff --git a/circuits/test/common/index.ts b/circuits/test/common/index.ts index ae54fc1..7439022 100644 --- a/circuits/test/common/index.ts +++ b/circuits/test/common/index.ts @@ -1,8 +1,72 @@ +import { assert } from "chai"; import { Circomkit, WitnessTester } from "circomkit"; -import 'mocha'; +import "mocha"; export const circomkit = new Circomkit({ - verbose: false, + verbose: false, }); export { WitnessTester }; + +export function hexToBitArray(hex: string): number[] { + // Remove '0x' prefix if present and ensure lowercase + hex = hex.replace(/^0x/i, "").toLowerCase(); + + // Ensure even number of characters + if (hex.length % 2 !== 0) { + hex = "0" + hex; + } + + return ( + hex + // Split into pairs of characters + .match(/.{2}/g)! + .flatMap((pair) => { + const byte = parseInt(pair, 16); + // map byte to 8-bits. Apologies for the obtuse mapping; + // which cycles through the bits in byte and extracts them one by one. + return Array.from({ length: 8 }, (_, i) => (byte >> (7 - i)) & 1); + }) + ); +} + +export function bitArrayToHex(bits: number[]): string { + // console.log(bits); + if (bits.length % 8 !== 0) { + throw new Error("Input length must be a multiple of 8 bits"); + } + + return bits + .reduce((acc, bit, index) => { + const byteIndex = Math.floor(index / 8); + const bitPosition = 7 - (index % 8); + acc[byteIndex] = (acc[byteIndex] || 0) | (bit << bitPosition); + return acc; + }, new Array(bits.length / 8).fill(0)) + .map((byte) => byte.toString(16).padStart(2, "0")) + .join(""); +} + +it("tests hexToBitArray", async () => { + let hex = "0F"; + let expectedBits = [0, 0, 0, 0, 1, 1, 1, 1]; + let result = hexToBitArray(hex); + assert.deepEqual(result, expectedBits); + + hex = "1248"; + expectedBits = [0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0]; + result = hexToBitArray(hex); + assert.deepEqual(result, expectedBits); +}); + +it("tests bitArrayToHexString", async () => { + let bits = [0, 0, 0, 0, 1, 1, 1, 1]; + let expectedHex = "0f"; + let result = bitArrayToHex(bits); + assert.equal(result, expectedHex); + + bits = [1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1]; + expectedHex = "8b09"; + result = bitArrayToHex(bits); + assert.equal(result, expectedHex); +}); diff --git a/circuits/test/gfmulx/ghash_mulx.test.ts b/circuits/test/gfmulx/ghash_mulx.test.ts index 98437ba..3e6f684 100644 --- a/circuits/test/gfmulx/ghash_mulx.test.ts +++ b/circuits/test/gfmulx/ghash_mulx.test.ts @@ -1,6 +1,6 @@ import chai, { assert, expect } from "chai"; import { WitnessTester } from "circomkit"; -import { circomkit } from "../common"; +import { bitArrayToHex, circomkit, hexToBitArray } from "../common"; // Disable truncation of arrays in error messages chai.config.truncateThreshold = 0; @@ -148,7 +148,7 @@ describe("ghash_GFMulX", () => { const result = bitArrayToHex( (_res.out as (number | bigint)[]).map((bit) => Number(bit)) ); - console.log("expect: ", expect, "\nresult: ", result); + // console.log("expect: ", expect, "\nresult: ", result); assert.equal(expect, result); bits = hexToBitArray(result); } @@ -162,180 +162,8 @@ describe("ghash_GFMulX", () => { const result = bitArrayToHex( (_res.out as (number | bigint)[]).map((bit) => Number(bit)) ); - console.log("expect: ", expect, "\nresult: ", result); + // console.log("expect: ", expect, "\nresult: ", result); assert.equal(expect, result); }); }); }); - -function hexToBitArray(hex: string): number[] { - // Remove '0x' prefix if present and ensure lowercase - hex = hex.replace(/^0x/i, "").toLowerCase(); - - // Ensure even number of characters - if (hex.length % 2 !== 0) { - hex = "0" + hex; - } - - return ( - hex - // Split into pairs of characters - .match(/.{2}/g)! - .flatMap((pair) => { - const byte = parseInt(pair, 16); - // map byte to 8-bits. Apologies for the obtuse mapping; - // which cycles through the bits in byte and extracts them one by one. - return Array.from({ length: 8 }, (_, i) => (byte >> (7 - i)) & 1); - }) - ); -} - -function bitArrayToHex(bits: number[]): string { - // console.log(bits); - if (bits.length % 8 !== 0) { - throw new Error("Input length must be a multiple of 8 bits"); - } - - return bits - .reduce((acc, bit, index) => { - const byteIndex = Math.floor(index / 8); - const bitPosition = 7 - (index % 8); - acc[byteIndex] = (acc[byteIndex] || 0) | (bit << bitPosition); - return acc; - }, new Array(bits.length / 8).fill(0)) - .map((byte) => byte.toString(16).padStart(2, "0")) - .join(""); -} - -describe("LeftShiftLE", () => { - const shift_1 = 1; - const shift_2 = 2; - let circuit: WitnessTester<["in"], ["out"]>; - - describe("leftshiftLE", () => { - before(async () => { - circuit = await circomkit.WitnessTester(`LeftShiftLE`, { - file: "aes-gcm/gfmulx", - template: "LeftShiftLE", - params: [shift_1], - }); - }); - - it("tests leftshiftLE", async () => { - let bits = [1].concat(Array(127).fill(0)); - let expect = Array(15).fill(0).concat([1]).concat(Array(112).fill(0)); - let _res = await circuit.compute({ in: bits }, ["out"]); - let result = (_res.out as (number | bigint)[]).map((bit) => Number(bit)); - assert.deepEqual(result, expect); - - bits = [0, 1].concat(Array(126).fill(0)); - expect = [1].concat(Array(127).fill(0)); - _res = await circuit.compute({ in: bits }, ["out"]); - result = (_res.out as (number | bigint)[]).map((bit) => Number(bit)); - assert.deepEqual(result, expect); - }); - }); - - describe("leftshiftLE shift 2", () => { - before(async () => { - circuit = await circomkit.WitnessTester(`LeftShiftLE`, { - file: "aes-gcm/gfmulx", - template: "LeftShiftLE", - params: [shift_2], - }); - }); - - it("tests leftshiftLE", async () => { - let bits = [1].concat(Array(127).fill(0)); - let expect = Array(14).fill(0).concat([1]).concat(Array(113).fill(0)); - let _res = await circuit.compute({ in: bits }, ["out"]); - let result = (_res.out as (number | bigint)[]).map((bit) => Number(bit)); - assert.deepEqual(result, expect); - }); - }); -}); - -// // ghash irreducible polynomial: x^128 = x^7 + x^2 + x + 1 -// const ghashIrreduciblePolynomial: number[] = Array(120) -// .fill(0) -// .concat([1, 0, 0, 0, 0, 1, 1, 1]); - -// describe("ghash_GFMulX", () => { -// let circuit: WitnessTester<["in"], ["out"]>; - -// describe("ghash GF Mul X test", () => { -// before(async () => { -// circuit = await circomkit.WitnessTester(`gfmulx`, { -// file: "aes-gcm/gfmulx", -// template: "ghash_GFMULX", -// }); -// console.log("#constraints:", await circuit.getConstraintCount()); -// }); - -// it("send 1000... to the irreducible polynomial", async () => { -// const test = [1].concat(Array(127).fill(0)); -// await circuit.expectPass( -// { in: test }, -// { out: ghashIrreduciblePolynomial } -// ); -// }); - -// // ref: https://datatracker.ietf.org/doc/html/rfc8452#appendix-A -// // todo -// it("compute IETF test 1: expect 2", async () => { -// let bits = hexToBitArray("01000000000000000000000000000000"); -// let expected = hexToBitArray("00800000000000000000000000000000"); -// // let expected = [0,0,8].concat(Array(29).fill(0)) -// // console.log(bits); -// // console.log(expected); -// await circuit.expectPass({ in: bits }, { out: expected }); -// }); - -// // ref: https://datatracker.ietf.org/doc/html/rfc8452#appendix-A -// it("compute IETF test 2", async () => { -// let bits = hexToBitArray("9c98c04df9387ded828175a92ba652d8"); -// let expected_output = hexToBitArray("4e4c6026fc9c3ef6c140bad495d3296c"); -// // console.log(bits); -// // console.log(expected_output); -// await circuit.expectPass({ in: bits }, { out: expected_output }); -// }); - -// it("tests hexToBitArray", async () => { -// let hex = "0F"; -// let expectedBits = [0, 0, 0, 0, 1, 1, 1, 1]; -// let result = hexToBitArray(hex); -// result.forEach((bit, index) => { -// expect(bit).equals(expectedBits[index]); -// }); - -// hex = "1248"; -// expectedBits = [0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0]; -// result = hexToBitArray(hex); -// result.forEach((bit, index) => { -// expect(bit).equals(expectedBits[index]); -// }); -// }); -// }); -// }); - -// function hexToBitArray(hex: string): number[] { -// // Remove '0x' prefix if present and ensure lowercase -// hex = hex.replace(/^0x/i, "").toLowerCase(); - -// // Ensure even number of characters -// if (hex.length % 2 !== 0) { -// hex = "0" + hex; -// } - -// return ( -// hex -// // Split into pairs of characters -// .match(/.{2}/g)! -// .flatMap((pair) => { -// const byte = parseInt(pair, 16); -// // map byte to 8-bits. Apologies for the obtuse mapping; -// // which cycles through the bits in byte and extracts them one by one. -// return Array.from({ length: 8 }, (_, i) => (byte >> (7 - i)) & 1); -// }) -// ); -// } diff --git a/circuits/test/gfmulx/polyval_mulx.test.ts b/circuits/test/gfmulx/polyval_mulx.test.ts index 8633cf0..600329b 100644 --- a/circuits/test/gfmulx/polyval_mulx.test.ts +++ b/circuits/test/gfmulx/polyval_mulx.test.ts @@ -1,6 +1,6 @@ import chai, { assert, expect } from "chai"; import { WitnessTester } from "circomkit"; -import { circomkit } from "../common"; +import { bitArrayToHex, circomkit, hexToBitArray } from "../common"; // Disable truncation of arrays in error messages chai.config.truncateThreshold = 0; @@ -156,7 +156,7 @@ describe("polyval_GFMulX", () => { const result = bitArrayToHex( (_res.out as (number | bigint)[]).map((bit) => Number(bit)) ); - console.log("expect: ", expect, "\nresult: ", result); + // console.log("expect: ", expect, "\nresult: ", result); assert.equal(expect, result); bits = hexToBitArray(result); } @@ -170,75 +170,12 @@ describe("polyval_GFMulX", () => { const result = bitArrayToHex( (_res.out as (number | bigint)[]).map((bit) => Number(bit)) ); - console.log("expect: ", expect, "\nresult: ", result); + // console.log("expect: ", expect, "\nresult: ", result); assert.equal(expect, result); }); - - it("tests hexToBitArray", async () => { - let hex = "0F"; - let expectedBits = [0, 0, 0, 0, 1, 1, 1, 1]; - let result = hexToBitArray(hex); - assert.deepEqual(result, expectedBits); - - hex = "1248"; - expectedBits = [0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0]; - result = hexToBitArray(hex); - assert.deepEqual(result, expectedBits); - }); - - it("tests bitArrayToHexString", async () => { - let bits = [0, 0, 0, 0, 1, 1, 1, 1]; - let expectedHex = "0f"; - let result = bitArrayToHex(bits); - assert.equal(result, expectedHex); - - bits = [1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1]; - expectedHex = "8b09"; - result = bitArrayToHex(bits); - assert.equal(result, expectedHex); - }); }); }); -function hexToBitArray(hex: string): number[] { - // Remove '0x' prefix if present and ensure lowercase - hex = hex.replace(/^0x/i, "").toLowerCase(); - - // Ensure even number of characters - if (hex.length % 2 !== 0) { - hex = "0" + hex; - } - - return ( - hex - // Split into pairs of characters - .match(/.{2}/g)! - .flatMap((pair) => { - const byte = parseInt(pair, 16); - // map byte to 8-bits. Apologies for the obtuse mapping; - // which cycles through the bits in byte and extracts them one by one. - return Array.from({ length: 8 }, (_, i) => (byte >> (7 - i)) & 1); - }) - ); -} - -function bitArrayToHex(bits: number[]): string { - // console.log(bits); - if (bits.length % 8 !== 0) { - throw new Error("Input length must be a multiple of 8 bits"); - } - - return bits - .reduce((acc, bit, index) => { - const byteIndex = Math.floor(index / 8); - const bitPosition = 7 - (index % 8); - acc[byteIndex] = (acc[byteIndex] || 0) | (bit << bitPosition); - return acc; - }, new Array(bits.length / 8).fill(0)) - .map((byte) => byte.toString(16).padStart(2, "0")) - .join(""); -} - describe("LeftShiftLE", () => { const shift_1 = 1; const shift_2 = 2; From 2f9adeada039711a15aa93c39df76d2a676b3b8c Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 15 Aug 2024 21:13:26 -0700 Subject: [PATCH 14/21] setup to implement ghash --- circuits/aes-gcm/ghash.circom | 77 --------------- circuits/aes-gcm/hashes.circom | 139 +++++++++++++++++++++++++++ circuits/aes-gcm/polyval.circom | 62 ------------ circuits/test/hashes/ghash.test.ts | 20 +++- circuits/test/hashes/polyval.test.ts | 2 +- 5 files changed, 155 insertions(+), 145 deletions(-) delete mode 100644 circuits/aes-gcm/ghash.circom create mode 100644 circuits/aes-gcm/hashes.circom delete mode 100644 circuits/aes-gcm/polyval.circom diff --git a/circuits/aes-gcm/ghash.circom b/circuits/aes-gcm/ghash.circom deleted file mode 100644 index e497d27..0000000 --- a/circuits/aes-gcm/ghash.circom +++ /dev/null @@ -1,77 +0,0 @@ -pragma circom 2.1.9; - -include "gfmul_int.circom"; -include "helper_functions.circom"; - -// GHASH computes the authentication tag for AES-GCM. -// Inputs: -// - `H` the hash key -// - `AAD` authenticated additional data -// - `msg` the message to authenticate -// -// Outputs: -// - `result` the authentication tag -// -// Computes: -// let M = pad(AAD) || pad(msg) || len_64(AAD) || let_64(msg) -// X_0 = 0^128 -// X_{i+1} = (X_i xor M_{i+1}) * H -// output: X_{n+1} where n is the number of blocks. -template GHASH(n_msg_bits) -{ - signal input msg[n_msg_bits]; - signal input H[128]; - signal input AAD[2][64]; - signal output result[2][64]; - - var n_msg_bytes = n_msg_bits/8; - var current_res[2][64] = AAD, in_t[2][64]; // result intermediate state - var i, j, k; - var n_msg_blocks = n_msg_bytes/16; - - component xor_1[n_msg_blocks][2][64]; - component gfmul_int_1[n_msg_blocks]; - - if(n_msg_blocks != 0) - { - // for each bit in the message - for(i=0; i { before(async () => { circuit = await circomkit.WitnessTester(`ghash`, { - file: "aes-gcm/ghash", + file: "aes-gcm/hashes", template: "GHASH", params: [128], }); - console.log("#constraints:", await circuit.getConstraintCount()); + // console.log("#constraints:", await circuit.getConstraintCount()); }); - it("should have correct number of constraints", async () => { - await circuit.expectConstraintCount(74754, true); + it("test ghash", async () => { + // let bits = hexToBitArray("01000000000000000000000000000000"); + // for (let i = 0; i < mulXTestVectors.length; i++) { + // const expect = mulXTestVectors[i]; + // const _res = await circuit.compute({ in: bits }, ["out"]); + // const result = bitArrayToHex( + // (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + // ); + // // console.log("expect: ", expect, "\nresult: ", result); + // assert.equal(expect, result); + // bits = hexToBitArray(result); + // } }); -}); \ No newline at end of file +}); diff --git a/circuits/test/hashes/polyval.test.ts b/circuits/test/hashes/polyval.test.ts index 20c3ba0..69e2e90 100644 --- a/circuits/test/hashes/polyval.test.ts +++ b/circuits/test/hashes/polyval.test.ts @@ -6,7 +6,7 @@ describe("polyval", () => { before(async () => { circuit = await circomkit.WitnessTester(`polyval`, { - file: "aes-gcm/polyval", + file: "aes-gcm/hashes", template: "POLYVAL", params: [128], }); From 9ea4edec7fadda6be71f2ac3de814ec89c4199a5 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 15 Aug 2024 22:09:37 -0700 Subject: [PATCH 15/21] impl reverse_byte array --- circuits/aes-gcm/hashes.circom | 3 +- circuits/aes-gcm/helper_functions.circom | 12 ++++++ circuits/test/hashes/ghash.test.ts | 52 ++++++++++++++++++------ 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/circuits/aes-gcm/hashes.circom b/circuits/aes-gcm/hashes.circom index 58fb29e..de2bbef 100644 --- a/circuits/aes-gcm/hashes.circom +++ b/circuits/aes-gcm/hashes.circom @@ -18,9 +18,10 @@ include "helper_functions.circom"; // X_{i+1} = (X_i xor M_{i+1}) * H // output: X_{n+1} where n is the number of blocks. template GHASH(n_msg_bits) { - + } + // { // signal input msg[n_msg_bits]; // signal input H[128]; diff --git a/circuits/aes-gcm/helper_functions.circom b/circuits/aes-gcm/helper_functions.circom index 7424b5c..d867828 100644 --- a/circuits/aes-gcm/helper_functions.circom +++ b/circuits/aes-gcm/helper_functions.circom @@ -257,3 +257,15 @@ template ReverseBitsArray(n) { out[i] <== in[n-i-1]; } } + +// reverse the byte order in a 16 byte array +template ReverseByteArray() { + signal input in[128]; + signal output out[128]; + + for (var i = 0; i < 16; i++) { + for (var j = 0; j < 8; j++) { + out[j + 8*i] <== in[(15-i)*8 +j]; + } + } +} diff --git a/circuits/test/hashes/ghash.test.ts b/circuits/test/hashes/ghash.test.ts index 721caa8..563d76b 100644 --- a/circuits/test/hashes/ghash.test.ts +++ b/circuits/test/hashes/ghash.test.ts @@ -1,5 +1,10 @@ import { WitnessTester } from "circomkit"; -import { circomkit } from "../common"; +import { bitArrayToHex, circomkit, hexToBitArray } from "../common"; +import { assert } from "chai"; + +const H = "25629347589242761d31f826ba4b757b"; +const X1 = "4f4f95668c83dfb6401762bb2d01a262"; +const X2 = "d1a24ddd2721d006bbe45f20d3c9f362"; describe("ghash", () => { let circuit: WitnessTester<["in"], ["out"]>; @@ -8,22 +13,43 @@ describe("ghash", () => { circuit = await circomkit.WitnessTester(`ghash`, { file: "aes-gcm/hashes", template: "GHASH", - params: [128], }); // console.log("#constraints:", await circuit.getConstraintCount()); }); it("test ghash", async () => { - // let bits = hexToBitArray("01000000000000000000000000000000"); - // for (let i = 0; i < mulXTestVectors.length; i++) { - // const expect = mulXTestVectors[i]; - // const _res = await circuit.compute({ in: bits }, ["out"]); - // const result = bitArrayToHex( - // (_res.out as (number | bigint)[]).map((bit) => Number(bit)) - // ); - // // console.log("expect: ", expect, "\nresult: ", result); - // assert.equal(expect, result); - // bits = hexToBitArray(result); - // } + // let bits = hexToBitArray("01000000000000000000000000000000"); + // for (let i = 0; i < mulXTestVectors.length; i++) { + // const expect = mulXTestVectors[i]; + // const _res = await circuit.compute({ in: bits }, ["out"]); + // const result = bitArrayToHex( + // (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + // ); + // // console.log("expect: ", expect, "\nresult: ", result); + // assert.equal(expect, result); + // bits = hexToBitArray(result); + // } + }); +}); + +describe("reverse_byte_array", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester(`ghash`, { + file: "aes-gcm/helper_functions", + template: "ReverseByteArray", + }); + }); + + it("test reverse_byte_array", async () => { + let bits = hexToBitArray("0102030405060708091011121314151f"); + let expect = "1f151413121110090807060504030201"; + const _res = await circuit.compute({ in: bits }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + // console.log("expect: ", expect, "\nresult: ", result); + assert.equal(expect, result); }); }); From eefa067e6c5396a015a533af11884057a4f6fe18 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 15 Aug 2024 22:43:57 -0700 Subject: [PATCH 16/21] circomkit bug? can't declare input to GHASH --- circuits/aes-gcm/hashes.circom | 5 ++++- circuits/test/hashes/ghash.test.ts | 30 +++++++++++++++++------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/circuits/aes-gcm/hashes.circom b/circuits/aes-gcm/hashes.circom index de2bbef..d1bd1ce 100644 --- a/circuits/aes-gcm/hashes.circom +++ b/circuits/aes-gcm/hashes.circom @@ -18,7 +18,10 @@ include "helper_functions.circom"; // X_{i+1} = (X_i xor M_{i+1}) * H // output: X_{n+1} where n is the number of blocks. template GHASH(n_msg_bits) { - + signal input msg[n_msg_bits]; + signal input H[128]; + // signal input AAD[128]; // included in msg + signal output out[128]; } diff --git a/circuits/test/hashes/ghash.test.ts b/circuits/test/hashes/ghash.test.ts index 563d76b..af8df8b 100644 --- a/circuits/test/hashes/ghash.test.ts +++ b/circuits/test/hashes/ghash.test.ts @@ -2,33 +2,37 @@ import { WitnessTester } from "circomkit"; import { bitArrayToHex, circomkit, hexToBitArray } from "../common"; import { assert } from "chai"; -const H = "25629347589242761d31f826ba4b757b"; +const H = hexToBitArray("25629347589242761d31f826ba4b757b"); const X1 = "4f4f95668c83dfb6401762bb2d01a262"; const X2 = "d1a24ddd2721d006bbe45f20d3c9f362"; +const M = hexToBitArray(X1.concat(X2)); -describe("ghash", () => { +describe("ghash-hash", () => { let circuit: WitnessTester<["in"], ["out"]>; before(async () => { circuit = await circomkit.WitnessTester(`ghash`, { file: "aes-gcm/hashes", template: "GHASH", + params: [128 * 2], }); // console.log("#constraints:", await circuit.getConstraintCount()); }); + // https://datatracker.ietf.org/doc/html/rfc8452#appendix-A it("test ghash", async () => { - // let bits = hexToBitArray("01000000000000000000000000000000"); - // for (let i = 0; i < mulXTestVectors.length; i++) { - // const expect = mulXTestVectors[i]; - // const _res = await circuit.compute({ in: bits }, ["out"]); - // const result = bitArrayToHex( - // (_res.out as (number | bigint)[]).map((bit) => Number(bit)) - // ); - // // console.log("expect: ", expect, "\nresult: ", result); - // assert.equal(expect, result); - // bits = hexToBitArray(result); - // } + const expect = "bd9b3997046731fb96251b91f9c99d7a"; + // console.log("input: ", input.length); + + const input = { msg: M, H: H }; + // const inp = { in: input, H: H }; + // const inp = { msg: input, H: H }; // todo: circomkit forcing me to call msg ->"in" + const _res = await circuit.compute(input, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + console.log("expect: ", expect, "\nresult: ", result); + assert.equal(expect, result); }); }); From f4f8020f50fa85d072caf3f7aa82070f981c9422 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 15 Aug 2024 23:20:23 -0700 Subject: [PATCH 17/21] ghash tests implemented --- circuits/aes-gcm/hashes.circom | 4 ++++ circuits/test/hashes/ghash.test.ts | 21 ++++++++++----------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/circuits/aes-gcm/hashes.circom b/circuits/aes-gcm/hashes.circom index d1bd1ce..e345a18 100644 --- a/circuits/aes-gcm/hashes.circom +++ b/circuits/aes-gcm/hashes.circom @@ -23,6 +23,10 @@ template GHASH(n_msg_bits) { // signal input AAD[128]; // included in msg signal output out[128]; + for (var i = 0; i < 128; i++) { + out[i] <== 1; + } + } // { diff --git a/circuits/test/hashes/ghash.test.ts b/circuits/test/hashes/ghash.test.ts index af8df8b..1bbc211 100644 --- a/circuits/test/hashes/ghash.test.ts +++ b/circuits/test/hashes/ghash.test.ts @@ -2,13 +2,14 @@ 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)); describe("ghash-hash", () => { - let circuit: WitnessTester<["in"], ["out"]>; + let circuit: WitnessTester<["msg", "H"], ["out"]>; before(async () => { circuit = await circomkit.WitnessTester(`ghash`, { @@ -19,20 +20,18 @@ describe("ghash-hash", () => { // console.log("#constraints:", await circuit.getConstraintCount()); }); - // https://datatracker.ietf.org/doc/html/rfc8452#appendix-A it("test ghash", async () => { - const expect = "bd9b3997046731fb96251b91f9c99d7a"; - // console.log("input: ", input.length); - const input = { msg: M, H: H }; - // const inp = { in: input, H: H }; - // const inp = { msg: input, H: H }; // todo: circomkit forcing me to call msg ->"in" + // https://datatracker.ietf.org/doc/html/rfc8452#appendix-A + const expect = "bd9b3997046731fb96251b91f9c99d7a"; const _res = await circuit.compute(input, ["out"]); + // TODO(TK 2024-08-15): bug, result returns 256 bits + // take the first 32 bytes const result = bitArrayToHex( - (_res.out as (number | bigint)[]).map((bit) => Number(bit)) - ); + (_res.out as number[]).map((bit) => Number(bit)) + ).slice(0, 32); console.log("expect: ", expect, "\nresult: ", result); - assert.equal(expect, result); + assert.equal(result, expect); }); }); @@ -51,7 +50,7 @@ describe("reverse_byte_array", () => { let expect = "1f151413121110090807060504030201"; const _res = await circuit.compute({ in: bits }, ["out"]); const result = bitArrayToHex( - (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + (_res.out as number[]).map((bit) => Number(bit)) ); // console.log("expect: ", expect, "\nresult: ", result); assert.equal(expect, result); From af3fafaa546f0e3d7b3072066dfdb0b161f6aa64 Mon Sep 17 00:00:00 2001 From: KaiGeffen Date: Mon, 19 Aug 2024 17:39:18 -0400 Subject: [PATCH 18/21] bitreversal test compares output instead of expectPass --- circuits/test/bitreversal.test.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/circuits/test/bitreversal.test.ts b/circuits/test/bitreversal.test.ts index f6a89f4..5fd7df6 100644 --- a/circuits/test/bitreversal.test.ts +++ b/circuits/test/bitreversal.test.ts @@ -1,3 +1,4 @@ +import { assert } from "chai"; import { WitnessTester } from "circomkit"; import { circomkit } from "./common"; @@ -13,13 +14,11 @@ describe("bitreversal", () => { }); let bit_array = [1,0,0,0,0,0,0,0]; - let expected_output = [0,0,0,0,0,0,0,1]; + let expected_output = [0,0,0,0,0,0,0,1].map((x) => BigInt(x)); it("should have correct output", async () => { - const witness = await circuit.expectPass({ in: bit_array}, { out: expected_output }); - circuit.expectPass({in: bit_array}); + const witness = await circuit.compute({ in: bit_array }, ["out"]) - // I was initially not sure what the first bit was doing in the witness, but it's just the success flag - console.log("witness: from input: [1,0,0,0,0,0,0,0]", witness); + assert.deepEqual(witness.out, expected_output) }); }); \ No newline at end of file From 0f27e05bf01a8be1eb32293bc1feb7e82547b476 Mon Sep 17 00:00:00 2001 From: KaiGeffen Date: Mon, 19 Aug 2024 17:39:48 -0400 Subject: [PATCH 19/21] Corrected ghash_gfmul comment --- circuits/aes-gcm/gfmulx.circom | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuits/aes-gcm/gfmulx.circom b/circuits/aes-gcm/gfmulx.circom index 36e40dc..3981de8 100644 --- a/circuits/aes-gcm/gfmulx.circom +++ b/circuits/aes-gcm/gfmulx.circom @@ -19,7 +19,7 @@ template ghash_GFMULX() { for (var i = 1; i < 128; i++) { v[i] <== in[i-1]; } // if MSB set, assign irreducible poly bits, otherwise zero - // irreducible_poly has 1s at positions 1, 2, 7, 127 + // irreducible_poly has 1s at positions 1, 2, 7, 128 signal irreducible_poly[128]; for (var i = 0; i < 128; i++) { if (i==0 || i == 1 || i==6 || i==127) { From b21c3ce9e0c64f477c29f1b61a75ec4f0e814ea6 Mon Sep 17 00:00:00 2001 From: KaiGeffen Date: Mon, 19 Aug 2024 18:42:17 -0400 Subject: [PATCH 20/21] Added polyval test consistent with ghash --- circuits/aes-gcm/hashes.circom | 13 ++++++++++++- circuits/test/hashes/ghash.test.ts | 7 +++---- circuits/test/hashes/polyval.test.ts | 26 +++++++++++++++++++++++--- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/circuits/aes-gcm/hashes.circom b/circuits/aes-gcm/hashes.circom index e345a18..fdac7d2 100644 --- a/circuits/aes-gcm/hashes.circom +++ b/circuits/aes-gcm/hashes.circom @@ -88,7 +88,18 @@ template GHASH(n_msg_bits) { // } -// template POLYVAL(n_bits) +template POLYVAL(n_msg_bits) +{ + signal input msg[n_msg_bits]; + signal input H[128]; + // signal input T[2][64]; // TODO + signal output out[128]; + + for (var i = 0; i < 128; i++) { + out[i] <== 1; + } + +} // { // var msg_len = n_bits/8; // signal input in[n_bits]; diff --git a/circuits/test/hashes/ghash.test.ts b/circuits/test/hashes/ghash.test.ts index 1bbc211..9cac752 100644 --- a/circuits/test/hashes/ghash.test.ts +++ b/circuits/test/hashes/ghash.test.ts @@ -7,6 +7,7 @@ const H = hexToBitArray("25629347589242761d31f826ba4b757b"); const X1 = "4f4f95668c83dfb6401762bb2d01a262"; const X2 = "d1a24ddd2721d006bbe45f20d3c9f362"; const M = hexToBitArray(X1.concat(X2)); +const EXPECT = "bd9b3997046731fb96251b91f9c99d7a"; describe("ghash-hash", () => { let circuit: WitnessTester<["msg", "H"], ["out"]>; @@ -22,16 +23,14 @@ describe("ghash-hash", () => { it("test ghash", async () => { const input = { msg: M, H: H }; - // https://datatracker.ietf.org/doc/html/rfc8452#appendix-A - const expect = "bd9b3997046731fb96251b91f9c99d7a"; const _res = await circuit.compute(input, ["out"]); // TODO(TK 2024-08-15): bug, result returns 256 bits // take the first 32 bytes const result = bitArrayToHex( (_res.out as number[]).map((bit) => Number(bit)) ).slice(0, 32); - console.log("expect: ", expect, "\nresult: ", result); - assert.equal(result, expect); + console.log("expect: ", EXPECT, "\nresult: ", result); + assert.equal(result, EXPECT); }); }); diff --git a/circuits/test/hashes/polyval.test.ts b/circuits/test/hashes/polyval.test.ts index 69e2e90..75ca7ca 100644 --- a/circuits/test/hashes/polyval.test.ts +++ b/circuits/test/hashes/polyval.test.ts @@ -1,14 +1,22 @@ import { WitnessTester } from "circomkit"; -import { circomkit } from "../common"; +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 = "f7a3b47b846119fae5b7866cf5e5b77e"; describe("polyval", () => { - let circuit: WitnessTester<["in"], ["out"]>; + let circuit: WitnessTester<["msg", "H"], ["out"]>; before(async () => { circuit = await circomkit.WitnessTester(`polyval`, { file: "aes-gcm/hashes", template: "POLYVAL", - params: [128], + params: [128 * 2], }); console.log("#constraints:", await circuit.getConstraintCount()); }); @@ -16,4 +24,16 @@ describe("polyval", () => { it("should have correct number of constraints", async () => { await circuit.expectConstraintCount(74754, true); }); + + it("todo name polyval", async () => { + const input = { msg: M, H: H }; + const _res = await circuit.compute(input, ["out"]); + // TODO(TK 2024-08-15): bug, result returns 256 bits + // take the first 32 bytes + const result = bitArrayToHex( + (_res.out as number[]).map((bit) => Number(bit)) + ).slice(0, 32); + console.log("expect: ", EXPECT, "\nresult: ", result); + assert.equal(result, EXPECT); + }); }); \ No newline at end of file From bab3274f694896fdc7c1ba58ebc0a3a7980e7bfe Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Mon, 19 Aug 2024 16:16:35 -0700 Subject: [PATCH 21/21] lint comments in circuit --- circuits/aes-gcm/gfmulx.circom | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/circuits/aes-gcm/gfmulx.circom b/circuits/aes-gcm/gfmulx.circom index 5451b73..8a6198f 100644 --- a/circuits/aes-gcm/gfmulx.circom +++ b/circuits/aes-gcm/gfmulx.circom @@ -19,7 +19,7 @@ template ghash_GFMULX() { for (var i = 1; i < 128; i++) { v[i] <== in[i-1]; } // if MSB set, assign irreducible poly bits, otherwise zero - // irreducible_poly has 1s at positions 1, 2, 7, 128 + // irreducible_poly has 1s at positions 0, 1, 6, 127 signal irreducible_poly[128]; for (var i = 0; i < 128; i++) { if (i==0 || i == 1 || i==6 || i==127) { @@ -60,13 +60,12 @@ template polyval_GFMULX() { v[i] <== left_shift.out[i]; } + // NOTE: LE logic explaining: // irreducible_poly has 1s at positions 1, 121, 126, 127 - // 0000 0001... <== encodes 1 - // ...1100 0010 <== encodes 121, 126, 127 - // ...0100 0010 <== encodes 121, 126 + // 0000 0001... <== bit at pos 7 encodes x^0 + // ...1100 0010 <== bits at pos 121, 122, 126 encode 127, 126, 121 respectively for (var i = 0; i < 128; i++) { - if (i==7 || i == 120 || i==121 || i==126) { // passes rust-crypto - // if (i==7 || i==121 || i==126) { // passes ietf spec? + if (i==7 || i == 120 || i==121 || i==126) { irreducible_poly[i] <== msb; } else { irreducible_poly[i] <== 0; @@ -112,4 +111,4 @@ template LeftShiftLE(shift) { out[j + 8*i] <== mid_2[7-j + 8*i]; } } -} \ No newline at end of file +}