mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-10 23:27:56 -05:00
contract/consensus: chop chop
This commit is contained in:
@@ -23,24 +23,17 @@ use darkfi::{
|
||||
zkas::ZkBinary,
|
||||
Result,
|
||||
};
|
||||
use darkfi_money_contract::client::{ConsensusNote, MoneyNote};
|
||||
use darkfi_money_contract::client::ConsensusNote;
|
||||
use darkfi_sdk::{
|
||||
crypto::{
|
||||
pasta_prelude::*, pedersen_commitment_base, pedersen_commitment_u64, poseidon_hash, Coin,
|
||||
MerkleNode, MerklePosition, Nullifier, PublicKey, SecretKey,
|
||||
pasta_prelude::*, pedersen_commitment_u64, poseidon_hash, Coin, MerkleNode, MerklePosition,
|
||||
Nullifier, PublicKey, SecretKey,
|
||||
},
|
||||
incrementalmerkletree::Hashable,
|
||||
pasta::pallas,
|
||||
};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
pub struct TransactionBuilderInputInfo {
|
||||
pub leaf_position: MerklePosition,
|
||||
pub merkle_path: Vec<MerkleNode>,
|
||||
pub secret: SecretKey,
|
||||
pub note: MoneyNote,
|
||||
}
|
||||
|
||||
pub struct ConsensusMintOutputInfo {
|
||||
pub value: u64,
|
||||
pub epoch: u64,
|
||||
@@ -200,112 +193,3 @@ pub fn create_consensus_burn_proof(
|
||||
|
||||
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<pallas::Base> {
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -35,12 +35,7 @@ pub mod genesis_stake_v1;
|
||||
pub mod stake_v1;
|
||||
|
||||
/// Proposal transaction building API.
|
||||
/// This transaction is a chain of `Consensus::ProposalBurnV1`, `Consensus::ProposalRewardV1`
|
||||
/// and `Consensus::ProposalMintV1` contract calls.
|
||||
pub mod proposal_v1;
|
||||
|
||||
/// Proposal transaction building API.
|
||||
pub mod proposal_v1_2;
|
||||
|
||||
/// `Consensus::UnstakeV1` API
|
||||
pub mod unstake_v1;
|
||||
|
||||
@@ -25,53 +25,45 @@ use darkfi::{
|
||||
Result,
|
||||
};
|
||||
use darkfi_money_contract::{
|
||||
client::{MoneyNote, OwnCoin},
|
||||
model::{ConsensusInput, Input, Output, PALLAS_ZERO},
|
||||
client::{ConsensusNote, ConsensusOwnCoin},
|
||||
model::{ConsensusInput, ConsensusOutput, PALLAS_ZERO},
|
||||
};
|
||||
use darkfi_sdk::{
|
||||
crypto::{
|
||||
ecvrf::VrfProof, note::AeadEncryptedNote, pasta_prelude::*, pedersen_commitment_base,
|
||||
pedersen_commitment_u64, poseidon_hash, Coin, MerkleTree, Nullifier, PublicKey, SecretKey,
|
||||
TokenId, CONSENSUS_CONTRACT_ID, DARK_TOKEN_ID,
|
||||
pedersen_commitment_u64, poseidon_hash, Coin, MerkleNode, MerkleTree, Nullifier, PublicKey,
|
||||
SecretKey,
|
||||
},
|
||||
incrementalmerkletree::Tree,
|
||||
incrementalmerkletree::{Hashable, Tree},
|
||||
pasta::{group::ff::FromUniformBytes, pallas},
|
||||
};
|
||||
use log::{debug, info};
|
||||
use log::debug;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use crate::{
|
||||
client::common::{create_unstake_burn_proof, TransactionBuilderInputInfo as UnstakeTBII},
|
||||
client::common::{ConsensusBurnInputInfo, ConsensusMintOutputInfo},
|
||||
model::{
|
||||
ConsensusProposalBurnParamsV1, ConsensusProposalMintParamsV1,
|
||||
ConsensusProposalRewardParamsV1, HEADSTART, MU_RHO_PREFIX, MU_Y_PREFIX, REWARD,
|
||||
REWARD_PALLAS, SEED_PREFIX, SERIAL_PREFIX,
|
||||
ConsensusProposalParamsV1, HEADSTART, MU_RHO_PREFIX, MU_Y_PREFIX, REWARD, REWARD_PALLAS,
|
||||
SEED_PREFIX, SERIAL_PREFIX,
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: Remove TransactionBuilderOutputInfo
|
||||
pub struct TransactionBuilderOutputInfo {
|
||||
pub value: u64,
|
||||
pub token_id: TokenId,
|
||||
pub public_key: PublicKey,
|
||||
}
|
||||
|
||||
pub struct ConsensusProposalCallDebris {
|
||||
pub burn_params: ConsensusProposalBurnParamsV1,
|
||||
pub burn_proofs: Vec<Proof>,
|
||||
pub reward_params: ConsensusProposalRewardParamsV1,
|
||||
pub reward_proofs: Vec<Proof>,
|
||||
pub mint_params: ConsensusProposalMintParamsV1,
|
||||
pub mint_proofs: Vec<Proof>,
|
||||
pub params: ConsensusProposalParamsV1,
|
||||
pub proofs: Vec<Proof>,
|
||||
pub signature_secret: SecretKey,
|
||||
}
|
||||
|
||||
pub struct ConsensusProposalRewardRevealed {
|
||||
pub struct ConsensusProposalRevealed {
|
||||
pub nullifier: Nullifier,
|
||||
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 vrf_proof: VrfProof,
|
||||
pub mu_y: pallas::Base,
|
||||
pub y: pallas::Base,
|
||||
@@ -81,25 +73,31 @@ pub struct ConsensusProposalRewardRevealed {
|
||||
pub sigma2: pallas::Base,
|
||||
}
|
||||
|
||||
impl ConsensusProposalRewardRevealed {
|
||||
impl ConsensusProposalRevealed {
|
||||
pub fn to_vec(&self) -> Vec<pallas::Base> {
|
||||
let epoch_palas = pallas::Base::from(self.epoch);
|
||||
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();
|
||||
|
||||
// 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,
|
||||
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(),
|
||||
self.mu_y,
|
||||
self.y,
|
||||
self.mu_rho,
|
||||
@@ -111,258 +109,180 @@ impl ConsensusProposalRewardRevealed {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConsensusProposalMintRevealed {
|
||||
pub coin: Coin,
|
||||
pub value_commit: pallas::Point,
|
||||
pub token_commit: pallas::Point,
|
||||
pub serial_commit: pallas::Point,
|
||||
}
|
||||
|
||||
impl ConsensusProposalMintRevealed {
|
||||
pub fn to_vec(&self) -> Vec<pallas::Base> {
|
||||
let valcom_coords = self.value_commit.to_affine().coordinates().unwrap();
|
||||
let tokcom_coords = self.token_commit.to_affine().coordinates().unwrap();
|
||||
let sercom_coords = self.serial_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
// NOTE: It's important to keep these in the same order
|
||||
// as the `constrain_instance` calls in the zkas code.
|
||||
vec![
|
||||
self.coin.inner(),
|
||||
*valcom_coords.x(),
|
||||
*valcom_coords.y(),
|
||||
*tokcom_coords.x(),
|
||||
*tokcom_coords.y(),
|
||||
*sercom_coords.x(),
|
||||
*sercom_coords.y(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct holding necessary information to build a proposal transaction.
|
||||
pub struct ConsensusProposalCallBuilder {
|
||||
/// `OwnCoin` we're given to use in this builder
|
||||
pub coin: OwnCoin,
|
||||
/// Recipient's public key
|
||||
pub recipient: PublicKey,
|
||||
/// `ConsensusOwnCoin` we're given to use in this builder
|
||||
pub coin: ConsensusOwnCoin,
|
||||
/// Rewarded slot checkpoint
|
||||
pub slot_checkpoint: SlotCheckpoint,
|
||||
/// Merkle tree of coins used to create inclusion proofs
|
||||
pub tree: MerkleTree,
|
||||
/// `Burn_V1` zkas circuit ZkBinary
|
||||
pub burn_zkbin: ZkBinary,
|
||||
/// Proving key for the `Burn_V1` zk circuit
|
||||
pub burn_pk: ProvingKey,
|
||||
/// `ProposalReward_V1` zkas circuit ZkBinary
|
||||
pub reward_zkbin: ZkBinary,
|
||||
/// Proving key for the `Reward_V1` zk circuit
|
||||
pub reward_pk: ProvingKey,
|
||||
/// `ProposalMint_V1` zkas circuit ZkBinary
|
||||
pub mint_zkbin: ZkBinary,
|
||||
/// Proving key for the `Mint_V1` zk circuit
|
||||
pub mint_pk: ProvingKey,
|
||||
/// `Proposal_V1` zkas circuit ZkBinary
|
||||
pub proposal_zkbin: ZkBinary,
|
||||
/// Proving key for the `Proposal_V1` zk circuit
|
||||
pub proposal_pk: ProvingKey,
|
||||
}
|
||||
|
||||
impl ConsensusProposalCallBuilder {
|
||||
pub fn build(&self) -> Result<ConsensusProposalCallDebris> {
|
||||
debug!("Building Consensus::ProposalBurnV1 contract call for proposal");
|
||||
let value = self.coin.note.value;
|
||||
let token_id = self.coin.note.token_id;
|
||||
assert!(value != 0);
|
||||
assert!(token_id == *DARK_TOKEN_ID);
|
||||
|
||||
debug!("Building anonymous input for proposal");
|
||||
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 input = UnstakeTBII {
|
||||
let input = ConsensusBurnInputInfo {
|
||||
leaf_position,
|
||||
merkle_path,
|
||||
secret: self.coin.secret,
|
||||
note: self.coin.note.clone(),
|
||||
value_blind: pallas::Scalar::random(&mut OsRng),
|
||||
};
|
||||
debug!("Finished building input for proposal");
|
||||
|
||||
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 for proposal");
|
||||
let (proof, public_inputs) = create_unstake_burn_proof(
|
||||
&self.burn_zkbin,
|
||||
&self.burn_pk,
|
||||
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 contract call for proposal");
|
||||
let (proof, public_inputs) = create_proposal_proof(
|
||||
&self.proposal_zkbin,
|
||||
&self.proposal_pk,
|
||||
&input,
|
||||
value_blind,
|
||||
token_blind,
|
||||
user_data_blind,
|
||||
signature_secret,
|
||||
&output,
|
||||
&self.slot_checkpoint,
|
||||
)?;
|
||||
|
||||
let input = Input {
|
||||
let input = ConsensusInput {
|
||||
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,
|
||||
signature_public: public_inputs.public_key,
|
||||
};
|
||||
|
||||
// We now fill this with necessary stuff
|
||||
let burnt_secret_key = self.coin.secret.inner();
|
||||
let public_key = PublicKey::from_secret(burnt_secret_key.into());
|
||||
let burn_params =
|
||||
ConsensusProposalBurnParamsV1 { token_blind, input: input.clone(), public_key };
|
||||
let burn_proofs = vec![proof];
|
||||
let burnt_input = input;
|
||||
|
||||
debug!("Building Consensus::ProposalMintV1 contract call for proposal");
|
||||
let new_value = value + REWARD;
|
||||
let nullifier = public_inputs.nullifier;
|
||||
let merkle_root = public_inputs.merkle_root;
|
||||
let signature_public = public_inputs.signature_public;
|
||||
|
||||
debug!("Building anonymous output for proposal");
|
||||
let output =
|
||||
TransactionBuilderOutputInfo { value: new_value, token_id, public_key: self.recipient };
|
||||
debug!("Finished building output for proposal");
|
||||
|
||||
let burnt_serial = self.coin.note.serial;
|
||||
let spend_hook = CONSENSUS_CONTRACT_ID.inner();
|
||||
let user_data = pallas::Base::random(&mut OsRng);
|
||||
let coin_blind = pallas::Base::random(&mut OsRng);
|
||||
let serial_blind = pallas::Scalar::random(&mut OsRng);
|
||||
|
||||
info!("Creating stake mint proof for output for proposal");
|
||||
let (proof, public_inputs, serial) = create_proposal_mint_proof(
|
||||
&self.mint_zkbin,
|
||||
&self.mint_pk,
|
||||
&output,
|
||||
value_blind,
|
||||
token_blind,
|
||||
serial_blind,
|
||||
burnt_secret_key,
|
||||
burnt_serial,
|
||||
spend_hook,
|
||||
user_data,
|
||||
coin_blind,
|
||||
)?;
|
||||
|
||||
// Encrypted note
|
||||
let note = MoneyNote {
|
||||
serial,
|
||||
let note = ConsensusNote {
|
||||
serial: public_inputs.new_serial,
|
||||
value: output.value,
|
||||
token_id: output.token_id,
|
||||
spend_hook,
|
||||
user_data,
|
||||
coin_blind,
|
||||
value_blind,
|
||||
token_blind,
|
||||
memo: vec![],
|
||||
epoch: 0,
|
||||
coin_blind: new_coin_blind,
|
||||
value_blind: new_value_blind,
|
||||
reward: REWARD,
|
||||
reward_blind,
|
||||
};
|
||||
|
||||
let encrypted_note = AeadEncryptedNote::encrypt(¬e, &output.public_key, &mut OsRng)?;
|
||||
|
||||
let output = Output {
|
||||
value_commit: public_inputs.value_commit,
|
||||
token_commit: public_inputs.token_commit,
|
||||
coin: public_inputs.coin,
|
||||
let output = ConsensusOutput {
|
||||
value_commit: public_inputs.new_value_commit,
|
||||
coin: public_inputs.new_coin,
|
||||
note: encrypted_note,
|
||||
};
|
||||
|
||||
// TODO: epoch = current
|
||||
let input = ConsensusInput {
|
||||
epoch: 0,
|
||||
value_commit: public_inputs.value_commit,
|
||||
nullifier,
|
||||
merkle_root,
|
||||
signature_public,
|
||||
};
|
||||
|
||||
// We now fill this with necessary stuff
|
||||
let serial_commit = public_inputs.serial_commit;
|
||||
let mint_params = ConsensusProposalMintParamsV1 {
|
||||
input: input.clone(),
|
||||
output: output.clone(),
|
||||
serial_commit,
|
||||
};
|
||||
let mint_proofs = vec![proof];
|
||||
let mint_input = input;
|
||||
|
||||
debug!("Building Consensus::ProposalRewardV1 contract call for proposal");
|
||||
let secret_key = self.coin.secret.inner();
|
||||
let serial = self.coin.note.serial;
|
||||
let (proof, public_inputs) = create_proposal_reward_proof(
|
||||
&self.reward_zkbin,
|
||||
&self.reward_pk,
|
||||
&self.slot_checkpoint,
|
||||
secret_key,
|
||||
serial,
|
||||
value,
|
||||
value_blind,
|
||||
serial_blind,
|
||||
)?;
|
||||
|
||||
// We now fill this with necessary stuff
|
||||
let burnt_public_key = public_inputs.public_key;
|
||||
let new_serial_commit = serial_commit;
|
||||
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;
|
||||
let reward_params = ConsensusProposalRewardParamsV1 {
|
||||
burnt_input,
|
||||
burnt_public_key,
|
||||
mint_input,
|
||||
let params = ConsensusProposalParamsV1 {
|
||||
input,
|
||||
output,
|
||||
reward: REWARD,
|
||||
reward_blind,
|
||||
new_serial_commit,
|
||||
slot,
|
||||
vrf_proof,
|
||||
y,
|
||||
rho,
|
||||
};
|
||||
let reward_proofs = vec![proof];
|
||||
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 {
|
||||
burn_params,
|
||||
burn_proofs,
|
||||
reward_params,
|
||||
reward_proofs,
|
||||
mint_params,
|
||||
mint_proofs,
|
||||
signature_secret,
|
||||
};
|
||||
let debris =
|
||||
ConsensusProposalCallDebris { params, proofs, signature_secret: self.coin.secret };
|
||||
Ok(debris)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create_proposal_reward_proof(
|
||||
pub fn create_proposal_proof(
|
||||
zkbin: &ZkBinary,
|
||||
pk: &ProvingKey,
|
||||
input: &ConsensusBurnInputInfo,
|
||||
output: &ConsensusMintOutputInfo,
|
||||
slot_checkpoint: &SlotCheckpoint,
|
||||
secret_key: pallas::Base,
|
||||
serial: pallas::Base,
|
||||
value: u64,
|
||||
value_blind: pallas::Scalar,
|
||||
new_serial_blind: pallas::Scalar,
|
||||
) -> Result<(Proof, ConsensusProposalRewardRevealed)> {
|
||||
) -> Result<(Proof, ConsensusProposalRevealed)> {
|
||||
// Proof parameters
|
||||
let nullifier = Nullifier::from(poseidon_hash([secret_key, serial]));
|
||||
let public_key = PublicKey::from_secret(secret_key.into());
|
||||
let value_commit = pedersen_commitment_u64(value, value_blind);
|
||||
let new_serial = poseidon_hash([SERIAL_PREFIX, secret_key, serial, PALLAS_ZERO]);
|
||||
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);
|
||||
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,
|
||||
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
|
||||
};
|
||||
|
||||
// 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(value + REWARD, value_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_ZERO,
|
||||
new_serial,
|
||||
output.coin_blind,
|
||||
]));
|
||||
|
||||
let slot_pallas = pallas::Base::from(slot_checkpoint.slot);
|
||||
let seed = poseidon_hash([SEED_PREFIX, serial, PALLAS_ZERO]);
|
||||
let seed = poseidon_hash([SEED_PREFIX, input.note.serial]);
|
||||
// NOTE: slot checkpoint eta to be renamed to previous_eta,
|
||||
// corresponding to previous block eta.
|
||||
let mut vrf_input = [0u8; 64];
|
||||
vrf_input[..32].copy_from_slice(&slot_checkpoint.eta.to_repr());
|
||||
vrf_input[32..].copy_from_slice(&slot_pallas.to_repr());
|
||||
let vrf_proof = VrfProof::prove(secret_key.into(), &vrf_input, &mut OsRng);
|
||||
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);
|
||||
@@ -373,12 +293,16 @@ pub fn create_proposal_reward_proof(
|
||||
let (sigma1, sigma2) = (slot_checkpoint.sigma1, slot_checkpoint.sigma2);
|
||||
|
||||
// Generate public inputs, witnesses and proof
|
||||
let public_inputs = ConsensusProposalRewardRevealed {
|
||||
let public_inputs = ConsensusProposalRevealed {
|
||||
nullifier,
|
||||
epoch,
|
||||
public_key,
|
||||
merkle_root,
|
||||
value_commit,
|
||||
new_serial,
|
||||
new_serial_commit,
|
||||
new_value_commit,
|
||||
new_coin,
|
||||
vrf_proof,
|
||||
mu_y,
|
||||
y,
|
||||
@@ -389,12 +313,20 @@ pub fn create_proposal_reward_proof(
|
||||
};
|
||||
|
||||
let prover_witnesses = vec![
|
||||
Witness::Base(Value::known(secret_key)),
|
||||
Witness::Base(Value::known(serial)),
|
||||
Witness::Base(Value::known(pallas::Base::from(value))),
|
||||
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::Scalar(Value::known(value_blind)),
|
||||
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::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)),
|
||||
@@ -407,58 +339,3 @@ pub fn create_proposal_reward_proof(
|
||||
|
||||
Ok((proof, public_inputs))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create_proposal_mint_proof(
|
||||
zkbin: &ZkBinary,
|
||||
pk: &ProvingKey,
|
||||
output: &TransactionBuilderOutputInfo,
|
||||
value_blind: pallas::Scalar,
|
||||
token_blind: pallas::Scalar,
|
||||
serial_blind: pallas::Scalar,
|
||||
burnt_secret_key: pallas::Base,
|
||||
burnt_serial: pallas::Base,
|
||||
spend_hook: pallas::Base,
|
||||
user_data: pallas::Base,
|
||||
coin_blind: pallas::Base,
|
||||
) -> Result<(Proof, ConsensusProposalMintRevealed, pallas::Base)> {
|
||||
let serial = poseidon_hash([SERIAL_PREFIX, burnt_secret_key, burnt_serial, PALLAS_ZERO]);
|
||||
let value_commit = pedersen_commitment_u64(output.value, value_blind);
|
||||
let token_commit = pedersen_commitment_base(output.token_id.inner(), token_blind);
|
||||
let serial_commit = pedersen_commitment_base(serial, serial_blind);
|
||||
let (pub_x, pub_y) = output.public_key.xy();
|
||||
|
||||
let coin = Coin::from(poseidon_hash([
|
||||
pub_x,
|
||||
pub_y,
|
||||
pallas::Base::from(output.value),
|
||||
output.token_id.inner(),
|
||||
serial,
|
||||
spend_hook,
|
||||
user_data,
|
||||
coin_blind,
|
||||
]));
|
||||
|
||||
let public_inputs =
|
||||
ConsensusProposalMintRevealed { coin, value_commit, token_commit, serial_commit };
|
||||
|
||||
let prover_witnesses = vec![
|
||||
Witness::Base(Value::known(pub_x)),
|
||||
Witness::Base(Value::known(pub_y)),
|
||||
Witness::Base(Value::known(pallas::Base::from(output.value))),
|
||||
Witness::Base(Value::known(output.token_id.inner())),
|
||||
Witness::Base(Value::known(burnt_secret_key)),
|
||||
Witness::Base(Value::known(burnt_serial)),
|
||||
Witness::Base(Value::known(coin_blind)),
|
||||
Witness::Base(Value::known(spend_hook)),
|
||||
Witness::Base(Value::known(user_data)),
|
||||
Witness::Scalar(Value::known(value_blind)),
|
||||
Witness::Scalar(Value::known(token_blind)),
|
||||
Witness::Scalar(Value::known(serial_blind)),
|
||||
];
|
||||
|
||||
let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
|
||||
let proof = Proof::create(pk, &[circuit], &public_inputs.to_vec(), &mut OsRng)?;
|
||||
|
||||
Ok((proof, public_inputs, serial))
|
||||
}
|
||||
|
||||
@@ -1,341 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2023 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! This API is crufty. Please rework it into something nice to read and nice to use.
|
||||
|
||||
use darkfi::{
|
||||
consensus::SlotCheckpoint,
|
||||
zk::{halo2::Value, Proof, ProvingKey, Witness, ZkCircuit},
|
||||
zkas::ZkBinary,
|
||||
Result,
|
||||
};
|
||||
use darkfi_money_contract::{
|
||||
client::{ConsensusNote, ConsensusOwnCoin},
|
||||
model::{ConsensusInput, ConsensusOutput, PALLAS_ZERO},
|
||||
};
|
||||
use darkfi_sdk::{
|
||||
crypto::{
|
||||
ecvrf::VrfProof, note::AeadEncryptedNote, pasta_prelude::*, pedersen_commitment_base,
|
||||
pedersen_commitment_u64, poseidon_hash, Coin, MerkleNode, MerkleTree, Nullifier, PublicKey,
|
||||
SecretKey,
|
||||
},
|
||||
incrementalmerkletree::{Hashable, Tree},
|
||||
pasta::{group::ff::FromUniformBytes, pallas},
|
||||
};
|
||||
use log::debug;
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct ConsensusProposalCallDebris {
|
||||
pub params: ConsensusProposalParamsV1,
|
||||
pub proofs: Vec<Proof>,
|
||||
pub signature_secret: SecretKey,
|
||||
}
|
||||
|
||||
pub struct ConsensusProposalRevealed {
|
||||
pub nullifier: Nullifier,
|
||||
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 vrf_proof: VrfProof,
|
||||
pub mu_y: pallas::Base,
|
||||
pub y: pallas::Base,
|
||||
pub mu_rho: pallas::Base,
|
||||
pub rho: pallas::Base,
|
||||
pub sigma1: pallas::Base,
|
||||
pub sigma2: pallas::Base,
|
||||
}
|
||||
|
||||
impl ConsensusProposalRevealed {
|
||||
pub fn to_vec(&self) -> Vec<pallas::Base> {
|
||||
let epoch_palas = pallas::Base::from(self.epoch);
|
||||
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();
|
||||
|
||||
// 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,
|
||||
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(),
|
||||
self.mu_y,
|
||||
self.y,
|
||||
self.mu_rho,
|
||||
self.rho,
|
||||
self.sigma1,
|
||||
self.sigma2,
|
||||
HEADSTART,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct holding necessary information to build a proposal transaction.
|
||||
pub struct ConsensusProposalCallBuilder {
|
||||
/// `ConsensusOwnCoin` we're given to use in this builder
|
||||
pub coin: ConsensusOwnCoin,
|
||||
/// Rewarded slot checkpoint
|
||||
pub slot_checkpoint: SlotCheckpoint,
|
||||
/// Merkle tree of coins used to create inclusion proofs
|
||||
pub tree: MerkleTree,
|
||||
/// `Proposal_V1` zkas circuit ZkBinary
|
||||
pub proposal_zkbin: ZkBinary,
|
||||
/// Proving key for the `Proposal_V1` zk circuit
|
||||
pub proposal_pk: ProvingKey,
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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 input = ConsensusBurnInputInfo {
|
||||
leaf_position,
|
||||
merkle_path,
|
||||
secret: self.coin.secret,
|
||||
note: self.coin.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 contract call for proposal");
|
||||
let (proof, public_inputs) = create_proposal_proof(
|
||||
&self.proposal_zkbin,
|
||||
&self.proposal_pk,
|
||||
&input,
|
||||
&output,
|
||||
&self.slot_checkpoint,
|
||||
)?;
|
||||
|
||||
let input = ConsensusInput {
|
||||
epoch: self.coin.note.epoch,
|
||||
value_commit: public_inputs.value_commit,
|
||||
nullifier: public_inputs.nullifier,
|
||||
merkle_root: public_inputs.merkle_root,
|
||||
signature_public: public_inputs.public_key,
|
||||
};
|
||||
|
||||
// Encrypted note
|
||||
let note = ConsensusNote {
|
||||
serial: public_inputs.new_serial,
|
||||
value: output.value,
|
||||
epoch: 0,
|
||||
coin_blind: new_coin_blind,
|
||||
value_blind: new_value_blind,
|
||||
reward: REWARD,
|
||||
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,
|
||||
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;
|
||||
let params = ConsensusProposalParamsV1 {
|
||||
input,
|
||||
output,
|
||||
reward: REWARD,
|
||||
reward_blind,
|
||||
new_serial_commit,
|
||||
slot,
|
||||
vrf_proof,
|
||||
y,
|
||||
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 };
|
||||
Ok(debris)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proposal_proof(
|
||||
zkbin: &ZkBinary,
|
||||
pk: &ProvingKey,
|
||||
input: &ConsensusBurnInputInfo,
|
||||
output: &ConsensusMintOutputInfo,
|
||||
slot_checkpoint: &SlotCheckpoint,
|
||||
) -> Result<(Proof, ConsensusProposalRevealed)> {
|
||||
// Proof parameters
|
||||
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);
|
||||
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,
|
||||
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
|
||||
};
|
||||
|
||||
// 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_ZERO,
|
||||
new_serial,
|
||||
output.coin_blind,
|
||||
]));
|
||||
|
||||
let slot_pallas = pallas::Base::from(slot_checkpoint.slot);
|
||||
let seed = poseidon_hash([SEED_PREFIX, input.note.serial]);
|
||||
// NOTE: slot checkpoint eta to be renamed to previous_eta,
|
||||
// corresponding to previous block eta.
|
||||
let mut vrf_input = [0u8; 64];
|
||||
vrf_input[..32].copy_from_slice(&slot_checkpoint.eta.to_repr());
|
||||
vrf_input[32..].copy_from_slice(&slot_pallas.to_repr());
|
||||
let vrf_proof = VrfProof::prove(input.secret.into(), &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
|
||||
let public_inputs = ConsensusProposalRevealed {
|
||||
nullifier,
|
||||
epoch,
|
||||
public_key,
|
||||
merkle_root,
|
||||
value_commit,
|
||||
new_serial,
|
||||
new_serial_commit,
|
||||
new_value_commit,
|
||||
new_coin,
|
||||
vrf_proof,
|
||||
mu_y,
|
||||
y,
|
||||
mu_rho,
|
||||
rho,
|
||||
sigma1,
|
||||
sigma2,
|
||||
};
|
||||
|
||||
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::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::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)),
|
||||
];
|
||||
|
||||
let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
|
||||
let proof = Proof::create(pk, &[circuit], &public_inputs.to_vec(), &mut OsRng)?;
|
||||
|
||||
Ok((proof, public_inputs))
|
||||
}
|
||||
@@ -32,10 +32,7 @@ use darkfi_sdk::{
|
||||
};
|
||||
use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
|
||||
|
||||
use crate::{
|
||||
model::{ConsensusProposalRewardUpdateV1, ConsensusProposalUpdateV1},
|
||||
ConsensusFunction,
|
||||
};
|
||||
use crate::{model::ConsensusProposalUpdateV1, ConsensusFunction};
|
||||
|
||||
/// `Consensus::GenesisStake` functions
|
||||
mod genesis_stake_v1;
|
||||
@@ -50,27 +47,6 @@ use stake_v1::{
|
||||
consensus_stake_process_update_v1,
|
||||
};
|
||||
|
||||
/// `Consensus::ProposalBurn` functions
|
||||
mod proposal_burn_v1;
|
||||
use proposal_burn_v1::{
|
||||
consensus_proposal_burn_get_metadata_v1, consensus_proposal_burn_process_instruction_v1,
|
||||
consensus_proposal_burn_process_update_v1,
|
||||
};
|
||||
|
||||
/// `Consensus::ProposalRewardV1` functions
|
||||
mod proposal_reward_v1;
|
||||
use proposal_reward_v1::{
|
||||
consensus_proposal_reward_get_metadata_v1, consensus_proposal_reward_process_instruction_v1,
|
||||
consensus_proposal_reward_process_update_v1,
|
||||
};
|
||||
|
||||
/// `Consensus::ProposalMintV1` functions
|
||||
mod proposal_mint_v1;
|
||||
use proposal_mint_v1::{
|
||||
consensus_proposal_mint_get_metadata_v1, consensus_proposal_mint_process_instruction_v1,
|
||||
consensus_proposal_mint_process_update_v1,
|
||||
};
|
||||
|
||||
/// `Consensus::ProposalV1` functions
|
||||
mod proposal_v1;
|
||||
use proposal_v1::{
|
||||
@@ -101,21 +77,13 @@ fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
|
||||
// respective db functions. The special `zkas db` operations exist in
|
||||
// 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 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");
|
||||
let consensus_proposal_v1_bincode = include_bytes!("../proof/consensus_proposal_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(&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[..])?;
|
||||
zkas_db_set(&consensus_proposal_v1_bincode[..])?;
|
||||
|
||||
// Set up a database tree to hold Merkle roots of all coins
|
||||
@@ -187,18 +155,6 @@ fn get_metadata(cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
let metadata = consensus_stake_get_metadata_v1(cid, call_idx, calls)?;
|
||||
Ok(set_return_data(&metadata)?)
|
||||
}
|
||||
ConsensusFunction::ProposalBurnV1 => {
|
||||
let metadata = consensus_proposal_burn_get_metadata_v1(cid, call_idx, calls)?;
|
||||
Ok(set_return_data(&metadata)?)
|
||||
}
|
||||
ConsensusFunction::ProposalRewardV1 => {
|
||||
let metadata = consensus_proposal_reward_get_metadata_v1(cid, call_idx, calls)?;
|
||||
Ok(set_return_data(&metadata)?)
|
||||
}
|
||||
ConsensusFunction::ProposalMintV1 => {
|
||||
let metadata = consensus_proposal_mint_get_metadata_v1(cid, call_idx, calls)?;
|
||||
Ok(set_return_data(&metadata)?)
|
||||
}
|
||||
ConsensusFunction::ProposalV1 => {
|
||||
let metadata = consensus_proposal_get_metadata_v1(cid, call_idx, calls)?;
|
||||
Ok(set_return_data(&metadata)?)
|
||||
@@ -234,19 +190,6 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
let update_data = consensus_stake_process_instruction_v1(cid, call_idx, calls)?;
|
||||
Ok(set_return_data(&update_data)?)
|
||||
}
|
||||
ConsensusFunction::ProposalBurnV1 => {
|
||||
let update_data = consensus_proposal_burn_process_instruction_v1(cid, call_idx, calls)?;
|
||||
Ok(set_return_data(&update_data)?)
|
||||
}
|
||||
ConsensusFunction::ProposalRewardV1 => {
|
||||
let update_data =
|
||||
consensus_proposal_reward_process_instruction_v1(cid, call_idx, calls)?;
|
||||
Ok(set_return_data(&update_data)?)
|
||||
}
|
||||
ConsensusFunction::ProposalMintV1 => {
|
||||
let update_data = consensus_proposal_mint_process_instruction_v1(cid, call_idx, calls)?;
|
||||
Ok(set_return_data(&update_data)?)
|
||||
}
|
||||
ConsensusFunction::ProposalV1 => {
|
||||
let update_data = consensus_proposal_process_instruction_v1(cid, call_idx, calls)?;
|
||||
Ok(set_return_data(&update_data)?)
|
||||
@@ -273,18 +216,6 @@ fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
|
||||
let update: ConsensusStakeUpdateV1 = deserialize(&update_data[1..])?;
|
||||
Ok(consensus_stake_process_update_v1(cid, update)?)
|
||||
}
|
||||
ConsensusFunction::ProposalBurnV1 => {
|
||||
let update: ConsensusUnstakeUpdateV1 = deserialize(&update_data[1..])?;
|
||||
Ok(consensus_proposal_burn_process_update_v1(cid, update)?)
|
||||
}
|
||||
ConsensusFunction::ProposalRewardV1 => {
|
||||
let update: ConsensusProposalRewardUpdateV1 = deserialize(&update_data[1..])?;
|
||||
Ok(consensus_proposal_reward_process_update_v1(cid, update)?)
|
||||
}
|
||||
ConsensusFunction::ProposalMintV1 => {
|
||||
let update: ConsensusStakeUpdateV1 = deserialize(&update_data[1..])?;
|
||||
Ok(consensus_proposal_mint_process_update_v1(cid, update)?)
|
||||
}
|
||||
ConsensusFunction::ProposalV1 => {
|
||||
let update: ConsensusProposalUpdateV1 = deserialize(&update_data[1..])?;
|
||||
Ok(consensus_proposal_process_update_v1(cid, update)?)
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2023 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use darkfi_money_contract::{
|
||||
error::MoneyError,
|
||||
model::{ConsensusUnstakeUpdateV1, PALLAS_ZERO},
|
||||
CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_NULLIFIERS_TREE,
|
||||
MONEY_CONTRACT_ZKAS_BURN_NS_V1,
|
||||
};
|
||||
use darkfi_sdk::{
|
||||
crypto::{
|
||||
pasta_prelude::*, pedersen_commitment_base, ContractId, CONSENSUS_CONTRACT_ID,
|
||||
DARK_TOKEN_ID,
|
||||
},
|
||||
db::{db_contains_key, db_lookup, db_set},
|
||||
error::{ContractError, ContractResult},
|
||||
msg,
|
||||
pasta::pallas,
|
||||
ContractCall,
|
||||
};
|
||||
use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
|
||||
|
||||
use crate::{
|
||||
model::{ConsensusProposalBurnParamsV1, ConsensusProposalRewardParamsV1},
|
||||
ConsensusFunction,
|
||||
};
|
||||
|
||||
/// `get_metadata` function for `Consensus::ProposalBurnV1`
|
||||
pub(crate) fn consensus_proposal_burn_get_metadata_v1(
|
||||
_cid: ContractId,
|
||||
call_idx: u32,
|
||||
calls: Vec<ContractCall>,
|
||||
) -> Result<Vec<u8>, ContractError> {
|
||||
let self_ = &calls[call_idx as usize];
|
||||
let params: ConsensusProposalBurnParamsV1 = deserialize(&self_.data[1..])?;
|
||||
let input = ¶ms.input;
|
||||
|
||||
// Public inputs for the ZK proofs we have to verify
|
||||
let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
|
||||
// Public keys for the transaction signatures we have to verify
|
||||
let signature_pubkeys = vec![input.signature_public];
|
||||
|
||||
// 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();
|
||||
|
||||
// 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(),
|
||||
vec![
|
||||
input.nullifier.inner(),
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
*token_coords.x(),
|
||||
*token_coords.y(),
|
||||
input.merkle_root.inner(),
|
||||
input.user_data_enc,
|
||||
sig_x,
|
||||
sig_y,
|
||||
],
|
||||
));
|
||||
|
||||
// Serialize everything gathered and return it
|
||||
let mut metadata = vec![];
|
||||
zk_public_inputs.encode(&mut metadata)?;
|
||||
signature_pubkeys.encode(&mut metadata)?;
|
||||
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
/// `process_instruction` function for `Consensus::ProposalBurnV1`
|
||||
pub(crate) fn consensus_proposal_burn_process_instruction_v1(
|
||||
cid: ContractId,
|
||||
call_idx: u32,
|
||||
calls: Vec<ContractCall>,
|
||||
) -> Result<Vec<u8>, ContractError> {
|
||||
let self_ = &calls[call_idx as usize];
|
||||
let params: ConsensusProposalBurnParamsV1 = deserialize(&self_.data[1..])?;
|
||||
|
||||
// Access the necessary databases where there is information to
|
||||
// validate this state transition.
|
||||
let nullifiers_db = db_lookup(cid, CONSENSUS_CONTRACT_NULLIFIERS_TREE)?;
|
||||
let coin_roots_db = db_lookup(cid, CONSENSUS_CONTRACT_COIN_ROOTS_TREE)?;
|
||||
|
||||
// ===================================
|
||||
// Perform the actual state transition
|
||||
// ===================================
|
||||
|
||||
msg!("[ConsensusProposalBurnV1] Validating anonymous input");
|
||||
let input = ¶ms.input;
|
||||
|
||||
// Only native token can be burned in a proposal
|
||||
if input.token_commit != pedersen_commitment_base(DARK_TOKEN_ID.inner(), params.token_blind) {
|
||||
msg!("[ConsensusProposalBurnV1] 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))? {
|
||||
msg!("[ConsensusProposalBurnV1] Error: Merkle root not found in previous state");
|
||||
return Err(MoneyError::TransferMerkleRootNotFound.into())
|
||||
}
|
||||
|
||||
// The nullifiers should not already exist. It is the double-spend protection.
|
||||
if db_contains_key(nullifiers_db, &serialize(&input.nullifier))? {
|
||||
msg!("[ConsensusProposalBurnV1] Error: Duplicate nullifier found");
|
||||
return Err(MoneyError::DuplicateNullifier.into())
|
||||
}
|
||||
|
||||
// Check next call is consensus contract
|
||||
let next_call_idx = call_idx + 1;
|
||||
if next_call_idx >= calls.len() as u32 {
|
||||
msg!("[ConsensusProposalBurnV1] Error: next_call_idx out of bounds");
|
||||
return Err(MoneyError::SpendHookOutOfBounds.into())
|
||||
}
|
||||
|
||||
let next = &calls[next_call_idx as usize];
|
||||
if next.contract_id.inner() != CONSENSUS_CONTRACT_ID.inner() {
|
||||
msg!("[ConsensusProposalBurnV1] Error: Next contract call is not consensus contract");
|
||||
return Err(MoneyError::StakeNextCallNotConsensusContract.into())
|
||||
}
|
||||
|
||||
// Check if spend hook is set and its correctness
|
||||
if input.spend_hook == PALLAS_ZERO {
|
||||
msg!("[ConsensusProposalBurnV1] Error: Missing spend hook");
|
||||
return Err(MoneyError::StakeMissingSpendHook.into())
|
||||
}
|
||||
|
||||
if input.spend_hook != CONSENSUS_CONTRACT_ID.inner() {
|
||||
msg!("[ConsensusProposalBurnV1] Error: Spend hook is not consensus contract");
|
||||
return Err(MoneyError::UnstakeSpendHookNotConsensusContract.into())
|
||||
}
|
||||
|
||||
// Verify next call corresponds to Consensus::ProposalRewardV1 (0x03)
|
||||
if next.data[0] != 0x03 {
|
||||
msg!("[ConsensusProposalBurnV1] Error: Next call function mismatch");
|
||||
return Err(MoneyError::NextCallFunctionMissmatch.into())
|
||||
}
|
||||
|
||||
// Verify next call StakeInput is the same as this calls input
|
||||
let next_params: ConsensusProposalRewardParamsV1 = deserialize(&next.data[1..])?;
|
||||
if input != &next_params.burnt_input || params.public_key != next_params.burnt_public_key {
|
||||
msg!("[ConsensusProposalBurnV1] Error: Next call input mismatch");
|
||||
return Err(MoneyError::NextCallInputMissmatch.into())
|
||||
}
|
||||
|
||||
// At this point the state transition has passed, so we create a state update
|
||||
let update = ConsensusUnstakeUpdateV1 { nullifier: input.nullifier };
|
||||
let mut update_data = vec![];
|
||||
update_data.write_u8(ConsensusFunction::UnstakeV1 as u8)?;
|
||||
update.encode(&mut update_data)?;
|
||||
|
||||
// and return it
|
||||
Ok(update_data)
|
||||
}
|
||||
|
||||
/// `process_update` function for `Consensus::ProposalBurnV1`
|
||||
pub(crate) fn consensus_proposal_burn_process_update_v1(
|
||||
cid: ContractId,
|
||||
update: ConsensusUnstakeUpdateV1,
|
||||
) -> ContractResult {
|
||||
// Grab all necessary db handles for where we want to write
|
||||
let nullifiers_db = db_lookup(cid, CONSENSUS_CONTRACT_NULLIFIERS_TREE)?;
|
||||
|
||||
msg!("[ConsensusProposalBurnV1] Adding new nullifier to the set");
|
||||
db_set(nullifiers_db, &serialize(&update.nullifier), &[])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2023 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use darkfi_money_contract::{
|
||||
error::MoneyError,
|
||||
model::{ConsensusStakeUpdateV1, PALLAS_ZERO},
|
||||
CONSENSUS_CONTRACT_COINS_TREE, CONSENSUS_CONTRACT_COIN_MERKLE_TREE,
|
||||
CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_INFO_TREE,
|
||||
CONSENSUS_CONTRACT_NULLIFIERS_TREE, CONSENSUS_CONTRACT_ZKAS_PROPOSAL_MINT_NS_V1,
|
||||
};
|
||||
use darkfi_sdk::{
|
||||
crypto::{pasta_prelude::*, ContractId, MerkleNode, CONSENSUS_CONTRACT_ID},
|
||||
db::{db_contains_key, db_lookup, db_set},
|
||||
error::{ContractError, ContractResult},
|
||||
merkle_add, msg,
|
||||
pasta::pallas,
|
||||
ContractCall,
|
||||
};
|
||||
use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
|
||||
|
||||
use crate::{
|
||||
model::{ConsensusProposalMintParamsV1, ConsensusProposalRewardParamsV1},
|
||||
ConsensusFunction,
|
||||
};
|
||||
|
||||
/// `get_metadata` function for `Consensus::ProposalMintV1`
|
||||
pub(crate) fn consensus_proposal_mint_get_metadata_v1(
|
||||
_cid: ContractId,
|
||||
call_idx: u32,
|
||||
calls: Vec<ContractCall>,
|
||||
) -> Result<Vec<u8>, ContractError> {
|
||||
let self_ = &calls[call_idx as usize];
|
||||
let params: ConsensusProposalMintParamsV1 = deserialize(&self_.data[1..])?;
|
||||
|
||||
// Public inputs for the ZK proofs we have to verify
|
||||
let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
|
||||
// Public keys for the transaction signatures we have to verify
|
||||
let signature_pubkeys = vec![params.input.signature_public];
|
||||
|
||||
// Grab the pedersen commitment from the anonymous output
|
||||
let output = ¶ms.output;
|
||||
let value_coords = output.value_commit.to_affine().coordinates().unwrap();
|
||||
let token_coords = output.token_commit.to_affine().coordinates().unwrap();
|
||||
let serial_coords = ¶ms.serial_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
zk_public_inputs.push((
|
||||
CONSENSUS_CONTRACT_ZKAS_PROPOSAL_MINT_NS_V1.to_string(),
|
||||
vec![
|
||||
output.coin.inner(),
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
*token_coords.x(),
|
||||
*token_coords.y(),
|
||||
*serial_coords.x(),
|
||||
*serial_coords.y(),
|
||||
],
|
||||
));
|
||||
|
||||
// Serialize everything gathered and return it
|
||||
let mut metadata = vec![];
|
||||
zk_public_inputs.encode(&mut metadata)?;
|
||||
signature_pubkeys.encode(&mut metadata)?;
|
||||
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
/// `process_instruction` function for `Consensus::ProposalMintV1`
|
||||
pub(crate) fn consensus_proposal_mint_process_instruction_v1(
|
||||
cid: ContractId,
|
||||
call_idx: u32,
|
||||
calls: Vec<ContractCall>,
|
||||
) -> Result<Vec<u8>, ContractError> {
|
||||
let self_ = &calls[call_idx as usize];
|
||||
let params: ConsensusProposalMintParamsV1 = deserialize(&self_.data[1..])?;
|
||||
|
||||
// Access the necessary databases where there is information to
|
||||
// validate this state transition.
|
||||
let coins_db = db_lookup(cid, CONSENSUS_CONTRACT_COINS_TREE)?;
|
||||
let nullifiers_db = db_lookup(cid, CONSENSUS_CONTRACT_NULLIFIERS_TREE)?;
|
||||
let coin_roots_db = db_lookup(cid, CONSENSUS_CONTRACT_COIN_ROOTS_TREE)?;
|
||||
|
||||
// ===================================
|
||||
// Perform the actual state transition
|
||||
// ===================================
|
||||
|
||||
msg!("[ConsensusProposalMintV1] Validating anonymous output");
|
||||
let input = ¶ms.input;
|
||||
let output = ¶ms.output;
|
||||
|
||||
/*
|
||||
// Only native token can be minted in a proposal
|
||||
if output.token_commit != pedersen_commitment_base(DARK_TOKEN_ID.inner(), input.token_blind) {
|
||||
msg!("[ConsensusProposalMintV1] Error: Input used non-native token");
|
||||
return Err(MoneyError::StakeInputNonNativeToken.into())
|
||||
}
|
||||
*/
|
||||
|
||||
// Verify value commits match
|
||||
if output.value_commit != input.value_commit {
|
||||
msg!("[ConsensusProposalMintV1] Error: Value commitments do not match");
|
||||
return Err(MoneyError::ValueMismatch.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))? {
|
||||
msg!("[ConsensusProposalMintV1] Error: Merkle root not found in previous state");
|
||||
return Err(MoneyError::TransferMerkleRootNotFound.into())
|
||||
}
|
||||
|
||||
// The nullifiers should already exist. It is the double-mint protection.
|
||||
if db_contains_key(nullifiers_db, &serialize(&input.nullifier))? {
|
||||
msg!("[ConsensusProposalMintV1] Error: Missing nullifier");
|
||||
return Err(MoneyError::StakeMissingNullifier.into())
|
||||
}
|
||||
|
||||
// Check previous call is consensus contract
|
||||
if call_idx == 0 {
|
||||
msg!("[ConsensusProposalMintV1] Error: previous_call_idx will be out of bounds");
|
||||
return Err(MoneyError::SpendHookOutOfBounds.into())
|
||||
}
|
||||
|
||||
let previous_call_idx = call_idx - 1;
|
||||
let previous = &calls[previous_call_idx as usize];
|
||||
if previous.contract_id.inner() != CONSENSUS_CONTRACT_ID.inner() {
|
||||
msg!("[ConsensusProposalMintV1] Error: Previous contract call is not consensus contract");
|
||||
return Err(MoneyError::UnstakePreviousCallNotConsensusContract.into())
|
||||
}
|
||||
|
||||
// Verify previous call corresponds to Consensus::ProposalRewardV1 (0x03)
|
||||
if previous.data[0] != 0x03 {
|
||||
msg!("[ConsensusProposalMintV1] Error: Previous call function mismatch");
|
||||
return Err(MoneyError::PreviousCallFunctionMissmatch.into())
|
||||
}
|
||||
|
||||
// Verify previous call input is the same as this calls StakeInput
|
||||
let previous_params: ConsensusProposalRewardParamsV1 = deserialize(&previous.data[1..])?;
|
||||
let previous_input = &previous_params.mint_input;
|
||||
if previous_input != input ||
|
||||
&previous_params.output != output ||
|
||||
previous_params.new_serial_commit != params.serial_commit
|
||||
{
|
||||
msg!("[ConsensusProposalMintV1] Error: Previous call input mismatch");
|
||||
return Err(MoneyError::PreviousCallInputMissmatch.into())
|
||||
}
|
||||
|
||||
// If spend hook is set, check its correctness
|
||||
let previous_input = &previous_params.burnt_input;
|
||||
if previous_input.spend_hook != PALLAS_ZERO &&
|
||||
previous_input.spend_hook != CONSENSUS_CONTRACT_ID.inner()
|
||||
{
|
||||
msg!("[ConsensusProposalMintV1] Error: Invoking contract call does not match spend hook in input");
|
||||
return Err(MoneyError::SpendHookMismatch.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.
|
||||
if db_contains_key(coins_db, &serialize(&output.coin))? {
|
||||
msg!("[ConsensusProposalMintV1] Error: Duplicate coin found in output");
|
||||
return Err(MoneyError::DuplicateCoin.into())
|
||||
}
|
||||
|
||||
// Create a state update.
|
||||
let update = ConsensusStakeUpdateV1 { coin: output.coin };
|
||||
let mut update_data = vec![];
|
||||
update_data.write_u8(ConsensusFunction::StakeV1 as u8)?;
|
||||
update.encode(&mut update_data)?;
|
||||
|
||||
Ok(update_data)
|
||||
}
|
||||
|
||||
/// `process_update` function for `Consensus::ProposalMintV1`
|
||||
pub(crate) fn consensus_proposal_mint_process_update_v1(
|
||||
cid: ContractId,
|
||||
update: ConsensusStakeUpdateV1,
|
||||
) -> ContractResult {
|
||||
// Grab all necessary db handles for where we want to write
|
||||
let info_db = db_lookup(cid, CONSENSUS_CONTRACT_INFO_TREE)?;
|
||||
let coins_db = db_lookup(cid, CONSENSUS_CONTRACT_COINS_TREE)?;
|
||||
let coin_roots_db = db_lookup(cid, CONSENSUS_CONTRACT_COIN_ROOTS_TREE)?;
|
||||
|
||||
msg!("[ConsensusProposalMintV1] Adding new coin to the set");
|
||||
db_set(coins_db, &serialize(&update.coin), &[])?;
|
||||
|
||||
msg!("[ConsensusProposalMintV1] Adding new coin to the Merkle tree");
|
||||
let coins: Vec<_> = vec![MerkleNode::from(update.coin.inner())];
|
||||
merkle_add(info_db, coin_roots_db, &serialize(&CONSENSUS_CONTRACT_COIN_MERKLE_TREE), &coins)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2023 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use darkfi_money_contract::{
|
||||
error::MoneyError, model::PALLAS_ZERO, CONSENSUS_CONTRACT_ZKAS_PROPOSAL_REWARD_NS_V1,
|
||||
};
|
||||
use darkfi_sdk::{
|
||||
crypto::{
|
||||
pasta_prelude::*, pedersen_commitment_u64, poseidon_hash, ContractId, CONSENSUS_CONTRACT_ID,
|
||||
},
|
||||
error::{ContractError, ContractResult},
|
||||
msg,
|
||||
pasta::{group::ff::FromUniformBytes, pallas},
|
||||
util::get_slot_checkpoint,
|
||||
ContractCall,
|
||||
};
|
||||
use darkfi_serial::{deserialize, Encodable, WriteExt};
|
||||
|
||||
use crate::{
|
||||
error::ConsensusError,
|
||||
model::{
|
||||
ConsensusProposalBurnParamsV1, ConsensusProposalMintParamsV1,
|
||||
ConsensusProposalRewardParamsV1, ConsensusProposalRewardUpdateV1, SlotCheckpoint,
|
||||
HEADSTART, MU_RHO_PREFIX, MU_Y_PREFIX, REWARD,
|
||||
},
|
||||
ConsensusFunction,
|
||||
};
|
||||
|
||||
/// `get_metadata` function for `Consensus::ProposalRewardV1`
|
||||
pub(crate) fn consensus_proposal_reward_get_metadata_v1(
|
||||
_cid: ContractId,
|
||||
call_idx: u32,
|
||||
calls: Vec<ContractCall>,
|
||||
) -> Result<Vec<u8>, ContractError> {
|
||||
let self_ = &calls[call_idx as usize];
|
||||
let params: ConsensusProposalRewardParamsV1 = deserialize(&self_.data[1..])?;
|
||||
|
||||
// Public inputs for the ZK proofs we have to verify
|
||||
let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
|
||||
// Public keys for the transaction signatures we have to verify
|
||||
let signature_pubkeys = vec![params.mint_input.signature_public];
|
||||
|
||||
// Grab the nullifier for the burnt coin
|
||||
let nullifier = ¶ms.burnt_input.nullifier;
|
||||
|
||||
// Grab the public key coordinates for the burnt coin
|
||||
let (pub_x, pub_y) = ¶ms.burnt_public_key.xy();
|
||||
|
||||
// Grab the pedersen commitment for the burnt value
|
||||
let value_coords = ¶ms.burnt_input.value_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
// Grab the pedersen commitment for the minted serial number
|
||||
let new_serial_coords = ¶ms.new_serial_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
// Grab the pedersen commitment for the minted value
|
||||
let new_value_coords = ¶ms.mint_input.value_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
// 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!("[ConsensusProposalRewardV1] Error: Missing slot checkpoint {} from db", slot);
|
||||
return Err(ConsensusError::ProposalMissingSlotCheckpoint.into())
|
||||
};
|
||||
let slot_checkpoint: SlotCheckpoint = deserialize(&slot_checkpoint)?;
|
||||
|
||||
// Verify eta VRF proof
|
||||
let slot_pallas = pallas::Base::from(slot_checkpoint.slot);
|
||||
// NOTE: slot checkpoint eta to be renamed to previous_eta,
|
||||
// corresponding to previous block eta.
|
||||
let mut vrf_input = [0u8; 64];
|
||||
vrf_input[..32].copy_from_slice(&slot_checkpoint.eta.to_repr());
|
||||
vrf_input[32..].copy_from_slice(&slot_pallas.to_repr());
|
||||
let vrf_proof = ¶ms.vrf_proof;
|
||||
if !vrf_proof.verify(params.burnt_public_key, &vrf_input) {
|
||||
msg!("[ConsensusProposalRewardV1] Error: eta VRF proof couldn't be verified");
|
||||
return Err(ConsensusError::ProposalErroneousVrfProof.into())
|
||||
}
|
||||
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);
|
||||
|
||||
// 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]);
|
||||
|
||||
// Grab sigmas from slot checkpoint
|
||||
let (sigma1, sigma2) = (slot_checkpoint.sigma1, slot_checkpoint.sigma2);
|
||||
|
||||
zk_public_inputs.push((
|
||||
CONSENSUS_CONTRACT_ZKAS_PROPOSAL_REWARD_NS_V1.to_string(),
|
||||
vec![
|
||||
nullifier.inner(),
|
||||
*pub_x,
|
||||
*pub_y,
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
*new_serial_coords.x(),
|
||||
*new_serial_coords.y(),
|
||||
*new_value_coords.x(),
|
||||
*new_value_coords.y(),
|
||||
mu_y,
|
||||
*y,
|
||||
mu_rho,
|
||||
*rho,
|
||||
sigma1,
|
||||
sigma2,
|
||||
HEADSTART,
|
||||
],
|
||||
));
|
||||
|
||||
// Serialize everything gathered and return it
|
||||
let mut metadata = vec![];
|
||||
zk_public_inputs.encode(&mut metadata)?;
|
||||
signature_pubkeys.encode(&mut metadata)?;
|
||||
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
/// `process_instruction` function for `Consensus::ProposalRewardV1`
|
||||
pub(crate) fn consensus_proposal_reward_process_instruction_v1(
|
||||
_cid: ContractId,
|
||||
call_idx: u32,
|
||||
calls: Vec<ContractCall>,
|
||||
) -> Result<Vec<u8>, ContractError> {
|
||||
let self_ = &calls[call_idx as usize];
|
||||
let params: ConsensusProposalRewardParamsV1 = deserialize(&self_.data[1..])?;
|
||||
|
||||
// ===================================
|
||||
// Perform the actual state transition
|
||||
// ===================================
|
||||
|
||||
msg!("[ConsensusProposalRewardV1] Validating anonymous inputs");
|
||||
let burnt_input = ¶ms.burnt_input;
|
||||
let mint_input = ¶ms.mint_input;
|
||||
let output = ¶ms.output;
|
||||
|
||||
/*
|
||||
// Only native token can be rewarded in a proposal
|
||||
let dark_token_commit = pedersen_commitment_base(DARK_TOKEN_ID.inner(), mint_input.token_blind);
|
||||
if burnt_input.token_commit != dark_token_commit || output.token_commit != dark_token_commit {
|
||||
msg!("[ConsensusProposalRewardV1] Error: Input used non-native token");
|
||||
return Err(MoneyError::StakeInputNonNativeToken.into())
|
||||
}
|
||||
*/
|
||||
|
||||
// Verify value commits match
|
||||
let mut valcom_total = pallas::Point::identity();
|
||||
valcom_total += burnt_input.value_commit;
|
||||
valcom_total += pedersen_commitment_u64(REWARD, pallas::Scalar::zero());
|
||||
valcom_total -= mint_input.value_commit;
|
||||
if valcom_total != pallas::Point::identity() {
|
||||
msg!("[ConsensusProposalRewardV1] Error: Value commitments do not result in identity");
|
||||
return Err(MoneyError::ValueMismatch.into())
|
||||
}
|
||||
|
||||
// Check previous call is consensus contract
|
||||
if call_idx == 0 {
|
||||
msg!("[ConsensusProposalRewardV1] Error: previous_call_idx will be out of bounds");
|
||||
return Err(MoneyError::SpendHookOutOfBounds.into())
|
||||
}
|
||||
|
||||
let previous_call_idx = call_idx - 1;
|
||||
let previous = &calls[previous_call_idx as usize];
|
||||
if previous.contract_id.inner() != CONSENSUS_CONTRACT_ID.inner() {
|
||||
msg!("[ConsensusProposalRewardV1] Error: Previous contract call is not consensus contract");
|
||||
return Err(MoneyError::UnstakePreviousCallNotConsensusContract.into())
|
||||
}
|
||||
|
||||
// Verify previous call corresponds to Consensus::ProposalBurnV1 (0x02)
|
||||
if previous.data[0] != 0x02 {
|
||||
msg!("[ConsensusProposalRewardV1] Error: Previous call function mismatch");
|
||||
return Err(MoneyError::PreviousCallFunctionMissmatch.into())
|
||||
}
|
||||
|
||||
// Verify previous call input is the same as this calls StakeInput
|
||||
let previous_params: ConsensusProposalBurnParamsV1 = deserialize(&previous.data[1..])?;
|
||||
let previous_input = &previous_params.input;
|
||||
if previous_input != burnt_input || previous_params.public_key != params.burnt_public_key {
|
||||
msg!("[ConsensusProposalRewardV1] Error: Previous call input mismatch");
|
||||
return Err(MoneyError::PreviousCallInputMissmatch.into())
|
||||
}
|
||||
|
||||
// If spend hook is set, check its correctness
|
||||
if previous_input.spend_hook != PALLAS_ZERO &&
|
||||
previous_input.spend_hook != CONSENSUS_CONTRACT_ID.inner()
|
||||
{
|
||||
msg!("[ConsensusProposalRewardV1] Error: Invoking contract call does not match spend hook in input");
|
||||
return Err(MoneyError::SpendHookMismatch.into())
|
||||
}
|
||||
|
||||
// Check next call is consensus contract
|
||||
let next_call_idx = call_idx + 1;
|
||||
if next_call_idx >= calls.len() as u32 {
|
||||
msg!("[ConsensusProposalRewardV1] Error: next_call_idx out of bounds");
|
||||
return Err(MoneyError::SpendHookOutOfBounds.into())
|
||||
}
|
||||
|
||||
let next = &calls[next_call_idx as usize];
|
||||
if next.contract_id.inner() != CONSENSUS_CONTRACT_ID.inner() {
|
||||
msg!("[ConsensusProposalRewardV1] Error: Next contract call is not consensus contract");
|
||||
return Err(MoneyError::StakeNextCallNotConsensusContract.into())
|
||||
}
|
||||
|
||||
// Verify next call corresponds to Consensus::ProposalMintV1 (0x04)
|
||||
if next.data[0] != 0x04 {
|
||||
msg!("[ConsensusProposalRewardV1] Error: Next call function mismatch");
|
||||
return Err(MoneyError::NextCallFunctionMissmatch.into())
|
||||
}
|
||||
|
||||
// Verify next call StakeInput is the same as this calls input
|
||||
let next_params: ConsensusProposalMintParamsV1 = deserialize(&next.data[1..])?;
|
||||
if mint_input != &next_params.input ||
|
||||
output != &next_params.output ||
|
||||
params.new_serial_commit != next_params.serial_commit
|
||||
{
|
||||
msg!("[ConsensusProposalRewardV1] Error: Next call input mismatch");
|
||||
return Err(MoneyError::NextCallInputMissmatch.into())
|
||||
}
|
||||
|
||||
// Create a state update.
|
||||
let update = ConsensusProposalRewardUpdateV1 {};
|
||||
let mut update_data = vec![];
|
||||
update_data.write_u8(ConsensusFunction::ProposalRewardV1 as u8)?;
|
||||
update.encode(&mut update_data)?;
|
||||
|
||||
Ok(update_data)
|
||||
}
|
||||
|
||||
/// `process_update` function for `Consensus::ProposalRewardV1`
|
||||
pub(crate) fn consensus_proposal_reward_process_update_v1(
|
||||
_cid: ContractId,
|
||||
_update: ConsensusProposalRewardUpdateV1,
|
||||
) -> ContractResult {
|
||||
// This contract call doesn't produce any updates
|
||||
Ok(())
|
||||
}
|
||||
@@ -26,11 +26,8 @@ use darkfi_sdk::error::ContractError;
|
||||
pub enum ConsensusFunction {
|
||||
GenesisStakeV1 = 0x00,
|
||||
StakeV1 = 0x01,
|
||||
ProposalBurnV1 = 0x02,
|
||||
ProposalRewardV1 = 0x03,
|
||||
ProposalMintV1 = 0x04,
|
||||
UnstakeV1 = 0x05,
|
||||
ProposalV1 = 0x06,
|
||||
ProposalV1 = 0x02,
|
||||
UnstakeV1 = 0x03,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for ConsensusFunction {
|
||||
@@ -40,11 +37,8 @@ impl TryFrom<u8> for ConsensusFunction {
|
||||
match b {
|
||||
0x00 => Ok(Self::GenesisStakeV1),
|
||||
0x01 => Ok(Self::StakeV1),
|
||||
0x02 => Ok(Self::ProposalBurnV1),
|
||||
0x03 => Ok(Self::ProposalRewardV1),
|
||||
0x04 => Ok(Self::ProposalMintV1),
|
||||
0x05 => Ok(Self::UnstakeV1),
|
||||
0x06 => Ok(Self::ProposalV1),
|
||||
0x02 => Ok(Self::ProposalV1),
|
||||
0x03 => Ok(Self::UnstakeV1),
|
||||
_ => Err(ContractError::InvalidFunction),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ use rand::rngs::OsRng;
|
||||
use darkfi_consensus_contract::{
|
||||
client::{
|
||||
genesis_stake_v1::ConsensusGenesisStakeCallBuilder,
|
||||
proposal_v1_2::ConsensusProposalCallBuilder, stake_v1::ConsensusStakeCallBuilder,
|
||||
proposal_v1::ConsensusProposalCallBuilder, stake_v1::ConsensusStakeCallBuilder,
|
||||
unstake_v1::ConsensusUnstakeCallBuilder,
|
||||
},
|
||||
model::{ConsensusGenesisStakeParamsV1, ConsensusProposalParamsV1},
|
||||
@@ -63,8 +63,7 @@ use darkfi_money_contract::{
|
||||
Output,
|
||||
},
|
||||
MoneyFunction, CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1, CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1,
|
||||
CONSENSUS_CONTRACT_ZKAS_PROPOSAL_MINT_NS_V1, CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1,
|
||||
CONSENSUS_CONTRACT_ZKAS_PROPOSAL_REWARD_NS_V1, MONEY_CONTRACT_ZKAS_BURN_NS_V1,
|
||||
CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1, MONEY_CONTRACT_ZKAS_BURN_NS_V1,
|
||||
MONEY_CONTRACT_ZKAS_MINT_NS_V1,
|
||||
};
|
||||
|
||||
@@ -233,12 +232,8 @@ impl ConsensusTestHarness {
|
||||
&CONSENSUS_CONTRACT_ID,
|
||||
SMART_CONTRACT_ZKAS_DB_NAME,
|
||||
)?;
|
||||
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);
|
||||
mkpk!(CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1);
|
||||
|
||||
holders.insert(Holder::Alice, alice);
|
||||
|
||||
@@ -142,8 +142,8 @@ pub(crate) fn money_unstake_process_instruction_v1(
|
||||
return Err(MoneyError::UnstakePreviousCallNotConsensusContract.into())
|
||||
}
|
||||
|
||||
// Verify previous call corresponds to Consensus::UnstakeV1 (0x05)
|
||||
if previous.data[0] != 0x05 {
|
||||
// Verify previous call corresponds to Consensus::UnstakeV1 (0x03)
|
||||
if previous.data[0] != 0x03 {
|
||||
msg!("[MoneyUnstakeV1] Error: Previous call function mismatch");
|
||||
return Err(MoneyError::PreviousCallFunctionMissmatch.into())
|
||||
}
|
||||
|
||||
@@ -104,9 +104,5 @@ pub const CONSENSUS_CONTRACT_COIN_MERKLE_TREE: &str = "consensus_coin_tree";
|
||||
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
|
||||
pub const CONSENSUS_CONTRACT_ZKAS_PROPOSAL_MINT_NS_V1: &str = "ProposalMint_V1";
|
||||
/// zkas proposal circuit namespace
|
||||
pub const CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1: &str = "ConsensusProposal_V1";
|
||||
|
||||
Reference in New Issue
Block a user