diff --git a/draft-irtf-cfrg-vdaf.md b/draft-irtf-cfrg-vdaf.md index 7d5b0dfe..6a75e2b0 100644 --- a/draft-irtf-cfrg-vdaf.md +++ b/draft-irtf-cfrg-vdaf.md @@ -5521,7 +5521,7 @@ 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. -## Choosing the Field Size {#security-multiproof} +## Choosing FLP Parameters {#security-multiproof} Prio3 and other systems built from FLPs ({{flp-bbcggi19}} in particular) may benefit from choosing a field size that is as small as possible. Generally @@ -5543,13 +5543,25 @@ enough field ensures this computation is too expensive to be feasible. (See Another way to mitigate this issue (or improve robustness in general) is to generate and verify multiple, independent proofs. (See {{multiproofs}}.) For Prio3, the `PROOFS` parameter controls the number of proofs (at least one) that -are generated and verified. +are generated and verified. In general the soundness error of the FLP is given +by the following formula: -In general, Field128 is RECOMMENDED for use in Prio3 when the circuit uses -joint randomness (`JOINT_RAND_LEN > 0`) and `PROOFS == 1`. Field64 MAY be used -instead, but `PROOFS` MUST be set to at least `3`. Breaking robustness for -`PROOFS == 2` is feasible, if impractical; but `PROOFS == 1` is completely -broken for such a small field. +~~~ +(circuit_soundness + flp_soundness) ** PROOFS +~~~ + +where: + +* `circuit_soundness` is the soundness of the validity circuit + ({{flp-bbcggi19-valid}}) +* `flp_soundness` is the base soundness of the proof system ({{BBCGGI19}}, + Theorem 4.3) + +For circuits involving joint randomness, we aim for the soundness error to be +close to `2^-128` in order to mitigate offline attacks. Such circuits MUST use +Field128 with at least one proof or Field64 with at least three proofs. +Depending on the circuit, Field64 with two proofs might have significantly +lower soundness than Field128 with one proof. We stress that weak parameters (too small a field, too few proofs, or both) can be exploited to attack any aggregation task using those parameters. To diff --git a/poc/plot_prio3_multiproof_robustness.py b/poc/plot_prio3_multiproof_robustness.py index 55ee0527..6b8666cf 100644 --- a/poc/plot_prio3_multiproof_robustness.py +++ b/poc/plot_prio3_multiproof_robustness.py @@ -1,42 +1,38 @@ -# plot_prio3_multiproof_robustness.py - Plot robustness bounds for various -# parameters. -# Use `sage -python plot_prio3_multiproof_robustness.py` +# Plot robustness bounds for various parameters. +# +# sage -python plot_prio3_multiproof_robustness.py import math -from typing import cast +from typing import TypeVar import matplotlib.pyplot as plt -from vdaf_poc.field import Field64, Field128 -from vdaf_poc.flp_bbcggi19 import FlpBBCGGI19 -from vdaf_poc.vdaf_prio3 import Prio3SumVec +from vdaf_poc.field import Field64, Field128, NttField +from vdaf_poc.flp_bbcggi19 import FlpBBCGGI19, SumVec -NUM_REPORTS = 1000000000 +Measurement = TypeVar("Measurement") +AggResult = TypeVar("AggResult") +F = TypeVar("F", bound=NttField) -def soundness(gadget_calls: int, gadget_degree: int, field_size: int) -> float: +def base_soundness(flp: FlpBBCGGI19[Measurement, AggResult, F]) -> float: ''' ia.cr/2019/188, Theorem 4.3 - - gadget_calls - number of times the gadget is called - - gadget_degree - arithmetic degree of the gadget - - field_size - size of the field ''' - return gadget_calls * gadget_degree / (field_size - gadget_calls) + return sum((g_calls * g.DEGREE) / (flp.field.MODULUS - g_calls) + for (g, g_calls) in zip(flp.valid.GADGETS, flp.valid.GADGET_CALLS)) def robustness( - epsilon: float, + soundness: float, ro_queries: int, prep_queries: int, - num_proofs: int, - seed_bits: int) -> float: + num_proofs: int) -> float: ''' ia.cr/2023/130, Theorem 1, assuming the bound can be modified by raising - `epsilon` to the power of the number of FLPs. + `epsilon` to the power of the number of FLPs. We're also assuming the first + term dominates, i.e., we're ignoring the seed size. - epsilon - soundness of the base FLP + soundness - soundness of the FLP ro_queries - random oracle queries, a proxy for the amount of precomputation done by the adversary @@ -44,67 +40,64 @@ def robustness( prep_queries - number of online attempts, a proxy for the batch size num_proofs - number of FLPs - - seed_bits - the size of the XOF seed in bits ''' - return (ro_queries + prep_queries) * epsilon**num_proofs + \ - (ro_queries + prep_queries**2) / 2**(seed_bits - 1) + return (ro_queries + prep_queries) * soundness**num_proofs -def sum_vec(field_size: int, num_proofs: int, length: int) -> float: +def sum_vec(field: type[NttField], num_proofs: int, length: int) -> float: ''' - Prio3SumVec (draft-irtf-cfrg-vdaf-08, Section 7.4.3): Probability of - accepting one report in a batch of NUM_REPORTS. Assuming the asymptotically - optimal chunk length. + Maximum probability of at least 1 in 1 billion attacks on Prio3SumVec + robustness succeeding after doing 2^80 random oracle queries. ''' bits = 1 chunk_length = max(1, length**(1/2)) - vdaf = Prio3SumVec(2, length, bits, chunk_length) - valid = cast(FlpBBCGGI19[list[int], list[int], Field128], vdaf.flp).valid - gadget_calls = valid.GADGET_CALLS[0] - gadget_degree = valid.GADGETS[0].DEGREE - - base_flp_soundness = soundness(gadget_calls, gadget_degree, field_size) + flp = FlpBBCGGI19(SumVec(field, length, bits, chunk_length)) - # SumVec interprets the inner Mul-gadget outputs as coefficients of a - # polynomial and evaluates the polynomial at a random point. If a gadget - # output is non-zero, then the output is non-zero except with this - # probability. This is bounded by the number of roots of the polynomial. - circuit_soundness = length * bits / field_size + # Assuming we adopt the improvement from + # https://github.com/cfrg/draft-irtf-cfrg-vdaf/issues/427 + soundness = chunk_length / field.MODULUS + base_soundness(flp) return robustness( - base_flp_soundness + circuit_soundness, # ia.cr/2019/188, Theorem 5.3 - 2**80, - NUM_REPORTS, + soundness, + 2**80, # ro queries + 1_000_000_000, # prep queries num_proofs, - vdaf.xof.SEED_SIZE * 8, ) -print(math.log2(sum_vec(Field128.MODULUS, 1, 100000))) - -lengths = range(100, 10**6, 100) -plt.plot( - lengths, - [sum_vec(Field128.MODULUS, 1, length) for length in lengths], - label='Field128/1', -) -plt.plot( - lengths, - [sum_vec(Field64.MODULUS, 2, length) for length in lengths], - label='Field64/2', -) -plt.plot( - lengths, - [sum_vec(Field64.MODULUS, 3, length) for length in lengths], - label='Field64/3', -) - -plt.xscale('log', base=10) -plt.yscale('log', base=2) -plt.xlabel('Length') -plt.ylabel('Prob(1 in {} accepted reports being invalid)'.format(NUM_REPORTS)) -plt.title('Prio3SumvVec (field/number of proofs)') -plt.legend() -plt.grid() -plt.show() +if __name__ == '__main__': + print(-math.log2(sum_vec(Field128, 1, 100_000))) + print(-math.log2(sum_vec(Field64, 3, 100_000))) + print(-math.log2(sum_vec(Field64, 2, 100_000))) + print(-math.log2(sum_vec(Field64, 1, 100_000))) + + lengths = range(100, 10**6, 100) + plt.plot( + lengths, + [sum_vec(Field128, 1, length) for length in lengths], + label='Field128/1', + ) + plt.plot( + lengths, + [sum_vec(Field64, 3, length) for length in lengths], + label='Field64/3', + ) + plt.plot( + lengths, + [sum_vec(Field64, 2, length) for length in lengths], + label='Field64/2', + ) + # plt.plot( + # lengths, + # [sum_vec(Field64, 1, length) for length in lengths], + # label='Field64/1', + # ) + + plt.xscale('log', base=10) + plt.yscale('log', base=2) + plt.xlabel('Length') + plt.ylabel('Prob') + plt.title('Prio3SumvVec (field/number of proofs)') + plt.legend() + plt.grid() + plt.show()