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

script/research/crypsinous_playground: epoch simulation using crypsinous functions.

aggstam 3 лет назад
Родитель
Сommit
961c092656

+ 2 - 0
script/research/crypsinous_playground/.gitignore

@@ -0,0 +1,2 @@
+/target
+Cargo.lock

+ 21 - 0
script/research/crypsinous_playground/Cargo.toml

@@ -0,0 +1,21 @@
+[package]
+name = "crypsinous_playground"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+blake3 = "1.3.1"
+darkfi = {path = "../../../", features = ["crypto"]}
+darkfi-sdk = {path = "../../../src/sdk"}
+dashu = { version = "0.2.0", git = "https://github.com/ertosns/dashu" }
+halo2_gadgets = "0.2.0"
+halo2_proofs = "0.2.0"
+incrementalmerkletree = "0.3.0"
+pasta_curves = "0.4.0"
+rand = "0.8.5"
+
+# Misc
+log = "0.4.17"
+simplelog = "0.12.0"
+
+[workspace]

+ 333 - 0
script/research/crypsinous_playground/src/coins.rs

@@ -0,0 +1,333 @@
+use halo2_gadgets::poseidon::primitives as poseidon;
+use halo2_proofs::arithmetic::Field;
+use incrementalmerkletree::{bridgetree::BridgeTree, Tree};
+use log::info;
+use pasta_curves::{
+    arithmetic::CurveAffine,
+    group::{ff::PrimeField, Curve},
+    pallas,
+};
+use rand::{thread_rng, Rng};
+
+use darkfi::crypto::{
+    coin::OwnCoin,
+    keypair::{Keypair, SecretKey},
+    leadcoin::LeadCoin,
+    types::DrkValueBlind,
+    util::{mod_r_p, pedersen_commitment_base, pedersen_commitment_u64},
+};
+use darkfi_sdk::crypto::{constants::MERKLE_DEPTH_ORCHARD, MerkleNode};
+
+use crate::utils::{Float10, fbig2base};
+
+// Epoch configuration
+const EPOCH_LENGTH: u64 = 10;
+const REWARD: u64 = 420;
+
+// TODO: Describe constant meaning in comment
+const RADIX_BITS: usize = 76;
+const P: &str = "28948022309329048855892746252171976963363056481941560715954676764349967630337";
+const LOTTERY_HEAD_START: u64 = 1;
+const PRF_NULLIFIER_PREFIX: u64 = 0;
+const MERKLE_DEPTH: u8 = MERKLE_DEPTH_ORCHARD as u8;
+
+/// Retrieve previous epoch competing coins frequency.
+fn get_frequency() -> Float10 {
+    //TODO: Actually retrieve frequency of coins from the previous epoch.
+    let one: Float10 = Float10::from_str_native("1").unwrap().with_precision(RADIX_BITS).value();
+    let two: Float10 = Float10::from_str_native("2").unwrap().with_precision(RADIX_BITS).value();
+    one / two
+}
+
+/// Calculate nodes total stake for specific epoch and slot.
+fn total_stake(epoch: u64, slot: u64) -> u64 {    
+    (epoch * EPOCH_LENGTH + slot + 1) * REWARD
+}
+
+/// Generate epoch competing coins. 
+pub fn create_epoch_coins(eta: pallas::Base, owned: &Vec<OwnCoin>, epoch: u64, slot: u64) -> Vec<Vec<LeadCoin>> {
+    info!("Creating coins for epoch: {}", epoch);
+
+    // Retrieve previous epoch competing coins frequency
+    let frequency = get_frequency().with_precision(RADIX_BITS).value();
+    info!("Previous epoch frequency: {}", frequency);
+    
+    // Generating sigmas
+    let total_stake = total_stake(epoch, slot); // only used for fine tunning
+    info!("Node total stake: {}", total_stake);    
+    let one: Float10 = Float10::from_str_native("1").unwrap().with_precision(RADIX_BITS).value();
+    let two: Float10 = Float10::from_str_native("2").unwrap().with_precision(RADIX_BITS).value();
+    let field_p = Float10::from_str_native(P).unwrap().with_precision(RADIX_BITS).value();
+    let total_sigma = Float10::try_from(total_stake).unwrap().with_precision(RADIX_BITS).value();
+    let x = one - frequency;
+    info!("x: {}", x);
+    let c = x.ln();
+    info!("c: {}", c);
+    let sigma1_fbig = c.clone() / total_sigma.clone() * field_p.clone();
+    info!("sigma1: {}", sigma1_fbig);
+    let sigma1: pallas::Base = fbig2base(sigma1_fbig);
+    info!("sigma1 base: {:?}", sigma1);
+    let sigma2_fbig = (c.clone() / total_sigma.clone()).powf(two.clone()) * (field_p.clone() / two.clone());
+    info!("sigma2: {}", sigma2_fbig);
+    let sigma2: pallas::Base = fbig2base(sigma2_fbig);
+    info!("sigma2 base: {:?}", sigma2);
+    
+    create_coins(eta, owned, sigma1, sigma2)  
+}
+
+/// Generate coins for provided sigmas.
+/// Note: the strategy here is single competing coin per slot.
+fn create_coins(eta: pallas::Base, owned: &Vec<OwnCoin>, sigma1: pallas::Base, sigma2: pallas::Base) -> Vec<Vec<LeadCoin>> {
+    let mut rng = thread_rng();
+    let mut seeds: Vec<u64> = vec![];
+    for _i in 0..EPOCH_LENGTH {
+        let rho: u64 = rng.gen();
+        seeds.push(rho);
+    }    
+    let (sks, root_sks, path_sks) = create_coins_sks();
+    
+    // Leadcoins matrix were each row represents a slot and contains its competing coins.
+    let mut coins: Vec<Vec<LeadCoin>> = vec![];
+    for i in 0..EPOCH_LENGTH {
+        let index = i as usize;
+        // Use existing stake
+        if !owned.is_empty() {
+            let mut slot_coins = vec![];
+            for elem in owned {
+                let coin = create_leadcoin(
+                    eta,
+                    sigma1,
+                    sigma2,
+                    elem.note.value,
+                    index,
+                    root_sks[index],
+                    path_sks[index],
+                    seeds[index],
+                    sks[index],
+                );
+                slot_coins.push(coin);
+            }
+            coins.push(slot_coins);
+            continue
+        }
+        
+        // Compete with zero stake
+        let coin = create_leadcoin(
+            eta,
+            sigma1,
+            sigma2,
+            LOTTERY_HEAD_START,
+            index,
+            root_sks[index],
+            path_sks[index],
+            seeds[index],
+            sks[index],
+        );
+        coins.push(vec![coin]);
+    }
+    
+    coins
+}
+
+/// Generate epoch coins secret keys.
+/// First slot coin secret key is sampled at random, 
+/// while the secret keys of the rest slots derive from previous slot secret.
+/// Clarification:
+///     sk[0] -> random,
+///     sk[1] -> derive_function(sk[0]),
+///     ...
+///     sk[n] -> derive_function(sk[n-1]),
+fn create_coins_sks() -> (Vec<SecretKey>, Vec<MerkleNode>, Vec<[MerkleNode; MERKLE_DEPTH_ORCHARD]>) {
+    let mut rng = thread_rng();
+    let mut tree = BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(EPOCH_LENGTH as usize);
+    let mut sks: Vec<SecretKey> = vec![];
+    let mut root_sks: Vec<MerkleNode> = vec![];
+    let mut path_sks: Vec<[MerkleNode; MERKLE_DEPTH_ORCHARD]> = vec![];
+    let mut prev_sk_base: pallas::Base = pallas::Base::one();
+    for _i in 0..EPOCH_LENGTH {
+        let base: pallas::Point = if _i == 0 {
+            pedersen_commitment_u64(1, pallas::Scalar::random(&mut rng))
+        } else {
+            pedersen_commitment_u64(1, mod_r_p(prev_sk_base))
+        };
+        let coord = base.to_affine().coordinates().unwrap();
+        let sk_x = *coord.x();
+        let sk_y = *coord.y();
+        let sk_coord_ar = [sk_x, sk_y];
+        let sk_base: pallas::Base = poseidon::Hash::<_, poseidon::P128Pow5T3, poseidon::ConstantLength<2>, 3, 2>::init().hash(sk_coord_ar);
+        sks.push(SecretKey::from(sk_base));
+        prev_sk_base = sk_base;
+        let sk_bytes = sk_base.to_repr();
+        let node = MerkleNode::from_bytes(sk_bytes).unwrap();
+        tree.append(&node.clone());
+        let leaf_position = tree.witness();
+        let root = tree.root(0).unwrap();
+        let path = tree.authentication_path(leaf_position.unwrap(), &root).unwrap();
+        root_sks.push(root);
+        path_sks.push(path.as_slice().try_into().unwrap());
+    }
+    (sks, root_sks, path_sks)
+}
+
+/// Generate lead coin for provided sigmas and secret keys.
+fn create_leadcoin(
+    eta: pallas::Base,
+    sigma1: pallas::Base,
+    sigma2: pallas::Base,
+    value: u64,
+    i: usize,
+    c_root_sk: MerkleNode,
+    c_path_sk: [MerkleNode; MERKLE_DEPTH_ORCHARD],
+    seed: u64,
+    sk: SecretKey,
+) -> LeadCoin {
+    // keypair
+    let keypair: Keypair = Keypair::new(sk);
+    //random commitment blinding values
+    let mut rng = thread_rng();
+    let one = pallas::Base::one();
+    let c_cm1_blind: DrkValueBlind = pallas::Scalar::random(&mut rng);
+    let c_cm2_blind: DrkValueBlind = pallas::Scalar::random(&mut rng);
+    let mut tree_cm = BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(EPOCH_LENGTH as usize);
+    let c_v = pallas::Base::from(value);
+    // coin relative slot index in the epoch
+    let c_sl = pallas::Base::from(u64::try_from(i).unwrap());
+    //
+    //let's assume it's sl for simplicity
+    let c_tau = pallas::Base::from(u64::try_from(i).unwrap());
+    //
+
+    //let coin_pk_msg = [c_tau, c_root_sk.inner()];
+    //let c_pk: pallas::Base = poseidon::Hash::<_, poseidon::P128Pow5T3, poseidon::ConstantLength<2>, 3, 2>::init().hash(coin_pk_msg);
+    let c_pk: pallas::Point = keypair.public.0;
+    let c_pk_coord = c_pk.to_affine().coordinates().unwrap();
+    let c_pk_x = c_pk_coord.x();
+    let c_pk_y = c_pk_coord.y();
+
+    let c_seed = pallas::Base::from(seed);
+    let sn_msg = [c_seed, c_root_sk.inner()];
+    let c_sn: pallas::Base =
+        poseidon::Hash::<_, poseidon::P128Pow5T3, poseidon::ConstantLength<2>, 3, 2>::init()
+            .hash(sn_msg);
+
+    let coin_commit_msg_input =
+        [pallas::Base::from(PRF_NULLIFIER_PREFIX), *c_pk_x, *c_pk_y, c_v, c_seed, one];
+    let coin_commit_msg: pallas::Base =
+        poseidon::Hash::<_, poseidon::P128Pow5T3, poseidon::ConstantLength<6>, 3, 2>::init()
+            .hash(coin_commit_msg_input);
+    let c_cm: pallas::Point = pedersen_commitment_base(coin_commit_msg, c_cm1_blind);
+    let c_cm_coordinates = c_cm.to_affine().coordinates().unwrap();
+    let c_cm_base: pallas::Base = c_cm_coordinates.x() * c_cm_coordinates.y();
+    let c_cm_node = MerkleNode::from(c_cm_base);
+    tree_cm.append(&c_cm_node.clone());
+    let leaf_position = tree_cm.witness();
+    let c_root_cm = tree_cm.root(0).unwrap();
+    let c_cm_path = tree_cm.authentication_path(leaf_position.unwrap(), &c_root_cm).unwrap();
+
+    let coin_nonce2_msg = [c_seed, c_root_sk.inner()];
+    let c_seed2: pallas::Base =
+        poseidon::Hash::<_, poseidon::P128Pow5T3, poseidon::ConstantLength<2>, 3, 2>::init()
+            .hash(coin_nonce2_msg);
+
+    let coin2_commit_msg_input =
+        [pallas::Base::from(PRF_NULLIFIER_PREFIX), *c_pk_x, *c_pk_y, c_v, c_seed2, one];
+    let coin2_commit_msg: pallas::Base =
+        poseidon::Hash::<_, poseidon::P128Pow5T3, poseidon::ConstantLength<6>, 3, 2>::init()
+            .hash(coin2_commit_msg_input);
+    let c_cm2 = pedersen_commitment_base(coin2_commit_msg, c_cm2_blind);
+
+    // election seeds
+    let (y_mu, rho_mu) = create_coins_election_seeds(eta, c_sl);
+    let coin = LeadCoin {
+        value: Some(value),
+        cm: Some(c_cm),
+        cm2: Some(c_cm2),
+        idx: u32::try_from(i).unwrap(), //TODO should be abs slot
+        sl: Some(c_sl),
+        tau: Some(c_tau),
+        nonce: Some(c_seed),
+        nonce_cm: Some(c_seed2),
+        sn: Some(c_sn),
+        keypair: Some(keypair),
+        root_cm: Some(mod_r_p(c_root_cm.inner())),
+        root_sk: Some(c_root_sk.inner()),
+        path: Some(c_cm_path.as_slice().try_into().unwrap()),
+        path_sk: Some(c_path_sk),
+        c1_blind: Some(c_cm1_blind),
+        c2_blind: Some(c_cm2_blind),
+        y_mu: Some(y_mu),
+        rho_mu: Some(rho_mu),
+        sigma1: Some(sigma1),
+        sigma2: Some(sigma2),
+    };
+    coin
+}
+
+fn create_coins_election_seeds(eta: pallas::Base, slot: pallas::Base) -> (pallas::Base, pallas::Base) {
+    let election_seed_nonce: pallas::Base = pallas::Base::from(3);
+    let election_seed_lead: pallas::Base = pallas::Base::from(22);
+
+    // mu_rho
+    let nonce_mu_msg = [election_seed_nonce, eta, slot];
+    let nonce_mu: pallas::Base =
+        poseidon::Hash::<_, poseidon::P128Pow5T3, poseidon::ConstantLength<3>, 3, 2>::init()
+            .hash(nonce_mu_msg);
+    // mu_y
+    let lead_mu_msg = [election_seed_lead, eta, slot];
+    let lead_mu: pallas::Base =
+        poseidon::Hash::<_, poseidon::P128Pow5T3, poseidon::ConstantLength<3>, 3, 2>::init()
+            .hash(lead_mu_msg);
+    (lead_mu, nonce_mu)
+}
+
+/// Check that the provided participant/stakeholder coins win the slot lottery.
+/// If the stakeholder have multiple competing winning coins, only the highest value coin is selected,
+/// since the stakeholder can't give more than a proof per block(slot).
+/// * `slot` - slot relative index
+/// * `epoch_coins` - stakeholders epoch coins
+/// Returns: (check: bool, idx: usize) where idx is the winning coin index
+pub fn is_leader(slot: u64, epoch_coins: &Vec<Vec<LeadCoin>>) -> (bool, usize) {
+    let slot_usize = slot as usize;
+    info!("slot: {}, coins len: {}", slot, epoch_coins.len());
+    assert!(slot_usize < epoch_coins.len());
+    let competing_coins: &Vec<LeadCoin> = &epoch_coins[slot_usize];
+    let mut won = false;
+    let mut highest_stake = 0;
+    let mut highest_stake_idx: usize = 0;
+    for (winning_idx, coin) in competing_coins.iter().enumerate() {
+        let y_exp = [coin.root_sk.unwrap(), coin.nonce.unwrap()];
+        let y_exp_hash: pallas::Base =
+            poseidon::Hash::<_, poseidon::P128Pow5T3, poseidon::ConstantLength<2>, 3, 2>::init(
+            )
+            .hash(y_exp);
+        let y_coordinates = pedersen_commitment_base(coin.y_mu.unwrap(), mod_r_p(y_exp_hash))
+            .to_affine()
+            .coordinates()
+            .unwrap();
+        //
+        let y_x: pallas::Base = *y_coordinates.x();
+        let y_y: pallas::Base = *y_coordinates.y();
+        let y_coord_arr = [y_x, y_y];
+        let y: pallas::Base =
+            poseidon::Hash::<_, poseidon::P128Pow5T3, poseidon::ConstantLength<2>, 3, 2>::init(
+            )
+            .hash(y_coord_arr);
+        //
+        let val_base = pallas::Base::from(coin.value.unwrap());
+        let target_base =
+            coin.sigma1.unwrap() * val_base + coin.sigma2.unwrap() * val_base * val_base;
+        info!("y: {:?}", y);
+        info!("T: {:?}", target_base);
+        if y >= target_base {
+            continue
+        }
+        
+        won = true;        
+        if coin.value.unwrap() > highest_stake {
+            highest_stake = coin.value.unwrap();
+            highest_stake_idx = winning_idx;
+        }
+    }
+    
+    (won, highest_stake_idx)
+}

+ 71 - 0
script/research/crypsinous_playground/src/main.rs

@@ -0,0 +1,71 @@
+use log::{error, info};
+use simplelog::{ColorChoice, Config, LevelFilter, TermLogger, TerminalMode};
+
+use darkfi::{
+    crypto::{
+        lead_proof,
+        proof::{ProvingKey, VerifyingKey},
+    },
+    zk::circuit::LeadContract,
+};
+
+mod coins;
+mod utils;
+
+/// The porpuse of this script is to simulate a staker actions through an epoch.
+/// Main focus is the crypsinous lottery mechanism and the leader proof creation and validation.
+/// Other flows that happen through a slot, like broadcasting blocks or syncing are out of scope.
+fn main() {
+    // Initiate logger
+    TermLogger::init(
+        LevelFilter::Debug,
+        Config::default(),
+        TerminalMode::Mixed,
+        ColorChoice::Auto,
+    )
+    .unwrap();
+    
+    // Generating leader proof keys    
+    let k: u32 = 13; // Proof rows number
+    info!("Generating proof keys with k: {}", k);
+    let proving_key = ProvingKey::build(k, &LeadContract::default());
+    let verifying_key = VerifyingKey::build(k, &LeadContract::default());
+    
+    // Simulating an epoch with 10 slots
+    let epoch = 0;
+    let slot = 0;
+    info!("Epoch {} started!", epoch);
+    
+    // Generating epoch coins
+    // TODO: Retrieve own coins
+    let owned = vec![];
+    // TODO: Retrieve previous lead proof
+    let eta = utils::get_eta(blake3::hash(b"Erebus"));    
+    let epoch_coins = coins::create_epoch_coins(eta, &owned, epoch, slot);
+    info!("Generated epoch_coins: {}", epoch_coins.len());    
+    for slot in 0..10 {
+        // Checking if slot leader
+        info!("Slot {} started!", slot);
+        let (won, idx) = coins::is_leader(slot, &epoch_coins);
+        info!("Lottery outcome: {}", won);
+        if !won {
+            continue
+        }
+        // TODO: Generate rewards transaction
+        info!("Winning coin index: {}", idx);
+        // Generating leader proof
+        let coin = epoch_coins[slot as usize][idx];
+        let proof = lead_proof::create_lead_proof(&proving_key, coin);
+        if proof.is_err() {
+            error!("Error during leader proof creation: {}", proof.err().unwrap());
+            continue
+        }
+        //Verifying generated proof against winning coin public inputs
+        info!("Leader proof generated successfully, veryfing...");
+        match lead_proof::verify_lead_proof(&verifying_key, &proof.unwrap(), &coin.public_inputs()) {
+            Ok(_) => info!("Proof veryfied succsessfully!"),
+            Err(e) => error!("Error during leader proof verification: {}", e),
+        }
+        
+    }    
+}

+ 48 - 0
script/research/crypsinous_playground/src/utils.rs

@@ -0,0 +1,48 @@
+use dashu::{
+    float::{round::mode::Zero, FBig},
+    integer::{IBig, Sign},
+};
+use log::{debug, info};
+use pasta_curves::{group::ff::PrimeField, pallas};
+
+pub type Float10 = FBig<Zero, 10>;
+
+pub fn fbig2ibig(f: Float10) -> IBig {
+    let rad = IBig::try_from(10).unwrap();
+    let sig = f.repr().significand();
+    let exp = f.repr().exponent();
+    let val: IBig = if exp >= 0 {
+        sig.clone() * rad.pow(exp as usize)
+    } else {
+        sig.clone()
+    };
+    debug!("fbig2ibig (f): {}", f);
+    debug!("fbig2ibig (i): {}", val);
+    val
+}
+
+pub fn fbig2base(f: Float10) -> pallas::Base {
+    info!("fbig -> base (f): {}", f);
+    let val: IBig = fbig2ibig(f);
+    let (sign, word) = val.as_sign_words();
+    //TODO (res) set pallas base sign, i.e sigma1 is negative.
+    let mut words: [u64; 4] = [0, 0, 0, 0];
+    for i in 0..word.len() {
+        words[i] = word[i];
+    }
+    let base = match sign {
+        Sign::Positive => pallas::Base::from_raw(words),
+        Sign::Negative => pallas::Base::from_raw(words).neg(),
+    };
+    base
+}
+
+/// Extract leader selection lottery randomness(eta)
+/// using the hash of the previous lead proof, converted to pallas base.
+pub fn get_eta(proof_tx_hash: blake3::Hash) -> pallas::Base {
+    let mut bytes: [u8; 32] = *proof_tx_hash.as_bytes();
+    // read first 254 bits
+    bytes[30] = 0;
+    bytes[31] = 0;
+    pallas::Base::from_repr(bytes).unwrap()
+}