From 6b8182d68bfbf42aec957effad757f63853b30cf Mon Sep 17 00:00:00 2001 From: turnoffthiscomputer Date: Thu, 12 Sep 2024 17:47:58 +0200 Subject: [PATCH] add leafHasherLight and improve leafHasher --- .../tests/utils/leafHasherLight_tester.circom | 5 ++ .../tests/utils/leafHasher_tester.circom | 2 +- .../circuits/utils/LeafHasherLight.circom | 30 ++++++++ circuits/circuits/utils/leafHasher.circom | 9 +-- circuits/tests/utils/leaf_hasher.test.ts | 71 +++++++++++++++++++ common/src/utils/csca.ts | 58 ++++++++++----- common/src/utils/poseidon.ts | 27 +++++++ 7 files changed, 181 insertions(+), 21 deletions(-) create mode 100644 circuits/circuits/tests/utils/leafHasherLight_tester.circom create mode 100644 circuits/circuits/utils/LeafHasherLight.circom create mode 100644 circuits/tests/utils/leaf_hasher.test.ts create mode 100644 common/src/utils/poseidon.ts diff --git a/circuits/circuits/tests/utils/leafHasherLight_tester.circom b/circuits/circuits/tests/utils/leafHasherLight_tester.circom new file mode 100644 index 000000000..d04e6a2b9 --- /dev/null +++ b/circuits/circuits/tests/utils/leafHasherLight_tester.circom @@ -0,0 +1,5 @@ +pragma circom 2.1.6; + +include "../../utils/LeafHasherLight.circom"; + +component main = LeafHasherLight(35); \ No newline at end of file diff --git a/circuits/circuits/tests/utils/leafHasher_tester.circom b/circuits/circuits/tests/utils/leafHasher_tester.circom index 949d80092..46bdfccb1 100644 --- a/circuits/circuits/tests/utils/leafHasher_tester.circom +++ b/circuits/circuits/tests/utils/leafHasher_tester.circom @@ -2,4 +2,4 @@ pragma circom 2.1.6; include "../../utils/leafHasher.circom"; -component main = LeafHasher(32); \ No newline at end of file +component main = LeafHasher(120,65); \ No newline at end of file diff --git a/circuits/circuits/utils/LeafHasherLight.circom b/circuits/circuits/utils/LeafHasherLight.circom new file mode 100644 index 000000000..21577316a --- /dev/null +++ b/circuits/circuits/utils/LeafHasherLight.circom @@ -0,0 +1,30 @@ +pragma circom 2.1.6; +include "@zk-email/circuits/lib/fp.circom"; +include "circomlib/circuits/poseidon.circom"; + +template LeafHasherLight(k) { + signal input in[k]; + signal output out; + var rounds = div_ceil(k, 16); + + component hash[rounds]; + for (var i = 0; i < rounds ; i ++){ + hash[i] = Poseidon(16); + } + + for (var i = 0; i < rounds ; i ++){ + for (var j = 0; j < 16 ; j ++){ + if (i * 16 + j < k){ + hash[i].inputs[j] <== in[i * 16 + j]; + } else { + hash[i].inputs[j] <== 0; + } + } + } + + component finalHash = Poseidon(rounds); + for (var i = 0 ; i < rounds ; i++){ + finalHash.inputs[i] <== hash[i].out; + } + out <== finalHash.out; +} diff --git a/circuits/circuits/utils/leafHasher.circom b/circuits/circuits/utils/leafHasher.circom index 1120875aa..74076b468 100644 --- a/circuits/circuits/utils/leafHasher.circom +++ b/circuits/circuits/utils/leafHasher.circom @@ -1,14 +1,15 @@ pragma circom 2.1.6; - +include "@zk-email/circuits/lib/fp.circom"; include "circomlib/circuits/poseidon.circom"; +include "../utils/splitSignalsToWords.circom"; template LeafHasher(n, k) { signal input in[k]; signal output out; - - component splitSignalsToWords = SplitSignalsToWords(n, k, 64, 64); + var wordsSize = div_ceil(n * k, 64); + component splitSignalsToWords = SplitSignalsToWords(n, k, wordsSize, 64); splitSignalsToWords.in <== in; - + component hash[4]; for (var i = 0; i < 4 ; i ++){ hash[i] = Poseidon(16); diff --git a/circuits/tests/utils/leaf_hasher.test.ts b/circuits/tests/utils/leaf_hasher.test.ts new file mode 100644 index 000000000..a50cfe585 --- /dev/null +++ b/circuits/tests/utils/leaf_hasher.test.ts @@ -0,0 +1,71 @@ +import { expect } from 'chai'; +import { X509Certificate } from 'crypto'; +import path from 'path'; +import { computeLeafFromModulusBigInt, getCSCAInputs, getTBSHash, leafHasherLight } from '../../../common/src/utils/csca'; +import { wasm as wasm_tester } from 'circom_tester'; +import forge from 'node-forge'; + +import { + mock_dsc_sha256_rsa_2048, + mock_csca_sha256_rsa_2048, + mock_dsc_sha1_rsa_2048, + mock_csca_sha1_rsa_2048, +} from '../../../common/src/constants/mockCertificates'; +import { splitToWords } from '../../../common/src/utils/utils'; + +function loadCertificates(dscCertContent: string, cscaCertContent: string) { + const dscCert = new X509Certificate(dscCertContent); + const cscaCert = new X509Certificate(cscaCertContent); + const dscCert_forge = forge.pki.certificateFromPem(dscCertContent); + const cscaCert_forge = forge.pki.certificateFromPem(cscaCertContent); + + return { dscCert, cscaCert, dscCert_forge, cscaCert_forge }; +} + +describe('LeafHasher Light', function () { + this.timeout(0); + let circuit; + + this.beforeAll(async () => { + const circuitPath = path.resolve(__dirname, '../../circuits/tests/utils/leafHasherLight_tester.circom'); + circuit = await wasm_tester(circuitPath, { + include: [ + 'node_modules', + './node_modules/@zk-kit/binary-merkle-root.circom/src', + './node_modules/circomlib/circuits', + ], + }); + }); + describe('Circuit', () => { + it('should compile and load the circuit', () => { + expect(circuit).not.to.be.undefined; + }); + }); + + describe('SHA-256 certificates', async () => { + const { dscCert, cscaCert, dscCert_forge, cscaCert_forge } = loadCertificates( + mock_dsc_sha256_rsa_2048, + mock_csca_sha256_rsa_2048 + ); + const bigInt = BigInt(2n ** 4096n - 1n); + const leaf = computeLeafFromModulusBigInt(bigInt); + const n = 120; + const k = 35; + const bigInt_formatted = splitToWords(bigInt, BigInt(n), BigInt(k)); + const leaf_light = leafHasherLight(bigInt_formatted); + console.log("\x1b[34m", "leafHasher: ", leaf, "\x1b[0m"); + console.log("\x1b[34m", "leafHasherLight: ", leaf_light, "\x1b[0m"); + + + it('should extract and log certificate information', async () => { + const inputs = { + in: splitToWords(bigInt, BigInt(n), BigInt(k)) + }; + const witness = await circuit.calculateWitness(inputs, true); + const output = await circuit.getOutput(witness, ["out"]); + console.log("\x1b[34m", "output: ", output, "\x1b[0m"); + }); + }); + + +}); diff --git a/common/src/utils/csca.ts b/common/src/utils/csca.ts index 7bd504738..641982370 100644 --- a/common/src/utils/csca.ts +++ b/common/src/utils/csca.ts @@ -7,7 +7,7 @@ import { IMT } from "@zk-kit/imt"; import serialized_csca_tree from "../../pubkeys/serialized_csca_tree.json" import { createHash } from "crypto"; import axios from "axios"; - +import { flexiblePoseidon } from "./poseidon"; export function findStartIndex(modulus: string, messagePadded: Uint8Array): number { const modulusNumArray = []; for (let i = 0; i < modulus.length; i += 2) { @@ -166,29 +166,26 @@ export function getCSCAModulusMerkleTree() { export function computeLeafFromModulusFormatted(modulus_formatted: string[]) { if (modulus_formatted.length <= 64) { - const hashInputs = new Array(4); - for (let i = 0; i < 4; i++) { - hashInputs[i] = new Array(16).fill(BigInt(0)); + const hashInputs = new Array(4).fill(null).map(() => new Array(16).fill(BigInt(0))); + + for (let i = 0; i < Math.min(modulus_formatted.length, 64); i++) { + hashInputs[Math.floor(i / 16)][i % 16] = BigInt(modulus_formatted[i]); } - for (let i = 0; i < 64; i++) { - if (i < modulus_formatted.length) { - hashInputs[i % 4][Math.floor(i / 4)] = BigInt(modulus_formatted[i]); - } - } - for (let i = 0; i < 4; i++) { - hashInputs[i] = poseidon16(hashInputs[i].map(input => input.toString())); - } - const finalHash = poseidon4(hashInputs.map(h => h)); + + const intermediateHashes = hashInputs.map(inputs => poseidon16(inputs)); + const finalHash = poseidon4(intermediateHashes); + console.log(finalHash); return finalHash.toString(); - } - else { + } else { throw new Error("Modulus length is too long"); } } export function computeLeafFromModulusBigInt(modulus_bigint: bigint) { + const bitsSize = getBitsSize(modulus_bigint); + const wordsSize = Math.ceil(bitsSize / 64); if (modulus_bigint <= BigInt(2n ** 4096n - 1n)) { - const modulus_formatted = splitToWords(modulus_bigint, BigInt(64), BigInt(64)); + const modulus_formatted = splitToWords(modulus_bigint, BigInt(wordsSize), BigInt(64)); const hashInputs = new Array(4); for (let i = 0; i < 4; i++) { hashInputs[i] = new Array(16).fill(BigInt(0)); @@ -210,6 +207,35 @@ export function computeLeafFromModulusBigInt(modulus_bigint: bigint) { } } +export function computeLeafFromPubKey(pubkey, n, k) { + const pubKeyFormatted = splitToWords(pubkey, BigInt(n), BigInt(k)); + return leafHasherLight(pubKeyFormatted); +} + +export function leafHasherLight(pubKeyFormatted: string[]) { + const rounds = Math.ceil(pubKeyFormatted.length / 16); + const hash = new Array(rounds); + for (let i = 0; i < rounds; i++) { + // Initialize each element of hash as an object with an inputs array + hash[i] = { inputs: new Array(16).fill(BigInt(0)) }; + } + for (let i = 0; i < rounds; i++) { + for (let j = 0; j < 16; j++) { + if (i * 16 + j < pubKeyFormatted.length) { + hash[i].inputs[j] = BigInt(pubKeyFormatted[i * 16 + j]); + } + } + } + // Use the inputs array for poseidon4 + const finalHash = flexiblePoseidon(hash.map(h => poseidon16(h.inputs))); + return finalHash.toString(); +} + +function getBitsSize(modulus_bigint: bigint) { + const i = (modulus_bigint.toString(16).length - 1) * 4 + return i + 32 - Math.clz32(Number(modulus_bigint >> BigInt(i))) +} + export function getCSCAModulusProof(leaf, n, k) { let tree = new IMT(poseidon2, CSCA_TREE_DEPTH, 0, 2); tree.setNodes(serialized_csca_tree); diff --git a/common/src/utils/poseidon.ts b/common/src/utils/poseidon.ts new file mode 100644 index 000000000..67f3ce00a --- /dev/null +++ b/common/src/utils/poseidon.ts @@ -0,0 +1,27 @@ +import { + poseidon1, poseidon2, poseidon3, poseidon4, poseidon5, poseidon6, poseidon7, poseidon8, + poseidon9, poseidon10, poseidon11, poseidon12, poseidon13, poseidon14, poseidon15, poseidon16 +} from 'poseidon-lite'; + +export function flexiblePoseidon(inputs: bigint[]): bigint { + switch (inputs.length) { + case 1: return poseidon1(inputs); + case 2: return poseidon2(inputs); + case 3: return poseidon3(inputs); + case 4: return poseidon4(inputs); + case 5: return poseidon5(inputs); + case 6: return poseidon6(inputs); + case 7: return poseidon7(inputs); + case 8: return poseidon8(inputs); + case 9: return poseidon9(inputs); + case 10: return poseidon10(inputs); + case 11: return poseidon11(inputs); + case 12: return poseidon12(inputs); + case 13: return poseidon13(inputs); + case 14: return poseidon14(inputs); + case 15: return poseidon15(inputs); + case 16: return poseidon16(inputs); + default: + throw new Error(`Unsupported number of inputs: ${inputs.length}`); + } +}