stoffel-vm 0.1.0

Register-based virtual machine runtime and MPC execution engine for Stoffel programs
Documentation
use super::HoneyBadgerMpcEngine;
use crate::net::curve::SupportedMpcField;
use crate::net::mpc_engine::{MpcEngineOperationResultExt, MpcEngineReservation, MpcEngineResult};
use crate::net::reservation::{ReservationGrant, ReservationRegistry};
use crate::storage::preproc::{self, PreprocKeyScope};
use ark_ec::{CurveGroup, PrimeGroup};
use stoffelmpc_mpc::honeybadger::robust_interpolate::robust_interpolate::RobustShare;
use stoffelnet::network_utils::ClientId;

impl<F, G> HoneyBadgerMpcEngine<F, G>
where
    F: SupportedMpcField,
    G: CurveGroup<ScalarField = F> + PrimeGroup + Send + Sync + 'static,
{
    async fn persist_reservation_state_if_configured(&self) -> Result<(), String> {
        let reg_guard = self.reservation.read().await;
        let Some(reg) = reg_guard.as_ref() else {
            return Ok(());
        };
        let store = self.preproc_store.read().await.clone();
        if let Some(store) = store {
            reg.persist(store.as_ref())
                .await
                .map_err(|e| e.to_string())?;
        }
        Ok(())
    }
}

#[async_trait::async_trait]
impl<F, G> MpcEngineReservation for HoneyBadgerMpcEngine<F, G>
where
    F: SupportedMpcField,
    G: CurveGroup<ScalarField = F> + PrimeGroup + Send + Sync + 'static,
{
    async fn init_reservations(
        &self,
        program_hash: [u8; 32],
        capacity: u64,
    ) -> MpcEngineResult<()> {
        async {
            let store = self.preproc_store.read().await.clone();
            let persistent_identity = self.persistent_identity();
            if let Some(store) = store {
                if let Some(restored) =
                    ReservationRegistry::load(store.as_ref(), &program_hash, persistent_identity)
                        .await
                        .map_err(|e| e.to_string())?
                {
                    *self.reservation.write().await = Some(restored);
                    return Ok::<(), String>(());
                }
            }
            *self.reservation.write().await = Some(ReservationRegistry::new(
                program_hash,
                persistent_identity,
                capacity,
            ));
            Ok::<(), String>(())
        }
        .await
        .map_mpc_engine_operation("init_reservations")
    }

    async fn reserve_masks(
        &self,
        client_id: ClientId,
        n: u64,
    ) -> MpcEngineResult<ReservationGrant> {
        async {
            let guard = self.reservation.read().await;
            let reg = guard.as_ref().ok_or("reservations not initialized")?;
            let grant = reg
                .reserve(self.client_identity(client_id), n)
                .await
                .map_err(|e| e.to_string())?;
            drop(guard);
            self.persist_reservation_state_if_configured().await?;
            Ok::<ReservationGrant, String>(grant)
        }
        .await
        .map_mpc_engine_operation("reserve_masks")
    }

    async fn get_mask_share(&self, index: u64) -> MpcEngineResult<Vec<u8>> {
        async {
            let store = self.preproc_store.read().await.clone();
            let hash = *self.program_hash.read().await;
            let persistent_identity = self.persistent_identity();
            let (store, hash) = match (store, hash) {
                (Some(s), Some(h)) => (s, h),
                _ => return Err::<Vec<u8>, String>("preproc store not configured".to_owned()),
            };

            let key = PreprocKeyScope::new(
                hash,
                F::field_kind(),
                self.topology.n_parties(),
                self.topology.threshold(),
                persistent_identity,
            )
            .random_share();
            let blob = store.load(&key).await?.ok_or("no random shares stored")?;
            let index = preproc::u32_index(index, "preprocessing random share index")?;
            store.reserve_at(&key, index, 1).await?;
            let share =
                preproc::deserialize_one_robust_share::<F>(&blob.data, blob.meta.item_size, index)?;
            Self::encode_share(&share)
        }
        .await
        .map_mpc_engine_operation("get_mask_share")
    }

    async fn submit_masked_input(
        &self,
        client_id: ClientId,
        index: u64,
        value: Vec<u8>,
    ) -> MpcEngineResult<()> {
        async {
            let guard = self.reservation.read().await;
            let reg = guard.as_ref().ok_or("reservations not initialized")?;
            reg.submit_masked_input(self.client_identity(client_id), index, value)
                .await
                .map_err(|e| e.to_string())?;
            drop(guard);
            self.persist_reservation_state_if_configured().await
        }
        .await
        .map_mpc_engine_operation("submit_masked_input")
    }

    async fn consume_masked_inputs(&self, indices: &[u64]) -> MpcEngineResult<Vec<(u64, Vec<u8>)>> {
        async {
            let masked_inputs = {
                let reg_guard = self.reservation.read().await;
                let reg = reg_guard.as_ref().ok_or("reservations not initialized")?;
                let mut inputs = Vec::with_capacity(indices.len());
                for &idx in indices {
                    let masked_input = reg
                        .get_masked_input(idx)
                        .await
                        .ok_or_else(|| format!("no masked input for index {idx}"))?;
                    inputs.push((idx, masked_input));
                }
                inputs
            };

            let store = self.preproc_store.read().await.clone();
            let hash = *self.program_hash.read().await;
            let persistent_identity = self.persistent_identity();
            let (store, hash) = match (store, hash) {
                (Some(s), Some(h)) => (s, h),
                _ => {
                    return Err::<Vec<(u64, Vec<u8>)>, String>(
                        "preproc store not configured".to_owned(),
                    );
                }
            };
            let key = PreprocKeyScope::new(
                hash,
                F::field_kind(),
                self.topology.n_parties(),
                self.topology.threshold(),
                persistent_identity,
            )
            .random_share();
            let blob = store.load(&key).await?.ok_or("no random shares stored")?;

            let mut result = Vec::with_capacity(indices.len());
            for (idx, masked_input_bytes) in &masked_inputs {
                let mask_index = preproc::u32_index(*idx, "preprocessing masked input index")?;
                let mask_share = preproc::deserialize_one_robust_share::<F>(
                    &blob.data,
                    blob.meta.item_size,
                    mask_index,
                )?;
                let masked_input = Self::decode_share(masked_input_bytes)?;

                let input_elem = masked_input.share[0] - mask_share.share[0];
                let input_share = RobustShare::new(input_elem, mask_share.id, mask_share.degree);
                result.push((*idx, Self::encode_share(&input_share)?));
            }

            {
                let reg_guard = self.reservation.read().await;
                let reg = reg_guard.as_ref().ok_or("reservations not initialized")?;
                reg.consume(indices).await.map_err(|e| e.to_string())?;
            }
            self.persist_reservation_state_if_configured().await?;
            let all_reserved_slots_consumed = {
                let reg_guard = self.reservation.read().await;
                let reg = reg_guard.as_ref().ok_or("reservations not initialized")?;
                reg.all_reserved_slots_consumed().await
            };
            // Keep the mask blob while any allocated slot may still need it for unmasking.
            if all_reserved_slots_consumed && store.available(&key).await? == 0 {
                store.delete(&key).await?;
            }
            Ok::<Vec<(u64, Vec<u8>)>, String>(result)
        }
        .await
        .map_mpc_engine_operation("consume_masked_inputs")
    }

    async fn available_masks(&self) -> u64 {
        let guard = self.reservation.read().await;
        match guard.as_ref() {
            Some(reg) => reg.available().await,
            None => 0,
        }
    }

    async fn persist_reservations(&self) -> MpcEngineResult<()> {
        async {
            let reg_guard = self.reservation.read().await;
            let reg = match reg_guard.as_ref() {
                Some(r) => r,
                None => return Ok::<(), String>(()),
            };
            let store = self.preproc_store.read().await.clone();
            if let Some(store) = store {
                reg.persist(store.as_ref())
                    .await
                    .map_err(|e| e.to_string())?;
            }
            Ok::<(), String>(())
        }
        .await
        .map_mpc_engine_operation("persist_reservations")
    }
}