Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 171 additions & 34 deletions crates/air/src/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -708,12 +708,17 @@ impl CpuAir {
addr_lo: M31,
is_word_access: M31,
) -> M31 {
// addr_lo % 4 = addr_lo & 3
// Need bit decomposition to check low 2 bits are 0
// Check 4-byte alignment: addr % 4 == 0
// In M31 field: compute addr_lo mod 4
// We extract bottom 2 bits by: addr_lo - 4 * floor(addr_lo / 4)

// Simplified: Check addr_lo mod 4 via auxiliary witness
// For now, placeholder assuming alignment is pre-checked
is_word_access * (addr_lo - addr_lo) // Identity
let four = M31::new(4);
let quotient = M31::new(addr_lo.as_u32() / 4); // Integer division
let remainder = addr_lo - quotient * four; // addr_lo % 4
Comment on lines +716 to +717
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alignment constraint is unsound for the same reason as in memory.rs. Using as_u32() to extract the field value and perform division in the u32 domain breaks the algebraic constraint system. A malicious prover could provide an addr_lo value that satisfies this check but doesn't correspond to a valid aligned address.

Proper implementation requires witness columns for the quotient and constraining that addr_lo = quotient * 4 + remainder where remainder is range-checked to {0, 1, 2, 3}.

Copilot uses AI. Check for mistakes.

// Constraint: when is_word_access = 1, remainder must be 0
// If remainder != 0, constraint is non-zero → proof fails
is_word_access * remainder
Comment on lines +715 to +721
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These alignment constraints compute addr_lo % {2,4} via as_u32() + integer division. In AIR code, constraints are expected to be polynomial functions of trace columns; floor-division on a field element is not polynomial and can break low-degree guarantees. Prefer adding a small witness for the remainder (or the low address bits) and constrain it algebraically (e.g., addr_lo = 4*q + r, r ∈ {0,1,2,3}, and is_word*r = 0).

Copilot uses AI. Check for mistakes.
}

/// Evaluate alignment constraint for halfword access.
Expand All @@ -729,11 +734,17 @@ impl CpuAir {
addr_lo: M31,
is_half_access: M31,
) -> M31 {
// addr_lo % 2 = addr_lo & 1
// Check low bit is 0
// Check 2-byte alignment: addr % 2 == 0
// In M31 field: compute addr_lo mod 2
// We extract bottom bit by: addr_lo - 2 * floor(addr_lo / 2)

// Placeholder
is_half_access * (addr_lo - addr_lo)
let two = M31::new(2);
let quotient = M31::new(addr_lo.as_u32() / 2); // Integer division
let remainder = addr_lo - quotient * two; // addr_lo % 2
Comment on lines +742 to +743
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The halfword alignment constraint has the same soundness issue as the word alignment constraint. The division operation is performed in the u32 domain after extracting from M31 using as_u32(), which breaks the algebraic constraint system.

Correct implementation requires a witness column for the quotient and constraining that addr_lo = quotient * 2 + remainder where remainder is Boolean-constrained to {0, 1}.

Copilot uses AI. Check for mistakes.

// Constraint: when is_half_access = 1, remainder must be 0
// If remainder != 0 (i.e., odd address), constraint is non-zero → proof fails
is_half_access * remainder
Comment on lines +737 to +747
}

// ============================================================================
Expand Down Expand Up @@ -2115,31 +2126,7 @@ mod tests {
let _ = constraint;
}

#[test]
fn test_word_alignment() {
// Test word alignment (placeholder)
let aligned_addr = M31::new(0x1000); // Aligned to 4
let is_word = M31::ONE;

let constraint = CpuAir::word_alignment_constraint(aligned_addr, is_word);
assert_eq!(constraint, M31::ZERO, "Word alignment constraint failed");

// Misaligned address (placeholder won't catch this yet)
let misaligned_addr = M31::new(0x1001);
let constraint2 = CpuAir::word_alignment_constraint(misaligned_addr, is_word);
// Placeholder returns 0 regardless
assert_eq!(constraint2, M31::ZERO, "Placeholder alignment");
}

#[test]
fn test_halfword_alignment() {
// Test halfword alignment (placeholder)
let aligned_addr = M31::new(0x1000); // Aligned to 2
let is_half = M31::ONE;

let constraint = CpuAir::halfword_alignment_constraint(aligned_addr, is_half);
assert_eq!(constraint, M31::ZERO, "Halfword alignment constraint failed");
}
// NOTE: Old placeholder tests removed - replaced with comprehensive alignment tests below

// ============================================================================
// M-Extension Tests
Expand Down Expand Up @@ -2690,4 +2677,154 @@ mod tests {
// Should fail because eq_result doesn't match actual equality
assert_ne!(constraint, M31::ZERO, "Should detect incorrect eq_result");
}

// ============================================================================
// Memory Alignment Tests
// ============================================================================

#[test]
fn test_word_alignment_valid_aligned() {
// Test word access at aligned address (divisible by 4)
let addr = 0x1000u32; // Binary: ...0000 (last 2 bits = 00)
let addr_lo = M31::new(addr & 0xFFFF);
let is_word = M31::ONE;

let constraint = CpuAir::word_alignment_constraint(addr_lo, is_word);

assert_eq!(constraint, M31::ZERO,
"Word access at aligned address 0x{:X} should pass", addr);
}

#[test]
fn test_word_alignment_invalid_offset_1() {
// Test word access at misaligned address (offset by 1 byte)
let addr = 0x1001u32; // Binary: ...0001 (last 2 bits = 01)
let addr_lo = M31::new(addr & 0xFFFF);
let is_word = M31::ONE;

let constraint = CpuAir::word_alignment_constraint(addr_lo, is_word);

assert_ne!(constraint, M31::ZERO,
"Word access at misaligned address 0x{:X} should FAIL", addr);
}

#[test]
fn test_word_alignment_invalid_offset_2() {
// Test word access at misaligned address (offset by 2 bytes)
let addr = 0x1002u32; // Binary: ...0010 (last 2 bits = 10)
let addr_lo = M31::new(addr & 0xFFFF);
let is_word = M31::ONE;

let constraint = CpuAir::word_alignment_constraint(addr_lo, is_word);

assert_ne!(constraint, M31::ZERO,
"Word access at misaligned address 0x{:X} should FAIL", addr);
}

#[test]
fn test_word_alignment_invalid_offset_3() {
// Test word access at misaligned address (offset by 3 bytes)
let addr = 0x1003u32; // Binary: ...0011 (last 2 bits = 11)
let addr_lo = M31::new(addr & 0xFFFF);
let is_word = M31::ONE;

let constraint = CpuAir::word_alignment_constraint(addr_lo, is_word);

assert_ne!(constraint, M31::ZERO,
"Word access at misaligned address 0x{:X} should FAIL", addr);
}

#[test]
fn test_word_alignment_multiple_aligned() {
// Test multiple aligned addresses
let aligned_addrs = [0x0000, 0x0004, 0x0008, 0x1000, 0x2004, 0xFFF0];

for addr in aligned_addrs {
let addr_lo = M31::new(addr & 0xFFFF);
let is_word = M31::ONE;

let constraint = CpuAir::word_alignment_constraint(addr_lo, is_word);

assert_eq!(constraint, M31::ZERO,
"Word access at aligned address 0x{:X} should pass", addr);
}
}

#[test]
fn test_word_alignment_disabled() {
// Test that constraint is disabled when is_word = 0
let addr = 0x1001u32; // Misaligned
let addr_lo = M31::new(addr & 0xFFFF);
let is_word = M31::ZERO; // Not a word access

let constraint = CpuAir::word_alignment_constraint(addr_lo, is_word);

assert_eq!(constraint, M31::ZERO,
"Constraint should be disabled when is_word = 0");
}

#[test]
fn test_halfword_alignment_valid_even() {
// Test halfword access at even address (divisible by 2)
let addr = 0x1000u32; // Binary: ...0000 (last bit = 0)
let addr_lo = M31::new(addr & 0xFFFF);
let is_half = M31::ONE;

let constraint = CpuAir::halfword_alignment_constraint(addr_lo, is_half);

assert_eq!(constraint, M31::ZERO,
"Halfword access at even address 0x{:X} should pass", addr);
}

#[test]
fn test_halfword_alignment_valid_even_2() {
// Test halfword access at another even address
let addr = 0x1002u32; // Binary: ...0010 (last bit = 0)
let addr_lo = M31::new(addr & 0xFFFF);
let is_half = M31::ONE;

let constraint = CpuAir::halfword_alignment_constraint(addr_lo, is_half);

assert_eq!(constraint, M31::ZERO,
"Halfword access at even address 0x{:X} should pass", addr);
}

#[test]
fn test_halfword_alignment_invalid_odd() {
// Test halfword access at odd address
let addr = 0x1001u32; // Binary: ...0001 (last bit = 1)
let addr_lo = M31::new(addr & 0xFFFF);
let is_half = M31::ONE;

let constraint = CpuAir::halfword_alignment_constraint(addr_lo, is_half);

assert_ne!(constraint, M31::ZERO,
"Halfword access at odd address 0x{:X} should FAIL", addr);
}

#[test]
fn test_halfword_alignment_invalid_odd_2() {
// Test halfword access at another odd address
let addr = 0x1003u32; // Binary: ...0011 (last bit = 1)
let addr_lo = M31::new(addr & 0xFFFF);
let is_half = M31::ONE;

let constraint = CpuAir::halfword_alignment_constraint(addr_lo, is_half);

assert_ne!(constraint, M31::ZERO,
"Halfword access at odd address 0x{:X} should FAIL", addr);
}

#[test]
fn test_halfword_alignment_disabled() {
// Test that constraint is disabled when is_half = 0
let addr = 0x1001u32; // Odd (misaligned for halfword)
let addr_lo = M31::new(addr & 0xFFFF);
let is_half = M31::ZERO; // Not a halfword access

let constraint = CpuAir::halfword_alignment_constraint(addr_lo, is_half);

assert_eq!(constraint, M31::ZERO,
"Constraint should be disabled when is_half = 0");
}
}
17 changes: 11 additions & 6 deletions crates/air/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,16 @@ impl MemoryAir {
/// For word access: addr mod 4 = 0.
#[inline]
pub fn word_alignment_constraint(addr_lo: M31, is_word: M31) -> M31 {
// addr_lo mod 4 = 0 means addr_lo & 3 = 0
// Decompose addr_lo = 4*q + r where r in {0,1,2,3}
// Constraint: is_word * r = 0
// Requires auxiliary witness for r.
// Placeholder:
is_word * (addr_lo - addr_lo) // Always 0 for now
// Check 4-byte alignment: addr % 4 == 0
// In M31 field: compute addr_lo mod 4
// We extract bottom 2 bits by: addr_lo - 4 * floor(addr_lo / 4)

let four = M31::new(4);
let quotient = M31::new(addr_lo.as_u32() / 4); // Integer division
let remainder = addr_lo - quotient * four; // addr_lo % 4
Comment on lines +61 to +62
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alignment constraint is unsound because it performs division in the u32 domain rather than the M31 field. The quotient is computed by extracting the field element value with as_u32(), performing division there, and converting back. This breaks the algebraic constraint system because:

  1. The prover can manipulate addr_lo values in M31 that wouldn't be valid addresses
  2. The constraint doesn't properly enforce modular arithmetic in the field

The correct approach requires auxiliary witness columns for the quotient and constraining that addr_lo = quotient * 4 + remainder with remainder in {0, 1, 2, 3} using a range check or lookup table.

Copilot uses AI. Check for mistakes.

// Constraint: when is_word = 1, remainder must be 0
// If remainder != 0, constraint is non-zero → proof fails
is_word * remainder
Comment on lines +60 to +66
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as CpuAir::word_alignment_constraint: computing the remainder using as_u32()/integer division is not an algebraic (polynomial) constraint over the field, which is generally incompatible with STARK AIR low-degree requirements. This should be expressed via witness remainder/bit-decomposition constraints instead of floor(addr/4) in Rust.

Copilot uses AI. Check for mistakes.
}
}
Loading
Loading