auth_xfer.rs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use darkfi_money_contract::{
  19. model::{Coin, MoneyTransferParamsV1},
  20. MoneyFunction,
  21. };
  22. use darkfi_sdk::{
  23. crypto::{ContractId, PublicKey, DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
  24. dark_tree::DarkLeaf,
  25. error::ContractError,
  26. msg,
  27. pasta::pallas,
  28. ContractCall,
  29. };
  30. use darkfi_serial::{deserialize, Encodable, WriteExt};
  31. use crate::{
  32. error::DaoError,
  33. model::{DaoAuthCall, DaoAuthMoneyTransferParams, DaoExecParams, VecAuthCallCommit},
  34. DaoFunction, DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS,
  35. DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS,
  36. };
  37. /// `get_metdata` function for `Dao::Exec`
  38. pub(crate) fn dao_authxfer_get_metadata(
  39. _cid: ContractId,
  40. call_idx: u32,
  41. calls: Vec<DarkLeaf<ContractCall>>,
  42. ) -> Result<Vec<u8>, ContractError> {
  43. let self_ = &calls[call_idx as usize];
  44. let self_params: DaoAuthMoneyTransferParams = deserialize(&self_.data.data[1..])?;
  45. let sibling_idx = call_idx + 1;
  46. let xfer_call = &calls[sibling_idx as usize].data;
  47. let xfer_params: MoneyTransferParamsV1 = deserialize(&xfer_call.data[1..])?;
  48. let parent_idx = calls[call_idx as usize].parent_index.unwrap();
  49. let exec_callnode = &calls[parent_idx];
  50. let exec_params: DaoExecParams = deserialize(&exec_callnode.data.data[1..])?;
  51. assert!(xfer_params.inputs.len() > 0);
  52. assert!(xfer_params.outputs.len() > 0);
  53. let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
  54. let signature_pubkeys: Vec<PublicKey> = vec![];
  55. for (output, enc_attrs) in xfer_params.outputs.iter().zip(self_params.enc_attrs.iter()) {
  56. let coin = output.coin;
  57. let (ephem_x, ephem_y) = enc_attrs.ephem_public.xy();
  58. zk_public_inputs.push((
  59. DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS.to_string(),
  60. vec![
  61. coin.inner(),
  62. ephem_x,
  63. ephem_y,
  64. enc_attrs.encrypted_values[0],
  65. enc_attrs.encrypted_values[1],
  66. enc_attrs.encrypted_values[2],
  67. enc_attrs.encrypted_values[3],
  68. enc_attrs.encrypted_values[4],
  69. ],
  70. ));
  71. }
  72. // This value should be the same for all inputs, as enforced in process_instruction() below.
  73. let input_user_data_enc = xfer_params.inputs[0].user_data_enc;
  74. // Also check the coin in the change output
  75. let last_coin = xfer_params.outputs.last().unwrap().coin;
  76. let (ephem_x, ephem_y) = self_params.dao_change_attrs.ephem_public.xy();
  77. zk_public_inputs.push((
  78. DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS.to_string(),
  79. vec![
  80. exec_params.proposal_bulla.inner(),
  81. input_user_data_enc,
  82. last_coin.inner(),
  83. DAO_CONTRACT_ID.inner(),
  84. exec_params.proposal_auth_calls.commit(),
  85. ephem_x,
  86. ephem_y,
  87. self_params.dao_change_attrs.encrypted_values[0],
  88. self_params.dao_change_attrs.encrypted_values[1],
  89. self_params.dao_change_attrs.encrypted_values[2],
  90. ],
  91. ));
  92. let mut metadata = vec![];
  93. zk_public_inputs.encode(&mut metadata)?;
  94. signature_pubkeys.encode(&mut metadata)?;
  95. Ok(metadata)
  96. }
  97. fn find_auth_in_parent(
  98. exec_callnode: &DarkLeaf<ContractCall>,
  99. proposal_auth_calls: Vec<DaoAuthCall>,
  100. self_call_idx: u32,
  101. ) -> Option<DaoAuthCall> {
  102. for (auth_call, child_idx) in
  103. proposal_auth_calls.into_iter().zip(exec_callnode.children_indexes.iter())
  104. {
  105. if *child_idx == self_call_idx as usize {
  106. return Some(auth_call)
  107. }
  108. }
  109. return None
  110. }
  111. /// `process_instruction` function for `Dao::Exec`
  112. pub(crate) fn dao_authxfer_process_instruction(
  113. _cid: ContractId,
  114. call_idx: u32,
  115. calls: Vec<DarkLeaf<ContractCall>>,
  116. ) -> Result<Vec<u8>, ContractError> {
  117. let sibling_idx = call_idx + 1;
  118. let xfer_call = &calls[sibling_idx as usize].data;
  119. ///////////////////////////////////////////////////
  120. // 1. Next call should be money transfer
  121. ///////////////////////////////////////////////////
  122. if xfer_call.contract_id != *MONEY_CONTRACT_ID {
  123. return Err(DaoError::AuthXferSiblingWrongContractId.into())
  124. }
  125. let xfer_call_function_code = xfer_call.data[0];
  126. if xfer_call_function_code != MoneyFunction::TransferV1 as u8 {
  127. return Err(DaoError::AuthXferSiblingWrongFunctionCode.into())
  128. }
  129. ///////////////////////////////////////////////////
  130. // 2. money::transfer() inputs should all have the same user_data
  131. ///////////////////////////////////////////////////
  132. let xfer_params: MoneyTransferParamsV1 = deserialize(&xfer_call.data[1..])?;
  133. assert!(xfer_params.inputs.len() > 0);
  134. // We need the last output to be the change
  135. assert!(xfer_params.outputs.len() > 1);
  136. // MoneyTransfer should all have the same user_data set.
  137. // We check this by ensuring that user_data_enc is also the same for all inputs.
  138. // This means using the same blinding factor for all input's user_data.
  139. let user_data_enc = xfer_params.inputs[0].user_data_enc;
  140. for input in &xfer_params.inputs[1..] {
  141. if input.user_data_enc != user_data_enc {
  142. msg!("[Dao::Exec] Error: Money inputs unmatched user_data_enc");
  143. return Err(DaoError::AuthXferNonMatchingEncInputUserData.into())
  144. }
  145. }
  146. ///////////////////////////////////////////////////
  147. // 3. Check the coins on transfer outputs match
  148. ///////////////////////////////////////////////////
  149. // Find this auth_call in the parent DAO::exec()
  150. let parent_idx = calls[call_idx as usize].parent_index.unwrap();
  151. let exec_callnode = &calls[parent_idx];
  152. let exec_params: DaoExecParams = deserialize(&exec_callnode.data.data[1..])?;
  153. let auth_call = find_auth_in_parent(&exec_callnode, exec_params.proposal_auth_calls, call_idx);
  154. if auth_call.is_none() {
  155. return Err(DaoError::AuthXferCallNotFoundInParent.into())
  156. }
  157. // Read the proposal auth data which should be Vec<CoinAttributes>
  158. let proposal_coins: Vec<Coin> = deserialize(&auth_call.unwrap().auth_data[..])?;
  159. // Check all the outputs except the last match
  160. // There is the additional DAO change output which is always last.
  161. let outs = xfer_params.outputs;
  162. if outs.len() != proposal_coins.len() + 1 {
  163. return Err(DaoError::AuthXferWrongNumberOutputs.into())
  164. }
  165. for (output, coin) in outs.iter().zip(proposal_coins.iter()) {
  166. if output.coin != *coin {
  167. return Err(DaoError::AuthXferWrongOutputCoin.into())
  168. }
  169. }
  170. ///////////////////////////////////////////////////
  171. // 4. Change belongs to the DAO
  172. ///////////////////////////////////////////////////
  173. // The last output is sent back to the DAO. This is verified inside ZK.
  174. // Also the public_key should match.
  175. // We do not need to check the amounts, since sum(input values) == sum(output values)
  176. // otherwise the money::transfer() call is invalid.
  177. let mut update_data = vec![];
  178. update_data.write_u8(DaoFunction::AuthMoneyTransfer as u8)?;
  179. Ok(update_data)
  180. }