Skip to content

Commit e8d728f

Browse files
committed
perf: eliminate REGISTER table by computing bus contributions in verifier
The REGISTER table (67 rows, 5 columns) provided init/fini anchors on the Memory bus for all registers. Since all init and fini values are fully deterministic from the ELF entry point, the verifier can compute these bus contributions directly instead of proving them. Changes: - Add HALT finalization for x0 and x254 at ts=MAX, making all 67 register word addresses have deterministic fini state (value=0 except x255[510]=1, ts=MAX for all). x254 uses width-1 format (write2=0). - Add `compute_register_bus_offset` in prover/src/lib.rs: computes the LogUp sum for REG-C1 (receiver, init) and REG-C2 (sender, fini) across all 67 addresses using the same fingerprint formula as the STARK prover. - Rename `compute_expected_commit_bus_balance` to `compute_expected_bus_balance` which now sums both COMMIT and REGISTER offsets. - Remove REGISTER from VmAirs (struct, air_trace_pairs, air_refs, new). - Remove register trace from Traces struct and generation pipeline. - Update expected_proof_count from 5 fixed tables to 4 (bitwise, decode, halt, commit). This saves one full table proof (LDE, Merkle tree, FRI) and its preprocessed commitment computation per proof.
1 parent c99dd88 commit e8d728f

6 files changed

Lines changed: 219 additions & 177 deletions

File tree

prover/src/lib.rs

Lines changed: 107 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ use crate::test_utils::{
4242
E, F, VmAir, create_bitwise_air, create_branch_air, create_commit_air, create_cpu_air,
4343
create_decode_air, create_dvrm_air, create_halt_air, create_load_air, create_lt_air,
4444
create_memw_air, create_memw_aligned_air, create_memw_register_air, create_mul_air,
45-
create_page_air, create_register_air, create_shift_air,
45+
create_page_air, create_shift_air,
4646
};
4747

4848
use stark::proof::options::{GoldilocksCubicProofOptions, ProofOptions};
@@ -196,7 +196,6 @@ pub(crate) struct VmAirs {
196196
pub branches: Vec<VmAir>,
197197
pub halt: VmAir,
198198
pub commit: VmAir,
199-
pub register: VmAir,
200199
pub pages: Vec<VmAir>,
201200
pub memw_registers: Vec<VmAir>,
202201
}
@@ -209,7 +208,6 @@ impl VmAirs {
209208
(&self.decode, &mut traces.decode, &()),
210209
(&self.halt, &mut traces.halt, &()),
211210
(&self.commit, &mut traces.commit, &()),
212-
(&self.register, &mut traces.register, &()),
213211
];
214212

215213
for (air, trace) in self.cpus.iter().zip(traces.cpus.iter_mut()) {
@@ -264,7 +262,6 @@ impl VmAirs {
264262
&self.decode,
265263
&self.halt,
266264
&self.commit,
267-
&self.register,
268265
];
269266

270267
for air in &self.cpus {
@@ -359,10 +356,6 @@ impl VmAirs {
359356
.collect();
360357
let halt = create_halt_air(proof_options);
361358
let commit = create_commit_air(proof_options);
362-
let register = create_register_air(proof_options).with_preprocessed(
363-
register::preprocessed_commitment(proof_options, elf.entry_point),
364-
register::NUM_PREPROCESSED_COLS,
365-
);
366359
let pages: Vec<_> = page_configs
367360
.iter()
368361
.map(|config| {
@@ -393,7 +386,6 @@ impl VmAirs {
393386
branches,
394387
halt,
395388
commit,
396-
register,
397389
pages,
398390
memw_registers,
399391
}
@@ -459,18 +451,109 @@ pub(crate) fn compute_commit_bus_offset(
459451
Some(total)
460452
}
461453

462-
/// Compute the expected COMMIT bus balance for a `MultiProof`.
454+
/// Compute the bus balance offset for the REGISTER table on the Memory bus.
455+
///
456+
/// For each of the 67 register word addresses, computes the LogUp contribution
457+
/// of both REG-C1 (receiver, init token) and REG-C2 (sender, fini token).
458+
///
459+
/// After HALT finalization, all registers have deterministic fini state:
460+
/// - x0-x31 (addr 0-63): fini=0, ts=MAX
461+
/// - x254 (addr 508): fini=0, ts=MAX
462+
/// - x255 (addr 510-511): fini_lo=1, fini_hi=0, ts=MAX
463+
///
464+
/// Init values are also deterministic from the ELF entry point:
465+
/// - SP (x2): init = STACK_TOP
466+
/// - PC (x255): init = entry_point
467+
/// - All others: init = 0
468+
///
469+
/// The fingerprint formula per bus interaction is:
470+
/// `lc = BusId::Memory * α^0 + is_register * α^1 + addr * α^2 + addr_hi * α^3
471+
/// + ts_lo * α^4 + ts_hi * α^5 + value * α^6`
472+
/// `fingerprint = z - lc`
473+
///
474+
/// REG-C1 (receiver): contribution = -1/fingerprint
475+
/// REG-C2 (sender): contribution = +1/fingerprint
476+
///
477+
/// Returns `Some(Σ contributions)`, or `None` on a fingerprint collision.
478+
pub(crate) fn compute_register_bus_offset(
479+
entry_point: u64,
480+
z: &FieldElement<E>,
481+
alpha: &FieldElement<E>,
482+
) -> Option<FieldElement<E>> {
483+
let bus_id = FieldElement::<E>::from(BusId::Memory as u64);
484+
let alpha2 = alpha * alpha;
485+
let alpha3 = &alpha2 * alpha;
486+
let alpha4 = &alpha3 * alpha;
487+
let alpha5 = &alpha4 * alpha;
488+
let alpha6 = &alpha5 * alpha;
489+
490+
// Precompute constants that are the same for all addresses
491+
let is_register = FieldElement::<E>::one(); // is_register = 1
492+
// addr_hi = 0, contributes nothing
493+
let base_lc = &bus_id + &(&is_register * alpha);
494+
495+
// Fini timestamp: ts = u64::MAX = 0xFFFFFFFF_FFFFFFFF
496+
let ts_lo = FieldElement::<E>::from(0xFFFF_FFFFu64);
497+
let ts_hi = FieldElement::<E>::from(0xFFFF_FFFFu64);
498+
let fini_ts_contrib = &(&ts_lo * &alpha4) + &(&ts_hi * &alpha5);
499+
500+
let mut total = FieldElement::<E>::zero();
501+
502+
let addr_list = register::register_word_address_list();
503+
504+
for &addr in &addr_list {
505+
let addr_fe = FieldElement::<E>::from(addr);
506+
let addr_contrib = &addr_fe * &alpha2;
507+
508+
// Init value: deterministic from entry_point
509+
let init_value = register::init_value_for_address(addr, entry_point);
510+
511+
// Fini value: deterministic after HALT finalization
512+
// HALT writes 0 to all registers except x255 which gets value=1.
513+
// x255 lo word (addr 510) → fini=1, x255 hi word (addr 511) → fini=0.
514+
let fini_value: u32 = if addr == 510 { 1 } else { 0 };
515+
516+
// REG-C1 (receiver, sign=-1): init token with ts=0
517+
// lc = base_lc + addr * α^2 + 0*α^3 + 0*α^4 + 0*α^5 + init_value * α^6
518+
let lc_init = &base_lc
519+
+ &addr_contrib
520+
+ &(FieldElement::<E>::from(init_value as u64) * &alpha6);
521+
let fp_init = z - &lc_init;
522+
let inv_init = fp_init.inv().ok()?;
523+
total = total - inv_init; // receiver: -1/fp
524+
525+
// REG-C2 (sender, sign=+1): fini token with ts=MAX
526+
// lc = base_lc + addr * α^2 + 0*α^3 + MAX_LO*α^4 + MAX_HI*α^5 + fini_value * α^6
527+
let lc_fini = &base_lc
528+
+ &addr_contrib
529+
+ &fini_ts_contrib
530+
+ &(FieldElement::<E>::from(fini_value as u64) * &alpha6);
531+
let fp_fini = z - &lc_fini;
532+
let inv_fini = fp_fini.inv().ok()?;
533+
total = total + inv_fini; // sender: +1/fp
534+
}
535+
536+
Some(total)
537+
}
538+
539+
/// Compute the expected bus balance for a `MultiProof`.
463540
///
464541
/// Replays Phase A of the transcript to recover (z, alpha), then computes
465-
/// the offset from the given public output bytes. Call this after `multi_prove`
466-
/// and before `multi_verify`.
467-
pub(crate) fn compute_expected_commit_bus_balance(
542+
/// the combined offset from:
543+
/// 1. COMMIT output bus (public output bytes)
544+
/// 2. REGISTER Memory bus (verifier-computed init/fini tokens)
545+
///
546+
/// Call this after `multi_prove` and before `multi_verify`.
547+
pub(crate) fn compute_expected_bus_balance(
468548
airs: &[&dyn AIR<Field = F, FieldExtension = E, PublicInputs = ()>],
469549
proof: &MultiProof<F, E, ()>,
470550
public_output_bytes: &[u8],
551+
entry_point: u64,
471552
) -> Option<FieldElement<E>> {
472553
let (z, alpha) = replay_transcript_phase_a(airs, proof);
473-
compute_commit_bus_offset(public_output_bytes, &z, &alpha)
554+
let commit_offset = compute_commit_bus_offset(public_output_bytes, &z, &alpha)?;
555+
let register_offset = compute_register_bus_offset(entry_point, &z, &alpha)?;
556+
Some(commit_offset + register_offset)
474557
}
475558

476559
// =============================================================================
@@ -612,11 +695,12 @@ pub fn verify_with_options(
612695
Traces::page_configs_from_elf_and_runtime(&program, &vm_proof.runtime_page_ranges);
613696

614697
// Cross-check: table_counts must match the number of sub-proofs.
615-
// Fixed tables (bitwise, decode, halt, commit, register) = 5, plus page tables.
616-
let expected_proof_count = vm_proof.table_counts.total() + 5 + page_configs.len();
698+
// Fixed tables (bitwise, decode, halt, commit) = 4, plus page tables.
699+
// REGISTER table is eliminated: its bus contribution is verifier-computed.
700+
let expected_proof_count = vm_proof.table_counts.total() + 4 + page_configs.len();
617701
if expected_proof_count != vm_proof.proof.proofs.len() {
618702
return Err(Error::InvalidTableCounts(format!(
619-
"table_counts total ({}) + 5 fixed + {} pages = {}, but proof contains {} sub-proofs",
703+
"table_counts total ({}) + 4 fixed + {} pages = {}, but proof contains {} sub-proofs",
620704
vm_proof.table_counts.total(),
621705
page_configs.len(),
622706
expected_proof_count,
@@ -632,14 +716,16 @@ pub fn verify_with_options(
632716
&vm_proof.table_counts,
633717
);
634718

635-
// Recompute the COMMIT output bus offset from VmProof.public_output.
636-
// If public_output was tampered, the recomputed offset won't match the
637-
// actual bus total in the proof, and multi_verify will reject.
719+
// Recompute bus offsets from verifier-known data:
720+
// 1. COMMIT output bus: from VmProof.public_output
721+
// 2. REGISTER Memory bus: from ELF entry_point (init/fini are deterministic)
722+
// Tampering with either will cause bus imbalance and multi_verify rejection.
638723
let air_refs = airs.air_refs();
639-
let expected_bus_balance = match compute_expected_commit_bus_balance(
724+
let expected_bus_balance = match compute_expected_bus_balance(
640725
&air_refs,
641726
&vm_proof.proof,
642727
&vm_proof.public_output,
728+
program.entry_point,
643729
) {
644730
Some(balance) => balance,
645731
None => return Ok(false),

prover/src/tables/halt.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,16 +131,43 @@ fn halt_write_bus_values(base_addr: u64, value_lo: u64) -> Vec<BusValue> {
131131
]
132132
}
133133

134+
/// Returns the 16-element MEMW write bus values for a width-1 register finalization.
135+
///
136+
/// Format matches CO17 (write receiver): `[is_register, base_addr[0..1], value[0..7],
137+
/// timestamp[0..1], write2, write4, write8]` with write2=write4=write8=0 (width=1).
138+
fn halt_write_bus_values_w1(base_addr: u64, value_lo: u64) -> Vec<BusValue> {
139+
vec![
140+
BusValue::constant(1), // is_register = 1
141+
BusValue::constant(base_addr), // base_address[0]
142+
BusValue::constant(0), // base_address[1]
143+
BusValue::constant(value_lo), // value[0]
144+
BusValue::constant(0), // value[1]
145+
BusValue::constant(0), // value[2]
146+
BusValue::constant(0), // value[3]
147+
BusValue::constant(0), // value[4]
148+
BusValue::constant(0), // value[5]
149+
BusValue::constant(0), // value[6]
150+
BusValue::constant(0), // value[7]
151+
BusValue::constant(0xFFFF_FFFF), // timestamp[0] = lo(2^64-1)
152+
BusValue::constant(0xFFFF_FFFF), // timestamp[1] = hi(2^64-1)
153+
BusValue::constant(0), // write2 = 0 (width-1)
154+
BusValue::constant(0), // write4 = 0
155+
BusValue::constant(0), // write8 = 0
156+
]
157+
}
158+
134159
/// Creates all bus interactions for the HALT table.
135160
///
136161
/// - **ECALL receiver**: receives `[timestamp, cast(rv1, DWordWL)]` from CPU
137-
/// - **MEMW senders** (32 total): register finalization at `ts = 2^64-1`
162+
/// - **MEMW senders** (34 total): register finalization at `ts = 2^64-1`
163+
/// - x0: write 0 (explicit finalization for verifier-computed bus balance)
138164
/// - x1-x9: write 0 (zeroize lo GPRs)
139165
/// - x10: read with old=0 (enforce exit_code=0)
140166
/// - x11-x31: write 0 (zeroize hi GPRs)
167+
/// - x254: write 0 (finalize commit index, width-1)
141168
/// - x255: write 1 (PC halted sentinel)
142169
pub fn bus_interactions() -> Vec<BusInteraction> {
143-
let mut interactions = Vec::with_capacity(33);
170+
let mut interactions = Vec::with_capacity(36);
144171

145172
// ECALL receiver: receives [timestamp, cast(rv1, DWordWL)] from CPU
146173
// rv1 must be 93 (sys_exit) for bus to balance; otherwise proof fails.
@@ -161,6 +188,15 @@ pub fn bus_interactions() -> Vec<BusInteraction> {
161188
],
162189
));
163190

191+
// x0: write 0 at ts=2^64-1 (explicit finalization)
192+
// x0 is hardwired zero, but we finalize it so the verifier can compute
193+
// the REGISTER bus balance deterministically (no self-cancellation needed).
194+
interactions.push(BusInteraction::sender(
195+
BusId::Memw,
196+
Multiplicity::One,
197+
halt_write_bus_values(0, 0),
198+
));
199+
164200
// x1-x9: write 0 at ts=2^64-1 (zeroize lo registers)
165201
for i in 1..=9u64 {
166202
interactions.push(BusInteraction::sender(
@@ -188,6 +224,14 @@ pub fn bus_interactions() -> Vec<BusInteraction> {
188224
));
189225
}
190226

227+
// x254 (commit index): write 0 at ts=2^64-1 (finalize, width-1)
228+
// Single-word register at address 508; uses width-1 format (write2=0).
229+
interactions.push(BusInteraction::sender(
230+
BusId::Memw,
231+
Multiplicity::One,
232+
halt_write_bus_values_w1(508, 0),
233+
));
234+
191235
// x255 (PC): write 1 at ts=2^64-1 (halted sentinel)
192236
interactions.push(BusInteraction::sender(
193237
BusId::Memw,

prover/src/tables/register.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ pub type FinalRegisterStateMap = HashMap<u64, FinalRegisterWordState>;
100100
/// Returns the Word addresses for all register table rows.
101101
///
102102
/// x0-x31 use addresses 0..63, x254 uses address 508, x255 uses 510..511.
103-
fn register_word_address_list() -> [u64; NUM_REGISTER_ADDRESSES] {
103+
pub(crate) fn register_word_address_list() -> [u64; NUM_REGISTER_ADDRESSES] {
104104
let mut addrs = [0u64; NUM_REGISTER_ADDRESSES];
105105
// x0-x31: addresses 0..63
106106
for (i, addr) in addrs.iter_mut().enumerate().take(64) {
@@ -120,7 +120,7 @@ fn register_word_address_list() -> [u64; NUM_REGISTER_ADDRESSES] {
120120
/// - x254 at offset 508 is the synthetic commit index, initialized to 0
121121
/// - PC (x255) words at offset 510,511 hold entry_point
122122
/// - All others are 0
123-
fn init_value_for_address(word_addr: u64, entry_point: u64) -> u32 {
123+
pub(crate) fn init_value_for_address(word_addr: u64, entry_point: u64) -> u32 {
124124
match word_addr {
125125
4 => (STACK_TOP & 0xFFFF_FFFF) as u32,
126126
5 => (STACK_TOP >> 32) as u32,

0 commit comments

Comments
 (0)