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