use aes::cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit};
use aes::Aes128;
use stoffel::prelude::*;
const PROGRAM: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../stoffel-lang/examples/mpc_aes128_secure_decrypt/main.stfl"
);
const KEY: [u8; 16] = [
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
];
const CTR0: [u8; 16] = [
240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,
];
const MESSAGE: &[u8; 16] = b"hello stoffel vm";
const EXPECTED: &[u8; 16] = b"HELLO STOFFEL VM";
fn ctr_encrypt_block(plaintext: &[u8; 16]) -> [u8; 16] {
let cipher = Aes128::new(GenericArray::from_slice(&KEY));
let mut keystream = GenericArray::clone_from_slice(&CTR0);
cipher.encrypt_block(&mut keystream);
let mut ciphertext = [0u8; 16];
for i in 0..16 {
ciphertext[i] = plaintext[i] ^ keystream[i];
}
ciphertext
}
fn bits_lsb_first(bytes: &[u8]) -> Vec<bool> {
let mut bits = Vec::with_capacity(bytes.len() * 8);
for byte in bytes {
for b in 0..8 {
bits.push((byte >> b) & 1 == 1);
}
}
bits
}
#[tokio::main(flavor = "multi_thread")]
async fn main() -> stoffel::Result<()> {
let ciphertext = ctr_encrypt_block(MESSAGE);
let ciphertext_bits = bits_lsb_first(&ciphertext);
let key_bits = bits_lsb_first(&KEY);
println!(
"Client message = {:?}",
std::str::from_utf8(MESSAGE).unwrap()
);
println!(
"Client ciphertext = {}",
ciphertext
.iter()
.map(|b| format!("{b:02x}"))
.collect::<String>()
);
println!("Running AES-128 secure decryption + uppercase over MPC...");
println!("(client 0 = ciphertext, client 1 = key; plaintext never revealed to nodes)");
let (_returned, client_outputs) = Stoffel::compile_file(PROGRAM)?
.parties(5)
.threshold(1)
.expected_output_clients(2)
.with_client_input(0, &ciphertext_bits) .with_client_input(1, &key_bits) .execute_local_capturing_client_outputs()
.await?;
let client0 = client_outputs
.iter()
.find(|o| o.client_slot == 0)
.expect("client 0 should receive a client output");
assert_eq!(
client0.values.len(),
128,
"expected 128 output bits, got {}",
client0.values.len()
);
let received = client0.bytes();
let received_text = String::from_utf8_lossy(&received);
println!("client-received text = {received_text:?}");
println!(
"expected = {:?}",
std::str::from_utf8(EXPECTED).unwrap()
);
assert_eq!(
received.as_slice(),
EXPECTED.as_slice(),
"client should receive the decrypted-and-uppercased plaintext"
);
println!("PASS: nodes decrypted + transformed the text without ever seeing the plaintext");
Ok(())
}