fcomm 0.2.0

Functional commitments with Lurk
Documentation
use assert_cmd::prelude::*;
use predicates::prelude::*;
use std::ffi::OsStr;
use std::fs::File;
use std::io::Write;
use std::process::Command;
use tempfile::{Builder, TempDir};

use pasta_curves::pallas;

use fcomm::{Commitment, CommittedExpression, FileStore, LurkPtr, Proof};
use lurk::store::Store;

pub type S1 = pallas::Scalar;

fn fcomm_cmd() -> std::process::Command {
    Command::cargo_bin("fcomm").unwrap()
}

#[test]
fn test_bad_command() {
    let mut cmd = fcomm_cmd();

    cmd.arg("uiop");
    cmd.assert().failure().stderr(predicate::str::contains(
        "error: Found argument 'uiop' which wasn't expected, or isn't valid in this context",
    ));
}

#[test]
fn test_eval_expression() {
    let mut cmd = fcomm_cmd();

    let expression = "((LAMBDA (A B) (+ (* A 3) B)) 9 7)";

    let tmp_dir = Builder::new().prefix("tmp").tempdir().unwrap();
    let expression_path = tmp_dir.path().join("expression.lurk");

    let mut expression_file = File::create(&expression_path).unwrap();
    write!(expression_file, "{expression}").unwrap();

    cmd.arg("eval")
        .arg("--expression")
        .arg(expression_path)
        .arg("--lurk");

    cmd.assert()
        .success()
        .stdout("{\"expr\":\"((LAMBDA (A B) (+ (* A 3) B)) 9 7)\",\"env\":\"NIL\",\"cont\":\"Outermost\",\"expr_out\":\"34\",\"env_out\":\"NIL\",\"cont_out\":\"Terminal\",\"status\":\"Terminal\",\"iterations\":17}");
}

fn test_prove_expression<T: AsRef<OsStr>>(
    cmd: &mut Command,
    expression_path: T,
    proof_path: T,
    data_path: T,
) {
    cmd.env("FCOMM_DATA_PATH", data_path)
        .arg("prove")
        .arg("--expression")
        .arg(expression_path)
        .arg("--proof")
        .arg(proof_path)
        .arg("--lurk");

    cmd.assert().success();
}

fn test_open_commitment<T: AsRef<OsStr>>(
    mut cmd: Command,
    commitment: String,
    input_path: T,
    proof_path: T,
    data_path: T,
    chained: bool,
) {
    cmd.env("FCOMM_DATA_PATH", data_path)
        .arg("open")
        .arg("--commitment")
        .arg(commitment)
        .arg("--input")
        .arg(input_path)
        .arg("--proof")
        .arg(proof_path);

    if chained {
        cmd.arg("--chain");
    };

    cmd.assert().success();
}

fn test_verify_expression_proof<T: AsRef<OsStr>>(mut cmd: Command, proof_path: T, _data_path: T) {
    cmd.arg("verify").arg("--proof").arg(proof_path);

    cmd.assert().success().stdout("{\"verified\":true}");
}

fn test_verify_opening<T: AsRef<OsStr>>(mut cmd: Command, proof_path: T, _data_path: T) {
    cmd.arg("verify").arg("--proof").arg(proof_path);

    cmd.assert().success().stdout("{\"verified\":true}");
}

#[test]
#[ignore]
fn test_prove_and_verify_expression() {
    let expression = "(* 9 7)";
    let expected = "63";

    let tmp_dir = Builder::new().prefix("tmp").tempdir().unwrap();
    let proof_path = tmp_dir.path().join("proof.json");
    let fcomm_data_path = tmp_dir.path().join("fcomm_data");
    let expression_path = tmp_dir.path().join("expression.lurk");

    let mut expression_file = File::create(&expression_path).unwrap();
    write!(expression_file, "{expression}").unwrap();

    {
        test_prove_expression(
            &mut fcomm_cmd(),
            &expression_path,
            &proof_path,
            &fcomm_data_path,
        );

        let proof = Proof::<S1>::read_from_path(&proof_path).unwrap();

        assert_eq!(
            proof
                .claim
                .evaluation()
                .expect("expected evaluation claim")
                .expr_out,
            expected
        );
    }

    test_verify_expression_proof(fcomm_cmd(), &proof_path, &fcomm_data_path);
}

fn commit<T: AsRef<OsStr>>(function_path: T, commitment_path: T, data_path: T) {
    let mut cmd = fcomm_cmd();
    cmd.env("FCOMM_DATA_PATH", data_path)
        .arg("commit")
        .arg("--function")
        .arg(&function_path)
        .arg("--commitment")
        .arg(&commitment_path)
        .assert()
        .success();
}
fn test_create_open_and_verify_functional_commitment_aux(
    function_source: &str,
    function_input: &str,
    expected_output: &str,
) {
    let tmp_dir = Builder::new().prefix("tmp").tempdir().unwrap();

    test_aux(
        function_source,
        vec![(function_input, expected_output)],
        false,
        tmp_dir,
    );
}

fn test_create_open_and_verify_chained_functional_commitment_aux(
    function_source: &str,
    expected_io: Vec<(&str, &str)>,
) {
    let tmp_dir = Builder::new().prefix("tmp").tempdir().unwrap();

    test_aux(function_source, expected_io, true, tmp_dir);
}

fn test_aux(
    function_source: &str,
    expected_io: Vec<(&str, &str)>,
    chained: bool,
    tmp_dir: TempDir,
) {
    let function = CommittedExpression::<S1> {
        expr: LurkPtr::Source(function_source.into()),
        secret: None,
        commitment: None,
    };

    test_function_aux(function, expected_io, chained, tmp_dir)
}

fn test_function_aux(
    function: CommittedExpression<S1>,
    expected_io: Vec<(&str, &str)>,
    chained: bool,
    tmp_dir: TempDir,
) {
    use lurk::writer::Write;

    let io = expected_io.iter();

    let proof_path = tmp_dir.path().join("proof.json");
    let function_path = tmp_dir.path().join("function.json");
    let input_path = tmp_dir.path().join("input.lurk");
    let commitment_path = tmp_dir.path().join("commitment.json");
    let fcomm_data_path = tmp_dir.path().join("fcomm_data");

    function.write_to_path(&function_path);

    commit(&function_path, &commitment_path, &fcomm_data_path);

    let mut commitment: Commitment<S1> = Commitment::read_from_path(&commitment_path).unwrap();

    for (function_input, expected_output) in io {
        let mut input_file = File::create(&input_path).unwrap();

        write!(input_file, "{function_input}").unwrap();

        test_open_commitment(
            fcomm_cmd(),
            commitment.to_string(),
            &input_path,
            &proof_path,
            &fcomm_data_path,
            chained,
        );

        let proof = Proof::<S1>::read_from_path(&proof_path).unwrap();
        let opening = proof.claim.opening().expect("expected opening claim");
        dbg!(&opening);

        let mut store = Store::<S1>::default();

        let input = store.read(function_input).unwrap();
        let canonical_input = input.fmt_to_string(&store);

        let canonical_output = store.read(expected_output).unwrap().fmt_to_string(&store);

        assert_eq!(canonical_input, opening.input);
        assert_eq!(*expected_output, canonical_output);

        test_verify_opening(fcomm_cmd(), &proof_path, &fcomm_data_path);

        if chained {
            match opening.new_commitment {
                Some(c) => commitment = c,
                _ => panic!("new commitment missing"),
            }
        }
    }
}

#[test]
#[ignore]
fn test_create_open_and_verify_functional_commitment() {
    let function_source = "(lambda (x) (+ x 3))";
    let function_input = "22";
    let expected_output = "25";
    test_create_open_and_verify_functional_commitment_aux(
        function_source,
        function_input,
        expected_output,
    );
}

#[test]
#[ignore]
fn test_create_open_and_verify_higher_order_functional_commitment() {
    let function_source = "(lambda (f) (+ (f 3) 1))";
    let function_input = "(lambda (x) (* x 5))";
    let expected_output = "16";
    test_create_open_and_verify_functional_commitment_aux(
        function_source,
        function_input,
        expected_output,
    );
}

#[test]
#[ignore]
fn test_create_open_and_verify_chained_functional_commitment() {
    let function_source = "(letrec ((secret 12345) (a (lambda (acc x) (let ((acc (+ acc x))) (cons acc (hide secret (a acc))))))) (a 0))";

    let expected_io = vec![("5", "5"), ("3", "8")];

    test_create_open_and_verify_chained_functional_commitment_aux(function_source, expected_io);
}

#[test]
#[ignore]
fn test_create_open_and_verify_complicated_higher_order_functional_commitment1() {
    let function_source = "(let ((nums '(1 2 3 4 5))) (lambda (f) (f nums)))";
    let function_input = "(letrec ((sum-aux (lambda (acc nums)
                                              (if nums
                                                (sum-aux (+ acc (car nums)) (cdr nums))
                                                acc)))
                                   (sum (sum-aux 0)))
                             (lambda (nums)
                               (sum nums)))";
    let expected_output = "15";

    test_create_open_and_verify_functional_commitment_aux(
        function_source,
        function_input,
        expected_output,
    );
}

#[test]
#[ignore]
fn test_create_open_and_verify_complicated_higher_order_functional_commitment2() {
    let function_source = "(letrec ((secret-data '((joe 4 3) (bill 10 2 3) (jane 8 7 6 10) (carol 3 5 8))) (filter (lambda (data predicate) (if data (if (predicate (cdr (car data))) (cons (car data) (filter (cdr data) predicate)) (filter (cdr data) predicate))))) (f (lambda (predicate) (car (car (filter secret-data predicate)))))) f)";

    let function_input = "(letrec ((sum-aux (lambda (acc nums)
                                              (if nums
                                                (sum-aux (+ acc (car nums)) (cdr nums))
                                                acc)))
                                   (sum (sum-aux 0)))
                             (lambda (nums)
                               (= (sum nums) 15)))";
    let expected_output = "BILL";

    test_create_open_and_verify_functional_commitment_aux(
        function_source,
        function_input,
        expected_output,
    );
}