1use darkfi_money_contract::model::CoinAttributes;
20use darkfi_sdk::{
21 bridgetree,
22 bridgetree::Hashable,
23 crypto::{
24 note::AeadEncryptedNote,
25 pasta_prelude::*,
26 pedersen::pedersen_commitment_u64,
27 poseidon_hash,
28 smt::{PoseidonFp, SparseMerkleTree, StorageAdapter, SMT_FP_DEPTH},
29 Blind, FuncId, MerkleNode, PublicKey, ScalarBlind, SecretKey,
30 },
31 pasta::pallas,
32};
33use rand::rngs::OsRng;
34
35use darkfi::{
36 zk::{halo2::Value, Proof, ProvingKey, Witness, ZkCircuit},
37 zkas::ZkBinary,
38 ClientFailed, Result,
39};
40
41use crate::{
42 error::DaoError,
43 model::{Dao, DaoProposal, DaoProposeParams, DaoProposeParamsInput, VecAuthCallCommit},
44};
45
46pub struct DaoProposeStakeInput {
47 pub secret: SecretKey,
48 pub note: darkfi_money_contract::client::MoneyNote,
49 pub leaf_position: bridgetree::Position,
50 pub merkle_path: Vec<MerkleNode>,
51}
52
53pub struct DaoProposeCall<'a, T: StorageAdapter<Value = pallas::Base>> {
54 pub money_null_smt:
55 &'a SparseMerkleTree<'a, SMT_FP_DEPTH, { SMT_FP_DEPTH + 1 }, pallas::Base, PoseidonFp, T>,
56 pub inputs: Vec<DaoProposeStakeInput>,
57 pub proposal: DaoProposal,
58 pub dao: Dao,
59 pub dao_leaf_position: bridgetree::Position,
60 pub dao_merkle_path: Vec<MerkleNode>,
61 pub dao_merkle_root: MerkleNode,
62 pub signature_secret: SecretKey,
63}
64
65impl<T: StorageAdapter<Value = pallas::Base>> DaoProposeCall<'_, T> {
66 pub fn make(
67 self,
68 dao_proposer_secret_key: &SecretKey,
69 burn_zkbin: &ZkBinary,
70 burn_pk: &ProvingKey,
71 main_zkbin: &ZkBinary,
72 main_pk: &ProvingKey,
73 ) -> Result<(DaoProposeParams, Vec<Proof>)> {
74 let mut proofs = vec![];
75
76 let gov_token_blind = Blind::random(&mut OsRng);
77
78 let smt_null_root = self.money_null_smt.root();
79 let signature_public = PublicKey::from_secret(self.signature_secret);
80 let (sig_x, sig_y) = signature_public.xy();
81
82 let mut inputs = vec![];
83 let mut total_funds = 0;
84 let mut total_funds_blinds = ScalarBlind::ZERO;
85
86 for input in self.inputs {
87 let funds_blind = Blind::random(&mut OsRng);
88 total_funds += input.note.value;
89 total_funds_blinds += funds_blind;
90
91 let note = input.note;
93 let leaf_pos: u64 = input.leaf_position.into();
94
95 let public_key = PublicKey::from_secret(input.secret);
96 let coin = CoinAttributes {
97 public_key,
98 value: note.value,
99 token_id: note.token_id,
100 spend_hook: FuncId::none(),
101 user_data: pallas::Base::ZERO,
102 blind: note.coin_blind,
103 }
104 .to_coin();
105 let nullifier = poseidon_hash([input.secret.inner(), coin.inner()]);
106
107 let smt_null_path = self.money_null_smt.prove_membership(&nullifier);
108 if !smt_null_path.verify(&smt_null_root, &pallas::Base::ZERO, &nullifier) {
109 return Err(
110 ClientFailed::VerifyError(DaoError::InvalidInputMerkleRoot.to_string()).into()
111 )
112 }
113
114 let prover_witnesses = vec![
115 Witness::Base(Value::known(input.secret.inner())),
116 Witness::Base(Value::known(pallas::Base::from(note.value))),
117 Witness::Base(Value::known(note.token_id.inner())),
118 Witness::Base(Value::known(pallas::Base::ZERO)),
119 Witness::Base(Value::known(pallas::Base::ZERO)),
120 Witness::Base(Value::known(note.coin_blind.inner())),
121 Witness::Scalar(Value::known(funds_blind.inner())),
122 Witness::Base(Value::known(gov_token_blind.inner())),
123 Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())),
124 Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
125 Witness::SparseMerklePath(Value::known(smt_null_path.path)),
126 Witness::Base(Value::known(self.signature_secret.inner())),
127 ];
128
129 let merkle_coin_root = {
132 let position: u64 = input.leaf_position.into();
133 let mut current = MerkleNode::from(coin.inner());
134 for (level, sibling) in input.merkle_path.iter().enumerate() {
135 let level = level as u8;
136 current = if position & (1 << level) == 0 {
137 MerkleNode::combine(level.into(), ¤t, sibling)
138 } else {
139 MerkleNode::combine(level.into(), sibling, ¤t)
140 };
141 }
142 current
143 };
144
145 let token_commit = poseidon_hash([note.token_id.inner(), gov_token_blind.inner()]);
146 if note.token_id != self.dao.gov_token_id {
147 return Err(ClientFailed::InvalidTokenId(note.token_id.to_string()).into())
148 }
149
150 let value_commit = pedersen_commitment_u64(note.value, funds_blind);
151 let value_coords = value_commit.to_affine().coordinates().unwrap();
152
153 let public_inputs = vec![
154 smt_null_root,
155 *value_coords.x(),
156 *value_coords.y(),
157 token_commit,
158 merkle_coin_root.inner(),
159 sig_x,
160 sig_y,
161 ];
162 let circuit = ZkCircuit::new(prover_witnesses, burn_zkbin);
164
165 let proving_key = &burn_pk;
166 let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)?;
167 proofs.push(input_proof);
168
169 let input = DaoProposeParamsInput {
170 value_commit,
171 merkle_coin_root,
172 smt_null_root,
173 signature_public,
174 };
175 inputs.push(input);
176 }
177
178 let total_funds_commit = pedersen_commitment_u64(total_funds, total_funds_blinds);
179 let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap();
180 let total_funds = pallas::Base::from(total_funds);
181
182 let token_commit = poseidon_hash([self.dao.gov_token_id.inner(), gov_token_blind.inner()]);
183
184 let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit);
185 let dao_quorum = pallas::Base::from(self.dao.quorum);
186 let dao_early_exec_quorum = pallas::Base::from(self.dao.early_exec_quorum);
187 let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot);
188 let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
189 let (dao_notes_pub_x, dao_notes_pub_y) = self.dao.notes_public_key.xy();
190 let (dao_proposals_pub_x, dao_proposals_pub_y) = self.dao.proposals_public_key.xy();
191 let (dao_votes_pub_x, dao_votes_pub_y) = self.dao.votes_public_key.xy();
192 let (dao_exec_pub_x, dao_exec_pub_y) = self.dao.exec_public_key.xy();
193 let (dao_early_exec_pub_x, dao_early_exec_pub_y) = self.dao.early_exec_public_key.xy();
194
195 let dao_leaf_position: u64 = self.dao_leaf_position.into();
196
197 if self.dao.to_bulla() != self.proposal.dao_bulla {
198 return Err(ClientFailed::VerifyError(DaoError::InvalidCalls.to_string()).into())
199 }
200 let proposal_bulla = self.proposal.to_bulla();
201
202 let prover_witnesses = vec![
203 Witness::Base(Value::known(total_funds)),
205 Witness::Scalar(Value::known(total_funds_blinds.inner())),
206 Witness::Base(Value::known(gov_token_blind.inner())),
208 Witness::Base(Value::known(self.proposal.auth_calls.commit())),
210 Witness::Base(Value::known(pallas::Base::from(self.proposal.creation_blockwindow))),
211 Witness::Base(Value::known(pallas::Base::from(self.proposal.duration_blockwindows))),
212 Witness::Base(Value::known(self.proposal.user_data)),
213 Witness::Base(Value::known(self.proposal.blind.inner())),
214 Witness::Base(Value::known(dao_proposer_limit)),
216 Witness::Base(Value::known(dao_quorum)),
217 Witness::Base(Value::known(dao_early_exec_quorum)),
218 Witness::Base(Value::known(dao_approval_ratio_quot)),
219 Witness::Base(Value::known(dao_approval_ratio_base)),
220 Witness::Base(Value::known(self.dao.gov_token_id.inner())),
221 Witness::Base(Value::known(dao_notes_pub_x)),
222 Witness::Base(Value::known(dao_notes_pub_y)),
223 Witness::Base(Value::known(dao_proposer_secret_key.inner())),
224 Witness::Base(Value::known(dao_proposals_pub_x)),
225 Witness::Base(Value::known(dao_proposals_pub_y)),
226 Witness::Base(Value::known(dao_votes_pub_x)),
227 Witness::Base(Value::known(dao_votes_pub_y)),
228 Witness::Base(Value::known(dao_exec_pub_x)),
229 Witness::Base(Value::known(dao_exec_pub_y)),
230 Witness::Base(Value::known(dao_early_exec_pub_x)),
231 Witness::Base(Value::known(dao_early_exec_pub_y)),
232 Witness::Base(Value::known(self.dao.bulla_blind.inner())),
233 Witness::Uint32(Value::known(dao_leaf_position.try_into().unwrap())),
234 Witness::MerklePath(Value::known(self.dao_merkle_path.try_into().unwrap())),
235 ];
236 let public_inputs = vec![
237 token_commit,
238 self.dao_merkle_root.inner(),
239 proposal_bulla.inner(),
240 pallas::Base::from(self.proposal.creation_blockwindow),
241 *total_funds_coords.x(),
242 *total_funds_coords.y(),
243 ];
244 let circuit = ZkCircuit::new(prover_witnesses, main_zkbin);
246
247 let main_proof = Proof::create(main_pk, &[circuit], &public_inputs, &mut OsRng)?;
248 proofs.push(main_proof);
249
250 let enc_note =
251 AeadEncryptedNote::encrypt(&self.proposal, &self.dao.proposals_public_key, &mut OsRng)
252 .unwrap();
253 let params = DaoProposeParams {
254 dao_merkle_root: self.dao_merkle_root,
255 proposal_bulla,
256 token_commit,
257 note: enc_note,
258 inputs,
259 };
260
261 Ok((params, proofs))
262 }
263}