use std::{format, string::String, vec::Vec};
use k256::{ProjectivePoint, elliptic_curve::sec1::ToSec1Point};
use miden_core::{Felt, utils::Matrix};
use miden_precompiles::{CurveId, CurvePoint, phi_generator};
use crate::{
ec::msm::EcMsmAir,
math::{U256, from_hex, from_limbs32, to_limbs32},
session::{
EcNode, Session,
strategies::{
glv_joint_wnaf_with_tables, joint_naf, joint_wnaf, straus, wnaf_msm, wnaf_table,
wnaf_table_endo,
},
},
tests::{SessionTracesTestExt, check_local_inputs, verify_deferred},
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_intro_endo_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 _e = s.msm_intro_endo(&g_pt);
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_endo_checks() {
let traces = msm_intro_endo_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn msm_intro_endo_proves() {
verify_deferred(&msm_intro_endo_traces().prove())
.expect("EcMsm intro_endo round-trip must verify");
}
#[test]
fn msm_intro_endo_value_matches_endomorphism_image() {
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_endo(&g_pt);
let (x, y) = s.msm_value_coords(expr);
let CurvePoint::Affine { x: expected_x, y: expected_y } = phi_generator() else {
panic!("phi(G) must be an affine point");
};
assert_eq!(x, from_limbs32(&expected_x), "intro_endo's φ(G).x must match phi_generator()");
assert_eq!(y, from_limbs32(&expected_y), "intro_endo's φ(G).y must match phi_generator()");
}
#[test]
fn glv_joint_wnaf_value_matches_native_mul_scalar() {
let curve = CurveId::Secp256k1;
let g = ProjectivePoint::GENERATOR;
let (gx, gy) = k256_coords(&g);
let u = from_hex("89abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345");
let mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let plain = wnaf_table(&mut s, &g_pt, 5);
let endo = wnaf_table_endo(&mut s, &g_pt, 5);
let expr = glv_joint_wnaf_with_tables(&mut s, &[(&plain, Some(&endo), u)]);
let (x, y) = s.msm_value_coords(expr);
let expected = curve
.mul_scalar(curve.generator(), to_limbs32(u))
.expect("valid scalar multiplication");
let CurvePoint::Affine { x: expected_x, y: expected_y } = expected else {
panic!("u·G must be finite for this u");
};
assert_eq!(x, from_limbs32(&expected_x), "GLV value.x must match the native reference");
assert_eq!(y, from_limbs32(&expected_y), "GLV value.y must match the native reference");
}
#[test]
fn glv_joint_wnaf_with_tables_reused_across_scalars() {
let curve = CurveId::Secp256k1;
let g = ProjectivePoint::GENERATOR;
let (gx, gy) = k256_coords(&g);
let mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let plain = wnaf_table(&mut s, &g_pt, 5);
let endo = wnaf_table_endo(&mut s, &g_pt, 5);
for u in [
from_hex("1"),
from_hex("89abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345"),
from_hex("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
from_hex("123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde"),
] {
let expr = glv_joint_wnaf_with_tables(&mut s, &[(&plain, Some(&endo), u)]);
let (x, y) = s.msm_value_coords(expr);
let expected =
curve.mul_scalar(curve.generator(), to_limbs32(u)).expect("valid scalar mul");
let CurvePoint::Affine { x: expected_x, y: expected_y } = expected else {
panic!("u·G must be finite for this u");
};
assert_eq!(x, from_limbs32(&expected_x), "cached GLV value.x must match for u={u:?}");
assert_eq!(y, from_limbs32(&expected_y), "cached GLV value.y must match for u={u:?}");
}
}
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_joint_wnaf_unit_scalars(count: usize) -> crate::session::SessionTraces {
let mut s = Session::new();
let mut point = ProjectivePoint::IDENTITY;
let mut expected = ProjectivePoint::IDENTITY;
let mut terms = Vec::with_capacity(count);
for _ in 0..count {
point += ProjectivePoint::GENERATOR;
expected += point;
let (x, y) = k256_coords(&point);
terms.push((create(&mut s, x, y), U256::from(1u64)));
}
s.constrain_scalar_bound(&terms[0].0, SN_PTR);
let expr = joint_wnaf(&mut s, &terms, 2);
let one = s.uint_leaf(U256::from(1u64), SN_PTR);
let claim_terms: Vec<_> = terms.iter().map(|(point, _)| (*point, one)).collect();
let value = s.ec_msm(expr, &claim_terms);
let (x, y) = k256_coords(&expected);
let expected = create(&mut s, x, y);
let claim = s.ec_is(&value, &expected);
let root = s.assert_and_fold([claim]);
s.finish(root)
}
#[test]
fn msm_joint_wnaf_large_arity_avoids_quadratic_term_rows_and_proves() {
use crate::ec::msm::COL_ACT;
let small = msm_joint_wnaf_unit_scalars(32);
let large = msm_joint_wnaf_unit_scalars(64);
let rows = |traces: &crate::session::SessionTraces| {
let msm = traces.mains()[9];
msm.values
.chunks_exact(msm.width)
.filter(|row| row[COL_ACT] == Felt::ONE)
.count()
};
let (small_rows, large_rows) = (rows(&small), rows(&large));
assert!(
large_rows < 3 * small_rows,
"doubling arity must not quadruple MSM term rows: {small_rows} -> {large_rows}"
);
small.check();
large.check();
verify_deferred(&large.prove()).expect("large-arity joint wNAF claim 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 = "claim terms do not match this MSM expression")]
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)]);
}
fn msm_resolve_repeated_base_via_terms_preserving_combine_traces() -> crate::session::SessionTraces
{
let g = ProjectivePoint::GENERATOR;
let (gx, gy) = k256_coords(&g);
let (rx, ry) = k256_coords(&(g + g));
let mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let r_pt = create(&mut s, rx, ry);
let ga = s.msm_intro(&g_pt);
let expr = s.msm_combine_terms_preserving(ga, ga);
let one = s.uint_leaf(from_hex("1"), SN_PTR);
let value = s.ec_msm(expr, &[(g_pt, one), (g_pt, one)]);
let claim = s.ec_is(&value, &r_pt);
let root = s.assert_and_fold([claim]);
s.finish(root)
}
#[test]
fn msm_resolve_repeated_base_via_terms_preserving_combine_checks() {
let traces = msm_resolve_repeated_base_via_terms_preserving_combine_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn msm_resolve_repeated_base_via_terms_preserving_combine_proves() {
verify_deferred(&msm_resolve_repeated_base_via_terms_preserving_combine_traces().prove())
.expect("EcMsm repeated-base term-preserving-combine round-trip must verify");
}
fn msm_resolve_zero_scalar_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_zero(&g_pt);
let zero = s.uint_leaf(from_hex("0"), SN_PTR);
let value = s.ec_msm(expr, &[(g_pt, zero)]);
let pai_pt = s.ec_sub(&g_pt, &g_pt);
let claim = s.ec_is(&value, &pai_pt);
let root = s.assert_and_fold([claim]);
s.finish(root)
}
#[test]
fn msm_resolve_zero_scalar_checks() {
let traces = msm_resolve_zero_scalar_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn msm_resolve_zero_scalar_proves() {
verify_deferred(&msm_resolve_zero_scalar_traces().prove())
.expect("EcMsm zero-scalar resolve round-trip must verify");
}
fn msm_resolve_mixed_zero_and_nonzero_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 mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let q_pt = create(&mut s, qx, qy);
let za = s.msm_intro_zero(&g_pt);
let qb = s.msm_intro(&q_pt);
let expr = s.msm_combine_terms_preserving(za, qb);
let zero = s.uint_leaf(from_hex("0"), SN_PTR);
let one = s.uint_leaf(from_hex("1"), SN_PTR);
let value = s.ec_msm(expr, &[(g_pt, zero), (q_pt, one)]);
let claim = s.ec_is(&value, &q_pt);
let root = s.assert_and_fold([claim]);
s.finish(root)
}
#[test]
fn msm_resolve_mixed_zero_and_nonzero_checks() {
let traces = msm_resolve_mixed_zero_and_nonzero_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn msm_resolve_mixed_zero_and_nonzero_proves() {
verify_deferred(&msm_resolve_mixed_zero_and_nonzero_traces().prove())
.expect("EcMsm mixed zero/nonzero resolve round-trip must verify");
}
fn msm_resolve_all_zero_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 mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let q_pt = create(&mut s, qx, qy);
let za = s.msm_intro_zero(&g_pt);
let zb = s.msm_intro_zero(&q_pt);
let expr = s.msm_combine_terms_preserving(za, zb);
let zero = s.uint_leaf(from_hex("0"), SN_PTR);
let value = s.ec_msm(expr, &[(g_pt, zero), (q_pt, zero)]);
let pai_pt = s.ec_sub(&g_pt, &g_pt);
let claim = s.ec_is(&value, &pai_pt);
let root = s.assert_and_fold([claim]);
s.finish(root)
}
#[test]
fn msm_resolve_all_zero_checks() {
let traces = msm_resolve_all_zero_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn msm_resolve_all_zero_proves() {
verify_deferred(&msm_resolve_all_zero_traces().prove())
.expect("EcMsm all-zero resolve round-trip must verify");
}
fn msm_resolve_balanced_tree_five_terms_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 mut s = Session::new();
let g_pt = create(&mut s, gx, gy);
let q_pt = create(&mut s, qx, qy);
let l1 = s.msm_intro(&g_pt);
let l2 = s.msm_intro_zero(&g_pt);
let l3 = s.msm_intro(&q_pt);
let l4 = s.msm_intro(&g_pt);
let l5 = s.msm_intro_zero(&q_pt);
let a = s.msm_combine_terms_preserving(l1, l2);
let b = s.msm_combine_terms_preserving(l3, l4);
let c = s.msm_combine_terms_preserving(a, b);
let expr = s.msm_combine_terms_preserving(c, l5);
let zero = s.uint_leaf(from_hex("0"), SN_PTR);
let one = s.uint_leaf(from_hex("1"), SN_PTR);
let value =
s.ec_msm(expr, &[(g_pt, one), (g_pt, zero), (q_pt, one), (g_pt, one), (q_pt, zero)]);
let expected_pt = s.ec_add(&q_pt, &q_pt);
let claim = s.ec_is(&value, &expected_pt);
let root = s.assert_and_fold([claim]);
s.finish(root)
}
#[test]
fn msm_resolve_balanced_tree_five_terms_checks() {
let traces = msm_resolve_balanced_tree_five_terms_traces();
traces.check();
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn msm_resolve_balanced_tree_five_terms_proves() {
verify_deferred(&msm_resolve_balanced_tree_five_terms_traces().prove())
.expect("EcMsm balanced-tree 5-term fallback resolve round-trip must verify");
}
#[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()[4]; 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());
}