use dusk_plonk::prelude::{
BlsScalar, Circuit, Compiler, Composer, Constraint, Error as PlonkError,
Proof, Prover, PublicParameters, Verifier,
};
use once_cell::sync::Lazy;
use piecrust::{
ContractData, Error, HostQuery, SessionData, VM, contract_bytecode,
};
use rand::rngs::OsRng;
use rkyv::Deserialize;
use std::any::Any;
const OWNER: [u8; 32] = [0u8; 32];
const LIMIT: u64 = 1_000_000;
fn get_prover_verifier() -> &'static (Prover, Verifier) {
static PROVER_VERIFIER: Lazy<(Prover, Verifier)> = Lazy::new(|| {
let mut rng = OsRng;
let pp = PublicParameters::setup(1 << 4, &mut rng)
.expect("Generating public parameters should succeed");
let label = b"dusk-network";
let (prover, verifier) = Compiler::compile::<TestCircuit>(&pp, label)
.expect("Compiling circuit should succeed");
(prover, verifier)
});
&PROVER_VERIFIER
}
fn hash(buf: &mut [u8], len: u32) -> u32 {
let a = unsafe { rkyv::archived_root::<Vec<u8>>(&buf[..len as usize]) };
let v: Vec<u8> = a.deserialize(&mut rkyv::Infallible).unwrap();
let hash = blake3::hash(&v);
buf[..32].copy_from_slice(&hash.as_bytes()[..]);
32
}
fn verify_proof(buf: &mut [u8], len: u32) -> u32 {
let a = unsafe {
rkyv::archived_root::<(Proof, Vec<BlsScalar>)>(&buf[..len as usize])
};
let (proof, public_inputs): (Proof, Vec<BlsScalar>) =
a.deserialize(&mut rkyv::Infallible).unwrap();
let (_, verifier) = get_prover_verifier();
let valid = verifier.verify(&proof, &public_inputs).is_ok();
let valid_bytes = rkyv::to_bytes::<_, 8>(&valid).unwrap();
buf[..valid_bytes.len()].copy_from_slice(&valid_bytes);
valid_bytes.len() as u32
}
struct VeryExpensiveQuery;
impl HostQuery for VeryExpensiveQuery {
fn deserialize_and_price(
&self,
_arg_buf: &[u8],
_arg: &mut Box<dyn Any>,
) -> u64 {
u64::MAX
}
fn execute(&self, _arg: &Box<dyn Any>, _arg_buf: &mut [u8]) -> u32 {
unreachable!("Query will never be executed since its price is too high")
}
}
fn new_ephemeral_vm() -> Result<VM, Error> {
let mut vm = VM::ephemeral()?;
vm.register_host_query("hash", hash);
vm.register_host_query("verify_proof", verify_proof);
vm.register_host_query("very_expensive", VeryExpensiveQuery);
Ok(vm)
}
#[test]
pub fn host_hash() -> Result<(), Error> {
let vm = new_ephemeral_vm()?;
let mut session = vm.session(SessionData::builder())?;
let id = session.deploy(
contract_bytecode!("host"),
ContractData::builder().owner(OWNER),
LIMIT,
)?;
let v = vec![0u8, 1, 2];
let h = session
.call::<_, [u8; 32]>(id, "host_hash", &v, LIMIT)
.expect("query should succeed")
.data;
assert_eq!(blake3::hash(&[0u8, 1, 2]).as_bytes(), &h);
Ok(())
}
#[test]
pub fn host_very_expensive_oog() -> Result<(), Error> {
let vm = new_ephemeral_vm()?;
let mut session = vm.session(SessionData::builder())?;
let id = session.deploy(
contract_bytecode!("host"),
ContractData::builder().owner(OWNER),
LIMIT,
)?;
let err = session
.call::<_, String>(id, "host_very_expensive", &(), LIMIT)
.expect_err("query should fail since it's too expensive");
assert!(matches!(err, Error::OutOfGas));
Ok(())
}
#[derive(Default)]
struct TestCircuit {
a: BlsScalar,
b: BlsScalar,
c: BlsScalar,
}
impl Circuit for TestCircuit {
fn circuit(&self, composer: &mut Composer) -> Result<(), PlonkError> {
let a_w = composer.append_witness(self.a);
let b_w = composer.append_witness(self.b);
let constraint = Constraint::new()
.left(1)
.a(a_w)
.right(1)
.b(b_w)
.public(-self.c);
composer.append_gate(constraint);
Ok(())
}
}
#[test]
pub fn host_proof() -> Result<(), Error> {
let vm = new_ephemeral_vm()?;
let mut session = vm.session(SessionData::builder())?;
let id = session.deploy(
contract_bytecode!("host"),
ContractData::builder().owner(OWNER),
LIMIT,
)?;
let (prover, _) = get_prover_verifier();
let rng = &mut OsRng;
let circuit = TestCircuit {
a: BlsScalar::from(2u64),
b: BlsScalar::from(2u64),
c: BlsScalar::from(4u64),
};
let (proof, public_inputs) =
prover.prove(rng, &circuit).expect("Proving should succeed");
let receipt = session.call::<_, String>(
id,
"host_verify",
&(proof, public_inputs),
LIMIT,
)?;
assert_eq!(receipt.data, "PROOF IS VALID");
let wrong_proof = Proof::default();
let receipt = session.call::<_, String>(
id,
"host_verify",
&(wrong_proof, vec![BlsScalar::default()]),
LIMIT,
)?;
assert_eq!(receipt.data, "PROOF IS INVALID");
Ok(())
}