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