I named this system zkgate.fun, aiming to leverage the features of zero-knowledge proofs combined with blockchain to create a small utility.
The main function is to allow users to prove that they belong to a certain group, without revealing their real (on-chain) identity.
Here’s the current design idea: the administrator first has a list of names, which can be an array of Ethereum addresses. Based on this address list, a Merkle Root Hash is computed.
Then, this root hash is submitted to a smart contract.
People included in this list can use a proving key from a Circom circuit to generate a zk proof for themselves, and then submit the zk proof to the smart contract.
On the smart contract side, the verifier.sol
generated by the Circom circuit will be used to verify the received zk proof, determine whether the address used to generate the zk proof is included in the Merkle Root Hash, and finally return the result of the verification.
In this way, the administrator doesn’t need to reveal which addresses are in the group, and addresses that belong to the group don’t need to declare their identity. They just need to submit a zk proof generated by the zero-knowledge proof system to prove that they indeed belong to the group.
Next, I will proceed to implement this design from a technical perspective.
There is an existing zk protocol supported by the Ethereum Foundation, with a relatively mature toolchain and ecosystem, also designed for identity verification. It’s called Semaphore. You can try out the demo with a frontend interface directly on their official website:
In the first two iterations of zkgate.fun, I did not choose to use Semaphore’s EdDSA account system solution, mainly because I did not want to deviate from Ethereum’s account system, nor did I want to abandon ECDSA. However, in reality, only EdDSA is zk-friendly. It supports signing with Poseidon Hash, allowing signature verification directly within the zk circuit, eliminating the need for the awkward approach of “off-chain signature, on-chain recover.”
I have to admit, from a personal learning perspective, although it has only been a few days, I have already roughly understood the operation process of zk (toolchain). From the perspective of cutting-edge industry technology, it’s impossible for me to do better than Semaphore with just my personal efforts. Even if zkgate.fun were to further develop a frontend interface to visually demonstrate the specific interaction process, at most it would look like Semaphore’s Demo, and technically it would not be as sophisticated as Semaphore.
Therefore, the zkgate.fun project will no longer continue development. The domain will automatically expire after one year and will not be renewed.
This version addresses the issue of verifying address ownership. The basic idea is to separate zk proof from the ownership proof. Off-chain, zk is used to prove the path of the address in the Merkle Root. On-chain, users need to submit a signature of the root signed with their private key, and the signature is then submitted to the blockchain. The contract recovers the address from the signature and compares it with the address information contained in the zk circuit proof.
1. zk proof includes address information -> On-chain verification of zk proof -> Obtain address information from zk proof
2. Sign the root with private key -> Obtain signature on-chain -> Recover address from the signature
3. Check if the address in zk proof == address recovered from signature
Specific code changes in the demo:
inputs.json
already includes the key information in inputs.At this point, the functionality implemented by zkgate.fun allows group admins to avoid publicly disclosing their group member information on-chain. They only need to submit the Merkle Root Hash. For group members, with access to the full member list and a signature from their private key for their address, they can generate a zk proof to verify on-chain that they are indeed part of the group.
In this process, zk hides only one piece of information: the complete list of group members does not need to be publicly disclosed on-chain—only a Merkle Root Hash is required. However, the user’s address cannot be hidden for now, as it must be submitted on-chain for verification.
First, let’s correct a previous design mistake: administrators must publicly share their group address list. Otherwise, it’s impossible to generate a Merkle Tree based on the address list, and users won’t be able to locate their address within the tree structure or generate a path proof.
Secondly, I’m happy to report that a very basic demo is now working (smallyunet/zkgate-demo). While this demo is quite rudimentary and doesn’t yet verify address ownership within the circuit, it does demonstrate a functional toolchain.
Here’s how the implementation works:
If an address is not in the group list, two scenarios are possible:
Currently, the biggest flaw in this initial demo is that the proof is built using plaintext addresses, such as:
const members = [
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
];
const proofKey = toField(members[0]);
const { siblings } = await tree.find(proofKey);
This code instructs the zk circuit to check whether members[0]
is part of the Merkle tree constructed from the members
array — and of course, it is. To construct a proof for a non-member address, one simply needs to replace the proofKey
:
const nonMemberAddress = "0x1234567890123456789012345678901234567890";
const proofKey = toField(nonMemberAddress);
const { siblings } = await tree.find(proofKey);
In other words, the members
list must be public. Right now, the program can only verify whether an address is in the members
array. But even if members[0]
is not my address, I can still construct a valid proof with it. So what’s the point of zk?
The next step is to have users sign a message with their private key, then use the zk circuit to recover the address from the signature, and finally check whether the recovered address is in the members
array.
Sounds simple, right? But in practice, recovering an address from an ECDSA signature within a zk circuit is not only extremely complex—it’s like trying to build a nuclear reactor out of LEGO. No wonder people say working with zk makes your hair fall out.