Files
anonklub.github.io/docs/prove/spartan.md
Iskander 9d18fe70d9 docs: add halo2 page (#15)
* 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>
2024-10-01 13:36:38 +02:00

7.8 KiB

Refer to the Spartan circuit in anonklub/anonklub repository This work here is mainly based on spartan-ecdsa from Personae labs.

Requirements

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:

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:

  1. Call the prepare() function as previously described.
  2. 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.
  3. Define the parameters needed to generate the Merkle proof, including the number of leaves in the tree, the tree's depth (e.g., default: 15), and the specific leaf (address) for which you want to prove membership.
  4. 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:

  1. Call the prepare function as outlined earlier.
  2. Sign a messageand obtain the signature sig in hexadecimal format.
  3. Pass message, sig and the previously generate merkle proof merkleProofBytesSerialized as parameters to proveMembership.
  4. 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 membershipProofSerialized output 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]);
};