Original text:
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.

Reviewer comments:
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.

Revised text:
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.