Skip to content

is_last_segment incorrectly constrained only on first row, still enabling early termination attacks #22

@Koukyosyumei

Description

@Koukyosyumei

Currently, witness generation fills the entire is_last_segment column with self.is_last_segment:

cols.is_last_segment = SC::Val::from_canonical_u32(self.is_last_segment);

However, the AIR constraint only enforces correctness on the first row:

.when_first_row()

This means a malicious prover can arbitrarily flip non–first-row values of is_last_segment. Doing so reintroduces the early termination vulnerability (see #16). Importantly, the fix in #17 assumes that is_last_segment is trustworthy, and manipulating later rows bypasses that protection.

A working PoC is available: https://github.com/Koukyosyumei/valida-vm/tree/fix-poc-mal-DidStop-flag

This PoC reproduces the attack from #16 with a minor mutation in the CPU chip’s op_to_row:

if self.registers[clk as usize].pc != 0 {
    cols.is_last_segment = SC::Val::zero();
}

Proposed Fix:
Remove the .when_first_row() restriction in:

.when_first_row()

and instead enforce:

assert_eq(local.is_last_segment, public.is_last_segment);

on all rows, ensuring that every value of is_last_segment is consistent and not forgeable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions