stoffel-vm 0.1.2

Register-based virtual machine runtime and MPC execution engine for Stoffel programs
Documentation
use super::*;
use crate::net::curve::MpcFieldKind;
use crate::net::mpc_engine::{MpcSessionTopology, ShareAlgebraResult};
use std::sync::{Arc, Mutex};
use stoffel_vm_types::core_types::{ClearShareInput, ClearShareValue, ShareData, ShareType, Value};

#[derive(Default)]
struct PermissiveEngine {
    binary_calls: Mutex<usize>,
    batch_calls: Mutex<usize>,
    interpolate_calls: Mutex<usize>,
}

impl MpcEngine for PermissiveEngine {
    fn protocol_name(&self) -> &'static str {
        "permissive"
    }

    fn topology(&self) -> MpcSessionTopology {
        MpcSessionTopology::try_new(0, 0, 3, 1).expect("test topology should be valid")
    }

    fn is_ready(&self) -> bool {
        true
    }

    fn start(&self) -> crate::net::mpc_engine::MpcEngineResult<()> {
        Ok(())
    }

    fn input_share(
        &self,
        _clear: ClearShareInput,
    ) -> crate::net::mpc_engine::MpcEngineResult<ShareData> {
        Err(crate::net::mpc_engine::MpcEngineError::operation_failed(
            "test_mpc_engine",
            "not implemented",
        ))
    }

    fn open_share(
        &self,
        _ty: ShareType,
        _share_bytes: &[u8],
    ) -> crate::net::mpc_engine::MpcEngineResult<ClearShareValue> {
        Err(crate::net::mpc_engine::MpcEngineError::operation_failed(
            "test_mpc_engine",
            "not implemented",
        ))
    }

    fn batch_open_shares(
        &self,
        _ty: ShareType,
        shares: &[Vec<u8>],
    ) -> crate::net::mpc_engine::MpcEngineResult<Vec<ClearShareValue>> {
        *self.batch_calls.lock().unwrap() += 1;
        Ok(vec![ClearShareValue::Integer(0); shares.len()])
    }

    fn add_share_local(
        &self,
        _ty: ShareType,
        _lhs_bytes: &[u8],
        _rhs_bytes: &[u8],
    ) -> ShareAlgebraResult<Vec<u8>> {
        *self.binary_calls.lock().unwrap() += 1;
        Ok(vec![9])
    }

    fn interpolate_shares_local(
        &self,
        _ty: ShareType,
        _shares: &[Vec<u8>],
    ) -> ShareAlgebraResult<Value> {
        *self.interpolate_calls.lock().unwrap() += 1;
        Ok(Value::I64(0))
    }

    fn field_kind(&self) -> MpcFieldKind {
        MpcFieldKind::Bls12_381Fr
    }
}

fn runtime_for(engine: &PermissiveEngine) -> MpcShareRuntime<'_> {
    MpcShareRuntime::from_configured(Some(engine)).expect("configured engine")
}

fn feldman_share_data() -> ShareData {
    ShareData::Feldman {
        data: vec![2].into(),
        commitments: vec![vec![3]].into(),
    }
}

#[test]
fn binary_ops_reject_mixed_share_data_formats_before_backend_dispatch() {
    let engine = Arc::new(PermissiveEngine::default());
    let runtime = runtime_for(engine.as_ref());

    let err = runtime
        .add_data(
            ShareType::secret_int(64),
            &ShareData::Opaque(vec![1].into()),
            &feldman_share_data(),
        )
        .expect_err("mixed share data formats should be rejected");

    assert_eq!(
        err.to_string(),
        "Share data format mismatch in add_share_local: left is opaque, right is feldman"
    );
    assert_eq!(*engine.binary_calls.lock().unwrap(), 0);
}

#[test]
fn batch_open_rejects_mixed_share_data_formats_before_backend_dispatch() {
    let engine = Arc::new(PermissiveEngine::default());
    let runtime = runtime_for(engine.as_ref());

    let err = runtime
        .batch_open_share_data(
            ShareType::secret_int(64),
            &[ShareData::Opaque(vec![1].into()), feldman_share_data()],
        )
        .expect_err("mixed batch share data formats should be rejected");

    assert_eq!(
        err.to_string(),
        "Share data format mismatch in batch_open_shares at index 1: expected opaque, got feldman"
    );
    assert_eq!(*engine.batch_calls.lock().unwrap(), 0);
}

#[test]
fn interpolate_rejects_mixed_share_data_formats_before_backend_dispatch() {
    let engine = Arc::new(PermissiveEngine::default());
    let runtime = runtime_for(engine.as_ref());

    let err = runtime
        .interpolate_share_data_local(
            ShareType::secret_int(64),
            &[ShareData::Opaque(vec![1].into()), feldman_share_data()],
        )
        .expect_err("mixed interpolation share data formats should be rejected");

    assert_eq!(
        err.to_string(),
        "Share data format mismatch in interpolate_shares_local at index 1: expected opaque, got feldman"
    );
    assert_eq!(*engine.interpolate_calls.lock().unwrap(), 0);
}