diff --git a/src/contract/consensus/src/client/common.rs b/src/contract/consensus/src/client/common.rs index b96e0eaa4..bb33b1459 100644 --- a/src/contract/consensus/src/client/common.rs +++ b/src/contract/consensus/src/client/common.rs @@ -23,16 +23,32 @@ use darkfi::{ zkas::ZkBinary, Result, }; +use darkfi_money_contract::client::MoneyNote; use darkfi_sdk::{ crypto::{ - pasta_prelude::*, pedersen_commitment_u64, poseidon_hash, Coin, PublicKey, TokenId, - CONSENSUS_CONTRACT_ID, + pasta_prelude::*, pedersen_commitment_base, pedersen_commitment_u64, poseidon_hash, Coin, + MerkleNode, MerklePosition, Nullifier, PublicKey, SecretKey, TokenId, }, + incrementalmerkletree::Hashable, pasta::pallas, }; use rand::rngs::OsRng; -use crate::model::ZERO; +use crate::client::ConsensusNote; + +pub struct TransactionBuilderInputInfo { + pub leaf_position: MerklePosition, + pub merkle_path: Vec, + pub secret: SecretKey, + pub note: MoneyNote, +} + +pub struct TransactionBuilderConsensusInputInfo { + pub leaf_position: MerklePosition, + pub merkle_path: Vec, + pub secret: SecretKey, + pub note: ConsensusNote, +} pub struct TransactionBuilderOutputInfo { pub value: u64, @@ -41,7 +57,7 @@ pub struct TransactionBuilderOutputInfo { } pub struct ConsensusMintRevealed { - pub epoch: pallas::Base, + pub epoch: u64, pub coin: Coin, pub value_commit: pallas::Point, } @@ -49,10 +65,11 @@ pub struct ConsensusMintRevealed { impl ConsensusMintRevealed { pub fn to_vec(&self) -> Vec { let valcom_coords = self.value_commit.to_affine().coordinates().unwrap(); + let epoch_palas = pallas::Base::from(self.epoch); // NOTE: It's important to keep these in the same order // as the `constrain_instance` calls in the zkas code. - vec![self.epoch, self.coin.inner(), *valcom_coords.x(), *valcom_coords.y()] + vec![epoch_palas, self.coin.inner(), *valcom_coords.x(), *valcom_coords.y()] } } @@ -73,7 +90,7 @@ pub fn create_consensus_mint_proof( let coin = Coin::from(poseidon_hash([pub_x, pub_y, value_pallas, epoch_pallas, serial, coin_blind])); - let public_inputs = ConsensusMintRevealed { epoch: epoch_pallas, coin, value_commit }; + let public_inputs = ConsensusMintRevealed { epoch, coin, value_commit }; let prover_witnesses = vec![ Witness::Base(Value::known(pub_x)), @@ -90,3 +107,202 @@ pub fn create_consensus_mint_proof( Ok((proof, public_inputs)) } + +pub struct ConsensusBurnRevealed { + pub nullifier: Nullifier, + pub epoch: u64, + pub signature_public: PublicKey, + pub merkle_root: MerkleNode, + pub value_commit: pallas::Point, +} + +impl ConsensusBurnRevealed { + pub fn to_vec(&self) -> Vec { + let valcom_coords = self.value_commit.to_affine().coordinates().unwrap(); + let sigpub_coords = self.signature_public.inner().to_affine().coordinates().unwrap(); + let epoch_palas = pallas::Base::from(self.epoch); + + // NOTE: It's important to keep these in the same order + // as the `constrain_instance` calls in the zkas code. + vec![ + self.nullifier.inner(), + epoch_palas, + *sigpub_coords.x(), + *sigpub_coords.y(), + self.merkle_root.inner(), + *valcom_coords.x(), + *valcom_coords.y(), + ] + } +} + +pub fn create_consensus_burn_proof( + zkbin: &ZkBinary, + pk: &ProvingKey, + input: &TransactionBuilderConsensusInputInfo, + value_blind: pallas::Scalar, +) -> Result<(Proof, ConsensusBurnRevealed, SecretKey)> { + let nullifier = Nullifier::from(poseidon_hash([input.secret.inner(), input.note.serial])); + let epoch = input.note.epoch; + let epoch_pallas = pallas::Base::from(epoch); + let value_pallas = pallas::Base::from(input.note.value); + let value_commit = pedersen_commitment_u64(input.note.value, value_blind); + let public_key = PublicKey::from_secret(input.secret); + let (pub_x, pub_y) = public_key.xy(); + + let coin = poseidon_hash([ + pub_x, + pub_y, + value_pallas, + epoch_pallas, + input.note.serial, + input.note.coin_blind, + ]); + + let merkle_root = { + let position: u64 = input.leaf_position.into(); + let mut current = MerkleNode::from(coin); + for (level, sibling) in input.merkle_path.iter().enumerate() { + let level = level as u8; + current = if position & (1 << level) == 0 { + MerkleNode::combine(level.into(), ¤t, sibling) + } else { + MerkleNode::combine(level.into(), sibling, ¤t) + }; + } + current + }; + + let public_inputs = ConsensusBurnRevealed { + nullifier, + epoch, + signature_public: public_key, + merkle_root, + value_commit, + }; + + let prover_witnesses = vec![ + Witness::Base(Value::known(value_pallas)), + Witness::Base(Value::known(epoch_pallas)), + Witness::Base(Value::known(input.note.serial)), + Witness::Base(Value::known(input.note.coin_blind)), + Witness::Scalar(Value::known(value_blind)), + Witness::Base(Value::known(input.secret.inner())), + Witness::Uint32(Value::known(u64::from(input.leaf_position).try_into().unwrap())), + Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())), + ]; + + let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone()); + let proof = Proof::create(pk, &[circuit], &public_inputs.to_vec(), &mut OsRng)?; + + Ok((proof, public_inputs, input.secret)) +} + +// TODO: Remove everything following +pub struct ConsensusUnstakeBurnRevealed { + pub value_commit: pallas::Point, + pub token_commit: pallas::Point, + pub nullifier: Nullifier, + pub merkle_root: MerkleNode, + pub spend_hook: pallas::Base, + pub user_data_enc: pallas::Base, + pub signature_public: PublicKey, +} + +impl ConsensusUnstakeBurnRevealed { + 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 sigpub_coords = self.signature_public.inner().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.nullifier.inner(), + *valcom_coords.x(), + *valcom_coords.y(), + *tokcom_coords.x(), + *tokcom_coords.y(), + self.merkle_root.inner(), + self.user_data_enc, + *sigpub_coords.x(), + *sigpub_coords.y(), + ] + } +} + +pub fn create_unstake_burn_proof( + zkbin: &ZkBinary, + pk: &ProvingKey, + input: &TransactionBuilderInputInfo, + value_blind: pallas::Scalar, + token_blind: pallas::Scalar, + user_data_blind: pallas::Base, + signature_secret: SecretKey, +) -> Result<(Proof, ConsensusUnstakeBurnRevealed)> { + let nullifier = Nullifier::from(poseidon_hash([input.secret.inner(), input.note.serial])); + let public_key = PublicKey::from_secret(input.secret); + let (pub_x, pub_y) = public_key.xy(); + + let signature_public = PublicKey::from_secret(signature_secret); + + let coin = poseidon_hash([ + pub_x, + pub_y, + pallas::Base::from(input.note.value), + input.note.token_id.inner(), + input.note.serial, + input.note.spend_hook, + input.note.user_data, + input.note.coin_blind, + ]); + + let merkle_root = { + let position: u64 = input.leaf_position.into(); + let mut current = MerkleNode::from(coin); + for (level, sibling) in input.merkle_path.iter().enumerate() { + let level = level as u8; + current = if position & (1 << level) == 0 { + MerkleNode::combine(level.into(), ¤t, sibling) + } else { + MerkleNode::combine(level.into(), sibling, ¤t) + }; + } + current + }; + + let user_data_enc = poseidon_hash([input.note.user_data, user_data_blind]); + let value_commit = pedersen_commitment_u64(input.note.value, value_blind); + let token_commit = pedersen_commitment_base(input.note.token_id.inner(), token_blind); + + let public_inputs = ConsensusUnstakeBurnRevealed { + value_commit, + token_commit, + nullifier, + merkle_root, + spend_hook: input.note.spend_hook, + user_data_enc, + signature_public, + }; + + let prover_witnesses = vec![ + Witness::Base(Value::known(pallas::Base::from(input.note.value))), + Witness::Base(Value::known(input.note.token_id.inner())), + Witness::Scalar(Value::known(value_blind)), + Witness::Scalar(Value::known(token_blind)), + Witness::Base(Value::known(input.note.serial)), + Witness::Base(Value::known(input.note.spend_hook)), + Witness::Base(Value::known(input.note.user_data)), + Witness::Base(Value::known(user_data_blind)), + Witness::Base(Value::known(input.note.coin_blind)), + Witness::Base(Value::known(input.secret.inner())), + Witness::Uint32(Value::known(u64::from(input.leaf_position).try_into().unwrap())), + Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())), + Witness::Base(Value::known(signature_secret.inner())), + ]; + + let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone()); + let proof = Proof::create(pk, &[circuit], &public_inputs.to_vec(), &mut OsRng)?; + + Ok((proof, public_inputs)) +} diff --git a/src/contract/consensus/src/client/genesis_stake_v1.rs b/src/contract/consensus/src/client/genesis_stake_v1.rs index 21a69533b..f98412ee4 100644 --- a/src/contract/consensus/src/client/genesis_stake_v1.rs +++ b/src/contract/consensus/src/client/genesis_stake_v1.rs @@ -23,20 +23,20 @@ use darkfi::{ zkas::ZkBinary, Result, }; -use darkfi_money_contract::{client::MoneyNote, model::ClearInput}; +use darkfi_money_contract::model::ClearInput; use darkfi_sdk::{ - crypto::{ - note::AeadEncryptedNote, pasta_prelude::*, Keypair, PublicKey, CONSENSUS_CONTRACT_ID, - DARK_TOKEN_ID, - }, + crypto::{note::AeadEncryptedNote, pasta_prelude::*, Keypair, PublicKey, DARK_TOKEN_ID}, pasta::pallas, }; use log::{debug, info}; use rand::rngs::OsRng; use crate::{ - client::common::{create_consensus_mint_proof, TransactionBuilderOutputInfo}, - model::{ConsensusGenesisStakeParamsV1, ConsensusOutput, ZERO}, + client::{ + common::{create_consensus_mint_proof, TransactionBuilderOutputInfo}, + ConsensusNote, + }, + model::{ConsensusGenesisStakeParamsV1, ConsensusOutput}, }; pub struct ConsensusGenesisStakeCallDebris { @@ -96,17 +96,7 @@ impl ConsensusGenesisStakeCallBuilder { )?; // Encrypted note - let note = MoneyNote { - serial, - value: output.value, - token_id: output.token_id, - spend_hook: CONSENSUS_CONTRACT_ID.inner(), - user_data: ZERO, - coin_blind, - value_blind, - token_blind, - memo: vec![], - }; + let note = ConsensusNote { serial, value: output.value, epoch, coin_blind, value_blind }; let encrypted_note = AeadEncryptedNote::encrypt(¬e, &output.public_key, &mut OsRng)?; diff --git a/src/contract/consensus/src/client/mod.rs b/src/contract/consensus/src/client/mod.rs index 4d2ab255e..0d3fb1867 100644 --- a/src/contract/consensus/src/client/mod.rs +++ b/src/contract/consensus/src/client/mod.rs @@ -25,6 +25,15 @@ //! the necessary objects provided by the caller. This is intentional, so we //! are able to abstract away any wallet interfaces to client implementations. +use darkfi_money_contract::client::{MoneyNote, OwnCoin}; +use darkfi_sdk::{ + crypto::{pasta_prelude::Field, Coin, MerklePosition, Nullifier, SecretKey, DARK_TOKEN_ID}, + pasta::pallas, +}; +use darkfi_serial::{SerialDecodable, SerialEncodable}; + +use crate::model::ZERO; + /// Common functions pub(crate) mod common; @@ -41,3 +50,61 @@ pub mod proposal_v1; /// `Consensus::UnstakeV1` API pub mod unstake_v1; + +/// `ConsensusNote` holds the inner attributes of a `Coin`. +#[derive(Debug, Clone, Eq, PartialEq, SerialEncodable, SerialDecodable)] +pub struct ConsensusNote { + /// Serial number of the coin, used for the nullifier + pub serial: pallas::Base, + /// Value of the coin + pub value: u64, + /// Epoch the coin was minted + pub epoch: u64, + /// Blinding factor for the coin bulla + pub coin_blind: pallas::Base, + /// Blinding factor for the value pedersen commitment + pub value_blind: pallas::Scalar, +} + +impl From for MoneyNote { + fn from(consensus_note: ConsensusNote) -> Self { + MoneyNote { + serial: consensus_note.serial, + value: consensus_note.value, + token_id: *DARK_TOKEN_ID, + spend_hook: ZERO, + user_data: ZERO, + coin_blind: consensus_note.coin_blind, + value_blind: consensus_note.value_blind, + token_blind: pallas::Scalar::zero(), + memo: vec![], + } + } +} + +/// `ConsensusOwnCoin` is a representation of `Coin` with its respective metadata. +#[derive(Debug, Clone, Eq, PartialEq, SerialEncodable, SerialDecodable)] +pub struct ConsensusOwnCoin { + /// The coin hash + pub coin: Coin, + /// The attached `ConsensusNote` + pub note: ConsensusNote, + /// Coin's secret key + pub secret: SecretKey, + /// Coin's nullifier + pub nullifier: Nullifier, + /// Coin's leaf position in the Merkle tree of coins + pub leaf_position: MerklePosition, +} + +impl From for OwnCoin { + fn from(consensus_own_coin: ConsensusOwnCoin) -> Self { + OwnCoin { + coin: consensus_own_coin.coin, + note: consensus_own_coin.note.into(), + secret: consensus_own_coin.secret, + nullifier: consensus_own_coin.nullifier, + leaf_position: consensus_own_coin.leaf_position, + } + } +} diff --git a/src/contract/consensus/src/client/proposal_v1.rs b/src/contract/consensus/src/client/proposal_v1.rs index 3b5f74fcb..cdce46ddf 100644 --- a/src/contract/consensus/src/client/proposal_v1.rs +++ b/src/contract/consensus/src/client/proposal_v1.rs @@ -42,8 +42,8 @@ use rand::rngs::OsRng; use crate::{ client::{ + common::{create_unstake_burn_proof, TransactionBuilderInputInfo as UnstakeTBII}, stake_v1::{TransactionBuilderOutputInfo as StakeTBOI, TransactionBuilderOutputInfo}, - unstake_v1::{create_unstake_burn_proof, TransactionBuilderInputInfo as UnstakeTBII}, }, model::{ ConsensusProposalBurnParamsV1, ConsensusProposalMintParamsV1, diff --git a/src/contract/consensus/src/client/unstake_v1.rs b/src/contract/consensus/src/client/unstake_v1.rs index 4e4f897d1..d5f165bd2 100644 --- a/src/contract/consensus/src/client/unstake_v1.rs +++ b/src/contract/consensus/src/client/unstake_v1.rs @@ -19,25 +19,24 @@ //! This API is crufty. Please rework it into something nice to read and nice to use. use darkfi::{ - zk::{halo2::Value, Proof, ProvingKey, Witness, ZkCircuit}, + zk::{Proof, ProvingKey}, zkas::ZkBinary, Result, }; -use darkfi_money_contract::{ - client::{MoneyNote, OwnCoin}, - model::{ConsensusUnstakeParamsV1, Input}, -}; +use darkfi_money_contract::model::{ConsensusUnstakeParamsV1, UnstakeInput}; use darkfi_sdk::{ - crypto::{ - pasta_prelude::*, pedersen_commitment_base, pedersen_commitment_u64, poseidon_hash, - MerkleNode, MerklePosition, MerkleTree, Nullifier, PublicKey, SecretKey, DARK_TOKEN_ID, - }, - incrementalmerkletree::{Hashable, Tree}, + crypto::{pasta_prelude::*, MerkleTree, SecretKey}, + incrementalmerkletree::Tree, pasta::pallas, }; use log::{debug, info}; use rand::rngs::OsRng; +use crate::client::{ + common::{create_consensus_burn_proof, TransactionBuilderConsensusInputInfo}, + ConsensusOwnCoin, +}; + pub struct ConsensusUnstakeCallDebris { pub params: ConsensusUnstakeParamsV1, pub proofs: Vec, @@ -45,54 +44,15 @@ pub struct ConsensusUnstakeCallDebris { pub value_blind: pallas::Scalar, } -pub struct ConsensusUnstakeBurnRevealed { - pub value_commit: pallas::Point, - pub token_commit: pallas::Point, - pub nullifier: Nullifier, - pub merkle_root: MerkleNode, - pub spend_hook: pallas::Base, - pub user_data_enc: pallas::Base, - pub signature_public: PublicKey, -} - -impl ConsensusUnstakeBurnRevealed { - 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 sigpub_coords = self.signature_public.inner().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.nullifier.inner(), - *valcom_coords.x(), - *valcom_coords.y(), - *tokcom_coords.x(), - *tokcom_coords.y(), - self.merkle_root.inner(), - self.user_data_enc, - *sigpub_coords.x(), - *sigpub_coords.y(), - ] - } -} - -pub struct TransactionBuilderInputInfo { - pub leaf_position: MerklePosition, - pub merkle_path: Vec, - pub secret: SecretKey, - pub note: MoneyNote, -} - /// Struct holding necessary information to build a `Consensus::UnstakeV1` contract call. pub struct ConsensusUnstakeCallBuilder { - /// `OwnCoin` we're given to use in this builder - pub coin: OwnCoin, + /// `ConsensusOwnCoin` we're given to use in this builder + pub coin: ConsensusOwnCoin, /// Merkle tree of coins used to create inclusion proofs pub tree: MerkleTree, - /// `Burn_V1` zkas circuit ZkBinary + /// `ConsensusBurn_V1` zkas circuit ZkBinary pub burn_zkbin: ZkBinary, - /// Proving key for the `Burn_V1` zk circuit + /// Proving key for the `ConsensusBurn_V1` zk circuit pub burn_pk: ProvingKey, } @@ -100,13 +60,12 @@ impl ConsensusUnstakeCallBuilder { pub fn build(&self) -> Result { debug!("Building Consensus::UnstakeV1 contract call"); assert!(self.coin.note.value != 0); - assert!(self.coin.note.token_id == *DARK_TOKEN_ID); debug!("Building anonymous input"); let leaf_position = self.coin.leaf_position; let root = self.tree.root(0).unwrap(); let merkle_path = self.tree.authentication_path(leaf_position, &root).unwrap(); - let input = TransactionBuilderInputInfo { + let input = TransactionBuilderConsensusInputInfo { leaf_position, merkle_path, secret: self.coin.secret, @@ -115,32 +74,20 @@ impl ConsensusUnstakeCallBuilder { debug!("Finished building input"); let value_blind = pallas::Scalar::random(&mut OsRng); - let token_blind = pallas::Scalar::random(&mut OsRng); - let signature_secret = SecretKey::random(&mut OsRng); - let user_data_blind = pallas::Base::random(&mut OsRng); info!("Creating unstake burn proof for input"); - let (proof, public_inputs) = create_unstake_burn_proof( - &self.burn_zkbin, - &self.burn_pk, - &input, - value_blind, - token_blind, - user_data_blind, - signature_secret, - )?; + let (proof, public_inputs, signature_secret) = + create_consensus_burn_proof(&self.burn_zkbin, &self.burn_pk, &input, value_blind)?; - let input = Input { + let input = UnstakeInput { + epoch: self.coin.note.epoch, value_commit: public_inputs.value_commit, - token_commit: public_inputs.token_commit, nullifier: public_inputs.nullifier, merkle_root: public_inputs.merkle_root, - spend_hook: public_inputs.spend_hook, - user_data_enc: public_inputs.user_data_enc, signature_public: public_inputs.signature_public, }; // We now fill this with necessary stuff - let params = ConsensusUnstakeParamsV1 { token_blind, input }; + let params = ConsensusUnstakeParamsV1 { input }; let proofs = vec![proof]; // Now we should have all the params, zk proof, signature secret and token blind. @@ -149,79 +96,3 @@ impl ConsensusUnstakeCallBuilder { Ok(debris) } } - -pub fn create_unstake_burn_proof( - zkbin: &ZkBinary, - pk: &ProvingKey, - input: &TransactionBuilderInputInfo, - value_blind: pallas::Scalar, - token_blind: pallas::Scalar, - user_data_blind: pallas::Base, - signature_secret: SecretKey, -) -> Result<(Proof, ConsensusUnstakeBurnRevealed)> { - let nullifier = Nullifier::from(poseidon_hash([input.secret.inner(), input.note.serial])); - let public_key = PublicKey::from_secret(input.secret); - let (pub_x, pub_y) = public_key.xy(); - - let signature_public = PublicKey::from_secret(signature_secret); - - let coin = poseidon_hash([ - pub_x, - pub_y, - pallas::Base::from(input.note.value), - input.note.token_id.inner(), - input.note.serial, - input.note.spend_hook, - input.note.user_data, - input.note.coin_blind, - ]); - - let merkle_root = { - let position: u64 = input.leaf_position.into(); - let mut current = MerkleNode::from(coin); - for (level, sibling) in input.merkle_path.iter().enumerate() { - let level = level as u8; - current = if position & (1 << level) == 0 { - MerkleNode::combine(level.into(), ¤t, sibling) - } else { - MerkleNode::combine(level.into(), sibling, ¤t) - }; - } - current - }; - - let user_data_enc = poseidon_hash([input.note.user_data, user_data_blind]); - let value_commit = pedersen_commitment_u64(input.note.value, value_blind); - let token_commit = pedersen_commitment_base(input.note.token_id.inner(), token_blind); - - let public_inputs = ConsensusUnstakeBurnRevealed { - value_commit, - token_commit, - nullifier, - merkle_root, - spend_hook: input.note.spend_hook, - user_data_enc, - signature_public, - }; - - let prover_witnesses = vec![ - Witness::Base(Value::known(pallas::Base::from(input.note.value))), - Witness::Base(Value::known(input.note.token_id.inner())), - Witness::Scalar(Value::known(value_blind)), - Witness::Scalar(Value::known(token_blind)), - Witness::Base(Value::known(input.note.serial)), - Witness::Base(Value::known(input.note.spend_hook)), - Witness::Base(Value::known(input.note.user_data)), - Witness::Base(Value::known(user_data_blind)), - Witness::Base(Value::known(input.note.coin_blind)), - Witness::Base(Value::known(input.secret.inner())), - Witness::Uint32(Value::known(u64::from(input.leaf_position).try_into().unwrap())), - Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())), - Witness::Base(Value::known(signature_secret.inner())), - ]; - - let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone()); - let proof = Proof::create(pk, &[circuit], &public_inputs.to_vec(), &mut OsRng)?; - - Ok((proof, public_inputs)) -} diff --git a/src/contract/consensus/src/entrypoint.rs b/src/contract/consensus/src/entrypoint.rs index d918ee715..043cf816f 100644 --- a/src/contract/consensus/src/entrypoint.rs +++ b/src/contract/consensus/src/entrypoint.rs @@ -92,15 +92,17 @@ fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult { // order to be able to verify the circuits being bundled and enforcing // a specific tree inside sled, and also creation of VerifyingKey. let money_mint_v1_bincode = include_bytes!("../../money/proof/mint_v1.zk.bin"); - let consensus_mint_v1_bincode = include_bytes!("../proof/consensus_mint_v1.zk.bin"); let money_burn_v1_bincode = include_bytes!("../../money/proof/burn_v1.zk.bin"); + let consensus_mint_v1_bincode = include_bytes!("../proof/consensus_mint_v1.zk.bin"); + let consensus_burn_v1_bincode = include_bytes!("../proof/consensus_burn_v1.zk.bin"); let proposal_reward_v1_bincode = include_bytes!("../proof/proposal_reward_v1.zk.bin"); let proposal_mint_v1_bincode = include_bytes!("../proof/proposal_mint_v1.zk.bin"); // For that, we use `zkas_db_set` and pass in the bincode. zkas_db_set(&money_mint_v1_bincode[..])?; - zkas_db_set(&consensus_mint_v1_bincode[..])?; zkas_db_set(&money_burn_v1_bincode[..])?; + zkas_db_set(&consensus_mint_v1_bincode[..])?; + zkas_db_set(&consensus_burn_v1_bincode[..])?; zkas_db_set(&proposal_reward_v1_bincode[..])?; zkas_db_set(&proposal_mint_v1_bincode[..])?; diff --git a/src/contract/consensus/src/entrypoint/unstake_v1.rs b/src/contract/consensus/src/entrypoint/unstake_v1.rs index 5aa804b73..8ac1318ba 100644 --- a/src/contract/consensus/src/entrypoint/unstake_v1.rs +++ b/src/contract/consensus/src/entrypoint/unstake_v1.rs @@ -20,13 +20,10 @@ use darkfi_money_contract::{ error::MoneyError, model::{ConsensusUnstakeParamsV1, ConsensusUnstakeUpdateV1, MoneyUnstakeParamsV1}, CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_NULLIFIERS_TREE, - MONEY_CONTRACT_ZKAS_BURN_NS_V1, + CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1, }; use darkfi_sdk::{ - crypto::{ - pasta_prelude::*, pedersen_commitment_base, ContractId, CONSENSUS_CONTRACT_ID, - DARK_TOKEN_ID, MONEY_CONTRACT_ID, - }, + crypto::{pasta_prelude::*, ContractId, MONEY_CONTRACT_ID}, db::{db_contains_key, db_lookup, db_set}, error::{ContractError, ContractResult}, msg, @@ -35,7 +32,7 @@ use darkfi_sdk::{ }; use darkfi_serial::{deserialize, serialize, Encodable, WriteExt}; -use crate::{model::ZERO, ConsensusFunction}; +use crate::ConsensusFunction; /// `get_metadata` function for `Consensus::UnstakeV1` pub(crate) fn consensus_unstake_get_metadata_v1( @@ -55,24 +52,22 @@ pub(crate) fn consensus_unstake_get_metadata_v1( // Grab the pedersen commitments and signature pubkeys from the // anonymous input let value_coords = input.value_commit.to_affine().coordinates().unwrap(); - let token_coords = input.token_commit.to_affine().coordinates().unwrap(); let (sig_x, sig_y) = input.signature_public.xy(); + let epoch_palas = pallas::Base::from(input.epoch); // 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(), + CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1.to_string(), vec![ input.nullifier.inner(), - *value_coords.x(), - *value_coords.y(), - *token_coords.x(), - *token_coords.y(), - input.merkle_root.inner(), - input.user_data_enc, + epoch_palas, sig_x, sig_y, + input.merkle_root.inner(), + *value_coords.x(), + *value_coords.y(), ], )); @@ -105,12 +100,6 @@ pub(crate) fn consensus_unstake_process_instruction_v1( msg!("[ConsensusUnstakeV1] Validating anonymous input"); let input = ¶ms.input; - // Only native token can be unstaked - if input.token_commit != pedersen_commitment_base(DARK_TOKEN_ID.inner(), params.token_blind) { - msg!("[ConsensusUnstakeV1] Error: Input used non-native token"); - return Err(MoneyError::StakeInputNonNativeToken.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))? { @@ -137,17 +126,6 @@ pub(crate) fn consensus_unstake_process_instruction_v1( return Err(MoneyError::UnstakeNextCallNotMoneyContract.into()) } - // Check if spend hook is set and its correctness - if input.spend_hook == ZERO { - msg!("[ConsensusUnstakeV1] Error: Missing spend hook"); - return Err(MoneyError::StakeMissingSpendHook.into()) - } - - if input.spend_hook != CONSENSUS_CONTRACT_ID.inner() { - msg!("[ConsensusUnstakeV1] Error: Spend hook is not consensus contract"); - return Err(MoneyError::UnstakeSpendHookNotConsensusContract.into()) - } - // Verify next call corresponds to Money::UnstakeV1 (0x07) if next.data[0] != 0x07 { msg!("[ConsensusUnstakeV1] Error: Next call function mismatch"); diff --git a/src/contract/consensus/tests/genesis_stake_unstake.rs b/src/contract/consensus/tests/genesis_stake_unstake.rs index 6e1497db8..94a21c0ac 100644 --- a/src/contract/consensus/tests/genesis_stake_unstake.rs +++ b/src/contract/consensus/tests/genesis_stake_unstake.rs @@ -106,48 +106,50 @@ async fn consensus_contract_genesis_stake_unstake() -> Result<()> { // Gather new staked owncoin let alice_staked_oc = - th.gather_consensus_owncoin(Holder::Alice, genesis_stake_params.output)?; + th.gather_consensus_owncoin(Holder::Alice, genesis_stake_params.output, None)?; // Verify values match assert!(ALICE_INITIAL == alice_staked_oc.note.value); + /* + // We simulate the proposal of genesis slot + let slot_checkpoint = th.get_slot_checkpoints_by_slot(current_slot).await?; - // We simulate the proposal of genesis slot - let slot_checkpoint = th.get_slot_checkpoints_by_slot(current_slot).await?; + // With alice's current coin value she can become the slot proposer, + // so she creates a proposal transaction to burn her staked coin, + // reward herself and mint the new coin. + info!(target: "consensus", "[Alice] ===================="); + info!(target: "consensus", "[Alice] Building proposal tx"); + info!(target: "consensus", "[Alice] ===================="); + let (proposal_tx, proposal_params) = + th.proposal(Holder::Alice, slot_checkpoint, alice_staked_oc.clone())?; - // With alice's current coin value she can become the slot proposer, - // so she creates a proposal transaction to burn her staked coin, - // reward herself and mint the new coin. - info!(target: "consensus", "[Alice] ===================="); - info!(target: "consensus", "[Alice] Building proposal tx"); - info!(target: "consensus", "[Alice] ===================="); - let (proposal_tx, proposal_params) = - th.proposal(Holder::Alice, slot_checkpoint, alice_staked_oc.clone())?; + info!(target: "consensus", "[Faucet] ==========================="); + info!(target: "consensus", "[Faucet] Executing Alice proposal tx"); + info!(target: "consensus", "[Faucet] ==========================="); + th.execute_proposal_tx(Holder::Faucet, proposal_tx.clone(), &proposal_params, current_slot) + .await?; - info!(target: "consensus", "[Faucet] ==========================="); - info!(target: "consensus", "[Faucet] Executing Alice proposal tx"); - info!(target: "consensus", "[Faucet] ==========================="); - th.execute_proposal_tx(Holder::Faucet, proposal_tx.clone(), &proposal_params, current_slot) - .await?; + info!(target: "consensus", "[Alice] ==========================="); + info!(target: "consensus", "[Alice] Executing Alice proposal tx"); + info!(target: "consensus", "[Alice] ==========================="); + th.execute_proposal_tx(Holder::Alice, proposal_tx, &proposal_params, current_slot).await?; - info!(target: "consensus", "[Alice] ==========================="); - info!(target: "consensus", "[Alice] Executing Alice proposal tx"); - info!(target: "consensus", "[Alice] ==========================="); - th.execute_proposal_tx(Holder::Alice, proposal_tx, &proposal_params, current_slot).await?; + th.assert_trees(); - th.assert_trees(); + // Gather new staked owncoin which includes the reward + let alice_rewarded_staked_oc = + th.gather_consensus_owncoin(Holder::Alice, proposal_params.output)?; - // Gather new staked owncoin which includes the reward - let alice_rewarded_staked_oc = - th.gather_owncoin(Holder::Alice, proposal_params.output, true)?; - - // Verify values match - assert!((alice_staked_oc.note.value + REWARD) == alice_rewarded_staked_oc.note.value); + // Verify values match + assert!((alice_staked_oc.note.value + REWARD) == alice_rewarded_staked_oc.note.value); + */ + let alice_rewarded_staked_oc = alice_staked_oc; // Now Alice can unstake her owncoin info!(target: "consensus", "[Alice] ==================="); info!(target: "consensus", "[Alice] Building unstake tx"); info!(target: "consensus", "[Alice] ==================="); - let (unstake_tx, unstake_params) = + let (unstake_tx, unstake_params, unstake_secret_key) = th.unstake_native(Holder::Alice, alice_rewarded_staked_oc.clone())?; info!(target: "consensus", "[Faucet] =========================="); @@ -164,7 +166,8 @@ async fn consensus_contract_genesis_stake_unstake() -> Result<()> { th.assert_trees(); // Gather new unstaked owncoin - let alice_unstaked_oc = th.gather_owncoin(Holder::Alice, unstake_params.output, false)?; + let alice_unstaked_oc = + th.gather_owncoin(Holder::Alice, unstake_params.output, Some(unstake_secret_key))?; // Verify values match assert!(alice_rewarded_staked_oc.note.value == alice_unstaked_oc.note.value); diff --git a/src/contract/consensus/tests/harness.rs b/src/contract/consensus/tests/harness.rs index 6f0637496..91f08a4b3 100644 --- a/src/contract/consensus/tests/harness.rs +++ b/src/contract/consensus/tests/harness.rs @@ -35,7 +35,7 @@ use darkfi::{ use darkfi_sdk::{ crypto::{ merkle_prelude::*, poseidon_hash, Coin, Keypair, MerkleNode, MerkleTree, Nullifier, - PublicKey, CONSENSUS_CONTRACT_ID, DARK_TOKEN_ID, MONEY_CONTRACT_ID, + PublicKey, SecretKey, CONSENSUS_CONTRACT_ID, DARK_TOKEN_ID, MONEY_CONTRACT_ID, }, pasta::pallas, ContractCall, @@ -48,7 +48,7 @@ use darkfi_consensus_contract::{ client::{ genesis_stake_v1::ConsensusGenesisStakeCallBuilder, proposal_v1::ConsensusProposalCallBuilder, stake_v1::ConsensusStakeCallBuilder, - unstake_v1::ConsensusUnstakeCallBuilder, + unstake_v1::ConsensusUnstakeCallBuilder, ConsensusNote, ConsensusOwnCoin, }, model::{ConsensusGenesisStakeParamsV1, ConsensusOutput, ConsensusProposalMintParamsV1}, ConsensusFunction, @@ -59,9 +59,9 @@ use darkfi_money_contract::{ unstake_v1::MoneyUnstakeCallBuilder, MoneyNote, OwnCoin, }, model::{ConsensusStakeParamsV1, MoneyTransferParamsV1, MoneyUnstakeParamsV1, Output}, - MoneyFunction, CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1, CONSENSUS_CONTRACT_ZKAS_PROPOSAL_MINT_NS_V1, - CONSENSUS_CONTRACT_ZKAS_PROPOSAL_REWARD_NS_V1, MONEY_CONTRACT_ZKAS_BURN_NS_V1, - MONEY_CONTRACT_ZKAS_MINT_NS_V1, + MoneyFunction, CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1, CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1, + CONSENSUS_CONTRACT_ZKAS_PROPOSAL_MINT_NS_V1, CONSENSUS_CONTRACT_ZKAS_PROPOSAL_REWARD_NS_V1, + MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1, }; pub fn init_logger() { @@ -232,6 +232,7 @@ impl ConsensusTestHarness { mkpk!(MONEY_CONTRACT_ZKAS_MINT_NS_V1); mkpk!(CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1); mkpk!(MONEY_CONTRACT_ZKAS_BURN_NS_V1); + mkpk!(CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1); mkpk!(CONSENSUS_CONTRACT_ZKAS_PROPOSAL_REWARD_NS_V1); mkpk!(CONSENSUS_CONTRACT_ZKAS_PROPOSAL_MINT_NS_V1); @@ -606,10 +607,11 @@ impl ConsensusTestHarness { pub fn unstake_native( &mut self, holder: Holder, - staked_oc: OwnCoin, - ) -> Result<(Transaction, MoneyUnstakeParamsV1)> { + staked_oc: ConsensusOwnCoin, + ) -> Result<(Transaction, MoneyUnstakeParamsV1, SecretKey)> { let wallet = self.holders.get_mut(&holder).unwrap(); - let (burn_pk, burn_zkbin) = self.proving_keys.get(&MONEY_CONTRACT_ZKAS_BURN_NS_V1).unwrap(); + let (burn_pk, burn_zkbin) = + self.proving_keys.get(&CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1).unwrap(); let (mint_pk, mint_zkbin) = self.proving_keys.get(&MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap(); let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::Unstake).unwrap(); let timer = Instant::now(); @@ -636,10 +638,8 @@ impl ConsensusTestHarness { // Building Money::Unstake params let money_unstake_call_debris = MoneyUnstakeCallBuilder { - coin: staked_oc, - recipient: wallet.keypair.public, + coin: staked_oc.into(), value_blind: consensus_unstake_value_blind, - token_blind: consensus_unstake_params.token_blind, nullifier: consensus_unstake_params.input.nullifier, merkle_root: consensus_unstake_params.input.merkle_root, signature_public: consensus_unstake_params.input.signature_public, @@ -675,7 +675,7 @@ impl ConsensusTestHarness { let size = ::std::mem::size_of_val(&*base58); tx_action_benchmark.broadcasted_sizes.push(size); - Ok((unstake_tx, money_unstake_params)) + Ok((unstake_tx, money_unstake_params, consensus_unstake_secret_key)) } pub async fn execute_unstake_native_tx( @@ -698,10 +698,19 @@ impl ConsensusTestHarness { Ok(()) } - pub fn gather_owncoin(&mut self, holder: Holder, output: Output) -> Result { + pub fn gather_owncoin( + &mut self, + holder: Holder, + output: Output, + secret_key: Option, + ) -> Result { let wallet = self.holders.get_mut(&holder).unwrap(); let leaf_position = wallet.merkle_tree.witness().unwrap(); - let note: MoneyNote = output.note.decrypt(&wallet.keypair.secret)?; + let secret_key = match secret_key { + Some(key) => key, + None => wallet.keypair.secret, + }; + let note: MoneyNote = output.note.decrypt(&secret_key)?; let oc = OwnCoin { coin: Coin::from(output.coin), note: note.clone(), @@ -717,11 +726,16 @@ impl ConsensusTestHarness { &mut self, holder: Holder, output: ConsensusOutput, - ) -> Result { + secret_key: Option, + ) -> Result { let wallet = self.holders.get_mut(&holder).unwrap(); let leaf_position = wallet.consensus_merkle_tree.witness().unwrap(); - let note: MoneyNote = output.note.decrypt(&wallet.keypair.secret)?; - let oc = OwnCoin { + let secret_key = match secret_key { + Some(key) => key, + None => wallet.keypair.secret, + }; + let note: ConsensusNote = output.note.decrypt(&secret_key)?; + let oc = ConsensusOwnCoin { coin: Coin::from(output.coin), note: note.clone(), secret: wallet.keypair.secret, diff --git a/src/contract/money/src/client/unstake_v1.rs b/src/contract/money/src/client/unstake_v1.rs index 5a63b281c..cbee16591 100644 --- a/src/contract/money/src/client/unstake_v1.rs +++ b/src/contract/money/src/client/unstake_v1.rs @@ -77,12 +77,8 @@ pub struct TransactionBuilderOutputInfo { pub struct MoneyUnstakeCallBuilder { /// `OwnCoin` we're given to use in this builder pub coin: OwnCoin, - /// Recipient's public key - pub recipient: PublicKey, /// Blinding factor for value commitment pub value_blind: pallas::Scalar, - /// Blinding factor for `token_id` - pub token_blind: pallas::Scalar, /// Revealed nullifier pub nullifier: Nullifier, /// Revealed Merkle root @@ -105,13 +101,14 @@ impl MoneyUnstakeCallBuilder { let output = TransactionBuilderOutputInfo { value: self.coin.note.value, token_id: self.coin.note.token_id, - public_key: self.recipient, + public_key: self.signature_public, }; debug!("Finished building output"); let serial = pallas::Base::random(&mut OsRng); let spend_hook = pallas::Base::zero(); let user_data_enc = pallas::Base::zero(); + let token_blind = pallas::Scalar::random(&mut OsRng); let coin_blind = pallas::Base::random(&mut OsRng); info!("Creating unstake mint proof for output"); @@ -120,7 +117,7 @@ impl MoneyUnstakeCallBuilder { &self.mint_pk, &output, self.value_blind, - self.token_blind, + token_blind, serial, spend_hook, user_data_enc, @@ -136,7 +133,7 @@ impl MoneyUnstakeCallBuilder { user_data: user_data_enc, coin_blind, value_blind: self.value_blind, - token_blind: self.token_blind, + token_blind, memo: vec![], }; @@ -150,7 +147,7 @@ impl MoneyUnstakeCallBuilder { }; let input = StakeInput { - token_blind: self.token_blind, + token_blind, value_commit: public_inputs.value_commit, nullifier: self.nullifier, merkle_root: self.merkle_root, diff --git a/src/contract/money/src/entrypoint/unstake_v1.rs b/src/contract/money/src/entrypoint/unstake_v1.rs index b2eebc839..f87dae78e 100644 --- a/src/contract/money/src/entrypoint/unstake_v1.rs +++ b/src/contract/money/src/entrypoint/unstake_v1.rs @@ -152,12 +152,6 @@ pub(crate) fn money_unstake_process_instruction_v1( return Err(MoneyError::PreviousCallInputMissmatch.into()) } - // Check spend hook correctness - if previous_input.spend_hook != CONSENSUS_CONTRACT_ID.inner() { - msg!("[MoneyUnstakeV1] Error: Invoking contract call does not match spend hook in input"); - return Err(MoneyError::SpendHookMismatch.into()) - } - // If next spend hook is set, check its correctness if params.spend_hook != pallas::Base::zero() { let next_call_idx = call_idx + 1; diff --git a/src/contract/money/src/lib.rs b/src/contract/money/src/lib.rs index 3d89e18a9..4bd57fe02 100644 --- a/src/contract/money/src/lib.rs +++ b/src/contract/money/src/lib.rs @@ -102,6 +102,8 @@ pub const CONSENSUS_CONTRACT_COIN_MERKLE_TREE: &str = "consensus_coin_tree"; /// zkas consensus mint circuit namespace pub const CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1: &str = "ConsensusMint_V1"; +/// zkas consensus burn circuit namespace +pub const CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1: &str = "ConsensusBurn_V1"; /// zkas proposal reward circuit namespace pub const CONSENSUS_CONTRACT_ZKAS_PROPOSAL_REWARD_NS_V1: &str = "ProposalReward_V1"; /// zkas proposal mint circuit namespace diff --git a/src/contract/money/src/model.rs b/src/contract/money/src/model.rs index 05180d264..6570049ef 100644 --- a/src/contract/money/src/model.rs +++ b/src/contract/money/src/model.rs @@ -84,6 +84,30 @@ impl PartialEq for Input { } } +/// Anonymous input for unstaking contract calls +#[derive(Clone, Debug, PartialEq, SerialEncodable, SerialDecodable)] +pub struct UnstakeInput { + /// Epoch the coin was minted + pub epoch: u64, + /// Pedersen commitment for the staked coin's value + pub value_commit: pallas::Point, + /// Revealed nullifier + pub nullifier: Nullifier, + /// Revealed Merkle root + pub merkle_root: MerkleNode, + /// Public key for the signature + pub signature_public: PublicKey, +} + +impl PartialEq for UnstakeInput { + fn eq(&self, other: &StakeInput) -> bool { + self.value_commit == other.value_commit && + self.nullifier == other.nullifier && + self.merkle_root == other.merkle_root && + self.signature_public == other.signature_public + } +} + /// A contract call's anonymous output #[derive(Clone, Debug, PartialEq, SerialEncodable, SerialDecodable)] pub struct Output { @@ -208,10 +232,8 @@ pub struct ConsensusStakeUpdateV1 { /// Parameters for `Consensus::Unstake` #[derive(Clone, Debug, SerialEncodable, SerialDecodable)] pub struct ConsensusUnstakeParamsV1 { - /// Blinding factor for `token_id` - pub token_blind: pallas::Scalar, /// Anonymous input - pub input: Input, + pub input: UnstakeInput, } /// State update for `Consensus::Unstake`