diff --git a/Nargo.toml b/Nargo.toml index e6ba684..06e4e50 100644 --- a/Nargo.toml +++ b/Nargo.toml @@ -6,3 +6,4 @@ compiler_version = ">=0.18.0" [dependencies] aztec = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "aztec-packages-v0.62.0", directory = "noir-projects/aztec-nr/aztec" } +value_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="aztec-packages-v0.62.0", directory="noir-projects/aztec-nr/value-note" } \ No newline at end of file diff --git a/src/main.nr b/src/main.nr index 3001e9a..bddc948 100644 --- a/src/main.nr +++ b/src/main.nr @@ -1,19 +1,26 @@ mod test; +mod types; use dep::aztec::macros::aztec; #[aztec] contract EasyPrivateVoting { + use crate::types::shared_note::{SHARED_NOTE_LEN, SharedNote}; use dep::aztec::{ + encrypted_logs::encrypted_note_emission::encode_and_encrypt_note, keys::getters::get_public_keys, macros::{functions::{initializer, internal, private, public}, storage::storage}, + oracle::random::random, }; - use dep::aztec::prelude::{AztecAddress, Map, PublicMutable, SharedImmutable}; + use dep::aztec::prelude::{AztecAddress, Map, PrivateSet, PublicMutable, SharedImmutable}; + use dep::value_note::value_note::ValueNote; + #[storage] struct Storage { admin: PublicMutable, // admin can end vote tally: Map, Context>, // we will store candidate as key and number of votes as value vote_ended: PublicMutable, // vote_ended is boolean active_at_block: SharedImmutable, // when people can start voting + set: PrivateSet, } #[public] @@ -27,7 +34,7 @@ contract EasyPrivateVoting { #[private] // annotation to mark function as private and expose private context - fn cast_vote(candidate: Field) { + fn cast_vote(candidate: Field, bob: AztecAddress, charlie: AztecAddress) { let msg_sender_npk_m_hash = get_public_keys(context.msg_sender()).npk_m.hash(); let secret = context.request_nsk_app(msg_sender_npk_m_hash); // get secret key of caller of function @@ -36,6 +43,21 @@ contract EasyPrivateVoting { EasyPrivateVoting::at(context.this_address()).add_to_tally_public(candidate).enqueue( &mut context, ); + // + let mut shared_note = SharedNote::new(context.msg_sender(), bob, random()); + let owner_ovpk = get_public_keys(context.msg_sender()).ovpk_m; + + let n = storage.set.insert(&mut shared_note); + + n.emit(encode_and_encrypt_note( + &mut context, + owner_ovpk, + context.msg_sender(), + context.msg_sender(), + )); + + n.emit(encode_and_encrypt_note(&mut context, owner_ovpk, bob, context.msg_sender())); + n.emit(encode_and_encrypt_note(&mut context, owner_ovpk, charlie, context.msg_sender())); } #[public] diff --git a/src/test/first.nr b/src/test/first.nr index 18b2a0b..8fbc94f 100644 --- a/src/test/first.nr +++ b/src/test/first.nr @@ -1,12 +1,15 @@ use crate::test::utils; -use dep::aztec::test::{helpers::{cheatcodes, test_environment::TestEnvironment}}; -use dep::aztec::protocol_types::storage::map::derive_storage_slot_in_map; +use dep::aztec::hash::compute_secret_hash; use dep::aztec::note::note_getter::{MAX_NOTES_PER_PAGE, view_notes}; use dep::aztec::note::note_viewer_options::NoteViewerOptions; -use dep::aztec::hash::compute_secret_hash; +use dep::aztec::protocol_types::storage::map::derive_storage_slot_in_map; +use dep::aztec::test::helpers::{cheatcodes, test_environment::TestEnvironment}; -use dep::aztec::{oracle::{execution::{get_block_number, get_contract_address}, storage::storage_read}}; +use dep::aztec::oracle::{ + execution::{get_block_number, get_contract_address}, + storage::storage_read, +}; use crate::EasyPrivateVoting; @@ -60,11 +63,16 @@ unconstrained fn test_fail_end_vote_by_non_admin() { unconstrained fn test_cast_vote() { let (env, voting_contract_address, _) = utils::setup(); let alice = env.create_account(); + let bob = env.create_account(); + let charlie = env.create_account(); + env.impersonate(alice); let candidate = 1; env.advance_block_by(6); - EasyPrivateVoting::at(voting_contract_address).cast_vote(candidate).call(&mut env.private()); + EasyPrivateVoting::at(voting_contract_address).cast_vote(candidate, bob, charlie).call( + &mut env.private(), + ); // Read vote count from storage let block_number = get_block_number(); @@ -80,16 +88,21 @@ unconstrained fn test_cast_vote_with_separate_accounts() { let (env, voting_contract_address, _) = utils::setup(); let alice = env.create_account(); let bob = env.create_account(); + let charlie = env.create_account(); let candidate = 101; - + env.impersonate(alice); env.advance_block_by(1); - EasyPrivateVoting::at(voting_contract_address).cast_vote(candidate).call(&mut env.private()); + EasyPrivateVoting::at(voting_contract_address).cast_vote(candidate, bob, charlie).call( + &mut env.private(), + ); env.impersonate(bob); env.advance_block_by(1); - EasyPrivateVoting::at(voting_contract_address).cast_vote(candidate).call(&mut env.private()); + EasyPrivateVoting::at(voting_contract_address).cast_vote(candidate, bob, charlie).call( + &mut env.private(), + ); // Read vote count from storage let block_number = get_block_number(); @@ -104,14 +117,20 @@ unconstrained fn test_cast_vote_with_separate_accounts() { unconstrained fn test_fail_vote_twice() { let (env, voting_contract_address, _) = utils::setup(); let alice = env.create_account(); + let bob = env.create_account(); + let charlie = env.create_account(); let candidate = 101; - + env.impersonate(alice); env.advance_block_by(1); - EasyPrivateVoting::at(voting_contract_address).cast_vote(candidate).call(&mut env.private()); + EasyPrivateVoting::at(voting_contract_address).cast_vote(candidate, bob, charlie).call( + &mut env.private(), + ); // Vote again as alice env.advance_block_by(1); - EasyPrivateVoting::at(voting_contract_address).cast_vote(candidate).call(&mut env.private()); + EasyPrivateVoting::at(voting_contract_address).cast_vote(candidate, bob, charlie).call( + &mut env.private(), + ); } diff --git a/src/test/index.test.ts b/src/test/index.test.ts index 42bade6..08ee174 100644 --- a/src/test/index.test.ts +++ b/src/test/index.test.ts @@ -22,6 +22,7 @@ describe("Voting", () => { pxe = await setupSandbox(); wallets = await getInitialTestAccountsWallets(pxe); + accounts = wallets.map(w => w.getCompleteAddress()) }) @@ -63,20 +64,30 @@ describe("Voting", () => { it("It casts a vote", async () => { const candidate = new Fr(1) + const [deployerWallet, bobWallet, charlieWallet] = wallets; // using first account as deployer and second as bob + + const contract = await EasyPrivateVotingContract.deploy(deployerWallet, deployerWallet.getAddress()).send().deployed(); + const receipt = await contract.methods.cast_vote(candidate, bobWallet.getAddress(), charlieWallet.getAddress()).send().wait({ debug: true }); + + console.log(receipt); + const { visibleIncomingNotes, visibleOutgoingNotes } = receipt.debugInfo! + console.log("Visible Incoming Notes:"); + console.log(visibleIncomingNotes); + console.log("Visible Outgoing Notes:"); + console.log(visibleOutgoingNotes); - const contract = await EasyPrivateVotingContract.deploy(wallets[0], accounts[0].address).send().deployed(); - const tx = await contract.methods.cast_vote(candidate).send().wait(); let count = await contract.methods.get_vote(candidate).simulate(); expect(count).toBe(1n); }, 300_000) it("It should fail when trying to vote twice", async () => { const candidate = new Fr(1) + const [deployerWallet, bobWallet, charlieWallet] = wallets; // using first account as deployer and second as bob - const contract = await EasyPrivateVotingContract.deploy(wallets[0], accounts[0].address).send().deployed(); - await contract.methods.cast_vote(candidate).send().wait(); + const contract = await EasyPrivateVotingContract.deploy(deployerWallet, deployerWallet.getAddress()).send().deployed(); + await contract.methods.cast_vote(candidate, bobWallet.getAddress(), charlieWallet.getAddress()).send().wait(); - const secondVoteReceipt = await contract.methods.cast_vote(candidate).send().getReceipt(); + const secondVoteReceipt = await contract.methods.cast_vote(candidate, bobWallet.getAddress(), charlieWallet.getAddress()).send().getReceipt(); expect(secondVoteReceipt).toEqual( expect.objectContaining({ status: TxStatus.DROPPED, diff --git a/src/types.nr b/src/types.nr new file mode 100644 index 0000000..2ee7074 --- /dev/null +++ b/src/types.nr @@ -0,0 +1 @@ +mod shared_note; diff --git a/src/types/shared_note.nr b/src/types/shared_note.nr new file mode 100644 index 0000000..e2bf58e --- /dev/null +++ b/src/types/shared_note.nr @@ -0,0 +1,64 @@ +use dep::aztec::{ + context::PrivateContext, + macros::notes::note, + note::{ + note_header::NoteHeader, note_interface::NullifiableNote, + utils::compute_note_hash_for_nullify, + }, + prelude::AztecAddress, + protocol_types::{ + constants::GENERATOR_INDEX__NOTE_NULLIFIER, + hash::poseidon2_hash_with_separator, + traits::{Deserialize, Serialize}, + }, +}; + +/// @member alice: the user who shares the note +/// @member bob: the user who the note is shared with +/// @member shared_nullifier_key: the key used to nullify the note +#[note] +#[derive(Serialize, Deserialize)] +pub struct SharedNote { + alice: AztecAddress, // msg_sender + bob: AztecAddress, // inputted by msg_sender + shared_nullifier_key: Field, // oracle::random +} + +/// @notice Declare the length of the note. +global SHARED_NOTE_LEN: Field = 4; + +impl SharedNote { + pub fn new(alice: AztecAddress, bob: AztecAddress, shared_nullifier_key: Field) -> Self { + SharedNote { alice, bob, shared_nullifier_key, header: NoteHeader::empty() } + } +} + +impl Eq for SharedNote { + fn eq(self, other: SharedNote) -> bool { + self.alice.eq(other.alice) + & self.bob.eq(other.bob) + & self.shared_nullifier_key.eq(other.shared_nullifier_key) + } +} + +impl NullifiableNote for SharedNote { + fn compute_nullifier( + self, + context: &mut PrivateContext, + note_hash_for_nullify: Field, + ) -> Field { + // let note_hash_for_nullify = compute_note_hash_for_nullify(self); + poseidon2_hash_with_separator( + [self.shared_nullifier_key], + GENERATOR_INDEX__NOTE_NULLIFIER as Field, + ) + } + + unconstrained fn compute_nullifier_without_context(self) -> Field { + // let note_hash_for_nullify = compute_note_hash_for_nullify(self); + poseidon2_hash_with_separator( + [self.shared_nullifier_key], + GENERATOR_INDEX__NOTE_NULLIFIER as Field, + ) + } +}