use std::{collections::HashMap, format, string::String, vec, vec::Vec};
use k256::{ProjectivePoint, Scalar, elliptic_curve::sec1::ToSec1Point};
use miden_air::lookup::Challenges;
use miden_core::{
Felt,
field::QuadFelt,
utils::{Matrix, RowMajorMatrix},
};
use miden_lifted_air::{MultiAir, ProverStatement, ReductionError, Statement};
use miden_lifted_stark::{Preprocessed, ProverInstance, VerifierInstance};
use rand::{Rng, RngExt, SeedableRng, rngs::StdRng};
use crate::logup::{NUM_PUBLIC_VALUES, sigma_sum};
use crate::{
ec::{
COL_IS_CERT, EcRequire,
add::{
CELL_R, COL_CANCEL, COL_DBL, COL_GEN, COL_MINTS, COL_PAI_P, COL_PAI_Q, EcGroupAddAir,
NUM_MAIN_COLS as ADD_COLS, PERIOD, ROW_RES,
trace::{EcAddRequires, generate_trace as ec_add_trace},
},
point_store_groups::{
EcPointStoreGroupsAir, NUM_MAIN_COLS as POINTS_GROUPS_COLS,
trace::generate_trace as ec_points_groups_trace,
},
trace::{EcGroupPtr, EcPointPtr, EcStoreRequires},
},
math::{U256, from_hex},
primitives::byte_pair_lut::{BytePairLutAir, BytePairLutRequires, generate_trace as bpl_trace},
relations::{MAX_MESSAGE_WIDTH, NUM_BUS_IDS},
session::ChipletAir,
stark_config::{test_challenger, test_config},
tests::bus_balance::fold_balance,
uint::{
UintRequire,
add::{
UintAddAir,
trace::{UintAddRequires, generate_trace as uint_add_trace},
},
mul::trace::UintMulRequires,
store_mul::{UintStoreMulAir, trace::generate_trace as uint_store_mul_trace},
trace::{UintPtr, UintStoreRequires},
},
};
fn rand_qf(rng: &mut impl Rng) -> QuadFelt {
QuadFelt::new([Felt::from(rng.random::<u32>()), Felt::from(rng.random::<u32>())])
}
const P_MINUS_1: &str = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2E";
const GX: &str = "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798";
const GY: &str = "483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8";
const G2X: &str = "C6047F9441ED7D6D3045406E95C07CD85C778E4B8CEF3CA7ABAC09B95C709EE5";
const G2Y: &str = "1AE168FEA63DC339A3C58419466CEAEEF7F632653266D0E1236431A950CFE52A";
const G3X: &str = "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9";
const G3Y: &str = "388F7B0F632DE8140FE337E62A37F3566500A99934C2231B6CB9FD7584B8E672";
const NEG_GY: &str = "B7C52588D95C3B9AA25B0403F1EEF75702E84BB7597AABE663B82F6F04EF2777";
const BETA_GX: &str = "BCACE2E99DA01887AB0102B696902325872844067F15E98DA7BBA04400B88FCB";
const FP: u32 = 1000;
const NUM_STACK: usize = 5;
struct EcStack {
store: UintStoreRequires,
fp: UintPtr,
adds: UintAddRequires,
muls: UintMulRequires,
ec: EcStoreRequires,
ec_add: EcAddRequires,
}
impl EcStack {
fn new(bound: U256) -> Self {
let mut store = UintStoreRequires::new();
store.pin_modulus(1, U256::ZERO);
let fp = store.pin_modulus(FP, bound);
Self {
store,
fp,
adds: UintAddRequires::new(),
muls: UintMulRequires::new(),
ec: EcStoreRequires::new(),
ec_add: EcAddRequires::new(),
}
}
fn require(&mut self) -> EcRequire<'_> {
EcRequire::new(
&mut self.ec,
&mut self.ec_add,
UintRequire::new(&mut self.store, &mut self.adds, &mut self.muls),
)
}
fn point_coords(&self, point: EcPointPtr) -> (U256, U256) {
let (_, coords) = self.ec.point_params(point);
let (x_ptr, y_ptr) = coords.expect("PAI has no coordinates");
(self.store.uint(x_ptr).value, self.store.uint(y_ptr).value)
}
fn traces(mut self) -> EcStackTraces {
let mut bpl = BytePairLutRequires::new();
let add = uint_add_trace(self.adds, &mut self.store);
let ec_add = ec_add_trace(self.ec_add, &mut self.ec, &mut bpl);
let uint = uint_store_mul_trace(self.store, self.muls, &mut bpl);
let ec_points_groups = ec_points_groups_trace(self.ec);
EcStackTraces([bpl_trace(bpl), uint, add, ec_points_groups, ec_add])
}
}
struct EcStackTraces([RowMajorMatrix<Felt>; NUM_STACK]);
fn stack_airs() -> [ChipletAir; NUM_STACK] {
[
ChipletAir::BytePairLut,
ChipletAir::UintStoreMul,
ChipletAir::UintAdd,
ChipletAir::EcPointStoreGroups,
ChipletAir::EcGroupAdd,
]
}
#[derive(Clone, Debug)]
struct EcStackMultiAir {
airs: Vec<ChipletAir>,
}
impl EcStackMultiAir {
fn new() -> Self {
Self { airs: stack_airs().to_vec() }
}
}
impl MultiAir<Felt, QuadFelt> for EcStackMultiAir {
type Air = ChipletAir;
fn airs(&self) -> &[ChipletAir] {
&self.airs
}
fn eval_external(
&self,
_challenges: &[QuadFelt],
_air_inputs: &[Felt],
_aux_inputs: &[Felt],
aux_values: &[&[QuadFelt]],
_log_trace_heights: &[u8],
) -> Result<Vec<QuadFelt>, ReductionError> {
Ok(vec![sigma_sum(aux_values)])
}
}
impl EcStackTraces {
fn mains(&self) -> [&RowMajorMatrix<Felt>; NUM_STACK] {
let [a, b, c, d, e] = &self.0;
[a, b, c, d, e]
}
fn ec_add_main(&self) -> &RowMajorMatrix<Felt> {
&self.0[4]
}
fn ec_points_main(&self) -> &RowMajorMatrix<Felt> {
&self.0[3]
}
fn check(&self) {
for (air, main) in stack_airs().into_iter().zip(&self.0) {
crate::tests::check_local(air, main);
}
}
fn dummy_air_inputs() -> Vec<Felt> {
vec![Felt::ZERO; NUM_PUBLIC_VALUES]
}
fn prover_statement(&self) -> ProverStatement<Felt, QuadFelt, EcStackMultiAir> {
let statement =
Statement::new(EcStackMultiAir::new(), Self::dummy_air_inputs(), Vec::new())
.expect("subset statement inputs are valid");
let mains: Vec<RowMajorMatrix<Felt>> = self.0.to_vec();
ProverStatement::new(statement, mains).expect("subset trace shapes are valid")
}
fn prove_and_verify(&self) {
let config = test_config();
let prover_statement = self.prover_statement();
let preprocessed = Preprocessed::build(prover_statement.statement(), &config);
let output = ProverInstance::new(&config, &prover_statement, preprocessed.as_ref())
.expect("preprocessed bundle matches the declared columns")
.prove(test_challenger())
.expect("prove");
let statement =
Statement::new(EcStackMultiAir::new(), Self::dummy_air_inputs(), Vec::new())
.expect("subset statement inputs are valid");
let preprocessed = Preprocessed::build(&statement, &config);
let digest = VerifierInstance::new(
&config,
&statement,
preprocessed.as_ref().map(Preprocessed::commitment),
)
.expect("preprocessed commitment matches the declared columns")
.verify(&output.proof, test_challenger())
.expect("the arithmetic + EC subset must verify");
assert_eq!(digest, output.digest, "prover/verifier digests must agree");
}
}
fn stack_residual(mains: &[&RowMajorMatrix<Felt>; NUM_STACK], rng: &mut impl Rng) -> usize {
let challenges = Challenges::new(rand_qf(rng), rand_qf(rng), MAX_MESSAGE_WIDTH, NUM_BUS_IDS);
let mut net: HashMap<QuadFelt, (Felt, String)> = HashMap::new();
fold_balance(&BytePairLutAir, mains[0], &challenges, &mut net);
fold_balance(&UintStoreMulAir, mains[1], &challenges, &mut net);
fold_balance(&UintAddAir, mains[2], &challenges, &mut net);
fold_balance(&EcPointStoreGroupsAir, mains[3], &challenges, &mut net);
fold_balance(&EcGroupAddAir, mains[4], &challenges, &mut net);
net.into_values().filter(|(m, _)| *m != Felt::ZERO).count()
}
struct K1 {
stack: EcStack,
group: EcGroupPtr,
pai: EcPointPtr,
g_pt: EcPointPtr,
g2_pt: EcPointPtr,
}
fn k1_stack() -> K1 {
let mut stack = EcStack::new(from_hex(P_MINUS_1));
let fp = stack.fp;
let mut ec = stack.require();
let (group, pai) = ec.create_group(from_hex("0"), from_hex("7"), fp);
let g_pt = ec.add_point(group, from_hex(GX), from_hex(GY));
let g2_pt = ec.add_point(group, from_hex(G2X), from_hex(G2Y));
K1 { stack, group, pai, g_pt, g2_pt }
}
fn check_ec_add(main: &RowMajorMatrix<Felt>) {
crate::tests::check_local(EcGroupAddAir, main);
}
fn tamper_block0(main: &RowMajorMatrix<Felt>, cols: &[(usize, u32)]) -> RowMajorMatrix<Felt> {
let mut m = main.clone();
for row in 0..PERIOD {
for &(col, v) in cols {
m.values[row * ADD_COLS + col] = Felt::from(v);
}
}
m
}
fn tamper_cell(m: &mut RowMajorMatrix<Felt>, row: usize, cell: usize, v: u32) {
m.values[row * ADD_COLS + cell] = Felt::from(v);
}
fn tamper_ec_points(
main: &RowMajorMatrix<Felt>,
row: usize,
col: usize,
v: u32,
) -> RowMajorMatrix<Felt> {
let mut m = main.clone();
m.values[row * POINTS_GROUPS_COLS + col] = Felt::from(v);
m
}
fn block0_flags(main: &RowMajorMatrix<Felt>) -> [Felt; 5] {
[COL_PAI_P, COL_PAI_Q, COL_CANCEL, COL_DBL, COL_GEN].map(|c| main.values[c])
}
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) -> Option<(U256, U256)> {
let enc = p.to_affine().to_sec1_point(false);
Some((be_to_u256(enc.x()?), be_to_u256(enc.y()?)))
}
fn k256_validated_stack() -> EcStack {
let g = ProjectivePoint::GENERATOR;
let mut s = EcStack::new(from_hex(P_MINUS_1));
let fp = s.fp;
let (group, pai) = s.require().create_group(from_hex("0"), from_hex("7"), fp);
for &(a, b) in &[(1u64, 2u64), (3, 7), (9, 4), (5, 5), (6, 6)] {
let (pa, pb) = (g * Scalar::from(a), g * Scalar::from(b));
let (pax, pay) = k256_coords(&pa).expect("aG is finite");
let (pbx, pby) = k256_coords(&pb).expect("bG is finite");
let p_pt = s.require().add_point(group, pax, pay);
let q_pt = s.require().add_point(group, pbx, pby);
let r = s.require().add(p_pt, q_pt, 0);
match k256_coords(&(pa + pb)) {
Some(want) => assert_eq!(s.point_coords(r), want, "P+Q vs k256 (a={a}, b={b})"),
None => assert_eq!(r, pai, "k256 says ∞ (a={a}, b={b})"),
}
}
let p = g * Scalar::from(8u64);
let (px, py) = k256_coords(&p).unwrap();
let (nx, ny) = k256_coords(&(-p)).unwrap();
let p_pt = s.require().add_point(group, px, py);
let n_pt = s.require().add_point(group, nx, ny);
assert_eq!(s.require().add(p_pt, n_pt, 0), pai, "P + (−P) = ∞");
let q = g * Scalar::from(12u64);
let (qx, qy) = k256_coords(&q).unwrap();
let q_pt = s.require().add_point(group, qx, qy);
assert_eq!(s.require().add(pai, q_pt, 0), q_pt, "∞ + Q = Q");
assert_eq!(s.require().add(q_pt, pai, 0), q_pt, "P + ∞ = P");
assert_eq!(s.require().add(pai, pai, 0), pai, "∞ + ∞ = ∞");
s
}
#[test]
fn ec_add_matches_k256() {
let traces = k256_validated_stack().traces();
let mut rng = StdRng::seed_from_u64(0x000e_cadd_c256);
traces.check();
assert_eq!(stack_residual(&traces.mains(), &mut rng), 0, "subset must balance");
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn ec_add_matches_k256_proves() {
k256_validated_stack().traces().prove_and_verify();
}
#[test]
fn create_group_dedups_by_curve() {
let mut s = EcStack::new(from_hex(P_MINUS_1));
let fp = s.fp;
let (g1, pai1) = s.require().create_group(from_hex("0"), from_hex("7"), fp);
let (g2, pai2) = s.require().create_group(from_hex("0"), from_hex("7"), fp);
assert_eq!(g1, g2, "same curve must share one group");
assert_eq!(pai1, pai2, "and its one canonical PAI");
let (g3, _) = s.require().create_group(from_hex("0"), from_hex("3"), fp);
assert_ne!(g1, g3, "a different curve (b = 3) is a distinct group");
}
#[test]
fn generic_add_computes_kat() {
let mut k1 = k1_stack();
let r = k1.stack.require().add(k1.g_pt, k1.g2_pt, 0);
let (x3, y3) = k1.stack.point_coords(r);
assert_eq!(x3, from_hex(G3X), "x₃ must be the 3G KAT");
assert_eq!(y3, from_hex(G3Y), "y₃ must be the 3G KAT");
let traces = k1.stack.traces();
assert_eq!(
block0_flags(traces.ec_add_main()),
[0, 0, 0, 0, 1].map(Felt::from_u32),
"the block claims generic",
);
assert_eq!(
traces.ec_add_main().values[ROW_RES * ADD_COLS + CELL_R],
Felt::from(r.addr()),
"the res row hosts the result ptr",
);
assert_eq!(traces.ec_add_main().height(), PERIOD, "one add op = one block");
let mut rng = StdRng::seed_from_u64(0xecad_d001);
traces.check();
assert_eq!(stack_residual(&traces.mains(), &mut rng), 0);
}
#[test]
fn duplicate_adds_collapse() {
let mut k1 = k1_stack();
let r1 = k1.stack.require().add(k1.g_pt, k1.g2_pt, 0);
let r2 = k1.stack.require().add(k1.g_pt, k1.g2_pt, 0);
assert_eq!(r1, r2, "the repeat returns the recorded result");
let traces = k1.stack.traces();
assert_eq!(
traces.ec_add_main().height(),
PERIOD,
"two identical adds collapse onto one block",
);
let mut rng = StdRng::seed_from_u64(0xecad_dded);
traces.check();
assert_eq!(stack_residual(&traces.mains(), &mut rng), 0);
}
#[test]
fn double_binds_canonically() {
let mut k1 = k1_stack();
let r = k1.stack.require().add(k1.g_pt, k1.g_pt, 0);
assert_eq!(r, k1.g2_pt, "the doubling result dedups onto the 2G row");
assert_eq!(k1.stack.point_coords(r), (from_hex(G2X), from_hex(G2Y)));
let traces = k1.stack.traces();
assert_eq!(block0_flags(traces.ec_add_main()), [0, 0, 0, 1, 0].map(Felt::from_u32));
let mut rng = StdRng::seed_from_u64(0xecad_d002);
traces.check();
assert_eq!(stack_residual(&traces.mains(), &mut rng), 0);
}
#[test]
fn cancel_resolves_to_canonical_pai() {
let mut k1 = k1_stack();
let mut ec = k1.stack.require();
let neg_g = ec.add_point(k1.group, from_hex(GX), from_hex(NEG_GY));
let r = ec.add(k1.g_pt, neg_g, 0);
assert_eq!(r, k1.pai);
let traces = k1.stack.traces();
assert_eq!(block0_flags(traces.ec_add_main()), [0, 0, 1, 0, 0].map(Felt::from_u32));
let mut rng = StdRng::seed_from_u64(0xecad_d003);
traces.check();
assert_eq!(stack_residual(&traces.mains(), &mut rng), 0);
}
#[test]
fn pai_passthroughs_tie_results() {
let mut k1 = k1_stack();
let mut ec = k1.stack.require();
assert_eq!(ec.add(k1.pai, k1.g_pt, 0), k1.g_pt);
assert_eq!(ec.add(k1.g2_pt, k1.pai, 0), k1.g2_pt);
assert_eq!(ec.add(k1.pai, k1.pai, 0), k1.pai);
let traces = k1.stack.traces();
let main = traces.ec_add_main();
assert_eq!(main.height(), 16, "three blocks pad to four");
let block2 = 2 * PERIOD * ADD_COLS;
assert_eq!(
[COL_PAI_P, COL_PAI_Q].map(|c| main.values[block2 + c]),
[Felt::ONE; 2],
"∞ + ∞ sets both pass flags",
);
let mut rng = StdRng::seed_from_u64(0xecad_d004);
traces.check();
assert_eq!(stack_residual(&traces.mains(), &mut rng), 0);
}
#[test]
fn ed25519_torsion_doubles_to_pai() {
let bound = "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC";
let a_w = "2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA984914A144";
let b_w = "7B425ED097B425ED097B425ED097B425ED097B425ED097B4260B5E9C7710C864";
let x_t = "2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2451";
let mut stack = EcStack::new(from_hex(bound));
let fp = stack.fp;
let mut ec = stack.require();
let (group, pai) = ec.create_group(from_hex(a_w), from_hex(b_w), fp);
let t_pt = ec.add_point(group, from_hex(x_t), from_hex("0"));
let r = ec.add(t_pt, t_pt, 0);
assert_eq!(r, pai, "2-torsion doubling cancels to ∞");
let traces = stack.traces();
assert_eq!(block0_flags(traces.ec_add_main()), [0, 0, 1, 0, 0].map(Felt::from_u32));
let mut rng = StdRng::seed_from_u64(0xecadd_25519);
traces.check();
assert_eq!(stack_residual(&traces.mains(), &mut rng), 0);
}
#[test]
fn empty_trace_holds() {
let main = ec_add_trace(
EcAddRequires::new(),
&mut EcStoreRequires::new(),
&mut BytePairLutRequires::new(),
);
assert_eq!(main.height(), PERIOD);
check_ec_add(&main);
}
#[test]
fn log_quotient_degree_matches_design_target() {
assert_eq!(crate::tests::log_quotient_degree(&EcGroupAddAir), 1);
}
#[test]
#[ignore = "full prove/verify round-trip; run explicitly"]
fn arithmetic_ec_stack_proves() {
let mut k1 = k1_stack();
let mut ec = k1.stack.require();
let r3 = ec.add(k1.g_pt, k1.g2_pt, 0);
let r2 = ec.add(k1.g_pt, k1.g_pt, 0);
let (_, r2_coords) = k1.stack.ec.point_params(r2);
let (_, g2_coords) = k1.stack.ec.point_params(k1.g2_pt);
assert_eq!(r2_coords, g2_coords);
assert_eq!(k1.stack.point_coords(r3).0, from_hex(G3X));
k1.stack.traces().prove_and_verify();
}
#[test]
fn double_forged_as_generic_unbalances() {
let mut k1 = k1_stack();
k1.stack.require().add(k1.g_pt, k1.g_pt, 0);
let traces = k1.stack.traces();
let forged = tamper_block0(traces.ec_add_main(), &[(COL_DBL, 0), (COL_GEN, 1)]);
let mut rng = StdRng::seed_from_u64(0xecad_da01);
check_ec_add(&forged);
let mut mains = traces.mains();
mains[4] = &forged;
assert_ne!(stack_residual(&mains, &mut rng), 0);
}
#[test]
#[should_panic(expected = "constraint")]
fn generic_forged_as_double_rejected() {
let mut k1 = k1_stack();
k1.stack.require().add(k1.g_pt, k1.g2_pt, 0);
let traces = k1.stack.traces();
let forged = tamper_block0(traces.ec_add_main(), &[(COL_GEN, 0), (COL_DBL, 1)]);
check_ec_add(&forged);
}
#[test]
#[should_panic(expected = "constraint")]
fn cancel_forged_on_distinct_x_rejected() {
let mut k1 = k1_stack();
let pai = k1.pai;
let mut ec = k1.stack.require();
let q_pt = ec.add_point(k1.group, from_hex(BETA_GX), from_hex(NEG_GY));
ec.add(k1.g_pt, q_pt, 0);
let traces = k1.stack.traces();
let mut forged =
tamper_block0(traces.ec_add_main(), &[(COL_GEN, 0), (COL_CANCEL, 1), (COL_MINTS, 0)]);
tamper_cell(&mut forged, ROW_RES, CELL_R, pai.addr());
check_ec_add(&forged);
}
#[test]
fn finite_forged_as_pai_unbalances() {
let mut k1 = k1_stack();
k1.stack.require().add(k1.g_pt, k1.g2_pt, 0);
let q_ptr = k1.g2_pt;
let traces = k1.stack.traces();
let mut forged =
tamper_block0(traces.ec_add_main(), &[(COL_GEN, 0), (COL_PAI_P, 1), (COL_MINTS, 0)]);
tamper_cell(&mut forged, ROW_RES, CELL_R, q_ptr.addr());
let mut rng = StdRng::seed_from_u64(0xecad_da03);
check_ec_add(&forged);
let mut mains = traces.mains();
mains[4] = &forged;
assert_ne!(stack_residual(&mains, &mut rng), 0);
}
#[test]
fn double_forged_as_cancel_unbalances() {
let mut k1 = k1_stack();
k1.stack.require().add(k1.g_pt, k1.g_pt, 0);
let pai = k1.pai;
let traces = k1.stack.traces();
let mut forged = tamper_block0(traces.ec_add_main(), &[(COL_DBL, 0), (COL_CANCEL, 1)]);
tamper_cell(&mut forged, ROW_RES, CELL_R, pai.addr());
let mut rng = StdRng::seed_from_u64(0xecad_da04);
check_ec_add(&forged);
let mut mains = traces.mains();
mains[4] = &forged;
assert_ne!(stack_residual(&mains, &mut rng), 0);
}
#[test]
fn ed25519_torsion_forged_as_double_unbalances() {
let bound = "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC";
let a_w = "2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA984914A144";
let b_w = "7B425ED097B425ED097B425ED097B425ED097B425ED097B4260B5E9C7710C864";
let x_t = "2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2451";
let mut stack = EcStack::new(from_hex(bound));
let fp = stack.fp;
let mut ec = stack.require();
let (group, _pai) = ec.create_group(from_hex(a_w), from_hex(b_w), fp);
let t_pt = ec.add_point(group, from_hex(x_t), from_hex("0"));
ec.add(t_pt, t_pt, 0); let traces = stack.traces();
let forged = tamper_block0(traces.ec_add_main(), &[(COL_CANCEL, 0), (COL_DBL, 1)]);
let mut rng = StdRng::seed_from_u64(0xecadd_25519f);
check_ec_add(&forged);
let mut mains = traces.mains();
mains[4] = &forged;
assert_ne!(stack_residual(&mains, &mut rng), 0);
}
#[test]
fn forged_result_ptr_unbalances() {
let mut k1 = k1_stack();
k1.stack.require().add(k1.g_pt, k1.g2_pt, 0);
let g_pt = k1.g_pt;
let traces = k1.stack.traces();
let mut forged = tamper_block0(traces.ec_add_main(), &[(COL_MINTS, 0)]);
tamper_cell(&mut forged, ROW_RES, CELL_R, g_pt.addr());
let mut rng = StdRng::seed_from_u64(0xecad_da05);
check_ec_add(&forged);
let mut mains = traces.mains();
mains[4] = &forged;
assert_ne!(stack_residual(&mains, &mut rng), 0);
}
#[test]
#[should_panic(expected = "constraint")]
fn passthrough_cannot_mint() {
let mut k1 = k1_stack();
k1.stack.require().add(k1.pai, k1.g_pt, 0);
let traces = k1.stack.traces();
let forged = tamper_block0(traces.ec_add_main(), &[(COL_MINTS, 1)]);
check_ec_add(&forged);
}
#[test]
#[should_panic(expected = "constraint")]
fn mint_result_equal_operand_rejected() {
let mut k1 = k1_stack();
k1.stack.require().add(k1.g_pt, k1.g2_pt, 0);
let g_pt = k1.g_pt;
let traces = k1.stack.traces();
let mut forged = traces.ec_add_main().clone();
tamper_cell(&mut forged, ROW_RES, CELL_R, g_pt.addr());
check_ec_add(&forged);
}
#[test]
fn cert_point_forged_as_trio_unbalances() {
let mut k1 = k1_stack();
let r = k1.stack.require().add(k1.g_pt, k1.g2_pt, 0);
let traces = k1.stack.traces();
let forged = tamper_ec_points(traces.ec_points_main(), r.addr() as usize - 1, COL_IS_CERT, 0);
crate::tests::check_local(EcPointStoreGroupsAir, &forged);
let mut rng = StdRng::seed_from_u64(0xecad_dce3);
let mut mains = traces.mains();
mains[3] = &forged;
assert_ne!(stack_residual(&mains, &mut rng), 0);
}