Просмотр исходного кода

contract/money/fee: Force 1 input and 1 output, and use own ZK circuit.

parazyd 2 лет назад
Родитель
Сommit
ade30c9071

+ 112 - 0
src/contract/money/proof/fee_v1.zk

@@ -0,0 +1,112 @@
+k = 13;
+field = "pallas";
+
+constant "Fee_V1" {
+    EcFixedPointShort VALUE_COMMIT_VALUE,
+    EcFixedPoint VALUE_COMMIT_RANDOM,
+    EcFixedPointBase NULLIFIER_K,
+}
+
+witness "Fee_V1" {
+    # Secret key used to derive input's nullifier
+    Base input_secret,
+    # Input coin's leaf position in the Merkle tree of coins
+    Uint32 input_leaf_pos,
+    # Merkle path to the coin
+    MerklePath input_path,
+    # Secret key used to derive public key for the tx signature
+    Base signature_secret,
+    # Value of the input coin
+    Base input_value,
+    # Random blinding factor for the input value commitment
+    Scalar input_value_blind,
+    # Unique serial corresponding to the input coin
+    Base input_serial,
+    # Input coin's spend hook
+    Base input_spend_hook,
+    # Data passed from the input to the invoked contract
+    Base input_user_data,
+    # Blinding factor for the encrypted user_data
+    Base input_user_data_blind,
+    # Value of the output coin
+    Base output_value,
+    # Output coin's spend hook
+    Base output_spend_hook,
+    # Data passed from the output coin to the invoked contract
+    Base output_user_data,
+    # Random blinding factor for the output value commitment
+    Scalar output_value_blind,
+    # Unique serial corresponding to the output coin
+    Base output_serial,
+    # Token ID
+    Base token,
+    # Random blinding factor for the token ID
+    Base token_blind,
+
+}
+
+circuit "Fee_V1" {
+    nullifier = poseidon_hash(input_secret, input_serial);
+    constrain_instance(nullifier);
+
+    # Pedersen commitment for the input coin value
+    input_vcv = ec_mul_short(input_value, VALUE_COMMIT_VALUE);
+    input_vcr = ec_mul(input_value_blind, VALUE_COMMIT_RANDOM);
+    input_value_commit = ec_add(input_vcv, input_vcr);
+    constrain_instance(ec_get_x(input_value_commit));
+    constrain_instance(ec_get_y(input_value_commit));
+
+    # Commitment for the token ID
+    token_commit = poseidon_hash(token, token_blind);
+    constrain_instance(token_commit);
+
+    # Derive the input coin
+    pub = ec_mul_base(input_secret, NULLIFIER_K);
+    pub_x = ec_get_x(pub);
+    pub_y = ec_get_y(pub);
+    input_coin = poseidon_hash(
+        pub_x,
+        pub_y,
+        input_value,
+        token,
+        input_serial,
+        input_spend_hook,
+        input_user_data,
+    );
+
+    # Merkle root
+    root = merkle_root(input_leaf_pos, input_path, input_coin);
+    constrain_instance(root);
+
+    # Export user_data
+    user_data_enc = poseidon_hash(input_user_data, input_user_data_blind);
+    constrain_instance(user_data_enc);
+
+    # Reveal spend_hook
+    constrain_instance(input_spend_hook);
+
+    # Derive a public key for the signature and
+    # constrain its coordinates
+    signature_public = ec_mul_base(signature_secret, NULLIFIER_K);
+    constrain_instance(ec_get_x(signature_public));
+    constrain_instance(ec_get_y(signature_public));
+
+    # Derive output coin
+    output_coin = poseidon_hash(
+        pub_x,
+        pub_y,
+        output_value,
+        token,
+        output_serial,
+        output_spend_hook,
+        output_user_data,
+    );
+    constrain_instance(output_coin);
+
+    # Pedersen commitment for the output coin value
+    output_vcv = ec_mul_short(output_value, VALUE_COMMIT_VALUE);
+    output_vcr = ec_mul(output_value_blind, VALUE_COMMIT_RANDOM);
+    output_value_commit = ec_add(output_vcv, output_vcr);
+    constrain_instance(ec_get_x(output_value_commit));
+    constrain_instance(ec_get_y(output_value_commit));
+}

+ 2 - 0
src/contract/money/src/entrypoint.rs

@@ -120,12 +120,14 @@ fn init_contract(cid: ContractId, ix: &[u8]) -> ContractResult {
     // respective db functions. The special `zkas db` operations exist in
     // order to be able to verify the circuits being bundled and enforcing
     // a specific tree inside sled, and also creation of VerifyingKey.
+    let fee_v1_bincode = include_bytes!("../proof/fee_v1.zk.bin");
     let mint_v1_bincode = include_bytes!("../proof/mint_v1.zk.bin");
     let burn_v1_bincode = include_bytes!("../proof/burn_v1.zk.bin");
     let token_mint_v1_bincode = include_bytes!("../proof/token_mint_v1.zk.bin");
     let token_frz_v1_bincode = include_bytes!("../proof/token_freeze_v1.zk.bin");
 
     // For that, we use `zkas_db_set` and pass in the bincode.
+    zkas_db_set(&fee_v1_bincode[..])?;
     zkas_db_set(&mint_v1_bincode[..])?;
     zkas_db_set(&burn_v1_bincode[..])?;
     zkas_db_set(&token_mint_v1_bincode[..])?;

+ 67 - 112
src/contract/money/src/entrypoint/fee_v1.rs

@@ -35,8 +35,7 @@ use crate::{
     model::{MoneyFeeParamsV1, MoneyFeeUpdateV1},
     MoneyFunction, MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE,
     MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_INFO_TREE, MONEY_CONTRACT_LATEST_COIN_ROOT,
-    MONEY_CONTRACT_NULLIFIERS_TREE, MONEY_CONTRACT_TOTAL_FEES_PAID, MONEY_CONTRACT_ZKAS_BURN_NS_V1,
-    MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+    MONEY_CONTRACT_NULLIFIERS_TREE, MONEY_CONTRACT_TOTAL_FEES_PAID, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
 };
 
 /// `get_metadata` function for `Money::FeeV1`
@@ -53,44 +52,30 @@ pub(crate) fn money_fee_get_metadata_v1(
     // Public inputs for the ZK proofs we have to verify
     let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
     // Public keys for the transaction signatures we have to verify
-    let mut signature_pubkeys: Vec<PublicKey> = vec![];
-
-    // Grab the pedersen commitments and signature pubkeys from the
-    // anonymous inputs
-    for input in &params.inputs {
-        let value_coords = input.value_commit.to_affine().coordinates().unwrap();
-        let (sig_x, sig_y) = input.signature_public.xy();
-
-        // It is very important that these are in the same order as the
-        // `constrain_instance` calls in the zkas code.
-        // Otherwise verification will fail.
-        zk_public_inputs.push((
-            MONEY_CONTRACT_ZKAS_BURN_NS_V1.to_string(),
-            vec![
-                input.nullifier.inner(),
-                *value_coords.x(),
-                *value_coords.y(),
-                input.token_commit,
-                input.merkle_root.inner(),
-                input.user_data_enc,
-                input.spend_hook,
-                sig_x,
-                sig_y,
-            ],
-        ));
-
-        signature_pubkeys.push(input.signature_public);
-    }
-
-    // Grab the pedersen commitments from the anonymous outputs
-    for output in &params.outputs {
-        let value_coords = output.value_commit.to_affine().coordinates().unwrap();
-
-        zk_public_inputs.push((
-            MONEY_CONTRACT_ZKAS_MINT_NS_V1.to_string(),
-            vec![output.coin.inner(), *value_coords.x(), *value_coords.y(), output.token_commit],
-        ));
-    }
+    let signature_pubkeys: Vec<PublicKey> = vec![params.input.signature_public];
+
+    // Grab the Pedersen commitments and the signature pubkey from the params
+    let input_value_coords = params.input.value_commit.to_affine().coordinates().unwrap();
+    let output_value_coords = params.output.value_commit.to_affine().coordinates().unwrap();
+    let (sig_x, sig_y) = params.input.signature_public.xy();
+
+    zk_public_inputs.push((
+        MONEY_CONTRACT_ZKAS_FEE_NS_V1.to_string(),
+        vec![
+            params.input.nullifier.inner(),
+            *input_value_coords.x(),
+            *input_value_coords.y(),
+            params.input.token_commit,
+            params.input.merkle_root.inner(),
+            params.input.user_data_enc,
+            params.input.spend_hook,
+            sig_x,
+            sig_y,
+            params.output.coin.inner(),
+            *output_value_coords.x(),
+            *output_value_coords.y(),
+        ],
+    ));
 
     // Serialize everything gathered and return it
     let mut metadata = vec![];
@@ -110,13 +95,7 @@ pub(crate) fn money_fee_process_instruction_v1(
     let fee: u64 = deserialize(&self_.data.data[1..9])?;
     let params: MoneyFeeParamsV1 = deserialize(&self_.data.data[9..])?;
 
-    // We need at least one input, but we shouldn't require any outputs.
-    if params.inputs.is_empty() {
-        msg!("[FeeV1] Error: No inputs in the call");
-        return Err(MoneyError::FeeMissingInputs.into())
-    }
-
-    // Though, we should have some fee paid...
+    // We should have some fee paid...
     /* XXX:
     if fee == 0 {
         msg!("[FeeV1] Error: Paid fee is 0");
@@ -144,65 +123,47 @@ pub(crate) fn money_fee_process_instruction_v1(
     // ===================================
     // Perform the actual state transition
     // ===================================
+    if params.input.token_commit != native_token_commit {
+        msg!("[FeeV1] Error: Input token commitment is not the native token");
+        return Err(MoneyError::TokenMismatch.into())
+    }
 
-    // For anonymous inputs, we must gather all the new nullifiers that
-    // are introduced.
-    let mut new_nullifiers = Vec::with_capacity(params.inputs.len());
-    msg!("[FeeV1] Iterating over anonymous inputs");
-    for (i, input) in params.inputs.iter().enumerate() {
-        // Verify that the token commitment matches
-        if input.token_commit != native_token_commit {
-            msg!("[FeeV1] Error: Token commitment is not native token (input {})", i);
-            return Err(MoneyError::TokenMismatch.into())
-        }
-
-        // The spend hook must be zero.
-        if input.spend_hook != pallas::Base::ZERO {
-            msg!("[FeeV1] Error: Input spend hook is nonzero (input {})", i);
-            return Err(MoneyError::SpendHookNonZero.into())
-        }
-
-        // The Merkle root is used to know whether this is a coin that
-        // existed in a previous state.
-        if !db_contains_key(coin_roots_db, &serialize(&input.merkle_root))? {
-            msg!("[FeeV1] Error: Merkle root not found in previous state (input {})", i);
-            return Err(MoneyError::CoinMerkleRootNotFound.into())
-        }
+    // The spend hook must be zero.
+    if params.input.spend_hook != pallas::Base::ZERO {
+        msg!("[FeeV1] Error: Input spend hook is nonzero");
+        return Err(MoneyError::SpendHookNonZero.into())
+    }
 
-        // The nullifiers should not already exist. It is the double-spend protection.
-        if new_nullifiers.contains(&input.nullifier) ||
-            db_contains_key(nullifiers_db, &serialize(&input.nullifier))?
-        {
-            msg!("[FeeV1] Error: Duplicate nullifier found (input {})", i);
-            return Err(MoneyError::DuplicateNullifier.into())
-        }
+    // The Merkle root is used to know whether this is a coin that
+    // existed in a previous state.
+    if !db_contains_key(coin_roots_db, &serialize(&params.input.merkle_root))? {
+        msg!("[FeeV1] Error: Input Merkle root not found in previous state");
+        return Err(MoneyError::CoinMerkleRootNotFound.into())
+    }
 
-        // Append this new nullifier to seen nullifiers, and accumulate the value commitment.
-        new_nullifiers.push(input.nullifier);
-        valcom_total += input.value_commit;
+    // The nullifiers should not already exist. It is the double-spend protection.
+    if db_contains_key(nullifiers_db, &serialize(&params.input.nullifier))? {
+        msg!("[FeeV1] Error: Duplicate nullifier found");
+        return Err(MoneyError::DuplicateNullifier.into())
     }
 
-    // Newly created coins for this call are in the outputs. Here we gather them,
-    // and we also check that they haven't existed before.
-    let mut new_coins = Vec::with_capacity(params.outputs.len());
-    for (i, output) in params.outputs.iter().enumerate() {
-        // Verify that the token commitment matches
-        if output.token_commit != native_token_commit {
-            msg!("[FeeV1] Error: Token commitment is not native token (output {})", i);
-            return Err(MoneyError::TokenMismatch.into())
-        }
+    // Append this new nullifier to seen nullifiers, and accumulate the value commitment.
+    valcom_total += params.input.value_commit;
 
-        if new_coins.contains(&output.coin) || db_contains_key(coins_db, &serialize(&output.coin))?
-        {
-            msg!("[FeeV1] Error: Duplicate coin found (output {})", i);
-            return Err(MoneyError::DuplicateCoin.into())
-        }
+    // Verify that the token commitment matches
+    if params.output.token_commit != native_token_commit {
+        msg!("[FeeV1] Error: Output token commitment is not native token");
+        return Err(MoneyError::TokenMismatch.into())
+    }
 
-        // Append this new coin to seen coins, and subtract the value commitment
-        new_coins.push(output.coin);
-        valcom_total -= output.value_commit;
+    if db_contains_key(coins_db, &serialize(&params.output.coin))? {
+        msg!("[FeeV1] Error: Duplicate coin found");
+        return Err(MoneyError::DuplicateCoin.into())
     }
 
+    // Subtract the value commitment
+    valcom_total -= params.output.value_commit;
+
     // Now subtract the fee from the accumulator
     valcom_total -= pedersen_commitment_u64(fee, params.fee_value_blind);
 
@@ -219,7 +180,11 @@ pub(crate) fn money_fee_process_instruction_v1(
     paid_fee += fee;
 
     // At this point the state transition has passed, so we create a state update.
-    let update = MoneyFeeUpdateV1 { nullifiers: new_nullifiers, coins: new_coins, fee: paid_fee };
+    let update = MoneyFeeUpdateV1 {
+        nullifier: params.input.nullifier,
+        coin: params.output.coin,
+        fee: paid_fee,
+    };
     let mut update_data = vec![];
     update_data.write_u8(MoneyFunction::FeeV1 as u8)?;
     update.encode(&mut update_data)?;
@@ -238,27 +203,17 @@ pub(crate) fn money_fee_process_update_v1(
     let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
     let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
 
-    msg!("[FeeV1] Adding new nullifiers to the set");
-    for nullifier in &update.nullifiers {
-        db_set(nullifiers_db, &serialize(nullifier), &[])?;
-    }
-
-    msg!("[FeeV1] Adding new coins to the set");
-    for coin in &update.coins {
-        db_set(coins_db, &serialize(coin), &[])?;
-    }
+    db_set(info_db, MONEY_CONTRACT_TOTAL_FEES_PAID, &serialize(&update.fee))?;
+    db_set(nullifiers_db, &serialize(&update.nullifier), &[])?;
+    db_set(coins_db, &serialize(&update.coin), &[])?;
 
-    msg!("[FeeV1] Adding new coins to the Merkle tree");
-    let coins: Vec<_> = update.coins.iter().map(|x| MerkleNode::from(x.inner())).collect();
     merkle_add(
         info_db,
         coin_roots_db,
         MONEY_CONTRACT_LATEST_COIN_ROOT,
         MONEY_CONTRACT_COIN_MERKLE_TREE,
-        &coins,
+        &[MerkleNode::from(update.coin.inner())],
     )?;
 
-    db_set(info_db, MONEY_CONTRACT_TOTAL_FEES_PAID, &serialize(&update.fee))?;
-
     Ok(())
 }

+ 2 - 0
src/contract/money/src/lib.rs

@@ -82,6 +82,8 @@ pub const MONEY_CONTRACT_LATEST_COIN_ROOT: &[u8] = b"last_root";
 pub const MONEY_CONTRACT_FAUCET_PUBKEYS: &[u8] = b"faucet_pubkeys";
 pub const MONEY_CONTRACT_TOTAL_FEES_PAID: &[u8] = b"total_fees_paid";
 
+/// zkas fee circuit namespace
+pub const MONEY_CONTRACT_ZKAS_FEE_NS_V1: &str = "Fee_V1";
 /// zkas mint circuit namespace
 pub const MONEY_CONTRACT_ZKAS_MINT_NS_V1: &str = "Mint_V1";
 /// zkas burn circuit namespace

+ 7 - 7
src/contract/money/src/model.rs

@@ -167,10 +167,10 @@ pub struct ConsensusOutput {
 /// Parameters for `Money::Fee`
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct MoneyFeeParamsV1 {
-    /// Anonymous inputs
-    pub inputs: Vec<Input>,
+    /// Anonymous input
+    pub input: Input,
     /// Anonymous outputs
-    pub outputs: Vec<Output>,
+    pub output: Output,
     /// Fee value blind
     pub fee_value_blind: pallas::Scalar,
     /// Token ID blind
@@ -180,10 +180,10 @@ pub struct MoneyFeeParamsV1 {
 /// State update for `Money::Fee`
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct MoneyFeeUpdateV1 {
-    /// Revealed nullifiers
-    pub nullifiers: Vec<Nullifier>,
-    /// Minted coins
-    pub coins: Vec<Coin>,
+    /// Revealed nullifier
+    pub nullifier: Nullifier,
+    /// Minted coin
+    pub coin: Coin,
     /// Fee paid
     pub fee: u64,
 }