From adbbf2c3a69bc404a09d85b1dee1148cab89e963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jepsen=20=E2=9C=A8?= <57912727+0xJepsen@users.noreply.github.com> Date: Fri, 1 Nov 2024 07:13:48 +0700 Subject: [PATCH] migrate aes (#20) * aes-gcm: aes components and tests aes-gcm: aes components and tests * aes-gcm: Ghash components and tests * aes-gcm gctr and gcm aes-gcm gctr and gcm * aes-gcm nivc components and tests * test and document new utils * final nivc component final nivc component * Resolving CI errors * test path fix --- circuits/aes-gcm/aes-gcm.circom | 145 ++++++++ circuits/aes-gcm/aes/cipher.circom | 159 +++++++++ circuits/aes-gcm/aes/key_expansion.circom | 179 ++++++++++ circuits/aes-gcm/aes/mix_columns.circom | 209 ++++++++++++ circuits/aes-gcm/aes/sbox.circom | 298 +++++++++++++++++ circuits/aes-gcm/gctr.circom | 129 ++++++++ circuits/aes-gcm/ghash/ghash.circom | 69 ++++ circuits/aes-gcm/ghash/gmul.circom | 133 ++++++++ circuits/aes-gcm/nivc/aes-gctr-nivc.circom | 70 ++++ circuits/aes-gcm/nivc/gctr-nivc.circom | 76 +++++ circuits/aes-gctr-nivc.circom | 7 + circuits/aes_gcm.circom | 91 ----- circuits/test/aes-gcm/aes-gcm.test.ts | 67 ++++ circuits/test/aes-gcm/aes/cipher.test.ts | 193 +++++++++++ .../test/aes-gcm/aes/key_expansion.test.ts | 106 ++++++ circuits/test/aes-gcm/aes/sbox.test.ts | 125 +++++++ .../test/aes-gcm/aes/transformations.test.ts | 74 +++++ circuits/test/aes-gcm/gctr.test.ts | 31 ++ circuits/test/aes-gcm/ghash/ghash.test.ts | 30 ++ circuits/test/aes-gcm/ghash/gmul.test.ts | 117 +++++++ .../test/aes-gcm/nivc/aes-gctr-nivc.test.ts | 100 ++++++ circuits/test/common/index.ts | 11 + circuits/test/utils/array.test.ts | 311 +++++++++++++++++- circuits/utils/array.circom | 257 ++++++++++++++- circuits/utils/bytes.circom | 168 ++++++++++ package-lock.json | 22 +- package.json | 27 +- 27 files changed, 3094 insertions(+), 110 deletions(-) create mode 100644 circuits/aes-gcm/aes-gcm.circom create mode 100644 circuits/aes-gcm/aes/cipher.circom create mode 100644 circuits/aes-gcm/aes/key_expansion.circom create mode 100644 circuits/aes-gcm/aes/mix_columns.circom create mode 100644 circuits/aes-gcm/aes/sbox.circom create mode 100644 circuits/aes-gcm/gctr.circom create mode 100644 circuits/aes-gcm/ghash/ghash.circom create mode 100644 circuits/aes-gcm/ghash/gmul.circom create mode 100644 circuits/aes-gcm/nivc/aes-gctr-nivc.circom create mode 100644 circuits/aes-gcm/nivc/gctr-nivc.circom create mode 100644 circuits/aes-gctr-nivc.circom delete mode 100644 circuits/aes_gcm.circom create mode 100644 circuits/test/aes-gcm/aes-gcm.test.ts create mode 100644 circuits/test/aes-gcm/aes/cipher.test.ts create mode 100644 circuits/test/aes-gcm/aes/key_expansion.test.ts create mode 100644 circuits/test/aes-gcm/aes/sbox.test.ts create mode 100644 circuits/test/aes-gcm/aes/transformations.test.ts create mode 100644 circuits/test/aes-gcm/gctr.test.ts create mode 100644 circuits/test/aes-gcm/ghash/ghash.test.ts create mode 100644 circuits/test/aes-gcm/ghash/gmul.test.ts create mode 100644 circuits/test/aes-gcm/nivc/aes-gctr-nivc.test.ts diff --git a/circuits/aes-gcm/aes-gcm.circom b/circuits/aes-gcm/aes-gcm.circom new file mode 100644 index 0000000..ef9373b --- /dev/null +++ b/circuits/aes-gcm/aes-gcm.circom @@ -0,0 +1,145 @@ +pragma circom 2.1.9; + +include "ghash/ghash.circom"; +include "aes/cipher.circom"; +include "../utils/array.circom"; +include "gctr.circom"; + + +/// AES-GCM with 128 bit key authenticated encryption according to: https://nvlpubs.nist.gov/nistpubs/legacy/sp/nistspecialpublication800-38d.pdf +/// +/// Parameters: +/// l: length of the plaintext +/// +/// Inputs: +/// key: 128-bit key +/// iv: initialization vector +/// plainText: plaintext to be encrypted +/// aad: additional data to be authenticated +/// +/// Outputs: +/// cipherText: encrypted ciphertext +/// authTag: authentication tag +/// +template AESGCM(l) { + // Inputs + signal input key[16]; // 128-bit key + signal input iv[12]; // IV length is 96 bits (12 bytes) + signal input plainText[l]; + signal input aad[16]; // AAD length is 128 bits (16 bytes) + + // Outputs + signal output cipherText[l]; + signal output authTag[16]; // Authentication tag length is 128 bits (16 bytes) + + component zeroBlock = ToBlocks(16); + for (var i = 0; i < 16; i++) { + zeroBlock.stream[i] <== 0; + } + + // Step 1: Let H = aes(key, zeroBlock) + component cipherH = Cipher(); + cipherH.key <== key; + cipherH.block <== zeroBlock.blocks[0]; + + // Step 2: Define a block, J0 with 96 bits of iv and 32 bits of 0s + component J0builder = ToBlocks(16); + for (var i = 0; i < 12; i++) { + J0builder.stream[i] <== iv[i]; + } + for (var i = 12; i < 16; i++) { + J0builder.stream[i] <== 0; + } + component J0WordIncrementer = IncrementWord(); + J0WordIncrementer.in <== J0builder.blocks[0][3]; + + component J0WordIncrementer2 = IncrementWord(); + J0WordIncrementer2.in <== J0WordIncrementer.out; + + signal J0[4][4]; + for (var i = 0; i < 3; i++) { + J0[i] <== J0builder.blocks[0][i]; + } + J0[3] <== J0WordIncrementer2.out; + + // Step 3: Let C = GCTRK(inc32(J0), P) + component gctr = GCTR(l); + gctr.key <== key; + gctr.initialCounterBlock <== J0; + gctr.plainText <== plainText; + + + // Step 4: Let u and v (v is always zero with out key size and aad length) + var blockCount = l\16; + if(l%16 > 0){ + blockCount = blockCount + 1; + } + // so the reason there is a plus two is because + // the first block is the aad + // the second is the ciphertext + // the last is the length of the aad and ciphertext + // i.e. S = GHASHH (A || C || [len(A)] || [len(C)]). <- which is always 48 bytes: 3 blocks + var ghashblocks = blockCount + 2; + signal ghashMessage[ghashblocks][4][4]; + + // set aad as first block + for (var i=0; i < 4; i++) { + for (var j=0; j < 4; j++) { + ghashMessage[0][i][j] <== aad[i*4+j]; + } + } + // set cipher text block padded + component ciphertextBlocks = ToBlocks(l); + ciphertextBlocks.stream <== gctr.cipherText; + + for (var i=0; i> i*8+j) & 1; + } + ghashMessage[ghashblocks-1][i\4+2][i%4] <== byte_value; + } + + // Step 5: Define a block, S + // needs to take in the number of blocks + component ghash = GHASH(ghashblocks); + component hashKeyToStream = ToStream(1, 16); + hashKeyToStream.blocks[0] <== cipherH.cipher; + ghash.HashKey <== hashKeyToStream.stream; + // S = GHASHH (A || 0^v || C || 0^u || [len(A)] || [len(C)]). + component selectedBlocksToStream[ghashblocks]; + for (var i = 0 ; i 0 && round <= 10); + + var rcon[10][4] = [ + [0x01, 0x00, 0x00, 0x00], + [0x02, 0x00, 0x00, 0x00], + [0x04, 0x00, 0x00, 0x00], + [0x08, 0x00, 0x00, 0x00], + [0x10, 0x00, 0x00, 0x00], + [0x20, 0x00, 0x00, 0x00], + [0x40, 0x00, 0x00, 0x00], + [0x80, 0x00, 0x00, 0x00], + [0x1b, 0x00, 0x00, 0x00], + [0x36, 0x00, 0x00, 0x00] + ]; + + out <== rcon[round-1]; +} + +// Rotates an array of bytes to the left by a specified rotation +template Rotate(rotation, length) { + assert(rotation < length); + signal input bytes[length]; + signal output rotated[length]; + + for(var i = 0; i < length - rotation; i++) { + rotated[i] <== bytes[i + rotation]; + } + + for(var i = length - rotation; i < length; i++) { + rotated[i] <== bytes[i - length + rotation]; + } +} + +// Substitutes each byte in a word using the S-Box +template SubstituteWord() { + signal input bytes[4]; + signal output substituted[4]; + + component sbox[4]; + + for(var i = 0; i < 4; i++) { + sbox[i] = SBox128(); + sbox[i].in <== bytes[i]; + substituted[i] <== sbox[i].out; + } +} \ No newline at end of file diff --git a/circuits/aes-gcm/aes/mix_columns.circom b/circuits/aes-gcm/aes/mix_columns.circom new file mode 100644 index 0000000..f52a40a --- /dev/null +++ b/circuits/aes-gcm/aes/mix_columns.circom @@ -0,0 +1,209 @@ +// from: https://github.com/crema-labs/aes-circom/tree/main/circuits +pragma circom 2.1.9; + +include "key_expansion.circom"; +include "circomlib/circuits/bitify.circom"; +include "../../utils/bytes.circom"; + +// MixColumns: Applies the equation for each column: +// [s'0,c] [2 3 1 1][s0,c] +// [s'1,c] = [1 2 3 1][s1,c] +// [s'2,c] [1 1 2 3][s2,c] +// [s'3,c] [3 1 1 2][s3,c] +// Where c is the column number, s are input bytes, s' are output bytes +template MixColumns(){ + signal input state[4][4]; + signal output out[4][4]; + + component s0[4]; + component s1[4]; + component s2[4]; + component s3[4]; + + for (var i = 0; i < 4; i++) { + s0[i] = S0(); + s1[i] = S1(); + s2[i] = S2(); + s3[i] = S3(); + + for(var j = 0; j < 4; j++) { + s0[i].in[j] <== state[j][i]; + s1[i].in[j] <== state[j][i]; + s2[i].in[j] <== state[j][i]; + s3[i].in[j] <== state[j][i]; + } + + out[0][i] <== s0[i].out; + out[1][i] <== s1[i].out; + out[2][i] <== s2[i].out; + out[3][i] <== s3[i].out; + } +} + +// S0: Implements the equation +// out = (2 • in[0]) ⊕ (3 • in[1]) ⊕ in[2] ⊕ in[3] +template S0(){ + signal input in[4]; + signal output out; + component num2bits[4]; + component xor[3]; + + for (var i = 2; i < 4; i++) { + num2bits[i] = Num2Bits(8); + num2bits[i].in <== in[i]; + } + + num2bits[0] = Num2Bits(8); + num2bits[0].in <== TBox(0)(in[0]); + + num2bits[1] = Num2Bits(8); + num2bits[1].in <== TBox(1)(in[1]); + + xor[0] = BitwiseXor(8); + xor[0].a <== num2bits[0].out; + xor[0].b <== num2bits[1].out; + + xor[1] = BitwiseXor(8); + xor[1].a <== xor[0].out; + xor[1].b <== num2bits[2].out; + + xor[2] = BitwiseXor(8); + xor[2].a <== xor[1].out; + xor[2].b <== num2bits[3].out; + + component b2n = Bits2Num(8); + for (var i = 0; i < 8; i++) { + b2n.in[i] <== xor[2].out[i]; + } + + out <== b2n.out; +} + +// S1: Implements the equation +// out = in[0] ⊕ (2 • in[1]) ⊕ (3 • in[2]) ⊕ in[3] +template S1(){ + signal input in[4]; + signal output out; + component num2bits[4]; + component xor[3]; + + num2bits[0] = Num2Bits(8); + num2bits[0].in <== in[0]; + + num2bits[1] = Num2Bits(8); + num2bits[1].in <== TBox(0)(in[1]); + + num2bits[2] = Num2Bits(8); + num2bits[2].in <== TBox(1)(in[2]); + + num2bits[3] = Num2Bits(8); + num2bits[3].in <== in[3]; + + xor[0] = BitwiseXor(8); + xor[0].a <== num2bits[0].out; + xor[0].b <== num2bits[1].out; + + xor[1] = BitwiseXor(8); + xor[1].a <== xor[0].out; + xor[1].b <== num2bits[2].out; + + xor[2] = BitwiseXor(8); + xor[2].a <== xor[1].out; + xor[2].b <== num2bits[3].out; + + component b2n = Bits2Num(8); + for (var i = 0; i < 8; i++) { + b2n.in[i] <== xor[2].out[i]; + } + + out <== b2n.out; +} + +// S2: Implements the equation +// out = in[0] ⊕ in[1] ⊕ (2 • in[2]) ⊕ (3 • in[3]) +template S2() { + signal input in[4]; + signal output out; + component num2bits[4]; + component xor[3]; + + for (var i = 0; i < 2; i++) { + num2bits[i] = Num2Bits(8); + num2bits[i].in <== in[i]; + } + + num2bits[2] = Num2Bits(8); + num2bits[2].in <== TBox(0)(in[2]); + + num2bits[3] = Num2Bits(8); + num2bits[3].in <== TBox(1)(in[3]); + + xor[0] = BitwiseXor(8); + xor[0].a <== num2bits[0].out; + xor[0].b <== num2bits[1].out; + + xor[1] = BitwiseXor(8); + xor[1].a <== xor[0].out; + xor[1].b <== num2bits[2].out; + + xor[2] = BitwiseXor(8); + xor[2].a <== xor[1].out; + xor[2].b <== num2bits[3].out; + + component b2n = Bits2Num(8); + for (var i = 0; i < 8; i++) { + b2n.in[i] <== xor[2].out[i]; + } + + out <== b2n.out; +} + +// S3: Implements the equation +// out = (3 • in[0]) ⊕ in[1] ⊕ in[2] ⊕ (2 • in[3]) +template S3() { + signal input in[4]; + signal output out; + component num2bits[4]; + component xor[3]; + + for (var i = 1; i < 3; i++) { + num2bits[i] = Num2Bits(8); + num2bits[i].in <== in[i]; + } + + num2bits[0] = Num2Bits(8); + num2bits[0].in <== TBox(1)(in[0]); + + num2bits[3] = Num2Bits(8); + num2bits[3].in <== TBox(0)(in[3]); + + xor[0] = BitwiseXor(8); + xor[0].a <== num2bits[0].out; + xor[0].b <== num2bits[1].out; + + xor[1] = BitwiseXor(8); + xor[1].a <== xor[0].out; + xor[1].b <== num2bits[2].out; + + xor[2] = BitwiseXor(8); + xor[2].a <== num2bits[3].out; + xor[2].b <== xor[1].out; + + component b2n = Bits2Num(8); + for (var i = 0; i < 8; i++) { + b2n.in[i] <== xor[2].out[i]; + } + + out <== b2n.out; +} + +template TBox(index) { + signal input subindex; + signal output out; + + if (index == 0) { + out <== FieldMul2()(subindex); + } else if (index == 1) { + out <== FieldMul3()(subindex); + } +} \ No newline at end of file diff --git a/circuits/aes-gcm/aes/sbox.circom b/circuits/aes-gcm/aes/sbox.circom new file mode 100644 index 0000000..bbea049 --- /dev/null +++ b/circuits/aes-gcm/aes/sbox.circom @@ -0,0 +1,298 @@ +// from: https://github.com/crema-labs/aes-circom/tree/main/circuits +pragma circom 2.1.9; + +include "circomlib/circuits/comparators.circom"; +include "circomlib/circuits/bitify.circom"; +include "circomlib/circuits/gates.circom"; +include "../../utils/array.circom"; +include "../../utils/bytes.circom"; + +template SBox128() { + signal input in; + signal output out; + + signal inv <== FieldInv()(in); + signal invBits[8] <== Num2Bits(8)(inv); + signal outBits[8] <== AffineTransform()(invBits); + out <== Bits2Num(8)(outBits); +} + + + +// All this is for a properly constrained sbox since we don't have lookup arguments +// Finite field addition, the signal variable plus a compile-time constant +template FieldAddConst(c) { + signal input in[8]; + // control bit, if 0, then do not perform addition + signal input control; + signal output out[8]; + + for (var i=0; i<8; i++) { + if(c & (1<>1 if LSB1(Vi) = 0; + // Vi+1 ⎨ + // ⎩ (Vi >>1) ⊕ R if LSB1(Vi) =1. + // + component bit[16]; + component z_i_update[128]; + component mulx[128]; + component bytesToBits = BytesToBits(16); + bytesToBits.in <== X; + signal bitsX[16*8]; + bitsX <== bytesToBits.out; + for (var i = 0; i < 128; i++) { + // z_i_update + z_i_update[i] = Z_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]; +} + + +// if bit value is 0, then Z_new = Z +// if bit value is 1, then Z_new = Z xor V +template Z_UPDATE(n_bytes) { + signal input Z[n_bytes]; // this is Zero block in first itteration + signal input V[n_bytes]; // this is Y in first itteration + signal input bit_val; + signal output Z_new[n_bytes]; + + component mux = ArrayMux(n_bytes); + mux.sel <== bit_val; + mux.a <== Z; + component xorBlock = XORBLOCK(n_bytes); + xorBlock.a <== Z; + xorBlock.b <== V; + mux.b <== xorBlock.out; + Z_new <== mux.out; +} + + + + +// right shift by one bit. If msb is 1: +// then we xor the first byte with 0xE1 (11100001: 1 + X + X^2 + X^7) +// this is the irreducible polynomial used in AES-GCM +template Mulx(n_bytes) { + signal input in[n_bytes]; + signal output out[n_bytes]; + + signal intermediate[n_bytes]; + + component blockRightShift = BlockRightShift(n_bytes); + blockRightShift.in <== in; + intermediate <== blockRightShift.out; + + component xorByte = XorByte(); + xorByte.a <== intermediate[0]; + xorByte.b <== 0xE1; // 11100001 + + // if msb is 1, then we xor the first byte with R[0] + component mux = Mux1(); + mux.s <== blockRightShift.msb; + mux.c[0] <== intermediate[0]; + mux.c[1] <== xorByte.out; + + for (var i = 1; i < n_bytes; i++) { + out[i] <== intermediate[i]; + } + out[0] <== mux.out; +} + +// right shifts 16 bytes by one bit and returns the msb before the shift +template BlockRightShift(n_bytes) { + signal input in[n_bytes]; + signal output out[n_bytes]; + signal output msb; + + signal shiftedbits[n_bytes*8]; + component bytesToBits = BytesToBits(n_bytes); + for (var i = 0; i < n_bytes; i++) { + bytesToBits.in[i] <== in[i]; + } + msb <== bytesToBits.out[n_bytes*8 - 1]; + + component BitwiseRightShift = BitwiseRightShift(n_bytes*8, 1); + BitwiseRightShift.in <== bytesToBits.out; + shiftedbits <== BitwiseRightShift.out; + + component bitsToBytes = BitsToBytes(n_bytes); + bitsToBytes.in <== shiftedbits; + out <== bitsToBytes.out; +} \ No newline at end of file diff --git a/circuits/aes-gcm/nivc/aes-gctr-nivc.circom b/circuits/aes-gcm/nivc/aes-gctr-nivc.circom new file mode 100644 index 0000000..689da97 --- /dev/null +++ b/circuits/aes-gcm/nivc/aes-gctr-nivc.circom @@ -0,0 +1,70 @@ +pragma circom 2.1.9; + +include "gctr-nivc.circom"; +include "../../utils/array.circom"; + + +// Compute AES-GCTR +template AESGCTRFOLD(INPUT_LEN) { + assert(INPUT_LEN % 16 == 0); + var DATA_BYTES = (INPUT_LEN * 2) + 4; + signal input key[16]; + signal input iv[12]; + signal input aad[16]; + signal input plainText[16]; + + // step_in[0..INPUT_LEN] => accumulate plaintext blocks + // step_in[INPUT_LEN..INPUT_LEN*2] => accumulate ciphertext blocks + // step_in[INPUT_LEN*2..INPUT_LEN*2+4] => accumulate counter + signal input step_in[DATA_BYTES]; + signal output step_out[DATA_BYTES]; + signal counter; + + // We extract the number from the 4 byte word counter + component last_counter_bits = BytesToBits(4); + for(var i = 0; i < 4; i ++) { + last_counter_bits.in[i] <== step_in[INPUT_LEN*2 + i]; + } + component last_counter_num = Bits2Num(32); + // pass in reverse order + for (var i = 0; i< 32; i++){ + last_counter_num.in[i] <== last_counter_bits.out[31 - i]; + } + + counter <== last_counter_num.out - 1; + + // write new plain text block. + signal plainTextAccumulator[DATA_BYTES]; + component writeToIndex = WriteToIndex(DATA_BYTES, 16); + writeToIndex.array_to_write_to <== step_in; + writeToIndex.array_to_write_at_index <== plainText; + writeToIndex.index <== counter * 16; + writeToIndex.out ==> plainTextAccumulator; + + // folds one block + component aes = AESGCTRFOLDABLE(); + aes.key <== key; + aes.iv <== iv; + aes.aad <== aad; + aes.plainText <== plainText; + + for(var i = 0; i < 4; i++) { + aes.lastCounter[i] <== step_in[INPUT_LEN*2 + i]; + } + + // accumulate cipher text + signal cipherTextAccumulator[DATA_BYTES]; + component writeCipherText = WriteToIndex(DATA_BYTES, 16); + writeCipherText.array_to_write_to <== plainTextAccumulator; + writeCipherText.array_to_write_at_index <== aes.cipherText; + writeCipherText.index <== INPUT_LEN + counter * 16; + writeCipherText.out ==> cipherTextAccumulator; + + // get counter + signal counterAccumulator[DATA_BYTES]; + component writeCounter = WriteToIndex(DATA_BYTES, 4); + writeCounter.array_to_write_to <== cipherTextAccumulator; + writeCounter.array_to_write_at_index <== aes.counter; + writeCounter.index <== INPUT_LEN*2; + writeCounter.out ==> step_out; +} \ No newline at end of file diff --git a/circuits/aes-gcm/nivc/gctr-nivc.circom b/circuits/aes-gcm/nivc/gctr-nivc.circom new file mode 100644 index 0000000..bde7fc9 --- /dev/null +++ b/circuits/aes-gcm/nivc/gctr-nivc.circom @@ -0,0 +1,76 @@ +pragma circom 2.1.9; + +include "../aes/cipher.circom"; +include "../../utils/array.circom"; +include "../gctr.circom"; + +/// AES-GCTR with 128 bit key encryption according to: https://nvlpubs.nist.gov/nistpubs/legacy/sp/nistspecialpublication800-38d.pdf +/// +/// Parameters: +/// l: length of the plaintext +/// +/// Inputs: +/// key: 128-bit key +/// iv: initialization vector +/// plainText: plaintext to be encrypted +/// aad: additional data to be authenticated +/// +/// Outputs: +/// cipherText: encrypted ciphertext +/// +/// This folds a single block without authentication via ghash. +template AESGCTRFOLDABLE() { + // Inputs + signal input key[16]; // 128-bit key + signal input iv[12]; // IV length is 96 bits (12 bytes) + signal input plainText[16]; // only fold 16 bytes at a time. + signal input aad[16]; // AAD length is 128 bits (16 bytes) + + // Fold inputs + signal input lastCounter[4]; // Always start at one, then bring forward last counter. + + // Fold outputs + signal output counter[4]; + signal output cipherText[16]; + + component zeroBlock = ToBlocks(16); + for (var i = 0; i < 16; i++) { + zeroBlock.stream[i] <== 0; + } + + // Step 1: Let HashKey = aes(key, zeroBlock) + component cipherH = Cipher(); + cipherH.key <== key; + cipherH.block <== zeroBlock.blocks[0]; + + // Step 2: Define a block, J0 with 96 bits of iv and 32 bits of 0s + component J0builder = ToBlocks(16); + for (var i = 0; i < 12; i++) { + J0builder.stream[i] <== iv[i]; + } + // Use the fold counter as input. + for (var i = 12; i < 16; i++) { + J0builder.stream[i] <== lastCounter[i%4]; // initialize to 0001. + } + + component J0WordIncrementer = IncrementWord(); + J0WordIncrementer.in <== J0builder.blocks[0][3]; + + signal J0[4][4]; + for (var i = 0; i < 3; i++) { + J0[i] <== J0builder.blocks[0][i]; + } + J0[3] <== J0WordIncrementer.out; + + // Step 3: Let C = GCTRK(inc32(J0), P) + component gctr = GCTR(16); + gctr.key <== key; + gctr.initialCounterBlock <== J0; + gctr.plainText <== plainText; + cipherText <== gctr.cipherText; + + // extract the counter column wise. + for (var i = 0; i < 4; i++) { + counter[i] <== J0[i][3]; + } +} \ No newline at end of file diff --git a/circuits/aes-gctr-nivc.circom b/circuits/aes-gctr-nivc.circom new file mode 100644 index 0000000..7886f37 --- /dev/null +++ b/circuits/aes-gctr-nivc.circom @@ -0,0 +1,7 @@ +pragma circom 2.1.9; + +include "aes-gcm/nivc/aes-gctr-nivc.circom"; + +// Note(WJ 2024-10-31): I put this here like this because i have tests i wanted to include for this component +// the circomkit tests become unhappy when there is a main. +component main { public [step_in] } = AESGCTRFOLD(48); \ No newline at end of file diff --git a/circuits/aes_gcm.circom b/circuits/aes_gcm.circom deleted file mode 100644 index 70ceabf..0000000 --- a/circuits/aes_gcm.circom +++ /dev/null @@ -1,91 +0,0 @@ -pragma circom 2.1.9; - -include "aes-proof/circuits/aes-gcm/aes-gcm-foldable.circom"; -include "aes-proof/circuits/aes-gcm/utils.circom"; -include "@zk-email/circuits/utils/array.circom"; - - -template AESGCMFOLD(bytesPerFold, totalBytes, inputBytes) { - // cannot fold outside chunk boundaries. - assert(bytesPerFold % 16 == 0); - assert(totalBytes % 16 == 0); - - signal input key[16]; - signal input iv[12]; - signal input aad[16]; - signal input plainText[bytesPerFold]; - // signal input cipherText[totalBytes]; - - // Output from the last encryption step - // Always use last bytes for inputs which are not same size. - // step_in[0..4] => lastCounter - // step_in[4..20] => lastTag - // step_in[20..21] => foldedBlocks - // step_in[22] => plainText matching // TODO: ccan just check, don't need to carry over - // step_in[23] => cipherText matching // TODO: ccan just check, don't need to carry over - signal input step_in[inputBytes]; - - // For now, attempt to support variable fold size. Potential fix at 16 in the future. - component aes = AESGCMFOLDABLE(bytesPerFold, totalBytes\16); - aes.key <== key; - aes.iv <== iv; - aes.aad <== aad; - aes.plainText <== plainText; - - // Fold inputs - var inputIndex = bytesPerFold - 4; - for(var i = 0; i < 4; i++) { - aes.lastCounter[i] <== step_in[inputIndex]; - inputIndex+=1; - } - - for(var i = 0; i < 16; i++) { - aes.lastTag[i] <== step_in[inputIndex]; - inputIndex+=1; - } - // TODO: range check, assertions, stuff. - inputIndex+=15; - aes.foldedBlocks <== step_in[inputIndex]; - - // Fold Outputs - signal output step_out[inputBytes]; - var outputIndex = 0; - for (var i = 0 ; i < bytesPerFold - 4 ; i++) { - step_out[outputIndex] <== 0; - outputIndex += 1; - } - for(var i = 0; i < 4; i++) { - step_out[outputIndex] <== aes.counter[i]; - outputIndex += 1; - } - for(var i = 0; i < 16; i++) { - step_out[outputIndex] <== aes.authTag[i]; - outputIndex += 1; - } - for (var i = 0 ; i < 15 ; i++) { - // TODO: check correctness - step_out[outputIndex] <== 0; - outputIndex += 1; - } - step_out[outputIndex] <== step_in[47] + bytesPerFold \ 16; - - // // Constrain - // signal plainTextCheckIndex <== 48 + ((step_in[15] - 1) * bytesPerFold); - // signal plainTextBlock[bytesPerFold] <== SelectSubArray(inputBytes, bytesPerFold)(step_in, plainTextCheckIndex, bytesPerFold); - // signal isPlainTextMatch <== IsEqualArray(bytesPerFold)([plainText, plainTextBlock]); - step_out[48] <== step_in[48]; - // step_out[48] <== step_in[48] * isPlainTextMatch; - - // // Constrain ciphertext matches - // signal cipherTextCheckIndex <== (step_in[15] - 1) * bytesPerFold; - // signal cipherTextBlock[bytesPerFold] <== SelectSubArray(totalBytes, bytesPerFold)(cipherText, cipherTextCheckIndex, bytesPerFold); - // signal isCipherTextMatch <== IsEqualArray(bytesPerFold)([aes.cipherText, cipherTextBlock]); - step_out[49] <== step_in[49]; - // step_out[49] <== step_in[49] * isCipherTextMatch; - - for(var i = 50 ; i < inputBytes ; i++) { - step_out[i] <== step_in[i]; - } -} - -component main { public [step_in] } = AESGCMFOLD(16, 320, 4160); \ No newline at end of file diff --git a/circuits/test/aes-gcm/aes-gcm.test.ts b/circuits/test/aes-gcm/aes-gcm.test.ts new file mode 100644 index 0000000..ac07df5 --- /dev/null +++ b/circuits/test/aes-gcm/aes-gcm.test.ts @@ -0,0 +1,67 @@ +import { assert } from "chai"; +import { WitnessTester } from "circomkit"; +import { circomkit, hexBytesToBigInt, hexToBytes } from "../common"; + +describe("aes-gcm", () => { + let circuit: WitnessTester<["key", "iv", "plainText", "aad"], ["cipherText", "tag"]>; + + before(async () => { + circuit = await circomkit.WitnessTester(`aes-gcm`, { + file: "aes-gcm/aes-gcm", + template: "AESGCM", + params: [16], + }); + }); + + it("should have correct output", async () => { + let key = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let plainText = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let iv = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let aad = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let expected_output = [0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92, 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2, 0xfe, 0x78]; + + const witness = await circuit.compute({ key: key, iv: iv, plainText: plainText, aad: aad }, ["cipherText", "authTag"]) + + assert.deepEqual(witness.cipherText, hexBytesToBigInt(expected_output)) + }); + + it("should work for self generated test case", async () => { + let circuit_one_block: WitnessTester<["key", "iv", "plainText", "aad"], ["cipherText", "tag"]>; + circuit_one_block = await circomkit.WitnessTester(`aes-gcm`, { + file: "aes-gcm/aes-gcm", + template: "AESGCM", + params: [16], + }); + + const key = hexToBytes('31313131313131313131313131313131'); + const iv = hexToBytes('313131313131313131313131'); + const msg = hexToBytes('7465737468656c6c6f30303030303030'); + const aad = hexToBytes('00000000000000000000000000000000') + const ct = hexToBytes('2929d2bb1ae94804402b8e776e0d3356'); + const auth_tag = hexToBytes('0cab39e1a491b092185965f7b554aea0'); + + const witness = await circuit_one_block.compute({ key: key, iv: iv, plainText: msg, aad: aad }, ["cipherText", "authTag"]) + + assert.deepEqual(witness.cipherText, hexBytesToBigInt(ct)) + }); + + it("should work for multiple blocks", async () => { + let circuit_one_block: WitnessTester<["key", "iv", "plainText", "aad"], ["cipherText", "tag"]>; + circuit_one_block = await circomkit.WitnessTester(`aes-gcm`, { + file: "aes-gcm/aes-gcm", + template: "AESGCM", + params: [32], + }); + + const key = hexToBytes('31313131313131313131313131313131'); + const iv = hexToBytes('313131313131313131313131'); + const msg = hexToBytes('7465737468656c6c6f303030303030307465737468656c6c6f30303030303030'); + const aad = hexToBytes('00000000000000000000000000000000') + const ct = hexToBytes('2929d2bb1ae94804402b8e776e0d335626756530713e4c065af1d3c4f56e0204'); + const auth_tag = hexToBytes('438542d7f387568c84d23df60b223ecb'); + + const witness = await circuit_one_block.compute({ key: key, iv: iv, plainText: msg, aad: aad }, ["cipherText", "authTag"]) + + assert.deepEqual(witness.cipherText, hexBytesToBigInt(ct)) + }); +}); \ No newline at end of file diff --git a/circuits/test/aes-gcm/aes/cipher.test.ts b/circuits/test/aes-gcm/aes/cipher.test.ts new file mode 100644 index 0000000..f54d90d --- /dev/null +++ b/circuits/test/aes-gcm/aes/cipher.test.ts @@ -0,0 +1,193 @@ +import { WitnessTester } from "circomkit"; +import { circomkit } from "../../common"; + +describe("Cipher", () => { + let circuit: WitnessTester<["block", "key"], ["cipher"]>; + it("should perform Cipher#1", async () => { + circuit = await circomkit.WitnessTester(`Cipher`, { + file: "aes-gcm/aes/cipher", + template: "Cipher", + }); + + await circuit.expectPass( + { + block: [ + [0x32, 0x88, 0x31, 0xe0], + [0x43, 0x5a, 0x31, 0x37], + [0xf6, 0x30, 0x98, 0x07], + [0xa8, 0x8d, 0xa2, 0x34], + ], + key: [ + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, + 0x88, 0x09, 0xcf, 0x4f, 0x3c, + ], + }, + { + cipher: [ + [0x39, 0x02, 0xdc, 0x19], + [0x25, 0xdc, 0x11, 0x6a], + [0x84, 0x09, 0x85, 0x0b], + [0x1d, 0xfb, 0x97, 0x32], + ], + } + ); + }); + + // in : f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff + // out : ec8cdf7398607cb0f2d21675ea9ea1e4 + // key : 2b7e151628aed2a6abf7158809cf4f3c + it("should perform Cipher#2", async () => { + circuit = await circomkit.WitnessTester(`Cipher`, { + file: "aes-gcm/aes/cipher", + template: "Cipher", + }); + + await circuit.expectPass( + { + block: [ + [0xf0, 0xf4, 0xf8, 0xfc], + [0xf1, 0xf5, 0xf9, 0xfd], + [0xf2, 0xf6, 0xfa, 0xfe], + [0xf3, 0xf7, 0xfb, 0xff], + ], + key: [ + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, + 0x88, 0x09, 0xcf, 0x4f, 0x3c, + ], + }, + { + cipher: [ + [0xec, 0x98, 0xf2, 0xea], + [0x8c, 0x60, 0xd2, 0x9e], + [0xdf, 0x7c, 0x16, 0xa1], + [0x73, 0xb0, 0x75, 0xe4], + ], + } + ); + }); + describe("AddRoundKey", () => { + let circuit: WitnessTester<["state", "roundKey"], ["newState"]>; + it("should perform AddRoundKey", async () => { + circuit = await circomkit.WitnessTester(`AddRoundKey`, { + file: "aes-gcm/aes/cipher", + template: "AddRoundKey", + }); + + // 0x57 . 2 = 0xae + await circuit.expectPass( + { + state: [ + [4, 224, 72, 40], + [102, 203, 248, 6], + [129, 25, 211, 38], + [229, 154, 122, 76], + ], + roundKey: [ + [160, 250, 254, 23], + [136, 84, 44, 177], + [35, 163, 57, 57], + [42, 108, 118, 5], + ], + }, + { + newState: [ + [164, 104, 107, 2], + [156, 159, 91, 106], + [127, 53, 234, 80], + [242, 43, 67, 73], + ], + } + ); + }); + }); + + describe("NextRound", () => { + async function generatePassCase( + round: number, + key: number[][], + expectedKey: number[][] + ) { + circuit = await circomkit.WitnessTester(`NextRound_${4}_${4}`, { + file: "aes-gcm/aes/cipher", + template: "NextRound", + }); + } + + it("should compute correctly", async () => { + const key = [ + [0x2b, 0x7e, 0x15, 0x16], + [0x28, 0xae, 0xd2, 0xa6], + [0xab, 0xf7, 0x15, 0x88], + [0x09, 0xcf, 0x4f, 0x3c], + ]; + + const expectedNextKey = [ + [0xa0, 0xfa, 0xfe, 0x17], + [0x88, 0x54, 0x2c, 0xb1], + [0x23, 0xa3, 0x39, 0x39], + [0x2a, 0x6c, 0x76, 0x05], + ]; + + await generatePassCase(1, key, expectedNextKey); + }); + }); + + describe("SubBlock", () => { + let circuit: WitnessTester<["state"], ["newState"]>; + it("should perform SubBlock", async () => { + circuit = await circomkit.WitnessTester(`SubBlock`, { + file: "aes-gcm/aes/cipher", + template: "SubBlock", + }); + // 0x57 . 2 = 0xae + await circuit.expectPass( + { + state: [ + [25, 160, 154, 233], + [61, 244, 198, 248], + [227, 226, 141, 72], + [190, 43, 42, 8], + ], + }, + { + newState: [ + [212, 224, 184, 30], + [39, 191, 180, 65], + [17, 152, 93, 82], + [174, 241, 229, 48], + ], + } + ); + }); + }); + + describe("ShiftRows", () => { + let circuit: WitnessTester<["state"], ["newState"]>; + it("should perform ShiftRows", async () => { + circuit = await circomkit.WitnessTester(`ShiftRows`, { + file: "aes-gcm/aes/cipher", + template: "ShiftRows", + params: [], + }); + // 0x57 . 2 = 0xae + await circuit.expectPass( + { + state: [ + [212, 224, 184, 30], + [39, 191, 180, 65], + [17, 152, 93, 82], + [174, 241, 229, 48], + ], + }, + { + newState: [ + [212, 224, 184, 30], + [191, 180, 65, 39], + [93, 82, 17, 152], + [48, 174, 241, 229], + ], + } + ); + }); + }); +}); diff --git a/circuits/test/aes-gcm/aes/key_expansion.test.ts b/circuits/test/aes-gcm/aes/key_expansion.test.ts new file mode 100644 index 0000000..906fc44 --- /dev/null +++ b/circuits/test/aes-gcm/aes/key_expansion.test.ts @@ -0,0 +1,106 @@ +import { WitnessTester } from "circomkit"; +import { circomkit } from "../../common"; + +describe("KeyExpansion", () => { + it("should compute correctly for aes128", async () => { + const circuit: WitnessTester<["key"], ["keyExpanded"]> = await circomkit.WitnessTester(`SubBytes`, { + file: "aes-gcm/aes/key_expansion", + template: "KeyExpansion", + params: [], + }); + const key = [0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c]; + const keyExpanded = [ + [0x2b, 0x7e, 0x15, 0x16], + [0x28, 0xae, 0xd2, 0xa6], + [0xab, 0xf7, 0x15, 0x88], + [0x09, 0xcf, 0x4f, 0x3c], + [0xa0, 0xfa, 0xfe, 0x17], + [0x88, 0x54, 0x2c, 0xb1], + [0x23, 0xa3, 0x39, 0x39], + [0x2a, 0x6c, 0x76, 0x05], + [0xf2, 0xc2, 0x95, 0xf2], + [0x7a, 0x96, 0xb9, 0x43], + [0x59, 0x35, 0x80, 0x7a], + [0x73, 0x59, 0xf6, 0x7f], + [0x3d, 0x80, 0x47, 0x7d], + [0x47, 0x16, 0xfe, 0x3e], + [0x1e, 0x23, 0x7e, 0x44], + [0x6d, 0x7a, 0x88, 0x3b], + [0xef, 0x44, 0xa5, 0x41], + [0xa8, 0x52, 0x5b, 0x7f], + [0xb6, 0x71, 0x25, 0x3b], + [0xdb, 0x0b, 0xad, 0x00], + [0xd4, 0xd1, 0xc6, 0xf8], + [0x7c, 0x83, 0x9d, 0x87], + [0xca, 0xf2, 0xb8, 0xbc], + [0x11, 0xf9, 0x15, 0xbc], + [0x6d, 0x88, 0xa3, 0x7a], + [0x11, 0x0b, 0x3e, 0xfd], + [0xdb, 0xf9, 0x86, 0x41], + [0xca, 0x00, 0x93, 0xfd], + [0x4e, 0x54, 0xf7, 0x0e], + [0x5f, 0x5f, 0xc9, 0xf3], + [0x84, 0xa6, 0x4f, 0xb2], + [0x4e, 0xa6, 0xdc, 0x4f], + [0xea, 0xd2, 0x73, 0x21], + [0xb5, 0x8d, 0xba, 0xd2], + [0x31, 0x2b, 0xf5, 0x60], + [0x7f, 0x8d, 0x29, 0x2f], + [0xac, 0x77, 0x66, 0xf3], + [0x19, 0xfa, 0xdc, 0x21], + [0x28, 0xd1, 0x29, 0x41], + [0x57, 0x5c, 0x00, 0x6e], + [0xd0, 0x14, 0xf9, 0xa8], + [0xc9, 0xee, 0x25, 0x89], + [0xe1, 0x3f, 0x0c, 0xc8], + [0xb6, 0x63, 0x0c, 0xa6], + ]; + await circuit.expectPass({ key }, { keyExpanded }); + }); + + describe("Rotate", () => { + let circuit: WitnessTester<["bytes"], ["rotated"]>; + + it("should rotate correctly", async () => { + circuit = await circomkit.WitnessTester(`Rotate`, { + file: "aes-gcm/aes/key_expansion", + template: "Rotate", + params: [1, 4], + }); + await circuit.expectPass({ bytes: [0x01, 0x12, 0x02, 0x30] }, { rotated: [0x12, 0x02, 0x30, 0x01] }); + }); + }); + + describe("SubstituteWord", () => { + let circuit: WitnessTester<["bytes"], ["substituted"]>; + before(async () => { + circuit = await circomkit.WitnessTester(`SubstituteWord`, { + file: "aes-gcm/aes/key_expansion", + template: "SubstituteWord", + }); + }); + + it("should substitute correctly", async () => { + await circuit.expectPass({ bytes: [0x00, 0x10, 0x20, 0x30] }, { substituted: [0x63, 0xca, 0xb7, 0x04] }); + }); + }); + + describe("RCon", () => { + let circuit: WitnessTester<[], ["out"]>; + + async function generatePassCase(round: number, out: number[]) { + circuit = await circomkit.WitnessTester(`RCon`, { + file: "aes-gcm/aes/key_expansion", + template: "RCon", + params: [round] + }); + await circuit.expectPass({}, { out: out }); + } + + it("should compute round constant correctly", async () => { + await generatePassCase(1, [0x01, 0x00, 0x00, 0x00]); + await generatePassCase(2, [0x02, 0x00, 0x00, 0x00]); + await generatePassCase(10, [0x36, 0x00, 0x00, 0x00]); + }); + }); +}); diff --git a/circuits/test/aes-gcm/aes/sbox.test.ts b/circuits/test/aes-gcm/aes/sbox.test.ts new file mode 100644 index 0000000..13b344a --- /dev/null +++ b/circuits/test/aes-gcm/aes/sbox.test.ts @@ -0,0 +1,125 @@ +import { WitnessTester } from "circomkit"; +import { circomkit } from "../../common"; + +describe("SBox", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + describe("SubBox", () => { + before(async () => { + circuit = await circomkit.WitnessTester(`SubBytes`, { + file: "aes-gcm/aes/sbox", + template: "SBox128", + }); + }); + + it("should compute correctly", async () => { + await circuit.expectPass({ in: 0x53 }, { out: 0xed }); + await circuit.expectPass({ in: 0x00 }, { out: 0x63 }); + }); + }); +}); + +describe("Field", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester(`FieldInv`, { + file: "aes-gcm/aes/sbox", + template: "FieldInv", + }); + }); + + let inverses = [0x00, 0x01, 0x8d, 0xf6, 0xcb, 0x52, 0x7b, 0xd1, 0xe8, 0x4f, 0x29, 0xc0, 0xb0, 0xe1, 0xe5, 0xc7, + 0x74, 0xb4, 0xaa, 0x4b, 0x99, 0x2b, 0x60, 0x5f, 0x58, 0x3f, 0xfd, 0xcc, 0xff, 0x40, 0xee, 0xb2, + 0x3a, 0x6e, 0x5a, 0xf1, 0x55, 0x4d, 0xa8, 0xc9, 0xc1, 0x0a, 0x98, 0x15, 0x30, 0x44, 0xa2, 0xc2, + 0x2c, 0x45, 0x92, 0x6c, 0xf3, 0x39, 0x66, 0x42, 0xf2, 0x35, 0x20, 0x6f, 0x77, 0xbb, 0x59, 0x19, + 0x1d, 0xfe, 0x37, 0x67, 0x2d, 0x31, 0xf5, 0x69, 0xa7, 0x64, 0xab, 0x13, 0x54, 0x25, 0xe9, 0x09, + 0xed, 0x5c, 0x05, 0xca, 0x4c, 0x24, 0x87, 0xbf, 0x18, 0x3e, 0x22, 0xf0, 0x51, 0xec, 0x61, 0x17, + 0x16, 0x5e, 0xaf, 0xd3, 0x49, 0xa6, 0x36, 0x43, 0xf4, 0x47, 0x91, 0xdf, 0x33, 0x93, 0x21, 0x3b, + 0x79, 0xb7, 0x97, 0x85, 0x10, 0xb5, 0xba, 0x3c, 0xb6, 0x70, 0xd0, 0x06, 0xa1, 0xfa, 0x81, 0x82, + 0x83, 0x7e, 0x7f, 0x80, 0x96, 0x73, 0xbe, 0x56, 0x9b, 0x9e, 0x95, 0xd9, 0xf7, 0x02, 0xb9, 0xa4, + 0xde, 0x6a, 0x32, 0x6d, 0xd8, 0x8a, 0x84, 0x72, 0x2a, 0x14, 0x9f, 0x88, 0xf9, 0xdc, 0x89, 0x9a, + 0xfb, 0x7c, 0x2e, 0xc3, 0x8f, 0xb8, 0x65, 0x48, 0x26, 0xc8, 0x12, 0x4a, 0xce, 0xe7, 0xd2, 0x62, + 0x0c, 0xe0, 0x1f, 0xef, 0x11, 0x75, 0x78, 0x71, 0xa5, 0x8e, 0x76, 0x3d, 0xbd, 0xbc, 0x86, 0x57, + 0x0b, 0x28, 0x2f, 0xa3, 0xda, 0xd4, 0xe4, 0x0f, 0xa9, 0x27, 0x53, 0x04, 0x1b, 0xfc, 0xac, 0xe6, + 0x7a, 0x07, 0xae, 0x63, 0xc5, 0xdb, 0xe2, 0xea, 0x94, 0x8b, 0xc4, 0xd5, 0x9d, 0xf8, 0x90, 0x6b, + 0xb1, 0x0d, 0xd6, 0xeb, 0xc6, 0x0e, 0xcf, 0xad, 0x08, 0x4e, 0xd7, 0xe3, 0x5d, 0x50, 0x1e, 0xb3, + 0x5b, 0x23, 0x38, 0x34, 0x68, 0x46, 0x03, 0x8c, 0xdd, 0x9c, 0x7d, 0xa0, 0xcd, 0x1a, 0x41, 0x1c]; + + it("should compute correctly", async () => { + for (let i = 0; i < 256; i++) { + await circuit.expectPass({ in: i }, { out: inverses[i] }); + } + }); + + describe("XTimes1 with XTimes", () => { + let circuit: WitnessTester<["in"], ["out"]>; + it("should perform 1 times with XTERMS", async () => { + circuit = await circomkit.WitnessTester(`XTimes`, { + file: "aes-gcm/aes/sbox", + template: "XTimes", + params: [0x1], + }); + // 0x57 . 2 = 0xae + await circuit.expectPass({ in: [1, 1, 1, 0, 1, 0, 1, 0] }, { out: [1, 1, 1, 0, 1, 0, 1, 0] }); + // 0x54 . 2 = 0xa8 + await circuit.expectPass({ in: [0, 0, 1, 0, 1, 0, 1, 0] }, { out: [0, 0, 1, 0, 1, 0, 1, 0] }); + // 0xae . 2 = 0x47 + await circuit.expectPass({ in: [0, 1, 1, 1, 0, 1, 0, 1] }, { out: [0, 1, 1, 1, 0, 1, 0, 1] }); + // 0x47 . 2 = 0x8e + await circuit.expectPass({ in: [1, 1, 1, 0, 1, 0, 1, 0] }, { out: [1, 1, 1, 0, 1, 0, 1, 0] }); + }); + }); + + describe("XTimes2", () => { + let circuit: WitnessTester<["in"], ["out"]>; + it("should perform 2 times", async () => { + circuit = await circomkit.WitnessTester(`XTimes2`, { + file: "aes-gcm/aes/sbox", + template: "XTimes2", + }); + + // 0x57 . 2 = 0xae + await circuit.expectPass({ in: [1, 1, 1, 0, 1, 0, 1, 0] }, { out: [0, 1, 1, 1, 0, 1, 0, 1] }); + // 0x54 . 2 = 0xa8 + await circuit.expectPass({ in: [0, 0, 1, 0, 1, 0, 1, 0] }, { out: [0, 0, 0, 1, 0, 1, 0, 1] }); + // 0xae . 2 = 0x47 + await circuit.expectPass({ in: [0, 1, 1, 1, 0, 1, 0, 1] }, { out: [1, 1, 1, 0, 0, 0, 1, 0] }); + // 0x47 . 2 = 0x8e + await circuit.expectPass({ in: [1, 1, 1, 0, 0, 0, 1, 0] }, { out: [0, 1, 1, 1, 0, 0, 0, 1] }); + }); + }); + + describe("XTimes", () => { + let circuit: WitnessTester<["in"], ["out"]>; + it("should perform xtimes", async () => { + circuit = await circomkit.WitnessTester(`XTimes`, { + file: "aes-gcm/aes/sbox", + template: "XTimes", + params: [0x13], + }); + // 0x57 . 0x13 = 0xfe + await circuit.expectPass({ in: [1, 1, 1, 0, 1, 0, 1, 0] }, { out: [0, 1, 1, 1, 1, 1, 1, 1] }); + }); + }); + + describe("XTimes2 with XTimes", () => { + let circuit: WitnessTester<["in"], ["out"]>; + it("should perform 2 times with XTERMS", async () => { + circuit = await circomkit.WitnessTester(`XTimes`, { + file: "aes-gcm/aes/sbox", + template: "XTimes", + params: [0x2], + }); + // 0x57 . 2 = 0xae + await circuit.expectPass({ in: [1, 1, 1, 0, 1, 0, 1, 0] }, { out: [0, 1, 1, 1, 0, 1, 0, 1] }); + // 0x54 . 2 = 0xa8 + await circuit.expectPass({ in: [0, 0, 1, 0, 1, 0, 1, 0] }, { out: [0, 0, 0, 1, 0, 1, 0, 1] }); + // 0xae . 2 = 0x47 + await circuit.expectPass({ in: [0, 1, 1, 1, 0, 1, 0, 1] }, { out: [1, 1, 1, 0, 0, 0, 1, 0] }); + // 0x47 . 2 = 0x8e + await circuit.expectPass({ in: [1, 1, 1, 0, 0, 0, 1, 0] }, { out: [0, 1, 1, 1, 0, 0, 0, 1] }); + }); + }); +}); + diff --git a/circuits/test/aes-gcm/aes/transformations.test.ts b/circuits/test/aes-gcm/aes/transformations.test.ts new file mode 100644 index 0000000..79f11f8 --- /dev/null +++ b/circuits/test/aes-gcm/aes/transformations.test.ts @@ -0,0 +1,74 @@ +import { WitnessTester } from "circomkit"; +import { circomkit } from "../../common"; + +describe("MixColumns", () => { + it("s0 should compute correctly", async () => { + let circuit: WitnessTester<["in"], ["out"]>; + circuit = await circomkit.WitnessTester(`s0`, { + file: "aes-gcm/aes/mix_columns", + template: "S0", + params: [], + }); + + await circuit.expectPass({ in: [0xd4, 0xbf, 0x5d, 0x30] }, { out: 0x04 }); + }); + + it("s1 should compute correctly", async () => { + let circuit: WitnessTester<["in"], ["out"]>; + circuit = await circomkit.WitnessTester(`s1`, { + file: "aes-gcm/aes/mix_columns", + template: "S1", + params: [], + }); + + await circuit.expectPass({ in: [0xd4, 0xbf, 0x5d, 0x30] }, { out: 0x66 }); + }); + + it("s2 should compute correctly", async () => { + let circuit: WitnessTester<["in"], ["out"]>; + circuit = await circomkit.WitnessTester(`s2`, { + file: "aes-gcm/aes/mix_columns", + template: "S2", + params: [], + }); + + await circuit.expectPass({ in: [0xd4, 0xbf, 0x5d, 0x30] }, { out: 0x81 }); + }); + + it("s3 should compute correctly", async () => { + let circuit: WitnessTester<["in"], ["out"]>; + circuit = await circomkit.WitnessTester(`s3`, { + file: "aes-gcm/aes/mix_columns", + template: "S3", + params: [], + }); + + await circuit.expectPass({ in: [0xd4, 0xbf, 0x5d, 0x30] }, { out: 0xe5 }); + }); + + it("s4 should compute correctly", async () => { + let circuit: WitnessTester<["state"], ["out"]>; + circuit = await circomkit.WitnessTester(`MixColumns`, { + file: "aes-gcm/aes/mix_columns", + template: "MixColumns", + params: [], + }); + const state = [ + [0xd4, 0xe0, 0xb8, 0x1e], + [0xbf, 0xb4, 0x41, 0x27], + [0x5d, 0x52, 0x11, 0x98], + [0x30, 0xae, 0xf1, 0xe5], + ]; + + const out = [ + [0x04, 0xe0, 0x48, 0x28], + [0x66, 0xcb, 0xf8, 0x06], + [0x81, 0x19, 0xd3, 0x26], + [0xe5, 0x9a, 0x7a, 0x4c], + ]; + + await circuit.expectPass({ state }, { out }); + }); +}); + + diff --git a/circuits/test/aes-gcm/gctr.test.ts b/circuits/test/aes-gcm/gctr.test.ts new file mode 100644 index 0000000..4af80f7 --- /dev/null +++ b/circuits/test/aes-gcm/gctr.test.ts @@ -0,0 +1,31 @@ +import { WitnessTester } from "circomkit"; +import { circomkit } from "../common"; +import { assert } from "chai"; + +describe("GCTR", () => { + let circuit: WitnessTester<["plainText", "initialCounterBlock", "key"], ["cipherText"]>; + it("should encrypt the plaintext", async () => { + circuit = await circomkit.WitnessTester(`GCTR`, { + file: "aes-gcm/gctr", + template: "GCTR", + params: [16], + }); + + // GOOD TEST CASE. + const key = [0xca, 0xaa, 0x3f, 0x6f, 0xd3, 0x18, 0x22, 0xed, 0x2d, 0x21, 0x25, 0xf2, 0x25, 0xb0, 0x16, 0x9f]; + const column_wise_icb = [0x7f, 0x48, 0x12, 0x00, 0x6d, 0x3e, 0xfa, 0x00, 0x90, 0x8c, 0x55, 0x00, 0x41, 0x14, 0x2a, 0x02]; + const pt = [0x84, 0xc9, 0x07, 0xb1, 0x1a, 0xe3, 0xb7, 0x9f, 0xc4, 0x45, 0x1d, 0x1b, 0xf1, 0x7f, 0x4a, 0x99]; + const ct = [0xfd, 0xb4, 0xaa, 0xfa, 0x35, 0x19, 0xd3, 0xc0, 0x55, 0xbe, 0x8b, 0x34, 0x77, 0x64, 0xea, 0x33]; + + const witness = await circuit.compute({ key: key, initialCounterBlock: column_wise_icb, plainText: pt }, ["cipherText"]) + + assert.deepEqual(witness.cipherText, hexBytesToBigInt(ct)) + }); +}); + +function hexBytesToBigInt(hexBytes: number[]): any[] { + return hexBytes.map(byte => { + let n = BigInt(byte); + return n; + }); + } \ No newline at end of file diff --git a/circuits/test/aes-gcm/ghash/ghash.test.ts b/circuits/test/aes-gcm/ghash/ghash.test.ts new file mode 100644 index 0000000..bddebca --- /dev/null +++ b/circuits/test/aes-gcm/ghash/ghash.test.ts @@ -0,0 +1,30 @@ +import { WitnessTester } from "circomkit"; +import { circomkit } from "../../common"; + + + +describe("GHASH", () => { + let circuit: WitnessTester<["HashKey", "msg"], ["tag"]>; + + before(async () => { + circuit = await circomkit.WitnessTester(`ghash`, { + file: "aes-gcm/ghash/ghash", + template: "GHASH", + params: [2], + }); + }); + + it("test ghash", async () => { + // 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]; + await circuit.expectPass({ HashKey: H, msg: M }, { tag: EXPECT }); + }); +}); + + + + diff --git a/circuits/test/aes-gcm/ghash/gmul.test.ts b/circuits/test/aes-gcm/ghash/gmul.test.ts new file mode 100644 index 0000000..b0b65ba --- /dev/null +++ b/circuits/test/aes-gcm/ghash/gmul.test.ts @@ -0,0 +1,117 @@ +import { WitnessTester } from "circomkit"; +import { circomkit } from "../../common"; + + +describe("GhashMul", () => { + let circuit: WitnessTester<["X", "Y"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("ghash_gmul", { + file: "aes-gcm/ghash/gmul", + template: "GhashMul", + }); + }); + + it("Should Compute GhashMul Correctly", async () => { + + let X = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let Y = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + const expected = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + await circuit.expectPass({ X: X, Y: Y }, { out: expected }); + }); + + it("Should Compute NistGMulByte of LSB=1 Correctly", async () => { + + let X = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + let Y = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + + const expected = [0xe6, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03]; + await circuit.expectPass({ X: X, Y: Y }, { out: expected }); + }); + it("Should Compute NistGMulByte of LSB=1 Correctly", async () => { + + // x = "aae06992acbf52a3e8f4a96ec9300bd7" + // y = "98e7247c07f0fe411c267e4384b0f600" + // expected = "90e87315fb7d4e1b4092ec0cbfda5d7d" + let X = [0xaa, 0xe0, 0x69, 0x92, 0xac, 0xbf, 0x52, 0xa3, 0xe8, 0xf4, 0xa9, 0x6e, 0xc9, 0x30, 0x0b, 0xd7]; + let Y = [0x98, 0xe7, 0x24, 0x7c, 0x07, 0xf0, 0xfe, 0x41, 0x1c, 0x26, 0x7e, 0x43, 0x84, 0xb0, 0xf6, 0x00]; + + const expected = [0x90, 0xe8, 0x73, 0x15, 0xfb, 0x7d, 0x4e, 0x1b, 0x40, 0x92, 0xec, 0x0c, 0xbf, 0xda, 0x5d, 0x7d]; + await circuit.expectPass({ X: X, Y: Y }, { out: expected }); + }); + + describe("BlockRightShift", () => { + let circuit: WitnessTester<["in"], ["out", "msb"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("BlockRightShift", { + file: "aes-gcm/ghash/gmul", + template: "BlockRightShift", + params: [16] + }); + }); + + it("Should Compute BlockRightShift Correctly", async () => { + let input = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + const expected = [0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + await circuit.expectPass({ in: input }, { out: expected, msb: 0 }); + }); + it("Should Compute BlockRightShift Correctly", async () => { + let input = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + const expected = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + await circuit.expectPass({ in: input }, { out: expected, msb: 1 }); + }); + }); + + describe("Mulx", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("Mulx", { + file: "aes-gcm/ghash/gmul", + template: "Mulx", + params: [16] + }); + }); + // msb is 1 so we xor the first byte with 0xE1 + it("Should Compute Mulx Correctly", async () => { + let input = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; + const expected = [0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + await circuit.expectPass({ in: input }, { out: expected }); + }); + }); + + describe("Z_UPDATE", () => { + let circuit: WitnessTester<["Z", "V", "bit_val"], ["Z_new"]>; + + before(async () => { + circuit = await circomkit.WitnessTester("XORBLOCK", { + file: "aes-gcm/ghash/gmul", + template: "Z_UPDATE", + params: [16] + }); + }); + // 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 }); + }); + }); + + +}); + + + diff --git a/circuits/test/aes-gcm/nivc/aes-gctr-nivc.test.ts b/circuits/test/aes-gcm/nivc/aes-gctr-nivc.test.ts new file mode 100644 index 0000000..4b63918 --- /dev/null +++ b/circuits/test/aes-gcm/nivc/aes-gctr-nivc.test.ts @@ -0,0 +1,100 @@ +import { assert } from "chai"; +import { WitnessTester } from "circomkit"; +import { circomkit } from "../../common"; + +describe("aes-gctr-nivc", () => { + let circuit_one_block: WitnessTester<["key", "iv", "plainText", "aad", "step_in"], ["step_out"]>; + + it("all correct for self generated single zero pt block case", async () => { + circuit_one_block = await circomkit.WitnessTester("aes-gcm-fold", { + file: "aes-gcm/nivc/aes-gctr-nivc", + template: "AESGCTRFOLD", + params: [16], // input len is 16 bytes + }); + + let key = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let plainText = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let iv = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let aad = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let ct = [0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92, 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2, 0xfe, 0x78]; + + const counter = [0x00, 0x00, 0x00, 0x01]; + const step_in = new Array(32).fill(0x00).concat(counter); + + let expected = plainText.concat(ct).concat([0x00, 0x00, 0x00, 0x02]); + const witness = await circuit_one_block.compute({ key: key, iv: iv, plainText: plainText, aad: aad, step_in: step_in }, ["step_out"]) + assert.deepEqual(witness.step_out, expected.map(BigInt)); + }); + + it("all correct for self generated single non zero pt block", async () => { + circuit_one_block = await circomkit.WitnessTester("aes-gcm-fold", { + file: "aes-gcm/nivc/aes-gctr-nivc", + template: "AESGCTRFOLD", + params: [16], // input len is 16 bytes + }); + + let key = [0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31]; + let plainText = [0x74, 0x65, 0x73, 0x74, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30]; + let iv = [0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31]; + let aad = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let ct = [0x29, 0x29, 0xd2, 0xbb, 0x1a, 0xe9, 0x48, 0x04, 0x40, 0x2b, 0x8e, 0x77, 0x6e, 0x0d, 0x33, 0x56]; + + const counter = [0x00, 0x00, 0x00, 0x01]; + const step_in = new Array(32).fill(0x00).concat(counter); + + let expected = plainText.concat(ct).concat([0x00, 0x00, 0x00, 0x02]); + + const witness = await circuit_one_block.compute({ key: key, iv: iv, plainText: plainText, aad: aad, step_in: step_in }, ["step_out"]) + assert.deepEqual(witness.step_out, expected.map(BigInt)); + }); + + + + it("all correct for self generated two block case first fold", async () => { + circuit_one_block = await circomkit.WitnessTester("aes-gcm-fold", { + file: "aes-gcm/nivc/aes-gctr-nivc", + template: "AESGCTRFOLD", + params: [32], // input len is 32 bytes + }); + + let zero_block = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let key = [0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31]; + let plainText1 = [0x74, 0x65, 0x73, 0x74, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30]; + let plainText2 = [0x74, 0x65, 0x73, 0x74, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30]; + let iv = [0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31]; + let aad = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let ct_part1 = [0x29, 0x29, 0xd2, 0xbb, 0x1a, 0xe9, 0x48, 0x04, 0x40, 0x2b, 0x8e, 0x77, 0x6e, 0x0d, 0x33, 0x56]; + let ct_part2 = [0x26, 0x75, 0x65, 0x30, 0x71, 0x3e, 0x4c, 0x06, 0x5a, 0xf1, 0xd3, 0xc4, 0xf5, 0x6e, 0x02, 0x04]; + + const counter = [0x00, 0x00, 0x00, 0x01]; + const step_in = new Array(64).fill(0x00).concat(counter); + let expected = plainText1.concat(zero_block).concat(ct_part1).concat(zero_block).concat([0x00, 0x00, 0x00, 0x02]); + + const witness = await circuit_one_block.compute({ key: key, iv: iv, plainText: plainText1, aad: aad, step_in: step_in }, ["step_out"]) + assert.deepEqual(witness.step_out, expected.map(BigInt)); + }); + + it("all correct for self generated two block case second fold", async () => { + circuit_one_block = await circomkit.WitnessTester("aes-gcm-fold", { + file: "aes-gcm/nivc/aes-gctr-nivc", + template: "AESGCTRFOLD", + params: [32], // input len is 32 bytes + }); + + let zero_block = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let key = [0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31]; + let plainText1 = [0x74, 0x65, 0x73, 0x74, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30]; + let plainText2 = [0x74, 0x65, 0x73, 0x74, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30]; + let iv = [0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31]; + let aad = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let ct_part1 = [0x29, 0x29, 0xd2, 0xbb, 0x1a, 0xe9, 0x48, 0x04, 0x40, 0x2b, 0x8e, 0x77, 0x6e, 0x0d, 0x33, 0x56]; + let ct_part2 = [0x26, 0x75, 0x65, 0x30, 0x71, 0x3e, 0x4c, 0x06, 0x5a, 0xf1, 0xd3, 0xc4, 0xf5, 0x6e, 0x02, 0x04]; + + const counter = [0x00, 0x00, 0x00, 0x02]; + const step_in = plainText1.concat(zero_block).concat(ct_part1).concat(zero_block).concat(counter); + let expected = plainText1.concat(plainText2).concat(ct_part1).concat(ct_part2).concat([0x00, 0x00, 0x00, 0x03]); + + const witness = await circuit_one_block.compute({ key: key, iv: iv, plainText: plainText2, aad: aad, step_in: step_in }, ["step_out"]) + assert.deepEqual(witness.step_out, expected.map(BigInt)); + }); +}); \ No newline at end of file diff --git a/circuits/test/common/index.ts b/circuits/test/common/index.ts index 65f1bdb..b659a16 100644 --- a/circuits/test/common/index.ts +++ b/circuits/test/common/index.ts @@ -86,4 +86,15 @@ export function toByte(data: string): number[] { byteArray.push(data.charCodeAt(i)); } return byteArray +} + +export function hexToBytes(hex: any) { + return hex.match(/.{1,2}/g).map((byte: any) => parseInt(byte, 16)); + } + +export function hexBytesToBigInt(hexBytes: number[]): any[] { +return hexBytes.map(byte => { + let n = BigInt(byte); + return n; +}); } \ No newline at end of file diff --git a/circuits/test/utils/array.test.ts b/circuits/test/utils/array.test.ts index 02b2fce..b8538e6 100644 --- a/circuits/test/utils/array.test.ts +++ b/circuits/test/utils/array.test.ts @@ -152,4 +152,313 @@ describe("GenericArrayAdd", () => { ); }); -}); \ No newline at end of file +}); + +describe("array_builder", () => { + it("test array builder", async () => { + let circuit: WitnessTester<["array_to_write_to", "array_to_write_at_index", "index"], ["out"]>; + circuit = await circomkit.WitnessTester(`ArrayBuilder`, { + file: "utils/array", + template: "WriteToIndex", + params: [160, 16], + }); + + let array_to_write_to = new Array(160).fill(0x00); + let array_to_write_at_index = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10]; + let expected = array_to_write_at_index.concat(new Array(160 - array_to_write_at_index.length).fill(0x00)); + let index = 0; + + await circuit.expectPass( + { + array_to_write_to: array_to_write_to, + array_to_write_at_index: array_to_write_at_index, + index: index + }, + { + out: expected + } + ); + }); + it("test array builder", async () => { + let circuit: WitnessTester<["array_to_write_to", "array_to_write_at_index", "index"], ["out"]>; + circuit = await circomkit.WitnessTester(`ArrayBuilder`, { + file: "utils/array", + template: "WriteToIndex", + params: [160, 16], + }); + + let array_to_write_to = new Array(160).fill(0x00); + let array_to_write_at_index = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10]; + let expected = [0x00].concat(array_to_write_at_index).concat(new Array(160 - array_to_write_at_index.length - 1).fill(0x00)); + let index = 1; + + await circuit.expectPass( + { + array_to_write_to: array_to_write_to, + array_to_write_at_index: array_to_write_at_index, + index: index + }, + { + out: expected + } + ); + }); + it("test array builder", async () => { + let circuit: WitnessTester<["array_to_write_to", "array_to_write_at_index", "index"], ["out"]>; + circuit = await circomkit.WitnessTester(`ArrayBuilder`, { + file: "utils/array", + template: "WriteToIndex", + params: [160, 16], + }); + + let array_to_write_to = new Array(160).fill(0x00); + let array_to_write_at_index = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10]; + let expected = [0x00, 0x00].concat(array_to_write_at_index).concat(new Array(160 - array_to_write_at_index.length - 2).fill(0x00)); + let index = 2; + + await circuit.expectPass( + { + array_to_write_to: array_to_write_to, + array_to_write_at_index: array_to_write_at_index, + index: index + }, + { + out: expected + } + ); + }); + it("test array builder with index = n", async () => { + let circuit: WitnessTester<["array_to_write_to", "array_to_write_at_index", "index"], ["out"]>; + circuit = await circomkit.WitnessTester(`ArrayBuilder`, { + file: "utils/array", + template: "WriteToIndex", + params: [37, 16], + }); + + let array_to_write_to = new Array(37).fill(0x00); + let array_to_write_at_index = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10]; + let expected = new Array(16).fill(0x00).concat(array_to_write_at_index).concat(new Array(37 - array_to_write_at_index.length - 16).fill(0x00)); + let index = 16; + + await circuit.expectPass( + { + array_to_write_to: array_to_write_to, + array_to_write_at_index: array_to_write_at_index, + index: index + }, + { + out: expected + } + ); + }); + + it("test array builder with index > n", async () => { + let circuit: WitnessTester<["array_to_write_to", "array_to_write_at_index", "index"], ["out"]>; + circuit = await circomkit.WitnessTester(`ArrayBuilder`, { + file: "utils/array", + template: "WriteToIndex", + params: [37, 4], + }); + + let array_to_write_to = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x88, 0xDA, 0xCE, 0x60, 0xB6, 0xA3, 0x92, 0xF3, 0x28, 0xC2, 0xB9, 0x71, 0xB2, 0xFE, 0x78, + 0x00, 0x00, 0x00, 0x00, 0x00 + ]; + let array_to_write_at_index = [0x00, 0x00, 0x00, 0x01]; + let expected = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x88, 0xDA, 0xCE, 0x60, 0xB6, 0xA3, 0x92, 0xF3, 0x28, 0xC2, 0xB9, 0x71, 0xB2, 0xFE, 0x78, + 0x00, 0x00, 0x00, 0x01, 0x00 + ]; + let index = 32; + + await circuit.expectPass( + { + array_to_write_to: array_to_write_to, + array_to_write_at_index: array_to_write_at_index, + index: index + }, + { + out: expected + } + ); + }); +}); + +describe("ToBlocks", () => { + let circuit: WitnessTester<["stream"], ["blocks"]>; + it("should convert stream to block", async () => { + circuit = await circomkit.WitnessTester(`ToBlocks`, { + file: "utils/array", + template: "ToBlocks", + params: [16], + }); + await circuit.expectPass( + { + stream: [0x32, 0x88, 0x31, 0xe0, 0x43, 0x5a, 0x31, 0x37, 0xf6, 0x30, 0x98, 0x07, 0xa8, 0x8d, 0xa2, 0x34], + }, + { + blocks: [ + [ + [0x32, 0x43, 0xf6, 0xa8], + [0x88, 0x5a, 0x30, 0x8d], + [0x31, 0x31, 0x98, 0xa2], + [0xe0, 0x37, 0x07, 0x34], + ], + ], + } + ); + }); + it("should pad 1 in block", async () => { + circuit = await circomkit.WitnessTester(`ToBlocks`, { + file: "utils/array", + template: "ToBlocks", + params: [15], + }); + await circuit.expectPass( + { + stream: [0x32, 0x88, 0x31, 0xe0, 0x43, 0x5a, 0x31, 0x37, 0xf6, 0x30, 0x98, 0x07, 0xa8, 0x8d, 0xa2], + }, + { + blocks: [ + [ + [0x32, 0x43, 0xf6, 0xa8], + [0x88, 0x5a, 0x30, 0x8d], + [0x31, 0x31, 0x98, 0xa2], + [0xe0, 0x37, 0x07, 0x01], + ], + ], + } + ); + }); + it("should pad 0's in block", async () => { + circuit = await circomkit.WitnessTester(`ToBlocks`, { + file: "utils/array", + template: "ToBlocks", + params: [14], + }); + await circuit.expectPass( + { + stream: [0x32, 0x88, 0x31, 0xe0, 0x43, 0x5a, 0x31, 0x37, 0xf6, 0x30, 0x98, 0x07, 0xa8, 0x8d], + }, + { + blocks: [ + [ + [0x32, 0x43, 0xf6, 0xa8], + [0x88, 0x5a, 0x30, 0x8d], + [0x31, 0x31, 0x98, 0x01], + [0xe0, 0x37, 0x07, 0x00], + ], + ], + } + ); + }); + it("should generate enough blocks", async () => { + circuit = await circomkit.WitnessTester(`ToBlocks`, { + file: "utils/array", + template: "ToBlocks", + params: [17], + }); + await circuit.expectPass( + { + stream: [0x32, 0x88, 0x31, 0xe0, 0x42, 0x5a, 0x31, 0x37, 0xf6, 0x30, 0x98, 0x07, 0xa8, 0x8d, 0xa2, 0x34, 0x12], + }, + { + blocks: [ + [ + [0x32, 0x42, 0xf6, 0xa8], + [0x88, 0x5a, 0x30, 0x8d], + [0x31, 0x31, 0x98, 0xa2], + [0xe0, 0x37, 0x07, 0x34], + ], + [ + [0x12, 0x00, 0x00, 0x00], + [0x01, 0x00, 0x00, 0x00], + [0x00, 0x00, 0x00, 0x00], + [0x00, 0x00, 0x00, 0x00], + ], + ], + } + ); + }); + }); + + + describe("ToStream", () => { + let circuit: WitnessTester<["blocks"], ["stream"]>; + it("should convert blocks to stream#1", async () => { + circuit = await circomkit.WitnessTester(`ToStream`, { + file: "utils/array", + template: "ToStream", + params: [1, 16], + }); + await circuit.expectPass( + { + blocks: [ + [ + [0x32, 0x43, 0xf6, 0xa8], + [0x88, 0x5a, 0x30, 0x8d], + [0x31, 0x31, 0x98, 0xa2], + [0xe0, 0x37, 0x07, 0x34], + ], + ], + }, + { + stream: [0x32, 0x88, 0x31, 0xe0, 0x43, 0x5a, 0x31, 0x37, 0xf6, 0x30, 0x98, 0x07, 0xa8, 0x8d, 0xa2, 0x34], + } + ); + }); + it("should convert blocks to stream#2", async () => { + circuit = await circomkit.WitnessTester(`ToStream`, { + file: "utils/array", + template: "ToStream", + params: [1, 15], + }); + await circuit.expectPass( + { + blocks: [ + [ + [0x32, 0x43, 0xf6, 0xa8], + [0x88, 0x5a, 0x30, 0x8d], + [0x31, 0x31, 0x98, 0xa2], + [0xe0, 0x37, 0x07, 0x01], + ], + ], + }, + { + stream: [0x32, 0x88, 0x31, 0xe0, 0x43, 0x5a, 0x31, 0x37, 0xf6, 0x30, 0x98, 0x07, 0xa8, 0x8d, 0xa2], + } + ); + }); + it("should convert multiple blocks to stream", async () => { + circuit = await circomkit.WitnessTester(`ToStream`, { + file: "utils/array", + template: "ToStream", + params: [2, 18], + }); + await circuit.expectPass( + { + blocks: [ + [ + [0x32, 0x43, 0xf6, 0xa8], + [0x88, 0x5a, 0x30, 0x8d], + [0x31, 0x31, 0x98, 0xa2], + [0xe0, 0x37, 0x07, 0x01], + ], + [ + [0x32, 0x43, 0xf6, 0xa8], + [0x88, 0x5a, 0x30, 0x8d], + [0x31, 0x31, 0x98, 0xa2], + [0xe0, 0x37, 0x07, 0x01], + ], + ], + }, + { + stream: [ + 0x32, 0x88, 0x31, 0xe0, 0x43, 0x5a, 0x31, 0x37, 0xf6, 0x30, 0x98, 0x07, 0xa8, 0x8d, 0xa2, 0x01, 0x32, 0x88, + ], + } + ); + }); + }); \ No newline at end of file diff --git a/circuits/utils/array.circom b/circuits/utils/array.circom index fb28002..5b393f5 100644 --- a/circuits/utils/array.circom +++ b/circuits/utils/array.circom @@ -1,6 +1,8 @@ pragma circom 2.1.9; include "circomlib/circuits/comparators.circom"; +include "circomlib/circuits/gates.circom"; +include "circomlib/circuits/mux1.circom"; /* This template is an indicator for two equal array inputs. @@ -196,9 +198,17 @@ template ScalarArrayMul(n) { } } -// TODO: Below really needs documentation. +/* +This template sums over the elements in an array +# Params: + - `n`: the length of the array + +# Inputs: + - `array[n]`: an array of `n` numbers -// from: https://github.com/pluto/aes-proof/blob/main/circuits/aes-gcm/helper_functions.circom +# Outputs: + - `sum`: the sum of the array elements +*/ template SumMultiple(n) { signal input nums[n]; signal output sum; @@ -213,6 +223,17 @@ template SumMultiple(n) { sum <== sums[n-1]; } +/* +This template selects a the value of an array at an index +# Params: + - `n`: the length of the array + +# Inputs: + - `index`: the index to select + +# Outputs: + - `out`: the value of the array at this index +*/ template IndexSelector(total) { signal input in[total]; signal input index; @@ -233,6 +254,18 @@ template IndexSelector(total) { out <== calcTotal.sum; } +/* +This template selects an array in a mxn matrix +# Params: + - `m`: the row dimensions + - `n`: the column dimensions + +# Inputs: + - `index`: the index to select + +# Outputs: + - `out`: the array at index +*/ template ArraySelector(m, n) { signal input in[m][n]; signal input index; @@ -252,7 +285,6 @@ template ArraySelector(m, n) { sum === 1; signal sums[n][m+1]; - // note: loop order is column-wise, not row-wise for (var j = 0; j < n; j++) { sums[j][0] <== 0; for (var i = 0; i < m; i++) { @@ -260,4 +292,223 @@ template ArraySelector(m, n) { } out[j] <== sums[j][m]; } +} + +/* +This template is multiplexer for two arrays of length n +# Params: + - `n`: the array length + +# Inputs: + - `a`: the first array + - `b`: the second array + - `sel`: the selector (1 or 0) + +# Outputs: + - `out`: the array selected +*/ +template ArrayMux(n) { + signal input a[n]; + signal input b[n]; + signal input sel; + signal output out[n]; + + for (var i = 0; i < n; i++) { + out[i] <== (b[i] - a[i]) * sel + a[i]; + } +} + +/* +This template writes one array to a larger array of fixed size starting at an index +E.g., given an array of m=160, we want to write at `index` to the n=16 bytes at that index. +This is used to write to nivc signals that are incrementally written to on each fold. +# Params: + - `m`: the length of the array writing to + - `n`: the array be written + +# Inputs: + - `array_to_write_to`: the array we of length m we are writing to + - `array_to_write_at_index`: the array of length n we are writing to the array of length m + - `index`: the index we are writing to `array_to_write_to` + +# Outputs: + - `out`: the new array +*/ +template WriteToIndex(m, n) { + signal input array_to_write_to[m]; + signal input array_to_write_at_index[n]; + signal input index; + + signal output out[m]; + + assert(m >= n); + + // Note: this is underconstrained, we need to constrain that index + n <= m + // Need to constrain that index + n <= m -- can't be an assertion, because uses a signal + // ------------------------- // + + // Here, we get an array of ALL zeros, except at the `index` AND `index + n` + // beginning-------^^^^^ end---^^^^^^^^^ + signal indexMatched[m]; + component indexBegining[m]; + component indexEnding[m]; + for(var i = 0 ; i < m ; i++) { + indexBegining[i] = IsZero(); + indexBegining[i].in <== i - index; + indexEnding[i] = IsZero(); + indexEnding[i].in <== i - (index + n); + indexMatched[i] <== indexBegining[i].out + indexEnding[i].out; + } + + // E.g., index == 31, m == 160, n == 16 + // => indexMatch[31] == 1; + // => indexMatch[47] == 1; + // => otherwise, all 0. + + signal accum[m]; + accum[0] <== indexMatched[0]; + + component writeAt = IsZero(); + writeAt.in <== accum[0] - 1; + + component or = OR(); + or.a <== (writeAt.out * array_to_write_at_index[0]); + or.b <== (1 - writeAt.out) * array_to_write_to[0]; + out[0] <== or.out; + // IF accum == 1 then { array_to_write_at } ELSE IF accum != 1 then { array to write_to } + var accum_index = accum[0]; + + component writeSelector[m - 1]; + component indexSelector[m - 1]; + component ors[m-1]; + for(var i = 1 ; i < m ; i++) { + // accum will be 1 at all indices where we want to write the new array + accum[i] <== accum[i-1] + indexMatched[i]; + writeSelector[i-1] = IsZero(); + writeSelector[i-1].in <== accum[i] - 1; + // IsZero(accum[i] - 1); --> tells us we are in the range where we want to write the new array + + indexSelector[i-1] = IndexSelector(n); + indexSelector[i-1].index <== accum_index; + indexSelector[i-1].in <== array_to_write_at_index; + // When accum is not zero, out is array_to_write_at_index, otherwise it is array_to_write_to + + ors[i-1] = OR(); + ors[i-1].a <== (writeSelector[i-1].out * indexSelector[i-1].out); + ors[i-1].b <== (1 - writeSelector[i-1].out) * array_to_write_to[i]; + out[i] <== ors[i-1].out; + accum_index += writeSelector[i-1].out; + } +} + + +/* +Convert stream of plain text to blocks of 16 bytes +# Params: + - `l`: the length of the byte stream + +# Inputs: + - `stream`: the stream of bytes of length l + +# Outputs: + - `out`: n 4x4 blocks representing 16 bytes +*/ +template ToBlocks(l){ + signal input stream[l]; + + var n = l\16; + if(l%16 > 0){ + n = n + 1; + } + signal output blocks[n][4][4]; + + var i, j, k; + + for (var idx = 0; idx < l; idx++) { + blocks[i][k][j] <== stream[idx]; + k = k + 1; + if (k == 4){ + k = 0; + j = j + 1; + if (j == 4){ + j = 0; + i = i + 1; + } + } + } + + if (l%16 > 0){ + blocks[i][k][j] <== 1; + k = k + 1; + } +} + + +/* +convert blocks of 16 bytes to stream of bytes +# Params: + - `l`: the length of the byte stream + - `n`: the number of blocks + +# Inputs: + - `blocks`: n 4x4 blocks representing 16 bytes + +# Outputs: + - `out`: the stream of bytes of length l +*/ +template ToStream(n,l){ + signal input blocks[n][4][4]; + + signal output stream[l]; + + var i, j, k; + + while(i*16 + j*4 + k < l){ + stream[i*16 + j*4 + k] <== blocks[i][k][j]; + k = k + 1; + if (k == 4){ + k = 0; + j = j + 1; + if (j == 4){ + j = 0; + i = i + 1; + } + } + } +} + +/* +Increment a 32-bit word, represented as a 4-byte array +# Inputs: + - `in`: a 4 byte word + +# Outputs: + - `out`: an incremented 4 byte word +*/ +template IncrementWord() { + signal input in[4]; + signal output out[4]; + signal carry[4]; + carry[3] <== 1; + + component IsGreaterThan[4]; + component mux[4]; + for (var i = 3; i >= 0; i--) { + // check to carry overflow + IsGreaterThan[i] = GreaterThan(8); + IsGreaterThan[i].in[0] <== in[i] + carry[i]; + IsGreaterThan[i].in[1] <== 0xFF; + + // multiplexer to select the output + mux[i] = Mux1(); + mux[i].c[0] <== in[i] + carry[i]; + mux[i].c[1] <== 0x00; + mux[i].s <== IsGreaterThan[i].out; + out[i] <== mux[i].out; + + // propagate the carry to the next bit + if (i > 0) { + carry[i - 1] <== IsGreaterThan[i].out; + } + } } \ No newline at end of file diff --git a/circuits/utils/bytes.circom b/circuits/utils/bytes.circom index 65f8f80..10090cc 100644 --- a/circuits/utils/bytes.circom +++ b/circuits/utils/bytes.circom @@ -1,6 +1,7 @@ pragma circom 2.1.9; include "circomlib/circuits/bitify.circom"; +include "circomlib/circuits/gates.circom"; /* This template passes if a given array contains only valid ASCII values (e.g., u8 vals). @@ -19,4 +20,171 @@ template ASCII(n) { Byte[i] = Num2Bits(8); Byte[i].in <== in[i]; } +} + +/* +This template converts bytes to bits. +# Params: + - `n`: the number of bytes + +# Inputs: + - `in[n]`: array of bytes of length n +# Outputs: + - `out`: an array of bits of length n * 8 +*/ +template BytesToBits(n_bytes) { + signal input in[n_bytes]; + signal output out[n_bytes*8]; + component num2bits[n_bytes]; + for (var i = 0; i < n_bytes; i++) { + num2bits[i] = Num2Bits(8); + num2bits[i].in <== in[i]; + for (var j = 7; j >=0; j--) { + out[i*8 + j] <== num2bits[i].out[7 -j]; + } + } +} + +/* +This template converts bits to bytes. +# Params: + - `n`: the number of bytes you want out +# Inputs: + - `in[n]`: array of bits of length n * 8 +# Outputs: + - `out`: an array of bytes of length n +*/ +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; + } +} + +/* +This template XORs two bytes. +# Inputs: + - `a`: a single byte + - `b`: a single byte +# Outputs: + - `out`: a XOR b +*/ +template XorByte(){ + signal input a; + signal input b; + signal output out; + + component abits = Num2Bits(8); + abits.in <== a; + + component bbits = Num2Bits(8); + bbits.in <== b; + + component XorBits = BitwiseXor(8); + XorBits.a <== abits.out; + XorBits.b <== bbits.out; + + component num = Bits2Num(8); + num.in <== XorBits.out; + + out <== num.out; +} + +/* +This template XORs n bytes. +# Inputs: + - `a`: an array of bytes of length n + - `b`: an array of bytes of length n +# Outputs: + - `out`: a XOR b +*/ +template XORBLOCK(n_bytes){ + signal input a[n_bytes]; + signal input b[n_bytes]; + signal output out[n_bytes]; + + component xorByte[n_bytes]; + for (var i = 0; i < n_bytes; i++) { + xorByte[i] = XorByte(); + xorByte[i].a <== a[i]; + xorByte[i].b <== b[i]; + out[i] <== xorByte[i].out; + } +} + +/* +This template right shifts an n bit array by r. +# Params: + - `n`: length of bits to right shift + - `r`: number of bits to right shift by +# Inputs: + - `in`: an array of bits of length n +# Outputs: + - `out`: the bit array right shifted by r +*/ +template BitwiseRightShift(n, r) { + signal input in[n]; + signal output out[n]; + for (var i=0; i