use std::{format, string::String, vec};
use k256::{ProjectivePoint, elliptic_curve::sec1::ToSec1Point};
use miden_air::lookup::Challenges;
use miden_core::{
Felt,
field::QuadFelt,
utils::{Matrix, RowMajorMatrix},
};
use miden_precompiles::CurveId;
use rand::{Rng, RngExt, SeedableRng, rngs::StdRng};
use crate::{
math::{U256, from_hex},
relations::{MAX_MESSAGE_WIDTH, NUM_BUS_IDS},
session::{Session, SessionTraces},
tests::{SessionTracesTestExt, bus_balance::session_stack_residual, verify_deferred},
transcript::eval::{
COL_A_PTR, COL_B_PTR, COL_BOUND_PTR, COL_IS_EC_CREATE, COL_IS_EC_OP, COL_IS_EC_PAI,
COL_IS_SUB, COL_LHS_BEGIN, COL_PTR, COL_RHS_BEGIN, DIGEST_WIDTH,
NUM_MAIN_COLS as EVAL_COLS, TranscriptEvalAir,
},
};
const FP: u32 = CurveId::Secp256k1.base_domain().bound_ptr();
const GROUP_PTR: u32 = CurveId::Secp256k1.group_ptr();
fn rand_qf(rng: &mut impl Rng) -> QuadFelt {
QuadFelt::new([Felt::from(rng.random::<u32>()), Felt::from(rng.random::<u32>())])
}
fn be_to_u256(bytes: impl AsRef<[u8]>) -> U256 {
let hex: String = bytes.as_ref().iter().map(|b| format!("{b:02x}")).collect();
from_hex(&hex)
}
fn k256_coords(p: &ProjectivePoint) -> (U256, U256) {
let enc = p.to_affine().to_sec1_point(false);
(
be_to_u256(enc.x().expect("finite point")),
be_to_u256(enc.y().expect("finite point")),
)
}
fn ec_dag_3g_traces() -> SessionTraces {
let g = ProjectivePoint::GENERATOR;
let (gx, gy) = k256_coords(&g);
let (g2x, g2y) = k256_coords(&(g + g));
let (g3x, g3y) = k256_coords(&(g + g + g));
let mut s = Session::new();
let gx_n = s.uint_leaf(gx, FP);
let gy_n = s.uint_leaf(gy, FP);
let g2x_n = s.uint_leaf(g2x, FP);
let g2y_n = s.uint_leaf(g2y, FP);
let g_pt = s.ec_create(GROUP_PTR, &gx_n, &gy_n);
let g2_pt = s.ec_create(GROUP_PTR, &g2x_n, &g2y_n);
let r = s.ec_add(&g_pt, &g2_pt);
let g3x_n = s.uint_leaf(g3x, FP);
let g3y_n = s.uint_leaf(g3y, FP);
let expected = s.ec_create(GROUP_PTR, &g3x_n, &g3y_n);
let claim = s.ec_is(&r, &expected);
let root = s.assert_and_fold([claim]);
s.finish(root)
}
#[test]
fn ec_dag_add_matches_k256() {
let traces = ec_dag_3g_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn ec_dag_add_proves() {
let traces = ec_dag_3g_traces();
let proof = traces.prove();
verify_deferred(&proof).expect("EC DAG round-trip must verify");
}
fn ec_dag_sub_from_pai_traces() -> SessionTraces {
let g = ProjectivePoint::GENERATOR;
let (gx, gy) = k256_coords(&g);
let (g2x, g2y) = k256_coords(&(g + g));
let (g3x, g3y) = k256_coords(&(g + g + g));
let (ngx, ngy) = k256_coords(&(-g));
let mut s = Session::new();
let create = |s: &mut Session, x: U256, y: U256| {
let xn = s.uint_leaf(x, FP);
let yn = s.uint_leaf(y, FP);
s.ec_create(GROUP_PTR, &xn, &yn)
};
let g_pt = create(&mut s, gx, gy);
let inf = s.ec_pai(GROUP_PTR);
let neg_g = s.ec_sub(&inf, &g_pt);
let neg_kat = create(&mut s, ngx, ngy);
let neg_claim = s.ec_is(&neg_g, &neg_kat);
let g3_pt = create(&mut s, g3x, g3y);
let diff = s.ec_sub(&g3_pt, &g_pt);
let g2_kat = create(&mut s, g2x, g2y);
let sub_claim = s.ec_is(&diff, &g2_kat);
let root = s.assert_and_fold([neg_claim, sub_claim]);
s.finish(root)
}
#[test]
fn ec_dag_sub_from_pai_matches_k256() {
let traces = ec_dag_sub_from_pai_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn ec_dag_sub_from_pai_proves() {
verify_deferred(&ec_dag_sub_from_pai_traces().prove())
.expect("EC DAG sub-from-PAI/sub round-trip must verify");
}
fn ec_dag_pai_traces() -> SessionTraces {
let g = ProjectivePoint::GENERATOR;
let (gx, gy) = k256_coords(&g);
let (g2x, g2y) = k256_coords(&(g + g));
let mut s = Session::new();
let create = |s: &mut Session, x: U256, y: U256| {
let xn = s.uint_leaf(x, FP);
let yn = s.uint_leaf(y, FP);
s.ec_create(GROUP_PTR, &xn, &yn)
};
let inf = s.ec_pai(GROUP_PTR);
let g_pt = create(&mut s, gx, gy);
let g2_pt = create(&mut s, g2x, g2y);
let pp = s.ec_add(&inf, &g_pt); let pq = s.ec_add(&g2_pt, &inf); let bb = s.ec_add(&inf, &inf);
let c1 = s.ec_is(&pp, &g_pt);
let c2 = s.ec_is(&pq, &g2_pt);
let c3 = s.ec_is(&bb, &inf);
let root = s.assert_and_fold([c1, c2, c3]);
s.finish(root)
}
#[test]
fn ec_dag_pai_passthroughs_hold() {
let traces = ec_dag_pai_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn ec_dag_pai_proves() {
verify_deferred(&ec_dag_pai_traces().prove()).expect("EC DAG PAI round-trip must verify");
}
fn ec_dag_double_traces() -> SessionTraces {
let g = ProjectivePoint::GENERATOR;
let (gx, gy) = k256_coords(&g);
let (g2x, g2y) = k256_coords(&(g + g));
let mut s = Session::new();
let gx_n = s.uint_leaf(gx, FP);
let gy_n = s.uint_leaf(gy, FP);
let g_pt = s.ec_create(GROUP_PTR, &gx_n, &gy_n);
let dbl = s.ec_add(&g_pt, &g_pt);
let g2x_n = s.uint_leaf(g2x, FP);
let g2y_n = s.uint_leaf(g2y, FP);
let g2_kat = s.ec_create(GROUP_PTR, &g2x_n, &g2y_n);
let claim = s.ec_is(&dbl, &g2_kat);
let root = s.assert_and_fold([claim]);
s.finish(root)
}
#[test]
fn ec_dag_double_matches_k256() {
let traces = ec_dag_double_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn ec_dag_double_proves() {
verify_deferred(&ec_dag_double_traces().prove()).expect("EC DAG double round-trip must verify");
}
fn dag_residual(
traces: &SessionTraces,
eval_main: &RowMajorMatrix<Felt>,
rng: &mut impl Rng,
) -> usize {
dag_residual_with(traces, eval_main, traces.mains()[8], rng)
}
fn dag_residual_with(
traces: &SessionTraces,
eval_main: &RowMajorMatrix<Felt>,
add_main: &RowMajorMatrix<Felt>,
rng: &mut impl Rng,
) -> usize {
let mains = traces.mains();
let challenges = Challenges::new(rand_qf(rng), rand_qf(rng), MAX_MESSAGE_WIDTH, NUM_BUS_IDS);
session_stack_residual(&mains, &[(4, eval_main), (8, add_main)], &challenges).len()
}
fn first_row_with_flag(main: &RowMajorMatrix<Felt>, col: usize) -> usize {
let ncols = main.width();
(0..main.height())
.find(|&r| main.values[r * ncols + col] == Felt::ONE)
.expect("no row with that flag")
}
fn first_ec_op_row(main: &RowMajorMatrix<Felt>, op_col: usize) -> usize {
(0..main.height())
.find(|&r| {
main.values[r * EVAL_COLS + COL_IS_EC_OP] == Felt::ONE
&& main.values[r * EVAL_COLS + op_col] == Felt::ONE
})
.expect("no ec-op row with that op flag")
}
fn tamper(
main: &RowMajorMatrix<Felt>,
row: usize,
cells: &[(usize, Felt)],
) -> RowMajorMatrix<Felt> {
let ncols = main.width();
let mut m = main.clone();
for &(col, v) in cells {
m.values[row * ncols + col] = v;
}
m
}
fn eval_locally_holds(traces: &SessionTraces, eval_main: &RowMajorMatrix<Felt>) {
crate::tests::check_local_inputs(
TranscriptEvalAir,
eval_main,
traces.public_root().as_array().to_vec(),
);
}
#[test]
#[should_panic(expected = "constraint not satisfied")]
fn dag_pai_payload_must_be_true_true() {
let traces = ec_dag_pai_traces();
let eval = traces.mains()[4];
let row = first_row_with_flag(eval, COL_IS_EC_PAI);
let forged = tamper(eval, row, &[(COL_LHS_BEGIN, Felt::ONE)]);
eval_locally_holds(&traces, &forged);
}
#[test]
fn dag_finite_forged_as_pai_unbalances() {
let traces = ec_dag_3g_traces();
let mut rng = StdRng::seed_from_u64(0xec_da9_f01);
let eval = traces.mains()[4];
assert_eq!(dag_residual(&traces, eval, &mut rng), 0, "honest stack must balance");
let row = first_row_with_flag(eval, COL_IS_EC_CREATE);
let mut cells = vec![
(COL_IS_EC_CREATE, Felt::ZERO),
(COL_IS_EC_PAI, Felt::ONE),
(COL_BOUND_PTR, Felt::ZERO),
(COL_A_PTR, Felt::ZERO),
(COL_B_PTR, Felt::ZERO),
];
for i in 0..DIGEST_WIDTH {
cells.push((COL_LHS_BEGIN + i, Felt::ZERO));
cells.push((COL_RHS_BEGIN + i, Felt::ZERO));
}
let forged = tamper(eval, row, &cells);
eval_locally_holds(&traces, &forged);
assert_ne!(
dag_residual(&traces, &forged, &mut rng),
0,
"the bus must catch a finite point forged as ∞",
);
}
#[test]
fn dag_sub_result_forged_unbalances() {
let traces = ec_dag_sub_from_pai_traces();
let mut rng = StdRng::seed_from_u64(0xec_da9_f03);
let eval = traces.mains()[4];
assert_eq!(dag_residual(&traces, eval, &mut rng), 0, "honest stack must balance");
let row = first_ec_op_row(eval, COL_IS_SUB);
let r = eval.values[row * EVAL_COLS + COL_PTR]; let forged = tamper(eval, row, &[(COL_PTR, r + Felt::ONE)]);
eval_locally_holds(&traces, &forged);
assert_ne!(
dag_residual(&traces, &forged, &mut rng),
0,
"the bus must catch a forged Sub result",
);
}