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

contract/consensus: Clean up Proposal client API

parazyd 3 лет назад
Родитель
Сommit
7b24085790

+ 140 - 145
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<Proof>,
+    /// 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<pallas::Base> {
-        let epoch_palas = pallas::Base::from(self.epoch);
+    fn to_vec(&self) -> Vec<pallas::Base> {
         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<ConsensusProposalCallDebris> {
-        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);
+        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;
+
+        // 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.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,
+            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,
         };
-        debug!("Finished building output");
 
-        debug!("Building Consensus::ProposalV1 contract call for proposal");
+        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(&note, &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());

+ 0 - 1
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,

+ 0 - 1
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,

+ 9 - 6
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)
 }

+ 0 - 1
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,

+ 0 - 2
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