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

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

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