diff --git a/examples/vote/.gitignore b/examples/vote/.gitignore new file mode 100644 index 0000000000..b28696155d --- /dev/null +++ b/examples/vote/.gitignore @@ -0,0 +1,2 @@ +outputs/ +build/ diff --git a/examples/vote/README.md b/examples/vote/README.md new file mode 100644 index 0000000000..be944f965c --- /dev/null +++ b/examples/vote/README.md @@ -0,0 +1,93 @@ +# Leo Vote + +## Summary + +`vote.leo` is a general vote program. + +Anyone can propose new proposals, proposer can issue tickets to the voters, and the voter can vote without exposing privacy (with the help of Aleo's flexible privacy mechanism). + +Proposal information and statistical voting results is public, while the correlation between the voter and the vote is private(protected by zk). + +This example is inspired by the [aleo-vote](https://github.com/zkprivacy/aleo-vote) example written by the Aleo community. + +## Noteworthy Features + +mappings etc. + + + +## How to Run + +To compile this Leo program, run: +```bash +leo build +``` + +Make changes to `vote/inputs/vote.in` before running each command. + +### Propose + +Anyone can propose new proposals publicly by calling `propose` function. + +Run `propose`: + +``` +leo run propose +``` + +Output sample: + +``` + { + owner: aleo1kkk52quhnxgn2nfrcd9jqk7c9x27c23f2wvw7fyzcze56yahvcgszgttu2.private, + gates: 0u64.private, + id: 2805252584833208809872967597325381727971256629741137995614832105537063464740field.private, + info: { + title: 2077160157502449938194577302446444field.private, + content: 1452374294790018907888397545906607852827800436field.private, + proposer: aleo1kkk52quhnxgn2nfrcd9jqk7c9x27c23f2wvw7fyzcze56yahvcgszgttu2.private + }, + _nonce: 1639660347839832220966145410710039205878572956621820215177036061076060242021group.public +} +``` + +### Create Ticket + +Proposer can create new tickets for proposed proposals. + +Ticket is a record with `owner` and `pid`, it can be used to vote for the specific proposal - `pid`, and can only be used(voted) by the ticket `owner`. + +Run `new_ticket`: + +``` +aleo run new_ticket +``` + +Output sample: + +``` +{ + owner: aleo1kkk52quhnxgn2nfrcd9jqk7c9x27c23f2wvw7fyzcze56yahvcgszgttu2.private, + gates: 0u64.private, + pid: 2264670486490520844857553240576860973319410481267184439818180411609250173817field.private, + _nonce: 1637267040221574073903539416642641433705357302885235345311606754421919550724group.public +} +``` + +### Vote + +Ticket owner can use the ticket to vote `agree` / `disagree` with the specific proposal - `pid`. + +As the ticket record can be used as an input privately, the voter's privacy is protected by zk. + +Run `agree`: + +``` +leo run agree +``` + +Run `disagree`: + +``` +leo run disagree +``` diff --git a/examples/vote/inputs/vote.in b/examples/vote/inputs/vote.in new file mode 100644 index 0000000000..6b75b0b36a --- /dev/null +++ b/examples/vote/inputs/vote.in @@ -0,0 +1,27 @@ +// The program input for vote/src/main.leo +[propose] +info: ProposalInfo = ProposalInfo { + title: 2077160157502449938194577302446444field, + content: 1452374294790018907888397545906607852827800436field, + proposer: aleo1kkk52quhnxgn2nfrcd9jqk7c9x27c23f2wvw7fyzcze56yahvcgszgttu2, +}; + +[new_ticket] +pid: field = 2264670486490520844857553240576860973319410481267184439818180411609250173817field; +voter: address = aleo1kkk52quhnxgn2nfrcd9jqk7c9x27c23f2wvw7fyzcze56yahvcgszgttu2; + +[agree] +ticket: Ticket = Ticket { + owner: aleo1kkk52quhnxgn2nfrcd9jqk7c9x27c23f2wvw7fyzcze56yahvcgszgttu2, + gates: 0u64, + pid: 2264670486490520844857553240576860973319410481267184439818180411609250173817field, + _nonce: 1637267040221574073903539416642641433705357302885235345311606754421919550724group +}; + +[disagree] +ticket: Ticket = Ticket { + owner: aleo1kkk52quhnxgn2nfrcd9jqk7c9x27c23f2wvw7fyzcze56yahvcgszgttu2, + gates: 0u64, + pid: 2264670486490520844857553240576860973319410481267184439818180411609250173817field, + _nonce: 1637267040221574073903539416642641433705357302885235345311606754421919550724group +}; \ No newline at end of file diff --git a/examples/vote/program.json b/examples/vote/program.json new file mode 100644 index 0000000000..81ef8504d2 --- /dev/null +++ b/examples/vote/program.json @@ -0,0 +1,10 @@ +{ + "program": "vote.aleo", + "version": "0.0.0", + "description": "", + "development": { + "private_key": "APrivateKey1zkpBDEpwdFWqxe1NdB9fxZxiX9LahJ3CdqchSs7FGZVggNw", + "address": "aleo1kkk52quhnxgn2nfrcd9jqk7c9x27c23f2wvw7fyzcze56yahvcgszgttu2" + }, + "license": "MIT" +} diff --git a/examples/vote/run.sh b/examples/vote/run.sh new file mode 100755 index 0000000000..1d03126123 --- /dev/null +++ b/examples/vote/run.sh @@ -0,0 +1,9 @@ +# Build the Leo vote program. +( + leo build || exit +) + +# Run the `propose` program function +( + leo run propose || exit +) diff --git a/examples/vote/src/main.leo b/examples/vote/src/main.leo new file mode 100644 index 0000000000..47cc5f2be5 --- /dev/null +++ b/examples/vote/src/main.leo @@ -0,0 +1,96 @@ +// The 'vote.leo' program. + +// Proposal details +circuit ProposalInfo { + title: field, + content: field, + proposer: address, +} + +// Proposal record records proposal info publicly +record Proposal { + owner: address, + gates: u64, + id: field, // todo: make public + info: ProposalInfo, // todo make public +} + +// Save proposal info in public storage. +mapping proposals: field => ProposalInfo; + +// Privacy tickets to vote +record Ticket { + owner: address, + gates: u64, + pid: field, +} + +// Count the total tickets issued for each proposal +mapping tickets: field => u64; + +mapping agree_votes: field => u64; + +mapping disagree_votes: field => u64; + +// Propose a new proposal to vote on. +@program +function propose(public info: ProposalInfo) -> Proposal { + // Authenticate proposer. + console.assert_eq(self.caller, info.proposer); + + // Generate a new proposal id. + let id: field = BHP256::hash(info.title); + + // Finalize the proposal id. + finalize(id); + + // Return a new record for the proposal. + return Proposal { + owner: self.caller, + gates: 0u64, + id, + info, + }; +} +// Create a new proposal in the "tickets" mapping. +finalize propose(public id: field) { + increment(tickets, id, 0u64); +} + +// Create a new ticket to vote with. +@program +function new_ticket( + public pid: field, + public voter: address, +) -> Ticket { + // Finalize the proposal id for the ticket. + finalize(pid); + + return Ticket { + owner: voter, + gates: 0u64, + pid, + }; +} +// Create a new ticket on a proposal in the "tickets" mapping. +finalize new_ticket(public pid: field) { + increment(tickets, pid, 1u64); +} + +// Vote to agree with a proposal. +@program +function agree(ticket: Ticket) {// Privately cast the vote. + finalize(ticket.pid); +} +finalize agree(public pid: field) {// Publicly increment the number of agree votes. + increment(agree_votes, pid, 1u64); +} + +// Vote to disagree with a proposal. +@program +function disagree(ticket: Ticket) {// Privately cast the vote. + finalize(ticket.pid); +} +finalize disagree(pid: field) {// Publicly increment the number of disagree votes. + increment(disagree_votes, pid, 1u64); +} \ No newline at end of file