Repository navigation
Expand file tree
/
Copy pathihat.cpp
More file actions
135 lines (115 loc) · 4.97 KB
/
Copy pathihat.cpp
File metadata and controls
135 lines (115 loc) · 4.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// VOLE-IHAT, a post-quantum Issuer-Hiding Anonymous Token (IHAT).
//
// A Client obtains a token from an Anchor, then redeems it with a Moderator. The Moderator learns
// that the token was issued by some Anchor in its trusted Anchor Set, but not which one.
#include <vole_mole/ihat.hpp>
#include <vole_mole/uov.hpp>
#include <algorithm>
#include <array>
#include <bit>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <optional>
#include <print>
#include <set>
#include <stdexcept>
#include <string_view>
#include <utility>
#include <vector>
using namespace vole_mole;
namespace {
// Contexts are application-defined 16-byte values that the parties agree on out of band. Here we
// 10*-pad a short label. Another option would be to hash the label into the context string.
ihat::Context make_context(std::string_view label) {
if (label.size() >= ihat::CTX_BYTES) {
throw std::invalid_argument("context label is too long");
}
ihat::Context ctx{};
std::ranges::copy(label, ctx.begin());
ctx[label.size()] = 1;
return ctx;
}
// A Moderator, identified by its redemption context.
struct Moderator {
std::string_view name;
std::set<ihat::Nullifier> spent;
// Handle a redemption request. Returns true if the token is accepted.
bool handle_redeem(const ihat::AnchorSet& anchors, const ihat::Context& ctx_iss,
const ihat::RedeemRequest& request,
const ihat::ChallengeDigest& challenge_digest) {
const auto ctx_red = make_context(name);
const auto nf = ihat::finalize_redeem(anchors, request, ctx_iss, ctx_red, challenge_digest);
if (!nf) {
std::println("Moderator {}: rejected an invalid token", name);
return false;
} else if (!spent.insert(*nf).second) {
std::println("Moderator {}: rejected a token that was already redeemed", name);
return false;
}
std::println("Moderator {}: accepted a token", name);
return true;
}
};
} // namespace
int main() {
// The issuance context might be used to bound the epoch in which the token was issued. The
// Moderator might accept tokens issued in the current epoch or the previous one. In this
// example, the context indicates Tue Oct 06 2026 00:00:00 GMT+0000 (UNIX time 1791244800) as
// the start of the epoch and 86400 as the duration of the epoch in seconds.
const auto ctx_iss = std::bit_cast<ihat::Context>(std::array<uint64_t, 2>{1791244800, 86400});
// Setup.
//
// Key generation: Each Anchor generates a secret key, then publishes the corresponding
// public key where it can be accessed by Clients and Moderators.
constexpr size_t NUM_ANCHORS = 3;
std::vector<uov::SecretKey> anchor_sks;
for (size_t u = 0; u < NUM_ANCHORS; ++u) {
anchor_sks.emplace_back(uov::generate_secret_key_bytes());
}
std::vector<uov::PublicKey> anchor_pks;
for (const uov::SecretKey& sk : anchor_sks) {
anchor_pks.emplace_back(sk.public_key_bytes());
}
// The Anchor Set trusted by the Moderator. Clients redeem tokens against this set.
const ihat::AnchorSet anchors(anchor_pks); // anchor_pks must outlive anchors
// The redemption context might be used to bound the identity of the Moderator. This ensures the
// token can only be redeemed by the Moderator the Client committed to during issuance.
Moderator moderator{.name = "example.com", .spent = {}};
// Issue.
//
// Client: Request a token from an Anchor.
constexpr size_t ISSUER = 1;
auto [pending_token, issue_request] =
ihat::issue_request(ctx_iss, make_context(moderator.name));
// Anchor: Issue a token to the Client.
const auto issue_response = ihat::issue_response(anchor_sks[ISSUER], issue_request, ctx_iss);
if (!issue_response) {
std::println(stderr, "Anchor: the issue request is invalid");
return EXIT_FAILURE;
}
// Client: Finalize issuance.
auto token = std::move(pending_token).finalize_issue(anchor_pks[ISSUER], *issue_response);
if (!token) {
std::println(stderr, "Client: the Anchor's response is invalid");
return EXIT_FAILURE;
}
// Redeem.
//
// Client and Moderator compute the digest of the challenge that triggered redemption. Use
// an illustrative fixed digest here. This value is bound to the redemption proof.
const ihat::ChallengeDigest challenge_digest{0x01};
// Client: Request redemption.
const auto redeem_request = std::move(*token).redeem_request(
anchors, ctx_iss, make_context(moderator.name), challenge_digest);
// Moderator: Finalize redemption.
if (!moderator.handle_redeem(anchors, ctx_iss, redeem_request, challenge_digest)) {
return EXIT_FAILURE;
}
// Malicious Client: Try to replay the redemption request.
if (moderator.handle_redeem(anchors, ctx_iss, redeem_request, challenge_digest)) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}