• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
26 namespace anonymous_tokens {
27 
AnonymousTokensRedemptionClient(const AnonymousTokensUseCase use_case,const int64_t key_version)28 AnonymousTokensRedemptionClient::AnonymousTokensRedemptionClient(
29     const AnonymousTokensUseCase use_case, const int64_t key_version)
30     : use_case_(AnonymousTokensUseCase_Name(use_case)),
31       key_version_(key_version) {}
32 
33 absl::StatusOr<std::unique_ptr<AnonymousTokensRedemptionClient>>
Create(const AnonymousTokensUseCase use_case,const int64_t key_version)34 AnonymousTokensRedemptionClient::Create(const AnonymousTokensUseCase use_case,
35                                         const int64_t key_version) {
36   if (key_version <= 0) {
37     return absl::InvalidArgumentError("Key version must be greater than 0.");
38   } else if (use_case == ANONYMOUS_TOKENS_USE_CASE_UNDEFINED) {
39     return absl::InvalidArgumentError("Use case must be defined.");
40   }
41   return absl::WrapUnique(
42       new AnonymousTokensRedemptionClient(use_case, key_version));
43 }
44 
45 absl::StatusOr<AnonymousTokensRedemptionRequest>
CreateAnonymousTokensRedemptionRequest(const std::vector<RSABlindSignatureTokenWithInput> & tokens_with_inputs)46 AnonymousTokensRedemptionClient::CreateAnonymousTokensRedemptionRequest(
47     const std::vector<RSABlindSignatureTokenWithInput>& tokens_with_inputs) {
48   if (tokens_with_inputs.empty()) {
49     return absl::InvalidArgumentError("Cannot create an empty request.");
50   } else if (!token_to_input_map_.empty()) {
51     return absl::FailedPreconditionError("Redemption request already created.");
52   }
53   // Request to output
54   AnonymousTokensRedemptionRequest request;
55   for (const RSABlindSignatureTokenWithInput& token_with_input :
56        tokens_with_inputs) {
57     if (token_with_input.token().token().empty()) {
58       return absl::InvalidArgumentError(
59           "Cannot send an empty token to redeem.");
60     } else if (!token_with_input.token().message_mask().empty() &&
61                token_with_input.token().message_mask().size() <
62                    kRsaMessageMaskSizeInBytes32) {
63       return absl::InvalidArgumentError(
64           "Message mask must be of at least 32 bytes, if it exists.");
65     }
66     // Check if token is repeated in the input and keep state for response
67     // processing if it was not repeated.
68     auto maybe_inserted = token_to_input_map_.insert(
69         {token_with_input.token().token(),
70          {
71              .input = token_with_input.input(),
72              .mask = token_with_input.token().message_mask(),
73          }});
74     if (!maybe_inserted.second) {
75       return absl::InvalidArgumentError(
76           "Token should not be repeated in the input to "
77           "CreateAnonymousTokensRedemptionRequest.");
78     }
79 
80     // Create the AnonymousTokenToRedeem to put in the request.
81     AnonymousTokensRedemptionRequest_AnonymousTokenToRedeem* at_to_redeem =
82         request.add_anonymous_tokens_to_redeem();
83     at_to_redeem->set_use_case(use_case_);
84     at_to_redeem->set_key_version(key_version_);
85     at_to_redeem->set_public_metadata(
86         token_with_input.input().public_metadata());
87     at_to_redeem->set_serialized_unblinded_token(
88         token_with_input.token().token());
89     at_to_redeem->set_plaintext_message(
90         token_with_input.input().plaintext_message());
91     at_to_redeem->set_message_mask(token_with_input.token().message_mask());
92   }
93   return request;
94 }
95 
96 absl::StatusOr<std::vector<RSABlindSignatureRedemptionResult>>
ProcessAnonymousTokensRedemptionResponse(const AnonymousTokensRedemptionResponse & redemption_response)97 AnonymousTokensRedemptionClient::ProcessAnonymousTokensRedemptionResponse(
98     const AnonymousTokensRedemptionResponse& redemption_response) {
99   if (token_to_input_map_.empty()) {
100     return absl::FailedPreconditionError(
101         "A valid Redemption request was not created before calling "
102         "ProcessAnonymousTokensRedemptionResponse.");
103   } else if (redemption_response.anonymous_token_redemption_results().empty()) {
104     return absl::InvalidArgumentError("Cannot process an empty response.");
105   } else if (redemption_response.anonymous_token_redemption_results().size() !=
106              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 
199