|
|
@@ -30,27 +30,31 @@ use darkfi_money_contract::{
|
|
|
};
|
|
|
use darkfi_sdk::{
|
|
|
crypto::{
|
|
|
- ecvrf::VrfProof, note::AeadEncryptedNote, pasta_prelude::*, pedersen_commitment_base,
|
|
|
- pedersen_commitment_u64, poseidon_hash, MerkleNode, MerkleTree, Nullifier, PublicKey,
|
|
|
- SecretKey,
|
|
|
+ ecvrf::VrfProof, note::AeadEncryptedNote, pasta_prelude::*, pedersen_commitment_u64,
|
|
|
+ poseidon_hash, Keypair, MerkleNode, MerkleTree, Nullifier, PublicKey, SecretKey,
|
|
|
},
|
|
|
incrementalmerkletree::{Hashable, Tree},
|
|
|
pasta::{group::ff::FromUniformBytes, pallas},
|
|
|
};
|
|
|
-use log::debug;
|
|
|
+use log::{debug, info};
|
|
|
use rand::rngs::OsRng;
|
|
|
|
|
|
use crate::{
|
|
|
client::common::{ConsensusBurnInputInfo, ConsensusMintOutputInfo},
|
|
|
model::{
|
|
|
- ConsensusProposalParamsV1, HEADSTART, MU_RHO_PREFIX, MU_Y_PREFIX, REWARD, REWARD_PALLAS,
|
|
|
- SEED_PREFIX, SERIAL_PREFIX,
|
|
|
+ ConsensusProposalParamsV1, HEADSTART, MU_RHO_PREFIX, MU_Y_PREFIX, REWARD, SEED_PREFIX,
|
|
|
+ SERIAL_PREFIX,
|
|
|
},
|
|
|
};
|
|
|
|
|
|
pub struct ConsensusProposalCallDebris {
|
|
|
+ /// Payload params
|
|
|
pub params: ConsensusProposalParamsV1,
|
|
|
+ /// ZK proofs
|
|
|
pub proofs: Vec<Proof>,
|
|
|
+ /// The new output keypair (used in the minted coin)
|
|
|
+ pub keypair: Keypair,
|
|
|
+ /// Secret key used to sign the transaction
|
|
|
pub signature_secret: SecretKey,
|
|
|
}
|
|
|
|
|
|
@@ -59,11 +63,10 @@ pub struct ConsensusProposalRevealed {
|
|
|
pub epoch: u64,
|
|
|
pub public_key: PublicKey,
|
|
|
pub merkle_root: MerkleNode,
|
|
|
- pub value_commit: pallas::Point,
|
|
|
- pub new_serial: pallas::Base,
|
|
|
- pub new_serial_commit: pallas::Point,
|
|
|
- pub new_value_commit: pallas::Point,
|
|
|
- pub new_coin: Coin,
|
|
|
+ pub input_value_commit: pallas::Point,
|
|
|
+ pub reward: u64,
|
|
|
+ pub output_value_commit: pallas::Point,
|
|
|
+ pub output_coin: Coin,
|
|
|
pub vrf_proof: VrfProof,
|
|
|
pub mu_y: pallas::Base,
|
|
|
pub y: pallas::Base,
|
|
|
@@ -71,40 +74,36 @@ pub struct ConsensusProposalRevealed {
|
|
|
pub rho: pallas::Base,
|
|
|
pub sigma1: pallas::Base,
|
|
|
pub sigma2: pallas::Base,
|
|
|
+ pub headstart: pallas::Base,
|
|
|
}
|
|
|
|
|
|
impl ConsensusProposalRevealed {
|
|
|
- pub fn to_vec(&self) -> Vec<pallas::Base> {
|
|
|
- let epoch_palas = pallas::Base::from(self.epoch);
|
|
|
+ fn to_vec(&self) -> Vec<pallas::Base> {
|
|
|
let (pub_x, pub_y) = self.public_key.xy();
|
|
|
- let value_coords = self.value_commit.to_affine().coordinates().unwrap();
|
|
|
- let new_serial_coords = self.new_serial_commit.to_affine().coordinates().unwrap();
|
|
|
- let reward_pallas = pallas::Base::from(REWARD);
|
|
|
- let new_value_coords = self.new_value_commit.to_affine().coordinates().unwrap();
|
|
|
+ let input_value_coords = self.input_value_commit.to_affine().coordinates().unwrap();
|
|
|
+ let output_value_coords = self.output_value_commit.to_affine().coordinates().unwrap();
|
|
|
|
|
|
// NOTE: It's important to keep these in the same order
|
|
|
// as the `constrain_instance` calls in the zkas code.
|
|
|
vec![
|
|
|
self.nullifier.inner(),
|
|
|
- epoch_palas,
|
|
|
+ pallas::Base::from(self.epoch),
|
|
|
pub_x,
|
|
|
pub_y,
|
|
|
self.merkle_root.inner(),
|
|
|
- *value_coords.x(),
|
|
|
- *value_coords.y(),
|
|
|
- *new_serial_coords.x(),
|
|
|
- *new_serial_coords.y(),
|
|
|
- reward_pallas,
|
|
|
- *new_value_coords.x(),
|
|
|
- *new_value_coords.y(),
|
|
|
- self.new_coin.inner(),
|
|
|
+ *input_value_coords.x(),
|
|
|
+ *input_value_coords.y(),
|
|
|
+ pallas::Base::from(self.reward),
|
|
|
+ *output_value_coords.x(),
|
|
|
+ *output_value_coords.y(),
|
|
|
+ self.output_coin.inner(),
|
|
|
self.mu_y,
|
|
|
self.y,
|
|
|
self.mu_rho,
|
|
|
self.rho,
|
|
|
self.sigma1,
|
|
|
self.sigma2,
|
|
|
- HEADSTART,
|
|
|
+ self.headstart,
|
|
|
]
|
|
|
}
|
|
|
}
|
|
|
@@ -112,7 +111,7 @@ impl ConsensusProposalRevealed {
|
|
|
/// Struct holding necessary information to build a proposal transaction.
|
|
|
pub struct ConsensusProposalCallBuilder {
|
|
|
/// `ConsensusOwnCoin` we're given to use in this builder
|
|
|
- pub coin: ConsensusOwnCoin,
|
|
|
+ pub owncoin: ConsensusOwnCoin,
|
|
|
/// Rewarded slot checkpoint
|
|
|
pub slot_checkpoint: SlotCheckpoint,
|
|
|
/// Extending fork last proposal/block hash
|
|
|
@@ -120,7 +119,7 @@ pub struct ConsensusProposalCallBuilder {
|
|
|
/// Extending fork second to last proposal/block hash
|
|
|
pub fork_previous_hash: blake3::Hash,
|
|
|
/// Merkle tree of coins used to create inclusion proofs
|
|
|
- pub tree: MerkleTree,
|
|
|
+ pub merkle_tree: MerkleTree,
|
|
|
/// `Proposal_V1` zkas circuit ZkBinary
|
|
|
pub proposal_zkbin: ZkBinary,
|
|
|
/// Proving key for the `Proposal_V1` zk circuit
|
|
|
@@ -129,129 +128,148 @@ pub struct ConsensusProposalCallBuilder {
|
|
|
|
|
|
impl ConsensusProposalCallBuilder {
|
|
|
pub fn build(&self) -> Result<ConsensusProposalCallDebris> {
|
|
|
- debug!("Building Consensus::ProposalBurnV1 contract call for proposal");
|
|
|
- let value = self.coin.note.value;
|
|
|
- assert!(value != 0);
|
|
|
+ info!("Building Consensus::ProposalBurnV1 contract call");
|
|
|
+ assert!(self.owncoin.note.value != 0);
|
|
|
|
|
|
debug!("Building Consensus::ProposalV1 anonymous input");
|
|
|
- let leaf_position = self.coin.leaf_position;
|
|
|
- let root = self.tree.root(0).unwrap();
|
|
|
- let merkle_path = self.tree.authentication_path(leaf_position, &root).unwrap();
|
|
|
+ let root = self.merkle_tree.root(0).unwrap();
|
|
|
+ let merkle_path =
|
|
|
+ self.merkle_tree.authentication_path(self.owncoin.leaf_position, &root).unwrap();
|
|
|
+
|
|
|
let input = ConsensusBurnInputInfo {
|
|
|
- leaf_position,
|
|
|
+ leaf_position: self.owncoin.leaf_position,
|
|
|
merkle_path,
|
|
|
- secret: self.coin.secret,
|
|
|
- note: self.coin.note.clone(),
|
|
|
+ secret: self.owncoin.secret,
|
|
|
+ note: self.owncoin.note.clone(),
|
|
|
value_blind: pallas::Scalar::random(&mut OsRng),
|
|
|
};
|
|
|
|
|
|
- debug!("Building anonymous output");
|
|
|
- let reward_blind = pallas::Scalar::random(&mut OsRng);
|
|
|
- let new_value_blind = input.value_blind + reward_blind;
|
|
|
- let new_coin_blind = pallas::Base::random(&mut OsRng);
|
|
|
+ debug!("Building Consensus::ProposalV1 anonymous output");
|
|
|
+ let output_coin_blind = pallas::Base::random(&mut OsRng);
|
|
|
+ let output_reward_blind = pallas::Scalar::random(&mut OsRng);
|
|
|
+ let output_value_blind = input.value_blind + output_reward_blind;
|
|
|
+
|
|
|
+ // We create a new random keypair for the output
|
|
|
+ let output_keypair = Keypair::random(&mut OsRng);
|
|
|
+
|
|
|
+ // The output's serial is derived from the old serial
|
|
|
+ let new_serial =
|
|
|
+ poseidon_hash([SERIAL_PREFIX, self.owncoin.secret.inner(), self.owncoin.note.serial]);
|
|
|
+
|
|
|
let output = ConsensusMintOutputInfo {
|
|
|
- value: self.coin.note.value + REWARD,
|
|
|
- epoch: 0,
|
|
|
- public_key: PublicKey::from_secret(self.coin.secret),
|
|
|
- value_blind: new_value_blind,
|
|
|
- serial: self.coin.note.serial,
|
|
|
- coin_blind: new_coin_blind,
|
|
|
+ value: self.owncoin.note.value + REWARD,
|
|
|
+ epoch: 0, // We set the epoch as 0 here to eliminate a potential timelock
|
|
|
+ public_key: output_keypair.public,
|
|
|
+ value_blind: output_value_blind,
|
|
|
+ serial: new_serial,
|
|
|
+ coin_blind: output_coin_blind,
|
|
|
};
|
|
|
- debug!("Finished building output");
|
|
|
|
|
|
- debug!("Building Consensus::ProposalV1 contract call for proposal");
|
|
|
+ info!("Building Consensus::ProposalV1 VRF proof");
|
|
|
+ let mut vrf_input = Vec::with_capacity(32 + blake3::OUT_LEN + 32);
|
|
|
+ vrf_input.extend_from_slice(&self.slot_checkpoint.previous_eta.to_repr());
|
|
|
+ vrf_input.extend_from_slice(self.fork_previous_hash.as_bytes());
|
|
|
+ vrf_input.extend_from_slice(&pallas::Base::from(self.slot_checkpoint.slot).to_repr());
|
|
|
+ let vrf_proof = VrfProof::prove(input.secret, &vrf_input, &mut OsRng);
|
|
|
+
|
|
|
+ info!("Building Consensus::ProposalV1 ZK proof");
|
|
|
let (proof, public_inputs) = create_proposal_proof(
|
|
|
&self.proposal_zkbin,
|
|
|
&self.proposal_pk,
|
|
|
&input,
|
|
|
&output,
|
|
|
&self.slot_checkpoint,
|
|
|
- self.fork_hash,
|
|
|
- self.fork_previous_hash,
|
|
|
+ &vrf_proof,
|
|
|
)?;
|
|
|
|
|
|
- let input = ConsensusInput {
|
|
|
- epoch: self.coin.note.epoch,
|
|
|
- coin: self.coin.coin,
|
|
|
- value_commit: public_inputs.value_commit,
|
|
|
+ let tx_input = ConsensusInput {
|
|
|
+ epoch: input.note.epoch,
|
|
|
+ value_commit: public_inputs.input_value_commit,
|
|
|
nullifier: public_inputs.nullifier,
|
|
|
merkle_root: public_inputs.merkle_root,
|
|
|
signature_public: public_inputs.public_key,
|
|
|
};
|
|
|
|
|
|
- // Encrypted note
|
|
|
+ // Output's encrypted note
|
|
|
let note = ConsensusNote {
|
|
|
- serial: public_inputs.new_serial,
|
|
|
+ serial: output.serial,
|
|
|
value: output.value,
|
|
|
- epoch: 0,
|
|
|
- coin_blind: new_coin_blind,
|
|
|
- value_blind: new_value_blind,
|
|
|
+ epoch: output.epoch,
|
|
|
+ coin_blind: output.coin_blind,
|
|
|
+ value_blind: output.value_blind,
|
|
|
reward: REWARD,
|
|
|
- reward_blind,
|
|
|
+ reward_blind: output_reward_blind,
|
|
|
};
|
|
|
|
|
|
let encrypted_note = AeadEncryptedNote::encrypt(¬e, &output.public_key, &mut OsRng)?;
|
|
|
|
|
|
- let output = ConsensusOutput {
|
|
|
- value_commit: public_inputs.new_value_commit,
|
|
|
- coin: public_inputs.new_coin,
|
|
|
+ let tx_output = ConsensusOutput {
|
|
|
+ value_commit: public_inputs.output_value_commit,
|
|
|
+ coin: public_inputs.output_coin,
|
|
|
note: encrypted_note,
|
|
|
};
|
|
|
|
|
|
- // We now fill this with necessary stuff
|
|
|
- let new_serial_commit = public_inputs.new_serial_commit;
|
|
|
- let slot = self.slot_checkpoint.slot;
|
|
|
- let vrf_proof = public_inputs.vrf_proof;
|
|
|
- let y = public_inputs.y;
|
|
|
- let rho = public_inputs.rho;
|
|
|
+ // Construct params
|
|
|
let params = ConsensusProposalParamsV1 {
|
|
|
- input,
|
|
|
- output,
|
|
|
+ input: tx_input,
|
|
|
+ output: tx_output,
|
|
|
reward: REWARD,
|
|
|
- reward_blind,
|
|
|
- new_serial_commit,
|
|
|
- slot,
|
|
|
+ reward_blind: output_reward_blind,
|
|
|
+ slot: self.slot_checkpoint.slot,
|
|
|
fork_hash: self.fork_hash,
|
|
|
fork_previous_hash: self.fork_previous_hash,
|
|
|
vrf_proof,
|
|
|
- y,
|
|
|
- rho,
|
|
|
+ y: public_inputs.y,
|
|
|
+ rho: public_inputs.rho,
|
|
|
};
|
|
|
- let proofs = vec![proof];
|
|
|
|
|
|
- // Now we should have all the params, zk proofs and signature secret.
|
|
|
- // We return it all and let the caller deal with it.
|
|
|
- let debris =
|
|
|
- ConsensusProposalCallDebris { params, proofs, signature_secret: self.coin.secret };
|
|
|
+ // Construct debris
|
|
|
+ let debris = ConsensusProposalCallDebris {
|
|
|
+ params,
|
|
|
+ proofs: vec![proof],
|
|
|
+ keypair: output_keypair,
|
|
|
+ signature_secret: input.secret,
|
|
|
+ };
|
|
|
Ok(debris)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-pub fn create_proposal_proof(
|
|
|
+fn create_proposal_proof(
|
|
|
zkbin: &ZkBinary,
|
|
|
pk: &ProvingKey,
|
|
|
input: &ConsensusBurnInputInfo,
|
|
|
output: &ConsensusMintOutputInfo,
|
|
|
- slot_checkpoint: &SlotCheckpoint,
|
|
|
- _fork_hash: blake3::Hash,
|
|
|
- fork_previous_hash: blake3::Hash,
|
|
|
+ checkpoint: &SlotCheckpoint,
|
|
|
+ vrf_proof: &VrfProof,
|
|
|
) -> Result<(Proof, ConsensusProposalRevealed)> {
|
|
|
// TODO: fork_hash to be used as part of rank constrain in the proof
|
|
|
- // Proof parameters
|
|
|
+ // Calculate lottery parameters
|
|
|
+ let seed = poseidon_hash([SEED_PREFIX, input.note.serial]);
|
|
|
+
|
|
|
+ let mut eta = [0u8; 64];
|
|
|
+ eta[..blake3::OUT_LEN].copy_from_slice(vrf_proof.hash_output().as_bytes());
|
|
|
+ let eta = pallas::Base::from_uniform_bytes(&eta);
|
|
|
+
|
|
|
+ let mu_y = poseidon_hash([MU_Y_PREFIX, eta, pallas::Base::from(checkpoint.slot)]);
|
|
|
+ let y = poseidon_hash([seed, mu_y]);
|
|
|
+ let mu_rho = poseidon_hash([MU_RHO_PREFIX, eta, pallas::Base::from(checkpoint.slot)]);
|
|
|
+ let rho = poseidon_hash([seed, mu_rho]);
|
|
|
+
|
|
|
+ // Derive the input's nullifier
|
|
|
let nullifier = Nullifier::from(poseidon_hash([input.secret.inner(), input.note.serial]));
|
|
|
- let epoch = input.note.epoch;
|
|
|
- let epoch_pallas = pallas::Base::from(epoch);
|
|
|
- let value_pallas = pallas::Base::from(input.note.value);
|
|
|
- let value_commit = pedersen_commitment_u64(input.note.value, input.value_blind);
|
|
|
+
|
|
|
+ // Create the value commitment for the input
|
|
|
+ let input_value_commit = pedersen_commitment_u64(input.note.value, input.value_blind);
|
|
|
+
|
|
|
+ // Merkle inclusion proof for the input
|
|
|
let public_key = PublicKey::from_secret(input.secret);
|
|
|
let (pub_x, pub_y) = public_key.xy();
|
|
|
|
|
|
- // Burnt coin and its merkle_root
|
|
|
let coin = poseidon_hash([
|
|
|
pub_x,
|
|
|
pub_y,
|
|
|
- value_pallas,
|
|
|
- epoch_pallas,
|
|
|
+ pallas::Base::from(input.note.value),
|
|
|
+ pallas::Base::from(input.note.epoch),
|
|
|
input.note.serial,
|
|
|
input.note.coin_blind,
|
|
|
]);
|
|
|
@@ -270,79 +288,56 @@ pub fn create_proposal_proof(
|
|
|
current
|
|
|
};
|
|
|
|
|
|
- // New coin
|
|
|
- let new_serial = poseidon_hash([SERIAL_PREFIX, input.secret.inner(), input.note.serial]);
|
|
|
- let new_serial_blind = pallas::Scalar::random(&mut OsRng);
|
|
|
- let new_serial_commit = pedersen_commitment_base(new_serial, new_serial_blind);
|
|
|
- let new_value_commit = pedersen_commitment_u64(output.value, output.value_blind);
|
|
|
- let new_value_pallas = pallas::Base::from(output.value);
|
|
|
- let (new_pub_x, new_pub_y) = output.public_key.xy();
|
|
|
-
|
|
|
- let new_coin = Coin::from(poseidon_hash([
|
|
|
- new_pub_x,
|
|
|
- new_pub_y,
|
|
|
- new_value_pallas,
|
|
|
- pallas::Base::ZERO,
|
|
|
- new_serial,
|
|
|
+ // Derive the new output coin
|
|
|
+ let (output_x, output_y) = output.public_key.xy();
|
|
|
+ let output_coin = Coin::from(poseidon_hash([
|
|
|
+ output_x,
|
|
|
+ output_y,
|
|
|
+ pallas::Base::from(output.value),
|
|
|
+ pallas::Base::from(output.epoch),
|
|
|
+ output.serial,
|
|
|
output.coin_blind,
|
|
|
]));
|
|
|
|
|
|
- let slot_pallas = pallas::Base::from(slot_checkpoint.slot);
|
|
|
- let seed = poseidon_hash([SEED_PREFIX, input.note.serial]);
|
|
|
- let mut vrf_input = Vec::with_capacity(32 + blake3::OUT_LEN + 32);
|
|
|
- vrf_input.extend_from_slice(&slot_checkpoint.previous_eta.to_repr());
|
|
|
- vrf_input.extend_from_slice(fork_previous_hash.as_bytes());
|
|
|
- vrf_input.extend_from_slice(&slot_pallas.to_repr());
|
|
|
- let vrf_proof = VrfProof::prove(input.secret, &vrf_input, &mut OsRng);
|
|
|
- let mut eta = [0u8; 64];
|
|
|
- eta[..blake3::OUT_LEN].copy_from_slice(vrf_proof.hash_output().as_bytes());
|
|
|
- let eta = pallas::Base::from_uniform_bytes(&eta);
|
|
|
- let mu_y = poseidon_hash([MU_Y_PREFIX, eta, slot_pallas]);
|
|
|
- let y = poseidon_hash([seed, mu_y]);
|
|
|
- let mu_rho = poseidon_hash([MU_RHO_PREFIX, eta, slot_pallas]);
|
|
|
- let rho = poseidon_hash([seed, mu_rho]);
|
|
|
- let (sigma1, sigma2) = (slot_checkpoint.sigma1, slot_checkpoint.sigma2);
|
|
|
-
|
|
|
- // Generate public inputs, witnesses and proof
|
|
|
+ // Create the ZK proof
|
|
|
let public_inputs = ConsensusProposalRevealed {
|
|
|
nullifier,
|
|
|
- epoch,
|
|
|
+ epoch: input.note.epoch,
|
|
|
public_key,
|
|
|
merkle_root,
|
|
|
- value_commit,
|
|
|
- new_serial,
|
|
|
- new_serial_commit,
|
|
|
- new_value_commit,
|
|
|
- new_coin,
|
|
|
- vrf_proof,
|
|
|
+ input_value_commit,
|
|
|
+ reward: REWARD,
|
|
|
+ output_value_commit: pedersen_commitment_u64(output.value, output.value_blind),
|
|
|
+ output_coin,
|
|
|
+ vrf_proof: *vrf_proof,
|
|
|
mu_y,
|
|
|
y,
|
|
|
mu_rho,
|
|
|
rho,
|
|
|
- sigma1,
|
|
|
- sigma2,
|
|
|
+ sigma1: checkpoint.sigma1,
|
|
|
+ sigma2: checkpoint.sigma2,
|
|
|
+ headstart: HEADSTART,
|
|
|
};
|
|
|
|
|
|
let prover_witnesses = vec![
|
|
|
Witness::Base(Value::known(input.secret.inner())),
|
|
|
Witness::Base(Value::known(input.note.serial)),
|
|
|
Witness::Base(Value::known(pallas::Base::from(input.note.value))),
|
|
|
- Witness::Base(Value::known(epoch_pallas)),
|
|
|
- Witness::Base(Value::known(REWARD_PALLAS)),
|
|
|
+ Witness::Base(Value::known(pallas::Base::from(input.note.epoch))),
|
|
|
+ Witness::Base(Value::known(pallas::Base::from(REWARD))),
|
|
|
Witness::Scalar(Value::known(input.value_blind)),
|
|
|
Witness::Base(Value::known(input.note.coin_blind)),
|
|
|
Witness::Uint32(Value::known(u64::from(input.leaf_position).try_into().unwrap())),
|
|
|
Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
|
|
|
- Witness::Scalar(Value::known(new_serial_blind)),
|
|
|
- Witness::Base(Value::known(new_pub_x)),
|
|
|
- Witness::Base(Value::known(new_pub_y)),
|
|
|
+ Witness::Base(Value::known(output_x)),
|
|
|
+ Witness::Base(Value::known(output_y)),
|
|
|
Witness::Scalar(Value::known(output.value_blind)),
|
|
|
Witness::Base(Value::known(output.coin_blind)),
|
|
|
- Witness::Base(Value::known(mu_y)),
|
|
|
- Witness::Base(Value::known(mu_rho)),
|
|
|
- Witness::Base(Value::known(sigma1)),
|
|
|
- Witness::Base(Value::known(sigma2)),
|
|
|
- Witness::Base(Value::known(HEADSTART)),
|
|
|
+ Witness::Base(Value::known(public_inputs.mu_y)),
|
|
|
+ Witness::Base(Value::known(public_inputs.mu_rho)),
|
|
|
+ Witness::Base(Value::known(public_inputs.sigma1)),
|
|
|
+ Witness::Base(Value::known(public_inputs.sigma2)),
|
|
|
+ Witness::Base(Value::known(public_inputs.headstart)),
|
|
|
];
|
|
|
|
|
|
let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
|