/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2024 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 .
*/
use darkfi_sdk::{
crypto::{
pedersen::{pedersen_commitment_base, pedersen_commitment_u64},
poseidon_hash,
util::fp_mod_fv,
MerkleNode, MerkleTree, SecretKey,
},
pasta::{arithmetic::CurveAffine, group::Curve, pallas},
};
use darkfi_serial::{async_trait, SerialDecodable, SerialEncodable};
use halo2_proofs::{arithmetic::Field, circuit::Value};
use log::info;
use rand::rngs::OsRng;
use super::constants::EPOCH_LENGTH;
use crate::{
consensus::{constants, utils::fbig2base, Float10, TransferStx, TxRcpt},
zk::{
proof::{Proof, ProvingKey},
vm::ZkCircuit,
vm_heap::Witness,
},
zkas::ZkBinary,
Result,
};
use std::{
fs::File,
io::{prelude::*, BufWriter},
};
pub const MERKLE_DEPTH_LEAD_COIN: usize = 32;
pub const MERKLE_DEPTH: u8 = 32;
pub const ZERO: pallas::Base = pallas::Base::zero();
pub const ONE: pallas::Base = pallas::Base::one();
pub const PREFIX_EVL: u64 = 2;
pub const PREFIX_SEED: u64 = 3;
pub const PREFIX_CM: u64 = 4;
pub const PREFIX_PK: u64 = 5;
pub const PREFIX_SN: u64 = 6;
// TODO: Unify item names with the names in the ZK proof (those are more descriptive)
/// Structure representing the consensus leader coin
#[derive(Debug, Clone, SerialDecodable, SerialEncodable)]
pub struct LeadCoin {
/// Coin's stake value
pub value: u64,
/// Coin creation slot.
pub slot: u64,
/// Coin nonce
pub nonce: pallas::Base,
/// Commitment for coin1
pub coin1_commitment: pallas::Point,
/// Merkle root of coin1 commitment
pub coin1_commitment_root: MerkleNode,
/// Coin commitment position
pub coin1_commitment_pos: u32,
/// Merkle path to the coin1's commitment
pub coin1_commitment_merkle_path: Vec,
/// coin1 sk
pub coin1_sk: pallas::Base,
/// Merkle root of the `coin1` secret key
pub coin1_sk_root: MerkleNode,
/// coin1 sk position in merkle tree
pub coin1_sk_pos: u32,
/// Merkle path to the secret key of `coin1`
pub coin1_sk_merkle_path: Vec,
/// coin1 commitment blinding factor
pub coin1_blind: pallas::Scalar,
}
impl LeadCoin {
/// Create a new `LeadCoin` object using given parameters.
#[allow(clippy::too_many_arguments)]
pub fn new(
// emulation of global random oracle output from previous epoch randomness.
//eta: pallas::Base,
// Stake value
value: u64,
// Slot absolute index
slot: u64,
// coin1 sk
coin1_sk: pallas::Base,
// Merkle root of the `coin_1` secret key in the Merkle tree of secret keys
coin1_sk_root: MerkleNode,
// sk pos
coin1_sk_pos: usize,
// Merkle path to the secret key of `coin_1` in the Merkle tree of secret keys
coin1_sk_merkle_path: Vec,
// coin1 nonce
seed: pallas::Base,
// Merkle tree of coin commitments
coin_commitment_tree: &mut MerkleTree,
) -> Self {
// Generate random blinding values for commitments:
let coin1_blind = pallas::Scalar::random(&mut OsRng);
//let coin2_blind = pallas::Scalar::random(&mut OsRng);
// pk
let pk = Self::util_pk(coin1_sk_root, slot);
let coin1_commitment = Self::commitment(pk, pallas::Base::from(value), seed, coin1_blind);
// Hash its coordinates to get a base field element
let c1_cm_coords = coin1_commitment.to_affine().coordinates().unwrap();
let c1_base_msg = [*c1_cm_coords.x(), *c1_cm_coords.y()];
let coin1_commitment_base = poseidon_hash(c1_base_msg);
// Append the element to the Merkle tree
coin_commitment_tree.append(MerkleNode::from(coin1_commitment_base));
let coin1_commitment_pos = coin_commitment_tree.mark().unwrap();
let coin1_commitment_root = coin_commitment_tree.root(0).unwrap();
let coin1_commitment_merkle_path =
coin_commitment_tree.witness(coin1_commitment_pos, 0).unwrap();
Self {
value,
slot,
nonce: seed,
coin1_commitment,
coin1_commitment_root,
coin1_commitment_pos: u32::try_from(u64::from(coin1_commitment_pos)).unwrap(),
coin1_commitment_merkle_path,
coin1_sk,
coin1_sk_root,
coin1_sk_pos: u32::try_from(coin1_sk_pos).unwrap(),
coin1_sk_merkle_path,
coin1_blind,
}
}
pub fn sn(&self) -> pallas::Base {
let sn_msg = [pallas::Base::from(PREFIX_SN), self.coin1_sk_root.inner(), self.nonce, ZERO];
poseidon_hash(sn_msg)
}
pub fn election_seeds_u64(eta: pallas::Base, slotu64: u64) -> (pallas::Base, pallas::Base) {
Self::election_seeds(eta, pallas::Base::from(slotu64))
}
/// Derive election seeds from given parameters
pub fn election_seeds(eta: pallas::Base, slot: pallas::Base) -> (pallas::Base, pallas::Base) {
info!(target: "consensus::leadcoin", "election_seeds: eta: {:?}, slot: {:?}", eta, slot);
let election_seed_nonce = pallas::Base::from(3);
let election_seed_lead = pallas::Base::from(22);
// mu_y
let lead_msg = [election_seed_lead, eta, slot];
let lead_mu = poseidon_hash(lead_msg);
// mu_rho
let nonce_msg = [election_seed_nonce, eta, slot];
let nonce_mu = poseidon_hash(nonce_msg);
(lead_mu, nonce_mu)
}
/// Create a vector of `pallas::Base` elements from the `LeadCoin` to be
/// used as public inputs for the ZK proof.
pub fn public_inputs(
&self,
sigma1: pallas::Base,
sigma2: pallas::Base,
current_eta: pallas::Base,
current_slot: pallas::Base,
derived_blind: pallas::Scalar,
) -> Vec {
// pk
let pk = self.pk();
// coin 1-2 cm/commitment
let c1_cm_coord = self.coin1_commitment.to_affine().coordinates().unwrap();
let c2_cm_coord = self.derived_commitment(derived_blind).to_affine().coordinates().unwrap();
// lottery seed
let seed_msg =
[pallas::Base::from(PREFIX_SEED), self.coin1_sk_root.inner(), self.nonce, ZERO];
let seed = poseidon_hash(seed_msg);
// y
let (y_mu, rho_mu) = Self::election_seeds(current_eta, current_slot);
let y_msg = [seed, y_mu];
let y = poseidon_hash(y_msg);
// rho
let rho_msg = [seed, rho_mu];
let rho = poseidon_hash(rho_msg);
let public_inputs = vec![
pk,
*c1_cm_coord.x(),
*c1_cm_coord.y(),
*c2_cm_coord.x(),
*c2_cm_coord.y(),
self.coin1_commitment_root.inner(),
self.coin1_sk_root.inner(),
self.sn(),
y_mu,
y,
rho_mu,
rho,
sigma1,
sigma2,
];
public_inputs
}
fn util_pk(sk_root: MerkleNode, slot: u64) -> pallas::Base {
let pk_msg =
[pallas::Base::from(PREFIX_PK), sk_root.inner(), pallas::Base::from(slot), ZERO];
poseidon_hash(pk_msg)
}
/// calculate coin public key: hash of root coin secret key
/// and creation slot.
pub fn pk(&self) -> pallas::Base {
Self::util_pk(self.coin1_sk_root, self.slot)
}
fn util_derived_rho(sk_root: MerkleNode, nonce: pallas::Base) -> pallas::Base {
let rho_msg = [pallas::Base::from(PREFIX_EVL), sk_root.inner(), nonce, ZERO];
poseidon_hash(rho_msg)
}
/// calculate derived coin nonce: hash of root coin secret key
/// and old nonce
pub fn derived_rho(&self) -> pallas::Base {
Self::util_derived_rho(self.coin1_sk_root, self.nonce)
}
pub fn headstart() -> pallas::Base {
let headstart = constants::MIN_F.clone() * Float10::try_from(constants::P).unwrap();
fbig2base(headstart)
}
pub fn is_leader(
&self,
sigma1: pallas::Base,
sigma2: pallas::Base,
current_eta: pallas::Base,
current_slot: pallas::Base,
) -> bool {
let y_seed =
[pallas::Base::from(PREFIX_SEED), self.coin1_sk_root.inner(), self.nonce, ZERO];
let y_seed_hash = poseidon_hash(y_seed);
let (y_mu, _) = Self::election_seeds(current_eta, current_slot);
let y_msg = [y_seed_hash, y_mu];
let y = poseidon_hash(y_msg);
let value = pallas::Base::from(self.value);
let headstart = Self::headstart();
let target = sigma1 * value + sigma2 * value * value + headstart;
let y_t_str = format!("{:?},{:?}\n", y, target);
let f =
File::options().append(true).create(true).open(constants::LOTTERY_HISTORY_LOG).unwrap();
{
let mut writer = BufWriter::new(f);
let _ = writer.write(&y_t_str.into_bytes()).unwrap();
}
info!(target: "consensus::leadcoin", "is_leader(): y = {:?}", y);
info!(target: "consensus::leadcoin", "is_leader(): T = {:?}", target);
y < target
}
fn commitment(
pk: pallas::Base,
value: pallas::Base,
seed: pallas::Base,
blind: pallas::Scalar,
) -> pallas::Point {
let commit_msg = [pallas::Base::from(PREFIX_CM), pk, value, seed];
// Create commitment to coin
let commit_v = poseidon_hash(commit_msg);
pedersen_commitment_base(commit_v, blind)
}
/// calculated derived coin commitment
pub fn derived_commitment(&self, blind: pallas::Scalar) -> pallas::Point {
let pk = self.pk();
let rho = self.derived_rho();
Self::commitment(pk, pallas::Base::from(self.value + constants::REWARD), rho, blind)
}
/// the new coin to be minted after the current coin is spent
/// in lottery.
pub fn derive_coin(
&self,
coin_commitment_tree: &mut MerkleTree,
derived_blind: pallas::Scalar,
) -> LeadCoin {
info!(target: "consensus::leadcoin", "derive_coin(): Deriving new coin!");
let derived_c1_rho = self.derived_rho();
let derived_c1_cm = self.derived_commitment(derived_blind);
let derived_c1_cm_coord = derived_c1_cm.to_affine().coordinates().unwrap();
let derived_c1_cm_msg = [*derived_c1_cm_coord.x(), *derived_c1_cm_coord.y()];
let derived_c1_cm_base = poseidon_hash(derived_c1_cm_msg);
coin_commitment_tree.append(MerkleNode::from(derived_c1_cm_base));
let leaf_pos = coin_commitment_tree.mark().unwrap();
let commitment_root = coin_commitment_tree.root(0).unwrap();
let commitment_merkle_path = coin_commitment_tree.witness(leaf_pos, 0).unwrap();
LeadCoin {
value: self.value + constants::REWARD,
slot: self.slot,
nonce: derived_c1_rho,
coin1_commitment: derived_c1_cm,
coin1_commitment_root: commitment_root,
coin1_commitment_pos: u32::try_from(u64::from(leaf_pos)).unwrap(),
coin1_commitment_merkle_path: commitment_merkle_path,
coin1_sk: self.coin1_sk,
coin1_sk_root: self.coin1_sk_root,
coin1_sk_pos: self.coin1_sk_pos,
coin1_sk_merkle_path: self.coin1_sk_merkle_path.clone(),
coin1_blind: derived_blind,
}
}
/// Try to create a ZK proof of consensus leadership
pub fn create_lead_proof(
&self,
sigma1: pallas::Base,
sigma2: pallas::Base,
eta: pallas::Base,
slot: pallas::Base, //current slot index.
pk: &ProvingKey,
derived_blind: pallas::Scalar,
) -> (Result, Vec) {
let (y_mu, rho_mu) = Self::election_seeds(eta, slot);
let bincode = include_bytes!("../../proof/lead.zk.bin");
let zkbin = ZkBinary::decode(bincode).unwrap();
let headstart = Self::headstart();
let coin1_commitment_merkle_path: [MerkleNode; MERKLE_DEPTH_LEAD_COIN] =
self.coin1_commitment_merkle_path.clone().try_into().unwrap();
let coin1_sk_merkle_path: [MerkleNode; MERKLE_DEPTH_LEAD_COIN] =
self.coin1_sk_merkle_path.clone().try_into().unwrap();
let witnesses = vec![
Witness::MerklePath(Value::known(coin1_commitment_merkle_path)),
Witness::Uint32(Value::known(self.coin1_commitment_pos)),
Witness::Uint32(Value::known(self.coin1_sk_pos)),
Witness::Base(Value::known(self.coin1_sk)),
Witness::Base(Value::known(self.coin1_sk_root.inner())),
Witness::MerklePath(Value::known(coin1_sk_merkle_path)),
Witness::Base(Value::known(pallas::Base::from(self.slot))),
Witness::Base(Value::known(self.nonce)),
Witness::Scalar(Value::known(self.coin1_blind)),
Witness::Base(Value::known(pallas::Base::from(self.value))),
Witness::Scalar(Value::known(derived_blind)),
Witness::Base(Value::known(rho_mu)),
Witness::Base(Value::known(y_mu)),
Witness::Base(Value::known(sigma1)),
Witness::Base(Value::known(sigma2)),
Witness::Base(Value::known(headstart)),
];
let circuit = ZkCircuit::new(witnesses, &zkbin);
let public_inputs = self.public_inputs(sigma1, sigma2, eta, slot, derived_blind);
(Ok(Proof::create(pk, &[circuit], &public_inputs, &mut OsRng).unwrap()), public_inputs)
}
#[allow(clippy::too_many_arguments)]
pub fn create_xfer_proof(
&self,
pk: &ProvingKey,
change_coin: TxRcpt,
change_pk: pallas::Base, //change coin public key
transfered_coin: TxRcpt,
transfered_pk: pallas::Base, // recipient coin's public key
sigma1: pallas::Base,
sigma2: pallas::Base,
current_eta: pallas::Base,
current_slot: pallas::Base,
derived_blind: pallas::Scalar,
) -> Result {
assert!(change_coin.value + transfered_coin.value == self.value && self.value > 0);
let bincode = include_bytes!("../../proof/tx.zk.bin");
let zkbin = ZkBinary::decode(bincode)?;
let retval = pallas::Base::from(change_coin.value);
let xferval = pallas::Base::from(transfered_coin.value);
let pos: u32 = self.coin1_commitment_pos;
let value = pallas::Base::from(self.value);
let coin1_sk_merkle_path: [MerkleNode; MERKLE_DEPTH_LEAD_COIN] =
self.coin1_sk_merkle_path.clone().try_into().unwrap();
let coin1_commitment_merkle_path: [MerkleNode; MERKLE_DEPTH_LEAD_COIN] =
self.coin1_commitment_merkle_path.clone().try_into().unwrap();
let witnesses = vec![
// coin (1) burned coin
Witness::Base(Value::known(self.coin1_commitment_root.inner())),
Witness::Base(Value::known(self.coin1_sk_root.inner())),
Witness::Base(Value::known(self.coin1_sk)),
Witness::MerklePath(Value::known(coin1_sk_merkle_path)),
Witness::Uint32(Value::known(self.coin1_sk_pos)),
Witness::Base(Value::known(self.nonce)),
Witness::Scalar(Value::known(self.coin1_blind)),
Witness::Base(Value::known(value)),
Witness::MerklePath(Value::known(coin1_commitment_merkle_path)),
Witness::Uint32(Value::known(pos)),
Witness::Base(Value::known(self.sn())),
// coin (3)
Witness::Base(Value::known(change_pk)),
Witness::Base(Value::known(change_coin.rho)),
Witness::Scalar(Value::known(change_coin.opening)),
Witness::Base(Value::known(retval)),
// coin (4)
Witness::Base(Value::known(transfered_pk)),
Witness::Base(Value::known(transfered_coin.rho)),
Witness::Scalar(Value::known(transfered_coin.opening)),
Witness::Base(Value::known(xferval)),
];
let circuit = ZkCircuit::new(witnesses, &zkbin);
let proof = Proof::create(
pk,
&[circuit],
&self.public_inputs(sigma1, sigma2, current_eta, current_slot, derived_blind),
&mut OsRng,
)?;
let cm3_msg_in = [
pallas::Base::from(PREFIX_CM),
change_pk,
pallas::Base::from(change_coin.value),
change_coin.rho,
];
let cm3_msg = poseidon_hash(cm3_msg_in);
let cm3 = pedersen_commitment_base(cm3_msg, change_coin.opening);
let cm4_msg_in = [
pallas::Base::from(PREFIX_CM),
transfered_pk,
pallas::Base::from(transfered_coin.value),
transfered_coin.rho,
];
let cm4_msg = poseidon_hash(cm4_msg_in);
let cm4 = pedersen_commitment_base(cm4_msg, transfered_coin.opening);
let tx = TransferStx {
coin_commitment: self.coin1_commitment,
coin_pk: self.pk(),
coin_root_sk: self.coin1_sk_root,
change_coin_commitment: cm3,
transfered_coin_commitment: cm4,
nullifier: self.sn(),
slot: pallas::Base::from(self.slot),
root: self.coin1_commitment_root,
proof,
};
Ok(tx)
}
}
/// This struct holds the secrets for creating LeadCoins during one epoch.
pub struct LeadCoinSecrets {
pub secret_keys: Vec,
pub merkle_roots: Vec,
pub merkle_paths: Vec>,
}
impl LeadCoinSecrets {
/// Generate epoch coins secret keys.
/// First clot coin secret key is sampled at random, while the secret keys of the
/// remaining slots derive from the previous slot secret.
/// Clarification:
/// ```plaintext
/// sk[0] -> random,
/// sk[1] -> derive_function(sk[0]),
/// ...
/// sk[n] -> derive_function(sk[n-1]),
/// ```
pub fn generate() -> Self {
let mut tree = MerkleTree::new(EPOCH_LENGTH);
let mut sks = Vec::with_capacity(EPOCH_LENGTH);
let mut root_sks = Vec::with_capacity(EPOCH_LENGTH);
let mut path_sks = Vec::with_capacity(EPOCH_LENGTH);
let mut prev_sk = SecretKey::from(pallas::Base::one());
for i in 0..EPOCH_LENGTH {
let secret = if i == 0 {
pedersen_commitment_u64(1, pallas::Scalar::random(&mut OsRng))
} else {
pedersen_commitment_u64(1, fp_mod_fv(prev_sk.inner()))
};
let secret_coords = secret.to_affine().coordinates().unwrap();
let secret_msg = [*secret_coords.x(), *secret_coords.y()];
let secret_key = SecretKey::from(poseidon_hash(secret_msg));
sks.push(secret_key);
prev_sk = secret_key;
let node = MerkleNode::from(secret_key.inner());
tree.append(node);
let leaf_pos = tree.mark().unwrap();
let root = tree.root(0).unwrap();
let path = tree.witness(leaf_pos, 0).unwrap();
root_sks.push(root);
path_sks.push(path);
}
Self { secret_keys: sks, merkle_roots: root_sks, merkle_paths: path_sks }
}
}