Skip to content

Commit

Permalink
flp: Make the output of Valid a vector
Browse files Browse the repository at this point in the history
If the input is valid, then each output should be zero. To save
bandwidth, we generate an extra element of query randomness and use it
to reduce the result.

To exercise this feature, implement a new circuit, `Sum2`, that
generalizes `Sum` to support any bound, including non-powers-of-two.
  • Loading branch information
cjpatton committed Jun 8, 2024
1 parent 3ac0362 commit f5cddfb
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 73 deletions.
143 changes: 82 additions & 61 deletions draft-irtf-cfrg-vdaf.md
Original file line number Diff line number Diff line change
Expand Up @@ -2997,7 +2997,7 @@ the gadget polynomial, are described in detail in {{flp-generic-construction}}.
#### Extensions {#flp-generic-overview-extensions}

The FLP described in the next section extends the proof system of {{BBCGGI19}},
Section 4.2 in three ways.
Section 4.2 in a few ways.

First, the validity circuit in our construction includes an additional, random
input (this is the "joint randomness" derived from the measurement shares in
Expand Down Expand Up @@ -3038,30 +3038,38 @@ inputs are in range `[0,2)` and the last `N-L` inputs are in range `[0,3)`. Of
course, the same circuit can be expressed using a sub-component that the
gadgets have in common, namely `Mul`, but the resulting proof would be longer.

Finally, {{BBCGGI19}}, Theorem 4.3 makes no restrictions on the choice of the
Third, {{BBCGGI19}}, Theorem 4.3 makes no restrictions on the choice of the
fixed points `alpha[0], ..., alpha[M-1]`, other than to require that the points
are distinct. In this document, the fixed points are chosen so that the gadget
polynomial can be constructed efficiently using the Cooley-Tukey FFT ("Fast
Fourier Transform") algorithm. Note that this requires the field to be
"FFT-friendly" as defined in {{field-fft-friendly}}.

Finally, the validity circuit in our FLP may have any number of outputs. The
input is said to be valid if each of the outputs is zero. To save bandwidth, we
compute interpret the output as coefficients of a polynomial and evaluate the
polynomial at a random point. Thus if each of the outputs zero, then the
reduced output will be zero; on the other hand, if one of the outputs is
non-zero, then the reduced output will be non-zero with high probability.

### Validity Circuits {#flp-generic-valid}

The FLP described in {{flp-generic-construction}} is defined in terms of a
validity circuit `Valid` that implements the interface described here.

A concrete `Valid` defines the following parameters:

| Parameter | Description |
|:-----------------|:--------------------------------------|
| `GADGETS` | A list of gadgets |
| `GADGET_CALLS` | Number of times each gadget is called |
| `MEAS_LEN` | Length of the measurement |
| `OUTPUT_LEN` | Length of the aggregatable output |
| `JOINT_RAND_LEN` | Length of the random input |
| `Measurement` | The type of measurement |
| `AggResult` | Type of the aggregate result |
| `Field` | An FFT-friendly finite field as defined in {{field-fft-friendly}} |
| Parameter | Description |
|:------------------|:--------------------------------------|
| `GADGETS` | A list of gadgets |
| `GADGET_CALLS` | Number of times each gadget is called |
| `MEAS_LEN` | Length of the measurement |
| `OUTPUT_LEN` | Length of the aggregatable output |
| `JOINT_RAND_LEN` | Length of the random input |
| `EVAL_OUTPUT_LEN` | Length of the circuit output |
| `Measurement` | The type of measurement |
| `AggResult` | Type of the aggregate result |
| `Field` | An FFT-friendly finite field as defined in {{field-fft-friendly}} |
{: title="Validity circuit parameters."}

Each gadget `G` in `GADGETS` defines a constant `DEGREE` that specifies the
Expand Down Expand Up @@ -3099,7 +3107,10 @@ def prove_rand_len(self):

def query_rand_len(self):
"""Length of the query randomness."""
return len(Valid.GADGETS)
query_rand_len = len(self.GADGETS)
if self.EVAL_OUTPUT_LEN > 1:
query_rand_len += 1
return query_rand_len

def proof_len(self):
"""Length of the proof."""
Expand Down Expand Up @@ -3210,9 +3221,13 @@ is generated as follows:
of each gadget in the corresponding table. This step is similar to the
prover's step (3.) except the verifier does not evaluate the gadgets.
Instead, it computes the output of the `k`th call to `G_i` by evaluating
`poly_gadget_i(alpha_i^k)`. Let `v` denote the output of the circuit
`poly_gadget_i(alpha_i^k)`. Let `out` denote the output of the circuit
evaluation.

1. Next, reduce `out` as follows. If `EVAL_OUTPUT_LEN > 1`, then consume the
first element of `query_rand` by letting `[r], query_rand = front(1,
query_rand)`. Then let `v = out[0] + r*out[1] + r**2*out[2] + ...`.

1. Compute the wire polynomials just as in the prover's step (4.).

1. Compute the tests for well-formedness of the gadget polynomials. That is, for
Expand Down Expand Up @@ -3283,23 +3298,26 @@ The `Count` validity circuit is defined as

~~~
def eval(self, meas, joint_rand, _num_shares):
return self.GADGETS[0].eval(self.Field, [meas[0], meas[0]]) \
out = self.GADGETS[0].eval(self.Field, [meas[0], meas[0]]) \
- meas[0]
return [out]

~~~

The measurement is encoded and decoded as a singleton vector in the natural
way. The parameters for this circuit are summarized below.

| Parameter | Value |
|:-----------------|:-----------------------|
| `GADGETS` | `[Mul]` |
| `GADGET_CALLS` | `[1]` |
| `MEAS_LEN` | `1` |
| `OUTPUT_LEN` | `1` |
| `JOINT_RAND_LEN` | `0` |
| `Measurement` | `int` in `range(2)` |
| `AggResult` | `int` |
| `Field` | `Field64` ({{fields}}) |
| Parameter | Value |
|:------------------|:-----------------------|
| `GADGETS` | `[Mul]` |
| `GADGET_CALLS` | `[1]` |
| `MEAS_LEN` | `1` |
| `OUTPUT_LEN` | `1` |
| `JOINT_RAND_LEN` | `0` |
| `EVAL_OUTPUT_LEN` | `1` |
| `Measurement` | `int` in `range(2)` |
| `AggResult` | `int` |
| `Field` | `Field64` ({{fields}}) |
{: title="Parameters of validity circuit Count."}

### Prio3Sum
Expand Down Expand Up @@ -3348,19 +3366,20 @@ def eval(self, meas, joint_rand, _num_shares):
for b in meas:
out += r * self.GADGETS[0].eval(self.Field, [b])
r *= joint_rand[0]
return out
~~~

| Parameter | Value |
|:-----------------|:--------------------------|
| `GADGETS` | `[Range2]` |
| `GADGET_CALLS` | `[bits]` |
| `MEAS_LEN` | `bits` |
| `OUTPUT_LEN` | `1` |
| `JOINT_RAND_LEN` | `1` |
| `Measurement` | `int` in `range(2**bits)` |
| `AggResult` | `int` |
| `Field` | `Field128` ({{fields}}) |
return [out]
~~~

| Parameter | Value |
|:------------------|:--------------------------|
| `GADGETS` | `[Range2]` |
| `GADGET_CALLS` | `[bits]` |
| `MEAS_LEN` | `bits` |
| `OUTPUT_LEN` | `1` |
| `JOINT_RAND_LEN` | `1` |
| `EVAL_OUTPUT_LEN` | `1` |
| `Measurement` | `int` in `range(2**bits)` |
| `AggResult` | `int` |
| `Field` | `Field128` ({{fields}}) |
{: title="Parameters of validity circuit Sum."}

### Prio3SumVec
Expand Down Expand Up @@ -3429,7 +3448,7 @@ def eval(self, Field, inp):
start_index = i * self.subcircuit.ARITY
end_index = (i + 1) * self.subcircuit.ARITY
out += self.subcircuit.eval(Field, inp[start_index:end_index])
return out
return [out]
~~~

The `SumVec` validity circuit checks that the encoded measurement consists of
Expand Down Expand Up @@ -3468,19 +3487,20 @@ def eval(self, meas, joint_rand, num_shares):

out += self.GADGETS[0].eval(self.Field, inputs)

return out
return [out]
~~~

| Parameter | Value |
|:-----------------|:-------------------------------------------------------|
| `GADGETS` | `[ParallelSum(Mul(), chunk_length)]` |
| `GADGET_CALLS` | `[(length * bits + chunk_length - 1) // chunk_length]` |
| `MEAS_LEN` | `length * bits` |
| `OUTPUT_LEN` | `length` |
| `JOINT_RAND_LEN` | `1` |
| `Measurement` | `list[int]`, each element in `range(2**bits)` |
| `AggResult` | `list[int]` |
| `Field` | `Field128` ({{fields}}) |
| Parameter | Value |
|:------------------|:-------------------------------------------------------|
| `GADGETS` | `[ParallelSum(Mul(), chunk_length)]` |
| `GADGET_CALLS` | `[(length * bits + chunk_length - 1) // chunk_length]` |
| `MEAS_LEN` | `length * bits` |
| `OUTPUT_LEN` | `length` |
| `JOINT_RAND_LEN` | `1` |
| `EVAL_OUTPUT_LEN` | `1` |
| `Measurement` | `list[int]`, each element in `range(2**bits)` |
| `AggResult` | `list[int]` |
| `Field` | `Field128` ({{fields}}) |
{: title="Parameters of validity circuit SumVec."}

#### Selection of `ParallelSum` chunk length {#parallel-sum-chunk-length}
Expand Down Expand Up @@ -3577,22 +3597,23 @@ def eval(self, meas, joint_rand, num_shares):

out = joint_rand[1] * range_check + \
joint_rand[1] ** 2 * sum_check
return out
return [out]
~~~

Note that this circuit depends on the number of shares into which the
measurement is sharded. This is provided to the FLP by Prio3.

| Parameter | Value |
|:-----------------|:------------------------------------------------|
| `GADGETS` | `[ParallelSum(Mul(), chunk_length)]` |
| `GADGET_CALLS` | `[(length + chunk_length - 1) // chunk_length]` |
| `MEAS_LEN` | `length` |
| `OUTPUT_LEN` | `length` |
| `JOINT_RAND_LEN` | `2` |
| `Measurement` | `int` |
| `AggResult` | `list[int]` |
| `Field` | `Field128` ({{fields}}) |
| Parameter | Value |
|:------------------|:------------------------------------------------|
| `GADGETS` | `[ParallelSum(Mul(), chunk_length)]` |
| `GADGET_CALLS` | `[(length + chunk_length - 1) // chunk_length]` |
| `MEAS_LEN` | `length` |
| `OUTPUT_LEN` | `length` |
| `JOINT_RAND_LEN` | `2` |
| `EVAL_OUTPUT_LEN` | `1` |
| `Measurement` | `int` |
| `AggResult` | `list[int]` |
| `Field` | `Field128` ({{fields}}) |
{: title="Parameters of validity circuit Histogram."}

# Poplar1 {#poplar1}
Expand Down
Loading

0 comments on commit f5cddfb

Please sign in to comment.