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 <memory>
18 #include <utility>
19 #include <vector>
20 
21 #include "absl/container/flat_hash_set.h"
22 #include "anonymous_tokens/cpp/crypto/constants.h"
23 #include "anonymous_tokens/proto/anonymous_tokens.pb.h"
24 
25 namespace anonymous_tokens {
26 
AnonymousTokensRedemptionClient(const AnonymousTokensUseCase use_case,const uint64_t key_version)27 AnonymousTokensRedemptionClient::AnonymousTokensRedemptionClient(
28     const AnonymousTokensUseCase use_case, const uint64_t key_version)
29     : use_case_(AnonymousTokensUseCase_Name(use_case)),
30       key_version_(key_version) {}
31 
32 absl::StatusOr<std::unique_ptr<AnonymousTokensRedemptionClient>>
Create(const AnonymousTokensUseCase use_case,const uint64_t key_version)33 AnonymousTokensRedemptionClient::Create(const AnonymousTokensUseCase use_case,
34                                         const uint64_t key_version) {
35   if (key_version == 0) {
36     return absl::InvalidArgumentError("Key version must be greater than 0.");
37   } else if (use_case == ANONYMOUS_TOKENS_USE_CASE_UNDEFINED) {
38     return absl::InvalidArgumentError("Use case must be defined.");
39   }
40   return absl::WrapUnique(
41       new AnonymousTokensRedemptionClient(use_case, key_version));
42 }
43 
44 absl::StatusOr<AnonymousTokensRedemptionRequest>
CreateAnonymousTokensRedemptionRequest(const std::vector<RSABlindSignatureTokenWithInput> & tokens_with_inputs)45 AnonymousTokensRedemptionClient::CreateAnonymousTokensRedemptionRequest(
46     const std::vector<RSABlindSignatureTokenWithInput>& tokens_with_inputs) {
47   if (tokens_with_inputs.empty()) {
48     return absl::InvalidArgumentError("Cannot create an empty request.");
49   } else if (!token_to_input_map_.empty()) {
50     return absl::FailedPreconditionError("Redemption request already created.");
51   }
52   // Request to output
53   AnonymousTokensRedemptionRequest request;
54   for (const RSABlindSignatureTokenWithInput& token_with_input :
55        tokens_with_inputs) {
56     if (token_with_input.token().token().empty()) {
57       return absl::InvalidArgumentError(
58           "Cannot send an empty token to redeem.");
59     } else if (!token_with_input.token().message_mask().empty() &&
60                token_with_input.token().message_mask().size() <
61                    kRsaMessageMaskSizeInBytes32) {
62       return absl::InvalidArgumentError(
63           "Message mask must be of at least 32 bytes, if it exists.");
64     }
65     // Check if token is repeated in the input and keep state for response
66     // processing if it was not repeated.
67     auto maybe_inserted = token_to_input_map_.insert(
68         {token_with_input.token().token(),
69          {
70              .input = token_with_input.input(),
71              .mask = token_with_input.token().message_mask(),
72          }});
73     if (!maybe_inserted.second) {
74       return absl::InvalidArgumentError(
75           "Token should not be repeated in the input to "
76           "CreateAnonymousTokensRedemptionRequest.");
77     }
78 
79     // Create the AnonymousTokenToRedeem to put in the request.
80     AnonymousTokensRedemptionRequest_AnonymousTokenToRedeem* at_to_redeem =
81         request.add_anonymous_tokens_to_redeem();
82     at_to_redeem->set_use_case(use_case_);
83     at_to_redeem->set_key_version(key_version_);
84     at_to_redeem->set_public_metadata(
85         token_with_input.input().public_metadata());
86     at_to_redeem->set_serialized_unblinded_token(
87         token_with_input.token().token());
88     at_to_redeem->set_plaintext_message(
89         token_with_input.input().plaintext_message());
90     at_to_redeem->set_message_mask(token_with_input.token().message_mask());
91   }
92   return request;
93 }
94 
95 absl::StatusOr<std::vector<RSABlindSignatureRedemptionResult>>
ProcessAnonymousTokensRedemptionResponse(const AnonymousTokensRedemptionResponse & redemption_response)96 AnonymousTokensRedemptionClient::ProcessAnonymousTokensRedemptionResponse(
97     const AnonymousTokensRedemptionResponse& redemption_response) {
98   if (token_to_input_map_.empty()) {
99     return absl::FailedPreconditionError(
100         "A valid Redemption request was not created before calling "
101         "ProcessAnonymousTokensRedemptionResponse.");
102   } else if (redemption_response.anonymous_token_redemption_results().empty()) {
103     return absl::InvalidArgumentError("Cannot process an empty response.");
104   } else if (static_cast<size_t>(
105                  redemption_response.anonymous_token_redemption_results()
106                      .size()) != token_to_input_map_.size()) {
107     return absl::InvalidArgumentError(
108         "Response is missing some requested token redemptions.");
109   }
110   // Vector to accumulate redemption results to output.
111   std::vector<RSABlindSignatureRedemptionResult>
112       rsa_blind_sig_redemption_results;
113   // Temporary set structure to check for duplicate token in the redemption
114   // response.
115   absl::flat_hash_set<absl::string_view> tokens;
116 
117   // Loop over all the results in the response.
118   for (const AnonymousTokensRedemptionResponse_AnonymousTokenRedemptionResult&
119            redemption_result :
120        redemption_response.anonymous_token_redemption_results()) {
121     // Basic validity checks on the response.
122     if (redemption_result.use_case() != use_case_) {
123       return absl::InvalidArgumentError(
124           "Use case does not match the requested use case.");
125     } else if (redemption_result.key_version() != key_version_) {
126       return absl::InvalidArgumentError(
127           "Key version does not match the requested key version.");
128     } else if (redemption_result.serialized_unblinded_token().empty()) {
129       return absl::InvalidArgumentError("Token cannot be empty in response.");
130     } else if (!redemption_result.message_mask().empty() &&
131                redemption_result.message_mask().size() <
132                    kRsaMessageMaskSizeInBytes32) {
133       return absl::InvalidArgumentError(
134           "Message mask must be of at least 32 bytes, if it exists.");
135     }
136     // Check for duplicate in responses.
137     auto maybe_inserted =
138         tokens.insert(redemption_result.serialized_unblinded_token());
139     if (!maybe_inserted.second) {
140       return absl::InvalidArgumentError("Token was repeated in the response.");
141     }
142 
143     // Retrieve redemption info associated with this redemption result.
144     auto it = token_to_input_map_.find(
145         redemption_result.serialized_unblinded_token());
146     if (it == token_to_input_map_.end()) {
147       return absl::InvalidArgumentError(
148           "Server responded with some tokens whose redemptions were not "
149           "requested.");
150     }
151     const RedemptionInfo& redemption_info = it->second;
152 
153     // Check if inputs in the redemption request and response match
154     if (redemption_info.input.public_metadata() !=
155         redemption_result.public_metadata()) {
156       return absl::InvalidArgumentError(
157           "Response metadata does not match input metadata.");
158     } else if (redemption_info.input.plaintext_message() !=
159                redemption_result.plaintext_message()) {
160       return absl::InvalidArgumentError(
161           "Response plaintext message does not match input plaintext message.");
162     } else if (redemption_info.mask != redemption_result.message_mask()) {
163       return absl::InvalidArgumentError(
164           "Response message mask does not match input message mask.");
165     }
166 
167     PlaintextMessageWithPublicMetadata message_and_metadata;
168     // Put the correct plaintext message in final redemption result
169     message_and_metadata.set_plaintext_message(
170         redemption_result.plaintext_message());
171     // Put the correct public metadata in final redemption result
172     message_and_metadata.set_public_metadata(
173         redemption_result.public_metadata());
174 
175     RSABlindSignatureToken token;
176     // Put the correct anonymous token in final redemption result
177     token.set_token(redemption_result.serialized_unblinded_token());
178     // Put the correct message mask in final redemption result
179     token.set_message_mask(redemption_result.message_mask());
180 
181     // Construct the final redemption result.
182     RSABlindSignatureRedemptionResult final_redemption_result;
183     *final_redemption_result.mutable_token_with_input()->mutable_token() =
184         token;
185     *final_redemption_result.mutable_token_with_input()->mutable_input() =
186         message_and_metadata;
187     final_redemption_result.set_redeemed(redemption_result.verified());
188     final_redemption_result.set_double_spent(redemption_result.double_spent());
189     // Add the redemption result to the output vector.
190     rsa_blind_sig_redemption_results.push_back(
191         std::move(final_redemption_result));
192   }
193 
194   return rsa_blind_sig_redemption_results;
195 }
196 
197 }  // namespace anonymous_tokens
198