Hi all, sharing the latest progress on zkDonation.
Over the past two weeks, we’ve laid the groundwork for zkDonation like UI design and prototyping smart contracts.
Here’s what we’ve been up to:
1. UI Design (Work in Progress)
The UI is shaping up to be intuitive and accessible, ensuring donors can navigate the platform with ease. We’re finishing the design and expect to complete this phase by the end of next week. Regardless of the completion of the design, we will start building the app.
2. Prototyped Smart Contract Logic
We’ve also made headway on the smart contract side of the project. Please refer to the code here. Our team has developed a prototype for the core smart contract logic that will power zkDonation. The code implements a zk donation system where donors can contribute amounts privately. It verifies donations without revealing sensitive details like the donation amount. The system uses a Merkle tree to track commitments (hashed values representing donation states) for each donor. Please note that this is a very primitive version of the work we will make, and we will keep progressing with the codes along the way.
export let ZkonProof_ = ZkProgram.Proof(mockZkProgram);
export class ZkonProof extends ZkonProof_ { }
class DonationPublicInput extends Struct({
donor: PublicKey,
oldCommitment: Field,
newCommitment: Field,
zkOnProof: ZkonProof,
}) { }
const DonationProgram = ZkProgram({
name: "DonationProgram",
publicInput: DonationPublicInput,
methods: {
proveDonation: {
privateInputs: [Field, Field, Field, Field, Field], // oldTotalAmount, oldRandomness, newDonationAmount, newTotalAmount, newRandomness
method: async (
input: DonationPublicInput,
oldTotalAmount: Field,
oldRandomness: Field,
newDonationAmount: Field,
newTotalAmount: Field,
newRandomness: Field
) => {
// Verify the old commitment
const computedOldCommitment = Poseidon.hash([oldTotalAmount, oldRandomness]);
computedOldCommitment.assertEquals(input.oldCommitment);
// Verify the new total amount
newTotalAmount.assertEquals(oldTotalAmount.add(newDonationAmount));
// Verify the new commitment
const computedNewCommitment = Poseidon.hash([newTotalAmount, newRandomness]);
computedNewCommitment.assertEquals(input.newCommitment);
// Ensure the new donation amount is positive
newDonationAmount.assertGreaterThan(Field(0));
// Verify the ZKON proof
await input.zkOnProof.verify();
},
},
},
});
class DonationProof extends Proof<DonationPublicInput, Void> {
static program = DonationProgram;
static publicInputType = DonationPublicInput;
static publicOutputType = Void;
}
class DonationEvent extends Struct({
donor: PublicKey,
}) { }
export class DonationZkApp extends SmartContract {
// TODO: this should be extended to incorporate various users and donations
@state(Field) treeRoot = State<Field>();
init() {
this.treeRoot.set(Field(0));
}
@method async donate(proof: DonationProof, witness: MerkleMapWitness) {
// Verify the ZK proof
proof.verify();
const input = proof.publicInput;
input.donor.assertEquals(this.sender.getUnconstrained());
const userKey = Poseidon.hash(input.donor.toFields());
const currentRoot = this.treeRoot.get();
this.treeRoot.get().assertEquals(currentRoot);
// Use the witness to verify the old commitment and compute the new root with the new commitment
const [computedRoot, key] = witness.computeRootAndKey(input.oldCommitment);
computedRoot.assertEquals(currentRoot);
key.assertEquals(userKey);
const [newRoot, _] = witness.computeRootAndKey(input.newCommitment);
this.treeRoot.set(newRoot);
// Event emission
this.emitEvent("donation", new DonationEvent({ donor: input.donor }));
}
}
Next Steps:
- Complete UI Design
- Implement IndexedMerkle tree to incorporate various users and donations
- Perform PoC with ZKON
- Develop frontend
Happy to receive feedback from the community.