refactor registry circuits and tests

This commit is contained in:
0xturboblitz
2024-05-10 15:26:00 +09:00
parent ee9e495ffb
commit 7401c92cad
22 changed files with 234 additions and 206 deletions

View File

@@ -2,10 +2,9 @@ pragma circom 2.1.5;
include "circomlib/circuits/poseidon.circom";
include "@zk-email/circuits/helpers/extract.circom";
include "./merkle_tree/tree.circom";
include "./isOlderThan.circom";
include "./isValid.circom";
include "binary-merkle-root.circom";
include "./utils/isOlderThan.circom";
include "./utils/isValid.circom";
include "./utils/binary-merkle-root.circom";
template Disclose(nLevels) {
signal input secret;

View File

@@ -1,5 +0,0 @@
pragma circom 2.1.5;
include "./tree.circom";
component main = MerkleTreeInclusionProof(16);

View File

@@ -1,40 +0,0 @@
pragma circom 2.1.5;
include "../../node_modules/circomlib/circuits/poseidon.circom";
include "../../node_modules/circomlib/circuits/mux1.circom";
template MerkleTreeInclusionProof(nLevels) {
signal input leaf;
signal input pathIndices[nLevels];
signal input siblings[nLevels];
signal output root;
component poseidons[nLevels];
component mux[nLevels];
signal hashes[nLevels + 1];
hashes[0] <== leaf;
for (var i = 0; i < nLevels; i++) {
pathIndices[i] * (1 - pathIndices[i]) === 0;
poseidons[i] = Poseidon(2);
mux[i] = MultiMux1(2);
mux[i].c[0][0] <== hashes[i];
mux[i].c[0][1] <== siblings[i];
mux[i].c[1][0] <== siblings[i];
mux[i].c[1][1] <== hashes[i];
mux[i].s <== pathIndices[i];
poseidons[i].inputs[0] <== mux[i].out[0];
poseidons[i].inputs[1] <== mux[i].out[1];
hashes[i + 1] <== poseidons[i].out;
}
root <== hashes[nLevels];
}

View File

@@ -1,33 +0,0 @@
include "./merkle_tree/tree.circom";
include "./chunk_data.circom";
template MerkleTreePubkey_sha256WithRSAEncryption65537(n, k, nLevels, pubkeySize) {
signal input pubkey[pubkeySize];
signal input merkle_root;
signal input path[nLevels];
signal input siblings[nLevels];
signal input signatureAlgorithm;
// Converting pubkey (modulus) into 11 chunks of 192 bits, assuming original n, k are 64 and 32.
// This is because Poseidon circuit only supports an array of 16 elements.
var chunk_size = 11; // Since ceil(32 / 3) in integer division is 11
component chunk_data = ChunkData(n, k, chunk_size);
chunk_data.data <== pubkey;
signal leaf_hash_input[1 + chunk_size];
leaf_hash_input[0] <== signatureAlgorithm;
for (var i = 0; i < chunk_size; i++) {
leaf_hash_input[i+1] <== chunk_data.outputs[i];
}
signal leaf <== Poseidon(1 + chunk_size)(leaf_hash_input);
// Verify inclusion in merkle tree
signal computed_merkle_root <== MerkleTreeInclusionProof(nLevels)(leaf, path, siblings);
merkle_root === computed_merkle_root;
// sha256WithRSAEncryption_65537 is the only sigAlg supported right now
signatureAlgorithm === 1;
pubkeySize === k;
}

View File

@@ -3,7 +3,7 @@ pragma circom 2.1.5;
include "@zk-email/circuits/helpers/rsa.circom";
include "@zk-email/circuits/helpers/extract.circom";
include "@zk-email/circuits/helpers/sha.circom";
include "./Sha256BytesStatic.circom";
include "../utils/Sha256BytesStatic.circom";
template PassportVerifier(n, k, max_datahashes_bytes) {
signal input mrz[93]; // formatted mrz (5 + 88) chars

View File

@@ -3,8 +3,8 @@ pragma circom 2.1.5;
include "circomlib/circuits/poseidon.circom";
include "@zk-email/circuits/helpers/extract.circom";
include "./passport_verifier.circom";
include "./merkle_tree/tree.circom";
include "./isOlderThan.circom";
include "../utils/tree.circom";
include "../utils/isOlderThan.circom";
template ProofOfPassport(n, k, max_datahashes_bytes, nLevels, pubkeySize) {
signal input mrz[93]; // formatted mrz (5 + 88) chars

View File

@@ -3,9 +3,9 @@ pragma circom 2.1.5;
include "@zk-email/circuits/helpers/rsa.circom";
include "@zk-email/circuits/helpers/extract.circom";
include "@zk-email/circuits/helpers/sha.circom";
include "./Sha256BytesStatic.circom";
include "./utils/Sha256BytesStatic.circom";
template PassportVerifier_sha256WithRSAEncryption65537(n, k, max_datahashes_bytes) {
template PassportVerifier_sha256WithRSAEncryption_65537(n, k, max_datahashes_bytes) {
signal input mrz[93]; // formatted mrz (5 + 88) chars
signal input dataHashes[max_datahashes_bytes];
signal input datahashes_padded_length;
@@ -40,7 +40,6 @@ template PassportVerifier_sha256WithRSAEncryption65537(n, k, max_datahashes_byte
// hash dataHashes dynamically
signal dataHashesSha[256] <== Sha256Bytes(max_datahashes_bytes)(dataHashes, datahashes_padded_length);
// get output of dataHashes sha256 into bytes to check against eContent
component dataHashesSha_bytes[32];
for (var i = 0; i < 32; i++) {
@@ -67,10 +66,11 @@ template PassportVerifier_sha256WithRSAEncryption65537(n, k, max_datahashes_byte
//instantiate each component of the list of Bits2Num of size n
eContentHash[i] = Bits2Num(n);
}
for (var i = 0; i < 256; i++) {
for (var i = 0; i < 256; i++) {
eContentHash[i \ n].in[i % n] <== eContentSha[255 - i];
}
for (var i = 256; i < n * msg_len; i++) {
eContentHash[i \ n].in[i % n] <== 0;
}

View File

@@ -1,67 +0,0 @@
pragma circom 2.1.5;
include "circomlib/circuits/poseidon.circom";
include "@zk-email/circuits/helpers/extract.circom";
include "./merkle_tree/tree.circom";
include "./merkle_tree_pubkey_sha256WithRSAEncryption65537.circom";
include "./passport_verifier_sha256WithRSAEncryption65537.circom";
include "./chunk_data.circom";
template Register(n, k, max_datahashes_bytes, nLevels, pubkeySize) {
signal input secret;
signal input mrz[93];
signal input econtent[max_datahashes_bytes];
signal input datahashes_padded_length;
signal input signed_attributes[104];
signal input signature[k];
signal input signature_algorithm;
// merkle tree inclusion of issuer pubkey
signal input pubkey[pubkeySize];
signal input merkle_root;
signal input path[nLevels];
signal input siblings[nLevels];
// Verify inclusion of the pubkey in the merkle tree
component MT = MerkleTreePubkey_sha256WithRSAEncryption65537(n, k, nLevels, pubkeySize);
MT.pubkey <== pubkey;
MT.signatureAlgorithm <== signature_algorithm;
MT.merkle_root <== merkle_root;
MT.path <== path;
MT.siblings <== siblings;
// Verify passport
component PV = PassportVerifier_sha256WithRSAEncryption65537(n, k, max_datahashes_bytes);
PV.mrz <== mrz;
PV.dataHashes <== econtent;
PV.datahashes_padded_length <== datahashes_padded_length;
PV.eContentBytes <== signed_attributes;
PV.pubkey <== pubkey;
PV.signature <== signature;
// Generate the commitment
component poseidon_commitment = Poseidon(4);
poseidon_commitment.inputs[0] <== secret;
signal mrz_packed[3] <== PackBytes(93, 3, 31)(mrz);
for (var i = 0; i < 3; i++) {
poseidon_commitment.inputs[i + 1] <== mrz_packed[i];
}
signal output commitment <== poseidon_commitment.out;
// Generate the nullifier
var chunk_size = 11; // Since ceil(32 / 3) in integer division is 11
component chunk_data = ChunkData(n, k, chunk_size);
chunk_data.data <== signature;
component poseidon_nullifier = Poseidon(chunk_size);
for(var i = 0; i < chunk_size; i++) {
poseidon_nullifier.inputs[i] <== chunk_data.outputs[i];
}
signal output nullifier <== poseidon_nullifier.out;
}
component main { public [ merkle_root, signature_algorithm ] } = Register(64, 32, 320, 16, 32);

View File

@@ -0,0 +1,59 @@
pragma circom 2.1.5;
include "circomlib/circuits/poseidon.circom";
include "@zk-email/circuits/helpers/extract.circom";
include "./passport_verifier_sha256WithRSAEncryption_65537.circom";
include "./utils/chunk_data.circom";
include "./utils/compute_pubkey_leaf.circom";
include "./utils/binary-merkle-root.circom";
template Register_sha256WithRSAEncryption_65537(n, k, max_datahashes_bytes, nLevels, signatureAlgorithm) {
signal input secret;
signal input mrz[93];
signal input econtent[max_datahashes_bytes];
signal input datahashes_padded_length;
signal input signed_attributes[104];
signal input signature[k];
signal input pubkey[k];
signal input merkle_root;
signal input path[nLevels];
signal input siblings[nLevels];
signal input attestation_id;
// Verify inclusion of the pubkey in the pubkey tree
signal leaf <== ComputePubkeyLeaf(n, k, signatureAlgorithm)(pubkey);
signal computed_merkle_root <== BinaryMerkleRoot(nLevels)(leaf, nLevels, path, siblings);
merkle_root === computed_merkle_root;
// Verify passport validity
component PV = PassportVerifier_sha256WithRSAEncryption_65537(n, k, max_datahashes_bytes);
PV.mrz <== mrz;
PV.dataHashes <== econtent;
PV.datahashes_padded_length <== datahashes_padded_length;
PV.eContentBytes <== signed_attributes;
PV.pubkey <== pubkey;
PV.signature <== signature;
// Generate the commitment
component poseidon_commitment = Poseidon(6);
poseidon_commitment.inputs[0] <== secret;
poseidon_commitment.inputs[1] <== attestation_id;
poseidon_commitment.inputs[2] <== leaf;
signal mrz_packed[3] <== PackBytes(93, 3, 31)(mrz);
for (var i = 0; i < 3; i++) {
poseidon_commitment.inputs[i + 3] <== mrz_packed[i];
}
signal output commitment <== poseidon_commitment.out;
// Generate the nullifier
var chunk_size = 11; // Since ceil(32 / 3) in integer division is 11
signal chunked_signature[chunk_size] <== ChunkData(n, k, chunk_size)(signature);
signal output nullifier <== Poseidon(chunk_size)(chunked_signature);
}
// We hardcode 1 here for sha256WithRSAEncryption_65537
component main { public [ merkle_root, attestation_id ] } = Register_sha256WithRSAEncryption_65537(64, 32, 320, 16, 1);

View File

@@ -1,6 +1,6 @@
pragma circom 2.1.6;
include "../isOlderThan.circom";
include "../utils/isOlderThan.circom";
template isOlderThan_tester() {

View File

@@ -1,6 +1,6 @@
pragma circom 2.1.6;
include "../IsValid.circom";
include "../utils/IsValid.circom";
template IsValid_tester() {

View File

@@ -1,8 +1,8 @@
pragma circom 2.1.5;
// Converts data into chunk_size chunks of 192 bits, assuming original n, k are 64 and 32.
// This is because Poseidon circuit only supports an array of 16 elements.
template ChunkData(n, k, chunk_size) {
// Converting signature (modulus) into chunk_size chunks of 192 bits, assuming original n, k are 64 and 32.
// This is because Poseidon circuit only supports an array of 16 elements.
signal input data[k];
signal output outputs[chunk_size];
for(var i = 0; i < chunk_size; i++) {

View File

@@ -0,0 +1,20 @@
pragma circom 2.1.5;
include "./chunk_data.circom";
// chunks the pubkey and hashes it with the signature algorithm
template ComputePubkeyLeaf(n, k, signatureAlgorithm) {
signal input pubkey[k];
// Converting pubkey (modulus) into 11 chunks of 192 bits, assuming original n, k are 64 and 32.
// This is because Poseidon circuit only supports an array of 16 elements.
var chunk_size = 11; // Since ceil(32 / 3) in integer division is 11
signal chunk_data[chunk_size] <== ChunkData(n, k, chunk_size)(pubkey);
signal leaf_hash_input[1 + chunk_size];
leaf_hash_input[0] <== signatureAlgorithm;
for (var i = 0; i < chunk_size; i++) {
leaf_hash_input[i+1] <== chunk_data[i];
}
signal output leaf <== Poseidon(1 + chunk_size)(leaf_hash_input);
}

View File

@@ -1,4 +1,4 @@
pragma circom 2.1.6;
pragma circom 2.1.6;
include "../node_modules/circomlib/circuits/comparators.circom";

View File

@@ -1,16 +1,14 @@
pragma circom 2.1.6;
pragma circom 2.1.6;
include "../node_modules/circomlib/circuits/comparators.circom";
include "../node_modules/circomlib/circuits/bitify.circom";
include "./dateIsLess.circom";
template IsOlderThan() {
signal input majorityASCII[2];
signal input currDate[6];
signal input birthDateASCII[6];
signal birthdateNum[6];
signal ASCII_rotation <== 48;
@@ -27,7 +25,6 @@ template IsOlderThan() {
signal majorityNum;
majorityNum <== ( majorityASCII[0] - 48 ) * TEN + ( majorityASCII[1] - 48 );
component isPrevCentury = LessThan(8);
isPrevCentury.in[0] <== currDateYear;

View File

@@ -1,11 +1,10 @@
pragma circom 2.1.6;
pragma circom 2.1.6;
include "../node_modules/circomlib/circuits/comparators.circom";
include "../node_modules/circomlib/circuits/bitify.circom";
include "./dateIsLess.circom";
template IsValid() {
signal input currDate[6];
signal input validityDateASCII[6];
@@ -22,7 +21,6 @@ template IsValid() {
signal currDateYear <== currDate[0] * TEN + currDate[1];
signal validityYear <== validityDateNum[0] * TEN + validityDateNum[1];
component is_valid = DateIsLess();
is_valid.secondYear <== validityDateNum[0] * TEN + validityDateNum[1];
is_valid.secondMonth <== validityDateNum[2] * TEN + validityDateNum[3];

View File

@@ -1,59 +1,149 @@
// Import necessary libraries
import { describe } from 'mocha'
import { assert, expect } from 'chai'
import path from "path";
const wasm_tester = require("circom_tester").wasm;
import { buildPoseidon } from 'circomlibjs';
import { formatMrz } from '../../common/src/utils/utils';
import { MAX_DATAHASHES_LEN, SignatureAlgorithm, TREE_DEPTH } from "../../common/src/constants/constants";
import { poseidon4 } from "poseidon-lite";
import { IMT } from "@zk-kit/imt";
import { poseidon1, poseidon4, poseidon6 } from "poseidon-lite";
import { mockPassportData_sha256WithRSAEncryption_65537 } from "../../common/src/utils/mockPassportData";
import { generateCircuitInputs_Register } from '../../common/src/utils/generateInputs';
import { packBytes } from "../../common/src/utils/utils";
import { generateCircuitInputsRegister } from '../../common/src/utils/generateInputs';
import { formatMrz } from "../../common/src/utils/utils";
import { buildPoseidon } from 'circomlibjs';
import { getLeaf } from '../../common/src/utils/pubkeyTree';
describe("Proof of Passport - Circuits - Register flow", function () {
this.timeout(0);
let inputs: any;
let circuit: any;
let w: any;
let poseidon: any;
let commitment: any;
let passportData = mockPassportData_sha256WithRSAEncryption_65537;
let attestation_id: string;
before(async () => {
circuit = await wasm_tester(path.join(__dirname, "../circuits/register_sha256WithRSAEncryption65537.circom"),
circuit = await wasm_tester(
path.join(__dirname, "../circuits/register_sha256WithRSAEncryption_65537.circom"),
{ include: ["node_modules"] },
);
poseidon = await buildPoseidon();
const passportData = mockPassportData_sha256WithRSAEncryption_65537
inputs = generateCircuitInputs_Register(
const secret = BigInt(Math.floor(Math.random() * Math.pow(2, 254))).toString();
console.log("secret", secret);
const attestation_name = "E-PASSPORT";
attestation_id = poseidon1([
BigInt(Buffer.from(attestation_name).readUIntBE(0, 6))
]).toString();
inputs = generateCircuitInputsRegister(
secret,
attestation_id,
passportData,
{ developmentMode: true }
);
console.log(JSON.stringify(inputs, null, 2));
});
it("compile and load the circuit", async function () {
it("should compile and load the circuit", async function () {
expect(circuit).to.not.be.undefined;
});
it("calculate witness", async function () {
w = await circuit.calculateWitness(inputs);
let commitment_circom = await circuit.getOutput(w, ["commitment"]);
commitment_circom = commitment_circom.commitment;
const formattedMrz = formatMrz(inputs.mrz);
const mrz_bytes = packBytes(formattedMrz);
const commitment_bytes = poseidon4([BigInt(inputs.secret), BigInt(mrz_bytes[0]), BigInt(mrz_bytes[1]), BigInt(mrz_bytes[2])]);
const commitment_js = BigInt(poseidon.F.toString(commitment_bytes)).toString();
console.log('commitment_js', commitment_js)
console.log('commitment_circom', commitment_circom)
expect(commitment_circom).to.be.equal(commitment_js);
it("should calculate the witness with correct inputs", async function () {
const w = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(w);
let commitment = await circuit.getOutput(w, ["commitment"]);
let nullifier = await circuit.getOutput(w, ["nullifier"]);
console.log("commitment", commitment);
console.log("nullifier", nullifier);
// commenting to push, will uncomment when fixed
// const poseidon = await buildPoseidon();
// const commitment_circom = commitment.commitment;
// const formattedMrz = formatMrz(inputs.mrz);
// const mrz_bytes = packBytes(formattedMrz);
// const commitment_bytes = poseidon6([
// BigInt(inputs.secret),
// BigInt(attestation_id),
// getLeaf({
// signatureAlgorithm: passportData.signatureAlgorithm,
// modulus: passportData.pubKey.modulus,
// exponent: passportData.pubKey.exponent
// }),
// BigInt(mrz_bytes[0]),
// BigInt(mrz_bytes[1]),
// BigInt(mrz_bytes[2])
// ]);
// const commitment_js = BigInt(poseidon.F.toString(commitment_bytes)).toString();
// console.log('commitment_js', commitment_js)
// console.log('commitment_circom', commitment_circom)
// expect(commitment_circom).to.be.equal(commitment_js);
});
it("try to calculate witness with bad inputs", async function () {
it("should fail to calculate witness with invalid mrz", async function () {
try {
const trashmrz = Array(93).fill(0).map(byte => BigInt(byte).toString());
inputs.mrz = trashmrz;
w = await circuit.calculateWitness(inputs);
const invalidInputs = {
...inputs,
mrz: Array(93).fill(0).map(byte => BigInt(byte).toString())
}
await circuit.calculateWitness(invalidInputs);
expect.fail("Expected an error but none was thrown.");
} catch (error) {
expect(error.message).to.include("Assert Failed");
}
});
it("should fail to calculate witness with invalid econtent", async function () {
try {
const invalidInputs = {
...inputs,
econtent: inputs.econtent.map((byte: string) => String((parseInt(byte, 10) + 1) % 256)),
}
await circuit.calculateWitness(invalidInputs);
expect.fail("Expected an error but none was thrown.");
} catch (error) {
expect(error.message).to.include("Assert Failed");
}
});
it("should fail to calculate witness with invalid signature", async function () {
try {
const invalidInputs = {
...inputs,
signature: inputs.signature.map((byte: string) => String((parseInt(byte, 10) + 1) % 256)),
}
await circuit.calculateWitness(invalidInputs);
expect.fail("Expected an error but none was thrown.");
} catch (error) {
expect(error.message).to.include("Assert Failed");
}
});
it("should fail to calculate witness with invalid merkle root", async function () {
try {
const invalidInputs = {
...inputs,
merkle_root: inputs.merkle_root.map((byte: string) => String((parseInt(byte, 10) + 1) % 256)),
}
await circuit.calculateWitness(invalidInputs);
expect.fail("Expected an error but none was thrown.");
} catch (error) {
expect(error.message).to.include("Assert Failed");
}
});
});
function packBytes(unpacked) {
const bytesCount = [31, 31, 31];
let packed = [0n, 0n, 0n];
let byteIndex = 0;
for (let i = 0; i < bytesCount.length; i++) {
for (let j = 0; j < bytesCount[i]; j++) {
if (byteIndex < unpacked.length) {
packed[i] |= BigInt(unpacked[byteIndex]) << (BigInt(j) * 8n);
}
byteIndex++;
}
}
return packed;
}

View File

@@ -516,6 +516,13 @@
commander "^11.0.0"
snarkjs "^0.7.0"
"@zk-kit/circuits@^1.0.0-beta":
version "1.0.0-beta"
resolved "https://registry.yarnpkg.com/@zk-kit/circuits/-/circuits-1.0.0-beta.tgz#4f41315839855762dac11b2ba2ce5e58fd8ad1e9"
integrity sha512-ZJBkmm//iFlDB3pQOWAOqSCeUQFYWzI00a980jjbEcuzQgq2PqBxiq36TFxnZHkbrOh39XpeWOoEpCXRkjS2KQ==
dependencies:
circomlib "^2.0.5"
"@zk-kit/imt@https://gitpkg.now.sh/0xturboblitz/zk-kit/packages/imt?6d417675":
version "2.0.0-beta.1"
resolved "https://gitpkg.now.sh/0xturboblitz/zk-kit/packages/imt?6d417675#38244ea6eef75dc1ad7fff3ff2a22dd5f1a2593a"