diff --git a/ChangeLog b/ChangeLog index 747f360bc..937f40cca 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,4 +2,5 @@ * wasm: implemented basic db functions needed for persistent state storage * wasm: merge branch and implement prototype working smart contracts. * doc: improved formatting and reorganized the structure. - + * consensus: auto tuning lottery for single leader per slot. + * consensus: change y,rho to PRF rather than commitment to be able to constrain it's pallas::Base public values. diff --git a/proof/lead.zk b/proof/lead.zk index a49ad9470..c57e6c062 100644 --- a/proof/lead.zk +++ b/proof/lead.zk @@ -16,8 +16,8 @@ contract "Lead" { Scalar c1_opening, Base value, Scalar c2_opening, - Scalar rho_opening, - Scalar y_opening, + Base mu_rho, + Base mu_y, Base sigma1, Base sigma2, } @@ -30,6 +30,14 @@ circuit "Lead" { PREFIX_CM = witness_base(4); PREFIX_PK = witness_base(5); PREFIX_SN = witness_base(6); + + # constrain public values + + # sigma1 + #constrain_instance(sigma1); + # sigma2 + #constrain_instance(sigma2); + # coin (1) pk pk = poseidon_hash(PREFIX_PK, c1_sk_root, c1_tau, ZERO); constrain_instance(pk); @@ -66,27 +74,18 @@ circuit "Lead" { # lottery seed seed = poseidon_hash(PREFIX_SEED, c1_sk_root, c1_rho, ZERO); # y - y_v = ec_mul_base(seed, NULLIFIER_K); - y_r = ec_mul(y_opening, VALUE_COMMIT_RANDOM); - y = ec_add(y_v, y_r); - y_x = ec_get_x(y); - y_y = ec_get_y(y); - y_hash = poseidon_hash(y_x, y_y); - constrain_instance(y_x); - constrain_instance(y_y); + y = poseidon_hash(seed, mu_y); + constrain_instance(mu_y); + constrain_instance(y); # rho - rho_v = ec_mul_base(seed, NULLIFIER_K); - rho_r = ec_mul(rho_opening, VALUE_COMMIT_RANDOM); - rho = ec_add(rho_v, rho_r); - rho_x = ec_get_x(rho); - rho_y = ec_get_y(rho); - constrain_instance(rho_x); - constrain_instance(rho_y); + rho = poseidon_hash(seed, mu_rho); + constrain_instance(mu_rho); + constrain_instance(rho); # target term1 = base_mul(sigma1, value); term2_1 = base_mul(sigma2, value); term2 = base_mul(term2_1, value); target = base_add(term1, term2); #lottery - less_than_loose(y_hash, target); + less_than_loose(y, target); } diff --git a/src/consensus/leadcoin.rs b/src/consensus/leadcoin.rs index decc9ba41..e8e68d861 100644 --- a/src/consensus/leadcoin.rs +++ b/src/consensus/leadcoin.rs @@ -100,7 +100,7 @@ impl LeadCoin { eta: pallas::Base, // Stake value value: u64, - // Slot index in the epoch + // Slot absolute index slot_index: u64, // coin1 sk coin1_sk: pallas::Base, @@ -184,7 +184,8 @@ impl LeadCoin { } /// Derive election seeds from given parameters - fn election_seeds(eta: pallas::Base, slot: pallas::Base) -> (pallas::Base, pallas::Base) { + pub fn election_seeds(eta: pallas::Base, slot: pallas::Base) -> (pallas::Base, pallas::Base) { + info!("LeadCoin::election_seeds(): eta: {:?}, slot: {:?}", eta, slot); let election_seed_nonce = pallas::Base::from(3); let election_seed_lead = pallas::Base::from(22); @@ -223,11 +224,17 @@ impl LeadCoin { ]; let seed = poseidon_hash(seed_msg); // y - let y = pedersen_commitment_base(seed, mod_r_p(self.y_mu)); - let y_coords = y.to_affine().coordinates().unwrap(); + let y_msg = [ + seed, + self.y_mu, + ]; + let y = poseidon_hash(y_msg); // rho - let rho = pedersen_commitment_base(seed, mod_r_p(self.rho_mu)); - let rho_coord = rho.to_affine().coordinates().unwrap(); + let rho_msg = [ + seed, + self.rho_mu, + ]; + let rho = poseidon_hash(rho_msg); vec![ pk, *c1_cm.x(), @@ -237,10 +244,10 @@ impl LeadCoin { self.coin1_commitment_root.inner(), self.coin1_sk_root.inner(), self.sn(), - *y_coords.x(), - *y_coords.y(), - *rho_coord.x(), - *rho_coord.y(), + self.y_mu, + y, + self.rho_mu, + rho, ] } @@ -349,8 +356,8 @@ impl LeadCoin { Witness::Scalar(Value::known(self.coin1_blind)), Witness::Base(Value::known(pallas::Base::from(self.value))), Witness::Scalar(Value::known(self.coin2_blind)), - Witness::Scalar(Value::known(mod_r_p(self.rho_mu))), - Witness::Scalar(Value::known(mod_r_p(self.y_mu))), + Witness::Base(Value::known(self.rho_mu)), + Witness::Base(Value::known(self.y_mu)), Witness::Base(Value::known(sigma1)), Witness::Base(Value::known(sigma2)), ]; diff --git a/src/consensus/state.rs b/src/consensus/state.rs index 459ab2adb..3cd65e7c0 100644 --- a/src/consensus/state.rs +++ b/src/consensus/state.rs @@ -64,6 +64,8 @@ use crate::{ const PI_NULLIFIER_INDEX: usize = 7; const PI_COMMITMENT_X_INDEX: usize = 1; const PI_COMMITMENT_Y_INDEX: usize = 2; +const PI_MU_Y_INDEX: usize = 8; +const PI_MU_RHO_INDEX: usize = 10; /// This struct represents the information required by the consensus algorithm #[derive(Debug)] pub struct ConsensusState { @@ -428,9 +430,7 @@ impl ValidatorState { /// `epoch: absolute epoch index /// `slot: relative slot index fn sigmas(&mut self, epoch: u64, slot: u64) -> (pallas::Base, pallas::Base) { - //let f = Self::leadership_probability_with_all_stake().with_precision(RADIX_BITS).value(); let f = self.win_prob_with_full_stake(); - info!("Consensus: f: {}", f); // Generate sigmas let total_stake = self.total_stake_plus(epoch, slot); // Only used for fine-tuning @@ -466,6 +466,7 @@ impl ValidatorState { /// Generate coins for provided sigmas. /// NOTE: The strategy here is having a single competing coin per slot. async fn create_coins(&self, eta: pallas::Base) -> Result>> { + let slot = self.current_slot(); let mut rng = thread_rng(); let mut seeds: Vec = Vec::with_capacity(EPOCH_LENGTH); @@ -488,10 +489,10 @@ impl ValidatorState { let coin = LeadCoin::new( eta, LOTTERY_HEAD_START, // TODO: TESTNET: Why is this constant being used? - i as u64, + slot as u64, epoch_secrets.secret_keys[i].inner(), epoch_secrets.merkle_roots[i], - i, //TODO same as idx now for simplicity. + i, epoch_secrets.merkle_paths[i], seeds[i], epoch_secrets.secret_keys[i], @@ -603,6 +604,7 @@ impl ValidatorState { let p = self.f_dif(); let i = self.f_int(); let d = self.f_der(); + info!("Consensus::win_prob_with_full_stake(): Kp: {}", self.Kp.clone()); while f <= zero || f >= one { f = self.Kp.clone() * (p.clone() + @@ -613,6 +615,7 @@ impl ValidatorState { } else if f <= zero { self.Kp += step.clone(); } + info!("Consensus::win_prob_with_full_stake(): f: {}", f); } f } @@ -630,7 +633,7 @@ impl ValidatorState { // Stakeholder's epoch coins let coins = &self.consensus.coins; - info!("consensus::is_leader(): slot: {}, coins len: {}", slot, coins.len()); + info!("Consensus::is_leader(): slot: {}, coins len: {}", slot, coins.len()); assert!((slot as usize) < coins.len()); let competing_coins = &coins[slot as usize]; @@ -766,6 +769,9 @@ impl ValidatorState { /// it extends. If the proposal extends the canonical blockchain, a new fork chain is created. pub async fn receive_proposal(&mut self, proposal: &BlockProposal) -> Result<()> { let current = self.current_slot(); + + let eta = self.consensus.epoch_eta; + let (mu_y, mu_rho) = LeadCoin::election_seeds(eta, pallas::Base::from(current)); // Node hasn't started participating match self.consensus.participating { Some(start) => { @@ -829,6 +835,21 @@ impl ValidatorState { }; info!("receive_proposal(): Leader proof verified successfully!"); + // verify proposal public values + // mu values + // y + let prop_mu_y = md.public_inputs[PI_MU_Y_INDEX]; + if mu_y != prop_mu_y { + error!("failed to verify mu_y: {:?}, proposed: {:?}", mu_y, prop_mu_y); + return Err(Error::ProposalPublicValuesMismatched) + } + // rho + let prop_mu_rho = md.public_inputs[PI_MU_RHO_INDEX]; + if mu_rho != prop_mu_rho { + error!("failed to verify mu_rho: {:?}, proposed: {:?}", mu_rho, prop_mu_rho); + return Err(Error::ProposalPublicValuesMismatched) + } + // Verify proposal public inputs let prop_sn = md.public_inputs[PI_NULLIFIER_INDEX]; for sn in &self.leaders_nullifiers { diff --git a/src/consensus/utils.rs b/src/consensus/utils.rs index 3149ab930..a50c5b7fd 100644 --- a/src/consensus/utils.rs +++ b/src/consensus/utils.rs @@ -18,7 +18,7 @@ use super::Float10; use dashu::integer::{IBig, Sign}; -use log::{debug, info}; +use log::{debug}; use pasta_curves::pallas; //use pasta_curves::{group::ff::PrimeField}; //use dashu::integer::{UBig}; @@ -49,13 +49,11 @@ pub fn base2ibig(base: pallas::Base) -> IBig { } */ pub fn fbig2base(f: Float10) -> pallas::Base { - info!("fbig -> base (f): {}", f); + debug!("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]; words[..word.len()].copy_from_slice(word); - match sign { Sign::Positive => pallas::Base::from_raw(words), Sign::Negative => pallas::Base::from_raw(words).neg(), diff --git a/src/error.rs b/src/error.rs index 03d19954b..a1b4fa21d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -256,6 +256,8 @@ pub enum Error { #[error("unable to verify transfer transaction")] TransferTxVerification, + #[error("unable to verify proposed mu values")] + ProposalPublicValuesMismatched, // =============== // Database errors // ===============