From c7e58dd7cf69e7f5224cb9fb43fa74355f6e7cab Mon Sep 17 00:00:00 2001 From: Friedel Ziegelmayer Date: Mon, 25 Oct 2021 22:28:49 +0200 Subject: [PATCH] feat: allow for window post proving on a partition basis (#1526) * * init decoupling-post-worker code * unify compound verification * reduce code duplication in partitioning * simplify partition count retrieval * feat: attempt to generate parameters on OS X * feat: make PartitionSnarkProof clone-able * fix: apply review feedback Co-authored-by: gegezai Co-authored-by: dignifiedquire Co-authored-by: nemo --- .circleci/config.yml | 1 + filecoin-proofs/src/api/post_util.rs | 230 ++++++++++++++------ filecoin-proofs/src/api/window_post.rs | 73 ++++++- filecoin-proofs/src/types/mod.rs | 4 + filecoin-proofs/tests/api.rs | 166 +++++++++++++- storage-proofs-post/src/fallback/vanilla.rs | 222 +++++++++++-------- 6 files changed, 528 insertions(+), 168 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c33cfbaa8..70ed65c95 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -294,6 +294,7 @@ jobs: - run: rustup default $(cat rust-toolchain) - run: cargo update - run: cargo fetch + - ensure_filecoin_parameters - run: name: Test Darwin command: | diff --git a/filecoin-proofs/src/api/post_util.rs b/filecoin-proofs/src/api/post_util.rs index f3ffbf69a..c734df82e 100644 --- a/filecoin-proofs/src/api/post_util.rs +++ b/filecoin-proofs/src/api/post_util.rs @@ -5,7 +5,7 @@ use std::path::Path; use anyhow::{anyhow, ensure, Context, Result}; use bincode::deserialize; use filecoin_hashers::Hasher; -use log::{info, trace}; +use log::{debug, info}; use storage_proofs_core::{ cache_key::CacheKey, merkle::MerkleTreeTrait, proof::ProofScheme, sector::SectorId, }; @@ -18,7 +18,7 @@ use crate::{ ChallengeSeed, FallbackPoStSectorProof, PoStConfig, PrivateReplicaInfo, ProverId, TemporaryAux, VanillaProof, }, - PoStType, + PartitionSnarkProof, PoStType, SnarkProof, SINGLE_PARTITION_PROOF_LEN, }; // Ensure that any associated cached data persisted is discarded. @@ -208,80 +208,29 @@ pub fn partition_vanilla_proofs( match post_config.typ { PoStType::Window => { for (j, sectors_chunk) in pub_inputs.sectors.chunks(num_sectors_per_chunk).enumerate() { - trace!("processing partition {}", j); - - let mut sector_proofs = Vec::with_capacity(num_sectors_per_chunk); - - for pub_sector in sectors_chunk.iter() { - let cur_proof = vanilla_proofs - .iter() - .find(|&proof| proof.sector_id == pub_sector.id) - .expect("failed to locate sector proof"); - - // Note: Window post requires all inclusion proofs (based on the challenge - // count per sector) per sector proof. - sector_proofs.extend(cur_proof.vanilla_proof.sectors.clone()); - } - - // If there were less than the required number of sectors provided, we duplicate the last one - // to pad the proof out, such that it works in the circuit part. - while sector_proofs.len() < num_sectors_per_chunk { - sector_proofs.push(sector_proofs[sector_proofs.len() - 1].clone()); - } - - partition_proofs.push(fallback::Proof::<::Proof> { - sectors: sector_proofs, - }); + let proof = single_partition_vanilla_proofs( + post_config, + pub_params, + &fallback::PublicInputs { + randomness: pub_inputs.randomness, + prover_id: pub_inputs.prover_id, + sectors: sectors_chunk.to_vec(), + k: Some(j), + }, + vanilla_proofs, + )?; + partition_proofs.push(proof); } } PoStType::Winning => { for (j, sectors_chunk) in vanilla_proofs.chunks(num_sectors_per_chunk).enumerate() { - trace!("processing partition {}", j); - - // Sanity check incoming structure - ensure!( - sectors_chunk.len() == 1, - "Invalid sector chunk for Winning PoSt" - ); - ensure!( - sectors_chunk[0].vanilla_proof.sectors.len() == 1, - "Invalid sector count for Winning PoSt chunk" - ); - - // Winning post sector_count is winning post challenges per sector - ensure!( - post_config.sector_count == sectors_chunk[j].vanilla_proof.sectors.len(), - "invalid number of sector proofs for Winning PoSt" - ); - - let mut sector_proofs = Vec::with_capacity(post_config.challenge_count); - let cur_sector_proof = §ors_chunk[0].vanilla_proof.sectors[0]; - - // Unroll inclusions proofs from the single provided sector_proof (per partition) - // into individual sector proofs, required for winning post. - for cur_inclusion_proof in cur_sector_proof.inclusion_proofs() { - sector_proofs.push(SectorProof { - inclusion_proofs: vec![cur_inclusion_proof.clone()], - comm_c: cur_sector_proof.comm_c, - comm_r_last: cur_sector_proof.comm_r_last, - }); - } - - // If there were less than the required number of sectors provided, we duplicate the last one - // to pad the proof out, such that it works in the circuit part. - while sector_proofs.len() < num_sectors_per_chunk { - sector_proofs.push(sector_proofs[sector_proofs.len() - 1].clone()); - } - - // Winning post Challenge count is the total winning post challenges - ensure!( - sector_proofs.len() == post_config.challenge_count, - "invalid number of partition proofs based on Winning PoSt challenges" - ); - - partition_proofs.push(fallback::Proof::<::Proof> { - sectors: sector_proofs, - }); + let proof = single_partition_vanilla_proofs( + post_config, + pub_params, + &fallback::FallbackPoSt::::with_partition(pub_inputs.clone(), Some(j)), + sectors_chunk, + )?; + partition_proofs.push(proof); } } } @@ -308,3 +257,140 @@ pub(crate) fn get_partitions_for_window_post( None } } + +pub fn single_partition_vanilla_proofs( + post_config: &PoStConfig, + pub_params: &fallback::PublicParams, + pub_inputs: &fallback::PublicInputs<::Domain>, + vanilla_proofs: &[FallbackPoStSectorProof], +) -> Result> { + info!("single_partition_vanilla_proofs:start"); + ensure!(pub_inputs.k.is_some(), "must have a partition index"); + let partition_index = pub_inputs.k.expect("prechecked"); + + debug!("processing partition: {}", partition_index); + ensure!( + post_config.typ == PoStType::Window || post_config.typ == PoStType::Winning, + "invalid post config type" + ); + + let num_sectors_per_chunk = pub_params.sector_count; + let num_sectors = pub_inputs.sectors.len(); + ensure!( + num_sectors <= num_sectors_per_chunk, + "can only prove a single partition" + ); + + // Note that the partition proofs returned are shaped differently + // based on which type of PoSt is being considered. + let partition_proof = match post_config.typ { + PoStType::Window => { + let sectors_chunk = &pub_inputs.sectors; + + let mut sector_proofs = Vec::with_capacity(num_sectors_per_chunk); + + for pub_sector in sectors_chunk.iter() { + let cur_proof = vanilla_proofs + .iter() + .find(|&proof| proof.sector_id == pub_sector.id) + .expect("failed to locate sector proof"); + + // Note: Window post requires all inclusion proofs (based on the challenge + // count per sector) per sector proof. + sector_proofs.extend(cur_proof.vanilla_proof.sectors.clone()); + } + + // If there were less than the required number of sectors provided, we duplicate the last one + // to pad the proof out, such that it works in the circuit part. + while sector_proofs.len() < num_sectors_per_chunk { + sector_proofs.push(sector_proofs[sector_proofs.len() - 1].clone()); + } + + fallback::Proof::<::Proof> { + sectors: sector_proofs, + } + } + PoStType::Winning => { + let sectors_chunk = vanilla_proofs; + // Sanity check incoming structure + ensure!( + sectors_chunk.len() == 1, + "Invalid sector chunk for Winning PoSt" + ); + ensure!( + sectors_chunk[0].vanilla_proof.sectors.len() == 1, + "Invalid sector count for Winning PoSt chunk" + ); + + // Winning post sector_count is winning post challenges per sector + ensure!( + post_config.sector_count + == sectors_chunk[partition_index].vanilla_proof.sectors.len(), + "invalid number of sector proofs for Winning PoSt" + ); + + let mut sector_proofs = Vec::with_capacity(post_config.challenge_count); + let cur_sector_proof = §ors_chunk[0].vanilla_proof.sectors[0]; + + // Unroll inclusions proofs from the single provided sector_proof (per partition) + // into individual sector proofs, required for winning post. + for cur_inclusion_proof in cur_sector_proof.inclusion_proofs() { + sector_proofs.push(SectorProof { + inclusion_proofs: vec![cur_inclusion_proof.clone()], + comm_c: cur_sector_proof.comm_c, + comm_r_last: cur_sector_proof.comm_r_last, + }); + } + + // If there were less than the required number of sectors provided, we duplicate the last one + // to pad the proof out, such that it works in the circuit part. + while sector_proofs.len() < num_sectors_per_chunk { + sector_proofs.push(sector_proofs[sector_proofs.len() - 1].clone()); + } + + // Winning post Challenge count is the total winning post challenges + ensure!( + sector_proofs.len() == post_config.challenge_count, + "invalid number of partition proofs based on Winning PoSt challenges" + ); + + fallback::Proof::<::Proof> { + sectors: sector_proofs, + } + } + }; + + info!("single_partition_vanilla_proofs:finish"); + + ensure!( + FallbackPoSt::::verify(pub_params, pub_inputs, &partition_proof)?, + "partitioned vanilla proofs failed to verify" + ); + + Ok(partition_proof) +} + +pub fn merge_window_post_partition_proofs( + mut proofs: Vec, +) -> Result { + let mut proof = Vec::with_capacity(proofs.len() * SINGLE_PARTITION_PROOF_LEN); + for p in proofs.iter_mut() { + proof.append(&mut p.0); + } + + Ok(proof) +} + +pub fn get_num_partition_for_fallback_post(config: &PoStConfig, num_sectors: usize) -> usize { + match config.typ { + PoStType::Window => { + let partitions = (num_sectors as f32 / config.sector_count as f32).ceil() as usize; + if partitions > 1 { + partitions + } else { + 1 + } + } + PoStType::Winning => 1, + } +} diff --git a/filecoin-proofs/src/api/window_post.rs b/filecoin-proofs/src/api/window_post.rs index 6707bd835..0a3214b87 100644 --- a/filecoin-proofs/src/api/window_post.rs +++ b/filecoin-proofs/src/api/window_post.rs @@ -14,14 +14,17 @@ use storage_proofs_post::fallback::{ }; use crate::{ - api::{as_safe_commitment, get_partitions_for_window_post, partition_vanilla_proofs}, + api::{ + as_safe_commitment, get_partitions_for_window_post, partition_vanilla_proofs, + single_partition_vanilla_proofs, + }, caches::{get_post_params, get_post_verifying_key}, parameters::window_post_setup_params, types::{ ChallengeSeed, FallbackPoStSectorProof, PoStConfig, PrivateReplicaInfo, ProverId, PublicReplicaInfo, SnarkProof, }, - PoStType, + PartitionSnarkProof, PoStType, }; /// Generates a Window proof-of-spacetime with provided vanilla proofs. @@ -242,3 +245,69 @@ pub fn verify_window_post( Ok(true) } + +/// Generates a Window proof-of-spacetime with provided vanilla proofs of a single partition. +pub fn generate_single_window_post_with_vanilla( + post_config: &PoStConfig, + randomness: &ChallengeSeed, + prover_id: ProverId, + vanilla_proofs: Vec>, + partition_index: usize, +) -> Result { + info!("generate_single_window_post_with_vanilla:start"); + ensure!( + post_config.typ == PoStType::Window, + "invalid post config type" + ); + + let randomness_safe: ::Domain = + as_safe_commitment(randomness, "randomness")?; + let prover_id_safe: ::Domain = + as_safe_commitment(&prover_id, "prover_id")?; + + let vanilla_params = window_post_setup_params(post_config); + let partitions = get_partitions_for_window_post(vanilla_proofs.len(), post_config); + + let setup_params = compound_proof::SetupParams { + vanilla_params, + partitions, + priority: post_config.priority, + }; + + let pub_params: compound_proof::PublicParams<'_, FallbackPoSt<'_, Tree>> = + FallbackPoStCompound::setup(&setup_params)?; + let groth_params = get_post_params::(post_config)?; + + let mut pub_sectors = Vec::with_capacity(vanilla_proofs.len()); + for vanilla_proof in &vanilla_proofs { + pub_sectors.push(PublicSector { + id: vanilla_proof.sector_id, + comm_r: vanilla_proof.comm_r, + }); + } + + let pub_inputs = fallback::PublicInputs { + randomness: randomness_safe, + prover_id: prover_id_safe, + sectors: pub_sectors, + k: Some(partition_index), + }; + + let partitioned_proofs = single_partition_vanilla_proofs( + post_config, + &pub_params.vanilla_params, + &pub_inputs, + &vanilla_proofs, + )?; + + let proof = FallbackPoStCompound::prove_with_vanilla( + &pub_params, + &pub_inputs, + vec![partitioned_proofs], + &groth_params, + )?; + + info!("generate_single_window_post_with_vanilla:finish"); + + proof.to_vec().map(PartitionSnarkProof) +} diff --git a/filecoin-proofs/src/types/mod.rs b/filecoin-proofs/src/types/mod.rs index 938be912f..0f44aa56c 100644 --- a/filecoin-proofs/src/types/mod.rs +++ b/filecoin-proofs/src/types/mod.rs @@ -82,6 +82,10 @@ pub struct SealPreCommitPhase1Output { pub comm_d: Commitment, } +#[repr(transparent)] +#[derive(Clone, Debug)] +pub struct PartitionSnarkProof(pub Vec); + pub type SnarkProof = Vec; pub type AggregateSnarkProof = Vec; pub type VanillaProof = fallback::Proof<::Proof>; diff --git a/filecoin-proofs/tests/api.rs b/filecoin-proofs/tests/api.rs index de8b9b8a5..9f10ea923 100644 --- a/filecoin-proofs/tests/api.rs +++ b/filecoin-proofs/tests/api.rs @@ -13,8 +13,10 @@ use filecoin_hashers::Hasher; use filecoin_proofs::{ add_piece, aggregate_seal_commit_proofs, clear_cache, compute_comm_d, fauxrep_aux, generate_fallback_sector_challenges, generate_piece_commitment, generate_single_vanilla_proof, - generate_window_post, generate_window_post_with_vanilla, generate_winning_post, - generate_winning_post_sector_challenge, generate_winning_post_with_vanilla, get_seal_inputs, + generate_single_window_post_with_vanilla, generate_window_post, + generate_window_post_with_vanilla, generate_winning_post, + generate_winning_post_sector_challenge, generate_winning_post_with_vanilla, + get_num_partition_for_fallback_post, get_seal_inputs, merge_window_post_partition_proofs, seal_commit_phase1, seal_commit_phase2, seal_pre_commit_phase1, seal_pre_commit_phase2, unseal_range, validate_cache_for_commit, validate_cache_for_precommit_phase2, verify_aggregate_seal_commit_proofs, verify_seal, verify_window_post, verify_winning_post, @@ -1074,6 +1076,166 @@ fn test_window_post_single_partition_matching_2kib_base_8() -> Result<()> { ) } +#[test] +fn test_window_post_partition_matching_2kib_base_8() -> Result<()> { + let sector_size = SECTOR_SIZE_2_KIB; + let sector_count = *WINDOW_POST_SECTOR_COUNT + .read() + .expect("WINDOW_POST_SECTOR_COUNT poisoned") + .get(§or_size) + .expect("unknown sector size"); + + partition_window_post::( + sector_size, + 3, // Validate the scenarios of two partition + sector_count, + false, + ApiVersion::V1_0_0, + )?; + partition_window_post::( + sector_size, + 3, + sector_count, + true, + ApiVersion::V1_0_0, + )?; + partition_window_post::( + sector_size, + 3, + sector_count, + false, + ApiVersion::V1_1_0, + )?; + partition_window_post::(sector_size, 3, sector_count, true, ApiVersion::V1_1_0) +} + +fn partition_window_post( + sector_size: u64, + total_sector_count: usize, + sector_count: usize, + fake: bool, + api_version: ApiVersion, +) -> Result<()> { + use anyhow::anyhow; + + let mut rng = XorShiftRng::from_seed(TEST_SEED); + + let mut sectors = Vec::with_capacity(total_sector_count); + let mut pub_replicas = BTreeMap::new(); + let mut priv_replicas = BTreeMap::new(); + + let prover_fr: ::Domain = Fr::random(&mut rng).into(); + let mut prover_id = [0u8; 32]; + prover_id.copy_from_slice(AsRef::<[u8]>::as_ref(&prover_fr)); + + let porep_id = match api_version { + ApiVersion::V1_0_0 => ARBITRARY_POREP_ID_V1_0_0, + ApiVersion::V1_1_0 => ARBITRARY_POREP_ID_V1_1_0, + }; + + for _ in 0..total_sector_count { + let (sector_id, replica, comm_r, cache_dir) = if fake { + create_fake_seal::<_, Tree>(&mut rng, sector_size, &porep_id, api_version)? + } else { + create_seal::<_, Tree>( + &mut rng, + sector_size, + prover_id, + true, + &porep_id, + api_version, + )? + }; + priv_replicas.insert( + sector_id, + PrivateReplicaInfo::new(replica.path().into(), comm_r, cache_dir.path().into())?, + ); + pub_replicas.insert(sector_id, PublicReplicaInfo::new(comm_r)?); + sectors.push((sector_id, replica, comm_r, cache_dir, prover_id)); + } + assert_eq!(priv_replicas.len(), total_sector_count); + assert_eq!(pub_replicas.len(), total_sector_count); + assert_eq!(sectors.len(), total_sector_count); + + let random_fr: ::Domain = Fr::random(&mut rng).into(); + let mut randomness = [0u8; 32]; + randomness.copy_from_slice(AsRef::<[u8]>::as_ref(&random_fr)); + + let config = PoStConfig { + sector_size: sector_size.into(), + sector_count, + challenge_count: WINDOW_POST_CHALLENGE_COUNT, + typ: PoStType::Window, + priority: false, + api_version, + }; + + let replica_sectors = priv_replicas + .iter() + .map(|(sector, _replica)| *sector) + .collect::>(); + + let challenges = generate_fallback_sector_challenges::( + &config, + &randomness, + &replica_sectors, + prover_id, + )?; + + let num_sectors_per_chunk = config.sector_count; + let mut proofs = Vec::new(); + + let partitions = get_num_partition_for_fallback_post(&config, replica_sectors.len()); + for partition_index in 0..partitions { + let sector_ids = replica_sectors + .chunks(num_sectors_per_chunk) + .nth(partition_index) + .ok_or_else(|| anyhow!("invalid number of sectors/partition index"))?; + + let mut partition_priv_replicas = BTreeMap::new(); + for id in sector_ids { + let p_sector = match priv_replicas.get(id) { + Some(v) => v, + _ => { + continue; + } + }; + + partition_priv_replicas.insert(*id, p_sector); + } + + let mut vanilla_proofs = Vec::new(); + for (sector_id, sector) in partition_priv_replicas.iter() { + let sector_challenges = &challenges[sector_id]; + let single_proof = generate_single_vanilla_proof::( + &config, + *sector_id, + sector, + sector_challenges, + )?; + + vanilla_proofs.push(single_proof); + } + + let proof = generate_single_window_post_with_vanilla( + &config, + &randomness, + prover_id, + vanilla_proofs, + partition_index, + )?; + + proofs.push(proof); + } + + let final_proof = merge_window_post_partition_proofs(proofs)?; + let valid = + verify_window_post::(&config, &randomness, &pub_replicas, prover_id, &final_proof)?; + assert!(valid, "proofs did not verify"); + + Ok(()) +} + fn window_post( sector_size: u64, total_sector_count: usize, diff --git a/storage-proofs-post/src/fallback/vanilla.rs b/storage-proofs-post/src/fallback/vanilla.rs index a6930fa6f..cf28db29d 100644 --- a/storage-proofs-post/src/fallback/vanilla.rs +++ b/storage-proofs-post/src/fallback/vanilla.rs @@ -496,7 +496,6 @@ impl<'a, Tree: 'a + MerkleTreeTrait> ProofScheme<'a> for FallbackPoSt<'a, Tree> pub_inputs: &Self::PublicInputs, partition_proofs: &[Self::Proof], ) -> Result { - let challenge_count = pub_params.challenge_count; let num_sectors_per_chunk = pub_params.sector_count; let num_sectors = pub_inputs.sectors.len(); @@ -513,98 +512,17 @@ impl<'a, Tree: 'a + MerkleTreeTrait> ProofScheme<'a> for FallbackPoSt<'a, Tree> .zip(pub_inputs.sectors.chunks(num_sectors_per_chunk)) .enumerate() { - ensure!( - pub_sectors_chunk.len() <= num_sectors_per_chunk, - "inconsistent number of public sectors: {} > {}", - pub_sectors_chunk.len(), - num_sectors_per_chunk, - ); - ensure!( - proof.sectors.len() == num_sectors_per_chunk, - "invalid number of sectors in the partition proof {}: {} != {}", - j, - proof.sectors.len(), - num_sectors_per_chunk, - ); - - let is_valid = pub_sectors_chunk - .par_iter() - .zip(proof.sectors.par_iter()) - .enumerate() - .map(|(i, (pub_sector, sector_proof))| { - let sector_id = pub_sector.id; - let comm_r = &pub_sector.comm_r; - let comm_c = sector_proof.comm_c; - let inclusion_proofs = §or_proof.inclusion_proofs; - - // Verify that H(Comm_c || Comm_r_last) == Comm_R - - // comm_r_last is the root of the proof - let comm_r_last = inclusion_proofs[0].root(); - - if AsRef::<[u8]>::as_ref(&::Function::hash2( - &comm_c, - &comm_r_last, - )) != AsRef::<[u8]>::as_ref(comm_r) - { - error!("hash(comm_c || comm_r_last) != comm_r: {:?}", sector_id); - return Ok(false); - } - - ensure!( - challenge_count == inclusion_proofs.len(), - "unexpected number of inclusion proofs: {} != {}", - challenge_count, - inclusion_proofs.len() - ); + let is_valid = Self::verify( + pub_params, + &PublicInputs { + randomness: pub_inputs.randomness, + prover_id: pub_inputs.prover_id, + sectors: pub_sectors_chunk.to_vec(), + k: Some(j), + }, + proof, + )?; - // avoid rehashing fixed inputs - let mut challenge_hasher = Sha256::new(); - challenge_hasher.update(AsRef::<[u8]>::as_ref(&pub_inputs.randomness)); - challenge_hasher.update(&u64::from(sector_id).to_le_bytes()[..]); - - let is_valid_list = inclusion_proofs - .par_iter() - .enumerate() - .map(|(n, inclusion_proof)| -> Result { - let challenge_index = - (j * num_sectors_per_chunk + i) * pub_params.challenge_count + n; - let challenged_leaf = - generate_leaf_challenge_inner::<::Domain>( - challenge_hasher.clone(), - pub_params, - challenge_index as u64, - ); - - // validate all comm_r_lasts match - if inclusion_proof.root() != comm_r_last { - error!("inclusion proof root != comm_r_last: {:?}", sector_id); - return Ok(false); - } - - // validate the path length - let expected_path_length = inclusion_proof - .expected_len(pub_params.sector_size as usize / NODE_SIZE); - - if expected_path_length != inclusion_proof.path().len() { - error!("wrong path length: {:?}", sector_id); - return Ok(false); - } - - if !inclusion_proof.validate(challenged_leaf as usize) { - error!("invalid inclusion proof: {:?}", sector_id); - return Ok(false); - } - Ok(true) - }) - .collect::>>()?; - - Ok(is_valid_list.into_iter().all(|v| v)) - }) - .reduce( - || Ok(true), - |all_valid, is_valid| Ok(all_valid? && is_valid?), - )?; if !is_valid { return Ok(false); } @@ -612,6 +530,11 @@ impl<'a, Tree: 'a + MerkleTreeTrait> ProofScheme<'a> for FallbackPoSt<'a, Tree> Ok(true) } + fn with_partition(mut pub_in: Self::PublicInputs, k: Option) -> Self::PublicInputs { + pub_in.k = k; + pub_in + } + fn satisfies_requirements( public_params: &Self::PublicParams, requirements: &Self::Requirements, @@ -630,4 +553,119 @@ impl<'a, Tree: 'a + MerkleTreeTrait> ProofScheme<'a> for FallbackPoSt<'a, Tree> checked * public_params.challenge_count >= requirements.minimum_challenge_count } + + fn verify( + pub_params: &Self::PublicParams, + pub_inputs: &Self::PublicInputs, + partition_proof: &Self::Proof, + ) -> Result { + ensure!( + pub_inputs.k.is_some(), + "must be called with a partition index" + ); + let partition_index = pub_inputs.k.expect("prechecked"); + let challenge_count = pub_params.challenge_count; + let num_sectors_per_chunk = pub_params.sector_count; + + let j = partition_index; + let proof = partition_proof; + let pub_sectors_chunk = &pub_inputs.sectors; + + ensure!( + pub_sectors_chunk.len() <= num_sectors_per_chunk, + "inconsistent number of public sectors: {} > {}", + pub_sectors_chunk.len(), + num_sectors_per_chunk, + ); + ensure!( + proof.sectors.len() == num_sectors_per_chunk, + "invalid number of sectors in the partition proof {}: {} != {}", + j, + proof.sectors.len(), + num_sectors_per_chunk, + ); + + let is_valid = pub_sectors_chunk + .par_iter() + .zip(proof.sectors.par_iter()) + .enumerate() + .map(|(i, (pub_sector, sector_proof))| { + let sector_id = pub_sector.id; + let comm_r = &pub_sector.comm_r; + let comm_c = sector_proof.comm_c; + let inclusion_proofs = §or_proof.inclusion_proofs; + + // Verify that H(Comm_c || Comm_r_last) == Comm_R + + // comm_r_last is the root of the proof + let comm_r_last = inclusion_proofs[0].root(); + + if AsRef::<[u8]>::as_ref(&::Function::hash2( + &comm_c, + &comm_r_last, + )) != AsRef::<[u8]>::as_ref(comm_r) + { + error!("hash(comm_c || comm_r_last) != comm_r: {:?}", sector_id); + return Ok(false); + } + + ensure!( + challenge_count == inclusion_proofs.len(), + "unexpected number of inclusion proofs: {} != {}", + challenge_count, + inclusion_proofs.len() + ); + + // avoid rehashing fixed inputs + let mut challenge_hasher = Sha256::new(); + challenge_hasher.update(AsRef::<[u8]>::as_ref(&pub_inputs.randomness)); + challenge_hasher.update(&u64::from(sector_id).to_le_bytes()[..]); + + let is_valid_list = inclusion_proofs + .par_iter() + .enumerate() + .map(|(n, inclusion_proof)| -> Result { + let challenge_index = + (j * num_sectors_per_chunk + i) * pub_params.challenge_count + n; + let challenged_leaf = + generate_leaf_challenge_inner::<::Domain>( + challenge_hasher.clone(), + pub_params, + challenge_index as u64, + ); + + // validate all comm_r_lasts match + if inclusion_proof.root() != comm_r_last { + error!("inclusion proof root != comm_r_last: {:?}", sector_id); + return Ok(false); + } + + // validate the path length + let expected_path_length = inclusion_proof + .expected_len(pub_params.sector_size as usize / NODE_SIZE); + + if expected_path_length != inclusion_proof.path().len() { + error!("wrong path length: {:?}", sector_id); + return Ok(false); + } + + if !inclusion_proof.validate(challenged_leaf as usize) { + error!("invalid inclusion proof: {:?}", sector_id); + return Ok(false); + } + Ok(true) + }) + .collect::>>()?; + + Ok(is_valid_list.into_iter().all(|v| v)) + }) + .reduce( + || Ok(true), + |all_valid, is_valid| Ok(all_valid? && is_valid?), + )?; + if !is_valid { + return Ok(false); + } + Ok(true) + } }