diff --git a/bitcoin-script-riscv/Cargo.toml b/bitcoin-script-riscv/Cargo.toml index a6e9a2c..4100636 100644 --- a/bitcoin-script-riscv/Cargo.toml +++ b/bitcoin-script-riscv/Cargo.toml @@ -10,7 +10,7 @@ bitcoin-script-stack = { git = "https://github.com/FairgateLabs/rust-bitcoin-scr "interactive", ], branch = "v2" } bitcoin-script-functions = { git = "https://github.com/FairgateLabs/rust-bitcoin-script-functions" } -bitcoin = "=0.32.5" +bitcoin = "=0.32.6" bitcoin-script = { git = "https://github.com/FairgateLabs/rust-bitcoin-script", branch = "bitvmx" } riscv-decode = "0.2.1" thiserror = "1.0.61" diff --git a/bitcoin-script-riscv/src/riscv/challenges.rs b/bitcoin-script-riscv/src/riscv/challenges.rs index e2c44c7..8bef550 100644 --- a/bitcoin-script-riscv/src/riscv/challenges.rs +++ b/bitcoin-script-riscv/src/riscv/challenges.rs @@ -3,16 +3,15 @@ use bitcoin_script_stack::stack::{StackTracker, StackVariable}; use bitvmx_cpu_definitions::{ challenge::ChallengeType, constants::LAST_STEP_INIT, - memory::{MemoryAccessType, SectionDefinition}, + memory::{Chunk, MemoryAccessType, SectionDefinition}, trace::{generate_initial_step_hash, hashvec_to_string}, }; use crate::riscv::{ memory_alignment::{is_aligned, load_lower_half_nibble_table, load_upper_half_nibble_table}, - operations::sub, script_utils::{ - address_not_in_sections, is_equal_to, is_lower_than, nibbles_to_number, shift_number, - witness_equals, StackTables, WordTable, + address_in_range, address_in_sections, address_not_in_sections, get_selected_vars, + is_lower_than, verify_wrong_chunk_value, witness_equals, StackTables, }, }; @@ -418,43 +417,182 @@ pub fn addresses_sections_challenge( stack.drop(memory_witness); } -pub fn opcode_challenge(stack: &mut StackTracker, chunk_base: u32, opcodes_chunk: &Vec) { +pub fn opcode_challenge(stack: &mut StackTracker, chunk: &Chunk) { stack.clear_definitions(); let pc = stack.define(8, "prover_pc"); let opcode = stack.define(8, "prover_opcode"); - let tables = StackTables::new(stack, true, false, 0, 0, 0); + let tables = StackTables::new(stack, true, false, 2, 2, 0); - let start = stack.number_u32(chunk_base); - let end = stack.number_u32(chunk_base + 4 * opcodes_chunk.len() as u32); + address_in_range(stack, &chunk.range(), &pc); + stack.op_verify(); + + verify_wrong_chunk_value(stack, &tables, chunk, pc, opcode); + tables.drop(stack); +} + +pub fn initialized_challenge(stack: &mut StackTracker, chunk: &Chunk) { + stack.clear_definitions(); + + let read_addr_1 = stack.define(8, "prover_read_addr_1"); + let read_value_1 = stack.define(8, "prover_read_value_1"); + let read_step_1 = stack.define(16, "prover_read_step_1"); + + let read_addr_2 = stack.define(8, "prover_read_addr_2"); + let read_value_2 = stack.define(8, "prover_read_value_2"); + let read_step_2 = stack.define(16, "prover_read_step_2"); + + let read_selector = stack.define(1, "read_selector"); + + let [read_addr, read_value, read_step] = get_selected_vars( + stack, + [read_addr_1, read_value_1, read_step_1], + [read_addr_2, read_value_2, read_step_2], + read_selector, + ); + + address_in_range(stack, &chunk.range(), &read_addr); + stack.op_verify(); + + let init = stack.number_u64(LAST_STEP_INIT); + stack.equality(read_step, true, init, true, true, true); + + let tables = &StackTables::new(stack, true, false, 2, 2, 0); + verify_wrong_chunk_value(stack, tables, chunk, read_addr, read_value); + + tables.drop(stack); +} + +pub fn uninitialized_challenge( + stack: &mut StackTracker, + uninitialized_sections: &SectionDefinition, +) { + stack.clear_definitions(); + + let read_addr_1 = stack.define(8, "prover_read_addr_1"); + let read_value_1 = stack.define(8, "prover_read_value_1"); + let read_step_1 = stack.define(16, "prover_read_step_1"); + + let read_addr_2 = stack.define(8, "prover_read_addr_2"); + let read_value_2 = stack.define(8, "prover_read_value_2"); + let read_step_2 = stack.define(16, "prover_read_step_2"); + + let read_selector = stack.define(1, "read_selector"); + + let [read_addr, read_value, read_step] = get_selected_vars( + stack, + [read_addr_1, read_value_1, read_step_1], + [read_addr_2, read_value_2, read_step_2], + read_selector, + ); + + // TODO: ASSERT NOT TOO MANY SECTIONS + address_in_sections(stack, &read_addr, uninitialized_sections); + stack.op_verify(); - let start_copy = stack.copy_var(start); - let pc_copy = stack.copy_var(pc); - is_equal_to(stack, &start_copy, &pc_copy); - is_lower_than(stack, start_copy, pc_copy, true); + let init = stack.number_u64(LAST_STEP_INIT); + stack.equality(read_step, true, init, true, true, true); + + let zero = stack.number_u32(0); + stack.equality(read_value, true, zero, true, false, true); + + stack.drop(read_addr); +} + +pub fn read_value_challenge(stack: &mut StackTracker) { + stack.clear_definitions(); + + let read_addr_1 = stack.define(8, "prover_read_addr_1"); + let read_value_1 = stack.define(8, "prover_read_value_1"); + let read_step_1 = stack.define(16, "prover_read_step_1"); + + let read_addr_2 = stack.define(8, "prover_read_addr_2"); + let read_value_2 = stack.define(8, "prover_read_value_2"); + let read_step_2 = stack.define(16, "prover_read_step_2"); + + let read_selector = stack.define(1, "read_selector"); + + let step_hash = stack.define(40, "step_hash"); + + let write_addr = stack.define(8, "write_addr"); + let write_value = stack.define(8, "write_value"); + let write_pc = stack.define(8, "write_pc"); + let write_micro = stack.define(2, "write_micro"); + + let next_hash = stack.define(40, "next_hash"); + + let write_step = stack.define(16, "write_step"); + + let [read_addr, read_value, read_step] = get_selected_vars( + stack, + [read_addr_1, read_value_1, read_step_1], + [read_addr_2, read_value_2, read_step_2], + read_selector, + ); + + // if read_step == write_step -> write_addr != read_addr || write_value != read_value + stack.equality(read_step, false, write_step, false, true, false); + + stack.equality(write_addr, false, read_addr, false, false, false); + stack.equality(write_value, false, read_value, true, false, false); stack.op_boolor(); + stack.op_booland(); + + let init = stack.number_u64(LAST_STEP_INIT); - let pc_copy = stack.copy_var(pc); - is_lower_than(stack, pc_copy, end, true); + // if read_step == INIT || read_step < write_step -> write_addr == read_addr + stack.equality(read_step, false, init, true, true, false); + is_lower_than(stack, read_step, write_step, true); + stack.op_boolor(); + + stack.equality(write_addr, false, read_addr, true, true, false); stack.op_booland(); + stack.op_boolor(); + stack.op_verify(); - let opcodes_table = WordTable::new(stack, opcodes_chunk.clone()); + //save the hash to compare + stack.to_altstack(); - let to_shift = stack.number(2); - let opcode_offset = sub(stack, &tables, pc, start); - let opcode_index = shift_number(stack, to_shift, opcode_offset, true, false); + stack.explode(step_hash); + stack.explode(write_addr); + stack.explode(write_value); + stack.explode(write_pc); + stack.explode(write_micro); - let index_nibbles = stack.explode(opcode_index); - nibbles_to_number(stack, index_nibbles); + let result = blake3::blake3(stack, (40 + 8 + 8 + 8 + 2) / 2, 5); + stack.from_altstack(); + stack.equals(result, true, next_hash, true); +} + +pub fn correct_hash_challenge(stack: &mut StackTracker) { + stack.clear_definitions(); - let real_opcode = opcodes_table.peek(stack); + let prover_hash = stack.define(40, "prover_hash"); + let verifier_hash = stack.define(40, "verifier_hash"); - stack.equality(real_opcode, true, opcode, true, false, true); + let write_addr = stack.define(8, "write_addr"); + let write_value = stack.define(8, "write_value"); + let write_pc = stack.define(8, "write_pc"); + let write_micro = stack.define(2, "write_micro"); - opcodes_table.drop(stack); - tables.drop(stack); + let next_hash = stack.define(40, "next_hash"); + + stack.not_equal(prover_hash, true, verifier_hash, false); + + //save the hash to compare + stack.to_altstack(); + + stack.explode(verifier_hash); + stack.explode(write_addr); + stack.explode(write_value); + stack.explode(write_pc); + stack.explode(write_micro); + + let result = blake3::blake3(stack, (40 + 8 + 8 + 8 + 2) / 2, 5); + stack.from_altstack(); + stack.equals(result, true, next_hash, true); } //TODO: memory section challenge @@ -496,20 +634,53 @@ pub fn execute_challenge(challege_type: &ChallengeType) -> bool { stack.number_u32(prover_pc_read.pc.get_address()); stack.byte(prover_pc_read.pc.get_micro()); - stack.hexstr_as_nibbles(&prover_step_hash); + stack.hexstr_as_nibbles(prover_step_hash); program_counter_challenge(&mut stack); } + ChallengeType::Opcode(pc_read, _, chunk) => { + stack.number_u32(pc_read.pc.get_address()); + stack.number_u32(pc_read.opcode); + opcode_challenge(&mut stack, chunk.as_ref().unwrap()); + } ChallengeType::InputData(read_1, read_2, address, input_for_address) => { stack.number_u32(*input_for_address); //TODO: this should make input_wots[address] stack.number_u32(read_1.address); stack.number_u32(read_1.value); stack.number_u64(read_1.last_step); + stack.number_u32(read_2.address); stack.number_u32(read_2.value); stack.number_u64(read_2.last_step); + input_challenge(&mut stack, *address); } + ChallengeType::InitializedData(read_1, read_2, read_selector, _, chunk) => { + stack.number_u32(read_1.address); + stack.number_u32(read_1.value); + stack.number_u64(read_1.last_step); + + stack.number_u32(read_2.address); + stack.number_u32(read_2.value); + stack.number_u64(read_2.last_step); + + stack.number(*read_selector); + + initialized_challenge(&mut stack, chunk.as_ref().unwrap()); + } + ChallengeType::UninitializedData(read_1, read_2, read_selector, uninitialized_sections) => { + stack.number_u32(read_1.address); + stack.number_u32(read_1.value); + stack.number_u64(read_1.last_step); + + stack.number_u32(read_2.address); + stack.number_u32(read_2.value); + stack.number_u64(read_2.last_step); + + stack.number(*read_selector); + + uninitialized_challenge(&mut stack, uninitialized_sections.as_ref().unwrap()); + } ChallengeType::RomData(read_1, read_2 ,address, input_for_address ) => { stack.number_u32(read_1.address); stack.number_u32(read_1.value); @@ -544,10 +715,55 @@ pub fn execute_challenge(challege_type: &ChallengeType) -> bool { code_sections.as_ref().unwrap(), ); } - ChallengeType::Opcode(pc_read, _, chunk_base, opcodes_chunk) => { - stack.number_u32(pc_read.pc.get_address()); - stack.number_u32(pc_read.opcode); - opcode_challenge(&mut stack, chunk_base.unwrap(), opcodes_chunk.as_ref().unwrap()); + ChallengeType::ReadValue { + read_1, + read_2, + read_selector, + step_hash, + trace, + next_hash, + step, + } => { + stack.number_u32(read_1.address); + stack.number_u32(read_1.value); + stack.number_u64(read_1.last_step); + + stack.number_u32(read_2.address); + stack.number_u32(read_2.value); + stack.number_u64(read_2.last_step); + + stack.number(*read_selector); + + stack.hexstr_as_nibbles(step_hash); + + stack.number_u32(trace.get_write().address); + stack.number_u32(trace.get_write().value); + stack.number_u32(trace.get_pc().get_address()); + stack.byte(trace.get_pc().get_micro()); + + stack.hexstr_as_nibbles(next_hash); + + stack.number_u64(*step); + + read_value_challenge(&mut stack); + } + ChallengeType::CorrectHash { + prover_hash, + verifier_hash, + trace, + next_hash, + } => { + stack.hexstr_as_nibbles(prover_hash); + stack.hexstr_as_nibbles(verifier_hash); + + stack.number_u32(trace.get_write().address); + stack.number_u32(trace.get_write().value); + stack.number_u32(trace.get_pc().get_address()); + stack.byte(trace.get_pc().get_micro()); + + stack.hexstr_as_nibbles(next_hash); + + correct_hash_challenge(&mut stack); } _ => { return false; @@ -560,7 +776,10 @@ pub fn execute_challenge(challege_type: &ChallengeType) -> bool { #[cfg(test)] mod tests { - use bitvmx_cpu_definitions::{memory::MemoryWitness, trace::TraceRead}; + use bitvmx_cpu_definitions::{ + memory::MemoryWitness, + trace::{ProgramCounter, TraceRead, TraceStep, TraceWrite}, + }; use super::*; @@ -768,6 +987,72 @@ mod tests { )); } + fn test_correct_hash_aux( + prover_hash: &str, + verifier_hash: &str, + write_add: u32, + write_value: u32, + pc: u32, + micro: u8, + next_hash: &str, + ) -> bool { + let mut stack = StackTracker::new(); + + stack.hexstr_as_nibbles(prover_hash); + stack.hexstr_as_nibbles(verifier_hash); + + stack.number_u32(write_add); + stack.number_u32(write_value); + stack.number_u32(pc); + stack.byte(micro); + + stack.hexstr_as_nibbles(next_hash); + + correct_hash_challenge(&mut stack); + + stack.op_true(); + stack.run().success + } + + #[test] + fn test_correct_hash() { + let wrong_hash = "006942ae363a1a52823aa28eebe597d32b9d92e9"; + let correct_hash = "e2f115006467b4b1b2b27612bbfd40ed3bc8299b"; + let next_hash = "345721506e79c53d2549fc63d02ba8fc3b17efa4"; + + // can't challenge if prover has same hash + assert!(!test_correct_hash_aux( + correct_hash, + correct_hash, + 0xf0000028, + 0x00000001, + 0x8000010c, + 0x00, + next_hash + )); + + // can't challenge if verifier has wrong hash + assert!(!test_correct_hash_aux( + correct_hash, + wrong_hash, + 0xf0000028, + 0x00000001, + 0x8000010c, + 0x00, + next_hash + )); + + // challenge is valid if prover has wrong hash and verifier correct hash + assert!(test_correct_hash_aux( + wrong_hash, + correct_hash, + 0xf0000028, + 0x00000001, + 0x8000010c, + 0x00, + next_hash + )); + } #[test] fn test_padding_hash() { let pre_hash = "006942ae363a1a52823aa28eebe597d32b9d92e9"; @@ -1246,7 +1531,13 @@ mod tests { stack.number_u32(pc); stack.number_u32(opcode); - opcode_challenge(&mut stack, chunk_base, opcodes_chunk); + opcode_challenge( + &mut stack, + &Chunk { + base_addr: chunk_base, + data: opcodes_chunk.to_vec(), + }, + ); stack.op_true(); let r = stack.run(); @@ -1268,4 +1559,283 @@ mod tests { assert!(test_opcode_aux(0xab00_0000, 8888, 0xab00_0000, opcodes)); assert!(test_opcode_aux(0xab00_0004, 8888, 0xab00_0000, opcodes)); } + + fn test_initialized_aux( + read_1: &TraceRead, + read_2: &TraceRead, + read_selector: u32, + chunk: &Chunk, + ) -> bool { + let mut stack = StackTracker::new(); + + stack.number_u32(read_1.address); + stack.number_u32(read_1.value); + stack.number_u64(read_1.last_step); + + stack.number_u32(read_2.address); + stack.number_u32(read_2.value); + stack.number_u64(read_2.last_step); + + stack.number(read_selector); + + initialized_challenge(&mut stack, chunk); + + stack.op_true(); + stack.run().success + } + + #[test] + fn test_initialized() { + let chunk = &Chunk { + base_addr: 0x1000_0000, + data: vec![0x1111_1111, 0x2222_2222, 0x3333_3333, 0x4444_4444], + }; + + // can't challenge not init state + let read_1 = TraceRead::new(0x1000_0000, 0x1234_5678, 1); + let read_2 = TraceRead::new(0x1000_0004, 0x1234_5678, 2); + assert!(!test_initialized_aux(&read_1, &read_2, 1, chunk)); + assert!(!test_initialized_aux(&read_1, &read_2, 2, chunk)); + + // can't challenge if value is right + let read_1 = TraceRead::new(0x1000_0000, 0x1111_1111, LAST_STEP_INIT); + let read_2 = TraceRead::new(0x1000_0004, 0x2222_2222, LAST_STEP_INIT); + assert!(!test_initialized_aux(&read_1, &read_2, 1, chunk)); + assert!(!test_initialized_aux(&read_1, &read_2, 2, chunk)); + + // can't challenge if address is outside chunk + let read_1 = TraceRead::new(0x0000_0004, 0x1234_5678, LAST_STEP_INIT); + let read_2 = TraceRead::new(0x0000_0008, 0x1234_5678, LAST_STEP_INIT); + assert!(!test_initialized_aux(&read_1, &read_2, 1, chunk)); + assert!(!test_initialized_aux(&read_1, &read_2, 2, chunk)); + + // challenge is valid if the address is the same but the value differs in both + let read_1 = TraceRead::new(0x1000_0000, 0x1234_5678, LAST_STEP_INIT); + let read_2 = TraceRead::new(0x1000_0004, 0x1234_5678, LAST_STEP_INIT); + assert!(test_initialized_aux(&read_1, &read_2, 1, chunk)); + assert!(test_initialized_aux(&read_1, &read_2, 2, chunk)); + + // challenge is valid if the address is the same but the value differs in read_1 and uses correct selector + let read_1 = TraceRead::new(0x1000_0000, 0x1234_5678, LAST_STEP_INIT); + let read_2 = TraceRead::new(0x1000_0004, 0x2222_2222, LAST_STEP_INIT); + assert!(test_initialized_aux(&read_1, &read_2, 1, chunk)); + assert!(!test_initialized_aux(&read_1, &read_2, 2, chunk)); + + // challenge is valid if the address is the same but the value differs in read_2 and uses correct selector + let read_1 = TraceRead::new(0x1000_0000, 0x1111_1111, LAST_STEP_INIT); + let read_2 = TraceRead::new(0x1000_0004, 0x1234_5678, LAST_STEP_INIT); + assert!(!test_initialized_aux(&read_1, &read_2, 1, chunk)); + assert!(test_initialized_aux(&read_1, &read_2, 2, chunk)); + } + + fn test_uninitialized_aux( + read_1: &TraceRead, + read_2: &TraceRead, + read_selector: u32, + sections: &SectionDefinition, + ) -> bool { + let mut stack = StackTracker::new(); + + stack.number_u32(read_1.address); + stack.number_u32(read_1.value); + stack.number_u64(read_1.last_step); + + stack.number_u32(read_2.address); + stack.number_u32(read_2.value); + stack.number_u64(read_2.last_step); + + stack.number(read_selector); + + uninitialized_challenge(&mut stack, sections); + + stack.op_true(); + stack.run().success + } + + #[test] + fn test_uninitialized() { + let uninitialized_sections = &SectionDefinition { + ranges: vec![(0x1000_0000, 0x2000_0000), (0xA000_0000, 0xB000_0000)], + }; + + // can't challenge not init state + let read_1 = TraceRead::new(0x1000_0000, 0x1234_5678, 1); + let read_2 = TraceRead::new(0xA000_0000, 0x1234_5678, 2); + assert!(!test_uninitialized_aux( + &read_1, + &read_2, + 1, + uninitialized_sections + )); + assert!(!test_uninitialized_aux( + &read_1, + &read_2, + 2, + uninitialized_sections + )); + + // can't challenge if value is right + let read_1 = TraceRead::new(0x1000_0000, 0, LAST_STEP_INIT); + let read_2 = TraceRead::new(0xA000_0000, 0, LAST_STEP_INIT); + assert!(!test_uninitialized_aux( + &read_1, + &read_2, + 1, + uninitialized_sections + )); + assert!(!test_uninitialized_aux( + &read_1, + &read_2, + 2, + uninitialized_sections + )); + + // can't challenge if address is outside uninitialized sections + let read_1 = TraceRead::new(0x0000_0002, 0x1234_5678, LAST_STEP_INIT); + let read_2 = TraceRead::new(0x0000_0002, 0x1234_5678, LAST_STEP_INIT); + assert!(!test_uninitialized_aux( + &read_1, + &read_2, + 1, + uninitialized_sections + )); + assert!(!test_uninitialized_aux( + &read_1, + &read_2, + 2, + uninitialized_sections + )); + + // challenge is valid if the address is the same but the value differs in both + let read_1 = TraceRead::new(0x1000_0000, 0x1234_5678, LAST_STEP_INIT); + let read_2 = TraceRead::new(0xA000_0000, 0x1234_5678, LAST_STEP_INIT); + assert!(test_uninitialized_aux( + &read_1, + &read_2, + 1, + uninitialized_sections + )); + assert!(test_uninitialized_aux( + &read_1, + &read_2, + 2, + uninitialized_sections + )); + + // challenge is valid if the address is the same but the value differs in read_1 and uses correct selector + let read_1 = TraceRead::new(0x1000_0000, 0x1234_5678, LAST_STEP_INIT); + let read_2 = TraceRead::new(0xA000_0000, 0, LAST_STEP_INIT); + assert!(test_uninitialized_aux( + &read_1, + &read_2, + 1, + uninitialized_sections + )); + assert!(!test_uninitialized_aux( + &read_1, + &read_2, + 2, + uninitialized_sections + )); + + // challenge is valid if the address is the same but the value differs in read_2 and uses correct selector + let read_1 = TraceRead::new(0x1000_0000, 0, LAST_STEP_INIT); + let read_2 = TraceRead::new(0xA000_0000, 0x1234_5678, LAST_STEP_INIT); + assert!(!test_uninitialized_aux( + &read_1, + &read_2, + 1, + uninitialized_sections + )); + assert!(test_uninitialized_aux( + &read_1, + &read_2, + 2, + uninitialized_sections + )); + } + + fn test_read_value_aux( + read: TraceRead, + trace: &TraceStep, + step_hash: &str, + next_hash: &str, + write_step: u64, + ) -> bool { + let stack = &mut StackTracker::new(); + + stack.number_u32(read.address); + stack.number_u32(read.value); + stack.number_u64(read.last_step); + + stack.number_u32(read.address); + stack.number_u32(read.value); + stack.number_u64(read.last_step); + + stack.number(1); + + stack.hexstr_as_nibbles(&step_hash); + + stack.number_u32(trace.get_write().address); + stack.number_u32(trace.get_write().value); + stack.number_u32(trace.get_pc().get_address()); + stack.byte(trace.get_pc().get_micro()); + + stack.hexstr_as_nibbles(&next_hash); + + stack.number_u64(write_step); + + read_value_challenge(stack); + + stack.op_true(); + stack.run().success + } + #[test] + fn test_read_value() { + let hash = "e2f115006467b4b1b2b27612bbfd40ed3bc8299b"; + let next_hash = "345721506e79c53d2549fc63d02ba8fc3b17efa4"; + let wrong_hash = "006942ae363a1a52823aa28eebe597d32b9d92e9"; + + let write_pc = ProgramCounter::new(0x8000010c, 0x00); + let write = TraceWrite::new(0xf0000028, 1); + let trace = &TraceStep::new(write, write_pc); + + // can't challenge if read is correct + let read = TraceRead::new(0xf0000028, 1, 100); + let step = 100; + assert!(!test_read_value_aux(read, trace, hash, next_hash, step)); + + // can't challenge if write is older + let read = TraceRead::new(0xf0000028, 0, 100); + let step = 50; + assert!(!test_read_value_aux(read, trace, hash, next_hash, step)); + + // can't challenge if write is newer but for different address + let read = TraceRead::new(0x1000_0000, 0, 100); + let step = 200; + assert!(!test_read_value_aux(read, trace, hash, next_hash, step)); + + // challenge is valid if write is newer for the same address and has correct hash + let read = TraceRead::new(0xf0000028, 0, 100); + let step = 200; + assert!(test_read_value_aux( + read.clone(), + trace, + hash, + next_hash, + step + )); + // can't challenge if hash is wrong + assert!(!test_read_value_aux(read, trace, hash, wrong_hash, step)); + + // challenge is valid if write is at the same step but for different address + let read = TraceRead::new(0x1000_0000, 0, 100); + let step = 100; + assert!(test_read_value_aux(read, trace, hash, next_hash, step)); + + // challenge is valid if write is at the same step for different address but different value + let read = TraceRead::new(0xf0000028, 0, 100); + let step = 100; + assert!(test_read_value_aux(read, trace, hash, next_hash, step)); + } } diff --git a/bitcoin-script-riscv/src/riscv/script_utils.rs b/bitcoin-script-riscv/src/riscv/script_utils.rs index 4f5e1b6..98684ad 100644 --- a/bitcoin-script-riscv/src/riscv/script_utils.rs +++ b/bitcoin-script-riscv/src/riscv/script_utils.rs @@ -3,7 +3,7 @@ use bitcoin_script_stack::stack::{StackTracker, StackVariable}; pub use bitcoin_script::{define_pushable, script}; define_pushable!(); pub use bitcoin::ScriptBuf as Script; -use bitvmx_cpu_definitions::memory::{MemoryAccessType, SectionDefinition}; +use bitvmx_cpu_definitions::memory::{Chunk, MemoryAccessType, SectionDefinition}; use super::operations::{sort_nibbles, sub}; @@ -67,6 +67,34 @@ pub fn nib_to_bin(stack: &mut StackTracker) { } } +pub fn static_right_shift_2( + stack: &mut StackTracker, + tables: &StackTables, + number: StackVariable, +) -> StackVariable { + let size = stack.get_size(number); + for n in 0..size { + stack.move_var_sub_n(number, 0); + if n < size - 1 { + stack.op_dup(); + stack.get_value_from_table(tables.lshift.shift_2, None); + stack.to_altstack(); + } + + stack.get_value_from_table(tables.rshift.shift_2, None); + + if n > 0 { + stack.op_add(); + } + + if n < size - 1 { + stack.from_altstack(); + } + } + + stack.join_in_stack(size, None, Some("right_shift_2")) +} + //expects the shift ammount and the number to be shifted on the stack pub fn shift_number( stack: &mut StackTracker, @@ -636,6 +664,9 @@ pub fn is_lower_than( than: StackVariable, unsigned: bool, ) -> StackVariable { + assert_eq!(stack.get_size(value), stack.get_size(than)); + let size = stack.get_size(value); + if !unsigned { stack.copy_var_sub_n(value, 0); if_greater(stack, 7, 1, 0); //1 if negative @@ -643,6 +674,7 @@ pub fn is_lower_than( if_greater(stack, 7, 1, 0); //1 if negative stack.op_2dup(); stack.op_equal(); + let n = 2_i32.pow(size + 1); stack .custom( script! { @@ -652,9 +684,9 @@ pub fn is_lower_than( OP_ELSE OP_GREATERTHAN OP_IF - 512 + { n } OP_ELSE - { -512 } + { -n } OP_ENDIF OP_ENDIF }, @@ -666,8 +698,8 @@ pub fn is_lower_than( .unwrap(); } - for i in 0..8 { - let n: i32 = 2_i32.pow(8 - i); + for i in 0..size { + let n: i32 = 2_i32.pow(size - i); stack.move_var_sub_n(value, 0); stack.move_var_sub_n(than, 0); stack.op_2dup(); @@ -677,7 +709,7 @@ pub fn is_lower_than( script! { OP_IF OP_2DROP - { n} + { n } OP_ELSE OP_EQUAL OP_IF @@ -694,7 +726,7 @@ pub fn is_lower_than( ) .unwrap(); } - for _ in 0..7 { + for _ in 0..size - 1 { stack.op_add(); } if !unsigned { @@ -1418,32 +1450,115 @@ pub fn witness_equals( stack.op_equal(); } -pub fn address_not_in_sections( +pub fn verify_wrong_chunk_value( stack: &mut StackTracker, - address: &StackVariable, - sections: &SectionDefinition, + tables: &StackTables, + chunk: &Chunk, + address: StackVariable, + value: StackVariable, ) { - for range in §ions.ranges { - assert!(range.0 + 3 <= range.1); - let section_start = stack.number_u32(range.0); - let address_copy: StackVariable = stack.copy_var(*address); + let chunk_table = WordTable::new(stack, chunk.data.clone()); - is_lower_than(stack, address_copy, section_start, true); + let base_addr = stack.number_u32(chunk.base_addr); + let offset = sub(stack, &tables, address, base_addr); - // when we do a read on an address, we also read the 3 addresses after - let section_end = stack.number_u32(range.1 - 3); - let address_copy = stack.copy_var(*address); + let index = static_right_shift_2(stack, tables, offset); - is_lower_than(stack, section_end, address_copy, true); + let index_nibbles = stack.explode(index); + nibbles_to_number(stack, index_nibbles); - stack.op_boolor(); + let real_opcode = chunk_table.peek(stack); + + stack.equality(real_opcode, true, value, true, false, true); + chunk_table.drop(stack); +} + +pub fn get_selected_vars( + stack: &mut StackTracker, + vars_1: [StackVariable; N], + vars_2: [StackVariable; N], + var_selector: StackVariable, +) -> [StackVariable; N] { + assert_eq!(vars_1.len(), vars_2.len()); + let consumes = vars_1.len() as u32 * 2; + + let output: Vec<_> = vars_1 + .iter() + .enumerate() + .map(|(i, var)| (stack.get_size(*var), format!("var_{}", i))) + .collect(); + + // we need the variables to be on top of the stack, or we will break variables that are higher when merging the branches + for (var_1, var_2) in vars_1.iter().zip(vars_2.iter()) { + assert_eq!(stack.get_size(*var_1), stack.get_size(*var_2)); + stack.move_var(*var_1); + stack.move_var(*var_2); + } + + stack.move_var(var_selector); + stack.number(1); + stack.op_equal(); + let (mut chose_var_1, mut chose_var_2) = stack.open_if(); + + for (var_1, var_2) in vars_1.into_iter().zip(vars_2.into_iter()) { + chose_var_1.move_var(var_2); + chose_var_1.drop(var_2); + chose_var_1.move_var(var_1); + + chose_var_2.move_var(var_1); + chose_var_2.drop(var_1); + chose_var_2.move_var(var_2); + } + + stack + .end_if(chose_var_1, chose_var_2, consumes, output, 0) + .try_into() + .ok() + .expect("Vec length does not match expected array size") +} + +pub fn address_in_range(stack: &mut StackTracker, range: &(u32, u32), address: &StackVariable) { + let start = stack.number_u32(range.0); + let end = stack.number_u32(range.1); + let address_copy = stack.copy_var(*address); + + // start <= address + is_equal_to(stack, &start, &address_copy); + is_lower_than(stack, start, address_copy, true); + stack.op_boolor(); + + // address <= end + let address_copy = stack.copy_var(*address); + is_equal_to(stack, &end, &address_copy); + is_lower_than(stack, address_copy, end, true); + stack.op_boolor(); + + stack.op_booland(); +} + +pub fn address_in_sections( + stack: &mut StackTracker, + address: &StackVariable, + sections: &SectionDefinition, +) { + for range in §ions.ranges { + address_in_range(stack, range, address); } for _ in 0..sections.ranges.len() - 1 { - stack.op_booland(); + stack.op_boolor(); } } +pub fn address_not_in_sections( + stack: &mut StackTracker, + address: &StackVariable, + sections: &SectionDefinition, +) { + address_in_sections(stack, address, sections); + stack.op_not(); +} + pub fn nibbles_to_number(stack: &mut StackTracker, nibbles: Vec) -> StackVariable { let mut result = stack.number(0); @@ -1626,6 +1741,26 @@ mod tests { test_shift_case(0xF100_0013, 13, false, false, 0x0002_6000); } + fn test_static_right_shift_2_case(value: u32, expected: u32) { + let mut stack = StackTracker::new(); + let tables = &StackTables::new(&mut stack, false, false, 2, 2, 0); + let number = stack.number_u32(value); + let shifted = static_right_shift_2(&mut stack, tables, number); + println!("Size: {} ", stack.get_script().len()); + let expected = stack.number_u32(expected); + stack.equals(shifted, true, expected, true); + tables.drop(&mut stack); + stack.op_true(); + assert!(stack.run().success); + } + + #[test] + fn test_static_right_shift_2() { + test_static_right_shift_2_case(0b1101_1011, 0b0011_0110); + test_static_right_shift_2_case(0xF100_0013, 0x3C40_0004); + test_static_right_shift_2_case(3, 0); + } + #[test] fn test_nib_to_bin() { for i in 0..16 { @@ -1654,6 +1789,24 @@ mod tests { test_lower_helper(0x0000_0000, 0xffff_f800, 0, false); } + fn test_lower_helper_64bits(value: u64, than: u64, expected: u32, unsigned: bool) { + let mut stack = StackTracker::new(); + let value = stack.number_u64(value); + let than = stack.number_u64(than); + is_lower_than(&mut stack, value, than, unsigned); + stack.number(expected); + stack.op_equal(); + assert!(stack.run().success); + } + + #[test] + fn test_lower_64bits() { + test_lower_helper_64bits(0x0000_0000_0000_0000, 0xffff_ffff_ffff_ffff, 1, true); + test_lower_helper_64bits(0x0000_0000_0000_0000, 0xffff_ffff_ffff_ffff, 0, false); + test_lower_helper_64bits(0xf000_0000_0000_0000, 0xffff_ffff_ffff_ffff, 1, false); + test_lower_helper_64bits(0x0000_0000_0000_0000, 0xffff_f800_0000_0000, 0, false); + } + #[test] fn test_shift_dynamic() { for y in 0..16 { @@ -1790,12 +1943,12 @@ mod tests { test_witness_equals_aux(memory_witness, 1, false, MemoryAccessType::Unused); } - fn test_address_not_in_sections_aux(address: u32, sections: &SectionDefinition) -> bool { + fn test_address_in_range_aux(address: u32, range: &(u32, u32)) -> bool { let mut stack = StackTracker::new(); let address = stack.number_u32(address); - address_not_in_sections(&mut stack, &address, sections); + address_in_range(&mut stack, range, &address); stack.op_verify(); stack.drop(address); @@ -1804,27 +1957,18 @@ mod tests { } #[test] - fn test_address_not_in_sections() { - const START_1: u32 = 0x0000_00f0; - const START_2: u32 = 0x000f_00f0; - const END_1: u32 = 0x0000_f003; - const END_2: u32 = 0x000f_f003; - - let sections = &SectionDefinition { - ranges: vec![(START_1, END_1), (START_2, END_2)], - }; + fn test_address_in_range() { + const START: u32 = 0x0000_00f0; + const END: u32 = 0x0000_f003; + + let range = &(START, END); - assert!(!test_address_not_in_sections_aux(START_1, sections)); - assert!(!test_address_not_in_sections_aux(0x0000_0f00, sections)); - assert!(!test_address_not_in_sections_aux(END_1 - 3, sections)); - assert!(!test_address_not_in_sections_aux(START_2, sections)); - assert!(!test_address_not_in_sections_aux(0x000f_0f00, sections)); - assert!(!test_address_not_in_sections_aux(END_2 - 3, sections)); + assert!(test_address_in_range_aux(START, range)); + assert!(test_address_in_range_aux((START + END) / 2, range)); + assert!(test_address_in_range_aux(END, range)); - assert!(test_address_not_in_sections_aux(START_1 - 1, sections)); - assert!(test_address_not_in_sections_aux(END_1 - 2, sections)); - assert!(test_address_not_in_sections_aux(START_2 - 1, sections)); - assert!(test_address_not_in_sections_aux(END_2 - 2, sections)); + assert!(!test_address_in_range_aux(START - 1, range)); + assert!(!test_address_in_range_aux(END + 1, range)); } #[test] @@ -1839,4 +1983,86 @@ mod tests { stack.op_true(); assert!(stack.run().success); } + + fn test_get_selected_vars_aux(var_selector: u32) { + let mut stack = StackTracker::new(); + + let previous_var = stack.number_u32(0x3333_3333); + + let var_1 = stack.number_u32(0x1111_1111); + let var_2 = stack.number_u32(0x2222_2222); + let selector = stack.number(var_selector); + + let next_var = stack.number_u32(0x4444_4444); + + let [chosen_var] = get_selected_vars(&mut stack, [var_1], [var_2], selector); + + // we should get the selected variable + let expected_var = if var_selector == 1 { + 0x1111_1111 + } else { + 0x2222_2222 + }; + let expected_var = stack.number_u32(expected_var); + stack.equality(chosen_var, true, expected_var, true, true, true); + + // previous variable should remain unchanged + let expected_var = stack.number_u32(0x3333_3333); + stack.equality(previous_var, true, expected_var, true, true, true); + + // next variable should also remain unchanged + let expected_var = stack.number_u32(0x4444_4444); + stack.equality(next_var, true, expected_var, true, true, true); + + stack.op_true(); + assert!(stack.run().success); + } + + #[test] + fn test_get_selected_vars() { + test_get_selected_vars_aux(1); + test_get_selected_vars_aux(2); + } + + fn test_verify_wrong_chunk_value_aux(address: u32, value: u32, chunk: &Chunk) -> bool { + let mut stack = StackTracker::new(); + + let address = stack.number_u32(address); + let value = stack.number_u32(value); + let tables = &StackTables::new(&mut stack, true, false, 2, 2, 0); + + verify_wrong_chunk_value(&mut stack, tables, chunk, address, value); + tables.drop(&mut stack); + stack.op_true(); + stack.run().success + } + + #[test] + fn test_verify_wrong_chunk_value() { + let chunk = &Chunk { + base_addr: 0x1000_0000, + data: vec![0x1111_1111, 0x2222_2222], + }; + + assert!(test_verify_wrong_chunk_value_aux( + 0x1000_0000, + 0x1234_5678, + chunk + )); + assert!(test_verify_wrong_chunk_value_aux( + 0x1000_0004, + 0x1234_5678, + chunk + )); + assert!(!test_verify_wrong_chunk_value_aux( + 0x1000_0000, + 0x1111_1111, + chunk + )); + assert!(!test_verify_wrong_chunk_value_aux( + 0x1000_0004, + 0x2222_2222, + chunk + )); + } } diff --git a/definitions/src/challenge.rs b/definitions/src/challenge.rs index 38bcee1..af9d3c5 100644 --- a/definitions/src/challenge.rs +++ b/definitions/src/challenge.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::{ - memory::{MemoryWitness, SectionDefinition}, + memory::{Chunk, MemoryWitness, SectionDefinition}, trace::{ProgramCounter, TraceRWStep, TraceRead, TraceReadPC, TraceStep, TraceWrite}, }; @@ -12,8 +12,10 @@ pub enum ChallengeType { TraceHashZero(TraceStep, String), // PROVER_TRACE_STEP, PROVER_STEP_HASH EntryPoint(TraceReadPC, u64, Option), // (PROVER_READ_PC, PROVER_READ_MICRO), PROVER_TRACE_STEP, ENTRYPOINT (only used on test) ProgramCounter(String, TraceStep, String, TraceReadPC), - Opcode(TraceReadPC, u32, Option, Option>), // (PROVER_PC, PROVER_OPCODE), CHUNK_INDEX, CHUNK_BASE_ADDRESS, OPCODES_CHUNK + Opcode(TraceReadPC, u32, Option), // (PROVER_PC, PROVER_OPCODE), CHUNK_INDEX, CHUNK_BASE_ADDRESS, OPCODES_CHUNK InputData(TraceRead, TraceRead, u32, u32), + InitializedData(TraceRead, TraceRead, u32, u32, Option), + UninitializedData(TraceRead, TraceRead, u32, Option), RomData(TraceRead, TraceRead, u32, u32), AddressesSections( TraceRead, @@ -26,6 +28,22 @@ pub enum ChallengeType { Option, // register sections Option, // code sections ), + ReadValueNArySearch(u32), + ReadValue { + read_1: TraceRead, + read_2: TraceRead, + read_selector: u32, + step_hash: String, + trace: TraceStep, + next_hash: String, + step: u64, + }, + CorrectHash { + prover_hash: String, + verifier_hash: String, + trace: TraceStep, + next_hash: String, + }, No, } diff --git a/definitions/src/lib.rs b/definitions/src/lib.rs index 20c37e8..56760c0 100644 --- a/definitions/src/lib.rs +++ b/definitions/src/lib.rs @@ -4,5 +4,5 @@ pub mod trace; pub mod constants { pub const LAST_STEP_INIT: u64 = 0xFFFF_FFFF_FFFF_FFFF; - pub const CODE_CHUNK_SIZE: u32 = 500; + pub const CHUNK_SIZE: u32 = 100; } diff --git a/definitions/src/memory.rs b/definitions/src/memory.rs index ca0e0fd..7ac209e 100644 --- a/definitions/src/memory.rs +++ b/definitions/src/memory.rs @@ -110,6 +110,21 @@ impl MemoryWitness { } } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Chunk { + pub base_addr: u32, + pub data: Vec, +} + +impl Chunk { + pub fn range(&self) -> (u32, u32) { + ( + self.base_addr, + self.base_addr + self.data.len() as u32 * 4 - 1, + ) + } +} + //create tests for the functions #[cfg(test)] mod tests { diff --git a/docker-riscv32 b/docker-riscv32 index abe6769..34a6997 160000 --- a/docker-riscv32 +++ b/docker-riscv32 @@ -1 +1 @@ -Subproject commit abe67699a6d6a09cce15d1476ce2aec676de30a6 +Subproject commit 34a699736f50516d754441239cd8be2b26bc3a41 diff --git a/emulator/src/decision/challenge.rs b/emulator/src/decision/challenge.rs index 843e844..547c2f1 100644 --- a/emulator/src/decision/challenge.rs +++ b/emulator/src/decision/challenge.rs @@ -1,6 +1,7 @@ use bitvmx_cpu_definitions::{ challenge::ChallengeType, - constants::{CODE_CHUNK_SIZE, LAST_STEP_INIT}, + constants::{CHUNK_SIZE, LAST_STEP_INIT}, + memory::Chunk, trace::{generate_initial_step_hash, hashvec_to_string, validate_step_hash, TraceRWStep}, }; @@ -12,7 +13,7 @@ use tracing::{error, info, warn}; use crate::{ decision::{ execution_log::VerifierChallengeLog, - nary_search::{choose_segment, ExecutionHashes}, + nary_search::{choose_segment, ExecutionHashes, NArySearchType}, }, executor::utils::FailConfiguration, loader::program_definition::ProgramDefinition, @@ -27,10 +28,15 @@ pub fn prover_execute( checkpoint_path: &str, force: bool, fail_config: Option, + save_non_checkpoint_steps: bool, ) -> Result<(ExecutionResult, u64, String), EmulatorError> { let program_def = ProgramDefinition::from_config(program_definition_file)?; - let (result, last_step, last_hash) = - program_def.get_execution_result(input.clone(), checkpoint_path, fail_config)?; + let (result, last_step, last_hash) = program_def.get_execution_result( + input.clone(), + checkpoint_path, + fail_config, + save_non_checkpoint_steps, + )?; if result != ExecutionResult::Halt(0, last_step) { error!( "The execution of the program {} failed with error: {:?}. The claim should not be commited on-chain.", @@ -58,28 +64,32 @@ pub fn prover_get_hashes_for_round( round: u8, verifier_decision: u32, fail_config: Option, + nary_type: NArySearchType, ) -> Result, EmulatorError> { let mut challenge_log = ProverChallengeLog::load(checkpoint_path)?; - let base = challenge_log.base_step; - + let input = challenge_log.input.clone(); + let nary_log = challenge_log.get_nary_log(nary_type); let program_def = ProgramDefinition::from_config(program_definition_file)?; let new_base = match round { - 1 => base, - _ => program_def - .nary_def() - .step_from_base_and_bits(round - 1, base, verifier_decision), + 1 => nary_log.base_step, + _ => program_def.nary_def().step_from_base_and_bits( + round - 1, + nary_log.base_step, + verifier_decision, + ), }; - challenge_log.base_step = new_base; + nary_log.base_step = new_base; let hashes = program_def.get_round_hashes( checkpoint_path, + input, round, - challenge_log.base_step, + nary_log.base_step, fail_config, )?; - challenge_log.hash_rounds.push(hashes.clone()); - challenge_log.verifier_decisions.push(verifier_decision); + nary_log.hash_rounds.push(hashes.clone()); + nary_log.verifier_decisions.push(verifier_decision); challenge_log.save(checkpoint_path)?; Ok(hashes) } @@ -92,34 +102,36 @@ pub fn verifier_check_execution( claim_last_hash: &str, force_condition: ForceCondition, fail_config: Option, + save_non_checkpoint_steps: bool, ) -> Result, EmulatorError> { let program_def = ProgramDefinition::from_config(program_definition_file)?; - let (result, last_step, last_hash) = - program_def.get_execution_result(input.clone(), checkpoint_path, fail_config)?; + let (result, last_step, last_hash) = program_def.get_execution_result( + input.clone(), + checkpoint_path, + fail_config, + save_non_checkpoint_steps, + )?; - let mut should_challenge = true; + let input_is_valid = result == ExecutionResult::Halt(0, last_step); + let same_step_and_hash = last_step == claim_last_step && last_hash == claim_last_hash; - if result == ExecutionResult::Halt(0, last_step) { - info!("The program executed successfully with the prover input"); - info!("Do not challenge."); + let should_challenge = force_condition == ForceCondition::Always + || !input_is_valid + || (force_condition == ForceCondition::ValidInputWrongStepOrHash && !same_step_and_hash) + || (force_condition == ForceCondition::ValidInputStepAndHash && same_step_and_hash); - if claim_last_step != last_step || claim_last_hash != last_hash { + if !should_challenge { + if same_step_and_hash { + info!("The program executed successfully with the prover input"); + info!("Do not challenge."); + } else { warn!("The prover provided a valid input, but the last step or hash differs"); - warn!("Do not challenge (as the challenge is not waranteed to be successful)"); + warn!("Do not challenge (as the challenge is not guaranteed to be successful)"); warn!("Report this case to be evaluated by the security team"); - should_challenge = force_condition == ForceCondition::ValidInputWrongStepOrHash - || force_condition == ForceCondition::Allways; - } else { - should_challenge = force_condition == ForceCondition::ValidInputStepAndHash - || force_condition == ForceCondition::Allways; } + return Ok(None); } - // if !should_challenge { - // // TODO: Should be removed after here, not creating challenge_log. - // return Ok(None); - // } - warn!("There is a discrepancy between the prover and verifier execution"); warn!("This execution will be challenged"); @@ -140,11 +152,6 @@ pub fn verifier_check_execution( ); challenge_log.save(checkpoint_path)?; - if !should_challenge { - // TODO: Remove - return Ok(None); - } - Ok(Some(step_to_challenge)) } @@ -154,30 +161,39 @@ pub fn verifier_choose_segment( round: u8, prover_last_hashes: Vec, fail_config: Option, + nary_type: NArySearchType, ) -> Result { let mut challenge_log = VerifierChallengeLog::load(checkpoint_path)?; - let base = challenge_log.base_step; + let input = challenge_log.input.clone(); + let nary_log = challenge_log.get_nary_log(nary_type); let program_def = ProgramDefinition::from_config(program_definition_file)?; - let hashes = program_def.get_round_hashes(checkpoint_path, round, base, fail_config)?; + let hashes = program_def.get_round_hashes( + checkpoint_path, + input, + round, + nary_log.base_step, + fail_config, + )?; let claim_hashes = ExecutionHashes::from_hexstr(&prover_last_hashes); let my_hashes = ExecutionHashes::from_hexstr(&hashes); let (bits, base, new_selected) = choose_segment( &program_def.nary_def(), - base, - challenge_log.step_to_challenge, + nary_log.base_step, + nary_log.step_to_challenge, round, &claim_hashes, &my_hashes, + nary_type, ); - challenge_log.base_step = base; - challenge_log.step_to_challenge = new_selected; - challenge_log.verifier_decisions.push(bits); - challenge_log.prover_hash_rounds.push(prover_last_hashes); - challenge_log.verifier_hash_rounds.push(hashes); + nary_log.base_step = base; + nary_log.step_to_challenge = new_selected; + nary_log.verifier_decisions.push(bits); + nary_log.prover_hash_rounds.push(prover_last_hashes); + nary_log.verifier_hash_rounds.push(hashes); challenge_log.save(checkpoint_path)?; info!("Verifier selects bits: {bits} base: {base} selection: {new_selected}"); @@ -192,22 +208,24 @@ pub fn prover_final_trace( fail_config: Option, ) -> Result { let mut challenge_log = ProverChallengeLog::load(checkpoint_path)?; - let base = challenge_log.base_step; + let input = challenge_log.input.clone(); + let nary_log = challenge_log.get_nary_log(NArySearchType::ConflictStep); let program_def = ProgramDefinition::from_config(program_definition_file)?; let nary_def = program_def.nary_def(); let total_rounds = nary_def.total_rounds(); - let final_step = nary_def.step_from_base_and_bits(total_rounds, base, final_bits); + let final_step = nary_def.step_from_base_and_bits(total_rounds, nary_log.base_step, final_bits); - challenge_log.base_step = final_step; - challenge_log.verifier_decisions.push(final_bits); + nary_log.base_step = final_step; + nary_log.verifier_decisions.push(final_bits); info!("The prover needs to provide the full trace for the selected step {final_step}"); - challenge_log.final_trace = - program_def.get_trace_step(checkpoint_path, final_step, fail_config)?; + let final_trace = + program_def.get_trace_step(checkpoint_path, input, final_step, fail_config)?; + nary_log.final_trace = final_trace.clone(); challenge_log.save(checkpoint_path)?; - Ok(challenge_log.final_trace) + Ok(final_trace) } pub fn get_hashes( @@ -237,14 +255,18 @@ pub fn get_hashes( #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, EnumString, Display, EnumIter)] #[strum(serialize_all = "snake_case")] pub enum ForceChallenge { + CorrectHash, TraceHash, TraceHashZero, EntryPoint, ProgramCounter, Opcode, InputData, - RomData, + InitializedData, + UninitializedData, AddressesSections, + ReadValueNArySearch, + ReadValue, No, } @@ -253,10 +275,17 @@ pub enum ForceChallenge { pub enum ForceCondition { ValidInputStepAndHash, ValidInputWrongStepOrHash, - Allways, + Always, No, } +fn find_chunk_index(chunks: &[Chunk], address: u32) -> Option { + chunks.iter().position(|Chunk { base_addr, data }| { + let chunk_size = data.len(); + *base_addr <= address && address < *base_addr + chunk_size as u32 * 4 + }) +} + pub fn verifier_choose_challenge( program_definition_file: &str, checkpoint_path: &str, @@ -266,14 +295,23 @@ pub fn verifier_choose_challenge( return_script_parameters: bool, ) -> Result { let program_def = ProgramDefinition::from_config(program_definition_file)?; - let program = program_def.load_program_from_checkpoint(checkpoint_path, 0)?; + let mut program = program_def.load_program()?; let nary_def = program_def.nary_def(); - let verifier_log = VerifierChallengeLog::load(checkpoint_path)?; + + let mut verifier_log = VerifierChallengeLog::load(checkpoint_path)?; + let conflict_step_log = &mut verifier_log.conflict_step_log; + conflict_step_log.final_trace = trace.clone(); + + program.load_input( + verifier_log.input.clone(), + &program_def.input_section_name, + false, + )?; let (step_hash, next_hash) = get_hashes( - &nary_def.step_mapping(&verifier_log.verifier_decisions), - &verifier_log.prover_hash_rounds, - verifier_log.step_to_challenge, + &nary_def.step_mapping(&conflict_step_log.verifier_decisions), + &conflict_step_log.prover_hash_rounds, + conflict_step_log.step_to_challenge, ); // check trace_hash @@ -283,11 +321,11 @@ pub fn verifier_choose_challenge( || force == ForceChallenge::TraceHashZero { if trace.step_number == 1 { - info!("Veifier choose to challenge TRACE_HASH_ZERO"); + info!("Verifier choose to challenge TRACE_HASH_ZERO"); return Ok(ChallengeType::TraceHashZero(trace.trace_step, next_hash)); } - info!("Veifier choose to challenge TRACE_HASH"); + info!("Verifier choose to challenge TRACE_HASH"); return Ok(ChallengeType::TraceHash( step_hash, trace.trace_step, @@ -295,7 +333,7 @@ pub fn verifier_choose_challenge( )); } - let step = verifier_log.step_to_challenge; + let step = conflict_step_log.step_to_challenge; let mut steps = vec![step, step + 1]; let mut my_trace_idx = 1; if step > 0 { @@ -305,7 +343,13 @@ pub fn verifier_choose_challenge( //obtain all the steps needed let my_execution = program_def - .execute_helper(checkpoint_path, vec![], Some(steps), fail_config)? + .execute_helper( + checkpoint_path, + verifier_log.input.clone(), + Some(steps), + fail_config, + false, + )? .1; info!("execution: {:?}", my_execution); let my_trace = my_execution[my_trace_idx].0.clone(); @@ -334,8 +378,7 @@ pub fn verifier_choose_challenge( && force == ForceChallenge::No) || force == ForceChallenge::AddressesSections { - info!("Verifier choose to challenge invalid ADDRESS SECTION"); - + info!("Verifier choose to challenge invalid ADDRESS_SECTION"); return Ok(ChallengeType::AddressesSections( trace.read_1, trace.read_2, @@ -358,16 +401,17 @@ pub fn verifier_choose_challenge( || force == ForceChallenge::ProgramCounter { if trace.step_number == 1 { - info!("Veifier choose to challenge ENTRYPOINT"); + info!("Verifier choose to challenge ENTRYPOINT"); return Ok(ChallengeType::EntryPoint( trace.read_pc, trace.step_number, return_script_parameters.then_some(program.pc.get_address()), //this parameter is only used for the test )); } else { - info!("Veifier choose to challenge PROGRAM_COUNTER"); + info!("Verifier choose to challenge PROGRAM_COUNTER"); let pre_pre_hash = my_execution[0].1.clone(); let pre_step = my_execution[1].0.clone(); + return Ok(ChallengeType::ProgramCounter( pre_pre_hash, pre_step.trace_step, @@ -381,66 +425,202 @@ pub fn verifier_choose_challenge( || force == ForceChallenge::Opcode { info!("Verifier choose to challenge invalid OPCODE"); - let pc = trace.read_pc.pc.get_address(); - - let (chunk_index, chunk_base_addr, chunk_start) = program.get_chunk_info(pc, CODE_CHUNK_SIZE); - - let section = program.find_section(pc).unwrap(); - let chunk_end = (chunk_start + CODE_CHUNK_SIZE as usize).min(section.data.len()); - - let opcodes_chunk: Vec = section.data[chunk_start..chunk_end] - .iter() - .map(|opcode| u32::from_be(*opcode)) - .collect(); + let code_chunks = program.get_code_chunks(CHUNK_SIZE); + let chunk_index = find_chunk_index(&code_chunks, pc).unwrap(); return Ok(ChallengeType::Opcode( trace.read_pc, - chunk_index, - return_script_parameters.then_some(chunk_base_addr), - return_script_parameters.then_some(opcodes_chunk), + chunk_index as u32, + return_script_parameters.then_some(code_chunks[chunk_index].clone()), )); } + // TODO: check read_step < current_step + // check const read value - let conflict_read_1 = - trace.read_1.value != my_trace.read_1.value && trace.read_1.last_step == LAST_STEP_INIT; - let conflict_read_2 = - trace.read_2.value != my_trace.read_2.value && trace.read_2.last_step == LAST_STEP_INIT; - if conflict_read_1 || conflict_read_2 { - let conflict_address = if conflict_read_1 { - trace.read_1.address + let is_read_1_conflict = trace.read_1.value != my_trace.read_1.value; + let is_read_2_conflict = trace.read_2.value != my_trace.read_2.value; + + if ((is_read_1_conflict || is_read_2_conflict) && force == ForceChallenge::No) + || force == ForceChallenge::InputData + || force == ForceChallenge::InitializedData + || force == ForceChallenge::UninitializedData + || force == ForceChallenge::ReadValueNArySearch + { + let (conflict_read, my_conflict_read, read_selector) = if is_read_1_conflict { + (trace.read_1.clone(), my_trace.read_1.clone(), 1) } else { - trace.read_2.address + (trace.read_2.clone(), my_trace.read_2.clone(), 2) }; - let section = program.find_section(conflict_address)?; - //TODO: Check if the address is in the input section rom ram or registers - let value = program.read_mem(conflict_address)?; - if (section.name == program_def.input_section_name && force == ForceChallenge::No) + + let conflict_address = conflict_read.address; + let conflict_last_step = conflict_read.last_step; + let my_conflict_last_step = my_conflict_read.last_step; + + let section_idx = program.find_section_idx(conflict_address)?; + let section = program.sections.get(section_idx).unwrap(); + + if (conflict_last_step == LAST_STEP_INIT + && my_conflict_last_step == LAST_STEP_INIT + && force == ForceChallenge::No) || force == ForceChallenge::InputData + || force == ForceChallenge::InitializedData + || force == ForceChallenge::UninitializedData { - info!("Verifier choose to challenge invalid INPUT DATA"); - return Ok(ChallengeType::InputData( - trace.read_1.clone(), - trace.read_2.clone(), - conflict_address, - value, - )); - } else if (!section.is_write && force == ForceChallenge::No) || force == ForceChallenge::RomData { - info!("Verifier choose to challenge invalid ROM DATA"); - - return Ok(ChallengeType::RomData( - trace.read_1.clone(), - trace.read_2.clone(), - conflict_address, - value, - )); + let input_size = program_def + .inputs + .iter() + .fold(0, |acc, input| acc + input.size); + + if (section.name == program_def.input_section_name + && conflict_address < section.start + input_size as u32 + && force == ForceChallenge::No) + || force == ForceChallenge::InputData + { + info!("Verifier choose to challenge invalid INPUT DATA"); + let value = program.read_mem(conflict_address)?; + + return Ok(ChallengeType::InputData( + trace.read_1, + trace.read_2, + conflict_address, + value, + )); + } else if (section.initialized && force == ForceChallenge::No) + || force == ForceChallenge::InitializedData + { + info!("Verifier choose to challenge invalid INITIALIZED DATA"); + let initialized_chunks = program.get_initialized_chunks(CHUNK_SIZE); + let chunk_index = find_chunk_index(&initialized_chunks, conflict_address).unwrap(); + + return Ok(ChallengeType::InitializedData( + trace.read_1, + trace.read_2, + read_selector, + chunk_index as u32, + return_script_parameters.then_some(initialized_chunks[chunk_index].clone()), + )); + } else if (!section.initialized && force == ForceChallenge::No) + || force == ForceChallenge::UninitializedData + { + info!("Verifier choose to challenge invalid UNINITIALIZED DATA"); + let uninitilized_ranges = program.get_uninitialized_ranges(&program_def); + + return Ok(ChallengeType::UninitializedData( + trace.read_1, + trace.read_2, + read_selector, + return_script_parameters.then_some(uninitilized_ranges), + )); + } + } else { + let step_to_challenge = if conflict_last_step == LAST_STEP_INIT { + my_conflict_last_step + } else if my_conflict_last_step == LAST_STEP_INIT { + conflict_last_step + } else { + conflict_last_step.max(my_conflict_last_step) + }; + + let bits = nary_def.step_bits_for_round(1, step_to_challenge - 1); + + let read_challenge_log = &mut verifier_log.read_challenge_log; + read_challenge_log.step_to_challenge = step_to_challenge - 1; + read_challenge_log.base_step = nary_def.step_from_base_and_bits(1, 0, bits); + read_challenge_log.verifier_decisions.push(bits); + + read_challenge_log + .prover_hash_rounds + .push(conflict_step_log.prover_hash_rounds[0].clone()); + + read_challenge_log + .verifier_hash_rounds + .push(conflict_step_log.verifier_hash_rounds[0].clone()); + + verifier_log.read_step = step_to_challenge - 1; + verifier_log.read_selector = read_selector; + verifier_log.save(checkpoint_path)?; + + return Ok(ChallengeType::ReadValueNArySearch(bits)); } } - + verifier_log.save(checkpoint_path)?; Ok(ChallengeType::No) } +pub fn verifier_choose_challenge_for_read_challenge( + program_definition_file: &str, + checkpoint_path: &str, + fail_config: Option, + force: ForceChallenge, +) -> Result { + let program_def = ProgramDefinition::from_config(program_definition_file)?; + let nary_def = program_def.nary_def(); + let verifier_log = VerifierChallengeLog::load(checkpoint_path)?; + + let read_challenge_log = verifier_log.read_challenge_log; + let challenge_step = read_challenge_log.step_to_challenge; + + let (step_hash, next_hash) = get_hashes( + &nary_def.step_mapping(&read_challenge_log.verifier_decisions), + &read_challenge_log.prover_hash_rounds, + challenge_step, + ); + + let (my_step_hash, my_next_hash) = get_hashes( + &nary_def.step_mapping(&read_challenge_log.verifier_decisions), + &read_challenge_log.verifier_hash_rounds, + challenge_step, + ); + + assert_eq!(next_hash, my_next_hash); + + let my_execution = program_def + .execute_helper( + checkpoint_path, + verifier_log.input.clone(), + Some(vec![challenge_step + 1]), + fail_config, + false, + )? + .1; + info!("execution: {:?}", my_execution); + let my_trace = my_execution[0].0.clone(); + + if (step_hash != my_step_hash && force == ForceChallenge::No) + || force == ForceChallenge::CorrectHash + { + return Ok(ChallengeType::CorrectHash { + prover_hash: step_hash, + verifier_hash: my_step_hash, + trace: my_trace.trace_step, + next_hash, + }); + } + + let read_step = verifier_log.read_step; + if (read_step == challenge_step && force == ForceChallenge::No) + || force == ForceChallenge::ReadValue + { + let conflict_step_trace = verifier_log.conflict_step_log.final_trace; + let read_1 = conflict_step_trace.read_1; + let read_2 = conflict_step_trace.read_2; + let read_selector = verifier_log.read_selector; + + return Ok(ChallengeType::ReadValue { + read_1, + read_2, + read_selector, + step_hash, + trace: my_trace.trace_step, + next_hash, + step: challenge_step + 1, + }); + } + + Ok(ChallengeType::No) +} /* TEST CASES ---------- @@ -546,10 +726,13 @@ mod tests { input: u8, execute_err: bool, fail_config_prover: Option, + fail_config_prover_read_challenge: Option, fail_config_verifier: Option, + fail_config_verifier_read_challenge: Option, challenge_ok: bool, force_condition: ForceCondition, force: ForceChallenge, + force_read_challenge: ForceChallenge, ) { let pdf = &format!("../docker-riscv32/riscv32/build/{}", pdf); let input = vec![17, 17, 17, input]; @@ -566,6 +749,7 @@ mod tests { chk_prover_path, true, fail_config_prover.clone(), + false, ) .unwrap(); info!("{:?}", result_1); @@ -579,6 +763,7 @@ mod tests { &result_1.2, force_condition, fail_config_verifier.clone(), + false, ) .unwrap(); info!("{:?}", result); @@ -593,6 +778,7 @@ mod tests { round, v_decision, fail_config_prover.clone(), + NArySearchType::ConflictStep, ) .unwrap(); info!("{:?}", &hashes); @@ -603,6 +789,7 @@ mod tests { round, hashes, fail_config_verifier.clone(), + NArySearchType::ConflictStep, ) .unwrap(); info!("{:?}", v_decision); @@ -629,18 +816,55 @@ mod tests { let challenge = verifier_choose_challenge( pdf, - &chk_verifier_path, + chk_verifier_path, final_trace, force, fail_config_verifier, true, ) .unwrap(); - info!("Challenge: {:?}", challenge); - let result = execute_challenge(&challenge); - assert_eq!(result, challenge_ok); + let challenge = match &challenge { + ChallengeType::ReadValueNArySearch(bits) => { + let mut v_decision = *bits; + for round in 2..nary_def.total_rounds() + 1 { + let hashes = prover_get_hashes_for_round( + pdf, + chk_prover_path, + round, + v_decision, + fail_config_prover_read_challenge.clone(), + NArySearchType::ReadValueChallenge, + ) + .unwrap(); + info!("{:?}", &hashes); + + v_decision = verifier_choose_segment( + pdf, + chk_verifier_path, + round, + hashes, + fail_config_verifier_read_challenge.clone(), + NArySearchType::ReadValueChallenge, + ) + .unwrap(); + info!("{:?}", v_decision); + } + + verifier_choose_challenge_for_read_challenge( + pdf, + chk_verifier_path, + fail_config_verifier_read_challenge, + force_read_challenge, + ) + .unwrap() + } + _ => challenge, + }; + + let result = execute_challenge(&challenge); info!("Challenge: {:?} result: {}", challenge, result); + assert_eq!(result, challenge_ok); } #[test] @@ -654,9 +878,12 @@ mod tests { true, None, None, + None, + None, false, ForceCondition::No, ForceChallenge::No, + ForceChallenge::No, ); //good input: expect execute step to succeed test_challenge_aux( @@ -666,9 +893,12 @@ mod tests { false, None, None, + None, + None, false, ForceCondition::ValidInputStepAndHash, ForceChallenge::No, + ForceChallenge::No, ); } @@ -684,9 +914,12 @@ mod tests { false, fail_hash.clone(), None, + None, + None, true, ForceCondition::ValidInputWrongStepOrHash, ForceChallenge::No, + ForceChallenge::No, ); test_challenge_aux( "4", @@ -694,10 +927,13 @@ mod tests { 17, false, None, + None, fail_hash, + None, false, ForceCondition::ValidInputWrongStepOrHash, ForceChallenge::TraceHash, + ForceChallenge::No, ); } @@ -713,9 +949,12 @@ mod tests { false, fail_hash.clone(), None, + None, + None, true, ForceCondition::ValidInputWrongStepOrHash, ForceChallenge::No, + ForceChallenge::No, ); test_challenge_aux( "6", @@ -723,10 +962,13 @@ mod tests { 17, false, None, + None, fail_hash, + None, false, ForceCondition::ValidInputWrongStepOrHash, ForceChallenge::TraceHashZero, + ForceChallenge::No, ); } @@ -741,9 +983,12 @@ mod tests { false, fail_entrypoint.clone(), None, + None, + None, true, ForceCondition::ValidInputWrongStepOrHash, ForceChallenge::No, + ForceChallenge::No, ); test_challenge_aux( "8", @@ -751,10 +996,13 @@ mod tests { 17, false, None, + None, fail_entrypoint, + None, false, ForceCondition::ValidInputWrongStepOrHash, ForceChallenge::EntryPoint, + ForceChallenge::No, ); } @@ -769,9 +1017,12 @@ mod tests { false, fail_pc.clone(), None, + None, + None, true, ForceCondition::ValidInputWrongStepOrHash, ForceChallenge::No, + ForceChallenge::No, ); test_challenge_aux( "10", @@ -779,10 +1030,13 @@ mod tests { 17, false, None, + None, fail_pc, + None, false, ForceCondition::ValidInputWrongStepOrHash, ForceChallenge::ProgramCounter, + ForceChallenge::No, ); } @@ -792,7 +1046,7 @@ mod tests { let fail_args = vec![ "1106", "0xaa000000", - "0x11111111", + "0x11111100", "0xaa000000", "0xffffffffffffffff", ] @@ -806,44 +1060,31 @@ mod tests { test_challenge_aux( "11", "hello-world.yaml", - 0, + 17, false, - fail_read_2, + fail_read_2.clone(), + None, + None, None, true, - ForceCondition::No, + ForceCondition::ValidInputWrongStepOrHash, + ForceChallenge::No, ForceChallenge::No, ); - // if we use the same fail_read as before, the prover won't challenge - // because there is no hash difference, the previous fail_read reads - // the value 0x11111111 and that's what we are already reading - // because we pass 17 as input instead of 0 - let fail_args = vec![ - "1106", - "0xaa000000", - "0x11111100", // different input value - "0xaa000000", - "0xffffffffffffffff", - ] - .iter() - .map(|x| x.to_string()) - .collect::>(); - let fail_read_2 = Some(FailConfiguration::new_fail_reads(FailReads::new( - None, - Some(&fail_args), - ))); - test_challenge_aux( "12", "hello-world.yaml", 17, false, None, + None, fail_read_2, + None, false, - ForceCondition::ValidInputStepAndHash, + ForceCondition::No, ForceChallenge::InputData, + ForceChallenge::No, ); } @@ -881,9 +1122,12 @@ mod tests { false, fail_execute, None, + None, + None, true, ForceCondition::No, ForceChallenge::No, + ForceChallenge::No, ); let fail_args = vec![ @@ -907,10 +1151,13 @@ mod tests { 17, false, None, + None, fail_read_2, + None, false, ForceCondition::No, ForceChallenge::AddressesSections, + ForceChallenge::No, ); } @@ -948,9 +1195,12 @@ mod tests { false, fail_execute, None, + None, + None, true, ForceCondition::No, ForceChallenge::No, + ForceChallenge::No, ); let fail_args = vec![ @@ -974,10 +1224,13 @@ mod tests { 17, false, None, + None, fail_read_2, + None, false, ForceCondition::No, ForceChallenge::AddressesSections, + ForceChallenge::No, ); } @@ -1012,9 +1265,12 @@ mod tests { false, fail_execute, None, + None, + None, true, ForceCondition::No, ForceChallenge::No, + ForceChallenge::No, ); let fail_args = vec!["1106", "0xaa000000", "0x11111100", "0x00000000"] @@ -1031,10 +1287,13 @@ mod tests { 17, false, None, + None, fail_write, + None, false, - ForceCondition::No, + ForceCondition::ValidInputWrongStepOrHash, ForceChallenge::AddressesSections, + ForceChallenge::No, ); } @@ -1072,9 +1331,12 @@ mod tests { false, fail_execute, None, + None, + None, true, ForceCondition::No, ForceChallenge::No, + ForceChallenge::No, ); let fail_args = vec!["1106", "0xaa000000", "0x11111100", "0xf0000004"] .iter() @@ -1090,10 +1352,13 @@ mod tests { 17, false, None, + None, fail_write, + None, false, - ForceCondition::No, + ForceCondition::ValidInputWrongStepOrHash, ForceChallenge::AddressesSections, + ForceChallenge::No, ); } @@ -1131,9 +1396,12 @@ mod tests { false, fail_execute, None, + None, + None, true, ForceCondition::No, ForceChallenge::No, + ForceChallenge::No, ); let fail_args = vec!["1106", "0xaa000000", "0x11111100", "0x80000000"] @@ -1150,10 +1418,13 @@ mod tests { 17, false, None, + None, fail_write, + None, false, - ForceCondition::No, + ForceCondition::ValidInputWrongStepOrHash, ForceChallenge::AddressesSections, + ForceChallenge::No, ); } @@ -1188,9 +1459,12 @@ mod tests { false, fail_execute.clone(), None, + None, + None, true, ForceCondition::No, ForceChallenge::No, + ForceChallenge::No, ); test_challenge_aux( @@ -1199,10 +1473,13 @@ mod tests { 17, false, None, + None, fail_execute, + None, false, - ForceCondition::ValidInputWrongStepOrHash, + ForceCondition::No, ForceChallenge::AddressesSections, + ForceChallenge::No, ); } @@ -1237,9 +1514,12 @@ mod tests { false, fail_execute.clone(), None, + None, + None, true, ForceCondition::No, ForceChallenge::No, + ForceChallenge::No, ); test_challenge_aux( @@ -1248,10 +1528,13 @@ mod tests { 17, false, None, + None, fail_execute, + None, false, - ForceCondition::ValidInputWrongStepOrHash, + ForceCondition::No, ForceChallenge::AddressesSections, + ForceChallenge::No, ); } @@ -1274,9 +1557,12 @@ mod tests { false, fail_opcode.clone(), None, + None, + None, true, ForceCondition::ValidInputWrongStepOrHash, ForceChallenge::No, + ForceChallenge::No, ); test_challenge_aux( @@ -1285,15 +1571,18 @@ mod tests { 17, false, None, + None, fail_opcode, + None, false, ForceCondition::ValidInputWrongStepOrHash, ForceChallenge::Opcode, + ForceChallenge::No, ); } #[test] - fn test_challenge_rom() { + fn test_challenge_initialized() { init_trace(); let fail_execute = FailExecute { step: 32, @@ -1302,7 +1591,10 @@ mod tests { TraceRead::new(4026531900, 2952790016, 31), TraceRead::new(2952790016, 0, LAST_STEP_INIT), // read a different value from ROM TraceReadPC::new(ProgramCounter::new(2147483708, 0), 509699), - TraceStep::new(TraceWrite::new(4026531896, 0), ProgramCounter::new(2147483712, 0)), + TraceStep::new( + TraceWrite::new(4026531896, 0), + ProgramCounter::new(2147483712, 0), + ), None, MemoryWitness::new( MemoryAccessType::Register, @@ -1321,9 +1613,12 @@ mod tests { false, fail_execute.clone(), None, + None, + None, true, ForceCondition::ValidInputWrongStepOrHash, ForceChallenge::No, + ForceChallenge::No, ); test_challenge_aux( @@ -1332,10 +1627,205 @@ mod tests { 17, false, None, + None, fail_execute, + None, + false, + ForceCondition::ValidInputWrongStepOrHash, + ForceChallenge::InitializedData, + ForceChallenge::No, + ); + } + + #[test] + fn test_challenge_uninitialized() { + init_trace(); + + let fail_args = vec![ + "9", + "0xa0001004", + "0x11111100", + "0xa0001004", + "0xffffffffffffffff", + ] + .iter() + .map(|x| x.to_string()) + .collect::>(); + let fail_read_2 = Some(FailConfiguration::new_fail_reads(FailReads::new( + None, + Some(&fail_args), + ))); + + test_challenge_aux( + "31", + "hello-world-uninitialized.yaml", + 0, + false, + fail_read_2.clone(), + None, + None, + None, + true, + ForceCondition::No, + ForceChallenge::No, + ForceChallenge::No, + ); + + test_challenge_aux( + "32", + "hello-world-uninitialized.yaml", + 17, + false, + None, + None, + fail_read_2, + None, + false, + ForceCondition::ValidInputWrongStepOrHash, + ForceChallenge::UninitializedData, + ForceChallenge::No, + ); + } + + #[test] + fn test_challenge_modified_value_lies_all_hashes_from_write_step() { + init_trace(); + let fail_read_args = vec!["1106", "0xaa000000", "0x11111100", "0xaa000000", "600"] + .iter() + .map(|x| x.to_string()) + .collect::>(); + + let fail_read_2 = Some(FailConfiguration::new_fail_reads(FailReads::new( + None, + Some(&fail_read_args), + ))); + + let fail_write_args = vec!["600", "0xaa000000", "0x11111100", "0xaa000000"] + .iter() + .map(|x| x.to_string()) + .collect::>(); + let fail_write = Some(FailConfiguration::new_fail_write(FailWrite::new( + &fail_write_args, + ))); + + test_challenge_aux( + "35", + "hello-world.yaml", + 17, + false, + fail_read_2.clone(), + fail_write.clone(), + None, + None, + true, + ForceCondition::ValidInputWrongStepOrHash, + ForceChallenge::No, + ForceChallenge::No, + ); + + test_challenge_aux( + "36", + "hello-world.yaml", + 17, + false, + None, + None, + fail_read_2, + fail_write, false, ForceCondition::ValidInputWrongStepOrHash, - ForceChallenge::RomData, + ForceChallenge::ReadValueNArySearch, + ForceChallenge::TraceHash, + ); + } + #[test] + fn test_challenge_modified_value_lies_hashes_until_step() { + init_trace(); + let fail_read_args = vec!["1106", "0xaa000000", "0x11111100", "0xaa000000", "600"] + .iter() + .map(|x| x.to_string()) + .collect::>(); + + let fail_read_2 = Some(FailConfiguration::new_fail_reads(FailReads::new( + None, + Some(&fail_read_args), + ))); + + let fail_hash_until = Some(FailConfiguration::new_fail_hash_until(700)); + + test_challenge_aux( + "37", + "hello-world.yaml", + 17, + false, + fail_read_2.clone(), + fail_hash_until.clone(), + None, + None, + true, + ForceCondition::ValidInputWrongStepOrHash, + ForceChallenge::No, + ForceChallenge::No, + ); + + test_challenge_aux( + "38", + "hello-world.yaml", + 17, + false, + None, + None, + fail_read_2, + fail_hash_until, + false, + ForceCondition::ValidInputWrongStepOrHash, + ForceChallenge::ReadValueNArySearch, + ForceChallenge::TraceHash, + ); + } + + // TODO: add case for write to the same address and different value + #[test] + fn test_challenge_modified_value_doesnt_lie() { + init_trace(); + let fail_read_args = vec!["1106", "0xaa000000", "0x11111100", "0xaa000000", "600"] + .iter() + .map(|x| x.to_string()) + .collect::>(); + + let fail_read_2 = Some(FailConfiguration::new_fail_reads(FailReads::new( + None, + Some(&fail_read_args), + ))); + + test_challenge_aux( + "39", + "hello-world.yaml", + 17, + false, + fail_read_2.clone(), + None, + None, + None, + true, + ForceCondition::ValidInputWrongStepOrHash, + ForceChallenge::No, + ForceChallenge::No, + ); + + test_challenge_aux( + "40", + "hello-world.yaml", + 17, + false, + None, + None, + fail_read_2, + None, + false, + ForceCondition::ValidInputWrongStepOrHash, + ForceChallenge::ReadValueNArySearch, + ForceChallenge::ReadValue, ); } @@ -1352,9 +1842,12 @@ mod tests { false, Some(fail_mem_protection), None, + None, + None, true, ForceCondition::No, ForceChallenge::No, + ForceChallenge::No, ); } } diff --git a/emulator/src/decision/execution_log.rs b/emulator/src/decision/execution_log.rs index 3b8c33c..d93cd8a 100644 --- a/emulator/src/decision/execution_log.rs +++ b/emulator/src/decision/execution_log.rs @@ -1,7 +1,7 @@ use bitvmx_cpu_definitions::trace::TraceRWStep; use serde::{Deserialize, Serialize}; -use crate::{EmulatorError, ExecutionResult}; +use crate::{decision::nary_search::NArySearchType, EmulatorError, ExecutionResult}; #[derive(Debug, Serialize, Deserialize)] pub struct ExecutionLog { @@ -20,25 +20,29 @@ impl ExecutionLog { } } -#[derive(Debug, Serialize, Deserialize)] -pub struct ProverChallengeLog { - pub execution: ExecutionLog, - pub input: Vec, +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct ProverNAryLog { pub base_step: u64, pub verifier_decisions: Vec, pub hash_rounds: Vec>, pub final_trace: TraceRWStep, } +#[derive(Debug, Serialize, Deserialize)] +pub struct ProverChallengeLog { + pub execution: ExecutionLog, + pub input: Vec, + pub conflict_step_log: ProverNAryLog, + pub read_challenge_log: ProverNAryLog, +} + impl ProverChallengeLog { pub fn new(execution: ExecutionLog, input: Vec) -> Self { Self { execution, input, - base_step: 0, - verifier_decisions: Vec::new(), - hash_rounds: Vec::new(), - final_trace: TraceRWStep::default(), + conflict_step_log: ProverNAryLog::default(), + read_challenge_log: ProverNAryLog::default(), } } @@ -49,13 +53,17 @@ impl ProverChallengeLog { pub fn load(path: &str) -> Result { deserialize_challenge_log(path) } + + pub fn get_nary_log(&mut self, nary_search: NArySearchType) -> &mut ProverNAryLog { + match nary_search { + NArySearchType::ConflictStep => &mut self.conflict_step_log, + NArySearchType::ReadValueChallenge => &mut self.read_challenge_log, + } + } } -#[derive(Debug, Serialize, Deserialize)] -pub struct VerifierChallengeLog { - pub prover_claim_execution: ExecutionLog, - pub execution: ExecutionLog, - pub input: Vec, +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct VerifierNAryLog { pub base_step: u64, pub step_to_challenge: u64, pub verifier_decisions: Vec, @@ -64,6 +72,26 @@ pub struct VerifierChallengeLog { pub final_trace: TraceRWStep, } +impl VerifierNAryLog { + pub fn new(step_to_challenge: u64) -> Self { + Self { + step_to_challenge, + ..Default::default() + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct VerifierChallengeLog { + pub prover_claim_execution: ExecutionLog, + pub execution: ExecutionLog, + pub input: Vec, + pub conflict_step_log: VerifierNAryLog, + pub read_challenge_log: VerifierNAryLog, + pub read_selector: u32, + pub read_step: u64, +} + impl VerifierChallengeLog { pub fn new( prover_execution: ExecutionLog, @@ -75,12 +103,10 @@ impl VerifierChallengeLog { prover_claim_execution: prover_execution, execution, input, - base_step: 0, - step_to_challenge, - verifier_decisions: Vec::new(), - prover_hash_rounds: Vec::new(), - verifier_hash_rounds: Vec::new(), - final_trace: TraceRWStep::default(), + conflict_step_log: VerifierNAryLog::new(step_to_challenge), + read_challenge_log: VerifierNAryLog::default(), + read_selector: 0, + read_step: 0, } } @@ -91,6 +117,13 @@ impl VerifierChallengeLog { pub fn load(path: &str) -> Result { deserialize_challenge_log(path) } + + pub fn get_nary_log(&mut self, nary_search: NArySearchType) -> &mut VerifierNAryLog { + match nary_search { + NArySearchType::ConflictStep => &mut self.conflict_step_log, + NArySearchType::ReadValueChallenge => &mut self.read_challenge_log, + } + } } pub fn serialize_challenge_log(path: &str, data: &T) -> Result<(), EmulatorError> { diff --git a/emulator/src/decision/nary_search.rs b/emulator/src/decision/nary_search.rs index 472c38c..86a5d8a 100644 --- a/emulator/src/decision/nary_search.rs +++ b/emulator/src/decision/nary_search.rs @@ -1,8 +1,35 @@ -use std::collections::HashMap; +use std::{collections::HashMap, str::FromStr}; -use serde::Serialize; +use clap::ValueEnum; +use serde::{Deserialize, Serialize}; use tracing::{error, info}; +#[derive(Clone, Copy, PartialEq, ValueEnum, Serialize, Deserialize, Debug)] +pub enum NArySearchType { + ConflictStep, + ReadValueChallenge, +} +impl ToString for NArySearchType { + fn to_string(&self) -> String { + match self { + NArySearchType::ConflictStep => "conflict-step".to_string(), + NArySearchType::ReadValueChallenge => "read-value-challenge".to_string(), + } + } +} + +impl FromStr for NArySearchType { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "conflict-step" => Ok(NArySearchType::ConflictStep), + "read-value-challenge" => Ok(NArySearchType::ReadValueChallenge), + _ => Err(format!("'{}' is not a valid NArySearchType", s)), + } + } +} + #[derive(Debug, Clone, Serialize)] pub struct NArySearchDefinition { pub max_steps: u64, @@ -157,19 +184,26 @@ pub fn choose_segment( round: u8, prover_hashes: &ExecutionHashes, my_hashes: &ExecutionHashes, + nary_type: NArySearchType, ) -> (u32, u64, u64) { if prover_hashes.hashes.len() != my_hashes.hashes.len() { error!("Prover and my hashes should have the same length"); } // finds if there is any difference in the hashes - let mut selection = prover_hashes.hashes.len() + 1; + let mut selection = if nary_type == NArySearchType::ConflictStep { + prover_hashes.hashes.len() + 1 + } else { + 1 + }; for i in 0..prover_hashes.hashes.len() { let prover_hash = &prover_hashes.hashes[i]; let my_hash = &my_hashes.hashes[i]; if prover_hash != my_hash { selection = i + 1; - break; + if nary_type == NArySearchType::ConflictStep { + break; + }; } } @@ -177,12 +211,25 @@ pub fn choose_segment( //println!("Selection: {}", selection); let mismatch_step = nary_defs.step_from_base_and_bits(round, base_step, selection as u32) - 1; //println!("Mismatch step: {}", mismatch_step); - let lower_limit_bits = if selected_step < mismatch_step { - nary_defs.step_bits_for_round(round, selected_step) + let (lower_limit_bits, choice) = if selected_step < mismatch_step { + if nary_type == NArySearchType::ConflictStep { + ( + nary_defs.step_bits_for_round(round, selected_step), + selected_step, + ) + } else { + (selection as u32, mismatch_step + 1) + } } else { - selection as u32 - 1 + if nary_type == NArySearchType::ConflictStep { + (selection as u32 - 1, mismatch_step) + } else { + ( + nary_defs.step_bits_for_round(round, selected_step), + selected_step, + ) + } }; - let choice = mismatch_step.min(selected_step); //println!("Lower limit bits: {}", lower_limit_bits); let base_step = nary_defs.step_from_base_and_bits(round, base_step, lower_limit_bits); @@ -331,6 +378,7 @@ mod tests { round, &prover_hashes.into(), &my_hashes.into(), + NArySearchType::ConflictStep, ); assert_eq!(bits, exp_bits); assert_eq!(base, exp_step); diff --git a/emulator/src/executor/fetcher.rs b/emulator/src/executor/fetcher.rs index 97b52d3..ea4974e 100644 --- a/emulator/src/executor/fetcher.rs +++ b/emulator/src/executor/fetcher.rs @@ -32,24 +32,17 @@ pub fn execute_program( trace_list: Option>, mem_dump: Option, fail_config: FailConfiguration, + save_non_checkpoint_steps: bool, ) -> (ExecutionResult, FullTrace) { let trace_set: Option> = trace_list.map(|vec| vec.into_iter().collect()); let mut traces = Vec::new(); - if !input.is_empty() { - if let Some(section) = program.find_section_by_name(input_section_name) { - let input_as_u32 = vec_u8_to_vec_u32(&input, little_endian); - for (i, byte) in input_as_u32.iter().enumerate() { - section.data[i] = *byte; - } - } else { - return ( - ExecutionResult::SectionNotFound(input_section_name.to_string()), - traces, - ); - } + let load_input_result = program.load_input(input.clone(), input_section_name, little_endian); + if load_input_result.is_err() { + return (load_input_result.err().unwrap(), traces); } + let instruction_mapping = match verify_on_chain && use_instruction_mapping { true => Some(create_verification_script_mapping( program.registers.get_base_address(), @@ -64,7 +57,9 @@ pub fn execute_program( if let Some(path) = &checkpoint_path { //create path if it does not exist std::fs::create_dir_all(path).unwrap(); - program.serialize_to_file(path); + if save_non_checkpoint_steps { + program.serialize_to_file(path); + } } if print_trace && (trace_set.is_none() || trace_set.as_ref().unwrap().contains(&program.step)) { @@ -181,7 +176,9 @@ pub fn execute_program( } if let Some(path) = &checkpoint_path { - if program.step % CHECKPOINT_SIZE == 0 || trace.is_err() || program.halt { + if program.step % CHECKPOINT_SIZE == 0 + || ((trace.is_err() || program.halt) && save_non_checkpoint_steps) + { program.serialize_to_file(path); } } diff --git a/emulator/src/executor/utils.rs b/emulator/src/executor/utils.rs index 1ca872d..b4d5138 100644 --- a/emulator/src/executor/utils.rs +++ b/emulator/src/executor/utils.rs @@ -152,7 +152,7 @@ pub struct FailWrite { impl FailWrite { pub fn new(args: &Vec) -> Self { Self { - step: parse_value::(&args[0]) - 1, + step: parse_value::(&args[0]), address_original: parse_value::(&args[1]), value: parse_value::(&args[2]), modified_address: parse_value::(&args[3]), @@ -219,6 +219,7 @@ impl FailOpcode { #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct FailConfiguration { pub fail_hash: Option, + pub fail_hash_until: Option, pub fail_execute: Option, pub fail_reads: Option, pub fail_write: Option, @@ -234,6 +235,12 @@ impl FailConfiguration { ..Default::default() } } + pub fn new_fail_hash_until(step: u64) -> Self { + Self { + fail_hash_until: Some(step), + ..Default::default() + } + } pub fn new_fail_execute(fail_execute: FailExecute) -> Self { Self { fail_execute: Some(fail_execute), @@ -414,7 +421,7 @@ mod utils_tests { "4026531900".to_string(), ]; let fail_write = FailWrite::new(&fail_write_args); - program.step = 9; + program.step = 10; fail_write.patch_mem(&mut program); let idx = program.registers.get_original_idx(4026531900); @@ -442,7 +449,7 @@ mod utils_tests { "4096".to_string(), ]; let fail_write = FailWrite::new(&fail_write_args); - program.step = 9; + program.step = 10; fail_write.patch_mem(&mut program); assert_eq!(program.read_mem(4096).unwrap(), 10); diff --git a/emulator/src/loader/program.rs b/emulator/src/loader/program.rs index 3086aaf..e390a7d 100644 --- a/emulator/src/loader/program.rs +++ b/emulator/src/loader/program.rs @@ -5,7 +5,7 @@ use bitcoin_script_riscv::riscv::instruction_mapping::{ }; use bitvmx_cpu_definitions::{ constants::LAST_STEP_INIT, - memory::{MemoryAccessType, SectionDefinition}, + memory::{Chunk, MemoryAccessType, SectionDefinition}, trace::{generate_initial_step_hash, ProgramCounter, TraceRead, TraceWrite}, }; use elf::{abi::SHF_EXECINSTR, abi::SHF_WRITE, endian::LittleEndian, ElfBytes}; @@ -13,7 +13,9 @@ use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; use tracing::{error, info}; -use crate::{constants::*, EmulatorError, ExecutionResult}; +use crate::{ + constants::*, loader::program_definition::ProgramDefinition, EmulatorError, ExecutionResult, +}; #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct Section { @@ -76,17 +78,12 @@ impl Section { (self.start, self.start + self.size - 1) } - pub fn contains(&self, address: u32) -> bool { - let (start, end) = self.range(); - return address >= start && address <= end - 3; - } - pub fn is_merge_compatible(&self, other: &Self) -> bool { - return self.is_code == other.is_code + self.is_code == other.is_code && self.is_write == other.is_write && self.initialized == other.initialized && self.registers == other.registers - && self.start + self.size == other.start; + && self.start + self.size == other.start } pub fn merge_in_place(&mut self, other: Self) { @@ -203,6 +200,30 @@ pub struct Program { } impl Program { + pub fn load_input( + &mut self, + input: Vec, + input_section_name: &str, + little_endian: bool, + ) -> Result<(), ExecutionResult> { + // if the step is non-zero then we are running the program from a checkpoint + // so we shouldn't rewrite the input. First, because it's already included in the checkpoint + // and second, because the input section is writable and the value could've been changed + if !input.is_empty() && self.step == 0 { + if let Some(section) = self.find_section_by_name_mut(input_section_name) { + let input_as_u32 = vec_u8_to_vec_u32(&input, little_endian); + for (i, byte) in input_as_u32.iter().enumerate() { + section.data[i] = *byte; + } + } else { + return Err(ExecutionResult::SectionNotFound( + input_section_name.to_string(), + )); + } + } + + Ok(()) + } pub fn serialize_to_file(&self, fpath: &str) { let fname = format!("{}/checkpoint.{}.json", fpath, self.step); let serialized = serde_json::to_string(self).unwrap(); @@ -256,7 +277,7 @@ impl Program { self.sections = merged; } - pub fn generate_sections_definitions(&mut self) { + fn generate_sections_definitions(&mut self) { for section in &self.sections { let section_range = section.range(); @@ -273,7 +294,7 @@ impl Program { } } - pub fn sanity_check(&self) -> Result<(), EmulatorError> { + pub fn sanity_check(&self, sp_base_address: Option) -> Result<(), EmulatorError> { //check overlapping sections for i in 0..self.sections.len() { for j in i + 1..self.sections.len() { @@ -289,6 +310,44 @@ impl Program { } } } + + let low_section = self.sections.iter().find(|section| section.start < 0x1000); + if let Some(low_section) = low_section { + return Err(EmulatorError::CantLoadPorgram(format!( + "Cannot load program: section '{}' starts at a low memory address (0x{:X}), which is below the allowed threshold of 0x1000.", + low_section.name, + low_section.start, + ))); + } + + if sp_base_address.is_none() { + return Ok(()); + } + + let stack_section = self.find_section(sp_base_address.unwrap()); + + if stack_section.is_err() { + return Ok(()); + } + + let stack_section = stack_section.unwrap(); + + let section_next_to_stack = self.sections.iter().find(|&other_section| { + let stack_section_end = stack_section.start + stack_section.size; + let other_section_end = other_section.start + other_section.size; + + (other_section != stack_section) + && (stack_section_end == other_section.start + || other_section_end == stack_section.start) + }); + + if let Some(section_next_to_stack) = section_next_to_stack { + return Err(EmulatorError::CantLoadPorgram(format!( + "Cannot load program: section '{}' is next to the stack section and a stack overflow could corrupt its content", + section_next_to_stack.name + ))); + } + Ok(()) } @@ -300,10 +359,9 @@ impl Program { self.sections.insert(pos, section); } - fn find_section_idx(&self, address: u32) -> Result { + pub fn find_section_idx(&self, address: u32) -> Result { // Binary search to find the appropriate section - Ok(self - .sections + self.sections .binary_search_by(|section| { if address < section.start { Ordering::Greater @@ -318,7 +376,7 @@ impl Program { "Address 0x{:08x} not found in any section", address )) - })?) + }) } // Find the section that contains the given address @@ -351,7 +409,11 @@ impl Program { Ok(section) } - pub fn find_section_by_name(&mut self, name: &str) -> Option<&mut Section> { + pub fn find_section_by_name(&self, name: &str) -> Option<&Section> { + self.sections.iter().find(|section| section.name == name) + } + + pub fn find_section_by_name_mut(&mut self, name: &str) -> Option<&mut Section> { self.sections .iter_mut() .find(|section| section.name == name) @@ -467,88 +529,79 @@ impl Program { } } - pub fn get_chunks(&self, chunk_size: u32) -> Vec<(u32, Vec)> { - let mut chunks = Vec::new(); - - for section in &self.sections { - if !section.is_code { - continue; - } - - for (index, chunk) in section.data.chunks(chunk_size as usize).enumerate() { - chunks.push(( - section.start + index as u32 * chunk_size, - chunk - .to_vec() - .iter() - .map(|opcode| u32::from_be(*opcode)) - .collect(), - )); - } - } - - chunks + pub fn get_chunks(&self, chunk_size: u32, filter: impl Fn(&&Section) -> bool) -> Vec { + self.sections + .iter() + .filter(filter) + .flat_map(|section| { + section + .data + .chunks(chunk_size as usize) + .enumerate() + .map(|(index, chunk)| { + let offset = section.start + index as u32 * chunk_size; + let data = chunk.iter().map(|opcode| u32::from_be(*opcode)).collect(); + Chunk { + base_addr: offset, + data, + } + }) + }) + .collect() } - // Avoids calling get_chunks().len() to prevent unnecessary Vec allocations and cloning - pub fn get_chunk_count(&self, chunk_size: u32) -> u32 { - let mut chunk_count = 0; - - for section in &self.sections { - if !section.is_code { - continue; - } - - let section_instrs = section.size / 4; - // only counts full chunks - let mut section_chunks = section_instrs / chunk_size; - - // if section_instrs isn't a multiple of chunk_size that means that there is a non-full chunk we have to count - if section_instrs % chunk_size != 0 { - section_chunks += 1; - } - - chunk_count += section_chunks; - } - - chunk_count + pub fn get_initialized_chunks(&self, chunk_size: u32) -> Vec { + self.get_chunks(chunk_size, |section| { + section.initialized && !section.is_code + }) } - pub fn get_chunk_info(&self, address: u32, chunk_size: u32) -> (u32, u32, usize) { - let mut chunk_index = 0; - - for section in &self.sections { - if !section.is_code { - continue; - } - - if section.contains(address) { - let section_start = section.start; - let offset = address - section_start; - let instr_index = offset / 4; + pub fn get_code_chunks(&self, chunk_size: u32) -> Vec { + self.get_chunks(chunk_size, |section| section.is_code) + } - chunk_index += instr_index / chunk_size; + pub fn get_uninitialized_ranges( + &self, + program_definition: &ProgramDefinition, + ) -> SectionDefinition { + let mut uninitialized: Vec<(u32, u32)> = self + .sections + .iter() + .filter(|section| { + // we do inputs separately + !section.initialized && section.name != program_definition.input_section_name + }) + .map(|section| section.range()) + .collect(); - let chunk_start_instr = instr_index - (instr_index % chunk_size); - let chunk_base_addr = section_start + chunk_start_instr * 4; - let chunk_start_index = chunk_start_instr as usize; + let input_section = self + .find_section_by_name(&program_definition.input_section_name) + .expect("Input section not found"); - return (chunk_index, chunk_base_addr, chunk_start_index); - } + // input section is usually bigger than the actual input of the program, so the remaining space should be uninitialized + let input_size = program_definition + .inputs + .iter() + .fold(0, |acc, input| acc + input.size); - let section_instrs = section.size / 4; - // only counts full chunks - let mut section_chunks = section_instrs / chunk_size; + let (start, end) = input_section.range(); + let uninit_start = start + input_size as u32; - // if section_instrs isn't a multiple of chunk_size that means that there is a non-full chunk we have to count - if section_instrs % chunk_size != 0 { - section_chunks += 1; - } + assert!( + uninit_start <= end, + "Input size ({}) exceeds input section size ({}..{})", + input_size, + start, + end + ); - chunk_index += section_chunks; + if uninit_start < end { + uninitialized.push((uninit_start, end)); } - unreachable!("Non-executable address: 0x{:08X}", address); + SectionDefinition { + ranges: uninitialized, + } } } @@ -660,9 +713,9 @@ pub fn load_elf(fname: &str, show_sections: bool) -> Result, steps: Option>, fail_config: Option, + save_non_checkpoint_steps: bool, ) -> Result<(ExecutionResult, FullTrace), EmulatorError> { let checkpoint_path_str = checkpoint_path.to_string(); let (mut program, checkpoint_path, output_trace) = match &steps { @@ -117,6 +116,7 @@ impl ProgramDefinition { steps, None, fail_config.unwrap_or_default(), + save_non_checkpoint_steps, )) } @@ -125,9 +125,15 @@ impl ProgramDefinition { input_data: Vec, checkpoint_path: &str, fail_config: Option, + save_non_checkpoint_steps: bool, ) -> Result<(ExecutionResult, u64, String), EmulatorError> { - let (result, trace) = - self.execute_helper(checkpoint_path, input_data, None, fail_config)?; + let (result, trace) = self.execute_helper( + checkpoint_path, + input_data, + None, + fail_config, + save_non_checkpoint_steps, + )?; if trace.len() == 0 { return Err(EmulatorError::CantObtainTrace); @@ -145,6 +151,7 @@ impl ProgramDefinition { pub fn get_round_hashes( &self, checkpoint_path: &str, + input: Vec, round: u8, base: u64, fail_config: Option, @@ -157,8 +164,13 @@ impl ProgramDefinition { let required_hashes = steps.len(); steps.insert(0, base); //asks base step as it should be always obtainable - let (_result, trace) = - self.execute_helper(checkpoint_path, vec![], Some(steps), fail_config)?; + let (_result, trace) = self.execute_helper( + checkpoint_path, + input, + Some(steps), + fail_config.clone(), + false, + )?; // at least the base step should be present if trace.len() == 0 { return Err(EmulatorError::CantObtainTrace); @@ -167,7 +179,27 @@ impl ProgramDefinition { // if there are actual steps skip the first one let skip = if trace.len() > 1 { 1 } else { 0 }; - let mut ret: Vec = trace.iter().skip(skip).map(|t| t.1.clone()).collect(); + let fail_config = &fail_config.unwrap_or_default(); + + let mut ret: Vec = trace + .iter() + .skip(skip) + .map(|t| { + let mut hash = t.1.clone(); + + if let Some(step) = fail_config.fail_hash_until { + if t.0.step_number < step { + let mut decoded = hex::decode(hash).unwrap(); + decoded[0] = decoded[0].wrapping_add(1); + + let new_hash: [u8; 20] = decoded.try_into().unwrap(); + hash = hash_to_string(&new_hash); + } + } + + hash + }) + .collect(); let obtained_hashes = ret.len(); assert!(obtained_hashes <= required_hashes); @@ -181,12 +213,13 @@ impl ProgramDefinition { pub fn get_trace_step( &self, checkpoint_path: &str, + input: Vec, step: u64, fail_config: Option, ) -> Result { let steps = vec![step]; let (_result, trace) = - self.execute_helper(checkpoint_path, vec![], Some(steps), fail_config)?; + self.execute_helper(checkpoint_path, input, Some(steps), fail_config, false)?; // at least the base step should be present if trace.len() == 0 { return Err(EmulatorError::CantObtainTrace); diff --git a/emulator/src/main.rs b/emulator/src/main.rs index eefaeda..5643cab 100644 --- a/emulator/src/main.rs +++ b/emulator/src/main.rs @@ -3,9 +3,14 @@ use bitvmx_cpu_definitions::{challenge::EmulatorResultType, trace::TraceRWStep}; use clap::{Parser, Subcommand}; use emulator::{ constants::REGISTERS_BASE_ADDRESS, - decision::challenge::{ - prover_execute, prover_final_trace, prover_get_hashes_for_round, verifier_check_execution, - verifier_choose_challenge, verifier_choose_segment, ForceChallenge, ForceCondition, + decision::{ + challenge::{ + prover_execute, prover_final_trace, prover_get_hashes_for_round, + verifier_check_execution, verifier_choose_challenge, + verifier_choose_challenge_for_read_challenge, verifier_choose_segment, ForceChallenge, + ForceCondition, + }, + nary_search::NArySearchType, }, executor::{ fetcher::execute_program, @@ -52,6 +57,10 @@ enum Commands { /// Command File to write the result #[arg(short, long, value_name = "COMMAND_PATH")] command_file: String, + + /// Should we save steps that are not checkpoints (like first, error and halt steps) + #[arg(short, long, default_value = "true")] + save_non_checkpoint_steps: bool, }, VerifierCheckExecution { @@ -86,6 +95,10 @@ enum Commands { /// Command File to write the result #[arg(short, long, value_name = "COMMAND_PATH")] command_file: String, + + /// Should we save steps that are not checkpoints (like first, error and halt steps) + #[arg(short, long, default_value = "true")] + save_non_checkpoint_steps: bool, }, ProverGetHashesForRound { @@ -112,6 +125,10 @@ enum Commands { /// Command File to write the result #[arg(short, long, value_name = "COMMAND_PATH")] command_file: String, + + /// Nary Search type + #[arg(short, long, value_name = "NARY_TYPE")] + nary_type: NArySearchType, }, VerifierChooseSegment { @@ -138,6 +155,10 @@ enum Commands { /// Command File to write the result #[arg(short, long, value_name = "COMMAND_PATH")] command_file: String, + + /// Nary Search type + #[arg(short, long, value_name = "NARY_TYPE")] + nary_type: NArySearchType, }, ProverFinalTrace { @@ -188,6 +209,28 @@ enum Commands { command_file: String, }, + VerifierChooseChallengeForReadChallenge { + /// Yaml file to load + #[arg(short, long, value_name = "FILE")] + pdf: String, + + /// Checkpoint verifier path + #[arg(short, long, value_name = "CHECKPOINT_VERIFIER_PATH")] + checkpoint_verifier_path: String, + + /// Fail Configuration + #[arg(short, long, value_name = "FailConfigVerifier")] + fail_config_verifier: Option, + + /// Force + #[arg(short, long, default_value = "no")] + force: ForceChallenge, + + /// Command File to write the result + #[arg(short, long, value_name = "COMMAND_PATH")] + command_file: String, + }, + ///Generate the instruction mapping InstructionMapping, @@ -265,6 +308,12 @@ enum Commands { #[arg(long)] fail_hash: Option, + /// Fail producing hash but only for steps until a specific one. + /// fail_hash will propagate the error to the next steps due to the hash of a step depending on the previous hash. + /// this one doesn't since we modify the hash after all the hashes have been calculated in get_round_hashes + #[arg(long)] + fail_hash_until: Option, + /// Fail producing the write value for a specific step #[arg(long, value_names = &["step", "fake_trace"], num_args = 2)] fail_execute: Option>, @@ -296,6 +345,10 @@ enum Commands { /// Memory dump at given step #[arg(short, long)] dump_mem: Option, + + /// Should we save steps that are not checkpoints (like first, error and halt steps) + #[arg(short, long, default_value = "true")] + save_non_checkpoint_steps: bool, }, } @@ -340,6 +393,7 @@ fn main() -> Result<(), EmulatorError> { sections, checkpoint_path, fail_hash, + fail_hash_until, fail_execute: fail_execute_args, list, fail_read_1: fail_read_1_args, @@ -348,6 +402,7 @@ fn main() -> Result<(), EmulatorError> { fail_opcode: fail_opcode_args, dump_mem, fail_pc, + save_non_checkpoint_steps, }) => { if elf.is_none() && step.is_none() { error!("To execute an elf file or a checkpoint step is required"); @@ -409,6 +464,7 @@ fn main() -> Result<(), EmulatorError> { let debugvar = *debug; let fail_config = FailConfiguration { fail_hash: *fail_hash, + fail_hash_until: *fail_hash_until, fail_execute, fail_reads, fail_write, @@ -432,6 +488,7 @@ fn main() -> Result<(), EmulatorError> { numbers, *dump_mem, fail_config, + *save_non_checkpoint_steps, ) .0; info!("Execution result: {:?}", result); @@ -443,6 +500,7 @@ fn main() -> Result<(), EmulatorError> { force, fail_config_prover, command_file, + save_non_checkpoint_steps, }) => { let input_bytes = hex::decode(input).expect("Invalid hex string"); let result = prover_execute( @@ -451,6 +509,7 @@ fn main() -> Result<(), EmulatorError> { checkpoint_prover_path, *force, fail_config_prover.clone(), + *save_non_checkpoint_steps, )?; info!("Prover execute: {:?}", result); @@ -479,6 +538,7 @@ fn main() -> Result<(), EmulatorError> { force, fail_config_verifier, command_file, + save_non_checkpoint_steps, }) => { let input_bytes = hex::decode(input).expect("Invalid hex string"); let result = verifier_check_execution( @@ -489,6 +549,7 @@ fn main() -> Result<(), EmulatorError> { claim_last_hash, force.clone(), fail_config_verifier.clone(), + *save_non_checkpoint_steps, )?; info!("Verifier checks execution: {:?}", result); @@ -505,6 +566,7 @@ fn main() -> Result<(), EmulatorError> { v_decision, fail_config_prover, command_file, + nary_type, }) => { let result = prover_get_hashes_for_round( pdf, @@ -512,6 +574,7 @@ fn main() -> Result<(), EmulatorError> { *round_number, *v_decision, fail_config_prover.clone(), + *nary_type, )?; info!("Prover get hashes for round: {:?}", result); @@ -531,6 +594,7 @@ fn main() -> Result<(), EmulatorError> { hashes, fail_config_verifier, command_file, + nary_type, }) => { let result = verifier_choose_segment( pdf, @@ -538,6 +602,7 @@ fn main() -> Result<(), EmulatorError> { *round_number, hashes.clone(), fail_config_verifier.clone(), + *nary_type, )?; info!("Verifier choose segment: {:?}", result); @@ -599,6 +664,29 @@ fn main() -> Result<(), EmulatorError> { file.write_all(result.to_string().as_bytes()) .expect("Failed to write JSON to file"); } + Some(Commands::VerifierChooseChallengeForReadChallenge { + pdf, + checkpoint_verifier_path, + fail_config_verifier, + force, + command_file, + }) => { + let result = verifier_choose_challenge_for_read_challenge( + pdf, + checkpoint_verifier_path, + fail_config_verifier.clone(), + force.clone(), + )?; + info!("Verifier choose challenge: {:?}", result); + + let result = EmulatorResultType::VerifierChooseChallengeResult { + challenge: result.clone(), + } + .to_value()?; + let mut file = create_or_open_file(command_file); + file.write_all(result.to_string().as_bytes()) + .expect("Failed to write JSON to file"); + } None => { error!("No command specified"); } diff --git a/emulator/tests/challenge.rs b/emulator/tests/challenge.rs index e4cdbb3..388590c 100644 --- a/emulator/tests/challenge.rs +++ b/emulator/tests/challenge.rs @@ -1,6 +1,6 @@ use emulator::{ constants::REGISTERS_BASE_ADDRESS, - decision::nary_search::{choose_segment, ExecutionHashes}, + decision::nary_search::{choose_segment, ExecutionHashes, NArySearchType}, executor::verifier::verify_script, loader::program_definition::ProgramDefinition, }; @@ -19,9 +19,9 @@ fn test_nary_search_trace_aux(input: u8, expect_err: bool, checkpoint_path: &str let program_def = ProgramDefinition::from_config(program_definition_file).unwrap(); let defs = program_def.nary_def(); - + let input = vec![17, 17, 17, input]; let (_bad_result, last_step, _last_hash) = program_def - .get_execution_result(vec![17, 17, 17, input], checkpoint_path, None) + .get_execution_result(input.clone(), checkpoint_path, None, true) .unwrap(); let challenge_selected_step = last_step.min(1500); @@ -34,15 +34,22 @@ fn test_nary_search_trace_aux(input: u8, expect_err: bool, checkpoint_path: &str for round in 1..defs.total_rounds() + 1 { info!("Prover gets the steps required by the n-ary search round: {round}"); let reply_hashes = program_def - .get_round_hashes(checkpoint_path, round, base, None) + .get_round_hashes(checkpoint_path, input.clone(), round, base, None) .unwrap(); //get_hashes(&bad_trace, &steps); info!("Hashes: {:?}", reply_hashes); let claim_hashes = ExecutionHashes::from_hexstr(&reply_hashes); let my_hashes = ExecutionHashes::from_hexstr(&reply_hashes); - let (bits, new_base, new_selected) = - choose_segment(&defs, base, selected, round, &claim_hashes, &my_hashes); + let (bits, new_base, new_selected) = choose_segment( + &defs, + base, + selected, + round, + &claim_hashes, + &my_hashes, + NArySearchType::ConflictStep, + ); base = new_base; selected = new_selected; @@ -51,7 +58,7 @@ fn test_nary_search_trace_aux(input: u8, expect_err: bool, checkpoint_path: &str info!("The prover needs to provide the full trace for the selected step {selected}"); let trace = program_def - .get_trace_step(checkpoint_path, selected, None) + .get_trace_step(checkpoint_path, input, selected, None) .unwrap(); info!("{:?}", trace.to_csv()); diff --git a/emulator/tests/compliance.rs b/emulator/tests/compliance.rs index 08d1dfa..eadacff 100644 --- a/emulator/tests/compliance.rs +++ b/emulator/tests/compliance.rs @@ -31,6 +31,7 @@ fn verify_file( None, None, FailConfiguration::default(), + false, )) } diff --git a/emulator/tests/exceptions.rs b/emulator/tests/exceptions.rs index 0f17009..f74b527 100644 --- a/emulator/tests/exceptions.rs +++ b/emulator/tests/exceptions.rs @@ -32,6 +32,7 @@ fn verify_file( None, None, FailConfiguration::default(), + false, )) }