|
@@ -19,8 +19,7 @@
|
|
|
use darkfi_money_contract::{
|
|
use darkfi_money_contract::{
|
|
|
error::MoneyError, CONSENSUS_CONTRACT_COINS_TREE, CONSENSUS_CONTRACT_COIN_MERKLE_TREE,
|
|
error::MoneyError, CONSENSUS_CONTRACT_COINS_TREE, CONSENSUS_CONTRACT_COIN_MERKLE_TREE,
|
|
|
CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_INFO_TREE,
|
|
CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_INFO_TREE,
|
|
|
- CONSENSUS_CONTRACT_NULLIFIERS_TREE, CONSENSUS_CONTRACT_UNSTAKED_COINS_TREE,
|
|
|
|
|
- CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1,
|
|
|
|
|
|
|
+ CONSENSUS_CONTRACT_NULLIFIERS_TREE, CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1,
|
|
|
};
|
|
};
|
|
|
use darkfi_sdk::{
|
|
use darkfi_sdk::{
|
|
|
crypto::{pasta_prelude::*, pedersen_commitment_u64, poseidon_hash, ContractId, MerkleNode},
|
|
crypto::{pasta_prelude::*, pedersen_commitment_u64, poseidon_hash, ContractId, MerkleNode},
|
|
@@ -36,8 +35,8 @@ use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
|
|
|
use crate::{
|
|
use crate::{
|
|
|
error::ConsensusError,
|
|
error::ConsensusError,
|
|
|
model::{
|
|
model::{
|
|
|
- calculate_grace_period, ConsensusProposalParamsV1, ConsensusProposalUpdateV1,
|
|
|
|
|
- SlotCheckpoint, HEADSTART, MU_RHO_PREFIX, MU_Y_PREFIX,
|
|
|
|
|
|
|
+ ConsensusProposalParamsV1, ConsensusProposalUpdateV1, SlotCheckpoint, GRACE_PERIOD,
|
|
|
|
|
+ HEADSTART, MU_RHO_PREFIX, MU_Y_PREFIX,
|
|
|
},
|
|
},
|
|
|
ConsensusFunction,
|
|
ConsensusFunction,
|
|
|
};
|
|
};
|
|
@@ -53,44 +52,27 @@ pub(crate) fn consensus_proposal_get_metadata_v1(
|
|
|
|
|
|
|
|
// Public inputs for the ZK proofs we have to verify
|
|
// Public inputs for the ZK proofs we have to verify
|
|
|
let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
|
|
let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
|
|
|
- // Public keys for the transaction signatures we have to verify
|
|
|
|
|
|
|
+ // Public keys for the transaction signatures we have to verify.
|
|
|
|
|
+ // The transaction should be signed with the same key that is used for
|
|
|
|
|
+ // the VRF proof, and also constrained in ZK by enforcing its derivation.
|
|
|
let signature_pubkeys = vec![params.input.signature_public];
|
|
let signature_pubkeys = vec![params.input.signature_public];
|
|
|
|
|
|
|
|
- // Grab the nullifier for the burnt coin
|
|
|
|
|
- let nullifier = ¶ms.input.nullifier;
|
|
|
|
|
-
|
|
|
|
|
- // Grab the mint epoch pallas for the burnt coin
|
|
|
|
|
- let epoch_pallas = pallas::Base::from(params.input.epoch);
|
|
|
|
|
-
|
|
|
|
|
// Grab the public key coordinates for the burnt coin
|
|
// Grab the public key coordinates for the burnt coin
|
|
|
let (pub_x, pub_y) = ¶ms.input.signature_public.xy();
|
|
let (pub_x, pub_y) = ¶ms.input.signature_public.xy();
|
|
|
|
|
|
|
|
- // Grab the burnt coin merkle root
|
|
|
|
|
- let merkle_root = params.input.merkle_root.inner();
|
|
|
|
|
-
|
|
|
|
|
// Grab the pedersen commitment for the burnt value
|
|
// Grab the pedersen commitment for the burnt value
|
|
|
- let value_coords = ¶ms.input.value_commit.to_affine().coordinates().unwrap();
|
|
|
|
|
-
|
|
|
|
|
- // Grab the reward pallas
|
|
|
|
|
- let reward_pallas = pallas::Base::from(params.reward);
|
|
|
|
|
|
|
+ let input_value_coords = ¶ms.input.value_commit.to_affine().coordinates().unwrap();
|
|
|
|
|
|
|
|
// Grab the pedersen commitment for the minted value
|
|
// Grab the pedersen commitment for the minted value
|
|
|
- let new_value_coords = ¶ms.output.value_commit.to_affine().coordinates().unwrap();
|
|
|
|
|
|
|
+ let output_value_coords = ¶ms.output.value_commit.to_affine().coordinates().unwrap();
|
|
|
|
|
|
|
|
- // Grab the new coin
|
|
|
|
|
- let new_coin = params.output.coin.inner();
|
|
|
|
|
-
|
|
|
|
|
- // Grab proposal coin y and rho for lottery
|
|
|
|
|
- let y = ¶ms.y;
|
|
|
|
|
- let rho = ¶ms.rho;
|
|
|
|
|
-
|
|
|
|
|
- // Grab the slot checkpoint to validate consensus parameters against
|
|
|
|
|
- let slot = ¶ms.slot;
|
|
|
|
|
- let Some(slot_checkpoint) = get_slot_checkpoint(*slot)? else {
|
|
|
|
|
- msg!("[ConsensusProposalV1] Error: Missing slot checkpoint {} from db", slot);
|
|
|
|
|
|
|
+ // Grab the slot checkpoint to validate consensus params against
|
|
|
|
|
+ let Some(slot_checkpoint) = get_slot_checkpoint(params.slot)? else {
|
|
|
|
|
+ msg!("[ConsensusProposalV1] Error: Missing slot checkpoint {} from db", params.slot);
|
|
|
return Err(ConsensusError::ProposalMissingSlotCheckpoint.into())
|
|
return Err(ConsensusError::ProposalMissingSlotCheckpoint.into())
|
|
|
};
|
|
};
|
|
|
let slot_checkpoint: SlotCheckpoint = deserialize(&slot_checkpoint)?;
|
|
let slot_checkpoint: SlotCheckpoint = deserialize(&slot_checkpoint)?;
|
|
|
|
|
+ let slot_fp = pallas::Base::from(slot_checkpoint.slot);
|
|
|
|
|
|
|
|
// Verify proposal extends a known fork
|
|
// Verify proposal extends a known fork
|
|
|
if !slot_checkpoint.fork_hashes.contains(¶ms.fork_hash) {
|
|
if !slot_checkpoint.fork_hashes.contains(¶ms.fork_hash) {
|
|
@@ -98,35 +80,35 @@ pub(crate) fn consensus_proposal_get_metadata_v1(
|
|
|
return Err(ConsensusError::ProposalExtendsUnknownFork.into())
|
|
return Err(ConsensusError::ProposalExtendsUnknownFork.into())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // TODO: add fork rank check using params.fork_hash
|
|
|
|
|
|
|
+ // TODO: Add fork rank check using params.fork_hash
|
|
|
|
|
|
|
|
- // And sequence is correct
|
|
|
|
|
|
|
+ // Verify sequence is correct
|
|
|
if !slot_checkpoint.fork_previous_hashes.contains(¶ms.fork_previous_hash) {
|
|
if !slot_checkpoint.fork_previous_hashes.contains(¶ms.fork_previous_hash) {
|
|
|
- msg!(
|
|
|
|
|
- "[ConsensusProposalV1] Error: Proposal extends unknown fork {}",
|
|
|
|
|
- params.fork_previous_hash
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ let fork_prev = ¶ms.fork_previous_hash;
|
|
|
|
|
+ msg!("[ConsensusProposalV1] Error: Proposal extends unknown fork {}", fork_prev);
|
|
|
return Err(ConsensusError::ProposalExtendsUnknownFork.into())
|
|
return Err(ConsensusError::ProposalExtendsUnknownFork.into())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Verify eta VRF proof
|
|
|
|
|
- let slot_pallas = pallas::Base::from(slot_checkpoint.slot);
|
|
|
|
|
|
|
+ // Construct VRF input
|
|
|
let mut vrf_input = Vec::with_capacity(32 + blake3::OUT_LEN + 32);
|
|
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(&slot_checkpoint.previous_eta.to_repr());
|
|
|
vrf_input.extend_from_slice(params.fork_previous_hash.as_bytes());
|
|
vrf_input.extend_from_slice(params.fork_previous_hash.as_bytes());
|
|
|
- vrf_input.extend_from_slice(&slot_pallas.to_repr());
|
|
|
|
|
- let vrf_proof = ¶ms.vrf_proof;
|
|
|
|
|
- if !vrf_proof.verify(params.input.signature_public, &vrf_input) {
|
|
|
|
|
|
|
+ vrf_input.extend_from_slice(&slot_fp.to_repr());
|
|
|
|
|
+
|
|
|
|
|
+ // Verify VRF proof
|
|
|
|
|
+ if !params.vrf_proof.verify(params.input.signature_public, &vrf_input) {
|
|
|
msg!("[ConsensusProposalV1] Error: eta VRF proof couldn't be verified");
|
|
msg!("[ConsensusProposalV1] Error: eta VRF proof couldn't be verified");
|
|
|
return Err(ConsensusError::ProposalErroneousVrfProof.into())
|
|
return Err(ConsensusError::ProposalErroneousVrfProof.into())
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // Construct eta
|
|
|
let mut eta = [0u8; 64];
|
|
let mut eta = [0u8; 64];
|
|
|
- eta[..blake3::OUT_LEN].copy_from_slice(vrf_proof.hash_output().as_bytes());
|
|
|
|
|
|
|
+ eta[..blake3::OUT_LEN].copy_from_slice(params.vrf_proof.hash_output().as_bytes());
|
|
|
let eta = pallas::Base::from_uniform_bytes(&eta);
|
|
let eta = pallas::Base::from_uniform_bytes(&eta);
|
|
|
|
|
|
|
|
// Calculate election seeds
|
|
// Calculate election seeds
|
|
|
- let mu_y = poseidon_hash([MU_Y_PREFIX, eta, slot_pallas]);
|
|
|
|
|
- let mu_rho = poseidon_hash([MU_RHO_PREFIX, eta, slot_pallas]);
|
|
|
|
|
|
|
+ let mu_y = poseidon_hash([MU_Y_PREFIX, eta, slot_fp]);
|
|
|
|
|
+ let mu_rho = poseidon_hash([MU_RHO_PREFIX, eta, slot_fp]);
|
|
|
|
|
|
|
|
// Grab sigmas from slot checkpoint
|
|
// Grab sigmas from slot checkpoint
|
|
|
let (sigma1, sigma2) = (slot_checkpoint.sigma1, slot_checkpoint.sigma2);
|
|
let (sigma1, sigma2) = (slot_checkpoint.sigma1, slot_checkpoint.sigma2);
|
|
@@ -134,21 +116,21 @@ pub(crate) fn consensus_proposal_get_metadata_v1(
|
|
|
zk_public_inputs.push((
|
|
zk_public_inputs.push((
|
|
|
CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1.to_string(),
|
|
CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1.to_string(),
|
|
|
vec![
|
|
vec![
|
|
|
- nullifier.inner(),
|
|
|
|
|
- epoch_pallas,
|
|
|
|
|
|
|
+ params.input.nullifier.inner(),
|
|
|
|
|
+ pallas::Base::from(params.input.epoch),
|
|
|
*pub_x,
|
|
*pub_x,
|
|
|
*pub_y,
|
|
*pub_y,
|
|
|
- merkle_root,
|
|
|
|
|
- *value_coords.x(),
|
|
|
|
|
- *value_coords.y(),
|
|
|
|
|
- reward_pallas,
|
|
|
|
|
- *new_value_coords.x(),
|
|
|
|
|
- *new_value_coords.y(),
|
|
|
|
|
- new_coin,
|
|
|
|
|
|
|
+ params.input.merkle_root.inner(),
|
|
|
|
|
+ *input_value_coords.x(),
|
|
|
|
|
+ *input_value_coords.y(),
|
|
|
|
|
+ pallas::Base::from(params.reward),
|
|
|
|
|
+ *output_value_coords.x(),
|
|
|
|
|
+ *output_value_coords.y(),
|
|
|
|
|
+ params.output.coin.inner(),
|
|
|
mu_y,
|
|
mu_y,
|
|
|
- *y,
|
|
|
|
|
|
|
+ params.y,
|
|
|
mu_rho,
|
|
mu_rho,
|
|
|
- *rho,
|
|
|
|
|
|
|
+ params.rho,
|
|
|
sigma1,
|
|
sigma1,
|
|
|
sigma2,
|
|
sigma2,
|
|
|
HEADSTART,
|
|
HEADSTART,
|
|
@@ -179,7 +161,6 @@ pub(crate) fn consensus_proposal_process_instruction_v1(
|
|
|
let nullifiers_db = db_lookup(cid, CONSENSUS_CONTRACT_NULLIFIERS_TREE)?;
|
|
let nullifiers_db = db_lookup(cid, CONSENSUS_CONTRACT_NULLIFIERS_TREE)?;
|
|
|
let coins_db = db_lookup(cid, CONSENSUS_CONTRACT_COINS_TREE)?;
|
|
let coins_db = db_lookup(cid, CONSENSUS_CONTRACT_COINS_TREE)?;
|
|
|
let coin_roots_db = db_lookup(cid, CONSENSUS_CONTRACT_COIN_ROOTS_TREE)?;
|
|
let coin_roots_db = db_lookup(cid, CONSENSUS_CONTRACT_COIN_ROOTS_TREE)?;
|
|
|
- let unstaked_coins_db = db_lookup(cid, CONSENSUS_CONTRACT_UNSTAKED_COINS_TREE)?;
|
|
|
|
|
|
|
|
|
|
// ===================================
|
|
// ===================================
|
|
|
// Perform the actual state transition
|
|
// Perform the actual state transition
|
|
@@ -188,7 +169,10 @@ pub(crate) fn consensus_proposal_process_instruction_v1(
|
|
|
msg!("[ConsensusProposalV1] Validating anonymous input");
|
|
msg!("[ConsensusProposalV1] Validating anonymous input");
|
|
|
|
|
|
|
|
// The coin has passed through the grace period and is allowed to propose.
|
|
// The coin has passed through the grace period and is allowed to propose.
|
|
|
- if input.epoch != 0 && get_verifying_slot_epoch() - input.epoch <= calculate_grace_period() {
|
|
|
|
|
|
|
+ // Important to note is that the epoch has to be enforced through ZK. The
|
|
|
|
|
+ // only time when the epoch of the burned coin can be zero is when there
|
|
|
|
|
+ // was a previous winning proposal, or the coin was minted at genesis.
|
|
|
|
|
+ if input.epoch != 0 && get_verifying_slot_epoch() - input.epoch <= GRACE_PERIOD {
|
|
|
msg!("[ConsensusProposalV1] Error: Coin is not allowed to make proposals yet");
|
|
msg!("[ConsensusProposalV1] Error: Coin is not allowed to make proposals yet");
|
|
|
return Err(ConsensusError::CoinStillInGracePeriod.into())
|
|
return Err(ConsensusError::CoinStillInGracePeriod.into())
|
|
|
}
|
|
}
|
|
@@ -206,15 +190,8 @@ pub(crate) fn consensus_proposal_process_instruction_v1(
|
|
|
return Err(MoneyError::DuplicateNullifier.into())
|
|
return Err(MoneyError::DuplicateNullifier.into())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /*
|
|
|
|
|
- // Check that the coin hasn't existed before in unstake set.
|
|
|
|
|
- if db_contains_key(unstaked_coins_db, &serialize(&input.coin))? {
|
|
|
|
|
- msg!("[ConsensusProposalV1] Error: Unstaked coin found in input");
|
|
|
|
|
- return Err(MoneyError::DuplicateCoin.into())
|
|
|
|
|
- }
|
|
|
|
|
- */
|
|
|
|
|
-
|
|
|
|
|
- // Verify value commits match between burnt and mint inputs
|
|
|
|
|
|
|
+ // Verify value commits match between burnt and mint inputs.
|
|
|
|
|
+ // Here we check that input+reward == output
|
|
|
let mut valcom_total = pallas::Point::identity();
|
|
let mut valcom_total = pallas::Point::identity();
|
|
|
valcom_total += input.value_commit;
|
|
valcom_total += input.value_commit;
|
|
|
valcom_total += pedersen_commitment_u64(params.reward, params.reward_blind);
|
|
valcom_total += pedersen_commitment_u64(params.reward, params.reward_blind);
|
|
@@ -224,27 +201,18 @@ pub(crate) fn consensus_proposal_process_instruction_v1(
|
|
|
return Err(MoneyError::ValueMismatch.into())
|
|
return Err(MoneyError::ValueMismatch.into())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Newly created coin for this call is in the output. Here we gather it,
|
|
|
|
|
- // and we also check that it hasn't existed before.
|
|
|
|
|
- let coin = serialize(&output.coin);
|
|
|
|
|
- if db_contains_key(coins_db, &coin)? {
|
|
|
|
|
|
|
+ // Newly created coin for this call is in the output. Here we check that
|
|
|
|
|
+ // it hasn't existed before.
|
|
|
|
|
+ if db_contains_key(coins_db, &serialize(&output.coin))? {
|
|
|
msg!("[ConsensusProposalV1] Error: Duplicate coin found in output");
|
|
msg!("[ConsensusProposalV1] Error: Duplicate coin found in output");
|
|
|
return Err(MoneyError::DuplicateCoin.into())
|
|
return Err(MoneyError::DuplicateCoin.into())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Check that the coin hasn't existed before in unstake set.
|
|
|
|
|
- if db_contains_key(unstaked_coins_db, &coin)? {
|
|
|
|
|
- msg!("[ConsensusProposalV1] Error: Unstaked coin found in output");
|
|
|
|
|
- return Err(MoneyError::DuplicateCoin.into())
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
// At this point the state transition has passed, so we create a state update
|
|
// At this point the state transition has passed, so we create a state update
|
|
|
let update = ConsensusProposalUpdateV1 { nullifier: input.nullifier, coin: output.coin };
|
|
let update = ConsensusProposalUpdateV1 { nullifier: input.nullifier, coin: output.coin };
|
|
|
let mut update_data = vec![];
|
|
let mut update_data = vec![];
|
|
|
update_data.write_u8(ConsensusFunction::ProposalV1 as u8)?;
|
|
update_data.write_u8(ConsensusFunction::ProposalV1 as u8)?;
|
|
|
update.encode(&mut update_data)?;
|
|
update.encode(&mut update_data)?;
|
|
|
-
|
|
|
|
|
- // and return it
|
|
|
|
|
Ok(update_data)
|
|
Ok(update_data)
|
|
|
}
|
|
}
|
|
|
|
|
|