From 7b24085790ef1b2657b49ecbed17bd47cd81b93d Mon Sep 17 00:00:00 2001 From: parazyd Date: Thu, 8 Jun 2023 13:13:51 +0200 Subject: [PATCH] contract/consensus: Clean up Proposal client API --- .../consensus/src/client/proposal_v1.rs | 289 +++++++++--------- .../src/client/unstake_request_v1.rs | 1 - .../consensus/src/client/unstake_v1.rs | 1 - src/contract/consensus/src/model.rs | 15 +- src/contract/money/src/client/unstake_v1.rs | 1 - src/contract/money/src/model.rs | 2 - 6 files changed, 151 insertions(+), 158 deletions(-) diff --git a/src/contract/consensus/src/client/proposal_v1.rs b/src/contract/consensus/src/client/proposal_v1.rs index d443a815f..7e8fdb760 100644 --- a/src/contract/consensus/src/client/proposal_v1.rs +++ b/src/contract/consensus/src/client/proposal_v1.rs @@ -30,27 +30,31 @@ use darkfi_money_contract::{ }; use darkfi_sdk::{ crypto::{ - ecvrf::VrfProof, note::AeadEncryptedNote, pasta_prelude::*, pedersen_commitment_base, - pedersen_commitment_u64, poseidon_hash, MerkleNode, MerkleTree, Nullifier, PublicKey, - SecretKey, + ecvrf::VrfProof, note::AeadEncryptedNote, pasta_prelude::*, pedersen_commitment_u64, + poseidon_hash, Keypair, MerkleNode, MerkleTree, Nullifier, PublicKey, SecretKey, }, incrementalmerkletree::{Hashable, Tree}, pasta::{group::ff::FromUniformBytes, pallas}, }; -use log::debug; +use log::{debug, info}; use rand::rngs::OsRng; use crate::{ client::common::{ConsensusBurnInputInfo, ConsensusMintOutputInfo}, model::{ - ConsensusProposalParamsV1, HEADSTART, MU_RHO_PREFIX, MU_Y_PREFIX, REWARD, REWARD_PALLAS, - SEED_PREFIX, SERIAL_PREFIX, + ConsensusProposalParamsV1, HEADSTART, MU_RHO_PREFIX, MU_Y_PREFIX, REWARD, SEED_PREFIX, + SERIAL_PREFIX, }, }; pub struct ConsensusProposalCallDebris { + /// Payload params pub params: ConsensusProposalParamsV1, + /// ZK proofs pub proofs: Vec, + /// The new output keypair (used in the minted coin) + pub keypair: Keypair, + /// Secret key used to sign the transaction pub signature_secret: SecretKey, } @@ -59,11 +63,10 @@ pub struct ConsensusProposalRevealed { pub epoch: u64, pub public_key: PublicKey, pub merkle_root: MerkleNode, - pub value_commit: pallas::Point, - pub new_serial: pallas::Base, - pub new_serial_commit: pallas::Point, - pub new_value_commit: pallas::Point, - pub new_coin: Coin, + pub input_value_commit: pallas::Point, + pub reward: u64, + pub output_value_commit: pallas::Point, + pub output_coin: Coin, pub vrf_proof: VrfProof, pub mu_y: pallas::Base, pub y: pallas::Base, @@ -71,40 +74,36 @@ pub struct ConsensusProposalRevealed { pub rho: pallas::Base, pub sigma1: pallas::Base, pub sigma2: pallas::Base, + pub headstart: pallas::Base, } impl ConsensusProposalRevealed { - pub fn to_vec(&self) -> Vec { - let epoch_palas = pallas::Base::from(self.epoch); + fn to_vec(&self) -> Vec { let (pub_x, pub_y) = self.public_key.xy(); - let value_coords = self.value_commit.to_affine().coordinates().unwrap(); - let new_serial_coords = self.new_serial_commit.to_affine().coordinates().unwrap(); - let reward_pallas = pallas::Base::from(REWARD); - let new_value_coords = self.new_value_commit.to_affine().coordinates().unwrap(); + let input_value_coords = self.input_value_commit.to_affine().coordinates().unwrap(); + let output_value_coords = self.output_value_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.nullifier.inner(), - epoch_palas, + pallas::Base::from(self.epoch), pub_x, pub_y, self.merkle_root.inner(), - *value_coords.x(), - *value_coords.y(), - *new_serial_coords.x(), - *new_serial_coords.y(), - reward_pallas, - *new_value_coords.x(), - *new_value_coords.y(), - self.new_coin.inner(), + *input_value_coords.x(), + *input_value_coords.y(), + pallas::Base::from(self.reward), + *output_value_coords.x(), + *output_value_coords.y(), + self.output_coin.inner(), self.mu_y, self.y, self.mu_rho, self.rho, self.sigma1, self.sigma2, - HEADSTART, + self.headstart, ] } } @@ -112,7 +111,7 @@ impl ConsensusProposalRevealed { /// Struct holding necessary information to build a proposal transaction. pub struct ConsensusProposalCallBuilder { /// `ConsensusOwnCoin` we're given to use in this builder - pub coin: ConsensusOwnCoin, + pub owncoin: ConsensusOwnCoin, /// Rewarded slot checkpoint pub slot_checkpoint: SlotCheckpoint, /// Extending fork last proposal/block hash @@ -120,7 +119,7 @@ pub struct ConsensusProposalCallBuilder { /// Extending fork second to last proposal/block hash pub fork_previous_hash: blake3::Hash, /// Merkle tree of coins used to create inclusion proofs - pub tree: MerkleTree, + pub merkle_tree: MerkleTree, /// `Proposal_V1` zkas circuit ZkBinary pub proposal_zkbin: ZkBinary, /// Proving key for the `Proposal_V1` zk circuit @@ -129,129 +128,148 @@ pub struct ConsensusProposalCallBuilder { impl ConsensusProposalCallBuilder { pub fn build(&self) -> Result { - debug!("Building Consensus::ProposalBurnV1 contract call for proposal"); - let value = self.coin.note.value; - assert!(value != 0); + info!("Building Consensus::ProposalBurnV1 contract call"); + assert!(self.owncoin.note.value != 0); debug!("Building Consensus::ProposalV1 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 root = self.merkle_tree.root(0).unwrap(); + let merkle_path = + self.merkle_tree.authentication_path(self.owncoin.leaf_position, &root).unwrap(); + let input = ConsensusBurnInputInfo { - leaf_position, + leaf_position: self.owncoin.leaf_position, merkle_path, - secret: self.coin.secret, - note: self.coin.note.clone(), + secret: self.owncoin.secret, + note: self.owncoin.note.clone(), value_blind: pallas::Scalar::random(&mut OsRng), }; - debug!("Building anonymous output"); - let reward_blind = pallas::Scalar::random(&mut OsRng); - let new_value_blind = input.value_blind + reward_blind; - let new_coin_blind = pallas::Base::random(&mut OsRng); - let output = ConsensusMintOutputInfo { - value: self.coin.note.value + REWARD, - epoch: 0, - public_key: PublicKey::from_secret(self.coin.secret), - value_blind: new_value_blind, - serial: self.coin.note.serial, - coin_blind: new_coin_blind, - }; - debug!("Finished building output"); + debug!("Building Consensus::ProposalV1 anonymous output"); + let output_coin_blind = pallas::Base::random(&mut OsRng); + let output_reward_blind = pallas::Scalar::random(&mut OsRng); + let output_value_blind = input.value_blind + output_reward_blind; - debug!("Building Consensus::ProposalV1 contract call for proposal"); + // We create a new random keypair for the output + let output_keypair = Keypair::random(&mut OsRng); + + // The output's serial is derived from the old serial + let new_serial = + poseidon_hash([SERIAL_PREFIX, self.owncoin.secret.inner(), self.owncoin.note.serial]); + + let output = ConsensusMintOutputInfo { + value: self.owncoin.note.value + REWARD, + epoch: 0, // We set the epoch as 0 here to eliminate a potential timelock + public_key: output_keypair.public, + value_blind: output_value_blind, + serial: new_serial, + coin_blind: output_coin_blind, + }; + + info!("Building Consensus::ProposalV1 VRF proof"); + let mut vrf_input = Vec::with_capacity(32 + blake3::OUT_LEN + 32); + vrf_input.extend_from_slice(&self.slot_checkpoint.previous_eta.to_repr()); + vrf_input.extend_from_slice(self.fork_previous_hash.as_bytes()); + vrf_input.extend_from_slice(&pallas::Base::from(self.slot_checkpoint.slot).to_repr()); + let vrf_proof = VrfProof::prove(input.secret, &vrf_input, &mut OsRng); + + info!("Building Consensus::ProposalV1 ZK proof"); let (proof, public_inputs) = create_proposal_proof( &self.proposal_zkbin, &self.proposal_pk, &input, &output, &self.slot_checkpoint, - self.fork_hash, - self.fork_previous_hash, + &vrf_proof, )?; - let input = ConsensusInput { - epoch: self.coin.note.epoch, - coin: self.coin.coin, - value_commit: public_inputs.value_commit, + let tx_input = ConsensusInput { + epoch: input.note.epoch, + value_commit: public_inputs.input_value_commit, nullifier: public_inputs.nullifier, merkle_root: public_inputs.merkle_root, signature_public: public_inputs.public_key, }; - // Encrypted note + // Output's encrypted note let note = ConsensusNote { - serial: public_inputs.new_serial, + serial: output.serial, value: output.value, - epoch: 0, - coin_blind: new_coin_blind, - value_blind: new_value_blind, + epoch: output.epoch, + coin_blind: output.coin_blind, + value_blind: output.value_blind, reward: REWARD, - reward_blind, + reward_blind: output_reward_blind, }; let encrypted_note = AeadEncryptedNote::encrypt(¬e, &output.public_key, &mut OsRng)?; - let output = ConsensusOutput { - value_commit: public_inputs.new_value_commit, - coin: public_inputs.new_coin, + let tx_output = ConsensusOutput { + value_commit: public_inputs.output_value_commit, + coin: public_inputs.output_coin, note: encrypted_note, }; - // We now fill this with necessary stuff - let new_serial_commit = public_inputs.new_serial_commit; - let slot = self.slot_checkpoint.slot; - let vrf_proof = public_inputs.vrf_proof; - let y = public_inputs.y; - let rho = public_inputs.rho; + // Construct params let params = ConsensusProposalParamsV1 { - input, - output, + input: tx_input, + output: tx_output, reward: REWARD, - reward_blind, - new_serial_commit, - slot, + reward_blind: output_reward_blind, + slot: self.slot_checkpoint.slot, fork_hash: self.fork_hash, fork_previous_hash: self.fork_previous_hash, vrf_proof, - y, - rho, + y: public_inputs.y, + rho: public_inputs.rho, }; - let proofs = vec![proof]; - // Now we should have all the params, zk proofs and signature secret. - // We return it all and let the caller deal with it. - let debris = - ConsensusProposalCallDebris { params, proofs, signature_secret: self.coin.secret }; + // Construct debris + let debris = ConsensusProposalCallDebris { + params, + proofs: vec![proof], + keypair: output_keypair, + signature_secret: input.secret, + }; Ok(debris) } } -pub fn create_proposal_proof( +fn create_proposal_proof( zkbin: &ZkBinary, pk: &ProvingKey, input: &ConsensusBurnInputInfo, output: &ConsensusMintOutputInfo, - slot_checkpoint: &SlotCheckpoint, - _fork_hash: blake3::Hash, - fork_previous_hash: blake3::Hash, + checkpoint: &SlotCheckpoint, + vrf_proof: &VrfProof, ) -> Result<(Proof, ConsensusProposalRevealed)> { // TODO: fork_hash to be used as part of rank constrain in the proof - // Proof parameters + // Calculate lottery parameters + let seed = poseidon_hash([SEED_PREFIX, input.note.serial]); + + let mut eta = [0u8; 64]; + eta[..blake3::OUT_LEN].copy_from_slice(vrf_proof.hash_output().as_bytes()); + let eta = pallas::Base::from_uniform_bytes(&eta); + + let mu_y = poseidon_hash([MU_Y_PREFIX, eta, pallas::Base::from(checkpoint.slot)]); + let y = poseidon_hash([seed, mu_y]); + let mu_rho = poseidon_hash([MU_RHO_PREFIX, eta, pallas::Base::from(checkpoint.slot)]); + let rho = poseidon_hash([seed, mu_rho]); + + // Derive the input's nullifier 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, input.value_blind); + + // Create the value commitment for the input + let input_value_commit = pedersen_commitment_u64(input.note.value, input.value_blind); + + // Merkle inclusion proof for the input let public_key = PublicKey::from_secret(input.secret); let (pub_x, pub_y) = public_key.xy(); - // Burnt coin and its merkle_root let coin = poseidon_hash([ pub_x, pub_y, - value_pallas, - epoch_pallas, + pallas::Base::from(input.note.value), + pallas::Base::from(input.note.epoch), input.note.serial, input.note.coin_blind, ]); @@ -270,79 +288,56 @@ pub fn create_proposal_proof( current }; - // New coin - let new_serial = poseidon_hash([SERIAL_PREFIX, input.secret.inner(), input.note.serial]); - let new_serial_blind = pallas::Scalar::random(&mut OsRng); - let new_serial_commit = pedersen_commitment_base(new_serial, new_serial_blind); - let new_value_commit = pedersen_commitment_u64(output.value, output.value_blind); - let new_value_pallas = pallas::Base::from(output.value); - let (new_pub_x, new_pub_y) = output.public_key.xy(); - - let new_coin = Coin::from(poseidon_hash([ - new_pub_x, - new_pub_y, - new_value_pallas, - pallas::Base::ZERO, - new_serial, + // Derive the new output coin + let (output_x, output_y) = output.public_key.xy(); + let output_coin = Coin::from(poseidon_hash([ + output_x, + output_y, + pallas::Base::from(output.value), + pallas::Base::from(output.epoch), + output.serial, output.coin_blind, ])); - let slot_pallas = pallas::Base::from(slot_checkpoint.slot); - let seed = poseidon_hash([SEED_PREFIX, input.note.serial]); - let mut vrf_input = Vec::with_capacity(32 + blake3::OUT_LEN + 32); - vrf_input.extend_from_slice(&slot_checkpoint.previous_eta.to_repr()); - vrf_input.extend_from_slice(fork_previous_hash.as_bytes()); - vrf_input.extend_from_slice(&slot_pallas.to_repr()); - let vrf_proof = VrfProof::prove(input.secret, &vrf_input, &mut OsRng); - let mut eta = [0u8; 64]; - eta[..blake3::OUT_LEN].copy_from_slice(vrf_proof.hash_output().as_bytes()); - let eta = pallas::Base::from_uniform_bytes(&eta); - let mu_y = poseidon_hash([MU_Y_PREFIX, eta, slot_pallas]); - let y = poseidon_hash([seed, mu_y]); - let mu_rho = poseidon_hash([MU_RHO_PREFIX, eta, slot_pallas]); - let rho = poseidon_hash([seed, mu_rho]); - let (sigma1, sigma2) = (slot_checkpoint.sigma1, slot_checkpoint.sigma2); - - // Generate public inputs, witnesses and proof + // Create the ZK proof let public_inputs = ConsensusProposalRevealed { nullifier, - epoch, + epoch: input.note.epoch, public_key, merkle_root, - value_commit, - new_serial, - new_serial_commit, - new_value_commit, - new_coin, - vrf_proof, + input_value_commit, + reward: REWARD, + output_value_commit: pedersen_commitment_u64(output.value, output.value_blind), + output_coin, + vrf_proof: *vrf_proof, mu_y, y, mu_rho, rho, - sigma1, - sigma2, + sigma1: checkpoint.sigma1, + sigma2: checkpoint.sigma2, + headstart: HEADSTART, }; let prover_witnesses = vec![ Witness::Base(Value::known(input.secret.inner())), Witness::Base(Value::known(input.note.serial)), Witness::Base(Value::known(pallas::Base::from(input.note.value))), - Witness::Base(Value::known(epoch_pallas)), - Witness::Base(Value::known(REWARD_PALLAS)), + Witness::Base(Value::known(pallas::Base::from(input.note.epoch))), + Witness::Base(Value::known(pallas::Base::from(REWARD))), Witness::Scalar(Value::known(input.value_blind)), Witness::Base(Value::known(input.note.coin_blind)), Witness::Uint32(Value::known(u64::from(input.leaf_position).try_into().unwrap())), Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())), - Witness::Scalar(Value::known(new_serial_blind)), - Witness::Base(Value::known(new_pub_x)), - Witness::Base(Value::known(new_pub_y)), + Witness::Base(Value::known(output_x)), + Witness::Base(Value::known(output_y)), Witness::Scalar(Value::known(output.value_blind)), Witness::Base(Value::known(output.coin_blind)), - Witness::Base(Value::known(mu_y)), - Witness::Base(Value::known(mu_rho)), - Witness::Base(Value::known(sigma1)), - Witness::Base(Value::known(sigma2)), - Witness::Base(Value::known(HEADSTART)), + Witness::Base(Value::known(public_inputs.mu_y)), + Witness::Base(Value::known(public_inputs.mu_rho)), + Witness::Base(Value::known(public_inputs.sigma1)), + Witness::Base(Value::known(public_inputs.sigma2)), + Witness::Base(Value::known(public_inputs.headstart)), ]; let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone()); diff --git a/src/contract/consensus/src/client/unstake_request_v1.rs b/src/contract/consensus/src/client/unstake_request_v1.rs index f0b9d6f7b..8eea7e95a 100644 --- a/src/contract/consensus/src/client/unstake_request_v1.rs +++ b/src/contract/consensus/src/client/unstake_request_v1.rs @@ -90,7 +90,6 @@ impl ConsensusUnstakeRequestCallBuilder { let input = ConsensusInput { epoch: self.coin.note.epoch, - coin: self.coin.coin, value_commit: public_inputs.value_commit, nullifier: public_inputs.nullifier, merkle_root: public_inputs.merkle_root, diff --git a/src/contract/consensus/src/client/unstake_v1.rs b/src/contract/consensus/src/client/unstake_v1.rs index f8325ce5f..2e1d4838b 100644 --- a/src/contract/consensus/src/client/unstake_v1.rs +++ b/src/contract/consensus/src/client/unstake_v1.rs @@ -82,7 +82,6 @@ impl ConsensusUnstakeCallBuilder { let input = ConsensusInput { epoch: self.coin.note.epoch, - coin: self.coin.coin, value_commit: public_inputs.value_commit, nullifier: public_inputs.nullifier, merkle_root: public_inputs.merkle_root, diff --git a/src/contract/consensus/src/model.rs b/src/contract/consensus/src/model.rs index 9fe18a3a4..68012ee32 100644 --- a/src/contract/consensus/src/model.rs +++ b/src/contract/consensus/src/model.rs @@ -36,6 +36,7 @@ pub struct ConsensusGenesisStakeParamsV1 { /// Parameters for `Consensus::Proposal` #[derive(Clone, Debug, SerialEncodable, SerialDecodable)] +// ANCHOR: ConsensusProposalParams pub struct ConsensusProposalParamsV1 { /// Anonymous input pub input: ConsensusInput, @@ -43,10 +44,8 @@ pub struct ConsensusProposalParamsV1 { pub output: ConsensusOutput, /// Reward value pub reward: u64, - /// Blinding factor for reward value + /// Revealed blinding factor for reward value pub reward_blind: pallas::Scalar, - /// Pedersen commitment for the output's serial number - pub new_serial_commit: pallas::Point, /// Rewarded slot pub slot: u64, /// Extending fork last proposal/block hash @@ -60,6 +59,7 @@ pub struct ConsensusProposalParamsV1 { /// Lottery rho used pub rho: pallas::Base, } +// ANCHOR_END: ConsensusProposalParams /// State update for `Consensus::Proposal` #[derive(Clone, Debug, SerialEncodable, SerialDecodable)] @@ -88,8 +88,8 @@ pub struct ConsensusUnstakeRequestParamsV1 { pub const EPOCH_LENGTH: u64 = 10; /// Slot time in seconds pub const SLOT_TIME: u64 = 90; -/// Grace period days target -pub const GRACE_PERIOD_DAYS: u64 = 2; +// Stake/Unstake timelock length in epochs +pub const GRACE_PERIOD: u64 = calculate_grace_period(); /// Configured block reward (1 DRK == 1 * 10^8) pub const REWARD: u64 = 100_000_000; /// Reward `pallas::Base`, calculated by: pallas::Base::from(REWARD) @@ -130,10 +130,13 @@ pub struct SlotCheckpoint { pub sigma2: pallas::Base, } -/// Auxiliary function to calculate the grace(locked) period, denominated +/// Auxiliary function to calculate the grace (locked) period, denominated /// in epochs. #[inline] pub const fn calculate_grace_period() -> u64 { + // Grace period days target + const GRACE_PERIOD_DAYS: u64 = 2; + // 86400 seconds in a day (86400 * GRACE_PERIOD_DAYS) / (SLOT_TIME * EPOCH_LENGTH) } diff --git a/src/contract/money/src/client/unstake_v1.rs b/src/contract/money/src/client/unstake_v1.rs index 4cc470433..e3646ad25 100644 --- a/src/contract/money/src/client/unstake_v1.rs +++ b/src/contract/money/src/client/unstake_v1.rs @@ -147,7 +147,6 @@ impl MoneyUnstakeCallBuilder { let input = ConsensusInput { epoch: self.coin.note.epoch, - coin: self.coin.coin, value_commit: public_inputs.value_commit, nullifier: self.nullifier, merkle_root: self.merkle_root, diff --git a/src/contract/money/src/model.rs b/src/contract/money/src/model.rs index 23dd2ef12..ef0178461 100644 --- a/src/contract/money/src/model.rs +++ b/src/contract/money/src/model.rs @@ -101,8 +101,6 @@ pub struct Input { pub struct ConsensusInput { /// Epoch the coin was minted pub epoch: u64, - /// The coin - pub coin: Coin, /// Pedersen commitment for the staked coin's value pub value_commit: pallas::Point, /// Revealed nullifier