Переглянути джерело

dao: auth_xfer add verifiable encryption for all coins produced by money::transfer()

zero 2 роки тому
батько
коміт
7d80e22ba8

+ 85 - 0
src/contract/dao/proof/dao-auth-money-transfer-enc-coin.zk

@@ -0,0 +1,85 @@
+k = 13;
+field = "pallas";
+
+constant "DaoAuthMoneyTransferEncCoin" {
+    EcFixedPointShort VALUE_COMMIT_VALUE,
+    EcFixedPoint VALUE_COMMIT_RANDOM,
+    EcFixedPointBase NULLIFIER_K,
+}
+
+witness "DaoAuthMoneyTransferEncCoin" {
+    # Coin attributes
+    EcNiPoint public_key,
+    Base value,
+    Base token_id,
+    Base serial,
+    Base spend_hook,
+    Base user_data,
+
+    # Epehemeral secret used for diffie-hellman shared secret derivation
+    Base ephem_secret,
+}
+
+circuit "DaoAuthMoneyTransferEncCoin" {
+    # UGLY HACK -----------------
+    # (otherwise zkas refuses to compile)
+    ONE = witness_base(1);
+    pubkey = ec_mul_var_base(ONE, public_key);
+    # ---------------------------
+
+    coin = poseidon_hash(
+        ec_get_x(pubkey),
+        ec_get_y(pubkey),
+        value,
+        token_id,
+        serial,
+        spend_hook,
+        user_data,
+    );
+    constrain_instance(coin);
+
+    # Let e be the ephem_secret and P = dG be the public key.
+    # Then E = eG is the ephem_public.
+    ephem_public = ec_mul_base(ephem_secret, NULLIFIER_K);
+    constrain_instance(ec_get_x(ephem_public));
+    constrain_instance(ec_get_y(ephem_public));
+
+    # The shared_point C = eP = dE
+    shared_point = ec_mul_var_base(ephem_secret, public_key);
+    shared_secret = poseidon_hash(
+        ec_get_x(shared_point),
+        ec_get_y(shared_point),
+    );
+
+    # Now encrypt the coin attributes
+
+    const_1 = witness_base(1);
+    const_2 = witness_base(2);
+    const_3 = witness_base(3);
+    const_4 = witness_base(4);
+
+    # Each blinding value must be used only once otherwise they
+    # could be calculated.
+
+    # We can skip the public_key since it's inferred by the receiver
+
+    enc_value = base_add(value, shared_secret);
+    constrain_instance(enc_value);
+
+    shared_secret_1 = poseidon_hash(shared_secret, const_1);
+    enc_token_id = base_add(token_id, shared_secret_1);
+    constrain_instance(enc_token_id);
+
+    shared_secret_2 = poseidon_hash(shared_secret, const_2);
+    enc_serial = base_add(serial, shared_secret_2);
+    constrain_instance(enc_serial);
+
+    shared_secret_3 = poseidon_hash(shared_secret, const_3);
+    enc_spend_hook = base_add(spend_hook, shared_secret_3);
+    constrain_instance(enc_spend_hook);
+
+    shared_secret_4 = poseidon_hash(shared_secret, const_4);
+    enc_user_data = base_add(user_data, shared_secret_4);
+    constrain_instance(enc_user_data);
+}
+

+ 79 - 3
src/contract/dao/src/client/auth_xfer.rs

@@ -18,7 +18,7 @@
 
 use darkfi_money_contract::model::CoinAttributes;
 use darkfi_sdk::{
-    crypto::{poseidon_hash, DAO_CONTRACT_ID},
+    crypto::{pasta_prelude::*, poseidon_hash, util::mod_r_p, PublicKey, DAO_CONTRACT_ID},
     pasta::pallas,
 };
 
@@ -30,10 +30,13 @@ use darkfi::{
     Result,
 };
 
-use crate::model::{Dao, DaoAuthMoneyTransferParams, DaoProposal, VecAuthCallCommit};
+use crate::model::{
+    Dao, DaoAuthCoinAttrs, DaoAuthMoneyTransferParams, DaoProposal, VecAuthCallCommit,
+};
 
 pub struct DaoAuthMoneyTransferCall {
     pub proposal: DaoProposal,
+    pub proposal_coinattrs: Vec<CoinAttributes>,
     pub dao: Dao,
     pub input_user_data_blind: pallas::Base,
     pub dao_coin_attrs: CoinAttributes,
@@ -44,9 +47,82 @@ impl DaoAuthMoneyTransferCall {
         self,
         auth_xfer_zkbin: &ZkBinary,
         auth_xfer_pk: &ProvingKey,
+        auth_xfer_enc_coin_zkbin: &ZkBinary,
+        auth_xfer_enc_coin_pk: &ProvingKey,
     ) -> Result<(DaoAuthMoneyTransferParams, Vec<Proof>)> {
         let mut proofs = vec![];
-        let params = DaoAuthMoneyTransferParams {};
+
+        // Proof for each coin of verifiable encryption
+
+        let mut enc_attrs = vec![];
+        let mut proposal_coinattrs = self.proposal_coinattrs;
+        proposal_coinattrs.push(self.dao_coin_attrs.clone());
+        for coin_attrs in proposal_coinattrs {
+            let coin = coin_attrs.to_coin();
+
+            let ephem_secret = pallas::Base::random(&mut OsRng);
+            let ephem_pubkey = PublicKey::from_secret(ephem_secret.into());
+            let (ephem_x, ephem_y) = ephem_pubkey.xy();
+
+            let public_key = coin_attrs.public_key.inner();
+            let value_base = pallas::Base::from(coin_attrs.value);
+
+            let shared_point = public_key * mod_r_p(ephem_secret);
+            let shared_point_coords = shared_point.to_affine().coordinates().unwrap();
+            let (shared_point_x, shared_point_y) =
+                (*shared_point_coords.x(), *shared_point_coords.y());
+            let shared_secret = poseidon_hash([shared_point_x, shared_point_y]);
+            let enc_value = value_base + shared_secret;
+
+            let enc_token_id =
+                coin_attrs.token_id.inner() + poseidon_hash([shared_secret, pallas::Base::from(1)]);
+            let enc_serial =
+                coin_attrs.serial + poseidon_hash([shared_secret, pallas::Base::from(2)]);
+            let enc_spend_hook =
+                coin_attrs.spend_hook + poseidon_hash([shared_secret, pallas::Base::from(3)]);
+            let enc_user_data =
+                coin_attrs.user_data + poseidon_hash([shared_secret, pallas::Base::from(4)]);
+
+            let prover_witnesses = vec![
+                Witness::EcNiPoint(Value::known(public_key)),
+                Witness::Base(Value::known(value_base)),
+                Witness::Base(Value::known(coin_attrs.token_id.inner())),
+                Witness::Base(Value::known(coin_attrs.serial)),
+                Witness::Base(Value::known(coin_attrs.spend_hook)),
+                Witness::Base(Value::known(coin_attrs.user_data)),
+                Witness::Base(Value::known(ephem_secret)),
+            ];
+
+            let public_inputs = vec![
+                coin.inner(),
+                ephem_x,
+                ephem_y,
+                enc_value,
+                enc_token_id,
+                enc_serial,
+                enc_spend_hook,
+                enc_user_data,
+            ];
+
+            let circuit = ZkCircuit::new(prover_witnesses, auth_xfer_enc_coin_zkbin);
+            let proof =
+                Proof::create(auth_xfer_enc_coin_pk, &[circuit], &public_inputs, &mut OsRng)
+                    .expect("DAO::exec() proving error!)");
+            proofs.push(proof);
+
+            enc_attrs.push(DaoAuthCoinAttrs {
+                value: enc_value,
+                token_id: enc_token_id,
+                serial: enc_serial,
+                spend_hook: enc_spend_hook,
+                user_data: enc_user_data,
+                ephem_pubkey,
+            });
+        }
+
+        // Build the main proof
+
+        let params = DaoAuthMoneyTransferParams { enc_attrs };
 
         let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit);
         let dao_quorum = pallas::Base::from(self.dao.quorum);

+ 29 - 6
src/contract/dao/src/entrypoint/auth_xfer.rs

@@ -32,8 +32,9 @@ use darkfi_serial::{deserialize, Encodable, WriteExt};
 
 use crate::{
     error::DaoError,
-    model::{DaoAuthCall, DaoExecParams, VecAuthCallCommit},
-    DaoFunction, DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS,
+    model::{DaoAuthCall, DaoAuthMoneyTransferParams, DaoExecParams, VecAuthCallCommit},
+    DaoFunction, DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS,
+    DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS,
 };
 
 /// `get_metdata` function for `Dao::Exec`
@@ -42,6 +43,9 @@ pub(crate) fn dao_authxfer_get_metadata(
     call_idx: u32,
     calls: Vec<DarkLeaf<ContractCall>>,
 ) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let self_params: DaoAuthMoneyTransferParams = deserialize(&self_.data.data[1..])?;
+
     let sibling_idx = call_idx + 1;
     let xfer_call = &calls[sibling_idx as usize].data;
     let xfer_params: MoneyTransferParamsV1 = deserialize(&xfer_call.data[1..])?;
@@ -51,16 +55,35 @@ pub(crate) fn dao_authxfer_get_metadata(
     let exec_params: DaoExecParams = deserialize(&exec_callnode.data.data[1..])?;
 
     assert!(xfer_params.inputs.len() > 0);
+    assert!(xfer_params.outputs.len() > 0);
+
+    let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
+    let signature_pubkeys: Vec<PublicKey> = vec![];
+
+    for (output, attrs) in xfer_params.outputs.iter().zip(self_params.enc_attrs.iter()) {
+        let coin = output.coin;
+        let (ephem_x, ephem_y) = attrs.ephem_pubkey.xy();
+        zk_public_inputs.push((
+            DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS.to_string(),
+            vec![
+                coin.inner(),
+                ephem_x,
+                ephem_y,
+                attrs.value,
+                attrs.token_id,
+                attrs.serial,
+                attrs.spend_hook,
+                attrs.user_data,
+            ],
+        ));
+    }
+
     // This value should be the same for all inputs, as enforced in process_instruction() below.
     let input_user_data_enc = xfer_params.inputs[0].user_data_enc;
 
-    assert!(xfer_params.outputs.len() > 0);
     // Also check the coin in the change output
     let last_coin = xfer_params.outputs.last().unwrap().coin;
 
-    let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
-    let signature_pubkeys: Vec<PublicKey> = vec![];
-
     zk_public_inputs.push((
         DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS.to_string(),
         vec![

+ 3 - 0
src/contract/dao/src/lib.rs

@@ -86,6 +86,9 @@ pub const DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS: &str = "DaoProposeMain";
 pub const DAO_CONTRACT_ZKAS_DAO_EXEC_NS: &str = "DaoExec";
 /// zkas dao auth money_transfer circuit namespace
 pub const DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS: &str = "DaoAuthMoneyTransfer";
+/// zkas dao auth money_transfer encrypted coin circuit namespace
+pub const DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS: &str =
+    "DaoAuthMoneyTransferEncCoin";
 
 const SLOT_TIME: u64 = 90;
 const SECS_IN_DAY: u64 = 24 * 60 * 60;

+ 14 - 1
src/contract/dao/src/model.rs

@@ -341,6 +341,19 @@ pub struct DaoExecUpdate {
     pub proposal_bulla: DaoProposalBulla,
 }
 
+#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
+pub struct DaoAuthCoinAttrs {
+    pub value: pallas::Base,
+    pub token_id: pallas::Base,
+    pub serial: pallas::Base,
+    pub spend_hook: pallas::Base,
+    pub user_data: pallas::Base,
+
+    pub ephem_pubkey: PublicKey,
+}
+
 /// Parameters for `Dao::AuthMoneyTransfer`
 #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
-pub struct DaoAuthMoneyTransferParams {}
+pub struct DaoAuthMoneyTransferParams {
+    pub enc_attrs: Vec<DaoAuthCoinAttrs>,
+}

+ 14 - 4
src/contract/test-harness/src/dao_exec.rs

@@ -22,7 +22,8 @@ use darkfi::{tx::Transaction, Result};
 use darkfi_dao_contract::{
     client::{DaoAuthMoneyTransferCall, DaoExecCall},
     model::{Dao, DaoBulla, DaoExecParams, DaoProposal},
-    DaoFunction, DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS, DAO_CONTRACT_ZKAS_DAO_EXEC_NS,
+    DaoFunction, DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS,
+    DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS, DAO_CONTRACT_ZKAS_DAO_EXEC_NS,
 };
 use darkfi_money_contract::{
     client::transfer_v1 as xfer,
@@ -68,6 +69,10 @@ impl TestHarness {
             .proving_keys
             .get(&DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS.to_string())
             .unwrap();
+        let (dao_auth_xfer_enc_coin_pk, dao_auth_xfer_enc_coin_zkbin) = self
+            .proving_keys
+            .get(&DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS.to_string())
+            .unwrap();
 
         let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoExec).unwrap();
         let timer = Instant::now();
@@ -105,7 +110,7 @@ impl TestHarness {
         }
 
         let mut outputs = vec![];
-        for coin_attr in proposal_coinattrs {
+        for coin_attr in proposal_coinattrs.clone() {
             assert_eq!(proposal_token_id, coin_attr.token_id);
             outputs.push(coin_attr);
         }
@@ -177,12 +182,17 @@ impl TestHarness {
         // Auth module
         let auth_xfer_builder = DaoAuthMoneyTransferCall {
             proposal: proposal.clone(),
+            proposal_coinattrs,
             dao: dao.clone(),
             input_user_data_blind,
             dao_coin_attrs,
         };
-        let (auth_xfer_params, auth_xfer_proofs) =
-            auth_xfer_builder.make(dao_auth_xfer_zkbin, dao_auth_xfer_pk)?;
+        let (auth_xfer_params, auth_xfer_proofs) = auth_xfer_builder.make(
+            dao_auth_xfer_zkbin,
+            dao_auth_xfer_pk,
+            dao_auth_xfer_enc_coin_zkbin,
+            dao_auth_xfer_enc_coin_pk,
+        )?;
         let mut data = vec![DaoFunction::AuthMoneyTransfer as u8];
         auth_xfer_params.encode(&mut data)?;
         let auth_xfer_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };

+ 6 - 3
src/contract/test-harness/src/vks.rs

@@ -30,6 +30,7 @@ use darkfi::{
     Result,
 };
 use darkfi_dao_contract::{
+    DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS,
     DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS, DAO_CONTRACT_ZKAS_DAO_EXEC_NS,
     DAO_CONTRACT_ZKAS_DAO_MINT_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS,
     DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS,
@@ -49,8 +50,8 @@ use darkfi_serial::{deserialize, serialize};
 use log::debug;
 
 /// Update this if any circuits are changed
-const VKS_HASH: &str = "3571f82a2ff9f05dda4a280b6100aae77fc743f8dc2d6aa02c885ccc2a8f14c3";
-const PKS_HASH: &str = "f3399d23e4b917b2156e140227dac5ee5ba3977064ec2132954636441c4fbfb7";
+const VKS_HASH: &str = "8936e8806f8b05af04a5cbdd5dba446a7cb07ea46a9bcd0f2d7db6ff1c28da66";
+const PKS_HASH: &str = "4ca45a982ecc5e1689a95a6f2d341c61efd38a4b1ef6178eb6aecf9b8bf122a1";
 
 fn pks_path(typ: &str) -> Result<PathBuf> {
     let output = Command::new("git").arg("rev-parse").arg("--show-toplevel").output()?.stdout;
@@ -129,6 +130,7 @@ pub fn read_or_gen_vks_and_pks() -> Result<(Pks, Vks)> {
         &include_bytes!("../../dao/proof/dao-vote-main.zk.bin")[..],
         &include_bytes!("../../dao/proof/dao-exec.zk.bin")[..],
         &include_bytes!("../../dao/proof/dao-auth-money-transfer.zk.bin")[..],
+        &include_bytes!("../../dao/proof/dao-auth-money-transfer-enc-coin.zk.bin")[..],
         // Consensus
         &include_bytes!("../../consensus/proof/consensus_burn_v1.zk.bin")[..],
         &include_bytes!("../../consensus/proof/consensus_mint_v1.zk.bin")[..],
@@ -214,7 +216,8 @@ pub fn inject(sled_db: &sled::Db, vks: &Vks) -> Result<()> {
             DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS |
             DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS |
             DAO_CONTRACT_ZKAS_DAO_EXEC_NS |
-            DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS => {
+            DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS |
+            DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS => {
                 let key = serialize(&namespace.as_str());
                 let value = serialize(&(bincode.clone(), vk.clone()));
                 dao_zkas_tree.insert(key, value)?;