#[allow(dead_code)]
fn emit_pushint256_le(out: &mut Vec<u8>, le: &[u8; 32]) {
out.push(0x05);
out.extend_from_slice(le);
}
#[allow(dead_code)]
const SIGN_BIT_LE: [u8; 32] = {
let mut b = [0u8; 32];
b[31] = 0x80;
b
};
#[allow(dead_code)]
fn emit_uint256_unsigned_lt(out: &mut Vec<u8>) {
emit_pushint256_le(out, &SIGN_BIT_LE);
out.push(0x93); out.push(0x50); emit_pushint256_le(out, &SIGN_BIT_LE);
out.push(0x93); out.push(0x50); out.push(0xB5); }
#[allow(dead_code)]
fn emit_uint256_unsigned_gt(out: &mut Vec<u8>) {
emit_pushint256_le(out, &SIGN_BIT_LE);
out.push(0x93); out.push(0x50); emit_pushint256_le(out, &SIGN_BIT_LE);
out.push(0x93); out.push(0x50); out.push(0xB7); }
#[allow(dead_code)]
fn emit_uint256_unsigned_le(out: &mut Vec<u8>) {
emit_pushint256_le(out, &SIGN_BIT_LE);
out.push(0x93); out.push(0x50); emit_pushint256_le(out, &SIGN_BIT_LE);
out.push(0x93); out.push(0x50); out.push(0xB6); }
#[allow(dead_code)]
fn emit_uint256_unsigned_ge(out: &mut Vec<u8>) {
emit_pushint256_le(out, &SIGN_BIT_LE);
out.push(0x93); out.push(0x50); emit_pushint256_le(out, &SIGN_BIT_LE);
out.push(0x93); out.push(0x50); out.push(0xB8); }
#[allow(dead_code)]
const MASK128_LE: [u8; 32] = {
let mut b = [0u8; 32];
let mut i = 0;
while i < 16 {
b[i] = 0xFF;
i += 1;
}
b
};
#[allow(dead_code)]
const BIAS127_LE: [u8; 32] = {
let mut b = [0u8; 32];
b[15] = 0x80;
b
};
#[allow(dead_code)]
const MAX_INT256_LE: [u8; 32] = {
let mut b = [0xFFu8; 32];
b[31] = 0x7F;
b
};
#[allow(dead_code)]
fn emit_uint256_logical_shr(out: &mut Vec<u8>) {
out.push(0x57); out.push(0x02);
out.push(0x00);
out.push(0x71); out.push(0x70); emit_ldloc(out, 1); out.push(0x26); let jmpifnot_operand = out.len();
out.push(0x00);
emit_ldloc(out, 0); out.push(0x11); out.push(0xA9); emit_pushint256_le(out, &MAX_INT256_LE);
out.push(0x91); emit_ldloc(out, 1); out.push(0x11); out.push(0x9F); out.push(0xA9); out.push(0x22); let jmp_operand = out.len();
out.push(0x00);
let zero_pos = out.len();
emit_ldloc(out, 0); let end_pos = out.len();
out[jmpifnot_operand] = ((zero_pos as isize) - (jmpifnot_operand as isize - 1)) as i8 as u8;
out[jmp_operand] = ((end_pos as isize) - (jmp_operand as isize - 1)) as i8 as u8;
}
#[allow(dead_code)]
fn emit_uint256_shl(out: &mut Vec<u8>) {
out.push(0x57); out.push(0x05); out.push(0x00);
out.push(0x71); out.push(0x70); emit_ldloc(out, 0);
emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); emit_stloc(out, 2);
emit_ldloc(out, 0);
emit_push_u8(out, 128);
out.push(0xA9); emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); emit_stloc(out, 3);
emit_ldloc(out, 1);
emit_push_u8(out, 128);
out.push(0xB8); out.push(0x24); let jmpif_big = out.len();
out.push(0x00);
emit_pushint256_le(out, &MASK128_LE);
emit_ldloc(out, 1);
out.push(0xA9); emit_stloc(out, 4);
emit_ldloc(out, 2);
emit_ldloc(out, 4);
out.push(0x91); emit_ldloc(out, 1);
out.push(0xA8); emit_ldloc(out, 3);
emit_ldloc(out, 4);
out.push(0x91); emit_ldloc(out, 1);
out.push(0xA8); emit_ldloc(out, 2);
emit_push_u8(out, 128);
emit_ldloc(out, 1);
out.push(0x9F); out.push(0xA9); out.push(0x92); out.push(0x22); let jmp_build = out.len();
out.push(0x00);
let big_pos = out.len();
emit_pushint256_le(out, &MASK128_LE);
emit_ldloc(out, 1);
emit_push_u8(out, 128);
out.push(0x9F); out.push(0xA9); emit_stloc(out, 4);
out.push(0x10); emit_ldloc(out, 2);
emit_ldloc(out, 4);
out.push(0x91); emit_ldloc(out, 1);
emit_push_u8(out, 128);
out.push(0x9F); out.push(0xA8); let build_pos = out.len();
emit_pushint256_le(out, &BIAS127_LE);
out.push(0x93); emit_pushint256_le(out, &BIAS127_LE);
out.push(0x9F); emit_push_u8(out, 128);
out.push(0xA8); out.push(0x9E); out[jmpif_big] = ((big_pos as isize) - (jmpif_big as isize - 1)) as i8 as u8;
out[jmp_build] = ((build_pos as isize) - (jmp_build as isize - 1)) as i8 as u8;
}
#[allow(dead_code)]
const MASK64_LE: [u8; 32] = {
let mut b = [0u8; 32];
let mut i = 0;
while i < 8 {
b[i] = 0xFF;
i += 1;
}
b
};
#[allow(dead_code)]
fn emit_ldloc(out: &mut Vec<u8>, i: u8) {
if i <= 6 {
out.push(0x68 + i); } else {
out.push(0x6F); out.push(i);
}
}
#[allow(dead_code)]
fn emit_stloc(out: &mut Vec<u8>, i: u8) {
if i <= 6 {
out.push(0x70 + i); } else {
out.push(0x77); out.push(i);
}
}
#[allow(dead_code)]
fn emit_push_u8(out: &mut Vec<u8>, n: u8) {
if n <= 0x7F {
out.push(0x00); out.push(n);
} else {
out.push(0x01); out.extend_from_slice(&i16::from(n).to_le_bytes());
}
}
#[allow(dead_code)]
fn emit_uint256_unchecked_add(out: &mut Vec<u8>) {
out.push(0x57); out.push(0x03); out.push(0x00); emit_add_limb_prologue(out); emit_add_full_hi(out); emit_add_result_epilogue(out); }
#[allow(dead_code)]
fn emit_uint256_unchecked_sub(out: &mut Vec<u8>) {
out.push(0x57); out.push(0x03); out.push(0x00); out.push(0x71); out.push(0x70); out.push(0x68); emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); out.push(0x69); emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); out.push(0x9F); out.push(0x72); out.push(0x68); emit_push_u8(out, 128);
out.push(0xA9); emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); out.push(0x69); emit_push_u8(out, 128);
out.push(0xA9); emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); out.push(0x9F); out.push(0x6A); emit_push_u8(out, 128);
out.push(0xA9); out.push(0x9E); emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); emit_pushint256_le(out, &BIAS127_LE);
out.push(0x93); emit_pushint256_le(out, &BIAS127_LE);
out.push(0x9F); emit_push_u8(out, 128);
out.push(0xA8); out.push(0x6A); emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); out.push(0x9E); }
#[allow(dead_code)]
fn emit_add_limb_prologue(out: &mut Vec<u8>) {
out.push(0x71); out.push(0x70); out.push(0x68); emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); out.push(0x69); emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); out.push(0x9E); out.push(0x72); }
#[allow(dead_code)]
fn emit_add_full_hi(out: &mut Vec<u8>) {
out.push(0x68); emit_push_u8(out, 128);
out.push(0xA9); emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); out.push(0x69); emit_push_u8(out, 128);
out.push(0xA9); emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); out.push(0x9E); out.push(0x6A); emit_push_u8(out, 128);
out.push(0xA9); out.push(0x9E); }
#[allow(dead_code)]
fn emit_add_result_epilogue(out: &mut Vec<u8>) {
emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); emit_pushint256_le(out, &BIAS127_LE);
out.push(0x93); emit_pushint256_le(out, &BIAS127_LE);
out.push(0x9F); emit_push_u8(out, 128);
out.push(0xA8); out.push(0x6A); emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); out.push(0x9E); }
#[allow(dead_code)]
fn emit_uint256_checked_add(out: &mut Vec<u8>) {
out.push(0x57); out.push(0x03);
out.push(0x00);
emit_add_limb_prologue(out);
emit_add_full_hi(out); out.push(0x4A); emit_push_u8(out, 128);
out.push(0xA9); out.push(0x24); let jmpif_operand = out.len();
out.push(0x00); emit_add_result_epilogue(out);
out.push(0x22); let jmp_operand = out.len();
out.push(0x00); let throw_pos = out.len();
out.push(0x3A); let end_pos = out.len();
out[jmpif_operand] = ((throw_pos as isize) - (jmpif_operand as isize - 1)) as i8 as u8;
out[jmp_operand] = ((end_pos as isize) - (jmp_operand as isize - 1)) as i8 as u8;
}
#[allow(dead_code)]
fn emit_uint256_checked_sub(out: &mut Vec<u8>) {
out.push(0x57); out.push(0x03);
out.push(0x00);
out.push(0x71); out.push(0x70); out.push(0x68); emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); out.push(0x69); emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); out.push(0x9F); out.push(0x72); out.push(0x68); emit_push_u8(out, 128);
out.push(0xA9); emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); out.push(0x69); emit_push_u8(out, 128);
out.push(0xA9); emit_pushint256_le(out, &MASK128_LE);
out.push(0x91); out.push(0x9F); out.push(0x6A); emit_push_u8(out, 128);
out.push(0xA9); out.push(0x9E); out.push(0x4A); out.push(0x10); out.push(0xB5); out.push(0x24); let jmpif_operand = out.len();
out.push(0x00);
emit_add_result_epilogue(out);
out.push(0x22); let jmp_operand = out.len();
out.push(0x00);
let throw_pos = out.len();
out.push(0x3A); let end_pos = out.len();
out[jmpif_operand] = ((throw_pos as isize) - (jmpif_operand as isize - 1)) as i8 as u8;
out[jmp_operand] = ((end_pos as isize) - (jmp_operand as isize - 1)) as i8 as u8;
}
#[allow(dead_code)]
fn emit_mul_columns(out: &mut Vec<u8>) {
const A: u8 = 13; const B: u8 = 14; emit_stloc(out, B);
emit_stloc(out, A);
for i in 0..4u8 {
emit_ldloc(out, A);
if i > 0 {
emit_push_u8(out, 64 * i);
out.push(0xA9); }
emit_pushint256_le(out, &MASK64_LE);
out.push(0x91); emit_stloc(out, i);
}
for j in 0..4u8 {
emit_ldloc(out, B);
if j > 0 {
emit_push_u8(out, 64 * j);
out.push(0xA9); }
emit_pushint256_le(out, &MASK64_LE);
out.push(0x91); emit_stloc(out, 4 + j);
}
out.push(0x10); emit_stloc(out, 8);
for k in 0..4u8 {
emit_ldloc(out, 8); for i in 0..=k {
let j = k - i;
emit_ldloc(out, i); emit_ldloc(out, 4 + j); out.push(0xA0); out.push(0x9E); }
out.push(0x4A); emit_pushint256_le(out, &MASK64_LE);
out.push(0x91); emit_stloc(out, 9 + k); emit_push_u8(out, 64);
out.push(0xA9); emit_stloc(out, 8); }
}
#[allow(dead_code)]
fn emit_mul_build_result(out: &mut Vec<u8>) {
emit_ldloc(out, 9);
emit_ldloc(out, 10);
emit_push_u8(out, 64);
out.push(0xA8); out.push(0x9E); emit_stloc(out, 13);
emit_ldloc(out, 11);
emit_ldloc(out, 12);
emit_push_u8(out, 64);
out.push(0xA8); out.push(0x9E); emit_pushint256_le(out, &BIAS127_LE);
out.push(0x93); emit_pushint256_le(out, &BIAS127_LE);
out.push(0x9F); emit_push_u8(out, 128);
out.push(0xA8); emit_ldloc(out, 13); out.push(0x9E); }
#[allow(dead_code)]
fn emit_uint256_unchecked_mul(out: &mut Vec<u8>) {
out.push(0x57); out.push(15);
out.push(0x00);
emit_mul_columns(out);
emit_mul_build_result(out);
}
#[allow(dead_code)]
fn emit_uint256_checked_mul(out: &mut Vec<u8>) {
out.push(0x57); out.push(15);
out.push(0x00);
emit_mul_columns(out);
emit_ldloc(out, 8); for (i, j) in [(1u8, 3u8), (2, 2), (3, 1), (2, 3), (3, 2), (3, 3)] {
emit_ldloc(out, i);
emit_ldloc(out, 4 + j);
out.push(0xA0); out.push(0x9E); }
out.push(0x24); let jmpif_operand = out.len();
out.push(0x00);
emit_mul_build_result(out);
out.push(0x22); let jmp_operand = out.len();
out.push(0x00);
let throw_pos = out.len();
out.push(0x3A); let end_pos = out.len();
out[jmpif_operand] = ((throw_pos as isize) - (jmpif_operand as isize - 1)) as i8 as u8;
out[jmp_operand] = ((end_pos as isize) - (jmp_operand as isize - 1)) as i8 as u8;
}
#[allow(dead_code)]
fn patch_rel32(out: &mut [u8], operand_pos: usize, target: usize) {
let opcode_pos = operand_pos as isize - 1;
let off = (target as isize - opcode_pos) as i32;
out[operand_pos..operand_pos + 4].copy_from_slice(&off.to_le_bytes());
}
#[allow(dead_code)]
fn emit_branch_l_placeholder(out: &mut Vec<u8>, op: u8) -> usize {
out.push(op);
let pos = out.len();
out.extend_from_slice(&[0u8; 4]);
pos
}
#[allow(dead_code)]
fn emit_uint256_divmod_body(out: &mut Vec<u8>) -> (Vec<usize>, Vec<usize>) {
let mut add_sites: Vec<usize> = Vec::new();
let mut sub_sites: Vec<usize> = Vec::new();
out.push(0x57); out.push(0x07);
out.push(0x00);
out.push(0x71); out.push(0x70); emit_ldloc(out, 1);
let jz = emit_branch_l_placeholder(out, 0x27); emit_ldloc(out, 1);
out.push(0x10); out.push(0xB5); let jbig = emit_branch_l_placeholder(out, 0x25);
emit_ldloc(out, 0);
out.push(0x11); out.push(0xA9); emit_pushint256_le(out, &MAX_INT256_LE);
out.push(0x91); emit_stloc(out, 4); emit_ldloc(out, 4);
emit_ldloc(out, 1);
out.push(0xA1); emit_stloc(out, 5); emit_ldloc(out, 4);
emit_ldloc(out, 1);
out.push(0xA2); emit_stloc(out, 6); emit_ldloc(out, 5);
emit_ldloc(out, 5);
add_sites.push(emit_branch_l_placeholder(out, 0x35)); emit_stloc(out, 2); emit_ldloc(out, 6);
emit_ldloc(out, 6);
add_sites.push(emit_branch_l_placeholder(out, 0x35)); emit_ldloc(out, 0);
out.push(0x11); out.push(0x91); out.push(0x9E); emit_stloc(out, 3); emit_ldloc(out, 3);
emit_ldloc(out, 1);
emit_uint256_unsigned_ge(out); let jdone = emit_branch_l_placeholder(out, 0x27); emit_ldloc(out, 2);
out.push(0x11); out.push(0x9E); emit_stloc(out, 2);
emit_ldloc(out, 3);
emit_ldloc(out, 1);
sub_sites.push(emit_branch_l_placeholder(out, 0x35)); emit_stloc(out, 3);
let done_pos = out.len();
patch_rel32(out, jdone, done_pos);
emit_ldloc(out, 2); emit_ldloc(out, 3); out.push(0x40); let end_small = out.len();
let big_pos = out.len();
patch_rel32(out, jbig, big_pos);
emit_ldloc(out, 0);
emit_ldloc(out, 1);
emit_uint256_unsigned_ge(out);
emit_stloc(out, 2); emit_ldloc(out, 2);
let jq0 = emit_branch_l_placeholder(out, 0x27); emit_ldloc(out, 0);
emit_ldloc(out, 1);
sub_sites.push(emit_branch_l_placeholder(out, 0x35)); emit_stloc(out, 3); let jbdone = emit_branch_l_placeholder(out, 0x23); let q0_pos = out.len();
patch_rel32(out, jq0, q0_pos);
emit_ldloc(out, 0);
emit_stloc(out, 3); let big_done = out.len();
patch_rel32(out, jbdone, big_done);
emit_ldloc(out, 2); emit_ldloc(out, 3); out.push(0x40);
let divzero = out.len();
patch_rel32(out, jz, divzero);
out.push(0x3A); let _ = end_small;
(add_sites, sub_sites)
}
#[cfg(test)]
mod uint256_ops_tests {
use super::*;
use num_bigint::BigInt;
fn modulus() -> BigInt {
BigInt::from(1) << 256u32
}
fn u256_le(value: &BigInt) -> [u8; 32] {
let m = modulus();
let v: BigInt = ((value % &m) + &m) % &m;
let signed: BigInt = if v >= (BigInt::from(1) << 255u32) { &v - &m } else { v };
let bytes = signed.to_signed_bytes_le();
let fill: u8 = if signed.sign() == num_bigint::Sign::Minus { 0xFF } else { 0x00 };
let mut out = [fill; 32];
out[..bytes.len()].copy_from_slice(&bytes);
out
}
fn faithful_run(code: &[u8]) -> Result<Vec<BigInt>, String> {
let mut stack: Vec<BigInt> = Vec::new();
let mut frames: Vec<Vec<BigInt>> = vec![Vec::new()];
let mut ret_stack: Vec<usize> = Vec::new();
let mut ip = 0usize;
let check = |v: BigInt| -> Result<BigInt, String> {
if v.to_signed_bytes_le().len() > 32 {
Err("integer exceeds 32 bytes".into())
} else {
Ok(v)
}
};
while ip < code.len() {
let op = code[ip];
match op {
0x05 => {
let bytes = &code[ip + 1..ip + 33];
stack.push(BigInt::from_signed_bytes_le(bytes));
ip += 33;
}
0x93 => {
let b = stack.pop().ok_or("xor underflow")?;
let a = stack.pop().ok_or("xor underflow")?;
stack.push(check(a ^ b)?);
ip += 1;
}
0x50 => {
let n = stack.len();
if n < 2 {
return Err("swap underflow".into());
}
stack.swap(n - 1, n - 2);
ip += 1;
}
0xB5 => {
let x2 = stack.pop().ok_or("lt underflow")?;
let x1 = stack.pop().ok_or("lt underflow")?;
stack.push(BigInt::from(i32::from(x1 < x2)));
ip += 1;
}
0xB7 => {
let x2 = stack.pop().ok_or("gt underflow")?;
let x1 = stack.pop().ok_or("gt underflow")?;
stack.push(BigInt::from(i32::from(x1 > x2)));
ip += 1;
}
0xB6 => {
let x2 = stack.pop().ok_or("le underflow")?;
let x1 = stack.pop().ok_or("le underflow")?;
stack.push(BigInt::from(i32::from(x1 <= x2)));
ip += 1;
}
0xB8 => {
let x2 = stack.pop().ok_or("ge underflow")?;
let x1 = stack.pop().ok_or("ge underflow")?;
stack.push(BigInt::from(i32::from(x1 >= x2)));
ip += 1;
}
0x00 => {
stack.push(BigInt::from(code[ip + 1] as i8));
ip += 2;
}
0x01 => {
let v = i16::from_le_bytes([code[ip + 1], code[ip + 2]]);
stack.push(BigInt::from(v));
ip += 3;
}
0x57 => {
let nlocals = code[ip + 1] as usize;
*frames.last_mut().ok_or("no frame")? = vec![BigInt::from(0); nlocals];
ip += 3;
}
0x70..=0x76 => {
let i = (op - 0x70) as usize;
let v = stack.pop().ok_or("stloc underflow")?;
frames.last_mut().ok_or("no frame")?[i] = v;
ip += 1;
}
0x77 => {
let i = code[ip + 1] as usize;
let v = stack.pop().ok_or("stloc underflow")?;
frames.last_mut().ok_or("no frame")?[i] = v;
ip += 2;
}
0x68..=0x6E => {
let i = (op - 0x68) as usize;
stack.push(frames.last().ok_or("no frame")?[i].clone());
ip += 1;
}
0x6F => {
let i = code[ip + 1] as usize;
stack.push(frames.last().ok_or("no frame")?[i].clone());
ip += 2;
}
0x91 => {
let b = stack.pop().ok_or("and underflow")?;
let a = stack.pop().ok_or("and underflow")?;
stack.push(check(a & b)?);
ip += 1;
}
0x92 => {
let b = stack.pop().ok_or("or underflow")?;
let a = stack.pop().ok_or("or underflow")?;
stack.push(check(a | b)?);
ip += 1;
}
0x9E => {
let b = stack.pop().ok_or("add underflow")?;
let a = stack.pop().ok_or("add underflow")?;
stack.push(check(a + b)?);
ip += 1;
}
0x9F => {
let b = stack.pop().ok_or("sub underflow")?;
let a = stack.pop().ok_or("sub underflow")?;
stack.push(check(a - b)?);
ip += 1;
}
0xA0 => {
let b = stack.pop().ok_or("mul underflow")?;
let a = stack.pop().ok_or("mul underflow")?;
stack.push(check(a * b)?);
ip += 1;
}
0xA1 => {
let b = stack.pop().ok_or("div underflow")?;
let a = stack.pop().ok_or("div underflow")?;
if b == BigInt::from(0) {
return Err("DIV by zero".into());
}
stack.push(check(a / b)?);
ip += 1;
}
0xA2 => {
let b = stack.pop().ok_or("mod underflow")?;
let a = stack.pop().ok_or("mod underflow")?;
if b == BigInt::from(0) {
return Err("MOD by zero".into());
}
stack.push(check(a % b)?);
ip += 1;
}
0xA8 => {
let shift = stack.pop().ok_or("shl underflow")?;
let value = stack.pop().ok_or("shl underflow")?;
let s: u64 = u64::try_from(shift).map_err(|_| "bad shift")?;
stack.push(check(value << s as usize)?);
ip += 1;
}
0xA9 => {
let shift = stack.pop().ok_or("shr underflow")?;
let value = stack.pop().ok_or("shr underflow")?;
let s: u64 = u64::try_from(shift).map_err(|_| "bad shift")?;
stack.push(check(value >> s as usize)?);
ip += 1;
}
0x10..=0x20 => {
stack.push(BigInt::from(op - 0x10));
ip += 1;
}
0x4A => {
let top = stack.last().cloned().ok_or("dup underflow")?;
stack.push(top);
ip += 1;
}
0x22 => {
let off = code[ip + 1] as i8 as isize;
ip = (ip as isize + off) as usize;
}
0x23 => {
let off = i32::from_le_bytes(code[ip + 1..ip + 5].try_into().unwrap()) as isize;
ip = (ip as isize + off) as usize;
}
0x24 => {
let off = code[ip + 1] as i8 as isize;
let c = stack.pop().ok_or("jmpif underflow")?;
if c != BigInt::from(0) {
ip = (ip as isize + off) as usize;
} else {
ip += 2;
}
}
0x25 => {
let off = i32::from_le_bytes(code[ip + 1..ip + 5].try_into().unwrap()) as isize;
let c = stack.pop().ok_or("jmpif underflow")?;
if c != BigInt::from(0) {
ip = (ip as isize + off) as usize;
} else {
ip += 5;
}
}
0x26 => {
let off = code[ip + 1] as i8 as isize;
let c = stack.pop().ok_or("jmpifnot underflow")?;
if c == BigInt::from(0) {
ip = (ip as isize + off) as usize;
} else {
ip += 2;
}
}
0x27 => {
let off = i32::from_le_bytes(code[ip + 1..ip + 5].try_into().unwrap()) as isize;
let c = stack.pop().ok_or("jmpifnot underflow")?;
if c == BigInt::from(0) {
ip = (ip as isize + off) as usize;
} else {
ip += 5;
}
}
0x34 => {
let off = code[ip + 1] as i8 as isize;
ret_stack.push(ip + 2);
frames.push(Vec::new());
ip = (ip as isize + off) as usize;
}
0x35 => {
let off = i32::from_le_bytes(code[ip + 1..ip + 5].try_into().unwrap()) as isize;
ret_stack.push(ip + 5);
frames.push(Vec::new());
ip = (ip as isize + off) as usize;
}
0x3A => {
return Err("THROW".into());
}
0x40 => {
match ret_stack.pop() {
Some(r) => {
frames.pop();
ip = r;
}
None => break,
}
}
other => return Err(format!("faithful VM: unhandled opcode 0x{other:02x}")),
}
}
Ok(stack)
}
fn run_lt(a: &BigInt, b: &BigInt) -> bool {
let mut code = Vec::new();
emit_pushint256_le(&mut code, &u256_le(a));
emit_pushint256_le(&mut code, &u256_le(b));
emit_uint256_unsigned_lt(&mut code);
code.push(0x40);
let st = faithful_run(&code).expect("faithful run");
st.last().cloned().unwrap_or_else(|| BigInt::from(0)) != BigInt::from(0)
}
fn run_gt(a: &BigInt, b: &BigInt) -> bool {
let mut code = Vec::new();
emit_pushint256_le(&mut code, &u256_le(a));
emit_pushint256_le(&mut code, &u256_le(b));
emit_uint256_unsigned_gt(&mut code);
code.push(0x40);
let st = faithful_run(&code).expect("faithful run");
st.last().cloned().unwrap_or_else(|| BigInt::from(0)) != BigInt::from(0)
}
fn big(s: &str) -> BigInt {
BigInt::parse_bytes(s.as_bytes(), 10).unwrap()
}
fn pow2(n: u32) -> BigInt {
BigInt::from(1) << n
}
fn umax() -> BigInt {
modulus() - 1
}
#[test]
fn faithful_vm_rejects_oversize_integers() {
let v = BigInt::from(1) << 256u32; assert!(v.to_signed_bytes_le().len() > 32);
}
#[test]
fn unsigned_lt_small_values() {
assert!(run_lt(&BigInt::from(5), &BigInt::from(10)));
assert!(!run_lt(&BigInt::from(10), &BigInt::from(5)));
assert!(!run_lt(&BigInt::from(7), &BigInt::from(7)));
assert!(run_lt(&BigInt::from(0), &BigInt::from(1)));
}
#[test]
fn unsigned_lt_large_values_above_2_255() {
assert!(run_lt(&BigInt::from(5), &umax()), "5 < uint256.max");
assert!(!run_lt(&umax(), &BigInt::from(5)), "max not < 5");
assert!(run_lt(&BigInt::from(0), &umax()), "0 < max");
assert!(!run_lt(&umax(), &umax()), "max not < max");
assert!(run_lt(&pow2(255), &(pow2(255) + 1)), "2^255 < 2^255+1");
assert!(!run_lt(&(pow2(255) + 1), &pow2(255)));
assert!(run_lt(&(pow2(255) - 1), &pow2(255)), "2^255-1 < 2^255 (straddle)");
assert!(!run_lt(&pow2(255), &(pow2(255) - 1)));
assert!(run_lt(
&big("100"),
&big("115792089237316195423570985008687907853269984665640564039457584007913129639000")
));
}
#[test]
fn unsigned_gt_matches_lt() {
assert!(run_gt(&umax(), &BigInt::from(5)), "max > 5");
assert!(!run_gt(&BigInt::from(5), &umax()));
assert!(run_gt(&pow2(255), &(pow2(255) - 1)));
assert!(!run_gt(&BigInt::from(7), &BigInt::from(7)));
}
fn run_cmp(emit: fn(&mut Vec<u8>), a: &BigInt, b: &BigInt) -> bool {
let mut code = Vec::new();
emit_pushint256_le(&mut code, &u256_le(a));
emit_pushint256_le(&mut code, &u256_le(b));
emit(&mut code);
code.push(0x40);
let st = faithful_run(&code).expect("faithful run");
st.last().cloned().unwrap_or_else(|| BigInt::from(0)) != BigInt::from(0)
}
fn run_add(a: &BigInt, b: &BigInt) -> BigInt {
let mut code = Vec::new();
emit_pushint256_le(&mut code, &u256_le(a));
emit_pushint256_le(&mut code, &u256_le(b));
emit_uint256_unchecked_add(&mut code);
code.push(0x40);
let st = faithful_run(&code).expect("faithful run");
let signed = st.last().cloned().expect("result");
let m = modulus();
((signed % &m) + &m) % &m
}
fn run_sub(a: &BigInt, b: &BigInt) -> BigInt {
let mut code = Vec::new();
emit_pushint256_le(&mut code, &u256_le(a));
emit_pushint256_le(&mut code, &u256_le(b));
emit_uint256_unchecked_sub(&mut code);
code.push(0x40);
let st = faithful_run(&code).expect("faithful run");
let signed = st.last().cloned().expect("result");
let m = modulus();
((signed % &m) + &m) % &m
}
#[test]
fn unchecked_sub_wraps_mod_2_256_including_large() {
let m = modulus();
let cases = [
(BigInt::from(5), BigInt::from(3)),
(BigInt::from(3), BigInt::from(5)), (BigInt::from(0), BigInt::from(1)), (umax(), umax()), (umax(), BigInt::from(1)), (pow2(255), BigInt::from(1)), (pow2(255), pow2(255)), (pow2(128), BigInt::from(1)), (BigInt::from(0), umax()), (pow2(200), pow2(199)), ];
for (a, b) in cases {
let expect = ((&a - &b) % &m + &m) % &m;
assert_eq!(run_sub(&a, &b), expect, "sub({a}, {b})");
}
}
#[test]
fn unchecked_add_wraps_mod_2_256_including_large() {
let m = modulus();
let cases = [
(BigInt::from(2), BigInt::from(3)),
(BigInt::from(100), BigInt::from(200)),
(umax(), BigInt::from(1)), (umax(), BigInt::from(2)), (pow2(255), pow2(255)), (pow2(255) - 1, pow2(255) - 1), (pow2(128), pow2(128)), (pow2(200) + 5, pow2(200) + 7),
(umax(), umax()), ];
for (a, b) in cases {
let expect = ((&a + &b) % &m + &m) % &m;
assert_eq!(run_add(&a, &b), expect, "add({a}, {b})");
}
}
fn run_checked(emit: fn(&mut Vec<u8>), a: &BigInt, b: &BigInt) -> Result<BigInt, String> {
let mut code = Vec::new();
emit_pushint256_le(&mut code, &u256_le(a));
emit_pushint256_le(&mut code, &u256_le(b));
emit(&mut code);
code.push(0x40);
let st = faithful_run(&code)?;
let signed = st.last().cloned().ok_or("no result")?;
let m = modulus();
Ok(((signed % &m) + &m) % &m)
}
#[test]
fn checked_add_detects_overflow() {
assert_eq!(run_checked(emit_uint256_checked_add, &big("2"), &big("3")), Ok(big("5")));
assert_eq!(
run_checked(emit_uint256_checked_add, &(pow2(255) - 1), &BigInt::from(1)),
Ok(pow2(255)),
"2^255-1 + 1 = 2^255 (still < 2^256)"
);
assert_eq!(
run_checked(emit_uint256_checked_add, &(umax() - 1), &BigInt::from(1)),
Ok(umax()),
"max-1 + 1 = max"
);
assert!(run_checked(emit_uint256_checked_add, &umax(), &BigInt::from(1)).is_err());
assert!(run_checked(emit_uint256_checked_add, &pow2(255), &pow2(255)).is_err());
assert!(run_checked(emit_uint256_checked_add, &umax(), &umax()).is_err());
assert!(run_checked(emit_uint256_checked_add, &(pow2(255) + 7), &pow2(255)).is_err());
}
#[test]
fn checked_sub_detects_underflow() {
assert_eq!(run_checked(emit_uint256_checked_sub, &big("5"), &big("3")), Ok(big("2")));
assert_eq!(run_checked(emit_uint256_checked_sub, &umax(), &umax()), Ok(big("0")));
assert_eq!(
run_checked(emit_uint256_checked_sub, &pow2(255), &BigInt::from(1)),
Ok(pow2(255) - 1),
"2^255 - 1 crosses the sign boundary"
);
assert_eq!(run_checked(emit_uint256_checked_sub, &umax(), &big("1")), Ok(umax() - 1));
assert!(run_checked(emit_uint256_checked_sub, &big("3"), &big("5")).is_err());
assert!(run_checked(emit_uint256_checked_sub, &big("0"), &big("1")).is_err());
assert!(run_checked(emit_uint256_checked_sub, &big("0"), &umax()).is_err());
assert!(run_checked(emit_uint256_checked_sub, &(pow2(255) - 1), &pow2(255)).is_err());
}
fn run_mul(a: &BigInt, b: &BigInt) -> BigInt {
let mut code = Vec::new();
emit_pushint256_le(&mut code, &u256_le(a));
emit_pushint256_le(&mut code, &u256_le(b));
emit_uint256_unchecked_mul(&mut code);
code.push(0x40);
let st = faithful_run(&code).expect("faithful run");
let signed = st.last().cloned().expect("result");
let m = modulus();
((signed % &m) + &m) % &m
}
#[test]
fn unchecked_mul_wraps_mod_2_256_including_large() {
let m = modulus();
let cases = [
(BigInt::from(0), umax()),
(BigInt::from(1), umax()),
(BigInt::from(6), BigInt::from(7)),
(pow2(64), pow2(64)), (pow2(128), pow2(128)), (pow2(200), pow2(100)), (umax(), BigInt::from(2)), (umax(), umax()), (pow2(255), BigInt::from(3)), (big("123456789012345678901234567890"), big("987654321098765432109876543210")),
(pow2(130) + 7, pow2(130) + 9),
];
for (a, b) in cases {
let expect = (&a * &b) % &m;
assert_eq!(run_mul(&a, &b), expect, "mul({a}, {b})");
}
}
#[test]
fn checked_mul_detects_overflow() {
assert_eq!(run_checked(emit_uint256_checked_mul, &big("6"), &big("7")), Ok(big("42")));
assert_eq!(run_checked(emit_uint256_checked_mul, &umax(), &big("1")), Ok(umax()));
assert_eq!(run_checked(emit_uint256_checked_mul, &big("0"), &umax()), Ok(big("0")));
assert_eq!(
run_checked(emit_uint256_checked_mul, &(pow2(255) - 1), &big("2")),
Ok(pow2(256) - 2),
"(2^255-1)*2 = 2^256-2 (still fits)"
);
assert_eq!(
run_checked(emit_uint256_checked_mul, &pow2(128), &(pow2(128) - 1)),
Ok(pow2(256) - pow2(128)),
"2^128 * (2^128-1) = 2^256-2^128 (fits)"
);
assert!(run_checked(emit_uint256_checked_mul, &pow2(128), &pow2(128)).is_err());
assert!(run_checked(emit_uint256_checked_mul, &pow2(255), &big("2")).is_err());
assert!(run_checked(emit_uint256_checked_mul, &umax(), &big("2")).is_err());
assert!(run_checked(emit_uint256_checked_mul, &umax(), &umax()).is_err());
assert!(run_checked(emit_uint256_checked_mul, &(pow2(200)), &(pow2(100))).is_err());
}
fn run_shr(a: &BigInt, n: u32) -> BigInt {
let mut code = Vec::new();
emit_pushint256_le(&mut code, &u256_le(a));
emit_pushint256_le(&mut code, &u256_le(&BigInt::from(n)));
emit_uint256_logical_shr(&mut code);
code.push(0x40);
let st = faithful_run(&code).expect("faithful run");
let signed = st.last().cloned().expect("result");
let m = modulus();
((signed % &m) + &m) % &m
}
fn run_shl(a: &BigInt, n: u32) -> BigInt {
let mut code = Vec::new();
emit_pushint256_le(&mut code, &u256_le(a));
emit_pushint256_le(&mut code, &u256_le(&BigInt::from(n)));
emit_uint256_shl(&mut code);
code.push(0x40);
let st = faithful_run(&code).expect("faithful run");
let signed = st.last().cloned().expect("result");
let m = modulus();
((signed % &m) + &m) % &m
}
#[test]
fn shl_wraps_mod_2_256_including_large() {
let m = modulus();
let cases: [(BigInt, u32); 13] = [
(BigInt::from(1), 0),
(BigInt::from(1), 1),
(BigInt::from(1), 127),
(BigInt::from(1), 128),
(BigInt::from(1), 255), (BigInt::from(3), 255), (umax(), 1), (umax(), 128),
(umax(), 255), (pow2(100), 100), (pow2(100), 200), (big("123456789012345678901234567890"), 64),
(pow2(200) + BigInt::from(7), 60),
];
for (a, n) in cases {
let expect = ((&a << n) % &m + &m) % &m;
assert_eq!(run_shl(&a, n), expect, "shl({a}, {n})");
}
}
#[test]
fn logical_shr_is_unsigned_including_large() {
let cases: [(BigInt, u32); 12] = [
(umax(), 0),
(umax(), 1),
(umax(), 128),
(umax(), 255),
(umax(), 256), (pow2(255), 1), (pow2(255), 255), (pow2(255) + BigInt::from(1), 255), (pow2(200), 100), (pow2(128), 64), (big("115792089237316195423570985008687907853269984665640564039457584007913129639000"), 8),
(BigInt::from(0), 5),
];
for (a, n) in cases {
let expect = &a >> n; assert_eq!(run_shr(&a, n), expect, "shr({a}, {n})");
}
}
fn run_divmod(a: &BigInt, b: &BigInt) -> Result<(BigInt, BigInt), String> {
let mut code = Vec::new();
emit_pushint256_le(&mut code, &u256_le(a));
emit_pushint256_le(&mut code, &u256_le(b));
let call_divmod = emit_branch_l_placeholder(&mut code, 0x35); code.push(0x40); let divmod_off = code.len();
let (add_sites, sub_sites) = emit_uint256_divmod_body(&mut code);
let add_off = code.len();
emit_uint256_unchecked_add(&mut code);
code.push(0x40); let sub_off = code.len();
emit_uint256_unchecked_sub(&mut code);
code.push(0x40); patch_rel32(&mut code, call_divmod, divmod_off);
for s in add_sites {
patch_rel32(&mut code, s, add_off);
}
for s in sub_sites {
patch_rel32(&mut code, s, sub_off);
}
let st = faithful_run(&code)?;
if st.len() < 2 {
return Err("expected [q, r]".into());
}
let r = st[st.len() - 1].clone();
let q = st[st.len() - 2].clone();
let m = modulus();
let norm = |v: BigInt| ((v % &m) + &m) % &m;
Ok((norm(q), norm(r)))
}
#[test]
fn unsigned_divmod_including_large() {
let cases = [
(big("100"), big("7")),
(big("5"), big("7")), (umax(), big("1")), (umax(), big("2")),
(umax(), big("3")),
(umax(), big("1000000000000000000")), (pow2(255), big("2")), (pow2(255) + BigInt::from(1), big("2")),
(pow2(200), big("13")),
(umax(), umax()), (umax(), pow2(255)), (pow2(255), pow2(255)), (pow2(255) - BigInt::from(1), pow2(255)), (pow2(200), pow2(255) + BigInt::from(99)), (big("0"), umax()), ];
for (a, b) in cases {
let (q, r) = run_divmod(&a, &b).expect("no panic");
assert_eq!(q, &a / &b, "div({a}, {b})");
assert_eq!(r, &a % &b, "mod({a}, {b})");
assert_eq!(&q * &b + &r, a, "q*b+r == a for ({a},{b})");
assert!(r < b, "r < b for ({a},{b})");
}
}
#[test]
fn divmod_by_zero_panics() {
assert!(run_divmod(&big("5"), &big("0")).is_err());
assert!(run_divmod(&umax(), &big("0")).is_err());
assert!(run_divmod(&big("0"), &big("0")).is_err());
}
#[test]
fn unsigned_le_ge_including_large() {
assert!(run_cmp(emit_uint256_unsigned_le, &BigInt::from(7), &BigInt::from(7)));
assert!(run_cmp(emit_uint256_unsigned_le, &BigInt::from(5), &umax()));
assert!(!run_cmp(emit_uint256_unsigned_le, &umax(), &BigInt::from(5)));
assert!(run_cmp(emit_uint256_unsigned_le, &umax(), &umax()));
assert!(run_cmp(emit_uint256_unsigned_ge, &BigInt::from(7), &BigInt::from(7)));
assert!(run_cmp(emit_uint256_unsigned_ge, &umax(), &BigInt::from(5)));
assert!(!run_cmp(emit_uint256_unsigned_ge, &BigInt::from(5), &umax()));
assert!(run_cmp(emit_uint256_unsigned_ge, &pow2(255), &(pow2(255) - 1)));
}
}