From a23dd31ed844e19cee730eef74752d1aaac0e120 Mon Sep 17 00:00:00 2001 From: martyall Date: Thu, 30 Jan 2025 00:38:06 -0800 Subject: [PATCH 1/3] add verify-storage-proof command and stub --- saffron/src/cli.rs | 26 ++++++++++++++++++++++++++ saffron/src/main.rs | 3 +++ 2 files changed, 29 insertions(+) diff --git a/saffron/src/cli.rs b/saffron/src/cli.rs index 34e8a71a27..8cae3445ef 100644 --- a/saffron/src/cli.rs +++ b/saffron/src/cli.rs @@ -90,6 +90,30 @@ pub struct StorageProofArgs { pub challenge: HexString, } +#[derive(Parser)] +pub struct VerifyStorageProofArgs { + #[arg(long = "srs-filepath", value_name = "SRS_FILEPATH")] + pub srs_cache: Option, + + #[arg( + long, + short = 'c', + value_name = "COMMITMENT", + help = "commitment (hex encoded)" + )] + pub commitment: HexString, + + #[arg( + long = "challenge", + value_name = "CHALLENGE", + help = "challenge (hex encoded" + )] + pub challenge: HexString, + + #[arg(long, short = 'p', value_name = "PROOF", help = "proof (hex encoded)")] + pub proof: HexString, +} + #[derive(Parser)] #[command( name = "saffron", @@ -105,4 +129,6 @@ pub enum Commands { ComputeCommitment(ComputeCommitmentArgs), #[command(name = "storage-proof")] StorageProof(StorageProofArgs), + #[command(name = "verify-storage-proof")] + VerifyStorageProof(VerifyStorageProofArgs), } diff --git a/saffron/src/main.rs b/saffron/src/main.rs index d11b9dbbdc..cebef7e7cc 100644 --- a/saffron/src/main.rs +++ b/saffron/src/main.rs @@ -139,5 +139,8 @@ pub fn main() -> Result<()> { println!("{}", proof); Ok(()) } + cli::Commands::VerifyStorageProof(_) => { + todo!() + } } } From 9ff0ae7ae6f222f8b2792307a7b346b40ef64672 Mon Sep 17 00:00:00 2001 From: martyall Date: Thu, 30 Jan 2025 01:42:52 -0800 Subject: [PATCH 2/3] store the folded commitment and corresponding alpha in the blob metadata --- Cargo.lock | 1 - saffron/Cargo.toml | 1 - saffron/src/blob.rs | 31 +++++++++++++++++++++------ saffron/src/commitment.rs | 24 +++++++++++++++++++-- saffron/src/main.rs | 44 ++++++++++++++++++--------------------- saffron/src/proof.rs | 18 +++++++--------- 6 files changed, 74 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f99bb2c77d..011631b52c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2675,7 +2675,6 @@ dependencies = [ "rmp-serde", "serde", "serde_with", - "sha3", "time", "tracing", "tracing-subscriber", diff --git a/saffron/Cargo.toml b/saffron/Cargo.toml index 19b8bde121..fbedcc9bdb 100644 --- a/saffron/Cargo.toml +++ b/saffron/Cargo.toml @@ -34,7 +34,6 @@ rayon.workspace = true rmp-serde.workspace = true serde.workspace = true serde_with.workspace = true -sha3.workspace = true time = { version = "0.3", features = ["macros"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = [ "ansi", "env-filter", "fmt", "time" ] } diff --git a/saffron/src/blob.rs b/saffron/src/blob.rs index e43c6e91fb..2141215c36 100644 --- a/saffron/src/blob.rs +++ b/saffron/src/blob.rs @@ -1,7 +1,12 @@ -use crate::utils::{decode_into, encode_for_domain}; +use crate::{ + commitment::fold_commitments, + utils::{decode_into, encode_for_domain}, +}; use ark_ff::PrimeField; use ark_poly::{univariate::DensePolynomial, EvaluationDomain, Evaluations}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use kimchi::curve::KimchiCurve; +use mina_poseidon::FqSponge; use o1_utils::FieldHelpers; use poly_commitment::{commitment::CommitmentCurve, ipa::SRS, PolyComm, SRS as _}; use rayon::prelude::*; @@ -18,6 +23,9 @@ pub struct FieldBlob { pub n_bytes: usize, pub domain_size: usize, pub commitments: Vec>, + pub folded_commitment: PolyComm, + #[serde_as(as = "o1_utils::serialization::SerdeAs")] + pub alpha: G::ScalarField, #[serde_as(as = "Vec")] pub data: Vec>, } @@ -33,9 +41,12 @@ fn commit_to_blob_data( .collect() } -impl FieldBlob { +impl FieldBlob { #[instrument(skip_all, level = "debug")] - pub fn encode>( + pub fn encode< + D: EvaluationDomain, + EFqSponge: Clone + FqSponge, + >( srs: &SRS, domain: D, bytes: &[u8], @@ -52,6 +63,11 @@ impl FieldBlob { let commitments = commit_to_blob_data(srs, &data); + let (folded_commitment, alpha) = { + let mut sponge = EFqSponge::new(G::other_curve_sponge_params()); + fold_commitments(&mut sponge, &commitments) + }; + debug!( "Encoded {:.2} MB into {} polynomials", bytes.len() as f32 / 1_000_000.0, @@ -62,6 +78,8 @@ impl FieldBlob { n_bytes: bytes.len(), domain_size, commitments, + folded_commitment, + alpha, data, } } @@ -173,7 +191,8 @@ mod tests { use super::*; use ark_poly::Radix2EvaluationDomain; - use mina_curves::pasta::{Fp, Vesta}; + use mina_curves::pasta::{Fp, Vesta, VestaParameters}; + use mina_poseidon::{constants::PlonkSpongeConstantsKimchi, sponge::DefaultFqSponge}; use once_cell::sync::Lazy; use proptest::prelude::*; use test_utils::*; @@ -194,7 +213,7 @@ mod tests { #![proptest_config(ProptestConfig::with_cases(20))] #[test] fn test_round_trip_blob_encoding(BlobData(xs) in BlobData::arbitrary()) - { let blob = FieldBlob::::encode(&*SRS, *DOMAIN, &xs); + { let blob = FieldBlob::::encode::<_, DefaultFqSponge>(&*SRS, *DOMAIN, &xs); let bytes = rmp_serde::to_vec(&blob).unwrap(); let a = rmp_serde::from_slice(&bytes).unwrap(); // check that ark-serialize is behaving as expected @@ -211,7 +230,7 @@ mod tests { fn test_user_and_storage_provider_commitments_equal(BlobData(xs) in BlobData::arbitrary()) { let elems = encode_for_domain(&*DOMAIN, &xs); let user_commitments = commit_to_field_elems(&*SRS, *DOMAIN, elems); - let blob = FieldBlob::::encode(&*SRS, *DOMAIN, &xs); + let blob = FieldBlob::::encode::<_, DefaultFqSponge>(&*SRS, *DOMAIN, &xs); prop_assert_eq!(user_commitments, blob.commitments); } } diff --git a/saffron/src/commitment.rs b/saffron/src/commitment.rs index 55dfe24966..471103c767 100644 --- a/saffron/src/commitment.rs +++ b/saffron/src/commitment.rs @@ -1,6 +1,7 @@ use ark_ec::AffineRepr; use ark_ff::One; use ark_poly::{Evaluations, Radix2EvaluationDomain as D}; +use kimchi::curve::KimchiCurve; use mina_poseidon::FqSponge; use poly_commitment::{ commitment::{absorb_commitment, CommitmentCurve}, @@ -32,7 +33,7 @@ pub fn fold_commitments< >( sponge: &mut EFqSponge, commitments: &[PolyComm], -) -> PolyComm { +) -> (PolyComm, G::ScalarField) { for commitment in commitments { absorb_commitment(sponge, commitment) } @@ -45,5 +46,24 @@ pub fn fold_commitments< Some(res) }) .collect::>(); - PolyComm::multi_scalar_mul(&commitments.iter().collect::>(), &powers) + ( + PolyComm::multi_scalar_mul(&commitments.iter().collect::>(), &powers), + alpha, + ) +} + +pub fn user_commitment< + G: KimchiCurve, + EFqSponge: Clone + FqSponge, +>( + srs: &SRS, + domain: D, + field_elems: Vec>, +) -> PolyComm { + let commitments = commit_to_field_elems(srs, domain, field_elems); + let (commitment, _) = { + let mut sponge = EFqSponge::new(G::other_curve_sponge_params()); + fold_commitments(&mut sponge, &commitments) + }; + commitment } diff --git a/saffron/src/main.rs b/saffron/src/main.rs index cebef7e7cc..dcc4805827 100644 --- a/saffron/src/main.rs +++ b/saffron/src/main.rs @@ -9,9 +9,9 @@ use rand::rngs::OsRng; use saffron::{ blob::FieldBlob, cli::{self, HexString}, - commitment, env, proof, utils, + commitment::user_commitment, + env, proof, utils, }; -use sha3::{Digest, Sha3_256}; use std::{ fs::File, io::{Read, Write}, @@ -20,6 +20,8 @@ use tracing::{debug, debug_span}; const DEFAULT_SRS_SIZE: usize = 1 << 16; +type FqSponge = DefaultFqSponge; + fn get_srs(cache: Option) -> (SRS, Radix2EvaluationDomain) { let res = match cache { Some(cache) => { @@ -73,17 +75,16 @@ fn encode_file(args: cli::EncodeFileArgs) -> Result<()> { let mut file = File::open(args.input)?; let mut buf = Vec::new(); file.read_to_end(&mut buf)?; - let blob = FieldBlob::::encode(&srs, domain, &buf); + let blob = FieldBlob::::encode::<_, FqSponge>(&srs, domain, &buf); args.assert_commitment .into_iter() .for_each(|asserted_commitment| { - let bytes = rmp_serde::to_vec(&blob.commitments).unwrap(); - let hash = Sha3_256::new().chain_update(bytes).finalize().to_vec(); - if asserted_commitment.0 != hash { + let c = rmp_serde::from_slice(&asserted_commitment.0).unwrap(); + if blob.folded_commitment != c { panic!( "commitment hash mismatch: asserted {}, computed {}", asserted_commitment, - HexString(hash) + HexString(rmp_serde::encode::to_vec(&c).unwrap()) ); } }); @@ -99,28 +100,23 @@ pub fn compute_commitment(args: cli::ComputeCommitmentArgs) -> Result let mut buf = Vec::new(); file.read_to_end(&mut buf)?; let field_elems = utils::encode_for_domain(&domain_fp, &buf); - let commitments = commitment::commit_to_field_elems(&srs, domain_fp, field_elems); - let bytes = rmp_serde::to_vec(&commitments).unwrap(); - let hash = Sha3_256::new().chain_update(bytes).finalize().to_vec(); - Ok(HexString(hash)) + let commitment = user_commitment::<_, FqSponge>(&srs, domain_fp, field_elems); + let res = rmp_serde::to_vec(&commitment)?; + Ok(HexString(res)) } pub fn storage_proof(args: cli::StorageProofArgs) -> Result { let file = File::open(args.input)?; let blob: FieldBlob = rmp_serde::decode::from_read(file)?; - let proof = - { - let (srs, _) = get_srs(args.srs_cache); - let group_map = ::Map::setup(); - let mut rng = OsRng; - let evaluation_point = utils::encode(&args.challenge.0); - proof::storage_proof::< - Vesta, - DefaultFqSponge, - >(&srs, &group_map, blob, evaluation_point, &mut rng) - }; - let bytes = rmp_serde::to_vec(&proof).unwrap(); - Ok(HexString(bytes)) + let proof = { + let (srs, _) = get_srs(args.srs_cache); + let group_map = ::Map::setup(); + let mut rng = OsRng; + let evaluation_point = utils::encode(&args.challenge.0); + proof::storage_proof::(&srs, &group_map, blob, evaluation_point, &mut rng) + }; + let res = rmp_serde::to_vec(&proof)?; + Ok(HexString(res)) } pub fn main() -> Result<()> { diff --git a/saffron/src/proof.rs b/saffron/src/proof.rs index fd1dd03657..a13b063ba7 100644 --- a/saffron/src/proof.rs +++ b/saffron/src/proof.rs @@ -7,7 +7,7 @@ use kimchi::curve::KimchiCurve; use mina_poseidon::FqSponge; use o1_utils::ExtendedDensePolynomial; use poly_commitment::{ - commitment::{absorb_commitment, BatchEvaluationProof, CommitmentCurve, Evaluation}, + commitment::{BatchEvaluationProof, CommitmentCurve, Evaluation}, ipa::{OpeningProof, SRS}, utils::DensePolynomialOrEvaluations, PolyComm, @@ -37,19 +37,15 @@ pub fn storage_proof::new( @@ -162,7 +158,7 @@ mod tests { ); fold_commitments(&mut fq_sponge, &user_commitments) }; - let blob = FieldBlob::::encode(&*SRS, *DOMAIN, &data); + let blob = FieldBlob::::encode::<_, DefaultFqSponge>(&*SRS, *DOMAIN, &data); let evaluation_point = Fp::rand(&mut rng); let proof = storage_proof::< Vesta, DefaultFqSponge From cfa36b97a25016164f014b17173e25f7e977e282 Mon Sep 17 00:00:00 2001 From: martyall Date: Thu, 30 Jan 2025 02:01:45 -0800 Subject: [PATCH 3/3] added verify-storage-proof command and updated e2e script --- saffron/src/main.rs | 27 +++++++++++++++++++++++---- saffron/test-encoding.sh | 10 ++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/saffron/src/main.rs b/saffron/src/main.rs index dcc4805827..a5a8a76340 100644 --- a/saffron/src/main.rs +++ b/saffron/src/main.rs @@ -10,7 +10,9 @@ use saffron::{ blob::FieldBlob, cli::{self, HexString}, commitment::user_commitment, - env, proof, utils, + env, + proof::{self, StorageProof}, + utils, }; use std::{ fs::File, @@ -119,6 +121,25 @@ pub fn storage_proof(args: cli::StorageProofArgs) -> Result { Ok(HexString(res)) } +pub fn verify_storage_proof(args: cli::VerifyStorageProofArgs) -> Result<()> { + let (srs, _) = get_srs(args.srs_cache); + let group_map = ::Map::setup(); + let commitment = rmp_serde::from_slice(&args.commitment.0)?; + let evaluation_point = utils::encode(&args.challenge.0); + let proof: StorageProof = rmp_serde::from_slice(&args.proof.0)?; + let mut rng = OsRng; + let res = proof::verify_storage_proof::( + &srs, + &group_map, + commitment, + evaluation_point, + &proof, + &mut rng, + ); + assert!(res); + Ok(()) +} + pub fn main() -> Result<()> { env::init_console_subscriber(); let args = cli::Commands::parse(); @@ -135,8 +156,6 @@ pub fn main() -> Result<()> { println!("{}", proof); Ok(()) } - cli::Commands::VerifyStorageProof(_) => { - todo!() - } + cli::Commands::VerifyStorageProof(args) => verify_storage_proof(args), } } diff --git a/saffron/test-encoding.sh b/saffron/test-encoding.sh index a8ecbfeeb3..0402c71d0d 100755 --- a/saffron/test-encoding.sh +++ b/saffron/test-encoding.sh @@ -24,6 +24,7 @@ fi COMMITMENT=$(cargo run --release --bin saffron compute-commitment -i "$INPUT_FILE" $SRS_ARG | tee /dev/stderr | tail -n 1) + # Run encode with captured commitment echo "Encoding $INPUT_FILE to $ENCODED_FILE" if ! cargo run --release --bin saffron encode -i "$INPUT_FILE" -o "$ENCODED_FILE" --assert-commitment "$COMMITMENT" $SRS_ARG; then @@ -44,6 +45,15 @@ if [ $? -ne 0 ]; then exit 1 fi +# Verify the storage proof +echo "Verifying proof..." +if ! cargo run --release --bin saffron verify-storage-proof --commitment "$COMMITMENT" --challenge "$CHALLENGE" --proof "$PROOF" $SRS_ARG; then + echo "Proof verification failed" + exit 1 +fi +echo "✓ Proof verification successful" + + # Run decode echo "Decoding $ENCODED_FILE to $DECODED_FILE" if ! cargo run --release --bin saffron decode -i "$ENCODED_FILE" -o "$DECODED_FILE" $SRS_ARG; then