diff --git a/src/contract/consensus/proof/proposal_mint_v1.zk b/src/contract/consensus/proof/proposal_mint_v1.zk index 7774e1188..b26d27c3e 100644 --- a/src/contract/consensus/proof/proposal_mint_v1.zk +++ b/src/contract/consensus/proof/proposal_mint_v1.zk @@ -27,6 +27,8 @@ witness "ProposalMint_V1" { Scalar value_blind, # Random blinding factor for the token ID Scalar token_blind, + # Random blinding factor for the serial number + Scalar serial_blind, } circuit "ProposalMint_V1" { @@ -63,10 +65,19 @@ circuit "ProposalMint_V1" { tcv = ec_mul_base(token, NULLIFIER_K); tcr = ec_mul(token_blind, VALUE_COMMIT_RANDOM); token_commit = ec_add(tcv, tcr); - # Since token_commit is also a curve point, we'll do the same + # Since token commit is also a curve point, we'll do the same # coordinate dance: constrain_instance(ec_get_x(token_commit)); constrain_instance(ec_get_y(token_commit)); + # Pedersen commitment for coin's serial number + scv = ec_mul_base(serial, NULLIFIER_K); + scr = ec_mul(serial_blind, VALUE_COMMIT_RANDOM); + serial_commit = ec_add(scv, scr); + # Since serial commit is also a curve point, we'll do the same + # coordinate dance: + constrain_instance(ec_get_x(serial_commit)); + constrain_instance(ec_get_y(serial_commit)); + # At this point we've enforced all of our public inputs. } diff --git a/src/contract/consensus/src/client/proposal_v1.rs b/src/contract/consensus/src/client/proposal_v1.rs index 7aec28d4b..c841c0f9a 100644 --- a/src/contract/consensus/src/client/proposal_v1.rs +++ b/src/contract/consensus/src/client/proposal_v1.rs @@ -26,7 +26,7 @@ use darkfi::{ }; use darkfi_money_contract::{ client::{MoneyNote, OwnCoin}, - model::{ConsensusStakeParamsV1, ConsensusUnstakeParamsV1, Input, Output, StakeInput}, + model::{ConsensusUnstakeParamsV1, Input, Output, StakeInput}, }; use darkfi_sdk::{ crypto::{ @@ -42,24 +42,21 @@ use rand::rngs::OsRng; use crate::{ client::{ - stake_v1::{ - ConsensusMintRevealed, TransactionBuilderOutputInfo as StakeTBOI, - TransactionBuilderOutputInfo, - }, + stake_v1::{TransactionBuilderOutputInfo as StakeTBOI, TransactionBuilderOutputInfo}, unstake_v1::{create_unstake_burn_proof, TransactionBuilderInputInfo as UnstakeTBII}, }, model::{ - ConsensusRewardParamsV1, HEADSTART, MU_RHO_PREFIX, MU_Y_PREFIX, REWARD, REWARD_PALLAS, - SEED_PREFIX, SERIAL_PREFIX, ZERO, + ConsensusProposalMintParamsV1, ConsensusProposalRewardParamsV1, HEADSTART, MU_RHO_PREFIX, + MU_Y_PREFIX, REWARD, REWARD_PALLAS, SEED_PREFIX, SERIAL_PREFIX, ZERO, }, }; pub struct ConsensusProposalCallDebris { pub unstake_params: ConsensusUnstakeParamsV1, pub unstake_proofs: Vec, - pub reward_params: ConsensusRewardParamsV1, + pub reward_params: ConsensusProposalRewardParamsV1, pub reward_proofs: Vec, - pub stake_params: ConsensusStakeParamsV1, + pub stake_params: ConsensusProposalMintParamsV1, pub stake_proofs: Vec, pub signature_secret: SecretKey, } @@ -100,6 +97,33 @@ impl ConsensusProposalRewardRevealed { } } +pub struct ConsensusProposalMintRevealed { + pub coin: Coin, + pub value_commit: pallas::Point, + pub token_commit: pallas::Point, + pub serial_commit: pallas::Point, +} + +impl ConsensusProposalMintRevealed { + pub fn to_vec(&self) -> Vec { + let valcom_coords = self.value_commit.to_affine().coordinates().unwrap(); + let tokcom_coords = self.token_commit.to_affine().coordinates().unwrap(); + let sercom_coords = self.serial_commit.to_affine().coordinates().unwrap(); + + // NOTE: It's important to keep these in the same order + // as the `constrain_instance` calls in the zkas code. + vec![ + self.coin.inner(), + *valcom_coords.x(), + *valcom_coords.y(), + *tokcom_coords.x(), + *tokcom_coords.y(), + *sercom_coords.x(), + *sercom_coords.y(), + ] + } +} + /// Struct holding necessary information to build a proposal transaction. pub struct ConsensusProposalCallBuilder { /// `OwnCoin` we're given to use in this builder @@ -189,6 +213,7 @@ impl ConsensusProposalCallBuilder { let spend_hook = CONSENSUS_CONTRACT_ID.inner(); let user_data = pallas::Base::random(&mut OsRng); let coin_blind = pallas::Base::random(&mut OsRng); + let serial_blind = pallas::Scalar::random(&mut OsRng); info!("Creating stake mint proof for output for proposal"); let (proof, public_inputs, serial) = create_proposal_mint_proof( @@ -197,6 +222,7 @@ impl ConsensusProposalCallBuilder { &output, value_blind, token_blind, + serial_blind, burnt_secret_key, burnt_serial, spend_hook, @@ -235,7 +261,12 @@ impl ConsensusProposalCallBuilder { }; // We now fill this with necessary stuff - let stake_params = ConsensusStakeParamsV1 { input: input.clone(), output: output.clone() }; + let serial_commit = public_inputs.serial_commit; + let stake_params = ConsensusProposalMintParamsV1 { + input: input.clone(), + output: output.clone(), + serial_commit, + }; let stake_proofs = vec![proof]; let stake_input = input; @@ -257,7 +288,7 @@ impl ConsensusProposalCallBuilder { let y = public_inputs.y; let rho = public_inputs.rho; let reward_params = - ConsensusRewardParamsV1 { unstake_input, stake_input, output, slot, y, rho }; + ConsensusProposalRewardParamsV1 { unstake_input, stake_input, output, slot, y, rho }; let reward_proofs = vec![proof]; // Now we should have all the params, zk proofs and signature secret. @@ -334,15 +365,17 @@ pub fn create_proposal_mint_proof( output: &TransactionBuilderOutputInfo, value_blind: pallas::Scalar, token_blind: pallas::Scalar, + serial_blind: pallas::Scalar, burnt_secret_key: pallas::Base, burnt_serial: pallas::Base, spend_hook: pallas::Base, user_data: pallas::Base, coin_blind: pallas::Base, -) -> Result<(Proof, ConsensusMintRevealed, pallas::Base)> { +) -> Result<(Proof, ConsensusProposalMintRevealed, pallas::Base)> { let serial = poseidon_hash([SERIAL_PREFIX, burnt_secret_key, burnt_serial, ZERO]); let value_commit = pedersen_commitment_u64(output.value, value_blind); let token_commit = pedersen_commitment_base(output.token_id.inner(), token_blind); + let serial_commit = pedersen_commitment_base(serial, serial_blind); let (pub_x, pub_y) = output.public_key.xy(); let coin = Coin::from(poseidon_hash([ @@ -356,7 +389,8 @@ pub fn create_proposal_mint_proof( coin_blind, ])); - let public_inputs = ConsensusMintRevealed { coin, value_commit, token_commit }; + let public_inputs = + ConsensusProposalMintRevealed { coin, value_commit, token_commit, serial_commit }; let prover_witnesses = vec![ Witness::Base(Value::known(pub_x)), @@ -370,6 +404,7 @@ pub fn create_proposal_mint_proof( Witness::Base(Value::known(user_data)), Witness::Scalar(Value::known(value_blind)), Witness::Scalar(Value::known(token_blind)), + Witness::Scalar(Value::known(serial_blind)), ]; let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone()); diff --git a/src/contract/consensus/src/entrypoint.rs b/src/contract/consensus/src/entrypoint.rs index c4177c3f0..a3574aa98 100644 --- a/src/contract/consensus/src/entrypoint.rs +++ b/src/contract/consensus/src/entrypoint.rs @@ -32,7 +32,7 @@ use darkfi_sdk::{ }; use darkfi_serial::{deserialize, serialize, Encodable, WriteExt}; -use crate::{model::ConsensusRewardUpdateV1, ConsensusFunction}; +use crate::{model::ConsensusProposalRewardUpdateV1, ConsensusFunction}; /// `Consensus::Stake` functions mod stake_v1; @@ -235,7 +235,7 @@ fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult { Ok(consensus_proposal_burn_process_update_v1(cid, update)?) } ConsensusFunction::ProposalRewardV1 => { - let update: ConsensusRewardUpdateV1 = deserialize(&update_data[1..])?; + let update: ConsensusProposalRewardUpdateV1 = deserialize(&update_data[1..])?; Ok(consensus_proposal_reward_process_update_v1(cid, update)?) } ConsensusFunction::ProposalMintV1 => { diff --git a/src/contract/consensus/src/entrypoint/proposal_burn_v1.rs b/src/contract/consensus/src/entrypoint/proposal_burn_v1.rs index 35f49068d..fc6b67789 100644 --- a/src/contract/consensus/src/entrypoint/proposal_burn_v1.rs +++ b/src/contract/consensus/src/entrypoint/proposal_burn_v1.rs @@ -36,7 +36,7 @@ use darkfi_sdk::{ use darkfi_serial::{deserialize, serialize, Encodable, WriteExt}; use crate::{ - model::{ConsensusRewardParamsV1, ZERO}, + model::{ConsensusProposalRewardParamsV1, ZERO}, ConsensusFunction, }; @@ -160,7 +160,7 @@ pub(crate) fn consensus_proposal_burn_process_instruction_v1( } // Verify next call StakeInput is the same as this calls input - let next_params: ConsensusRewardParamsV1 = deserialize(&next.data[1..])?; + let next_params: ConsensusProposalRewardParamsV1 = deserialize(&next.data[1..])?; if input != &next_params.unstake_input { msg!("[ConsensusProposalBurnV1] Error: Next call input mismatch"); return Err(MoneyError::NextCallInputMissmatch.into()) diff --git a/src/contract/consensus/src/entrypoint/proposal_mint_v1.rs b/src/contract/consensus/src/entrypoint/proposal_mint_v1.rs index b4d20f557..7cd21dbe8 100644 --- a/src/contract/consensus/src/entrypoint/proposal_mint_v1.rs +++ b/src/contract/consensus/src/entrypoint/proposal_mint_v1.rs @@ -17,11 +17,10 @@ */ use darkfi_money_contract::{ - error::MoneyError, - model::{ConsensusStakeParamsV1, ConsensusStakeUpdateV1}, - CONSENSUS_CONTRACT_COINS_TREE, CONSENSUS_CONTRACT_COIN_MERKLE_TREE, - CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_INFO_TREE, - CONSENSUS_CONTRACT_NULLIFIERS_TREE, CONSENSUS_CONTRACT_ZKAS_PROPOSAL_MINT_NS_V1, + error::MoneyError, model::ConsensusStakeUpdateV1, CONSENSUS_CONTRACT_COINS_TREE, + CONSENSUS_CONTRACT_COIN_MERKLE_TREE, CONSENSUS_CONTRACT_COIN_ROOTS_TREE, + CONSENSUS_CONTRACT_INFO_TREE, CONSENSUS_CONTRACT_NULLIFIERS_TREE, + CONSENSUS_CONTRACT_ZKAS_PROPOSAL_MINT_NS_V1, }; use darkfi_sdk::{ crypto::{ @@ -37,7 +36,7 @@ use darkfi_sdk::{ use darkfi_serial::{deserialize, serialize, Encodable, WriteExt}; use crate::{ - model::{ConsensusRewardParamsV1, ZERO}, + model::{ConsensusProposalMintParamsV1, ConsensusProposalRewardParamsV1, ZERO}, ConsensusFunction, }; @@ -48,7 +47,7 @@ pub(crate) fn consensus_proposal_mint_get_metadata_v1( calls: Vec, ) -> Result, ContractError> { let self_ = &calls[call_idx as usize]; - let params: ConsensusStakeParamsV1 = deserialize(&self_.data[1..])?; + let params: ConsensusProposalMintParamsV1 = deserialize(&self_.data[1..])?; // Public inputs for the ZK proofs we have to verify let mut zk_public_inputs: Vec<(String, Vec)> = vec![]; @@ -59,6 +58,7 @@ pub(crate) fn consensus_proposal_mint_get_metadata_v1( let output = ¶ms.output; let value_coords = output.value_commit.to_affine().coordinates().unwrap(); let token_coords = output.token_commit.to_affine().coordinates().unwrap(); + let serial_coords = ¶ms.serial_commit.to_affine().coordinates().unwrap(); zk_public_inputs.push(( CONSENSUS_CONTRACT_ZKAS_PROPOSAL_MINT_NS_V1.to_string(), @@ -68,6 +68,8 @@ pub(crate) fn consensus_proposal_mint_get_metadata_v1( *value_coords.y(), *token_coords.x(), *token_coords.y(), + *serial_coords.x(), + *serial_coords.y(), ], )); @@ -88,7 +90,7 @@ pub(crate) fn consensus_proposal_mint_process_instruction_v1( calls: Vec, ) -> Result, ContractError> { let self_ = &calls[call_idx as usize]; - let params: ConsensusStakeParamsV1 = deserialize(&self_.data[1..])?; + let params: ConsensusProposalMintParamsV1 = deserialize(&self_.data[1..])?; // Access the necessary databases where there is information to // validate this state transition. @@ -149,7 +151,7 @@ pub(crate) fn consensus_proposal_mint_process_instruction_v1( } // Verify previous call input is the same as this calls StakeInput - let previous_params: ConsensusRewardParamsV1 = deserialize(&previous.data[1..])?; + let previous_params: ConsensusProposalRewardParamsV1 = deserialize(&previous.data[1..])?; let previous_input = &previous_params.stake_input; if &previous_input != &input { msg!("[ConsensusProposalMintV1] Error: Previous call input mismatch"); diff --git a/src/contract/consensus/src/entrypoint/proposal_reward_v1.rs b/src/contract/consensus/src/entrypoint/proposal_reward_v1.rs index 71f22d163..7261798c4 100644 --- a/src/contract/consensus/src/entrypoint/proposal_reward_v1.rs +++ b/src/contract/consensus/src/entrypoint/proposal_reward_v1.rs @@ -17,8 +17,7 @@ */ use darkfi_money_contract::{ - error::MoneyError, - model::{ConsensusStakeParamsV1, ConsensusUnstakeParamsV1}, + error::MoneyError, model::ConsensusUnstakeParamsV1, CONSENSUS_CONTRACT_ZKAS_PROPOSAL_REWARD_NS_V1, }; use darkfi_sdk::{ @@ -37,8 +36,9 @@ use darkfi_serial::{deserialize, Encodable, WriteExt}; use crate::{ error::ConsensusError, model::{ - ConsensusRewardParamsV1, ConsensusRewardUpdateV1, SlotCheckpoint, HEADSTART, MU_RHO_PREFIX, - MU_Y_PREFIX, REWARD, ZERO, + ConsensusProposalMintParamsV1, ConsensusProposalRewardParamsV1, + ConsensusProposalRewardUpdateV1, SlotCheckpoint, HEADSTART, MU_RHO_PREFIX, MU_Y_PREFIX, + REWARD, ZERO, }, ConsensusFunction, }; @@ -50,7 +50,7 @@ pub(crate) fn consensus_proposal_reward_get_metadata_v1( calls: Vec, ) -> Result, ContractError> { let self_ = &calls[call_idx as usize]; - let params: ConsensusRewardParamsV1 = deserialize(&self_.data[1..])?; + let params: ConsensusProposalRewardParamsV1 = deserialize(&self_.data[1..])?; // Public inputs for the ZK proofs we have to verify let mut zk_public_inputs: Vec<(String, Vec)> = vec![]; @@ -121,7 +121,7 @@ pub(crate) fn consensus_proposal_reward_process_instruction_v1( calls: Vec, ) -> Result, ContractError> { let self_ = &calls[call_idx as usize]; - let params: ConsensusRewardParamsV1 = deserialize(&self_.data[1..])?; + let params: ConsensusProposalRewardParamsV1 = deserialize(&self_.data[1..])?; // =================================== // Perform the actual state transition @@ -205,7 +205,7 @@ pub(crate) fn consensus_proposal_reward_process_instruction_v1( } // Verify next call StakeInput is the same as this calls input - let next_params: ConsensusStakeParamsV1 = deserialize(&next.data[1..])?; + let next_params: ConsensusProposalMintParamsV1 = deserialize(&next.data[1..])?; if stake_input != &next_params.input { msg!("[ConsensusProposalRewardV1] Error: Next call input mismatch"); return Err(MoneyError::NextCallInputMissmatch.into()) @@ -216,7 +216,7 @@ pub(crate) fn consensus_proposal_reward_process_instruction_v1( } // Create a state update. - let update = ConsensusRewardUpdateV1 {}; + let update = ConsensusProposalRewardUpdateV1 {}; let mut update_data = vec![]; update_data.write_u8(ConsensusFunction::ProposalRewardV1 as u8)?; update.encode(&mut update_data)?; @@ -227,7 +227,7 @@ pub(crate) fn consensus_proposal_reward_process_instruction_v1( /// `process_update` function for `Consensus::ProposalRewardV1` pub(crate) fn consensus_proposal_reward_process_update_v1( _cid: ContractId, - _update: ConsensusRewardUpdateV1, + _update: ConsensusProposalRewardUpdateV1, ) -> ContractResult { // This contract call doesn't produce any updates Ok(()) diff --git a/src/contract/consensus/src/model.rs b/src/contract/consensus/src/model.rs index 1b5fc3bfb..aa9f87210 100644 --- a/src/contract/consensus/src/model.rs +++ b/src/contract/consensus/src/model.rs @@ -20,9 +20,9 @@ use darkfi_money_contract::model::{Input, Output, StakeInput}; use darkfi_sdk::pasta::pallas; use darkfi_serial::{SerialDecodable, SerialEncodable}; -/// Parameters for `Consensus::Reward` +/// Parameters for `Consensus::ProposalReward` #[derive(Clone, Debug, SerialEncodable, SerialDecodable)] -pub struct ConsensusRewardParamsV1 { +pub struct ConsensusProposalRewardParamsV1 { /// Anonymous input of `Consensus::Unstake` pub unstake_input: Input, /// Burnt token revealed info of `Consensus::Stake` @@ -37,9 +37,20 @@ pub struct ConsensusRewardParamsV1 { pub rho: pallas::Base, } +/// Parameters for `Consensus::ProposalMint` +#[derive(Clone, Debug, SerialEncodable, SerialDecodable)] +pub struct ConsensusProposalMintParamsV1 { + /// Burnt token revealed info + pub input: StakeInput, + /// Anonymous output + pub output: Output, + /// Pedersen commitment for the output's serial number + pub serial_commit: pallas::Point, +} + /// State update for `Consensus::Reward` #[derive(Clone, Debug, SerialEncodable, SerialDecodable)] -pub struct ConsensusRewardUpdateV1 {} +pub struct ConsensusProposalRewardUpdateV1 {} // Consensus parameters configuration. // Note: Always verify `pallas::Base` are correct, in case of changes,