-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
passing test for gfmulx #48
Changes from all commits
287839e
3a4f781
ef54fc8
6dd3513
d2d72c1
b9bc6f6
51b35c6
9af7124
4538c2b
ddd4858
bbebe4e
91090b6
ddef808
f0846f7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
pragma circom 2.1.9; | ||
|
||
// include "circomlib/circuits/gates.circom"; | ||
include "helper_functions.circom"; | ||
|
||
// compute x * `in` over ghash polynomial | ||
// ghash irreducible polynomial x^128 = x^7 + x^2 + x + 1 | ||
// | ||
// spec: | ||
// https://tools.ietf.org/html/rfc8452#appendix-A | ||
template ghash_GFMULX() { | ||
signal input in[128]; | ||
signal output out[128]; | ||
var msb = in[127]; | ||
|
||
// v = in right-shifted by 1 | ||
signal v[128]; | ||
v[0] <== 0; | ||
for (var i = 1; i < 128; i++) { v[i] <== in[i-1]; } | ||
|
||
// if MSB set, assign irreducible poly bits, otherwise zero | ||
// irreducible_poly has 1s at positions 1, 2, 7, 127 | ||
signal irreducible_poly[128]; | ||
for (var i = 0; i < 128; i++) { | ||
if (i==0 || i == 1 || i==6 || i==127) { | ||
irreducible_poly[i] <== msb; | ||
} else { | ||
irreducible_poly[i] <== 0; | ||
} | ||
} | ||
|
||
component xor = BitwiseXor(128); | ||
xor.a <== v; | ||
xor.b <== irreducible_poly; | ||
out <== xor.out; | ||
} | ||
|
||
// compute x * `in` over polyval polynomial | ||
// polyval irreducible polynomial x^128 = x^127 + x^126 + x^121 + 1 | ||
// | ||
// spec: | ||
// https://tools.ietf.org/html/rfc8452#appendix-A | ||
// | ||
// rust-crypto reference implementation: | ||
// https://github.com/RustCrypto/universal-hashes/blob/master/polyval/src/mulx.rs#L11 | ||
template polyval_GFMULX() { | ||
signal input in[128]; | ||
signal output out[128]; | ||
// v = in << 1; observe that LE makes this less straightforward | ||
signal v[128]; | ||
// if MSB set, assign irreducible poly bits, otherwise zero | ||
signal irreducible_poly[128]; | ||
var msb = in[128 - 8]; | ||
|
||
component left_shift = LeftShiftLE(1); | ||
for (var i = 0; i < 128; i++) { | ||
left_shift.in[i] <== in[i]; | ||
} | ||
for (var i = 0; i < 128; i++) { | ||
v[i] <== left_shift.out[i]; | ||
} | ||
|
||
// irreducible_poly has 1s at positions 1, 121, 126, 127 | ||
// 0000 0001... <== encodes 1 | ||
// ...1100 0010 <== encodes 121, 126, 127 | ||
// ...0100 0010 <== encodes 121, 126 | ||
for (var i = 0; i < 128; i++) { | ||
if (i==7 || i == 120 || i==121 || i==126) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Be more explicit about LE stuff |
||
irreducible_poly[i] <== msb; | ||
} else { | ||
irreducible_poly[i] <== 0; | ||
} | ||
} | ||
|
||
component xor = BitwiseXor(128); | ||
xor.a <== v; | ||
xor.b <== irreducible_poly; | ||
out <== xor.out; | ||
} | ||
|
||
|
||
// Left shift a 128-bit little-endian array by `shift` bits | ||
// | ||
// example for 16 bit-array shifted by 1 bit: | ||
// in = [h g f e d c b a, p o n m l k j i] | ||
// mid1= [a b c d e f g h, i j k l m n o p] // swap order of bits in each byte | ||
// mid2= [0 a b c d e f g, h i j k l m n o] // shift bits right by 1 | ||
// out = [g f e d c b a 0, o n m l k j i h] // swap order of bits in each byte | ||
// TODO(TK 2024-08-15): optimize | ||
template LeftShiftLE(shift) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very readable. Don't optimize until necessary |
||
signal input in[128]; | ||
signal output out[128]; | ||
signal mid_1[128]; | ||
signal mid_2[128]; | ||
|
||
for (var i = 0; i < 16; i++) { | ||
for (var j = 0; j < 8; j++) { | ||
mid_1[j + 8*i] <== in[7-j + 8*i]; | ||
} | ||
} | ||
|
||
for (var i = 0; i < shift; i++) { | ||
mid_2[i] <== 0; | ||
} | ||
for (var i = shift; i < 128; i++) { | ||
mid_2[i] <== mid_1[i - shift]; | ||
} | ||
|
||
for (var i = 0; i < 16; i++) { | ||
for (var j = 0; j < 8; j++) { | ||
out[j + 8*i] <== mid_2[7-j + 8*i]; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,19 +13,13 @@ describe("bitreversal", () => { | |
}); | ||
|
||
let bit_array = [1,0,0,0,0,0,0,0]; | ||
let bit_array_1 = [1,0,0,0,0,0,0,1]; | ||
let expected_output = [0,0,0,0,0,0,0,1]; | ||
it("should have correct output", async () => { | ||
const witness = await circuit.calculateWitness({ in: bit_array }); | ||
const witness = await circuit.expectPass({ in: bit_array}, { out: expected_output }); | ||
circuit.expectPass({in: bit_array}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I recommend that tests get back the output and compare that to expected output instead of using const expect = ...
let result = await circuit.compute(input, ["out"]);
assert.equal(result, expected); If not, remove the second |
||
|
||
// I was initially not sure what the first bit was doing in the witness, but it's just the success flag | ||
console.log("witness: from input: [1,0,0,0,0,0,0,0]", witness); | ||
}); | ||
|
||
it("should fail with wrong input", async () => { | ||
const witness = await circuit.calculateWitness({ in: bit_array_1 }); | ||
circuit.expectPass({in: bit_array_1}); | ||
|
||
console.log("witness: from input: [1,0,0,0,0,0,0,1]", witness); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,72 @@ | ||
import { assert } from "chai"; | ||
import { Circomkit, WitnessTester } from "circomkit"; | ||
import 'mocha'; | ||
import "mocha"; | ||
|
||
export const circomkit = new Circomkit({ | ||
verbose: false, | ||
verbose: false, | ||
}); | ||
|
||
export { WitnessTester }; | ||
|
||
export function hexToBitArray(hex: string): number[] { | ||
// Remove '0x' prefix if present and ensure lowercase | ||
hex = hex.replace(/^0x/i, "").toLowerCase(); | ||
|
||
// Ensure even number of characters | ||
if (hex.length % 2 !== 0) { | ||
hex = "0" + hex; | ||
} | ||
|
||
return ( | ||
hex | ||
// Split into pairs of characters | ||
.match(/.{2}/g)! | ||
.flatMap((pair) => { | ||
const byte = parseInt(pair, 16); | ||
// map byte to 8-bits. Apologies for the obtuse mapping; | ||
// which cycles through the bits in byte and extracts them one by one. | ||
return Array.from({ length: 8 }, (_, i) => (byte >> (7 - i)) & 1); | ||
}) | ||
); | ||
} | ||
|
||
export function bitArrayToHex(bits: number[]): string { | ||
// console.log(bits); | ||
if (bits.length % 8 !== 0) { | ||
throw new Error("Input length must be a multiple of 8 bits"); | ||
} | ||
|
||
return bits | ||
.reduce((acc, bit, index) => { | ||
const byteIndex = Math.floor(index / 8); | ||
const bitPosition = 7 - (index % 8); | ||
acc[byteIndex] = (acc[byteIndex] || 0) | (bit << bitPosition); | ||
return acc; | ||
}, new Array(bits.length / 8).fill(0)) | ||
.map((byte) => byte.toString(16).padStart(2, "0")) | ||
.join(""); | ||
} | ||
|
||
it("tests hexToBitArray", async () => { | ||
let hex = "0F"; | ||
let expectedBits = [0, 0, 0, 0, 1, 1, 1, 1]; | ||
let result = hexToBitArray(hex); | ||
assert.deepEqual(result, expectedBits); | ||
|
||
hex = "1248"; | ||
expectedBits = [0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0]; | ||
result = hexToBitArray(hex); | ||
assert.deepEqual(result, expectedBits); | ||
}); | ||
|
||
it("tests bitArrayToHexString", async () => { | ||
let bits = [0, 0, 0, 0, 1, 1, 1, 1]; | ||
let expectedHex = "0f"; | ||
let result = bitArrayToHex(bits); | ||
assert.equal(result, expectedHex); | ||
|
||
bits = [1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1]; | ||
expectedHex = "8b09"; | ||
result = bitArrayToHex(bits); | ||
assert.equal(result, expectedHex); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
import chai, { assert, expect } from "chai"; | ||
import { WitnessTester } from "circomkit"; | ||
import { bitArrayToHex, circomkit, hexToBitArray } from "../common"; | ||
|
||
// Disable truncation of arrays in error messages | ||
chai.config.truncateThreshold = 0; | ||
|
||
const mulXTestVectors = [ | ||
"00800000000000000000000000000000", | ||
"00400000000000000000000000000000", | ||
"00200000000000000000000000000000", | ||
"00100000000000000000000000000000", | ||
"00080000000000000000000000000000", | ||
"00040000000000000000000000000000", | ||
"00020000000000000000000000000000", | ||
"00010000000000000000000000000000", | ||
"00008000000000000000000000000000", | ||
"00004000000000000000000000000000", | ||
"00002000000000000000000000000000", | ||
"00001000000000000000000000000000", | ||
"00000800000000000000000000000000", | ||
"00000400000000000000000000000000", | ||
"00000200000000000000000000000000", | ||
"00000100000000000000000000000000", | ||
"00000080000000000000000000000000", | ||
"00000040000000000000000000000000", | ||
"00000020000000000000000000000000", | ||
"00000010000000000000000000000000", | ||
"00000008000000000000000000000000", | ||
"00000004000000000000000000000000", | ||
"00000002000000000000000000000000", | ||
"00000001000000000000000000000000", | ||
"00000000800000000000000000000000", | ||
"00000000400000000000000000000000", | ||
"00000000200000000000000000000000", | ||
"00000000100000000000000000000000", | ||
"00000000080000000000000000000000", | ||
"00000000040000000000000000000000", | ||
"00000000020000000000000000000000", | ||
"00000000010000000000000000000000", | ||
"00000000008000000000000000000000", | ||
"00000000004000000000000000000000", | ||
"00000000002000000000000000000000", | ||
"00000000001000000000000000000000", | ||
"00000000000800000000000000000000", | ||
"00000000000400000000000000000000", | ||
"00000000000200000000000000000000", | ||
"00000000000100000000000000000000", | ||
"00000000000080000000000000000000", | ||
"00000000000040000000000000000000", | ||
"00000000000020000000000000000000", | ||
"00000000000010000000000000000000", | ||
"00000000000008000000000000000000", | ||
"00000000000004000000000000000000", | ||
"00000000000002000000000000000000", | ||
"00000000000001000000000000000000", | ||
"00000000000000800000000000000000", | ||
"00000000000000400000000000000000", | ||
"00000000000000200000000000000000", | ||
"00000000000000100000000000000000", | ||
"00000000000000080000000000000000", | ||
"00000000000000040000000000000000", | ||
"00000000000000020000000000000000", | ||
"00000000000000010000000000000000", | ||
"00000000000000008000000000000000", | ||
"00000000000000004000000000000000", | ||
"00000000000000002000000000000000", | ||
"00000000000000001000000000000000", | ||
"00000000000000000800000000000000", | ||
"00000000000000000400000000000000", | ||
"00000000000000000200000000000000", | ||
"00000000000000000100000000000000", | ||
"00000000000000000080000000000000", | ||
"00000000000000000040000000000000", | ||
"00000000000000000020000000000000", | ||
"00000000000000000010000000000000", | ||
"00000000000000000008000000000000", | ||
"00000000000000000004000000000000", | ||
"00000000000000000002000000000000", | ||
"00000000000000000001000000000000", | ||
"00000000000000000000800000000000", | ||
"00000000000000000000400000000000", | ||
"00000000000000000000200000000000", | ||
"00000000000000000000100000000000", | ||
"00000000000000000000080000000000", | ||
"00000000000000000000040000000000", | ||
"00000000000000000000020000000000", | ||
"00000000000000000000010000000000", | ||
"00000000000000000000008000000000", | ||
"00000000000000000000004000000000", | ||
"00000000000000000000002000000000", | ||
"00000000000000000000001000000000", | ||
"00000000000000000000000800000000", | ||
"00000000000000000000000400000000", | ||
"00000000000000000000000200000000", | ||
"00000000000000000000000100000000", | ||
"00000000000000000000000080000000", | ||
"00000000000000000000000040000000", | ||
"00000000000000000000000020000000", | ||
"00000000000000000000000010000000", | ||
"00000000000000000000000008000000", | ||
"00000000000000000000000004000000", | ||
"00000000000000000000000002000000", | ||
"00000000000000000000000001000000", | ||
"00000000000000000000000000800000", | ||
"00000000000000000000000000400000", | ||
"00000000000000000000000000200000", | ||
"00000000000000000000000000100000", | ||
"00000000000000000000000000080000", | ||
"00000000000000000000000000040000", | ||
"00000000000000000000000000020000", | ||
"00000000000000000000000000010000", | ||
"00000000000000000000000000008000", | ||
"00000000000000000000000000004000", | ||
"00000000000000000000000000002000", | ||
"00000000000000000000000000001000", | ||
"00000000000000000000000000000800", | ||
"00000000000000000000000000000400", | ||
"00000000000000000000000000000200", | ||
"00000000000000000000000000000100", | ||
"00000000000000000000000000000080", | ||
"00000000000000000000000000000040", | ||
"00000000000000000000000000000020", | ||
"00000000000000000000000000000010", | ||
"00000000000000000000000000000008", | ||
"00000000000000000000000000000004", | ||
"00000000000000000000000000000002", | ||
"00000000000000000000000000000001", | ||
]; | ||
|
||
describe("ghash_GFMulX", () => { | ||
let circuit: WitnessTester<["in"], ["out"]>; | ||
|
||
describe("ghash GF Mul X test", () => { | ||
before(async () => { | ||
circuit = await circomkit.WitnessTester(`gfmulx`, { | ||
file: "aes-gcm/gfmulx", | ||
template: "ghash_GFMULX", | ||
}); | ||
// console.log("#constraints:", await circuit.getConstraintCount()); | ||
}); | ||
|
||
it("test ghash at all bits set", async () => { | ||
let bits = hexToBitArray("01000000000000000000000000000000"); | ||
for (let i = 0; i < mulXTestVectors.length; i++) { | ||
const expect = mulXTestVectors[i]; | ||
const _res = await circuit.compute({ in: bits }, ["out"]); | ||
const result = bitArrayToHex( | ||
(_res.out as (number | bigint)[]).map((bit) => Number(bit)) | ||
); | ||
// console.log("expect: ", expect, "\nresult: ", result); | ||
assert.equal(expect, result); | ||
bits = hexToBitArray(result); | ||
} | ||
}); | ||
|
||
// ref: https://datatracker.ietf.org/doc/html/rfc8452#appendix-A | ||
it("compute IETF test 2", async () => { | ||
let bits = hexToBitArray("9c98c04df9387ded828175a92ba652d8"); | ||
let expect = "4e4c6026fc9c3ef6c140bad495d3296c"; | ||
const _res = await circuit.compute({ in: bits }, ["out"]); | ||
const result = bitArrayToHex( | ||
(_res.out as (number | bigint)[]).map((bit) => Number(bit)) | ||
); | ||
// console.log("expect: ", expect, "\nresult: ", result); | ||
assert.equal(expect, result); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be
at positions 1, 2, 7, 128