From 287839ec40dc168c135b625c37cca1b14b4b83bf Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Wed, 14 Aug 2024 15:22:20 -0700 Subject: [PATCH 01/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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 f0846f79e667e93ea2587310ed9716043e896a56 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 15 Aug 2024 22:25:09 -0700 Subject: [PATCH 14/14] resolve conflict between ietf and rust crypto test vectors --- circuits/aes-gcm/gfmulx.circom | 3 +-- circuits/test/gfmulx/polyval_mulx.test.ts | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/circuits/aes-gcm/gfmulx.circom b/circuits/aes-gcm/gfmulx.circom index 36e40dc..16ceab7 100644 --- a/circuits/aes-gcm/gfmulx.circom +++ b/circuits/aes-gcm/gfmulx.circom @@ -65,8 +65,7 @@ template polyval_GFMULX() { // ...1100 0010 <== encodes 121, 126, 127 // ...0100 0010 <== encodes 121, 126 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; diff --git a/circuits/test/gfmulx/polyval_mulx.test.ts b/circuits/test/gfmulx/polyval_mulx.test.ts index 600329b..243e72c 100644 --- a/circuits/test/gfmulx/polyval_mulx.test.ts +++ b/circuits/test/gfmulx/polyval_mulx.test.ts @@ -165,7 +165,9 @@ describe("polyval_GFMulX", () => { // ref: https://datatracker.ietf.org/doc/html/rfc8452#appendix-A it("compute IETF test 2", async () => { let bits = hexToBitArray("9c98c04df9387ded828175a92ba652d8"); - let expect = "3931819bf271fada0503eb52574ca5f2"; + // NB: the expected value in the IETF spec has a typo. This value is correct. + // source: https://github.com/RustCrypto/universal-hashes/blob/master/polyval/src/mulx.rs#L27 + let expect = "3931819bf271fada0503eb52574ca572"; const _res = await circuit.compute({ in: bits }, ["out"]); const result = bitArrayToHex( (_res.out as (number | bigint)[]).map((bit) => Number(bit))