Skip to content

Commit eb30e7b

Browse files
alinaT95claude
andcommitted
Add CHKHISTPROOF TVM instruction for layer hash verification
New instruction (opcode 0xC7 0x51) verifies that a key block's common section at a specified layer stores a particular layer hash value. Takes 3 stack args: hash (256-bit), block_height (u64), layer_number (u8). Returns boolean. Uses callback pattern for verification logic. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4ab13e2 commit eb30e7b

9 files changed

Lines changed: 224 additions & 1 deletion

File tree

tvm_assembler/src/simple.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,7 @@ impl Engine {
861861
CALCMINERTAPCOEF => 0xC7, 0x47
862862
CALCMINERREWARD => 0xC7, 0x48
863863
POSEIDON => 0xC7, 0x50
864+
CHKHISTPROOF => 0xC7, 0x51
864865
}
865866

866867
fn add_commands<'a>(

tvm_executor/src/transaction_executor.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ pub struct ExecuteParams {
149149
pub wasm_component_cache: HashMap<[u8; 32], wasmtime::component::Component>,
150150
pub mvconfig: MVConfig,
151151
pub engine_version: semver::Version,
152+
pub check_history_proof_hash:
153+
Option<Arc<dyn Send + Sync + Fn(u64, u8, [u8; 32]) -> bool>>,
152154
}
153155

154156
pub struct ActionPhaseResult {
@@ -202,6 +204,7 @@ impl Default for ExecuteParams {
202204
wasm_component_cache: HashMap::new(),
203205
mvconfig: MVConfig::default(),
204206
engine_version: "1.0.0".parse().unwrap(),
207+
check_history_proof_hash: None,
205208
}
206209
}
207210
}
@@ -600,6 +603,8 @@ pub trait TransactionExecutor {
600603
};
601604

602605
vm_setup = vm_setup.set_dapp_id(params.dapp_id.clone());
606+
vm_setup =
607+
vm_setup.set_check_history_proof_hash(params.check_history_proof_hash.clone());
603608

604609
let mut vm = vm_setup.create();
605610

tvm_executor/src/vmsetup.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ pub struct VMSetup {
5555
block_collation_was_finished: Arc<Mutex<bool>>,
5656
termination_deadline: Option<Instant>,
5757
execution_timeout: Option<Duration>,
58+
check_history_proof_hash:
59+
Option<Arc<dyn Send + Sync + Fn(u64, u8, [u8; 32]) -> bool>>,
5860
}
5961

6062
impl VMSetup {
@@ -83,6 +85,7 @@ impl VMSetup {
8385
block_collation_was_finished: Arc::new(Mutex::new(false)),
8486
termination_deadline: None,
8587
execution_timeout: None,
88+
check_history_proof_hash: None,
8689
}
8790
}
8891

@@ -199,6 +202,15 @@ impl VMSetup {
199202
self
200203
}
201204

205+
/// Sets check_history_proof_hash callback
206+
pub fn set_check_history_proof_hash(
207+
mut self,
208+
callback: Option<Arc<dyn Send + Sync + Fn(u64, u8, [u8; 32]) -> bool>>,
209+
) -> VMSetup {
210+
self.check_history_proof_hash = callback;
211+
self
212+
}
213+
202214
/// Sets account dapp_id
203215
pub fn set_dapp_id(mut self, dapp_id: Option<UInt256>) -> VMSetup {
204216
self.vm.set_dapp_id(dapp_id);
@@ -267,6 +279,9 @@ impl VMSetup {
267279
);
268280
vm.set_termination_deadline(self.termination_deadline);
269281
vm.set_execution_timeout(self.execution_timeout);
282+
if let Some(callback) = self.check_history_proof_hash {
283+
vm.set_check_history_proof_hash(callback);
284+
}
270285
vm
271286
}
272287
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// CHKHISTPROOF TVM instruction — verifies that a key block's common section
2+
// at a specified layer stores a particular layer hash value.
3+
//
4+
// Stack: hash (256-bit) | block_height (u64) | layer_number (u8, 1..=10)
5+
// Result: pushes boolean (-1 true, 0 false)
6+
7+
use crate::executor::Engine;
8+
use crate::executor::engine::storage::fetch_stack;
9+
use crate::executor::gas::gas_state::Gas;
10+
use crate::stack::StackItem;
11+
use crate::stack::integer::IntegerData;
12+
use crate::stack::integer::serialization::UnsignedIntegerBigEndianEncoding;
13+
use crate::types::Status;
14+
15+
pub const CHKHISTPROOF_GAS_PRICE: i64 = 100;
16+
17+
pub(super) fn execute_chk_hist_proof(engine: &mut Engine) -> Status {
18+
engine.mark_execution_as_block_related()?;
19+
engine.load_instruction(crate::executor::types::Instruction::new("CHKHISTPROOF"))?;
20+
engine.try_use_gas(Gas::chkhistproof_price())?;
21+
fetch_stack(engine, 3)?;
22+
23+
// Stack order: var(0)=top=layer_number, var(1)=block_height, var(2)=hash
24+
let layer_number: u8 = engine.cmd.var(0).as_integer()?.into(1..=10)?;
25+
let block_height: u64 = engine.cmd.var(1).as_integer()?.into(0..=u64::MAX)?;
26+
let hash_builder = engine
27+
.cmd
28+
.var(2)
29+
.as_integer()?
30+
.as_builder::<UnsignedIntegerBigEndianEncoding>(256)?;
31+
let mut hash_bytes = [0u8; 32];
32+
hash_bytes.copy_from_slice(hash_builder.data());
33+
34+
let result = match &engine.check_history_proof_hash {
35+
Some(callback) => callback(block_height, layer_number, hash_bytes),
36+
None => false,
37+
};
38+
39+
engine.cc.stack.push(boolean!(result));
40+
Ok(())
41+
}

tvm_vm/src/executor/engine/core.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ pub struct Engine {
153153
engine_version: semver::Version,
154154

155155
pub(in crate::executor) self_dapp_id: Option<UInt256>,
156+
157+
pub(in crate::executor) check_history_proof_hash:
158+
Option<Arc<dyn Send + Sync + Fn(u64, u8, [u8; 32]) -> bool>>,
156159
}
157160

158161
#[cfg(feature = "signature_no_check")]
@@ -327,6 +330,7 @@ impl Engine {
327330
self_dapp_id: None,
328331
mvconfig: MVConfig::default(),
329332
engine_version: "1.0.0".parse().unwrap(),
333+
check_history_proof_hash: None,
330334
}
331335
}
332336

@@ -504,6 +508,13 @@ impl Engine {
504508
self.self_dapp_id = dapp_id;
505509
}
506510

511+
pub fn set_check_history_proof_hash(
512+
&mut self,
513+
callback: Arc<dyn Send + Sync + Fn(u64, u8, [u8; 32]) -> bool>,
514+
) {
515+
self.check_history_proof_hash = Some(callback);
516+
}
517+
507518
#[cfg(feature = "wasmtime")]
508519
pub fn extern_wasm_engine_init() -> Result<wasmtime::Engine> {
509520
log::debug!("Extern Initialising Wasm Engine");

tvm_vm/src/executor/engine/handlers.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ use crate::executor::token::*;
4343
use crate::executor::tuple::*;
4444
use crate::executor::types::Instruction;
4545
use crate::executor::types::InstructionOptions;
46+
use crate::executor::chk_hist_proof::execute_chk_hist_proof;
4647
use crate::executor::zk::*;
4748
use crate::executor::zk_halo2::*;
4849
use crate::stack::integer::behavior::Quiet;
@@ -408,7 +409,8 @@ impl Handlers {
408409
.set(0x46, execute_calculate_mbk)
409410
.set(0x47, execute_calculate_miner_tap_coef)
410411
.set(0x48, execute_calculate_miner_reward)
411-
.set(0x49, execute_halo2_proof_verification);
412+
.set(0x49, execute_halo2_proof_verification)
413+
.set(0x51, execute_chk_hist_proof);
412414
// Pre-build VK + KZG params in background so the first
413415
// ZKHALO2VERIFY call doesn't block for seconds.
414416
crate::executor::zk_halo2::warmup_halo2();

tvm_vm/src/executor/gas/gas_state.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use tvm_types::error;
1717
use tvm_types::types::ExceptionCode;
1818

1919
use crate::error::TvmError;
20+
use crate::executor::chk_hist_proof::CHKHISTPROOF_GAS_PRICE;
2021
use crate::executor::zk::POSEIDON_ZK_LOGIN_GAS_PRICE;
2122
use crate::executor::zk::VERGRTH16_GAS_PRICE;
2223
use crate::types::Exception;
@@ -213,6 +214,15 @@ impl Gas {
213214
self.use_gas(VERGRTH16_GAS_PRICE)
214215
}
215216

217+
/// Compute CHKHISTPROOF usage cost
218+
pub const fn chkhistproof_price() -> i64 {
219+
CHKHISTPROOF_GAS_PRICE
220+
}
221+
222+
pub fn consume_chkhistproof(&mut self) -> i64 {
223+
self.use_gas(CHKHISTPROOF_GAS_PRICE)
224+
}
225+
216226
#[cfg(feature = "gosh")]
217227
/// line cost for diff
218228
pub fn diff_fee_for_line(lines_first_file: usize, lines_second_file: usize) -> i64 {

tvm_vm/src/executor/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ mod test_vergrth_bad_args;
6565
#[path = "../tests/test_poseidon_bad_args.rs"]
6666
mod test_poseidon_bad_args;
6767

68+
#[cfg(test)]
69+
#[path = "../tests/test_chk_hist_proof.rs"]
70+
mod test_chk_hist_proof;
71+
6872
#[cfg(test)]
6973
#[path = "../tests/test_executor.rs"]
7074
mod tests;
@@ -75,6 +79,7 @@ mod test_data;
7579
#[path = "../tests/test_helper.rs"]
7680
mod test_helper;
7781

82+
pub mod chk_hist_proof;
7883
pub mod zk;
7984

8085
pub mod zk_halo2;
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
use std::sync::Arc;
2+
3+
use tvm_types::SliceData;
4+
5+
use crate::executor::engine::Engine;
6+
use crate::executor::test_helper::*;
7+
use crate::executor::chk_hist_proof::execute_chk_hist_proof;
8+
use crate::stack::Stack;
9+
use crate::stack::StackItem;
10+
use crate::stack::integer::IntegerData;
11+
use crate::stack::savelist::SaveList;
12+
13+
fn setup_engine() -> Engine {
14+
let elector_code = load_boc("benches/elector-code.boc");
15+
let elector_data = load_boc("benches/elector-data.boc");
16+
let config_data = load_boc("benches/config-data.boc");
17+
let mut ctrls = SaveList::default();
18+
ctrls.put(4, &mut StackItem::Cell(elector_data)).unwrap();
19+
let params = vec![
20+
StackItem::int(0x76ef1ea),
21+
StackItem::int(0),
22+
StackItem::int(0),
23+
StackItem::int(1633458077),
24+
StackItem::int(0),
25+
StackItem::int(0),
26+
StackItem::int(0),
27+
StackItem::tuple(vec![StackItem::int(1000000000), StackItem::None]),
28+
StackItem::slice(
29+
SliceData::from_string(
30+
"9fe0000000000000000000000000000000000000000000000000000000000000001_",
31+
)
32+
.unwrap(),
33+
),
34+
StackItem::cell(config_data.reference(0).unwrap()),
35+
StackItem::None,
36+
StackItem::int(0),
37+
];
38+
ctrls.put(7, &mut StackItem::tuple(vec![StackItem::tuple(params)])).unwrap();
39+
let stack = Stack::new();
40+
Engine::with_capabilities(DEFAULT_CAPABILITIES).setup_with_libraries(
41+
SliceData::load_cell_ref(&elector_code).unwrap(),
42+
Some(ctrls),
43+
Some(stack),
44+
None,
45+
vec![],
46+
)
47+
}
48+
49+
fn make_hash_bytes() -> [u8; 32] {
50+
let mut hash = [0u8; 32];
51+
for i in 0..32 {
52+
hash[i] = (i + 1) as u8;
53+
}
54+
hash
55+
}
56+
57+
fn push_chk_hist_proof_args(engine: &mut Engine, hash: &[u8; 32], block_height: u64, layer: i32) {
58+
// Push in order: hash (bottom), block_height, layer_number (top)
59+
engine
60+
.cc
61+
.stack
62+
.push(StackItem::integer(IntegerData::from_unsigned_bytes_be(hash)));
63+
engine.cc.stack.push(StackItem::int(block_height));
64+
engine.cc.stack.push(StackItem::int(layer));
65+
}
66+
67+
#[test]
68+
fn test_chk_hist_proof_callback_returns_true() {
69+
let mut engine = setup_engine();
70+
let hash = make_hash_bytes();
71+
let expected_hash = hash;
72+
73+
engine.set_check_history_proof_hash(Arc::new(move |block_height, layer_number, h| {
74+
block_height == 42 && layer_number == 3 && h == expected_hash
75+
}));
76+
77+
push_chk_hist_proof_args(&mut engine, &hash, 42, 3);
78+
execute_chk_hist_proof(&mut engine).unwrap();
79+
80+
let result = engine.cc.stack.get(0).as_bool().unwrap();
81+
assert!(result, "Expected true (-1) when callback matches");
82+
}
83+
84+
#[test]
85+
fn test_chk_hist_proof_callback_returns_false() {
86+
let mut engine = setup_engine();
87+
let hash = make_hash_bytes();
88+
89+
engine.set_check_history_proof_hash(Arc::new(move |_block_height, _layer_number, _h| false));
90+
91+
push_chk_hist_proof_args(&mut engine, &hash, 100, 5);
92+
execute_chk_hist_proof(&mut engine).unwrap();
93+
94+
let result = engine.cc.stack.get(0).as_bool().unwrap();
95+
assert!(!result, "Expected false (0) when callback returns false");
96+
}
97+
98+
#[test]
99+
fn test_chk_hist_proof_no_callback() {
100+
let mut engine = setup_engine();
101+
let hash = make_hash_bytes();
102+
// No callback set — should return false
103+
104+
push_chk_hist_proof_args(&mut engine, &hash, 42, 3);
105+
execute_chk_hist_proof(&mut engine).unwrap();
106+
107+
let result = engine.cc.stack.get(0).as_bool().unwrap();
108+
assert!(!result, "Expected false (0) when no callback is set");
109+
}
110+
111+
#[test]
112+
fn test_chk_hist_proof_layer_zero_range_error() {
113+
let mut engine = setup_engine();
114+
let hash = make_hash_bytes();
115+
116+
engine.set_check_history_proof_hash(Arc::new(move |_, _, _| true));
117+
118+
push_chk_hist_proof_args(&mut engine, &hash, 42, 0);
119+
let result = execute_chk_hist_proof(&mut engine);
120+
assert!(result.is_err(), "layer_number=0 should cause RangeCheckError");
121+
}
122+
123+
#[test]
124+
fn test_chk_hist_proof_layer_eleven_range_error() {
125+
let mut engine = setup_engine();
126+
let hash = make_hash_bytes();
127+
128+
engine.set_check_history_proof_hash(Arc::new(move |_, _, _| true));
129+
130+
push_chk_hist_proof_args(&mut engine, &hash, 42, 11);
131+
let result = execute_chk_hist_proof(&mut engine);
132+
assert!(result.is_err(), "layer_number=11 should cause RangeCheckError");
133+
}

0 commit comments

Comments
 (0)