Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: triton-cli prove/verify, proof binary serialization #305

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
002b313
feat: triton-cli prove/verify, proof binary serialization
chancehudson Jul 11, 2024
1a2ce33
chore: remove proof file, add stdout info during proving
chancehudson Jul 11, 2024
e96012e
feat: support public and private inputs
chancehudson Jul 16, 2024
113eb16
one import per line
chancehudson Jul 18, 2024
d769901
remove unnecessary comment
chancehudson Jul 18, 2024
2990718
chore: tasm -> Triton assembly
chancehudson Jul 18, 2024
bea4c17
refactor: remove unnecessary struct
chancehudson Jul 18, 2024
607c70e
refactor: String -> &str, use display for BFieldElements, fix test
chancehudson Jul 18, 2024
e99b571
chore: remove redundant type specification
chancehudson Jul 18, 2024
43d7924
chore: remove unnecessary parse typing
chancehudson Jul 18, 2024
f3103e8
fix: comment array syntax
chancehudson Jul 18, 2024
1f1e339
fix: check if out filepath is empty before proving
chancehudson Jul 18, 2024
525aec1
refactor: anyhow error handling, use triton_program macro
chancehudson Jul 18, 2024
deb224a
refactor: use serde+bincode for serializing
chancehudson Jul 19, 2024
3cefec1
refactor: use bail instead of std::process::exit
chancehudson Jul 19, 2024
ce6cc4c
refactor: Result from verify
chancehudson Jul 19, 2024
43105cd
fix: derive debug/clone
chancehudson Jul 19, 2024
864d598
refactor: fs::read
chancehudson Jul 19, 2024
92354dd
fix: use DIGEST_LENGTH constant
chancehudson Jul 19, 2024
9d80690
refactor: leave strings on stack
chancehudson Jul 19, 2024
2d22aff
refactor: more semantic digest
chancehudson Jul 19, 2024
7e03fea
fix: bail if version is ne 1
chancehudson Jul 19, 2024
3c021dc
chore: newline in tasm example
chancehudson Jul 19, 2024
7303ec4
refactor: destructure in function def
chancehudson Jul 19, 2024
be10d5f
refactor: return Result directly
chancehudson Jul 28, 2024
740388a
refactor: remove manual length check
chancehudson Jul 28, 2024
553a028
feat: specify prover version in proof
chancehudson Jul 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["triton-vm", "constraint-evaluation-generator"]
members = ["triton-vm", "constraint-evaluation-generator", "triton-cli"]
resolver = "2"

[profile.test]
Expand Down
1 change: 1 addition & 0 deletions triton-cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.proof
21 changes: 21 additions & 0 deletions triton-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "triton-cli"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
readme.workspace = true
documentation.workspace = true

[dependencies]
anyhow.workspace = true
bincode = "1.3.0"
clap.workspace = true
serde.workspace = true
# use a crates.io version to avoid needing to generate constraints
triton-vm = "0.41.0"

[lints]
workspace = true
210 changes: 210 additions & 0 deletions triton-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
use std::fmt::Write as _;
use std::fs;
use std::io::Read;
use std::io::Write;

use anyhow::Context;
use anyhow::Result;
use clap::command;
use clap::Parser;
use serde::Deserialize;
use serde::Serialize;
use triton_vm::prelude::*;

#[derive(Debug, Parser)]
#[command(name = "triton-cli")]
#[command(about = "Compile, prove and verify Triton assembly programs", long_about = None)]
enum CliArg {
#[command(arg_required_else_help = true)]
Prove {
asm_path: String,
proof_out_path: String,
#[clap(long)]
/// Public inputs to pass to the proof, comma separated
public_inputs: Option<String>,
#[clap(long)]
/// Private inputs to pass to the proof, comma separated
private_inputs: Option<String>,
},
#[command(arg_required_else_help = true)]
Verify { proof_path: String },
}

fn main() -> Result<()> {
let arg = CliArg::parse();
match arg {
CliArg::Prove {
asm_path,
proof_out_path,
public_inputs,
private_inputs,
} => prove(&asm_path, &proof_out_path, public_inputs, private_inputs)?,
CliArg::Verify { proof_path } => verify(&proof_path),
}
Ok(())
}

fn digest_to_str(d: Digest) -> String {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the next version of triton-vm, the method digest.to_hex() will be available. This will guarantee that hex-ified digests are consistent everywhere. 😊

let mut hex = String::new();
d.0.iter()
.for_each(|v| write!(&mut hex, "{:16x}", u64::from(v)).unwrap());
format!("0x{hex}")
}

fn verify(proof_path: &str) {
chancehudson marked this conversation as resolved.
Show resolved Hide resolved
let (stark, claim, proof) = read_proof(proof_path).expect("Failed to load proof");

let verdict = triton_vm::verify(stark, &claim, &proof);
if !verdict {
println!("Proof is not valid!");
chancehudson marked this conversation as resolved.
Show resolved Hide resolved
std::process::exit(1);
}
println!("proof is valid!");
println!("program digest: {}", digest_to_str(claim.program_digest));
println!("=================");
println!("security level: {} bits", stark.security_level);
println!("FRI expansion factor: {}", stark.fri_expansion_factor);
println!("trace randomizers: {}", stark.num_trace_randomizers);
println!("colinearity checks: {}", stark.num_collinearity_checks);
println!("codeword checks: {}", stark.num_combination_codeword_checks);
println!("=================");
println!("public inputs:");
if claim.input.is_empty() {
println!("(none)");
}
for v in claim.input {
println!("{v}");
}
println!("public outputs:");
if claim.output.is_empty() {
println!("(none)");
}
for v in claim.output {
println!("{v}");
}
}

fn parse_inputs(inputs: Option<String>) -> Vec<BFieldElement> {
inputs
.unwrap_or_default()
.split(',')
.filter(|v| !v.is_empty())
jan-ferdinand marked this conversation as resolved.
Show resolved Hide resolved
.map(|v| v.parse().unwrap())
.collect()
}

fn prove(
asm_filepath: &str,
out: &str,
public_inputs: Option<String>,
private_inputs: Option<String>,
) -> Result<()> {
if std::path::Path::new(out).exists() {
println!("output file already exists: {out}");
std::process::exit(1);
chancehudson marked this conversation as resolved.
Show resolved Hide resolved
}
let asm = fs::read_to_string(asm_filepath)
.with_context(|| "Failed to read Triton assembly from file")?;
let program = triton_program!({ asm });

let public_input = PublicInput::from(parse_inputs(public_inputs));
let non_determinism = NonDeterminism::from(parse_inputs(private_inputs));
println!("proving...");
let data = triton_vm::prove_program(&program, public_input, non_determinism)
.with_context(|| "Triton VM errored during program execution")?;
println!("success!");

write_proof(data, out).with_context(|| "Failed to write proof to file")?;
println!("proof written to: {out}");
Ok(())
}

#[derive(Serialize, Deserialize)]
chancehudson marked this conversation as resolved.
Show resolved Hide resolved
struct SerializedProof {
version: u8,
security_level: u64,
fri_expansion_factor: u64,
num_trace_randomizers: u64,
num_colinearity_checks: u64,
num_combination_codeword_checks: u64,
program_digest: Vec<u64>,
public_inputs: Vec<u64>,
public_outputs: Vec<u64>,
proof: Vec<u64>,
}

fn read_proof(proof_path: &str) -> Result<(Stark, Claim, Proof)> {
let mut file = fs::File::open(proof_path)?;
let mut proof_bytes: Vec<u8> = Vec::new();
file.read_to_end(&mut proof_bytes)?;
chancehudson marked this conversation as resolved.
Show resolved Hide resolved
let serialized_proof: SerializedProof = bincode::deserialize(&proof_bytes)?;
let stark = Stark {
security_level: usize::try_from(serialized_proof.security_level)?,
fri_expansion_factor: usize::try_from(serialized_proof.fri_expansion_factor)?,
num_trace_randomizers: usize::try_from(serialized_proof.num_trace_randomizers)?,
num_collinearity_checks: usize::try_from(serialized_proof.num_colinearity_checks)?,
num_combination_codeword_checks: usize::try_from(
serialized_proof.num_combination_codeword_checks,
)?,
};
let mut digest = Digest::default();
assert_eq!(
digest.0.len(),
chancehudson marked this conversation as resolved.
Show resolved Hide resolved
serialized_proof.program_digest.len(),
"digest length mismatch!"
);
serialized_proof
.program_digest
.iter()
.enumerate()
.for_each(|(i, x)| digest.0[i] = BFieldElement::new(*x));
let claim = Claim {
program_digest: digest,
input: serialized_proof
.public_inputs
.iter()
.map(|v| BFieldElement::new(*v))
.collect(),
output: serialized_proof
.public_outputs
.iter()
.map(|v| BFieldElement::new(*v))
.collect(),
};
let proof_vec = serialized_proof
.proof
.iter()
.map(|v| BFieldElement::new(*v))
.collect();
Ok((stark, claim, Proof(proof_vec)))
}

fn write_proof(data: (Stark, Claim, Proof), out: &str) -> Result<()> {
let (stark, claim, proof) = data;
chancehudson marked this conversation as resolved.
Show resolved Hide resolved
let serialized = SerializedProof {
version: 1,
security_level: u64::try_from(stark.security_level)?,
fri_expansion_factor: u64::try_from(stark.fri_expansion_factor)?,
num_trace_randomizers: u64::try_from(stark.num_trace_randomizers)?,
num_colinearity_checks: u64::try_from(stark.num_collinearity_checks)?,
num_combination_codeword_checks: u64::try_from(stark.num_combination_codeword_checks)?,
program_digest: claim.program_digest.0.iter().map(u64::from).collect(),
chancehudson marked this conversation as resolved.
Show resolved Hide resolved
public_inputs: claim.input.iter().map(u64::from).collect(),
public_outputs: claim.output.iter().map(u64::from).collect(),
proof: proof.0.iter().map(u64::from).collect(),
};
let mut file = fs::File::create_new(out)?;
let proof_bytes = bincode::serialize(&serialized)?;
file.write_all(&proof_bytes)?;
Ok(())
}

#[test]
fn test_serialization() -> Result<()> {
let asm = "./test-vectors/simple.tasm".to_string();
let proof = "./test-vectors/simple.proof".to_string();
prove(&asm, &proof, None, None)?;
verify(&proof);
chancehudson marked this conversation as resolved.
Show resolved Hide resolved
fs::remove_file(proof).unwrap();
chancehudson marked this conversation as resolved.
Show resolved Hide resolved
Ok(())
}
24 changes: 24 additions & 0 deletions triton-cli/test-vectors/simple.tasm
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
push 10
chancehudson marked this conversation as resolved.
Show resolved Hide resolved
push 100
push 10000
push 5
swap 3
pop 1
push 5
swap 3
pop 1
push 5
swap 3
pop 1
push 5
swap 3
pop 1
push 5
swap 3
pop 1
push 5
swap 3
pop 1
push 5
swap 3
halt
chancehudson marked this conversation as resolved.
Show resolved Hide resolved