From 36987506424d77592b9c8a78aaba4ac679e3e49f Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Tue, 20 Aug 2024 15:57:01 -0700 Subject: [PATCH 01/21] impl bmul multiplication subroutine --- circuits/aes-gcm/gfmul.circom | 76 ++++++++++++++++++++++++ circuits/aes-gcm/hashes.circom | 17 +++++- circuits/aes-gcm/helper_functions.circom | 52 ++++++++++++++++ circuits/aes-gcm/mul.circom | 8 +++ circuits/test/gfmul.test.ts | 30 ++++++++++ 5 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 circuits/aes-gcm/gfmul.circom create mode 100644 circuits/test/gfmul.test.ts diff --git a/circuits/aes-gcm/gfmul.circom b/circuits/aes-gcm/gfmul.circom new file mode 100644 index 0000000..38b0163 --- /dev/null +++ b/circuits/aes-gcm/gfmul.circom @@ -0,0 +1,76 @@ +pragma circom 2.1.9; + +include "mul.circom"; + +// Multiplication in GF(2)[X], truncated to the low 64-bits, with “holes” +// (sequences of zeroes) to avoid carry spilling. +// +// When carries do occur, they wind up in a "hole" and are subsequently masked +// out of the result. +// +// ref: https://github.com/RustCrypto/universal-hashes/blob/master/polyval/src/backend/soft64.rs#L206 +template BMUL64() { + signal input x[64]; + signal input y[64]; + signal output out[64]; + + signal xs[4][64]; + signal ys[4][64]; + // var masks[4] = [0x1111111111111111, 0x2222222222222222, 0x4444444444444444, 0x8888888888888888]; + var masks[4][64] = [ + [0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1, + 0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1], + [0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0, + 0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0], + [0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0, + 0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0], + [1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0, + 1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0]]; + + component ands[3][4]; + for (var i = 0; i < 4; i++) { + ands[0][i] = BitwiseAnd(64); + ands[0][i].a <== masks[i]; + ands[0][i].b <== x; + xs[i] <== ands[0][i].out; + + ands[1][i] = BitwiseAnd(64); + ands[1][i].a <== masks[i]; + ands[1][i].b <== y; + ys[i] <== ands[1][i].out; + } + + // w_{i,j} = x_j * y_{i-j%4} + component muls[4][4]; + // z_i = XOR(w_{i,0}, w_{i,1}, w_{i,2}, w_{i,3}) + component xor_multiples[4]; + signal zs_mid[4][4][64]; + signal zs[4][64]; + for (var i = 0; i < 4; i++) { + for (var j = 0; j < 4; j++) { + var Y_INDEX = (i - j) % 4; + muls[i][j] = Mul64(); + muls[i][j].src1 <== xs[j]; + muls[i][j].src2 <== ys[Y_INDEX]; + zs_mid[i][j] <== muls[i][j].out; + } + + xor_multiples[i] = XorMultiple(4, 64); + xor_multiples[i].inputs <== zs_mid[i]; + zs[i] <== xor_multiples[i].out; + } + + // zs_masked[i] = zs[i] & masks[i] + signal zs_masked[4][64]; + for (var i = 0; i < 4; i++) { + ands[2][i] = BitwiseAnd(64); + ands[2][i].a <== masks[i]; + ands[2][i].b <== zs[i]; + zs_masked[i] <== ands[2][i].out; + } + + // out = zs_masked[0] | zs_masked[1] | zs_masked[2] | zs_masked[3] + component or_multiple = OrMultiple(4, 64); + or_multiple.inputs <== zs_masked; + out <== or_multiple.out; +} \ No newline at end of file diff --git a/circuits/aes-gcm/hashes.circom b/circuits/aes-gcm/hashes.circom index fdac7d2..61c20c7 100644 --- a/circuits/aes-gcm/hashes.circom +++ b/circuits/aes-gcm/hashes.circom @@ -88,11 +88,10 @@ template GHASH(n_msg_bits) { // } -template POLYVAL(n_msg_bits) -{ +template POLYVAL(n_msg_bits) { signal input msg[n_msg_bits]; + // Hash Key signal input H[128]; - // signal input T[2][64]; // TODO signal output out[128]; for (var i = 0; i < 128; i++) { @@ -100,6 +99,18 @@ template POLYVAL(n_msg_bits) } } + +// test re-implementation of POLYVAL avoiding 128-bit arrays for a possible speed-up +template POLYVAL_2_64(blocks) { + signal input msg[blocks][64]; + // Hash Key + signal input H[2][64]; + signal output out[2][64]; + + // 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/aes-gcm/helper_functions.circom b/circuits/aes-gcm/helper_functions.circom index d867828..648ab6b 100644 --- a/circuits/aes-gcm/helper_functions.circom +++ b/circuits/aes-gcm/helper_functions.circom @@ -117,6 +117,16 @@ template BitwiseAnd(n) { } } +template BitwiseOr(n) { + signal input a[n]; + signal input b[n]; + signal output out[n]; + + for (var i=0; i { + let circuit: WitnessTester<["x", "y"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester(`BMUL64`, { + file: "aes-gcm/gfmul", + template: "BMUL64", + // params: [8], + }); + }); + + // let bit_array = [1,0,0,0,0,0,0,0]; + // let expected_output = [0,0,0,0,0,0,0,1].map((x) => BigInt(x)); + it("bmul64", async () => { + const _res = await circuit.compute({ x: X, y: Y }, ["out"]); + // const result = bitArrayToHex( + // (_res.out as number[]).map((bit) => Number(bit)) + // ).slice(0, 32); + // console.log("expect: ", EXPECT, "\nresult: ", result); + // assert.equal(result, EXPECT); + }); +}); \ No newline at end of file From 0c4fd520e7823b70ae24971e63a7a9e3d171ed6c Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Tue, 20 Aug 2024 17:25:40 -0700 Subject: [PATCH 02/21] impl gfmul (mostly) --- circuits/aes-gcm/gfmul.circom | 177 +++++++++++++++++++++++++++++++--- circuits/test/gfmul.test.ts | 23 +++++ 2 files changed, 189 insertions(+), 11 deletions(-) diff --git a/circuits/aes-gcm/gfmul.circom b/circuits/aes-gcm/gfmul.circom index 38b0163..e43b01d 100644 --- a/circuits/aes-gcm/gfmul.circom +++ b/circuits/aes-gcm/gfmul.circom @@ -2,6 +2,151 @@ pragma circom 2.1.9; include "mul.circom"; +// Computes carryless POLYVAL multiplication over GF(2^128) in constant time. +// +// Method described at: +// +// +// POLYVAL multiplication is effectively the little endian equivalent of +// GHASH multiplication, aside from one small detail described here: +// +// +// +// > The product of two bit-reversed 128-bit polynomials yields the +// > bit-reversed result over 255 bits, not 256. The BearSSL code ends up +// > with a 256-bit result in zw[], and that value is shifted by one bit, +// > because of that reversed convention issue. Thus, the code must +// > include a shifting step to put it back where it should +// +// This shift is unnecessary for POLYVAL and has been removed. +// +// ref: https://github.com/RustCrypto/universal-hashes/blob/master/polyval/src/backend/soft64.rs#L151 +template MUL() { + signal input a[2][64]; + signal input b[2][64]; + signal output out[2][64]; + + // variable aliases to make indexing logic easier to state + signal h[3][64]; + signal y[3][64]; + signal h_r[3][64]; + signal y_r[3][64]; + + h[0] <== a[0]; + h[1] <== a[1]; + y[0] <== b[0]; + y[1] <== b[1]; + component Revs[4]; + for (var i = 0; i < 2; i++) { + Revs[i] = REV64(); + Revs[i].in <== h[i]; + h_r[i] <== Revs[i].out; + } + for (var i = 0; i < 2; i++) { + Revs[i+2] = REV64(); + Revs[i+2].in <== y[i]; + y_r[i] <== Revs[i+2].out; + } + + // h2 = h0^h1; y2 = y0^y1 + component Xors[4]; + for (var i = 0; i < 4; i++) Xors[i] = BitwiseXor(64); + Xors[0].a <== h[0]; + Xors[0].b <== h[1]; + h[2] <== Xors[0].out; + Xors[1].a <== y[0]; + Xors[1].b <== y[1]; + y[2] <== Xors[1].out; + + // h2_r = h0_r^h1_r; y2_r = y0_r^y1_r + Xors[2].a <== h_r[0]; + Xors[2].b <== h_r[1]; + h_r[2] <== Xors[2].out; + Xors[3].a <== y_r[0]; + Xors[3].b <== y_r[1]; + y_r[2] <== Xors[3].out; + + // z0 = bmul64(y0, h0); z1 = bmul64(y1, h1); z2 = bmul64(y2, h2); + // z0_h = bmul64(y0_r, h0_r); z1_h = bmul64(y1_r, h1_r); z2_h = bmul64(y2_r, h2_r); + component BMUL64_z[6]; + signal z[3][64]; + signal zh[3][64]; + for (var i = 0; i < 3; i++) { + BMUL64_z[i] = BMUL64(); + BMUL64_z[i].x <== y[i]; + BMUL64_z[i].y <== h[i]; + z[i] <== BMUL64_z[i].out; + + BMUL64_z[i+3] = BMUL64(); + BMUL64_z[i+3].x <== y_r[i]; + BMUL64_z[i+3].y <== h_r[i]; + zh[i] <== BMUL64_z[i+3].out; + } + + // _z2 = z0 ^ z1 ^ z2; + // _z2h = z0h ^ z1h ^ z2h; + signal _z2[64]; + signal _zh[3][64]; + component XorMultiples[2]; + XorMultiples[0] = XorMultiple(3, 64); + XorMultiples[0].inputs <== z; + _z2 <== XorMultiples[0].out; + + XorMultiples[1] = XorMultiple(3, 64); + XorMultiples[1].inputs <== zh; + _zh[0] <== XorMultiples[1].out; + _zh[1] <== zh[1]; + _zh[2] <== zh[2]; + + // z0h = rev64(z0h) >> 1; + // z1h = rev64(z1h) >> 1; + // _z2h = rev64(_z2h) >> 1; + // signal _zh[3][64]; + signal __zh[3][64]; + component Revs_zh[3]; + component RightShifts_zh[3]; + for (var i = 0; i < 3; i++) { + Revs_zh[i] = REV64(); + RightShifts_zh[i] = BitwiseRightShift(64, 1); + Revs_zh[i].in <== zh[i]; + RightShifts_zh[i].in <== Revs_zh[i].out; + __zh[i] <== RightShifts_zh[i].out; + } + + // let v0 = z0; + // let mut v1 = z0h ^ z2; + // let mut v2 = z1 ^ z2h; + // let mut v3 = z1h; + signal v[4][64]; + component Xors_v[2]; + v[0] <== z[0]; + v[3] <== __zh[1]; + Xors_v[0] = BitwiseXor(64); + Xors_v[0].a <== __zh[0]; + Xors_v[0].b <== _z2; + v[1] <== Xors_v[0].out; + Xors_v[1] = BitwiseXor(64); + Xors_v[1].a <== z[1]; + Xors_v[1].b <== __zh[2]; + v[2] <== Xors_v[1].out; + + + // _v2 = v2 ^ v0 ^ (v0 >> 1) ^ (v0 >> 2) ^ (v0 >> 7); + // _v1 = v1 ^ (v0 << 63) ^ (v0 << 62) ^ (v0 << 57); + // _v3 = v3 ^ _v1 ^ (_v1 >> 1) ^ (_v1 >> 2) ^ (_v1 >> 7); + // __v2 = _v2 ^ (_v1 << 63) ^ (_v1 << 62) ^ (_v1 << 57); + signal _v2[64]; + signal _v1[64]; + signal _v3[64]; + signal __v2[64]; + component XorMultiples_v[4]; + component LeftShifts_v[6]; + component RightShifts_v[6]; + + out <== [__v2, _v3]; + +} + // Multiplication in GF(2)[X], truncated to the low 64-bits, with “holes” // (sequences of zeroes) to avoid carry spilling. // @@ -44,33 +189,43 @@ template BMUL64() { component muls[4][4]; // z_i = XOR(w_{i,0}, w_{i,1}, w_{i,2}, w_{i,3}) component xor_multiples[4]; - signal zs_mid[4][4][64]; - signal zs[4][64]; + signal z_mid[4][4][64]; + signal z[4][64]; for (var i = 0; i < 4; i++) { for (var j = 0; j < 4; j++) { var Y_INDEX = (i - j) % 4; muls[i][j] = Mul64(); muls[i][j].src1 <== xs[j]; muls[i][j].src2 <== ys[Y_INDEX]; - zs_mid[i][j] <== muls[i][j].out; + z_mid[i][j] <== muls[i][j].out; } xor_multiples[i] = XorMultiple(4, 64); - xor_multiples[i].inputs <== zs_mid[i]; - zs[i] <== xor_multiples[i].out; + xor_multiples[i].inputs <== z_mid[i]; + z[i] <== xor_multiples[i].out; } - // zs_masked[i] = zs[i] & masks[i] - signal zs_masked[4][64]; + // z_masked[i] = z[i] & masks[i] + signal z_masked[4][64]; for (var i = 0; i < 4; i++) { ands[2][i] = BitwiseAnd(64); ands[2][i].a <== masks[i]; - ands[2][i].b <== zs[i]; - zs_masked[i] <== ands[2][i].out; + ands[2][i].b <== z[i]; + z_masked[i] <== ands[2][i].out; } - // out = zs_masked[0] | zs_masked[1] | zs_masked[2] | zs_masked[3] + // out = z_masked[0] | z_masked[1] | z_masked[2] | z_masked[3] component or_multiple = OrMultiple(4, 64); - or_multiple.inputs <== zs_masked; + or_multiple.inputs <== z_masked; out <== or_multiple.out; +} + +// todo: verify this is what was actually meant +template REV64(){ + signal input in[64]; + signal output out[64]; + + for (var i = 0; i < 64; i++) { + out[i] <== in[63 - i]; + } } \ No newline at end of file diff --git a/circuits/test/gfmul.test.ts b/circuits/test/gfmul.test.ts index 6c3d1d9..a0602a0 100644 --- a/circuits/test/gfmul.test.ts +++ b/circuits/test/gfmul.test.ts @@ -27,4 +27,27 @@ describe("BMUL64", () => { // console.log("expect: ", EXPECT, "\nresult: ", result); // assert.equal(result, EXPECT); }); +}); + +describe("MUL", () => { + let circuit: WitnessTester<["h", "rhs"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester(`MUL64`, { + file: "aes-gcm/gfmul", + template: "MUL", + // params: [8], + }); + }); + + // let bit_array = [1,0,0,0,0,0,0,0]; + // let expected_output = [0,0,0,0,0,0,0,1].map((x) => BigInt(x)); + it("mul", async () => { + const _res = await circuit.compute({ h: X, rhs: Y }, ["out"]); + // const result = bitArrayToHex( + // (_res.out as number[]).map((bit) => Number(bit)) + // ).slice(0, 32); + // console.log("expect: ", EXPECT, "\nresult: ", result); + // assert.equal(result, EXPECT); + }); }); \ No newline at end of file From b7064327f3f60c66187d5ccd5808a1f71f6cafd4 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Tue, 3 Sep 2024 18:13:58 -0700 Subject: [PATCH 03/21] implement rest of mul --- circuits/aes-gcm/component | 1 + circuits/aes-gcm/gfmul.circom | 53 +++++++++++++++++++++++++++++++---- circuits/aes-gcm/mul.circom | 2 +- 3 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 circuits/aes-gcm/component diff --git a/circuits/aes-gcm/component b/circuits/aes-gcm/component new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/circuits/aes-gcm/component @@ -0,0 +1 @@ + diff --git a/circuits/aes-gcm/gfmul.circom b/circuits/aes-gcm/gfmul.circom index e43b01d..a9bad81 100644 --- a/circuits/aes-gcm/gfmul.circom +++ b/circuits/aes-gcm/gfmul.circom @@ -139,12 +139,55 @@ template MUL() { signal _v1[64]; signal _v3[64]; signal __v2[64]; - component XorMultiples_v[4]; - component LeftShifts_v[6]; - component RightShifts_v[6]; + component RS_v[6]; + component LS_v[6]; - out <== [__v2, _v3]; + component XorMultiples_R[2]; + component XorMultiples_L[2]; + for (var i=0; i<2; i++) { + XorMultiples_R[i] = XorMultiple(5, 64); + XorMultiples_L[i] = XorMultiple(4, 64); + } + + RS_v[0] = BitwiseRightShift(64, 1); + RS_v[0].in <== v[0]; + RS_v[1] = BitwiseRightShift(64, 2); + RS_v[1].in <== v[0]; + RS_v[2] = BitwiseRightShift(64, 7); + RS_v[2].in <== v[0]; + + LS_v[0] = BitwiseLeftShift(64, 63); + LS_v[0].in <== v[0]; + LS_v[1] = BitwiseLeftShift(64, 62); + LS_v[1].in <== v[0]; + LS_v[2] = BitwiseLeftShift(64, 57); + LS_v[2].in <== v[0]; + + XorMultiples_R[0].inputs <== [v[2], v[0], RS_v[0].out, RS_v[1].out, RS_v[2].out]; + _v2 <== XorMultiples_R[0].out; + XorMultiples_L[0].inputs <== [v[1], LS_v[0].out, LS_v[1].out, LS_v[2].out]; + _v1 <== XorMultiples_L[0].out; + + RS_v[3] = BitwiseRightShift(64, 1); + RS_v[3].in <== _v1; + RS_v[4] = BitwiseRightShift(64, 2); + RS_v[4].in <== _v1; + RS_v[5] = BitwiseRightShift(64, 7); + RS_v[5].in <== _v1; + LS_v[3] = BitwiseLeftShift(64, 63); + LS_v[3].in <== _v1; + LS_v[4] = BitwiseLeftShift(64, 62); + LS_v[4].in <== _v1; + LS_v[5] = BitwiseLeftShift(64, 57); + LS_v[5].in <== _v1; + + XorMultiples_R[1].inputs <== [v[3], _v1, RS_v[3].out, RS_v[4].out, RS_v[5].out]; + _v3 <== XorMultiples_R[1].out; + XorMultiples_L[1].inputs <== [_v2, LS_v[0].out, LS_v[1].out, LS_v[2].out]; + __v2 <== XorMultiples_L[1].out; + + out <== [__v2, _v3]; } // Multiplication in GF(2)[X], truncated to the low 64-bits, with “holes” @@ -228,4 +271,4 @@ template REV64(){ for (var i = 0; i < 64; i++) { out[i] <== in[63 - i]; } -} \ No newline at end of file +} diff --git a/circuits/aes-gcm/mul.circom b/circuits/aes-gcm/mul.circom index d13bdc5..f064b84 100644 --- a/circuits/aes-gcm/mul.circom +++ b/circuits/aes-gcm/mul.circom @@ -86,4 +86,4 @@ template Mul() } } -} \ No newline at end of file +} From e9e4495b06ca7883c89ec10189a14a5f6ebc41d1 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 5 Sep 2024 14:30:50 -0700 Subject: [PATCH 04/21] notes on rev64 added, renamed mul64 -> wrappingMul64 for clarity --- circuits/aes-gcm/gfmul.circom | 7 +++++-- circuits/aes-gcm/mul.circom | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/circuits/aes-gcm/gfmul.circom b/circuits/aes-gcm/gfmul.circom index a9bad81..89037fb 100644 --- a/circuits/aes-gcm/gfmul.circom +++ b/circuits/aes-gcm/gfmul.circom @@ -237,7 +237,7 @@ template BMUL64() { for (var i = 0; i < 4; i++) { for (var j = 0; j < 4; j++) { var Y_INDEX = (i - j) % 4; - muls[i][j] = Mul64(); + muls[i][j] = WrappingMul64(); muls[i][j].src1 <== xs[j]; muls[i][j].src2 <== ys[Y_INDEX]; z_mid[i][j] <== muls[i][j].out; @@ -263,7 +263,10 @@ template BMUL64() { out <== or_multiple.out; } -// todo: verify this is what was actually meant +// Reverse the order of 64 bits. +// +// Potential optimization: +// https://github.com/RustCrypto/universal-hashes/blob/master/polyval/src/backend/soft64.rs#L230 template REV64(){ signal input in[64]; signal output out[64]; diff --git a/circuits/aes-gcm/mul.circom b/circuits/aes-gcm/mul.circom index f064b84..e603dbd 100644 --- a/circuits/aes-gcm/mul.circom +++ b/circuits/aes-gcm/mul.circom @@ -2,14 +2,16 @@ pragma circom 2.1.9; include "helper_functions.circom"; -template Mul64() -{ +// 64-bit wrapping multiplication +template WrappingMul64() { signal input src1[64]; signal input src2[64]; signal output out[64]; + // todo } +// todo: deprecate template Mul() { signal input src1[64]; From b839880c40c17720ab7774a69eb00df3aafc9334 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 5 Sep 2024 14:48:52 -0700 Subject: [PATCH 05/21] wrapping mul partial impl 1 - testing in progress --- circuits/aes-gcm/gfmul.circom | 4 +-- circuits/aes-gcm/mul.circom | 28 +++++++++++++++++--- circuits/test/gfmul.test.ts | 50 ++++++++++++++++++++++++++++++++++- 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/circuits/aes-gcm/gfmul.circom b/circuits/aes-gcm/gfmul.circom index 89037fb..a8130f5 100644 --- a/circuits/aes-gcm/gfmul.circom +++ b/circuits/aes-gcm/gfmul.circom @@ -238,8 +238,8 @@ template BMUL64() { for (var j = 0; j < 4; j++) { var Y_INDEX = (i - j) % 4; muls[i][j] = WrappingMul64(); - muls[i][j].src1 <== xs[j]; - muls[i][j].src2 <== ys[Y_INDEX]; + muls[i][j].a <== xs[j]; + muls[i][j].b <== ys[Y_INDEX]; z_mid[i][j] <== muls[i][j].out; } diff --git a/circuits/aes-gcm/mul.circom b/circuits/aes-gcm/mul.circom index e603dbd..9b6a5b2 100644 --- a/circuits/aes-gcm/mul.circom +++ b/circuits/aes-gcm/mul.circom @@ -4,11 +4,33 @@ include "helper_functions.circom"; // 64-bit wrapping multiplication template WrappingMul64() { - signal input src1[64]; - signal input src2[64]; + signal input a[64]; + signal input b[64]; signal output out[64]; - // todo + signal x[64][64]; + signal partialSum[64][64]; + + // Implement bit-level multiplication + for (var i = 0; i < 64; i++) { + for (var j = 0; j < 64; j++) { + if (i == 0) { + x[i][j] <== a[j] * b[i]; + } else { + x[i][j] <== partialSum[i-1][j] + a[j] * b[i]; + } + + if (j == 63) { + if (i == 63) { + out[i] <== x[i][j]; + } else { + partialSum[i][0] <== x[i][j]; + } + } else { + partialSum[i][j+1] <== x[i][j]; + } + } + } } // todo: deprecate diff --git a/circuits/test/gfmul.test.ts b/circuits/test/gfmul.test.ts index a0602a0..f437786 100644 --- a/circuits/test/gfmul.test.ts +++ b/circuits/test/gfmul.test.ts @@ -50,4 +50,52 @@ describe("MUL", () => { // console.log("expect: ", EXPECT, "\nresult: ", result); // assert.equal(result, EXPECT); }); -}); \ No newline at end of file +}); + +describe("WRAPPING_MUL", () => { + let circuit: WitnessTester<["a", "b"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester(`WrappingMul64`, { + file: "aes-gcm/mul", + template: "WrappingMul64", + // params: [8], + }); + }); + + it("should correctly multiply two 64-bit numbers", async () => { + const a = BigInt("0xFFFFFFFFFFFFFFFF"); // Max 64-bit unsigned integer + const b = BigInt(2); + const expected = (a * b) & BigInt("0xFFFFFFFFFFFFFFFF"); // Simulate 64-bit wrap + + const result = await circuit.calculateWitness({ a, b }, ["out"]); + + // const output = BigInt(result.out.toString()); + + // assert.equal(output, expected, "Multiplication result is incorrect"); + }); + + // it("should handle multiplication with zero", async () => { + // const a = BigInt("0xFFFFFFFFFFFFFFFF"); + // const b = BigInt(0); + // const expected = BigInt(0); + + // const result = await circuit.calculateWitness({ a, b }, ["out"]); + + // const output = BigInt(result.out.toString()); + + // assert.equal(output, expected, "Multiplication with zero is incorrect"); + // }); + + // it("should correctly wrap around on overflow", async () => { + // const a = BigInt("0xFFFFFFFFFFFFFFFF"); + // const b = BigInt("0xFFFFFFFFFFFFFFFF"); + // const expected = BigInt("0xFFFFFFFFFFFFFFFE0000000000000001"); + + // const result = await circuit.calculateWitness({ a, b }, ["out"]); + + // const output = BigInt(result.out.toString()); + + // assert.equal(output, expected, "Wrap-around on overflow is incorrect"); + // }); +}); From 02a183028f83b908d25b431e65e0bd62f7293e5a Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 5 Sep 2024 15:47:36 -0700 Subject: [PATCH 06/21] wrapping mul tests correctly running, not passing --- circuits/aes-gcm/ghash.circom | 82 +++++++++++++++++++++++++++++++++++ circuits/test/gfmul.test.ts | 72 +++++++++++++++++++----------- 2 files changed, 128 insertions(+), 26 deletions(-) create mode 100644 circuits/aes-gcm/ghash.circom diff --git a/circuits/aes-gcm/ghash.circom b/circuits/aes-gcm/ghash.circom new file mode 100644 index 0000000..02445c2 --- /dev/null +++ b/circuits/aes-gcm/ghash.circom @@ -0,0 +1,82 @@ +pragma circom 2.1.9; +include "helper_functions.circom"; +include "gfmul.circom"; + +// GHASH computes the authentication tag for AES-GCM. +// Inputs: +// - `HashKey` the hash key +// - `X` the input blocks +// +// Outputs: +// - `tag` the authentication tag +// +// Computes: +// Y_0 = 0^128 +// Y_{i+1} = (Y_i xor X_{i-1}) * H +// output: Y_{n+1} where n is the number of blocks. +// GHASH Process +// +// X1 X2 ... XM +// │ │ │ +// ▼ ▼ ▼ +// ┌────────────────┐ ┌──────────┐ ┌──────────┐ +// │ multiply by H │ ┌─────▶│ XOR │ ┌─────▶│ XOR │ +// └────────┬───────┘ | └────┬─────┘ | └────┬─────┘ +// │ │ │ | | +// │ │ ▼ | ▼ +// │ │ ┌────────────────┐ | ┌────────────────┐ +// │ │ │ multiply by H │ | │ multiply by H │ +// │ │ └───────┬────────┘ | └───────┬────────┘ +// │ │ │ | | +// ▼ │ ▼ | ▼ +// ┌─────────┐ │ ┌─────────┐ | ┌─────────┐ +// │ TAG1 │ ─────┘ │ TAG2 │ ──────┘ │ TAGM │ +// └─────────┘ └─────────┘ └─────────┘ +// + +template GHASH(NUM_BLOCKS) { + signal input HashKey[2][64]; // Hash subkey (128 bits) + signal input msg[NUM_BLOCKS][2][64]; // Input blocks (each 128 bits) + signal output tag[2][64]; // Output tag (128 bits) + + // Intermediate tags + signal intermediate[NUM_BLOCKS][2][64]; + + // Initialize first intermediate to zero + for (var j = 0; j < 64; j++) { + intermediate[0][0][j] <== 0; + intermediate[0][1][j] <== 0; + } + + // Initialize components + // two 64bit xor components for each block + component xor[NUM_BLOCKS][2]; + // one gfmul component for each block + component gfmul[NUM_BLOCKS]; + + // Accumulate each block using GHASH multiplication + for (var i = 1; i < NUM_BLOCKS; i++) { + xor[i][0] = BitwiseXor(64); + xor[i][1] = BitwiseXor(64); + gfmul[i] = MUL(); + + // XOR current block with the previous intermediate result + // note: intermediate[0] is initialized to zero, so all rounds are valid + xor[i][0].a <== intermediate[i-1][0]; + xor[i][1].a <== intermediate[i-1][1]; + xor[i][0].b <== msg[i][0]; + xor[i][1].b <== msg[i][1]; + + // Multiply the XOR result with the hash subkey H + gfmul[i].a[0] <== xor[i][0].out; + gfmul[i].a[1] <== xor[i][1].out; + gfmul[i].b <== HashKey; + + // Store the result in the next intermediate tag + intermediate[i][0] <== gfmul[i].out[0]; + intermediate[i][1] <== gfmul[i].out[1]; + } + // Assign the final tag + tag[0] <== intermediate[NUM_BLOCKS-1][0]; + tag[1] <== intermediate[NUM_BLOCKS-1][1]; +} diff --git a/circuits/test/gfmul.test.ts b/circuits/test/gfmul.test.ts index f437786..0a4181d 100644 --- a/circuits/test/gfmul.test.ts +++ b/circuits/test/gfmul.test.ts @@ -59,43 +59,63 @@ describe("WRAPPING_MUL", () => { circuit = await circomkit.WitnessTester(`WrappingMul64`, { file: "aes-gcm/mul", template: "WrappingMul64", - // params: [8], }); }); + // todo: choose a better test case, the expected value is wrong it("should correctly multiply two 64-bit numbers", async () => { - const a = BigInt("0xFFFFFFFFFFFFFFFF"); // Max 64-bit unsigned integer - const b = BigInt(2); - const expected = (a * b) & BigInt("0xFFFFFFFFFFFFFFFF"); // Simulate 64-bit wrap + const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); + const b = hexToBitArray("0x0000000000000002"); + const expected = "000000000000000F"; - const result = await circuit.calculateWitness({ a, b }, ["out"]); + // await circuit.expectPass({ a, b }, { out: hexToBitArray(expected) }); - // const output = BigInt(result.out.toString()); - - // assert.equal(output, expected, "Multiplication result is incorrect"); - }); + // const _res = await circuit.calculateWitness({ a, b }); + const _res = await circuit.compute({ a, b }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); - // it("should handle multiplication with zero", async () => { - // const a = BigInt("0xFFFFFFFFFFFFFFFF"); - // const b = BigInt(0); - // const expected = BigInt(0); + assert.deepEqual(result, expected, "Multiplication result is incorrect"); + }); - // const result = await circuit.calculateWitness({ a, b }, ["out"]); - // const output = BigInt(result.out.toString()); + it("should handle multiplication with zero", async () => { + const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); + const b = hexToBitArray("0x0000000000000000"); + const expected = "0000000000000000"; - // assert.equal(output, expected, "Multiplication with zero is incorrect"); - // }); - - // it("should correctly wrap around on overflow", async () => { - // const a = BigInt("0xFFFFFFFFFFFFFFFF"); - // const b = BigInt("0xFFFFFFFFFFFFFFFF"); - // const expected = BigInt("0xFFFFFFFFFFFFFFFE0000000000000001"); + const _res = await circuit.compute({ a, b }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + + assert.equal(result, expected, "Multiplication with zero is incorrect"); + }); - // const result = await circuit.calculateWitness({ a, b }, ["out"]); + it("should correctly wrap around on overflow", async () => { + const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); + const b = hexToBitArray("0xFFFFFFFFFFFFFFFF"); + const expected = "0000000000000001"; + + const _res = await circuit.compute({ a, b }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + + assert.equal(result, expected, "Wrap-around on overflow is incorrect"); + }); - // const output = BigInt(result.out.toString()); + it("should correctly handle a large multiplication within range", async () => { + const a = hexToBitArray("0x1234567890ABCDEF"); + const b = hexToBitArray("0xFEDCBA9876543210"); + const expected = "2236d88fe5618cf0"; - // assert.equal(output, expected, "Wrap-around on overflow is incorrect"); - // }); + const _res = await circuit.compute({ a, b }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + + assert.equal(result, expected, "Large multiplication within range is incorrect"); + }); }); From 8980c894931e20659d12e209adf425cfdcf30a3b Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Mon, 9 Sep 2024 12:42:01 -0700 Subject: [PATCH 07/21] rm component --- circuits/aes-gcm/component | 1 - 1 file changed, 1 deletion(-) delete mode 100644 circuits/aes-gcm/component diff --git a/circuits/aes-gcm/component b/circuits/aes-gcm/component deleted file mode 100644 index 8b13789..0000000 --- a/circuits/aes-gcm/component +++ /dev/null @@ -1 +0,0 @@ - From bed8b7a1a1f118fbbaea727f6847ca252d20695e Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Mon, 9 Sep 2024 13:04:24 -0700 Subject: [PATCH 08/21] debug wrapping mul - pause - tests for wrapping mul failing --- circuits/aes-gcm/mul.circom | 55 +++++++++++++++++++++++++------------ circuits/test/gfmul.test.ts | 30 +++++++++++++++----- 2 files changed, 60 insertions(+), 25 deletions(-) diff --git a/circuits/aes-gcm/mul.circom b/circuits/aes-gcm/mul.circom index 9b6a5b2..535fd73 100644 --- a/circuits/aes-gcm/mul.circom +++ b/circuits/aes-gcm/mul.circom @@ -2,35 +2,54 @@ pragma circom 2.1.9; include "helper_functions.circom"; -// 64-bit wrapping multiplication +// 64-bit wrapping multiplication. +// Implements multiplication mod 2^{64}. template WrappingMul64() { signal input a[64]; signal input b[64]; signal output out[64]; - signal x[64][64]; - signal partialSum[64][64]; - - // Implement bit-level multiplication + // Intermediate signals for partial products + signal partials[64][64]; + + // Calculate partial products for (var i = 0; i < 64; i++) { for (var j = 0; j < 64; j++) { - if (i == 0) { - x[i][j] <== a[j] * b[i]; - } else { - x[i][j] <== partialSum[i-1][j] + a[j] * b[i]; - } + partials[i][j] <== a[i] * b[j]; + } + } - if (j == 63) { - if (i == 63) { - out[i] <== x[i][j]; - } else { - partialSum[i][0] <== x[i][j]; - } - } else { - partialSum[i][j+1] <== x[i][j]; + // Sum up partial products with proper shifting + var sum[128]; + for (var i = 0; i < 128; i++) { + sum[i] = 0; + for (var j = 0; j <= i; j++) { + if (j < 64 && (i-j) < 64) { + sum[i] += partials[j][i-j]; } } } + + // Perform modular reduction (keep only the lower 64 bits) + for (var i = 0; i < 64; i++) { + out[i] <-- sum[i]; + } + + // Constraint to ensure out is binary + // for (var i = 0; i < 64; i++) { + // out[i] * (out[i] - 1) === 0; + // } + + // Constraint to ensure correctness of multiplication + // var lhs = 0; + // var rhs = 0; + // for (var i = 0; i < 64; i++) { + // lhs += out[i] * (1 << i); + // for (var j = 0; j < 64; j++) { + // rhs += a[i] * b[j] * (1 << (i + j)); + // } + // } + // lhs === rhs; } // todo: deprecate diff --git a/circuits/test/gfmul.test.ts b/circuits/test/gfmul.test.ts index 0a4181d..85d117f 100644 --- a/circuits/test/gfmul.test.ts +++ b/circuits/test/gfmul.test.ts @@ -52,7 +52,7 @@ describe("MUL", () => { }); }); -describe("WRAPPING_MUL", () => { +describe("WRAPPING", () => { let circuit: WitnessTester<["a", "b"], ["out"]>; before(async () => { @@ -63,10 +63,10 @@ describe("WRAPPING_MUL", () => { }); // todo: choose a better test case, the expected value is wrong - it("should correctly multiply two 64-bit numbers", async () => { - const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); - const b = hexToBitArray("0x0000000000000002"); - const expected = "000000000000000F"; + it("should correctly multiply two 64-bit numbers non-overflow", async () => { + const a = hexToBitArray("0x0000000000000002"); + const b = hexToBitArray("0x0000000000000004"); + const expected = "0000000000000008"; // await circuit.expectPass({ a, b }, { out: hexToBitArray(expected) }); @@ -79,7 +79,6 @@ describe("WRAPPING_MUL", () => { assert.deepEqual(result, expected, "Multiplication result is incorrect"); }); - it("should handle multiplication with zero", async () => { const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); const b = hexToBitArray("0x0000000000000000"); @@ -93,7 +92,24 @@ describe("WRAPPING_MUL", () => { assert.equal(result, expected, "Multiplication with zero is incorrect"); }); - it("should correctly wrap around on overflow", async () => { + // todo: choose a better test case, the expected value is wrong + it("should correctly multiply two 64-bit numbers with overflow", async () => { + const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); + const b = hexToBitArray("0x0000000000000002"); + const expected = "000000000000000F"; + + // await circuit.expectPass({ a, b }, { out: hexToBitArray(expected) }); + + // const _res = await circuit.calculateWitness({ a, b }); + const _res = await circuit.compute({ a, b }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + + assert.deepEqual(result, expected, "Multiplication result is incorrect"); + }); + + it("should correctly wrap on maximum overflow", async () => { const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); const b = hexToBitArray("0xFFFFFFFFFFFFFFFF"); const expected = "0000000000000001"; From 09a7674f9bb464f1258f828bd49094759a3b2706 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Tue, 10 Sep 2024 19:33:26 -0700 Subject: [PATCH 09/21] impl LE wrapping mul, tests pass --- circuits/aes-gcm/mul.circom | 45 ++++++++++++++--------------------- circuits/test/common/index.ts | 45 +++++++++++++++++++++++++++++++++++ circuits/test/gfmul.test.ts | 43 +++++++++++++++++---------------- 3 files changed, 85 insertions(+), 48 deletions(-) diff --git a/circuits/aes-gcm/mul.circom b/circuits/aes-gcm/mul.circom index 535fd73..88f81ec 100644 --- a/circuits/aes-gcm/mul.circom +++ b/circuits/aes-gcm/mul.circom @@ -2,54 +2,45 @@ pragma circom 2.1.9; include "helper_functions.circom"; -// 64-bit wrapping multiplication. +// 64-bit wrapping multiplication. Assumes MSB is first (Big E) // Implements multiplication mod 2^{64}. template WrappingMul64() { signal input a[64]; signal input b[64]; signal output out[64]; + // Intermediate signals for partial products + // partial[i,j corresponds to AND(a[i], b[j]) signal partials[64][64]; - - // Calculate partial products for (var i = 0; i < 64; i++) { for (var j = 0; j < 64; j++) { partials[i][j] <== a[i] * b[j]; } } + log(partials[62][63]); + + // 65, not 64, to allow for an extra carry without having to fiddle with overflow + var sum[65]; + for (var i=0; i<65; i++) { sum[i]=0; } - // Sum up partial products with proper shifting - var sum[128]; - for (var i = 0; i < 128; i++) { - sum[i] = 0; - for (var j = 0; j <= i; j++) { - if (j < 64 && (i-j) < 64) { - sum[i] += partials[j][i-j]; + for (var i = 0; i<64; i++) { + for (var j = 0; i+j<64; j++) { + var SUM_IDX = 64-i-j; + sum[SUM_IDX] += partials[63-i][63-j]; + + // covers the case that sum[i+j]=3 or more, due to prior carries + while (sum[SUM_IDX] > 1) { + sum[SUM_IDX] -= 2; + sum[SUM_IDX-1] += 1; } } } // Perform modular reduction (keep only the lower 64 bits) for (var i = 0; i < 64; i++) { - out[i] <-- sum[i]; + out[i] <-- sum[i+1]; } - - // Constraint to ensure out is binary - // for (var i = 0; i < 64; i++) { - // out[i] * (out[i] - 1) === 0; - // } - - // Constraint to ensure correctness of multiplication - // var lhs = 0; - // var rhs = 0; - // for (var i = 0; i < 64; i++) { - // lhs += out[i] * (1 << i); - // for (var j = 0; j < 64; j++) { - // rhs += a[i] * b[j] * (1 << (i + j)); - // } - // } - // lhs === rhs; } // todo: deprecate diff --git a/circuits/test/common/index.ts b/circuits/test/common/index.ts index 7439022..df9ab7e 100644 --- a/circuits/test/common/index.ts +++ b/circuits/test/common/index.ts @@ -47,6 +47,51 @@ export function bitArrayToHex(bits: number[]): string { .join(""); } +export function numberToBitArray(num: number): number[] { + if (!Number.isInteger(num) || num < 0) { + throw new Error('Input must be a non-negative integer'); + } + + if (num === 0) { + return [0]; + } + + const bitArray: number[] = []; + + while (num > 0) { + bitArray.unshift(num & 1); + num = num >>> 1; // Zero-fill right shift + } + + return bitArray; +} + +export function padArrayTo64Bits(array: number[]): number[] { + if (array.length > 64) { + throw new Error('Input array must have at most 64 elements'); + } + return new Array(64 - array.length).fill(0).concat(array); +} + +export function numberTo16Hex(num: number): string { + // Convert the number to a hexadecimal string + let hexString = num.toString(16); + + // Ensure the string is uppercase + hexString = hexString.toLowerCase(); + + // Pad with leading zeros if necessary + hexString = hexString.padStart(16, '0'); + + // If the number is too large and results in a string longer than 16 characters, + // we'll take the last 16 characters to maintain the fixed length + if (hexString.length > 16) { + hexString = hexString.slice(-16); + } + + return hexString; +} + it("tests hexToBitArray", async () => { let hex = "0F"; let expectedBits = [0, 0, 0, 0, 1, 1, 1, 1]; diff --git a/circuits/test/gfmul.test.ts b/circuits/test/gfmul.test.ts index 85d117f..111bc48 100644 --- a/circuits/test/gfmul.test.ts +++ b/circuits/test/gfmul.test.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; import { WitnessTester } from "circomkit"; -import { bitArrayToHex, circomkit, hexToBitArray } from "./common"; +import { padArrayTo64Bits, bitArrayToHex, circomkit, hexToBitArray, numberTo16Hex, numberToBitArray } from "./common"; const X = hexToBitArray("0x0100000000000000"); const Y = hexToBitArray("0x0100000000000000"); @@ -52,7 +52,7 @@ describe("MUL", () => { }); }); -describe("WRAPPING", () => { +describe("WRAPPING_LE", () => { let circuit: WitnessTester<["a", "b"], ["out"]>; before(async () => { @@ -62,11 +62,25 @@ describe("WRAPPING", () => { }); }); + it("wrapping mul low values", async () => { + const a = 2; + const a_arr = padArrayTo64Bits(numberToBitArray(a)); + for (var b = 1; b < 16; b++) { + const expected = numberTo16Hex(a * b); + const _res = await circuit.compute({ a: a_arr, b: hexToBitArray(numberTo16Hex(b)) }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + + assert.deepEqual(result, expected, "Multiplication result is incorrect"); + } + }) + // todo: choose a better test case, the expected value is wrong it("should correctly multiply two 64-bit numbers non-overflow", async () => { const a = hexToBitArray("0x0000000000000002"); const b = hexToBitArray("0x0000000000000004"); - const expected = "0000000000000008"; + const expected = "0000000000000008"; // await circuit.expectPass({ a, b }, { out: hexToBitArray(expected) }); @@ -83,12 +97,12 @@ describe("WRAPPING", () => { const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); const b = hexToBitArray("0x0000000000000000"); const expected = "0000000000000000"; - + const _res = await circuit.compute({ a, b }, ["out"]); const result = bitArrayToHex( (_res.out as (number | bigint)[]).map((bit) => Number(bit)) ); - + assert.equal(result, expected, "Multiplication with zero is incorrect"); }); @@ -96,7 +110,7 @@ describe("WRAPPING", () => { it("should correctly multiply two 64-bit numbers with overflow", async () => { const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); const b = hexToBitArray("0x0000000000000002"); - const expected = "000000000000000F"; + const expected = "fffffffffffffffe"; // await circuit.expectPass({ a, b }, { out: hexToBitArray(expected) }); @@ -113,25 +127,12 @@ describe("WRAPPING", () => { const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); const b = hexToBitArray("0xFFFFFFFFFFFFFFFF"); const expected = "0000000000000001"; - - const _res = await circuit.compute({ a, b }, ["out"]); - const result = bitArrayToHex( - (_res.out as (number | bigint)[]).map((bit) => Number(bit)) - ); - - assert.equal(result, expected, "Wrap-around on overflow is incorrect"); - }); - it("should correctly handle a large multiplication within range", async () => { - const a = hexToBitArray("0x1234567890ABCDEF"); - const b = hexToBitArray("0xFEDCBA9876543210"); - const expected = "2236d88fe5618cf0"; - const _res = await circuit.compute({ a, b }, ["out"]); const result = bitArrayToHex( (_res.out as (number | bigint)[]).map((bit) => Number(bit)) ); - - assert.equal(result, expected, "Large multiplication within range is incorrect"); + + assert.equal(result, expected, "Wrap-around on overflow is incorrect"); }); }); From ec817c8befa621f03718d0c930573480c2f6804c Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 12 Sep 2024 13:02:47 -0700 Subject: [PATCH 10/21] parse int le/be --- circuits/aes-gcm/helper_functions.circom | 39 ++++++++++++++++++++ circuits/aes-gcm/mul.circom | 2 +- circuits/test/parse_bytes.test.ts | 46 ++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 circuits/test/parse_bytes.test.ts diff --git a/circuits/aes-gcm/helper_functions.circom b/circuits/aes-gcm/helper_functions.circom index 648ab6b..a581f47 100644 --- a/circuits/aes-gcm/helper_functions.circom +++ b/circuits/aes-gcm/helper_functions.circom @@ -4,6 +4,45 @@ include "../lib_circuits/bitify.circom"; include "../lib_circuits/gates.circom"; include "../lib_circuits/comparators.circom"; +template ParseLEBytes64() { + signal input in[64]; + signal output out; + var temp = 0; + + // Iterate through the input bits + for (var i = 7; i >= 0; i--) { + for (var j = 0; j < 8; j++) { + // Shift the existing value left by 1 and add the new bit + var IDX = i*8+j; + temp = temp * 2 + in[IDX]; + } + } + + // Assign the final value to the output signal + out <-- temp; +} + +// parse 64-bits to integer value +template ParseBEBytes64() { + signal input in[64]; + signal output out; + var temp = 0; + + // Iterate through the input bits + for (var i = 0; i < 64; i++) { + // Shift the existing value left by 1 and add the new bit + temp = temp * 2 + in[i]; + } + + // Assign the final value to the output signal + out <-- temp; + + // // constrain each input bit to be either 0 or 1 + // for (var i = 0; i < 64; i++) { + // in[i] * (1 - in[i]) === 0; + // } +} + template BitwiseRightShift(n, r) { signal input in[n]; signal output out[n]; diff --git a/circuits/aes-gcm/mul.circom b/circuits/aes-gcm/mul.circom index 88f81ec..378a2e8 100644 --- a/circuits/aes-gcm/mul.circom +++ b/circuits/aes-gcm/mul.circom @@ -2,7 +2,7 @@ pragma circom 2.1.9; include "helper_functions.circom"; -// 64-bit wrapping multiplication. Assumes MSB is first (Big E) +// 64-bit BE wrapping multiplication. // Implements multiplication mod 2^{64}. template WrappingMul64() { signal input a[64]; diff --git a/circuits/test/parse_bytes.test.ts b/circuits/test/parse_bytes.test.ts new file mode 100644 index 0000000..dba7991 --- /dev/null +++ b/circuits/test/parse_bytes.test.ts @@ -0,0 +1,46 @@ + +import { assert } from "chai"; +import { WitnessTester } from "circomkit"; +import { circomkit, hexToBitArray } from "./common"; + +describe("ParseBytesBE", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester(`ParseBEBytes64`, { + file: "aes-gcm/helper_functions", + template: "ParseBEBytes64", + }); + }); + + it("Should parse bytes in BE order", async () => { + const X = hexToBitArray("0x0000000000000001"); + const expected = 1; + const _result = await circuit.compute({ in: X }, ["out"]); + const result = _result.out as number; + + assert.equal(result, expected, "parse incorrect"); + }); +}); + + +describe("ParseBytesLE", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester(`ParseLEBytes64`, { + file: "aes-gcm/helper_functions", + template: "ParseLEBytes64", + }); + }); + + it("Should parse bytes in LE order", async () => { + const X = hexToBitArray("0x0100000000000000"); + const expected = 1; + const _result = await circuit.compute({ in: X }, ["out"]); + const result = _result.out as number; + + assert.equal(result, expected, "parse incorrect"); + }); +}); + From 35549ee87cbfbd83ff9e7b1e0f985fa1b281de84 Mon Sep 17 00:00:00 2001 From: KaiGeffen Date: Thu, 12 Sep 2024 17:07:34 -0400 Subject: [PATCH 11/21] Switched suite name from Wrapping_LE to Wrapping_BE --- circuits/test/gfmul.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuits/test/gfmul.test.ts b/circuits/test/gfmul.test.ts index 111bc48..f229c1e 100644 --- a/circuits/test/gfmul.test.ts +++ b/circuits/test/gfmul.test.ts @@ -52,7 +52,7 @@ describe("MUL", () => { }); }); -describe("WRAPPING_LE", () => { +describe("WRAPPING_BE", () => { let circuit: WitnessTester<["a", "b"], ["out"]>; before(async () => { From 8a18505f5011108fdd0a78cfb4a30e8eee3295a4 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 12 Sep 2024 14:13:39 -0700 Subject: [PATCH 12/21] wrapping mul moved --- circuits/test/gfmul.test.ts | 85 ----------------------------- circuits/test/wrapping_mul.test.ts | 88 ++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 85 deletions(-) create mode 100644 circuits/test/wrapping_mul.test.ts diff --git a/circuits/test/gfmul.test.ts b/circuits/test/gfmul.test.ts index f229c1e..6c886c2 100644 --- a/circuits/test/gfmul.test.ts +++ b/circuits/test/gfmul.test.ts @@ -51,88 +51,3 @@ describe("MUL", () => { // assert.equal(result, EXPECT); }); }); - -describe("WRAPPING_BE", () => { - let circuit: WitnessTester<["a", "b"], ["out"]>; - - before(async () => { - circuit = await circomkit.WitnessTester(`WrappingMul64`, { - file: "aes-gcm/mul", - template: "WrappingMul64", - }); - }); - - it("wrapping mul low values", async () => { - const a = 2; - const a_arr = padArrayTo64Bits(numberToBitArray(a)); - for (var b = 1; b < 16; b++) { - const expected = numberTo16Hex(a * b); - const _res = await circuit.compute({ a: a_arr, b: hexToBitArray(numberTo16Hex(b)) }, ["out"]); - const result = bitArrayToHex( - (_res.out as (number | bigint)[]).map((bit) => Number(bit)) - ); - - assert.deepEqual(result, expected, "Multiplication result is incorrect"); - } - }) - - // todo: choose a better test case, the expected value is wrong - it("should correctly multiply two 64-bit numbers non-overflow", async () => { - const a = hexToBitArray("0x0000000000000002"); - const b = hexToBitArray("0x0000000000000004"); - const expected = "0000000000000008"; - - // await circuit.expectPass({ a, b }, { out: hexToBitArray(expected) }); - - // const _res = await circuit.calculateWitness({ a, b }); - const _res = await circuit.compute({ a, b }, ["out"]); - const result = bitArrayToHex( - (_res.out as (number | bigint)[]).map((bit) => Number(bit)) - ); - - assert.deepEqual(result, expected, "Multiplication result is incorrect"); - }); - - it("should handle multiplication with zero", async () => { - const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); - const b = hexToBitArray("0x0000000000000000"); - const expected = "0000000000000000"; - - const _res = await circuit.compute({ a, b }, ["out"]); - const result = bitArrayToHex( - (_res.out as (number | bigint)[]).map((bit) => Number(bit)) - ); - - assert.equal(result, expected, "Multiplication with zero is incorrect"); - }); - - // todo: choose a better test case, the expected value is wrong - it("should correctly multiply two 64-bit numbers with overflow", async () => { - const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); - const b = hexToBitArray("0x0000000000000002"); - const expected = "fffffffffffffffe"; - - // await circuit.expectPass({ a, b }, { out: hexToBitArray(expected) }); - - // const _res = await circuit.calculateWitness({ a, b }); - const _res = await circuit.compute({ a, b }, ["out"]); - const result = bitArrayToHex( - (_res.out as (number | bigint)[]).map((bit) => Number(bit)) - ); - - assert.deepEqual(result, expected, "Multiplication result is incorrect"); - }); - - it("should correctly wrap on maximum overflow", async () => { - const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); - const b = hexToBitArray("0xFFFFFFFFFFFFFFFF"); - const expected = "0000000000000001"; - - const _res = await circuit.compute({ a, b }, ["out"]); - const result = bitArrayToHex( - (_res.out as (number | bigint)[]).map((bit) => Number(bit)) - ); - - assert.equal(result, expected, "Wrap-around on overflow is incorrect"); - }); -}); diff --git a/circuits/test/wrapping_mul.test.ts b/circuits/test/wrapping_mul.test.ts new file mode 100644 index 0000000..18d295e --- /dev/null +++ b/circuits/test/wrapping_mul.test.ts @@ -0,0 +1,88 @@ +import { assert } from "chai"; +import { WitnessTester } from "circomkit"; +import { padArrayTo64Bits, bitArrayToHex, circomkit, hexToBitArray, numberTo16Hex, numberToBitArray } from "./common"; + +describe("WRAPPING_LE", () => { + let circuit: WitnessTester<["a", "b"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester(`WrappingMul64`, { + file: "aes-gcm/mul", + template: "WrappingMul64", + }); + }); + + it("wrapping mul low values", async () => { + const a = 2; + const a_arr = padArrayTo64Bits(numberToBitArray(a)); + for (var b = 1; b < 16; b++) { + const expected = numberTo16Hex(a * b); + const _res = await circuit.compute({ a: a_arr, b: hexToBitArray(numberTo16Hex(b)) }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + + assert.deepEqual(result, expected, "Multiplication result is incorrect"); + } + }) + + // todo: choose a better test case, the expected value is wrong + it("should correctly multiply two 64-bit numbers non-overflow", async () => { + const a = hexToBitArray("0x0000000000000002"); + const b = hexToBitArray("0x0000000000000004"); + const expected = "0000000000000008"; + + // await circuit.expectPass({ a, b }, { out: hexToBitArray(expected) }); + + // const _res = await circuit.calculateWitness({ a, b }); + const _res = await circuit.compute({ a, b }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + + assert.deepEqual(result, expected, "Multiplication result is incorrect"); + }); + + it("should handle multiplication with zero", async () => { + const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); + const b = hexToBitArray("0x0000000000000000"); + const expected = "0000000000000000"; + + const _res = await circuit.compute({ a, b }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + + assert.equal(result, expected, "Multiplication with zero is incorrect"); + }); + + // todo: choose a better test case, the expected value is wrong + it("should correctly multiply two 64-bit numbers with overflow", async () => { + const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); + const b = hexToBitArray("0x0000000000000002"); + const expected = "fffffffffffffffe"; + + // await circuit.expectPass({ a, b }, { out: hexToBitArray(expected) }); + + // const _res = await circuit.calculateWitness({ a, b }); + const _res = await circuit.compute({ a, b }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + + assert.deepEqual(result, expected, "Multiplication result is incorrect"); + }); + + it("should correctly wrap on maximum overflow", async () => { + const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); + const b = hexToBitArray("0xFFFFFFFFFFFFFFFF"); + const expected = "0000000000000001"; + + const _res = await circuit.compute({ a, b }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + + assert.equal(result, expected, "Wrap-around on overflow is incorrect"); + }); +}); From dcc42ae34a53a0fb6851f2188b83063282b9be32 Mon Sep 17 00:00:00 2001 From: KaiGeffen Date: Thu, 12 Sep 2024 17:24:15 -0400 Subject: [PATCH 13/21] Removed unused test impl / resolved test todos --- circuits/test/wrapping_mul.test.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/circuits/test/wrapping_mul.test.ts b/circuits/test/wrapping_mul.test.ts index 18d295e..d731729 100644 --- a/circuits/test/wrapping_mul.test.ts +++ b/circuits/test/wrapping_mul.test.ts @@ -2,7 +2,7 @@ import { assert } from "chai"; import { WitnessTester } from "circomkit"; import { padArrayTo64Bits, bitArrayToHex, circomkit, hexToBitArray, numberTo16Hex, numberToBitArray } from "./common"; -describe("WRAPPING_LE", () => { +describe("WRAPPING_BE", () => { let circuit: WitnessTester<["a", "b"], ["out"]>; before(async () => { @@ -26,15 +26,11 @@ describe("WRAPPING_LE", () => { } }) - // todo: choose a better test case, the expected value is wrong it("should correctly multiply two 64-bit numbers non-overflow", async () => { const a = hexToBitArray("0x0000000000000002"); const b = hexToBitArray("0x0000000000000004"); const expected = "0000000000000008"; - // await circuit.expectPass({ a, b }, { out: hexToBitArray(expected) }); - - // const _res = await circuit.calculateWitness({ a, b }); const _res = await circuit.compute({ a, b }, ["out"]); const result = bitArrayToHex( (_res.out as (number | bigint)[]).map((bit) => Number(bit)) @@ -56,15 +52,11 @@ describe("WRAPPING_LE", () => { assert.equal(result, expected, "Multiplication with zero is incorrect"); }); - // todo: choose a better test case, the expected value is wrong it("should correctly multiply two 64-bit numbers with overflow", async () => { const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); const b = hexToBitArray("0x0000000000000002"); const expected = "fffffffffffffffe"; - // await circuit.expectPass({ a, b }, { out: hexToBitArray(expected) }); - - // const _res = await circuit.calculateWitness({ a, b }); const _res = await circuit.compute({ a, b }, ["out"]); const result = bitArrayToHex( (_res.out as (number | bigint)[]).map((bit) => Number(bit)) From 0fb4b233046c4b181f93bfcd67bfd91677da2079 Mon Sep 17 00:00:00 2001 From: KaiGeffen Date: Thu, 12 Sep 2024 17:41:31 -0400 Subject: [PATCH 14/21] Added wrap_mul test, fixed minor scope smell --- circuits/test/wrapping_mul.test.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/circuits/test/wrapping_mul.test.ts b/circuits/test/wrapping_mul.test.ts index d731729..315732e 100644 --- a/circuits/test/wrapping_mul.test.ts +++ b/circuits/test/wrapping_mul.test.ts @@ -15,7 +15,7 @@ describe("WRAPPING_BE", () => { it("wrapping mul low values", async () => { const a = 2; const a_arr = padArrayTo64Bits(numberToBitArray(a)); - for (var b = 1; b < 16; b++) { + for (let b = 1; b < 16; b++) { const expected = numberTo16Hex(a * b); const _res = await circuit.compute({ a: a_arr, b: hexToBitArray(numberTo16Hex(b)) }, ["out"]); const result = bitArrayToHex( @@ -77,4 +77,30 @@ describe("WRAPPING_BE", () => { assert.equal(result, expected, "Wrap-around on overflow is incorrect"); }); + + it("should correctly wrap on maximum overflow", async () => { + const a = hexToBitArray("0xFFFFFFFFFFFFFFFF"); + const b = hexToBitArray("0xFFFFFFFFFFFFFFFF"); + const expected = "0000000000000001"; + + const _res = await circuit.compute({ a, b }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + + assert.equal(result, expected, "Wrap-around on overflow is incorrect"); + }); + + it("should correctly wrap on large numbers below max", async () => { + const a = hexToBitArray("0xa5a5a5a5a5a5a5a5"); + const b = hexToBitArray("0x5a5a5a5a5a5a5a5a"); + const expected = "a76b2ef2b67a3e02"; + + const _res = await circuit.compute({ a, b }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + + assert.equal(result, expected, "Wrap-around on overflow is incorrect"); + }); }); From 177658bc31dde931cd8c16148ae3a878938a9bc8 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 12 Sep 2024 16:35:01 -0700 Subject: [PATCH 15/21] tests written for bmul --- circuits/aes-gcm/mul.circom | 1 - circuits/test/gfmul.test.ts | 60 ++++++++++++++++++++++++++++--------- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/circuits/aes-gcm/mul.circom b/circuits/aes-gcm/mul.circom index 378a2e8..64a95a7 100644 --- a/circuits/aes-gcm/mul.circom +++ b/circuits/aes-gcm/mul.circom @@ -18,7 +18,6 @@ template WrappingMul64() { partials[i][j] <== a[i] * b[j]; } } - log(partials[62][63]); // 65, not 64, to allow for an extra carry without having to fiddle with overflow var sum[65]; diff --git a/circuits/test/gfmul.test.ts b/circuits/test/gfmul.test.ts index 6c886c2..49cfd39 100644 --- a/circuits/test/gfmul.test.ts +++ b/circuits/test/gfmul.test.ts @@ -2,10 +2,6 @@ import { assert } from "chai"; import { WitnessTester } from "circomkit"; import { padArrayTo64Bits, bitArrayToHex, circomkit, hexToBitArray, numberTo16Hex, numberToBitArray } from "./common"; -const X = hexToBitArray("0x0100000000000000"); -const Y = hexToBitArray("0x0100000000000000"); -const EXPECT = ""; - describe("BMUL64", () => { let circuit: WitnessTester<["x", "y"], ["out"]>; @@ -13,20 +9,56 @@ describe("BMUL64", () => { circuit = await circomkit.WitnessTester(`BMUL64`, { file: "aes-gcm/gfmul", template: "BMUL64", - // params: [8], }); }); - // let bit_array = [1,0,0,0,0,0,0,0]; - // let expected_output = [0,0,0,0,0,0,0,1].map((x) => BigInt(x)); - it("bmul64", async () => { + it("bmul64 multiplies 1", async () => { + const X = hexToBitArray("0x0000000000000001"); + const Y = hexToBitArray("0x0000000000000001"); + const expected = "0000000000000001"; const _res = await circuit.compute({ x: X, y: Y }, ["out"]); - // const result = bitArrayToHex( - // (_res.out as number[]).map((bit) => Number(bit)) - // ).slice(0, 32); - // console.log("expect: ", EXPECT, "\nresult: ", result); - // assert.equal(result, EXPECT); + const result = bitArrayToHex( + (_res.out as number[]).map((bit) => Number(bit)) + ).slice(0, 32); + + assert.equal(result, expected, "parse incorrect"); }); + + it("bmul64 multiplies 0", async () => { + const X = hexToBitArray("0x0000000000000000"); + const Y = hexToBitArray("0xFFFFFFFFFFFFFFFF"); + const expected = "0000000000000000"; + const _res = await circuit.compute({ x: X, y: Y }, ["out"]); + const result = bitArrayToHex( + (_res.out as number[]).map((bit) => Number(bit)) + ).slice(0, 32); + + assert.equal(result, expected, "parse incorrect"); + }); + + it("bmul64 multiplies large number", async () => { + const X = hexToBitArray("0x1111111111111111"); + const Y = hexToBitArray("0x1111111111111111"); + const expected = "101010101010101"; + const _res = await circuit.compute({ x: X, y: Y }, ["out"]); + const result = bitArrayToHex( + (_res.out as number[]).map((bit) => Number(bit)) + ).slice(0, 32); + + assert.equal(result, expected, "parse incorrect"); + }); + + // it("bmul64 multiplies large number 2", async () => { + // const X = hexToBitArray("0x1111222211118888"); + // const Y = hexToBitArray("0x1111222211118888"); + // const expected = "101010140404040"; + // const _res = await circuit.compute({ x: X, y: Y }, ["out"]); + // const result = bitArrayToHex( + // (_res.out as number[]).map((bit) => Number(bit)) + // ).slice(0, 32); + + // assert.equal(result, expected, "parse incorrect"); + // }); }); describe("MUL", () => { @@ -43,7 +75,7 @@ describe("MUL", () => { // let bit_array = [1,0,0,0,0,0,0,0]; // let expected_output = [0,0,0,0,0,0,0,1].map((x) => BigInt(x)); it("mul", async () => { - const _res = await circuit.compute({ h: X, rhs: Y }, ["out"]); + // const _res = await circuit.compute({ h: X, rhs: Y }, ["out"]); // const result = bitArrayToHex( // (_res.out as number[]).map((bit) => Number(bit)) // ).slice(0, 32); From f8fe80b95d84823a4dacfc7bfdd672dba3f194c4 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 12 Sep 2024 19:25:58 -0700 Subject: [PATCH 16/21] bugfix: indexing error in BMUL --- circuits/aes-gcm/gfmul.circom | 4 ++-- circuits/aes-gcm/gfmul_int.circom | 7 +----- circuits/aes-gcm/mul.circom | 1 - circuits/test/gfmul.test.ts | 34 ++++++++++++++++++++---------- circuits/test/wrapping_mul.test.ts | 13 ++++++++++++ 5 files changed, 39 insertions(+), 20 deletions(-) diff --git a/circuits/aes-gcm/gfmul.circom b/circuits/aes-gcm/gfmul.circom index a8130f5..c901228 100644 --- a/circuits/aes-gcm/gfmul.circom +++ b/circuits/aes-gcm/gfmul.circom @@ -236,7 +236,7 @@ template BMUL64() { signal z[4][64]; for (var i = 0; i < 4; i++) { for (var j = 0; j < 4; j++) { - var Y_INDEX = (i - j) % 4; + var Y_INDEX = (4 + i - j) % 4; muls[i][j] = WrappingMul64(); muls[i][j].a <== xs[j]; muls[i][j].b <== ys[Y_INDEX]; @@ -247,7 +247,7 @@ template BMUL64() { xor_multiples[i].inputs <== z_mid[i]; z[i] <== xor_multiples[i].out; } - + // z_masked[i] = z[i] & masks[i] signal z_masked[4][64]; for (var i = 0; i < 4; i++) { diff --git a/circuits/aes-gcm/gfmul_int.circom b/circuits/aes-gcm/gfmul_int.circom index 85a7e2f..31cd849 100644 --- a/circuits/aes-gcm/gfmul_int.circom +++ b/circuits/aes-gcm/gfmul_int.circom @@ -33,11 +33,6 @@ template GFMULInt() var i, j, k; - log("GFMULInt"); - log(a[0][0]); - // log(b); - // log("res", res); - component num2bits_1[2]; var XMMMASK_bits[2][64]; for(i=0; i<2; i++) @@ -188,4 +183,4 @@ template GFMULInt() res[i][j] <== xor_6[i][j].out; } } -} \ No newline at end of file +} diff --git a/circuits/aes-gcm/mul.circom b/circuits/aes-gcm/mul.circom index 64a95a7..c4108a1 100644 --- a/circuits/aes-gcm/mul.circom +++ b/circuits/aes-gcm/mul.circom @@ -9,7 +9,6 @@ template WrappingMul64() { signal input b[64]; signal output out[64]; - // Intermediate signals for partial products // partial[i,j corresponds to AND(a[i], b[j]) signal partials[64][64]; diff --git a/circuits/test/gfmul.test.ts b/circuits/test/gfmul.test.ts index 49cfd39..bd279c6 100644 --- a/circuits/test/gfmul.test.ts +++ b/circuits/test/gfmul.test.ts @@ -39,7 +39,7 @@ describe("BMUL64", () => { it("bmul64 multiplies large number", async () => { const X = hexToBitArray("0x1111111111111111"); const Y = hexToBitArray("0x1111111111111111"); - const expected = "101010101010101"; + const expected = "0101010101010101"; const _res = await circuit.compute({ x: X, y: Y }, ["out"]); const result = bitArrayToHex( (_res.out as number[]).map((bit) => Number(bit)) @@ -48,17 +48,29 @@ describe("BMUL64", () => { assert.equal(result, expected, "parse incorrect"); }); - // it("bmul64 multiplies large number 2", async () => { - // const X = hexToBitArray("0x1111222211118888"); - // const Y = hexToBitArray("0x1111222211118888"); - // const expected = "101010140404040"; - // const _res = await circuit.compute({ x: X, y: Y }, ["out"]); - // const result = bitArrayToHex( - // (_res.out as number[]).map((bit) => Number(bit)) - // ).slice(0, 32); + it("bmul64 multiplies large number 2", async () => { + const X = hexToBitArray("0x1111222211118888"); + const Y = hexToBitArray("0x1111222211118888"); + const expected = "0101010140404040"; + const _res = await circuit.compute({ x: X, y: Y }, ["out"]); + const result = bitArrayToHex( + (_res.out as number[]).map((bit) => Number(bit)) + ).slice(0, 32); + + assert.equal(result, expected, "parse incorrect"); + }); - // assert.equal(result, expected, "parse incorrect"); - // }); + it("bmul64 multiplies large number 3", async () => { + const X = hexToBitArray("0xCFAF222D1A198287"); + const Y = hexToBitArray("0xFBFF2C2218118182"); + const expected = "40468c9202c4418e"; + const _res = await circuit.compute({ x: X, y: Y }, ["out"]); + const result = bitArrayToHex( + (_res.out as number[]).map((bit) => Number(bit)) + ).slice(0, 32); + + assert.equal(result, expected, "parse incorrect"); + }); }); describe("MUL", () => { diff --git a/circuits/test/wrapping_mul.test.ts b/circuits/test/wrapping_mul.test.ts index 315732e..9f0f42a 100644 --- a/circuits/test/wrapping_mul.test.ts +++ b/circuits/test/wrapping_mul.test.ts @@ -103,4 +103,17 @@ describe("WRAPPING_BE", () => { assert.equal(result, expected, "Wrap-around on overflow is incorrect"); }); + + it("should correctly multiply this one particular case", async () => { + const a = hexToBitArray("0x0000000000008888"); + const b = hexToBitArray("0x0000000000008888"); + const expected = "0000000048d0c840"; + + const _res = await circuit.compute({ a, b }, ["out"]); + const result = bitArrayToHex( + (_res.out as (number | bigint)[]).map((bit) => Number(bit)) + ); + + assert.equal(result, expected, "Wrap-around on overflow is incorrect"); + }); }); From 42fe433a062d59bc6f8740aa2e1ab89c618dc0f8 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 12 Sep 2024 20:20:40 -0700 Subject: [PATCH 17/21] tests for gfmul --- circuits/test/gfmul.test.ts | 92 +++++++++++++++++++++++++++++-------- 1 file changed, 74 insertions(+), 18 deletions(-) diff --git a/circuits/test/gfmul.test.ts b/circuits/test/gfmul.test.ts index bd279c6..d610a36 100644 --- a/circuits/test/gfmul.test.ts +++ b/circuits/test/gfmul.test.ts @@ -2,6 +2,10 @@ import { assert } from "chai"; import { WitnessTester } from "circomkit"; import { padArrayTo64Bits, bitArrayToHex, circomkit, hexToBitArray, numberTo16Hex, numberToBitArray } from "./common"; +const ZERO = hexToBitArray("0x000000000000000"); +const ONE = hexToBitArray("0x0000000000000001"); +const MAX = hexToBitArray("0xFFFFFFFFFFFFFFFF"); + describe("BMUL64", () => { let circuit: WitnessTester<["x", "y"], ["out"]>; @@ -13,10 +17,8 @@ describe("BMUL64", () => { }); it("bmul64 multiplies 1", async () => { - const X = hexToBitArray("0x0000000000000001"); - const Y = hexToBitArray("0x0000000000000001"); const expected = "0000000000000001"; - const _res = await circuit.compute({ x: X, y: Y }, ["out"]); + const _res = await circuit.compute({ x: ONE, y: ONE }, ["out"]); const result = bitArrayToHex( (_res.out as number[]).map((bit) => Number(bit)) ).slice(0, 32); @@ -25,10 +27,8 @@ describe("BMUL64", () => { }); it("bmul64 multiplies 0", async () => { - const X = hexToBitArray("0x0000000000000000"); - const Y = hexToBitArray("0xFFFFFFFFFFFFFFFF"); const expected = "0000000000000000"; - const _res = await circuit.compute({ x: X, y: Y }, ["out"]); + const _res = await circuit.compute({ x: ZERO, y: MAX }, ["out"]); const result = bitArrayToHex( (_res.out as number[]).map((bit) => Number(bit)) ).slice(0, 32); @@ -73,25 +73,81 @@ describe("BMUL64", () => { }); }); -describe("MUL", () => { - let circuit: WitnessTester<["h", "rhs"], ["out"]>; +describe("GF_MUL", () => { + let circuit: WitnessTester<["a", "b"], ["out"]>; before(async () => { circuit = await circomkit.WitnessTester(`MUL64`, { file: "aes-gcm/gfmul", template: "MUL", - // params: [8], }); }); - // let bit_array = [1,0,0,0,0,0,0,0]; - // let expected_output = [0,0,0,0,0,0,0,1].map((x) => BigInt(x)); - it("mul", async () => { - // const _res = await circuit.compute({ h: X, rhs: Y }, ["out"]); - // const result = bitArrayToHex( - // (_res.out as number[]).map((bit) => Number(bit)) - // ).slice(0, 32); - // console.log("expect: ", EXPECT, "\nresult: ", result); - // assert.equal(result, EXPECT); + it("GF_MUL 0", async () => { + const expected = "0000000000000000"; + const _res = await circuit.compute({ a: [MAX, MAX], b: [ZERO, ZERO] }, ["out"]); + const result = bitArrayToHex( + (_res.out as number[]).map((bit) => Number(bit)) + ).slice(0, 64); + + assert.deepEqual(result, expected, "parse incorrect"); + }); + + it("GF_MUL 1", async () => { + const expected = "0000000000000001"; + + const _res = await circuit.compute({ a: [ZERO, ONE], b: [ZERO, ONE] }, ["out"]); + const result = bitArrayToHex( + (_res.out as number[]).map((bit) => Number(bit)) + ).slice(0, 64); + + assert.equal(result, expected, "parse incorrect"); + }); + + it("GF_MUL 2", async () => { + const expected = "C323456789ABCDEF0000000000000001"; + const _res = await circuit.compute({ a: [ZERO, ONE], b: [ONE, ZERO] }, ["out"]); + const result = bitArrayToHex( + (_res.out as number[]).map((bit) => Number(bit)) + ).slice(0, 64); + + assert.equal(result, expected, "parse incorrect"); + }); + + it("GF_MUL 3", async () => { + const A = hexToBitArray("0x00000000000000F1"); + const B = hexToBitArray("0x000000000000BB00"); + const expected = "006F2B000000000000000000"; + + const _res = await circuit.compute({ a: [ZERO, A], b: [ZERO, B] }, ["out"]); + const result = bitArrayToHex( + (_res.out as number[]).map((bit) => Number(bit)) + ).slice(0, 64); + + assert.equal(result, expected, "parse incorrect"); + }); + + it("GF_MUL 4", async () => { + const A = hexToBitArray("0x00000000000000F1"); + const B = hexToBitArray("0x000000000000BB00"); + const expected = "006F2B000000000000000000"; + + const _res = await circuit.compute({ a: [ZERO, A], b: [B, ZERO] }, ["out"]); + const result = bitArrayToHex( + (_res.out as number[]).map((bit) => Number(bit)) + ).slice(0, 64); + + assert.equal(result, expected, "parse incorrect"); + }); + + it("GF_MUL 5", async () => { + const expected = "55555555555555557A01555555555555"; + + const _res = await circuit.compute({ a: [MAX, MAX], b: [MAX, MAX] }, ["out"]); + const result = bitArrayToHex( + (_res.out as number[]).map((bit) => Number(bit)) + ).slice(0, 64); + + assert.equal(result, expected, "parse incorrect"); }); }); From 30460742c8e1ecc81bfdc080b1bc73ea2dbfb804 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Thu, 12 Sep 2024 23:01:32 -0700 Subject: [PATCH 18/21] annotate gfmul empty bits issue --- circuits/aes-gcm/gfmul.circom | 6 +++++- circuits/test/gfmul.test.ts | 28 ++++++++++++++++------------ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/circuits/aes-gcm/gfmul.circom b/circuits/aes-gcm/gfmul.circom index c901228..a6aab63 100644 --- a/circuits/aes-gcm/gfmul.circom +++ b/circuits/aes-gcm/gfmul.circom @@ -187,7 +187,11 @@ template MUL() { XorMultiples_L[1].inputs <== [_v2, LS_v[0].out, LS_v[1].out, LS_v[2].out]; __v2 <== XorMultiples_L[1].out; - out <== [__v2, _v3]; + log(__v2[0]); + log(_v3[0]); + out[0] <== __v2; + out[1] <== _v3; + // out <== [__v2, _v3]; } // Multiplication in GF(2)[X], truncated to the low 64-bits, with “holes” diff --git a/circuits/test/gfmul.test.ts b/circuits/test/gfmul.test.ts index d610a36..ed56d9a 100644 --- a/circuits/test/gfmul.test.ts +++ b/circuits/test/gfmul.test.ts @@ -3,7 +3,7 @@ import { WitnessTester } from "circomkit"; import { padArrayTo64Bits, bitArrayToHex, circomkit, hexToBitArray, numberTo16Hex, numberToBitArray } from "./common"; const ZERO = hexToBitArray("0x000000000000000"); -const ONE = hexToBitArray("0x0000000000000001"); +const BE_ONE = hexToBitArray("0x0000000000000001"); const MAX = hexToBitArray("0xFFFFFFFFFFFFFFFF"); describe("BMUL64", () => { @@ -18,7 +18,7 @@ describe("BMUL64", () => { it("bmul64 multiplies 1", async () => { const expected = "0000000000000001"; - const _res = await circuit.compute({ x: ONE, y: ONE }, ["out"]); + const _res = await circuit.compute({ x: BE_ONE, y: BE_ONE }, ["out"]); const result = bitArrayToHex( (_res.out as number[]).map((bit) => Number(bit)) ).slice(0, 32); @@ -84,29 +84,30 @@ describe("GF_MUL", () => { }); it("GF_MUL 0", async () => { - const expected = "0000000000000000"; - const _res = await circuit.compute({ a: [MAX, MAX], b: [ZERO, ZERO] }, ["out"]); - const result = bitArrayToHex( - (_res.out as number[]).map((bit) => Number(bit)) - ).slice(0, 64); - - assert.deepEqual(result, expected, "parse incorrect"); + const expected = hexToBitArray("0000000000000000"); + await circuit.expectPass({ a: [MAX, MAX], b: [ZERO, ZERO] }, { out: [expected, expected] }); }); + // TODO(TK 2024-09-12): expected is 16 bytes, when it should be 32 bytes. + // How do I obtain all 32 bytes in the `out` field? it("GF_MUL 1", async () => { const expected = "0000000000000001"; + // const expected = "00000000000000000000000000000001"; + + const _res = await circuit.compute({ a: [ZERO, BE_ONE], b: [ZERO, BE_ONE] }, ["out"]); + // const _res = await circuit.compute({ a: [ZERO, BE_ONE], b: [ZERO, BE_ONE] }, ["out[0]", "out[1]"]); + // console.log(_res.out as number[]); - const _res = await circuit.compute({ a: [ZERO, ONE], b: [ZERO, ONE] }, ["out"]); const result = bitArrayToHex( (_res.out as number[]).map((bit) => Number(bit)) - ).slice(0, 64); + ); assert.equal(result, expected, "parse incorrect"); }); it("GF_MUL 2", async () => { const expected = "C323456789ABCDEF0000000000000001"; - const _res = await circuit.compute({ a: [ZERO, ONE], b: [ONE, ZERO] }, ["out"]); + const _res = await circuit.compute({ a: [ZERO, BE_ONE], b: [BE_ONE, ZERO] }, ["out"]); const result = bitArrayToHex( (_res.out as number[]).map((bit) => Number(bit)) ).slice(0, 64); @@ -120,9 +121,12 @@ describe("GF_MUL", () => { const expected = "006F2B000000000000000000"; const _res = await circuit.compute({ a: [ZERO, A], b: [ZERO, B] }, ["out"]); + // console.log(_res.out); + console.log(_res.out as number[]); const result = bitArrayToHex( (_res.out as number[]).map((bit) => Number(bit)) ).slice(0, 64); + console.log(result); assert.equal(result, expected, "parse incorrect"); }); From a37608cba44e2c11c2e8d632838a36c3bfd64d5d Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Fri, 13 Sep 2024 01:09:18 -0700 Subject: [PATCH 19/21] gfmul debugged --- circuits/aes-gcm/gfmul.circom | 35 ++++++++++-------------- circuits/aes-gcm/helper_functions.circom | 25 +++++++---------- circuits/test/gfmul.test.ts | 6 ++-- 3 files changed, 28 insertions(+), 38 deletions(-) diff --git a/circuits/aes-gcm/gfmul.circom b/circuits/aes-gcm/gfmul.circom index a6aab63..8d1df8b 100644 --- a/circuits/aes-gcm/gfmul.circom +++ b/circuits/aes-gcm/gfmul.circom @@ -36,6 +36,7 @@ template MUL() { h[1] <== a[1]; y[0] <== b[0]; y[1] <== b[1]; + component Revs[4]; for (var i = 0; i < 2; i++) { Revs[i] = REV64(); @@ -82,7 +83,7 @@ template MUL() { BMUL64_z[i+3].y <== h_r[i]; zh[i] <== BMUL64_z[i+3].out; } - + // _z2 = z0 ^ z1 ^ z2; // _z2h = z0h ^ z1h ^ z2h; signal _z2[64]; @@ -94,14 +95,13 @@ template MUL() { XorMultiples[1] = XorMultiple(3, 64); XorMultiples[1].inputs <== zh; - _zh[0] <== XorMultiples[1].out; + _zh[2] <== XorMultiples[1].out; _zh[1] <== zh[1]; - _zh[2] <== zh[2]; + _zh[0] <== zh[0]; - // z0h = rev64(z0h) >> 1; - // z1h = rev64(z1h) >> 1; - // _z2h = rev64(_z2h) >> 1; - // signal _zh[3][64]; + // __z0h = rev64(z0h) >> 1; + // __z1h = rev64(z1h) >> 1; + // __z2h = rev64(_z2h) >> 1; signal __zh[3][64]; component Revs_zh[3]; component RightShifts_zh[3]; @@ -120,7 +120,6 @@ template MUL() { signal v[4][64]; component Xors_v[2]; v[0] <== z[0]; - v[3] <== __zh[1]; Xors_v[0] = BitwiseXor(64); Xors_v[0].a <== __zh[0]; Xors_v[0].b <== _z2; @@ -129,16 +128,12 @@ template MUL() { Xors_v[1].a <== z[1]; Xors_v[1].b <== __zh[2]; v[2] <== Xors_v[1].out; - + v[3] <== __zh[1]; // _v2 = v2 ^ v0 ^ (v0 >> 1) ^ (v0 >> 2) ^ (v0 >> 7); // _v1 = v1 ^ (v0 << 63) ^ (v0 << 62) ^ (v0 << 57); - // _v3 = v3 ^ _v1 ^ (_v1 >> 1) ^ (_v1 >> 2) ^ (_v1 >> 7); - // __v2 = _v2 ^ (_v1 << 63) ^ (_v1 << 62) ^ (_v1 << 57); signal _v2[64]; signal _v1[64]; - signal _v3[64]; - signal __v2[64]; component RS_v[6]; component LS_v[6]; @@ -168,6 +163,10 @@ template MUL() { XorMultiples_L[0].inputs <== [v[1], LS_v[0].out, LS_v[1].out, LS_v[2].out]; _v1 <== XorMultiples_L[0].out; + // __v3 = v3 ^ _v1 ^ (_v1 >> 1) ^ (_v1 >> 2) ^ (_v1 >> 7); + // __v2 = _v2 ^ (_v1 << 63) ^ (_v1 << 62) ^ (_v1 << 57); + signal __v3[64]; + signal __v2[64]; RS_v[3] = BitwiseRightShift(64, 1); RS_v[3].in <== _v1; RS_v[4] = BitwiseRightShift(64, 2); @@ -183,15 +182,11 @@ template MUL() { LS_v[5].in <== _v1; XorMultiples_R[1].inputs <== [v[3], _v1, RS_v[3].out, RS_v[4].out, RS_v[5].out]; - _v3 <== XorMultiples_R[1].out; - XorMultiples_L[1].inputs <== [_v2, LS_v[0].out, LS_v[1].out, LS_v[2].out]; + __v3 <== XorMultiples_R[1].out; + XorMultiples_L[1].inputs <== [_v2, LS_v[3].out, LS_v[4].out, LS_v[5].out]; __v2 <== XorMultiples_L[1].out; - log(__v2[0]); - log(_v3[0]); - out[0] <== __v2; - out[1] <== _v3; - // out <== [__v2, _v3]; + out <== [__v2, __v3]; } // Multiplication in GF(2)[X], truncated to the low 64-bits, with “holes” diff --git a/circuits/aes-gcm/helper_functions.circom b/circuits/aes-gcm/helper_functions.circom index a581f47..74ed1a5 100644 --- a/circuits/aes-gcm/helper_functions.circom +++ b/circuits/aes-gcm/helper_functions.circom @@ -46,13 +46,11 @@ template ParseBEBytes64() { template BitwiseRightShift(n, r) { signal input in[n]; signal output out[n]; - - for(var i=0; i=n){ - out[i] <== 0; - } else { - out[i] <== in[i+r]; - } + for (var i=0; i { }); it("GF_MUL 4", async () => { - const A = hexToBitArray("0x00000000000000F1"); - const B = hexToBitArray("0x000000000000BB00"); + const f1 = hexToBitArray("0x00000000000000F1"); + const bb = hexToBitArray("0x000000000000BB00"); const expected = "006F2B000000000000000000"; - const _res = await circuit.compute({ a: [ZERO, A], b: [B, ZERO] }, ["out"]); + const _res = await circuit.compute({ a: [bb, ZERO], b: [ZERO, f1] }, ["out"]); const result = bitArrayToHex( (_res.out as number[]).map((bit) => Number(bit)) ).slice(0, 64); From c585377fa4240e846826cd5de9b8a7a7ccdf886a Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Fri, 13 Sep 2024 11:48:30 -0700 Subject: [PATCH 20/21] adjusted expected values in gfmul tests; circomkit still only capturing 64 of 128 bits in out --- circuits/test/gfmul.test.ts | 35 +++++++++++------------------------ 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/circuits/test/gfmul.test.ts b/circuits/test/gfmul.test.ts index a2012a9..b7689a4 100644 --- a/circuits/test/gfmul.test.ts +++ b/circuits/test/gfmul.test.ts @@ -77,48 +77,35 @@ describe("GF_MUL", () => { let circuit: WitnessTester<["a", "b"], ["out"]>; before(async () => { - circuit = await circomkit.WitnessTester(`MUL64`, { + circuit = await circomkit.WitnessTester(`MUL`, { file: "aes-gcm/gfmul", template: "MUL", }); }); it("GF_MUL 0", async () => { - const expected = hexToBitArray("0000000000000000"); - await circuit.expectPass({ a: [MAX, MAX], b: [ZERO, ZERO] }, { out: [expected, expected] }); + await circuit.expectPass({ a: [MAX, MAX], b: [ZERO, ZERO] }, { out: [ZERO, ZERO] }); }); // TODO(TK 2024-09-12): expected is 16 bytes, when it should be 32 bytes. // How do I obtain all 32 bytes in the `out` field? it("GF_MUL 1", async () => { - const expected = "0000000000000001"; - // const expected = "00000000000000000000000000000001"; - - const _res = await circuit.compute({ a: [ZERO, BE_ONE], b: [ZERO, BE_ONE] }, ["out"]); - // const _res = await circuit.compute({ a: [ZERO, BE_ONE], b: [ZERO, BE_ONE] }, ["out[0]", "out[1]"]); - // console.log(_res.out as number[]); - - const result = bitArrayToHex( - (_res.out as number[]).map((bit) => Number(bit)) - ); - - assert.equal(result, expected, "parse incorrect"); + await circuit.expectPass({ a: [ZERO, BE_ONE], b: [ZERO, BE_ONE] }, { out: [BE_ONE, ZERO] }); }); it("GF_MUL 2", async () => { - const expected = "C323456789ABCDEF0000000000000001"; - const _res = await circuit.compute({ a: [ZERO, BE_ONE], b: [BE_ONE, ZERO] }, ["out"]); - const result = bitArrayToHex( - (_res.out as number[]).map((bit) => Number(bit)) - ).slice(0, 64); - - assert.equal(result, expected, "parse incorrect"); + // const P1 = hexToBitArray("C323456789ABCDEF"); + // const P2 = hexToBitArray("0000000000000001"); + const E1 = hexToBitArray("C200000000000000"); + const E2 = hexToBitArray("0000000000000001"); + await circuit.expectPass({ a: [ZERO, BE_ONE], b: [BE_ONE, ZERO] }, { out: [E2, E1] }); + // await circuit.expectPass({ a: [ZERO, BE_ONE], b: [BE_ONE, ZERO] }, { out: [E1, E2] }); }); it("GF_MUL 3", async () => { const A = hexToBitArray("0x00000000000000F1"); const B = hexToBitArray("0x000000000000BB00"); - const expected = "006F2B000000000000000000"; + const expected = "00000000006F2B000000000000000000"; const _res = await circuit.compute({ a: [ZERO, A], b: [ZERO, B] }, ["out"]); // console.log(_res.out); @@ -134,7 +121,7 @@ describe("GF_MUL", () => { it("GF_MUL 4", async () => { const f1 = hexToBitArray("0x00000000000000F1"); const bb = hexToBitArray("0x000000000000BB00"); - const expected = "006F2B000000000000000000"; + const expected = "0000000000000000000000000043AA16"; const _res = await circuit.compute({ a: [bb, ZERO], b: [ZERO, f1] }, ["out"]); const result = bitArrayToHex( From b11e9beaffe082bbb62d5b01ca643a13cb600af3 Mon Sep 17 00:00:00 2001 From: Thor Kampefner Date: Fri, 13 Sep 2024 16:22:31 -0700 Subject: [PATCH 21/21] gfmul tests fixed --- circuits/test/gfmul.test.ts | 43 +++++++------------------------------ 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/circuits/test/gfmul.test.ts b/circuits/test/gfmul.test.ts index b7689a4..6ccf9ea 100644 --- a/circuits/test/gfmul.test.ts +++ b/circuits/test/gfmul.test.ts @@ -87,58 +87,31 @@ describe("GF_MUL", () => { await circuit.expectPass({ a: [MAX, MAX], b: [ZERO, ZERO] }, { out: [ZERO, ZERO] }); }); - // TODO(TK 2024-09-12): expected is 16 bytes, when it should be 32 bytes. - // How do I obtain all 32 bytes in the `out` field? it("GF_MUL 1", async () => { await circuit.expectPass({ a: [ZERO, BE_ONE], b: [ZERO, BE_ONE] }, { out: [BE_ONE, ZERO] }); }); it("GF_MUL 2", async () => { - // const P1 = hexToBitArray("C323456789ABCDEF"); - // const P2 = hexToBitArray("0000000000000001"); const E1 = hexToBitArray("C200000000000000"); const E2 = hexToBitArray("0000000000000001"); - await circuit.expectPass({ a: [ZERO, BE_ONE], b: [BE_ONE, ZERO] }, { out: [E2, E1] }); - // await circuit.expectPass({ a: [ZERO, BE_ONE], b: [BE_ONE, ZERO] }, { out: [E1, E2] }); + await circuit.expectPass({ a: [ZERO, BE_ONE], b: [BE_ONE, ZERO] }, { out: [E1, E2] }); }); it("GF_MUL 3", async () => { - const A = hexToBitArray("0x00000000000000F1"); - const B = hexToBitArray("0x000000000000BB00"); - const expected = "00000000006F2B000000000000000000"; - - const _res = await circuit.compute({ a: [ZERO, A], b: [ZERO, B] }, ["out"]); - // console.log(_res.out); - console.log(_res.out as number[]); - const result = bitArrayToHex( - (_res.out as number[]).map((bit) => Number(bit)) - ).slice(0, 64); - console.log(result); - - assert.equal(result, expected, "parse incorrect"); + const E1 = hexToBitArray("0x00000000006F2B00"); + await circuit.expectPass({ a: [ZERO, hexToBitArray("0x00000000000000F1")], b: [ZERO, hexToBitArray("0x000000000000BB00")] }, { out: [E1, ZERO] }); }); it("GF_MUL 4", async () => { + const E1 = hexToBitArray("0x000000000043AA16"); const f1 = hexToBitArray("0x00000000000000F1"); const bb = hexToBitArray("0x000000000000BB00"); - const expected = "0000000000000000000000000043AA16"; - - const _res = await circuit.compute({ a: [bb, ZERO], b: [ZERO, f1] }, ["out"]); - const result = bitArrayToHex( - (_res.out as number[]).map((bit) => Number(bit)) - ).slice(0, 64); - - assert.equal(result, expected, "parse incorrect"); + await circuit.expectPass({ a: [bb, ZERO], b: [ZERO, f1] }, { out: [ZERO, E1] }); }); it("GF_MUL 5", async () => { - const expected = "55555555555555557A01555555555555"; - - const _res = await circuit.compute({ a: [MAX, MAX], b: [MAX, MAX] }, ["out"]); - const result = bitArrayToHex( - (_res.out as number[]).map((bit) => Number(bit)) - ).slice(0, 64); - - assert.equal(result, expected, "parse incorrect"); + const fives = hexToBitArray("0x5555555555555555"); + const rest = hexToBitArray("0x7A01555555555555"); + await circuit.expectPass({ a: [MAX, MAX], b: [MAX, MAX] }, { out: [fives, rest] }); }); });