Viewing is open to everyone. Downloading and starring need a GitHub sign-in. Sign in with GitHub

main.cpp

v0.1.0-build.85 Latest Commit ede38f5

Size
27 KB
Lines
395
Language
cpp
SHA-256
4691a5c4d734…
View on GitHub

Sign in with GitHub to download. Each download is recorded with your GitHub username.

1#include "ckey/ckey.h"
2#include "print_output.h"
3#include <cstdint>
4#include <cstring>
5#include <ctime>
6#include <cstdio>
7#include <fstream>
8#include <iostream>
9#include <memory>
10#include <cmath>
11#include <cstdlib>
12#include <string>
13#include <vector>
14
15static int hex_digit(char c) {
16 if (c >= '0' && c <= '9') return c - '0';
17 if (c >= 'a' && c <= 'f') return c - 'a' + 10;
18 if (c >= 'A' && c <= 'F') return c - 'A' + 10;
19 return -1;
20}
21
22// Strict: an even number of hexadecimal digits and nothing else (no sign,
23// whitespace, or 0x prefix).
24static bool from_hex(const std::string &input, std::vector<uint8_t> &out) {
25 if (input.size() % 2 != 0) return false;
26 out.resize(input.size() / 2);
27 for (size_t i = 0; i < out.size(); ++i) {
28 const int hi = hex_digit(input[2 * i]), lo = hex_digit(input[2 * i + 1]);
29 if (hi < 0 || lo < 0) return false;
30 out[i] = static_cast<uint8_t>(hi << 4 | lo);
31 }
32 return true;
33}
34
35static bool write_svg(const std::string &path, const std::string &svg) {
36 std::ofstream file(path, std::ios::binary);
37 if (!file) return false;
38 file.write(svg.data(), static_cast<std::streamsize>(svg.size() - 1));
39 return static_cast<bool>(file);
40}
41
42static std::string base64(const std::string &in) {
43 static const char *t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
44 std::string out; size_t i = 0;
45 for (; i + 2 < in.size(); i += 3) {
46 const uint32_t v = (uint8_t(in[i]) << 16) | (uint8_t(in[i + 1]) << 8) | uint8_t(in[i + 2]);
47 out += t[v >> 18]; out += t[(v >> 12) & 63]; out += t[(v >> 6) & 63]; out += t[v & 63];
48 }
49 if (i < in.size()) {
50 const uint32_t v = (uint8_t(in[i]) << 16) | (i + 1 < in.size() ? uint8_t(in[i + 1]) << 8 : 0);
51 out += t[v >> 18]; out += t[(v >> 12) & 63]; out += i + 1 < in.size() ? t[(v >> 6) & 63] : '='; out += '=';
52 }
53 return out;
54}
55
56// ckey encode-scalable (--utf8 <text> | --hex <hex>) --out <file.svg>
57// [--version 1-31] [--level L|M|Q|H] [--preset plain|concept]
58// [--modules rounded|square|circle|diamond] [--rings joined|dots] [--rays round|flat]
59// [--logo <png|jpg|svg file>] [--logo-radius <units>]
60static int encode_scalable(int argc, char **argv) {
61 std::string mode, value, output, logo_href, png, raster_out, pdf_out; uint32_t version = 0; ckey_ecc_level level = CKEY_ECC_M;
62 bool transparent = false; ckey_raster_policy policy = CKEY_RASTER_COVERAGE; uint32_t size_px = 1024;
63 double size_in = 1.0, dpi = 600;
64 ckey_scalable_layout layout = CKEY_LAYOUT_STANDARD;
65 ckey_style style; ckey_style_default(&style);
66 for (int i = 2; i + 1 < argc; i += 2) {
67 const std::string flag = argv[i], arg = argv[i + 1];
68 if (flag == "--utf8" || flag == "--hex") { mode = flag; value = arg; }
69 else if (flag == "--out") output = arg;
70 else if (flag == "--version") {
71 char *end = nullptr; const long v = std::strtol(arg.c_str(), &end, 10);
72 if (!end || *end || v < 1 || v > static_cast<long>(CKEY_SCALABLE_VERSION_COUNT)) { std::cerr << "--version must be 1 to " << CKEY_SCALABLE_VERSION_COUNT << '\n'; return 2; }
73 version = static_cast<uint32_t>(v);
74 } else if (flag == "--level") {
75 if (arg == "L") level = CKEY_ECC_L; else if (arg == "M") level = CKEY_ECC_M; else if (arg == "Q") level = CKEY_ECC_Q; else if (arg == "H") level = CKEY_ECC_H;
76 else { std::cerr << "--level must be L, M, Q, or H\n"; return 2; }
77 } else if (flag == "--png") { png = arg;
78 } else if (flag == "--raster") { raster_out = arg;
79 } else if (flag == "--pdf") { pdf_out = arg;
80 } else if (flag == "--transparent") {
81 if (arg == "yes") transparent = true; else if (arg == "no") transparent = false; else { std::cerr << "--transparent must be yes or no\n"; return 2; }
82 } else if (flag == "--policy") {
83 if (arg == "coverage") policy = CKEY_RASTER_COVERAGE; else if (arg == "binary") policy = CKEY_RASTER_BINARY; else if (arg == "hybrid") policy = CKEY_RASTER_HYBRID;
84 else { std::cerr << "--policy must be coverage, binary, or hybrid\n"; return 2; }
85 } else if (flag == "--size-px") {
86 char *end = nullptr; const long v = std::strtol(arg.c_str(), &end, 10);
87 if (!end || *end || v < 16 || v > 16384) { std::cerr << "--size-px must be 16 to 16384\n"; return 2; }
88 size_px = static_cast<uint32_t>(v);
89 } else if (flag == "--size-in" || flag == "--dpi") {
90 char *end = nullptr; const double v = std::strtod(arg.c_str(), &end);
91 if (!end || *end || !(v > 0)) { std::cerr << flag << " must be a positive number\n"; return 2; }
92 (flag == "--dpi" ? dpi : size_in) = v;
93 } else if (flag == "--layout") {
94 if (arg == "standard") layout = CKEY_LAYOUT_STANDARD; else if (arg == "print") layout = CKEY_LAYOUT_PRINT;
95 else { std::cerr << "--layout must be standard or print\n"; return 2; }
96 } else if (flag == "--preset") {
97 if (arg == "concept") ckey_style_preset_fill(&style, CKEY_PRESET_CONCEPT);
98 else if (arg == "plain") ckey_style_preset_fill(&style, CKEY_PRESET_PLAIN);
99 else { std::cerr << "--preset must be plain or concept\n"; return 2; }
100 } else if (flag == "--modules") {
101 if (arg == "rounded") style.modules = CKEY_MODULE_ROUNDED; else if (arg == "square") style.modules = CKEY_MODULE_SQUARE;
102 else if (arg == "circle") style.modules = CKEY_MODULE_CIRCLE; else if (arg == "diamond") style.modules = CKEY_MODULE_DIAMOND;
103 else { std::cerr << "--modules must be rounded, square, circle, or diamond\n"; return 2; }
104 } else if (flag == "--rings") {
105 if (arg == "joined") style.rings = CKEY_RING_JOINED; else if (arg == "dots") style.rings = CKEY_RING_DOTS;
106 else { std::cerr << "--rings must be joined or dots\n"; return 2; }
107 } else if (flag == "--rays") {
108 if (arg == "round") style.rays = CKEY_RAY_ROUND; else if (arg == "flat") style.rays = CKEY_RAY_FLAT;
109 else { std::cerr << "--rays must be round or flat\n"; return 2; }
110 } else if (flag == "--logo") {
111 std::ifstream file(arg, std::ios::binary);
112 if (!file) { std::cerr << "cannot read logo " << arg << '\n'; return 2; }
113 const std::string bytes((std::istreambuf_iterator<char>(file)), {});
114 const std::string ext = arg.size() > 4 ? arg.substr(arg.size() - 4) : "";
115 const std::string type = ext == ".svg" ? "image/svg+xml" : ext == ".png" ? "image/png" : "image/jpeg";
116 logo_href = "data:" + type + ";base64," + base64(bytes); style.logo_plate = 1;
117 } else if (flag == "--logo-radius") {
118 char *end = nullptr; style.logo_radius = std::strtod(arg.c_str(), &end);
119 if (!end || *end || !(style.logo_radius >= 0)) { std::cerr << "--logo-radius must be a non-negative number\n"; return 2; }
120 } else { std::cerr << "unknown option " << flag << '\n'; return 2; }
121 }
122 if (!logo_href.empty()) style.logo_href = logo_href.c_str();
123 if ((argc % 2) != 0 || mode.empty() || (output.empty() && png.empty() && raster_out.empty() && pdf_out.empty())) {
124 std::cerr << "usage: ckey encode-scalable (--utf8 <text> | --hex <hex>) --out <file.svg> [--version 1-31] [--level L|M|Q|H] [--layout standard|print]\n"
125 " [--preset plain|concept] [--modules rounded|square|circle|diamond] [--rings joined|dots] [--rays round|flat]\n"
126 " [--logo <file>] [--logo-radius <units>]\n"
127 " [--raster <file.png> [--size-px N] [--policy coverage|binary|hybrid] [--transparent yes|no]] [--pdf <file.pdf>]\n"
128 " [--png <print.png> [--size-in IN] [--dpi DPI]]\n"; return 2;
129 }
130 std::vector<uint8_t> payload;
131 if (mode == "--hex") { if (!from_hex(value, payload)) { std::cerr << "hex input must be an even number of hexadecimal digits\n"; return 2; } }
132 else payload.assign(value.begin(), value.end());
133 auto symbol = std::make_unique<ckey_scalable_symbol>(); ckey_diagnostics diag{};
134 if (ckey_encode_scalable(payload.data(), payload.size(), version, level, layout, symbol.get(), &diag) != CKEY_OK) { std::cerr << diag.message << '\n'; return 1; }
135 ckey_diagnostics check{};
136 if (ckey_check_scalable_style(symbol.get(), &style, &check) != CKEY_OK) { std::cerr << "design refused: " << check.message << '\n'; return 1; }
137 size_t needed = 0; ckey_render_scalable_styled_svg(symbol.get(), &style, 16, nullptr, 0, &needed);
138 std::string svg(needed, '\0');
139 if (ckey_render_scalable_styled_svg(symbol.get(), &style, 16, svg.data(), svg.size(), &needed) != CKEY_OK || (!output.empty() && !write_svg(output, svg))) { std::cerr << "SVG rendering failed\n"; return 1; }
140 if (!raster_out.empty()) {
141 // Screen raster straight from geometry (no SVG). A logo file is not
142 // decoded here, so the raster shows only its plate.
143 ckey_raster_options o; ckey_raster_options_default(&o);
144 o.size_px = size_px; o.policy = policy; o.transparent = transparent ? 1u : 0u;
145 size_t need = 0;
146 if (ckey_render_scalable_raster(symbol.get(), &style, &o, nullptr, 0, &need) != CKEY_INSUFFICIENT_BUFFER) { std::cerr << "raster rendering failed\n"; return 1; }
147 std::vector<uint8_t> px(need);
148 size_t png_need = 0;
149 if (ckey_render_scalable_raster(symbol.get(), &style, &o, px.data(), px.size(), &need) != CKEY_OK ||
150 ckey_encode_png(px.data(), size_px, size_px, 0, CKEY_PNG_RGBA8, 0, nullptr, 0, &png_need) != CKEY_INSUFFICIENT_BUFFER) { std::cerr << "raster rendering failed\n"; return 1; }
151 std::vector<uint8_t> file(png_need);
152 if (ckey_encode_png(px.data(), size_px, size_px, 0, CKEY_PNG_RGBA8, 0, file.data(), file.size(), &png_need) != CKEY_OK) { std::cerr << "PNG encoding failed\n"; return 1; }
153 std::ofstream f(raster_out, std::ios::binary); f.write(reinterpret_cast<const char *>(file.data()), static_cast<std::streamsize>(file.size()));
154 if (!f) { std::cerr << "could not write " << raster_out << '\n'; return 1; }
155 std::cout << "raster PNG: " << raster_out << " (" << size_px << " px" << (transparent ? ", transparent" : "") << ")\n";
156 }
157 if (!pdf_out.empty()) {
158 size_t need = 0;
159 if (ckey_render_scalable_pdf(symbol.get(), &style, size_in * 72.0, transparent ? 1u : 0u, nullptr, 0, &need) != CKEY_INSUFFICIENT_BUFFER) { std::cerr << "PDF rendering failed\n"; return 1; }
160 std::vector<uint8_t> file(need);
161 if (ckey_render_scalable_pdf(symbol.get(), &style, size_in * 72.0, transparent ? 1u : 0u, file.data(), file.size(), &need) != CKEY_OK) { std::cerr << "PDF rendering failed\n"; return 1; }
162 std::ofstream f(pdf_out, std::ios::binary); f.write(reinterpret_cast<const char *>(file.data()), static_cast<std::streamsize>(file.size()));
163 if (!f) { std::cerr << "could not write " << pdf_out << '\n'; return 1; }
164 std::cout << "vector PDF: " << pdf_out << " (" << size_in << " in)\n";
165 }
166 if (!png.empty()) {
167 // Print export: black ink on paper on the printer's dot grid. Colours,
168 // materials, and logo images are screen styling and are not printed.
169 printout::Page page; page.width = page.height = static_cast<int>(std::lround(size_in * dpi)); page.ink.assign(static_cast<size_t>(page.width) * page.height, 0);
170 if (!printout::draw_symbol(page, *symbol, &style, 0, 0, page.width, dpi) || !printout::write_png(png, page, dpi)) { std::cerr << "could not write " << png << '\n'; return 1; }
171 std::cout << "print PNG: " << png << " (" << size_in << " in at " << dpi << " dpi, 1-bit, black on paper)\n";
172 }
173 std::cout << diag.message << "; profile=0" << (layout == CKEY_LAYOUT_PRINT ? ".1" : "") << "; format=0."
174 << (layout == CKEY_LAYOUT_PRINT ? CKEY_SCALABLE_PRINT_FORMAT_VERSION : CKEY_SCALABLE_FORMAT_VERSION) << ".0; version=" << symbol->version
175 << "; payload-bytes=" << payload.size() << "; capacity=" << ckey_scalable_capacity(symbol->version, level, layout) << "; svg=" << output << '\n';
176 return 0;
177}
178
179// ckey print-sheet --out sheet.png [--dpi 600] [--set small|large]
180// A US Letter calibration ladder of P0 and P0.1 symbols at several payloads
181// and printed sizes, as a 1-bit device-grid PNG. Each symbol's payload names
182// its own cell ("CKEY-CAL P0.1 120B 0.50in ..."), so a scan identifies it.
183static int print_sheet(int argc, char **argv) {
184 std::string out, set = "small"; double dpi = 600;
185 for (int i = 2; i + 1 < argc; i += 2) {
186 const std::string flag = argv[i], arg = argv[i + 1];
187 if (flag == "--out") out = arg; else if (flag == "--set") set = arg;
188 else if (flag == "--dpi") { dpi = std::strtod(arg.c_str(), nullptr); if (!(dpi >= 150 && dpi <= 2400)) { std::cerr << "--dpi must be 150..2400\n"; return 2; } }
189 else { std::cerr << "unknown option " << flag << '\n'; return 2; }
190 }
191 if (out.empty() || (set != "small" && set != "large") || (argc % 2) != 0) { std::cerr << "usage: ckey print-sheet --out sheet.png [--dpi 600] [--set small|large]\n"; return 2; }
192 const std::vector<size_t> payloads = set == "small" ? std::vector<size_t>{8, 30, 120, 400} : std::vector<size_t>{1200, 2400};
193 const std::vector<double> sizes = set == "small" ? std::vector<double>{0.25, 0.375, 0.5, 0.75, 1.0} : std::vector<double>{1.0, 1.25, 1.5, 2.0};
194 printout::Page page; page.width = static_cast<int>(std::lround(8.5 * dpi)); page.height = static_cast<int>(std::lround(11 * dpi));
195 page.ink.assign(static_cast<size_t>(page.width) * page.height, 0);
196 const int text = std::max(2, static_cast<int>(std::lround(dpi / 150))); // label scale
197 const auto in = [&](double v) { return static_cast<int>(std::lround(v * dpi)); };
198 printout::draw_text(page, "CKey print calibration - print at 100% actual size", in(0.5), in(0.35), text + 1);
199 printout::draw_text(page, "dpi " + std::to_string(static_cast<int>(dpi)) + " level M black on paper", in(0.5), in(0.35) + 9 * (text + 1), text);
200 int y = in(0.9); const double max_size = sizes.back();
201 for (size_t bytes : payloads) for (auto layout : {CKEY_LAYOUT_STANDARD, CKEY_LAYOUT_PRINT}) {
202 printout::draw_text(page, std::string(layout ? "P0.1 " : "P0 ") + std::to_string(bytes) + "B", in(0.5), y + in(max_size / 2), text);
203 int x = in(1.5);
204 for (double size : sizes) {
205 char label[64]; std::snprintf(label, sizeof(label), "CKEY-CAL %s %zuB %.2fin ", layout ? "P0.1" : "P0", bytes, size);
206 std::string payload = label; while (payload.size() < bytes) payload += "0123456789";
207 payload.resize(bytes);
208 auto symbol = std::make_unique<ckey_scalable_symbol>(); ckey_diagnostics diag{};
209 if (ckey_encode_scalable(reinterpret_cast<const uint8_t *>(payload.data()), payload.size(), 0, CKEY_ECC_M, layout, symbol.get(), &diag) != CKEY_OK) {
210 std::cerr << "cannot encode " << label << ": " << diag.message << '\n'; return 1;
211 }
212 if (!printout::draw_symbol(page, *symbol, nullptr, x, y, in(size), dpi)) { std::cerr << "cannot render " << label << '\n'; return 1; }
213 char caption[32]; std::snprintf(caption, sizeof(caption), "v%u %.2fin", symbol->version, size);
214 printout::draw_text(page, caption, x, y + in(size) + text * 2, text);
215 x += in(size) + in(0.3);
216 }
217 y += in(max_size) + in(0.3);
218 }
219 if (!printout::write_png(out, page, dpi)) { std::cerr << "could not write " << out << '\n'; return 1; }
220 std::cout << "calibration sheet: " << out << " (US Letter, " << dpi << " dpi, 1-bit). Print at 100% / actual size.\n";
221 return 0;
222}
223
224// Safe Actions (spec/CKey-Safe-Actions.md) for issuers: make a key, sign, verify.
225static std::string to_hex(const uint8_t *p, size_t n) {
226 static const char d[] = "0123456789abcdef";
227 std::string s;
228 for (size_t i = 0; i < n; ++i) { s.push_back(d[p[i] >> 4]); s.push_back(d[p[i] & 15]); }
229 return s;
230}
231
232static bool read_seed(const std::string &path, std::vector<uint8_t> &seed) {
233 std::ifstream f(path);
234 std::string text;
235 if (!(f >> text) || !from_hex(text, seed) || seed.size() != 32) return false;
236 return true;
237}
238
239static int safe_action(int argc, char **argv) {
240 const std::string sub = argc >= 3 ? argv[2] : "";
241 auto opt = [&](const char *name, const char *fallback = nullptr) -> std::string {
242 for (int i = 3; i + 1 < argc; i += 2) if (std::string(argv[i]) == name) return argv[i + 1];
243 return fallback ? fallback : "";
244 };
245 if (sub == "keygen") {
246 const std::string out = opt("--out");
247 if (out.empty()) { std::cerr << "usage: ckey action keygen --out issuer.key\n"; return 2; }
248 std::ifstream urandom("/dev/urandom", std::ios::binary);
249 uint8_t seed[32];
250 if (!urandom.read(reinterpret_cast<char *>(seed), 32)) { std::cerr << "no system random source\n"; return 1; }
251 std::ofstream f(out);
252 f << to_hex(seed, 32) << "\n";
253 if (!f) { std::cerr << "could not write " << out << "\n"; return 1; }
254 uint8_t pk[32];
255 ckey_ed25519_public_key(seed, pk);
256 std::cout << "private seed written to " << out << " (keep it on the issuer's server only)\n"
257 << "public key (hex): " << to_hex(pk, 32) << "\n";
258 return 0;
259 }
260 if (sub == "public-key") {
261 std::vector<uint8_t> seed;
262 if (!read_seed(opt("--key"), seed)) { std::cerr << "usage: ckey action public-key --key issuer.key\n"; return 2; }
263 uint8_t pk[32];
264 ckey_ed25519_public_key(seed.data(), pk);
265 std::cout << to_hex(pk, 32) << "\n";
266 return 0;
267 }
268 if (sub == "sign") {
269 std::vector<uint8_t> seed;
270 auto fields = std::make_unique<ckey_safe_action>();
271 const std::string issuer = opt("--issuer"), app = opt("--app"), action = opt("--action"), key_id = opt("--key-id"),
272 request = opt("--request"), scope = opt("--scope", "same-device"), params = opt("--params"),
273 ttl = opt("--ttl", "120"), now = opt("--now");
274 if (!read_seed(opt("--key"), seed) || issuer.empty() || app.empty() || action.empty() || key_id.empty() || request.empty() ||
275 (scope != "same-device" && scope != "cross-device")) {
276 std::cerr << "usage: ckey action sign --key issuer.key --issuer citeory.com --app com.viadere --action signin.request\n"
277 " --key-id 2026-11 --request <single-use id> [--scope same-device|cross-device]\n"
278 " [--params '{\"json\":true}'] [--ttl 120] [--now <unix seconds>]\n";
279 return 2;
280 }
281 auto copy = [](char *dst, size_t cap, const std::string &v) { if (v.size() >= cap) return false; std::memcpy(dst, v.data(), v.size()); dst[v.size()] = 0; return true; };
282 if (!copy(fields->issuer, sizeof(fields->issuer), issuer) || !copy(fields->app, sizeof(fields->app), app) ||
283 !copy(fields->action, sizeof(fields->action), action) || !copy(fields->key_id, sizeof(fields->key_id), key_id) ||
284 !copy(fields->request_id, sizeof(fields->request_id), request) || params.size() > sizeof(fields->params)) {
285 std::cerr << "a field is too long\n";
286 return 2;
287 }
288 fields->scope = scope == "cross-device" ? CKEY_SCOPE_CROSS_DEVICE : CKEY_SCOPE_SAME_DEVICE;
289 fields->issued_at = now.empty() ? static_cast<uint64_t>(std::time(nullptr)) : std::stoull(now);
290 fields->expires_at = fields->issued_at + std::stoull(ttl);
291 std::memcpy(fields->params, params.data(), params.size());
292 fields->params_len = params.size();
293 size_t need = 0;
294 ckey_diagnostics diag{};
295 ckey_safe_action_sign(fields.get(), seed.data(), nullptr, 0, &need, &diag);
296 std::vector<char> out(need + 1);
297 if (ckey_safe_action_sign(fields.get(), seed.data(), out.data(), out.size(), &need, &diag) != CKEY_OK) { std::cerr << diag.message << "\n"; return 1; }
298 std::cout << out.data() << "\n";
299 return 0;
300 }
301 if (sub == "verify") {
302 const std::string payload = opt("--payload"), key = opt("--public-key"), now = opt("--now"), max = opt("--max-lifetime", "600");
303 std::vector<uint8_t> pk;
304 if (payload.empty() || !from_hex(key, pk) || pk.size() != 32) { std::cerr << "usage: ckey action verify --payload 'ckey-action:1:...' --public-key <64 hex> [--now <unix seconds>] [--max-lifetime 600]\n"; return 2; }
305 auto out = std::make_unique<ckey_safe_action>();
306 ckey_diagnostics diag{};
307 const ckey_status st = ckey_safe_action_verify(reinterpret_cast<const uint8_t *>(payload.data()), payload.size(), pk.data(),
308 now.empty() ? static_cast<uint64_t>(std::time(nullptr)) : std::stoull(now),
309 static_cast<uint32_t>(std::stoul(max)), out.get(), &diag);
310 std::cout << diag.message << "\n";
311 if (st == CKEY_OK || st == CKEY_EXPIRED)
312 std::cout << "issuer " << out->issuer << ", app " << out->app << ", action " << out->action << ", key " << out->key_id
313 << ", request " << out->request_id << ", " << (out->scope ? "cross-device" : "same-device") << ", valid " << out->issued_at
314 << ".." << out->expires_at << ", params " << std::string(reinterpret_cast<const char *>(out->params), out->params_len) << "\n";
315 return st == CKEY_OK ? 0 : 1;
316 }
317 std::cerr << "usage: ckey action (keygen | public-key | sign | verify) ...\n";
318 return 2;
319}
320
321int main(int argc, char **argv) {
322 if (argc >= 2 && std::string(argv[1]) == "encode-scalable") return encode_scalable(argc, argv);
323 if (argc >= 2 && std::string(argv[1]) == "print-sheet") return print_sheet(argc, argv);
324 if (argc >= 2 && std::string(argv[1]) == "action") return safe_action(argc, argv);
325 const bool tierOption = argc == 8 && std::string(argv[2]) == "--tier";
326 const int modeIndex = tierOption ? 4 : 2;
327 const int outIndex = tierOption ? 6 : 4;
328 if ((argc != 6 && !tierOption) || (std::string(argv[modeIndex]) != "--hex" && std::string(argv[modeIndex]) != "--utf8") || std::string(argv[outIndex]) != "--out") {
329 std::cerr << "usage:\n"
330 << " ckey encode-scalable (--utf8 <text> | --hex <hex>) --out <file.svg> [--version 1-31] [--level L|M|Q|H]\n"
331 << " ckey encode --hex <32-hex-chars> --out <locator.svg>\n"
332 << " ckey encode-text --utf8 <up-to-200-byte-text> --out <text.svg>\n"
333 << " ckey encode-text --hex <up-to-400-hex-chars> --out <text.svg>\n"
334 << " ckey encode-extended --utf8 <up-to-2048-byte-text> --out <extended.svg>\n"
335 << " ckey encode-extended --tier <256|384|512|768|1024|1536|2048> --utf8 <text> --out <extended.svg>\n"
336 << " ckey encode-extended --hex <up-to-4096-hex-chars> --out <extended.svg>\n"
337 << " ckey action (keygen | public-key | sign | verify) ... Safe Actions for issuers\n";
338 return 2;
339 }
340 const std::string command = argv[1], mode = argv[modeIndex], value = argv[modeIndex+1], output = argv[outIndex+1];
341 if (tierOption && command != "encode-extended") { std::cerr << "--tier applies only to encode-extended\n"; return 2; }
342 ckey_extended_tier requestedTier = CKEY_EXTENDED_TIER_AUTO;
343 if (tierOption) {
344 char *end = nullptr;
345 const long parsed = std::strtol(argv[3], &end, 10);
346 if (!end || *end != '\0') { std::cerr << "invalid capacity tier\n"; return 2; }
347 switch (parsed) {
348 case 256: requestedTier=CKEY_EXTENDED_TIER_256; break;
349 case 384: requestedTier=CKEY_EXTENDED_TIER_384; break;
350 case 512: requestedTier=CKEY_EXTENDED_TIER_512; break;
351 case 768: requestedTier=CKEY_EXTENDED_TIER_768; break;
352 case 1024: requestedTier=CKEY_EXTENDED_TIER_1024; break;
353 case 1536: requestedTier=CKEY_EXTENDED_TIER_1536; break;
354 case 2048: requestedTier=CKEY_EXTENDED_TIER_2048; break;
355 default: std::cerr << "unsupported capacity tier\n"; return 2;
356 }
357 }
358 ckey_diagnostics diag{};
359 std::vector<uint8_t> payload;
360 if (mode == "--hex") {
361 if (!from_hex(value, payload)) { std::cerr << "hex input must be an even number of hexadecimal digits\n"; return 2; }
362 } else payload.assign(value.begin(), value.end());
363 if (command == "encode") {
364 if (mode != "--hex" || payload.size() != CKEY_LOCATOR_PAYLOAD_BYTES) { std::cerr << "locator input must be exactly 16 bytes encoded as 32 hexadecimal characters\n"; return 2; }
365 ckey_symbol symbol{};
366 if (ckey_encode_locator(payload.data(), payload.size(), &symbol, &diag) != CKEY_OK) { std::cerr << diag.message << '\n'; return 1; }
367 size_t needed = 0; ckey_render_svg(&symbol, 16, nullptr, 0, &needed);
368 std::string svg(needed, '\0');
369 if (ckey_render_svg(&symbol, 16, svg.data(), svg.size(), &needed) != CKEY_OK || !write_svg(output, svg)) { std::cerr << "SVG rendering failed\n"; return 1; }
370 std::cout << diag.message << "; profile=1; format=0." << CKEY_FORMAT_VERSION << ".0; payload-bytes=16; svg=" << output << '\n';
371 return 0;
372 }
373 if (command == "encode-text") {
374 if (payload.empty() || payload.size() > CKEY_TEXT_PAYLOAD_MAX_BYTES) { std::cerr << "text input must contain 1 to 200 UTF-8 bytes\n"; return 2; }
375 ckey_text_symbol symbol{};
376 if (ckey_encode_text(payload.data(), payload.size(), &symbol, &diag) != CKEY_OK) { std::cerr << diag.message << '\n'; return 1; }
377 size_t needed = 0; ckey_render_text_svg(&symbol, 16, nullptr, 0, &needed);
378 std::string svg(needed, '\0');
379 if (ckey_render_text_svg(&symbol, 16, svg.data(), svg.size(), &needed) != CKEY_OK || !write_svg(output, svg)) { std::cerr << "SVG rendering failed\n"; return 1; }
380 std::cout << diag.message << "; profile=2; format=0." << CKEY_TEXT_FORMAT_VERSION << ".0; payload-bytes=" << payload.size() << "; codeword-bytes=255; svg=" << output << '\n';
381 return 0;
382 }
383 if (command == "encode-extended") {
384 if (payload.empty() || payload.size() > CKEY_EXTENDED_PAYLOAD_MAX_BYTES) { std::cerr << "extended input must contain 1 to 2048 bytes\n"; return 2; }
385 ckey_extended_symbol symbol{};
386 const ckey_status encoded = ckey_encode_extended_tiered(payload.data(), payload.size(), requestedTier, &symbol, &diag);
387 if (encoded != CKEY_OK) { std::cerr << diag.message << '\n'; return 1; }
388 size_t needed = 0; ckey_render_extended_svg(&symbol, 16, nullptr, 0, &needed);
389 std::string svg(needed, '\0');
390 if (ckey_render_extended_svg(&symbol, 16, svg.data(), svg.size(), &needed) != CKEY_OK || !write_svg(output, svg)) { std::cerr << "extended SVG rendering failed\n"; return 1; }
391 std::cout << diag.message << "; profile=3; format=0." << CKEY_EXTENDED_FORMAT_VERSION << ".0; payload-bytes=" << payload.size() << "; codeword-bytes=" << CKEY_EXTENDED_CODEWORD_BYTES << "; svg=" << output << '\n';
392 return 0;
393 }
394 std::cerr << "unknown command\n"; return 2;
395}