1 // Copyright 2023 Google LLC
2 //
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 #include "anonymous_tokens/cpp/client/anonymous_tokens_redemption_client.h"
16 
17 #include <cstdint>
18 #include <memory>
19 #include <random>
20 #include <string>
21 #include <utility>
22 #include <vector>
23 
24 #include <gmock/gmock.h>
25 #include <gtest/gtest.h>
26 #include "anonymous_tokens/cpp/testing/utils.h"
27 #include "anonymous_tokens/proto/anonymous_tokens.pb.h"
28 
29 namespace anonymous_tokens {
30 namespace {
31 
32 using ::testing::HasSubstr;
33 
34 // Generates a random string of size n.
GetRandomString(int n,std::uniform_int_distribution<int> * distr_u8,std::mt19937_64 * generator)35 std::string GetRandomString(int n, std::uniform_int_distribution<int>* distr_u8,
36                             std::mt19937_64* generator) {
37   std::string rand(n, 0);
38   for (int i = 0; i < n; ++i) {
39     rand[i] = static_cast<uint8_t>((*distr_u8)(*generator));
40   }
41   return rand;
42 }
43 
44 // Saves redemption related public metadata and result for testing purposes for
45 // one token.
46 struct RedemptionInfoAndResult {
47   std::string plaintext_message;
48   std::string public_metadata;
49   std::string message_mask;
50   bool redeemed;
51   bool double_spent;
52 };
53 
54 // Takes as input AnonymousTokensRedemptionResponse and uses that to create a
55 // map of token to their respective RedemptionResult for testing purposes.
56 absl::flat_hash_map<std::string, RedemptionInfoAndResult>
CreateTokenToRedemptionResultMap(const AnonymousTokensRedemptionResponse & response)57 CreateTokenToRedemptionResultMap(
58     const AnonymousTokensRedemptionResponse& response) {
59   absl::flat_hash_map<std::string, RedemptionInfoAndResult> response_map;
60   for (const auto& result : response.anonymous_token_redemption_results()) {
61     response_map[result.serialized_unblinded_token()] = {
62         .plaintext_message = result.plaintext_message(),
63         .public_metadata = result.public_metadata(),
64         .message_mask = result.message_mask(),
65         .redeemed = result.verified(),
66         .double_spent = result.double_spent(),
67     };
68   }
69   return response_map;
70 }
71 
72 class AnonymousTokensRedemptionClientTest : public testing::Test {
73  protected:
SetUp()74   void SetUp() override {
75     ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
76         client_, AnonymousTokensRedemptionClient::Create(TEST_USE_CASE, 1));
77     dummy_token_with_input_ = GetRandomDummyTokenWithInput();
78     *(dummy_response_.add_anonymous_token_redemption_results()) =
79         CreateRedemptionResultForTesting(dummy_token_with_input_);
80     generator_.seed(GTEST_FLAG_GET(random_seed));
81   }
82 
83   // Generates a dummy RSABlindSignatureTokenWithInput which is not
84   // cryptographically valid as that is not needed for testing purposes.
GetRandomDummyTokenWithInput()85   RSABlindSignatureTokenWithInput GetRandomDummyTokenWithInput() {
86     PlaintextMessageWithPublicMetadata input;
87     input.set_plaintext_message(GetRandomString(20, &distr_u8_, &generator_));
88     input.set_public_metadata(GetRandomString(10, &distr_u8_, &generator_));
89     RSABlindSignatureToken token;
90     token.set_token(GetRandomString(512, &distr_u8_, &generator_));
91     token.set_message_mask(GetRandomString(32, &distr_u8_, &generator_));
92     RSABlindSignatureTokenWithInput token_with_input;
93     *token_with_input.mutable_input() = input;
94     *token_with_input.mutable_token() = token;
95     return token_with_input;
96   }
97 
98   // Creates a fake token redemption response for one
99   // RSABlindSignatureTokenWithInput and outputs it as
100   // AnonymousTokenRedemptionResult
101   AnonymousTokensRedemptionResponse_AnonymousTokenRedemptionResult
CreateRedemptionResultForTesting(RSABlindSignatureTokenWithInput token_with_input,bool verified=true,bool double_spent=false,AnonymousTokensUseCase use_case=TEST_USE_CASE,int64_t key_version=1)102   CreateRedemptionResultForTesting(
103       RSABlindSignatureTokenWithInput token_with_input, bool verified = true,
104       bool double_spent = false,
105       AnonymousTokensUseCase use_case = TEST_USE_CASE,
106       int64_t key_version = 1) {
107     AnonymousTokensRedemptionResponse_AnonymousTokenRedemptionResult result;
108     result.set_use_case(AnonymousTokensUseCase_Name(use_case));
109     result.set_key_version(key_version);
110     result.set_public_metadata(token_with_input.input().public_metadata());
111     result.set_serialized_unblinded_token(token_with_input.token().token());
112     result.set_plaintext_message(token_with_input.input().plaintext_message());
113     result.set_message_mask(token_with_input.token().message_mask());
114     result.set_verified(verified);
115     result.set_double_spent(double_spent);
116     return result;
117   }
118 
119   std::mt19937_64 generator_;
120   std::uniform_int_distribution<int> distr_u8_ =
121       std::uniform_int_distribution<int>{0, 255};
122 
123   std::unique_ptr<AnonymousTokensRedemptionClient> client_;
124   RSABlindSignatureTokenWithInput dummy_token_with_input_;
125   AnonymousTokensRedemptionResponse dummy_response_;
126 };
127 
TEST_F(AnonymousTokensRedemptionClientTest,UndefinedUseCase)128 TEST_F(AnonymousTokensRedemptionClientTest, UndefinedUseCase) {
129   // Use case undefined.
130   absl::StatusOr<std::unique_ptr<AnonymousTokensRedemptionClient>>
131       redemption_client = AnonymousTokensRedemptionClient::Create(
132           ANONYMOUS_TOKENS_USE_CASE_UNDEFINED, 1);
133   EXPECT_EQ(redemption_client.status().code(),
134             absl::StatusCode::kInvalidArgument);
135   EXPECT_THAT(redemption_client.status().message(),
136               HasSubstr("must be defined"));
137 }
138 
TEST_F(AnonymousTokensRedemptionClientTest,InvalidKeyVersions)139 TEST_F(AnonymousTokensRedemptionClientTest, InvalidKeyVersions) {
140   // Key version 0.
141   absl::StatusOr<std::unique_ptr<AnonymousTokensRedemptionClient>>
142       redemption_client_1 =
143           AnonymousTokensRedemptionClient::Create(TEST_USE_CASE, 0);
144   EXPECT_EQ(redemption_client_1.status().code(),
145             absl::StatusCode::kInvalidArgument);
146   EXPECT_THAT(redemption_client_1.status().message(),
147               HasSubstr("must be greater than 0"));
148 }
149 
TEST_F(AnonymousTokensRedemptionClientTest,EmptyRequest)150 TEST_F(AnonymousTokensRedemptionClientTest, EmptyRequest) {
151   absl::StatusOr<AnonymousTokensRedemptionRequest> redemption_request =
152       client_->CreateAnonymousTokensRedemptionRequest({});
153   EXPECT_EQ(redemption_request.status().code(),
154             absl::StatusCode::kInvalidArgument);
155   EXPECT_THAT(redemption_request.status().message(),
156               HasSubstr("empty request"));
157 }
158 
TEST_F(AnonymousTokensRedemptionClientTest,CreatingRequestAgain)159 TEST_F(AnonymousTokensRedemptionClientTest, CreatingRequestAgain) {
160   ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
161       auto _, client_->CreateAnonymousTokensRedemptionRequest(
162                   {dummy_token_with_input_}));
163   // Creating same request again with same client.
164   absl::StatusOr<AnonymousTokensRedemptionRequest> redemption_request_2 =
165       client_->CreateAnonymousTokensRedemptionRequest(
166           {dummy_token_with_input_});
167   EXPECT_EQ(redemption_request_2.status().code(),
168             absl::StatusCode::kFailedPrecondition);
169   EXPECT_THAT(redemption_request_2.status().message(),
170               HasSubstr("already created"));
171   // Creating different request with the same client.
172   absl::StatusOr<AnonymousTokensRedemptionRequest> redemption_request_3 =
173       client_->CreateAnonymousTokensRedemptionRequest(
174           {GetRandomDummyTokenWithInput()});
175   EXPECT_EQ(redemption_request_3.status().code(),
176             absl::StatusCode::kFailedPrecondition);
177   EXPECT_THAT(redemption_request_3.status().message(),
178               HasSubstr("already created"));
179 }
180 
TEST_F(AnonymousTokensRedemptionClientTest,MissingTokenInRequest)181 TEST_F(AnonymousTokensRedemptionClientTest, MissingTokenInRequest) {
182   dummy_token_with_input_.mutable_token()->clear_token();
183   absl::StatusOr<AnonymousTokensRedemptionRequest> redemption_request =
184       client_->CreateAnonymousTokensRedemptionRequest(
185           {dummy_token_with_input_});
186   EXPECT_EQ(redemption_request.status().code(),
187             absl::StatusCode::kInvalidArgument);
188   EXPECT_THAT(redemption_request.status().message(), HasSubstr("empty token"));
189 }
190 
TEST_F(AnonymousTokensRedemptionClientTest,WrongMaskSize)191 TEST_F(AnonymousTokensRedemptionClientTest, WrongMaskSize) {
192   dummy_token_with_input_.mutable_token()->set_message_mask("wrongmasksize");
193   absl::StatusOr<AnonymousTokensRedemptionRequest> redemption_request =
194       client_->CreateAnonymousTokensRedemptionRequest(
195           {dummy_token_with_input_});
196   EXPECT_EQ(redemption_request.status().code(),
197             absl::StatusCode::kInvalidArgument);
198   EXPECT_THAT(redemption_request.status().message(),
199               HasSubstr("at least 32 bytes"));
200 }
201 
TEST_F(AnonymousTokensRedemptionClientTest,RepeatedTokenInRequest)202 TEST_F(AnonymousTokensRedemptionClientTest, RepeatedTokenInRequest) {
203   absl::StatusOr<AnonymousTokensRedemptionRequest> redemption_request =
204       client_->CreateAnonymousTokensRedemptionRequest(
205           {dummy_token_with_input_, dummy_token_with_input_});
206   EXPECT_EQ(redemption_request.status().code(),
207             absl::StatusCode::kInvalidArgument);
208   EXPECT_THAT(redemption_request.status().message(),
209               HasSubstr("should not be repeated"));
210 }
211 
TEST_F(AnonymousTokensRedemptionClientTest,ProcessBeforeRequestCreation)212 TEST_F(AnonymousTokensRedemptionClientTest, ProcessBeforeRequestCreation) {
213   AnonymousTokensRedemptionResponse redemption_resp;
214   absl::StatusOr<std::vector<RSABlindSignatureRedemptionResult>>
215       redemption_result =
216           client_->ProcessAnonymousTokensRedemptionResponse(redemption_resp);
217   EXPECT_EQ(redemption_result.status().code(),
218             absl::StatusCode::kFailedPrecondition);
219   EXPECT_THAT(redemption_result.status().message(),
220               HasSubstr("request was not created"));
221 }
222 
TEST_F(AnonymousTokensRedemptionClientTest,EmptyResponseProcessing)223 TEST_F(AnonymousTokensRedemptionClientTest, EmptyResponseProcessing) {
224   ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
225       auto _, client_->CreateAnonymousTokensRedemptionRequest(
226                   {dummy_token_with_input_}));
227   AnonymousTokensRedemptionResponse redemption_resp;
228   absl::StatusOr<std::vector<RSABlindSignatureRedemptionResult>>
229       redemption_result =
230           client_->ProcessAnonymousTokensRedemptionResponse(redemption_resp);
231   EXPECT_EQ(redemption_result.status().code(),
232             absl::StatusCode::kInvalidArgument);
233   EXPECT_THAT(redemption_result.status().message(),
234               HasSubstr("empty response"));
235 }
236 
TEST_F(AnonymousTokensRedemptionClientTest,WrongSizeOfResponse)237 TEST_F(AnonymousTokensRedemptionClientTest, WrongSizeOfResponse) {
238   ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
239       auto _, client_->CreateAnonymousTokensRedemptionRequest(
240                   {dummy_token_with_input_, GetRandomDummyTokenWithInput()}));
241   absl::StatusOr<std::vector<RSABlindSignatureRedemptionResult>>
242       redemption_result =
243           client_->ProcessAnonymousTokensRedemptionResponse(dummy_response_);
244   EXPECT_EQ(redemption_result.status().code(),
245             absl::StatusCode::kInvalidArgument);
246   EXPECT_THAT(redemption_result.status().message(),
247               HasSubstr("missing some requested token redemptions"));
248 }
249 
TEST_F(AnonymousTokensRedemptionClientTest,UseCaseMismatch)250 TEST_F(AnonymousTokensRedemptionClientTest, UseCaseMismatch) {
251   ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
252       auto _, client_->CreateAnonymousTokensRedemptionRequest(
253                   {dummy_token_with_input_}));
254   dummy_response_.mutable_anonymous_token_redemption_results(0)->set_use_case(
255       AnonymousTokensUseCase_Name(TEST_USE_CASE_2));
256   absl::StatusOr<std::vector<RSABlindSignatureRedemptionResult>>
257       redemption_result =
258           client_->ProcessAnonymousTokensRedemptionResponse(dummy_response_);
259   EXPECT_EQ(redemption_result.status().code(),
260             absl::StatusCode::kInvalidArgument);
261   EXPECT_THAT(redemption_result.status().message(),
262               HasSubstr("Use case does not match"));
263 }
264 
TEST_F(AnonymousTokensRedemptionClientTest,KeyVersionMismatch)265 TEST_F(AnonymousTokensRedemptionClientTest, KeyVersionMismatch) {
266   ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
267       auto _, client_->CreateAnonymousTokensRedemptionRequest(
268                   {dummy_token_with_input_}));
269   dummy_response_.mutable_anonymous_token_redemption_results(0)
270       ->set_key_version(2);
271   absl::StatusOr<std::vector<RSABlindSignatureRedemptionResult>>
272       redemption_result =
273           client_->ProcessAnonymousTokensRedemptionResponse(dummy_response_);
274   EXPECT_EQ(redemption_result.status().code(),
275             absl::StatusCode::kInvalidArgument);
276   EXPECT_THAT(redemption_result.status().message(),
277               HasSubstr("Key version does not match"));
278 }
279 
TEST_F(AnonymousTokensRedemptionClientTest,EmptyTokenInResponse)280 TEST_F(AnonymousTokensRedemptionClientTest, EmptyTokenInResponse) {
281   ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
282       auto _, client_->CreateAnonymousTokensRedemptionRequest(
283                   {dummy_token_with_input_}));
284   dummy_response_.mutable_anonymous_token_redemption_results(0)
285       ->clear_serialized_unblinded_token();
286   absl::StatusOr<std::vector<RSABlindSignatureRedemptionResult>>
287       redemption_result =
288           client_->ProcessAnonymousTokensRedemptionResponse(dummy_response_);
289   EXPECT_EQ(redemption_result.status().code(),
290             absl::StatusCode::kInvalidArgument);
291   EXPECT_THAT(redemption_result.status().message(),
292               HasSubstr("Token cannot be empty"));
293 }
294 
TEST_F(AnonymousTokensRedemptionClientTest,MissingMaskInResponse)295 TEST_F(AnonymousTokensRedemptionClientTest, MissingMaskInResponse) {
296   ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
297       auto _, client_->CreateAnonymousTokensRedemptionRequest(
298                   {dummy_token_with_input_}));
299   dummy_response_.mutable_anonymous_token_redemption_results(0)
300       ->clear_message_mask();
301   absl::StatusOr<std::vector<RSABlindSignatureRedemptionResult>>
302       redemption_result =
303           client_->ProcessAnonymousTokensRedemptionResponse(dummy_response_);
304   EXPECT_EQ(redemption_result.status().code(),
305             absl::StatusCode::kInvalidArgument);
306   EXPECT_THAT(
307       redemption_result.status().message(),
308       HasSubstr("Response message mask does not match input message mask"));
309 }
310 
TEST_F(AnonymousTokensRedemptionClientTest,WrongMaskSizeInResponse)311 TEST_F(AnonymousTokensRedemptionClientTest, WrongMaskSizeInResponse) {
312   ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
313       auto _, client_->CreateAnonymousTokensRedemptionRequest(
314                   {dummy_token_with_input_}));
315   dummy_response_.mutable_anonymous_token_redemption_results(0)
316       ->set_message_mask(GetRandomString(31, &distr_u8_, &generator_));
317   absl::StatusOr<std::vector<RSABlindSignatureRedemptionResult>>
318       redemption_result =
319           client_->ProcessAnonymousTokensRedemptionResponse(dummy_response_);
320   EXPECT_EQ(redemption_result.status().code(),
321             absl::StatusCode::kInvalidArgument);
322   EXPECT_THAT(redemption_result.status().message(),
323               HasSubstr("at least 32 bytes"));
324 }
325 
TEST_F(AnonymousTokensRedemptionClientTest,RepeatedTokenInResponse)326 TEST_F(AnonymousTokensRedemptionClientTest, RepeatedTokenInResponse) {
327   auto another_dummy_token_with_input = GetRandomDummyTokenWithInput();
328   ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
329       auto _, client_->CreateAnonymousTokensRedemptionRequest(
330                   {dummy_token_with_input_, another_dummy_token_with_input}));
331   another_dummy_token_with_input.mutable_token()->set_token(
332       dummy_token_with_input_.token().token());
333   *(dummy_response_.add_anonymous_token_redemption_results()) =
334       CreateRedemptionResultForTesting(another_dummy_token_with_input);
335   absl::StatusOr<std::vector<RSABlindSignatureRedemptionResult>>
336       redemption_result =
337           client_->ProcessAnonymousTokensRedemptionResponse(dummy_response_);
338   EXPECT_EQ(redemption_result.status().code(),
339             absl::StatusCode::kInvalidArgument);
340   EXPECT_THAT(redemption_result.status().message(),
341               HasSubstr("Token was repeated"));
342 }
343 
TEST_F(AnonymousTokensRedemptionClientTest,NewTokenInResponse)344 TEST_F(AnonymousTokensRedemptionClientTest, NewTokenInResponse) {
345   ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
346       auto _, client_->CreateAnonymousTokensRedemptionRequest(
347                   {dummy_token_with_input_}));
348   dummy_response_.mutable_anonymous_token_redemption_results(0)
349       ->set_serialized_unblinded_token(
350           GetRandomString(512, &distr_u8_, &generator_));
351   absl::StatusOr<std::vector<RSABlindSignatureRedemptionResult>>
352       redemption_result =
353           client_->ProcessAnonymousTokensRedemptionResponse(dummy_response_);
354   EXPECT_EQ(redemption_result.status().code(),
355             absl::StatusCode::kInvalidArgument);
356   EXPECT_THAT(redemption_result.status().message(),
357               HasSubstr("tokens whose redemptions were not requested"));
358 }
359 
TEST_F(AnonymousTokensRedemptionClientTest,PublicMetadataMismatch)360 TEST_F(AnonymousTokensRedemptionClientTest, PublicMetadataMismatch) {
361   ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
362       auto _, client_->CreateAnonymousTokensRedemptionRequest(
363                   {dummy_token_with_input_}));
364   dummy_response_.mutable_anonymous_token_redemption_results(0)
365       ->set_public_metadata(GetRandomString(10, &distr_u8_, &generator_));
366   absl::StatusOr<std::vector<RSABlindSignatureRedemptionResult>>
367       redemption_result =
368           client_->ProcessAnonymousTokensRedemptionResponse(dummy_response_);
369   EXPECT_EQ(redemption_result.status().code(),
370             absl::StatusCode::kInvalidArgument);
371   EXPECT_THAT(redemption_result.status().message(),
372               HasSubstr("Response metadata does not match"));
373 }
374 
TEST_F(AnonymousTokensRedemptionClientTest,PlaintextMessageMismatch)375 TEST_F(AnonymousTokensRedemptionClientTest, PlaintextMessageMismatch) {
376   ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
377       auto _, client_->CreateAnonymousTokensRedemptionRequest(
378                   {dummy_token_with_input_}));
379   dummy_response_.mutable_anonymous_token_redemption_results(0)
380       ->set_plaintext_message(GetRandomString(20, &distr_u8_, &generator_));
381   absl::StatusOr<std::vector<RSABlindSignatureRedemptionResult>>
382       redemption_result =
383           client_->ProcessAnonymousTokensRedemptionResponse(dummy_response_);
384   EXPECT_EQ(redemption_result.status().code(),
385             absl::StatusCode::kInvalidArgument);
386   EXPECT_THAT(redemption_result.status().message(),
387               HasSubstr("Response plaintext message does not match"));
388 }
389 
TEST_F(AnonymousTokensRedemptionClientTest,MessageMaskMismatch)390 TEST_F(AnonymousTokensRedemptionClientTest, MessageMaskMismatch) {
391   ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
392       auto _, client_->CreateAnonymousTokensRedemptionRequest(
393                   {dummy_token_with_input_}));
394   dummy_response_.mutable_anonymous_token_redemption_results(0)
395       ->set_message_mask(GetRandomString(32, &distr_u8_, &generator_));
396   absl::StatusOr<std::vector<RSABlindSignatureRedemptionResult>>
397       redemption_result =
398           client_->ProcessAnonymousTokensRedemptionResponse(dummy_response_);
399   EXPECT_EQ(redemption_result.status().code(),
400             absl::StatusCode::kInvalidArgument);
401   EXPECT_THAT(redemption_result.status().message(),
402               HasSubstr("Response message mask does not match"));
403 }
404 
TEST_F(AnonymousTokensRedemptionClientTest,SuccessfulResponseProcessingWithOneToken)405 TEST_F(AnonymousTokensRedemptionClientTest,
406        SuccessfulResponseProcessingWithOneToken) {
407   // Only one token in request
408   ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
409       auto redemption_request, client_->CreateAnonymousTokensRedemptionRequest(
410                                    {dummy_token_with_input_}));
411   ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
412       auto rsa_blind_sig_redemption_results,
413       client_->ProcessAnonymousTokensRedemptionResponse(dummy_response_));
414   auto tokens_to_result_map = CreateTokenToRedemptionResultMap(dummy_response_);
415   // Checks
416   ASSERT_EQ(rsa_blind_sig_redemption_results.size(), 1u);
417   std::string token =
418       rsa_blind_sig_redemption_results[0].token_with_input().token().token();
419   ASSERT_TRUE(tokens_to_result_map.contains(token));
420   EXPECT_EQ(rsa_blind_sig_redemption_results[0]
421                 .token_with_input()
422                 .input()
423                 .plaintext_message(),
424             tokens_to_result_map[token].plaintext_message);
425   EXPECT_TRUE(!rsa_blind_sig_redemption_results[0]
426                    .token_with_input()
427                    .token()
428                    .message_mask()
429                    .empty());
430   EXPECT_EQ(rsa_blind_sig_redemption_results[0]
431                 .token_with_input()
432                 .input()
433                 .public_metadata(),
434             tokens_to_result_map[token].public_metadata);
435   EXPECT_EQ(rsa_blind_sig_redemption_results[0].redeemed(),
436             tokens_to_result_map[token].redeemed);
437   EXPECT_EQ(rsa_blind_sig_redemption_results[0].double_spent(),
438             tokens_to_result_map[token].double_spent);
439 }
440 
TEST_F(AnonymousTokensRedemptionClientTest,SuccessfulResponseProcessingWithMultipleToken)441 TEST_F(AnonymousTokensRedemptionClientTest,
442        SuccessfulResponseProcessingWithMultipleToken) {
443   RSABlindSignatureTokenWithInput token_with_empty_message =
444       GetRandomDummyTokenWithInput();
445   token_with_empty_message.mutable_input()->clear_plaintext_message();
446   RSABlindSignatureTokenWithInput token_with_empty_mask =
447       GetRandomDummyTokenWithInput();
448   token_with_empty_mask.mutable_token()->clear_message_mask();
449   std::vector<RSABlindSignatureTokenWithInput> tokens_with_inputs = {
450       dummy_token_with_input_, GetRandomDummyTokenWithInput(),
451       GetRandomDummyTokenWithInput(), token_with_empty_message,
452       token_with_empty_mask};
453   ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
454       auto _,
455       client_->CreateAnonymousTokensRedemptionRequest(tokens_with_inputs));
456   *(dummy_response_.add_anonymous_token_redemption_results()) =
457       CreateRedemptionResultForTesting(tokens_with_inputs[1], false, true,
458                                        TEST_USE_CASE, 1);
459   for (size_t i = 2; i < tokens_with_inputs.size(); ++i) {
460     *(dummy_response_.add_anonymous_token_redemption_results()) =
461         CreateRedemptionResultForTesting(tokens_with_inputs[i]);
462   }
463   ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
464       auto rsa_blind_sig_redemption_results,
465       client_->ProcessAnonymousTokensRedemptionResponse(dummy_response_));
466   auto tokens_to_result_map = CreateTokenToRedemptionResultMap(dummy_response_);
467   // Checks
468   ASSERT_EQ(tokens_with_inputs.size(), rsa_blind_sig_redemption_results.size());
469   for (size_t i = 0; i < rsa_blind_sig_redemption_results.size(); ++i) {
470     std::string token =
471         rsa_blind_sig_redemption_results[i].token_with_input().token().token();
472     ASSERT_TRUE(tokens_to_result_map.contains(token));
473     EXPECT_EQ(rsa_blind_sig_redemption_results[i]
474                   .token_with_input()
475                   .input()
476                   .plaintext_message(),
477               tokens_to_result_map[token].plaintext_message);
478     EXPECT_EQ(rsa_blind_sig_redemption_results[i]
479                   .token_with_input()
480                   .token()
481                   .message_mask(),
482               tokens_to_result_map[token].message_mask);
483     EXPECT_EQ(rsa_blind_sig_redemption_results[i]
484                   .token_with_input()
485                   .input()
486                   .public_metadata(),
487               tokens_to_result_map[token].public_metadata);
488     EXPECT_EQ(rsa_blind_sig_redemption_results[i].redeemed(),
489               tokens_to_result_map[token].redeemed);
490     EXPECT_EQ(rsa_blind_sig_redemption_results[i].double_spent(),
491               tokens_to_result_map[token].double_spent);
492   }
493 }
494 
495 }  // namespace
496 }  // namespace anonymous_tokens
497