Skip to main content

runar_compiler_rust/codegen/
stack.rs

1//! Pass 5: Stack Lower -- converts ANF IR to Stack IR.
2//!
3//! The fundamental challenge: ANF uses named temporaries but Bitcoin Script
4//! operates on an anonymous stack. We maintain a "stack map" that tracks
5//! which named value lives at which stack position, then emit PICK/ROLL/DUP
6//! operations to shuttle values to the top when they are needed.
7//!
8//! This matches the TypeScript reference compiler and aligned Go compiler:
9//! - Private methods are inlined at call sites rather than compiled separately
10//! - Constructor is skipped
11//! - @ref: aliases are handled via PICK (non-consuming copy)
12//! - @this is a compile-time placeholder (push 0)
13//! - super() is a no-op at stack level
14
15use std::collections::{HashMap, HashSet};
16
17use crate::ir::{ANFBinding, ANFMethod, ANFProgram, ANFProperty, ANFValue, ConstValue};
18
19// ---------------------------------------------------------------------------
20// Constants
21// ---------------------------------------------------------------------------
22
23const MAX_STACK_DEPTH: usize = 800;
24
25// ---------------------------------------------------------------------------
26// Stack IR types
27// ---------------------------------------------------------------------------
28
29/// A single stack-machine operation.
30#[derive(Debug, Clone)]
31pub enum StackOp {
32    Push(PushValue),
33    Dup,
34    Swap,
35    Roll { depth: usize },
36    Pick { depth: usize },
37    Drop,
38    Nip,
39    Over,
40    Rot,
41    Tuck,
42    Opcode(String),
43    If {
44        then_ops: Vec<StackOp>,
45        else_ops: Vec<StackOp>,
46    },
47    Placeholder {
48        param_index: usize,
49        param_name: String,
50    },
51}
52
53/// Typed value for push operations.
54#[derive(Debug, Clone)]
55pub enum PushValue {
56    Bool(bool),
57    Int(i128),
58    Bytes(Vec<u8>),
59}
60
61/// A stack-lowered method.
62#[derive(Debug, Clone)]
63pub struct StackMethod {
64    pub name: String,
65    pub ops: Vec<StackOp>,
66    pub max_stack_depth: usize,
67}
68
69// ---------------------------------------------------------------------------
70// Builtin function -> opcode mapping
71// ---------------------------------------------------------------------------
72
73fn is_ec_builtin(name: &str) -> bool {
74    matches!(
75        name,
76        "ecAdd"
77            | "ecMul"
78            | "ecMulGen"
79            | "ecNegate"
80            | "ecOnCurve"
81            | "ecModReduce"
82            | "ecEncodeCompressed"
83            | "ecMakePoint"
84            | "ecPointX"
85            | "ecPointY"
86    )
87}
88
89fn builtin_opcodes(name: &str) -> Option<Vec<&'static str>> {
90    match name {
91        "sha256" => Some(vec!["OP_SHA256"]),
92        "ripemd160" => Some(vec!["OP_RIPEMD160"]),
93        "hash160" => Some(vec!["OP_HASH160"]),
94        "hash256" => Some(vec!["OP_HASH256"]),
95        "checkSig" => Some(vec!["OP_CHECKSIG"]),
96        "checkMultiSig" => Some(vec!["OP_CHECKMULTISIG"]),
97        "len" => Some(vec!["OP_SIZE"]),
98        "cat" => Some(vec!["OP_CAT"]),
99        "num2bin" => Some(vec!["OP_NUM2BIN"]),
100        "bin2num" => Some(vec!["OP_BIN2NUM"]),
101        "abs" => Some(vec!["OP_ABS"]),
102        "min" => Some(vec!["OP_MIN"]),
103        "max" => Some(vec!["OP_MAX"]),
104        "within" => Some(vec!["OP_WITHIN"]),
105        "split" => Some(vec!["OP_SPLIT"]),
106        "left" => Some(vec!["OP_SPLIT", "OP_DROP"]),
107        "int2str" => Some(vec!["OP_NUM2BIN"]),
108        "bool" => Some(vec!["OP_0NOTEQUAL"]),
109        "unpack" => Some(vec!["OP_BIN2NUM"]),
110        _ => None,
111    }
112}
113
114fn binop_opcodes(op: &str) -> Option<Vec<&'static str>> {
115    match op {
116        "+" => Some(vec!["OP_ADD"]),
117        "-" => Some(vec!["OP_SUB"]),
118        "*" => Some(vec!["OP_MUL"]),
119        "/" => Some(vec!["OP_DIV"]),
120        "%" => Some(vec!["OP_MOD"]),
121        "===" => Some(vec!["OP_NUMEQUAL"]),
122        "!==" => Some(vec!["OP_NUMEQUAL", "OP_NOT"]),
123        "<" => Some(vec!["OP_LESSTHAN"]),
124        ">" => Some(vec!["OP_GREATERTHAN"]),
125        "<=" => Some(vec!["OP_LESSTHANOREQUAL"]),
126        ">=" => Some(vec!["OP_GREATERTHANOREQUAL"]),
127        "&&" => Some(vec!["OP_BOOLAND"]),
128        "||" => Some(vec!["OP_BOOLOR"]),
129        "&" => Some(vec!["OP_AND"]),
130        "|" => Some(vec!["OP_OR"]),
131        "^" => Some(vec!["OP_XOR"]),
132        "<<" => Some(vec!["OP_LSHIFT"]),
133        ">>" => Some(vec!["OP_RSHIFT"]),
134        _ => None,
135    }
136}
137
138fn unaryop_opcodes(op: &str) -> Option<Vec<&'static str>> {
139    match op {
140        "!" => Some(vec!["OP_NOT"]),
141        "-" => Some(vec!["OP_NEGATE"]),
142        "~" => Some(vec!["OP_INVERT"]),
143        _ => None,
144    }
145}
146
147// ---------------------------------------------------------------------------
148// Stack map
149// ---------------------------------------------------------------------------
150
151/// Tracks named values on the stack. Index 0 is the bottom; last is the top.
152/// Empty string means anonymous/consumed slot.
153#[derive(Debug, Clone)]
154struct StackMap {
155    slots: Vec<String>,
156}
157
158impl StackMap {
159    fn new(initial: &[String]) -> Self {
160        StackMap {
161            slots: initial.to_vec(),
162        }
163    }
164
165    fn depth(&self) -> usize {
166        self.slots.len()
167    }
168
169    fn push(&mut self, name: &str) {
170        self.slots.push(name.to_string());
171    }
172
173    fn pop(&mut self) -> String {
174        self.slots.pop().expect("stack underflow")
175    }
176
177    fn find_depth(&self, name: &str) -> Option<usize> {
178        for (i, slot) in self.slots.iter().enumerate().rev() {
179            if slot == name {
180                return Some(self.slots.len() - 1 - i);
181            }
182        }
183        None
184    }
185
186    fn has(&self, name: &str) -> bool {
187        self.slots.iter().any(|s| s == name)
188    }
189
190    fn remove_at_depth(&mut self, depth_from_top: usize) -> String {
191        let index = self.slots.len() - 1 - depth_from_top;
192        self.slots.remove(index)
193    }
194
195    fn peek_at_depth(&self, depth_from_top: usize) -> &str {
196        let index = self.slots.len() - 1 - depth_from_top;
197        &self.slots[index]
198    }
199
200    fn swap(&mut self) {
201        let n = self.slots.len();
202        assert!(n >= 2, "stack underflow on swap");
203        self.slots.swap(n - 1, n - 2);
204    }
205
206    fn dup(&mut self) {
207        assert!(!self.slots.is_empty(), "stack underflow on dup");
208        let top = self.slots.last().unwrap().clone();
209        self.slots.push(top);
210    }
211
212    /// Get the set of all non-empty slot names.
213    fn named_slots(&self) -> HashSet<String> {
214        self.slots.iter().filter(|s| !s.is_empty()).cloned().collect()
215    }
216}
217
218// ---------------------------------------------------------------------------
219// Use analysis
220// ---------------------------------------------------------------------------
221
222fn compute_last_uses(bindings: &[ANFBinding]) -> HashMap<String, usize> {
223    let mut last_use = HashMap::new();
224    for (i, binding) in bindings.iter().enumerate() {
225        for r in collect_refs(&binding.value) {
226            last_use.insert(r, i);
227        }
228    }
229    last_use
230}
231
232fn collect_refs(value: &ANFValue) -> Vec<String> {
233    let mut refs = Vec::new();
234    match value {
235        ANFValue::LoadParam { name } => {
236            // Track param name so last-use analysis keeps the param on the stack
237            // (via PICK) until its final load_param, then consumes it (via ROLL).
238            refs.push(name.clone());
239        }
240        ANFValue::LoadProp { .. }
241        | ANFValue::GetStateScript { .. } => {}
242
243        ANFValue::LoadConst { value: v } => {
244            // load_const with @ref: values reference another binding
245            if let Some(s) = v.as_str() {
246                if s.len() > 5 && &s[..5] == "@ref:" {
247                    refs.push(s[5..].to_string());
248                }
249            }
250        }
251
252        ANFValue::BinOp { left, right, .. } => {
253            refs.push(left.clone());
254            refs.push(right.clone());
255        }
256        ANFValue::UnaryOp { operand, .. } => {
257            refs.push(operand.clone());
258        }
259        ANFValue::Call { args, .. } => {
260            refs.extend(args.iter().cloned());
261        }
262        ANFValue::MethodCall { object, args, .. } => {
263            refs.push(object.clone());
264            refs.extend(args.iter().cloned());
265        }
266        ANFValue::If {
267            cond,
268            then,
269            else_branch,
270        } => {
271            refs.push(cond.clone());
272            for b in then {
273                refs.extend(collect_refs(&b.value));
274            }
275            for b in else_branch {
276                refs.extend(collect_refs(&b.value));
277            }
278        }
279        ANFValue::Loop { body, .. } => {
280            for b in body {
281                refs.extend(collect_refs(&b.value));
282            }
283        }
284        ANFValue::Assert { value } => {
285            refs.push(value.clone());
286        }
287        ANFValue::UpdateProp { value, .. } => {
288            refs.push(value.clone());
289        }
290        ANFValue::CheckPreimage { preimage } => {
291            refs.push(preimage.clone());
292        }
293        ANFValue::DeserializeState { preimage } => {
294            refs.push(preimage.clone());
295        }
296        ANFValue::AddOutput { satoshis, state_values, preimage } => {
297            refs.push(satoshis.clone());
298            refs.extend(state_values.iter().cloned());
299            if !preimage.is_empty() {
300                refs.push(preimage.clone());
301            }
302        }
303    }
304    refs
305}
306
307// ---------------------------------------------------------------------------
308// Lowering context
309// ---------------------------------------------------------------------------
310
311struct LoweringContext {
312    sm: StackMap,
313    ops: Vec<StackOp>,
314    max_depth: usize,
315    properties: Vec<ANFProperty>,
316    private_methods: HashMap<String, ANFMethod>,
317    /// Binding names defined in the current lowerBindings scope.
318    /// Used by @ref: handler to decide whether to consume (local) or copy (outer-scope).
319    local_bindings: HashSet<String>,
320    /// Parent-scope refs that must not be consumed (used after current if-branch).
321    outer_protected_refs: Option<HashSet<String>>,
322}
323
324impl LoweringContext {
325    fn new(params: &[String], properties: &[ANFProperty]) -> Self {
326        let mut ctx = LoweringContext {
327            sm: StackMap::new(params),
328            ops: Vec::new(),
329            max_depth: 0,
330            properties: properties.to_vec(),
331            private_methods: HashMap::new(),
332            local_bindings: HashSet::new(),
333            outer_protected_refs: None,
334        };
335        ctx.track_depth();
336        ctx
337    }
338
339    fn track_depth(&mut self) {
340        if self.sm.depth() > self.max_depth {
341            self.max_depth = self.sm.depth();
342        }
343    }
344
345    fn emit_op(&mut self, op: StackOp) {
346        self.ops.push(op);
347        self.track_depth();
348    }
349
350    fn is_last_use(&self, name: &str, current_index: usize, last_uses: &HashMap<String, usize>) -> bool {
351        match last_uses.get(name) {
352            None => true,
353            Some(&last) => last <= current_index,
354        }
355    }
356
357    fn bring_to_top(&mut self, name: &str, consume: bool) {
358        let depth = self
359            .sm
360            .find_depth(name)
361            .unwrap_or_else(|| panic!("value '{}' not found on stack", name));
362
363        if depth == 0 {
364            if !consume {
365                self.emit_op(StackOp::Dup);
366                self.sm.dup();
367            }
368            return;
369        }
370
371        if depth == 1 && consume {
372            self.emit_op(StackOp::Swap);
373            self.sm.swap();
374            return;
375        }
376
377        if consume {
378            if depth == 2 {
379                self.emit_op(StackOp::Rot);
380                let removed = self.sm.remove_at_depth(2);
381                self.sm.push(&removed);
382            } else {
383                self.emit_op(StackOp::Push(PushValue::Int(depth as i128)));
384                self.sm.push(""); // temporary depth literal
385                self.emit_op(StackOp::Roll { depth });
386                self.sm.pop(); // remove depth literal
387                let rolled = self.sm.remove_at_depth(depth);
388                self.sm.push(&rolled);
389            }
390        } else {
391            if depth == 1 {
392                self.emit_op(StackOp::Over);
393                let picked = self.sm.peek_at_depth(1).to_string();
394                self.sm.push(&picked);
395            } else {
396                self.emit_op(StackOp::Push(PushValue::Int(depth as i128)));
397                self.sm.push(""); // temporary
398                self.emit_op(StackOp::Pick { depth });
399                self.sm.pop(); // remove depth literal
400                let picked = self.sm.peek_at_depth(depth).to_string();
401                self.sm.push(&picked);
402            }
403        }
404
405        self.track_depth();
406    }
407
408    // -----------------------------------------------------------------------
409    // Lower bindings
410    // -----------------------------------------------------------------------
411
412    fn lower_bindings(&mut self, bindings: &[ANFBinding], terminal_assert: bool) {
413        self.local_bindings = bindings.iter().map(|b| b.name.clone()).collect();
414        let mut last_uses = compute_last_uses(bindings);
415
416        // Protect parent-scope refs that are still needed after this scope
417        if let Some(ref protected) = self.outer_protected_refs {
418            for r in protected {
419                last_uses.insert(r.clone(), bindings.len());
420            }
421        }
422
423        // Find the terminal binding index (if terminal_assert is set).
424        // If the last binding is an 'if' whose branches end in asserts,
425        // that 'if' is the terminal point (not an earlier standalone assert).
426        let mut last_assert_idx: isize = -1;
427        let mut terminal_if_idx: isize = -1;
428        if terminal_assert {
429            let last_binding = bindings.last();
430            if let Some(b) = last_binding {
431                if matches!(&b.value, ANFValue::If { .. }) {
432                    terminal_if_idx = (bindings.len() - 1) as isize;
433                } else {
434                    for i in (0..bindings.len()).rev() {
435                        if matches!(&bindings[i].value, ANFValue::Assert { .. }) {
436                            last_assert_idx = i as isize;
437                            break;
438                        }
439                    }
440                }
441            }
442        }
443
444        for (i, binding) in bindings.iter().enumerate() {
445            if matches!(&binding.value, ANFValue::Assert { .. }) && i as isize == last_assert_idx {
446                // Terminal assert: leave value on stack instead of OP_VERIFY
447                if let ANFValue::Assert { value } = &binding.value {
448                    self.lower_assert(value, i, &last_uses, true);
449                }
450            } else if matches!(&binding.value, ANFValue::If { .. }) && i as isize == terminal_if_idx {
451                // Terminal if: propagate terminalAssert into both branches
452                if let ANFValue::If { cond, then, else_branch } = &binding.value {
453                    self.lower_if(&binding.name, cond, then, else_branch, i, &last_uses, true);
454                }
455            } else {
456                self.lower_binding(binding, i, &last_uses);
457            }
458        }
459    }
460
461    fn lower_binding(
462        &mut self,
463        binding: &ANFBinding,
464        binding_index: usize,
465        last_uses: &HashMap<String, usize>,
466    ) {
467        let name = &binding.name;
468        match &binding.value {
469            ANFValue::LoadParam {
470                name: param_name, ..
471            } => {
472                self.lower_load_param(name, param_name, binding_index, last_uses);
473            }
474            ANFValue::LoadProp {
475                name: prop_name, ..
476            } => {
477                self.lower_load_prop(name, prop_name);
478            }
479            ANFValue::LoadConst { .. } => {
480                self.lower_load_const(name, &binding.value, binding_index, last_uses);
481            }
482            ANFValue::BinOp {
483                op, left, right, result_type, ..
484            } => {
485                self.lower_bin_op(name, op, left, right, binding_index, last_uses, result_type.as_deref());
486            }
487            ANFValue::UnaryOp { op, operand } => {
488                self.lower_unary_op(name, op, operand, binding_index, last_uses);
489            }
490            ANFValue::Call {
491                func: func_name,
492                args,
493            } => {
494                self.lower_call(name, func_name, args, binding_index, last_uses);
495            }
496            ANFValue::MethodCall {
497                object,
498                method,
499                args,
500            } => {
501                self.lower_method_call(name, object, method, args, binding_index, last_uses);
502            }
503            ANFValue::If {
504                cond,
505                then,
506                else_branch,
507            } => {
508                self.lower_if(name, cond, then, else_branch, binding_index, last_uses, false);
509            }
510            ANFValue::Loop {
511                count,
512                body,
513                iter_var,
514            } => {
515                self.lower_loop(name, *count, body, iter_var);
516            }
517            ANFValue::Assert { value } => {
518                self.lower_assert(value, binding_index, last_uses, false);
519            }
520            ANFValue::UpdateProp {
521                name: prop_name,
522                value,
523            } => {
524                self.lower_update_prop(prop_name, value, binding_index, last_uses);
525            }
526            ANFValue::GetStateScript {} => {
527                self.lower_get_state_script(name);
528            }
529            ANFValue::CheckPreimage { preimage } => {
530                self.lower_check_preimage(name, preimage, binding_index, last_uses);
531            }
532            ANFValue::DeserializeState { preimage } => {
533                self.lower_deserialize_state(preimage, binding_index, last_uses);
534            }
535            ANFValue::AddOutput { satoshis, state_values, preimage } => {
536                self.lower_add_output(name, satoshis, state_values, preimage, binding_index, last_uses);
537            }
538        }
539    }
540
541    // -----------------------------------------------------------------------
542    // Individual lowering methods
543    // -----------------------------------------------------------------------
544
545    fn lower_load_param(
546        &mut self,
547        binding_name: &str,
548        param_name: &str,
549        binding_index: usize,
550        last_uses: &HashMap<String, usize>,
551    ) {
552        if self.sm.has(param_name) {
553            let is_last = self.is_last_use(param_name, binding_index, last_uses);
554            self.bring_to_top(param_name, is_last);
555            self.sm.pop();
556            self.sm.push(binding_name);
557        } else {
558            self.emit_op(StackOp::Push(PushValue::Int(0)));
559            self.sm.push(binding_name);
560        }
561    }
562
563    fn lower_load_prop(&mut self, binding_name: &str, prop_name: &str) {
564        let prop = self.properties.iter().find(|p| p.name == prop_name).cloned();
565
566        if self.sm.has(prop_name) {
567            // Property has been updated (via update_prop) — use the stack value.
568            // Must check this BEFORE initial_value — after update_prop, we need the
569            // updated value, not the original constant.
570            self.bring_to_top(prop_name, false);
571            self.sm.pop();
572        } else if let Some(ref p) = prop {
573            if let Some(ref val) = p.initial_value {
574                self.push_json_value(val);
575            } else {
576                // Property value will be provided at deployment time; emit a placeholder.
577                // The emitter records byte offsets so the SDK can splice in real values.
578                let param_index = self
579                    .properties
580                    .iter()
581                    .position(|p2| p2.name == prop_name)
582                    .unwrap_or(0);
583                self.emit_op(StackOp::Placeholder {
584                    param_index,
585                    param_name: prop_name.to_string(),
586                });
587            }
588        } else {
589            // Property not found and not on stack — emit placeholder with index 0.
590            let param_index = self
591                .properties
592                .iter()
593                .position(|p2| p2.name == prop_name)
594                .unwrap_or(0);
595            self.emit_op(StackOp::Placeholder {
596                param_index,
597                param_name: prop_name.to_string(),
598            });
599        }
600        self.sm.push(binding_name);
601    }
602
603    fn push_json_value(&mut self, val: &serde_json::Value) {
604        match val {
605            serde_json::Value::Bool(b) => {
606                self.emit_op(StackOp::Push(PushValue::Bool(*b)));
607            }
608            serde_json::Value::Number(n) => {
609                let i = n.as_i64().map(|v| v as i128).unwrap_or(0);
610                self.emit_op(StackOp::Push(PushValue::Int(i)));
611            }
612            serde_json::Value::String(s) => {
613                let bytes = hex_to_bytes(s);
614                self.emit_op(StackOp::Push(PushValue::Bytes(bytes)));
615            }
616            _ => {
617                self.emit_op(StackOp::Push(PushValue::Int(0)));
618            }
619        }
620    }
621
622    fn lower_load_const(&mut self, binding_name: &str, value: &ANFValue, binding_index: usize, last_uses: &HashMap<String, usize>) {
623        // Handle @ref: aliases (ANF variable aliasing)
624        // When a load_const has a string value starting with "@ref:", it's an alias
625        // to another binding. We bring that value to the top via PICK (non-consuming)
626        // unless this is the last use, in which case we consume it via ROLL.
627        if let Some(ConstValue::Str(ref s)) = value.const_value() {
628            if s.len() > 5 && &s[..5] == "@ref:" {
629                let ref_name = &s[5..];
630                if self.sm.has(ref_name) {
631                    // Only consume (ROLL) if the ref target is a local binding in the
632                    // current scope. Outer-scope refs must be copied (PICK) so that the
633                    // parent stackMap stays in sync (critical for IfElse branches and
634                    // BoundedLoop iterations).
635                    let consume = self.local_bindings.contains(ref_name)
636                        && self.is_last_use(ref_name, binding_index, last_uses);
637                    self.bring_to_top(ref_name, consume);
638                    self.sm.pop();
639                    self.sm.push(binding_name);
640                } else {
641                    // Referenced value not on stack -- push a placeholder
642                    self.emit_op(StackOp::Push(PushValue::Int(0)));
643                    self.sm.push(binding_name);
644                }
645                return;
646            }
647            // Handle @this marker -- compile-time concept, not a runtime value
648            if s == "@this" {
649                self.emit_op(StackOp::Push(PushValue::Int(0)));
650                self.sm.push(binding_name);
651                return;
652            }
653        }
654
655        match value.const_value() {
656            Some(ConstValue::Bool(b)) => {
657                self.emit_op(StackOp::Push(PushValue::Bool(b)));
658            }
659            Some(ConstValue::Int(n)) => {
660                self.emit_op(StackOp::Push(PushValue::Int(n)));
661            }
662            Some(ConstValue::Str(s)) => {
663                let bytes = hex_to_bytes(&s);
664                self.emit_op(StackOp::Push(PushValue::Bytes(bytes)));
665            }
666            None => {
667                self.emit_op(StackOp::Push(PushValue::Int(0)));
668            }
669        }
670        self.sm.push(binding_name);
671    }
672
673    fn lower_bin_op(
674        &mut self,
675        binding_name: &str,
676        op: &str,
677        left: &str,
678        right: &str,
679        binding_index: usize,
680        last_uses: &HashMap<String, usize>,
681        result_type: Option<&str>,
682    ) {
683        let left_is_last = self.is_last_use(left, binding_index, last_uses);
684        self.bring_to_top(left, left_is_last);
685
686        let right_is_last = self.is_last_use(right, binding_index, last_uses);
687        self.bring_to_top(right, right_is_last);
688
689        self.sm.pop();
690        self.sm.pop();
691
692        // For equality operators, choose OP_EQUAL vs OP_NUMEQUAL based on operand type.
693        if result_type == Some("bytes") && (op == "===" || op == "!==") {
694            self.emit_op(StackOp::Opcode("OP_EQUAL".to_string()));
695            if op == "!==" {
696                self.emit_op(StackOp::Opcode("OP_NOT".to_string()));
697            }
698        } else {
699            let codes = binop_opcodes(op)
700                .unwrap_or_else(|| panic!("unknown binary operator: {}", op));
701            for code in codes {
702                self.emit_op(StackOp::Opcode(code.to_string()));
703            }
704        }
705
706        self.sm.push(binding_name);
707        self.track_depth();
708    }
709
710    fn lower_unary_op(
711        &mut self,
712        binding_name: &str,
713        op: &str,
714        operand: &str,
715        binding_index: usize,
716        last_uses: &HashMap<String, usize>,
717    ) {
718        let is_last = self.is_last_use(operand, binding_index, last_uses);
719        self.bring_to_top(operand, is_last);
720
721        self.sm.pop();
722
723        let codes = unaryop_opcodes(op)
724            .unwrap_or_else(|| panic!("unknown unary operator: {}", op));
725        for code in codes {
726            self.emit_op(StackOp::Opcode(code.to_string()));
727        }
728
729        self.sm.push(binding_name);
730        self.track_depth();
731    }
732
733    fn lower_call(
734        &mut self,
735        binding_name: &str,
736        func_name: &str,
737        args: &[String],
738        binding_index: usize,
739        last_uses: &HashMap<String, usize>,
740    ) {
741        // Special handling for assert
742        if func_name == "assert" {
743            if !args.is_empty() {
744                let is_last = self.is_last_use(&args[0], binding_index, last_uses);
745                self.bring_to_top(&args[0], is_last);
746                self.sm.pop();
747                self.emit_op(StackOp::Opcode("OP_VERIFY".to_string()));
748                self.sm.push(binding_name);
749            }
750            return;
751        }
752
753        // super() in constructor -- no opcode emission needed.
754        // Constructor args are already on the stack.
755        if func_name == "super" {
756            self.sm.push(binding_name);
757            return;
758        }
759
760        if func_name == "__array_access" {
761            self.lower_array_access(binding_name, args, binding_index, last_uses);
762            return;
763        }
764
765        if func_name == "reverseBytes" {
766            self.lower_reverse_bytes(binding_name, args, binding_index, last_uses);
767            return;
768        }
769
770        if func_name == "substr" {
771            self.lower_substr(binding_name, args, binding_index, last_uses);
772            return;
773        }
774
775        if func_name == "verifyRabinSig" {
776            self.lower_verify_rabin_sig(binding_name, args, binding_index, last_uses);
777            return;
778        }
779
780        if func_name == "verifyWOTS" {
781            self.lower_verify_wots(binding_name, args, binding_index, last_uses);
782            return;
783        }
784
785        if func_name.starts_with("verifySLHDSA_") {
786            let param_key = func_name.trim_start_matches("verifySLHDSA_");
787            self.lower_verify_slh_dsa(binding_name, param_key, args, binding_index, last_uses);
788            return;
789        }
790
791        if is_ec_builtin(func_name) {
792            self.lower_ec_builtin(binding_name, func_name, args, binding_index, last_uses);
793            return;
794        }
795
796        if func_name == "safediv" {
797            self.lower_safediv(binding_name, args, binding_index, last_uses);
798            return;
799        }
800
801        if func_name == "safemod" {
802            self.lower_safemod(binding_name, args, binding_index, last_uses);
803            return;
804        }
805
806        if func_name == "clamp" {
807            self.lower_clamp(binding_name, args, binding_index, last_uses);
808            return;
809        }
810
811        if func_name == "pow" {
812            self.lower_pow(binding_name, args, binding_index, last_uses);
813            return;
814        }
815
816        if func_name == "mulDiv" {
817            self.lower_mul_div(binding_name, args, binding_index, last_uses);
818            return;
819        }
820
821        if func_name == "percentOf" {
822            self.lower_percent_of(binding_name, args, binding_index, last_uses);
823            return;
824        }
825
826        if func_name == "sqrt" {
827            self.lower_sqrt(binding_name, args, binding_index, last_uses);
828            return;
829        }
830
831        if func_name == "gcd" {
832            self.lower_gcd(binding_name, args, binding_index, last_uses);
833            return;
834        }
835
836        if func_name == "divmod" {
837            self.lower_divmod(binding_name, args, binding_index, last_uses);
838            return;
839        }
840
841        if func_name == "log2" {
842            self.lower_log2(binding_name, args, binding_index, last_uses);
843            return;
844        }
845
846        if func_name == "sign" {
847            self.lower_sign(binding_name, args, binding_index, last_uses);
848            return;
849        }
850
851        if func_name == "right" {
852            self.lower_right(binding_name, args, binding_index, last_uses);
853            return;
854        }
855
856        // pack and toByteString are no-ops: the value is already on the stack in
857        // the correct representation. We just consume the arg and rename.
858        if func_name == "pack" || func_name == "toByteString" {
859            if !args.is_empty() {
860                let is_last = self.is_last_use(&args[0], binding_index, last_uses);
861                self.bring_to_top(&args[0], is_last);
862                self.sm.pop();
863            }
864            self.sm.push(binding_name);
865            return;
866        }
867
868        // computeStateOutputHash(preimage, stateBytes) — builds full BIP-143 output
869        // serialization for single-output stateful continuation, then hashes it.
870        if func_name == "computeStateOutputHash" {
871            self.lower_compute_state_output_hash(binding_name, args, binding_index, last_uses);
872            return;
873        }
874
875        // computeStateOutput(preimage, stateBytes) — same as computeStateOutputHash
876        // but returns raw output bytes WITHOUT hashing. Used when the output bytes
877        // need to be concatenated with a change output before hashing.
878        if func_name == "computeStateOutput" {
879            self.lower_compute_state_output(binding_name, args, binding_index, last_uses);
880            return;
881        }
882
883        // buildChangeOutput(pkh, amount) — builds a P2PKH output serialization:
884        //   amount(8LE) + varint(25) + OP_DUP OP_HASH160 OP_PUSHBYTES_20 <pkh> OP_EQUALVERIFY OP_CHECKSIG
885        //   = amount(8LE) + 0x19 + 76a914 <pkh:20> 88ac
886        if func_name == "buildChangeOutput" {
887            self.lower_build_change_output(binding_name, args, binding_index, last_uses);
888            return;
889        }
890
891        // Preimage field extractors — each needs a custom OP_SPLIT sequence
892        // because OP_SPLIT produces two stack values and the intermediate stack
893        // management cannot be expressed in the simple builtin_opcodes table.
894        if func_name.starts_with("extract") {
895            self.lower_extractor(binding_name, func_name, args, binding_index, last_uses);
896            return;
897        }
898
899        // General builtin: push args in order, then emit opcodes
900        for arg in args {
901            let is_last = self.is_last_use(arg, binding_index, last_uses);
902            self.bring_to_top(arg, is_last);
903        }
904
905        for _ in args {
906            self.sm.pop();
907        }
908
909        if let Some(codes) = builtin_opcodes(func_name) {
910            for code in codes {
911                self.emit_op(StackOp::Opcode(code.to_string()));
912            }
913        } else {
914            // Unknown function -- push a placeholder
915            self.emit_op(StackOp::Push(PushValue::Int(0)));
916            self.sm.push(binding_name);
917            return;
918        }
919
920        if func_name == "split" {
921            self.sm.push("");
922            self.sm.push(binding_name);
923        } else if func_name == "len" {
924            self.sm.push("");
925            self.sm.push(binding_name);
926        } else {
927            self.sm.push(binding_name);
928        }
929
930        self.track_depth();
931    }
932
933    fn lower_method_call(
934        &mut self,
935        binding_name: &str,
936        _object: &str,
937        method: &str,
938        args: &[String],
939        binding_index: usize,
940        last_uses: &HashMap<String, usize>,
941    ) {
942        if method == "getStateScript" {
943            self.lower_get_state_script(binding_name);
944            return;
945        }
946
947        // Check if this is a private method call that should be inlined
948        if let Some(private_method) = self.private_methods.get(method).cloned() {
949            self.inline_method_call(binding_name, &private_method, args, binding_index, last_uses);
950            return;
951        }
952
953        // For other method calls, treat like a function call
954        self.lower_call(binding_name, method, args, binding_index, last_uses);
955    }
956
957    /// Inline a private method by lowering its body in the current context.
958    /// The method's parameters are bound to the call arguments.
959    fn inline_method_call(
960        &mut self,
961        binding_name: &str,
962        method: &ANFMethod,
963        args: &[String],
964        binding_index: usize,
965        last_uses: &HashMap<String, usize>,
966    ) {
967        // First, bring all args to the top of the stack and rename them to the method param names
968        for (i, arg) in args.iter().enumerate() {
969            if i < method.params.len() {
970                let is_last = self.is_last_use(arg, binding_index, last_uses);
971                self.bring_to_top(arg, is_last);
972                // Rename to param name
973                self.sm.pop();
974                self.sm.push(&method.params[i].name);
975            }
976        }
977
978        // Lower the method body
979        self.lower_bindings(&method.body, false);
980
981        // The last binding's result should be on top of the stack.
982        // Rename it to the calling binding name.
983        if !method.body.is_empty() {
984            let last_binding_name = &method.body[method.body.len() - 1].name;
985            if self.sm.depth() > 0 {
986                let top_name = self.sm.peek_at_depth(0).to_string();
987                if top_name == *last_binding_name {
988                    self.sm.pop();
989                    self.sm.push(binding_name);
990                }
991            }
992        }
993    }
994
995    fn lower_if(
996        &mut self,
997        binding_name: &str,
998        cond: &str,
999        then_bindings: &[ANFBinding],
1000        else_bindings: &[ANFBinding],
1001        binding_index: usize,
1002        last_uses: &HashMap<String, usize>,
1003        terminal_assert: bool,
1004    ) {
1005        let is_last = self.is_last_use(cond, binding_index, last_uses);
1006        self.bring_to_top(cond, is_last);
1007        self.sm.pop(); // OP_IF consumes condition
1008
1009        // Identify parent-scope items still needed after this if-expression.
1010        let mut protected_refs = HashSet::new();
1011        for (ref_name, &last_idx) in last_uses.iter() {
1012            if last_idx > binding_index && self.sm.has(ref_name) {
1013                protected_refs.insert(ref_name.clone());
1014            }
1015        }
1016
1017        // Snapshot parent stackMap names before branches run
1018        let pre_if_names = self.sm.named_slots();
1019
1020        // Lower then-branch
1021        let mut then_ctx = LoweringContext::new(&[], &self.properties);
1022        then_ctx.sm = self.sm.clone();
1023        then_ctx.outer_protected_refs = Some(protected_refs.clone());
1024        then_ctx.lower_bindings(then_bindings, terminal_assert);
1025
1026        if terminal_assert && then_ctx.sm.depth() > 1 {
1027            let excess = then_ctx.sm.depth() - 1;
1028            for _ in 0..excess {
1029                then_ctx.emit_op(StackOp::Nip);
1030                then_ctx.sm.remove_at_depth(1);
1031            }
1032        }
1033
1034        // Lower else-branch
1035        let mut else_ctx = LoweringContext::new(&[], &self.properties);
1036        else_ctx.sm = self.sm.clone();
1037        else_ctx.outer_protected_refs = Some(protected_refs);
1038        else_ctx.lower_bindings(else_bindings, terminal_assert);
1039
1040        if terminal_assert && else_ctx.sm.depth() > 1 {
1041            let excess = else_ctx.sm.depth() - 1;
1042            for _ in 0..excess {
1043                else_ctx.emit_op(StackOp::Nip);
1044                else_ctx.sm.remove_at_depth(1);
1045            }
1046        }
1047
1048        // Balance stack between branches so both end at the same depth.
1049        // When addOutput is inside an if-then with no else, the then-branch
1050        // consumes stack items and pushes a serialized output, while the
1051        // else-branch leaves the stack unchanged. Both must end at the same
1052        // depth for correct execution after OP_ENDIF.
1053        //
1054        // Fix: identify items consumed by the then-branch (present in parent
1055        // but gone after then). Emit targeted ROLL+DROP in the else-branch
1056        // to remove those same items, then push empty bytes as placeholder.
1057        // OP_CAT with empty bytes is identity (no-op for output hashing).
1058        let post_then_names = then_ctx.sm.named_slots();
1059        let mut consumed_names: Vec<String> = Vec::new();
1060        for name in &pre_if_names {
1061            if !post_then_names.contains(name) && else_ctx.sm.has(name) {
1062                consumed_names.push(name.clone());
1063            }
1064        }
1065        if !consumed_names.is_empty() {
1066            // Remove consumed items from else-branch, deepest first to keep depths stable
1067            let mut depths: Vec<usize> = consumed_names
1068                .iter()
1069                .map(|n| else_ctx.sm.find_depth(n).unwrap())
1070                .collect();
1071            depths.sort_by(|a, b| b.cmp(a));
1072            for depth in depths {
1073                if depth == 0 {
1074                    else_ctx.emit_op(StackOp::Drop);
1075                    else_ctx.sm.pop();
1076                } else if depth == 1 {
1077                    else_ctx.emit_op(StackOp::Nip);
1078                    else_ctx.sm.remove_at_depth(1);
1079                } else {
1080                    // Push depth, ROLL to bring item to top, then DROP
1081                    else_ctx.emit_op(StackOp::Push(PushValue::Int(depth as i128)));
1082                    else_ctx.sm.push("");
1083                    else_ctx.emit_op(StackOp::Roll { depth });
1084                    else_ctx.sm.pop(); // remove depth literal
1085                    let rolled = else_ctx.sm.remove_at_depth(depth);
1086                    else_ctx.sm.push(&rolled);
1087                    else_ctx.emit_op(StackOp::Drop);
1088                    else_ctx.sm.pop();
1089                }
1090            }
1091            // Push empty bytes as placeholder result
1092            else_ctx.emit_op(StackOp::Push(PushValue::Bytes(Vec::new())));
1093            else_ctx.sm.push("");
1094        }
1095        // Handle the reverse case symmetrically (unlikely but safe)
1096        let post_else_names = else_ctx.sm.named_slots();
1097        let mut else_consumed_names: Vec<String> = Vec::new();
1098        for name in &pre_if_names {
1099            if !post_else_names.contains(name) && then_ctx.sm.has(name) {
1100                else_consumed_names.push(name.clone());
1101            }
1102        }
1103        if !else_consumed_names.is_empty() {
1104            let mut depths: Vec<usize> = else_consumed_names
1105                .iter()
1106                .map(|n| then_ctx.sm.find_depth(n).unwrap())
1107                .collect();
1108            depths.sort_by(|a, b| b.cmp(a));
1109            for depth in depths {
1110                if depth == 0 {
1111                    then_ctx.emit_op(StackOp::Drop);
1112                    then_ctx.sm.pop();
1113                } else if depth == 1 {
1114                    then_ctx.emit_op(StackOp::Nip);
1115                    then_ctx.sm.remove_at_depth(1);
1116                } else {
1117                    then_ctx.emit_op(StackOp::Push(PushValue::Int(depth as i128)));
1118                    then_ctx.sm.push("");
1119                    then_ctx.emit_op(StackOp::Roll { depth });
1120                    then_ctx.sm.pop();
1121                    let rolled = then_ctx.sm.remove_at_depth(depth);
1122                    then_ctx.sm.push(&rolled);
1123                    then_ctx.emit_op(StackOp::Drop);
1124                    then_ctx.sm.pop();
1125                }
1126            }
1127            then_ctx.emit_op(StackOp::Push(PushValue::Bytes(Vec::new())));
1128            then_ctx.sm.push("");
1129        }
1130
1131        let then_ops = then_ctx.ops;
1132        let else_ops = else_ctx.ops;
1133
1134        self.emit_op(StackOp::If {
1135            then_ops,
1136            else_ops: if else_ops.is_empty() {
1137                Vec::new()
1138            } else {
1139                else_ops
1140            },
1141        });
1142
1143        // Reconcile parent stackMap: remove items consumed by the branches.
1144        let post_branch_names = then_ctx.sm.named_slots();
1145        for name in &pre_if_names {
1146            if !post_branch_names.contains(name) && self.sm.has(name) {
1147                if let Some(depth) = self.sm.find_depth(name) {
1148                    self.sm.remove_at_depth(depth);
1149                }
1150            }
1151        }
1152
1153        self.sm.push(binding_name);
1154        self.track_depth();
1155
1156        if then_ctx.max_depth > self.max_depth {
1157            self.max_depth = then_ctx.max_depth;
1158        }
1159        if else_ctx.max_depth > self.max_depth {
1160            self.max_depth = else_ctx.max_depth;
1161        }
1162    }
1163
1164    fn lower_loop(
1165        &mut self,
1166        _binding_name: &str,
1167        count: usize,
1168        body: &[ANFBinding],
1169        iter_var: &str,
1170    ) {
1171        // Collect outer-scope names referenced in the loop body.
1172        // These must not be consumed in non-final iterations.
1173        let body_binding_names: HashSet<String> = body.iter().map(|b| b.name.clone()).collect();
1174        let mut outer_refs = HashSet::new();
1175        for b in body {
1176            if let ANFValue::LoadParam { name } = &b.value {
1177                if name != iter_var {
1178                    outer_refs.insert(name.clone());
1179                }
1180            }
1181            // Also protect @ref: targets from outer scope (not redefined in body)
1182            if let ANFValue::LoadConst { value: v } = &b.value {
1183                if let Some(s) = v.as_str() {
1184                    if s.len() > 5 && &s[..5] == "@ref:" {
1185                        let ref_name = &s[5..];
1186                        if !body_binding_names.contains(ref_name) {
1187                            outer_refs.insert(ref_name.to_string());
1188                        }
1189                    }
1190                }
1191            }
1192        }
1193
1194        // Temporarily extend localBindings with body binding names so
1195        // @ref: to body-internal values can consume on last use.
1196        let prev_local_bindings = self.local_bindings.clone();
1197        self.local_bindings = self.local_bindings.union(&body_binding_names).cloned().collect();
1198
1199        for i in 0..count {
1200            self.emit_op(StackOp::Push(PushValue::Int(i as i128)));
1201            self.sm.push(iter_var);
1202
1203            let mut last_uses = compute_last_uses(body);
1204
1205            // In non-final iterations, prevent outer-scope refs from being
1206            // consumed by setting their last-use beyond any body binding index.
1207            if i < count - 1 {
1208                for ref_name in &outer_refs {
1209                    last_uses.insert(ref_name.clone(), body.len());
1210                }
1211            }
1212
1213            for (j, binding) in body.iter().enumerate() {
1214                self.lower_binding(binding, j, &last_uses);
1215            }
1216
1217            // Clean up the iteration variable if it was not consumed by the body.
1218            // The body may not reference iter_var at all, leaving it on the stack.
1219            if self.sm.has(iter_var) {
1220                let depth = self.sm.find_depth(iter_var);
1221                if let Some(0) = depth {
1222                    self.emit_op(StackOp::Drop);
1223                    self.sm.pop();
1224                }
1225            }
1226        }
1227        // Restore localBindings
1228        self.local_bindings = prev_local_bindings;
1229        // Note: loops are statements, not expressions — they don't produce a
1230        // physical stack value. Do NOT push a dummy stackMap entry, as it would
1231        // desync the stackMap depth from the physical stack.
1232    }
1233
1234    fn lower_assert(
1235        &mut self,
1236        value_ref: &str,
1237        binding_index: usize,
1238        last_uses: &HashMap<String, usize>,
1239        terminal: bool,
1240    ) {
1241        let is_last = self.is_last_use(value_ref, binding_index, last_uses);
1242        self.bring_to_top(value_ref, is_last);
1243        if terminal {
1244            // Terminal assert: leave value on stack for Bitcoin Script's
1245            // final truthiness check (no OP_VERIFY).
1246        } else {
1247            self.sm.pop();
1248            self.emit_op(StackOp::Opcode("OP_VERIFY".to_string()));
1249        }
1250        self.track_depth();
1251    }
1252
1253    fn lower_update_prop(
1254        &mut self,
1255        prop_name: &str,
1256        value_ref: &str,
1257        binding_index: usize,
1258        last_uses: &HashMap<String, usize>,
1259    ) {
1260        let is_last = self.is_last_use(value_ref, binding_index, last_uses);
1261        self.bring_to_top(value_ref, is_last);
1262        self.sm.pop();
1263        self.sm.push(prop_name);
1264        self.track_depth();
1265    }
1266
1267    fn lower_get_state_script(&mut self, binding_name: &str) {
1268        let state_props: Vec<ANFProperty> = self
1269            .properties
1270            .iter()
1271            .filter(|p| !p.readonly)
1272            .cloned()
1273            .collect();
1274
1275        if state_props.is_empty() {
1276            self.emit_op(StackOp::Push(PushValue::Bytes(Vec::new())));
1277            self.sm.push(binding_name);
1278            return;
1279        }
1280
1281        let mut first = true;
1282        for prop in &state_props {
1283            if self.sm.has(&prop.name) {
1284                self.bring_to_top(&prop.name, true); // consume: raw value dead after serialization
1285            } else if let Some(ref val) = prop.initial_value {
1286                self.push_json_value(val);
1287                self.sm.push("");
1288            } else {
1289                self.emit_op(StackOp::Push(PushValue::Int(0)));
1290                self.sm.push("");
1291            }
1292
1293            // Convert numeric/boolean values to fixed-width bytes via OP_NUM2BIN
1294            if prop.prop_type == "bigint" {
1295                self.emit_op(StackOp::Push(PushValue::Int(8)));
1296                self.sm.push("");
1297                self.emit_op(StackOp::Opcode("OP_NUM2BIN".to_string()));
1298                self.sm.pop(); // pop the width
1299            } else if prop.prop_type == "boolean" {
1300                self.emit_op(StackOp::Push(PushValue::Int(1)));
1301                self.sm.push("");
1302                self.emit_op(StackOp::Opcode("OP_NUM2BIN".to_string()));
1303                self.sm.pop(); // pop the width
1304            }
1305            // Byte types (ByteString, PubKey, Sig, Sha256, etc.) need no conversion
1306
1307            if !first {
1308                self.sm.pop();
1309                self.sm.pop();
1310                self.emit_op(StackOp::Opcode("OP_CAT".to_string()));
1311                self.sm.push("");
1312            }
1313            first = false;
1314        }
1315
1316        self.sm.pop();
1317        self.sm.push(binding_name);
1318        self.track_depth();
1319    }
1320
1321    /// Builds the full BIP-143 output serialization for a single-output stateful
1322    /// continuation and hashes it with SHA256d. Extracts varint+scriptCode+amount
1323    /// from the preimage, replaces state bytes with new state, builds:
1324    /// amount(8LE) + varint(scriptLen) + codePart + OP_RETURN + stateBytes.
1325    fn lower_compute_state_output_hash(
1326        &mut self,
1327        binding_name: &str,
1328        args: &[String],
1329        binding_index: usize,
1330        last_uses: &std::collections::HashMap<String, usize>,
1331    ) {
1332        // Compute fixed state serialization length from non-readonly properties.
1333        let mut state_len: usize = 0;
1334        for p in &self.properties {
1335            if p.readonly {
1336                continue;
1337            }
1338            state_len += match p.prop_type.as_str() {
1339                "bigint" => 8,
1340                "boolean" => 1,
1341                "PubKey" => 33,
1342                "Addr" => 20,
1343                "Sha256" => 32,
1344                "Point" => 64,
1345                _ => panic!(
1346                    "computeStateOutputHash: unsupported mutable property type '{}' for '{}'",
1347                    p.prop_type, p.name
1348                ),
1349            };
1350        }
1351
1352        let preimage_ref = &args[0];
1353        let state_bytes_ref = &args[1];
1354
1355        // Bring stateBytes then preimage to top.
1356        let sb_last = self.is_last_use(state_bytes_ref, binding_index, last_uses);
1357        self.bring_to_top(state_bytes_ref, sb_last);
1358        let pre_last = self.is_last_use(preimage_ref, binding_index, last_uses);
1359        self.bring_to_top(preimage_ref, pre_last);
1360
1361        // Stack: [..., stateBytes, preimage]
1362
1363        // Step 1: Extract middle: varint + scriptCode + amount.
1364        self.emit_op(StackOp::Push(PushValue::Int(104)));
1365        self.sm.push("");
1366        self.emit_op(StackOp::Opcode("OP_SPLIT".into())); // [prefix, rest]
1367        self.sm.pop(); // 104
1368        self.sm.pop(); // preimage
1369        self.sm.push(""); // prefix
1370        self.sm.push(""); // rest
1371        self.emit_op(StackOp::Nip); // drop prefix
1372        self.sm.pop();
1373        self.sm.pop();
1374        self.sm.push(""); // rest
1375
1376        // Drop last 44 bytes.
1377        self.emit_op(StackOp::Opcode("OP_SIZE".into())); // [rest, restLen]
1378        self.sm.push(""); // restLen (1 push for OP_SIZE)
1379        self.emit_op(StackOp::Push(PushValue::Int(44)));
1380        self.sm.push("");
1381        self.emit_op(StackOp::Opcode("OP_SUB".into()));
1382        self.sm.pop();
1383        self.sm.pop();
1384        self.sm.push("");
1385        self.emit_op(StackOp::Opcode("OP_SPLIT".into())); // [middle, tail44]
1386        self.sm.pop(); // position
1387        self.sm.pop(); // rest
1388        self.sm.push(""); // middle
1389        self.sm.push(""); // tail44
1390        self.emit_op(StackOp::Drop);
1391        self.sm.pop();
1392
1393        // Step 2: Split off amount (last 8 bytes), save to altstack.
1394        self.emit_op(StackOp::Opcode("OP_SIZE".into()));
1395        self.sm.push("");
1396        self.emit_op(StackOp::Push(PushValue::Int(8)));
1397        self.sm.push("");
1398        self.emit_op(StackOp::Opcode("OP_SUB".into()));
1399        self.sm.pop();
1400        self.sm.pop();
1401        self.sm.push("");
1402        self.emit_op(StackOp::Opcode("OP_SPLIT".into())); // [varint+sc, amount]
1403        self.sm.pop(); // position
1404        self.sm.pop(); // middle
1405        self.sm.push(""); // varint+sc
1406        self.sm.push(""); // amount
1407        self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
1408        self.sm.pop();
1409
1410        // Step 3: Strip state + OP_RETURN from end (stateLen + 1 bytes).
1411        self.emit_op(StackOp::Opcode("OP_SIZE".into()));
1412        self.sm.push("");
1413        self.emit_op(StackOp::Push(PushValue::Int((state_len + 1) as i128)));
1414        self.sm.push("");
1415        self.emit_op(StackOp::Opcode("OP_SUB".into()));
1416        self.sm.pop();
1417        self.sm.pop();
1418        self.sm.push("");
1419        self.emit_op(StackOp::Opcode("OP_SPLIT".into())); // [varint+code, opReturn+state]
1420        self.sm.pop(); // position
1421        self.sm.pop(); // varint+sc
1422        self.sm.push(""); // varint+code
1423        self.sm.push(""); // opReturn+state
1424        self.emit_op(StackOp::Drop);
1425        self.sm.pop();
1426
1427        // Step 4: Append OP_RETURN byte (0x6a as data).
1428        self.emit_op(StackOp::Push(PushValue::Bytes(vec![0x6a])));
1429        self.sm.push("");
1430        self.emit_op(StackOp::Opcode("OP_CAT".into()));
1431        self.sm.pop();
1432        self.sm.pop();
1433        self.sm.push("");
1434
1435        // Step 5: Swap and CAT to append stateBytes.
1436        self.emit_op(StackOp::Swap);
1437        self.sm.swap();
1438        self.emit_op(StackOp::Opcode("OP_CAT".into()));
1439        self.sm.pop();
1440        self.sm.pop();
1441        self.sm.push("");
1442
1443        // Step 6: Prepend amount from altstack.
1444        self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
1445        self.sm.push("");
1446        self.emit_op(StackOp::Swap);
1447        self.sm.swap();
1448        self.emit_op(StackOp::Opcode("OP_CAT".into()));
1449        self.sm.pop();
1450        self.sm.pop();
1451        self.sm.push("");
1452
1453        // Step 7: Hash with SHA256d.
1454        self.emit_op(StackOp::Opcode("OP_HASH256".into()));
1455
1456        self.sm.pop();
1457        self.sm.push(binding_name);
1458        self.track_depth();
1459    }
1460
1461    /// `computeStateOutput(preimage, stateBytes)` — same as `computeStateOutputHash`
1462    /// but returns raw output bytes WITHOUT the final OP_HASH256. This allows the
1463    /// caller to concatenate additional outputs (e.g., change output) before hashing.
1464    fn lower_compute_state_output(
1465        &mut self,
1466        binding_name: &str,
1467        args: &[String],
1468        binding_index: usize,
1469        last_uses: &std::collections::HashMap<String, usize>,
1470    ) {
1471        // Compute fixed state serialization length from non-readonly properties.
1472        let mut state_len: usize = 0;
1473        for p in &self.properties {
1474            if p.readonly {
1475                continue;
1476            }
1477            state_len += match p.prop_type.as_str() {
1478                "bigint" => 8,
1479                "boolean" => 1,
1480                "PubKey" => 33,
1481                "Addr" => 20,
1482                "Sha256" => 32,
1483                "Point" => 64,
1484                _ => panic!(
1485                    "computeStateOutput: unsupported mutable property type '{}' for '{}'",
1486                    p.prop_type, p.name
1487                ),
1488            };
1489        }
1490
1491        let preimage_ref = &args[0];
1492        let state_bytes_ref = &args[1];
1493
1494        // Bring stateBytes then preimage to top.
1495        let sb_last = self.is_last_use(state_bytes_ref, binding_index, last_uses);
1496        self.bring_to_top(state_bytes_ref, sb_last);
1497        let pre_last = self.is_last_use(preimage_ref, binding_index, last_uses);
1498        self.bring_to_top(preimage_ref, pre_last);
1499
1500        // Stack: [..., stateBytes, preimage]
1501
1502        // Step 1: Extract middle: varint + scriptCode + amount.
1503        self.emit_op(StackOp::Push(PushValue::Int(104)));
1504        self.sm.push("");
1505        self.emit_op(StackOp::Opcode("OP_SPLIT".into())); // [prefix, rest]
1506        self.sm.pop(); // 104
1507        self.sm.pop(); // preimage
1508        self.sm.push(""); // prefix
1509        self.sm.push(""); // rest
1510        self.emit_op(StackOp::Nip); // drop prefix
1511        self.sm.pop();
1512        self.sm.pop();
1513        self.sm.push(""); // rest
1514
1515        // Drop last 44 bytes.
1516        self.emit_op(StackOp::Opcode("OP_SIZE".into())); // [rest, restLen]
1517        self.sm.push(""); // restLen (1 push for OP_SIZE)
1518        self.emit_op(StackOp::Push(PushValue::Int(44)));
1519        self.sm.push("");
1520        self.emit_op(StackOp::Opcode("OP_SUB".into()));
1521        self.sm.pop();
1522        self.sm.pop();
1523        self.sm.push("");
1524        self.emit_op(StackOp::Opcode("OP_SPLIT".into())); // [middle, tail44]
1525        self.sm.pop(); // position
1526        self.sm.pop(); // rest
1527        self.sm.push(""); // middle
1528        self.sm.push(""); // tail44
1529        self.emit_op(StackOp::Drop);
1530        self.sm.pop();
1531
1532        // Step 2: Split off amount (last 8 bytes), save to altstack.
1533        self.emit_op(StackOp::Opcode("OP_SIZE".into()));
1534        self.sm.push("");
1535        self.emit_op(StackOp::Push(PushValue::Int(8)));
1536        self.sm.push("");
1537        self.emit_op(StackOp::Opcode("OP_SUB".into()));
1538        self.sm.pop();
1539        self.sm.pop();
1540        self.sm.push("");
1541        self.emit_op(StackOp::Opcode("OP_SPLIT".into())); // [varint+sc, amount]
1542        self.sm.pop(); // position
1543        self.sm.pop(); // middle
1544        self.sm.push(""); // varint+sc
1545        self.sm.push(""); // amount
1546        self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
1547        self.sm.pop();
1548
1549        // Step 3: Strip state + OP_RETURN from end (stateLen + 1 bytes).
1550        self.emit_op(StackOp::Opcode("OP_SIZE".into()));
1551        self.sm.push("");
1552        self.emit_op(StackOp::Push(PushValue::Int((state_len + 1) as i128)));
1553        self.sm.push("");
1554        self.emit_op(StackOp::Opcode("OP_SUB".into()));
1555        self.sm.pop();
1556        self.sm.pop();
1557        self.sm.push("");
1558        self.emit_op(StackOp::Opcode("OP_SPLIT".into())); // [varint+code, opReturn+state]
1559        self.sm.pop(); // position
1560        self.sm.pop(); // varint+sc
1561        self.sm.push(""); // varint+code
1562        self.sm.push(""); // opReturn+state
1563        self.emit_op(StackOp::Drop);
1564        self.sm.pop();
1565
1566        // Step 4: Append OP_RETURN byte (0x6a as data).
1567        self.emit_op(StackOp::Push(PushValue::Bytes(vec![0x6a])));
1568        self.sm.push("");
1569        self.emit_op(StackOp::Opcode("OP_CAT".into()));
1570        self.sm.pop();
1571        self.sm.pop();
1572        self.sm.push("");
1573
1574        // Step 5: Swap and CAT to append stateBytes.
1575        self.emit_op(StackOp::Swap);
1576        self.sm.swap();
1577        self.emit_op(StackOp::Opcode("OP_CAT".into()));
1578        self.sm.pop();
1579        self.sm.pop();
1580        self.sm.push("");
1581
1582        // Step 6: Prepend amount from altstack.
1583        self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
1584        self.sm.push("");
1585        self.emit_op(StackOp::Swap);
1586        self.sm.swap();
1587        self.emit_op(StackOp::Opcode("OP_CAT".into()));
1588        self.sm.pop();
1589        self.sm.pop();
1590        self.sm.push("");
1591        // --- Stack: [..., fullOutputSerialization] --- (NO hash)
1592
1593        self.sm.pop();
1594        self.sm.push(binding_name);
1595        self.track_depth();
1596    }
1597
1598    /// `buildChangeOutput(pkh, amount)` — builds a P2PKH output serialization:
1599    ///   amount(8LE) + 0x19 + 76a914 <pkh:20bytes> 88ac
1600    /// Total: 34 bytes (8 + 1 + 25).
1601    fn lower_build_change_output(
1602        &mut self,
1603        binding_name: &str,
1604        args: &[String],
1605        binding_index: usize,
1606        last_uses: &std::collections::HashMap<String, usize>,
1607    ) {
1608        let pkh_ref = &args[0];
1609        let amount_ref = &args[1];
1610
1611        // Step 1: Build the P2PKH locking script with length prefix.
1612        // Push prefix: varint(25) + OP_DUP + OP_HASH160 + OP_PUSHBYTES_20 = 0x1976a914
1613        self.emit_op(StackOp::Push(PushValue::Bytes(vec![0x19, 0x76, 0xa9, 0x14])));
1614        self.sm.push("");
1615
1616        // Push the 20-byte PKH
1617        let pkh_last = self.is_last_use(pkh_ref, binding_index, last_uses);
1618        self.bring_to_top(pkh_ref, pkh_last);
1619        // CAT: prefix || pkh
1620        self.emit_op(StackOp::Opcode("OP_CAT".into()));
1621        self.sm.pop();
1622        self.sm.pop();
1623        self.sm.push("");
1624
1625        // Push suffix: OP_EQUALVERIFY + OP_CHECKSIG = 0x88ac
1626        self.emit_op(StackOp::Push(PushValue::Bytes(vec![0x88, 0xac])));
1627        self.sm.push("");
1628        // CAT: (prefix || pkh) || suffix
1629        self.emit_op(StackOp::Opcode("OP_CAT".into()));
1630        self.sm.pop();
1631        self.sm.pop();
1632        self.sm.push("");
1633        // --- Stack: [..., 0x1976a914{pkh}88ac] ---
1634
1635        // Step 2: Prepend amount as 8-byte LE.
1636        let amount_last = self.is_last_use(amount_ref, binding_index, last_uses);
1637        self.bring_to_top(amount_ref, amount_last);
1638        self.emit_op(StackOp::Push(PushValue::Int(8)));
1639        self.sm.push("");
1640        self.emit_op(StackOp::Opcode("OP_NUM2BIN".into()));
1641        self.sm.pop(); // pop width
1642        // Stack: [..., script, amount(8LE)]
1643        self.emit_op(StackOp::Swap);
1644        self.sm.swap();
1645        // Stack: [..., amount(8LE), script]
1646        self.emit_op(StackOp::Opcode("OP_CAT".into()));
1647        self.sm.pop();
1648        self.sm.pop();
1649        self.sm.push("");
1650        // --- Stack: [..., amount(8LE)+0x1976a914{pkh}88ac] ---
1651
1652        self.sm.pop();
1653        self.sm.push(binding_name);
1654        self.track_depth();
1655    }
1656
1657    fn lower_add_output(
1658        &mut self,
1659        binding_name: &str,
1660        satoshis: &str,
1661        state_values: &[String],
1662        preimage: &str,
1663        binding_index: usize,
1664        last_uses: &HashMap<String, usize>,
1665    ) {
1666        // Build a full BIP-143 output serialization:
1667        //   amount(8LE) + varint(scriptLen) + codePart + OP_RETURN + stateBytes
1668
1669        let state_props: Vec<ANFProperty> = self
1670            .properties
1671            .iter()
1672            .filter(|p| !p.readonly)
1673            .cloned()
1674            .collect();
1675
1676        // Compute fixed state serialization length.
1677        let mut state_len: usize = 0;
1678        for p in &state_props {
1679            state_len += match p.prop_type.as_str() {
1680                "bigint" => 8,
1681                "boolean" => 1,
1682                "PubKey" => 33,
1683                "Addr" => 20,
1684                "Sha256" => 32,
1685                "Point" => 64,
1686                _ => panic!("addOutput: unsupported mutable property type '{}'", p.prop_type),
1687            };
1688        }
1689
1690        // --- Extract varint + codePart from preimage ---
1691        let pre_is_last = self.is_last_use(preimage, binding_index, last_uses);
1692        self.bring_to_top(preimage, pre_is_last);
1693
1694        // Step 1: Extract middle: varint + scriptCode + amount.
1695        self.emit_op(StackOp::Push(PushValue::Int(104)));
1696        self.sm.push("");
1697        self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
1698        self.sm.pop();
1699        self.sm.pop();
1700        self.sm.push(""); // prefix
1701        self.sm.push(""); // rest
1702        self.emit_op(StackOp::Nip);
1703        self.sm.pop();
1704        self.sm.pop();
1705        self.sm.push(""); // rest
1706
1707        // Drop last 44 bytes.
1708        self.emit_op(StackOp::Opcode("OP_SIZE".into()));
1709        self.sm.push("");
1710        self.emit_op(StackOp::Push(PushValue::Int(44)));
1711        self.sm.push("");
1712        self.emit_op(StackOp::Opcode("OP_SUB".into()));
1713        self.sm.pop();
1714        self.sm.pop();
1715        self.sm.push("");
1716        self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
1717        self.sm.pop();
1718        self.sm.pop();
1719        self.sm.push(""); // middle
1720        self.sm.push(""); // tail44
1721        self.emit_op(StackOp::Drop);
1722        self.sm.pop();
1723
1724        // Step 2: Split off amount (last 8 bytes) and drop it.
1725        self.emit_op(StackOp::Opcode("OP_SIZE".into()));
1726        self.sm.push("");
1727        self.emit_op(StackOp::Push(PushValue::Int(8)));
1728        self.sm.push("");
1729        self.emit_op(StackOp::Opcode("OP_SUB".into()));
1730        self.sm.pop();
1731        self.sm.pop();
1732        self.sm.push("");
1733        self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
1734        self.sm.pop();
1735        self.sm.pop();
1736        self.sm.push(""); // varint+sc
1737        self.sm.push(""); // amount
1738        self.emit_op(StackOp::Drop); // drop amount
1739        self.sm.pop();
1740
1741        // Step 3: Strip state + OP_RETURN from end.
1742        self.emit_op(StackOp::Opcode("OP_SIZE".into()));
1743        self.sm.push("");
1744        self.emit_op(StackOp::Push(PushValue::Int((state_len + 1) as i128)));
1745        self.sm.push("");
1746        self.emit_op(StackOp::Opcode("OP_SUB".into()));
1747        self.sm.pop();
1748        self.sm.pop();
1749        self.sm.push("");
1750        self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
1751        self.sm.pop();
1752        self.sm.pop();
1753        self.sm.push(""); // varint+code
1754        self.sm.push(""); // opReturn+state
1755        self.emit_op(StackOp::Drop);
1756        self.sm.pop();
1757
1758        // Step 4: Append OP_RETURN byte.
1759        self.emit_op(StackOp::Push(PushValue::Bytes(vec![0x6a])));
1760        self.sm.push("");
1761        self.emit_op(StackOp::Opcode("OP_CAT".into()));
1762        self.sm.pop();
1763        self.sm.pop();
1764        self.sm.push("");
1765
1766        // Step 5: Serialize each state value and concatenate.
1767        for (i, value_ref) in state_values.iter().enumerate() {
1768            if i >= state_props.len() {
1769                break;
1770            }
1771            let prop = &state_props[i];
1772
1773            let is_last = self.is_last_use(value_ref, binding_index, last_uses);
1774            self.bring_to_top(value_ref, is_last);
1775
1776            if prop.prop_type == "bigint" {
1777                self.emit_op(StackOp::Push(PushValue::Int(8)));
1778                self.sm.push("");
1779                self.emit_op(StackOp::Opcode("OP_NUM2BIN".to_string()));
1780                self.sm.pop();
1781            } else if prop.prop_type == "boolean" {
1782                self.emit_op(StackOp::Push(PushValue::Int(1)));
1783                self.sm.push("");
1784                self.emit_op(StackOp::Opcode("OP_NUM2BIN".to_string()));
1785                self.sm.pop();
1786            }
1787
1788            self.sm.pop();
1789            self.sm.pop();
1790            self.emit_op(StackOp::Opcode("OP_CAT".to_string()));
1791            self.sm.push("");
1792        }
1793
1794        // Step 6: Prepend satoshis as 8-byte LE.
1795        let is_last_satoshis = self.is_last_use(satoshis, binding_index, last_uses);
1796        self.bring_to_top(satoshis, is_last_satoshis);
1797        self.emit_op(StackOp::Push(PushValue::Int(8)));
1798        self.sm.push("");
1799        self.emit_op(StackOp::Opcode("OP_NUM2BIN".to_string()));
1800        self.sm.pop();
1801        self.emit_op(StackOp::Swap);
1802        self.sm.swap();
1803        self.sm.pop();
1804        self.sm.pop();
1805        self.emit_op(StackOp::Opcode("OP_CAT".to_string())); // satoshis || varint+script
1806        self.sm.push("");
1807
1808        // Rename top to binding name
1809        self.sm.pop();
1810        self.sm.push(binding_name);
1811        self.track_depth();
1812    }
1813
1814    fn lower_check_preimage(
1815        &mut self,
1816        binding_name: &str,
1817        preimage: &str,
1818        binding_index: usize,
1819        last_uses: &HashMap<String, usize>,
1820    ) {
1821        // OP_PUSH_TX: verify the sighash preimage matches the current spending
1822        // transaction.  See https://wiki.bitcoinsv.io/index.php/OP_PUSH_TX
1823        //
1824        // The technique uses a well-known ECDSA keypair where private key = 1
1825        // (so the public key is the secp256k1 generator point G, compressed:
1826        //   0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798).
1827        //
1828        // At spending time the SDK must:
1829        //   1. Serialise the BIP-143 sighash preimage for the current input.
1830        //   2. Compute sighash = SHA256(SHA256(preimage)).
1831        //   3. Derive an ECDSA signature (r, s) with privkey = 1:
1832        //        r = Gx  (x-coordinate of the generator point, constant)
1833        //        s = (sighash + r) mod n
1834        //   4. DER-encode (r, s) and append the SIGHASH_ALL|FORKID byte (0x41).
1835        //   5. Push <sig> <preimage> (plus any other method args) as the
1836        //      unlocking script.
1837        //
1838        // The locking script sequence:
1839        //   [bring preimage to top]     -- via PICK or ROLL
1840        //   [bring _opPushTxSig to top] -- via ROLL (consuming)
1841        //   <G>                         -- push compressed generator point
1842        //   OP_CHECKSIG                 -- verify sig over SHA256(SHA256(preimage))
1843        //   OP_VERIFY                   -- abort if invalid
1844        //   -- preimage remains on stack for field extractors
1845        //
1846        // Stack map trace:
1847        //   After bring_to_top(preimage):  [..., preimage]
1848        //   After bring_to_top(sig, true): [..., preimage, _opPushTxSig]
1849        //   After push G:                  [..., preimage, _opPushTxSig, null(G)]
1850        //   After OP_CHECKSIG:             [..., preimage, null(result)]
1851        //   After OP_VERIFY:               [..., preimage]
1852
1853        // Step 1: Bring preimage to top.
1854        let is_last = self.is_last_use(preimage, binding_index, last_uses);
1855        self.bring_to_top(preimage, is_last);
1856
1857        // Step 2: Bring the implicit _opPushTxSig to top (consuming).
1858        self.bring_to_top("_opPushTxSig", true);
1859
1860        // Step 3: Push compressed secp256k1 generator point G (33 bytes).
1861        let g: Vec<u8> = vec![
1862            0x02, 0x79, 0xBE, 0x66, 0x7E, 0xF9, 0xDC, 0xBB,
1863            0xAC, 0x55, 0xA0, 0x62, 0x95, 0xCE, 0x87, 0x0B,
1864            0x07, 0x02, 0x9B, 0xFC, 0xDB, 0x2D, 0xCE, 0x28,
1865            0xD9, 0x59, 0xF2, 0x81, 0x5B, 0x16, 0xF8, 0x17,
1866            0x98,
1867        ];
1868        self.emit_op(StackOp::Push(PushValue::Bytes(g)));
1869        self.sm.push(""); // G on stack
1870
1871        // Step 4: OP_CHECKSIG -- pops pubkey (G) and sig, pushes boolean result.
1872        self.emit_op(StackOp::Opcode("OP_CHECKSIG".to_string()));
1873        self.sm.pop(); // G consumed
1874        self.sm.pop(); // _opPushTxSig consumed
1875        self.sm.push(""); // boolean result
1876
1877        // Step 5: OP_VERIFY -- abort if false, removes result from stack.
1878        self.emit_op(StackOp::Opcode("OP_VERIFY".to_string()));
1879        self.sm.pop(); // result consumed
1880
1881        // The preimage is now on top (from Step 1). Rename to binding name
1882        // so field extractors can reference it.
1883        self.sm.pop();
1884        self.sm.push(binding_name);
1885
1886        self.track_depth();
1887    }
1888
1889    /// Lower `deserialize_state(preimage)` — extracts mutable property values
1890    /// from the BIP-143 preimage's scriptCode field. The state is stored as the
1891    /// last `stateLen` bytes of the scriptCode (after OP_RETURN).
1892    ///
1893    /// For each mutable property, the value is extracted, converted to the
1894    /// correct type (BIN2NUM for bigint/boolean), and pushed onto the stack
1895    /// with the property name in the stackMap. This allows `load_prop` to
1896    /// find the deserialized values instead of using hardcoded initial values.
1897    fn lower_deserialize_state(
1898        &mut self,
1899        preimage_ref: &str,
1900        binding_index: usize,
1901        last_uses: &HashMap<String, usize>,
1902    ) {
1903        // Collect mutable (non-readonly) properties and their serialized sizes.
1904        // We clone the data upfront to avoid borrowing self.properties while
1905        // mutating self later.
1906        let mut prop_names: Vec<String> = Vec::new();
1907        let mut prop_types: Vec<String> = Vec::new();
1908        let mut prop_sizes: Vec<i128> = Vec::new();
1909        let mut state_len: i128 = 0;
1910
1911        for p in &self.properties {
1912            if p.readonly {
1913                continue;
1914            }
1915            prop_names.push(p.name.clone());
1916            prop_types.push(p.prop_type.clone());
1917            let sz: i128 = match p.prop_type.as_str() {
1918                "bigint" => 8,
1919                "boolean" => 1,
1920                "PubKey" => 33,
1921                "Addr" => 20,
1922                "Sha256" => 32,
1923                "Point" => 64,
1924                _ => panic!("deserialize_state: unsupported type: {}", p.prop_type),
1925            };
1926            prop_sizes.push(sz);
1927            state_len += sz;
1928        }
1929
1930        if prop_names.is_empty() {
1931            return;
1932        }
1933
1934        // Bring preimage to top of stack.
1935        let is_last = self.is_last_use(preimage_ref, binding_index, last_uses);
1936        self.bring_to_top(preimage_ref, is_last);
1937
1938        // 1. Skip first 104 bytes (header), drop prefix.
1939        self.emit_op(StackOp::Push(PushValue::Int(104)));
1940        self.sm.push("");
1941        self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
1942        self.sm.pop();
1943        self.sm.pop();
1944        self.sm.push("");
1945        self.sm.push("");
1946        self.emit_op(StackOp::Nip);
1947        self.sm.pop();
1948        self.sm.pop();
1949        self.sm.push("");
1950
1951        // 2. Drop tail 44 bytes.
1952        self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
1953        self.sm.push("");
1954        self.emit_op(StackOp::Push(PushValue::Int(44)));
1955        self.sm.push("");
1956        self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
1957        self.sm.pop();
1958        self.sm.pop();
1959        self.sm.push("");
1960        self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
1961        self.sm.pop();
1962        self.sm.pop();
1963        self.sm.push("");
1964        self.sm.push("");
1965        self.emit_op(StackOp::Drop);
1966        self.sm.pop();
1967
1968        // 3. Drop amount (last 8 bytes).
1969        self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
1970        self.sm.push("");
1971        self.emit_op(StackOp::Push(PushValue::Int(8)));
1972        self.sm.push("");
1973        self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
1974        self.sm.pop();
1975        self.sm.pop();
1976        self.sm.push("");
1977        self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
1978        self.sm.pop();
1979        self.sm.pop();
1980        self.sm.push("");
1981        self.sm.push("");
1982        self.emit_op(StackOp::Drop);
1983        self.sm.pop();
1984
1985        // 4. Extract last stateLen bytes (the state section).
1986        self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
1987        self.sm.push("");
1988        self.emit_op(StackOp::Push(PushValue::Int(state_len)));
1989        self.sm.push("");
1990        self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
1991        self.sm.pop();
1992        self.sm.pop();
1993        self.sm.push("");
1994        self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
1995        self.sm.pop();
1996        self.sm.pop();
1997        self.sm.push("");
1998        self.sm.push("");
1999        self.emit_op(StackOp::Nip);
2000        self.sm.pop();
2001        self.sm.pop();
2002        self.sm.push("");
2003
2004        // 5. Split into individual properties.
2005        let num_props = prop_names.len();
2006
2007        if num_props == 1 {
2008            // Single property: convert if needed, name it.
2009            if prop_types[0] == "bigint" || prop_types[0] == "boolean" {
2010                self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2011            }
2012            self.sm.pop();
2013            self.sm.push(&prop_names[0]);
2014        } else {
2015            // Multiple properties: split at each boundary.
2016            for i in 0..num_props {
2017                let sz = prop_sizes[i];
2018                if i < num_props - 1 {
2019                    // Split off sz bytes for this property.
2020                    self.emit_op(StackOp::Push(PushValue::Int(sz)));
2021                    self.sm.push("");
2022                    self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2023                    self.sm.pop();
2024                    self.sm.pop();
2025                    self.sm.push("");
2026                    self.sm.push("");
2027                    // Swap so the extracted portion is on top.
2028                    self.emit_op(StackOp::Swap);
2029                    self.sm.swap();
2030                    // Convert if needed.
2031                    if prop_types[i] == "bigint" || prop_types[i] == "boolean" {
2032                        self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2033                    }
2034                    // Swap back and name the property.
2035                    self.emit_op(StackOp::Swap);
2036                    self.sm.swap();
2037                    self.sm.pop();
2038                    self.sm.pop();
2039                    self.sm.push(&prop_names[i]);
2040                    self.sm.push("");
2041                } else {
2042                    // Last property: just convert and name.
2043                    if prop_types[i] == "bigint" || prop_types[i] == "boolean" {
2044                        self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2045                    }
2046                    self.sm.pop();
2047                    self.sm.push(&prop_names[i]);
2048                }
2049            }
2050        }
2051
2052        self.track_depth();
2053    }
2054
2055    /// Lower a preimage field extractor call.
2056    ///
2057    /// The SigHashPreimage follows BIP-143 format:
2058    ///   Offset  Bytes  Field
2059    ///   0       4      nVersion (LE uint32)
2060    ///   4       32     hashPrevouts
2061    ///   36      32     hashSequence
2062    ///   68      36     outpoint (txid 32 + vout 4)
2063    ///   104     var    scriptCode (varint-prefixed)
2064    ///   var     8      amount (satoshis, LE int64)
2065    ///   var     4      nSequence
2066    ///   var     32     hashOutputs
2067    ///   var     4      nLocktime
2068    ///   var     4      sighashType
2069    ///
2070    /// Fixed-offset fields use absolute OP_SPLIT positions.
2071    /// Variable-offset fields use end-relative positions via OP_SIZE.
2072    fn lower_extractor(
2073        &mut self,
2074        binding_name: &str,
2075        func_name: &str,
2076        args: &[String],
2077        binding_index: usize,
2078        last_uses: &HashMap<String, usize>,
2079    ) {
2080        assert!(!args.is_empty(), "{} requires 1 argument", func_name);
2081        let is_last = self.is_last_use(&args[0], binding_index, last_uses);
2082        self.bring_to_top(&args[0], is_last);
2083
2084        // The preimage is now on top of the stack.
2085        self.sm.pop(); // consume the preimage from stack map
2086
2087        match func_name {
2088            "extractVersion" => {
2089                // <preimage> 4 OP_SPLIT OP_DROP OP_BIN2NUM
2090                self.emit_op(StackOp::Push(PushValue::Int(4)));
2091                self.sm.push("");
2092                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2093                self.sm.pop();
2094                self.sm.push("");
2095                self.sm.push("");
2096                self.emit_op(StackOp::Drop);
2097                self.sm.pop();
2098                self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2099            }
2100            "extractHashPrevouts" => {
2101                // <preimage> 4 OP_SPLIT OP_NIP 32 OP_SPLIT OP_DROP
2102                self.emit_op(StackOp::Push(PushValue::Int(4)));
2103                self.sm.push("");
2104                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2105                self.sm.pop();
2106                self.sm.push("");
2107                self.sm.push("");
2108                self.emit_op(StackOp::Nip);
2109                self.sm.pop();
2110                self.sm.pop();
2111                self.sm.push("");
2112                self.emit_op(StackOp::Push(PushValue::Int(32)));
2113                self.sm.push("");
2114                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2115                self.sm.pop(); // pop position (32)
2116                self.sm.pop(); // pop data being split
2117                self.sm.push("");
2118                self.sm.push("");
2119                self.emit_op(StackOp::Drop);
2120                self.sm.pop();
2121            }
2122            "extractHashSequence" => {
2123                // <preimage> 36 OP_SPLIT OP_NIP 32 OP_SPLIT OP_DROP
2124                self.emit_op(StackOp::Push(PushValue::Int(36)));
2125                self.sm.push("");
2126                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2127                self.sm.pop();
2128                self.sm.push("");
2129                self.sm.push("");
2130                self.emit_op(StackOp::Nip);
2131                self.sm.pop();
2132                self.sm.pop();
2133                self.sm.push("");
2134                self.emit_op(StackOp::Push(PushValue::Int(32)));
2135                self.sm.push("");
2136                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2137                self.sm.pop(); // pop position (32)
2138                self.sm.pop(); // pop data being split
2139                self.sm.push("");
2140                self.sm.push("");
2141                self.emit_op(StackOp::Drop);
2142                self.sm.pop();
2143            }
2144            "extractOutpoint" => {
2145                // <preimage> 68 OP_SPLIT OP_NIP 36 OP_SPLIT OP_DROP
2146                self.emit_op(StackOp::Push(PushValue::Int(68)));
2147                self.sm.push("");
2148                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2149                self.sm.pop();
2150                self.sm.push("");
2151                self.sm.push("");
2152                self.emit_op(StackOp::Nip);
2153                self.sm.pop();
2154                self.sm.pop();
2155                self.sm.push("");
2156                self.emit_op(StackOp::Push(PushValue::Int(36)));
2157                self.sm.push("");
2158                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2159                self.sm.pop(); // pop position (36)
2160                self.sm.pop(); // pop data being split
2161                self.sm.push("");
2162                self.sm.push("");
2163                self.emit_op(StackOp::Drop);
2164                self.sm.pop();
2165            }
2166            "extractSigHashType" => {
2167                // End-relative: last 4 bytes, converted to number.
2168                // <preimage> OP_SIZE 4 OP_SUB OP_SPLIT OP_NIP OP_BIN2NUM
2169                self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
2170                self.sm.push("");
2171                self.sm.push("");
2172                self.emit_op(StackOp::Push(PushValue::Int(4)));
2173                self.sm.push("");
2174                self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
2175                self.sm.pop();
2176                self.sm.pop();
2177                self.sm.push("");
2178                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2179                self.sm.pop();
2180                self.sm.pop();
2181                self.sm.push("");
2182                self.sm.push("");
2183                self.emit_op(StackOp::Nip);
2184                self.sm.pop();
2185                self.sm.pop();
2186                self.sm.push("");
2187                self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2188            }
2189            "extractLocktime" => {
2190                // End-relative: 4 bytes before the last 4 (sighashType).
2191                // <preimage> OP_SIZE 8 OP_SUB OP_SPLIT OP_NIP 4 OP_SPLIT OP_DROP OP_BIN2NUM
2192                self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
2193                self.sm.push("");
2194                self.sm.push("");
2195                self.emit_op(StackOp::Push(PushValue::Int(8)));
2196                self.sm.push("");
2197                self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
2198                self.sm.pop();
2199                self.sm.pop();
2200                self.sm.push("");
2201                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2202                self.sm.pop();
2203                self.sm.pop();
2204                self.sm.push("");
2205                self.sm.push("");
2206                self.emit_op(StackOp::Nip);
2207                self.sm.pop();
2208                self.sm.pop();
2209                self.sm.push("");
2210                self.emit_op(StackOp::Push(PushValue::Int(4)));
2211                self.sm.push("");
2212                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2213                self.sm.pop(); // pop position (4)
2214                self.sm.pop(); // pop value being split
2215                self.sm.push("");
2216                self.sm.push("");
2217                self.emit_op(StackOp::Drop);
2218                self.sm.pop();
2219                self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2220            }
2221            "extractOutputHash" | "extractOutputs" => {
2222                // End-relative: 32 bytes before the last 8 (nLocktime 4 + sighashType 4).
2223                // <preimage> OP_SIZE 40 OP_SUB OP_SPLIT OP_NIP 32 OP_SPLIT OP_DROP
2224                self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
2225                self.sm.push("");
2226                self.sm.push("");
2227                self.emit_op(StackOp::Push(PushValue::Int(40)));
2228                self.sm.push("");
2229                self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
2230                self.sm.pop();
2231                self.sm.pop();
2232                self.sm.push("");
2233                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2234                self.sm.pop();
2235                self.sm.pop();
2236                self.sm.push("");
2237                self.sm.push("");
2238                self.emit_op(StackOp::Nip);
2239                self.sm.pop();
2240                self.sm.pop();
2241                self.sm.push("");
2242                self.emit_op(StackOp::Push(PushValue::Int(32)));
2243                self.sm.push("");
2244                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2245                self.sm.pop(); // pop position (32)
2246                self.sm.pop(); // pop value being split (last40)
2247                self.sm.push("");
2248                self.sm.push("");
2249                self.emit_op(StackOp::Drop);
2250                self.sm.pop();
2251            }
2252            "extractAmount" => {
2253                // End-relative: 8 bytes at offset -(52) from end.
2254                // <preimage> OP_SIZE 52 OP_SUB OP_SPLIT OP_NIP 8 OP_SPLIT OP_DROP OP_BIN2NUM
2255                self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
2256                self.sm.push("");
2257                self.sm.push("");
2258                self.emit_op(StackOp::Push(PushValue::Int(52)));
2259                self.sm.push("");
2260                self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
2261                self.sm.pop();
2262                self.sm.pop();
2263                self.sm.push("");
2264                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2265                self.sm.pop();
2266                self.sm.pop();
2267                self.sm.push("");
2268                self.sm.push("");
2269                self.emit_op(StackOp::Nip);
2270                self.sm.pop();
2271                self.sm.pop();
2272                self.sm.push("");
2273                self.emit_op(StackOp::Push(PushValue::Int(8)));
2274                self.sm.push("");
2275                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2276                self.sm.pop(); // pop position (8)
2277                self.sm.pop(); // pop value being split
2278                self.sm.push("");
2279                self.sm.push("");
2280                self.emit_op(StackOp::Drop);
2281                self.sm.pop();
2282                self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2283            }
2284            "extractSequence" => {
2285                // End-relative: 4 bytes (nSequence) at offset -(44) from end.
2286                // <preimage> OP_SIZE 44 OP_SUB OP_SPLIT OP_NIP 4 OP_SPLIT OP_DROP OP_BIN2NUM
2287                self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
2288                self.sm.push("");
2289                self.sm.push("");
2290                self.emit_op(StackOp::Push(PushValue::Int(44)));
2291                self.sm.push("");
2292                self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
2293                self.sm.pop();
2294                self.sm.pop();
2295                self.sm.push("");
2296                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2297                self.sm.pop();
2298                self.sm.pop();
2299                self.sm.push("");
2300                self.sm.push("");
2301                self.emit_op(StackOp::Nip);
2302                self.sm.pop();
2303                self.sm.pop();
2304                self.sm.push("");
2305                self.emit_op(StackOp::Push(PushValue::Int(4)));
2306                self.sm.push("");
2307                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2308                self.sm.pop(); // pop position (4)
2309                self.sm.pop(); // pop value being split
2310                self.sm.push("");
2311                self.sm.push("");
2312                self.emit_op(StackOp::Drop);
2313                self.sm.pop();
2314                self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2315            }
2316            "extractScriptCode" => {
2317                // Variable-length field at offset 104. End-relative tail = 52 bytes.
2318                // <preimage> 104 OP_SPLIT OP_NIP OP_SIZE 52 OP_SUB OP_SPLIT OP_DROP
2319                self.emit_op(StackOp::Push(PushValue::Int(104)));
2320                self.sm.push("");
2321                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2322                self.sm.pop();
2323                self.sm.push("");
2324                self.sm.push("");
2325                self.emit_op(StackOp::Nip);
2326                self.sm.pop();
2327                self.sm.pop();
2328                self.sm.push("");
2329                self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
2330                self.sm.push("");
2331                self.emit_op(StackOp::Push(PushValue::Int(52)));
2332                self.sm.push("");
2333                self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
2334                self.sm.pop();
2335                self.sm.pop();
2336                self.sm.push("");
2337                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2338                self.sm.pop();
2339                self.sm.pop();
2340                self.sm.push("");
2341                self.sm.push("");
2342                self.emit_op(StackOp::Drop);
2343                self.sm.pop();
2344            }
2345            "extractInputIndex" => {
2346                // Input index = vout field of outpoint, at offset 100, 4 bytes.
2347                // <preimage> 100 OP_SPLIT OP_NIP 4 OP_SPLIT OP_DROP OP_BIN2NUM
2348                self.emit_op(StackOp::Push(PushValue::Int(100)));
2349                self.sm.push("");
2350                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2351                self.sm.pop();
2352                self.sm.push("");
2353                self.sm.push("");
2354                self.emit_op(StackOp::Nip);
2355                self.sm.pop();
2356                self.sm.pop();
2357                self.sm.push("");
2358                self.emit_op(StackOp::Push(PushValue::Int(4)));
2359                self.sm.push("");
2360                self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2361                self.sm.pop(); // pop position (4)
2362                self.sm.pop(); // pop value being split
2363                self.sm.push("");
2364                self.sm.push("");
2365                self.emit_op(StackOp::Drop);
2366                self.sm.pop();
2367                self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2368            }
2369            _ => panic!("unknown extractor: {}", func_name),
2370        }
2371
2372        // Rename top of stack to the binding name
2373        self.sm.pop();
2374        self.sm.push(binding_name);
2375        self.track_depth();
2376    }
2377
2378    /// Lower `__array_access(data, index)` — ByteString byte-level indexing.
2379    ///
2380    /// Compiled to:
2381    ///   `<data> <index> OP_SPLIT OP_NIP 1 OP_SPLIT OP_DROP OP_BIN2NUM`
2382    ///
2383    /// Stack trace:
2384    ///   `[..., data, index]`
2385    ///   `OP_SPLIT  → [..., left, right]`       (split at index)
2386    ///   `OP_NIP    → [..., right]`             (discard left)
2387    ///   `push 1    → [..., right, 1]`
2388    ///   `OP_SPLIT  → [..., firstByte, rest]`   (split off first byte)
2389    ///   `OP_DROP   → [..., firstByte]`         (discard rest)
2390    ///   `OP_BIN2NUM → [..., numericValue]`     (convert byte to bigint)
2391    fn lower_array_access(
2392        &mut self,
2393        binding_name: &str,
2394        args: &[String],
2395        binding_index: usize,
2396        last_uses: &HashMap<String, usize>,
2397    ) {
2398        assert!(args.len() >= 2, "__array_access requires 2 arguments (object, index)");
2399
2400        let obj = &args[0];
2401        let index = &args[1];
2402
2403        // Push the data (ByteString) onto the stack
2404        let obj_is_last = self.is_last_use(obj, binding_index, last_uses);
2405        self.bring_to_top(obj, obj_is_last);
2406
2407        // Push the index onto the stack
2408        let index_is_last = self.is_last_use(index, binding_index, last_uses);
2409        self.bring_to_top(index, index_is_last);
2410
2411        // OP_SPLIT at index: stack = [..., left, right]
2412        self.sm.pop();  // index consumed
2413        self.sm.pop();  // data consumed
2414        self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2415        self.sm.push("");  // left part (discard)
2416        self.sm.push("");  // right part (keep)
2417
2418        // OP_NIP: discard left, keep right: stack = [..., right]
2419        self.emit_op(StackOp::Nip);
2420        self.sm.pop();
2421        let right_part = self.sm.pop();
2422        self.sm.push(&right_part);
2423
2424        // Push 1 for the next split (extract 1 byte)
2425        self.emit_op(StackOp::Push(PushValue::Int(1)));
2426        self.sm.push("");
2427
2428        // OP_SPLIT: split off first byte: stack = [..., firstByte, rest]
2429        self.sm.pop();  // 1 consumed
2430        self.sm.pop();  // right consumed
2431        self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2432        self.sm.push("");  // first byte (keep)
2433        self.sm.push("");  // rest (discard)
2434
2435        // OP_DROP: discard rest: stack = [..., firstByte]
2436        self.emit_op(StackOp::Drop);
2437        self.sm.pop();
2438        self.sm.pop();
2439        self.sm.push("");
2440
2441        // OP_BIN2NUM: convert single byte to numeric value
2442        self.sm.pop();
2443        self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2444
2445        self.sm.push(binding_name);
2446        self.track_depth();
2447    }
2448
2449    fn lower_reverse_bytes(
2450        &mut self,
2451        binding_name: &str,
2452        args: &[String],
2453        binding_index: usize,
2454        last_uses: &HashMap<String, usize>,
2455    ) {
2456        assert!(!args.is_empty(), "reverseBytes requires 1 argument");
2457        let is_last = self.is_last_use(&args[0], binding_index, last_uses);
2458        self.bring_to_top(&args[0], is_last);
2459
2460        // Variable-length byte reversal using bounded unrolled loop.
2461        // Each iteration peels off the first byte and prepends it to the result.
2462        // 520 iterations covers the maximum BSV element size.
2463        self.sm.pop();
2464
2465        // Push empty result (OP_0), swap so data is on top
2466        self.emit_op(StackOp::Push(PushValue::Int(0)));
2467        self.emit_op(StackOp::Swap);
2468
2469        // 520 iterations (max BSV element size)
2470        for _ in 0..520 {
2471            // Stack: [result, data]
2472            self.emit_op(StackOp::Opcode("OP_DUP".to_string()));
2473            self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
2474            self.emit_op(StackOp::Nip);
2475            self.emit_op(StackOp::If {
2476                then_ops: vec![
2477                    StackOp::Push(PushValue::Int(1)),
2478                    StackOp::Opcode("OP_SPLIT".to_string()),
2479                    StackOp::Swap,
2480                    StackOp::Rot,
2481                    StackOp::Opcode("OP_CAT".to_string()),
2482                    StackOp::Swap,
2483                ],
2484                else_ops: vec![],
2485            });
2486        }
2487
2488        // Drop empty remainder
2489        self.emit_op(StackOp::Drop);
2490
2491        self.sm.push(binding_name);
2492        self.track_depth();
2493    }
2494
2495    fn lower_substr(
2496        &mut self,
2497        binding_name: &str,
2498        args: &[String],
2499        binding_index: usize,
2500        last_uses: &HashMap<String, usize>,
2501    ) {
2502        assert!(args.len() >= 3, "substr requires 3 arguments");
2503
2504        let data = &args[0];
2505        let start = &args[1];
2506        let length = &args[2];
2507
2508        let data_is_last = self.is_last_use(data, binding_index, last_uses);
2509        self.bring_to_top(data, data_is_last);
2510
2511        let start_is_last = self.is_last_use(start, binding_index, last_uses);
2512        self.bring_to_top(start, start_is_last);
2513
2514        self.sm.pop();
2515        self.sm.pop();
2516        self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2517        self.sm.push("");
2518        self.sm.push("");
2519
2520        self.emit_op(StackOp::Nip);
2521        self.sm.pop();
2522        let right_part = self.sm.pop();
2523        self.sm.push(&right_part);
2524
2525        let len_is_last = self.is_last_use(length, binding_index, last_uses);
2526        self.bring_to_top(length, len_is_last);
2527
2528        self.sm.pop();
2529        self.sm.pop();
2530        self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2531        self.sm.push("");
2532        self.sm.push("");
2533
2534        self.emit_op(StackOp::Drop);
2535        self.sm.pop();
2536        self.sm.pop();
2537
2538        self.sm.push(binding_name);
2539        self.track_depth();
2540    }
2541    fn lower_verify_rabin_sig(
2542        &mut self,
2543        binding_name: &str,
2544        args: &[String],
2545        binding_index: usize,
2546        last_uses: &HashMap<String, usize>,
2547    ) {
2548        assert!(args.len() >= 4, "verifyRabinSig requires 4 arguments");
2549
2550        // Stack input: <msg> <sig> <padding> <pubKey>
2551        // Computation: (sig^2 + padding) mod pubKey == SHA256(msg)
2552        // Opcode sequence: OP_SWAP OP_ROT OP_DUP OP_MUL OP_ADD OP_SWAP OP_MOD OP_SWAP OP_SHA256 OP_EQUAL
2553        let msg = &args[0];
2554        let sig = &args[1];
2555        let padding = &args[2];
2556        let pub_key = &args[3];
2557
2558        let msg_is_last = self.is_last_use(msg, binding_index, last_uses);
2559        self.bring_to_top(msg, msg_is_last);
2560
2561        let sig_is_last = self.is_last_use(sig, binding_index, last_uses);
2562        self.bring_to_top(sig, sig_is_last);
2563
2564        let padding_is_last = self.is_last_use(padding, binding_index, last_uses);
2565        self.bring_to_top(padding, padding_is_last);
2566
2567        let pub_key_is_last = self.is_last_use(pub_key, binding_index, last_uses);
2568        self.bring_to_top(pub_key, pub_key_is_last);
2569
2570        // Pop all 4 args from stack map
2571        self.sm.pop();
2572        self.sm.pop();
2573        self.sm.pop();
2574        self.sm.pop();
2575
2576        // Emit the Rabin signature verification opcode sequence
2577        // Stack: msg(3) sig(2) padding(1) pubKey(0)
2578        self.emit_op(StackOp::Opcode("OP_SWAP".to_string()));  // msg sig pubKey padding
2579        self.emit_op(StackOp::Opcode("OP_ROT".to_string()));   // msg pubKey padding sig
2580        self.emit_op(StackOp::Opcode("OP_DUP".to_string()));
2581        self.emit_op(StackOp::Opcode("OP_MUL".to_string()));   // msg pubKey padding sig^2
2582        self.emit_op(StackOp::Opcode("OP_ADD".to_string()));   // msg pubKey (sig^2+padding)
2583        self.emit_op(StackOp::Opcode("OP_SWAP".to_string()));  // msg (sig^2+padding) pubKey
2584        self.emit_op(StackOp::Opcode("OP_MOD".to_string()));   // msg ((sig^2+padding) mod pubKey)
2585        self.emit_op(StackOp::Opcode("OP_SWAP".to_string()));  // ((sig^2+padding) mod pubKey) msg
2586        self.emit_op(StackOp::Opcode("OP_SHA256".to_string()));
2587        self.emit_op(StackOp::Opcode("OP_EQUAL".to_string()));
2588
2589        self.sm.push(binding_name);
2590        self.track_depth();
2591    }
2592
2593    /// Lower sign(x) to Script that avoids division by zero for x == 0.
2594    /// OP_DUP OP_IF OP_DUP OP_ABS OP_SWAP OP_DIV OP_ENDIF
2595    fn lower_sign(
2596        &mut self,
2597        binding_name: &str,
2598        args: &[String],
2599        binding_index: usize,
2600        last_uses: &HashMap<String, usize>,
2601    ) {
2602        assert!(!args.is_empty(), "sign requires 1 argument");
2603        let x = &args[0];
2604
2605        let x_is_last = self.is_last_use(x, binding_index, last_uses);
2606        self.bring_to_top(x, x_is_last);
2607        self.sm.pop();
2608
2609        self.emit_op(StackOp::Opcode("OP_DUP".to_string()));
2610        self.emit_op(StackOp::If {
2611            then_ops: vec![
2612                StackOp::Opcode("OP_DUP".to_string()),
2613                StackOp::Opcode("OP_ABS".to_string()),
2614                StackOp::Swap,
2615                StackOp::Opcode("OP_DIV".to_string()),
2616            ],
2617            else_ops: vec![],
2618        });
2619
2620        self.sm.push(binding_name);
2621        self.track_depth();
2622    }
2623
2624    /// Lower right(data, len) to Script.
2625    /// OP_SWAP OP_SIZE OP_ROT OP_SUB OP_SPLIT OP_NIP
2626    fn lower_right(
2627        &mut self,
2628        binding_name: &str,
2629        args: &[String],
2630        binding_index: usize,
2631        last_uses: &HashMap<String, usize>,
2632    ) {
2633        assert!(args.len() >= 2, "right requires 2 arguments");
2634        let data = &args[0];
2635        let length = &args[1];
2636
2637        let data_is_last = self.is_last_use(data, binding_index, last_uses);
2638        self.bring_to_top(data, data_is_last);
2639
2640        let length_is_last = self.is_last_use(length, binding_index, last_uses);
2641        self.bring_to_top(length, length_is_last);
2642
2643        self.sm.pop(); // len
2644        self.sm.pop(); // data
2645
2646        self.emit_op(StackOp::Swap);                                     // <len> <data>
2647        self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));            // <len> <data> <size>
2648        self.emit_op(StackOp::Rot);                                      // <data> <size> <len>
2649        self.emit_op(StackOp::Opcode("OP_SUB".to_string()));             // <data> <size-len>
2650        self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));           // <left> <right>
2651        self.emit_op(StackOp::Nip);                                      // <right>
2652
2653        self.sm.push(binding_name);
2654        self.track_depth();
2655    }
2656
2657    /// Emit one WOTS+ chain with RFC 8391 tweakable hash.
2658    /// Stack entry: pubSeed(bottom) sig csum endpt digit(top)
2659    /// Stack exit:  pubSeed(bottom) sigRest newCsum newEndpt
2660    fn emit_wots_one_chain(&mut self, chain_index: usize) {
2661        // Save steps_copy = 15 - digit to alt (for checksum accumulation later)
2662        self.emit_op(StackOp::Opcode("OP_DUP".into()));
2663        self.emit_op(StackOp::Push(PushValue::Int(15)));
2664        self.emit_op(StackOp::Swap);
2665        self.emit_op(StackOp::Opcode("OP_SUB".into()));
2666        self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into())); // push#1: steps_copy
2667
2668        // Save endpt, csum to alt. Leave pubSeed+sig+digit on main.
2669        self.emit_op(StackOp::Swap);
2670        self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into())); // push#2: endpt
2671        self.emit_op(StackOp::Swap);
2672        self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into())); // push#3: csum
2673        // main: pubSeed sig digit
2674
2675        // Split 32B sig element
2676        self.emit_op(StackOp::Swap);
2677        self.emit_op(StackOp::Push(PushValue::Int(32)));
2678        self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
2679        self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into())); // push#4: sigRest
2680        self.emit_op(StackOp::Swap);
2681        // main: pubSeed sigElem digit
2682
2683        // Hash loop: skip first `digit` iterations, then apply F for the rest.
2684        // When digit > 0: decrement (skip). When digit == 0: hash at step j.
2685        // Stack: pubSeed(depth2) sigElem(depth1) digit(depth0=top)
2686        for j in 0..15usize {
2687            let adrs_bytes = vec![chain_index as u8, j as u8];
2688            self.emit_op(StackOp::Opcode("OP_DUP".into()));
2689            self.emit_op(StackOp::Opcode("OP_0NOTEQUAL".into()));
2690            self.emit_op(StackOp::If {
2691                then_ops: vec![
2692                    StackOp::Opcode("OP_1SUB".into()),            // skip: digit--
2693                ],
2694                else_ops: vec![
2695                    StackOp::Swap,                                  // pubSeed digit X
2696                    StackOp::Push(PushValue::Int(2)),
2697                    StackOp::Opcode("OP_PICK".into()),            // copy pubSeed
2698                    StackOp::Push(PushValue::Bytes(adrs_bytes)),   // ADRS [chainIndex, j]
2699                    StackOp::Opcode("OP_CAT".into()),              // pubSeed || adrs
2700                    StackOp::Swap,                                  // bring X to top
2701                    StackOp::Opcode("OP_CAT".into()),              // pubSeed || adrs || X
2702                    StackOp::Opcode("OP_SHA256".into()),           // F result
2703                    StackOp::Swap,                                  // pubSeed new_X digit(=0)
2704                ],
2705            });
2706        }
2707        self.emit_op(StackOp::Drop); // drop digit (now 0)
2708        // main: pubSeed endpoint
2709
2710        // Restore from alt (LIFO): sigRest, csum, endpt_acc, steps_copy
2711        self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
2712        self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
2713        self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
2714        self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
2715
2716        // csum += steps_copy
2717        self.emit_op(StackOp::Rot);
2718        self.emit_op(StackOp::Opcode("OP_ADD".into()));
2719
2720        // Concat endpoint to endpt_acc
2721        self.emit_op(StackOp::Swap);
2722        self.emit_op(StackOp::Push(PushValue::Int(3)));
2723        self.emit_op(StackOp::Opcode("OP_ROLL".into()));
2724        self.emit_op(StackOp::Opcode("OP_CAT".into()));
2725    }
2726
2727    /// WOTS+ signature verification with RFC 8391 tweakable hash (post-quantum).
2728    /// Parameters: w=16, n=32 (SHA-256), len=67 chains.
2729    /// pubkey is 64 bytes: pubSeed(32) || pkRoot(32).
2730    fn lower_verify_wots(
2731        &mut self,
2732        binding_name: &str,
2733        args: &[String],
2734        binding_index: usize,
2735        last_uses: &HashMap<String, usize>,
2736    ) {
2737        assert!(args.len() >= 3, "verifyWOTS requires 3 arguments: msg, sig, pubkey");
2738
2739        for arg in args.iter() {
2740            let is_last = self.is_last_use(arg, binding_index, last_uses);
2741            self.bring_to_top(arg, is_last);
2742        }
2743        for _ in 0..3 { self.sm.pop(); }
2744        // main: msg sig pubkey(64B: pubSeed||pkRoot)
2745
2746        // Split 64-byte pubkey into pubSeed(32) and pkRoot(32)
2747        self.emit_op(StackOp::Push(PushValue::Int(32)));
2748        self.emit_op(StackOp::Opcode("OP_SPLIT".into()));          // msg sig pubSeed pkRoot
2749        self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));    // pkRoot → alt
2750
2751        // Rearrange: put pubSeed at bottom, hash msg
2752        self.emit_op(StackOp::Rot);                                 // sig pubSeed msg
2753        self.emit_op(StackOp::Rot);                                 // pubSeed msg sig
2754        self.emit_op(StackOp::Swap);                                // pubSeed sig msg
2755        self.emit_op(StackOp::Opcode("OP_SHA256".into()));         // pubSeed sig msgHash
2756
2757        // Canonical layout: pubSeed(bottom) sig csum=0 endptAcc=empty hashRem(top)
2758        self.emit_op(StackOp::Swap);
2759        self.emit_op(StackOp::Push(PushValue::Int(0)));
2760        self.emit_op(StackOp::Opcode("OP_0".into()));
2761        self.emit_op(StackOp::Push(PushValue::Int(3)));
2762        self.emit_op(StackOp::Opcode("OP_ROLL".into()));
2763
2764        // Process 32 bytes → 64 message chains
2765        for byte_idx in 0..32 {
2766            if byte_idx < 31 {
2767                self.emit_op(StackOp::Push(PushValue::Int(1)));
2768                self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
2769                self.emit_op(StackOp::Swap);
2770            }
2771            // Unsigned byte conversion
2772            self.emit_op(StackOp::Push(PushValue::Int(0)));
2773            self.emit_op(StackOp::Push(PushValue::Int(1)));
2774            self.emit_op(StackOp::Opcode("OP_NUM2BIN".into()));
2775            self.emit_op(StackOp::Opcode("OP_CAT".into()));
2776            self.emit_op(StackOp::Opcode("OP_BIN2NUM".into()));
2777            // Extract nibbles
2778            self.emit_op(StackOp::Opcode("OP_DUP".into()));
2779            self.emit_op(StackOp::Push(PushValue::Int(16)));
2780            self.emit_op(StackOp::Opcode("OP_DIV".into()));
2781            self.emit_op(StackOp::Swap);
2782            self.emit_op(StackOp::Push(PushValue::Int(16)));
2783            self.emit_op(StackOp::Opcode("OP_MOD".into()));
2784
2785            if byte_idx < 31 {
2786                self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
2787                self.emit_op(StackOp::Swap);
2788                self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
2789            } else {
2790                self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
2791            }
2792
2793            self.emit_wots_one_chain(byte_idx * 2); // high nibble chain
2794
2795            if byte_idx < 31 {
2796                self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
2797                self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
2798                self.emit_op(StackOp::Swap);
2799                self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
2800            } else {
2801                self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
2802            }
2803
2804            self.emit_wots_one_chain(byte_idx * 2 + 1); // low nibble chain
2805
2806            if byte_idx < 31 {
2807                self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
2808            }
2809        }
2810
2811        // Checksum digits
2812        self.emit_op(StackOp::Swap);
2813        // d66
2814        self.emit_op(StackOp::Opcode("OP_DUP".into()));
2815        self.emit_op(StackOp::Push(PushValue::Int(16)));
2816        self.emit_op(StackOp::Opcode("OP_MOD".into()));
2817        self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
2818        // d65
2819        self.emit_op(StackOp::Opcode("OP_DUP".into()));
2820        self.emit_op(StackOp::Push(PushValue::Int(16)));
2821        self.emit_op(StackOp::Opcode("OP_DIV".into()));
2822        self.emit_op(StackOp::Push(PushValue::Int(16)));
2823        self.emit_op(StackOp::Opcode("OP_MOD".into()));
2824        self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
2825        // d64
2826        self.emit_op(StackOp::Push(PushValue::Int(256)));
2827        self.emit_op(StackOp::Opcode("OP_DIV".into()));
2828        self.emit_op(StackOp::Push(PushValue::Int(16)));
2829        self.emit_op(StackOp::Opcode("OP_MOD".into()));
2830        self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
2831
2832        // 3 checksum chains (indices 64, 65, 66)
2833        for ci in 0..3 {
2834            self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
2835            self.emit_op(StackOp::Push(PushValue::Int(0)));
2836            self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
2837            self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
2838            self.emit_wots_one_chain(64 + ci);
2839            self.emit_op(StackOp::Swap);
2840            self.emit_op(StackOp::Drop);
2841        }
2842
2843        // Final comparison
2844        self.emit_op(StackOp::Swap);
2845        self.emit_op(StackOp::Drop);
2846        // main: pubSeed endptAcc
2847        self.emit_op(StackOp::Opcode("OP_SHA256".into()));
2848        self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into())); // pkRoot
2849        self.emit_op(StackOp::Opcode("OP_EQUAL".into()));
2850        // Clean up pubSeed
2851        self.emit_op(StackOp::Swap);
2852        self.emit_op(StackOp::Drop);
2853
2854        self.sm.push(binding_name);
2855        self.track_depth();
2856    }
2857
2858    /// SLH-DSA (FIPS 205) signature verification.
2859    /// Brings all 3 args to the top, pops them, delegates to slh_dsa::emit_verify_slh_dsa,
2860    /// and pushes the boolean result.
2861    fn lower_verify_slh_dsa(
2862        &mut self,
2863        binding_name: &str,
2864        param_key: &str,
2865        args: &[String],
2866        binding_index: usize,
2867        last_uses: &HashMap<String, usize>,
2868    ) {
2869        assert!(
2870            args.len() >= 3,
2871            "verifySLHDSA requires 3 arguments: msg, sig, pubkey"
2872        );
2873
2874        // Bring args to top in order: msg, sig, pubkey
2875        for arg in args.iter() {
2876            let is_last = self.is_last_use(arg, binding_index, last_uses);
2877            self.bring_to_top(arg, is_last);
2878        }
2879        for _ in 0..3 {
2880            self.sm.pop();
2881        }
2882
2883        // Delegate to slh_dsa module
2884        super::slh_dsa::emit_verify_slh_dsa(&mut |op| self.ops.push(op), param_key);
2885
2886        self.sm.push(binding_name);
2887        self.track_depth();
2888    }
2889
2890    fn lower_ec_builtin(
2891        &mut self,
2892        binding_name: &str,
2893        func_name: &str,
2894        args: &[String],
2895        binding_index: usize,
2896        last_uses: &HashMap<String, usize>,
2897    ) {
2898        // Bring args to top in order
2899        for arg in args.iter() {
2900            let is_last = self.is_last_use(arg, binding_index, last_uses);
2901            self.bring_to_top(arg, is_last);
2902        }
2903        for _ in args {
2904            self.sm.pop();
2905        }
2906
2907        let emit = &mut |op: StackOp| self.ops.push(op);
2908
2909        match func_name {
2910            "ecAdd" => super::ec::emit_ec_add(emit),
2911            "ecMul" => super::ec::emit_ec_mul(emit),
2912            "ecMulGen" => super::ec::emit_ec_mul_gen(emit),
2913            "ecNegate" => super::ec::emit_ec_negate(emit),
2914            "ecOnCurve" => super::ec::emit_ec_on_curve(emit),
2915            "ecModReduce" => super::ec::emit_ec_mod_reduce(emit),
2916            "ecEncodeCompressed" => super::ec::emit_ec_encode_compressed(emit),
2917            "ecMakePoint" => super::ec::emit_ec_make_point(emit),
2918            "ecPointX" => super::ec::emit_ec_point_x(emit),
2919            "ecPointY" => super::ec::emit_ec_point_y(emit),
2920            _ => panic!("unknown EC builtin: {}", func_name),
2921        }
2922
2923        self.sm.push(binding_name);
2924        self.track_depth();
2925    }
2926
2927    /// safediv(a, b): a / b with division-by-zero check.
2928    /// Stack: a b -> OP_DUP OP_0NOTEQUAL OP_VERIFY OP_DIV -> result
2929    fn lower_safediv(
2930        &mut self,
2931        binding_name: &str,
2932        args: &[String],
2933        binding_index: usize,
2934        last_uses: &HashMap<String, usize>,
2935    ) {
2936        assert!(args.len() >= 2, "safediv requires 2 arguments");
2937
2938        let a_is_last = self.is_last_use(&args[0], binding_index, last_uses);
2939        self.bring_to_top(&args[0], a_is_last);
2940
2941        let b_is_last = self.is_last_use(&args[1], binding_index, last_uses);
2942        self.bring_to_top(&args[1], b_is_last);
2943
2944        self.sm.pop();
2945        self.sm.pop();
2946
2947        self.emit_op(StackOp::Opcode("OP_DUP".to_string()));
2948        self.emit_op(StackOp::Opcode("OP_0NOTEQUAL".to_string()));
2949        self.emit_op(StackOp::Opcode("OP_VERIFY".to_string()));
2950        self.emit_op(StackOp::Opcode("OP_DIV".to_string()));
2951
2952        self.sm.push(binding_name);
2953        self.track_depth();
2954    }
2955
2956    /// safemod(a, b): a % b with division-by-zero check.
2957    /// Stack: a b -> OP_DUP OP_0NOTEQUAL OP_VERIFY OP_MOD -> result
2958    fn lower_safemod(
2959        &mut self,
2960        binding_name: &str,
2961        args: &[String],
2962        binding_index: usize,
2963        last_uses: &HashMap<String, usize>,
2964    ) {
2965        assert!(args.len() >= 2, "safemod requires 2 arguments");
2966
2967        let a_is_last = self.is_last_use(&args[0], binding_index, last_uses);
2968        self.bring_to_top(&args[0], a_is_last);
2969
2970        let b_is_last = self.is_last_use(&args[1], binding_index, last_uses);
2971        self.bring_to_top(&args[1], b_is_last);
2972
2973        self.sm.pop();
2974        self.sm.pop();
2975
2976        self.emit_op(StackOp::Opcode("OP_DUP".to_string()));
2977        self.emit_op(StackOp::Opcode("OP_0NOTEQUAL".to_string()));
2978        self.emit_op(StackOp::Opcode("OP_VERIFY".to_string()));
2979        self.emit_op(StackOp::Opcode("OP_MOD".to_string()));
2980
2981        self.sm.push(binding_name);
2982        self.track_depth();
2983    }
2984
2985    /// clamp(val, lo, hi): clamp val to [lo, hi].
2986    /// Stack: val lo hi -> val lo OP_MAX hi OP_MIN -> result
2987    fn lower_clamp(
2988        &mut self,
2989        binding_name: &str,
2990        args: &[String],
2991        binding_index: usize,
2992        last_uses: &HashMap<String, usize>,
2993    ) {
2994        assert!(args.len() >= 3, "clamp requires 3 arguments");
2995
2996        let val_is_last = self.is_last_use(&args[0], binding_index, last_uses);
2997        self.bring_to_top(&args[0], val_is_last);
2998
2999        let lo_is_last = self.is_last_use(&args[1], binding_index, last_uses);
3000        self.bring_to_top(&args[1], lo_is_last);
3001
3002        self.sm.pop();
3003        self.sm.pop();
3004        self.emit_op(StackOp::Opcode("OP_MAX".to_string()));
3005        self.sm.push(""); // intermediate result
3006
3007        let hi_is_last = self.is_last_use(&args[2], binding_index, last_uses);
3008        self.bring_to_top(&args[2], hi_is_last);
3009
3010        self.sm.pop();
3011        self.sm.pop();
3012        self.emit_op(StackOp::Opcode("OP_MIN".to_string()));
3013
3014        self.sm.push(binding_name);
3015        self.track_depth();
3016    }
3017
3018    /// pow(base, exp): base^exp via 32-iteration bounded conditional multiply.
3019    /// Strategy: swap to get exp base, push 1 (acc), then 32 rounds of:
3020    ///   2 OP_PICK (get exp), push(i+1), OP_GREATERTHAN, OP_IF, OP_OVER, OP_MUL, OP_ENDIF
3021    /// After iterations: OP_NIP OP_NIP to get result.
3022    fn lower_pow(
3023        &mut self,
3024        binding_name: &str,
3025        args: &[String],
3026        binding_index: usize,
3027        last_uses: &HashMap<String, usize>,
3028    ) {
3029        assert!(args.len() >= 2, "pow requires 2 arguments");
3030
3031        let base_is_last = self.is_last_use(&args[0], binding_index, last_uses);
3032        self.bring_to_top(&args[0], base_is_last);
3033
3034        let exp_is_last = self.is_last_use(&args[1], binding_index, last_uses);
3035        self.bring_to_top(&args[1], exp_is_last);
3036
3037        self.sm.pop();
3038        self.sm.pop();
3039
3040        // Stack: base exp
3041        self.emit_op(StackOp::Swap);                                  // exp base
3042        self.emit_op(StackOp::Push(PushValue::Int(1)));               // exp base 1(acc)
3043
3044        for i in 0..32 {
3045            // Stack: exp base acc
3046            self.emit_op(StackOp::Push(PushValue::Int(2)));
3047            self.emit_op(StackOp::Opcode("OP_PICK".to_string()));     // exp base acc exp
3048            self.emit_op(StackOp::Push(PushValue::Int(i)));
3049            self.emit_op(StackOp::Opcode("OP_GREATERTHAN".to_string())); // exp base acc (exp > i)
3050            self.emit_op(StackOp::If {
3051                then_ops: vec![
3052                    StackOp::Over,                                    // exp base acc base
3053                    StackOp::Opcode("OP_MUL".to_string()),           // exp base (acc*base)
3054                ],
3055                else_ops: vec![],
3056            });
3057        }
3058        // Stack: exp base result
3059        self.emit_op(StackOp::Nip);                                   // exp result
3060        self.emit_op(StackOp::Nip);                                   // result
3061
3062        self.sm.push(binding_name);
3063        self.track_depth();
3064    }
3065
3066    /// mulDiv(a, b, c): (a * b) / c
3067    /// Stack: a b c -> a b OP_MUL c OP_DIV -> result
3068    fn lower_mul_div(
3069        &mut self,
3070        binding_name: &str,
3071        args: &[String],
3072        binding_index: usize,
3073        last_uses: &HashMap<String, usize>,
3074    ) {
3075        assert!(args.len() >= 3, "mulDiv requires 3 arguments");
3076
3077        let a_is_last = self.is_last_use(&args[0], binding_index, last_uses);
3078        self.bring_to_top(&args[0], a_is_last);
3079
3080        let b_is_last = self.is_last_use(&args[1], binding_index, last_uses);
3081        self.bring_to_top(&args[1], b_is_last);
3082
3083        self.sm.pop();
3084        self.sm.pop();
3085        self.emit_op(StackOp::Opcode("OP_MUL".to_string()));
3086        self.sm.push(""); // a*b
3087
3088        let c_is_last = self.is_last_use(&args[2], binding_index, last_uses);
3089        self.bring_to_top(&args[2], c_is_last);
3090
3091        self.sm.pop();
3092        self.sm.pop();
3093        self.emit_op(StackOp::Opcode("OP_DIV".to_string()));
3094
3095        self.sm.push(binding_name);
3096        self.track_depth();
3097    }
3098
3099    /// percentOf(amount, bps): (amount * bps) / 10000
3100    /// Stack: amount bps -> OP_MUL 10000 OP_DIV -> result
3101    fn lower_percent_of(
3102        &mut self,
3103        binding_name: &str,
3104        args: &[String],
3105        binding_index: usize,
3106        last_uses: &HashMap<String, usize>,
3107    ) {
3108        assert!(args.len() >= 2, "percentOf requires 2 arguments");
3109
3110        let amount_is_last = self.is_last_use(&args[0], binding_index, last_uses);
3111        self.bring_to_top(&args[0], amount_is_last);
3112
3113        let bps_is_last = self.is_last_use(&args[1], binding_index, last_uses);
3114        self.bring_to_top(&args[1], bps_is_last);
3115
3116        self.sm.pop();
3117        self.sm.pop();
3118
3119        self.emit_op(StackOp::Opcode("OP_MUL".to_string()));
3120        self.emit_op(StackOp::Push(PushValue::Int(10000)));
3121        self.emit_op(StackOp::Opcode("OP_DIV".to_string()));
3122
3123        self.sm.push(binding_name);
3124        self.track_depth();
3125    }
3126
3127    /// sqrt(n): integer square root via Newton's method, 16 iterations.
3128    /// Uses: guess = n, then 16x: guess = (guess + n/guess) / 2
3129    /// Guards against n == 0 to avoid division by zero.
3130    fn lower_sqrt(
3131        &mut self,
3132        binding_name: &str,
3133        args: &[String],
3134        binding_index: usize,
3135        last_uses: &HashMap<String, usize>,
3136    ) {
3137        assert!(!args.is_empty(), "sqrt requires 1 argument");
3138
3139        let n_is_last = self.is_last_use(&args[0], binding_index, last_uses);
3140        self.bring_to_top(&args[0], n_is_last);
3141
3142        self.sm.pop();
3143
3144        // Stack: n
3145        // Guard: if n == 0, skip Newton iteration entirely (result is 0).
3146        self.emit_op(StackOp::Opcode("OP_DUP".to_string()));
3147        // Stack: n n
3148
3149        // Build the Newton iteration ops inside the OP_IF branch
3150        let mut newton_ops = Vec::new();
3151        // Stack inside IF: n  (the DUP'd copy was consumed by OP_IF)
3152        // DUP to get initial guess = n
3153        newton_ops.push(StackOp::Opcode("OP_DUP".to_string()));
3154        // Stack: n guess
3155
3156        // 16 iterations of Newton's method: guess = (guess + n/guess) / 2
3157        for _ in 0..16 {
3158            // Stack: n guess
3159            newton_ops.push(StackOp::Over);                               // n guess n
3160            newton_ops.push(StackOp::Over);                               // n guess n guess
3161            newton_ops.push(StackOp::Opcode("OP_DIV".to_string()));      // n guess (n/guess)
3162            newton_ops.push(StackOp::Opcode("OP_ADD".to_string()));      // n (guess + n/guess)
3163            newton_ops.push(StackOp::Push(PushValue::Int(2)));            // n (guess + n/guess) 2
3164            newton_ops.push(StackOp::Opcode("OP_DIV".to_string()));      // n new_guess
3165        }
3166
3167        // Stack: n guess
3168        // Drop n, keep guess
3169        newton_ops.push(StackOp::Opcode("OP_NIP".to_string()));
3170
3171        self.emit_op(StackOp::If {
3172            then_ops: newton_ops,
3173            else_ops: vec![],  // n == 0, result is already 0 on stack
3174        });
3175
3176        self.sm.push(binding_name);
3177        self.track_depth();
3178    }
3179
3180    /// gcd(a, b): Euclidean algorithm, 256 iterations with conditional OP_IF.
3181    /// Each iteration: if b != 0 then (b, a % b) else (a, 0)
3182    fn lower_gcd(
3183        &mut self,
3184        binding_name: &str,
3185        args: &[String],
3186        binding_index: usize,
3187        last_uses: &HashMap<String, usize>,
3188    ) {
3189        assert!(args.len() >= 2, "gcd requires 2 arguments");
3190
3191        let a_is_last = self.is_last_use(&args[0], binding_index, last_uses);
3192        self.bring_to_top(&args[0], a_is_last);
3193
3194        let b_is_last = self.is_last_use(&args[1], binding_index, last_uses);
3195        self.bring_to_top(&args[1], b_is_last);
3196
3197        self.sm.pop();
3198        self.sm.pop();
3199
3200        // Stack: a b
3201        // Both should be absolute values
3202        self.emit_op(StackOp::Opcode("OP_ABS".to_string()));
3203        self.emit_op(StackOp::Swap);
3204        self.emit_op(StackOp::Opcode("OP_ABS".to_string()));
3205        self.emit_op(StackOp::Swap);
3206        // Stack: |a| |b|
3207
3208        // 256 iterations of Euclidean algorithm
3209        for _ in 0..256 {
3210            // Stack: a b
3211            // Check if b != 0
3212            self.emit_op(StackOp::Opcode("OP_DUP".to_string()));      // a b b
3213            self.emit_op(StackOp::Opcode("OP_0NOTEQUAL".to_string())); // a b (b!=0)
3214
3215            self.emit_op(StackOp::If {
3216                then_ops: vec![
3217                    // Stack: a b (b != 0)
3218                    // Compute a % b, then swap: new a = b, new b = a%b
3219                    StackOp::Opcode("OP_TUCK".to_string()),            // b a b
3220                    StackOp::Opcode("OP_MOD".to_string()),             // b (a%b)
3221                ],
3222                else_ops: vec![
3223                    // Stack: a b (b == 0), just keep as-is
3224                ],
3225            });
3226        }
3227
3228        // Stack: a b (where b should be 0)
3229        // Drop b, keep a (the GCD)
3230        self.emit_op(StackOp::Drop);
3231
3232        self.sm.push(binding_name);
3233        self.track_depth();
3234    }
3235
3236    /// divmod(a, b): computes both a/b and a%b, returns a/b (drops a%b).
3237    /// Stack: a b -> OP_2DUP OP_DIV OP_ROT OP_ROT OP_MOD OP_DROP -> quotient
3238    fn lower_divmod(
3239        &mut self,
3240        binding_name: &str,
3241        args: &[String],
3242        binding_index: usize,
3243        last_uses: &HashMap<String, usize>,
3244    ) {
3245        assert!(args.len() >= 2, "divmod requires 2 arguments");
3246
3247        let a_is_last = self.is_last_use(&args[0], binding_index, last_uses);
3248        self.bring_to_top(&args[0], a_is_last);
3249
3250        let b_is_last = self.is_last_use(&args[1], binding_index, last_uses);
3251        self.bring_to_top(&args[1], b_is_last);
3252
3253        self.sm.pop();
3254        self.sm.pop();
3255
3256        // Stack: a b
3257        self.emit_op(StackOp::Opcode("OP_2DUP".to_string()));         // a b a b
3258        self.emit_op(StackOp::Opcode("OP_DIV".to_string()));          // a b (a/b)
3259        self.emit_op(StackOp::Opcode("OP_ROT".to_string()));          // a (a/b) b
3260        self.emit_op(StackOp::Opcode("OP_ROT".to_string()));          // (a/b) b a
3261        self.emit_op(StackOp::Opcode("OP_MOD".to_string()));          // (a/b) (a%b) -- wait
3262        // ROT ROT on a b (a/b): ROT -> b (a/b) a, ROT -> (a/b) a b
3263        // Then MOD -> (a/b) (a%b)
3264        // DROP -> (a/b)
3265        self.emit_op(StackOp::Drop);
3266
3267        self.sm.push(binding_name);
3268        self.track_depth();
3269    }
3270
3271    /// log2(n): exact floor(log2(n)) via bit-scanning.
3272    ///
3273    /// Uses a bounded unrolled loop (64 iterations for bigint range):
3274    ///   counter = 0
3275    ///   while input > 1: input >>= 1, counter++
3276    ///   result = counter
3277    ///
3278    /// Stack layout during loop: <input> <counter>
3279    /// Each iteration: OP_SWAP OP_DUP 1 OP_GREATERTHAN OP_IF 2 OP_DIV OP_SWAP OP_1ADD OP_SWAP OP_ENDIF OP_SWAP
3280    fn lower_log2(
3281        &mut self,
3282        binding_name: &str,
3283        args: &[String],
3284        binding_index: usize,
3285        last_uses: &HashMap<String, usize>,
3286    ) {
3287        assert!(!args.is_empty(), "log2 requires 1 argument");
3288
3289        let n_is_last = self.is_last_use(&args[0], binding_index, last_uses);
3290        self.bring_to_top(&args[0], n_is_last);
3291
3292        self.sm.pop();
3293
3294        // Stack: <n>
3295        // Push counter = 0
3296        self.emit_op(StackOp::Push(PushValue::Int(0))); // n 0
3297
3298        // 64 iterations (sufficient for Bitcoin Script bigint range)
3299        const LOG2_ITERATIONS: usize = 64;
3300        for _ in 0..LOG2_ITERATIONS {
3301            // Stack: input counter
3302            self.emit_op(StackOp::Swap);                                     // counter input
3303            self.emit_op(StackOp::Opcode("OP_DUP".to_string()));            // counter input input
3304            self.emit_op(StackOp::Push(PushValue::Int(1)));                  // counter input input 1
3305            self.emit_op(StackOp::Opcode("OP_GREATERTHAN".to_string()));     // counter input (input>1)
3306            self.emit_op(StackOp::If {
3307                then_ops: vec![
3308                    StackOp::Push(PushValue::Int(2)),                        // counter input 2
3309                    StackOp::Opcode("OP_DIV".to_string()),                   // counter (input/2)
3310                    StackOp::Swap,                                           // (input/2) counter
3311                    StackOp::Opcode("OP_1ADD".to_string()),                  // (input/2) (counter+1)
3312                    StackOp::Swap,                                           // (counter+1) (input/2)
3313                ],
3314                else_ops: vec![],
3315            });
3316            // Stack: counter input (or input counter if swapped back)
3317            // After the if: stack is counter input (swap at start, then if-branch swaps back)
3318            self.emit_op(StackOp::Swap);                                     // input counter
3319        }
3320        // Stack: input counter
3321        // Drop input, keep counter
3322        self.emit_op(StackOp::Nip); // counter
3323
3324        self.sm.push(binding_name);
3325        self.track_depth();
3326    }
3327}
3328
3329// ---------------------------------------------------------------------------
3330// Public API
3331// ---------------------------------------------------------------------------
3332
3333/// Lower an ANF program to Stack IR.
3334/// Private methods are inlined at call sites rather than compiled separately.
3335/// The constructor is skipped since it's not emitted to Bitcoin Script.
3336pub fn lower_to_stack(program: &ANFProgram) -> Result<Vec<StackMethod>, String> {
3337    // Build map of private methods for inlining
3338    let mut private_methods: HashMap<String, ANFMethod> = HashMap::new();
3339    for method in &program.methods {
3340        if !method.is_public && method.name != "constructor" {
3341            private_methods.insert(method.name.clone(), method.clone());
3342        }
3343    }
3344
3345    let mut methods = Vec::new();
3346
3347    for method in &program.methods {
3348        // Skip constructor and private methods
3349        if method.name == "constructor" || (!method.is_public && method.name != "constructor") {
3350            continue;
3351        }
3352        let sm = lower_method_with_private_methods(method, &program.properties, &private_methods)?;
3353        methods.push(sm);
3354    }
3355
3356    Ok(methods)
3357}
3358
3359/// Check whether a method's body contains a CheckPreimage binding.
3360/// If found, the unlocking script will push an implicit <sig> parameter before
3361/// all declared parameters (OP_PUSH_TX pattern).
3362fn method_uses_check_preimage(bindings: &[ANFBinding]) -> bool {
3363    bindings.iter().any(|b| matches!(&b.value, ANFValue::CheckPreimage { .. }))
3364}
3365
3366fn lower_method_with_private_methods(
3367    method: &ANFMethod,
3368    properties: &[ANFProperty],
3369    private_methods: &HashMap<String, ANFMethod>,
3370) -> Result<StackMethod, String> {
3371    let mut param_names: Vec<String> = method.params.iter().map(|p| p.name.clone()).collect();
3372
3373    // If the method uses checkPreimage, the unlocking script pushes an
3374    // implicit <sig> before all declared parameters (OP_PUSH_TX pattern).
3375    // Insert _opPushTxSig at the base of the stack so it can be consumed
3376    // by lower_check_preimage later.
3377    if method_uses_check_preimage(&method.body) {
3378        param_names.insert(0, "_opPushTxSig".to_string());
3379    }
3380
3381    let mut ctx = LoweringContext::new(&param_names, properties);
3382    ctx.private_methods = private_methods.clone();
3383    // Pass terminal_assert=true for public methods so the last assert leaves
3384    // its value on the stack (Bitcoin Script requires a truthy top-of-stack).
3385    ctx.lower_bindings(&method.body, method.is_public);
3386
3387    // Clean up excess stack items left by deserialize_state.
3388    let has_deserialize_state = method.body.iter().any(|b| matches!(&b.value, ANFValue::DeserializeState { .. }));
3389    if method.is_public && has_deserialize_state && ctx.sm.depth() > 1 {
3390        let excess = ctx.sm.depth() - 1;
3391        for _ in 0..excess {
3392            ctx.emit_op(StackOp::Nip);
3393            ctx.sm.remove_at_depth(1);
3394        }
3395    }
3396
3397    if ctx.max_depth > MAX_STACK_DEPTH {
3398        return Err(format!(
3399            "method '{}' exceeds maximum stack depth of {} (actual: {}). Simplify the contract logic.",
3400            method.name, MAX_STACK_DEPTH, ctx.max_depth
3401        ));
3402    }
3403
3404    Ok(StackMethod {
3405        name: method.name.clone(),
3406        ops: ctx.ops,
3407        max_stack_depth: ctx.max_depth,
3408    })
3409}
3410
3411// ---------------------------------------------------------------------------
3412// Helpers
3413// ---------------------------------------------------------------------------
3414
3415fn hex_to_bytes(hex_str: &str) -> Vec<u8> {
3416    if hex_str.is_empty() {
3417        return Vec::new();
3418    }
3419    assert!(
3420        hex_str.len() % 2 == 0,
3421        "invalid hex string length: {}",
3422        hex_str.len()
3423    );
3424    (0..hex_str.len())
3425        .step_by(2)
3426        .map(|i| u8::from_str_radix(&hex_str[i..i + 2], 16).unwrap_or(0))
3427        .collect()
3428}
3429
3430// ---------------------------------------------------------------------------
3431// Tests
3432// ---------------------------------------------------------------------------
3433
3434#[cfg(test)]
3435mod tests {
3436    use super::*;
3437    use crate::ir::{ANFBinding, ANFMethod, ANFParam, ANFProgram, ANFProperty, ANFValue};
3438
3439    /// Build a minimal P2PKH IR program for testing stack lowering.
3440    fn p2pkh_program() -> ANFProgram {
3441        ANFProgram {
3442            contract_name: "P2PKH".to_string(),
3443            properties: vec![ANFProperty {
3444                name: "pubKeyHash".to_string(),
3445                prop_type: "Addr".to_string(),
3446                readonly: true,
3447                initial_value: None,
3448            }],
3449            methods: vec![ANFMethod {
3450                name: "unlock".to_string(),
3451                params: vec![
3452                    ANFParam {
3453                        name: "sig".to_string(),
3454                        param_type: "Sig".to_string(),
3455                    },
3456                    ANFParam {
3457                        name: "pubKey".to_string(),
3458                        param_type: "PubKey".to_string(),
3459                    },
3460                ],
3461                body: vec![
3462                    ANFBinding {
3463                        name: "t0".to_string(),
3464                        value: ANFValue::LoadParam {
3465                            name: "sig".to_string(),
3466                        },
3467                    },
3468                    ANFBinding {
3469                        name: "t1".to_string(),
3470                        value: ANFValue::LoadParam {
3471                            name: "pubKey".to_string(),
3472                        },
3473                    },
3474                    ANFBinding {
3475                        name: "t2".to_string(),
3476                        value: ANFValue::LoadProp {
3477                            name: "pubKeyHash".to_string(),
3478                        },
3479                    },
3480                    ANFBinding {
3481                        name: "t3".to_string(),
3482                        value: ANFValue::Call {
3483                            func: "hash160".to_string(),
3484                            args: vec!["t1".to_string()],
3485                        },
3486                    },
3487                    ANFBinding {
3488                        name: "t4".to_string(),
3489                        value: ANFValue::BinOp {
3490                            op: "===".to_string(),
3491                            left: "t3".to_string(),
3492                            right: "t2".to_string(),
3493                            result_type: None,
3494                        },
3495                    },
3496                    ANFBinding {
3497                        name: "t5".to_string(),
3498                        value: ANFValue::Assert {
3499                            value: "t4".to_string(),
3500                        },
3501                    },
3502                    ANFBinding {
3503                        name: "t6".to_string(),
3504                        value: ANFValue::Call {
3505                            func: "checkSig".to_string(),
3506                            args: vec!["t0".to_string(), "t1".to_string()],
3507                        },
3508                    },
3509                    ANFBinding {
3510                        name: "t7".to_string(),
3511                        value: ANFValue::Assert {
3512                            value: "t6".to_string(),
3513                        },
3514                    },
3515                ],
3516                is_public: true,
3517            }],
3518        }
3519    }
3520
3521    #[test]
3522    fn test_p2pkh_stack_lowering_produces_placeholder_ops() {
3523        let program = p2pkh_program();
3524        let methods = lower_to_stack(&program).expect("stack lowering should succeed");
3525        assert_eq!(methods.len(), 1);
3526        assert_eq!(methods[0].name, "unlock");
3527
3528        // There should be at least one Placeholder op (for the pubKeyHash property)
3529        let has_placeholder = methods[0].ops.iter().any(|op| {
3530            matches!(op, StackOp::Placeholder { .. })
3531        });
3532        assert!(
3533            has_placeholder,
3534            "P2PKH should have Placeholder ops for constructor params, ops: {:?}",
3535            methods[0].ops
3536        );
3537    }
3538
3539    #[test]
3540    fn test_placeholder_has_correct_param_index() {
3541        let program = p2pkh_program();
3542        let methods = lower_to_stack(&program).expect("stack lowering should succeed");
3543
3544        // Find the Placeholder op and check its param_index
3545        let placeholders: Vec<&StackOp> = methods[0]
3546            .ops
3547            .iter()
3548            .filter(|op| matches!(op, StackOp::Placeholder { .. }))
3549            .collect();
3550
3551        assert!(
3552            !placeholders.is_empty(),
3553            "should have at least one Placeholder"
3554        );
3555
3556        // pubKeyHash is the only property at index 0
3557        if let StackOp::Placeholder {
3558            param_index,
3559            param_name,
3560        } = placeholders[0]
3561        {
3562            assert_eq!(*param_index, 0);
3563            assert_eq!(param_name, "pubKeyHash");
3564        } else {
3565            panic!("expected Placeholder op");
3566        }
3567    }
3568
3569    #[test]
3570    fn test_with_initial_values_no_placeholder_ops() {
3571        let mut program = p2pkh_program();
3572        // Set an initial value for the property -- this bakes it in
3573        program.properties[0].initial_value =
3574            Some(serde_json::Value::String("aabbccdd".to_string()));
3575
3576        let methods = lower_to_stack(&program).expect("stack lowering should succeed");
3577        let has_placeholder = methods[0].ops.iter().any(|op| {
3578            matches!(op, StackOp::Placeholder { .. })
3579        });
3580        assert!(
3581            !has_placeholder,
3582            "with initial values, there should be no Placeholder ops"
3583        );
3584    }
3585
3586    #[test]
3587    fn test_stack_lowering_produces_standard_opcodes() {
3588        let program = p2pkh_program();
3589        let methods = lower_to_stack(&program).expect("stack lowering should succeed");
3590
3591        // Collect all Opcode strings
3592        let opcodes: Vec<&str> = methods[0]
3593            .ops
3594            .iter()
3595            .filter_map(|op| match op {
3596                StackOp::Opcode(code) => Some(code.as_str()),
3597                _ => None,
3598            })
3599            .collect();
3600
3601        // P2PKH should contain OP_HASH160, OP_NUMEQUAL (from ===), OP_VERIFY, OP_CHECKSIG
3602        assert!(
3603            opcodes.contains(&"OP_HASH160"),
3604            "expected OP_HASH160 in opcodes: {:?}",
3605            opcodes
3606        );
3607        assert!(
3608            opcodes.contains(&"OP_CHECKSIG"),
3609            "expected OP_CHECKSIG in opcodes: {:?}",
3610            opcodes
3611        );
3612    }
3613
3614    #[test]
3615    fn test_max_stack_depth_is_tracked() {
3616        let program = p2pkh_program();
3617        let methods = lower_to_stack(&program).expect("stack lowering should succeed");
3618        assert!(
3619            methods[0].max_stack_depth > 0,
3620            "max_stack_depth should be > 0"
3621        );
3622        // P2PKH has 2 params + some intermediates, so depth should be reasonable
3623        assert!(
3624            methods[0].max_stack_depth <= 10,
3625            "max_stack_depth should be reasonable for P2PKH, got: {}",
3626            methods[0].max_stack_depth
3627        );
3628    }
3629
3630    // -----------------------------------------------------------------------
3631    // Helper: collect all opcodes from a StackOp list (including inside If)
3632    // -----------------------------------------------------------------------
3633
3634    fn collect_all_opcodes(ops: &[StackOp]) -> Vec<String> {
3635        let mut result = Vec::new();
3636        for op in ops {
3637            match op {
3638                StackOp::Opcode(code) => result.push(code.clone()),
3639                StackOp::If { then_ops, else_ops } => {
3640                    result.push("OP_IF".to_string());
3641                    result.extend(collect_all_opcodes(then_ops));
3642                    result.push("OP_ELSE".to_string());
3643                    result.extend(collect_all_opcodes(else_ops));
3644                    result.push("OP_ENDIF".to_string());
3645                }
3646                StackOp::Push(PushValue::Int(n)) => {
3647                    result.push(format!("PUSH({})", n));
3648                }
3649                StackOp::Drop => result.push("OP_DROP".to_string()),
3650                StackOp::Swap => result.push("OP_SWAP".to_string()),
3651                StackOp::Dup => result.push("OP_DUP".to_string()),
3652                StackOp::Over => result.push("OP_OVER".to_string()),
3653                StackOp::Rot => result.push("OP_ROT".to_string()),
3654                StackOp::Nip => result.push("OP_NIP".to_string()),
3655                _ => {}
3656            }
3657        }
3658        result
3659    }
3660
3661    fn collect_opcodes_in_if_branches(ops: &[StackOp]) -> (Vec<String>, Vec<String>) {
3662        for op in ops {
3663            if let StackOp::If { then_ops, else_ops } = op {
3664                return (collect_all_opcodes(then_ops), collect_all_opcodes(else_ops));
3665            }
3666        }
3667        (vec![], vec![])
3668    }
3669
3670    // -----------------------------------------------------------------------
3671    // Fix #1: extractOutputHash offset must be 40, not 44
3672    // -----------------------------------------------------------------------
3673
3674    #[test]
3675    fn test_extract_output_hash_uses_offset_40() {
3676        // Build a stateful contract that calls extractOutputHash on a preimage
3677        let program = ANFProgram {
3678            contract_name: "TestExtract".to_string(),
3679            properties: vec![ANFProperty {
3680                name: "val".to_string(),
3681                prop_type: "bigint".to_string(),
3682                readonly: false,
3683                initial_value: Some(serde_json::Value::Number(serde_json::Number::from(0))),
3684            }],
3685            methods: vec![ANFMethod {
3686                name: "check".to_string(),
3687                params: vec![
3688                    ANFParam { name: "preimage".to_string(), param_type: "SigHashPreimage".to_string() },
3689                ],
3690                body: vec![
3691                    ANFBinding {
3692                        name: "t0".to_string(),
3693                        value: ANFValue::LoadParam { name: "preimage".to_string() },
3694                    },
3695                    ANFBinding {
3696                        name: "t1".to_string(),
3697                        value: ANFValue::Call {
3698                            func: "extractOutputHash".to_string(),
3699                            args: vec!["t0".to_string()],
3700                        },
3701                    },
3702                    ANFBinding {
3703                        name: "t2".to_string(),
3704                        value: ANFValue::LoadConst { value: serde_json::Value::Bool(true) },
3705                    },
3706                    ANFBinding {
3707                        name: "t3".to_string(),
3708                        value: ANFValue::Assert { value: "t2".to_string() },
3709                    },
3710                ],
3711                is_public: true,
3712            }],
3713        };
3714
3715        let methods = lower_to_stack(&program).expect("stack lowering should succeed");
3716        let opcodes = collect_all_opcodes(&methods[0].ops);
3717
3718        // The offset 40 should appear as PUSH(40), not PUSH(44)
3719        assert!(
3720            opcodes.contains(&"PUSH(40)".to_string()),
3721            "extractOutputHash should use offset 40 (BIP-143 hashOutputs starts at size-40), ops: {:?}",
3722            opcodes
3723        );
3724        assert!(
3725            !opcodes.contains(&"PUSH(44)".to_string()),
3726            "extractOutputHash should NOT use offset 44, ops: {:?}",
3727            opcodes
3728        );
3729    }
3730
3731    // -----------------------------------------------------------------------
3732    // Fix #3: Terminal-if propagation
3733    // -----------------------------------------------------------------------
3734
3735    #[test]
3736    fn test_terminal_if_propagates_terminal_assert() {
3737        // A public method ending with if/else where both branches have asserts.
3738        // The terminal asserts in both branches should NOT emit OP_VERIFY.
3739        let program = ANFProgram {
3740            contract_name: "TerminalIf".to_string(),
3741            properties: vec![],
3742            methods: vec![ANFMethod {
3743                name: "check".to_string(),
3744                params: vec![
3745                    ANFParam { name: "mode".to_string(), param_type: "boolean".to_string() },
3746                    ANFParam { name: "x".to_string(), param_type: "bigint".to_string() },
3747                ],
3748                body: vec![
3749                    ANFBinding {
3750                        name: "t0".to_string(),
3751                        value: ANFValue::LoadParam { name: "mode".to_string() },
3752                    },
3753                    ANFBinding {
3754                        name: "t1".to_string(),
3755                        value: ANFValue::LoadParam { name: "x".to_string() },
3756                    },
3757                    ANFBinding {
3758                        name: "t2".to_string(),
3759                        value: ANFValue::If {
3760                            cond: "t0".to_string(),
3761                            then: vec![
3762                                ANFBinding {
3763                                    name: "t3".to_string(),
3764                                    value: ANFValue::LoadConst {
3765                                        value: serde_json::Value::Number(serde_json::Number::from(10)),
3766                                    },
3767                                },
3768                                ANFBinding {
3769                                    name: "t4".to_string(),
3770                                    value: ANFValue::BinOp {
3771                                        op: ">".to_string(),
3772                                        left: "t1".to_string(),
3773                                        right: "t3".to_string(),
3774                                        result_type: None,
3775                                    },
3776                                },
3777                                ANFBinding {
3778                                    name: "t5".to_string(),
3779                                    value: ANFValue::Assert { value: "t4".to_string() },
3780                                },
3781                            ],
3782                            else_branch: vec![
3783                                ANFBinding {
3784                                    name: "t6".to_string(),
3785                                    value: ANFValue::LoadConst {
3786                                        value: serde_json::Value::Number(serde_json::Number::from(5)),
3787                                    },
3788                                },
3789                                ANFBinding {
3790                                    name: "t7".to_string(),
3791                                    value: ANFValue::BinOp {
3792                                        op: ">".to_string(),
3793                                        left: "t1".to_string(),
3794                                        right: "t6".to_string(),
3795                                        result_type: None,
3796                                    },
3797                                },
3798                                ANFBinding {
3799                                    name: "t8".to_string(),
3800                                    value: ANFValue::Assert { value: "t7".to_string() },
3801                                },
3802                            ],
3803                        },
3804                    },
3805                ],
3806                is_public: true,
3807            }],
3808        };
3809
3810        let methods = lower_to_stack(&program).expect("stack lowering should succeed");
3811
3812        // Get the opcodes inside the if branches
3813        let (then_opcodes, else_opcodes) = collect_opcodes_in_if_branches(&methods[0].ops);
3814
3815        // Neither branch should contain OP_VERIFY — the asserts are terminal
3816        assert!(
3817            !then_opcodes.contains(&"OP_VERIFY".to_string()),
3818            "then branch should not contain OP_VERIFY (terminal assert), got: {:?}",
3819            then_opcodes
3820        );
3821        assert!(
3822            !else_opcodes.contains(&"OP_VERIFY".to_string()),
3823            "else branch should not contain OP_VERIFY (terminal assert), got: {:?}",
3824            else_opcodes
3825        );
3826    }
3827
3828    // -----------------------------------------------------------------------
3829    // Fix #8: pack/unpack/toByteString builtins
3830    // -----------------------------------------------------------------------
3831
3832    #[test]
3833    fn test_unpack_emits_bin2num() {
3834        let program = ANFProgram {
3835            contract_name: "TestUnpack".to_string(),
3836            properties: vec![],
3837            methods: vec![ANFMethod {
3838                name: "check".to_string(),
3839                params: vec![
3840                    ANFParam { name: "data".to_string(), param_type: "ByteString".to_string() },
3841                ],
3842                body: vec![
3843                    ANFBinding {
3844                        name: "t0".to_string(),
3845                        value: ANFValue::LoadParam { name: "data".to_string() },
3846                    },
3847                    ANFBinding {
3848                        name: "t1".to_string(),
3849                        value: ANFValue::Call {
3850                            func: "unpack".to_string(),
3851                            args: vec!["t0".to_string()],
3852                        },
3853                    },
3854                    ANFBinding {
3855                        name: "t2".to_string(),
3856                        value: ANFValue::LoadConst {
3857                            value: serde_json::Value::Number(serde_json::Number::from(42)),
3858                        },
3859                    },
3860                    ANFBinding {
3861                        name: "t3".to_string(),
3862                        value: ANFValue::BinOp {
3863                            op: "===".to_string(),
3864                            left: "t1".to_string(),
3865                            right: "t2".to_string(),
3866                            result_type: None,
3867                        },
3868                    },
3869                    ANFBinding {
3870                        name: "t4".to_string(),
3871                        value: ANFValue::Assert { value: "t3".to_string() },
3872                    },
3873                ],
3874                is_public: true,
3875            }],
3876        };
3877
3878        let methods = lower_to_stack(&program).expect("stack lowering should succeed");
3879        let opcodes = collect_all_opcodes(&methods[0].ops);
3880        assert!(
3881            opcodes.contains(&"OP_BIN2NUM".to_string()),
3882            "unpack should emit OP_BIN2NUM, got: {:?}",
3883            opcodes
3884        );
3885    }
3886
3887    #[test]
3888    fn test_pack_is_noop() {
3889        let program = ANFProgram {
3890            contract_name: "TestPack".to_string(),
3891            properties: vec![],
3892            methods: vec![ANFMethod {
3893                name: "check".to_string(),
3894                params: vec![
3895                    ANFParam { name: "x".to_string(), param_type: "bigint".to_string() },
3896                ],
3897                body: vec![
3898                    ANFBinding {
3899                        name: "t0".to_string(),
3900                        value: ANFValue::LoadParam { name: "x".to_string() },
3901                    },
3902                    ANFBinding {
3903                        name: "t1".to_string(),
3904                        value: ANFValue::Call {
3905                            func: "pack".to_string(),
3906                            args: vec!["t0".to_string()],
3907                        },
3908                    },
3909                    ANFBinding {
3910                        name: "t2".to_string(),
3911                        value: ANFValue::LoadConst {
3912                            value: serde_json::Value::Bool(true),
3913                        },
3914                    },
3915                    ANFBinding {
3916                        name: "t3".to_string(),
3917                        value: ANFValue::Assert { value: "t2".to_string() },
3918                    },
3919                ],
3920                is_public: true,
3921            }],
3922        };
3923
3924        let methods = lower_to_stack(&program).expect("stack lowering should succeed");
3925        let opcodes = collect_all_opcodes(&methods[0].ops);
3926        // pack should NOT emit any conversion opcode — just pass through
3927        assert!(
3928            !opcodes.contains(&"OP_BIN2NUM".to_string()),
3929            "pack should not emit OP_BIN2NUM, got: {:?}",
3930            opcodes
3931        );
3932        assert!(
3933            !opcodes.contains(&"OP_NUM2BIN".to_string()),
3934            "pack should not emit OP_NUM2BIN, got: {:?}",
3935            opcodes
3936        );
3937    }
3938
3939    #[test]
3940    fn test_to_byte_string_is_noop() {
3941        let program = ANFProgram {
3942            contract_name: "TestToByteString".to_string(),
3943            properties: vec![],
3944            methods: vec![ANFMethod {
3945                name: "check".to_string(),
3946                params: vec![
3947                    ANFParam { name: "x".to_string(), param_type: "bigint".to_string() },
3948                ],
3949                body: vec![
3950                    ANFBinding {
3951                        name: "t0".to_string(),
3952                        value: ANFValue::LoadParam { name: "x".to_string() },
3953                    },
3954                    ANFBinding {
3955                        name: "t1".to_string(),
3956                        value: ANFValue::Call {
3957                            func: "toByteString".to_string(),
3958                            args: vec!["t0".to_string()],
3959                        },
3960                    },
3961                    ANFBinding {
3962                        name: "t2".to_string(),
3963                        value: ANFValue::LoadConst {
3964                            value: serde_json::Value::Bool(true),
3965                        },
3966                    },
3967                    ANFBinding {
3968                        name: "t3".to_string(),
3969                        value: ANFValue::Assert { value: "t2".to_string() },
3970                    },
3971                ],
3972                is_public: true,
3973            }],
3974        };
3975
3976        let methods = lower_to_stack(&program).expect("stack lowering should succeed");
3977        let opcodes = collect_all_opcodes(&methods[0].ops);
3978        // toByteString should NOT emit any conversion opcode — just pass through
3979        assert!(
3980            !opcodes.contains(&"OP_BIN2NUM".to_string()),
3981            "toByteString should not emit OP_BIN2NUM, got: {:?}",
3982            opcodes
3983        );
3984    }
3985
3986    // -----------------------------------------------------------------------
3987    // Fix #25: sqrt(0) guard
3988    // -----------------------------------------------------------------------
3989
3990    #[test]
3991    fn test_sqrt_has_zero_guard() {
3992        let program = ANFProgram {
3993            contract_name: "TestSqrt".to_string(),
3994            properties: vec![],
3995            methods: vec![ANFMethod {
3996                name: "check".to_string(),
3997                params: vec![
3998                    ANFParam { name: "n".to_string(), param_type: "bigint".to_string() },
3999                ],
4000                body: vec![
4001                    ANFBinding {
4002                        name: "t0".to_string(),
4003                        value: ANFValue::LoadParam { name: "n".to_string() },
4004                    },
4005                    ANFBinding {
4006                        name: "t1".to_string(),
4007                        value: ANFValue::Call {
4008                            func: "sqrt".to_string(),
4009                            args: vec!["t0".to_string()],
4010                        },
4011                    },
4012                    ANFBinding {
4013                        name: "t2".to_string(),
4014                        value: ANFValue::LoadConst {
4015                            value: serde_json::Value::Number(serde_json::Number::from(0)),
4016                        },
4017                    },
4018                    ANFBinding {
4019                        name: "t3".to_string(),
4020                        value: ANFValue::BinOp {
4021                            op: ">=".to_string(),
4022                            left: "t1".to_string(),
4023                            right: "t2".to_string(),
4024                            result_type: None,
4025                        },
4026                    },
4027                    ANFBinding {
4028                        name: "t4".to_string(),
4029                        value: ANFValue::Assert { value: "t3".to_string() },
4030                    },
4031                ],
4032                is_public: true,
4033            }],
4034        };
4035
4036        let methods = lower_to_stack(&program).expect("stack lowering should succeed");
4037        let opcodes = collect_all_opcodes(&methods[0].ops);
4038
4039        // The sqrt implementation should have OP_DUP followed by OP_IF (the zero guard).
4040        // The DUP duplicates n, then IF checks if n != 0 before Newton iteration.
4041        let dup_idx = opcodes.iter().position(|o| o == "OP_DUP");
4042        let if_idx = opcodes.iter().position(|o| o == "OP_IF");
4043
4044        assert!(
4045            dup_idx.is_some() && if_idx.is_some(),
4046            "sqrt should have OP_DUP and OP_IF for zero guard, got: {:?}",
4047            opcodes
4048        );
4049        assert!(
4050            dup_idx.unwrap() < if_idx.unwrap(),
4051            "OP_DUP should come before OP_IF in sqrt zero guard, got: {:?}",
4052            opcodes
4053        );
4054    }
4055
4056    // -----------------------------------------------------------------------
4057    // Fix #28: Loop cleanup of unused iteration variables
4058    // -----------------------------------------------------------------------
4059
4060    #[test]
4061    fn test_loop_cleans_up_unused_iter_var() {
4062        // A loop whose body has only asserts (which consume stack values).
4063        // After the body, the iter var ends up on top of the stack (depth 0),
4064        // so it should be dropped. The TS reference does this cleanup.
4065        let program = ANFProgram {
4066            contract_name: "TestLoopCleanup".to_string(),
4067            properties: vec![],
4068            methods: vec![ANFMethod {
4069                name: "check".to_string(),
4070                params: vec![
4071                    ANFParam { name: "x".to_string(), param_type: "bigint".to_string() },
4072                ],
4073                body: vec![
4074                    ANFBinding {
4075                        name: "t0".to_string(),
4076                        value: ANFValue::LoadParam { name: "x".to_string() },
4077                    },
4078                    ANFBinding {
4079                        name: "t_loop".to_string(),
4080                        value: ANFValue::Loop {
4081                            count: 3,
4082                            body: vec![
4083                                // Body uses x but not iter var __i, and asserts consume
4084                                ANFBinding {
4085                                    name: "t1".to_string(),
4086                                    value: ANFValue::LoadParam { name: "x".to_string() },
4087                                },
4088                                ANFBinding {
4089                                    name: "t2".to_string(),
4090                                    value: ANFValue::Assert { value: "t1".to_string() },
4091                                },
4092                            ],
4093                            iter_var: "__i".to_string(),
4094                        },
4095                    },
4096                    ANFBinding {
4097                        name: "t_final".to_string(),
4098                        value: ANFValue::LoadConst {
4099                            value: serde_json::Value::Bool(true),
4100                        },
4101                    },
4102                    ANFBinding {
4103                        name: "t_assert".to_string(),
4104                        value: ANFValue::Assert { value: "t_final".to_string() },
4105                    },
4106                ],
4107                is_public: true,
4108            }],
4109        };
4110
4111        let methods = lower_to_stack(&program).expect("stack lowering should succeed");
4112        let opcodes = collect_all_opcodes(&methods[0].ops);
4113
4114        // Each iteration pushes __i, then the body asserts (consuming its value).
4115        // After each iteration, __i is on top (depth 0) and should be dropped.
4116        // With 3 iterations, we expect at least 3 OP_DROP ops (one per iter var cleanup).
4117        let drop_count = opcodes.iter().filter(|o| o.as_str() == "OP_DROP").count();
4118        assert!(
4119            drop_count >= 3,
4120            "unused iter var should be dropped after each iteration; expected >= 3 OP_DROPs, got {}: {:?}",
4121            drop_count,
4122            opcodes
4123        );
4124    }
4125
4126    // -----------------------------------------------------------------------
4127    // Fix #29: PushValue::Int uses i128 (no overflow for large values)
4128    // -----------------------------------------------------------------------
4129
4130    #[test]
4131    fn test_push_value_int_large_values() {
4132        // Verify that PushValue::Int can hold values larger than i64::MAX
4133        let large_val: i128 = (i64::MAX as i128) + 1;
4134        let push = PushValue::Int(large_val);
4135        if let PushValue::Int(v) = push {
4136            assert_eq!(v, large_val, "PushValue::Int should store values > i64::MAX without truncation");
4137        } else {
4138            panic!("expected PushValue::Int");
4139        }
4140
4141        // Also test negative extreme
4142        let neg_val: i128 = (i64::MIN as i128) - 1;
4143        let push_neg = PushValue::Int(neg_val);
4144        if let PushValue::Int(v) = push_neg {
4145            assert_eq!(v, neg_val, "PushValue::Int should store values < i64::MIN without truncation");
4146        } else {
4147            panic!("expected PushValue::Int");
4148        }
4149    }
4150
4151    #[test]
4152    fn test_push_value_int_encodes_large_number() {
4153        // Verify that a large number (> i64::MAX) can be pushed and encoded
4154        use crate::codegen::emit::encode_push_int;
4155
4156        let large_val: i128 = 1i128 << 100;
4157        let (hex, _asm) = encode_push_int(large_val);
4158        // Should produce a valid hex encoding, not panic or truncate
4159        assert!(!hex.is_empty(), "encoding of 2^100 should produce non-empty hex");
4160
4161        // Verify the encoding length is reasonable for a 13-byte number
4162        // 2^100 needs 13 bytes in script number encoding (sign-magnitude)
4163        // Push data: 0x0d (length 13) + 13 bytes = 14 bytes = 28 hex chars
4164        assert!(
4165            hex.len() >= 26,
4166            "2^100 should need at least 13 bytes of push data, got hex length {}: {}",
4167            hex.len(),
4168            hex
4169        );
4170    }
4171
4172    // -----------------------------------------------------------------------
4173    // log2 uses bit-scanning (OP_DIV + OP_GREATERTHAN), not byte approx
4174    // -----------------------------------------------------------------------
4175
4176    #[test]
4177    fn test_log2_uses_bit_scanning_not_byte_approx() {
4178        let program = ANFProgram {
4179            contract_name: "TestLog2".to_string(),
4180            properties: vec![],
4181            methods: vec![ANFMethod {
4182                name: "check".to_string(),
4183                params: vec![
4184                    ANFParam { name: "n".to_string(), param_type: "bigint".to_string() },
4185                ],
4186                body: vec![
4187                    ANFBinding {
4188                        name: "t0".to_string(),
4189                        value: ANFValue::LoadParam { name: "n".to_string() },
4190                    },
4191                    ANFBinding {
4192                        name: "t1".to_string(),
4193                        value: ANFValue::Call {
4194                            func: "log2".to_string(),
4195                            args: vec!["t0".to_string()],
4196                        },
4197                    },
4198                    ANFBinding {
4199                        name: "t2".to_string(),
4200                        value: ANFValue::LoadConst {
4201                            value: serde_json::Value::Number(serde_json::Number::from(0)),
4202                        },
4203                    },
4204                    ANFBinding {
4205                        name: "t3".to_string(),
4206                        value: ANFValue::BinOp {
4207                            op: ">=".to_string(),
4208                            left: "t1".to_string(),
4209                            right: "t2".to_string(),
4210                            result_type: None,
4211                        },
4212                    },
4213                    ANFBinding {
4214                        name: "t4".to_string(),
4215                        value: ANFValue::Assert { value: "t3".to_string() },
4216                    },
4217                ],
4218                is_public: true,
4219            }],
4220        };
4221
4222        let methods = lower_to_stack(&program).expect("stack lowering should succeed");
4223        let opcodes = collect_all_opcodes(&methods[0].ops);
4224
4225        // The bit-scanning implementation must use OP_DIV and OP_GREATERTHAN
4226        assert!(
4227            opcodes.contains(&"OP_DIV".to_string()),
4228            "log2 should use OP_DIV (bit-scanning), got: {:?}",
4229            opcodes
4230        );
4231        assert!(
4232            opcodes.contains(&"OP_GREATERTHAN".to_string()),
4233            "log2 should use OP_GREATERTHAN (bit-scanning), got: {:?}",
4234            opcodes
4235        );
4236
4237        // The old byte-approximation used OP_SIZE and OP_MUL — must NOT be present
4238        assert!(
4239            !opcodes.contains(&"OP_SIZE".to_string()),
4240            "log2 should NOT use OP_SIZE (old byte approximation), got: {:?}",
4241            opcodes
4242        );
4243        assert!(
4244            !opcodes.contains(&"OP_MUL".to_string()),
4245            "log2 should NOT use OP_MUL (old byte approximation), got: {:?}",
4246            opcodes
4247        );
4248
4249        // Should have OP_1ADD for counter increment
4250        assert!(
4251            opcodes.contains(&"OP_1ADD".to_string()),
4252            "log2 should use OP_1ADD (counter increment), got: {:?}",
4253            opcodes
4254        );
4255    }
4256
4257    // -----------------------------------------------------------------------
4258    // reverseBytes uses OP_SPLIT + OP_CAT (not non-existent OP_REVERSE)
4259    // -----------------------------------------------------------------------
4260
4261    #[test]
4262    fn test_reverse_bytes_uses_split_cat_not_op_reverse() {
4263        let program = ANFProgram {
4264            contract_name: "TestReverse".to_string(),
4265            properties: vec![],
4266            methods: vec![ANFMethod {
4267                name: "check".to_string(),
4268                params: vec![
4269                    ANFParam { name: "data".to_string(), param_type: "ByteString".to_string() },
4270                ],
4271                body: vec![
4272                    ANFBinding {
4273                        name: "t0".to_string(),
4274                        value: ANFValue::LoadParam { name: "data".to_string() },
4275                    },
4276                    ANFBinding {
4277                        name: "t1".to_string(),
4278                        value: ANFValue::Call {
4279                            func: "reverseBytes".to_string(),
4280                            args: vec!["t0".to_string()],
4281                        },
4282                    },
4283                    ANFBinding {
4284                        name: "t2".to_string(),
4285                        value: ANFValue::LoadConst {
4286                            value: serde_json::Value::Bool(true),
4287                        },
4288                    },
4289                    ANFBinding {
4290                        name: "t3".to_string(),
4291                        value: ANFValue::Assert { value: "t2".to_string() },
4292                    },
4293                ],
4294                is_public: true,
4295            }],
4296        };
4297
4298        let methods = lower_to_stack(&program).expect("stack lowering should succeed");
4299        let opcodes = collect_all_opcodes(&methods[0].ops);
4300
4301        // Must NOT contain the non-existent OP_REVERSE
4302        assert!(
4303            !opcodes.contains(&"OP_REVERSE".to_string()),
4304            "reverseBytes must NOT emit OP_REVERSE (does not exist), got: {:?}",
4305            opcodes
4306        );
4307
4308        // Must use OP_SPLIT and OP_CAT for byte-by-byte reversal
4309        assert!(
4310            opcodes.contains(&"OP_SPLIT".to_string()),
4311            "reverseBytes should emit OP_SPLIT for byte peeling, got: {:?}",
4312            opcodes
4313        );
4314        assert!(
4315            opcodes.contains(&"OP_CAT".to_string()),
4316            "reverseBytes should emit OP_CAT for reassembly, got: {:?}",
4317            opcodes
4318        );
4319
4320        // Should use OP_SIZE to check remaining length
4321        assert!(
4322            opcodes.contains(&"OP_SIZE".to_string()),
4323            "reverseBytes should emit OP_SIZE for length check, got: {:?}",
4324            opcodes
4325        );
4326    }
4327}