Allow virtual targets to be routed (#84)

As in plonky1. The semantics of virtual targets in plonky1 were rather weird, but I think it's somewhat better here, since we already separate `generate_copy` and `assert_equal` methods. Users now make more of an explicit choice -- they can use a `VirtualTarget` for the witness generation only using `generate_copy`, or they can involve it in copy constraints.
This commit is contained in:
Daniel Lubarov 2021-07-01 08:12:12 -07:00 committed by GitHub
parent 574a3d4847
commit 95a875e28d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 24 additions and 21 deletions

View File

@ -36,7 +36,7 @@ pub struct CircuitBuilder<F: Extendable<D>, const D: usize> {
/// The next available index for a public input.
public_input_index: usize,
/// The next available index for a VirtualAdviceTarget.
/// The next available index for a `VirtualTarget`.
virtual_target_index: usize,
copy_constraints: Vec<(Target, Target)>,
@ -77,22 +77,18 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
(0..n).map(|_i| self.add_public_input()).collect()
}
/// Adds a new "virtual" advice target. This is not an actual wire in the witness, but just a
/// target that help facilitate witness generation. In particular, a generator can assign a
/// values to a virtual target, which can then be copied to other (virtual or concrete) targets
/// via `generate_copy`. When we generate the final witness (a grid of wire values), these
/// virtual targets will go away.
///
/// Since virtual targets are not part of the actual permutation argument, they cannot be used
/// with `assert_equal`.
pub fn add_virtual_advice_target(&mut self) -> Target {
/// Adds a new "virtual" target. This is not an actual wire in the witness, but just a target
/// that help facilitate witness generation. In particular, a generator can assign a values to a
/// virtual target, which can then be copied to other (virtual or concrete) targets. When we
/// generate the final witness (a grid of wire values), these virtual targets will go away.
pub fn add_virtual_target(&mut self) -> Target {
let index = self.virtual_target_index;
self.virtual_target_index += 1;
Target::VirtualAdviceTarget { index }
Target::VirtualTarget { index }
}
pub fn add_virtual_advice_targets(&mut self, n: usize) -> Vec<Target> {
(0..n).map(|_i| self.add_virtual_advice_target()).collect()
pub fn add_virtual_targets(&mut self, n: usize) -> Vec<Target> {
(0..n).map(|_i| self.add_virtual_target()).collect()
}
pub fn add_gate_no_constants(&mut self, gate_type: GateRef<F, D>) -> usize {
@ -368,7 +364,11 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
}
for index in 0..self.public_input_index {
target_partitions.add_partition(Target::PublicInput { index })
target_partitions.add_partition(Target::PublicInput { index });
}
for index in 0..self.virtual_target_index {
target_partitions.add_partition(Target::VirtualTarget { index });
}
for &(a, b) in &self.copy_constraints {

View File

@ -9,14 +9,14 @@ use crate::wire::Wire;
use crate::witness::PartialWitness;
impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
/// Split the given integer into a list of virtual advice targets, where each one represents a
/// bit of the integer, with little-endian ordering.
/// Split the given integer into a list of virtual targets, where each one represents a bit of
/// the integer, with little-endian ordering.
///
/// Note that this only handles witness generation; it does not enforce that the decomposition
/// is correct. The output should be treated as a "purported" decomposition which must be
/// enforced elsewhere.
pub(crate) fn split_le_virtual(&mut self, integer: Target, num_bits: usize) -> Vec<Target> {
let bit_targets = self.add_virtual_advice_targets(num_bits);
let bit_targets = self.add_virtual_targets(num_bits);
self.add_generator(SplitGenerator {
integer,
bits: bit_targets.clone(),

View File

@ -57,7 +57,7 @@ impl TargetPartitions {
}
pub fn to_wire_partitions(&self) -> WirePartitions {
// Here we just drop all CircuitInputs, leaving all GateInputs.
// Here we keep just the Wire targets, filtering out everything else.
let mut partitions = Vec::new();
let mut indices = HashMap::new();

View File

@ -8,7 +8,10 @@ use crate::wire::Wire;
pub enum Target {
Wire(Wire),
PublicInput { index: usize },
VirtualAdviceTarget { index: usize },
/// A target that doesn't have any inherent location in the witness (but it can be copied to
/// another target that does). This is useful for representing intermediate values in witness
/// generation.
VirtualTarget { index: usize },
}
impl Target {
@ -20,7 +23,7 @@ impl Target {
match self {
Target::Wire(wire) => wire.is_routable(config),
Target::PublicInput { .. } => true,
Target::VirtualAdviceTarget { .. } => false,
Target::VirtualTarget { .. } => true,
}
}

View File

@ -30,7 +30,7 @@ impl<F: Field> Witness<F> {
F: Extendable<D>,
{
for &(a, b) in copy_constraints {
// TODO: Take care of public inputs once they land.
// TODO: Take care of public inputs once they land, and virtual targets.
if let (
Target::Wire(Wire {
gate: a_gate,