use std::{format, string::String};
use k256::{ProjectivePoint, elliptic_curve::sec1::ToSec1Point};
use miden_core::{Felt, utils::Matrix};
use miden_precompiles::CurveId;
use crate::{
ec::msm::EcMsmAir,
math::{U256, from_hex},
session::{
EcNode, Session,
strategies::{joint_naf, joint_wnaf, straus, wnaf_msm, wnaf_table},
verify_deferred,
},
tests::check_local_inputs,
transcript::eval::{COL_IS_EC_MSM, COL_IS_MSM_LAST, COL_MSM_EXPR, TranscriptEvalAir},
};
const FP: u32 = CurveId::Secp256k1.base_domain().bound_ptr();
const GROUP_PTR: u32 = CurveId::Secp256k1.group_ptr();
const SN_PTR: u32 = CurveId::Secp256k1.scalar_domain().bound_ptr();
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 create(s: &mut Session, x: U256, y: U256) -> EcNode {
let xn = s.uint_leaf(x, FP);
let yn = s.uint_leaf(y, FP);
s.ec_create(GROUP_PTR, &xn, &yn)
}
fn msm_two_intro_traces() -> crate::session::SessionTraces {
let g = ProjectivePoint::GENERATOR;
let (gx, gy) = k256_coords(&g);
let (g2x, g2y) = k256_coords(&(g + g));
let mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let q_pt = create(&mut s, g2x, g2y);
let ga = s.msm_intro(&g_pt);
let qb = s.msm_intro(&q_pt);
let _c = s.msm_combine(ga, qb);
let claim_g = s.ec_is(&g_pt, &g_pt);
let claim_q = s.ec_is(&q_pt, &q_pt);
let root = s.assert_and_fold([claim_g, claim_q]);
s.finish(root)
}
#[test]
fn log_quotient_degree_matches_design_target() {
assert_eq!(crate::tests::log_quotient_degree(&EcMsmAir), 1);
}
#[test]
fn msm_two_intro_combine_checks() {
let traces = msm_two_intro_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn msm_two_intro_combine_proves() {
verify_deferred(&msm_two_intro_traces().prove())
.expect("EcMsm intro+combine round-trip must verify");
}
fn msm_scalar_bound_n_traces() -> crate::session::SessionTraces {
let g = ProjectivePoint::GENERATOR;
let (gx, gy) = k256_coords(&g);
let (g2x, g2y) = k256_coords(&(g + g));
let mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let q_pt = create(&mut s, g2x, g2y);
s.constrain_scalar_bound(&g_pt, SN_PTR);
let ga = s.msm_intro(&g_pt);
let qb = s.msm_intro(&q_pt);
let _c = s.msm_combine(ga, qb);
let claim_g = s.ec_is(&g_pt, &g_pt);
let claim_q = s.ec_is(&q_pt, &q_pt);
let root = s.assert_and_fold([claim_g, claim_q]);
s.finish(root)
}
#[test]
fn msm_scalar_bound_n_checks() {
let traces = msm_scalar_bound_n_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn msm_scalar_bound_n_proves() {
verify_deferred(&msm_scalar_bound_n_traces().prove())
.expect("MSM under scalar bound n ≠ p must verify");
}
fn msm_intro_neg_traces() -> crate::session::SessionTraces {
let g = ProjectivePoint::GENERATOR;
let (gx, gy) = k256_coords(&g);
let mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let ga = s.msm_intro(&g_pt);
let _n = s.msm_neg(ga);
let claim_g = s.ec_is(&g_pt, &g_pt);
let root = s.assert_and_fold([claim_g]);
s.finish(root)
}
#[test]
fn msm_intro_neg_checks() {
let traces = msm_intro_neg_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn msm_intro_neg_proves() {
verify_deferred(&msm_intro_neg_traces().prove())
.expect("EcMsm intro+neg round-trip must verify");
}
fn msm_resolve_one_term_traces() -> crate::session::SessionTraces {
let g = ProjectivePoint::GENERATOR;
let (gx, gy) = k256_coords(&g);
let mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let expr = s.msm_intro(&g_pt);
let one = s.uint_leaf(from_hex("1"), SN_PTR);
let value = s.ec_msm(expr, &[(g_pt, one)]);
let claim = s.ec_is(&value, &g_pt);
let root = s.assert_and_fold([claim]);
s.finish(root)
}
#[test]
fn msm_resolve_one_term_checks() {
let traces = msm_resolve_one_term_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn msm_resolve_one_term_proves() {
verify_deferred(&msm_resolve_one_term_traces().prove())
.expect("EcMsm 1-term resolve round-trip must verify");
}
fn msm_resolve_two_term_traces() -> crate::session::SessionTraces {
let g = ProjectivePoint::GENERATOR;
let (gx, gy) = k256_coords(&g);
let (g2x, g2y) = k256_coords(&(g + g));
let mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let q_pt = create(&mut s, g2x, g2y); let ga = s.msm_intro(&g_pt);
let qb = s.msm_intro(&q_pt);
let expr = s.msm_combine(ga, qb);
let one = s.uint_leaf(from_hex("1"), SN_PTR);
let r_pt = s.ec_add(&g_pt, &q_pt); let value = s.ec_msm(expr, &[(g_pt, one), (q_pt, one)]);
let claim = s.ec_is(&value, &r_pt);
let root = s.assert_and_fold([claim]);
s.finish(root)
}
#[test]
fn msm_resolve_two_term_checks() {
let traces = msm_resolve_two_term_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn msm_resolve_two_term_proves() {
verify_deferred(&msm_resolve_two_term_traces().prove())
.expect("EcMsm 2-term resolve round-trip must verify");
}
fn msm_straus_traces() -> crate::session::SessionTraces {
let g = ProjectivePoint::GENERATOR;
let q = g + g; let r = (0..13).fold(ProjectivePoint::IDENTITY, |acc, _| acc + g); let (gx, gy) = k256_coords(&g);
let (qx, qy) = k256_coords(&q);
let (rx, ry) = k256_coords(&r);
let mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let q_pt = create(&mut s, qx, qy);
let r_pt = create(&mut s, rx, ry);
let acc = straus(&mut s, &[(g_pt, from_hex("3")), (q_pt, from_hex("5"))]);
let s3 = s.uint_leaf(from_hex("3"), SN_PTR);
let s5 = s.uint_leaf(from_hex("5"), SN_PTR);
let value = s.ec_msm(acc, &[(g_pt, s3), (q_pt, s5)]);
let claim = s.ec_is(&value, &r_pt);
let root = s.assert_and_fold([claim]);
s.finish(root)
}
#[test]
fn msm_straus_checks() {
let traces = msm_straus_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn msm_straus_proves() {
verify_deferred(&msm_straus_traces().prove()).expect("Straus strategy round-trip must verify");
}
fn msm_wnaf_traces() -> crate::session::SessionTraces {
let g = ProjectivePoint::GENERATOR;
let q = g + g; let r = (0..13).fold(ProjectivePoint::IDENTITY, |acc, _| acc + g); let (gx, gy) = k256_coords(&g);
let (qx, qy) = k256_coords(&q);
let (rx, ry) = k256_coords(&r);
let mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let q_pt = create(&mut s, qx, qy);
let r_pt = create(&mut s, rx, ry);
let g_table = wnaf_table(&mut s, &g_pt, 4);
let q_table = wnaf_table(&mut s, &q_pt, 4);
let acc = wnaf_msm(&mut s, &[(&g_table, from_hex("3")), (&q_table, from_hex("5"))]);
let s3 = s.uint_leaf(from_hex("3"), SN_PTR);
let s5 = s.uint_leaf(from_hex("5"), SN_PTR);
let value = s.ec_msm(acc, &[(g_pt, s3), (q_pt, s5)]);
let claim = s.ec_is(&value, &r_pt);
let root = s.assert_and_fold([claim]);
s.finish(root)
}
#[test]
fn msm_wnaf_checks() {
let traces = msm_wnaf_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn msm_wnaf_proves() {
verify_deferred(&msm_wnaf_traces().prove())
.expect("separate-wNAF strategy round-trip must verify");
}
fn msm_joint_naf_traces() -> crate::session::SessionTraces {
let g = ProjectivePoint::GENERATOR;
let q = g + g; let r = (0..13).fold(ProjectivePoint::IDENTITY, |acc, _| acc + g); let (gx, gy) = k256_coords(&g);
let (qx, qy) = k256_coords(&q);
let (rx, ry) = k256_coords(&r);
let mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let q_pt = create(&mut s, qx, qy);
let r_pt = create(&mut s, rx, ry);
let acc = joint_naf(&mut s, &[(g_pt, from_hex("3")), (q_pt, from_hex("5"))]);
let s3 = s.uint_leaf(from_hex("3"), SN_PTR);
let s5 = s.uint_leaf(from_hex("5"), SN_PTR);
let value = s.ec_msm(acc, &[(g_pt, s3), (q_pt, s5)]);
let claim = s.ec_is(&value, &r_pt);
let root = s.assert_and_fold([claim]);
s.finish(root)
}
#[test]
fn msm_joint_naf_checks() {
let traces = msm_joint_naf_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn msm_joint_naf_proves() {
verify_deferred(&msm_joint_naf_traces().prove())
.expect("joint_naf strategy round-trip must verify");
}
fn msm_joint_wnaf_traces() -> crate::session::SessionTraces {
let g = ProjectivePoint::GENERATOR;
let q = g + g; let r = (0..13).fold(ProjectivePoint::IDENTITY, |acc, _| acc + g); let (gx, gy) = k256_coords(&g);
let (qx, qy) = k256_coords(&q);
let (rx, ry) = k256_coords(&r);
let mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let q_pt = create(&mut s, qx, qy);
let r_pt = create(&mut s, rx, ry);
let acc = joint_wnaf(&mut s, &[(g_pt, from_hex("3")), (q_pt, from_hex("5"))], 4);
let s3 = s.uint_leaf(from_hex("3"), SN_PTR);
let s5 = s.uint_leaf(from_hex("5"), SN_PTR);
let value = s.ec_msm(acc, &[(g_pt, s3), (q_pt, s5)]);
let claim = s.ec_is(&value, &r_pt);
let root = s.assert_and_fold([claim]);
s.finish(root)
}
#[test]
fn msm_joint_wnaf_checks() {
let traces = msm_joint_wnaf_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn msm_joint_wnaf_proves() {
verify_deferred(&msm_joint_wnaf_traces().prove())
.expect("joint_wnaf strategy round-trip must verify");
}
fn msm_dedup_traces() -> crate::session::SessionTraces {
let g = ProjectivePoint::GENERATOR;
let q = g + g; let (gx, gy) = k256_coords(&g);
let (qx, qy) = k256_coords(&q);
let (rx, ry) = k256_coords(&(g + q));
let mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let q_pt = create(&mut s, qx, qy);
let ga = s.msm_intro(&g_pt);
let ga_again = s.msm_intro(&g_pt);
assert_eq!(ga, ga_again, "intro(G) must dedup");
let qb = s.msm_intro(&q_pt);
let c1 = s.msm_combine(ga, qb);
let c2 = s.msm_combine(ga, qb);
assert_eq!(c1, c2, "combine(G, Q) must dedup");
assert_eq!(s.msm_expr_count(), 3, "only ⟨G⟩, ⟨Q⟩, ⟨G,Q⟩ laid — the repeats collapsed",);
let one = s.uint_leaf(from_hex("1"), SN_PTR);
let r_pt = create(&mut s, rx, ry);
let value = s.ec_msm(c1, &[(g_pt, one), (q_pt, one)]);
let claim = s.ec_is(&value, &r_pt);
let root = s.assert_and_fold([claim]);
s.finish(root)
}
#[test]
fn msm_dedup_checks() {
let traces = msm_dedup_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn msm_dedup_proves() {
verify_deferred(&msm_dedup_traces().prove()).expect("deduped MSM round-trip must verify");
}
fn msm_two_term_ordered(swap: bool) -> crate::session::SessionTraces {
let g = ProjectivePoint::GENERATOR;
let (gx, gy) = k256_coords(&g);
let (g2x, g2y) = k256_coords(&(g + g));
let mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let q_pt = create(&mut s, g2x, g2y);
let ga = s.msm_intro(&g_pt);
let qb = s.msm_intro(&q_pt);
let expr = s.msm_combine(ga, qb);
let one = s.uint_leaf(from_hex("1"), SN_PTR);
let r_pt = s.ec_add(&g_pt, &q_pt);
let value = if swap {
s.ec_msm(expr, &[(q_pt, one), (g_pt, one)])
} else {
s.ec_msm(expr, &[(g_pt, one), (q_pt, one)])
};
let claim = s.ec_is(&value, &r_pt);
let root = s.assert_and_fold([claim]);
s.finish(root)
}
#[test]
fn msm_resolve_absorb_order_is_caller_declared() {
let t_gq = msm_two_term_ordered(false);
let t_qg = msm_two_term_ordered(true);
let root_gq = t_gq.public_root().as_array();
let root_qg = t_qg.public_root().as_array();
assert_ne!(
root_gq, root_qg,
"absorb order (hence root) must follow the caller's term-pair order",
);
t_gq.check();
t_qg.check();
}
#[test]
#[should_panic(expected = "duplicate base")]
fn msm_resolve_duplicate_base_rejected() {
let g = ProjectivePoint::GENERATOR;
let (gx, gy) = k256_coords(&g);
let (g2x, g2y) = k256_coords(&(g + g));
let mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let q_pt = create(&mut s, g2x, g2y);
let ga = s.msm_intro(&g_pt);
let qb = s.msm_intro(&q_pt);
let expr = s.msm_combine(ga, qb);
let one = s.uint_leaf(from_hex("1"), SN_PTR);
let _ = s.ec_msm(expr, &[(g_pt, one), (g_pt, one)]);
}
#[test]
#[should_panic(expected = "constraint not satisfied")]
fn msm_resolve_run_expr_must_be_constant() {
let traces = msm_resolve_two_term_traces();
let eval = traces.mains()[5]; let ncols = eval.width();
let row = (0..eval.height())
.find(|&r| {
eval.values[r * ncols + COL_IS_EC_MSM] == Felt::ONE
&& eval.values[r * ncols + COL_IS_MSM_LAST] == Felt::ZERO
})
.expect("a non-boundary absorb row");
let mut forged = eval.clone();
let here = forged.values[row * ncols + COL_MSM_EXPR];
forged.values[row * ncols + COL_MSM_EXPR] = here + Felt::ONE;
check_local_inputs(TranscriptEvalAir, &forged, traces.public_root().as_array().to_vec());
}