Skip to content

Commit 5f54d52

Browse files
authored
Utils/parse proof (#25)
* feat: add utils for proof handling and public input management * test: integration tests for proof parsing and public input handling * docs: update README to include utilities for separating proof and public inputs * fix: update combine_proof_and_public_inputs function to accept separate proof and public inputs
1 parent 234fefb commit 5f54d52

File tree

6 files changed

+161
-1
lines changed

6 files changed

+161
-1
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,33 @@ fn main() {
124124
bb write_solidity_verifier -k ./target/vk -o ./target/Verifier.sol
125125
```
126126

127+
### Proof and Public Inputs Separation
128+
129+
Noir-rs provides utilities to separate public inputs from proof data, which is essential for constructing proper calldata for on-chain verification:
130+
131+
```rs
132+
use noir::utils::{
133+
parse_proof_with_public_inputs,
134+
get_num_public_inputs_from_circuit,
135+
combine_proof_and_public_inputs,
136+
};
137+
138+
// Get the number of public inputs from your circuit
139+
let num_public_inputs = get_num_public_inputs_from_circuit(BYTECODE)?;
140+
141+
// Generate proof (public inputs are concatenated at the beginning)
142+
let proof = prove_ultra_honk_keccak(BYTECODE, witness, vk, false, false)?;
143+
144+
// Parse the proof into separated components
145+
let proof_with_public_inputs = parse_proof_with_public_inputs(&proof, num_public_inputs)?;
146+
147+
// Access the separated proof and public inputs
148+
let pure_proof = &proof_with_public_inputs.proof;
149+
let public_inputs = &proof_with_public_inputs.public_inputs;
150+
151+
// Use pure_proof and public_inputs for constructing calldata for Solidity verifier
152+
```
153+
127154
## Downloading SRS (Structured Reference String)
128155

129156
Noir requires a Structured Reference String (SRS) for its operations. You can download the necessary SRS files using the `srs_downloader` utility included in the `noir` crate.

noir/src/backends/barretenberg/tests/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use bb::barretenberg_api::{acir::{get_circuit_sizes, acir_get_slow_low_memory},
33
use crate::backends::barretenberg::{srs::{setup_srs_from_bytecode, setup_srs, netsrs::NetSrs}, verify::{verify_ultra_honk, verify_ultra_honk_keccak, get_ultra_honk_verification_key, get_ultra_honk_keccak_verification_key}, prove::{prove_ultra_honk, prove_ultra_honk_keccak}, utils::compute_subgroup_size};
44
use acir::{FieldElement, native_types::{Witness, WitnessMap}};
55
use crate::{witness, circuit};
6+
mod proof_utils;
67
use serde_json;
78
use std::path::PathBuf;
89
use std::env;
@@ -59,6 +60,7 @@ fn get_circuit_path(filename: &str) -> PathBuf {
5960
PathBuf::from(format!("circuits/target/{}", filename))
6061
}
6162

63+
6264
#[test]
6365
fn test_acir_get_circuit_size() {
6466
let (_, constraint_system_buf) = circuit::decode_circuit(BYTECODE).unwrap();
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use crate::utils::proof_utils::*;
2+
use crate::witness::from_vec_to_witness_map;
3+
use crate::backends::barretenberg::{
4+
srs::setup_srs_from_bytecode,
5+
prove::prove_ultra_honk_keccak,
6+
verify::{verify_ultra_honk_keccak, get_ultra_honk_keccak_verification_key},
7+
};
8+
9+
// Multiplier2 circuit bytecode
10+
// This circuit multiplies x * y == result with result as public input
11+
const MULTIPLIER2_BYTECODE: &str = "H4sIAAAAAAAA/62QQQqAMAwErfigpEna5OZXLLb/f4KKLZbiTQdCQg7Dsm66mc9x00O717rhG9ico5cgMOfoMxJu4C2pAEsKioqisnslysoaLVkEQ6aMRYxKFc//ZYQr29L10XfhXv4jB52E+OpMAQAA";
12+
13+
#[test]
14+
fn test_get_num_public_inputs_multiplier2() {
15+
let num_public_inputs = get_num_public_inputs_from_circuit(MULTIPLIER2_BYTECODE).unwrap();
16+
17+
// Multiplier2 circuit has 1 public input: result
18+
assert_eq!(num_public_inputs, 1, "Multiplier2 circuit should have 1 public inputs");
19+
}
20+
21+
#[test]
22+
fn test_prove_and_verify_ultra_honk_keccak_multiplier2() {
23+
let num_public_inputs = get_num_public_inputs_from_circuit(MULTIPLIER2_BYTECODE).unwrap();
24+
assert_eq!(num_public_inputs, 1, "Multiplier2 circuit should have 1 public inputs");
25+
26+
setup_srs_from_bytecode(MULTIPLIER2_BYTECODE, None, false).unwrap();
27+
28+
let initial_witness = from_vec_to_witness_map(vec![3 as u128, 5 as u128, 15 as u128]).unwrap();
29+
let vk = get_ultra_honk_keccak_verification_key(MULTIPLIER2_BYTECODE, false, false).unwrap();
30+
let proof = prove_ultra_honk_keccak(MULTIPLIER2_BYTECODE, initial_witness, vk.clone(), false, false).unwrap();
31+
32+
// Parse the proof into separated components
33+
let proof_with_public_inputs = parse_proof_with_public_inputs(&proof, num_public_inputs).unwrap();
34+
35+
// Combine the proof and public inputs back into a single byte vector
36+
let combined_proof = combine_proof_and_public_inputs(proof_with_public_inputs.proof, proof_with_public_inputs.public_inputs);
37+
38+
let verdict = verify_ultra_honk_keccak(combined_proof, vk, false).unwrap();
39+
assert_eq!(verdict, true, "Multiplier2 circuit should be valid");
40+
}

noir/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ pub use acvm::*;
33

44
pub mod execute;
55
pub mod witness;
6-
pub mod circuit;
6+
pub mod circuit;
7+
pub mod utils;
78
mod backends;
89

910
#[cfg(feature = "barretenberg")]

noir/src/utils/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
pub mod proof_utils;
2+
3+
// Re-export commonly used items for convenience
4+
pub use proof_utils::{
5+
ProofWithPublicInputs,
6+
get_num_public_inputs_from_circuit,
7+
parse_proof_with_public_inputs,
8+
combine_proof_and_public_inputs,
9+
};

noir/src/utils/proof_utils.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use crate::circuit::get_program;
2+
3+
/// Struct representing a proof with separated public inputs
4+
#[derive(Debug, Clone)]
5+
pub struct ProofWithPublicInputs {
6+
/// The proof without public inputs
7+
pub proof: Vec<u8>,
8+
/// The public inputs as an array of 32-byte values
9+
pub public_inputs: Vec<Vec<u8>>,
10+
/// The number of public inputs
11+
pub num_public_inputs: usize,
12+
}
13+
14+
/// Get the number of public inputs from a circuit's bytecode
15+
/// This extracts the circuit information to count public parameters
16+
pub fn get_num_public_inputs_from_circuit(circuit_bytecode: &str) -> Result<usize, String> {
17+
let program = get_program(circuit_bytecode)
18+
.map_err(|e| format!("Failed to get program: {}", e))?;
19+
20+
// Get the first function (main function)
21+
let main_function = program.functions.first()
22+
.ok_or("No functions found in program")?;
23+
24+
// Count public parameters directly from the circuit
25+
let num_public_inputs = main_function.public_parameters.0.len();
26+
27+
Ok(num_public_inputs)
28+
}
29+
30+
/// Parse a combined proof (proof + public inputs) into separated components
31+
pub fn parse_proof_with_public_inputs(
32+
combined_proof: &[u8],
33+
num_public_inputs: usize
34+
) -> Result<ProofWithPublicInputs, String> {
35+
let public_inputs_size = num_public_inputs * 32; // Each field element is 32 bytes
36+
37+
if combined_proof.len() < public_inputs_size {
38+
return Err(format!(
39+
"Combined proof too small: {} bytes, expected at least {} bytes for {} public inputs",
40+
combined_proof.len(),
41+
public_inputs_size,
42+
num_public_inputs
43+
));
44+
}
45+
46+
// Public inputs are at the beginning of the combined proof
47+
let public_inputs_bytes = combined_proof[..public_inputs_size].to_vec();
48+
let proof_data = combined_proof[public_inputs_size..].to_vec();
49+
50+
if proof_data.len() % 32 != 0 {
51+
return Err("Proof data must be a multiple of 32 bytes".to_string());
52+
}
53+
54+
let public_inputs: Vec<Vec<u8>> = public_inputs_bytes
55+
.chunks(32)
56+
.map(|chunk| chunk.to_vec())
57+
.collect();
58+
59+
Ok(ProofWithPublicInputs {
60+
proof: proof_data,
61+
public_inputs: public_inputs,
62+
num_public_inputs,
63+
})
64+
}
65+
66+
/// Combine proof and public inputs back into a single proof with public inputs
67+
pub fn combine_proof_and_public_inputs(
68+
proof: Vec<u8>,
69+
public_inputs: Vec<Vec<u8>>,
70+
) -> Vec<u8> {
71+
let mut combined = Vec::new();
72+
73+
// Add public inputs first (32 bytes each)
74+
for public_input in &public_inputs {
75+
combined.extend_from_slice(public_input);
76+
}
77+
78+
combined.extend_from_slice(&proof);
79+
80+
combined
81+
}

0 commit comments

Comments
 (0)