1 /*
2  * Copyright 2019 Google LLC.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     https://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "private_join_and_compute/crypto/pedersen_over_zn.h"
17 
18 #include <algorithm>
19 #include <memory>
20 #include <string>
21 #include <utility>
22 #include <vector>
23 
24 #include "absl/strings/str_cat.h"
25 #include "private_join_and_compute/crypto/big_num.h"
26 #include "private_join_and_compute/crypto/proto/big_num.pb.h"
27 #include "private_join_and_compute/crypto/proto/pedersen.pb.h"
28 #include "private_join_and_compute/crypto/proto/proto_util.h"
29 #include "private_join_and_compute/util/status.inc"
30 
31 namespace private_join_and_compute {
32 
PedersenOverZn(Context * ctx,std::vector<BigNum> gs,const BigNum & h,const BigNum & n,std::unique_ptr<SimultaneousFixedBasesExp<ZnElement,ZnContext>> simultaneous_fixed_bases_exp)33 PedersenOverZn::PedersenOverZn(
34     Context* ctx, std::vector<BigNum> gs, const BigNum& h, const BigNum& n,
35     std::unique_ptr<SimultaneousFixedBasesExp<ZnElement, ZnContext>>
36         simultaneous_fixed_bases_exp)
37     : ctx_(ctx),
38       gs_(std::move(gs)),
39       h_(h),
40       n_(n),
41       simultaneous_fixed_bases_exp_(std::move(simultaneous_fixed_bases_exp)) {}
42 
Create(Context * ctx,std::vector<BigNum> gs,const BigNum & h,const BigNum & n,size_t num_simultaneous_exponentiations)43 StatusOr<std::unique_ptr<PedersenOverZn>> PedersenOverZn::Create(
44     Context* ctx, std::vector<BigNum> gs, const BigNum& h, const BigNum& n,
45     size_t num_simultaneous_exponentiations) {
46   // The set of bases is gs_, with h_ appended at the end.
47   std::vector<private_join_and_compute::BigNum> bases = gs;
48   bases.push_back(h);
49 
50   std::unique_ptr<ZnContext> zn_context(new ZnContext({n}));
51 
52   int adjusted_num_simultaneous_exponentiations =
53       std::min(bases.size(), num_simultaneous_exponentiations);
54 
55   auto simultaneous_fixed_bases_exp =
56       SimultaneousFixedBasesExp<ZnElement, ZnContext>::Create(
57           bases, ctx->One(), adjusted_num_simultaneous_exponentiations,
58           std::move(zn_context))
59           .value();
60 
61   return absl::WrapUnique(new PedersenOverZn(
62       ctx, std::move(gs), h, n, std::move(simultaneous_fixed_bases_exp)));
63 }
64 
FromProto(Context * ctx,const proto::PedersenParameters & parameters_proto,size_t num_simultaneous_exponentiations)65 StatusOr<std::unique_ptr<PedersenOverZn>> PedersenOverZn::FromProto(
66     Context* ctx, const proto::PedersenParameters& parameters_proto,
67     size_t num_simultaneous_exponentiations) {
68   ASSIGN_OR_RETURN(PedersenOverZn::Parameters parameters,
69                    PedersenOverZn::ParseParametersProto(ctx, parameters_proto));
70   return PedersenOverZn::Create(ctx, std::move(parameters.gs), parameters.h,
71                                 parameters.n, num_simultaneous_exponentiations);
72 }
73 
74 PedersenOverZn::~PedersenOverZn() = default;
75 
Commit(const std::vector<BigNum> & messages) const76 StatusOr<PedersenOverZn::CommitmentAndOpening> PedersenOverZn::Commit(
77     const std::vector<BigNum>& messages) const {
78   BigNum r = ctx_->GenerateRandLessThan(n_);
79   ASSIGN_OR_RETURN(auto commitment,
80                    PedersenOverZn::CommitWithRand(messages, r));
81   return {{std::move(commitment), std::move(r)}};
82 }
83 
CommitWithRand(const std::vector<BigNum> & messages,const BigNum & rand) const84 StatusOr<PedersenOverZn::Commitment> PedersenOverZn::CommitWithRand(
85     const std::vector<BigNum>& messages, const BigNum& rand) const {
86   if (messages.size() > gs_.size()) {
87     return InvalidArgumentError(
88         "PedersenOverZn::Commit() : too many messages provided");
89   }
90 
91   for (const auto& message : messages) {
92     if (!message.IsNonNegative()) {
93       return InvalidArgumentError(
94           "PedersenOverZn::Commit(): cannot commit to negative value.");
95     }
96   }
97   if (!rand.IsNonNegative()) {
98     return InvalidArgumentError(
99         "PedersenOverZn::CommitWithRand(): randomness must be nonnegative.");
100   }
101 
102   std::vector<BigNum> exponents = messages;
103   // Add dummy 0s if fewer messages were provided.
104   while (exponents.size() < gs_.size()) {
105     exponents.push_back(ctx_->Zero());
106   }
107   // Push back the exponent for h_.
108   exponents.push_back(rand);
109   ASSIGN_OR_RETURN(BigNum product,
110                    simultaneous_fixed_bases_exp_->SimultaneousExp(exponents));
111 
112   return std::move(product);
113 }
114 
Add(const PedersenOverZn::Commitment & com1,const PedersenOverZn::Commitment & com2) const115 PedersenOverZn::Commitment PedersenOverZn::Add(
116     const PedersenOverZn::Commitment& com1,
117     const PedersenOverZn::Commitment& com2) const {
118   return com1.ModMul(com2, n_);
119 }
120 
Multiply(const PedersenOverZn::Commitment & com,const BigNum & scalar) const121 PedersenOverZn::Commitment PedersenOverZn::Multiply(
122     const PedersenOverZn::Commitment& com, const BigNum& scalar) const {
123   return com.ModExp(scalar, n_);
124 }
125 
Verify(const PedersenOverZn::Commitment & commitment,const std::vector<BigNum> & messages,const PedersenOverZn::Opening & opening) const126 StatusOr<bool> PedersenOverZn::Verify(
127     const PedersenOverZn::Commitment& commitment,
128     const std::vector<BigNum>& messages,
129     const PedersenOverZn::Opening& opening) const {
130   if (messages.size() > gs_.size()) {
131     return InvalidArgumentError(
132         "PedersenOverZn::Verify() : too many messages provided");
133   }
134 
135   for (const auto& message : messages) {
136     if (!message.IsNonNegative()) {
137       return InvalidArgumentError(
138           "PedersenOverZn::Verify(): message in the opening is negative.");
139     }
140   }
141   if (!opening.IsNonNegative()) {
142     return InvalidArgumentError(
143         "PedersenOverZn::Verify(): randomness in the opening is negative.");
144   }
145 
146   std::vector<BigNum> exponents = messages;
147   // Add dummy 0s if fewer messages were provided.
148   while (exponents.size() < gs_.size()) {
149     exponents.push_back(ctx_->Zero());
150   }
151   // Push back the exponent for h_.
152   exponents.push_back(opening);
153   ASSIGN_OR_RETURN(BigNum product,
154                    simultaneous_fixed_bases_exp_->SimultaneousExp(exponents));
155 
156   return commitment == product;
157 }
158 
GenerateParameters(Context * ctx,const BigNum & n,int64_t num_gs)159 PedersenOverZn::Parameters PedersenOverZn::GenerateParameters(Context* ctx,
160                                                               const BigNum& n,
161                                                               int64_t num_gs) {
162   // Chooses a random quadratic residue as h = (x^2) mod n for random x. Except
163   // with probability O(1/n), this is a generator for the subgroup of order
164   // (p-1)(q-1)/4 in Z*n.
165   BigNum x = ctx->RelativelyPrimeRandomLessThan(n);
166   BigNum h = x.ModSqr(n);
167 
168   std::vector<BigNum> gs;
169   std::vector<BigNum> rs;
170   for (int i = 0; i < num_gs; i++) {
171     BigNum r =
172         ctx->GenerateRandLessThan(n.DivAndTruncate(ctx->CreateBigNum(4)));
173     gs.push_back(h.ModExp(r, n));
174     rs.push_back(std::move(r));
175   }
176   return {std::move(gs), std::move(h), n, std::move(rs)};
177 }
178 
GetGenProofChallenge(Context * ctx,const BigNum & g,const BigNum & h,const BigNum & n,const std::vector<std::unique_ptr<BigNum>> & dummy_gs,int num_repetitions)179 std::vector<uint8_t> PedersenOverZn::GetGenProofChallenge(
180     Context* ctx, const BigNum& g, const BigNum& h, const BigNum& n,
181     const std::vector<std::unique_ptr<BigNum>>& dummy_gs, int num_repetitions) {
182   std::string bytes;
183   bytes.append(g.ToBytes());
184   bytes.append(h.ToBytes());
185   bytes.append(n.ToBytes());
186   for (auto& dummy_g : dummy_gs) {
187     bytes.append(dummy_g->ToBytes());
188   }
189 
190   // Generates a single combined challenge, and then derive the individual
191   // challenges by breaking down the combined challenge into its individual
192   // bits.
193   BigNum combined_challenge =
194       ctx->RandomOracleSha512(bytes, ctx->One().Lshift(num_repetitions));
195 
196   std::vector<uint8_t> challenges;
197   for (int i = 0; i < num_repetitions; i++) {
198     uint8_t challenge = combined_challenge.IsBitSet(0);
199     challenges.push_back(challenge);
200     combined_challenge = combined_challenge.Rshift(1);
201   }
202 
203   return challenges;
204 }
205 
206 StatusOr<PedersenOverZn::ProofOfGen>
ProveParametersCorrectlyGenerated(Context * ctx,const BigNum & g,const BigNum & h,const BigNum & n,const BigNum & r,int num_repetitions,int zk_quality)207 PedersenOverZn::ProveParametersCorrectlyGenerated(
208     Context* ctx, const BigNum& g, const BigNum& h, const BigNum& n,
209     const BigNum& r, int num_repetitions, int zk_quality) {
210   if (num_repetitions <= 0) {
211     return InvalidArgumentError(
212         "PedersenOverZn::ProveParametersCorrectlyGenerated :: number of "
213         "repetitions "
214         "must be positive.");
215   }
216   if (zk_quality <= 0) {
217     return InvalidArgumentError(
218         "PedersenOverZn::ProveParametersCorrectlyGenerated :: zk_quality "
219         "parameter "
220         "must be positive.");
221   }
222   if (h.Gcd(n) != ctx->One()) {
223     return InvalidArgumentError(
224         "PedersenOverZn::ProveParametersCorrectlyGenerated :: parameters are "
225         "not "
226         "valid: h is not relatively prime to n.");
227   }
228   if (g != h.ModExp(r, n)) {
229     return InvalidArgumentError(
230         "PedersenOverZn::ProveParametersCorrectlyGenerated :: parameters are "
231         "not "
232         "valid: g != h^r mod n.");
233   }
234 
235   // Generate first prover message for each repetition of the sigma protocol.
236   std::vector<std::unique_ptr<BigNum>> dummy_rs;
237   std::vector<std::unique_ptr<BigNum>> dummy_gs;
238   for (int i = 0; i < num_repetitions; i++) {
239     std::unique_ptr<BigNum> dummy_r(
240         new BigNum(ctx->GenerateRandLessThan(n.Lshift(1 + zk_quality))));
241     std::unique_ptr<BigNum> dummy_g(new BigNum(h.ModExp(*dummy_r, n)));
242 
243     dummy_rs.push_back(std::move(dummy_r));
244     dummy_gs.push_back(std::move(dummy_g));
245   }
246 
247   // Generate boolean challenges for each repetition of the sigma protocol
248   std::vector<uint8_t> challenges =
249       GetGenProofChallenge(ctx, g, h, n, dummy_gs, num_repetitions);
250 
251   // Generate responses for each proof repetition. If the challenge for the
252   // repetition was "1", the response is dummy_r + r, otherwise, it is simply
253   // dummy_r.
254   std::vector<std::unique_ptr<BigNum>> responses;
255   for (int i = 0; i < num_repetitions; i++) {
256     std::unique_ptr<BigNum> response;
257     if (challenges[i] == 1) {
258       response = std::make_unique<BigNum>(dummy_rs[i]->Add(r));
259     } else {
260       response = std::make_unique<BigNum>(*dummy_rs[i]);
261     }
262 
263     responses.push_back(std::move(response));
264   }
265 
266   return PedersenOverZn::ProofOfGen{num_repetitions, std::move(dummy_gs),
267                                     std::move(responses)};
268 }
269 
VerifyParamsProof(Context * ctx,const BigNum & g,const BigNum & h,const BigNum & n,const PedersenOverZn::ProofOfGen & proof)270 Status PedersenOverZn::VerifyParamsProof(
271     Context* ctx, const BigNum& g, const BigNum& h, const BigNum& n,
272     const PedersenOverZn::ProofOfGen& proof) {
273   if (proof.num_repetitions <= 0) {
274     return InvalidArgumentError(
275         "PedersenOverZn::VerifyParamsProof :: proof is not valid: number of "
276         "repetitions must be positive.");
277   }
278   if (proof.dummy_gs.size() != proof.num_repetitions) {
279     return InvalidArgumentError(
280         "PedersenOverZn::VerifyParamsProof :: proof is not valid: number of "
281         "dummy_gs is different from number of repetitions specified.");
282   }
283   if (proof.responses.size() != proof.num_repetitions) {
284     return InvalidArgumentError(
285         "PedersenOverZn::VerifyParamsProof :: proof is not valid: number of "
286         "responses is different from number of repetitions specified.");
287   }
288   if (h.Gcd(n) != ctx->One()) {
289     return InvalidArgumentError(
290         "PedersenOverZn::VerifyParamsProof :: parameters are not valid, h is "
291         "not "
292         "relatively prime to n.");
293   }
294 
295   // reconstruct the challenges
296   std::vector<uint8_t> challenges = PedersenOverZn::GetGenProofChallenge(
297       ctx, g, h, n, proof.dummy_gs, proof.num_repetitions);
298 
299   // checks each response to make sure it is valid for the challenge.
300   for (int i = 0; i < proof.num_repetitions; i++) {
301     BigNum expected_output = *proof.dummy_gs[i];
302     if (challenges[i] == 1) {
303       expected_output = expected_output.ModMul(g, n);
304     }
305     if (h.ModExp(*(proof.responses[i]), n) != expected_output) {
306       return InvalidArgumentError(absl::StrCat(
307           "PedersenOverZn::VerifyParamsProof :: the proof verification formula "
308           "fails at index ",
309           i, "."));
310     }
311   }
312 
313   return OkStatus();
314 }
315 
GetTrustedGenProofChallenge(Context * ctx,const BigNum & g,const BigNum & h,const BigNum & n,const BigNum & dummy_g,int challenge_length)316 BigNum PedersenOverZn::GetTrustedGenProofChallenge(
317     Context* ctx, const BigNum& g, const BigNum& h, const BigNum& n,
318     const BigNum& dummy_g, int challenge_length) {
319   std::string bytes;
320   bytes.append(g.ToBytes());
321   bytes.append(h.ToBytes());
322   bytes.append(n.ToBytes());
323   bytes.append(dummy_g.ToBytes());
324   BigNum challenge =
325       ctx->RandomOracleSha512(bytes, ctx->One().Lshift(challenge_length));
326   return challenge;
327 }
328 
329 StatusOr<PedersenOverZn::ProofOfGenForTrustedModulus>
ProveParametersCorrectlyGeneratedForTrustedModulus(Context * ctx,const BigNum & g,const BigNum & h,const BigNum & n,const BigNum & r,int challenge_length,int zk_quality)330 PedersenOverZn::ProveParametersCorrectlyGeneratedForTrustedModulus(
331     Context* ctx, const BigNum& g, const BigNum& h, const BigNum& n,
332     const BigNum& r, int challenge_length, int zk_quality) {
333   if (challenge_length <= 0) {
334     return InvalidArgumentError(
335         "PedersenOverZn::ProveParametersCorrectlyGeneratedForTrustedModulus :: "
336         "challenge length must be positive.");
337   }
338   if (zk_quality <= 0) {
339     return InvalidArgumentError(
340         "PedersenOverZn::ProveParametersCorrectlyGeneratedForTrustedModulus :: "
341         "zk_quality parameter must be positive.");
342   }
343   if (h.Gcd(n) != ctx->One()) {
344     return InvalidArgumentError(
345         "PedersenOverZn::ProveParametersCorrectlyGeneratedForTrustedModulus :: "
346         "parameters are not valid: h is not relatively prime to n.");
347   }
348   if (g != h.ModExp(r, n)) {
349     return InvalidArgumentError(
350         "PedersenOverZn::ProveParametersCorrectlyGeneratedForTrustedModulus :: "
351         "parameters are not valid: g != h^r mod n.");
352   }
353 
354   BigNum dummy_r =
355       ctx->GenerateRandLessThan(n.Lshift(challenge_length + zk_quality));
356   BigNum dummy_g = h.ModExp(dummy_r, n);
357 
358   BigNum challenge = PedersenOverZn::GetTrustedGenProofChallenge(
359       ctx, g, h, n, dummy_g, challenge_length);
360 
361   BigNum response = dummy_r + (challenge * r);
362 
363   return {{challenge_length, std::move(dummy_g), std::move(response)}};
364 }
365 
VerifyParamsProofForTrustedModulus(Context * ctx,const BigNum & g,const BigNum & h,const BigNum & n,const PedersenOverZn::ProofOfGenForTrustedModulus & proof)366 Status PedersenOverZn::VerifyParamsProofForTrustedModulus(
367     Context* ctx, const BigNum& g, const BigNum& h, const BigNum& n,
368     const PedersenOverZn::ProofOfGenForTrustedModulus& proof) {
369   if (proof.challenge_length <= 0) {
370     return InvalidArgumentError(
371         "PedersenOverZn::VerifyParamsProofForTrustedModulus :: proof is not "
372         "valid: "
373         "challenge length must be positive.");
374   }
375   if (h.Gcd(n) != ctx->One()) {
376     return InvalidArgumentError(
377         "PedersenOverZn::VerifyParamsProofForTrustedModulus :: parameters are "
378         "not "
379         "valid, h is not relatively prime to n.");
380   }
381 
382   BigNum challenge = PedersenOverZn::GetTrustedGenProofChallenge(
383       ctx, g, h, n, proof.dummy_g, proof.challenge_length);
384 
385   // checks h^response == g^challenge * dummy_g mod n.
386   if (h.ModExp(proof.response, n) !=
387       g.ModExp(challenge, n).ModMul(proof.dummy_g, n)) {
388     return InvalidArgumentError(
389         "PedersenOverZn::VerifyParamsProofForTrustedModulus :: the proof "
390         "verification formula fails.");
391   }
392 
393   return OkStatus();
394 }
395 
ParametersToProto(const PedersenOverZn::Parameters & parameters)396 proto::PedersenParameters PedersenOverZn::ParametersToProto(
397     const PedersenOverZn::Parameters& parameters) {
398   proto::PedersenParameters parameters_proto;
399   parameters_proto.set_n(parameters.n.ToBytes());
400   *parameters_proto.mutable_gs() = BigNumVectorToProto(parameters.gs);
401   parameters_proto.set_h(parameters.h.ToBytes());
402   return parameters_proto;
403 }
404 
ParseParametersProto(Context * ctx,const proto::PedersenParameters & parameters_proto)405 StatusOr<PedersenOverZn::Parameters> PedersenOverZn::ParseParametersProto(
406     Context* ctx, const proto::PedersenParameters& parameters_proto) {
407   BigNum n = ctx->CreateBigNum(parameters_proto.n());
408   if (n <= ctx->Zero()) {
409     return absl::InvalidArgumentError(
410         "PedersenOverZn::FromProto: n must be positive.");
411   }
412   std::vector<BigNum> gs = ::private_join_and_compute::ParseBigNumVectorProto(
413       ctx, parameters_proto.gs());
414   for (const BigNum& g : gs) {
415     if (g <= ctx->Zero() || g >= n || g.Gcd(n) != ctx->One()) {
416       return absl::InvalidArgumentError(
417           "PedersenOverZn::FromProto: g must be in (0, n) and relatively prime "
418           "to n.");
419     }
420   }
421   BigNum h = ctx->CreateBigNum(parameters_proto.h());
422   if (h <= ctx->Zero() || h >= n || h.Gcd(n) != ctx->One()) {
423     return absl::InvalidArgumentError(
424         "PedersenOverZn::FromProto: h must be in (0, n) and relatively prime "
425         "to n.");
426   }
427   return PedersenOverZn::Parameters{
428       std::move(gs), std::move(h), std::move(n), {}};
429 }
430 
431 }  // namespace private_join_and_compute
432