1
0
Fork 0
mirror of synced 2025-09-24 04:40:05 +00:00
ZoKrates/zokrates_cli/src/bin.rs
2020-03-23 17:13:37 +01:00

787 lines
30 KiB
Rust

//
// @file bin.rs
// @author Jacob Eberhardt <jacob.eberhardt@tu-berlin.de>
// @author Dennis Kuhnert <dennis.kuhnert@campus.tu-berlin.de>
// @date 2017
use bincode::{deserialize_from, serialize_into, Infinite};
use clap::{App, AppSettings, Arg, SubCommand};
use serde_json::{from_reader, to_writer_pretty, Value};
use std::env;
use std::fs::File;
use std::io::{stdin, BufReader, BufWriter, Read, Write};
use std::path::{Path, PathBuf};
use std::string::String;
use zokrates_abi::Encode;
use zokrates_core::compile::{compile, CompilationArtifacts, CompileError};
use zokrates_core::ir;
use zokrates_core::proof_system::*;
use zokrates_core::typed_absy::abi::Abi;
use zokrates_core::typed_absy::{types::Signature, Type};
use zokrates_field::field::{Field, FieldPrime};
use zokrates_fs_resolver::FileSystemResolver;
fn main() {
cli().unwrap_or_else(|e| {
println!("{}", e);
std::process::exit(1);
})
}
fn cli() -> Result<(), String> {
const FLATTENED_CODE_DEFAULT_PATH: &str = "out";
const ABI_SPEC_DEFAULT_PATH: &str = "abi.json";
const VERIFICATION_KEY_DEFAULT_PATH: &str = "verification.key";
const PROVING_KEY_DEFAULT_PATH: &str = "proving.key";
const VERIFICATION_CONTRACT_DEFAULT_PATH: &str = "verifier.sol";
const WITNESS_DEFAULT_PATH: &str = "witness";
const JSON_PROOF_PATH: &str = "proof.json";
let default_scheme = env::var("ZOKRATES_PROVING_SCHEME").unwrap_or(String::from("g16"));
let default_solidity_abi = "v1";
// cli specification using clap library
let matches = App::new("ZoKrates")
.setting(AppSettings::SubcommandRequiredElseHelp)
.version(env!("CARGO_PKG_VERSION"))
.author("Jacob Eberhardt, Thibaut Schaeffer, Stefan Deml")
.about("Supports generation of zkSNARKs from high level language code including Smart Contracts for proof verification on the Ethereum Blockchain.\n'I know that I show nothing!'")
.subcommand(SubCommand::with_name("compile")
.about("Compiles into flattened conditions. Produces two files: human-readable '.ztf' file for debugging and binary file")
.arg(Arg::with_name("input")
.short("i")
.long("input")
.help("Path of the source code")
.value_name("FILE")
.takes_value(true)
.required(true)
).arg(Arg::with_name("abi_spec")
.short("s")
.long("abi_spec")
.help("Path of the ABI specification")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(ABI_SPEC_DEFAULT_PATH)
).arg(Arg::with_name("output")
.short("o")
.long("output")
.help("Path of the output binary")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(FLATTENED_CODE_DEFAULT_PATH)
).arg(Arg::with_name("light")
.long("light")
.help("Skip logs and human readable output")
.required(false)
)
)
.subcommand(SubCommand::with_name("setup")
.about("Performs a trusted setup for a given constraint system")
.arg(Arg::with_name("input")
.short("i")
.long("input")
.help("Path of the binary")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(FLATTENED_CODE_DEFAULT_PATH)
).arg(Arg::with_name("proving-key-path")
.short("p")
.long("proving-key-path")
.help("Path of the generated proving key file")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(PROVING_KEY_DEFAULT_PATH)
).arg(Arg::with_name("verification-key-path")
.short("v")
.long("verification-key-path")
.help("Path of the generated verification key file")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(VERIFICATION_KEY_DEFAULT_PATH)
).arg(Arg::with_name("proving-scheme")
.short("s")
.long("proving-scheme")
.help("Proving scheme to use in the setup. Available options are G16, PGHR13 and GM17")
.takes_value(true)
.required(false)
.default_value(&default_scheme)
).arg(Arg::with_name("light")
.long("light")
.help("Skip logs and human readable output")
.required(false)
)
)
.subcommand(SubCommand::with_name("export-verifier")
.about("Exports a verifier as Solidity smart contract")
.arg(Arg::with_name("input")
.short("i")
.long("input")
.help("Path of the generated verification key file")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(VERIFICATION_KEY_DEFAULT_PATH)
).arg(Arg::with_name("output")
.short("o")
.long("output")
.help("Path of the output file")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(VERIFICATION_CONTRACT_DEFAULT_PATH)
).arg(Arg::with_name("proving-scheme")
.short("s")
.long("proving-scheme")
.help("Proving scheme to use for exporting the verifier. Available options are G16 (default), PGHR13 and GM17")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(&default_scheme)
).arg(Arg::with_name("solidity-abi")
.short("a")
.long("solidity-abi")
.help("Flag for setting the version of the ABI Encoder used in the contract")
.takes_value(true)
.possible_values(&["v1", "v2"])
.default_value(&default_solidity_abi)
.required(false)
)
)
.subcommand(SubCommand::with_name("compute-witness")
.about("Calculates a witness for a given constraint system")
.arg(Arg::with_name("input")
.short("i")
.long("input")
.help("Path of the binary")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(FLATTENED_CODE_DEFAULT_PATH)
).arg(Arg::with_name("abi_spec")
.short("s")
.long("abi_spec")
.help("Path of the ABI specification")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(ABI_SPEC_DEFAULT_PATH)
).arg(Arg::with_name("output")
.short("o")
.long("output")
.help("Path of the output file")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(WITNESS_DEFAULT_PATH)
).arg(Arg::with_name("arguments")
.short("a")
.long("arguments")
.help("Arguments for the program's main function")
.takes_value(true)
.multiple(true) // allows multiple values
.required(false)
).arg(Arg::with_name("abi")
.long("abi")
.help("Use the ABI")
.required(false)
).arg(Arg::with_name("stdin")
.long("stdin")
.help("Read arguments from stdin")
.conflicts_with("arguments")
.required(false)
).arg(Arg::with_name("light")
.long("light")
.help("Skip logs and human readable output")
.required(false)
)
)
.subcommand(SubCommand::with_name("generate-proof")
.about("Calculates a proof for a given constraint system and witness.")
.arg(Arg::with_name("witness")
.short("w")
.long("witness")
.help("Path of the witness file")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(WITNESS_DEFAULT_PATH)
).arg(Arg::with_name("proving-key-path")
.short("p")
.long("proving-key-path")
.help("Path of the proving key file")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(PROVING_KEY_DEFAULT_PATH)
).arg(Arg::with_name("proof-path")
.short("j")
.long("proof-path")
.help("Path of the JSON proof file")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(JSON_PROOF_PATH)
).arg(Arg::with_name("input")
.short("i")
.long("input")
.help("Path of the binary")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(FLATTENED_CODE_DEFAULT_PATH)
).arg(Arg::with_name("proving-scheme")
.short("s")
.long("proving-scheme")
.help("Proving scheme to use for generating the proof. Available options are G16 (default), PGHR13 and GM17")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(&default_scheme)
)
)
.subcommand(SubCommand::with_name("print-proof")
.about("Prints proof in chosen format [remix, json]")
.arg(Arg::with_name("proof-path")
.short("j")
.long("proof-path")
.help("Path of the JSON proof file")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(JSON_PROOF_PATH)
).arg(Arg::with_name("format")
.short("f")
.long("format")
.value_name("FORMAT")
.help("Format in which the proof should be printed. [remix, json]")
.takes_value(true)
.possible_values(&["remix", "json"])
.required(true)
)
)
.subcommand(SubCommand::with_name("verify")
.about("Verifies a given proof with the given constraint system and verification key")
.arg(Arg::with_name("proof-path")
.short("j")
.long("proof-path")
.help("Path of the JSON proof file")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(JSON_PROOF_PATH)
).arg(Arg::with_name("verification-key-path")
.short("v")
.long("verification-key-path")
.help("Path of the generated verification key file")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(VERIFICATION_KEY_DEFAULT_PATH)
).arg(Arg::with_name("proving-scheme")
.short("s")
.long("proving-scheme")
.help("Proving scheme to use in the setup. Available options are G16 (default), PGHR13 and GM17")
.value_name("FILE")
.takes_value(true)
.required(false)
.default_value(&default_scheme)
)
)
.get_matches();
match matches.subcommand() {
("compile", Some(sub_matches)) => {
println!("Compiling {}\n", sub_matches.value_of("input").unwrap());
let path = PathBuf::from(sub_matches.value_of("input").unwrap());
let light = sub_matches.occurrences_of("light") > 0;
let bin_output_path = Path::new(sub_matches.value_of("output").unwrap());
let abi_spec_path = Path::new(sub_matches.value_of("abi_spec").unwrap());
let hr_output_path = bin_output_path.to_path_buf().with_extension("ztf");
let file = File::open(path.clone())
.map_err(|why| format!("Couldn't open input file {}: {}", path.display(), why))?;
let mut reader = BufReader::new(file);
let mut source = String::new();
reader.read_to_string(&mut source).unwrap();
let fmt_error = |e: &CompileError| {
format!(
"{}:{}",
e.file()
.canonicalize()
.unwrap()
.strip_prefix(std::env::current_dir().unwrap())
.unwrap()
.display(),
e.value()
)
};
let resolver = FileSystemResolver::new();
let artifacts: CompilationArtifacts<FieldPrime> =
compile(source, path, Some(&resolver)).map_err(|e| {
format!(
"Compilation failed:\n\n{}",
e.0.iter()
.map(|e| fmt_error(e))
.collect::<Vec<_>>()
.join("\n\n")
)
})?;
let program_flattened = artifacts.prog();
// number of constraints the flattened program will translate to.
let num_constraints = program_flattened.constraint_count();
// serialize flattened program and write to binary file
let bin_output_file = File::create(&bin_output_path)
.map_err(|why| format!("Couldn't create {}: {}", bin_output_path.display(), why))?;
let mut writer = BufWriter::new(bin_output_file);
serialize_into(&mut writer, &program_flattened, Infinite)
.map_err(|_| "Unable to write data to file.".to_string())?;
// serialize ABI spec and write to JSON file
let abi_spec_file = File::create(&abi_spec_path)
.map_err(|why| format!("Couldn't create {}: {}", abi_spec_path.display(), why))?;
let abi = artifacts.abi();
let mut writer = BufWriter::new(abi_spec_file);
to_writer_pretty(&mut writer, &abi)
.map_err(|_| "Unable to write data to file.".to_string())?;
if !light {
// write human-readable output file
let hr_output_file = File::create(&hr_output_path).map_err(|why| {
format!("couldn't create {}: {}", hr_output_path.display(), why)
})?;
let mut hrofb = BufWriter::new(hr_output_file);
write!(&mut hrofb, "{}\n", program_flattened)
.map_err(|_| "Unable to write data to file.".to_string())?;
hrofb
.flush()
.map_err(|_| "Unable to flush buffer.".to_string())?;
}
if !light {
// debugging output
println!("Compiled program:\n{}", program_flattened);
}
println!("Compiled code written to '{}'", bin_output_path.display());
if !light {
println!("Human readable code to '{}'", hr_output_path.display());
}
println!("Number of constraints: {}", num_constraints);
}
("compute-witness", Some(sub_matches)) => {
println!("Computing witness...");
// read compiled program
let path = Path::new(sub_matches.value_of("input").unwrap());
let file = File::open(&path)
.map_err(|why| format!("couldn't open {}: {}", path.display(), why))?;
let mut reader = BufReader::new(file);
let ir_prog: ir::Prog<FieldPrime> =
deserialize_from(&mut reader, Infinite).map_err(|why| why.to_string())?;
// print deserialized flattened program
if !sub_matches.is_present("light") {
println!("{}", ir_prog);
}
let is_stdin = sub_matches.is_present("stdin");
let is_abi = sub_matches.is_present("abi");
if !is_stdin && is_abi {
return Err(
"ABI input as inline argument is not supported. Please use `--stdin`.".into(),
);
}
let signature = match is_abi {
true => {
let path = Path::new(sub_matches.value_of("abi_spec").unwrap());
let file = File::open(&path)
.map_err(|why| format!("couldn't open {}: {}", path.display(), why))?;
let mut reader = BufReader::new(file);
let abi: Abi = from_reader(&mut reader).map_err(|why| why.to_string())?;
abi.signature()
}
false => Signature::new()
.inputs(vec![Type::FieldElement; ir_prog.main.arguments.len()])
.outputs(vec![Type::FieldElement; ir_prog.main.returns.len()]),
};
use zokrates_abi::Inputs;
// get arguments
let arguments = match is_stdin {
// take inline arguments
false => {
let arguments = sub_matches.values_of("arguments");
arguments
.map(|a| {
a.map(|x| FieldPrime::try_from_dec_str(x).map_err(|_| x.to_string()))
.collect::<Result<Vec<_>, _>>()
})
.unwrap_or(Ok(vec![]))
.map(|v| Inputs::Raw(v))
}
// take stdin arguments
true => {
let mut stdin = stdin();
let mut input = String::new();
match is_abi {
true => match stdin.read_to_string(&mut input) {
Ok(_) => {
use zokrates_abi::parse_strict;
parse_strict(&input, signature.inputs)
.map(|parsed| Inputs::Abi(parsed))
.map_err(|why| why.to_string())
}
Err(_) => Err(String::from("???")),
},
false => match ir_prog.arguments_count() {
0 => Ok(Inputs::Raw(vec![])),
_ => match stdin.read_to_string(&mut input) {
Ok(_) => {
input.retain(|x| x != '\n');
input
.split(" ")
.map(|x| {
FieldPrime::try_from_dec_str(x)
.map_err(|_| x.to_string())
})
.collect::<Result<Vec<_>, _>>()
.map(|v| Inputs::Raw(v))
}
Err(_) => Err(String::from("???")),
},
},
}
}
}
.map_err(|e| format!("Could not parse argument: {}", e))?;
let witness = ir_prog
.execute(&arguments.encode())
.map_err(|e| format!("Execution failed: {}", e))?;
use zokrates_abi::Decode;
let results_json_value: serde_json::Value =
zokrates_abi::CheckedValues::decode(witness.return_values(), signature.outputs)
.into();
println!("\nWitness: \n\n{}", results_json_value);
// write witness to file
let output_path = Path::new(sub_matches.value_of("output").unwrap());
let output_file = File::create(&output_path)
.map_err(|why| format!("couldn't create {}: {}", output_path.display(), why))?;
let writer = BufWriter::new(output_file);
witness
.write(writer)
.map_err(|why| format!("could not save witness: {:?}", why))?;
}
("setup", Some(sub_matches)) => {
let scheme = get_scheme(sub_matches.value_of("proving-scheme").unwrap())?;
println!("Performing setup...");
let path = Path::new(sub_matches.value_of("input").unwrap());
let file = File::open(&path)
.map_err(|why| format!("couldn't open {}: {}", path.display(), why))?;
let mut reader = BufReader::new(file);
let program: ir::Prog<FieldPrime> =
deserialize_from(&mut reader, Infinite).map_err(|why| format!("{:?}", why))?;
// print deserialized flattened program
if !sub_matches.is_present("light") {
println!("{}", program);
}
// get paths for proving and verification keys
let pk_path = Path::new(sub_matches.value_of("proving-key-path").unwrap());
let vk_path = Path::new(sub_matches.value_of("verification-key-path").unwrap());
// run setup phase
let keypair = scheme.setup(program);
// write verification key
let mut vk_file = File::create(vk_path)
.map_err(|why| format!("couldn't create {}: {}", vk_path.display(), why))?;
vk_file
.write(keypair.vk.as_ref())
.map_err(|why| format!("couldn't write to {}: {}", vk_path.display(), why))?;
// write proving key
let mut pk_file = File::create(pk_path)
.map_err(|why| format!("couldn't create {}: {}", pk_path.display(), why))?;
pk_file
.write(keypair.pk.as_ref())
.map_err(|why| format!("couldn't write to {}: {}", pk_path.display(), why))?;
println!("Setup completed.");
}
("export-verifier", Some(sub_matches)) => {
{
let scheme = get_scheme(sub_matches.value_of("proving-scheme").unwrap())?;
let is_abi_v2 = sub_matches.value_of("solidity-abi").unwrap() == "v2";
println!("Exporting verifier...");
// read vk file
let vk_path = Path::new(sub_matches.value_of("input").unwrap());
let vk_file = File::open(&vk_path)
.map_err(|why| format!("couldn't open {}: {}", vk_path.display(), why))?;
let mut reader = BufReader::new(vk_file);
let mut vk = String::new();
reader
.read_to_string(&mut vk)
.map_err(|why| format!("couldn't read {}: {}", vk_path.display(), why))?;
let verifier = scheme.export_solidity_verifier(vk, is_abi_v2);
//write output file
let output_path = Path::new(sub_matches.value_of("output").unwrap());
let output_file = File::create(&output_path)
.map_err(|why| format!("couldn't create {}: {}", output_path.display(), why))?;
let mut writer = BufWriter::new(output_file);
writer
.write_all(&verifier.as_bytes())
.map_err(|_| "Failed writing output to file.".to_string())?;
println!("Finished exporting verifier.");
}
}
("generate-proof", Some(sub_matches)) => {
println!("Generating proof...");
let scheme = get_scheme(sub_matches.value_of("proving-scheme").unwrap())?;
// deserialize witness
let witness_path = Path::new(sub_matches.value_of("witness").unwrap());
let witness_file = match File::open(&witness_path) {
Ok(file) => file,
Err(why) => panic!("couldn't open {}: {}", witness_path.display(), why),
};
let witness = ir::Witness::read(witness_file)
.map_err(|why| format!("could not load witness: {:?}", why))?;
let pk_path = Path::new(sub_matches.value_of("proving-key-path").unwrap());
let proof_path = Path::new(sub_matches.value_of("proof-path").unwrap());
let program_path = Path::new(sub_matches.value_of("input").unwrap());
let program_file = File::open(&program_path)
.map_err(|why| format!("couldn't open {}: {}", program_path.display(), why))?;
let mut reader = BufReader::new(program_file);
let program: ir::Prog<FieldPrime> =
deserialize_from(&mut reader, Infinite).map_err(|why| format!("{:?}", why))?;
let pk_file = File::open(&pk_path)
.map_err(|why| format!("couldn't open {}: {}", pk_path.display(), why))?;
let mut pk: Vec<u8> = Vec::new();
let mut pk_reader = BufReader::new(pk_file);
pk_reader
.read_to_end(&mut pk)
.map_err(|why| format!("couldn't read {}: {}", pk_path.display(), why))?;
let proof = scheme.generate_proof(program, witness, pk);
let mut proof_file = File::create(proof_path).unwrap();
proof_file
.write(proof.as_ref())
.map_err(|why| format!("couldn't write to {}: {}", proof_path.display(), why))?;
println!("generate-proof successful: {}", format!("{}", proof));
}
("print-proof", Some(sub_matches)) => {
let format = sub_matches.value_of("format").unwrap();
let path = Path::new(sub_matches.value_of("proof-path").unwrap());
let file = File::open(&path)
.map_err(|why| format!("couldn't open {}: {}", path.display(), why))?;
let proof_object: Value =
serde_json::from_reader(file).map_err(|why| format!("{:?}", why))?;
match format {
"json" => {
println!("~~~~~~~~ Copy the output below for valid ABIv2 format ~~~~~~~~");
println!();
print!("{}", proof_object["proof"]);
print!(",");
println!("{}", proof_object["inputs"]);
println!();
println!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
"remix" => {
println!("~~~~~~~~ Copy the output below for valid ABIv1 format ~~~~~~~~");
println!();
for (_, value) in proof_object["proof"].as_object().unwrap().iter() {
print!("{}", value);
print!(",");
}
println!("{}", proof_object["inputs"]);
println!();
println!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
_ => unreachable!(),
}
}
("verify", Some(sub_matches)) => {
let scheme = get_scheme(sub_matches.value_of("proving-scheme").unwrap())?;
let vk_path = Path::new(sub_matches.value_of("verification-key-path").unwrap());
let vk = std::fs::read_to_string(vk_path)
.map_err(|why| format!("couldn't read {}: {}", vk_path.display(), why))?;
let proof_path = Path::new(sub_matches.value_of("proof-path").unwrap());
let proof = std::fs::read_to_string(proof_path)
.map_err(|why| format!("couldn't read {}: {}", proof_path.display(), why))?;
println!("Performing verification...");
println!("Verified: {}", scheme.verify(vk, proof));
}
_ => unreachable!(),
}
Ok(())
}
fn get_scheme(scheme_str: &str) -> Result<&'static dyn ProofSystem, String> {
match scheme_str.to_lowercase().as_ref() {
#[cfg(feature = "libsnark")]
"pghr13" => Ok(&PGHR13 {}),
#[cfg(feature = "libsnark")]
"gm17" => Ok(&GM17 {}),
"g16" => Ok(&G16 {}),
s => Err(format!("Backend \"{}\" not supported", s)),
}
}
#[cfg(test)]
mod tests {
extern crate glob;
use self::glob::glob;
use super::*;
#[test]
fn examples() {
for p in glob("./examples/**/*.zok").expect("Failed to read glob pattern") {
let path = match p {
Ok(x) => x,
Err(why) => panic!("Error: {:?}", why),
};
if path.to_str().unwrap().contains("error") {
continue;
}
println!("Testing {:?}", path);
let file = File::open(path.clone()).unwrap();
let mut reader = BufReader::new(file);
let mut source = String::new();
reader.read_to_string(&mut source).unwrap();
let resolver = FileSystemResolver::new();
let _: CompilationArtifacts<FieldPrime> =
compile(source, path, Some(&resolver)).unwrap();
}
}
#[test]
fn examples_with_input_success() {
//these examples should compile and run
for p in glob("./examples/test*.zok").expect("Failed to read glob pattern") {
let path = match p {
Ok(x) => x,
Err(why) => panic!("Error: {:?}", why),
};
println!("Testing {:?}", path);
let file = File::open(path.clone()).unwrap();
let mut reader = BufReader::new(file);
let mut source = String::new();
reader.read_to_string(&mut source).unwrap();
let resolver = FileSystemResolver::new();
let artifacts: CompilationArtifacts<FieldPrime> =
compile(source, path, Some(&resolver)).unwrap();
let _ = artifacts
.prog()
.execute(&vec![FieldPrime::from(0)])
.unwrap();
}
}
#[test]
#[should_panic]
fn examples_with_input_failure() {
//these examples should compile but not run
for p in glob("./examples/runtime_errors/*.zok").expect("Failed to read glob pattern") {
let path = match p {
Ok(x) => x,
Err(why) => panic!("Error: {:?}", why),
};
println!("Testing {:?}", path);
let file = File::open(path.clone()).unwrap();
let mut reader = BufReader::new(file);
let mut source = String::new();
reader.read_to_string(&mut source).unwrap();
let resolver = FileSystemResolver::new();
let artifacts: CompilationArtifacts<FieldPrime> =
compile(source, path, Some(&resolver)).unwrap();
let _ = artifacts
.prog()
.execute(&vec![FieldPrime::from(0)])
.unwrap();
}
}
}