Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6e3ad65
docs(security): specify selective L2 sizing
quangvdao Aug 6, 2026
5f6f8db
docs(security): link selective L2 draft
quangvdao Aug 6, 2026
d86586f
security(sis): add physical norm foundations
quangvdao Aug 6, 2026
ed6c62f
feat(protocol): bind selective L2 proof shape
quangvdao Aug 6, 2026
395cbc7
feat(protocol): prove physical L2 fold norms
quangvdao Aug 6, 2026
92e7aee
feat(protocol): fuse physical L2 range proof
quangvdao Aug 6, 2026
0a7993d
feat(planner): select measured L2 fold routes
quangvdao Aug 7, 2026
fd68594
test(protocol): harden selective L2 fold path
quangvdao Aug 7, 2026
3f0931d
docs(security): document selective L2 fold sizing
quangvdao Aug 7, 2026
00a7a0c
refactor(protocol): isolate Linf fold grinding
quangvdao Aug 7, 2026
b09ab0a
fix(security): close selective L2 audit gaps
quangvdao Aug 7, 2026
c064889
docs(security): reconcile selective L2 lifecycle
quangvdao Aug 7, 2026
2280003
style(security): satisfy strict Clippy graphs
quangvdao Aug 7, 2026
4465e58
fix(security): close remote selective L2 gaps
quangvdao Aug 7, 2026
8edff34
fix(profile): report physical L2 proof costs
quangvdao Aug 7, 2026
b320182
fix(planner): bound selective L2 rollout
quangvdao Aug 7, 2026
a15d792
fix(profile): preserve failed benchmark reports
quangvdao Aug 7, 2026
dd0a09b
docs(security): complete selective L2 acceptance
quangvdao Aug 7, 2026
b7232a0
merge(main): integrate signed-sparse cutover
quangvdao Aug 7, 2026
e2adafb
refactor(l2): reuse canonical fold machinery
quangvdao Aug 7, 2026
5cce751
merge(main): integrate adaptive dimensions
quangvdao Aug 8, 2026
cba52fd
Merge branch 'main' into feat/selective-l2-fold-sizing
quangvdao Aug 9, 2026
aab0a13
chore(main): merge verifier cutover
quangvdao Aug 9, 2026
1163273
fix(planner): close l2 acceptance gaps
quangvdao Aug 9, 2026
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions book/src/how/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,5 @@ norm. Keep the fold-reprice correction explicit.
- Paper §3.12 `sec:batched-soundness` (`def:batched-weak-opening`, `lem:batched-weak-binding`, `prop:committed-fold-price`).
- `specs/weak-binding-norm-fix.md` (fold reprice — keep the correction section).
- `specs/fold-linf-rejection.md` (fold digit-count tightening).
- `specs/selective-l2-fold-security-sizing.md` (active physical norm correction
and optional L2 route).
22 changes: 12 additions & 10 deletions crates/akita-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ mod tests {
#[cfg(test)]
mod sis_schedule_width_audit {
use super::*;
use akita_types::sis::min_secure_rank;
use akita_types::sis::{min_secure_l2_rank, min_secure_rank, InnerCommitSecurityRoute};

pub(super) fn assert_schedule_stays_within_audited_sis_widths(
schedule: &FoldSchedule,
Expand All @@ -577,10 +577,13 @@ mod sis_schedule_width_audit {
{
let d = u32::try_from(lp.d_a()).expect("ring dimension fits in u32");

let a_rank = min_secure_rank(
lp.inner_commit_matrix.sis_table_key(),
u64::try_from(lp.inner_width()).expect("inner width should fit in u64"),
)
let width = u64::try_from(lp.inner_width()).expect("inner width should fit in u64");
let a_rank = match lp.inner_commit_matrix.security_route() {
InnerCommitSecurityRoute::Linf(key) => min_secure_rank(key, width),
InnerCommitSecurityRoute::L2 { table_key, .. } => {
min_secure_l2_rank(table_key, width)
}
}
.unwrap_or_else(|| {
panic!(
"missing audited A-row SIS width for D={d}, num_vars={num_vars}, level={level_idx}, lb={}, width={}",
Expand Down Expand Up @@ -690,11 +693,10 @@ mod fp128_policy_tests {
let schedule =
SmallCfg::get_params_for_prove(&opening_batch).expect("small-field schedule");
let root_params = &schedule.root.params.final_group.commitment;
assert!(
root_params.inner_commit_matrix.coeff_linf_bound()
>= root_params.outer_commit_matrix.coeff_linf_bound() * 2,
"A-role L-infinity bound should include the psi norm bound"
);
assert!(root_params
.inner_commit_matrix
.coeff_linf_bound()
.is_some_and(|bound| bound > 0));
}

#[test]
Expand Down
9 changes: 7 additions & 2 deletions crates/akita-config/tests/runtime_fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,16 @@ fn resolved_row_audit_rejects_low_rank_root_d_and_terminal_a() {
.witness
.inner_commit_matrix = akita_types::InnerCommitMatrixParams::new_unchecked(
matrix.security_policy(),
matrix.sis_table_key().table_digest,
matrix
.sis_table_key()
.expect("terminal test matrix is L infinity")
.table_digest,
matrix.sis_modulus_profile(),
0,
matrix.input_width(),
matrix.coeff_linf_bound(),
matrix
.coeff_linf_bound()
.expect("terminal test matrix is L infinity"),
matrix.ring_dimension(),
);
assert_mutated_row_is_rejected::<Cfg>(profiles, low_rank_terminal);
Expand Down
1 change: 1 addition & 0 deletions crates/akita-pcs/benches/setup_index_weight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ fn make_case_with_shape(
level_params
.inner_commit_matrix
.sis_table_key()
.expect("benchmark setup matrix is L infinity")
.table_digest,
level_params.inner_commit_matrix.sis_modulus_profile(),
n_a,
Expand Down
2 changes: 2 additions & 0 deletions crates/akita-pcs/src/scheme/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ fn expected_same_point_batched_shape(
stage1_stages: DigitRangePlan::new(1usize << root_params.log_basis_open)
.expect("scheduled root range basis")
.stage_shapes(root_rounds),
stage1_norm: None,
stage2_sumcheck_proof: vec![3; root_rounds],
stage3_sumcheck: None,
next_witness_binding: match root_successor {
Expand Down Expand Up @@ -197,6 +198,7 @@ fn expected_same_point_batched_shape(
stage1_stages: DigitRangePlan::new(1usize << level_params.log_basis_open)
.expect("scheduled range basis")
.stage_shapes(rounds),
stage1_norm: None,
stage2_sumcheck_proof: vec![3; rounds],
stage3_sumcheck: None,
next_witness_binding: match schedule.recursive_folds.get(index + 1) {
Expand Down
13 changes: 10 additions & 3 deletions crates/akita-planner/src/emit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@ fn generated_entry(
.params
.witness
.inner_commit_matrix
.coeff_linf_bound(),
.coeff_linf_bound()
.expect("validated terminal matrix is L infinity"),
z_admission_linf_cap: terminal_group.z_admission_linf_cap,
z_rice_low_bits: terminal_group.z_rice_low_bits,
z_payload_bytes: terminal_group.z_payload_bytes as u64,
Expand Down Expand Up @@ -269,7 +270,10 @@ fn emit_precommitted_group_key(layout: &CommittedGroupProfile) -> String {
"InnerCommitMatrixParams",
layout.inner_commit_matrix.output_rank(),
layout.inner_commit_matrix.input_width(),
layout.inner_commit_matrix.sis_table_key(),
layout
.inner_commit_matrix
.sis_table_key()
.expect("validated precommitted matrix is L infinity"),
),
layout.log_basis_outer,
layout.num_digits_outer,
Expand Down Expand Up @@ -297,7 +301,10 @@ fn emit_generated_precommitted_profile(profile: &CommittedGroupProfile) -> Strin
emit_generated_committed_profile_group(profile),
profile.num_digits_inner,
profile.inner_commit_matrix.output_rank(),
profile.inner_commit_matrix.coeff_linf_bound(),
profile
.inner_commit_matrix
.coeff_linf_bound()
.expect("validated precommitted matrix is L infinity"),
profile.num_digits_outer,
profile.outer_commit_matrix.output_rank(),
profile.outer_commit_matrix.coeff_linf_bound(),
Expand Down
10 changes: 7 additions & 3 deletions crates/akita-planner/src/planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,15 @@ fn materialize_precommitted_group_for_open_basis(
ring_challenge_cfg,
challenge_shape,
num_digits_fold,
policy.ring_subfield_norm_bound,
)
.ok_or_else(|| AkitaError::InvalidSetup("no precommitted A-role norm".to_string()))?;
if required_a_bound > group.inner_commit_matrix.coeff_linf_bound() {
let declared_a_bound = group
.inner_commit_matrix
.coeff_linf_bound()
.ok_or_else(|| {
AkitaError::InvalidSetup("precommitted A cannot use an L2 security route".to_string())
})?;
if required_a_bound > declared_a_bound {
return Err(AkitaError::InvalidSetup(
"precommitted A bound does not cover the certified opening basis".to_string(),
));
Expand Down Expand Up @@ -433,7 +438,6 @@ fn root_final_group_level_params_candidate(
ctx.ring_challenge_cfg,
fold_challenge_shape,
num_digits_fold,
policy.ring_subfield_norm_bound,
) else {
return Ok(None);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ pub(crate) fn recursive_fold_level_params_candidate(
ring_challenge_cfg,
fold_challenge_shape,
num_digits_fold,
policy.ring_subfield_norm_bound,
) else {
return Ok(None);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,6 @@ pub(in crate::schedule_params) fn derive_setup_prefix_group(
ring_challenge_cfg,
fold_shape,
num_digits_fold,
policy.ring_subfield_norm_bound,
) else {
continue;
};
Expand Down
133 changes: 115 additions & 18 deletions crates/akita-prover/src/protocol/core/fold/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,18 @@
use akita_field::AdditiveGroup;

use akita_types::{
dispatch_for_field, DigitRangeEqualityPoint, DigitRangePlan, OpeningClaimsLayout,
RelationRangeImagePlan,
dispatch_for_field, DigitRangeEqualityPoint, DigitRangePlan, InnerCommitSecurityRoute,
OpeningClaimsLayout, PhysicalResponsePlan, RelationRangeImagePlan,
};

pub(in crate::protocol::core) struct PhysicalL2ProverReplay<E: FieldCore> {
plan: PhysicalResponsePlan,
point: Vec<E>,
virtual_evaluations: Vec<E>,
batching: Vec<E>,
claim: E,
}

pub(in crate::protocol::core) use extension_claim::{
prepare_extension_claim_fold, ExtensionOpeningSource,
};
Expand Down Expand Up @@ -431,12 +439,27 @@
rs.alpha,
prepared_fold.instance.rhs(),
)?;
let (stage1_proof, stage1_point, range_image_evaluation) =
prove_stage1::<F, E, T>(transcript, &mut rs, &relation_range_image_plan)?;
let (stage1_proof, stage1_point, range_image_evaluation, physical_l2) =
prove_stage1::<F, E, T>(transcript, &mut rs, lp, &relation_range_image_plan)?;
transcript.append_serde(
ABSORB_RANGE_IMAGE_EVALUATION,
&stage1_proof.range_image_evaluation,
);
let physical_l2 = physical_l2.map(|mut replay| {
let eta = sample_ext_challenge::<F, E, T>(
transcript,
akita_transcript::labels::CHALLENGE_L2_VIRTUAL_BATCH,
);
let mut power = E::one();
replay.batching = Vec::with_capacity(replay.virtual_evaluations.len());
replay.claim = E::zero();
for &evaluation in &replay.virtual_evaluations {
replay.batching.push(power);
replay.claim += evaluation * power;
power *= eta;
}
replay
});
let stage1_proof = Some(stage1_proof);
let binary_batching = lp
.payload_mode
Expand Down Expand Up @@ -491,6 +514,7 @@
range_image_evaluation,
relation_claim,
binary_batching,
physical_l2,
evaluation_trace,
trace_opening_claim,
relation_range_image_plan,
Expand Down Expand Up @@ -584,8 +608,17 @@
pub(in crate::protocol::core) fn prove_stage1<F, E, T>(
transcript: &mut T,
rs: &mut RingSwitchOutput<E>,
lp: &CommittedGroupParams,
plan: &RelationRangeImagePlan,
) -> Result<(AkitaStage1Proof<E>, Vec<E>, E), AkitaError>
) -> Result<

Check failure on line 613 in crates/akita-prover/src/protocol/core/fold/mod.rs

View workflow job for this annotation

GitHub Actions / Clippy

very complex type used. Consider factoring parts into `type` definitions
(
AkitaStage1Proof<E>,
Vec<E>,
E,
Option<PhysicalL2ProverReplay<E>>,
),
AkitaError,
>
where
F: FieldCore + CanonicalField,
E: ExtField<F> + HasUnreducedOps + HasOptimizedFold + FromPrimitiveInt + AkitaSerialize,
Expand Down Expand Up @@ -617,9 +650,54 @@
domain,
equality_point,
)?;
let (stage1_proof, stage1_point) = stage1_prover.prove::<F, T>(transcript)?;
let physical_plan = PhysicalResponsePlan::new(lp, plan)?;
let (stage1_proof, stage1_point) = match physical_plan.as_ref() {
Some(physical_plan) => stage1_prover.prove_l2::<F, T>(physical_plan, transcript)?,
None => stage1_prover.prove::<F, T>(transcript)?,
};
let range_image_evaluation = stage1_proof.range_image_evaluation;
Ok((stage1_proof, stage1_point, range_image_evaluation))
let physical_l2 = match physical_plan {
Some(physical_plan) => {
let norm_proof = stage1_proof
.norm_proof
.as_ref()
.ok_or(AkitaError::InvalidProof)?;
let InnerCommitSecurityRoute::L2 {
response_l2_sq_cap, ..
} = lp.inner_commit_matrix.security_route()
else {
return Err(AkitaError::InvalidSetup(
"physical L2 plan disagrees with the A security route".into(),
));
};
if norm_proof.response_l2_sq > response_l2_sq_cap {
return Err(AkitaError::InvalidInput(
"folded response exceeds the scheduled L2 cap".into(),
));
}
Some(PhysicalL2ProverReplay {
plan: physical_plan,
point: stage1_point.clone(),
virtual_evaluations: norm_proof.virtual_evaluations.clone(),
batching: Vec::new(),
claim: E::zero(),
})
}
None => {
if stage1_proof.norm_proof.is_some() {
return Err(AkitaError::InvalidInput(
"L-infinity route produced an L2 norm proof".into(),
));
}
None
}
};
Ok((
stage1_proof,
stage1_point,
range_image_evaluation,
physical_l2,
))
}

#[allow(clippy::too_many_arguments)]
Expand All @@ -632,6 +710,7 @@
range_image_evaluation: E,
relation_claim: E,
binary_batching: Option<E>,
physical_l2: Option<PhysicalL2ProverReplay<E>>,
evaluation_trace: PreparedProverEvaluationTrace<E>,
trace_opening_claim: E,
plan: RelationRangeImagePlan,
Expand Down Expand Up @@ -665,23 +744,41 @@
common_alpha_factor.len(),
)));
}
let additional_relation_terms = rs
.compression_relation_weights
.map(|weights| {
let compression_domain_len = weights.physical_field_len();
let binary_support =
NegativeBinarySupport::new(plan.witness_layout(), compression_domain_len)?;
let domain_len = domain.domain_len();
let mut linear_weights = Vec::new();
let mut binary_intervals = Vec::new();
if let Some(weights) = rs.compression_relation_weights {
if weights.physical_field_len() != domain_len {
return Err(AkitaError::InvalidSetup(
"compression relation domain disagrees with Stage 2".into(),
));
}
linear_weights = weights.into_sparse_entries()?;
binary_intervals = NegativeBinarySupport::new(plan.witness_layout(), domain_len)?
.intervals()
.to_vec();
}
let physical_l2_claim = physical_l2.as_ref().map_or_else(E::zero, |norm| norm.claim);
if let Some(norm) = &physical_l2 {
linear_weights.extend(
norm.plan
.virtualization_weights(&norm.point, &norm.batching)?,
);
linear_weights.sort_unstable_by_key(|(index, _)| *index);
}
let additional_relation_terms = (!linear_weights.is_empty() || !binary_intervals.is_empty())
.then(|| {
AdditionalRelationTerms::new(
rs.w_evals_compact.as_ref(),
compression_domain_len,
weights.into_sparse_entries()?,
binary_support.intervals(),
domain_len,
linear_weights,
&binary_intervals,
stage1_point,
binary_batching.ok_or(AkitaError::InvalidProof)?,
binary_batching.unwrap_or_else(E::zero),
)
})
.transpose()?;
let ordinary_relation_claim = relation_claim
let ordinary_relation_claim = relation_claim + physical_l2_claim
- additional_relation_terms
.as_ref()
.map_or_else(E::zero, AdditionalRelationTerms::input_claim);
Expand Down
Loading
Loading