Skip to content

Commit

Permalink
Update draft-irtf-cfrg-vdaf.md
Browse files Browse the repository at this point in the history
Co-authored-by: Christopher Patton <[email protected]>
  • Loading branch information
albertpl and cjpatton committed Oct 27, 2023
1 parent 8a4243c commit 7d8f677
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 71 deletions.
144 changes: 77 additions & 67 deletions draft-irtf-cfrg-vdaf.md
Original file line number Diff line number Diff line change
Expand Up @@ -1029,8 +1029,9 @@ produces a public share:
the nonce and produces a public share, distributed to each of Aggregators,
and the corresponding sequence of input shares, one for each Aggregator.
Depending on the VDAF, the input shares may encode additional information
used to verify the recovered output shares (e.g., the "shares of the of the proof(s)" in Prio3
{{prio3}}). The length of the output vector MUST be `SHARES`.
used to verify the recovered output shares (e.g., the "shares of the of the
proof(s)" in Prio3 {{prio3}}). The length of the output vector MUST be
`SHARES`.

In order to ensure privacy of the measurement, the Client MUST generate the
random bytes and nonce using a CSPRNG. (See {{security}} for details.)
Expand Down Expand Up @@ -2167,14 +2168,36 @@ post-processing.
We remark that, taken together, these three functionalities correspond roughly
to the notion of "Affine-aggregatable encodings (AFEs)" from {{CGB17}}.

### Multiple proofs {#multiproofs}

To improve soundness, the prover can construct multiple unique proofs for
its measurement such that the verifier will only accept the measurement once
all proofs have been thoroughly verified. Notably, several proofs using smaller
field can offer the same level of soundness as a single proof using large field.

To generate these proofs for a specific measurement, the prover can call
`Flp.prove`, using different sets of prover/joint randomnesses. Then,
the shares of these proofs are distributed to the verifier. Subsequently,
the verifier utilizes `Flp.query` to compute one share of verifier message
for each proof share, based on the measurement, the proof share, the joint
randomness, and unique query randomness. Finally, the
verifier runs `Flp.decide` on each verifier message to make a decision. The
validity of the measurement is determined through the logical conjunction
of all Boolean outputs yielded by `Flp.decide`.


## Construction {#prio3-construction}

This section specifies `Prio3`, an implementation of the `Vdaf` interface
({{vdaf}}). It has two generic parameters: an `Flp` ({{flp}}) and a `Xof`
({{xof}}). The associated constants and types required by the `Vdaf` interface
are defined in {{prio3-param}}. The methods required for sharding, preparation,
aggregation, and unsharding are described in the remaining subsections. These
methods refer to constants enumerated in {{prio3-const}}.
({{xof}}). Moreover, an associated constant, `PROOFS`, can be defined for
each instance ({{prio3-instantiations}}). `PROOFS`, with a value within
the range of `[1, 256)`, denotes the number of proofs ({{multiproofs}})
and is set to `1` by default, unless explicitly specified. The associated
constants and types required by the `Vdaf` interface are defined in
{{prio3-param}}. The methods required for sharding, preparation, aggregation,
and unsharding are described in the remaining subsections. These methods
refer to constants enumerated in {{prio3-const}}.

| Parameter | Value |
|:------------------|:------------------------------------------------|
Expand Down Expand Up @@ -2206,15 +2229,6 @@ methods refer to constants enumerated in {{prio3-const}}.
| `USAGE_JOINT_RAND_PART: Unsigned` | 7 |
{: #prio3-const title="Constants used by Prio3."}

A list of Prio3 specific parameters, in {{prio3-specific-params}}, are provided by each instantiation {{prio3-instantiations}}.

| Parameter | Description |
|:-----------------------------------|:------|
| `PROOFS` | Number of proofs, in `[1, 256)`, default value is `1`. |
{: #prio3-specific-params title="Prio3 specific praameters."}

Note for current instantiations {{prio3-instantiations}}, `PROOFS=1`.

### Sharding

Recall from {{flp}} that the FLP syntax calls for "joint randomness" shared by
Expand All @@ -2228,13 +2242,24 @@ The sharding algorithm involves the following steps:

1. Encode the Client's measurement for the FLP
2. Shard the measurement into a sequence of measurement shares
3. Derive all the joint randomnesses from the measurement shares and nonce
4. Run the FLP proof-generation algorithm using the derived joint randomnesses
5. Shard the proofs into a sequence of proofs shares
6. Return the public share, consisting of the joint randomnesses parts, and the
input shares, each consisting of the measurement share, proofs share, and
3. Derive the joint randomness from the measurement shares and nonce
4. Run the FLP proof-generation algorithm using the derived joint randomness
5. Shard the proof into a sequence of proof shares
6. Return the public share, consisting of the joint randomness parts, and the
input shares, each consisting of the measurement share, proof share, and
blind of one of the Aggregators

In case we need more than one roofs ({{multiproofs}}), we will adjust the
process as following:

* In step 3, generate `PROOFS` different sets of joint randomness.

* Then, step 4 is repeated `PRROOFS` times, using its own set joint randomness
each time.

Note that the input shares contain `PROOFS` shares of proofs.


Depending on the FLP, joint randomness may not be required. In particular, when
`Flp.JOINT_RAND_LEN == 0`, the Client does not derive the joint randomness
(Step 3). The sharding algorithm is specified below.
Expand Down Expand Up @@ -2283,7 +2308,7 @@ def shard_without_joint_rand(Prio3, meas, seeds):
Prio3.helper_meas_share(j+1, k_helper_meas_shares[j]),
)

# Generate the proofs and shard it into proof(s) shares.
# Generate and shard each proof into shares.
prove_rands = Prio3.prove_rands(k_prove)
leader_proofs_share = []
for _ in range(Prio3.PROOFS):
Expand All @@ -2297,7 +2322,7 @@ def shard_without_joint_rand(Prio3, meas, seeds):
)

# Each Aggregator's input share contains its measurement share
# and proofs share.
# and share of proof(s).
input_shares = []
input_shares.append((
leader_meas_share,
Expand All @@ -2318,15 +2343,15 @@ The steps in this method are as follows:

1. Shard the encoded measurement into shares
1. Generate and shard each proof into shares
1. Encode each measurement and proofs share into an input share
1. Encode each measurement and shares of proof(s) into an input share

Notice that only one pair of measurement and proofs shares (called the "leader"
shares above) are vectors of field elements. The other shares (called the
"helper" shares) are represented instead by XOF seeds, which are expanded into
vectors of field elements.
Notice that only one pair of measurement and shares of proof(s) (called the
"leader" shares above) are vectors of field elements. The other shares
(called the "helper" shares) are represented instead by XOF seeds, which
are expanded into vectors of field elements.

The methods on `Prio3` for deriving the prover randomness, measurement shares,
and proofs shares and the methods for encoding the input shares are defined in
and proof shares and the methods for encoding the input shares are defined in
{{prio3-auxiliary}}.

#### FLPs with joint randomness
Expand Down Expand Up @@ -2366,7 +2391,7 @@ def shard_with_joint_rand(Prio3, meas, nonce, seeds):
k_joint_rand_parts.insert(0, Prio3.joint_rand_part(
0, k_leader_blind, leader_meas_share, nonce))

# Generate the proofs and shard it into proofs shares.
# Generate and shard each proof into shares.
prove_rands = Prio3.prove_rands(k_prove)
joint_rands = Prio3.joint_rands(
Prio3.joint_rand_seed(k_joint_rand_parts))
Expand All @@ -2385,7 +2410,7 @@ def shard_with_joint_rand(Prio3, meas, nonce, seeds):
)

# Each Aggregator's input share contains its measurement share,
# proofs share, and blind. The public share contains the
# share of proof(s), and blind. The public share contains the
# Aggregators' joint randomness parts.
input_shares = []
input_shares.append((
Expand All @@ -2410,15 +2435,15 @@ and pass each `joint_rand` to the proof generationg algorithm.
vector, `[]`.) This requires generating an additional value, called the
"blind", that is incorporated into each input share.

The joint randomnesses computation involves the following steps:
The joint randomness computation involves the following steps:

1. Compute a "joint randomness part" from each measurement share and blind
1. Compute a "joint randomness seed" from the joint randomness parts
1. Compute the joint randomness for each proof evaluation from the joint randomness seed

This three-step process is designed to ensure that the joint randomnesses do
This three-step process is designed to ensure that the joint randomness does
not leak the measurement to the Aggregators while preventing a malicious Client
from tampering with the joint randomnesses in a way that allows it to break
from tampering with the joint randomness in a way that allows it to break
robustness. To bootstrap the required check, the Client encodes the joint
randomness parts in the public share. (See {{prio3-preparation}} for details.)

Expand All @@ -2428,14 +2453,14 @@ The methods used in this computation are defined in {{prio3-auxiliary}}.

This section describes the process of recovering output shares from the input
shares. The high-level idea is that each Aggregator first queries its
measurement and proofs share locally, then exchanges its verifiers share with the
other Aggregators. The verifiers shares are then combined into the verifiers
message, which is used to decide whether to accept.
measurement and share of proof(s) locally, then exchanges its share of
verifier(s) with the other Aggregators. The shares of verifier(s) are then
combined into the verifier message(s), which is used to decide whether to accept.

In addition, for FLPs that require joint randomness, the Aggregators must
ensure that they have all used the same joint randomnesses for the
ensure that they have all used the same joint randomness for the
query-generation algorithm. To do so, they collectively re-derive the joint
randomnesses from their measurement shares just as the Client did during
randomness from their measurement shares just as the Client did during
sharding.

In order to avoid extra round of communication, the Client sends each
Expand All @@ -2444,7 +2469,8 @@ the possibility that the Client cheated by, say, forcing the Aggregators to use
joint randomness that biases the proof check procedure some way in its favor.
To mitigate this, the Aggregators also check that they have all computed the
same joint randomness seed before accepting their output shares. To do so, they
exchange their parts of the joint randomness along with their verifiers shares.
exchange their parts of the joint randomness along with their shares of
verifier(s).

The definitions of constants and a few auxiliary functions are defined in
{{prio3-auxiliary}}.
Expand Down Expand Up @@ -2510,7 +2536,7 @@ def prep_shares_to_prep(Prio3, _agg_param, prep_shares):
if Prio3.Flp.JOINT_RAND_LEN > 0:
k_joint_rand_parts.append(k_joint_rand_part)

# Verify that each proof is well-formed and accepts the measurement.
# Verify that each proof is well-formed and the input is valid
for _ in range(Prio3.PROOFS):
verifier, verifiers = front(Prio3.Flp.VERIFIER_LEN, verifiers)
if not Prio3.Flp.decide(verifier):
Expand Down Expand Up @@ -2687,9 +2713,10 @@ Aggregator's blind for generating its joint randomness part.

In addition, the encoding of the input shares depends on which aggregator is
receiving the message. If the aggregator ID is `0`, then the input share
includes the full measurement and proofs share. Otherwise, if the aggregator ID
is greater than `0`, then the measurement and proofs shares are represented by
XOF seeds. We shall call the former the "Leader" and the latter the "Helpers".
includes the full measurement and share of proof(s). Otherwise, if the aggregator ID
is greater than `0`, then the measurement and shares of proof(s) are represented
by XOF seeds. We shall call the former the "Leader" and the latter the
"Helpers".

In total there are four variants of the input share. When joint randomness is
not used, the Leader's share is structured as follows:
Expand Down Expand Up @@ -3193,11 +3220,6 @@ way. The parameters for this circuit are summarized below.
| `Field` | `Field64` ({{fields}}) |
{: title="Parameters of validity circuit Count."}

| Parameter | Value |
|:-----------------|:-----------------------------|
| `PROOFS` | `1` |
{: title="Values for Prio3 specific parameters."}

### Prio3Sum

The next instance of Prio3 supports summing of integers in a pre-determined
Expand Down Expand Up @@ -3258,11 +3280,6 @@ def eval(self, meas, joint_rand, _num_shares):
| `Field` | `Field128` ({{fields}}) |
{: title="Parameters of validity circuit Sum."}

| Parameter | Value |
|:-----------------|:-----------------------------|
| `PROOFS` | `1` |
{: title="Values for Prio3 specific parameters."}

### Prio3SumVec

This instance of Prio3 supports summing a vector of integers. It has three
Expand Down Expand Up @@ -3382,11 +3399,6 @@ def eval(self, meas, joint_rand, num_shares):
| `Field` | `Field128` ({{fields}}) |
{: title="Parameters of validity circuit SumVec."}

| Parameter | Value |
|:-----------------|:-----------------------------|
| `PROOFS` | `1` |
{: title="Values for Prio3 specific parameters."}

#### Selection of `ParallelSum` chunk length {#parallel-sum-chunk-length}

The `chunk_length` parameter provides a trade-off between the arity of the
Expand All @@ -3402,7 +3414,6 @@ will vary, and must be found through trial and error. Setting `chunk_length`
equal to the square root of the appropriate measurement length will result in
proofs up to 50% larger than the optimal proof size.


### Prio3Histogram

This instance of Prio3 allows for estimating the distribution of some quantity
Expand Down Expand Up @@ -3500,11 +3511,6 @@ measurement is sharded. This is provided to the FLP by Prio3.
| `Field` | `Field128` ({{fields}}) |
{: title="Parameters of validity circuit Histogram."}

| Parameter | Value |
|:-----------------|:-----------------------------|
| `PROOFS` | `1` |
{: title="Values for Prio3 specific parameters."}

# Poplar1 {#poplar1}

This section specifies Poplar1, a VDAF for the following task. Each Client holds
Expand Down Expand Up @@ -4616,6 +4622,10 @@ We also stress that even if the Idpf is not extractable, Poplar1 guarantees
that every client can contribute to at most one prefix among the ones being
evaluated by the helpers.

## Considerations for multiple proofs (`PROOFS`) {#security-multiproof}
> TODO on how changing `PROOFS` ({{multiproofs}}) impacts security, in particular when we go
for a smaller field for a given circuit.

# IANA Considerations

A codepoint for each (V)DAF in this document is defined in the table below. Note
Expand Down Expand Up @@ -4644,9 +4654,9 @@ analysis of {{DPRS23}}. Thanks to Hannah Davis and Mike Rosulek, who lent their
time to developing definitions and security proofs.

Thanks to Junye Chen, Henry Corrigan-Gibbs, Armando Faz-Hernández, Simon
Friedberger, Tim Geoghegan, Albert Liu, Brandon Pitman, Mariana Raykova, Jacob Rothstein,
Shan Wang, Xiao Wang, and Christopher Wood for useful feedback on and
contributions to the spec.
Friedberger, Tim Geoghegan, Albert Liu, Brandon Pitman, Mariana Raykova, Jacob
Rothstein, Shan Wang, Xiao Wang, and Christopher Wood for useful feedback on
and contributions to the spec.

# Test Vectors {#test-vectors}
{:numbered="false"}
Expand Down
8 changes: 4 additions & 4 deletions poc/vdaf_prio3.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def prep_shares_to_prep(Prio3, _agg_param, prep_shares):
if Prio3.Flp.JOINT_RAND_LEN > 0:
k_joint_rand_parts.append(k_joint_rand_part)

# Verify that each proof is well-formed and accepts the measurement.
# Verify that each proof is well-formed and input is valid
for _ in range(Prio3.PROOFS):
verifier, verifiers = front(Prio3.Flp.VERIFIER_LEN, verifiers)
if not Prio3.Flp.decide(verifier):
Expand Down Expand Up @@ -188,7 +188,7 @@ def shard_without_joint_rand(Prio3, meas, seeds):
Prio3.helper_meas_share(j+1, k_helper_meas_shares[j]),
)

# Generate the proofs and shard it into proof(s) shares.
# Generate and shard each proof into shares.
prove_rands = Prio3.prove_rands(k_prove)
leader_proofs_share = []
for _ in range(Prio3.PROOFS):
Expand All @@ -202,7 +202,7 @@ def shard_without_joint_rand(Prio3, meas, seeds):
)

# Each Aggregator's input share contains its measurement share
# and proof share.
# and share of proof(s).
input_shares = []
input_shares.append((
leader_meas_share,
Expand Down Expand Up @@ -268,7 +268,7 @@ def shard_with_joint_rand(Prio3, meas, nonce, seeds):
)

# Each Aggregator's input share contains its measurement share,
# proof share, and blind. The public share contains the
# share of proof(s), and blind. The public share contains the
# Aggregators' joint randomness parts.
input_shares = []
input_shares.append((
Expand Down

0 comments on commit 7d8f677

Please sign in to comment.