You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/developer-documentation/circuit-code-walkthrough.md
+8-15Lines changed: 8 additions & 15 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,13 +4,13 @@ This page provides a detailed explanation of the [Noir](https://noir-lang.org) c
4
4
5
5
## Overview
6
6
7
-
The circuit file is located at `packages/nextjs/public/circuit/src/main.nr`. It proves four things in a single proof: transaction hash commitment is correct, ECDSA signature is valid, prover is a member of authorized signers, and nullifier prevents double-signing.
7
+
The circuit file is located at `packages/nextjs/public/circuit/src/main.nr`. It proves four things in a single proof: transaction hash commitment is correct, ECDSA signature is valid, prover knows the secret for their commitment, and nullifier prevents double-signing.
8
8
9
9
## Circuit Structure
10
10
11
-
### Constants and Imports
11
+
### Imports
12
12
13
-
The circuit uses `keccak256` for Ethereum-compatible message hashing, `poseidon` as a ZK-friendly hash function for commitments and nullifiers, and sets `DEPTH = 4` meaning the Merkle tree supports 2^4 = 16 signers maximum.
13
+
The circuit uses `keccak256` for Ethereum-compatible message hashing and `poseidon` as a ZK-friendly hash function for commitments and nullifiers.
14
14
15
15
## Main Function Inputs
16
16
@@ -24,8 +24,6 @@ These inputs are hidden from everyone - only the prover knows them:
24
24
| pub_key_x |[u8; 32]| Public key X coordinate |
25
25
| pub_key_y |[u8; 32]| Public key Y coordinate |
26
26
| secret | Field | Signer's secret |
27
-
| leaf_index | Field | Position in Merkle tree |
28
-
| merkle_path |[Field; DEPTH]| Sibling hashes for proof |
29
27
| tx_hash_bytes |[u8; 32]| Transaction hash to sign |
30
28
31
29
### Public Inputs
@@ -35,7 +33,7 @@ These inputs are visible on-chain and used for verification:
35
33
| Input | Type | Description |
36
34
|-------|------|-------------|
37
35
| tx_hash_commitment | Field | Poseidon hash of tx_hash |
38
-
|merkle_root| Field |Root of authorized signers tree|
36
+
|commitment| Field |hash(secret, secret) - checked against signers list|
39
37
| nullifier | Field | Unique identifier to prevent double-signing |
40
38
41
39
## Step-by-Step Explanation
@@ -52,11 +50,11 @@ The circuit reconstructs Ethereum's `personal_sign` prefix `"\x19Ethereum Signed
52
50
53
51
**Why prefix?** Ethereum wallets always add this prefix when signing. We must match the exact message that was signed.
54
52
55
-
### Step 3: Verify Merkle Membership
53
+
### Step 3: Verify Commitment Ownership
56
54
57
-
The circuit computes commitment from secret using `commitment = hash(secret, secret)`, uses `leaf_index` and `merkle_path` to compute [Merkle](https://en.wikipedia.org/wiki/Merkle_tree) root, then compares with public `merkle_root`.
55
+
The circuit computes commitment from secret using `commitment = hash(secret, secret)`, then compares with public `commitment`.
58
56
59
-
**Privacy:** The circuit proves "I know a secret whose commitment is in the tree" without revealing which leaf.
57
+
**How authorization works:** The circuit proves "I know the secret for this commitment". Then the smart contract checks "Is this commitment in the signers list?" This two-step verification ensures only authorized signers can sign transactions.
60
58
61
59
### Step 4: Verify Nullifier
62
60
@@ -66,10 +64,6 @@ The circuit computes nullifier using `nullifier = hash(secret, tx_hash)` and com
66
64
67
65
## Helper Functions
68
66
69
-
### compute_merkle_root
70
-
71
-
This function converts `leaf_index` to bits (little-endian), then for each level, the bit determines left/right position: bit = 0 means current is left child, bit = 1 means current is right child. It hashes with sibling from `merkle_path` and repeats until reaching root.
72
-
73
67
### bytes_to_field
74
68
75
69
Converts 32-byte array to single Field element by treating bytes as big-endian number.
@@ -83,7 +77,7 @@ Wrapper for [Poseidon](https://www.poseidon-hash.info) hash with 2 inputs.
83
77
| Attack | Prevention |
84
78
|--------|------------|
85
79
| Fake signature | ECDSA verification in circuit |
86
-
| Non-member signing |Merkle membership proof|
80
+
| Non-member signing |Commitment checked against signers list on-chain|
Copy file name to clipboardExpand all lines: docs/zero-knowledge-implementation.md
+15-13Lines changed: 15 additions & 13 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,8 +15,8 @@ In a traditional multisig wallet:
15
15
- Signer addresses are public on blockchain
16
16
17
17
In PolyPay:
18
-
- You prove "I am an authorized signer" without revealing WHICH signer you are
19
-
-Only the proof is public, your identity stays private
18
+
- You prove "I know the secret for an authorized commitment" without revealing your EOA address
19
+
-Your Ethereum address stays private, only the commitment is visible
20
20
21
21
## The Four Proofs
22
22
@@ -44,18 +44,22 @@ When you sign a transaction in PolyPay, the ZK circuit proves four things simult
44
44
45
45
### Proof 3: "I am authorized"
46
46
47
-
**Problem:** How to prove you're in the signers list without revealing which one?
47
+
**Problem:** How to prove you're in the signers list?
48
48
49
49
**Solution:**
50
50
- Each signer has a secret "commitment" stored as: `commitment = hash(secret, secret)`
51
-
-All commitments form a [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree)
52
-
-You prove your commitment exists in the tree WITHOUT revealing which leaf
51
+
-The circuit proves you know the secret for a given commitment
52
+
-The smart contract checks if that commitment exists in the signers list
53
53
54
-
**Analogy:** Imagine a club membership list. You prove "my name is on the list" without pointing to which line.
54
+
**Analogy:** Imagine a club membership list. You prove "I know the password for one of these memberships" and the club verifies that membership is on the list.
55
55
56
-
**How Merkle Proof works:**
56
+
**How it works:**
57
57
58
-
You have a tree structure where your commitment is one of the leaves (A, B, C, or D). To prove membership, you provide sibling hashes along the path from your leaf to the root. The circuit computes the root from your path and checks it matches the public root.
58
+
The circuit verifies: `hash(secret, secret) == commitment`
59
+
60
+
Then the smart contract checks: `commitment in signers list?`
61
+
62
+
This two-step verification ensures only authorized signers can sign transactions while keeping their Ethereum addresses private.
59
63
60
64
### Proof 4: "I haven't signed before"
61
65
@@ -72,11 +76,11 @@ You have a tree structure where your commitment is one of the leaves (A, B, C, o
72
76
73
77
1.**User Signs:** User signs tx_hash with their Ethereum wallet → Produces signature, pub_key_x, pub_key_y
3.**Backend Verifies via zkVerify:** Proof submitted to [zkVerify](https://docs.zkverify.io) for verification → Returns aggregation_id, attestation
78
82
79
-
4.**Smart Contract Executes:** When threshold signatures reached, contract verifies all proofs on-chain, checks nullifiers not used, checks merkle_root matches current signers, then executes transaction
83
+
4.**Smart Contract Executes:** When threshold signatures reached, contract verifies all proofs on-chain, checks nullifiers not used, checks each commitment is in current signers list, then executes transaction
80
84
81
85
## Circuit Inputs Reference
82
86
@@ -88,16 +92,14 @@ You have a tree structure where your commitment is one of the leaves (A, B, C, o
0 commit comments