diff --git a/docs/content/en/docs/cli/index.mdx b/docs/content/en/docs/cli/index.mdx index a594382..fdbb4a5 100644 --- a/docs/content/en/docs/cli/index.mdx +++ b/docs/content/en/docs/cli/index.mdx @@ -46,16 +46,18 @@ maci registry check dora1abc...xyz Given an aMACI round contract address, the CLI audits: - All submitted proofs were **accepted by the smart contract** (`verifyResult = true`) +- On-chain **sign-up count** matches the indexed sign-up count - On-chain `MSG_CHAIN_LENGTH` matches the count of **indexed messages** -- **Batch coverage**: total processed messages ≥ chain length +- **Message batch coverage**: `msgProofCount × batchSize ≥ MSG_CHAIN_LENGTH` - Last message proof's **state commitment** matches on-chain `QueryCurrentStateCommitment` - Last tally proof's **tally commitment** matches on-chain `current_tally_commitment` With `--recheck`, it additionally: - Rebuilds the `MSG_HASHES` chain locally from indexed messages -- Reconstructs public signals (`input_hash`) from on-chain vkeys -- Runs `snarkjs.groth16.verify()` for **every** processMessage and tally proof +- Reconstructs public signals (`input_hash = SHA256(inputs) mod p`) from on-chain config (coordinatorHash, stateRoot, pollId, etc.) +- Fetches on-chain vkeys and runs `snarkjs.groth16.verify()` for **every** processMessage and tally proof +- **Tally batch coverage**: `tallyProofCount × 5^intStateTreeDepth ≥ numSignUps` ### Circuit Registry Verification (`maci registry check`) diff --git a/docs/content/zh/docs/cli/index.mdx b/docs/content/zh/docs/cli/index.mdx index 493b3df..8441d97 100644 --- a/docs/content/zh/docs/cli/index.mdx +++ b/docs/content/zh/docs/cli/index.mdx @@ -46,16 +46,18 @@ maci registry check dora1abc...xyz 给定一个 aMACI 轮次合约地址,CLI 将审计以下内容: - 所有已提交的证明均被智能合约**接受**(`verifyResult = true`) +- 链上**报名数**与已索引报名数一致 - 链上 `MSG_CHAIN_LENGTH` 与**已索引消息数**一致 -- **批次覆盖率**:已处理消息总数 ≥ 链长度 +- **消息批次覆盖率**:`msgProofCount × batchSize ≥ MSG_CHAIN_LENGTH` - 最后一条消息证明的**状态承诺**与链上 `QueryCurrentStateCommitment` 一致 - 最后一条 tally 证明的**计票承诺**与链上 `current_tally_commitment` 一致 启用 `--recheck` 时,还将额外执行: - 从已索引消息本地重建 `MSG_HASHES` 链 -- 从链上 vkeys 重新构造公开信号(`input_hash`) -- 对**每一条** processMessage 和 tally 证明运行 `snarkjs.groth16.verify()` +- 从链上配置(coordinatorHash、stateRoot、pollId 等)重新构造公开信号(`input_hash = SHA256(inputs) mod p`) +- 从链上读取 vkeys,对**每一条** processMessage 和 tally 证明运行 `snarkjs.groth16.verify()` +- **tally 批次覆盖率**:`tallyProofCount × 5^intStateTreeDepth ≥ numSignUps` ### 电路注册表验证(`maci registry check`) diff --git a/packages/cli/package.json b/packages/cli/package.json index e191677..bac6400 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@dorafactory/maci-cli", - "version": "0.1.0", + "version": "0.1.1", "description": "Public CLI tool for MACI circuit registry and proof verification", "type": "module", "bin": { diff --git a/packages/cli/src/commands/verify.ts b/packages/cli/src/commands/verify.ts index 2403731..88ce9c3 100644 --- a/packages/cli/src/commands/verify.ts +++ b/packages/cli/src/commands/verify.ts @@ -391,7 +391,7 @@ export const roundCommand: CommandModule = { .option('recheck', { type: 'boolean', description: - 'Layer 2: re-verify ZK proofs locally using snarkjs (experimental)', + 'Layer 2: re-verify ZK proofs locally using snarkjs', default: false, }), handler: (args) => diff --git a/packages/cli/src/core/indexer.ts b/packages/cli/src/core/indexer.ts index 74679d9..476fe6c 100644 --- a/packages/cli/src/core/indexer.ts +++ b/packages/cli/src/core/indexer.ts @@ -99,16 +99,21 @@ export async function getProofs( endpoint: string, contractAddress: string ): Promise { - const data = await graphql<{ - proofData: { nodes: ProofEntry[] }; + const PAGE_SIZE = 500; + const proofs: ProofEntry[] = []; + + const first = await graphql<{ + proofData: { totalCount: number; nodes: ProofEntry[] }; }>( endpoint, `query { proofData( - first: 10000 + first: ${PAGE_SIZE} + offset: 0 filter: { contractAddress: { equalTo: "${contractAddress}" } } orderBy: [TIMESTAMP_ASC] ) { + totalCount nodes { id txHash @@ -121,7 +126,40 @@ export async function getProofs( } }` ); - return data.proofData.nodes; + + const total = first.proofData.totalCount; + proofs.push(...first.proofData.nodes); + + let offset = PAGE_SIZE; + while (proofs.length < total) { + const page = await graphql<{ + proofData: { nodes: ProofEntry[] }; + }>( + endpoint, + `query { + proofData( + first: ${PAGE_SIZE} + offset: ${offset} + filter: { contractAddress: { equalTo: "${contractAddress}" } } + orderBy: [TIMESTAMP_ASC] + ) { + nodes { + id + txHash + timestamp + actionType + commitment + proof + verifyResult + } + } + }` + ); + proofs.push(...page.proofData.nodes); + offset += PAGE_SIZE; + } + + return proofs; } export async function getMessageCount( diff --git a/packages/cli/src/core/recheck.ts b/packages/cli/src/core/recheck.ts index e14de72..f6263a2 100644 --- a/packages/cli/src/core/recheck.ts +++ b/packages/cli/src/core/recheck.ts @@ -269,9 +269,6 @@ export async function runLayer2( // Next tally's prevTallyCommitment = this batch's newTallyCommitment currentTallyCommitment = newTallyCommitment; - - // tally batch size sanity check (informational) - void tallyBS; } catch (err) { results.push({ proofType: 'tally', @@ -283,5 +280,16 @@ export async function runLayer2( } } + // Tally batch coverage: tallyBS × tallyProofCount must cover all sign-ups + const tallyBatchesCovered = tallyBS * BigInt(tallyProofs.length); + const tallyCoverageOk = tallyBatchesCovered >= chainCfg.numSignUps; + results.push({ + proofType: 'tally', + proofIndex: -1, + batchLabel: `tally coverage (${tallyProofs.length}×${tallyBS})`, + passed: tallyCoverageOk, + detail: `${tallyBatchesCovered} ${tallyCoverageOk ? '≥' : '<'} ${chainCfg.numSignUps} sign-ups`, + }); + return results; }