arcis-compiler 0.11.0

A framework for writing secure multi-party computation (MPC) circuits to be executed on the Arcium network.
Documentation
use crate::core::{compile::*, instruction::ArcisInstruction, ir::IntermediateRepresentation};
use arcis_interface::path_with_hash::PathWithHash;
use serde::{Deserialize, Serialize};
use std::io::Write;

#[derive(Debug)]
pub struct ArcisCOptions {
    /// The output directory for the compiled circuit binaries.
    pub out_dir: Option<String>,
}

/// Compile a program.
fn compile(ir: IntermediateRepresentation) -> IntermediateRepresentation {
    Compiler::optimize_into_circuitable(ir)
}

pub fn write_ir(
    ir: IntermediateRepresentation,
    circuit_name: &str,
    arcisc_options: &ArcisCOptions,
) -> PathWithHash {
    match ir.write_bytes(circuit_name, arcisc_options.out_dir.clone()) {
        Err(e) => {
            panic!("Failed to write IR for '{}': {}", circuit_name, e);
        }
        Ok(s) => s,
    }
}

pub(crate) fn compile_and_write(
    path: &str,
    ir: IntermediateRepresentation,
) -> Result<CompilationOutput, std::io::Error> {
    let compiled_ir = compile(ir);
    let (temp_circuit, metadata, profile) = Compiler::ir_to_async_mpc_circuit(&compiled_ir);
    let compiled_circuit = Compiler::improve_async_mpc_circuit(temp_circuit);
    let circuit = ArcisInstruction {
        circuit: compiled_circuit,
        metadata,
    };

    let profile_bytes = serde_json::to_string(&profile).expect("Serialization failed.");
    let profile_file = PathWithHash::new(
        profile_bytes.as_bytes(),
        CompilationOutput::profile_path(path),
    )?;

    let circuit_bytes = circuit.to_bytes();
    let arcis_file = PathWithHash::new(&circuit_bytes, CompilationOutput::arcis_path(path))?;

    let hash = arcis_file.hash;
    let hash_json = serde_json::to_string(&hash).expect("Failed to serialize circuit hash");
    let hash_file = PathWithHash::new(hash_json.as_bytes(), CompilationOutput::hash_path(path))?;

    let profile_info = circuit.profile_info();
    let weight_json = profile_info.to_json();
    let weight_file =
        PathWithHash::new(weight_json.as_bytes(), CompilationOutput::weight_path(path))?;

    let weight = profile_info.weight();
    println!(
        "Built encrypted instruction weighing {:>12} ACUs named \"{}\".",
        weight,
        std::path::Path::new(path)
            .file_stem()
            .unwrap_or(std::ffi::OsStr::new("no-name"))
            .display(),
    );

    Ok(CompilationOutput {
        arcis_hash: arcis_file.hash,
        profile_hash: profile_file.hash,
        hash_hash: hash_file.hash,
        weight_hash: weight_file.hash,
    })
}

type Hash = [u8; 32];

#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct CompilationOutput {
    arcis_hash: Hash,
    profile_hash: Hash,
    hash_hash: Hash,
    weight_hash: Hash,
}

impl CompilationOutput {
    fn arcis_path(prefix: &str) -> String {
        format!("{}.arcis", prefix)
    }
    fn profile_path(prefix: &str) -> String {
        format!("{}.profile.json", prefix)
    }
    fn hash_path(prefix: &str) -> String {
        format!("{}.hash", prefix)
    }
    fn weight_path(prefix: &str) -> String {
        format!("{}.weight", prefix)
    }

    fn check_up_to_date(&self, prefix: &str) -> bool {
        fn check(path: String, hash: Hash) -> bool {
            PathWithHash { path, hash }.check()
        }
        check(Self::arcis_path(prefix), self.arcis_hash)
            && check(Self::profile_path(prefix), self.profile_hash)
            && check(Self::weight_path(prefix), self.weight_hash)
            && check(Self::hash_path(prefix), self.hash_hash)
    }
}
#[derive(Debug, Serialize, Deserialize)]
struct CompilationState {
    ir_hash: Hash,
    output: CompilationOutput,
}

impl CompilationState {
    fn path(prefix: &str) -> String {
        format!("{}.lock", prefix)
    }
    fn up_to_date(ir_hash: Hash, prefix: &str) -> bool {
        let state_file_path = Self::path(prefix);
        let Ok(state_file) = std::fs::read(&state_file_path) else {
            return false;
        };
        let Ok(state) = bincode::deserialize::<CompilationState>(&state_file) else {
            return false;
        };
        ir_hash == state.ir_hash && state.output.check_up_to_date(prefix)
    }
    fn save(&self, prefix: &str) -> std::io::Result<()> {
        let state_bytes = bincode::serialize(&self).expect("Serialization failed");
        let state_file_path = Self::path(prefix);
        let mut file = std::fs::File::create(&state_file_path)?;
        file.write_all(&state_bytes)?;
        Ok(())
    }
}

pub const TOUCH_FILE_EXTENSION: &str = ".touch";

pub fn compile_and_write_all(
    ir_paths: &[(&str, Hash)],
    other_paths: &[(&str, Hash)],
) -> Result<(), Box<dyn std::error::Error>> {
    // We check other paths first so that we fail fast, not after compiling.
    for &(path, hash) in other_paths {
        PathWithHash {
            path: path.to_string(),
            hash,
        }
        .checked_read()
        .map_err(|e| format!("Failed reading file at path \"{}\": {}", path, e))?;
    }

    for &(path, hash) in ir_paths {
        let ir_path = PathWithHash {
            path: path.to_string(),
            hash,
        };
        let prefix = path.strip_suffix(".arcis.ir").ok_or_else(|| {
            format!(
                "Internal Error. IR file path should end in \".arcis.ir\", is \"{}\" instead.",
                path
            )
        })?;
        std::fs::write(format!("{}{}", prefix, TOUCH_FILE_EXTENSION), [])?;
        if CompilationState::up_to_date(hash, prefix) {
            println!(
                "Encrypted instruction {} is already up-to-date and compiled.",
                prefix,
            );
            continue;
        }
        let ir_bytes = ir_path
            .checked_read()
            .map_err(|e| format!("Failed reading IR at path \"{}\": {}", path, e))?;
        let ir = IntermediateRepresentation::from_bytes(&ir_bytes)?;
        let output = compile_and_write(prefix, ir)?;
        let state = CompilationState {
            ir_hash: hash,
            output,
        };
        state.save(prefix)?;
    }
    Ok(())
}

pub fn read_ir(path: &str) -> Result<IntermediateRepresentation, std::io::Error> {
    let circ = std::fs::read(path)?;
    IntermediateRepresentation::from_bytes(&circ)
}