stoffel-vm 0.1.3

Register-based virtual machine runtime and MPC execution engine for Stoffel programs
Documentation
use super::error::{type_error, unsupported, ShareRuntimeProvider, ValueOpError, ValueOpResult};
use super::share_operands::{contains_share_operand, matching_share_pair};
use stoffel_vm_types::core_types::{ShareData, ShareType, Value};

pub(crate) fn bit_and(
    left: &Value,
    right: &Value,
    share_runtime: ShareRuntimeProvider<'_>,
) -> ValueOpResult<Value> {
    if let Some(result) = try_clear_bit_and(left, right) {
        return result;
    }

    if let Some(pair) = matching_share_pair("AND", left, right)? {
        ensure_secret_bool(pair.share_type, "Bitwise AND")?;
        let data = share_runtime()?.multiply_share_data(
            pair.share_type,
            pair.left_data,
            pair.right_data,
        )?;
        return Ok(Value::Share(pair.share_type, data));
    }

    if contains_share_operand(left, right) {
        unsupported("Bitwise AND is only supported on secret bool shares")
    } else {
        type_error("AND")
    }
}

pub(crate) fn bit_or(
    left: &Value,
    right: &Value,
    share_runtime: ShareRuntimeProvider<'_>,
) -> ValueOpResult<Value> {
    if let Some(result) = try_clear_bit_or(left, right) {
        return result;
    }

    if let Some(pair) = matching_share_pair("OR", left, right)? {
        ensure_secret_bool(pair.share_type, "Bitwise OR")?;
        let product = share_runtime()?.multiply_share_data(
            pair.share_type,
            pair.left_data,
            pair.right_data,
        )?;
        let data = bool_or_data(
            share_runtime,
            pair.share_type,
            pair.left_data,
            pair.right_data,
            &product,
        )?;
        return Ok(Value::Share(pair.share_type, data));
    }

    if contains_share_operand(left, right) {
        unsupported("Bitwise OR is only supported on secret bool shares")
    } else {
        type_error("OR")
    }
}

pub(crate) fn bit_xor(
    left: &Value,
    right: &Value,
    share_runtime: ShareRuntimeProvider<'_>,
) -> ValueOpResult<Value> {
    if let Some(result) = try_clear_bit_xor(left, right) {
        return result;
    }

    if let Some(pair) = matching_share_pair("XOR", left, right)? {
        ensure_secret_bool(pair.share_type, "Bitwise XOR")?;
        let product = share_runtime()?.multiply_share_data(
            pair.share_type,
            pair.left_data,
            pair.right_data,
        )?;
        let data = bool_xor_data(
            share_runtime,
            pair.share_type,
            pair.left_data,
            pair.right_data,
            &product,
        )?;
        return Ok(Value::Share(pair.share_type, data));
    }

    if contains_share_operand(left, right) {
        unsupported("Bitwise XOR is only supported on secret bool shares")
    } else {
        type_error("XOR")
    }
}

pub(crate) fn bit_not(
    value: &Value,
    share_runtime: ShareRuntimeProvider<'_>,
) -> ValueOpResult<Value> {
    if let Some(result) = try_clear_bit_not(value) {
        return result;
    }

    if let Value::Share(share_type, share_data) = value {
        ensure_secret_bool(*share_type, "Bitwise NOT")?;
        let data = share_runtime()?.scalar_sub_data(*share_type, 1, share_data)?;
        Ok(Value::Share(*share_type, data))
    } else if matches!(value, Value::Share(_, _)) {
        unsupported("Bitwise NOT is only supported on secret bool shares")
    } else {
        type_error("NOT")
    }
}

pub(crate) fn shl(left: &Value, right: &Value) -> ValueOpResult<Value> {
    if let Some(result) = try_clear_shl(left, right) {
        return result;
    }

    if contains_share_operand(left, right) {
        unsupported("Left shift is not supported on secret shares")
    } else {
        type_error("SHL")
    }
}

pub(crate) fn shr(left: &Value, right: &Value) -> ValueOpResult<Value> {
    if let Some(result) = try_clear_shr(left, right) {
        return result;
    }

    if contains_share_operand(left, right) {
        unsupported("Right shift is not supported on secret shares")
    } else {
        type_error("SHR")
    }
}

macro_rules! integer_bitwise_arms {
    ($left:expr, $right:expr, $op:tt) => {
        match ($left, $right) {
            (Value::I64(a), Value::I64(b)) => Some(Ok(Value::I64(a $op b))),
            (Value::I32(a), Value::I32(b)) => Some(Ok(Value::I32(a $op b))),
            (Value::I16(a), Value::I16(b)) => Some(Ok(Value::I16(a $op b))),
            (Value::I8(a), Value::I8(b)) => Some(Ok(Value::I8(a $op b))),
            (Value::U8(a), Value::U8(b)) => Some(Ok(Value::U8(a $op b))),
            (Value::U16(a), Value::U16(b)) => Some(Ok(Value::U16(a $op b))),
            (Value::U32(a), Value::U32(b)) => Some(Ok(Value::U32(a $op b))),
            (Value::U64(a), Value::U64(b)) => Some(Ok(Value::U64(a $op b))),
            _ => None,
        }
    };
}

pub(crate) fn try_clear_bit_and(left: &Value, right: &Value) -> Option<ValueOpResult<Value>> {
    if let (Value::Bool(a), Value::Bool(b)) = (left, right) {
        return Some(Ok(Value::Bool(*a && *b)));
    }
    integer_bitwise_arms!(left, right, &)
}

pub(crate) fn try_clear_bit_or(left: &Value, right: &Value) -> Option<ValueOpResult<Value>> {
    if let (Value::Bool(a), Value::Bool(b)) = (left, right) {
        return Some(Ok(Value::Bool(*a || *b)));
    }
    integer_bitwise_arms!(left, right, |)
}

pub(crate) fn try_clear_bit_xor(left: &Value, right: &Value) -> Option<ValueOpResult<Value>> {
    if let (Value::Bool(a), Value::Bool(b)) = (left, right) {
        return Some(Ok(Value::Bool(a ^ b)));
    }
    integer_bitwise_arms!(left, right, ^)
}

pub(crate) fn try_clear_bit_not(value: &Value) -> Option<ValueOpResult<Value>> {
    Some(match value {
        Value::I64(value) => Ok(Value::I64(!*value)),
        Value::I32(value) => Ok(Value::I32(!*value)),
        Value::I16(value) => Ok(Value::I16(!*value)),
        Value::I8(value) => Ok(Value::I8(!*value)),
        Value::U8(value) => Ok(Value::U8(!*value)),
        Value::U16(value) => Ok(Value::U16(!*value)),
        Value::U32(value) => Ok(Value::U32(!*value)),
        Value::U64(value) => Ok(Value::U64(!*value)),
        Value::Bool(value) => Ok(Value::Bool(!*value)),
        _ => return None,
    })
}

macro_rules! integer_shift_arms {
    ($left:expr, $right:expr, $operation:expr, $method:ident) => {
        match ($left, $right) {
            (Value::I64(a), Value::I64(b)) => {
                Some(checked_shift($operation, *a, *b, i64::$method).map(Value::I64))
            }
            (Value::I32(a), Value::I32(b)) => {
                Some(checked_shift($operation, *a, i64::from(*b), i32::$method).map(Value::I32))
            }
            (Value::I16(a), Value::I16(b)) => {
                Some(checked_shift($operation, *a, i64::from(*b), i16::$method).map(Value::I16))
            }
            (Value::I8(a), Value::I8(b)) => {
                Some(checked_shift($operation, *a, i64::from(*b), i8::$method).map(Value::I8))
            }
            (Value::U8(a), Value::U8(b)) => {
                Some(checked_shift($operation, *a, i64::from(*b), u8::$method).map(Value::U8))
            }
            (Value::U16(a), Value::U16(b)) => {
                Some(checked_shift($operation, *a, i64::from(*b), u16::$method).map(Value::U16))
            }
            (Value::U32(a), Value::U32(b)) => {
                Some(checked_shift($operation, *a, i64::from(*b), u32::$method).map(Value::U32))
            }
            (Value::U64(a), Value::U64(b)) => Some(match i64::try_from(*b) {
                Ok(amount) => checked_shift($operation, *a, amount, u64::$method).map(Value::U64),
                Err(_) => Err(ValueOpError::ShiftOutOfRange {
                    operation: $operation,
                    amount: u32::MAX,
                }),
            }),
            _ => None,
        }
    };
}

pub(crate) fn try_clear_shl(left: &Value, right: &Value) -> Option<ValueOpResult<Value>> {
    integer_shift_arms!(left, right, "SHL", checked_shl)
}

pub(crate) fn try_clear_shr(left: &Value, right: &Value) -> Option<ValueOpResult<Value>> {
    integer_shift_arms!(left, right, "SHR", checked_shr)
}

fn checked_shift_amount(operation: &'static str, amount: i64) -> ValueOpResult<u32> {
    u32::try_from(amount).map_err(|_| ValueOpError::InvalidShiftAmount { operation, amount })
}

fn checked_shift<T>(
    operation: &'static str,
    value: T,
    amount: i64,
    shift: impl FnOnce(T, u32) -> Option<T>,
) -> ValueOpResult<T> {
    let amount = checked_shift_amount(operation, amount)?;
    shift(value, amount).ok_or(ValueOpError::ShiftOutOfRange { operation, amount })
}

pub(crate) fn bool_xor_data(
    share_runtime: ShareRuntimeProvider<'_>,
    share_type: ShareType,
    left_data: &ShareData,
    right_data: &ShareData,
    product_data: &ShareData,
) -> ValueOpResult<ShareData> {
    ensure_secret_bool(share_type, "Bitwise XOR")?;
    let runtime = share_runtime()?;
    let sum = runtime.add_data(share_type, left_data, right_data)?;
    let doubled_product = runtime.mul_scalar_data(share_type, product_data, 2)?;
    Ok(runtime.sub_data(share_type, &sum, &doubled_product)?)
}

pub(crate) fn bool_or_data(
    share_runtime: ShareRuntimeProvider<'_>,
    share_type: ShareType,
    left_data: &ShareData,
    right_data: &ShareData,
    product_data: &ShareData,
) -> ValueOpResult<ShareData> {
    ensure_secret_bool(share_type, "Bitwise OR")?;
    let runtime = share_runtime()?;
    let sum = runtime.add_data(share_type, left_data, right_data)?;
    Ok(runtime.sub_data(share_type, &sum, product_data)?)
}

fn ensure_secret_bool(share_type: ShareType, operation: &'static str) -> ValueOpResult<()> {
    if share_type == ShareType::boolean() {
        Ok(())
    } else {
        unsupported(match operation {
            "Bitwise AND" => "Bitwise AND is only supported on secret bool shares",
            "Bitwise OR" => "Bitwise OR is only supported on secret bool shares",
            "Bitwise XOR" => "Bitwise XOR is only supported on secret bool shares",
            "Bitwise NOT" => "Bitwise NOT is only supported on secret bool shares",
            _ => "Bitwise share operation is only supported on secret bool shares",
        })
    }
}