Skip to content

Commit

Permalink
Merge pull request #79 from Galactica-corp/deriveMerkleRoot
Browse files Browse the repository at this point in the history
Derive merkle root from proofs
  • Loading branch information
zwilling authored Oct 3, 2024
2 parents 19006b1 + ab11b5f commit 8f09865
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 21 deletions.
1 change: 0 additions & 1 deletion packages/galactica-types/src/merkleProof.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@ export type MerkleProof = {
// The leafIndex can also be interpreted as binary number. If a bit is set, it means that the path is the right part of the parent node.
// The rightmost bit is for the leaf.
leafIndex: number;
root: string;
};
2 changes: 1 addition & 1 deletion packages/snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/Galactica-corp/galactica-snap.git"
},
"source": {
"shasum": "Kc9WKBQJvS3QeuQQGZ9L1GaQq/kKAkxw+XhMBt4j4rg=",
"shasum": "0Ag1aW0b/3d1m5Y9uB8kkSxfiEIdP0scL5VNacsHobs=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
14 changes: 10 additions & 4 deletions packages/snap/src/merkleProofSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import type {
ZkCertRegistered,
} from '@galactica-net/galactica-types';
import { GenericError } from '@galactica-net/snap-api';
import { fromHexToDec } from '@galactica-net/zk-certificates';
import {
fromHexToDec,
getMerkleRootFromProof,
} from '@galactica-net/zk-certificates';
import type { BaseProvider } from '@metamask/providers';
import { buildPoseidon } from 'circomlibjs';
import { Contract, providers } from 'ethers';

import { fetchWithTimeout } from './utils';
Expand Down Expand Up @@ -38,7 +42,11 @@ export async function getMerkleProof(
['function merkleRoot() external view returns (bytes32)'],
provider,
);
if (fromHexToDec(await registry.merkleRoot()) === zkCert.merkleProof.root) {
const poseidon = await buildPoseidon();
if (
fromHexToDec(await registry.merkleRoot()) ===
getMerkleRootFromProof(zkCert.merkleProof, poseidon)
) {
// The merkle root is the same as the one in the zkCert, so we can just use the old one
return zkCert.merkleProof;
}
Expand All @@ -63,7 +71,6 @@ export async function getMerkleProof(
const resJson = await response.json();
if (
resJson.proof === undefined ||
resJson.proof.root === undefined ||
resJson.proof.index === undefined ||
resJson.proof.path === undefined
) {
Expand All @@ -77,7 +84,6 @@ export async function getMerkleProof(

// Format into MerkleProof object
const merkleProof: MerkleProof = {
root: resJson.proof.root,
leaf: zkCert.leafHash,
pathElements: resJson.proof.path,
leafIndex: resJson.proof.index,
Expand Down
11 changes: 8 additions & 3 deletions packages/snap/src/proofGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ import type {
} from '@galactica-net/snap-api';
import { GenZKPError } from '@galactica-net/snap-api';
import type { ZkCertificate } from '@galactica-net/zk-certificates';
import { formatPrivKeyForBabyJub } from '@galactica-net/zk-certificates';
import {
formatPrivKeyForBabyJub,
getMerkleRootFromProof,
} from '@galactica-net/zk-certificates';
import { divider, heading, text } from '@metamask/snaps-ui';
import { Buffer } from 'buffer';
import { buildEddsa } from 'circomlibjs';
import { buildEddsa, buildPoseidon } from 'circomlibjs';
import { buildBls12381, buildBn128 } from 'ffjavascript';
import hash from 'object-hash';
import { groth16 } from 'snarkjs';
Expand Down Expand Up @@ -83,6 +86,8 @@ export const generateZkCertProof = async (
holder.eddsaKey,
params.userAddress,
);
const poseidon = await buildPoseidon();
const merkleRoot = getMerkleRootFromProof(merkleProof, poseidon);

const inputs: any = {
...params.input,
Expand All @@ -104,7 +109,7 @@ export const generateZkCertProof = async (
providerR8x: zkCert.providerData.r8x,
providerR8y: zkCert.providerData.r8y,

root: merkleProof.root,
root: merkleRoot,
pathElements: merkleProof.pathElements,
leafIndex: merkleProof.leafIndex,
};
Expand Down
18 changes: 12 additions & 6 deletions packages/snap/test/rpc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ import {
RpcResponseMsg,
ZkCertStandard,
} from '@galactica-net/snap-api';
import { fromDecToHex } from '@galactica-net/zk-certificates';
import type { Poseidon } from '@galactica-net/zk-certificates';
import {
fromDecToHex,
getMerkleRootFromProof,
} from '@galactica-net/zk-certificates';
import { decryptSafely, getEncryptionPublicKey } from '@metamask/eth-sig-util';
import chai, { expect } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import chaiFetchMock from 'chai-fetch-mock';
import { buildPoseidon } from 'circomlibjs';
import fetchMock from 'fetch-mock';
import { match } from 'sinon';
import sinonChai from 'sinon-chai';
Expand Down Expand Up @@ -105,7 +110,6 @@ async function verifyProof(
function merkleProofToServiceResponse(merkleProof: MerkleProof): any {
return {
proof: {
root: merkleProof.root,
index: merkleProof.leafIndex,
path: merkleProof.pathElements,
},
Expand All @@ -115,8 +119,9 @@ function merkleProofToServiceResponse(merkleProof: MerkleProof): any {
describe('Test rpc handler function', function () {
const snapProvider = mockSnapProvider();
const ethereumProvider = mockEthereumProvider();
let poseidon: Poseidon;

before(function () {
before(async function () {
// prepare URL to fetch provers from
const prover = testZkpParams.prover as ProverData; // we know it is a prover
fetchMock.get(testProverURL + subPathWasm, JSON.stringify(prover.wasm));
Expand All @@ -133,6 +138,7 @@ describe('Test rpc handler function', function () {
JSON.stringify(prover.zkeySections[i]),
);
}
poseidon = await buildPoseidon();
});

beforeEach(function () {
Expand All @@ -143,7 +149,7 @@ describe('Test rpc handler function', function () {
.resolves(testEntropyEncrypt);
ethereumProvider.rpcStubs.eth_chainId.resolves('41233');
ethereumProvider.rpcStubs.eth_call.resolves(
fromDecToHex(zkCert.merkleProof.root, true),
fromDecToHex(getMerkleRootFromProof(zkCert.merkleProof, poseidon), true),
);

// setting up merkle proof service for testing
Expand Down Expand Up @@ -634,7 +640,7 @@ describe('Test rpc handler function', function () {
);

const outdatedZkCert = JSON.parse(JSON.stringify(zkCert)); // deep copy to not mess up original
outdatedZkCert.merkleProof.root = '01234';
outdatedZkCert.merkleProof.pathElements[0] = '01234';
snapProvider.rpcStubs.snap_dialog.resolves(true);
snapProvider.rpcStubs.snap_manageState
.withArgs({ operation: 'get' })
Expand All @@ -660,7 +666,7 @@ describe('Test rpc handler function', function () {
snapProvider.rpcStubs.snap_dialog.resolves(true);

const outdatedZkCert = JSON.parse(JSON.stringify(zkCert)); // deep copy to not mess up original
outdatedZkCert.merkleProof.root = '01234';
outdatedZkCert.merkleProof.pathElements[0] = '01234';

snapProvider.rpcStubs.snap_manageState
.withArgs({ operation: 'get' })
Expand Down
26 changes: 25 additions & 1 deletion packages/zk-certificates/lib/merkleTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { MerkleProof } from '@galactica-net/galactica-types';
import keccak256 from 'keccak256';

import { arrayToBigInt, SNARK_SCALAR_FIELD } from './helpers';
import type { Poseidon } from './poseidon';

/**
* Class for managing and constructing merkle trees.
Expand Down Expand Up @@ -156,7 +157,30 @@ export class MerkleTree {
leaf,
pathElements: path,
leafIndex,
root: this.root,
};
}
}

/**
* Calculates the root hash of a merkle tree from a proof.
* @param proof - Merkle proof to calculate the root hash from.
* @param poseidon - Poseidon instance to use for hashing.
* @returns Root hash of the merkle tree.
*/
export function getMerkleRootFromProof(
proof: MerkleProof,
poseidon: Poseidon,
): string {
let currentNode = proof.leaf;
const dummyTree = new MerkleTree(0, poseidon);

// hash up the tree to the root
for (let i = 0; i < proof.pathElements.length; i++) {
const isNodeOnRight = (proof.leafIndex >> i) % 2 === 1;
const [left, right] = isNodeOnRight
? [proof.pathElements[i], currentNode]
: [currentNode, proof.pathElements[i]];
currentNode = dummyTree.calculateNodeHash(left, right);
}
return currentNode;
}
1 change: 0 additions & 1 deletion packages/zk-certificates/lib/sparseMerkleTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,6 @@ export class SparseMerkleTree {
leaf,
pathElements,
leafIndex,
root: this.root,
};
}

Expand Down
49 changes: 49 additions & 0 deletions packages/zk-certificates/test/lib/sparseMerkleTree.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* Copyright (C) 2023 Galactica Network. This file is part of zkKYC. zkKYC is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. zkKYC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */
import type { MerkleProof } from '@galactica-net/galactica-types';
import { expect } from 'chai';
import type { Eddsa } from 'circomlibjs';
import { buildEddsa } from 'circomlibjs';

import { getMerkleRootFromProof } from '../../lib';
import { SparseMerkleTree } from '../../lib/sparseMerkleTree';

describe('Sparse Merkle Tree', () => {
Expand Down Expand Up @@ -30,4 +32,51 @@ describe('Sparse Merkle Tree', () => {
);
expect(merkleTree.getFreeLeafIndex()).to.equal(13);
});

it('should evaluate merkle proof correctly', async () => {
const proof: MerkleProof = {
leaf: '1308179671240528936391414005825751669522278571022431835427338852808248413183',
leafIndex: 4,
pathElements: [
'6582701894083122514920251094569783799961281506932526561121219277665962099519',
'13882051640728242392041497514417915710871140573095154005188506574402825233001',
'17485298943231370992281377965028001621815847212498346835431786315904837099814',
'17619695615639375563172755451063681091123583187367666354590446695851847455206',
'13318301576191812234266801152872599855532005448246358193934877587650370582600',
'14788131755920683191475597296843560484793002846324723605628318076973413387512',
'15889843854411046052299062847446330225099449301489575711833732034292400193334',
'4591007468089219776529077618683677913362369124318235794006853887662826724179',
'974323504448759598753817959892943900419910101515018723175898332400800338902',
'10904304838309847003348248867595510063038089908778911273415397184640076197695',
'6882370933298714404012187108159138675240847601805332407879606734117764964844',
'5139203521709906739945343849817745409005203282448907255220261470507345543242',
'13660695785273441286119313134036776607743178109514008645018277634263858765331',
'10348593108579908024969691262542999418313940238885641489955258549772405516797',
'8081407491543416388951354446505389320018136283676956639992756527902136320118',
'9958479516685283258442625520693909575742244739421083147206991947039775937697',
'7970914938810054068245748769054430181949287449180056729094980613243958329268',
'9181633618293215208937072826349181607144232385752050143517655282584371194792',
'4290316886726748791387171617200449726541205208559598579274245616939964852707',
'6485208140905921389448627555662227594654261284121222408680793672083214472411',
'9758704411889015808755428886859795217744955029900206776077230470192243862856',
'2597152473563104183458372080692537737210460471555518794564105235328153976766',
'3463902188850558154963157993736984386286482462591640080583231993828223756729',
'4803991292849258082632334882589144741536815660863591403881043248209683263881',
'8436762241999885378816022437653918688617421907409515804233361706830437806851',
'1050020814711080606631372470935794540279414038427561141553730851484495104713',
'12563171857359400454610578260497195051079576349004486989747715063846486865999',
'15261846589675849940851399933657833195422666255877532937593219476893366898506',
'3948769100977277285624942212173034288901374055746067204399375431934078652233',
'5165855438174057791629208268983865460579098662614463291265268210129645045606',
'19766134122896885292208434174127396131016457922757580293859872286777805319620',
'21875366546070094216708763840902654314815506651483888537622737430893403929600',
],
};
const expectedRoot =
'11132090441876767700422147220270821668980081199638108774207159314279871102855';

expect(getMerkleRootFromProof(proof, eddsa.poseidon)).to.equal(
expectedRoot,
);
});
});
3 changes: 1 addition & 2 deletions test/zkCert.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@
"19766134122896885292208434174127396131016457922757580293859872286777805319620",
"21875366546070094216708763840902654314815506651483888537622737430893403929600"
],
"leafIndex": 4,
"root": "17637033816759361181326766036586003913218210680458915794825008024720818516964"
"leafIndex": 4
},
"registration": {
"address": "0x49FEc8ddf15a9731EfeD88b35685a45e5Fa95eFE",
Expand Down
3 changes: 1 addition & 2 deletions test/zkCert2.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@
"19766134122896885292208434174127396131016457922757580293859872286777805319620",
"21875366546070094216708763840902654314815506651483888537622737430893403929600"
],
"leafIndex": 6,
"root": "8399753718547829238177209909182513902959650136877209834788062496115328384201"
"leafIndex": 6
},
"registration": {
"address": "0x49FEc8ddf15a9731EfeD88b35685a45e5Fa95eFE",
Expand Down

0 comments on commit 8f09865

Please sign in to comment.