423 lines
15 KiB
Rust

// Sample cells
// consistent with:
// https://github.com/codex-storage/codex-storage-proofs-circuits/blob/master/circuit/codex/sample_cells.circom
// circuit consists of:
// - reconstruct the dataset merkle root using the slot root as leaf
// - samples multiple cells by calling the sample_cells
use std::marker::PhantomData;
use plonky2::{
field::extension::Extendable,
hash::{
hash_types::{HashOut, HashOutTarget, NUM_HASH_OUT_ELTS, RichField},
hashing::PlonkyPermutation,
},
iop::{
target::{BoolTarget, Target},
witness::{PartialWitness, WitnessWrite},
},
plonk::circuit_builder::CircuitBuilder,
};
use plonky2::plonk::config::AlgebraicHasher;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use crate::{
circuits::{
merkle_circuit::{MerkleProofTarget, MerkleTreeCircuit, MerkleTreeTargets},
params::CircuitParams,
sponge::{hash_n_no_padding, hash_n_with_padding},
utils::{assign_hash_out_targets, ceiling_log2},
},
Result,
error::CircuitError,
};
/// circuit for sampling a slot in a dataset merkle tree
#[derive(Clone, Debug)]
pub struct SampleCircuit<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
H: AlgebraicHasher<F>,
> {
params: CircuitParams,
phantom_data: PhantomData<(F,H)>,
}
impl<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
H: AlgebraicHasher<F>,
> SampleCircuit<F, D, H> {
pub fn new(params: CircuitParams) -> Self{
Self{
params,
phantom_data: Default::default(),
}
}
}
/// struct of input to the circuit as targets
/// used to build the circuit and can be assigned after building
#[derive(Clone, Debug)]
pub struct SampleTargets {
pub entropy: HashOutTarget, // public input
pub dataset_root: HashOutTarget, // public input
pub slot_index: Target, // public input
pub slot_root: HashOutTarget,
pub n_cells_per_slot: Target,
pub n_slots_per_dataset: Target,
pub slot_proof: MerkleProofTarget,
pub cell_data: Vec<CellTarget>,
pub merkle_paths: Vec<MerkleProofTarget>,
}
/// circuit input as field elements
#[derive(Clone, Debug, PartialEq)]
pub struct SampleCircuitInput<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
>{
pub entropy: HashOut<F>, // public input
pub dataset_root: HashOut<F>, // public input
pub slot_index: F, // public input
pub slot_root: HashOut<F>,
pub n_cells_per_slot: F,
pub n_slots_per_dataset: F,
pub slot_proof: Vec<HashOut<F>>,
pub cell_data: Vec<Cell<F,D>>,
pub merkle_paths: Vec<MerklePath<F,D>>,
}
/// merkle path from leaf to root as vec of HashOut (4 Goldilocks field elems)
#[derive(Clone, Debug, PartialEq)]
pub struct MerklePath<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
> {
pub path: Vec<HashOut<F>>
}
/// a vec of cell targets
#[derive(Clone, Debug, PartialEq)]
pub struct CellTarget {
pub data: Vec<Target>
}
/// cell data as field elements
#[derive(Clone, Debug, PartialEq)]
pub struct Cell<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
> {
pub data: Vec<F>,
}
//------- circuit impl --------
impl<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
H: AlgebraicHasher<F>,
> SampleCircuit<F, D, H> {
/// samples and registers the public input
pub fn sample_slot_circuit_with_public_input(
&self,
builder: &mut CircuitBuilder<F, D>,
) -> Result<SampleTargets> {
let targets = self.sample_slot_circuit(builder)?;
let mut pub_targets = vec![];
pub_targets.push(targets.slot_index);
pub_targets.extend_from_slice(&targets.dataset_root.elements);
pub_targets.extend_from_slice(&targets.entropy.elements);
builder.register_public_inputs(&pub_targets);
Ok(targets)
}
/// in-circuit sampling
/// WARNING: no public input are registered when calling this function
pub fn sample_slot_circuit(
&self,
builder: &mut CircuitBuilder::<F, D>,
) -> Result<SampleTargets> {
// circuit params
let CircuitParams {
max_depth,
max_log2_n_slots,
block_tree_depth,
n_field_elems_per_cell,
n_samples,
} = self.params;
// constants
let zero = builder.zero();
let one = builder.one();
// ***** prove slot root is in dataset tree *********
// Create virtual target for slot root and index
let slot_root = builder.add_virtual_hash();
let slot_index = builder.add_virtual_target();// public input
// let slot_index = builder.add_virtual_public_input();// public input
// dataset path bits (binary decomposition of leaf_index)
let d_path_bits = builder.split_le(slot_index,max_log2_n_slots);
// create virtual target for n_slots_per_dataset
let n_slots_per_dataset = builder.add_virtual_target();
// dataset last bits and mask bits
let (d_last_bits, d_mask_bits) =
ceiling_log2(builder, n_slots_per_dataset, max_log2_n_slots);
// dataset Merkle path (sibling hashes from leaf to root)
let d_merkle_path = MerkleProofTarget {
path: (0..max_log2_n_slots).map(|_| builder.add_virtual_hash()).collect(),
};
// create MerkleTreeTargets struct
let mut d_targets = MerkleTreeTargets{
leaf: slot_root,
path_bits: d_path_bits,
last_bits: d_last_bits,
mask_bits: d_mask_bits,
merkle_path: d_merkle_path,
};
// dataset reconstructed root
let d_reconstructed_root =
MerkleTreeCircuit::<F,D, H>::reconstruct_merkle_root_circuit_with_mask(builder, &mut d_targets, max_log2_n_slots)?;
// expected Merkle root
let d_expected_root = builder.add_virtual_hash(); // public input
// let d_expected_root = builder.add_virtual_hash_public_input(); // public input
// check equality with expected root
for i in 0..NUM_HASH_OUT_ELTS {
builder.connect(d_expected_root.elements[i], d_reconstructed_root.elements[i]);
}
//*********** do the sampling ************
let mut data_targets =vec![];
let mut slot_sample_proofs = vec![];
let entropy_target = builder.add_virtual_hash(); // public input
// let entropy_target = builder.add_virtual_hash_public_input(); // public input
// virtual target for n_cells_per_slot
let n_cells_per_slot = builder.add_virtual_target();
// calculate last index = n_cells_per_slot-1
let slot_last_index = builder.sub(n_cells_per_slot, one);
// create the mask bits
// TODO: re-use this for block and slot trees
let mask_bits = builder.split_le(slot_last_index,max_depth);
// last and mask bits for block tree
let mut b_last_bits = builder.split_le(slot_last_index,max_depth);
let mut b_mask_bits = builder.split_le(slot_last_index,max_depth);
// last and mask bits for the slot tree
let mut s_last_bits = b_last_bits.split_off(block_tree_depth);
let mut s_mask_bits = b_mask_bits.split_off(block_tree_depth);
// pad mask bits with 0
b_mask_bits.push(BoolTarget::new_unsafe(zero.clone()));
s_mask_bits.push(BoolTarget::new_unsafe(zero.clone()));
for i in 0..n_samples{
// cell data targets
let mut data_i = (0..n_field_elems_per_cell).map(|_| builder.add_virtual_target()).collect::<Vec<_>>();
// hash the cell data
let mut hash_inputs:Vec<Target>= Vec::new();
hash_inputs.extend_from_slice(&data_i);
// let data_i_hash = builder.hash_n_to_hash_no_pad::<HF>(hash_inputs);
let data_i_hash = hash_n_no_padding::<F,D,H>(builder, hash_inputs)?;
// make the counter into hash digest
let ctr_target = builder.constant(F::from_canonical_u64((i+1) as u64));
let mut ctr = builder.add_virtual_hash();
for i in 0..ctr.elements.len() {
if i==0 {
ctr.elements[i] = ctr_target;
}else{
ctr.elements[i] = zero.clone();
}
}
// paths for block and slot
let mut b_path_bits = self.calculate_cell_index_bits(builder, &entropy_target, &d_targets.leaf, &ctr, mask_bits.clone())?;
let mut s_path_bits = b_path_bits.split_off(block_tree_depth);
let mut b_merkle_path = MerkleProofTarget {
path: (0..block_tree_depth).map(|_| builder.add_virtual_hash()).collect(),
};
let mut s_merkle_path = MerkleProofTarget {
path: (0..(max_depth - block_tree_depth)).map(|_| builder.add_virtual_hash()).collect(),
};
let mut block_targets = MerkleTreeTargets {
leaf: data_i_hash,
path_bits:b_path_bits,
last_bits: b_last_bits.clone(),
mask_bits: b_mask_bits.clone(),
merkle_path: b_merkle_path,
};
// reconstruct block root
let b_root = MerkleTreeCircuit::<F,D,H>::reconstruct_merkle_root_circuit_with_mask(builder, &mut block_targets, block_tree_depth)?;
let mut slot_targets = MerkleTreeTargets {
leaf: b_root,
path_bits:s_path_bits,
last_bits:s_last_bits.clone(),
mask_bits:s_mask_bits.clone(),
merkle_path:s_merkle_path,
};
// reconstruct slot root with block root as leaf
let slot_reconstructed_root = MerkleTreeCircuit::<F,D,H>::reconstruct_merkle_root_circuit_with_mask(builder, &mut slot_targets, max_depth-block_tree_depth)?;
// check equality with expected root
for i in 0..NUM_HASH_OUT_ELTS {
builder.connect( d_targets.leaf.elements[i], slot_reconstructed_root.elements[i]);
}
// combine block and slot path to get the full path so we can assign it later.
let mut slot_sample_proof_target = MerkleProofTarget{
path: block_targets.merkle_path.path,
};
slot_sample_proof_target.path.extend_from_slice(&slot_targets.merkle_path.path);
let cell_i = CellTarget{
data: data_i
};
data_targets.push(cell_i);
slot_sample_proofs.push(slot_sample_proof_target);
}
let st = SampleTargets {
entropy: entropy_target,
dataset_root: d_expected_root,
slot_index,
slot_root: d_targets.leaf,
n_cells_per_slot,
n_slots_per_dataset,
slot_proof: d_targets.merkle_path,
cell_data: data_targets,
merkle_paths: slot_sample_proofs,
};
Ok(st)
}
/// calculate the cell index = H( entropy | slotRoot | counter ) `mod` nCells
pub fn calculate_cell_index_bits(&self, builder: &mut CircuitBuilder<F, D>, entropy: &HashOutTarget, slot_root: &HashOutTarget, ctr: &HashOutTarget, mask_bits: Vec<BoolTarget>) -> Result<Vec<BoolTarget>> {
let mut hash_inputs:Vec<Target>= Vec::new();
hash_inputs.extend_from_slice(&entropy.elements);
hash_inputs.extend_from_slice(&slot_root.elements);
hash_inputs.extend_from_slice(&ctr.elements);
let hash_out = hash_n_with_padding::<F,D,H>(builder, hash_inputs)?;
let cell_index_bits = builder.low_bits(hash_out.elements[0], self.params.max_depth, 64);
let mut masked_cell_index_bits = vec![];
// extract the lowest 32 bits using the bit mask
for i in 0..self.params.max_depth{
masked_cell_index_bits.push(BoolTarget::new_unsafe(builder.mul(mask_bits[i].target, cell_index_bits[i].target)));
}
Ok(masked_cell_index_bits)
}
/// helper method to assign the targets in the circuit to actual field elems
pub fn sample_slot_assign_witness(
&self,
pw: &mut PartialWitness<F>,
targets: &SampleTargets,
witnesses: &SampleCircuitInput<F, D>,
) -> Result<()>{
// circuit params
let CircuitParams {
max_depth,
max_log2_n_slots,
block_tree_depth,
n_field_elems_per_cell,
n_samples,
} = self.params;
// assign n_cells_per_slot
pw.set_target(targets.n_cells_per_slot, witnesses.n_cells_per_slot)
.map_err(|e| {
CircuitError::TargetAssignmentError("n_cells_per_slot".to_string(), e.to_string())
})?;
// assign n_slots_per_dataset
pw.set_target(targets.n_slots_per_dataset, witnesses.n_slots_per_dataset)
.map_err(|e| {
CircuitError::TargetAssignmentError("n_slots_per_dataset".to_string(), e.to_string())
})?;
// assign dataset proof
for (i, sibling_hash) in witnesses.slot_proof.iter().enumerate() {
pw.set_hash_target(targets.slot_proof.path[i], *sibling_hash)
.map_err(|e| {
CircuitError::HashTargetAssignmentError("slot_proof".to_string(), e.to_string())
})?;
}
// assign slot index
pw.set_target(targets.slot_index, witnesses.slot_index)
.map_err(|e| {
CircuitError::TargetAssignmentError("slot_index".to_string(), e.to_string())
})?;
// assign the expected Merkle root of dataset to the target
pw.set_hash_target(targets.dataset_root, witnesses.dataset_root)
.map_err(|e| {
CircuitError::HashTargetAssignmentError("dataset_root".to_string(), e.to_string())
})?;
// assign the sampled slot
pw.set_hash_target(targets.slot_root, witnesses.slot_root)
.map_err(|e| {
CircuitError::HashTargetAssignmentError("slot_root".to_string(), e.to_string())
})?;
// assign entropy
assign_hash_out_targets(pw, &targets.entropy, &witnesses.entropy)?;
// do the sample N times
for i in 0..n_samples {
// assign cell data
let leaf = witnesses.cell_data[i].data.clone();
for j in 0..n_field_elems_per_cell{
pw.set_target(targets.cell_data[i].data[j], leaf[j])
.map_err(|e| {
CircuitError::TargetAssignmentError("cell_data".to_string(), e.to_string())
})?;
}
// assign proof for that cell
let cell_proof = witnesses.merkle_paths[i].path.clone();
for k in 0..max_depth {
pw.set_hash_target(targets.merkle_paths[i].path[k], cell_proof[k])
.map_err(|e| {
CircuitError::HashTargetAssignmentError("merkle_paths".to_string(), e.to_string())
})?;
}
}
Ok(())
}
}