Skip to content

Commit 2afb7cb

Browse files
committed
Add IsByte range checks and state_addr binding
to keccak precompile, restore Commit syscall to 64
1 parent 8d99f47 commit 2afb7cb

8 files changed

Lines changed: 122 additions & 17 deletions

File tree

executor/programs/asm/test_commit_4.s

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
.globl main
33
main:
44
# Store 4 bytes [0xAA, 0xBB, 0xCC, 0xDD] on the stack, then commit them.
5-
# Commit syscall: x17=3, x10=1(fd), x11=buf_addr, x12=count
5+
# Commit syscall: x17=64, x10=1(fd), x11=buf_addr, x12=count
66

77
addi sp, sp, -16 # 1: allocate stack
88
addi t0, zero, 0xAA # 2: t0 = 0xAA (170)
@@ -18,7 +18,7 @@ main:
1818
li a0, 1 # 10: fd = 1 (stdout)
1919
mv a1, sp # 11: buf_addr = sp
2020
li a2, 4 # 12: count = 4
21-
li a7, 3 # 13: syscall = Commit
21+
li a7, 64 # 13: syscall = Commit
2222
ecall # 14: commit
2323

2424
# Halt

executor/src/vm/instruction/execution.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ const REGULAR_PC_UPDATE: u64 = 4;
1010
pub enum SyscallNumbers {
1111
Print = 1,
1212
Panic = 2,
13-
Commit = 3,
1413
GetPrivateInputs = 4,
14+
Commit = 64,
1515
Halt = 93,
1616
KeccakPermute = 94, // Actual syscall number is KECCAK_SYSCALL_NUMBER (u64::MAX - 1)
1717
}
@@ -27,8 +27,8 @@ impl TryFrom<u64> for SyscallNumbers {
2727
match value {
2828
1 => Ok(SyscallNumbers::Print),
2929
2 => Ok(SyscallNumbers::Panic),
30-
3 => Ok(SyscallNumbers::Commit),
3130
4 => Ok(SyscallNumbers::GetPrivateInputs),
31+
64 => Ok(SyscallNumbers::Commit),
3232
93 => Ok(SyscallNumbers::Halt),
3333
v if v == KECCAK_SYSCALL_NUMBER => Ok(SyscallNumbers::KeccakPermute),
3434
_ => Err(()),

prover/src/tables/commit.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ pub fn bus_interactions() -> Vec<BusInteraction> {
267267
vec![
268268
// 1. Receive ECALL from CPU (mult = first)
269269
// Payload: [timestamp_lo, timestamp_hi, syscall_lo32, syscall_hi32]
270-
// Hardcoded syscall number = 3 (Commit). Bus matching enforces CPU's rv1 == 3.
270+
// Hardcoded syscall number = 64 (Commit). Bus matching enforces CPU's rv1 == 64.
271271
BusInteraction::receiver(
272272
BusId::EcallCommit,
273273
Multiplicity::Column(cols::FIRST),
@@ -280,8 +280,8 @@ pub fn bus_interactions() -> Vec<BusInteraction> {
280280
start_column: cols::TIMESTAMP_1,
281281
packing: Packing::Direct,
282282
},
283-
BusValue::constant(3), // syscall number lo32 = Commit (3)
284-
BusValue::constant(0), // syscall number hi32 = 0
283+
BusValue::constant(64), // syscall number lo32 = Commit (64)
284+
BusValue::constant(0), // syscall number hi32 = 0
285285
],
286286
),
287287
// 2. Send to CommitNextByte (mult = mu - end)

prover/src/tables/cpu.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,14 @@ pub mod cols {
237237
/// branch_cond: Whether branch is taken
238238
pub const BRANCH_COND: usize = 75;
239239

240+
/// Keccak state address (DWordWL: lo32 and hi32).
241+
/// Non-zero only for KeccakPermute ECALLs. Carried in the EcallKeccak bus
242+
/// to directly bind the CPU's ECALL to the keccak chip's memory region.
243+
pub const KECCAK_STATE_ADDR_0: usize = 76;
244+
pub const KECCAK_STATE_ADDR_1: usize = 77;
245+
240246
/// Total number of columns
241-
pub const NUM_COLUMNS: usize = 76;
247+
pub const NUM_COLUMNS: usize = 78;
242248

243249
// -------------------------------------------------------------------------
244250
// Helper ranges for iteration
@@ -816,6 +822,10 @@ pub fn generate_cpu_trace(
816822
data[base + cols::ECALL] = FE::from(d.op_ecall as u64);
817823
data[base + cols::ECALL_COMMIT] = FE::from(op.ecall_commit as u64);
818824
data[base + cols::ECALL_KECCAK] = FE::from(op.ecall_keccak as u64);
825+
data[base + cols::KECCAK_STATE_ADDR_0] =
826+
FE::from(op.keccak_state_addr & 0xFFFF_FFFF);
827+
data[base + cols::KECCAK_STATE_ADDR_1] =
828+
FE::from(op.keccak_state_addr >> 32);
819829
data[base + cols::EBREAK] = FE::from(d.op_ebreak as u64);
820830

821831
// Output columns
@@ -2087,7 +2097,11 @@ pub fn bus_interactions() -> Vec<BusInteraction> {
20872097
// -------------------------------------------------------------------------
20882098
// Sends to the KECCAK table only for KeccakPermute ECALLs.
20892099
// Payload matches the KECCAK receiver:
2090-
// [timestamp_lo, timestamp_hi, syscall_lo32, syscall_hi32]
2100+
// [timestamp_lo, timestamp_hi, syscall_lo32, syscall_hi32, state_addr_lo32, state_addr_hi32]
2101+
//
2102+
// Including state_addr directly binds the CPU's keccak ECALL to the keccak
2103+
// chip's memory region, rather than relying solely on transitive enforcement
2104+
// through the MEMW bus chain.
20912105
interactions.push(BusInteraction::sender(
20922106
BusId::EcallKeccak,
20932107
Multiplicity::Column(cols::ECALL_KECCAK),
@@ -2111,6 +2125,14 @@ pub fn bus_interactions() -> Vec<BusInteraction> {
21112125
start_column: cols::RV1_2,
21122126
packing: Packing::Direct,
21132127
},
2128+
BusValue::Packed {
2129+
start_column: cols::KECCAK_STATE_ADDR_0,
2130+
packing: Packing::Direct,
2131+
},
2132+
BusValue::Packed {
2133+
start_column: cols::KECCAK_STATE_ADDR_1,
2134+
packing: Packing::Direct,
2135+
},
21142136
],
21152137
));
21162138

prover/src/tables/keccak.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Keccak-f[1600] permutation chip.
22
//!
3-
//! A single wide table (2,638 columns) that proves Keccak-f[1600] permutations.
3+
//! A single wide table (3,139 columns) that proves Keccak-f[1600] permutations.
44
//! Each permutation uses 24 rows (one per round). All intermediate values are
55
//! stored as columns and verified via purely polynomial constraints — no lookups.
66
//!
@@ -961,11 +961,17 @@ pub fn create_constraints(
961961
/// - `EcallKeccak` receiver on the first row of each real permutation.
962962
/// - 25 `Memw` reads on `first`, binding the preimage to memory at `timestamp`.
963963
/// - 25 `Memw` writes on `export`, binding the final state to memory at `timestamp + 1`.
964+
/// - `IsByte` range checks on preimage bytes (on `first`) and output bytes (on `export`).
964965
pub fn bus_interactions() -> Vec<BusInteraction> {
965-
let mut interactions = Vec::with_capacity(51);
966+
// 1 EcallKeccak + 25 Memw reads + 25 Memw writes + 200 IsByte preimage + 200 IsByte output
967+
let mut interactions = Vec::with_capacity(451);
966968
let syscall_lo = KECCAK_SYSCALL_NUMBER & 0xFFFF_FFFF;
967969
let syscall_hi = KECCAK_SYSCALL_NUMBER >> 32;
968970

971+
// EcallKeccak receiver: binds (timestamp, syscall_number, state_addr) from the CPU.
972+
// Including state_addr provides a direct binding between the CPU's ECALL and the
973+
// keccak permutation's memory region, rather than relying solely on transitive
974+
// enforcement through the MEMW bus.
969975
interactions.push(BusInteraction::receiver(
970976
BusId::EcallKeccak,
971977
Multiplicity::Column(cols::FIRST),
@@ -980,9 +986,49 @@ pub fn bus_interactions() -> Vec<BusInteraction> {
980986
},
981987
BusValue::constant(syscall_lo),
982988
BusValue::constant(syscall_hi),
989+
BusValue::Packed {
990+
start_column: cols::STATE_ADDR_0,
991+
packing: Packing::Direct,
992+
},
993+
BusValue::Packed {
994+
start_column: cols::STATE_ADDR_1,
995+
packing: Packing::Direct,
996+
},
983997
],
984998
));
985999

1000+
// -------------------------------------------------------------------------
1001+
// IsByte range checks — defense in depth
1002+
// -------------------------------------------------------------------------
1003+
// Byte columns are already transitively range-checked through the MEMW bus
1004+
// (MEMW table enforces IsByte on its VALUE columns). These explicit checks
1005+
// ensure keccak byte columns remain sound even if MEMW range checks are
1006+
// refactored in the future.
1007+
1008+
// Preimage bytes: range-checked on `first` (where they enter the MEMW bus)
1009+
for i in 0..200 {
1010+
interactions.push(BusInteraction::sender(
1011+
BusId::IsByte,
1012+
Multiplicity::Column(cols::FIRST),
1013+
vec![BusValue::Packed {
1014+
start_column: cols::PREIMAGE_BYTES + i,
1015+
packing: Packing::Direct,
1016+
}],
1017+
));
1018+
}
1019+
1020+
// Output bytes: range-checked on `export` (where they enter the MEMW bus)
1021+
for i in 0..200 {
1022+
interactions.push(BusInteraction::sender(
1023+
BusId::IsByte,
1024+
Multiplicity::Column(cols::EXPORT),
1025+
vec![BusValue::Packed {
1026+
start_column: cols::OUTPUT_BYTES + i,
1027+
packing: Packing::Direct,
1028+
}],
1029+
));
1030+
}
1031+
9861032
for lane_idx in 0..25 {
9871033
let x = lane_idx % 5;
9881034
let y = lane_idx / 5;

prover/src/tables/trace_builder.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1418,6 +1418,38 @@ fn expand_keccak_operations(cpu_ops: &[CpuOperation], elf: &Elf) -> Vec<KeccakOp
14181418
expand_keccak_operations_from_state(cpu_ops, &initial_memory_state)
14191419
}
14201420

1421+
/// Collect IsByte lookups for keccak preimage and output bytes.
1422+
///
1423+
/// The keccak AIR sends IsByte for each of the 200 preimage bytes (on `first`)
1424+
/// and 200 output bytes (on `export`). The bitwise table must account for these
1425+
/// in its MU_IS_BYTE multiplicity column.
1426+
fn collect_bitwise_from_keccak(keccak_ops: &[KeccakOperation]) -> Vec<BitwiseOperation> {
1427+
let mut ops = Vec::with_capacity(keccak_ops.len() * 400);
1428+
for op in keccak_ops {
1429+
// 200 preimage bytes (25 lanes × 8 bytes)
1430+
for lane in &op.input {
1431+
for byte_idx in 0..8u32 {
1432+
let byte_val = (lane >> (byte_idx * 8)) & 0xFF;
1433+
ops.push(BitwiseOperation::single_byte(
1434+
BitwiseOperationType::IsByte,
1435+
byte_val as u8,
1436+
));
1437+
}
1438+
}
1439+
// 200 output bytes (25 lanes × 8 bytes)
1440+
for lane in &op.output {
1441+
for byte_idx in 0..8u32 {
1442+
let byte_val = (lane >> (byte_idx * 8)) & 0xFF;
1443+
ops.push(BitwiseOperation::single_byte(
1444+
BitwiseOperationType::IsByte,
1445+
byte_val as u8,
1446+
));
1447+
}
1448+
}
1449+
}
1450+
ops
1451+
}
1452+
14211453
/// Collect bitwise lookups from COMMIT operations.
14221454
///
14231455
/// The COMMIT table sends:
@@ -1867,6 +1899,8 @@ impl Traces {
18671899

18681900
// Expand Keccak ECALL operations
18691901
let keccak_ops = expand_keccak_operations(&cpu_ops, elf);
1902+
// KECCAK table sends IsByte for preimage and output bytes
1903+
bitwise_ops.extend(collect_bitwise_from_keccak(&keccak_ops));
18701904

18711905
// CPU padding rows send IS_BYTE with all-zero values.
18721906
// Add corresponding ops so the bitwise table multiplicities balance.
@@ -2089,6 +2123,10 @@ impl Traces {
20892123
// COMMIT table sends IsByte and IsHalfword lookups
20902124
bitwise_ops.extend(collect_bitwise_from_commit(&commit_ops));
20912125

2126+
// KECCAK table sends IsByte for preimage and output bytes
2127+
let keccak_ops = expand_keccak_operations_from_state(&cpu_ops, &MemoryState::new());
2128+
bitwise_ops.extend(collect_bitwise_from_keccak(&keccak_ops));
2129+
20922130
// CPU padding rows send IS_BYTE with all-zero values.
20932131
let num_padding_rows: usize = cpu_ops
20942132
.chunks(max_rows.cpu)
@@ -2136,7 +2174,6 @@ impl Traces {
21362174
let register_final_state = register_state.to_final_state_map();
21372175

21382176
let commit_trace = commit::generate_commit_trace(&commit_ops);
2139-
let keccak_ops = expand_keccak_operations_from_state(&cpu_ops, &MemoryState::new());
21402177
let keccak_trace = keccak::generate_keccak_trace(&keccak_ops);
21412178

21422179
// Generate remaining traces in parallel (register, halt).

prover/src/tests/cpu_tests.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -330,15 +330,15 @@ fn test_bus_interactions_count() {
330330
// - 1 BRANCH (branch/jump target calculation)
331331
// - 1 ECALL → HALT (send to HALT table, mult = ECALL - ECALL_COMMIT)
332332
// - 1 ECALL → COMMIT (send to COMMIT table, mult = ECALL_COMMIT)
333+
// - 1 ECALL → KECCAK (send to KECCAK table, mult = ECALL_KECCAK)
333334
// - 27 IS_BYTE (byte range checks: RS1, RS2, RD, ARG1[0..7], ARG2[0..7], RES[0..7])
334-
// Total: 8 + 8 + 8 + 2 + 1 + 1 + 1 + 1 + 5 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 27 = 69
335-
// Note: ECALL → KECCAK interaction not yet implemented (pending MEMW bus integration)
336-
assert_eq!(interactions.len(), 69);
335+
// Total: 8 + 8 + 8 + 2 + 1 + 1 + 1 + 1 + 5 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 27 = 70
336+
assert_eq!(interactions.len(), 70);
337337
}
338338

339339
#[test]
340340
fn test_column_count() {
341-
assert_eq!(cols::NUM_COLUMNS, 76);
341+
assert_eq!(cols::NUM_COLUMNS, 78);
342342
}
343343

344344
#[test]

syscalls/src/syscalls.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ const MAX_PRIVATE_INPUT_SIZE: usize = 6700000;
99
enum SyscallNumbers {
1010
Print = 1,
1111
Panic = 2,
12-
Commit = 3,
1312
GetPrivateInputs = 4,
13+
Commit = 64,
1414
Halt = 93,
1515
}
1616

0 commit comments

Comments
 (0)