Skip to content

Commit 5f45386

Browse files
Expand verifier and core crates alongside redteam hardening
Large companion change to 3121bd9 (verifier fail-closed path): - verilm-core: updated attention, sampling, merkle, rope, types, and field modules to support the hardened canonical verifier - verilm-verify: expanded corridor, lib, and client; comprehensive test expansion in v4_e2e (4K+ lines), boundary_fuzz, bench_gate, cross_version, and gen_fixtures - verilm-keygen: updated key generation for new verifier shape checks - verilm-py: Python bindings updated to match new core/prover API - verilm-test-vectors: updated golden conformance and fiat-shamir tests All 337 local tests pass. Real-GPU validation: 36/36 adversarial (ap-7tbN8Jena5ycftyv7uc16m), freshness probe (ap-173XjnE6WMdFfTGuziAa9y), cross-model substitution clean rejection.
1 parent 3121bd9 commit 5f45386

32 files changed

Lines changed: 6681 additions & 1751 deletions

crates/verilm-core/src/attention.rs

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,7 @@ pub fn replay_attention_reference(
8686
let kv_head = qh / heads_per_kv;
8787

8888
// Extract Q head
89-
let q_head: Vec<f64> = (0..d_head)
90-
.map(|i| q_i8[qh * d_head + i] as f64)
91-
.collect();
89+
let q_head: Vec<f64> = (0..d_head).map(|i| q_i8[qh * d_head + i] as f64).collect();
9290

9391
// Compute attention scores: score[t] = q · k_t / sqrt(d)
9492
let scores: Vec<f64> = (0..seq_len)
@@ -153,16 +151,18 @@ pub fn replay_attention_roped(
153151
let heads_per_kv = cfg.n_q_heads / cfg.n_kv_heads;
154152
let inv_sqrt_d = 1.0 / (d_head as f64).sqrt();
155153
let seq_len = kv_cache_k_roped.len();
156-
let inv_scale = if scale_a.abs() > 1e-30 { 1.0 / scale_a } else { 1.0 };
154+
let inv_scale = if scale_a.abs() > 1e-30 {
155+
1.0 / scale_a
156+
} else {
157+
1.0
158+
};
157159

158160
let mut a = vec![0i8; cfg.hidden_dim];
159161

160162
for qh in 0..cfg.n_q_heads {
161163
let kv_head = qh / heads_per_kv;
162164

163-
let q_head: Vec<f64> = (0..d_head)
164-
.map(|i| q_roped[qh * d_head + i])
165-
.collect();
165+
let q_head: Vec<f64> = (0..d_head).map(|i| q_roped[qh * d_head + i]).collect();
166166

167167
// Attention scores: q · k_t / sqrt(d)
168168
let scores: Vec<f64> = (0..seq_len)
@@ -192,9 +192,7 @@ pub fn replay_attention_roped(
192192

193193
// Requantize: a_i8 = round(a_f64 / scale_a)
194194
for i in 0..d_head {
195-
a[qh * d_head + i] = (head_out[i] * inv_scale)
196-
.round()
197-
.clamp(-128.0, 127.0) as i8;
195+
a[qh * d_head + i] = (head_out[i] * inv_scale).round().clamp(-128.0, 127.0) as i8;
198196
}
199197
}
200198

@@ -217,17 +215,19 @@ pub fn replay_attention_roped_raw(
217215
let heads_per_kv = cfg.n_q_heads / cfg.n_kv_heads;
218216
let inv_sqrt_d = 1.0 / (d_head as f64).sqrt();
219217
let seq_len = kv_cache_k_roped.len();
220-
let inv_scale = if scale_a.abs() > 1e-30 { 1.0 / scale_a } else { 1.0 };
218+
let inv_scale = if scale_a.abs() > 1e-30 {
219+
1.0 / scale_a
220+
} else {
221+
1.0
222+
};
221223

222224
let mut a_i8 = vec![0i8; cfg.hidden_dim];
223225
let mut a_f64 = vec![0.0f64; cfg.hidden_dim];
224226

225227
for qh in 0..cfg.n_q_heads {
226228
let kv_head = qh / heads_per_kv;
227229

228-
let q_head: Vec<f64> = (0..d_head)
229-
.map(|i| q_roped[qh * d_head + i])
230-
.collect();
230+
let q_head: Vec<f64> = (0..d_head).map(|i| q_roped[qh * d_head + i]).collect();
231231

232232
let scores: Vec<f64> = (0..seq_len)
233233
.map(|t| {
@@ -255,9 +255,7 @@ pub fn replay_attention_roped_raw(
255255
for i in 0..d_head {
256256
let idx = qh * d_head + i;
257257
a_f64[idx] = head_out[i];
258-
a_i8[idx] = (head_out[i] * inv_scale)
259-
.round()
260-
.clamp(-128.0, 127.0) as i8;
258+
a_i8[idx] = (head_out[i] * inv_scale).round().clamp(-128.0, 127.0) as i8;
261259
}
262260
}
263261

@@ -337,8 +335,16 @@ pub fn measure_attention_diff(
337335
};
338336

339337
let n_f = n as f64;
340-
let frac_eq = if n > 0 { histogram[0] as f64 / n_f } else { 0.0 };
341-
let frac_le_1 = if n > 0 { (histogram[0] + histogram[1]) as f64 / n_f } else { 0.0 };
338+
let frac_eq = if n > 0 {
339+
histogram[0] as f64 / n_f
340+
} else {
341+
0.0
342+
};
343+
let frac_le_1 = if n > 0 {
344+
(histogram[0] + histogram[1]) as f64 / n_f
345+
} else {
346+
0.0
347+
};
342348
let frac_le_2 = if n > 0 {
343349
(histogram[0] + histogram[1] + histogram[2]) as f64 / n_f
344350
} else {
@@ -365,7 +371,11 @@ pub fn measure_attention_diff(
365371
/// Returns `None` if the vectors have equal length and the L-infinity difference
366372
/// is within `tolerance.max_abs_diff`. Returns `Some(i16::MAX)` if lengths differ
367373
/// (malformed input), or `Some(max_diff)` if the tolerance is exceeded.
368-
pub fn compare_attention_output(claimed: &[i8], replayed: &[i8], tolerance: &AttentionToleranceConfig) -> Option<i16> {
374+
pub fn compare_attention_output(
375+
claimed: &[i8],
376+
replayed: &[i8],
377+
tolerance: &AttentionToleranceConfig,
378+
) -> Option<i16> {
369379
if claimed.len() != replayed.len() {
370380
return Some(i16::MAX);
371381
}
@@ -480,11 +490,17 @@ mod tests {
480490
let claimed = vec![1i8, 2, 3];
481491
let replayed = vec![1i8, 2, 3, 4];
482492
let tol = AttentionToleranceConfig { max_abs_diff: 0 };
483-
assert_eq!(compare_attention_output(&claimed, &replayed, &tol), Some(i16::MAX));
493+
assert_eq!(
494+
compare_attention_output(&claimed, &replayed, &tol),
495+
Some(i16::MAX)
496+
);
484497

485498
// Extended claimed vector is also rejected
486499
let claimed2 = vec![1i8, 2, 3, 4, 5];
487-
assert_eq!(compare_attention_output(&claimed2, &replayed, &tol), Some(i16::MAX));
500+
assert_eq!(
501+
compare_attention_output(&claimed2, &replayed, &tol),
502+
Some(i16::MAX)
503+
);
488504
}
489505

490506
#[test]
@@ -508,7 +524,7 @@ mod tests {
508524
#[test]
509525
fn test_measure_diff_known_values() {
510526
// diffs: 0, 1, 2, 3, 5, 7
511-
let claimed = vec![10i8, 20, 30, 40, 50, 60];
527+
let claimed = vec![10i8, 20, 30, 40, 50, 60];
512528
let replayed = vec![10i8, 19, 28, 37, 45, 53];
513529
let stats = measure_attention_diff(&claimed, &replayed, 3, 10).unwrap();
514530
assert_eq!(stats.linf, 7);

crates/verilm-core/src/bounds.rs

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ pub fn compute_corridor_bound(
227227
// K,V committed (exact). Only Q has dequant+RoPE error.
228228
let eps_qk = params.c_rope * u;
229229
let eps_v = 0.0; // V is committed
230-
// Score error: |Δs_j| ≤ ε_qk·S_max (only Q perturbed)
230+
// Score error: |Δs_j| ≤ ε_qk·S_max (only Q perturbed)
231231
let softmax = params.bv_over_scale_a * 2.0 * eps_qk * params.s_max;
232232
let v_deq = 0.0;
233233
let fp32 = params.bv_over_scale_a * params.seq_len as f64 * u_f32;
@@ -350,12 +350,18 @@ mod tests {
350350
assert!((b.eps_qk - 5.0 * 2.0_f64.powi(-11)).abs() < 1e-10);
351351

352352
// softmax_term = 127 · 4 · 0.00244 · 20 ≈ 24.8
353-
assert!(b.softmax_term > 24.0 && b.softmax_term < 26.0,
354-
"softmax_term={}", b.softmax_term);
353+
assert!(
354+
b.softmax_term > 24.0 && b.softmax_term < 26.0,
355+
"softmax_term={}",
356+
b.softmax_term
357+
);
355358

356359
// Total ≈ 24.8 + 0.06 + negligible ≈ 24.9
357-
assert!(b.delta_o_over_scale_a > 24.0,
358-
"expected ~25, got {}", b.delta_o_over_scale_a);
360+
assert!(
361+
b.delta_o_over_scale_a > 24.0,
362+
"expected ~25, got {}",
363+
b.delta_o_over_scale_a
364+
);
359365
assert!(!b.achieves_leq_1);
360366
assert!(b.max_abs_diff_i8 >= 25);
361367
}
@@ -374,8 +380,11 @@ mod tests {
374380
let b = compute_corridor_bound(&params, CommittedIntermediates::CommittedKV);
375381

376382
// softmax_term = 127 · 2 · 0.00244 · 20 ≈ 12.4
377-
assert!(b.softmax_term > 12.0 && b.softmax_term < 13.0,
378-
"softmax_term={}", b.softmax_term);
383+
assert!(
384+
b.softmax_term > 12.0 && b.softmax_term < 13.0,
385+
"softmax_term={}",
386+
b.softmax_term
387+
);
379388
assert!(!b.achieves_leq_1);
380389
}
381390

@@ -393,8 +402,11 @@ mod tests {
393402
let b = compute_corridor_bound(&params, CommittedIntermediates::CommittedKV);
394403

395404
// 127 · 2 · 0.00244 · 8 ≈ 4.96
396-
assert!(b.softmax_term > 4.5 && b.softmax_term < 5.5,
397-
"softmax_term={}", b.softmax_term);
405+
assert!(
406+
b.softmax_term > 4.5 && b.softmax_term < 5.5,
407+
"softmax_term={}",
408+
b.softmax_term
409+
);
398410
// Still > 1
399411
assert!(!b.achieves_leq_1);
400412
}
@@ -413,8 +425,11 @@ mod tests {
413425
let b = compute_corridor_bound(&params, CommittedIntermediates::CommittedQKV);
414426

415427
// 127 · 3 · 4096 · 2^-24 ≈ 0.093
416-
assert!(b.delta_o_over_scale_a < 0.1,
417-
"expected < 0.1, got {}", b.delta_o_over_scale_a);
428+
assert!(
429+
b.delta_o_over_scale_a < 0.1,
430+
"expected < 0.1, got {}",
431+
b.delta_o_over_scale_a
432+
);
418433
assert!(b.achieves_leq_1);
419434
}
420435

@@ -432,8 +447,11 @@ mod tests {
432447
let b = compute_corridor_bound(&params, CommittedIntermediates::CommittedScores);
433448

434449
// v_dequant = 127 · 2^-11 ≈ 0.062
435-
assert!(b.v_dequant_term > 0.06 && b.v_dequant_term < 0.07,
436-
"v_dequant_term={}", b.v_dequant_term);
450+
assert!(
451+
b.v_dequant_term > 0.06 && b.v_dequant_term < 0.07,
452+
"v_dequant_term={}",
453+
b.v_dequant_term
454+
);
437455
assert!(b.delta_o_over_scale_a < 0.1);
438456
assert!(b.achieves_leq_1);
439457
}

crates/verilm-core/src/constants.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,9 @@ pub struct RopeScaling {
139139
#[derive(Debug, Clone, Serialize, Deserialize)]
140140
pub struct ModelConfig {
141141
pub name: String,
142-
pub hidden_dim: usize, // n
143-
pub kv_dim: usize, // n_kv = n_kv_heads * d_head
144-
pub ffn_dim: usize, // n_ffn
142+
pub hidden_dim: usize, // n
143+
pub kv_dim: usize, // n_kv = n_kv_heads * d_head
144+
pub ffn_dim: usize, // n_ffn
145145
pub d_head: usize,
146146
pub n_layers: usize,
147147
pub n_q_heads: usize,
@@ -161,7 +161,7 @@ impl ModelConfig {
161161
ModelConfig {
162162
name: "Llama-3-70B".into(),
163163
hidden_dim: 8192,
164-
kv_dim: 1024, // 8 KV heads * 128
164+
kv_dim: 1024, // 8 KV heads * 128
165165
ffn_dim: 28672,
166166
d_head: 128,
167167
n_layers: 80,
@@ -177,7 +177,7 @@ impl ModelConfig {
177177
ModelConfig {
178178
name: "Llama-3-8B".into(),
179179
hidden_dim: 4096,
180-
kv_dim: 1024, // 8 KV heads * 128
180+
kv_dim: 1024, // 8 KV heads * 128
181181
ffn_dim: 14336,
182182
d_head: 128,
183183
n_layers: 32,
@@ -194,7 +194,7 @@ impl ModelConfig {
194194
ModelConfig {
195195
name: "Llama-3.1-8B".into(),
196196
hidden_dim: 4096,
197-
kv_dim: 1024, // 8 KV heads * 128
197+
kv_dim: 1024, // 8 KV heads * 128
198198
ffn_dim: 14336,
199199
d_head: 128,
200200
n_layers: 32,
@@ -216,7 +216,7 @@ impl ModelConfig {
216216
ModelConfig {
217217
name: "Llama-3-405B".into(),
218218
hidden_dim: 16384,
219-
kv_dim: 1024, // 8 KV heads * 128
219+
kv_dim: 1024, // 8 KV heads * 128
220220
ffn_dim: 53248,
221221
d_head: 128,
222222
n_layers: 126,
@@ -233,7 +233,7 @@ impl ModelConfig {
233233
ModelConfig {
234234
name: "toy".into(),
235235
hidden_dim: 16,
236-
kv_dim: 4, // 2 KV heads * 2
236+
kv_dim: 4, // 2 KV heads * 2
237237
ffn_dim: 32,
238238
d_head: 2,
239239
n_layers: 2,

crates/verilm-core/src/field.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,11 @@ impl Fp64 {
140140
let lo2 = sum & P64;
141141
let hi2 = sum >> 61;
142142
let r = lo2 + hi2;
143-
if r >= P64 { r - P64 } else { r }
143+
if r >= P64 {
144+
r - P64
145+
} else {
146+
r
147+
}
144148
}
145149

146150
pub fn new(val: u64) -> Self {
@@ -379,7 +383,11 @@ impl Fp128 {
379383
let lo = val & P128;
380384
let hi = val >> 127;
381385
let r = lo + hi;
382-
if r >= P128 { r - P128 } else { r }
386+
if r >= P128 {
387+
r - P128
388+
} else {
389+
r
390+
}
383391
}
384392

385393
pub fn new(val: u128) -> Self {

crates/verilm-core/src/freivalds.rs

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
//! any input x without actually computing Wx. Since v = r^T W and the
1414
//! prover knows W (open weights), leaking v also leaks r.
1515
16-
use crate::field::{Fp, Fp64, Fp128, P64};
16+
use crate::field::{Fp, Fp128, Fp64, P64};
1717

1818
/// Precompute v = r^T W in F_p.
1919
///
@@ -112,7 +112,7 @@ pub fn derive_block_coefficients(
112112
matrix_idx: usize,
113113
n_blocks: usize,
114114
) -> Vec<Fp> {
115-
use sha2::{Sha256, Digest};
115+
use sha2::{Digest, Sha256};
116116

117117
let mut coeffs = Vec::with_capacity(n_blocks);
118118
for b in 0..n_blocks {
@@ -145,13 +145,7 @@ pub fn derive_block_coefficients(
145145
/// - `c`: random batching coefficients (length = n_blocks)
146146
///
147147
/// Returns true if the check passes.
148-
pub fn check_q8_blocks(
149-
v: &[Fp],
150-
x: &[i8],
151-
r: &[Fp],
152-
sumi: &[Vec<i32>],
153-
c: &[Fp],
154-
) -> bool {
148+
pub fn check_q8_blocks(v: &[Fp], x: &[i8], r: &[Fp], sumi: &[Vec<i32>], c: &[Fp]) -> bool {
155149
let n_blocks = sumi.len();
156150
assert_eq!(c.len(), n_blocks);
157151
assert_eq!(v.len(), n_blocks * Q8_0_BLOCK_SIZE);
@@ -433,10 +427,8 @@ mod tests {
433427
];
434428
let d_w = vec![
435429
// row 0: scale_b0, scale_b1
436-
1.0, 2.0,
437-
// row 1
438-
0.5, 1.5,
439-
// row 2
430+
1.0, 2.0, // row 1
431+
0.5, 1.5, // row 2
440432
1.0, 1.0,
441433
];
442434
let d_x = vec![1.0, 0.5];
@@ -479,7 +471,9 @@ mod tests {
479471
// Flat check
480472
let z: Vec<i32> = (0..rows)
481473
.map(|row| {
482-
(0..cols).map(|col| w[row * cols + col] as i32 * x[col] as i32).sum()
474+
(0..cols)
475+
.map(|col| w[row * cols + col] as i32 * x[col] as i32)
476+
.sum()
483477
})
484478
.collect();
485479
assert!(check(&v, &x, &r, &z));

0 commit comments

Comments
 (0)