Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
95 changes: 55 additions & 40 deletions batcher/aligned-batcher/src/mina/mod.rs
Original file line number Diff line number Diff line change
@@ -1,82 +1,97 @@
use std::array;
use std::array::TryFromSliceError;

use base64::prelude::*;
use log::{debug, warn};

const STATE_HASH_SIZE: usize = 32;

pub fn verify_protocol_state_proof_integrity(proof: &[u8], public_input: &[u8]) -> bool {
pub fn verify_proof_integrity(proof: &[u8], public_input: &[u8]) -> bool {
debug!("Checking Mina protocol state proof");
if let Err(err) = check_protocol_state_proof(proof) {
if let Err(err) = check_proof(proof) {
warn!("Protocol state proof check failed: {}", err);
return false;
}

debug!("Checking Mina protocol state public inputs");
if let Err(err) = check_protocol_state_pub(public_input) {
if let Err(err) = check_pub_inputs(public_input) {
warn!("Protocol state public inputs check failed: {}", err);
return false;
}

true
}

pub fn check_protocol_state_proof(protocol_state_proof_bytes: &[u8]) -> Result<(), String> {
// TODO(xqft): check binprot deserialization
let protocol_state_proof_base64 =
std::str::from_utf8(protocol_state_proof_bytes).map_err(|err| err.to_string())?;
BASE64_URL_SAFE
.decode(protocol_state_proof_base64)
.map_err(|err| err.to_string())?;
pub fn check_hash(pub_inputs: &[u8], offset: &mut usize) -> Result<(), String> {
pub_inputs
.get(*offset..*offset + STATE_HASH_SIZE)
.ok_or("Failed to slice candidate hash".to_string())?;

*offset += STATE_HASH_SIZE;

Ok(())
}

pub fn check_protocol_state_pub(protocol_state_pub: &[u8]) -> Result<(), String> {
// TODO(xqft): check hash and binprot deserialization
let candidate_protocol_state_len =
check_protocol_state_and_hash(protocol_state_pub, STATE_HASH_SIZE)?;
pub fn check_state(pub_inputs: &[u8], offset: &mut usize) -> Result<(), String> {
let state_len: usize = pub_inputs
.get(*offset..*offset + 4)
.ok_or("Failed to slice state len".to_string())
.and_then(|slice| {
slice
.try_into()
.map_err(|err: TryFromSliceError| err.to_string())
})
.map(u32::from_be_bytes)
.and_then(|len| usize::try_from(len).map_err(|err| err.to_string()))?;

pub_inputs
.get(*offset + 4..*offset + 4 + state_len)
.ok_or("Failed to slice state".to_string())
.and_then(|bytes| std::str::from_utf8(bytes).map_err(|err| err.to_string()))
.and_then(|base64| {
BASE64_STANDARD
.decode(base64)
.map_err(|err| err.to_string())
})?;
*offset += 4 + state_len;

Ok(())
}

let _tip_protocol_state_len = check_protocol_state_and_hash(
protocol_state_pub,
STATE_HASH_SIZE + 4 + candidate_protocol_state_len + STATE_HASH_SIZE,
)?;
pub fn check_pub_inputs(pub_inputs: &[u8]) -> Result<(), String> {
let mut offset = 0;

check_hash(pub_inputs, &mut offset)?; // candidate hash
check_hash(pub_inputs, &mut offset)?; // tip hash

check_state(pub_inputs, &mut offset)?; // candidate state
check_state(pub_inputs, &mut offset)?; // tip state

Ok(())
}

fn check_protocol_state_and_hash(protocol_state_pub: &[u8], start: usize) -> Result<usize, String> {
let protocol_state_len_vec: Vec<_> = protocol_state_pub.iter().skip(start).take(4).collect();
let protocol_state_len_bytes: [u8; 4] = array::from_fn(|i| protocol_state_len_vec[i].clone());
let protocol_state_len = u32::from_be_bytes(protocol_state_len_bytes) as usize;

let protocol_state_bytes: Vec<_> = protocol_state_pub
.iter()
.skip(start + 4)
.take(protocol_state_len)
.map(|byte| byte.clone())
.collect();
let protocol_state_base64 =
std::str::from_utf8(protocol_state_bytes.as_slice()).map_err(|err| err.to_string())?;
BASE64_STANDARD
.decode(protocol_state_base64)
.map_err(|err| err.to_string())?;

Ok(protocol_state_len)
pub fn check_proof(proof_bytes: &[u8]) -> Result<(), String> {
std::str::from_utf8(proof_bytes)
.map_err(|err| err.to_string())
.and_then(|base64| {
BASE64_URL_SAFE
.decode(base64)
.map_err(|err| err.to_string())
})?;
Ok(())
}

#[cfg(test)]
mod test {
use super::verify_protocol_state_proof_integrity;
use super::verify_proof_integrity;

const PROTOCOL_STATE_PROOF_BYTES: &[u8] =
include_bytes!("../../../../batcher/aligned/test_files/mina/protocol_state.proof");
const PROTOCOL_STATE_PUB_BYTES: &[u8] =
include_bytes!("../../../../batcher/aligned/test_files/mina/protocol_state.pub");

#[test]
fn verify_protocol_state_proof_integrity_does_not_fail() {
assert!(verify_protocol_state_proof_integrity(
fn verify_proof_integrity_does_not_fail() {
assert!(verify_proof_integrity(
PROTOCOL_STATE_PROOF_BYTES,
PROTOCOL_STATE_PUB_BYTES,
));
Expand Down
4 changes: 2 additions & 2 deletions batcher/aligned-batcher/src/zk_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::halo2::ipa::verify_halo2_ipa;
use crate::halo2::kzg::verify_halo2_kzg;
use crate::risc_zero::verify_risc_zero_proof;
use crate::sp1::verify_sp1_proof;
use crate::{gnark::verify_gnark, mina::verify_protocol_state_proof_integrity};
use crate::{gnark::verify_gnark, mina::verify_proof_integrity};
use aligned_sdk::core::types::{ProvingSystemId, VerificationData};
use log::{debug, warn};

Expand Down Expand Up @@ -86,7 +86,7 @@ pub(crate) fn verify(verification_data: &VerificationData) -> bool {
.pub_input
.as_ref()
.expect("Public input is required");
verify_protocol_state_proof_integrity(&verification_data.proof, pub_input)
verify_proof_integrity(&verification_data.proof, pub_input)
// TODO(xqft): add Pickles aggregator checks which are run alongside the Kimchi
// verifier. These checks are fast and if they aren't successful then the Pickles proof
// isn't valid.
Expand Down
Loading