Skip to content

Commit f37526c

Browse files
Merge branch 'main' into feat/higher-arity-fri
2 parents 10c9d8f + 7b42e51 commit f37526c

66 files changed

Lines changed: 10566 additions & 234 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/pr_spec.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Spec tests
2+
on:
3+
pull_request:
4+
branches:
5+
- main
6+
- 'spec/**'
7+
push:
8+
branches: ["**"]
9+
paths: ["spec/**"]
10+
11+
permissions:
12+
contents: read
13+
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
16+
cancel-in-progress: true
17+
18+
jobs:
19+
spec_structure:
20+
name: Spec structure test
21+
runs-on: ubuntu-latest
22+
steps:
23+
- uses: actions/checkout@v6
24+
- uses: actions/setup-python@v6
25+
- run: python3 spec/tooling/chip.py spec/src/config.toml spec/src/signatures.toml spec/src/*.toml

prover/src/lib.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ use crate::tables::types::BusId;
4141
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,
44-
create_memw_air, create_memw_aligned_air, create_mul_air, create_page_air, create_register_air,
45-
create_shift_air,
44+
create_memw_air, create_memw_aligned_air, create_memw_register_air, create_mul_air,
45+
create_page_air, create_register_air, create_shift_air,
4646
};
4747

4848
use stark::proof::options::{GoldilocksCubicProofOptions, ProofOptions};
@@ -73,6 +73,7 @@ pub struct TableCounts {
7373
pub dvrm: usize,
7474
pub shift: usize,
7575
pub branch: usize,
76+
pub memw_register: usize,
7677
}
7778

7879
impl TableCounts {
@@ -91,6 +92,7 @@ impl TableCounts {
9192
+ self.dvrm
9293
+ self.shift
9394
+ self.branch
95+
+ self.memw_register
9496
}
9597

9698
/// Validate that all required tables have at least one chunk.
@@ -108,6 +110,7 @@ impl TableCounts {
108110
("dvrm", self.dvrm),
109111
("shift", self.shift),
110112
("branch", self.branch),
113+
("memw_register", self.memw_register),
111114
];
112115
for (name, count) in checks {
113116
if count == 0 {
@@ -195,6 +198,7 @@ pub(crate) struct VmAirs {
195198
pub commit: VmAir,
196199
pub register: VmAir,
197200
pub pages: Vec<VmAir>,
201+
pub memw_registers: Vec<VmAir>,
198202
}
199203

200204
impl VmAirs {
@@ -242,6 +246,13 @@ impl VmAirs {
242246
for (air, trace) in self.pages.iter().zip(traces.pages.iter_mut()) {
243247
pairs.push((air, trace, &()));
244248
}
249+
for (air, trace) in self
250+
.memw_registers
251+
.iter()
252+
.zip(traces.memw_registers.iter_mut())
253+
{
254+
pairs.push((air, trace, &()));
255+
}
245256

246257
pairs
247258
}
@@ -286,6 +297,9 @@ impl VmAirs {
286297
for air in &self.pages {
287298
refs.push(air);
288299
}
300+
for air in &self.memw_registers {
301+
refs.push(air);
302+
}
289303

290304
refs
291305
}
@@ -358,6 +372,9 @@ impl VmAirs {
358372
)
359373
})
360374
.collect();
375+
let memw_registers: Vec<_> = (0..table_counts.memw_register)
376+
.map(|i| create_memw_register_air(proof_options).with_name(&format!("MEMW_R[{}]", i)))
377+
.collect();
361378

362379
#[cfg(feature = "debug-checks")]
363380
debug_report::print_bus_legend();
@@ -378,6 +395,7 @@ impl VmAirs {
378395
commit,
379396
register,
380397
pages,
398+
memw_registers,
381399
}
382400
}
383401
}

prover/src/tables/memw.rs

Lines changed: 52 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111
//! - `timestamp`: DWordWL (64-bit timestamp, 2 cols)
1212
//! - `write2/4/8`: Bit (access width flags)
1313
//! - `old[8]`: BaseField[8] (previous values at address)
14-
//! - `add_limb_overflow[7]`: Bit[7] (carry flags for base_address + i)
14+
//! - `carry[7]`: Bit[7] (carry flags for base_address + i)
1515
//! - `old_timestamp[8]`: DWordWL[8] (previous timestamps, 16 cols)
1616
//! - `mu_read`, `mu_write`: multiplicity columns
1717
//!
1818
//! ## Virtual (computed inline)
19-
//! - `address_add[i]` = (base_address_0 + i+1 - 2^32 * overflow[i], base_address_1 + overflow[i])
19+
//! - `address_add[i]` = (base_address_0 + i+1 - 2^32 * carry[i], base_address_1 + carry[i])
2020
//! - `w2`: write2 + write4 + write8 (writing at least 2 bytes)
2121
//! - `w4`: write4 + write8 (writing at least 4 bytes)
2222
//! - `μ_sum`: μ_read + μ_write
@@ -25,6 +25,8 @@
2525
//! - 8 LT timestamp checks (old_timestamp[i] < timestamp)
2626
//! - 16 Memory bus tokens (read old + write new, per byte)
2727
//! - 2 MEMW output interactions (read + write, from CPU)
28+
//!
29+
//! ## Constraints (11 total: 2 custom + 2 IS_BIT for multiplicities + 7 IS_BIT for carry)
2830
2931
use math::field::element::FieldElement;
3032
use math::field::traits::{IsField, IsSubFieldOf};
@@ -72,8 +74,8 @@ pub mod cols {
7274
pub const OLD: [usize; 8] = [16, 17, 18, 19, 20, 21, 22, 23];
7375

7476
// Auxiliary columns
75-
/// add_limb_overflow[7]: Bit columns indicating carry when adding i+1 to base_address_0
76-
pub const ADD_LIMB_OVERFLOW: [usize; 7] = [24, 25, 26, 27, 28, 29, 30];
77+
/// carry[7]: Bit columns indicating carry when adding i+1 to base_address_0
78+
pub const CARRY: [usize; 7] = [24, 25, 26, 27, 28, 29, 30];
7779

7880
/// old_timestamp[8]: each is DWordWL (2 words = 2 columns)
7981
/// Total: 8 * 2 = 16 columns
@@ -206,11 +208,11 @@ pub fn generate_memw_trace(
206208
data[base + cols::OLD[i]] = FE::from(op.old[i]);
207209
}
208210

209-
// Auxiliary: add_limb_overflow[7]
210-
// overflow[i] = 1 if (base_address_lo + i+1) >= 2^32
211+
// Auxiliary: carry[7]
212+
// carry[i] = 1 if (base_address_lo + i+1) >= 2^32
211213
for i in 0..7 {
212214
let overflows = base_addr_lo + (i as u64 + 1) >= (1u64 << 32);
213-
data[base + cols::ADD_LIMB_OVERFLOW[i]] = FE::from(overflows as u64);
215+
data[base + cols::CARRY[i]] = FE::from(overflows as u64);
214216
}
215217

216218
// Auxiliary: old_timestamp[8] - each as DWordWL (2 words)
@@ -245,18 +247,17 @@ pub fn bus_interactions() -> Vec<BusInteraction> {
245247
// Memory bus interactions (16 total)
246248
// -------------------------------------------------------------------------
247249
// address_add[i] is VIRTUAL:
248-
// lo = base_address_0 + (i+1) - 2^32 * add_limb_overflow[i]
249-
// hi = base_address_1 + add_limb_overflow[i]
250+
// lo = base_address_0 + (i+1) - 2^32 * carry[i]
251+
// hi = base_address_1 + carry[i]
250252
//
251253
// Safety: `hi` is at most `base_address_1 + 1`. This never reaches 2^32
252254
// because the CPU table splits addresses into (lo, hi) with both halves
253255
// in [0, 2^32), and the Memw bus ties MEMW's base_address to the CPU's
254256
// value. MEMW only receives accesses where base_address_1 <= 0xFFFF_FFFE
255257
// (addresses near u64::MAX are rejected by the executor before proving).
256-
// Consequently, `add_limb_overflow[i]` is implicitly correct: a wrong
257-
// carry bit produces a memory token at a wrong address that has no
258-
// matching PAGE/REGISTER token, causing multiset imbalance and an
259-
// invalid proof.
258+
// Consequently, `carry[i]` is implicitly correct: a wrong carry bit
259+
// produces a memory token at a wrong address that has no matching
260+
// PAGE/REGISTER token, causing multiset imbalance and an invalid proof.
260261

261262
// CM8: memory[is_register, base_address, old_timestamp[0], old[0]] with +μ_sum
262263
interactions.push(BusInteraction::sender(
@@ -323,8 +324,8 @@ pub fn bus_interactions() -> Vec<BusInteraction> {
323324
));
324325

325326
// CM10/11: byte 1, multiplicity w2 = write2 + write4 + write8
326-
// address_add[0] is virtual: lo = base_address_0 + 1 - 2^32 * overflow[0]
327-
// hi = base_address_1 + overflow[0]
327+
// address_add[0] is virtual: lo = base_address_0 + 1 - 2^32 * carry[0]
328+
// hi = base_address_1 + carry[0]
328329
let addr_add_0_lo = BusValue::linear(vec![
329330
LinearTerm::Column {
330331
coefficient: 1,
@@ -333,7 +334,7 @@ pub fn bus_interactions() -> Vec<BusInteraction> {
333334
LinearTerm::Constant(1),
334335
LinearTerm::Column {
335336
coefficient: -(1i64 << 32),
336-
column: cols::ADD_LIMB_OVERFLOW[0],
337+
column: cols::CARRY[0],
337338
},
338339
]);
339340
let addr_add_0_hi = BusValue::linear(vec![
@@ -343,7 +344,7 @@ pub fn bus_interactions() -> Vec<BusInteraction> {
343344
},
344345
LinearTerm::Column {
345346
coefficient: 1,
346-
column: cols::ADD_LIMB_OVERFLOW[0],
347+
column: cols::CARRY[0],
347348
},
348349
]);
349350

@@ -401,7 +402,7 @@ pub fn bus_interactions() -> Vec<BusInteraction> {
401402

402403
// CM12/13: bytes 2-3 with multiplicity w4 = write4 + write8
403404
for i in 2..=3 {
404-
let overflow_col = cols::ADD_LIMB_OVERFLOW[i - 1];
405+
let overflow_col = cols::CARRY[i - 1];
405406
let addr_add_lo = BusValue::linear(vec![
406407
LinearTerm::Column {
407408
coefficient: 1,
@@ -479,7 +480,7 @@ pub fn bus_interactions() -> Vec<BusInteraction> {
479480

480481
// CM14/15: bytes 4-7 with multiplicity write8
481482
for i in 4..=7 {
482-
let overflow_col = cols::ADD_LIMB_OVERFLOW[i - 1];
483+
let overflow_col = cols::CARRY[i - 1];
483484
let addr_add_lo = BusValue::linear(vec![
484485
LinearTerm::Column {
485486
coefficient: 1,
@@ -857,7 +858,7 @@ where
857858
}
858859

859860
// =========================================================================
860-
// Constraints (9 total: 2 custom + 7 IS_BIT)
861+
// Constraints (11 total: 2 custom + 2 IS_BIT for multiplicities + 7 IS_BIT for carry)
861862
// =========================================================================
862863

863864
/// MEMW table constraint kinds.
@@ -946,10 +947,12 @@ impl TransitionConstraint<GoldilocksField, GoldilocksExtension> for MemwConstrai
946947

947948
/// Creates all constraints for the MEMW table.
948949
///
949-
/// 9 constraints total:
950+
/// 11 constraints total:
950951
/// - IS_BIT<μ_sum> (1)
951952
/// - w2 => μ_sum (1)
952-
/// - IS_BIT for add_limb_overflow[0..6] (7)
953+
/// - IS_BIT<μ_read> (1)
954+
/// - IS_BIT<μ_write> (1)
955+
/// - IS_BIT for carry[0..6] (7)
953956
pub fn constraints() -> Vec<Box<dyn TransitionConstraint<GoldilocksField, GoldilocksExtension>>> {
954957
let mut constraints: Vec<Box<dyn TransitionConstraint<GoldilocksField, GoldilocksExtension>>> =
955958
Vec::new();
@@ -970,8 +973,19 @@ pub fn constraints() -> Vec<Box<dyn TransitionConstraint<GoldilocksField, Goldil
970973
)));
971974
idx += 1;
972975

973-
// IS_BIT for add_limb_overflow[0..6]
974-
for &col in &cols::ADD_LIMB_OVERFLOW {
976+
// IS_BIT<μ_read>
977+
constraints.push(Box::new(IsBitConstraint::unconditional(cols::MU_READ, idx)));
978+
idx += 1;
979+
980+
// IS_BIT<μ_write>
981+
constraints.push(Box::new(IsBitConstraint::unconditional(
982+
cols::MU_WRITE,
983+
idx,
984+
)));
985+
idx += 1;
986+
987+
// IS_BIT for carry[0..6]
988+
for &col in &cols::CARRY {
975989
constraints.push(Box::new(IsBitConstraint::unconditional(col, idx)));
976990
idx += 1;
977991
}
@@ -1013,45 +1027,45 @@ mod tests {
10131027
}
10141028

10151029
#[test]
1016-
fn test_add_limb_overflow() {
1017-
// Address 0xFFFF_FFFF should overflow when adding 1
1030+
fn test_carry_flags() {
1031+
// Address 0xFFFF_FFFF should carry when adding 1
10181032
let op =
10191033
MemwOperation::new(false, 0xFFFF_FFFF, [0; 8], 100, 8, false).with_old([0; 8], [50; 8]);
10201034
let trace = generate_memw_trace(&[op]);
10211035

1022-
// All 7 overflow flags should be 1 since 0xFFFF_FFFF + i >= 2^32 for i >= 1
1036+
// All 7 carry flags should be 1 since 0xFFFF_FFFF + i >= 2^32 for i >= 1
10231037
for i in 0..7 {
1024-
let val = trace.get_main(0, cols::ADD_LIMB_OVERFLOW[i]);
1025-
assert_eq!(*val, FE::one(), "overflow[{i}] should be 1");
1038+
let val = trace.get_main(0, cols::CARRY[i]);
1039+
assert_eq!(*val, FE::one(), "carry[{i}] should be 1");
10261040
}
10271041

1028-
// Address 0x0000_0000 should not overflow
1042+
// Address 0x0000_0000 should not carry
10291043
let op2 =
10301044
MemwOperation::new(false, 0x0000_0000, [0; 8], 100, 8, false).with_old([0; 8], [50; 8]);
10311045
let trace2 = generate_memw_trace(&[op2]);
10321046
for i in 0..7 {
1033-
let val = trace2.get_main(0, cols::ADD_LIMB_OVERFLOW[i]);
1034-
assert_eq!(*val, FE::zero(), "overflow[{i}] should be 0");
1047+
let val = trace2.get_main(0, cols::CARRY[i]);
1048+
assert_eq!(*val, FE::zero(), "carry[{i}] should be 0");
10351049
}
10361050

10371051
// Address 0xFFFF_FFFE with width=8 exercises mixed per-byte carry bits:
1038-
// overflow[0]=0 (0xFFFF_FFFE+1 = 0xFFFF_FFFF < 2^32)
1039-
// overflow[1..6]=1 (0xFFFF_FFFE+2..8 >= 2^32)
1052+
// carry[0]=0 (0xFFFF_FFFE+1 = 0xFFFF_FFFF < 2^32)
1053+
// carry[1..6]=1 (0xFFFF_FFFE+2..8 >= 2^32)
10401054
let op3 =
10411055
MemwOperation::new(false, 0xFFFF_FFFE, [0; 8], 100, 8, false).with_old([0; 8], [50; 8]);
10421056
let trace3 = generate_memw_trace(&[op3]);
1043-
let val0 = trace3.get_main(0, cols::ADD_LIMB_OVERFLOW[0]);
1057+
let val0 = trace3.get_main(0, cols::CARRY[0]);
10441058
assert_eq!(
10451059
*val0,
10461060
FE::zero(),
1047-
"overflow[0] should be 0 for base 0xFFFF_FFFE"
1061+
"carry[0] should be 0 for base 0xFFFF_FFFE"
10481062
);
10491063
for i in 1..7 {
1050-
let val = trace3.get_main(0, cols::ADD_LIMB_OVERFLOW[i]);
1064+
let val = trace3.get_main(0, cols::CARRY[i]);
10511065
assert_eq!(
10521066
*val,
10531067
FE::one(),
1054-
"overflow[{i}] should be 1 for base 0xFFFF_FFFE"
1068+
"carry[{i}] should be 1 for base 0xFFFF_FFFE"
10551069
);
10561070
}
10571071
}

0 commit comments

Comments
 (0)