Skip to content
2 changes: 1 addition & 1 deletion circuits/proof-builder/src/api_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ fn sort_json_value(value: &mut Value) {
}

/// Adds one length-prefixed field to the authentication digest.
fn hash_field(hasher: &mut Sha256, field: &[u8]) {
pub fn hash_field(hasher: &mut Sha256, field: &[u8]) {
hasher.update((field.len() as u64).to_be_bytes());
hasher.update(field);
}
Expand Down
1 change: 0 additions & 1 deletion crates/bitvm-gc/tests/regtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,6 @@ fn committee_pre_sign_graph(graph: &mut BitvmGcGraph, keys: &TestKeys) -> Result
}

fn committee_sign_pegin(graph: &BitvmGcGraph, keys: &TestKeys) -> Result<Transaction> {
let instance_id = graph.parameters.instance_parameters.instance_id;
let mut pub_nonces = Vec::with_capacity(keys.committee.len());
let mut sec_nonces = Vec::with_capacity(keys.committee.len());
for keypair in &keys.committee {
Expand Down
75 changes: 24 additions & 51 deletions crates/header-chain/src/header_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use bincode::Options as BincodeOptions;
use bitcoin::{
BlockHash, CompactTarget, Network, TxMerkleNode,
block::{Header, Version},
consensus::Params,
hashes::Hash,
};
use borsh::{BorshDeserialize, BorshSerialize};
Expand Down Expand Up @@ -100,10 +101,6 @@ pub const NETWORK_CONSTANTS: NetworkConstants = {
}
};

/// An epoch should be two weeks (represented as number of seconds)
/// seconds/minute * minutes/hour * hours/day * 14 days
const EXPECTED_EPOCH_TIMESPAN: u32 = 60 * 60 * 24 * 14;

/// Number of blocks per epoch
const BLOCKS_PER_EPOCH: u32 = 2016;

Expand Down Expand Up @@ -278,12 +275,12 @@ impl ChainState {
}

if !IS_REGTEST && self.block_height % BLOCKS_PER_EPOCH == BLOCKS_PER_EPOCH - 1 {
current_target_bytes = calculate_new_difficulty(
self.current_target_bits = calculate_new_difficulty(
self.epoch_start_time,
block_header.time,
self.current_target_bits,
);
self.current_target_bits = target_to_bits(&current_target_bytes);
current_target_bytes = bits_to_target(self.current_target_bits);
}
}

Expand Down Expand Up @@ -320,39 +317,18 @@ pub fn bits_to_target(bits: u32) -> [u8; 32] {
target.to_be_bytes()
}

fn target_to_bits(target: &[u8; 32]) -> u32 {
let target_u256 = U256::from_be_slice(target);
let target_bits = target_u256.bits();
let size = (263 - target_bits) / 8;
let mut compact_target = [0u8; 4];
compact_target[0] = 33 - size as u8;
compact_target[1] = target[size - 1_usize];
compact_target[2] = target[size];
compact_target[3] = target[size + 1_usize];
u32::from_be_bytes(compact_target)
}

fn calculate_new_difficulty(
epoch_start_time: u32,
last_timestamp: u32,
current_target: u32,
) -> [u8; 32] {
let mut actual_timespan = last_timestamp - epoch_start_time;
if actual_timespan < EXPECTED_EPOCH_TIMESPAN / 4 {
actual_timespan = EXPECTED_EPOCH_TIMESPAN / 4;
} else if actual_timespan > EXPECTED_EPOCH_TIMESPAN * 4 {
actual_timespan = EXPECTED_EPOCH_TIMESPAN * 4;
}

let new_target_bytes = bits_to_target(current_target);
let mut new_target = U256::from_be_bytes(new_target_bytes)
.wrapping_mul(&U256::from(actual_timespan))
.wrapping_div(&U256::from(EXPECTED_EPOCH_TIMESPAN));

if new_target > NETWORK_CONSTANTS.max_target {
new_target = NETWORK_CONSTANTS.max_target;
}
new_target.to_be_bytes()
) -> u32 {
let actual_timespan = last_timestamp.saturating_sub(epoch_start_time);
CompactTarget::from_next_work_required(
CompactTarget::from_consensus(current_target),
u64::from(actual_timespan),
Params::new(NETWORK),
)
.to_consensus()
}

fn check_hash_valid(hash: &[u8; 32], target_bytes: &[u8; 32]) {
Expand Down Expand Up @@ -1024,15 +1000,6 @@ mod tests {
}
}

#[test]
fn test_target_conversion() {
for (_, _, bits, _) in DIFFICULTY_ADJUSTMENTS {
let compact_target = bits_to_target(bits);
let nbits = target_to_bits(&compact_target);
assert_eq!(nbits, bits);
}
}

#[test]
fn test_bits_to_target() {
// https://learnmeabitcoin.com/explorer/block/00000000000000000002ebe388cb8fa0683fc34984cfc2d7d3b3f99bc0d51bfd
Expand All @@ -1041,21 +1008,27 @@ mod tests {
let bits: u32 = 0x1702f128;
let target = bits_to_target(bits);
assert_eq!(target, expected_target);

let converted_bits = target_to_bits(&target);

assert_eq!(converted_bits, bits);
}

#[test]
fn test_difficulty_adjustments() {
for (start_time, end_time, start_target, end_target) in DIFFICULTY_ADJUSTMENTS {
let new_target_bytes = calculate_new_difficulty(start_time, end_time, start_target);
let bits = target_to_bits(&new_target_bytes);
assert_eq!(bits, end_target);
let new_target = calculate_new_difficulty(start_time, end_time, start_target);
assert_eq!(new_target, end_target);
}
}

#[test]
fn test_difficulty_adjustment_handles_reversed_timestamps() {
let epoch_start_time = 1_700_002_000;
let current_target = 0x1d00ffff;
let expected = calculate_new_difficulty(epoch_start_time, epoch_start_time, current_target);

let actual = calculate_new_difficulty(epoch_start_time, 1_700_001_000, current_target);

assert_eq!(actual, expected);
}

#[test]
fn test_bridge_block_header_from_header() {
let header = Header {
Expand Down
13 changes: 8 additions & 5 deletions crates/state-chain/src/cbft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,14 @@ pub fn check_el_block_from_payload(
txs: &[Vec<u8>],
actual_data_hash: &[u8; 32],
) {
if let Some(payload) = parse_cbft_tx_payload(&txs[0]) {
assert_eq!(payload.block_number, el_block_number);
assert_eq!(payload.block_hash, el_block_hash);
assert_eq!(payload.parent_hash, el_parent_block_hash);
}
// The payload is what binds this EVM block to the chain we are extending, so
// it must be present: a `Tx` encoded without a body decodes to `body: None`,
// which used to skip all three assertions and leave only the merkle root.
assert!(!txs.is_empty(), "cosmos block carries no txs");
let payload = parse_cbft_tx_payload(&txs[0]).expect("missing MsgNewEthBlock payload");
assert_eq!(payload.block_number, el_block_number);
assert_eq!(payload.block_hash, el_block_hash);
assert_eq!(payload.parent_hash, el_parent_block_hash);
let computed_data_hash = merkle_root_from_base64_txns(txs);
assert_eq!(*actual_data_hash, computed_data_hash);
}
Expand Down
9 changes: 9 additions & 0 deletions crates/state-chain/src/state_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ impl StateChainState {
let cosmos_block: LightBlock = serde_json::from_slice(&block.cosmos_block)
.expect("failed to deserialize cosmos block");
if current_block_hash != self.genesis_evm_block_hash {
// Link this block to the one we last applied. Both ends of the
// chain are committed on Bitcoin - the genesis hash through the
// commit-chain digest, the final block through the signed cosmos
// header whose payload names it - so an unforgeable parent chain
// between them authenticates every intermediate block.
assert_eq!(
evm_header.parent_hash.0, self.latest_evm_block_hash,
"evm block does not extend the current chain tip"
);
let data_hash: [u8; 32] = cosmos_block
.signed_header
.header
Expand Down
Loading
Loading