Skip to content

Commit

Permalink
Explicit handling of public inputs
Browse files Browse the repository at this point in the history
  • Loading branch information
JackPiri committed Jun 5, 2024
1 parent b912231 commit e12b4c3
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 42 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
/Cargo.lock
lcov.info
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@ A verifier for [RISC-Zero](https://github.com/risc0/risc0) STARK proofs.
This crate provides a way for deserializing the proof and the verification key (aka image id) and a function to check if the proof is correct:

```rust
use risc0_verifier::{verify};
use risc0_verifier::{verify, extract_pubs_from_full_proof};
use serde::Deserialize;

#[derive(Deserialize)]
struct Data {
proof_raw_data: String,
image_id: [u32; 8],
full_proof: String,
}

let Data {
proof_raw_data,
image_id,
} = serde_json::from_reader(std::fs::File::open("./resources/valid_proof_1.json").unwrap())
.unwrap();
full_proof,
} = serde_json::from_reader(std::fs::File::open("./resources/valid_proof_1.json").unwrap()).unwrap();

let proof_raw_data = <Vec<u8>>::try_from(hex::decode(proof_raw_data).unwrap()).unwrap();
let full_proof = <Vec<u8>>::try_from(hex::decode(full_proof).unwrap()).unwrap();
let pubs = extract_pubs_from_full_proof(&full_proof).unwrap();

assert!(verify(&proof_raw_data, image_id.into()).is_ok());
assert!(verify(image_id.into(), &full_proof, pubs).is_ok());
```

## Develop
Expand Down
4 changes: 2 additions & 2 deletions resources/valid_proof_1.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions resources/valid_proof_2.json

Large diffs are not rendered by default.

41 changes: 35 additions & 6 deletions src/deserializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,48 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::proof::{FullProof, PublicInputs};
use risc0_zkvm::Receipt;
use snafu::Snafu;

/// Deserialization error
#[derive(Debug, Snafu)]
pub enum DeserializeError {
#[snafu(display("Invalid data for deserialization: [{:?}...{:?}]", first, last))]
InvalidData { first: Option<u8>, last: Option<u8> },
#[snafu(display("Invalid data for deserialization"))]
InvalidData,
#[snafu(display("Invalid public inputs for deserialization"))]
InvalidPublicInputs,
}

pub fn deserialize(byte_stream: &[u8]) -> Result<Receipt, DeserializeError> {
bincode::deserialize(byte_stream).map_err(|_x| DeserializeError::InvalidData {
first: byte_stream.first().copied(),
last: byte_stream.last().copied(),
})
bincode::deserialize(byte_stream).map_err(|_x| DeserializeError::InvalidData)
}

/// Extract public inputs from full proof
pub fn extract_pubs_from_full_proof(
full_proof: FullProof,
) -> Result<PublicInputs, DeserializeError> {
let receipt = deserialize(full_proof)?;

let mut pubs: PublicInputs = [0; 32];
let len = receipt.journal.bytes.len();
if len <= 32 {
pubs[..len].copy_from_slice(&receipt.journal.bytes[..len]);
} else {
return Err(DeserializeError::InvalidPublicInputs);
}

Ok(pubs)
}

pub fn extract_pubs_from_receipt(receipt: &Receipt) -> Result<PublicInputs, DeserializeError> {
let mut pubs: PublicInputs = [0; 32];
let len = receipt.journal.bytes.len();
if len <= 32 {
pubs[..len].copy_from_slice(&receipt.journal.bytes[..len]);
} else {
return Err(DeserializeError::InvalidPublicInputs);
}

Ok(pubs)
}
23 changes: 16 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ mod deserializer;
mod key;
mod proof;

use deserializer::{deserialize, DeserializeError};
pub use deserializer::extract_pubs_from_full_proof;
use deserializer::{deserialize, extract_pubs_from_receipt, DeserializeError};
pub use key::Vk;
pub use proof::ProofRawData;
pub use proof::{FullProof, PublicInputs};
use snafu::Snafu;

/// Deserialization error.
Expand All @@ -36,6 +37,9 @@ pub enum VerifyError {
#[snafu(source)]
cause: DeserializeError,
},
/// Mismatching public inputs
#[snafu(display("Mismatching public inputs"))]
MismatchingPublicInputs,
/// Verification failure
#[snafu(display("Failed to verify: [{}]", cause))]
Failure {
Expand All @@ -56,11 +60,16 @@ impl From<risc0_zkp::verify::VerificationError> for VerifyError {
}
}

/// Verify the given proof raw data `proof` using verification key `image_id`.
/// Verify the given proof and public inputs `full_proof` using verification key `image_id`.
/// Can fail if:
/// - the raw proof data is not serializable as a `risc0_zkvm::Receipt`
/// - the full proof is not serializable as a `risc0_zkvm::Receipt`
/// - the receipt is not valid for the given verification key
pub fn verify(raw_proof_data: ProofRawData, image_id: Vk) -> Result<(), VerifyError> {
let receipt = deserialize(raw_proof_data)?;
receipt.verify(image_id.0).map_err(Into::into)
pub fn verify(image_id: Vk, full_proof: FullProof, pubs: PublicInputs) -> Result<(), VerifyError> {
let receipt = deserialize(full_proof)?;
let extracted_pubs = extract_pubs_from_receipt(&receipt)?;
if pubs == extracted_pubs {
receipt.verify(image_id.0).map_err(Into::into)
} else {
Err(VerifyError::MismatchingPublicInputs)
}
}
7 changes: 5 additions & 2 deletions src/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

/// The proof raw data (a serialized `risc0_zkvm::Receipt`).
pub type ProofRawData<'a> = &'a [u8];
/// The full proof (a serialized `risc0_zkvm::Receipt`) containing both proof raw data and public inputs
pub type FullProof<'a> = &'a [u8];

/// The public inputs (a serialized `risc0_zkvm::Journal`)
pub type PublicInputs = [u8; 32];
48 changes: 32 additions & 16 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,58 +13,74 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use risc0_verifier::{verify, VerifyError};
use risc0_verifier::{extract_pubs_from_full_proof, verify, VerifyError};
use rstest::rstest;
use serde::Deserialize;
use std::path::{Path, PathBuf};

fn load_data(path: &Path) -> (Vec<u8>, [u32; 8]) {
fn load_data(path: &Path) -> ([u32; 8], Vec<u8>, [u8; 32]) {
#[derive(Deserialize)]
struct Data {
proof_raw_data: String,
image_id: [u32; 8],
full_proof: String,
}

let Data {
proof_raw_data,
image_id,
full_proof,
} = serde_json::from_reader(std::fs::File::open(path).unwrap()).unwrap();

let proof_raw_data = <Vec<u8>>::try_from(hex::decode(proof_raw_data).unwrap()).unwrap();
let full_proof = <Vec<u8>>::try_from(hex::decode(full_proof).unwrap()).unwrap();
let pubs = extract_pubs_from_full_proof(&full_proof).unwrap();

(proof_raw_data, image_id)
(image_id, full_proof, pubs)
}

#[rstest]
fn should_verify_valid_proof(#[files("./resources/valid_proof_*.json")] path: PathBuf) {
let (proof_raw_data, image_id_data) = load_data(&path);
let (image_id, full_proof, pubs) = load_data(&path);

assert!(verify(&proof_raw_data, image_id_data.into()).is_ok());
assert!(verify(image_id.into(), &full_proof, pubs).is_ok());
}

#[test]
fn should_not_verify_invalid_proof() {
let (mut proof_raw_data, image_id_data) =
load_data(Path::new("./resources/valid_proof_1.json"));
let (image_id, mut full_proof, pubs) = load_data(Path::new("./resources/valid_proof_1.json"));

proof_raw_data[0] = proof_raw_data.first().unwrap().wrapping_add(1);
full_proof[0] = full_proof.first().unwrap().wrapping_add(1);

assert!(matches!(
verify(&proof_raw_data, image_id_data.into()),
verify(image_id.into(), &full_proof, pubs),
Err(VerifyError::InvalidData { .. })
));
}

#[test]
fn should_not_verify_mismatching_inputs() {
let (image_id, mut full_proof, pubs) = load_data(Path::new("./resources/valid_proof_1.json"));

let len = full_proof.len();
full_proof[len - 1] = full_proof.last().unwrap().wrapping_add(1);

assert!(matches!(
verify(image_id.into(), &full_proof, pubs),
Err(VerifyError::MismatchingPublicInputs { .. })
));
}

#[test]
fn should_not_verify_false_proof() {
let (mut proof_raw_data, image_id_data) =
let (image_id, mut full_proof, mut pubs) =
load_data(Path::new("./resources/valid_proof_1.json"));

let len = proof_raw_data.len();
proof_raw_data[len - 1] = proof_raw_data.last().unwrap().wrapping_add(1);
// we know journal.bytes for that proof is 4 bytes
let journal_bytes_size = 4;
let len = full_proof.len();
full_proof[len - journal_bytes_size] = full_proof[len - journal_bytes_size].wrapping_add(1);
pubs[0] = pubs[0].wrapping_add(1);

assert!(matches!(
verify(&proof_raw_data, image_id_data.into()),
verify(image_id.into(), &full_proof, pubs),
Err(VerifyError::Failure { .. })
));
}

0 comments on commit e12b4c3

Please sign in to comment.