Skip to content

Commit

Permalink
feat: empty-sector-update circuit
Browse files Browse the repository at this point in the history
  • Loading branch information
DrPeterVanNostrand committed Sep 21, 2021
1 parent bd2e158 commit c0411a6
Show file tree
Hide file tree
Showing 10 changed files with 1,498 additions and 4 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -10,4 +10,5 @@ members = [
"fr32",
"sha2raw",
"filecoin-hashers",
"storage-proofs-update",
]
10 changes: 8 additions & 2 deletions storage-proofs-core/src/gadgets/por.rs
Expand Up @@ -457,7 +457,7 @@ impl<'a, Tree: MerkleTreeTrait> PoRCircuit<Tree> {
/// mulitple PoR challenges into a single public input when the challenge bit length is less than
/// `Fr::Capacity`.
pub fn por_no_challenge_input<Tree, CS>(
cs: &mut CS,
mut cs: CS,
// Least significant bit first, most significant bit last.
challenge_bits: Vec<AllocatedBit>,
leaf: AllocatedNum<Bls12>,
Expand Down Expand Up @@ -505,7 +505,13 @@ where
)?;
}
let computed_root = cur;
constraint::equal(cs, || "merkle root equality", &computed_root, &root);

cs.enforce(
|| "calculated root == provided root",
|lc| lc + computed_root.get_variable(),
|lc| lc + CS::one(),
|lc| lc + root.get_variable(),
);

Ok(())
}
Expand Down
10 changes: 8 additions & 2 deletions storage-proofs-core/tests/por_circuit.rs
Expand Up @@ -360,8 +360,14 @@ fn test_por_no_challenge_input() {
let root = AllocatedNum::alloc(cs.namespace(|| "root".to_string()), || Ok(root.into()))
.expect("failed to allocate root");

por_no_challenge_input::<Tree, _>(&mut cs, challenge, leaf, path_values, root)
.expect("por gadget failed");
por_no_challenge_input::<Tree, _>(
cs.namespace(|| "por"),
challenge,
leaf,
path_values,
root,
)
.expect("por gadget failed");

assert!(cs.is_satisfied());
let public_inputs = vec![];
Expand Down
31 changes: 31 additions & 0 deletions storage-proofs-update/Cargo.toml
@@ -0,0 +1,31 @@
[package]
name = "storage-proofs-update"
version = "9.0.1"
authors = ["dignifiedquire <me@dignifiedquire.com>"]
description = "Proof of SDR-PoRep Empty Sector Update"
license = "MIT OR Apache-2.0"
edition = "2018"
repository = "https://github.com/filecoin-project/rust-fil-proofs"
readme = "README.md"

[dependencies]
bellperson = { git = "https://github.com/filecoin-project/bellperson", branch = "master" }
blstrs = { git = "https://github.com/filecoin-project/blstrs", branch = "master" }
ff = "0.11.0"
filecoin-hashers = { path = "../filecoin-hashers", version = "^4.0.0", default-features = false, features = ["sha256", "poseidon"] }
generic-array = "0.14.4"
storage-proofs-core = { path = "../storage-proofs-core", version = "^9.0.0", default-features = false}

[dev-dependencies]
merkletree = "0.21.0"
rand = "0.8"
rand_xorshift = "0.3.0"
tempfile = "3"

[features]
default = []
gpu = [
"bellperson/gpu",
"filecoin-hashers/gpu",
"storage-proofs-core/gpu",
]
148 changes: 148 additions & 0 deletions storage-proofs-update/src/challenges.rs
@@ -0,0 +1,148 @@
use std::marker::PhantomData;

use blstrs::Scalar as Fr;
use ff::{PrimeField, PrimeFieldBits};
use filecoin_hashers::{Hasher, HashFunction};

use crate::constants::{challenge_count, partition_count};

pub struct Challenges<TreeRHasher: Hasher> {
challenge_bits_from_digest: usize,
challenges_per_digest: usize,
comm_r_new: TreeRHasher::Domain,
// A bit-mask used to add the partition-index `k` (as little-endian bits) to each generated
// challenge's bits.
partition_bits: usize,
// The index of the current digest.
j: usize,
// The index of the current challenge in the `j`-th digest.
i: usize,
// The number of challenges which have yet to be generated for this partition.
challenges_remaining: usize,
digest_j_bits: Vec<bool>,
_h: PhantomData<TreeRHasher>,
}

impl<TreeRHasher: Hasher> Challenges<TreeRHasher> {
pub fn new(sector_nodes: usize, comm_r_new: TreeRHasher::Domain, k: usize) -> Self {
let partitions = partition_count(sector_nodes);
assert!(k < partitions);
// The number of partitions is guaranteed to be a power of two.
let partition_bit_len = partitions.trailing_zeros() as usize;

let challenge_bit_len = sector_nodes.trailing_zeros() as usize;
let challenge_bits_from_digest = challenge_bit_len - partition_bit_len;
// `challenge_bit_len` will likely not divide `Fr::CAPACITY`, thus we must round down here.
let challenges_per_digest = Fr::CAPACITY as usize / challenge_bits_from_digest;

// let partition_bits: Vec<bool> = (0..partition_bit_len).map(|i| (k >> i) & 1 == 1).collect();
let partition_bits: usize = k << challenge_bits_from_digest;

Challenges {
challenge_bits_from_digest,
challenges_per_digest,
comm_r_new,
partition_bits,
j: 0,
i: 0,
digest_j_bits: Vec::with_capacity(Fr::NUM_BITS as usize),
challenges_remaining: challenge_count(sector_nodes),
_h: PhantomData,
}
}
}

impl<TreeRHasher: Hasher> Iterator for Challenges<TreeRHasher> {
// Return a `usize` (as opposed to something smaller like `u32`) because
// `MerkleTreeTrait::gen_proof()` takes `usize` challenges.
type Item = usize;

fn next(&mut self) -> Option<Self::Item> {
if self.challenges_remaining == 0 {
return None;
}

// Generate the `j`-th digest.
if self.i == 0 {
let j = Fr::from(self.j as u64);
let digest_j: Fr = TreeRHasher::Function::hash2(&self.comm_r_new, &j.into()).into();
self.digest_j_bits = digest_j.to_le_bits().into_iter().collect();
}

// Derive the `i`-th challenge `c` from `digest_j`.
let c_bits = {
let start = self.i * self.challenge_bits_from_digest;
let stop = start + self.challenge_bits_from_digest;
&self.digest_j_bits[start..stop]
};

let mut c: usize = 0;
let mut pow2: usize = 1;
for bit in c_bits {
c += *bit as usize * pow2;
pow2 <<= 1;
}
// Append the partition-index bits onto the most-significant end of `c`.
c |= self.partition_bits;

self.i += 1;
if self.i == self.challenges_per_digest {
self.i = 0;
self.j += 1;
}
self.challenges_remaining -= 1;
Some(c)
}
}

#[cfg(test)]
mod tests {
use super::*;

use filecoin_hashers::{
poseidon::{PoseidonDomain, PoseidonHasher},
Domain,
};
use rand::SeedableRng;
use rand_xorshift::XorShiftRng;
use storage_proofs_core::TEST_SEED;

use crate::constants::ALLOWED_SECTOR_SIZES;

#[test]
fn test_challenges() {
let mut rng = &mut XorShiftRng::from_seed(TEST_SEED);
let comm_r_new = PoseidonDomain::random(&mut rng);

for sector_nodes in ALLOWED_SECTOR_SIZES.iter().copied() {
let partitions = partition_count(sector_nodes);
let partition_nodes = sector_nodes / partitions;
let partition_challenges = challenge_count(sector_nodes);

// Right shift each challenge `c` by `get_partition_shr` to get the partition-index `k`.
let get_partition_shr =
(sector_nodes.trailing_zeros() - partitions.trailing_zeros()) as usize;

for k in 0..partitions {
let first_partition_node = k * partition_nodes;
let last_partition_node = first_partition_node + partition_nodes - 1;

let challenges: Vec<usize> = Challenges::<PoseidonHasher>::new(
sector_nodes,
comm_r_new.clone(),
k,
)
.collect();

assert_eq!(challenges.len(), partition_challenges);

for c in challenges.into_iter() {
assert!(c >= first_partition_node);
assert!(c <= last_partition_node);
// This is redundant with the above range check, but let's sanity check anyway.
assert_eq!(c >> get_partition_shr, k);
}
}
}
}
}

0 comments on commit c0411a6

Please sign in to comment.