2.  The protocol is executed by a large set of clients and a small set of servers.  We call the servers the _aggregators_. Each client's input to the protocol is a set of measurements (e.g., counts of some user obtain the final output "y = A[1] + A[2]".  This is correct because modular addition is commutative.  I.e., <del> "y = A[1] + A[2] = (x[1,1] + x[2,1] + x[3,1]) + (x[1,2] + x[2,2] + x[3,2]) = (x[1,1] + x[1,2]) + (x[2,1] + x[2,2]) + (x[3,1] + x[3,2]) = x[1] + x[2] + x[3] = F(x[1], x[2], x[3]) " </del> *Prio.* This approach can be used to privately compute any function "F" that can be expressed as a function of the sum of the users' inputs.  In Prio CB17, each user splits its input into shares and Each PA task in this document is divided into three sub-protocols as follows. <del> "+------------+ | | | Helper <---------------+ | | | +-----^------+ | | | 2. | 3. | | | +--------+ 1.  +-----v------+ +-----v-----+ | +----------> | 3. | | | Client +----------> Leader +---------> Collector | | +----------> | | | +--------+ +-----^------+ +-----^-----+ | | 2. | 3. | | | +-----V------+ | | | | | Helper <---------------+ | | +------------+ " </del> *Upload:* Each client assembles the measurements into an input for the given PA protocol.  It generates a proof of its input's validity and splits the input and proof into two shares, one for 3. <del> [TODO(cjpatton)] </del>  4. <del> [TODO(cjpatton): Rework this section into a specification of the protocol.] </del>  4.1. size.  The following SageMath script prints the parameters of a number of (probable) primes larger than 2^b for a given b: <del> "b = 116 for s in range(0,1000,1): B = 2^b p = (B*s).next_prime() if p-(B*s) == 1: bits = round(math.log2(p), 2) print(bits, p, GF(p).multiplicative_generator(), b, factor(s)) " </del> 4.3.2. Our instantiation of the input-validation protocol involves two 5. <del> [TODO:] </del>  6.

Based on . NAME NAME
I have a high level concern with the architecture here, following up from our meeting on Wednesday related to heavy hitters. TL;DR evaluating a hierarchy at only a subset of levels (which are specified at query time) is not optimal for performance if the verification step does not have access to those specified levels. For heavy hitters, we are considering a model where records form a binary tree, where aggregates can be reported at different levels of the tree. There are a few possible ways this could go: For every record, verify and evaluate the entire binary tree (i.e. bit-by-bit evaluation) For every record, verify and evaluate only a subset of the tree (at the limit, this can look more like Prio which only evaluates the last level of the tree) 2a. The subset of the tree to evaluate is configured within a particular record at record-creation time 2b. The subset of the tree to evaluate is configured dynamically at collection / aggregation time For our use-case (discussed in URL), we are interested in (2b), for a few reasons: (1) is potentially inefficient from both a performance and accuracy POV in that it requires more rounds, and additionally when used with differential privacy requires splitting a DP budget across all levels. Some prefixes might just not be interesting to the caller so we shouldn't waste privacy budget / compute on them. (2a) is problematic in two dimensions. Embedding configuration in records may compromise user privacy if the configurations can be set by an adversary colluding with one of the aggregators (e.g. you can leak log2(n choose k) bits of information if you allow any subset of k prefixes for an n-bit domain). Levels must be pre-specified at record-creation time, which is non-ideal if new information comes up at collection time which informs how aggregation should work (e.g. realizing you have fewer records than expected so it is better to focus on querying only up to a given prefix). However, I think (2b) runs into a few issues with this architecture that separates verification from collection, in that without knowing the specific levels to evaluate, we run the risk of spending unnecessary compute verifying levels that will never end up being used. The protocol is more efficient (less computation, fewer rounds) if we can verify multiple levels at once (i.e. the levels the collector cares about). Possible solutions: Add a step where the collector and leader communicate prior to verification (but keep verification and collection separate) More tightly couple "verify" and "collect" stages, so that verification can be done based on communication with the collector. In our setting, the "leader" and "collector" roles are somewhat merged which aligns with option (1), but it seems like a good idea to make the general architecture robust to this use-case. cc NAME who is working on our C++ implementation of IDPFs.
NAME can you please file an issue with your comment? It doesn't seem to apply to this PR, which punts on the aggregate protocol.
Ah yes sorry, I included it here because I thought it may impact design for upload / verify. Let me delete and re-upload as an issue.
TODO(NAME Allow an aribtrary number of verify requests
includes a detailed specification of how helpers advertise HPKE configs for clients to use when encrypting helper shares. We should consider how key rotation works.
Might this just be deployment specific, or should all helpers do the same thing? What we (i.e., Cloudflare) do for is rotate the HPKE config every hour. To add reliability in case a client has an old key, we keep around a rotating set of keys from the last hour or so. The config id is used to determine which key was used to encrypt the payload.
This seems deployment specific.
I agree that the rotation mechanism is deployment specific, but how the protocol reacts when a key rotation event occurs, possibly causing a mismatch between client and helper keys, should be in scope. (I assumed this issue was meant to address the latter.)
2021/06/16 design call: The servers need to agree, roughly, on how long reports are valid. (Once a helper rotates its key, reports encrypted under the previous key won't be decryptable.) Should the HPKE config lifetime be a paramater (e.g., of PAParam)? Does the report need to carry this parameter as well?
Do they need to agree, or should the data just naturally become invalid once rotation occurs?
It would be nice if submissions were rejected if they used invalid keys
Clients reports encrypted with a public key that's too old will indeed be rejected by helper. The validity period we're talking about is of interest for the collector in a protocol like Hits where the collector makes multiple, interactive requests to the aggregators: collectors need to have a sense of how long after the verify phase they can expect to be able to make collect requests before helpers are unable to decrypt the reports. i.e., collectors may not be able to make collect queries over reports that are more than 30 days old. In some cases, a validity period of hours could suffice if the successive queries are being made by an automated system, but maybe the queries are being formulated by a team of human analysts who need a day or so to consider what the n+1th query they want to send is based on the results of the nth query.
I think we might be converging on something like this: There's no explicit notion of report lifetime that's enforced by the protocol. Perhaps what we need instead is a way for the helper to tell the leader which reports are invalid and should be pruned. (Note that this is not unrelated to .)
Any solution to will also solve this problem. I'm going to make a call and say this issue is a duplicate and can be closed.
Per , the first step in the upload protocol is a POST to the leader server which returns information about helpers. In some yet to be specified cases, the response could include a challenge to be incorporated into reports. However I suspect that in most cases, the response will be static and so an idempotent, cacheable GET would be a better fit, especially since it would save clients a roundtrip to the helper on the hot path of submitting reports, and would make it possible for helpers to implement uploadstart by putting some static content in a CDN. HTTP GET requests don't have bodies, so we would have to figure out how to encode all the data in the current into the URI, which could be done as either path fragments in the URI (i.e., ) or as query parameters (i.e., ). We would of course need to make sure that don't preclude proof systems like the one alleged to exist in section 5.2 of "BBCp19".
Also, because the current protocol uses a POST for uploadstart, the implication is that for every report they submit, clients need to hit and then . If we make idempotent and cacheable, then that request can be skipped in the majority of cases. I suppose the significance of this depends on how we expect clients to use PDA: are they going to be continuously streaming hundreds or thousands of reports into the leader as events of interest occur (in which case I think eliminating requests is interesting) or are they expected to accumulate several reports locally before submitting them all in one or a few requests?
I agree this would be a good change, if we can swing it. It's notable that Prio v2 and HH don't require a random challenge from the leader. But as discussed, this is useful for other proof systems one might want to implement for Prio. For example, see the data type implemented here: URL See below for an explanation. There might be other ways to implement the challenge. One possibility is to derive the challenge from the TLS key schedule (or from whatever state the client and leader share for the secure channel). NAME and NAME have both pointed out that adding this dependency on the underlying transport is problematic. Another possibility might be to use some external source of randomness that the leader trusts. comes to mind, though it is somewhat problematic because clients would effectively use the same randomness for some period of time. The security model of BBCp19 would need to be extended to account for this. (I believe this could be done.) Some other solution I'm missing? _ This data type is used to compute the mean and variance of each integer in a sequence of integers. To do so, the client encodes each integer of its input vector as a pair , where is the representation of as a bit vector and is equal to . To prove validity of its input, the proof needs to do two things: verify that (a) is indeed a bit vector, and (b) the integer encoded by is the square root of . In order to check these things efficiently, the proof system uses a random challenge generated by the leader. At a very high level, the client wants to prove that the following boolean conjunction holds: is 1 or 0 AND is 1 or 0 AND ... AND encodes the square root of . In Prio, we need to represent this conjunction as an arithmetic circuit, which the random challenge helps us do. In a bit more detail now: Let's rewrite this expression as w[0] AND w[1] AND ... AND w[s]. As an arithmetic circuit, this expression is actually , where if and only if w[i] is true. If the input is valid, then the expression should evaluate to . But because we're talking about field elements, a malicious client may try to play games like setting and so that the sum is still , but the statement is false. To detect this, we actually compute the following expression: , where is a randomly chosen field element. If the statement is true, then the expression evaluates to . If the statement is false, then with high probability, the expression will evaluate to something other than . (This idea comes from Theorem 5.3)
Thank you for the detailed explanalation NAME Having thought about it more, I suspect an HTTP GET wouldn't be the right way to support proof systems that need a challenge. Assuming that a unique challenge is needed for every report[^1], then intuitively the leader would have to maintain a mapping of reports to issued challenges so that the leader and helper(s) can later use the challenge value when evaluating the client's proof. But now that the leader has to maintain state per-report, I think the upload protocol is missing a notion of a report identifier. The contains a , but IIUC the will be the same across many reports[^2] (e.g., if a browser is sending daily reports on how often users click a button, they use the same every time they submit). So needs to include a report ID, which must be echoed back to the leader in so that the leader can look up the challenge for the report. [^1] Is this true? Or would it suffice for each to use the same challenge across multiple reports? [^2] I notice that the design doc lacks a strong definition of a report and how it relates to tasks, inputs, measurements: because GET should be "safe" in the sense of not changing any server state, and it seems like the server would have to maintain a mapping of reports to issued challenges so that the leader and helper can later use the challenge value when evaluating the client's proof.
Continuing on the assumption that causes a state change on the leader, POST is then the appropriate HTTP method to use. , though it's a little unusual. There may be some big drawback to cacheable POST responses, but I think that might let us support proof systems that require live challenges while also eliminating the majority of requests when the proof system is such that the will change very rarely.
The requirement for the leader to keep state across upload start and upload finish may be avoidable. Suppose the leader has a long-lived PRF key K. It would could choose a random nonce N, compute R = PRF(K, N) as the challenge, and hand (N, R) to the client in its upload start response. The client would then use R to generate the proof and send N to the leader in its upload finish request. The leader could then re-derive R. NAME and I kicked around the idea of having the client generate a report ID. I think this may end up being useful, but we weren't sure if it was better to have the leader choose it or the client. To conform to the security model of BBCp19, each report would need its own challenge. It may be possible to relax this, but this would be risky.
I like this a lot! I'm a big fan of deterministic derivation schemes like this -- besides reintroducing the possibility of an idempotent GET, eliminating storage requirements for challenges makes it much easier to scale up leaders. If the report IDs are big enough (UUIDs?) we could even use them as . I would say the leader, because if you let the client do it, then the leader would have to check for things like collisions with existing report IDs or other malicious ID choices. If the leader chooses it, then all it has to do is generate a few bytes of randomness. I agree.
NAME we'd appreciate your feedback on this issue because it involves security considerations for ZKP systems on secret-shared data [1]. I'll quickly sum up the problem so that you don't need to worry about the conversation above. The problem at hand regards systems like [1, Theorem 5.3] that require interaction between the prover and verifier. Specifically, we're considering a special case of Theorem 5.3 in which the verifier sends the prover a random "challenge" that is used to generate and verify a (non-interactive) FLPCP (call it ): We refer to the prover's first and second messages as "upload start" and "upload finish" respectively. Each message corresponds to an HTTP request made by a web client to a web server. Ideally, the verifier would be able to handle these requests statelessly, i.e., without having to store . Consider the following change. Suppose the verifier has a long-term PRF key . In response to an upload start request, it chooses a unique "upload id" --- maybe this is a 16-byte integer chosen at random --- and computes and sends to the prover. In the upload finish request, the prover sends so that the verifier can re-derive instead of needing to remember it: The (potential) problem with this variant is that a malicious client is able to replay a challenge as many times as it wants. This behavior isn't accounted for in Definition 3.9, so when you try to "compile" this system into a ZKP on secret-shared data as described in Section 6.2 and consider its security with respect to Definition 6.4 (Setting I), you can no longer appeal to Theorem 6.6 to reason about its security. Our question is therefore: What impact do you expect this change to have on the soundness of the system? Can you think of an attack that exploits the fact that challenges can be replayed? My expectation is that the problem is merely theoretical, i.e., we ought to be able to close the gap with an appropriate refinement of Definition of 3.9 and reproving Theorem 5.3. [1] URL
Thanks for looping me in. In the discussion below, I am assuming that the client sends its data vector (called "x" in the paper [1]) at the same time as it sends its proof to the verifier. Even if the client sends its data vector in the first message, it seems that the client can reuse an (R,N) pair from upload request 1 to later submit upload request 2, in which case the client sees (R, N) before it chooses its data vector and proof. So I think we can assume that the client's data vector and proof can depend on the verifier's random value R. (I'll call this value "r" to match the paper.) For soundness: the client/prover must commit to its data vector before it learns the verifier's random value r. If the prover can choose its data value and proof in a way that depends on the verifier's randomness r, the soundness guarantee no longer holds. There is also an efficient attack. So I suspect that the stateless-verifier optimization is unsound. The attack works as follows: given r, the verifier finds a non-satisfying vector x such that sumi ri C(Ai(x)) = 0, again using the notation from Theorem 5.3 in the paper [1]. For the circuits C used in most applications, it will be easy to find such a vector x. Then, the prover constructs a proof that x that sumi ri C(Ai(x)) = 0. The proof is valid, so the verifier will accept always. To remove interaction, the Fiat-Shamir-like technique in Section 6.2.3 is probably the best way to go. We only sketched the idea in the paper (without formal analysis or proof) but if it's going to be important for your application, I'm happy to sanity-check the protocol you come up with. Another idea—the details of which I have not checked carefully—would be to modify your protocol as follows: The prover sends its data vector x to verifiers, split into k shares x1, ..., xk—one for each of the k verifiers. Each verifier holds a PRF key ki computes ri = PRF(ki, xi) and they return r = r1 + ... + rk to the prover. The verifiers store no state. The prover computes the proof pi and sends (x1, pi1), ..., (xk, pik) to the verifiers—again, one for each of the k verifiers. Each verifier computes ri = PRF(ki, xi), they jointly compute r = r1 + ... + r_k, and check the proof using randomness r. The Fiat-Shamir-like approach seems slightly cleaner to me, since it only requires a single prover-to-verifier message. Either way, please let me know if I misunderstood your question or if anything else here is unclear. [1] URL
Thanks for the nice explanation, Henry. It's perfectly clear and answers my question. It also points to something that I, for one, missed the importance of: the need for the prover to "commit" to the input shares before generating the r-dependent proof. I appreciate you pointing this out. The only requirement for making upload start not idempotent is so that the randomness r can be sent to the client. It would be nice to do away with this requirement. I think the way forward is using the Fiat-Shamir technique. NAME I'll get cracking on this and let you know when I have something for you to look at.
NAME I believe the change is something like this. Let be a hash function with range . Suppose there are verifiers. The prover proceeds as follows: Splits its inputs into shares . For each , do: Choose blinding factor from at random. Let . Set . Derive field element from (e.g., seed a PRNG with and map the output to a field element by rejection sampling on chunks of the output). Generate the proof using and . Split into shares . For each send to the -th verifier.
Yes, this looks right to me!
As of URL, the upload start request no longer exists. When we're ready to specify Prio, we'll want to include the Fiat-Shamir approach for protocols that use joint randomness. I've noted this and am closing the issue.
(moving a comment made in URL to an issue) I have a high level concern with the architecture here, following up from our meeting on Wednesday related to heavy hitters. TL;DR evaluating a hierarchy at only a subset of levels (which are specified at query time) is not optimal for performance if the verification step does not have access to those specified levels. For heavy hitters, we are considering a model where records form a binary tree, where aggregates can be reported at different levels of the tree. There are a few possible ways this could go: For every record, verify and evaluate the entire binary tree (i.e. bit-by-bit evaluation) For every record, verify and evaluate only a subset of the tree (at the limit, this can look more like Prio which only evaluates the last level of the tree) 2a. The subset of the tree to evaluate is configured within a particular record at record-creation time 2b. The subset of the tree to evaluate is configured dynamically at collection / aggregation time For our use-case (discussed in (comment)), we are interested in (2b), for a few reasons: (1) is potentially inefficient from both a performance and accuracy POV in that it requires more rounds, and additionally when used with differential privacy requires splitting a DP budget across all levels. Some prefixes might just not be interesting to the caller so we shouldn't waste privacy budget / compute on them. (2a) is problematic in two dimensions. Embedding configuration in records may compromise user privacy if the configurations can be set by an adversary colluding with one of the aggregators (e.g. you can leak log2(n choose k) bits of information if you allow any subset of k prefixes for an n-bit domain). Levels must be pre-specified at record-creation time, which is non-ideal if new information comes up at collection time which informs how aggregation should work (e.g. realizing you have fewer records than expected so it is better to focus on querying only up to a given prefix). However, I think (2b) runs into a few issues with this architecture that separates verification from collection, in that without knowing the specific levels to evaluate, we run the risk of spending unnecessary compute verifying levels that will never end up being used. The protocol is more efficient (less computation, fewer rounds) if we can verify multiple levels at once (i.e. the levels the collector cares about). Possible solutions: Add a step where the collector and leader communicate prior to verification (but keep verification and collection separate) More tightly couple "verify" and "collect" stages, so that verification can be done based on communication with the collector. In our setting, the "leader" and "collector" roles are somewhat merged which aligns with option (1), but it seems like a good idea to make the general architecture robust to this use-case. cc NAME who is working on our C++ implementation of IDPFs.
Thanks for the feedback NAME I've finally had a chance today to dig into the heavy hitters paper. I agree that the framework described in isn't going to be feasible for heavy hitters. I think your option (2b) is the only way to go, as I explain below. For context, thinks of PA ("Private Aggregation") protocols as having three distinct "phases": Upload: Each client uploads its input shares to the aggregators. Verify: The aggregators verify each client's input. Collect: Each aggregators add up their verified input shares and emits its output share to the collector. In order to fit heavy hitters into his framework, we could take your option (1) or (2a). However, these options are only feasible for very small inputs, say, , where is the length of each client input in bits. In order to make the protocol feasible for large inputs, heavy hitters involves multiple rounds of the Verify/Collect phases, where the output of the previous Collect phase is used to decide what to do in the next Verify phase. So that we're all on the same page about why interaction is necessary, I've included a brief overview of the protocol below [1]. (Please correct me if I've misunderstood anything!) Accounting for this interaction in the protocol spec should be fairly straightforward. The delta, I think, is that there needs to be a way to re-run the Verify/Collect phases on the same inputs, but using different parameters each time, i.e., with different sets of prefixes specified by the collector. _ [1] Here's a bird's eye view of the protocol, as I understand it. (I'm ignoring DP for now.) First, (Upload) each client generates IDPF shares of its -bit input and sends a share to each aggregator. After all clients have uploaded their IDPF key shares, the collector and aggregators verify and collect the heavy hitters as follows. Let . For each : (Verify) For each client upload: a. For each prefix in , each aggregator evaluates its IDPF share on input of . b. The aggregators verify (in zero-knowledge) that their IDPF outputs are well-formed, i.e., that only one of the outputs is and the rest are . (Collect) a. For each verified IDPF output, each aggregator adds up its shares and sends the resulting "histogram share" to the collector. b. The collector adds the histogram shares together to get the histogram. This encodes the frequency of each prefix in among the client inputs. c. The collector assembles , the next set of candidate prefixes. To do so, it collects the set of prefixes that appear at least times in the histogram. It lets be the set of strings obtained by appending or to each element of . d. Finally, the collector disseminates among the aggregators. What makes this feasible is that the size of each is at most the number of clients divided .
NAME this sounds about right to me. Just a small nit but I wanted to point out that your (Collect) stage, multiple interactions with the collector are not strictly necessary. What you described is a zero-knowledge protocol but alternatively the aggregators could interact to learn the aggregates at each level and proceed down without interaction with the collector. We discussed this variant a few weeks ago, it trades off aggregator knowledge with protocol simplicity (e.g. collectors don't need to follow an interactive protocol).
Let me see if I understand you correctly. Concretely, the helper would send the leader its share of the aggregate at the end of each round. Next, the leader would use the aggregate shares to compute the next set of prefixes, then send this to the helper for the next round. If my understanding is right, then it seems to me that this is the same protocol as [1], but with leader == collector. What do you think? Maybe I'm missing something. I'll do my best to read the design doc you shared by the end of the week.
Yes that's right. I was outlining a "simpler" communication model where helpers communicated directly with each other but in this architecture it would go through the leader.
I wrote up my outstanding questions and comments in distinct issues in the name of unblocking progress here (, , ). Otherwise this looks good to me.

2. <ins> [OPEN ISSUE: Rework this section in light of issue #44.] </ins> The protocol is executed by a large set of clients and a small set of servers.  We call the servers the _aggregators_. Each client's input to the protocol is a set of measurements (e.g., counts of some user obtain the final output "y = A[1] + A[2]".  This is correct because modular addition is commutative.  I.e.,  *Prio.* This approach can be used to privately compute any function "F" that can be expressed as a function of the sum of the users' inputs.  In Prio CB17, each user splits its input into shares and Each PA task in this document is divided into three sub-protocols as follows.  *Upload:* Each client assembles the measurements into an input for the given PA protocol.  It generates a proof of its input's validity and splits the input and proof into two shares, one for 3.  <ins> This section specifies a protocol for executing generic PA tasks. Concrete PA protocols are described in prio and hits. Each round of the protocol corresponds to an HTTP request and response.  The content type of each request is "application/octet- stream".  We assume that some transport layer security protocol (e.g., TLS or QUIC) is used between each pair of parties and that the server is always authenticated. [TODO: Decide how to authenticate the leader in leader-to-helper and aggregator-to-collector connections.  One option is to use client certificates for TLS; another is to have the leader sign its messages directly, as in Prio v2.] [TODO: @chris-wood suggested we specify APIs for producing and consuming each of the messages in the protocol.  Specific PA protocols would implement this API.] [OPEN ISSUE: This needs to be reworked in order to account for protocols, like Hits, that involve multiple rounds of verify/collect. See issue #44.] *Error handling.* In this section, we will use the verbs "abort" and "alert with "[some error message]"" to describe how protocol participants react to various error conditions.  The behavior is specified in pa-error.  For common errors, we may elide the verbs altogether and refer to pa-error-common-aborts. 3.1. 3.1.1. Each PA protocol is associated with a _PA task_ that specifies the measurements that are to be collected and the protocol that will be used to collect them: The first field, "version" specifies the version of this document. The second field, "id" is an opaque identifier used by the clients, aggregators, and collector to uniquely identify the PA task at hand. We will call it the _task id_ in the remainder. [TODO: Decide how the "PATask" is configured.  Eventually this will be distributed, in an authenticated manner, from the collector the other parties.  For now, we just assume this value is negotiated out- of-band.] 3.1.2. Associated to each task is the set of PA protocol parameters.  These are encoded by the "PAParam" structure, which also includes the task: The "batch_size" field encodes the _batch size_, the number of input shares accumulated by each aggregator before emitting its output share.  The "proto" field identifies the specific PA protocol in use. The rest of the structure contains any protocol-specific parameters that are required. 3.1.3. Our protocol uses HPKE for public-key encryption I-D.irtf-cfrg-hpke. Each helper specifies the HPKE public key that clients use to encrypt the helper's share.  The public key and associated parameters are structured as follows: [TODO: Decide whether to re-use the config from OHTTP/ECH.  This would add support for multiple cipher suites.] We call this the helper's _key configation_. The key configuration is used to set up a base-mode HPKE context to use to derive symmetric keys for protecting the shares sent to the helper.  The _config id_, "HpkeConfig.id", is forwarded by the client to the helper, who uses this value to decide if it knows how to decrypt a share it receives. 3.2. We assume the following conditions hold before the client begins uploading its data: The client, aggregators, and collector are configured with a specific PA task. The client knows the URL of the leader endpoint, e.g., "example.com/metrics".  We write this URL as "[leader]" below. (We write "[helper]" for a helper's URL.) The client and leader can establish a leader-authenticated secure channel. The leader and each helper can establish a leader-authenticated secure channel. Each helper has chosen an HPKE key pair. The aggregators agree on a set of PA tasks, as well as the PA protocol and parameters used for each task. [TODO: It would be clearer to include a "pre-conditions" section prior to each "phase" of the protocol.] 3.3. [TODO: Add an illustration of this sub-protocol.] Uploading a report involves two requests to the leader.  In the _upload start request_, the client discovers the protocol-specific parameters it needs to generate the report, as well as the endpoint URL of each helper.  In the _upload finish request_, it uploads its report to the leader. [NOTE: @acmiyaguchi pointed out that the use of an anonymizing proxy for uploading shares might be easier to implement if the "upload" phase involved a single HTTP request.  However, OHTTP (https//www.ietf.org/archive/id/draft-thomson-http-oblivious-01.html) allows clients to make multiple requests through a proxy, so these kinds of use cases should work.] [NOTE: @cjpatton: Breaking the upload phase into two requests is useful for supporting Prio-like proof systems in which the leader sends the client a "challenge" it uses to generate the proof.  (See the SIMD construction of BBCp19, Section 5.2.  Here, the "challenge" is a randomly generated field element.)] 3.3.1. The client sends a POST request to "[leader]/upload_start" with the following message: The "task" field corresponds to the PA task for which a report will be generated. The leader responds to well-formed requests to "[leader]/upload_start" with status 200 and the following message: The message includes the URL of each helper and any protocol-specific parameters the client needs to generate its report.  The leader's response to malformed requests is specified in pa-error-common- aborts. 3.3.2. For each URL "[helper]" in "PAUploadStartResp.helper_urls", the client sends a GET request to "[helper]/key_config".  The helper responds with status 200 and an "HpkeConfig" message.  Next, the client collects the set of helpers it will upload shares to.  It ignores a helper if: the client and helper failed to establish a secure, helper- authenticated channel; the GET request to the helper URL failed or didn't return a valid key config; or the key config specifies a KEM, KDF, or AEAD algorithm the client doesn't recognize. If the set of supported helpers is empty, then the client aborts and alerts the leader with "no supported helpers".  Otherwise, for each supported helper the client issues a POST request to "[leader]/upload_finish" with a payload constructed as described below. [OPEN ISSUE: @chris-wood: Can't the leader determine if helpers are "online"?  This seems to reveal information that's specific to clients.  Imagine, for example, that clients are prohibited from talking to helpers but not the leader.  Is it OK that leaders learn that about a client?  I'm not sure, so I'd be inclined to remove this unless we have a concrete use case.] The client begins by setting up an HPKE I-D.irtf-cfrg-hpke context for the helper by running where "pk" is the KEM public key encoded by "HpkeConfig.public_key". The outputs are the helper's encapsulated context "enc" and the context "context". [TODO: Decide what the info string should be.  At a minimum it should include "PAParam" so that the PA parameters are implicitly authenticated by the helper.  when it decapsulates "enc".  We might consider using the hash of the transcript between the client and leader so far, i.e., "PAUploadStartReq", ..., "PAUploadStartResp", as this would be the most conservative thing.  We just need to ensure that the helper can reproduce this transcript.  This should be possible given the information it has.] Next, the client encodes its measurements as an input for the PA protocol.  It then generates a validity proof for its input and uses "context" to split the input and proof into a _leader share_ and a _helper share_, where the latter is protected by the HPKE context. Note that the details of each of these processing steps -- encode, prove, split, and encrypt -- are specific to the PA protocol. [OPEN ISSUE: Is it safe to generate the proof once, then secret-share between each (leader, helper) pair?  Probably not in general, but maybe for Prio?] [OPEN ISSUE: allow server to send joint randomness in UploadStartResp, and then enforce uniqueness via double-spend state or something else] The payload of the POST request to "[leader]/upload_finish" is structured as follows: We sometimes refer to this message as the _report_. The message contains the "task" fields of the previous request.  In addition, it includes the helper's HPKE config id, endpoint URL, and the helper and leader shares.  The helper share has the following structure: Field "enc" encodes the helper's encapsulated HPKE context.  The remainder of the structure contains the share itself, the structure of which is specific to the PA protocol.  The structure of the leader share is similarly protocol specific: Note that the leader share is sent not encrypted. The leader responds to well-formed requests to "[leader]/upload_finish" with status 200 and an empty body. Malformed requests are handled as described in pa-error-common- aborts. 3.4. [TODO: Add an illustration of this sub-protocol.] After the client uploads a report to the leader, the leader and helper verify in zero knowledge that the proof is well-formed.  The exact procedure for doing so is protocol specific, but all protocols have the same basic structure.  In particular, the protocol is comprised of a sequence of HTTPS requests from the leader to the helper.  At the end of this phase, the leader and helper will have decided whether a set of client inputs are valid.  For each valid input, they proceed as described in pa-collect. The leader begins by collecting a sequence of reports that are all associated with the same PA task, helper URL, and helper HPKE config id.  Let "[helper]" denote the the URL.  The leader sends a POST request to "[helper]/verify" with the following message: The structure contains the PA task, the HPKE config id, and a sequence of _sub-requests_, each corresponding to a unique client report.  Sub-requests are structured as follows: The "enc" field is the helper's encapsulated HPKE context sent in the report.  The remainder of the me structure is dedicated to the protocol-specific helper share and request parameters used for the current round. The helper handles well-formed requests as follows.  (As usual, malformed requests are handled as described in pa-error-common- aborts.)  It first looks for the PA parameters "PAParam" for which "PAVerifyReq.task.id == PAParam.task.id".  Next, it looks up the HPKE config and corresponding secret key associated with "PAVerifyReq.key_config_id".  If not found, then it aborts and alerts the leader with "unrecognized key config".  [NOTE: In this situation, the leader has no choice but to abort.  This falls into the class of error scenarios that are addressable by running with multiple helpers.] The response is structured as a sequence of _sub-responses_, where the i-th sub-response corresponds to the sub-request for each i.  The structure of each sub-response is specific to the PA protocol: For each sub-request "PAVerifyReq", the helper computes the corresponding sub-response as follows.  It first checks that checks that "PAVerifyReq.proto == PAParam.proto".  If not, it aborts and alerts the leader with "incorrect protocol for sub-request". Otherwise, It computes the HPKE context as where "sk" is the secret key corresponding to the HPKE config.  Next, it computes the body of the "PAVerifyResp" according to the PA protocol. [OPEN ISSUE: encrypt and store Helper state at the Leader] 3.5. [TODO] 3.6. An _alert_ is a message sent either in an HTTP request or response that signals to the receiver that the peer has aborted the protocol. The payload is where "task" is the associated PA task (this value is always known) and "payload" is the message.  When sent by an aggregator in response to an HTTP request, the response status is 400.  When sent in a request to an aggregator, the URL is always "[aggregator]/error", where "[aggregator]" is the URL of the aggregator endpoint. 3.7. The following specify the "boiler-plate" behavior for various error conditions. The message type for the payload of each request and response is unique for a given URL.  If ever a client, aggregator, or collector receives a request or response to a request with a malformed payload, then the receiver aborts and alerts the peer with "unrecognized message". Each POST request to an aggregator contains a "PATask".  If the aggregator does not recognize the task, i.e., it can't find a "PAParam" for which "PATask.id == PAParam.task.id", then it aborts and alerts the peer with "unrecognized task". </ins> 4.  <ins> [TODO: Define "PrioParam"] [TODO: Define "PrioUploadStartResp"] [TODO: Define "PrioHelperShare"] [TODO: Define "PrioLeaderShare"] [TODO: Define "PrioVerifyReq"] [TODO: Define "PrioVerifyResp"] [TODO(cjpatton): Rework the remainder of this section.] </ins> 4.1. size.  The following SageMath script prints the parameters of a number of (probable) primes larger than 2^b for a given b:  4.3.2. Our instantiation of the input-validation protocol involves two 5.  <ins> [TODO: Define "HitsParam"] [TODO: Define "HitsUploadStartResp"] [TODO: Define "HitsHelperShare"] [TODO: Define "HitsLeaderShare"] [TODO: Define "HitsVerifyReq"] [TODO: Define "HitsVerifyResp"] </ins> 6.


04: <del> Resource oriented HTTP API. (#278, #398, #400) (*) </del>  <del> Clarify security requirements for choosing VDAF verify key (#407, #411). </del>  <del> Clients must provide nonce and random input when sharding inputs. (#394, #425) (*) </del>  <del> Collection message now includes interval of time spanned by constituent reports. (#397, #403) (*) </del>  Update share validation requirements based on latest security analysis. (#408, #410)

We need for this change to make sense.
DAP-04 will need some routine changes: [x] Update version tags for HPKE to () [x] Bump VDAF-03 to 05 () [x] Update change log () [x] Run spell check ()

04:  <ins> Introduce resource oriented HTTP API. (#278, #398, #400) (*) </ins>  <ins> Clarify security requirements for choosing VDAF verify key. (#407, #411) </ins>  <ins> Require clients to provide nonce and random input when sharding inputs. (#394, #425) (*) </ins>  <ins> Add interval of time spanned by constituent reports to Collection message.  (#397, #403) (*) </ins> Update share validation requirements based on latest security analysis. (#408, #410)


"encrypted_agg_share" computed above. After receiving the Helper's response, the Leader uses the <del> HpkeCiphertext to finialize a collection job (see collect- </del>  finalization). Once an AggregateShareReq has been issued for the batch determined by

DAP-04 will need some routine changes: [x] Update version tags for HPKE to () [x] Bump VDAF-03 to 05 () [x] Update change log () [x] Run spell check ()

"encrypted_agg_share" computed above. After receiving the Helper's response, the Leader uses the  <ins> HpkeCiphertext to finalize a collection job (see collect- </ins> finalization). Once an AggregateShareReq has been issued for the batch determined by


Malformed requests are handled as described in pa-error-common- aborts.  3.4. [TODO: Add an illustration of this sub-protocol.]

Addresses by adding a TODO. With this issue has been solved in part: To ensure consistency in case of helper failure, the leader specifies a set of helpers, and the client runs the protocol with the leader and each helper. Each cohort produces an output, and the collector will need a way of deciding which output is "correct". Let's wait to answer this question until we
At the moment, the spec thinks of a PA task as a one-way data-processing pipeline with three phases: upload, in which clients send inputs to the leader; verify, in which the leader and helper verify each input; and collect, in which the leader and helper aggregate inputs and push the output to the collector. The upload and verify phases are currently specified; our next task is to specify the collect phase. This pipeline model is a perfectly fine way of thinking about Prio. (Although there are ways in which one might use Prio metrics that don't quite fit into this model; see .) However, HH doesn't fit into this model at all (see ). It's apparently necessary to re-think the communication model for PA protocols. NAME and I were kicking this around and wanted to share some thoughts. First off, let's try to enumerate the relevant requirements for the PA framework. The helper is stateless. Aggregators (i.e., the leader and helper) enforce the batch size (): As long as one of the aggregators is honest, the collector should be unable to see outputs aggregated over a small amount of inputs. Aggregators should never learn more information about outputs than the collector. Moreover, it should be possible to design a PA protocol in which neither the aggregator sees the output in the clear. (This is possible for Prio. In HH, the aggregators inevitably learn the set of candidate prefixes at each round, which leaks some information about the output.) Implementing HH requires O(n) space for the leader, where n is the number of inputs. However, it should be possible for the leader to implement Prio with O(1) space. The aggregators verify and aggregate inputs over multiple rounds. In each round, the collector specifies the parameters that will be used. (In HH, the parameters are the set of candidate prefixes.) [Needs discussion] Not every PA protocol will verify inputs (). We can't satisfy all of these requirements in the upload->verify->collect model. Instead, we might think of a PA task as two concurrent processes running asynchronously: Upload process: Clients push inputs to the leader via the upload start and upload finish requests. Collect process: Collector pulls outputs from the leader via a yet-to-be-specified collect request. The request carries the parameters the aggregators will use for verification and aggregation. In this communication model, the collector may attempt to pull the output for a PA task whenever it likes. A pull will only be successful if the aggregators have verified and aggregated enough inputs. If/when inputs are verified is up to the PA protocol, but must occur before the leader can respond to a collect request. (In Prio, reports can verified as soon as they're uploaded; in HH, verification can only occur once all the reports have been uploaded and the collector has issued a collect request.) The helper will keep a long-term symmetric key for encrypting the state that's managed by the collector. This ensures privacy while allowing the helper to be stateless. At the end of aggregation, each of the aggregators holds a share of the output. Before responding to a collect request, the leader requests the helper's output share. To ensure that the leader doesn't see the output in the clear, the helper encrypts its share under the collector's HPKE public key. Open questions Should collect requests be idempotent? A collect request may take a long time. (For HH, the latency may be several minutes!) Is HTTP the right application? How does the helper learn the collector's HPKE public key? The collector needs to prove ownership of the corresponding secret key in some way.
Perhaps a slight adjustment to upload-verify-collect would be to split into and / like the v1/v2 protocols. It would prevent overloading the term e.g.: Prio: upload -> verify -> aggregate -> publish HH: upload -> (verify -> aggregate -> verify -> ... -> aggregate) -> publish I've been thinking about the protocol through the lens of map-reduce pipeline with network storage (e.g. Spark and S3), where computation is represented as a DAG. It looks the like HH computation would fit neatly into a batched pipeline, given a fixed number of verify-aggregate rounds. What is the benefit of idempotency in this context? This is so the results of earlier rounds can be used as inputs into later rounds to improve performance? One way to deal with the long latency time is to use a task queue e.g. rabbitmq to wait for responses. The collector would then poll the task queue for completion before moving onto the next step (pushing the results to the leader for another round?). Is this not solved as part of the upload phase? Or does the upload phase only authenticate the leader with the helpers?
This seems reasonable to me. The one downside I see is that the number of verify-aggregate rounds may not be fixed. For example, the collector might want to start with a set of candidate prefixes. By doing so, it can reduce the number of rounds significantly in applications where the search space can be constrained in some way. (I'd love to hear NAME thoughts on this.) Can you be more specific on how this fits into a map-reduce pipeline? My guess is that you'd have a map-reduce for each round. It seems to me like this is still possible. This question is about usability, not performance. Imagine the collector is some person on their laptop, interacting with the system. Should the protocol ensure that they can re-run a query multiple times and get back the same result? Or is this up to the implementation? Sorry, this is a bit confusing. The upload process involves the helper's public key (used by the clients); the collector phase involves collector's public key (used by the helper).
Each verification/aggregation step would just map over the data, joining appropriately with the outputs of the previous step. The reduce step is done at the very end (with the collect phase). Iterative algorithms work fine with map-reduce (e.g. power iteration with thresholds for eigenvector computation). It's trickier when the workflow involves multiple parties because you have to persist intermediate results to a shared location, but it's certainly doable. Coordinating work is the challenge, a task queue or something similar in the collector/leader would abstract some of those details. It would be difficult to guarantee idempotency if differential privacy were applied server-side, something which has been mentioned before.
NAME I think the difference being proposed in this issue is, rather than have the servers drive aggregation (by running some map and reduce functions), it has the collector drive aggregation. This seems to have the benefit of turning aggregators into very dumb entities that hold reports and compute functions over them. (One could still build the map-reduce pipeline that sends aggregate data to some sink this way by having the collector drive aggregation and write it to a sink.)
At a high level, there's still a comparable amount of coordination and delegation. Scheduling is nontrivial ("A collect request may take a long time") and will probably involve some task management in the collector. For reference, here's the cluster management model of Spark. I don't think it's too far fetched to imagine the collector as a driver program and stateless cloud functions as executors. ! URL This is (mostly?) orthogonal to the data flow e.g. upload -> verify -> aggregate -> publish.
Yep, they seem to match pretty nicely! I need to think about this more, but in my mental model, the protocol looks very different if, say, the collector drives things instead of the being a dumb consumer of data output from the aggregators, even though the flow of data is mostly the same.
A few thoughts here. As a preliminary, I want to talk about the commercial relationships rather than the network relationships. First, let's call the "Customer" the entity which is paying for the service and presumably (1) is deploying the clients and (2) is interested in the data. So, in the case where we are doing Fx telemetry, Mozilla would be the Customer. The Customer contracts with 1 or more Operators who collectively run the service. In the simplest conceptual topology, there are two Operators, call them A and B, each of whom runs an Aggregator. One could also have a situation in which the Customer contracts with one Operator and then runs the second Aggregator themselves, but that seems like a case that is likely to be less widely used. In the former scenario, With that as background: I think this is going to work best if the PA system presents as a single service to the common Customer, at least technically. By this I mean that once it's set up, the Customer/Collecter should only have to talk to one system (presumably operated by the same entity that operates the Leader). The Leader would in turn talk to the other aggregators. This does not preclude concealing the results from the Leader, but merely requires that the aggregated shares be encrypted so that the Leader cannot see them. We have been a bit vague about what the Leader/Collector interface is, but I think it's most reasonable to think of it as a standard Web service API. This of course means that if you just want a dashboard a la Amplitude, someone will have to implement that, but the most flexible way to do so is as a system that talks to the Leader over that API. It's been noted that there are times when things are long-running, but there are well-established mechanisms for Web services APIs to handle this kind of thing, such as by the Collector having a notification endpoint or polling. There are multiple kinds of state to be stored. My expectation is that the Leader operates as effectively a query oracle, but that the Collector be responsible for the direction and order of queries. The Leader will be responsible for storing the reports and whatever memoized information is necessary between queries (e.g., suppose for query A you need to verify X, Y, Z and then for B you need to verify X, Y, W, the Leader could memoize X and Y.). Some of this can be soft state and the Leader can recompute as needed. The Collector is responsible for storing its own context and for knowing what's already been asked and what is to be asked next. The Helpers are stateless. Note that this doesn't address the question of any limits on what queries can be made (e.g., for DP). Those have to be enforced at the Leader.
Two quick comments: Yes I think we want the computational model to be robust enough to handle dynamic # of rounds, although maybe it is sufficient to just have the "stopping point" of the protocol be dynamic to fail early when there is no point further expanding the domain. To ekr's point on helpers being stateless and what queries can be made (e.g., for DP). Those have to be enforced at the Leader I wanted to point out that this choice affects the security guarantees of DP since we wouldn't be ensuring DP for repeated queries / data-stuffing in the 2-pc model but by a single actor (the leader). Within the MPC system we'd only be guaranteeing per-query DP. cc NAME who is starting to look at these problems on our side as well.
Uh, not sure how that got closed! Re-opening.
Hi folks, PR tries to specify data collection in a way that satisfies the requirements enumerated here. Also, I believe this
As discussed on the 2021/7/14 call, all that's left for this issue is to enumerate the design constraints discussed here in the spec. (NAME will file a PR.)
enumerates the design constraints considered here. It's time to close out this sucker.
We assume that all aggregators process the share of a client's input, but what happens if one aggregator loses its share of the client input? How do we accommodate one aggregator losing its view of a share, either maliciously or accidentally?
consider two settings for ZKP systems on distributed data: one in which either the client or a subset of aggregators is malicious (this is the setting in which Prio was analyzed); and another in which the client may collude with a subset of aggregators. This issue corresponds to the latter setting. Security in this setting is addressed in Section 6.3. The basic idea seems to be to run the input-validation protocol with every subset of servers.
We addressed this problem in Prio v2 by having each aggregator evaluate proofs independently. So during intake, when an aggregator receives a data share, it extracts the proof share and transmits it to the other aggregator. At aggregation time, an aggregator will not include a data share in a sum unless it can assemble the triple of (data share, own proof share, peer proof share) and verify the proof. If it can't do that because the peer proof is unavailable, the aggregator drops the share from the sum, but still sums everything else. In our design, the leader could be responsible for this. Clients would send encrypted data shares and proof shares to aggregators, and aggregators would then have to extract the proof share and send it to the leader. The leader could then work out the intersection of the sets of proof shares provided by all aggregators (which is the set of data that can be summed in the aggregation) and then only instruct aggregators to sum that set of shares.
Hmm, having all of the proof shares is a potential privacy issue, no? If I have all of the proof shares, then I can assemble the entire proof polynomial, which gives me the output of each intermediate G-gate evaluation and not just the final output. (Perhaps you mean "verification share"? See URL) This sounds like a good idea.
As of 2021/7/14, our answer is as follows: all aggregators need to be online for the duration of the protocol. In order to recover in case an aggregator drops, the collector could spin up multiple tasks, each with different sets of aggregators. We briefly considered formalizing this in the protocol, but decided this was too complex. (See .) Another protocol-level option is, potentially, threshold secret sharing ().
LGTM but I suggest modifying the PR description so that GitHub doesn't when this gets merged.

Malformed requests are handled as described in pa-error-common- aborts. <ins> [TODO: Since we're running the protocol with multiple cohorts of aggregators, the collector needs to decide how to pick which cohort has the "correct" output.  This might be the cohort with the largest batch of inputs.  Figure this out once the collection is specified.] </ins> 3.4. [TODO: Add an illustration of this sub-protocol.]


a particular button, a client could report clicks when none occurred. <del> Input: The original data emitted by a client, before any encryption or secret sharing scheme is applied.  This may include multiple measurements. </del>  Input share: one of the shares output by feeding an input into a secret sharing scheme.  Each share is to be transmitted to one of fails.  For example, if the input is meant to be a bit vectors, then "[2, 1, 0]" is invalid.  Leader: A distinguished aggregator that coordinates input validation and data collection. Proof share: A share of a proof, used by an aggregator during the input-validation protocol. <del> Measurement: A single value (e.g., a count) being reported by a client.  Multiple measurements may be grouped into a single protocol input. </del>  2.

The design document discusses "reports" which are a group of measurements being submitted to a DPA instantiation. There should be an entry in the "Terminology" section that explicitly defines a report and how it relates to "measurements" and "inputs".
Thanks for putting the list back in alphabetical order, too!

a particular button, a client could report clicks when none occurred.  <ins> Input: The measurement (or measurements) emitted by a client, before any encryption or secret sharing scheme is applied. </ins> Input share: one of the shares output by feeding an input into a secret sharing scheme.  Each share is to be transmitted to one of fails.  For example, if the input is meant to be a bit vectors, then "[2, 1, 0]" is invalid. <ins> Measurement: A single value (e.g., a count) being reported by a client.  Multiple measurements may be grouped into a single protocol input. </ins> Leader: A distinguished aggregator that coordinates input validation and data collection. Proof share: A share of a proof, used by an aggregator during the input-validation protocol.  <ins> Report: Uploaded to the leader from the client.  A report contains the secret-shared and encrypted input and proof. </ins> 2.


Let x be an element of K^n for some n.  Suppose we split x into {x} by choosing {x:1}, ..., {x:s-1} at random and letting {x:s} = x - ({x:1} + ... + {x:s-1}).  We could instead choose s-1 random seeds <del> k[s-1], ..., k[s-1] for a pseudorandom number generator PRG and let {x:i} = PRG(k[i], n) for each i.  This effectively "compresses" s-1 </del>  of the shares to O(1) space.  [[OPEN ISSUE:Move this elsewhere or something.]]] To generate an aggregator's share, the client runs (c[i], k[i]) := Encaps(pk[i]) and sends c[i] to the aggregator.  To compute its share, the aggregator would run k[i] := Decaps(sk[i], c[i]) and <del> compute its share as {x:i} = PRG(k[i], n). </del>  HPKE [3] is a natural candidate for instantiating the KEM.  In "Export-Only" mode, HPKE provides an efficient scheme with all the 4.3.3. <del> A suitable PRG will have the following syntax.  Fix a finite field K: </del>  <del> x := PRG(k, n) denotes generation of a vector of n elements of K. </del>  <del> This can be instantiated using a standard stream cipher, e.g.., ChaCha20 as follows.  Interpret k as the cipher key, and using a fixed nonce, generate l*n bytes of output, where l is the number of bytes needed to encode an element of K, then map each chunk of l bytes to an element of K by interpreting the chunk as an l-byte integer and reducing it modulo the prime modulus. [[OPEN ISSUE: Mapping the output of PRG(.,.) to a vector over K induces a small amount of bias on the output.  How much bias is induced depends on the how close the prime is to a power of 2. Should this be a criterion for selecting the prime?]] </del>  5.

I strongly recommend against using a biased PRG (even though the bias is small). There are two easy way to solve this: Rejection sampling: Accept the -byte integer if it is strictly smaller than the prime. To increase the probability of accepting a number, you may want to zero the most significant bits (e.g., if the prime is in (2^125, 2^126), zero the two most significant bits of your 16-byte randomness, then do rejection sampling). This is the approach currently followed in and . The distribution is exactly the uniform distribution modulo the prime. Flooding/Extra Bits: Reduce a -byte integer modulo the prime, which gives a distribution close to the uniform distribution modulo the prime when l' is large enough. In practice however, we may want to set , which would be quite wasteful. Both approaches are recommended for example when generating keys for EC cryptography, see Sections B.4.1 and B.4.2 of .
My vote is for generating more bits than are necessary, where the amount of extra bits is a function of the system security parameter. The draft captures this quite nicely, I think. (Okay, I'm biased.) Somewhat of a bikeshed, but: is generating 8 extra bytes of randomness really a big deal?
Good question. I actually don't think it's a bikeshed, because the main difference with hash-to-curve and EC key generation is that, in Prio, there are > # bins random values to generate, per user, and the extra bits needs to be generated for every random value. For example, if p is a 32-bit prime, means that you are generating 12 bytes to obtain one 32-bit random integer via a modular reduction of a 96-bit integer. That's 3 times the amount of bytes used to represent the result and the code needs to perform modular arithmetic with numbers that do not fit in a (but fits in a ). With rejection sampling instead, in expectation you generate less than 2*4 = 8 bytes, and you only need to perform comparisons over , albeit the execution time may be varying.
Yeah, I get that it's excessively wasteful (relatively speaking). What it not clear to me is if it's actually wasteful in absolute terms. Either way, we can certainly be precise in the minimum number of extra bits needed. We should just do that!
I wrote a small to compare the performance of the two PRGs approaches above for different vector sizes and the cost of an elliptic curve Diffie-Hellman. You can run it as follows Disclaimer: It may not be the most optimized implementations I think it is valuable to keep this issue open and experiment more comprehensively before choosing the definitive approach, because: Computing the PRG is likely to become the bottleneck compared to the ECDH, henceforth it will be important to optimize it; Using rejection sampling is likely giving a 2-5x speedup compared to drawing extra bits. On my macbook air, I obtain the following timings Run on (4 X 1700 MHz CPU s) CPU Caches: L1 Data 32 KiB (x2) L1 Instruction 32 KiB (x2) L2 Unified 256 KiB (x2) L3 Unified 4096 KiB (x1) Load Average: 6.86, 6.66, 5.96
Benchmark Time CPU Iterations
BMExtra32Bits4293918721/8 438 ns 432 ns 1604290 BMExtra32Bits4293918721/64 895 ns 879 ns 794083 BMExtra32Bits4293918721/512 3236 ns 3206 ns 217997 BMExtra32Bits4293918721/4096 24580 ns 24432 ns 28410 BMExtra32Bits4293918721/32768 201733 ns 199986 ns 3485 BMExtra96Bits4293918721/8 556 ns 554 ns 1245042 BMExtra96Bits4293918721/64 916 ns 901 ns 765404 BMExtra96Bits4293918721/512 4930 ns 4899 ns 137825 BMExtra96Bits4293918721/4096 41145 ns 40808 ns 17028 BMExtra96Bits4293918721/32768 341149 ns 338716 ns 2073 BMExtra64Bits15564440312192434177/8 611 ns 574 ns 1243273 BMExtra64Bits15564440312192434177/64 916 ns 908 ns 770985 BMExtra64Bits15564440312192434177/512 5047 ns 4998 ns 136890 BMExtra64Bits15564440312192434177/4096 42041 ns 41743 ns 16057 BMExtra64Bits15564440312192434177/32768 356554 ns 349632 ns 2062 BMRejectionSampling4293918721/8 389 ns 387 ns 1772125 BMRejectionSampling4293918721/64 577 ns 574 ns 1179206 BMRejectionSampling4293918721/512 1417 ns 1400 ns 496095 BMRejectionSampling4293918721/4096 8928 ns 8879 ns 77181 BMRejectionSampling4293918721/32768 75661 ns 75260 ns 8604 BMRejectionSampling15564440312192434177/8 497 ns 491 ns 1390710 BMRejectionSampling15564440312192434177/64 806 ns 800 ns 826046 BMRejectionSampling15564440312192434177/512 2780 ns 2750 ns 255927 BMRejectionSampling15564440312192434177/4096 21339 ns 21210 ns 32438 BMRejectionSampling15564440312192434177/32768 174767 ns 173535 ns 4005 BMPrg/8 121 ns 120 ns 5771625 BMPrg/64 140 ns 128 ns 5836641 BMPrg/512 212 ns 211 ns 3297873 BMPrg/4096 1609 ns 1595 ns 434891 BMPrg/32768 12901 ns 12790 ns 54085 BMECDH 298 ns 286 ns 2516772 In particular, I looked at where is the number of extra bits, and looked at two moduli from the documentation, of respectively 32 and 64 bits. The value after the is the size of the output vector.
Additionally, I would like to point out that the analysis in the hash-to-curve draft is for generating a single random value. If the approach of extra bits is chosen, when generating random values, the statistical distance also depends on , and henceforth more than bits should be used for a statistical distance of for the whole vector. (In which case, you may actually want to consider different methods to quantify the closeness of the distributions, such as ).
It seems like rejection sampling is clearly the better method, based on the above benchmarks. It's also fairly straightforward to implement. I would favor updating the text to require this method.
I'm working on a PR to close this issue (we'll go with rejection sampling). NAME I can't confirm all of the behavior of libprio-rs and libprio-cc that you pointed to. Specifically, neither library appears to clear the two most significant bits, as you suggested they do. Here's the pertinent code in the latter: URL Can you confirm that the bits aren't being cleared?
In libprio-rs (at least initially) and libprio-cc, the prime is hardcoded to a 32-bit prime, so you actually should not clear the most significant bits. More generally, if p is a k-bit prime in a word of l bits, you will want to clear the most significant bits. for k = 32, l=32, you want to clear 0 bits; for k = 126, l=128, you want to clear 2 bits (example I gave in the first comment).
Ah, yes. That makes sense. Thanks!

Let x be an element of K^n for some n.  Suppose we split x into {x} by choosing {x:1}, ..., {x:s-1} at random and letting {x:s} = x - ({x:1} + ... + {x:s-1}).  We could instead choose s-1 random seeds  <ins> k[s-1], ..., k[s-1] for a pseudorandom number generator PRNG and let {x:i} = PRNG(k[i], n) for each i.  This effectively "compresses" s-1 </ins> of the shares to O(1) space.  [[OPEN ISSUE:Move this elsewhere or something.]]] To generate an aggregator's share, the client runs (c[i], k[i]) := Encaps(pk[i]) and sends c[i] to the aggregator.  To compute its share, the aggregator would run k[i] := Decaps(sk[i], c[i]) and  <ins> compute its share as {x:i} = PRNG(k[i], n). </ins> HPKE [3] is a natural candidate for instantiating the KEM.  In "Export-Only" mode, HPKE provides an efficient scheme with all the 4.3.3.  <ins> A suitable PRNG will have the following syntax.  Fix a finite field K: </ins>  <ins> x := PRNG(k, n) denotes generation of a vector of n elements of K. </ins>  <ins> This can be instantiated using a standard stream cipher, e.g., AES- CTR, as follows.  Interpret the seed k as the key and IV for generating the AES-CTR key stream.  Proceed by rejection sampling, as follows.  Let m be the number of bits needed to encode an element of K.  Generate the next m bits of key stream and interpret the bytes as an integer x, clearing the most significant m - l bits, where l is the bit-length of the modulus p.  If x < p, then output x. Otherwise, generate the next m bits of key stream and try again. Repeat this process indefinitely until a suitable output is found. </ins> 5.


is a principle, 2^b-th root of unity (i.e., g^(s*2^b) = 1), where g is the generator of the multiplicative subgroup.  This fact allows us to quickly evaluate and interpolate polynomials at <del> 2^a-th roots of unity for any 1 <= a <= b.  Note that b imposes n </del>  upper bound on the size of proofs, so it should be large enough to accommodate all foreseeable use cases.  Something like b >= 20 is probably good enough.

Removes the old Prio text, which by now is out-of-date. Addresses by changing the criteria.
The current criteria: Criterion (3) doesn't seem that important to me any more. I've yet to think of a use case where we want to do inversion in a validity circuit, or for which we don't have an alternative to FLT. In any case, we already can get pretty good performance with a more standard, generic algorithm: URL In addition to dropping (3), we should modify (2) by saying that needs to be large enough to accommodate the largest proofs we would ever generate. seems like a reasonable minimum. Finally, an important criterion to add would be to pick a field size that is as close as possible to a power of 2 without being larger. This helps to minimize the cost of rejection sampling when generating random field elements (see ).
Closing this since it's no longer relevant to this document. (It will be relevant to URL, however.)

is a principle, 2^b-th root of unity (i.e., g^(s*2^b) = 1), where g is the generator of the multiplicative subgroup.  This fact allows us to quickly evaluate and interpolate polynomials at  <ins> 2^a-th roots of unity for any 1 <= a <= b.  Note that b imposes an </ins> upper bound on the size of proofs, so it should be large enough to accommodate all foreseeable use cases.  Something like b >= 20 is probably good enough.


3.3.1. <del> The client sends a POST request to "[leader]/upload_start" with the following message: The "task" field corresponds to the PA task for which a report will be generated. The leader responds to well-formed requests to "[leader]/upload_start" with status 200 and the following message: </del>  The message includes the PA parameters the client will use to generate its report.  It also includes a protocol-specific message,

In we did away with the requirement for interaction between the client and leader. This PR resolves the issue by making the upload start request an idempotent GET.
Based on this week's design call (2021/7/7), "upload start" is likely going away altogether.

3.3.1.  <ins> The client sends a GET request to "[leader]/[version]/[task_id]/upload_start", where "[version] == PATask.version" and "[task_id] == PATask.id".  [TODO: Decide if adding the protocol version and task id to the URL is the right way to go.  Instead, we may want to make this a POST with a body that specifies the PATask.  (See issue#61.)] The leader respond with status 200 and the following message: </ins> The message includes the PA parameters the client will use to generate its report.  It also includes a protocol-specific message,


response.  The content type of each request is "application/octet- stream".  We assume that some transport layer security protocol (e.g., TLS or QUIC) is used between each pair of parties and that the <del> server is always authenticated. </del>  [TODO: Decide how to provide mutual authentication in leader-to- helper and collector-to-leader connections.  One option is to use 3.1.1. Each PA protocol is associated with a _PA task_ that specifies the <del> measurements that are to be collected: </del>  <del> The first field, "version" specifies the version of this document. The second field, "id", is an opaque identifier used by the clients, aggregators, and collector to uniquely identify the PA task at hand. We will call it the _task id_ in the remainder. [TODO: Decide how the "PATask" is configured.  Eventually this will be distributed, in an authenticated manner, from the collector to the other parties.  For now, we just assume this value is negotiated out- of-band.] 3.1.2. Associated to each task are the _PA Parameters_ encoded by the "PAParam" structure: "task": The PA task. "helper_url": The helpers endpoint URL. </del>  "collector_config": The HPKE configuration of the collector (described in hpke-config).  [OPEN ISSUE: Maybe the collector's "proto": The PA protocol, e.g., Prio or Hits.  The rest of the structure contains the protocol specific parameters. <del> [TODO: Decide how the "PAParam" is configured.  Like the task, this will need to be authenticated by the collector.] </del>  <del> [OPEN ISSUE: Is there just one PAParam for each PATask, or do we want to allow PAParam to change overtime?] </del>  <del> 3.1.3. </del>  Our protocol uses HPKE for public-key encryption I-D.irtf-cfrg-hpke. Each aggregator specifies the HPKE public key that clients use to 3.3.1. The client sends a GET request to <del> "[leader]/[version]/[task_id]/upload_start", where "[version] == PATask.version" and "[task_id] == PATask.id".  [TODO: Decide if adding the protocol version and task id to the URL is the right way to go.  Instead, we may want to make this a POST with a body that specifies the PATask.  (See issue#61.)] The leader respond with status 200 and the following message: </del>  The message includes the PA parameters the client will use to generate its report.  It also includes a protocol-specific message, either key config specifies a KEM, KDF, or AEAD algorithm the client doesn't recognize. <del> [OPEN ISSUE: Should the request URL encode the PA task?  This would </del>  be necessary if we make "upload_start" an idempotent GET per issue#48.] The payload is structured as follows: We sometimes refer to this message as the _report_. The message <del> contains the "task" fields of the previous request.  It also includes the time (in seconds since the beginning of UNIX time) at which the </del>  report was generated.  The rest of the message consists of the encrypted input shares, each of which has the following structure: The helper handles well-formed requests as follows.  (As usual, malformed requests are handled as described in pa-error-common- aborts.)  It first looks for the PA parameters "PAParam" for which <del> "PAAggregateReq.task.id == PAParam.task.id".  Next, it looks up the HPKE config and corresponding secret key associated with "PAAggregateReq.helper_hpke_config_id".  If not found, then it aborts and alerts the leader with "unrecognized key config".  [NOTE: In this situation, the leader has no choice but to abort.  This falls into the class of error scenarios that are addressable by running with multiple helpers.] </del>  [TODO: Don't require all sub-requests to pertain to the same HPKE config.] malformed payload, then the receiver aborts and alerts the peer with "unrecognized message". <del> Each POST request to an aggregator contains a "PATask".  If the </del>  aggregator does not recognize the task, i.e., it can't find a <del> "PAParam" for which "PATask.id == PAParam.task.id", then it aborts and alerts the peer with "unrecognized task". </del>  4.

by removing the field from each of the protocol messages. Partially addresses by establishing that the task id is derived from the PA parameters for a task. We still need to specify this procedure.
On the 2021/7/7 design call we decided that the ID should be derived deterministically from the serialized PAParam by, for example, hashing it. This solves a few problems simultaneously. This settles the question of whether there should be 1:1 correspondence between a task ID and the parameters used for that task. NAME brought up an interesting question, which is how to make the task ID unique when two different data collection tasks use the same parameters. Thoughts?
The consensus here is to do SHA-256 over the PDAParams structure and use that as the 32 byte PDATaskID. However, hashing a protocol message requires us to decide on a canonical representation of a protocol message, which I think forces us to decide how to encode messages on the wire. I filed for that question. I'll proceed with a PR for this issue on the assumption that we'll pick JSON.
As I mentioned there, the assumption would be that we're adopting TLS' binary encoding. If so, in TLS notation, you would write as the hash of , which itself is a serialized data structure.
All we require is that each PAParam have some unique value, right? A random nonce would probably suffice. r.e. the encoding, yes, let's stick with TLS's wire format as NAME suggests.
Yes, my thinking was that we would just stick 16 bytes of random data in (a UUID's worth). I agree that we should stick with TLS binary encoding.
This seems unnecessary.
The PATask is always the first bit of a protocol message, and the version is the first bit of the PATask. I figured it would be helpful to version each message because messages are carried by HTTP requests. There might be a simpler/better way to do this.
I don't think we should assume that's the case. If we want to have the version not determined at the URL level (a mistake, IMO) then we should have some sort of message wrapper that contains that, rather than overloading PATask.
I'm inclined to agree with NAME we're discussing versioning the whole API in , so PATask doesn't need to also provide version information. So, setting aside the question of message format versions, is there any value in maintaining a version on PATasks at the protocol level? I say no, because we want to be able to treat different PATasks completely independently and don't otherwise have any notion of a relationship between any two PATasks in the protocol.

response.  The content type of each request is "application/octet- stream".  We assume that some transport layer security protocol (e.g., TLS or QUIC) is used between each pair of parties and that the  <ins> server is authenticated. </ins> [TODO: Decide how to provide mutual authentication in leader-to- helper and collector-to-leader connections.  One option is to use 3.1.1. Each PA protocol is associated with a _PA task_ that specifies the  <ins> measurements that are to be collected.  Associated to each task is a set of _PA Parameters_, encoded by the following "PAParam" structure, which specify the protocol used to verify and aggregate the clients' measurements: </ins>  <ins> "helper_url": The helper's endpoint URL. </ins> "collector_config": The HPKE configuration of the collector (described in hpke-config).  [OPEN ISSUE: Maybe the collector's "proto": The PA protocol, e.g., Prio or Hits.  The rest of the structure contains the protocol specific parameters.  <ins> Each task has a unique _task id_ derived from the PA parameters: </ins>  <ins> The task id is derived using the following procedure.  [TODO: Specify derivation of the task ID.] </ins>  <ins> 3.1.2. </ins> Our protocol uses HPKE for public-key encryption I-D.irtf-cfrg-hpke. Each aggregator specifies the HPKE public key that clients use to 3.3.1. The client sends a GET request to  <ins> "[leader]/[version]/[task_id]/upload_start", where "[task_id] == PATaskID".  [TODO: Decide if adding the protocol version and task id to the URL is the right way to go.  Instead, we may want to make this a POST with a body that specifies the task id.  (See issue#61.)] The leader respond with status 200 and the following message: </ins> The message includes the PA parameters the client will use to generate its report.  It also includes a protocol-specific message, either key config specifies a KEM, KDF, or AEAD algorithm the client doesn't recognize.  <ins> [OPEN ISSUE: Should the request URL encode the task id?  This would </ins> be necessary if we make "upload_start" an idempotent GET per issue#48.] The payload is structured as follows: We sometimes refer to this message as the _report_. The message  <ins> contains the "task_id" of the previous request.  It also includes the time (in seconds since the beginning of UNIX time) at which the </ins> report was generated.  The rest of the message consists of the encrypted input shares, each of which has the following structure: The helper handles well-formed requests as follows.  (As usual, malformed requests are handled as described in pa-error-common- aborts.)  It first looks for the PA parameters "PAParam" for which  <ins> "PAAggregateReq.task_id" is equal to the task id derived from "PAParam".  Next, it looks up the HPKE config and corresponding secret key associated with "PAAggregateReq.helper_hpke_config_id". If not found, then it aborts and alerts the leader with "unrecognized key config".  [NOTE: In this situation, the leader has no choice but to abort.  This falls into the class of error scenarios that are addressable by running with multiple helpers.] </ins> [TODO: Don't require all sub-requests to pertain to the same HPKE config.] malformed payload, then the receiver aborts and alerts the peer with "unrecognized message".  <ins> Each POST request to an aggregator contains a "PATaskID".  If the </ins> aggregator does not recognize the task, i.e., it can't find a  <ins> "PAParam" for which the derived task id matches the "PATaskID", then it aborts and alerts the peer with "unrecognized task". </ins> 4.


We sometimes refer to this message as the _report_. The message contains the "task_id" of the previous request.  It also includes the time (in seconds since the beginning of UNIX time) at which the <del> report was generated.  The rest of the message consists of the encrypted input shares, each of which has the following structure: </del>  "config_id" is equal to "HpkeConfig.id", where "HpkeConfig" is the aggregator's key config. The helper handles each sub-request "PAAggregateSubReq" as follows. It first checks that checks that "PAAggregateSubReq.proto == PAParam.proto".  If not, it aborts and alerts the leader with <del> "incorrect protocol for sub-request".  Otherwise, It computes the </del>  HPKE context as where "sk" is its HPKE key and computes the body of the

Partially addresses via clarification. Intra-batch replay attacks need more text.
NAME I was going to file a separate issue to discuss it. Stay tuned!
Currently, clients specify the report timestamp when uploading a report for a task. Presumably this timestamp is used to determine the window/batch in which a report is aggregated. Do we care if clients can spoof or change this timestamp to put reports into different batches? Since clients have no understanding of what batch their report goes into, and this is something that aggregators manage, perhaps we should drop this and let the aggregators figure out the report<>batch grouping?
Yes, that's the idea. We have no way of ensuring that the client doesn't lie about the timestamp. This "attack" is akin to the client just lying about the input itself (see ): It's something we can't do anything about, short of using a secure enclave on the client's machine. I think dealing with these problems is orthogonal to the spec here. On the other hand, it should our goal to allow an honest aggregator to ensure that each report appears in most one batch. The timestamp and batch window is intended to allow an honest aggregator to manage this reportbatch mapping efficiently, i.e., with minimal state between aggregate requests. Unless we can think of a better way, I think we should keep this in.
This issue is now subsumed by .
Might be worth adding an OPEN ISSUE to note the inter-batch replay attack we talked about yesterday.

We sometimes refer to this message as the _report_. The message contains the "task_id" of the previous request.  It also includes the time (in seconds since the beginning of UNIX time) at which the  <ins> report was generated.  This field is present to ensure that each report is included in at most one batch.  The rest of the message consists of the encrypted input shares, each of which has the following structure: </ins> "config_id" is equal to "HpkeConfig.id", where "HpkeConfig" is the aggregator's key config. The helper handles each sub-request "PAAggregateSubReq" as follows. It first checks that checks that "PAAggregateSubReq.proto == PAParam.proto".  If not, it aborts and alerts the leader with  <ins> "incorrect protocol for sub-request".  Otherwise, it computes the </ins> HPKE context as where "sk" is its HPKE key and computes the body of the


We sometimes refer to this message as the _report_. The message contains the "task_id" of the previous request.  It also includes the <del> time (in seconds since the beginning of UNIX time) at which the report was generated.  This field is present to ensure that each report is included in at most one batch.  The rest of the message consists of the encrypted input shares, each of which has the following structure: </del>  "config_id" is equal to "HpkeConfig.id", where "HpkeConfig" is the aggregator's key config.

Introduces and definitions, principally to make it more obvious that is a number of seconds.
Yeah, this is a nice improvement. I'd keep Time and Duration colocated so we can more clearly define in terms of the other.Good idea!

We sometimes refer to this message as the _report_. The message contains the "task_id" of the previous request.  It also includes the  <ins> time at which the report was generated.  This field is present to ensure that each report is included in at most one batch.  The rest of the message consists of the encrypted input shares, each of which has the following structure: </ins> "config_id" is equal to "HpkeConfig.id", where "HpkeConfig" is the aggregator's key config.


Each task has a unique _task ID_ derived from the PDA parameters: <del> The task ID of a "PDAParam" `is derived using the following procedure: </del>  Where "SHA-256" is as specified in FIPS180-4. "[leader]" is the leader's endpoint URL.  The payload is structured as follows: <del> We sometimes refer to this message as the _report_. The message contains the "task_id" of the previous request.  It also includes the time at which the report was generated.  This field is present to ensure that each report is included in at most one batch.  The rest of the message consists of the encrypted input shares, each of which has the following structure: </del>  "config_id" is equal to "HpkeConfig.id", where "HpkeConfig" is the aggregator's key config. encrypt an input share, the client first generates an HPKE I-D.irtf- cfrg-hpke context for the aggregator by running <del> where "pk" is the aggregator's public key. "enc" is the encapsulate HPKE context and "context" is the HPKE context used by the client for encryption.  The payload is encrypted as </del>  <del> where "input_share" is the aggregator's input share. [TODO: Fully specify encryption of the shares.  We need to make sure we authenticate things like the PDA parameters and the report timestamp.  We need to decide what the info string for SetupBaseS() will be, as well as the aad for context.Seal().  The aad might be the entire "transcript" between the client and aggregator.] </del>  The leader responds to well-formed requests to "[leader]/upload" with status 200 and an empty body.  Malformed requests are handled as corresponding to a unique client report.  Sub-requests are structured as follows: <del> The "helper_share" field is the helper's encrypted input share as it appeared in the report uploaded by the client.  [OPEN ISSUE: We usually only need to send this in the first aggregate request.  Shall we exclude it in subsequent requests somehow?]  The "time" field should match the timestamp of the corresponding report.  The </del>  remainder of the structure is dedicated to the protocol-specific <del> helper share and request parameters used for the current round. </del>  The helper handles well-formed requests as follows.  (As usual, malformed requests are handled as described in pa-error-common- aborts.)  It first looks for the PDA parameters "PDAParam" for which "PDAAggregateReq.task_id" is equal to the task ID derived from <del> "PDAParam". </del>  The response consists of the helper's updated state and a sequence of _sub-responses_, where the i-th sub-response corresponds to the sub- Next, it attempts to decrypt the payload with the following procedure: <del> where "sk" is the HPKE secret key.  If decryption fails, then the sub-response consists of a "decryption error" alert.  [See issue#57.] Otherwise, the helper handles the request for its plaintext input share "input_share" and updates its state as specified by the PDA protocol. </del>  After processing all of the sub-requests, the helper encrypts its updated state and constructs its response to the aggregate request. 3.4.2.1.1.  The helper state is an optional parameter of an aggregate request that the can helper use to carry state across requests.  At least part of the state will usually need to be encrypted in order to cloud vendor would be able to reassemble all the input shares and defeat privacy.  8.7. 8.7.1.

For privacy reasons, leaders should not be allowed to replay a given report more than once within the context of a single batch. The current verify+collect model runs in a "streaming mode," wherein the leader sends slices of batch shares to helpers for verification and aggregation. If we changed this to require the leader to send all batch shares in a single verify+collect flow, then replay prevention would be simpler: just scan the list and check for dupes. If we stick with the current model, then the helper must necessarily store some state (on the leader, most likely) representing the shares processed. This state could then be consulted when asked to verify+collect any new share. (We can probably spell out a number of ways to do store this state, but ideally it should not be linear in the number of shares collected so far.) We'd likely also need a way for the leader to request that the helper produce the final aggregate output once the batch is "done" being verified and collected.
One downside to this approach is that it requires the leader to queue up the entire batch of reports before sending the first aggregate request to the helper. This may be OK for some deployments, but it might not be workable for all. I think this is worth pursuing. This is already in the protocol. (See "output share request".)
We could require that the leader feed the helper reports with monotonically increasing timestamps, such that the helper can simply maintain a single timestamp and reject any report older than that. However I'm not sure if that's practical given that clients may submit reports out of order.
One intermediate option would be to maintain a jitter buffer on the leader, so that they keep reports for a few minutes and then feed them out in order. You would still lose some reports, but not too many.
I have a few questions but nothing blocking.

Each task has a unique _task ID_ derived from the PDA parameters:  <ins> The task ID of a "PDAParam" is derived using the following procedure: </ins> Where "SHA-256" is as specified in FIPS180-4. "[leader]" is the leader's endpoint URL.  The payload is structured as follows:  <ins> This message is called the client's _report_. It contains the following fields: "task_id" is the task ID of the task for which the report is intended. "time" is the time at which the report was generated.  This field is used by the aggregators to ensure the report appears in at most one batch.  (See anti-replay.) "jitter" is a random number chosen by the client generating the report.  This and the timestamp field are used by the aggregators to ensure that each report appears at most once in a batch.  (See anti-replay.) "encrypted_input_shares" contains the encrypted input shares of each of the aggregators. Encrypted input shares are structured as follows: </ins> "config_id" is equal to "HpkeConfig.id", where "HpkeConfig" is the aggregator's key config. encrypt an input share, the client first generates an HPKE I-D.irtf- cfrg-hpke context for the aggregator by running  <ins> where "pk" is the aggregator's public key and "server_role" is a byte whose value is "0x01" if the aggregator is the leader and "0x00" if the aggregator is the helper. "enc" is the encapsulate HPKE context and "context" is the HPKE context used by the client for encryption. The payload is encrypted as </ins>  <ins> where "input_share" is the aggregator's input share and "task_id", "time", and "jitter" are the fields of "PDAReport". </ins> The leader responds to well-formed requests to "[leader]/upload" with status 200 and an empty body.  Malformed requests are handled as corresponding to a unique client report.  Sub-requests are structured as follows:  <ins> The "time" and "jitter" fields have the same value as those in the report uploaded by the client.  Similarly, the "helper_share" field is the helper's encrypted input share as it appeared in the report. [OPEN ISSUE: We usually only need to send this in the first aggregate request.  Shall we exclude it in subsequent requests somehow?]  The </ins> remainder of the structure is dedicated to the protocol-specific  <ins> request parameters. In order to provide replay protection, the leader is required to send aggregate sub-requests in ascending order, where the ordering on sub- requests is determined by the algorithm defined in anti-replay. Specifically, the leader constructs its request such that: * each sub-request follows the previous sub-request; and * the first sub- request follows the last sub-request in the previous aggregate request. </ins> The helper handles well-formed requests as follows.  (As usual, malformed requests are handled as described in pa-error-common- aborts.)  It first looks for the PDA parameters "PDAParam" for which "PDAAggregateReq.task_id" is equal to the task ID derived from  <ins> "PDAParam".  It then filters out out-of-order sub-requests by ignoring any sub-request that does not follow the previous one (See anti-replay.) </ins> The response consists of the helper's updated state and a sequence of _sub-responses_, where the i-th sub-response corresponds to the sub- Next, it attempts to decrypt the payload with the following procedure:  <ins> where "sk" is the HPKE secret key and "server_role" is the role of the server ("0x01" for the leader and "0x00" for the helper).  If decryption fails, then the sub-response consists of a "decryption error" alert.  [See issue#57.]  Otherwise, the helper handles the request for its plaintext input share "input_share" and updates its state as specified by the PDA protocol. </ins> After processing all of the sub-requests, the helper encrypts its updated state and constructs its response to the aggregate request. 3.4.2.1.1. <ins> The leader is required to issue aggregate requests in order, but reports are likely to arrive out-of-order.  The leader SHOULD buffer reports for a time period proportional to the batch window before issuing the first aggregate request.  Failure to do so will result in out-of-order reports being dropped by the helper. 3.4.2.1.2. </ins> The helper state is an optional parameter of an aggregate request that the can helper use to carry state across requests.  At least part of the state will usually need to be encrypted in order to cloud vendor would be able to reassemble all the input shares and defeat privacy. <ins> 8.6.1. Using a report in multiple batches, or multiple times within a single batch, is considered a privacy violation, since it may leak more information about that report than intended by the PDA protocol.  For example, this may violate differential privacy.  To mitigate this issue, the core specification imposes an ordering on reports so that aggregators can cheaply prevent replays attacks. Aggregate requests are ordered as follows: We say that a report "R2" _follows_ report "R1" if either "R2.time > R1.time" or "R2.time == R1.time" and "R2.jitter > R1.jitter".  If "R2.time < R1.time", or "R2.time == R1.time" but "R2.jitter <= R1.jitter", then we say that "R2" _does not follow_ "R1". To prevent replay attacks, it suffices for each aggregator to ensure that each report it consumes follows the previous one it consumed. To prevent the adversary from tampering with the ordering of reports, honest clients incorporate the ordering-sensitive parameters "(time, jitter)" into the AAD for HPKE encryption. Note that this strategy may result in dropping reports that happen to have the same timestamp and jitter value. </ins> 8.7. 8.7.1.


the aggregators is malicious.  Differential privacy is a strong privacy definition, and protects users in extreme circumstances: Even if an adversary has prior knowledge of every input in a batch except <del> for one, that one record is still protected. </del>  8.6.

Differential privacy can protect against attacks where a portion of the input data is known a priori, or when the size of a batch is small. This PR adds DP as an optional mitigation for PDA deployments and lists where it helps mitigate various threats. Additionally: Removes a false statement that revealing user input does not compromise other users' privacy. This is not necessarily the case (e.g. imagine if the batch size is 2 or if many users reveal their input). Removes a statement that clients revealing their inputs is outside the threat model (is this still relevant?)

the aggregators is malicious.  Differential privacy is a strong privacy definition, and protects users in extreme circumstances: Even if an adversary has prior knowledge of every input in a batch except  <ins> for one, that one record is still formally protected. [OPEN ISSUE: While parameters configuring the differential privacy noise (like specific distributions / variance) can be agreed upon out of band by the aggregators and collector, there may be benefits to adding explicit protocol support by encoding them into "PDAParams".] </ins> 8.6.