* feat: halo2 docs * chore: fmt md * chore: fmt * fix: pkg name * Update halo2.md Signed-off-by: Iskander <0xisk@proton.me> * feat: update halo2 docs with benchmarks * chore: dprint fmt * chore: update docs * chore: enahnce doc * fix: typo and verify interface * fix: typo * docs: refactor halo2 page * docs: update spartan page --------- Signed-off-by: Iskander <0xisk@proton.me> Co-authored-by: sripwoud <me@sripwoud.xyz>
7.8 KiB
Refer to the Spartan circuit in
anonklub/anonklubrepository This work here is mainly based on spartan-ecdsa from Personae labs.
Requirements
- Install @anonklub/merkle-tree-worker, a web worker that includes the WebAssembly compilation of a binary merkle tree rust implementation.
- Install @anonklub/spartan-ecdsa-worker, a web worker that contains the WebAssembly compilation of the Spartan circuit, implemented with the
sapirrust crate.
TLDR
// React.js / Next.js example
import { type ProveMembershipFn, SpartanEcdsaWorker, type VerifyMembershipFn } from '@anonklub/spartan-ecdsa-worker'
import { type GenerateMerkleProofFn, MerkleTreeWorker } from '@anonklub/merkle-tree-worker'
import { useEffect, useState } from 'react'
// A React hook example for dealing with web-workers; i.e. https://github.com/anonklub/anonklub/blob/main/ui/src/hooks/useWorker.ts
export const useWorker = (
worker: typeof SpartanEcdsaWorker | typeof MerkleTreeWorker
) => {
const [isWorkerReady, setIsWorkerReady] = useState(false)
useEffect(() => {
void (async () => {
await worker.prepare()
setIsWorkerReady(true)
})()
}, [])
return isWorkerReady
}
// A React hook example for integrating with `@anonklub/merkle-tree-worker`; https://github.com/anonklub/anonklub/blob/main/ui/src/hooks/useMerkleTreeWorker.ts
export const useMerkleTreeWasmWorker = () => {
const isWorkerReady = useWorker(MerkleTreeWorker)
const generateMerkleProof: GenerateMerkleProofFn = async (
leaves,
leaf,
depth,
): Promise<Uint8Array> => {
process.env.NODE_ENV === 'development' && console.time('==>merkle')
const proof = await MerkleTreeWorker.generateMerkleProof(
leaves,
leaf,
depth,
)
process.env.NODE_ENV === 'development' && console.timeEnd('==>merkle')
return proof
}
return {
generateMerkleProof,
isWorkerReady,
}
}
// A React hook example for integrating with `@anonklub/spartan-ecdsa-worker` circuit web-worker; https://github.com/anonklub/anonklub/blob/main/ui/src/hooks/useSpartanEcdsaWorker.ts
export const useSpartanEcdsaWorker = () => {
const isWorkerReady = useWorker(SpartanEcdsaWorker)
const proveMembership: ProveMembershipFn = async ({
merkleProofBytesSerialized,
message,
sig,
}): Promise<Uint8Array> => {
process.env.NODE_ENV === 'development' && console.time('==> Prove')
const proof = await SpartanEcdsaWorker.proveMembership({
merkleProofBytesSerialized,
message,
sig,
})
process.env.NODE_ENV === 'development' && console.timeEnd('==> Prove')
return proof
}
const verifyMembership: VerifyMembershipFn = async (
anonklubProof: Uint8Array,
): Promise<boolean> => {
process.env.NODE_ENV === 'development' && console.time('==> Verify')
const isVerified = await SpartanEcdsaWorker.verifyMembership(anonklubProof)
process.env.NODE_ENV === 'development' && console.timeEnd('==> Verify')
return isVerified
}
return {
isWorkerReady,
proveMembership,
verifyMembership,
}
}
Step By Step
The examples are based on the AnonKlub UI implementation, which especially features:
- a TypeScript alias named
@hooks- global state management with an
easy-peasystore- description of the parameters required to generate the proof with the
ProofRequestobject from@anonklub/proof(see also circom/Create a Proof Request)
Prepare
@anonklub/merkle-tree-worker and @anonklub/spartan-ecdsa-worker are designed to operate on the client side. In the example above, ensure that you prepare each worker using await worker.prepare(). Please check Wasm & Web-Workers doc for more details.
Merkle Proof
Generating a Merkle proof to verify the inclusion of an Ethereum address within a Merkle tree is crucial for the circuit's functionality. We use a binary Merkle tree structure @anonklub/merkle-tree-worker library in case of Spartan circuits. Follow these steps to generate a Merkle proof:
- Call the
prepare()function as previously described. - Prepare the list of Ethereum addresses. The Anonklub project can assist in building this list by querying services that serve indexed blockchain data, check out query docs.
- Define the parameters needed to generate the Merkle proof, including the number of
leavesin the tree, the tree'sdepth(e.g., default: 15), and the specificleaf(address) for which you want to prove membership. - The generated proof will be serialized (
Uint8Array), making it immediately usable as a parameter for the proof function in the circuit.
export type GenerateMerkleProofFn = (
leaves: string[],
leaf: string,
depth: number,
) => Promise<Uint8Array>
Membership Proof
Once you have generated the Merkle proof for an Ethereum address (leaf), you can proceed to create a Spartan proof for the membership of that address in the Merkle tree. Follow these steps to generate a Spartan membership proof:
- Call the
preparefunction as outlined earlier. - Sign a
messageand obtain the signaturesigin hexadecimal format. - Pass
message,sigand the previously generate merkle proofmerkleProofBytesSerializedas parameters toproveMembership. - The generated Spartan membership proof is serialized in a format (
Uint8Array) appropriate for verification purposes.
export interface ProveInputs {
sig: Hex
message: string
merkleProofBytesSerialized: Uint8Array
}
Example of use
import { useAsync } from 'react-use'
import type { Hex } from 'viem'
import { useSpartanEcdsaWorker } from '@hooks/useSpartanEcdsaWorker'
import { useStore } from '@hooks/useStore'
export const useProofResult = () => {
const { isWorkerReady, proveMembership } = useSpartanEcdsaWorker()
return useAsync(async () => {
if (proofRequest === null || !isWorkerReady) return
return await proveMembership({
merkleProofBytesSerialized: proofRequest.merkleProof,
message: proofRequest.message,
sig: proofRequest.rawSignature as Hex,
})
}, [isWorkerReady, proofRequest])
}
Membership Verification
After successfully generating the Spartan proof, you can proceed with verifying that proof.
- Ensure you have the
membershipProofSerializedoutput from the proof of membership step. - Pass it as argument to
verifyMembership
Example of use
import { useAsync } from 'react-use';
import { useSpartanEcdsaWorker } from '@hooks/useSpartanEcdsaWorker';
export const useVerifyProof = () => {
const { proof } = useStore();
const { verifyMembership } = useSpartanEcdsaWorker();
return useAsync(async () => {
if (proof === null) return;
return await verifyMembership(proof);
}, [proof]);
};