1use darkfi_money_contract::model::CoinAttributes;
20use darkfi_sdk::{
21 bridgetree,
22 bridgetree::Hashable,
23 crypto::{
24 note::ElGamalEncryptedNote,
25 pasta_prelude::*,
26 pedersen_commitment_u64, poseidon_hash,
27 smt::{PoseidonFp, SparseMerkleTree, StorageAdapter, SMT_FP_DEPTH},
28 util::fv_mod_fp_unsafe,
29 Blind, MerkleNode, PublicKey, SecretKey,
30 },
31 pasta::pallas,
32};
33use rand::rngs::OsRng;
34use tracing::debug;
35
36use darkfi::{
37 zk::{halo2::Value, Proof, ProvingKey, Witness, ZkCircuit},
38 zkas::ZkBinary,
39 ClientFailed, Result,
40};
41
42use crate::{
43 error::DaoError,
44 model::{Dao, DaoProposal, DaoVoteParams, DaoVoteParamsInput, VecAuthCallCommit},
45};
46
47pub struct DaoVoteInput {
48 pub secret: SecretKey,
49 pub note: darkfi_money_contract::client::MoneyNote,
50 pub leaf_position: bridgetree::Position,
51 pub merkle_path: Vec<MerkleNode>,
52}
53
54pub struct DaoVoteCall<'a, T: StorageAdapter<Value = pallas::Base>> {
56 pub money_null_smt:
57 &'a SparseMerkleTree<'a, SMT_FP_DEPTH, { SMT_FP_DEPTH + 1 }, pallas::Base, PoseidonFp, T>,
58 pub inputs: Vec<DaoVoteInput>,
59 pub vote_option: bool,
60 pub proposal: DaoProposal,
61 pub dao: Dao,
62 pub current_blockwindow: u64,
63}
64
65impl<T: StorageAdapter<Value = pallas::Base>> DaoVoteCall<'_, T> {
66 pub fn make(
67 self,
68 burn_zkbin: &ZkBinary,
69 burn_pk: &ProvingKey,
70 main_zkbin: &ZkBinary,
71 main_pk: &ProvingKey,
72 ) -> Result<(DaoVoteParams, Vec<Proof>, Vec<SecretKey>)> {
73 debug!(target: "contract::dao::client::vote", "make()");
74
75 if self.dao.to_bulla() != self.proposal.dao_bulla {
76 return Err(ClientFailed::VerifyError(DaoError::InvalidCalls.to_string()).into())
77 }
78 let proposal_bulla = self.proposal.to_bulla();
79
80 let mut proofs = vec![];
81 let mut signature_secrets = vec![];
82
83 let gov_token_blind = pallas::Base::random(&mut OsRng);
84
85 let mut inputs = vec![];
86 let mut all_vote_value = 0;
87 let mut all_vote_blind = pallas::Scalar::from(0);
88
89 let last_input_idx = self.inputs.len() - 1;
90 for (i, input) in self.inputs.into_iter().enumerate() {
91 let mut value_blind = pallas::Scalar::random(&mut OsRng);
97
98 if i == last_input_idx {
99 loop {
102 let av_blind = fv_mod_fp_unsafe(all_vote_blind + value_blind);
103
104 if av_blind.is_none().into() {
105 value_blind = pallas::Scalar::random(&mut OsRng);
106 continue
107 }
108
109 break
110 }
111 }
112
113 all_vote_value += input.note.value;
114 all_vote_blind += value_blind;
115
116 let signature_secret = SecretKey::random(&mut OsRng);
117 let signature_public = PublicKey::from_secret(signature_secret);
118
119 let note = input.note;
121 let leaf_pos: u64 = input.leaf_position.into();
122
123 let public_key = PublicKey::from_secret(input.secret);
124 let coin = CoinAttributes {
125 public_key,
126 value: note.value,
127 token_id: note.token_id,
128 spend_hook: note.spend_hook,
129 user_data: note.user_data,
130 blind: note.coin_blind,
131 }
132 .to_coin();
133 let nullifier = poseidon_hash([input.secret.inner(), coin.inner()]);
134
135 let smt_null_root = self.money_null_smt.root();
136 let smt_null_path = self.money_null_smt.prove_membership(&nullifier);
137 if !smt_null_path.verify(&smt_null_root, &pallas::Base::ZERO, &nullifier) {
138 return Err(
139 ClientFailed::VerifyError(DaoError::InvalidInputMerkleRoot.to_string()).into()
140 )
141 }
142
143 let prover_witnesses = vec![
144 Witness::Base(Value::known(input.secret.inner())),
145 Witness::Base(Value::known(pallas::Base::from(note.value))),
146 Witness::Base(Value::known(note.token_id.inner())),
147 Witness::Base(Value::known(pallas::Base::ZERO)),
148 Witness::Base(Value::known(pallas::Base::ZERO)),
149 Witness::Base(Value::known(note.coin_blind.inner())),
150 Witness::Base(Value::known(proposal_bulla.inner())),
151 Witness::Scalar(Value::known(value_blind)),
152 Witness::Base(Value::known(gov_token_blind)),
153 Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())),
154 Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
155 Witness::SparseMerklePath(Value::known(smt_null_path.path)),
156 Witness::Base(Value::known(signature_secret.inner())),
157 ];
158
159 let merkle_root = {
160 let position: u64 = input.leaf_position.into();
161 let mut current = MerkleNode::from(coin.inner());
162 for (level, sibling) in input.merkle_path.iter().enumerate() {
163 let level = level as u8;
164 current = if position & (1 << level) == 0 {
165 MerkleNode::combine(level.into(), ¤t, sibling)
166 } else {
167 MerkleNode::combine(level.into(), sibling, ¤t)
168 };
169 }
170 current
171 };
172
173 let token_commit = poseidon_hash([note.token_id.inner(), gov_token_blind]);
174 if note.token_id != self.dao.gov_token_id {
175 return Err(ClientFailed::InvalidTokenId(note.token_id.to_string()).into())
176 }
177
178 let vote_commit = pedersen_commitment_u64(note.value, Blind(value_blind));
179 let vote_commit_coords = vote_commit.to_affine().coordinates().unwrap();
180
181 let (sig_x, sig_y) = signature_public.xy();
182
183 let vote_nullifier =
184 poseidon_hash([nullifier, input.secret.inner(), proposal_bulla.inner()]);
185
186 let public_inputs = vec![
187 smt_null_root,
188 proposal_bulla.inner(),
189 vote_nullifier,
190 *vote_commit_coords.x(),
191 *vote_commit_coords.y(),
192 token_commit,
193 merkle_root.inner(),
194 sig_x,
195 sig_y,
196 ];
197
198 let circuit = ZkCircuit::new(prover_witnesses, burn_zkbin);
200 debug!(target: "contract::dao::client::vote", "input_proof Proof::create()");
201 let input_proof = Proof::create(burn_pk, &[circuit], &public_inputs, &mut OsRng)?;
202 proofs.push(input_proof);
203 signature_secrets.push(signature_secret);
204
205 let input = DaoVoteParamsInput {
206 vote_commit,
207 vote_nullifier: vote_nullifier.into(),
208 signature_public,
209 };
210 inputs.push(input);
211 }
212
213 let token_commit = poseidon_hash([self.dao.gov_token_id.inner(), gov_token_blind]);
214
215 let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit);
216 let dao_quorum = pallas::Base::from(self.dao.quorum);
217 let dao_early_exec_quorum = pallas::Base::from(self.dao.early_exec_quorum);
218 let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot);
219 let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
220 let (dao_notes_pub_x, dao_notes_pub_y) = self.dao.notes_public_key.xy();
221 let (dao_proposer_pub_x, dao_proposer_pub_y) = self.dao.proposer_public_key.xy();
222 let (dao_proposals_pub_x, dao_proposals_pub_y) = self.dao.proposals_public_key.xy();
223 let dao_votes_public_key = self.dao.votes_public_key.inner();
224 let (dao_exec_pub_x, dao_exec_pub_y) = self.dao.exec_public_key.xy();
225 let (dao_early_exec_pub_x, dao_early_exec_pub_y) = self.dao.early_exec_public_key.xy();
226
227 let vote_option = self.vote_option as u64;
228 if vote_option != 0 && vote_option != 1 {
229 return Err(ClientFailed::VerifyError(DaoError::VoteInputsEmpty.to_string()).into())
230 }
231
232 let yes_vote_blind = loop {
234 let blind = pallas::Scalar::random(&mut OsRng);
235 if fv_mod_fp_unsafe(blind).is_some().into() {
236 break blind
237 }
238 };
239 let yes_vote_commit =
240 pedersen_commitment_u64(vote_option * all_vote_value, Blind(yes_vote_blind));
241 let yes_vote_commit_coords = yes_vote_commit.to_affine().coordinates().unwrap();
242
243 let all_vote_commit = pedersen_commitment_u64(all_vote_value, Blind(all_vote_blind));
244 if all_vote_commit != inputs.iter().map(|i| i.vote_commit).sum() {
245 return Err(ClientFailed::VerifyError(DaoError::VoteCommitMismatch.to_string()).into())
246 }
247 let all_vote_commit_coords = all_vote_commit.to_affine().coordinates().unwrap();
248
249 let yes_vote_blind = Blind(fv_mod_fp_unsafe(yes_vote_blind).unwrap());
252 let all_vote_blind = Blind(fv_mod_fp_unsafe(all_vote_blind).unwrap());
253
254 let vote_option = pallas::Base::from(vote_option);
255 let all_vote_value_fp = pallas::Base::from(all_vote_value);
256 let ephem_secret = SecretKey::random(&mut OsRng);
257 let ephem_pubkey = PublicKey::from_secret(ephem_secret);
258 let (ephem_x, ephem_y) = ephem_pubkey.xy();
259
260 let current_blockwindow = pallas::Base::from(self.current_blockwindow);
261
262 let prover_witnesses = vec![
263 Witness::Base(Value::known(self.proposal.auth_calls.commit())),
265 Witness::Base(Value::known(pallas::Base::from(self.proposal.creation_blockwindow))),
266 Witness::Base(Value::known(pallas::Base::from(self.proposal.duration_blockwindows))),
267 Witness::Base(Value::known(self.proposal.user_data)),
268 Witness::Base(Value::known(self.proposal.blind.inner())),
269 Witness::Base(Value::known(dao_proposer_limit)),
271 Witness::Base(Value::known(dao_quorum)),
272 Witness::Base(Value::known(dao_early_exec_quorum)),
273 Witness::Base(Value::known(dao_approval_ratio_quot)),
274 Witness::Base(Value::known(dao_approval_ratio_base)),
275 Witness::Base(Value::known(self.dao.gov_token_id.inner())),
276 Witness::Base(Value::known(dao_notes_pub_x)),
277 Witness::Base(Value::known(dao_notes_pub_y)),
278 Witness::Base(Value::known(dao_proposer_pub_x)),
279 Witness::Base(Value::known(dao_proposer_pub_y)),
280 Witness::Base(Value::known(dao_proposals_pub_x)),
281 Witness::Base(Value::known(dao_proposals_pub_y)),
282 Witness::EcNiPoint(Value::known(dao_votes_public_key)),
283 Witness::Base(Value::known(dao_exec_pub_x)),
284 Witness::Base(Value::known(dao_exec_pub_y)),
285 Witness::Base(Value::known(dao_early_exec_pub_x)),
286 Witness::Base(Value::known(dao_early_exec_pub_y)),
287 Witness::Base(Value::known(self.dao.bulla_blind.inner())),
288 Witness::Base(Value::known(vote_option)),
290 Witness::Base(Value::known(yes_vote_blind.inner())),
291 Witness::Base(Value::known(all_vote_value_fp)),
293 Witness::Base(Value::known(all_vote_blind.inner())),
294 Witness::Base(Value::known(gov_token_blind)),
296 Witness::Base(Value::known(current_blockwindow)),
298 Witness::Base(Value::known(ephem_secret.inner())),
300 ];
301
302 let note = [vote_option, yes_vote_blind.inner(), all_vote_value_fp, all_vote_blind.inner()];
303 let enc_note =
304 ElGamalEncryptedNote::encrypt_unsafe(note, &ephem_secret, &self.dao.votes_public_key)?;
305
306 let public_inputs = vec![
307 token_commit,
308 proposal_bulla.inner(),
309 *yes_vote_commit_coords.x(),
310 *yes_vote_commit_coords.y(),
311 *all_vote_commit_coords.x(),
312 *all_vote_commit_coords.y(),
313 current_blockwindow,
314 ephem_x,
315 ephem_y,
316 enc_note.encrypted_values[0],
317 enc_note.encrypted_values[1],
318 enc_note.encrypted_values[2],
319 enc_note.encrypted_values[3],
320 ];
321
322 let circuit = ZkCircuit::new(prover_witnesses, main_zkbin);
324
325 debug!(target: "contract::dao::client::vote", "main_proof = Proof::create()");
326 let main_proof = Proof::create(main_pk, &[circuit], &public_inputs, &mut OsRng)?;
327 proofs.push(main_proof);
328
329 let params =
330 DaoVoteParams { token_commit, proposal_bulla, yes_vote_commit, note: enc_note, inputs };
331
332 Ok((params, proofs, signature_secrets))
333 }
334}