From adf1b4f60c117a67a5c3bc0aae58ce59114b0b8f Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Wed, 18 Sep 2024 15:04:06 -0600 Subject: [PATCH 01/10] block rightshift --- Cargo.lock | 7 + Cargo.toml | 1 + circuits/aes-gcm/nistgmul.circom | 215 +++++++++++++++++++++++ circuits/test/common/index.ts | 21 +++ circuits/test/gfmulint/nistgfmul.test.ts | 165 +++++++++++++++++ nistgmul.circom | 12 ++ src/main.rs | 31 ++++ 7 files changed, 452 insertions(+) create mode 100644 circuits/aes-gcm/nistgmul.circom create mode 100644 circuits/test/gfmulint/nistgfmul.test.ts create mode 100644 nistgmul.circom diff --git a/Cargo.lock b/Cargo.lock index f21ce3e..09614e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,6 +110,7 @@ dependencies = [ "ctr 0.9.2", "ghash 0.5.1", "hex", + "hex-literal", "serde", "serde_json", "tokio", @@ -1140,6 +1141,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "hmac" version = "0.12.1" diff --git a/Cargo.toml b/Cargo.toml index 9ace1be..ee430e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ ark-serialize = { version = "0.4.1", default-features = false } anyhow = "1.0.86" serde = "1.0.204" serde_json = "1.0.122" +hex-literal = "0.4.1" [profile.release] lto = true diff --git a/circuits/aes-gcm/nistgmul.circom b/circuits/aes-gcm/nistgmul.circom new file mode 100644 index 0000000..281602c --- /dev/null +++ b/circuits/aes-gcm/nistgmul.circom @@ -0,0 +1,215 @@ +pragma circom 2.1.9; + +include "utils.circom"; // xor +include "circomlib/circuits/comparators.circom"; // isZero +include "helper_functions.circom"; // bitwise right shift +include "circomlib/circuits/mux1.circom"; // multiplexer + +// Algorithm 1: X •Y +// Input: +// blocks X, Y. +// Output: +// block X •Y. +// multiplication of two blocks in the binary extension field defined by the irreducible polynomial +// 1 + X + X^2 + X^7 + X^128 +// computes a “product” block, denoted X •Y: + +template NistGMulBit() { + + signal input X[128]; + signal input Y[128]; + signal output out[128]; + + // Let R be the bit string 11100001 || 0120. Given two blocks X and Y + var R[128] = [ + 1, 1, 1, 0, 0, 0, 0, 1, // 8 + 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 0, 0, 0, 0, 0, 0, 0, 0, // 24 + 0, 0, 0, 0, 0, 0, 0, 0, // 32 + 0, 0, 0, 0, 0, 0, 0, 0, // 40 + 0, 0, 0, 0, 0, 0, 0, 0, // 48 + 0, 0, 0, 0, 0, 0, 0, 0, // 56 + 0, 0, 0, 0, 0, 0, 0, 0, // 64 + 0, 0, 0, 0, 0, 0, 0, 0, // 72 + 0, 0, 0, 0, 0, 0, 0, 0, // 80 + 0, 0, 0, 0, 0, 0, 0, 0, // 88 + 0, 0, 0, 0, 0, 0, 0, 0, // 96 + 0, 0, 0, 0, 0, 0, 0, 0, // 104 + 0, 0, 0, 0, 0, 0, 0, 0, // 112 + 0, 0, 0, 0, 0, 0, 0, 0, // 120 + 0, 0, 0, 0, 0, 0, 0, 0 + ]; + + // 1. Let x0, x1...x127 denote the sequence of bits in X. + // 2. Let Z0 = 0128 and V0 = Y. + signal Z[128]; + Z[0] <== 0; + /// State accumulator + signal V[128][128]; + V[0] <== Y; + + // + // 3. For i = 0 to 127, calculate blocks Zi+1 and Vi+1 as follows: + // + // ⎧ Zi if xi = 0; + // Zi+1 ⎨ + // ⎩ Zi ⊕Vi if xi = 1. + // + // ⎧ Vi >>1 if LSB1(Vi) = 0; // example 01101010 >> 1 = 00110101 right shift + // Vi+1 ⎨ // lsb1(001) = 1 (rightmost bit is 1) + // ⎩ (Vi >>1) ⊕ R if LSB1(Vi) = 1. + // + component XorBit[128]; + component XorArr[128]; + component IsZero[128]; + component Zmux[128]; + component Vmux[128]; + component BitwiseRightShift[128]; + for (var i = 0; i < 127; i++) { + IsZero[i] = IsZero(); + IsZero[i].in <== X[i]; + Zmux[i] = Mux1(); + Zmux[i].s <== IsZero[i].out; // selector if 0, if yes return 1, else return zero + + Zmux[i].c[1] <== Z[i]; // selector 1 + // if (IsZero[i].out == 0) { + // Z[i +1] <== Z[i]; + // } else { + XorBit[i] = XOR(); + XorBit[i].a <== Z[i]; + XorBit[i].b <== V[i][i]; + Zmux[i].c[0] <==XorBit[i].out; // if selector is 0 + // } + Z[i+1] <== Zmux[i].out; + + BitwiseRightShift[i] = BitwiseRightShift(128, 1); + BitwiseRightShift[i].in <== V[i]; + + Vmux[i] = ArrayMux(128); + Vmux[i].sel <== V[i][127]; // selector is LSB + Vmux[i].a <== BitwiseRightShift[i].out; // if selector is 0 + + XorArr[i] = BitwiseXor(128); + XorArr[i].a <== BitwiseRightShift[i].out; + XorArr[i].b <== R; + Vmux[i].b <== XorArr[i].out; // if selector is 1 + V[i+1] <== Vmux[i].out; + + } + // 4. Return Z128. + out <== Z; +} + +template ArrayMux(n) { + signal input a[n]; // First input array + signal input b[n]; // Second input array + signal input sel; // Selector signal (0 or 1) + signal output out[n]; // Output array + + for (var i = 0; i < n; i++) { + // If sel = 0, out[i] = b[i] + // If sel = 1, out[i] = a[i] + out[i] <== (a[i] - b[i]) * sel + b[i]; + } +} + +// template NistGMulByte() { + +// signal input X[16]; +// signal input Y[16]; +// signal output out[16]; + +// // Let R be the bit string 11100001 || 0120. Given two blocks X and Y +// // byte 0xE1 is 11100001 in binary +// var R[16] = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + +// // 1. Let x0, x1...x127 denote the sequence of bits in X. +// // 2. Let Z0 = 0128 and V0 = Y. +// signal Z[16] <== [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; +// /// State accumulator. ie. V[i] is V0 holding 16 bytes +// signal V[16][16]; +// V[0] <== Y; + +// // 3. For i = 0 to 127, calculate blocks Zi+1 and Vi+1 as follows: +// // +// // ⎧ Zi if xi = 0; +// // Zi+1 ⎨ +// // ⎩ Zi ⊕Vi if xi =1. +// // +// // ⎧ Vi >>1 if LSB1(Vi) = 0; +// // Vi+1 ⎨ +// // ⎩ (Vi >>1) ⊕ R if LSB1(Vi) =1. +// // +// component XorByte[16]; +// component IsZero[16]; +// component Zmux[16]; +// component Vmux[16]; +// component BitwiseRightShift[16]; +// for (var i = 0; i < 15; i++) { + +// IsZero[i] = IsZero(); +// IsZero[i].in <== X[i]; +// if (IsZero[i].out == 0) { +// Z[i +1] <== Z[i]; +// V[i] <== V[i]; +// } else { +// XorByte[i] = XorByte(); +// XorByte[i].a <== Z[i]; +// XorByte[i].b <== V[i]; +// Z[i] <== XorByte[i].out; +// } +// } +// // 4. Return Z128. + +// } + +// right shifts 16 bytes by one bit and returns the msb before the shift +template BlockRightShift() { + signal input in[16]; + signal output out[16]; + signal output msb; + + signal shiftedbits[128]; + component bytesToBits = BytesToBits(16); + for (var i = 0; i < 16; i++) { + bytesToBits.in[i] <== in[i]; + } + msb <== bytesToBits.out[127]; + + component BitwiseRightShift = BitwiseRightShift(128, 1); + BitwiseRightShift.in <== bytesToBits.out; + shiftedbits <== BitwiseRightShift.out; + + component bitsToBytes = BitsToBytes(16); + bitsToBytes.in <== shiftedbits; + out <== bitsToBytes.out; +} + +// n is the number of bytes to convert to bits +template BytesToBits(n) { + signal input in[n]; + signal output out[n*8]; + component num2bits[n]; + for (var i = 0; i < n; i++) { + num2bits[i] = Num2Bits(8); + num2bits[i].in <== in[i]; + for (var j = 7; j >=0; j--) { + out[i*8 + j] <== num2bits[i].out[7 -j]; + } + } +} + +// n is the number of bytes we want +template BitsToBytes(n) { + signal input in[n*8]; + signal output out[n]; + component bits2num[n]; + for (var i = 0; i < n; i++) { + bits2num[i] = Bits2Num(8); + for (var j = 0; j < 8; j++) { + bits2num[i].in[7 - j] <== in[i*8 + j]; + } + out[i] <== bits2num[i].out; + } +} + diff --git a/circuits/test/common/index.ts b/circuits/test/common/index.ts index 385bcc6..8996607 100644 --- a/circuits/test/common/index.ts +++ b/circuits/test/common/index.ts @@ -85,6 +85,27 @@ export function padArrayTo64Bits(array: number[]): number[] { return new Array(64 - array.length).fill(0).concat(array); } +export function hexByteToBigInt(hexByte: string): bigint { + if (typeof hexByte !== 'string') { + throw new TypeError('Input must be a string'); + } + + // Remove '0x' prefix if present and ensure lowercase + hexByte = hexByte.replace(/^0x/i, "").toLowerCase(); + + if (hexByte.length !== 2) { + throw new Error('Input must be exactly one byte (2 hex characters)'); + } + + const byte = parseInt(hexByte, 16); + + if (isNaN(byte)) { + throw new Error('Invalid hex byte'); + } + + return BigInt(byte); + } + export function numberTo16Hex(num: number): string { // Convert the number to a hexadecimal string let hexString = num.toString(16); diff --git a/circuits/test/gfmulint/nistgfmul.test.ts b/circuits/test/gfmulint/nistgfmul.test.ts new file mode 100644 index 0000000..878bcbf --- /dev/null +++ b/circuits/test/gfmulint/nistgfmul.test.ts @@ -0,0 +1,165 @@ +import { assert } from "chai"; +import { WitnessTester } from "circomkit"; +import { circomkit, hexToBitArray, hexByteToBigInt } from "../common"; + + +describe("nist", () => { + let circuit: WitnessTester<["X", "Y"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("nistgfmul", { + file: "aes-gcm/nistgmul", + template: "NistGMulBit", + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("Should Compute NistGMulBit Correctly", async () => { + let X = hexToBitArray("0xaae06992acbf52a3e8f4a96ec9300bd7"); // little endian hex vectors + let Y = hexToBitArray("0x98e7247c07f0fe411c267e4384b0f600"); + + + const expected = hexToBitArray("0x2ff58d80033927ab8ef4d4587514f0fb"); + const _res = await circuit.compute({ X, Y }, ["out"]); + assert.deepEqual(_res.out, expected); + }); +}); + +describe("ToBits", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("bytesToBits", { + file: "aes-gcm/nistgmul", + template: "BytesToBits", + params: [1] + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("Should Compute bytesToBits Correctly", async () => { + let input = 0x01; + const expected = hexToBitArray("0x01"); + console.log("expected", expected); + const _res = await circuit.expectPass({ in: input }, { out: expected }); + }); + it("Should Compute bytesToBits Correctly", async () => { + let input = 0xFF; + const expected = hexToBitArray("0xFF"); + console.log("expected", expected); + const _res = await circuit.expectPass({ in: input }, { out: expected }); + }); +}); +describe("ToBits", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("bytesToBits", { + file: "aes-gcm/nistgmul", + template: "BytesToBits", + params: [2] + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("Should Compute bytesToBits Correctly", async () => { + let input = [0x01, 0x00]; + const expected = hexToBitArray("0x0100"); + console.log("expected", expected); + const _res = await circuit.expectPass({ in: input }, { out: expected }); + }); + it("Should Compute bytesToBits Correctly", async () => { + let input = [0xFF, 0x00]; + const expected = hexToBitArray("0xFF00"); + console.log("expected", expected); + const _res = await circuit.expectPass({ in: input }, { out: expected }); + }); +}); + +describe("ToBytes", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("bytesToBits", { + file: "aes-gcm/nistgmul", + template: "BitsToBytes", + params: [1] + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("Should Compute bytesToBits Correctly", async () => { + let input = hexToBitArray("0x01"); + const expected = hexByteToBigInt("0x01"); + console.log("expected", expected); + const _res = await circuit.compute({ in: input }, ["out"]); + console.log("res:", _res.out); + assert.deepEqual(_res.out, expected); + }); + it("Should Compute bytesToBits Correctly", async () => { + let input = hexToBitArray("0xFF"); + const expected = hexByteToBigInt("0xFF"); + console.log("expected", expected); + const _res = await circuit.compute({ in: input }, ["out"]); + console.log("res:", _res.out); + assert.deepEqual(_res.out, expected); + }); +}); + + +describe("intrightshift", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("intrightshift", { + file: "aes-gcm/helper_functions", + template: "IntRightShift", + params: [8, 1] + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("Should Compute IntRightShift Correctly", async () => { + let input = 0x02; // little endian hex vectors + const expected = hexByteToBigInt("0x01"); + const _res = await circuit.compute({ in: input }, ["out"]); + console.log("res:", _res.out); + assert.deepEqual(_res.out, expected); + }); + + it("Should Compute IntRightShift Correctly", async () => { + let input = 0x04; // little endian hex vectors + const expected = hexByteToBigInt("0x02"); + const _res = await circuit.compute({ in: input }, ["out"]); + console.log("res:", _res.out); + assert.deepEqual(_res.out, expected); + }); + }); + + + describe("BlockRightShift", () => { + let circuit: WitnessTester<["in"], ["out", "msb"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("BlockRightShift", { + file: "aes-gcm/nistgmul", + template: "BlockRightShift", + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("Should Compute BlockRightShift Correctly", async () => { + let input = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + const expected = [0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + const _res = await circuit.expectPass({ in: input }, { out: expected, msb: 0 }); + }); + it("Should Compute BlockRightShift Correctly", async () => { + let input = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + const expected = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + const _res = await circuit.expectPass({ in: input }, { out: expected, msb: 1 }); + // console.log("res:", _res.out); + // assert.deepEqual(_res.out, expected); + // console.log("msb:", _res.msb); + // assert.deepEqual(_res.msb, 0x01); + }); +}); \ No newline at end of file diff --git a/nistgmul.circom b/nistgmul.circom new file mode 100644 index 0000000..8e49be9 --- /dev/null +++ b/nistgmul.circom @@ -0,0 +1,12 @@ +template BytesToBits(n) { + signal input in[n]; + signal output out[n*8]; + component num2bits[n]; + for (var i = 0; i < n; i++) { + // ... existing code ... + for (var j = 0; j < 8; j++) { + // Reverse the bit order within each byte + out[i*8 + (7 - j)] <== num2bits[i].out[j]; + } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 3d7670a..34f6dfc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -98,4 +98,35 @@ mod tests { println!("msg={}", hex::encode(message)); println!("ct={}", hex::encode(ct)); } + + #[tokio::test] + async fn test_ghash() { + use ghash::{ + universal_hash::{KeyInit, UniversalHash}, + GHash, + }; + use hex_literal::hex; + + const H: [u8; 16] = hex!("aae06992acbf52a3e8f4a96ec9300bd7"); + const X_1: [u8; 16] = hex!("98e7247c07f0fe411c267e4384b0f600"); + + let mut ghash = GHash::new(&H.into()); + ghash.update(&[X_1.into()]); + let result = ghash.finalize(); + + let hash_key = [0xaa, 0xe0, 0x69, 0x92, 0xac, 0xbf, 0x52, 0xa3, 0xe8, 0xf4, 0xa9, 0x6e, 0xc9, 0x30, 0x0b, 0xd7]; + let ct = [0x98, 0xe7, 0x24, 0x7c, 0x07, 0xf0, 0xfe, 0x41, 0x1c, 0x26, 0x7e, 0x43, 0x84, 0xb0, 0xf6, 0x00]; + let expected = [0x2f, 0xf5, 0x8d, 0x80, 0x03, 0x39, 0x27, 0xab,0x8e, 0xf4, 0xd4, 0x58, 0x75, 0x14, 0xf0, 0xfb]; + + // Alternative. + let mut ghash2 = GHash::new_with_init_block(&hash_key.into(), 0); + let ga_data = GenericArray::from_slice(&ct); + ghash2.update(&[*ga_data]); + let result2 = ghash2.finalize(); + + println!("GHASH NEW result: {:?}", hex::encode(result.as_slice())); + println!("GHASH OLD result: {:?}", hex::encode(result2.as_slice())); + println!("expected: {:?}", hex::encode(expected)); + + } } From 7ffd5e049f898021ee09d523536d05be3840687a Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Wed, 18 Sep 2024 16:37:54 -0600 Subject: [PATCH 02/10] right shiftmodpx --- circuits/aes-gcm/nistgmul.circom | 137 +++++++++++++++-------- circuits/test/gfmulint/nistgfmul.test.ts | 24 +++- 2 files changed, 107 insertions(+), 54 deletions(-) diff --git a/circuits/aes-gcm/nistgmul.circom b/circuits/aes-gcm/nistgmul.circom index 281602c..d6519bb 100644 --- a/circuits/aes-gcm/nistgmul.circom +++ b/circuits/aes-gcm/nistgmul.circom @@ -4,6 +4,7 @@ include "utils.circom"; // xor include "circomlib/circuits/comparators.circom"; // isZero include "helper_functions.circom"; // bitwise right shift include "circomlib/circuits/mux1.circom"; // multiplexer +include "../aes-ctr/utils.circom"; // xorbyte // Algorithm 1: X •Y // Input: @@ -113,55 +114,93 @@ template ArrayMux(n) { } } -// template NistGMulByte() { - -// signal input X[16]; -// signal input Y[16]; -// signal output out[16]; - -// // Let R be the bit string 11100001 || 0120. Given two blocks X and Y -// // byte 0xE1 is 11100001 in binary -// var R[16] = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; - -// // 1. Let x0, x1...x127 denote the sequence of bits in X. -// // 2. Let Z0 = 0128 and V0 = Y. -// signal Z[16] <== [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; -// /// State accumulator. ie. V[i] is V0 holding 16 bytes -// signal V[16][16]; -// V[0] <== Y; - -// // 3. For i = 0 to 127, calculate blocks Zi+1 and Vi+1 as follows: -// // -// // ⎧ Zi if xi = 0; -// // Zi+1 ⎨ -// // ⎩ Zi ⊕Vi if xi =1. -// // -// // ⎧ Vi >>1 if LSB1(Vi) = 0; -// // Vi+1 ⎨ -// // ⎩ (Vi >>1) ⊕ R if LSB1(Vi) =1. -// // -// component XorByte[16]; -// component IsZero[16]; -// component Zmux[16]; -// component Vmux[16]; -// component BitwiseRightShift[16]; -// for (var i = 0; i < 15; i++) { - -// IsZero[i] = IsZero(); -// IsZero[i].in <== X[i]; -// if (IsZero[i].out == 0) { -// Z[i +1] <== Z[i]; -// V[i] <== V[i]; -// } else { -// XorByte[i] = XorByte(); -// XorByte[i].a <== Z[i]; -// XorByte[i].b <== V[i]; -// Z[i] <== XorByte[i].out; -// } -// } -// // 4. Return Z128. - -// } +template NistGMulByte() { + + signal input X[16]; + signal input Y[16]; + signal output out[16]; + + // Let R be the bit string 11100001 || 0120. Given two blocks X and Y + // byte 0xE1 is 11100001 in binary + var R[16] = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + // 1. Let x0, x1...x127 denote the sequence of bits in X. + // 2. Let Z0 = 0128 and V0 = Y. + signal Z[16] <== [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + /// State accumulator. ie. V[i] is V0 holding 16 bytes + signal V[16][16]; + V[0] <== Y; + + // 3. For i = 0 to 127, calculate blocks Zi+1 and Vi+1 as follows: + // + // ⎧ Zi if xi = 0; + // Zi+1 ⎨ + // ⎩ Zi ⊕Vi if xi =1. + // + // ⎧ Vi >>1 if LSB1(Vi) = 0; + // Vi+1 ⎨ + // ⎩ (Vi >>1) ⊕ R if LSB1(Vi) =1. + // + component XorByte[16]; + component IsZero[16]; + component Zmux[16]; + component Vmux[16]; + component RightShift[16]; + + for (var i = 0; i < 16; i++) { + + /// In order to handle this for bytes i should iterate over each bit in the x_i byte + // Let 𝑏 = value of the 𝑗-th bit in 𝑥𝑖 + // Update 𝑍: + // If 𝑏 = 1, 𝑍 = 𝑍 ⊕ 𝑉. + + + + + // IsZero[i] = IsZero(); + // IsZero[i].in <== X[i]; + // if (IsZero[i].out == 0) { + // Z[i +1] <== Z[i]; + // V[i] <== V[i]; + // } else { + // XorByte[i] = XorByte(); + // XorByte[i].a <== Z[i]; + // XorByte[i].b <== V[i]; + // Z[i] <== XorByte[i].out; + // } + } + // 4. Return Z128. + +} + +// right shift by one bit. If msb is 1: +// then we xor the first byte with 0xE1 (11100001: 1 + X + X^2 + X^7) +// this is the irreducible polynomial used in AES-GCM +template RightShiftModPX() { + signal input in[16]; + signal output out[16]; + + signal intermediate[16]; + + component blockRightShift = BlockRightShift(); + blockRightShift.in <== in; + intermediate <== blockRightShift.out; + + component xorByte = XorByte(); + xorByte.a <== intermediate[0]; + xorByte.b <== 0xE1; // 11100001 + + // if msb is 1, then we xor the first byte with R[0] + component mux = Mux1(); + mux.s <== blockRightShift.msb; + mux.c[0] <== intermediate[0]; + mux.c[1] <== xorByte.out; + + for (var i = 1; i < 16; i++) { + out[i] <== intermediate[i]; + } + out[0] <== mux.out; +} // right shifts 16 bytes by one bit and returns the msb before the shift template BlockRightShift() { diff --git a/circuits/test/gfmulint/nistgfmul.test.ts b/circuits/test/gfmulint/nistgfmul.test.ts index 878bcbf..430e7b7 100644 --- a/circuits/test/gfmulint/nistgfmul.test.ts +++ b/circuits/test/gfmulint/nistgfmul.test.ts @@ -137,7 +137,7 @@ describe("intrightshift", () => { }); - describe("BlockRightShift", () => { +describe("BlockRightShift", () => { let circuit: WitnessTester<["in"], ["out", "msb"]>; before(async () => { @@ -157,9 +157,23 @@ describe("intrightshift", () => { let input = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; const expected = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; const _res = await circuit.expectPass({ in: input }, { out: expected, msb: 1 }); - // console.log("res:", _res.out); - // assert.deepEqual(_res.out, expected); - // console.log("msb:", _res.msb); - // assert.deepEqual(_res.msb, 0x01); + }); +}); + +describe("BlockRightShiftModPX", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("RightShiftModPX", { + file: "aes-gcm/nistgmul", + template: "RightShiftModPX", + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + // msb is 1 so we xor the first byte with 0xE1 + it("Should Compute BlockRightShift Correctly", async () => { + let input = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + const expected = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + const _res = await circuit.expectPass({ in: input }, { out: expected }); }); }); \ No newline at end of file From 4c1bb1f051ac2ef8719ef6838e254998324ce795 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Wed, 18 Sep 2024 16:51:17 -0600 Subject: [PATCH 03/10] z_update subroutine --- circuits/aes-gcm/nistgmul.circom | 62 ++++++++++++++---------- circuits/test/gfmulint/nistgfmul.test.ts | 15 +++--- 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/circuits/aes-gcm/nistgmul.circom b/circuits/aes-gcm/nistgmul.circom index d6519bb..d219e15 100644 --- a/circuits/aes-gcm/nistgmul.circom +++ b/circuits/aes-gcm/nistgmul.circom @@ -122,11 +122,12 @@ template NistGMulByte() { // Let R be the bit string 11100001 || 0120. Given two blocks X and Y // byte 0xE1 is 11100001 in binary - var R[16] = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + // var R[16] = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; // 1. Let x0, x1...x127 denote the sequence of bits in X. // 2. Let Z0 = 0128 and V0 = Y. - signal Z[16] <== [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + signal Z[16][16]; + Z[0] <== [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; /// State accumulator. ie. V[i] is V0 holding 16 bytes signal V[16][16]; V[0] <== Y; @@ -141,42 +142,51 @@ template NistGMulByte() { // Vi+1 ⎨ // ⎩ (Vi >>1) ⊕ R if LSB1(Vi) =1. // - component XorByte[16]; - component IsZero[16]; - component Zmux[16]; - component Vmux[16]; - component RightShift[16]; - + component bit[16] = Num2Bits(8); for (var i = 0; i < 16; i++) { - /// In order to handle this for bytes i should iterate over each bit in the x_i byte - // Let 𝑏 = value of the 𝑗-th bit in 𝑥𝑖 - // Update 𝑍: - // If 𝑏 = 1, 𝑍 = 𝑍 ⊕ 𝑉. + // call z_i_update + // do the mulx for v + } + // 4. Return Z128. +} +// TODO: Write a test for this +template z_i_update(bit_val) { + signal input Z[16]; + signal input V[16]; + signal output Z_new[16]; + + component mulx = Mulx(); + mulx.s <== bit_val; + mulx.c[0] <== Z; + component xorBlock = XORBLOCK(); + xorBlock.a <== Z; + xorBlock.b <== V; + mulx.c[1] <== xorBlock.out; + Z_new <== mulx.out; +} +// TODO: Write a test for this +template XORBLOCK(){ + signal input a[16]; + signal input b[16]; + signal output out[16]; - // IsZero[i] = IsZero(); - // IsZero[i].in <== X[i]; - // if (IsZero[i].out == 0) { - // Z[i +1] <== Z[i]; - // V[i] <== V[i]; - // } else { - // XorByte[i] = XorByte(); - // XorByte[i].a <== Z[i]; - // XorByte[i].b <== V[i]; - // Z[i] <== XorByte[i].out; - // } + component xorByte[16]; + for (var i = 0; i < 16; i++) { + xorByte[i] = XorByte(); + xorByte[i].a <== a[i]; + xorByte[i].b <== b[i]; + out[i] <== xorByte[i].out; } - // 4. Return Z128. - } // right shift by one bit. If msb is 1: // then we xor the first byte with 0xE1 (11100001: 1 + X + X^2 + X^7) // this is the irreducible polynomial used in AES-GCM -template RightShiftModPX() { +template Mulx() { signal input in[16]; signal output out[16]; diff --git a/circuits/test/gfmulint/nistgfmul.test.ts b/circuits/test/gfmulint/nistgfmul.test.ts index 430e7b7..c2f339f 100644 --- a/circuits/test/gfmulint/nistgfmul.test.ts +++ b/circuits/test/gfmulint/nistgfmul.test.ts @@ -3,24 +3,25 @@ import { WitnessTester } from "circomkit"; import { circomkit, hexToBitArray, hexByteToBigInt } from "../common"; -describe("nist", () => { +describe("NistGMulByte", () => { let circuit: WitnessTester<["X", "Y"], ["out"]>; before(async () => { circuit = await circomkit.WitnessTester("nistgfmul", { file: "aes-gcm/nistgmul", - template: "NistGMulBit", + template: "NistGMulByte", }); console.log("#constraints:", await circuit.getConstraintCount()); }); - it("Should Compute NistGMulBit Correctly", async () => { + it("Should Compute NistGMulByte Correctly", async () => { let X = hexToBitArray("0xaae06992acbf52a3e8f4a96ec9300bd7"); // little endian hex vectors let Y = hexToBitArray("0x98e7247c07f0fe411c267e4384b0f600"); const expected = hexToBitArray("0x2ff58d80033927ab8ef4d4587514f0fb"); const _res = await circuit.compute({ X, Y }, ["out"]); + console.log("res:", _res.out); assert.deepEqual(_res.out, expected); }); }); @@ -160,18 +161,18 @@ describe("BlockRightShift", () => { }); }); -describe("BlockRightShiftModPX", () => { +describe("Mulx", () => { let circuit: WitnessTester<["in"], ["out"]>; before(async () => { - circuit = await circomkit.WitnessTester("RightShiftModPX", { + circuit = await circomkit.WitnessTester("Mulx", { file: "aes-gcm/nistgmul", - template: "RightShiftModPX", + template: "Mulx", }); console.log("#constraints:", await circuit.getConstraintCount()); }); // msb is 1 so we xor the first byte with 0xE1 - it("Should Compute BlockRightShift Correctly", async () => { + it("Should Compute Mulx Correctly", async () => { let input = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; const expected = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; const _res = await circuit.expectPass({ in: input }, { out: expected }); From d262ebb470663170c063973ed51dcd1943fa74ef Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Thu, 19 Sep 2024 11:15:13 -0600 Subject: [PATCH 04/10] tests for zupdate, array multiplexer, and blockxor --- circuits/aes-gcm/nistgmul.circom | 45 +++++++------ circuits/test/gfmulint/nistgfmul.test.ts | 83 +++++++++++++++++++++++- 2 files changed, 104 insertions(+), 24 deletions(-) diff --git a/circuits/aes-gcm/nistgmul.circom b/circuits/aes-gcm/nistgmul.circom index d219e15..09be921 100644 --- a/circuits/aes-gcm/nistgmul.circom +++ b/circuits/aes-gcm/nistgmul.circom @@ -101,19 +101,6 @@ template NistGMulBit() { out <== Z; } -template ArrayMux(n) { - signal input a[n]; // First input array - signal input b[n]; // Second input array - signal input sel; // Selector signal (0 or 1) - signal output out[n]; // Output array - - for (var i = 0; i < n; i++) { - // If sel = 0, out[i] = b[i] - // If sel = 1, out[i] = a[i] - out[i] <== (a[i] - b[i]) * sel + b[i]; - } -} - template NistGMulByte() { signal input X[16]; @@ -152,23 +139,39 @@ template NistGMulByte() { } -// TODO: Write a test for this -template z_i_update(bit_val) { +// if bit value is 0, then Z_new = Z +// if bit value is 1, then Z_new = Z xor V +template Z_I_UPDATE() { signal input Z[16]; signal input V[16]; + signal input bit_val; signal output Z_new[16]; - component mulx = Mulx(); - mulx.s <== bit_val; - mulx.c[0] <== Z; + component mux = ArrayMux(16); + mux.sel <== bit_val; + mux.a <== Z; component xorBlock = XORBLOCK(); xorBlock.a <== Z; xorBlock.b <== V; - mulx.c[1] <== xorBlock.out; - Z_new <== mulx.out; + mux.b <== xorBlock.out; + Z_new <== mux.out; +} + +// multiplexer for arrays of length n +template ArrayMux(n) { + signal input a[n]; // First input array + signal input b[n]; // Second input array + signal input sel; // Selector signal (0 or 1) + signal output out[n]; // Output array + + for (var i = 0; i < n; i++) { + // If sel = 0, out[i] = a[i] + // If sel = 1, out[i] = b[i] + out[i] <== (b[i] - a[i]) * sel + a[i]; + } } -// TODO: Write a test for this +// XOR 16 bytes template XORBLOCK(){ signal input a[16]; signal input b[16]; diff --git a/circuits/test/gfmulint/nistgfmul.test.ts b/circuits/test/gfmulint/nistgfmul.test.ts index c2f339f..f0688a6 100644 --- a/circuits/test/gfmulint/nistgfmul.test.ts +++ b/circuits/test/gfmulint/nistgfmul.test.ts @@ -152,12 +152,12 @@ describe("BlockRightShift", () => { it("Should Compute BlockRightShift Correctly", async () => { let input = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; const expected = [0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; - const _res = await circuit.expectPass({ in: input }, { out: expected, msb: 0 }); + await circuit.expectPass({ in: input }, { out: expected, msb: 0 }); }); it("Should Compute BlockRightShift Correctly", async () => { let input = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; const expected = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; - const _res = await circuit.expectPass({ in: input }, { out: expected, msb: 1 }); + await circuit.expectPass({ in: input }, { out: expected, msb: 1 }); }); }); @@ -175,6 +175,83 @@ describe("Mulx", () => { it("Should Compute Mulx Correctly", async () => { let input = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; const expected = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; - const _res = await circuit.expectPass({ in: input }, { out: expected }); + await circuit.expectPass({ in: input }, { out: expected }); + }); +}); + +describe("XORBLOCK", () => { + let circuit: WitnessTester<["a", "b"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("XORBLOCK", { + file: "aes-gcm/nistgmul", + template: "XORBLOCK", + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + // msb is 1 so we xor the first byte with 0xE1 + it("Should Compute block XOR Correctly", async () => { + let inputa = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let inputb = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + const expected = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + await circuit.expectPass({ a: inputa, b: inputb }, { out: expected }); + }); +}); +describe("ArrayMux", () => { + let circuit: WitnessTester<["a", "b", "sel"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("XORBLOCK", { + file: "aes-gcm/nistgmul", + template: "ArrayMux", + params: [16] + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + // msb is 1 so we xor the first byte with 0xE1 + it("Should Compute selector mux Correctly", async () => { + let a= [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let b = [0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + let sel = 0x00; + let expected= [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + await circuit.expectPass({ a: a, b: b, sel: sel }, { out: expected }); + }); + + it("Should Compute block XOR Correctly", async () => { + let a= [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let b = [0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + let sel = 0x01; + let expected= [0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + await circuit.expectPass({ a: a, b: b, sel: sel }, { out: expected }); + }); + +}); + + +describe("Z_I_UPDATE", () => { + let circuit: WitnessTester<["Z", "V", "bit_val"], ["Z_new"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("XORBLOCK", { + file: "aes-gcm/nistgmul", + template: "Z_I_UPDATE", + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + // msb is 1 so we xor the first byte with 0xE1 + it("Should Compute block XOR Correctly", async () => { + let inputZ= [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let inputV = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + let inputc = 0x00; + let expected= [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + await circuit.expectPass({ Z: inputZ, V: inputV, bit_val: inputc }, { Z_new: expected }); + }); + + it("Should Compute block XOR Correctly", async () => { + let inputa = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let inputb = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + let inputc = 0x01; + const expected = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + await circuit.expectPass({ Z: inputa, V: inputb, bit_val: inputc }, { Z_new: expected }); }); }); \ No newline at end of file From fd83792fa4e67ff7f157ecf00f1f8dcef8abb049 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Thu, 19 Sep 2024 12:25:47 -0600 Subject: [PATCH 05/10] pain --- circuits/aes-gcm/nistgmul.circom | 119 +++++------------------ circuits/test/common/index.ts | 25 +++++ circuits/test/gfmulint/nistgfmul.test.ts | 22 +++-- 3 files changed, 66 insertions(+), 100 deletions(-) diff --git a/circuits/aes-gcm/nistgmul.circom b/circuits/aes-gcm/nistgmul.circom index 09be921..5ec9b3d 100644 --- a/circuits/aes-gcm/nistgmul.circom +++ b/circuits/aes-gcm/nistgmul.circom @@ -13,93 +13,7 @@ include "../aes-ctr/utils.circom"; // xorbyte // block X •Y. // multiplication of two blocks in the binary extension field defined by the irreducible polynomial // 1 + X + X^2 + X^7 + X^128 -// computes a “product” block, denoted X •Y: - -template NistGMulBit() { - - signal input X[128]; - signal input Y[128]; - signal output out[128]; - - // Let R be the bit string 11100001 || 0120. Given two blocks X and Y - var R[128] = [ - 1, 1, 1, 0, 0, 0, 0, 1, // 8 - 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 0, 0, 0, 0, 0, 0, 0, 0, // 32 - 0, 0, 0, 0, 0, 0, 0, 0, // 40 - 0, 0, 0, 0, 0, 0, 0, 0, // 48 - 0, 0, 0, 0, 0, 0, 0, 0, // 56 - 0, 0, 0, 0, 0, 0, 0, 0, // 64 - 0, 0, 0, 0, 0, 0, 0, 0, // 72 - 0, 0, 0, 0, 0, 0, 0, 0, // 80 - 0, 0, 0, 0, 0, 0, 0, 0, // 88 - 0, 0, 0, 0, 0, 0, 0, 0, // 96 - 0, 0, 0, 0, 0, 0, 0, 0, // 104 - 0, 0, 0, 0, 0, 0, 0, 0, // 112 - 0, 0, 0, 0, 0, 0, 0, 0, // 120 - 0, 0, 0, 0, 0, 0, 0, 0 - ]; - - // 1. Let x0, x1...x127 denote the sequence of bits in X. - // 2. Let Z0 = 0128 and V0 = Y. - signal Z[128]; - Z[0] <== 0; - /// State accumulator - signal V[128][128]; - V[0] <== Y; - - // - // 3. For i = 0 to 127, calculate blocks Zi+1 and Vi+1 as follows: - // - // ⎧ Zi if xi = 0; - // Zi+1 ⎨ - // ⎩ Zi ⊕Vi if xi = 1. - // - // ⎧ Vi >>1 if LSB1(Vi) = 0; // example 01101010 >> 1 = 00110101 right shift - // Vi+1 ⎨ // lsb1(001) = 1 (rightmost bit is 1) - // ⎩ (Vi >>1) ⊕ R if LSB1(Vi) = 1. - // - component XorBit[128]; - component XorArr[128]; - component IsZero[128]; - component Zmux[128]; - component Vmux[128]; - component BitwiseRightShift[128]; - for (var i = 0; i < 127; i++) { - IsZero[i] = IsZero(); - IsZero[i].in <== X[i]; - Zmux[i] = Mux1(); - Zmux[i].s <== IsZero[i].out; // selector if 0, if yes return 1, else return zero - - Zmux[i].c[1] <== Z[i]; // selector 1 - // if (IsZero[i].out == 0) { - // Z[i +1] <== Z[i]; - // } else { - XorBit[i] = XOR(); - XorBit[i].a <== Z[i]; - XorBit[i].b <== V[i][i]; - Zmux[i].c[0] <==XorBit[i].out; // if selector is 0 - // } - Z[i+1] <== Zmux[i].out; - - BitwiseRightShift[i] = BitwiseRightShift(128, 1); - BitwiseRightShift[i].in <== V[i]; - - Vmux[i] = ArrayMux(128); - Vmux[i].sel <== V[i][127]; // selector is LSB - Vmux[i].a <== BitwiseRightShift[i].out; // if selector is 0 - - XorArr[i] = BitwiseXor(128); - XorArr[i].a <== BitwiseRightShift[i].out; - XorArr[i].b <== R; - Vmux[i].b <== XorArr[i].out; // if selector is 1 - V[i+1] <== Vmux[i].out; - - } - // 4. Return Z128. - out <== Z; -} +// computes a “product” block, denoted X •Y template NistGMulByte() { @@ -113,10 +27,10 @@ template NistGMulByte() { // 1. Let x0, x1...x127 denote the sequence of bits in X. // 2. Let Z0 = 0128 and V0 = Y. - signal Z[16][16]; + signal Z[129][16]; Z[0] <== [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; /// State accumulator. ie. V[i] is V0 holding 16 bytes - signal V[16][16]; + signal V[129][16]; V[0] <== Y; // 3. For i = 0 to 127, calculate blocks Zi+1 and Vi+1 as follows: @@ -125,18 +39,35 @@ template NistGMulByte() { // Zi+1 ⎨ // ⎩ Zi ⊕Vi if xi =1. // + // The V update is actually just gmulx (multiply binary polynomial by x) + // // ⎧ Vi >>1 if LSB1(Vi) = 0; // Vi+1 ⎨ // ⎩ (Vi >>1) ⊕ R if LSB1(Vi) =1. // - component bit[16] = Num2Bits(8); + component bit[16]; + component z_i_update[128]; + component mulx[128]; for (var i = 0; i < 16; i++) { - - // call z_i_update - // do the mulx for v + bit[i] = BytesToBits(1); + bit[i].in[0] <== X[i]; + for (var j = 0; j < 8; j++) { + // log("i*8 + j", i*8 + j); + // z_i_update + z_i_update[i*8 + j] = Z_I_UPDATE(); + z_i_update[i*8 + j].Z <== Z[i]; + z_i_update[i*8 + j].V <== V[i]; + z_i_update[i*8 + j].bit_val <== bit[i].out[j]; + Z[i*8 + j + 1] <== z_i_update[i*8 + j].Z_new; + + // mulx to update V + mulx[i*8 + j] = Mulx(); + mulx[i*8 + j].in <== V[i]; + V[i*8 + j + 1] <== mulx[i*8 + j].out; + } } // 4. Return Z128. - + out <== Z[128]; } // if bit value is 0, then Z_new = Z diff --git a/circuits/test/common/index.ts b/circuits/test/common/index.ts index 8996607..3a09508 100644 --- a/circuits/test/common/index.ts +++ b/circuits/test/common/index.ts @@ -148,3 +148,28 @@ it("tests bitArrayToHexString", async () => { result = bitArrayToHex(bits); assert.equal(result, expectedHex); }); + +export function hexStringToByteArray(hexString: string): number[] { + // Ensure the string has an even number of characters + if (hexString.length % 2 !== 0) { + throw new Error('Hex string must have an even number of characters'); + } + + const byteArray: number[] = []; + + for (let i = 0; i < hexString.length; i += 2) { + const byteValue = parseInt(hexString.substr(i, 2), 16); + if (isNaN(byteValue)) { + throw new Error('Invalid hex string'); + } + byteArray.push(byteValue); + } + + return byteArray; +} + +export function byteArrayToHex(byteArray: any) { + return Array.from(byteArray, (byte: any) => { + return ('0' + (byte & 0xFF).toString(16)).slice(-2); + }).join(''); +} \ No newline at end of file diff --git a/circuits/test/gfmulint/nistgfmul.test.ts b/circuits/test/gfmulint/nistgfmul.test.ts index f0688a6..eb71260 100644 --- a/circuits/test/gfmulint/nistgfmul.test.ts +++ b/circuits/test/gfmulint/nistgfmul.test.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; import { WitnessTester } from "circomkit"; -import { circomkit, hexToBitArray, hexByteToBigInt } from "../common"; +import { circomkit, hexToBitArray, hexByteToBigInt, hexStringToByteArray, byteArrayToHex } from "../common"; describe("NistGMulByte", () => { @@ -15,12 +15,22 @@ describe("NistGMulByte", () => { }); it("Should Compute NistGMulByte Correctly", async () => { - let X = hexToBitArray("0xaae06992acbf52a3e8f4a96ec9300bd7"); // little endian hex vectors - let Y = hexToBitArray("0x98e7247c07f0fe411c267e4384b0f600"); - - const expected = hexToBitArray("0x2ff58d80033927ab8ef4d4587514f0fb"); - const _res = await circuit.compute({ X, Y }, ["out"]); + // let h = "aae06992acbf52a3e8f4a96ec9300bd7"; + // let x = "98e7247c07f0fe411c267e4384b0f600"; + + // let h_le = hexStringToByteArray(h).reverse(); + // let x_le = hexStringToByteArray(x).reverse(); + // // let X = hexToBitArray("0xaae06992acbf52a3e8f4a96ec9300bd7"); // little endian hex vectors + // let X = hexStringToByteArray("aae06992acbf52a3e8f4a96ec9300bd7"); + // // let Y = hexToBitArray("0x98e7247c07f0fe411c267e4384b0f600"); + // let Y = hexStringToByteArray("98e7247c07f0fe411c267e4384b0f600"); + let one = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + let zero = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + + // const expected = hexToBitArray("0x2ff58d80033927ab8ef4d4587514f0fb"); + const expected = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + const _res = await circuit.compute({ X: zero, Y: one }, ["out"]); console.log("res:", _res.out); assert.deepEqual(_res.out, expected); }); From 51e6048fe879340599b7849672b54913404aa8d8 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Thu, 19 Sep 2024 14:05:52 -0600 Subject: [PATCH 06/10] one passing test vector --- circuits/aes-gcm/nistgmul.circom | 8 ++------ circuits/test/gfmulint/nistgfmul.test.ts | 12 ++++++------ src/main.rs | 25 ++++++++++++------------ 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/circuits/aes-gcm/nistgmul.circom b/circuits/aes-gcm/nistgmul.circom index 5ec9b3d..80363c9 100644 --- a/circuits/aes-gcm/nistgmul.circom +++ b/circuits/aes-gcm/nistgmul.circom @@ -14,7 +14,6 @@ include "../aes-ctr/utils.circom"; // xorbyte // multiplication of two blocks in the binary extension field defined by the irreducible polynomial // 1 + X + X^2 + X^7 + X^128 // computes a “product” block, denoted X •Y - template NistGMulByte() { signal input X[16]; @@ -32,9 +31,6 @@ template NistGMulByte() { /// State accumulator. ie. V[i] is V0 holding 16 bytes signal V[129][16]; V[0] <== Y; - - // 3. For i = 0 to 127, calculate blocks Zi+1 and Vi+1 as follows: - // // ⎧ Zi if xi = 0; // Zi+1 ⎨ // ⎩ Zi ⊕Vi if xi =1. @@ -73,8 +69,8 @@ template NistGMulByte() { // if bit value is 0, then Z_new = Z // if bit value is 1, then Z_new = Z xor V template Z_I_UPDATE() { - signal input Z[16]; - signal input V[16]; + signal input Z[16]; // this is Zero block in first itteration + signal input V[16]; // this is Y in first itteration signal input bit_val; signal output Z_new[16]; diff --git a/circuits/test/gfmulint/nistgfmul.test.ts b/circuits/test/gfmulint/nistgfmul.test.ts index eb71260..8b341cb 100644 --- a/circuits/test/gfmulint/nistgfmul.test.ts +++ b/circuits/test/gfmulint/nistgfmul.test.ts @@ -25,14 +25,14 @@ describe("NistGMulByte", () => { // let X = hexStringToByteArray("aae06992acbf52a3e8f4a96ec9300bd7"); // // let Y = hexToBitArray("0x98e7247c07f0fe411c267e4384b0f600"); // let Y = hexStringToByteArray("98e7247c07f0fe411c267e4384b0f600"); - let one = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; - let zero = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + let one = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let zero = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; // const expected = hexToBitArray("0x2ff58d80033927ab8ef4d4587514f0fb"); - const expected = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; - const _res = await circuit.compute({ X: zero, Y: one }, ["out"]); - console.log("res:", _res.out); - assert.deepEqual(_res.out, expected); + const expected = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + await circuit.expectPass({ X: zero, Y: one }, { out: expected }); + // console.log("res:", _res.out); + // assert.deepEqual(_res.out, expected); }); }); diff --git a/src/main.rs b/src/main.rs index 34f6dfc..f0c74ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -107,26 +107,25 @@ mod tests { }; use hex_literal::hex; - const H: [u8; 16] = hex!("aae06992acbf52a3e8f4a96ec9300bd7"); - const X_1: [u8; 16] = hex!("98e7247c07f0fe411c267e4384b0f600"); + // first byte is 00000001 + const H: [u8; 16] = hex!("80000000000000000000000000000000"); + const X: [u8; 16] = hex!("80000000000000000000000000000000"); + let mut ghash = GHash::new(&H.into()); - ghash.update(&[X_1.into()]); + ghash.update(&[X.into()]); let result = ghash.finalize(); - let hash_key = [0xaa, 0xe0, 0x69, 0x92, 0xac, 0xbf, 0x52, 0xa3, 0xe8, 0xf4, 0xa9, 0x6e, 0xc9, 0x30, 0x0b, 0xd7]; - let ct = [0x98, 0xe7, 0x24, 0x7c, 0x07, 0xf0, 0xfe, 0x41, 0x1c, 0x26, 0x7e, 0x43, 0x84, 0xb0, 0xf6, 0x00]; - let expected = [0x2f, 0xf5, 0x8d, 0x80, 0x03, 0x39, 0x27, 0xab,0x8e, 0xf4, 0xd4, 0x58, 0x75, 0x14, 0xf0, 0xfb]; - + const H_1: [u8; 16] = hex!("81000000000000000000000000000000"); + const X_1: [u8; 16] = hex!("81000000000000000000000000000000"); // Alternative. - let mut ghash2 = GHash::new_with_init_block(&hash_key.into(), 0); - let ga_data = GenericArray::from_slice(&ct); - ghash2.update(&[*ga_data]); + let mut ghash2 = GHash::new(&H_1.into()); + ghash2.update(&[X_1.into()]); let result2 = ghash2.finalize(); - println!("GHASH NEW result: {:?}", hex::encode(result.as_slice())); - println!("GHASH OLD result: {:?}", hex::encode(result2.as_slice())); - println!("expected: {:?}", hex::encode(expected)); + println!("GHASH result_1: {:?}", hex::encode(result.as_slice())); + println!("GHASH result_2: {:?}", hex::encode(result2.as_slice())); + // println!("expected: {:?}", hex::encode(expected)); } } From 7e471896635b60fa201bf2972cd95db5600ef155 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Thu, 19 Sep 2024 15:46:30 -0600 Subject: [PATCH 07/10] two test vectors passing --- circuits/aes-gcm/nistgmul.circom | 172 ++++++++++++++++------- circuits/test/gfmulint/nistgfmul.test.ts | 87 ++++++++++-- src/main.rs | 4 +- 3 files changed, 197 insertions(+), 66 deletions(-) diff --git a/circuits/aes-gcm/nistgmul.circom b/circuits/aes-gcm/nistgmul.circom index 80363c9..1bb4b8b 100644 --- a/circuits/aes-gcm/nistgmul.circom +++ b/circuits/aes-gcm/nistgmul.circom @@ -44,40 +44,114 @@ template NistGMulByte() { component bit[16]; component z_i_update[128]; component mulx[128]; - for (var i = 0; i < 16; i++) { - bit[i] = BytesToBits(1); - bit[i].in[0] <== X[i]; - for (var j = 0; j < 8; j++) { - // log("i*8 + j", i*8 + j); - // z_i_update - z_i_update[i*8 + j] = Z_I_UPDATE(); - z_i_update[i*8 + j].Z <== Z[i]; - z_i_update[i*8 + j].V <== V[i]; - z_i_update[i*8 + j].bit_val <== bit[i].out[j]; - Z[i*8 + j + 1] <== z_i_update[i*8 + j].Z_new; - - // mulx to update V - mulx[i*8 + j] = Mulx(); - mulx[i*8 + j].in <== V[i]; - V[i*8 + j + 1] <== mulx[i*8 + j].out; - } + component bytesToBits = BytesToBits(16); + bytesToBits.in <== X; + signal bitsX[16*8]; + bitsX <== bytesToBits.out; + for (var i = 0; i < 128; i++) { + // log("i*8 + j", i*8 + j); + // z_i_update + z_i_update[i] = Z_I_UPDATE(16); + z_i_update[i].Z <== Z[i]; + z_i_update[i].V <== V[i]; + z_i_update[i].bit_val <== bitsX[i]; + Z[i + 1] <== z_i_update[i].Z_new; + + // mulx to update V + mulx[i] = Mulx(16); + mulx[i].in <== V[i]; + V[i + 1] <== mulx[i].out; } // 4. Return Z128. out <== Z[128]; } +// mul on 1 byte to debug by hand +template debug_1_byte() { + signal input X[1]; + signal input Y[1]; + signal output out[1]; + + signal bitsX[8]; + signal Z[9][1]; + signal V[9][1]; + Z[0] <== [0x00]; + V[0] <== Y; + + component z_i_update[8]; + component mulx[8]; + component bytesToBits = BytesToBits(1); + bytesToBits.in <== X; + bitsX <== bytesToBits.out; + for (var i = 0; i < 8; i++) { + log("i", i); + log("bitsX[i]", bitsX[i]); + z_i_update[i] = Z_I_UPDATE(1); + z_i_update[i].Z <== Z[i]; + z_i_update[i].V <== V[i]; + z_i_update[i].bit_val <== bitsX[i]; + Z[i + 1] <== z_i_update[i].Z_new; + + // mulx to update V + mulx[i] = Mulx(1); + mulx[i].in <== V[i]; + V[i + 1] <== mulx[i].out; + + log("V[i]", V[i][0]); + } + out <== Z[8]; +} + +// mul on 2 bytes to debug by hand +template debug_2_bytes() { + signal input X[2]; + signal input Y[2]; + signal output out[2]; + + signal bitsX[16]; + signal Z[17][2]; + signal V[17][2]; + Z[0] <== [0x00, 0x00]; + V[0] <== Y; + + component z_i_update[16]; + component mulx[16]; + component bytesToBits = BytesToBits(2); + bytesToBits.in <== X; + bitsX <== bytesToBits.out; + for (var i = 0; i < 16; i++) { + log("i", i); + log("bitsX[i]", bitsX[i]); + z_i_update[i] = Z_I_UPDATE(2); + z_i_update[i].Z <== Z[i]; + z_i_update[i].V <== V[i]; + z_i_update[i].bit_val <== bitsX[i]; + Z[i + 1] <== z_i_update[i].Z_new; + + // mulx to update V + mulx[i] = Mulx(2); + mulx[i].in <== V[i]; + V[i + 1] <== mulx[i].out; + + log("V[i][0]", V[i][0]); + log("V[i][1]", V[i][1]); + } + + out <== Z[16]; +} + // if bit value is 0, then Z_new = Z // if bit value is 1, then Z_new = Z xor V -template Z_I_UPDATE() { - signal input Z[16]; // this is Zero block in first itteration - signal input V[16]; // this is Y in first itteration +template Z_I_UPDATE(n_bytes) { + signal input Z[n_bytes]; // this is Zero block in first itteration + signal input V[n_bytes]; // this is Y in first itteration signal input bit_val; - signal output Z_new[16]; + signal output Z_new[n_bytes]; - component mux = ArrayMux(16); + component mux = ArrayMux(n_bytes); mux.sel <== bit_val; mux.a <== Z; - component xorBlock = XORBLOCK(); + component xorBlock = XORBLOCK(n_bytes); xorBlock.a <== Z; xorBlock.b <== V; mux.b <== xorBlock.out; @@ -99,13 +173,13 @@ template ArrayMux(n) { } // XOR 16 bytes -template XORBLOCK(){ - signal input a[16]; - signal input b[16]; - signal output out[16]; +template XORBLOCK(n_bytes){ + signal input a[n_bytes]; + signal input b[n_bytes]; + signal output out[n_bytes]; - component xorByte[16]; - for (var i = 0; i < 16; i++) { + component xorByte[n_bytes]; + for (var i = 0; i < n_bytes; i++) { xorByte[i] = XorByte(); xorByte[i].a <== a[i]; xorByte[i].b <== b[i]; @@ -116,13 +190,13 @@ template XORBLOCK(){ // right shift by one bit. If msb is 1: // then we xor the first byte with 0xE1 (11100001: 1 + X + X^2 + X^7) // this is the irreducible polynomial used in AES-GCM -template Mulx() { - signal input in[16]; - signal output out[16]; +template Mulx(n_bytes) { + signal input in[n_bytes]; + signal output out[n_bytes]; - signal intermediate[16]; + signal intermediate[n_bytes]; - component blockRightShift = BlockRightShift(); + component blockRightShift = BlockRightShift(n_bytes); blockRightShift.in <== in; intermediate <== blockRightShift.out; @@ -136,40 +210,40 @@ template Mulx() { mux.c[0] <== intermediate[0]; mux.c[1] <== xorByte.out; - for (var i = 1; i < 16; i++) { + for (var i = 1; i < n_bytes; i++) { out[i] <== intermediate[i]; } out[0] <== mux.out; } // right shifts 16 bytes by one bit and returns the msb before the shift -template BlockRightShift() { - signal input in[16]; - signal output out[16]; +template BlockRightShift(n_bytes) { + signal input in[n_bytes]; + signal output out[n_bytes]; signal output msb; - signal shiftedbits[128]; - component bytesToBits = BytesToBits(16); - for (var i = 0; i < 16; i++) { + signal shiftedbits[n_bytes*8]; + component bytesToBits = BytesToBits(n_bytes); + for (var i = 0; i < n_bytes; i++) { bytesToBits.in[i] <== in[i]; } - msb <== bytesToBits.out[127]; + msb <== bytesToBits.out[n_bytes*8 - 1]; - component BitwiseRightShift = BitwiseRightShift(128, 1); + component BitwiseRightShift = BitwiseRightShift(n_bytes*8, 1); BitwiseRightShift.in <== bytesToBits.out; shiftedbits <== BitwiseRightShift.out; - component bitsToBytes = BitsToBytes(16); + component bitsToBytes = BitsToBytes(n_bytes); bitsToBytes.in <== shiftedbits; out <== bitsToBytes.out; } // n is the number of bytes to convert to bits -template BytesToBits(n) { - signal input in[n]; - signal output out[n*8]; - component num2bits[n]; - for (var i = 0; i < n; i++) { +template BytesToBits(n_bytes) { + signal input in[n_bytes]; + signal output out[n_bytes*8]; + component num2bits[n_bytes]; + for (var i = 0; i < n_bytes; i++) { num2bits[i] = Num2Bits(8); num2bits[i].in <== in[i]; for (var j = 7; j >=0; j--) { diff --git a/circuits/test/gfmulint/nistgfmul.test.ts b/circuits/test/gfmulint/nistgfmul.test.ts index 8b341cb..7baf55b 100644 --- a/circuits/test/gfmulint/nistgfmul.test.ts +++ b/circuits/test/gfmulint/nistgfmul.test.ts @@ -16,26 +16,79 @@ describe("NistGMulByte", () => { it("Should Compute NistGMulByte Correctly", async () => { - // let h = "aae06992acbf52a3e8f4a96ec9300bd7"; - // let x = "98e7247c07f0fe411c267e4384b0f600"; - - // let h_le = hexStringToByteArray(h).reverse(); - // let x_le = hexStringToByteArray(x).reverse(); - // // let X = hexToBitArray("0xaae06992acbf52a3e8f4a96ec9300bd7"); // little endian hex vectors - // let X = hexStringToByteArray("aae06992acbf52a3e8f4a96ec9300bd7"); - // // let Y = hexToBitArray("0x98e7247c07f0fe411c267e4384b0f600"); - // let Y = hexStringToByteArray("98e7247c07f0fe411c267e4384b0f600"); - let one = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; - let zero = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let X = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let Y = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; - // const expected = hexToBitArray("0x2ff58d80033927ab8ef4d4587514f0fb"); const expected = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; - await circuit.expectPass({ X: zero, Y: one }, { out: expected }); - // console.log("res:", _res.out); - // assert.deepEqual(_res.out, expected); + await circuit.expectPass({ X: X, Y: Y }, { out: expected }); + }); + + it("Should Compute NistGMulByte of LSB=1 Correctly", async () => { + + let X = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + let Y = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + + const expected = [0xe6, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03]; + await circuit.expectPass({ X: X, Y: Y }, { out: expected }); }); }); +describe("debug1", () => { + let circuit: WitnessTester<["X", "Y"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("debug1", { + file: "aes-gcm/nistgmul", + template: "debug_1_byte", + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("Should Compute Correctly", async () => { + let inputX = [0x80]; + let inputY = [0x80]; + const expected = [0x80]; + console.log("expected", expected); + const _res = await circuit.expectPass({ X: inputX, Y: inputY }, { out: expected }); + }); + it("Should Compute Correctly", async () => { + let inputX = [0x01]; + let inputY = [0x01]; + + const expected = [0x5E]; + console.log("expected", expected); + await circuit.expectPass({ X: inputX, Y: inputY }, { out: expected }); + }); +}); + +describe("debug2", () => { + let circuit: WitnessTester<["X", "Y"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("debug1", { + file: "aes-gcm/nistgmul", + template: "debug_2_bytes", + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("Should Compute Correctly", async () => { + let inputX = [0x80, 0x00]; + let inputY = [0x80, 0x00]; + const expected = [0x80, 0x00]; + console.log("expected", expected); + const _res = await circuit.expectPass({ X: inputX, Y: inputY }, { out: expected }); + }); + it("Should Compute Correctly", async () => { + let inputX = [0x00, 0x01]; + let inputY = [0x00, 0x01]; + + const expected = [0xE6, 0x0B]; + console.log("expected", expected); + await circuit.expectPass({ X: inputX, Y: inputY }, { out: expected }); + }); +}); + describe("ToBits", () => { let circuit: WitnessTester<["in"], ["out"]>; @@ -155,6 +208,7 @@ describe("BlockRightShift", () => { circuit = await circomkit.WitnessTester("BlockRightShift", { file: "aes-gcm/nistgmul", template: "BlockRightShift", + params: [16] }); console.log("#constraints:", await circuit.getConstraintCount()); }); @@ -178,6 +232,7 @@ describe("Mulx", () => { circuit = await circomkit.WitnessTester("Mulx", { file: "aes-gcm/nistgmul", template: "Mulx", + params: [16] }); console.log("#constraints:", await circuit.getConstraintCount()); }); @@ -196,6 +251,7 @@ describe("XORBLOCK", () => { circuit = await circomkit.WitnessTester("XORBLOCK", { file: "aes-gcm/nistgmul", template: "XORBLOCK", + params: [16] }); console.log("#constraints:", await circuit.getConstraintCount()); }); @@ -245,6 +301,7 @@ describe("Z_I_UPDATE", () => { circuit = await circomkit.WitnessTester("XORBLOCK", { file: "aes-gcm/nistgmul", template: "Z_I_UPDATE", + params: [16] }); console.log("#constraints:", await circuit.getConstraintCount()); }); diff --git a/src/main.rs b/src/main.rs index f0c74ca..882f2cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -116,8 +116,8 @@ mod tests { ghash.update(&[X.into()]); let result = ghash.finalize(); - const H_1: [u8; 16] = hex!("81000000000000000000000000000000"); - const X_1: [u8; 16] = hex!("81000000000000000000000000000000"); + const H_1: [u8; 16] = hex!("00000000000000000000000000000001"); + const X_1: [u8; 16] = hex!("00000000000000000000000000000001"); // Alternative. let mut ghash2 = GHash::new(&H_1.into()); ghash2.update(&[X_1.into()]); From 8e1864d1e19ad17e89d9fe1f842be1c2e589f3bc Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Thu, 19 Sep 2024 15:52:39 -0600 Subject: [PATCH 08/10] tracies test vector passing --- circuits/test/gfmulint/nistgfmul.test.ts | 12 ++++++++++++ src/main.rs | 19 +++++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/circuits/test/gfmulint/nistgfmul.test.ts b/circuits/test/gfmulint/nistgfmul.test.ts index 7baf55b..f0a7ea4 100644 --- a/circuits/test/gfmulint/nistgfmul.test.ts +++ b/circuits/test/gfmulint/nistgfmul.test.ts @@ -31,6 +31,18 @@ describe("NistGMulByte", () => { const expected = [0xe6, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03]; await circuit.expectPass({ X: X, Y: Y }, { out: expected }); }); + it("Should Compute NistGMulByte of LSB=1 Correctly", async () => { + + // x = "aae06992acbf52a3e8f4a96ec9300bd7" + // y = "98e7247c07f0fe411c267e4384b0f600" + // expected = "90e87315fb7d4e1b4092ec0cbfda5d7d" + let X = [0xaa, 0xe0, 0x69, 0x92, 0xac, 0xbf, 0x52, 0xa3, 0xe8, 0xf4, 0xa9, 0x6e, 0xc9, 0x30, 0x0b, 0xd7]; + let Y = [0x98, 0xe7, 0x24, 0x7c, 0x07, 0xf0, 0xfe, 0x41, 0x1c, 0x26, 0x7e, 0x43, 0x84, 0xb0, 0xf6, 0x00]; + + const expected = [0x90, 0xe8, 0x73, 0x15, 0xfb, 0x7d, 0x4e, 0x1b, 0x40, 0x92, 0xec, 0x0c, 0xbf, 0xda, 0x5d, 0x7d]; + await circuit.expectPass({ X: X, Y: Y }, { out: expected }); + }); + }); describe("debug1", () => { diff --git a/src/main.rs b/src/main.rs index 882f2cd..7318140 100644 --- a/src/main.rs +++ b/src/main.rs @@ -107,7 +107,7 @@ mod tests { }; use hex_literal::hex; - // first byte is 00000001 + // first bit is 1 const H: [u8; 16] = hex!("80000000000000000000000000000000"); const X: [u8; 16] = hex!("80000000000000000000000000000000"); @@ -116,15 +116,26 @@ mod tests { ghash.update(&[X.into()]); let result = ghash.finalize(); + // last bit is 1 const H_1: [u8; 16] = hex!("00000000000000000000000000000001"); const X_1: [u8; 16] = hex!("00000000000000000000000000000001"); - // Alternative. + let mut ghash2 = GHash::new(&H_1.into()); ghash2.update(&[X_1.into()]); let result2 = ghash2.finalize(); - println!("GHASH result_1: {:?}", hex::encode(result.as_slice())); - println!("GHASH result_2: {:?}", hex::encode(result2.as_slice())); + // test vector of pain + const H_2: [u8; 16] = hex!("aae06992acbf52a3e8f4a96ec9300bd7"); + const X_2: [u8; 16] = hex!("98e7247c07f0fe411c267e4384b0f600"); + + let mut ghash3 = GHash::new(&H_2.into()); + ghash3.update(&[X_2.into()]); + let result3 = ghash3.finalize(); + + println!("GHASH Test vector 1: {:?}", hex::encode(result.as_slice())); + println!("GHASH Test vector 2: {:?}", hex::encode(result2.as_slice())); + println!("GHASH Test vector 3: {:?}", hex::encode(result3.as_slice())); + // println!("expected: {:?}", hex::encode(expected)); } From f252c0ec0c543a474ddf1cbca30c6256e811c050 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Thu, 19 Sep 2024 15:52:52 -0600 Subject: [PATCH 09/10] fmt --- src/main.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7318140..e275930 100644 --- a/src/main.rs +++ b/src/main.rs @@ -108,10 +108,9 @@ mod tests { use hex_literal::hex; // first bit is 1 - const H: [u8; 16] = hex!("80000000000000000000000000000000"); + const H: [u8; 16] = hex!("80000000000000000000000000000000"); const X: [u8; 16] = hex!("80000000000000000000000000000000"); - let mut ghash = GHash::new(&H.into()); ghash.update(&[X.into()]); let result = ghash.finalize(); @@ -119,7 +118,7 @@ mod tests { // last bit is 1 const H_1: [u8; 16] = hex!("00000000000000000000000000000001"); const X_1: [u8; 16] = hex!("00000000000000000000000000000001"); - + let mut ghash2 = GHash::new(&H_1.into()); ghash2.update(&[X_1.into()]); let result2 = ghash2.finalize(); @@ -127,7 +126,7 @@ mod tests { // test vector of pain const H_2: [u8; 16] = hex!("aae06992acbf52a3e8f4a96ec9300bd7"); const X_2: [u8; 16] = hex!("98e7247c07f0fe411c267e4384b0f600"); - + let mut ghash3 = GHash::new(&H_2.into()); ghash3.update(&[X_2.into()]); let result3 = ghash3.finalize(); @@ -137,6 +136,5 @@ mod tests { println!("GHASH Test vector 3: {:?}", hex::encode(result3.as_slice())); // println!("expected: {:?}", hex::encode(expected)); - } } From d940323c4de74ecaf42c25aa1fd387e44601be61 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Thu, 19 Sep 2024 16:25:12 -0600 Subject: [PATCH 10/10] ghash ietf test passing --- circuits/aes-gcm/ghash.circom | 95 +++++++----------------------- circuits/test/hashes/ghash.test.ts | 19 +++--- 2 files changed, 29 insertions(+), 85 deletions(-) diff --git a/circuits/aes-gcm/ghash.circom b/circuits/aes-gcm/ghash.circom index dd20b90..6b4275d 100644 --- a/circuits/aes-gcm/ghash.circom +++ b/circuits/aes-gcm/ghash.circom @@ -1,6 +1,6 @@ pragma circom 2.1.9; include "helper_functions.circom"; -include "gfmul.circom"; +include "nistgmul.circom"; // GHASH computes the authentication tag for AES-GCM. // Inputs: @@ -35,95 +35,42 @@ include "gfmul.circom"; // template GHASH(NUM_BLOCKS) { - signal input HashKey[4][4]; // Hash subkey (128 bits) - signal input msg[NUM_BLOCKS][4][4]; // Input blocks (each 128 bits) - signal output tag[128]; // Output tag (128 bits) - // signal output tag[2][64]; // Output tag (128 bits) - - // Janky convert [4][4] block into [2][64] bit lists - // TODO: Double check the endianness of this conversion. - signal hashBits[2][64]; - for(var i = 0; i < 4; i++) { - for(var j = 0; j < 4; j++) { - var bit = 1; - var lc = 0; - for(var k = 0; k < 8; k++) { - var bitIndex = (i*4*8)+(j*8)+k; - var bitValue = (HashKey[i][j] >> k) & 1; - var rowIndex = bitIndex\64; - var colIndex = bitIndex%64; - hashBits[rowIndex][colIndex] <-- bitValue; - hashBits[rowIndex][colIndex] * (hashBits[rowIndex][colIndex] - 1) === 0; - lc += hashBits[rowIndex][colIndex] * bit; - bit = bit+bit; - } - HashKey[i][j] === lc; - } - } - - signal msgBits[NUM_BLOCKS][2][64]; - for(var i = 0; i < NUM_BLOCKS; i++) { - for(var j = 0; j < 4; j++) { - for(var k=0; k < 4; k++) { - var bit = 1; - var lc = 0; - for(var l = 0; l < 8; l++) { - var bitIndex = (j*4*8)+(k*8)+l; - var bitValue = (msg[i][j][k] >> l) & 1; - var rowIndex = bitIndex\64; - var colIndex = bitIndex%64; - msgBits[i][rowIndex][colIndex] <-- bitValue; - msgBits[i][rowIndex][colIndex] * (msgBits[i][rowIndex][colIndex] - 1) === 0; - lc += msgBits[i][rowIndex][colIndex] * bit; - bit = bit+bit; - } - msg[i][j][k] === lc; - } - } - } + signal input HashKey[16]; // Hash subkey (128 bits) + signal input msg[NUM_BLOCKS][16]; // Input blocks (each 128 bits) + signal output tag[16]; // Output tag (128 bits) // Intermediate tags - signal intermediate[NUM_BLOCKS][2][64]; + signal intermediate[NUM_BLOCKS+1][16]; - // Initialize first intermediate to zero - for (var j = 0; j < 64; j++) { - intermediate[0][0][j] <== 0; - intermediate[0][1][j] <== 0; + // Initialize first intermediate block to zero + for (var j = 0; j < 16; j++) { + intermediate[0][j] <== 0; } // Initialize components // two 64bit xor components for each block - component xor[NUM_BLOCKS][2]; + component xor[NUM_BLOCKS]; // one gfmul component for each block component gfmul[NUM_BLOCKS]; // Accumulate each block using GHASH multiplication - for (var i = 1; i < NUM_BLOCKS; i++) { - xor[i][0] = BitwiseXor(64); - xor[i][1] = BitwiseXor(64); - gfmul[i] = MUL(); + for (var i = 0; i < NUM_BLOCKS; i++) { + xor[i] = XORBLOCK(16); + gfmul[i] = NistGMulByte(); // XOR current block with the previous intermediate result - // note: intermediate[0] is initialized to zero, so all rounds are valid - xor[i][0].a <== intermediate[i-1][0]; - xor[i][1].a <== intermediate[i-1][1]; - xor[i][0].b <== msgBits[i][0]; - xor[i][1].b <== msgBits[i][1]; + xor[i].a <== intermediate[i]; + xor[i].b <== msg[i]; // Multiply the XOR result with the hash subkey H - gfmul[i].a[0] <== xor[i][0].out; - gfmul[i].a[1] <== xor[i][1].out; - gfmul[i].b <== hashBits; + gfmul[i].X <== HashKey; + gfmul[i].Y <== xor[i].out; // Store the result in the next intermediate tag - intermediate[i][0] <== gfmul[i].out[0]; - intermediate[i][1] <== gfmul[i].out[1]; + intermediate[i+1] <== gfmul[i].out; } - // Assign the final tag - for (var j = 0; j < 64; j++) { - tag[j] <== intermediate[NUM_BLOCKS-1][0][j]; - tag[j+64] <== intermediate[NUM_BLOCKS-1][1][j]; - } - // tag[0] <== intermediate[NUM_BLOCKS-1][0]; - // tag[1] <== intermediate[NUM_BLOCKS-1][1]; + + // Final tag is the last intermediate block + tag <== intermediate[NUM_BLOCKS]; + } diff --git a/circuits/test/hashes/ghash.test.ts b/circuits/test/hashes/ghash.test.ts index 3b3fc4e..78fa45a 100644 --- a/circuits/test/hashes/ghash.test.ts +++ b/circuits/test/hashes/ghash.test.ts @@ -2,12 +2,7 @@ import { WitnessTester } from "circomkit"; import { bitArrayToHex, circomkit, hexToBitArray } from "../common"; import { assert } from "chai"; -// https://datatracker.ietf.org/doc/html/rfc8452#appendix-A -const H = hexToBitArray("25629347589242761d31f826ba4b757b"); -const X1 = "4f4f95668c83dfb6401762bb2d01a262"; -const X2 = "d1a24ddd2721d006bbe45f20d3c9f362"; -const M = hexToBitArray(X1.concat(X2)); -const EXPECT = hexToBitArray("bd9b3997046731fb96251b91f9c99d7a"); + describe("ghash-hash", () => { let circuit: WitnessTester<["HashKey", "msg"], ["tag"]>; @@ -22,11 +17,13 @@ describe("ghash-hash", () => { }); it("test ghash", async () => { - const input = { HashKey: H, msg: M }; - console.log("input message length: ", input.msg.length); - console.log("input hash key length: ", input.HashKey.length); - console.log("input message: ", EXPECT); - const _res = await circuit.expectPass(input, { tag: EXPECT }); + // https://datatracker.ietf.org/doc/html/rfc8452#appendix-A + const H = [0x25, 0x62, 0x93, 0x47, 0x58, 0x92, 0x42, 0x76, 0x1d, 0x31, 0xf8, 0x26, 0xba, 0x4b, 0x75, 0x7b]; + const X1 = [0x4f, 0x4f, 0x95, 0x66, 0x8c, 0x83, 0xdf, 0xb6, 0x40, 0x17, 0x62, 0xbb, 0x2d, 0x01, 0xa2, 0x62]; + const X2 = [0xd1, 0xa2, 0x4d, 0xdd, 0x27, 0x21, 0xd0, 0x06, 0xbb, 0xe4, 0x5f, 0x20, 0xd3, 0xc9, 0xf3, 0x62]; + const M = X1.concat(X2); + const EXPECT = [0xbd, 0x9b, 0x39, 0x97, 0x04, 0x67, 0x31, 0xfb, 0x96, 0x25, 0x1b, 0x91, 0xf9, 0xc9, 0x9d, 0x7a]; + const _res = await circuit.expectPass({ HashKey: H, msg: M }, { tag: EXPECT }); }); });