diff --git a/storage-proofs/core/src/gadgets/por.rs b/storage-proofs/core/src/gadgets/por.rs index 060aa2522..5792814de 100644 --- a/storage-proofs/core/src/gadgets/por.rs +++ b/storage-proofs/core/src/gadgets/por.rs @@ -439,16 +439,75 @@ impl<'a, Tree: MerkleTreeTrait> PoRCircuit { } } +/// Synthesizes a non-compound arity PoR without adding a public input for the challenge (whereas +/// `PoRCircuit` adds one public input for the challenge). This PoR gadget allows the caller to pack +/// mulitple PoR challenges into a single public input when the challenge bit length is less than +/// `Fr::Capacity`. +pub fn por_no_challenge_input( + cs: &mut CS, + // Least significant bit first, most significant bit last. + challenge_bits: Vec, + leaf: num::AllocatedNum, + path_values: Vec>>, + root: num::AllocatedNum, +) -> Result<(), SynthesisError> +where + Tree: MerkleTreeTrait, + CS: ConstraintSystem, +{ + let arity = Tree::Arity::to_usize(); + let arity_bit_len = arity.trailing_zeros() as usize; + let challenge_bit_len = challenge_bits.len(); + let height = path_values.len(); + + // Check that all path elements are consistent with the arity. + assert!(path_values + .iter() + .all(|siblings| siblings.len() == arity - 1)); + + // Check that the challenge bit length is consistent with the height and arity. + assert_eq!(challenge_bit_len, arity_bit_len * height); + + let challenge_bits: Vec = challenge_bits.into_iter().map(Boolean::from).collect(); + + // Compute a root from the provided path and check equality with the provided root. + let mut cur = leaf; + for (height, (siblings, insert_index)) in path_values + .iter() + .zip(challenge_bits.chunks(arity_bit_len)) + .enumerate() + { + let inputs = insert( + &mut cs.namespace(|| format!("merkle insert, height {}", height)), + &cur, + &insert_index, + &siblings, + )?; + cur = <::Function as HashFunction< + ::Domain, + >>::hash_multi_leaf_circuit::( + cs.namespace(|| format!("merkle hash, height {}", height)), + &inputs, + height, + )?; + } + let computed_root = cur; + constraint::equal(cs, || "merkle root equality", &computed_root, &root); + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; use bellperson::gadgets::multipack; + use bellperson::gadgets::num::AllocatedNum; use ff::Field; use generic_array::typenum; use merkletree::store::VecStore; use pretty_assertions::assert_eq; - use rand::SeedableRng; + use rand::{Rng, SeedableRng}; use rand_xorshift::XorShiftRng; use crate::compound_proof; @@ -959,4 +1018,97 @@ mod tests { assert!(cs.verify(&expected_inputs), "failed to verify inputs"); } } + + #[test] + fn test_por_no_challenge_input() { + type Arity = typenum::U8; + type Tree = TestTree; + + // == Setup + let rng = &mut XorShiftRng::from_seed(crate::TEST_SEED); + + let height = 3; + let n_leaves = Arity::to_usize() << height; + + let data: Vec = (0..n_leaves) + .flat_map(|_| fr_into_bytes(&Fr::random(rng))) + .collect(); + + let tree = create_base_merkle_tree::(None, n_leaves, &data) + .expect("create_base_merkle_tree failure"); + let root = tree.root(); + + let challenge = rng.gen::() % n_leaves; + let leaf_bytes = data_at_node(&data, challenge).expect("data_at_node failure"); + let leaf = bytes_into_fr(leaf_bytes).expect("bytes_into_fr failure"); + + // == Vanilla PoR proof + let proof = { + use por::{PoR, PrivateInputs, PublicInputs, PublicParams}; + let pub_params = PublicParams { + leaves: n_leaves, + private: false, + }; + let pub_inputs = PublicInputs { + challenge, + commitment: None, + }; + let priv_inputs = PrivateInputs { + leaf: leaf.into(), + tree: &tree, + }; + let proof = + PoR::::prove(&pub_params, &pub_inputs, &priv_inputs).expect("proving failed"); + let is_valid = + PoR::::verify(&pub_params, &pub_inputs, &proof).expect("verification failed"); + assert!(is_valid, "failed to verify por proof"); + proof.proof + }; + + // == Test PoR gadget + let mut cs = TestConstraintSystem::::new(); + + let challenge_bit_len = n_leaves.trailing_zeros() as usize; + let challenge: Vec = (0..challenge_bit_len) + .map(|i| { + AllocatedBit::alloc( + cs.namespace(|| format!("challenge bit {}", i)), + Some((challenge >> i) & 1 == 1), + ) + .expect("failed to allocate challenge bit") + }) + .collect(); + + let leaf = AllocatedNum::alloc(cs.namespace(|| "leaf".to_string()), || Ok(leaf)) + .expect("failed to allocate leaf"); + + let path_values: Vec>> = proof + .path() + .iter() + .enumerate() + .map(|(height, (siblings, _insert_index))| { + siblings + .iter() + .enumerate() + .map(|(sib_index, &sib)| { + AllocatedNum::alloc( + cs.namespace(|| format!("sib {}, height {}", sib_index, height)), + || Ok(sib.into()), + ) + .expect("failed to allocate sibling") + }) + .collect() + }) + .collect(); + + let root = AllocatedNum::alloc(cs.namespace(|| "root".to_string()), || Ok(root.into())) + .expect("failed to allocate root"); + + por_no_challenge_input::(&mut cs, challenge, leaf, path_values, root) + .expect("por gadget failed"); + + assert!(cs.is_satisfied()); + let public_inputs = vec![]; + assert!(cs.verify(&public_inputs)); + } }