Skip to content

Conversation

@Architsharma7
Copy link
Collaborator

@Architsharma7 Architsharma7 commented Oct 21, 2025

Multi-schnorr circuit implementation using gnark over the babyjubjub curve and using the MiMC hash function.

Motivation:

  • Batch-verify schnorr signatures while handling non-participating validators through conditional constraints.
  • Prove that each active signer belongs to a committed validator set (Merkle root).

Relevant information:

  • Validator set: S of size MaxK = 2^Depth, with depth as the depth of merkle tree of validator pubic key hashes and inactive entries gated by IsIgnore = 1.
  • Merkle binding: One-time MiMC tree built from S (leaf = MiMC(Ax, Ay)), less computation complexity than verifying each membership proof if k is large (eg. 2/3).
  • Membership: Membership (per candidate): enforce that (Ax,Ay) matches exactly one leaf in S
  • Verification: For each active entry, enforce [S]G = R + [e]A, with e = MiMC(Rx, Ry, Ax, Ay, Message).
  • Counting: SumValid accumulates all active, valid signatures, that can be compared against threshold in verifying smart contract

Files added:

  • circuit.go: Multi-Schnorr circuit definition.

  • compile_test.go: ensures compilation and constraint reporting.

  • profile_test.go: generates gnark.pprof for constraint profiling.

To run:

# Compile test
go test -run TestCompile -v

# Profile test
go test -run TestProfileCircuit -v 
go tool pprof -http=:8080 gnark.pprof

@socket-security
Copy link

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedgithub.com/​consensys/​gnark@​v0.14.075100100100100
Addedgithub.com/​consensys/​gnark-crypto@​v0.19.076100100100100

View full report

Comment on lines +123 to +141
// Merkle membership check
// Hash the current candidate's pubkey
h.Reset()
h.Write(wi.Ax, wi.Ay)
candidateLeaf := h.Sum()

// For each active signature, check membership by finding matching leaf
var merkleMatches frontend.Variable = 0 // Will be 1 if pubkey found in tree

// Check if this leaf appears anywhere in our tree
// (This is the membership test - pubkey must be in validator set)
for j := 0; j < MaxK; j++ {
leafMatch := api.IsZero(api.Sub(candidateLeaf, leaves[j]))
merkleMatches = api.Add(merkleMatches, leafMatch)
}

//enforces only a single match
api.AssertIsEqual(api.Mul(active, api.Sub(merkleMatches, 1)), 0)
merkleOK := api.IsZero(api.Sub(merkleMatches, 1))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? The circuit guarantees that the candidate list cannot change between the two for loops. So if in the first for loop we were able to construct the correct merkle root, we know that all candidates are part of the merkle tree. No need to re-check it here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants