Skip to content

Commit

Permalink
feat: added PackBits template and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
shreyas-londhe committed Feb 7, 2025
1 parent e108496 commit 26d2632
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 2 deletions.
7 changes: 5 additions & 2 deletions packages/circuits/email-verifier.circom
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,11 @@ template EmailVerifier(maxHeadersLength, maxBodyLength, n, k, ignoreBodyHashChec


// Calculate SHA256 hash of the `emailHeader` - 506,670 constraints
signal output sha[256] <== Sha256Bytes(maxHeadersLength)(emailHeader, emailHeaderLength);

signal sha[256] <== Sha256Bytes(maxHeadersLength)(emailHeader, emailHeaderLength);
component bitPacker = PackBits(256, 128);
bitPacker.in <== sha;
signal output shaHi <== bitPacker.out[0];
signal output shaLo <== bitPacker.out[1];

// Pack SHA output bytes to int[] for RSA input message
var rsaMessageSize = (256 + n) \ n;
Expand Down
108 changes: 108 additions & 0 deletions packages/circuits/tests/pack-bits.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { wasm as wasm_tester } from "circom_tester";
import path from "path";
import { shaHash } from "@zk-email/helpers/src/sha-utils";

describe("PackBits", () => {
jest.setTimeout(30 * 60 * 1000);

let circuit: any;

beforeAll(async () => {
circuit = await wasm_tester(
path.join(__dirname, "./test-circuits/pack-bits-test.circom"),
{
recompile: true,
include: path.join(__dirname, "../../../node_modules"),
output: path.join(__dirname, "./compiled-test-circuits"),
}
);
});

it("should pack 256 bits into 2x128 bits correctly", async () => {
// Generate test data using SHA256
const hash = shaHash(Buffer.from("test data", "ascii"));
// Convert to BE bits
const bits = Array.from(hash).flatMap((byte) =>
Array.from({ length: 8 }, (_, i) => (byte >> (7 - i)) & 1)
);

const witness = await circuit.calculateWitness({
in: bits,
});

// Calculate expected values (BE)
let expectedHi = 0n;
let expectedLo = 0n;

for (let i = 0; i < 128; i++) {
expectedHi += BigInt(bits[i]) << BigInt(127 - i);
expectedLo += BigInt(bits[i + 128]) << BigInt(127 - i);
}

await circuit.assertOut(witness, {
out: [expectedHi, expectedLo],
});
});

it("should handle zero input correctly", async () => {
const input = Array(256).fill(0);

const witness = await circuit.calculateWitness({
in: input,
});

await circuit.assertOut(witness, {
out: [0, 0],
});
});

it("should handle all ones correctly", async () => {
const input = Array(256).fill(1);

const witness = await circuit.calculateWitness({
in: input,
});

// Calculate max values for 128 bits (same in BE and LE)
const max128 = (1n << 128n) - 1n;

await circuit.assertOut(witness, {
out: [max128, max128],
});
});
});

describe("PackBits 10->4x3", () => {
jest.setTimeout(30 * 60 * 1000);

let circuit: any;

beforeAll(async () => {
circuit = await wasm_tester(
path.join(__dirname, "./test-circuits/pack-bits-10.circom"),
{
recompile: true,
include: path.join(__dirname, "../../../node_modules"),
output: path.join(__dirname, "./compiled-test-circuits"),
}
);
});

it("should pack 10 bits into 3-bit chunks correctly", async () => {
// Input in BE: [1,1,0] [1,0,1] [1,0,1] [1]
const input = [1, 1, 0, 1, 0, 1, 1, 0, 1, 1];

const witness = await circuit.calculateWitness({
in: input,
});

// Expected values in BE:
// First chunk (bits 0-2): 110 = 6
// Second chunk (bits 3-5): 101 = 5
// Third chunk (bits 6-8): 101 = 5
// Fourth chunk (bits 9): 100 = 4 (padded with zeros)
await circuit.assertOut(witness, {
out: [6, 5, 5, 4],
});
});
});
6 changes: 6 additions & 0 deletions packages/circuits/tests/test-circuits/pack-bits-10.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pragma circom 2.1.6;

include "../../utils/bytes.circom";

// Pack 10 bits into 3-bit chunks
component main = PackBits(10, 3);
6 changes: 6 additions & 0 deletions packages/circuits/tests/test-circuits/pack-bits-test.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pragma circom 2.1.6;

include "../../utils/bytes.circom";

// Test circuit for 256 -> 2x128
component main = PackBits(256, 128);
25 changes: 25 additions & 0 deletions packages/circuits/utils/bytes.circom
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,29 @@ template ByteMask(maxLength) {
bit_check[i].in <== mask[i];
out[i] <== in[i] * mask[i];
}
}

/// @title PackBits
/// @notice Packs an array of bits into field elements of specified bit size
/// @dev Assumes input bits are in big-endian order and maintains BE in output
/// @param numBits Total number of input bits
/// @param bitsPerElement Number of bits to pack into each element
/// @input in Array of bits in big-endian order
/// @output out Array of packed field elements in big-endian order
template PackBits(numBits, bitsPerElement) {
signal input in[numBits];
var numElements = (numBits + bitsPerElement - 1) \ bitsPerElement;
signal output out[numElements];

for (var i = 0; i < numElements; i++) {
var sum = 0;
for (var j = 0; j < bitsPerElement; j++) {
var idx = i * bitsPerElement + j;
if (idx < numBits) {
// Maintain BE order by using (bitsPerElement - 1 - j) for bit position
sum += in[idx] * (1 << (bitsPerElement - 1 - j));
}
}
out[i] <== sum;
}
}

0 comments on commit 26d2632

Please sign in to comment.