Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed batcher/aligned/test_files/mina/protocol_state.proof
Binary file not shown.
Binary file removed batcher/aligned/test_files/mina/protocol_state.pub
Binary file not shown.
Binary file not shown.
Binary file not shown.
4 changes: 2 additions & 2 deletions operator/mina/lib/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion operator/mina/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ bs58 = "0.5.1"
lazy_static = "1.5.0"
blake2 = "0.10.6"
once_cell = "1.19.0"
mina_bridge_core = { git = "https://github.com/lambdaclass/mina_bridge", branch = "relative_finalization" }
mina_bridge_core = { git = "https://github.com/lambdaclass/mina_bridge", branch = "new_account_proof" }
bincode = "1.3.3"

[patch.crates-io]
Expand Down
155 changes: 141 additions & 14 deletions operator/mina/lib/src/consensus_state.rs
Original file line number Diff line number Diff line change
@@ -1,47 +1,174 @@
use blake2::{Blake2b512, Digest};
use kimchi::o1_utils::FieldHelpers;
use mina_p2p_messages::{hash::MinaHash, v2::MinaStateProtocolStateValueStableV2};
use mina_p2p_messages::{
hash::MinaHash,
v2::{
ConsensusProofOfStakeDataConsensusStateValueStableV2 as MinaConsensusState,
MinaStateProtocolStateValueStableV2 as MinaProtocolState,
},
};
use std::cmp::{max, min, Ordering};

#[derive(PartialEq)]
pub enum LongerChainResult {
const GRACE_PERIOD_END: u32 = 1440;
const SUB_WINDOWS_PER_WINDOW: u32 = 11;
const SLOTS_PER_SUB_WINDOW: u32 = 7;

#[derive(Debug, PartialEq)]
pub enum ChainResult {
Bridge,
Candidate,
}

pub fn select_longer_chain(
candidate: &MinaStateProtocolStateValueStableV2,
tip: &MinaStateProtocolStateValueStableV2,
) -> LongerChainResult {
pub fn select_secure_chain(
candidate: &MinaProtocolState,
tip: &MinaProtocolState,
) -> Result<ChainResult, String> {
if is_short_range(candidate, tip)? {
Ok(select_longer_chain(candidate, tip))
} else {
let tip_density = relative_min_window_density(candidate, tip);
let candidate_density = relative_min_window_density(candidate, tip);
Ok(match candidate_density.cmp(&tip_density) {
Ordering::Less => ChainResult::Bridge,
Ordering::Equal => select_longer_chain(candidate, tip),
Ordering::Greater => ChainResult::Candidate,
})
}
}

fn select_longer_chain(candidate: &MinaProtocolState, tip: &MinaProtocolState) -> ChainResult {
let candidate_block_height = &candidate.body.consensus_state.blockchain_length.as_u32();
let tip_block_height = &tip.body.consensus_state.blockchain_length.as_u32();

if candidate_block_height > tip_block_height {
return LongerChainResult::Candidate;
return ChainResult::Candidate;
}
// tiebreak logic
else if candidate_block_height == tip_block_height {
// compare last VRF digests lexicographically
if hash_last_vrf(candidate) > hash_last_vrf(tip) {
return LongerChainResult::Candidate;
return ChainResult::Candidate;
} else if hash_last_vrf(candidate) == hash_last_vrf(tip) {
// compare consensus state hashes lexicographically
if hash_state(candidate) > hash_state(tip) {
return LongerChainResult::Candidate;
return ChainResult::Candidate;
}
}
}

LongerChainResult::Bridge
ChainResult::Bridge
}

/// Returns true if the fork is short-range, else the fork is long-range.
fn is_short_range(candidate: &MinaProtocolState, tip: &MinaProtocolState) -> Result<bool, String> {
// TODO(xqft): verify constants are correct
if tip.body.constants != candidate.body.constants {
return Err("Protocol constants on candidate and tip state are not equal".to_string());
}
let slots_per_epoch = tip.body.constants.slots_per_epoch.as_u32();

let candidate = &candidate.body.consensus_state;
let tip = &tip.body.consensus_state;

let check = |s1: &MinaConsensusState, s2: &MinaConsensusState| {
let s2_epoch_slot = s2.global_slot() % slots_per_epoch;
if s1.epoch_count.as_u32() == s2.epoch_count.as_u32() + 1
&& s2_epoch_slot >= slots_per_epoch * 2 / 3
{
s1.staking_epoch_data.lock_checkpoint == s2.next_epoch_data.lock_checkpoint
} else {
false
}
};

Ok(if candidate.epoch_count == tip.epoch_count {
candidate.staking_epoch_data.lock_checkpoint == tip.staking_epoch_data.lock_checkpoint
} else {
check(candidate, tip) || check(tip, candidate)
})
}

fn relative_min_window_density(candidate: &MinaProtocolState, tip: &MinaProtocolState) -> u32 {
let candidate = &candidate.body.consensus_state;
let tip = &tip.body.consensus_state;

let max_slot = max(candidate.global_slot(), tip.global_slot());

if max_slot < GRACE_PERIOD_END {
return candidate.min_window_density.as_u32();
}

let projected_window = {
let shift_count = (max_slot - candidate.global_slot() - 1).clamp(0, SUB_WINDOWS_PER_WINDOW);
let mut projected_window: Vec<_> = candidate
.sub_window_densities
.iter()
.map(|d| d.as_u32())
.collect();

let mut i = relative_sub_window(candidate);
for _ in 0..shift_count {
i = (i + 1) % SUB_WINDOWS_PER_WINDOW;
projected_window[i as usize] = 0
}

projected_window
};

let projected_window_density = projected_window.iter().sum();

min(
candidate.min_window_density.as_u32(),
projected_window_density,
)
}

fn hash_last_vrf(chain: &MinaStateProtocolStateValueStableV2) -> String {
fn relative_sub_window(state: &MinaConsensusState) -> u32 {
(state.global_slot() / SLOTS_PER_SUB_WINDOW) % SUB_WINDOWS_PER_WINDOW
}

fn hash_last_vrf(chain: &MinaProtocolState) -> String {
let mut hasher = Blake2b512::new();
hasher.update(chain.body.consensus_state.last_vrf_output.as_slice());
let digest = hasher.finalize().to_vec();

hex::encode(&digest)
hex::encode(digest)
}

fn hash_state(chain: &MinaStateProtocolStateValueStableV2) -> String {
fn hash_state(chain: &MinaProtocolState) -> String {
MinaHash::hash(chain).to_hex()
}

#[cfg(test)]
mod test {
use mina_bridge_core::proof::state_proof::MinaStateProof;

use super::*;

const PROOF_BYTES: &[u8] =
include_bytes!("../../../../scripts/test_files/mina/mina_state.proof");

#[test]
fn new_mina_state_passes_consensus_checks() {
let valid_proof: MinaStateProof = bincode::deserialize(PROOF_BYTES).unwrap();
let old_tip = valid_proof.bridge_tip_state;
let new_tip = valid_proof.candidate_chain_states.last().unwrap();

assert_eq!(
select_secure_chain(new_tip, &old_tip).unwrap(),
ChainResult::Candidate
);
}

#[test]
fn old_mina_state_fails_consensus_checks() {
let valid_proof: MinaStateProof = bincode::deserialize(PROOF_BYTES).unwrap();
let old_tip = valid_proof.bridge_tip_state;
let new_tip = valid_proof.candidate_chain_states.last().unwrap();

assert_eq!(
select_secure_chain(&old_tip, new_tip).unwrap(),
ChainResult::Bridge
);
}
}
62 changes: 23 additions & 39 deletions operator/mina/lib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
/// Consensus chain selection algorithms. The [`official specification`] was taken as a reference.
///
/// [`official specification`]: https://github.com/MinaProtocol/mina/blob/develop/docs/specs/consensus/README.md
mod consensus_state;
mod verifier_index;

use mina_bridge_core::proof::state_proof::{MinaStateProof, MinaStatePubInputs};

use ark_ec::short_weierstrass_jacobian::GroupAffine;
use consensus_state::{select_longer_chain, LongerChainResult};
use consensus_state::{select_secure_chain, ChainResult};
use kimchi::mina_curves::pasta::{Fp, PallasParameters};
use kimchi::verifier_index::VerifierIndex;
use lazy_static::lazy_static;
Expand All @@ -13,8 +17,6 @@ use mina_tree::proofs::verification::verify_block;
use mina_tree::verifier::get_srs;
use verifier_index::deserialize_blockchain_vk;

mod verifier_index;

lazy_static! {
static ref VERIFIER_INDEX: VerifierIndex<GroupAffine<PallasParameters>> =
deserialize_blockchain_vk().unwrap();
Expand Down Expand Up @@ -62,10 +64,16 @@ pub extern "C" fn verify_mina_state_ffi(
let srs = get_srs::<Fp>();
let srs = srs.lock().unwrap();

// Consensus check: Short fork rule
let longer_chain = select_longer_chain(&candidate_tip_state, &bridge_tip_state);
if longer_chain == LongerChainResult::Bridge {
eprintln!("Failed consensus checks for candidate tip state against bridge's tip");
// Consensus checks
let secure_chain = match select_secure_chain(&candidate_tip_state, &bridge_tip_state) {
Ok(res) => res,
Err(err) => {
eprintln!("Failed consensus checks for candidate tip: {err}");
return false;
}
};
if secure_chain == ChainResult::Bridge {
eprintln!("Failed consensus checks for candidate tip: bridge's tip is more secure");
return false;
}

Expand Down Expand Up @@ -187,17 +195,14 @@ mod test {
use super::*;

const PROOF_BYTES: &[u8] =
include_bytes!("../../../../batcher/aligned/test_files/mina/protocol_state.proof");
include_bytes!("../../../../scripts/test_files/mina/mina_state.proof");
const PUB_INPUT_BYTES: &[u8] =
include_bytes!("../../../../batcher/aligned/test_files/mina/protocol_state.pub");
const PROTOCOL_STATE_BAD_HASH_PUB_BYTES: &[u8] =
include_bytes!("../../../../batcher/aligned/test_files/mina/protocol_state_bad_hash.pub");
const PROTOCOL_STATE_BAD_CONSENSUS_PUB_BYTES: &[u8] = include_bytes!(
"../../../../batcher/aligned/test_files/mina/protocol_state_bad_consensus.pub"
);
include_bytes!("../../../../scripts/test_files/mina/mina_state.pub");
const BAD_HASH_PUB_INPUT_BYTES: &[u8] =
include_bytes!("../../../../scripts/test_files/mina/mina_state_bad_hash.pub");

#[test]
fn protocol_state_proof_verifies() {
fn valid_mina_state_proof_verifies() {
let mut proof_buffer = [0u8; super::MAX_PROOF_SIZE];
let proof_size = PROOF_BYTES.len();
assert!(proof_size <= proof_buffer.len());
Expand All @@ -214,40 +219,19 @@ mod test {
}

#[test]
fn proof_of_protocol_state_with_bad_hash_does_not_verify() {
fn mina_state_proof_with_bad_bridge_tip_hash_does_not_verify() {
let mut proof_buffer = [0u8; super::MAX_PROOF_SIZE];
let proof_size = PROOF_BYTES.len();
assert!(proof_size <= proof_buffer.len());
proof_buffer[..proof_size].clone_from_slice(PROOF_BYTES);

let mut pub_input_buffer = [0u8; super::MAX_PUB_INPUT_SIZE];
let pub_input_size = PROTOCOL_STATE_BAD_HASH_PUB_BYTES.len();
let pub_input_size = BAD_HASH_PUB_INPUT_BYTES.len();
assert!(pub_input_size <= pub_input_buffer.len());
pub_input_buffer[..pub_input_size].clone_from_slice(PROTOCOL_STATE_BAD_HASH_PUB_BYTES);
pub_input_buffer[..pub_input_size].clone_from_slice(BAD_HASH_PUB_INPUT_BYTES);

let result =
verify_mina_state_ffi(&proof_buffer, proof_size, &pub_input_buffer, pub_input_size);
assert!(!result);
}

#[test]
fn proof_of_protocol_state_with_bad_consensus_does_not_verify() {
let mut proof_buffer = [0u8; super::MAX_PROOF_SIZE];
let proof_size = PROOF_BYTES.len();
assert!(proof_size <= proof_buffer.len());
proof_buffer[..proof_size].clone_from_slice(PROOF_BYTES);

let mut pub_input_buffer = [0u8; super::MAX_PUB_INPUT_SIZE];
let pub_input_size = PROTOCOL_STATE_BAD_CONSENSUS_PUB_BYTES.len();
assert!(pub_input_size <= pub_input_buffer.len());
pub_input_buffer[..pub_input_size].clone_from_slice(PROTOCOL_STATE_BAD_CONSENSUS_PUB_BYTES);

let result = verify_protocol_state_proof_ffi(
&proof_buffer,
proof_size,
&pub_input_buffer,
pub_input_size,
);
assert!(!result);
}
}
4 changes: 2 additions & 2 deletions operator/mina/mina_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

func TestMinaStateProofVerifies(t *testing.T) {
fmt.Println(os.Getwd())
proofFile, err := os.Open("../../batcher/aligned/test_files/mina/protocol_state.proof")
proofFile, err := os.Open("../../scripts/test_files/mina/mina_state.proof")
if err != nil {
t.Errorf("could not open mina state proof file")
}
Expand All @@ -21,7 +21,7 @@ func TestMinaStateProofVerifies(t *testing.T) {
t.Errorf("could not read bytes from mina state proof file")
}

pubInputFile, err := os.Open("../../batcher/aligned/test_files/mina/protocol_state.pub")
pubInputFile, err := os.Open("../../scripts/test_files/mina/mina_state.pub")
if err != nil {
t.Errorf("could not open mina state hash file")
}
Expand Down
4 changes: 2 additions & 2 deletions operator/mina_account/mina_account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

func TestMinaStateProofVerifies(t *testing.T) {
fmt.Println(os.Getwd())
proofFile, err := os.Open("../../batcher/aligned/test_files/mina/account_B62qrQKS9ghd91shs73TCmBJRW9GzvTJK443DPx2YbqcyoLc56g1ny9.proof")
proofFile, err := os.Open("../../scripts/test_files/mina_account/account_B62qrQKS9ghd91shs73TCmBJRW9GzvTJK443DPx2YbqcyoLc56g1ny9.proof")
if err != nil {
t.Errorf("could not open mina account proof file")
}
Expand All @@ -21,7 +21,7 @@ func TestMinaStateProofVerifies(t *testing.T) {
t.Errorf("could not read bytes from mina account proof file")
}

pubInputFile, err := os.Open("../../batcher/aligned/test_files/mina/account_B62qrQKS9ghd91shs73TCmBJRW9GzvTJK443DPx2YbqcyoLc56g1ny9.pub")
pubInputFile, err := os.Open("../../scripts/test_files/mina_account/account_B62qrQKS9ghd91shs73TCmBJRW9GzvTJK443DPx2YbqcyoLc56g1ny9.pub")
if err != nil {
t.Errorf("could not open mina account pub inputs file")
}
Expand Down
Binary file added scripts/test_files/mina/mina_state.proof
Binary file not shown.
Binary file added scripts/test_files/mina/mina_state.pub
Binary file not shown.
Binary file added scripts/test_files/mina/mina_state_bad_hash.pub
Binary file not shown.