├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── data └── voters.json ├── ec ├── Cargo.toml ├── README.md ├── ec_private.pem ├── ec_public.pem ├── src │ ├── election.rs │ ├── main.rs │ ├── types.rs │ └── util.rs └── voters_pubkeys.json ├── keys └── negrunch.asc ├── logo.png ├── manifest.txt ├── manifest.txt.sig └── voter ├── Cargo.toml ├── README.md ├── ec_public.pem ├── settings.toml └── src ├── election.rs ├── main.rs ├── settings.rs └── util.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | app.log -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aead" 22 | version = "0.5.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" 25 | dependencies = [ 26 | "crypto-common", 27 | "generic-array", 28 | ] 29 | 30 | [[package]] 31 | name = "aho-corasick" 32 | version = "1.1.3" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 35 | dependencies = [ 36 | "memchr", 37 | ] 38 | 39 | [[package]] 40 | name = "allocator-api2" 41 | version = "0.2.21" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 44 | 45 | [[package]] 46 | name = "android-tzdata" 47 | version = "0.1.1" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 50 | 51 | [[package]] 52 | name = "android_system_properties" 53 | version = "0.1.5" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 56 | dependencies = [ 57 | "libc", 58 | ] 59 | 60 | [[package]] 61 | name = "anyhow" 62 | version = "1.0.98" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 65 | 66 | [[package]] 67 | name = "arraydeque" 68 | version = "0.5.1" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" 71 | 72 | [[package]] 73 | name = "arrayvec" 74 | version = "0.7.6" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 77 | 78 | [[package]] 79 | name = "async-trait" 80 | version = "0.1.88" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" 83 | dependencies = [ 84 | "proc-macro2", 85 | "quote", 86 | "syn", 87 | ] 88 | 89 | [[package]] 90 | name = "async-utility" 91 | version = "0.3.1" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "a34a3b57207a7a1007832416c3e4862378c8451b4e8e093e436f48c2d3d2c151" 94 | dependencies = [ 95 | "futures-util", 96 | "gloo-timers", 97 | "tokio", 98 | "wasm-bindgen-futures", 99 | ] 100 | 101 | [[package]] 102 | name = "async-wsocket" 103 | version = "0.13.1" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "9a7d8c7d34a225ba919dd9ba44d4b9106d20142da545e086be8ae21d1897e043" 106 | dependencies = [ 107 | "async-utility", 108 | "futures", 109 | "futures-util", 110 | "js-sys", 111 | "tokio", 112 | "tokio-rustls", 113 | "tokio-socks", 114 | "tokio-tungstenite", 115 | "url", 116 | "wasm-bindgen", 117 | "web-sys", 118 | ] 119 | 120 | [[package]] 121 | name = "atoi" 122 | version = "2.0.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" 125 | dependencies = [ 126 | "num-traits", 127 | ] 128 | 129 | [[package]] 130 | name = "atomic-destructor" 131 | version = "0.3.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "ef49f5882e4b6afaac09ad239a4f8c70a24b8f2b0897edb1f706008efd109cf4" 134 | 135 | [[package]] 136 | name = "autocfg" 137 | version = "1.4.0" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 140 | 141 | [[package]] 142 | name = "backtrace" 143 | version = "0.3.74" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 146 | dependencies = [ 147 | "addr2line", 148 | "cfg-if", 149 | "libc", 150 | "miniz_oxide", 151 | "object", 152 | "rustc-demangle", 153 | "windows-targets 0.52.6", 154 | ] 155 | 156 | [[package]] 157 | name = "base64" 158 | version = "0.21.7" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 161 | 162 | [[package]] 163 | name = "base64" 164 | version = "0.22.1" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 167 | 168 | [[package]] 169 | name = "base64ct" 170 | version = "1.7.3" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" 173 | 174 | [[package]] 175 | name = "bech32" 176 | version = "0.11.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" 179 | 180 | [[package]] 181 | name = "bip39" 182 | version = "2.1.0" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387" 185 | dependencies = [ 186 | "bitcoin_hashes 0.13.0", 187 | "serde", 188 | "unicode-normalization", 189 | ] 190 | 191 | [[package]] 192 | name = "bitcoin-internals" 193 | version = "0.2.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" 196 | 197 | [[package]] 198 | name = "bitcoin-io" 199 | version = "0.1.3" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" 202 | 203 | [[package]] 204 | name = "bitcoin_hashes" 205 | version = "0.13.0" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" 208 | dependencies = [ 209 | "bitcoin-internals", 210 | "hex-conservative 0.1.2", 211 | ] 212 | 213 | [[package]] 214 | name = "bitcoin_hashes" 215 | version = "0.14.0" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" 218 | dependencies = [ 219 | "bitcoin-io", 220 | "hex-conservative 0.2.1", 221 | "serde", 222 | ] 223 | 224 | [[package]] 225 | name = "bitflags" 226 | version = "2.9.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 229 | dependencies = [ 230 | "serde", 231 | ] 232 | 233 | [[package]] 234 | name = "blind-rsa-signatures" 235 | version = "0.15.2" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "730c6814c82af047b85a3edc88a3ed0129c6046a6488188ccdf560384643e48f" 238 | dependencies = [ 239 | "derive-new", 240 | "derive_more", 241 | "digest", 242 | "hmac-sha256", 243 | "hmac-sha512", 244 | "num-integer", 245 | "num-traits", 246 | "rand 0.8.5", 247 | "rsa 0.8.2", 248 | "serde", 249 | ] 250 | 251 | [[package]] 252 | name = "block-buffer" 253 | version = "0.10.4" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 256 | dependencies = [ 257 | "generic-array", 258 | ] 259 | 260 | [[package]] 261 | name = "block-padding" 262 | version = "0.3.3" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" 265 | dependencies = [ 266 | "generic-array", 267 | ] 268 | 269 | [[package]] 270 | name = "bumpalo" 271 | version = "3.17.0" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 274 | 275 | [[package]] 276 | name = "byteorder" 277 | version = "1.5.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 280 | 281 | [[package]] 282 | name = "bytes" 283 | version = "1.10.1" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 286 | 287 | [[package]] 288 | name = "cassowary" 289 | version = "0.3.0" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 292 | 293 | [[package]] 294 | name = "castaway" 295 | version = "0.2.3" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" 298 | dependencies = [ 299 | "rustversion", 300 | ] 301 | 302 | [[package]] 303 | name = "cbc" 304 | version = "0.1.2" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" 307 | dependencies = [ 308 | "cipher", 309 | ] 310 | 311 | [[package]] 312 | name = "cc" 313 | version = "1.2.19" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" 316 | dependencies = [ 317 | "shlex", 318 | ] 319 | 320 | [[package]] 321 | name = "cfg-if" 322 | version = "1.0.0" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 325 | 326 | [[package]] 327 | name = "chacha20" 328 | version = "0.9.1" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" 331 | dependencies = [ 332 | "cfg-if", 333 | "cipher", 334 | "cpufeatures", 335 | ] 336 | 337 | [[package]] 338 | name = "chacha20poly1305" 339 | version = "0.10.1" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" 342 | dependencies = [ 343 | "aead", 344 | "chacha20", 345 | "cipher", 346 | "poly1305", 347 | "zeroize", 348 | ] 349 | 350 | [[package]] 351 | name = "chrono" 352 | version = "0.4.40" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" 355 | dependencies = [ 356 | "android-tzdata", 357 | "iana-time-zone", 358 | "js-sys", 359 | "num-traits", 360 | "wasm-bindgen", 361 | "windows-link", 362 | ] 363 | 364 | [[package]] 365 | name = "cipher" 366 | version = "0.4.4" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 369 | dependencies = [ 370 | "crypto-common", 371 | "inout", 372 | "zeroize", 373 | ] 374 | 375 | [[package]] 376 | name = "compact_str" 377 | version = "0.8.1" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" 380 | dependencies = [ 381 | "castaway", 382 | "cfg-if", 383 | "itoa", 384 | "rustversion", 385 | "ryu", 386 | "static_assertions", 387 | ] 388 | 389 | [[package]] 390 | name = "concurrent-queue" 391 | version = "2.5.0" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 394 | dependencies = [ 395 | "crossbeam-utils", 396 | ] 397 | 398 | [[package]] 399 | name = "config" 400 | version = "0.15.11" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "595aae20e65c3be792d05818e8c63025294ac3cb7e200f11459063a352a6ef80" 403 | dependencies = [ 404 | "async-trait", 405 | "convert_case 0.6.0", 406 | "json5", 407 | "pathdiff", 408 | "ron", 409 | "rust-ini", 410 | "serde", 411 | "serde_json", 412 | "toml", 413 | "winnow", 414 | "yaml-rust2", 415 | ] 416 | 417 | [[package]] 418 | name = "const-oid" 419 | version = "0.9.6" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 422 | 423 | [[package]] 424 | name = "const-random" 425 | version = "0.1.18" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" 428 | dependencies = [ 429 | "const-random-macro", 430 | ] 431 | 432 | [[package]] 433 | name = "const-random-macro" 434 | version = "0.1.16" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" 437 | dependencies = [ 438 | "getrandom 0.2.15", 439 | "once_cell", 440 | "tiny-keccak", 441 | ] 442 | 443 | [[package]] 444 | name = "convert_case" 445 | version = "0.6.0" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" 448 | dependencies = [ 449 | "unicode-segmentation", 450 | ] 451 | 452 | [[package]] 453 | name = "convert_case" 454 | version = "0.7.1" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" 457 | dependencies = [ 458 | "unicode-segmentation", 459 | ] 460 | 461 | [[package]] 462 | name = "core-foundation" 463 | version = "0.9.4" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 466 | dependencies = [ 467 | "core-foundation-sys", 468 | "libc", 469 | ] 470 | 471 | [[package]] 472 | name = "core-foundation-sys" 473 | version = "0.8.7" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 476 | 477 | [[package]] 478 | name = "cpufeatures" 479 | version = "0.2.17" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 482 | dependencies = [ 483 | "libc", 484 | ] 485 | 486 | [[package]] 487 | name = "crc" 488 | version = "3.2.1" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" 491 | dependencies = [ 492 | "crc-catalog", 493 | ] 494 | 495 | [[package]] 496 | name = "crc-catalog" 497 | version = "2.4.0" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" 500 | 501 | [[package]] 502 | name = "crossbeam-queue" 503 | version = "0.3.12" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" 506 | dependencies = [ 507 | "crossbeam-utils", 508 | ] 509 | 510 | [[package]] 511 | name = "crossbeam-utils" 512 | version = "0.8.21" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 515 | 516 | [[package]] 517 | name = "crossterm" 518 | version = "0.28.1" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 521 | dependencies = [ 522 | "bitflags", 523 | "crossterm_winapi", 524 | "mio", 525 | "parking_lot", 526 | "rustix 0.38.44", 527 | "signal-hook", 528 | "signal-hook-mio", 529 | "winapi", 530 | ] 531 | 532 | [[package]] 533 | name = "crossterm" 534 | version = "0.29.0" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" 537 | dependencies = [ 538 | "bitflags", 539 | "crossterm_winapi", 540 | "derive_more", 541 | "document-features", 542 | "futures-core", 543 | "mio", 544 | "parking_lot", 545 | "rustix 1.0.5", 546 | "signal-hook", 547 | "signal-hook-mio", 548 | "winapi", 549 | ] 550 | 551 | [[package]] 552 | name = "crossterm_winapi" 553 | version = "0.9.1" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 556 | dependencies = [ 557 | "winapi", 558 | ] 559 | 560 | [[package]] 561 | name = "crunchy" 562 | version = "0.2.3" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" 565 | 566 | [[package]] 567 | name = "crypto-common" 568 | version = "0.1.6" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 571 | dependencies = [ 572 | "generic-array", 573 | "rand_core 0.6.4", 574 | "typenum", 575 | ] 576 | 577 | [[package]] 578 | name = "darling" 579 | version = "0.20.11" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" 582 | dependencies = [ 583 | "darling_core", 584 | "darling_macro", 585 | ] 586 | 587 | [[package]] 588 | name = "darling_core" 589 | version = "0.20.11" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" 592 | dependencies = [ 593 | "fnv", 594 | "ident_case", 595 | "proc-macro2", 596 | "quote", 597 | "strsim", 598 | "syn", 599 | ] 600 | 601 | [[package]] 602 | name = "darling_macro" 603 | version = "0.20.11" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" 606 | dependencies = [ 607 | "darling_core", 608 | "quote", 609 | "syn", 610 | ] 611 | 612 | [[package]] 613 | name = "data-encoding" 614 | version = "2.9.0" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" 617 | 618 | [[package]] 619 | name = "der" 620 | version = "0.6.1" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" 623 | dependencies = [ 624 | "const-oid", 625 | "pem-rfc7468 0.6.0", 626 | "zeroize", 627 | ] 628 | 629 | [[package]] 630 | name = "der" 631 | version = "0.7.10" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" 634 | dependencies = [ 635 | "const-oid", 636 | "pem-rfc7468 0.7.0", 637 | "zeroize", 638 | ] 639 | 640 | [[package]] 641 | name = "derive-new" 642 | version = "0.7.0" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc" 645 | dependencies = [ 646 | "proc-macro2", 647 | "quote", 648 | "syn", 649 | ] 650 | 651 | [[package]] 652 | name = "derive_more" 653 | version = "2.0.1" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" 656 | dependencies = [ 657 | "derive_more-impl", 658 | ] 659 | 660 | [[package]] 661 | name = "derive_more-impl" 662 | version = "2.0.1" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" 665 | dependencies = [ 666 | "convert_case 0.7.1", 667 | "proc-macro2", 668 | "quote", 669 | "syn", 670 | "unicode-xid", 671 | ] 672 | 673 | [[package]] 674 | name = "digest" 675 | version = "0.10.7" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 678 | dependencies = [ 679 | "block-buffer", 680 | "const-oid", 681 | "crypto-common", 682 | "subtle", 683 | ] 684 | 685 | [[package]] 686 | name = "dirs" 687 | version = "6.0.0" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" 690 | dependencies = [ 691 | "dirs-sys", 692 | ] 693 | 694 | [[package]] 695 | name = "dirs-sys" 696 | version = "0.5.0" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" 699 | dependencies = [ 700 | "libc", 701 | "option-ext", 702 | "redox_users", 703 | "windows-sys 0.59.0", 704 | ] 705 | 706 | [[package]] 707 | name = "displaydoc" 708 | version = "0.2.5" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 711 | dependencies = [ 712 | "proc-macro2", 713 | "quote", 714 | "syn", 715 | ] 716 | 717 | [[package]] 718 | name = "dlv-list" 719 | version = "0.5.2" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" 722 | dependencies = [ 723 | "const-random", 724 | ] 725 | 726 | [[package]] 727 | name = "document-features" 728 | version = "0.2.11" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" 731 | dependencies = [ 732 | "litrs", 733 | ] 734 | 735 | [[package]] 736 | name = "dotenvy" 737 | version = "0.15.7" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" 740 | 741 | [[package]] 742 | name = "ec" 743 | version = "0.1.1" 744 | dependencies = [ 745 | "anyhow", 746 | "base64 0.22.1", 747 | "blind-rsa-signatures", 748 | "chrono", 749 | "fern", 750 | "log", 751 | "nanoid", 752 | "nostr-sdk", 753 | "num-bigint-dig", 754 | "rand 0.8.5", 755 | "serde", 756 | "serde_json", 757 | "sha2", 758 | "tokio", 759 | "tracing-subscriber", 760 | ] 761 | 762 | [[package]] 763 | name = "either" 764 | version = "1.15.0" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 767 | dependencies = [ 768 | "serde", 769 | ] 770 | 771 | [[package]] 772 | name = "encoding_rs" 773 | version = "0.8.35" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 776 | dependencies = [ 777 | "cfg-if", 778 | ] 779 | 780 | [[package]] 781 | name = "equivalent" 782 | version = "1.0.2" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 785 | 786 | [[package]] 787 | name = "errno" 788 | version = "0.3.11" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" 791 | dependencies = [ 792 | "libc", 793 | "windows-sys 0.59.0", 794 | ] 795 | 796 | [[package]] 797 | name = "etcetera" 798 | version = "0.8.0" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" 801 | dependencies = [ 802 | "cfg-if", 803 | "home", 804 | "windows-sys 0.48.0", 805 | ] 806 | 807 | [[package]] 808 | name = "event-listener" 809 | version = "5.4.0" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" 812 | dependencies = [ 813 | "concurrent-queue", 814 | "parking", 815 | "pin-project-lite", 816 | ] 817 | 818 | [[package]] 819 | name = "fastrand" 820 | version = "2.3.0" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 823 | 824 | [[package]] 825 | name = "fern" 826 | version = "0.7.1" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29" 829 | dependencies = [ 830 | "log", 831 | ] 832 | 833 | [[package]] 834 | name = "flume" 835 | version = "0.11.1" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" 838 | dependencies = [ 839 | "futures-core", 840 | "futures-sink", 841 | "spin", 842 | ] 843 | 844 | [[package]] 845 | name = "fnv" 846 | version = "1.0.7" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 849 | 850 | [[package]] 851 | name = "foldhash" 852 | version = "0.1.5" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 855 | 856 | [[package]] 857 | name = "foreign-types" 858 | version = "0.3.2" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 861 | dependencies = [ 862 | "foreign-types-shared", 863 | ] 864 | 865 | [[package]] 866 | name = "foreign-types-shared" 867 | version = "0.1.1" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 870 | 871 | [[package]] 872 | name = "form_urlencoded" 873 | version = "1.2.1" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 876 | dependencies = [ 877 | "percent-encoding", 878 | ] 879 | 880 | [[package]] 881 | name = "futures" 882 | version = "0.3.31" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 885 | dependencies = [ 886 | "futures-channel", 887 | "futures-core", 888 | "futures-executor", 889 | "futures-io", 890 | "futures-sink", 891 | "futures-task", 892 | "futures-util", 893 | ] 894 | 895 | [[package]] 896 | name = "futures-channel" 897 | version = "0.3.31" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 900 | dependencies = [ 901 | "futures-core", 902 | "futures-sink", 903 | ] 904 | 905 | [[package]] 906 | name = "futures-core" 907 | version = "0.3.31" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 910 | 911 | [[package]] 912 | name = "futures-executor" 913 | version = "0.3.31" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 916 | dependencies = [ 917 | "futures-core", 918 | "futures-task", 919 | "futures-util", 920 | ] 921 | 922 | [[package]] 923 | name = "futures-intrusive" 924 | version = "0.5.0" 925 | source = "registry+https://github.com/rust-lang/crates.io-index" 926 | checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" 927 | dependencies = [ 928 | "futures-core", 929 | "lock_api", 930 | "parking_lot", 931 | ] 932 | 933 | [[package]] 934 | name = "futures-io" 935 | version = "0.3.31" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 938 | 939 | [[package]] 940 | name = "futures-macro" 941 | version = "0.3.31" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 944 | dependencies = [ 945 | "proc-macro2", 946 | "quote", 947 | "syn", 948 | ] 949 | 950 | [[package]] 951 | name = "futures-sink" 952 | version = "0.3.31" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 955 | 956 | [[package]] 957 | name = "futures-task" 958 | version = "0.3.31" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 961 | 962 | [[package]] 963 | name = "futures-util" 964 | version = "0.3.31" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 967 | dependencies = [ 968 | "futures-channel", 969 | "futures-core", 970 | "futures-io", 971 | "futures-macro", 972 | "futures-sink", 973 | "futures-task", 974 | "memchr", 975 | "pin-project-lite", 976 | "pin-utils", 977 | "slab", 978 | ] 979 | 980 | [[package]] 981 | name = "generic-array" 982 | version = "0.14.7" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 985 | dependencies = [ 986 | "typenum", 987 | "version_check", 988 | ] 989 | 990 | [[package]] 991 | name = "getrandom" 992 | version = "0.2.15" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 995 | dependencies = [ 996 | "cfg-if", 997 | "js-sys", 998 | "libc", 999 | "wasi 0.11.0+wasi-snapshot-preview1", 1000 | "wasm-bindgen", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "getrandom" 1005 | version = "0.3.2" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" 1008 | dependencies = [ 1009 | "cfg-if", 1010 | "libc", 1011 | "r-efi", 1012 | "wasi 0.14.2+wasi-0.2.4", 1013 | ] 1014 | 1015 | [[package]] 1016 | name = "gimli" 1017 | version = "0.31.1" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 1020 | 1021 | [[package]] 1022 | name = "gloo-timers" 1023 | version = "0.3.0" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" 1026 | dependencies = [ 1027 | "futures-channel", 1028 | "futures-core", 1029 | "js-sys", 1030 | "wasm-bindgen", 1031 | ] 1032 | 1033 | [[package]] 1034 | name = "hashbrown" 1035 | version = "0.14.5" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 1038 | 1039 | [[package]] 1040 | name = "hashbrown" 1041 | version = "0.15.2" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 1044 | dependencies = [ 1045 | "allocator-api2", 1046 | "equivalent", 1047 | "foldhash", 1048 | ] 1049 | 1050 | [[package]] 1051 | name = "hashlink" 1052 | version = "0.10.0" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" 1055 | dependencies = [ 1056 | "hashbrown 0.15.2", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "heck" 1061 | version = "0.5.0" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 1064 | 1065 | [[package]] 1066 | name = "hex" 1067 | version = "0.4.3" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 1070 | 1071 | [[package]] 1072 | name = "hex-conservative" 1073 | version = "0.1.2" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" 1076 | 1077 | [[package]] 1078 | name = "hex-conservative" 1079 | version = "0.2.1" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" 1082 | dependencies = [ 1083 | "arrayvec", 1084 | ] 1085 | 1086 | [[package]] 1087 | name = "hkdf" 1088 | version = "0.12.4" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" 1091 | dependencies = [ 1092 | "hmac", 1093 | ] 1094 | 1095 | [[package]] 1096 | name = "hmac" 1097 | version = "0.12.1" 1098 | source = "registry+https://github.com/rust-lang/crates.io-index" 1099 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 1100 | dependencies = [ 1101 | "digest", 1102 | ] 1103 | 1104 | [[package]] 1105 | name = "hmac-sha256" 1106 | version = "1.1.8" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "4a8575493d277c9092b988c780c94737fb9fd8651a1001e16bee3eccfc1baedb" 1109 | dependencies = [ 1110 | "digest", 1111 | ] 1112 | 1113 | [[package]] 1114 | name = "hmac-sha512" 1115 | version = "1.1.6" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "b0b3a0f572aa8389d325f5852b9e0a333a15b0f86ecccbb3fdb6e97cd86dc67c" 1118 | dependencies = [ 1119 | "digest", 1120 | ] 1121 | 1122 | [[package]] 1123 | name = "home" 1124 | version = "0.5.11" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 1127 | dependencies = [ 1128 | "windows-sys 0.59.0", 1129 | ] 1130 | 1131 | [[package]] 1132 | name = "http" 1133 | version = "1.3.1" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 1136 | dependencies = [ 1137 | "bytes", 1138 | "fnv", 1139 | "itoa", 1140 | ] 1141 | 1142 | [[package]] 1143 | name = "httparse" 1144 | version = "1.10.1" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 1147 | 1148 | [[package]] 1149 | name = "iana-time-zone" 1150 | version = "0.1.63" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" 1153 | dependencies = [ 1154 | "android_system_properties", 1155 | "core-foundation-sys", 1156 | "iana-time-zone-haiku", 1157 | "js-sys", 1158 | "log", 1159 | "wasm-bindgen", 1160 | "windows-core", 1161 | ] 1162 | 1163 | [[package]] 1164 | name = "iana-time-zone-haiku" 1165 | version = "0.1.2" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 1168 | dependencies = [ 1169 | "cc", 1170 | ] 1171 | 1172 | [[package]] 1173 | name = "icu_collections" 1174 | version = "1.5.0" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 1177 | dependencies = [ 1178 | "displaydoc", 1179 | "yoke", 1180 | "zerofrom", 1181 | "zerovec", 1182 | ] 1183 | 1184 | [[package]] 1185 | name = "icu_locid" 1186 | version = "1.5.0" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 1189 | dependencies = [ 1190 | "displaydoc", 1191 | "litemap", 1192 | "tinystr", 1193 | "writeable", 1194 | "zerovec", 1195 | ] 1196 | 1197 | [[package]] 1198 | name = "icu_locid_transform" 1199 | version = "1.5.0" 1200 | source = "registry+https://github.com/rust-lang/crates.io-index" 1201 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 1202 | dependencies = [ 1203 | "displaydoc", 1204 | "icu_locid", 1205 | "icu_locid_transform_data", 1206 | "icu_provider", 1207 | "tinystr", 1208 | "zerovec", 1209 | ] 1210 | 1211 | [[package]] 1212 | name = "icu_locid_transform_data" 1213 | version = "1.5.1" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" 1216 | 1217 | [[package]] 1218 | name = "icu_normalizer" 1219 | version = "1.5.0" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 1222 | dependencies = [ 1223 | "displaydoc", 1224 | "icu_collections", 1225 | "icu_normalizer_data", 1226 | "icu_properties", 1227 | "icu_provider", 1228 | "smallvec", 1229 | "utf16_iter", 1230 | "utf8_iter", 1231 | "write16", 1232 | "zerovec", 1233 | ] 1234 | 1235 | [[package]] 1236 | name = "icu_normalizer_data" 1237 | version = "1.5.1" 1238 | source = "registry+https://github.com/rust-lang/crates.io-index" 1239 | checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" 1240 | 1241 | [[package]] 1242 | name = "icu_properties" 1243 | version = "1.5.1" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 1246 | dependencies = [ 1247 | "displaydoc", 1248 | "icu_collections", 1249 | "icu_locid_transform", 1250 | "icu_properties_data", 1251 | "icu_provider", 1252 | "tinystr", 1253 | "zerovec", 1254 | ] 1255 | 1256 | [[package]] 1257 | name = "icu_properties_data" 1258 | version = "1.5.1" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" 1261 | 1262 | [[package]] 1263 | name = "icu_provider" 1264 | version = "1.5.0" 1265 | source = "registry+https://github.com/rust-lang/crates.io-index" 1266 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 1267 | dependencies = [ 1268 | "displaydoc", 1269 | "icu_locid", 1270 | "icu_provider_macros", 1271 | "stable_deref_trait", 1272 | "tinystr", 1273 | "writeable", 1274 | "yoke", 1275 | "zerofrom", 1276 | "zerovec", 1277 | ] 1278 | 1279 | [[package]] 1280 | name = "icu_provider_macros" 1281 | version = "1.5.0" 1282 | source = "registry+https://github.com/rust-lang/crates.io-index" 1283 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 1284 | dependencies = [ 1285 | "proc-macro2", 1286 | "quote", 1287 | "syn", 1288 | ] 1289 | 1290 | [[package]] 1291 | name = "ident_case" 1292 | version = "1.0.1" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 1295 | 1296 | [[package]] 1297 | name = "idna" 1298 | version = "1.0.3" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 1301 | dependencies = [ 1302 | "idna_adapter", 1303 | "smallvec", 1304 | "utf8_iter", 1305 | ] 1306 | 1307 | [[package]] 1308 | name = "idna_adapter" 1309 | version = "1.2.0" 1310 | source = "registry+https://github.com/rust-lang/crates.io-index" 1311 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 1312 | dependencies = [ 1313 | "icu_normalizer", 1314 | "icu_properties", 1315 | ] 1316 | 1317 | [[package]] 1318 | name = "indexmap" 1319 | version = "2.9.0" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 1322 | dependencies = [ 1323 | "equivalent", 1324 | "hashbrown 0.15.2", 1325 | ] 1326 | 1327 | [[package]] 1328 | name = "indoc" 1329 | version = "2.0.6" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" 1332 | 1333 | [[package]] 1334 | name = "inout" 1335 | version = "0.1.4" 1336 | source = "registry+https://github.com/rust-lang/crates.io-index" 1337 | checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" 1338 | dependencies = [ 1339 | "block-padding", 1340 | "generic-array", 1341 | ] 1342 | 1343 | [[package]] 1344 | name = "instability" 1345 | version = "0.3.7" 1346 | source = "registry+https://github.com/rust-lang/crates.io-index" 1347 | checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d" 1348 | dependencies = [ 1349 | "darling", 1350 | "indoc", 1351 | "proc-macro2", 1352 | "quote", 1353 | "syn", 1354 | ] 1355 | 1356 | [[package]] 1357 | name = "instant" 1358 | version = "0.1.13" 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" 1360 | checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" 1361 | dependencies = [ 1362 | "cfg-if", 1363 | "js-sys", 1364 | "wasm-bindgen", 1365 | "web-sys", 1366 | ] 1367 | 1368 | [[package]] 1369 | name = "itertools" 1370 | version = "0.13.0" 1371 | source = "registry+https://github.com/rust-lang/crates.io-index" 1372 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 1373 | dependencies = [ 1374 | "either", 1375 | ] 1376 | 1377 | [[package]] 1378 | name = "itoa" 1379 | version = "1.0.15" 1380 | source = "registry+https://github.com/rust-lang/crates.io-index" 1381 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1382 | 1383 | [[package]] 1384 | name = "js-sys" 1385 | version = "0.3.77" 1386 | source = "registry+https://github.com/rust-lang/crates.io-index" 1387 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 1388 | dependencies = [ 1389 | "once_cell", 1390 | "wasm-bindgen", 1391 | ] 1392 | 1393 | [[package]] 1394 | name = "json5" 1395 | version = "0.4.1" 1396 | source = "registry+https://github.com/rust-lang/crates.io-index" 1397 | checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" 1398 | dependencies = [ 1399 | "pest", 1400 | "pest_derive", 1401 | "serde", 1402 | ] 1403 | 1404 | [[package]] 1405 | name = "lazy_static" 1406 | version = "1.5.0" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1409 | dependencies = [ 1410 | "spin", 1411 | ] 1412 | 1413 | [[package]] 1414 | name = "libc" 1415 | version = "0.2.172" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 1418 | 1419 | [[package]] 1420 | name = "libm" 1421 | version = "0.2.11" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" 1424 | 1425 | [[package]] 1426 | name = "libredox" 1427 | version = "0.1.3" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 1430 | dependencies = [ 1431 | "bitflags", 1432 | "libc", 1433 | ] 1434 | 1435 | [[package]] 1436 | name = "libsqlite3-sys" 1437 | version = "0.30.1" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" 1440 | dependencies = [ 1441 | "cc", 1442 | "pkg-config", 1443 | "vcpkg", 1444 | ] 1445 | 1446 | [[package]] 1447 | name = "linux-raw-sys" 1448 | version = "0.4.15" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 1451 | 1452 | [[package]] 1453 | name = "linux-raw-sys" 1454 | version = "0.9.4" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 1457 | 1458 | [[package]] 1459 | name = "litemap" 1460 | version = "0.7.5" 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" 1462 | checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" 1463 | 1464 | [[package]] 1465 | name = "litrs" 1466 | version = "0.4.1" 1467 | source = "registry+https://github.com/rust-lang/crates.io-index" 1468 | checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" 1469 | 1470 | [[package]] 1471 | name = "lock_api" 1472 | version = "0.4.12" 1473 | source = "registry+https://github.com/rust-lang/crates.io-index" 1474 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 1475 | dependencies = [ 1476 | "autocfg", 1477 | "scopeguard", 1478 | ] 1479 | 1480 | [[package]] 1481 | name = "log" 1482 | version = "0.4.27" 1483 | source = "registry+https://github.com/rust-lang/crates.io-index" 1484 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 1485 | 1486 | [[package]] 1487 | name = "lru" 1488 | version = "0.12.5" 1489 | source = "registry+https://github.com/rust-lang/crates.io-index" 1490 | checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" 1491 | dependencies = [ 1492 | "hashbrown 0.15.2", 1493 | ] 1494 | 1495 | [[package]] 1496 | name = "lru" 1497 | version = "0.13.0" 1498 | source = "registry+https://github.com/rust-lang/crates.io-index" 1499 | checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" 1500 | 1501 | [[package]] 1502 | name = "md-5" 1503 | version = "0.10.6" 1504 | source = "registry+https://github.com/rust-lang/crates.io-index" 1505 | checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" 1506 | dependencies = [ 1507 | "cfg-if", 1508 | "digest", 1509 | ] 1510 | 1511 | [[package]] 1512 | name = "memchr" 1513 | version = "2.7.4" 1514 | source = "registry+https://github.com/rust-lang/crates.io-index" 1515 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1516 | 1517 | [[package]] 1518 | name = "miniz_oxide" 1519 | version = "0.8.8" 1520 | source = "registry+https://github.com/rust-lang/crates.io-index" 1521 | checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" 1522 | dependencies = [ 1523 | "adler2", 1524 | ] 1525 | 1526 | [[package]] 1527 | name = "mio" 1528 | version = "1.0.3" 1529 | source = "registry+https://github.com/rust-lang/crates.io-index" 1530 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 1531 | dependencies = [ 1532 | "libc", 1533 | "log", 1534 | "wasi 0.11.0+wasi-snapshot-preview1", 1535 | "windows-sys 0.52.0", 1536 | ] 1537 | 1538 | [[package]] 1539 | name = "nanoid" 1540 | version = "0.4.0" 1541 | source = "registry+https://github.com/rust-lang/crates.io-index" 1542 | checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" 1543 | dependencies = [ 1544 | "rand 0.8.5", 1545 | ] 1546 | 1547 | [[package]] 1548 | name = "native-tls" 1549 | version = "0.2.14" 1550 | source = "registry+https://github.com/rust-lang/crates.io-index" 1551 | checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" 1552 | dependencies = [ 1553 | "libc", 1554 | "log", 1555 | "openssl", 1556 | "openssl-probe", 1557 | "openssl-sys", 1558 | "schannel", 1559 | "security-framework", 1560 | "security-framework-sys", 1561 | "tempfile", 1562 | ] 1563 | 1564 | [[package]] 1565 | name = "negentropy" 1566 | version = "0.3.1" 1567 | source = "registry+https://github.com/rust-lang/crates.io-index" 1568 | checksum = "e664971378a3987224f7a0e10059782035e89899ae403718ee07de85bec42afe" 1569 | 1570 | [[package]] 1571 | name = "negentropy" 1572 | version = "0.5.0" 1573 | source = "registry+https://github.com/rust-lang/crates.io-index" 1574 | checksum = "f0efe882e02d206d8d279c20eb40e03baf7cb5136a1476dc084a324fbc3ec42d" 1575 | 1576 | [[package]] 1577 | name = "nostr" 1578 | version = "0.41.0" 1579 | source = "registry+https://github.com/rust-lang/crates.io-index" 1580 | checksum = "95d3f2f0d564cce3ebe09bb1343af67d88c60108cedc518a307dfdf6e3f503b6" 1581 | dependencies = [ 1582 | "base64 0.22.1", 1583 | "bech32", 1584 | "bip39", 1585 | "bitcoin_hashes 0.14.0", 1586 | "cbc", 1587 | "chacha20", 1588 | "chacha20poly1305", 1589 | "getrandom 0.2.15", 1590 | "instant", 1591 | "regex", 1592 | "scrypt", 1593 | "secp256k1", 1594 | "serde", 1595 | "serde_json", 1596 | "unicode-normalization", 1597 | "url", 1598 | ] 1599 | 1600 | [[package]] 1601 | name = "nostr-database" 1602 | version = "0.41.0" 1603 | source = "registry+https://github.com/rust-lang/crates.io-index" 1604 | checksum = "de6dc456a11d26f99a932b6531d94c20cb2e274ada6eab1d2438e2fb9c944af5" 1605 | dependencies = [ 1606 | "lru 0.13.0", 1607 | "nostr", 1608 | "tokio", 1609 | ] 1610 | 1611 | [[package]] 1612 | name = "nostr-relay-pool" 1613 | version = "0.41.0" 1614 | source = "registry+https://github.com/rust-lang/crates.io-index" 1615 | checksum = "23994b7a613540de50d0b6ac5fcdfeb65d814b0dc5630875d3e1d3e8f05e8c7c" 1616 | dependencies = [ 1617 | "async-utility", 1618 | "async-wsocket", 1619 | "atomic-destructor", 1620 | "lru 0.13.0", 1621 | "negentropy 0.3.1", 1622 | "negentropy 0.5.0", 1623 | "nostr", 1624 | "nostr-database", 1625 | "tokio", 1626 | "tracing", 1627 | ] 1628 | 1629 | [[package]] 1630 | name = "nostr-sdk" 1631 | version = "0.41.0" 1632 | source = "registry+https://github.com/rust-lang/crates.io-index" 1633 | checksum = "58d201c49818ef560a67f9c26c415007da38b007943e7d1644ac55aa98c55a42" 1634 | dependencies = [ 1635 | "async-utility", 1636 | "nostr", 1637 | "nostr-database", 1638 | "nostr-relay-pool", 1639 | "tokio", 1640 | "tracing", 1641 | ] 1642 | 1643 | [[package]] 1644 | name = "nu-ansi-term" 1645 | version = "0.46.0" 1646 | source = "registry+https://github.com/rust-lang/crates.io-index" 1647 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 1648 | dependencies = [ 1649 | "overload", 1650 | "winapi", 1651 | ] 1652 | 1653 | [[package]] 1654 | name = "num-bigint-dig" 1655 | version = "0.8.4" 1656 | source = "registry+https://github.com/rust-lang/crates.io-index" 1657 | checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" 1658 | dependencies = [ 1659 | "byteorder", 1660 | "lazy_static", 1661 | "libm", 1662 | "num-integer", 1663 | "num-iter", 1664 | "num-traits", 1665 | "rand 0.8.5", 1666 | "serde", 1667 | "smallvec", 1668 | "zeroize", 1669 | ] 1670 | 1671 | [[package]] 1672 | name = "num-integer" 1673 | version = "0.1.46" 1674 | source = "registry+https://github.com/rust-lang/crates.io-index" 1675 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 1676 | dependencies = [ 1677 | "num-traits", 1678 | ] 1679 | 1680 | [[package]] 1681 | name = "num-iter" 1682 | version = "0.1.45" 1683 | source = "registry+https://github.com/rust-lang/crates.io-index" 1684 | checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" 1685 | dependencies = [ 1686 | "autocfg", 1687 | "num-integer", 1688 | "num-traits", 1689 | ] 1690 | 1691 | [[package]] 1692 | name = "num-traits" 1693 | version = "0.2.19" 1694 | source = "registry+https://github.com/rust-lang/crates.io-index" 1695 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1696 | dependencies = [ 1697 | "autocfg", 1698 | "libm", 1699 | ] 1700 | 1701 | [[package]] 1702 | name = "object" 1703 | version = "0.36.7" 1704 | source = "registry+https://github.com/rust-lang/crates.io-index" 1705 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1706 | dependencies = [ 1707 | "memchr", 1708 | ] 1709 | 1710 | [[package]] 1711 | name = "once_cell" 1712 | version = "1.21.3" 1713 | source = "registry+https://github.com/rust-lang/crates.io-index" 1714 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1715 | 1716 | [[package]] 1717 | name = "opaque-debug" 1718 | version = "0.3.1" 1719 | source = "registry+https://github.com/rust-lang/crates.io-index" 1720 | checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" 1721 | 1722 | [[package]] 1723 | name = "openssl" 1724 | version = "0.10.72" 1725 | source = "registry+https://github.com/rust-lang/crates.io-index" 1726 | checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" 1727 | dependencies = [ 1728 | "bitflags", 1729 | "cfg-if", 1730 | "foreign-types", 1731 | "libc", 1732 | "once_cell", 1733 | "openssl-macros", 1734 | "openssl-sys", 1735 | ] 1736 | 1737 | [[package]] 1738 | name = "openssl-macros" 1739 | version = "0.1.1" 1740 | source = "registry+https://github.com/rust-lang/crates.io-index" 1741 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1742 | dependencies = [ 1743 | "proc-macro2", 1744 | "quote", 1745 | "syn", 1746 | ] 1747 | 1748 | [[package]] 1749 | name = "openssl-probe" 1750 | version = "0.1.6" 1751 | source = "registry+https://github.com/rust-lang/crates.io-index" 1752 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1753 | 1754 | [[package]] 1755 | name = "openssl-sys" 1756 | version = "0.9.107" 1757 | source = "registry+https://github.com/rust-lang/crates.io-index" 1758 | checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" 1759 | dependencies = [ 1760 | "cc", 1761 | "libc", 1762 | "pkg-config", 1763 | "vcpkg", 1764 | ] 1765 | 1766 | [[package]] 1767 | name = "option-ext" 1768 | version = "0.2.0" 1769 | source = "registry+https://github.com/rust-lang/crates.io-index" 1770 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 1771 | 1772 | [[package]] 1773 | name = "ordered-multimap" 1774 | version = "0.7.3" 1775 | source = "registry+https://github.com/rust-lang/crates.io-index" 1776 | checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" 1777 | dependencies = [ 1778 | "dlv-list", 1779 | "hashbrown 0.14.5", 1780 | ] 1781 | 1782 | [[package]] 1783 | name = "overload" 1784 | version = "0.1.1" 1785 | source = "registry+https://github.com/rust-lang/crates.io-index" 1786 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 1787 | 1788 | [[package]] 1789 | name = "parking" 1790 | version = "2.2.1" 1791 | source = "registry+https://github.com/rust-lang/crates.io-index" 1792 | checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" 1793 | 1794 | [[package]] 1795 | name = "parking_lot" 1796 | version = "0.12.3" 1797 | source = "registry+https://github.com/rust-lang/crates.io-index" 1798 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1799 | dependencies = [ 1800 | "lock_api", 1801 | "parking_lot_core", 1802 | ] 1803 | 1804 | [[package]] 1805 | name = "parking_lot_core" 1806 | version = "0.9.10" 1807 | source = "registry+https://github.com/rust-lang/crates.io-index" 1808 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1809 | dependencies = [ 1810 | "cfg-if", 1811 | "libc", 1812 | "redox_syscall", 1813 | "smallvec", 1814 | "windows-targets 0.52.6", 1815 | ] 1816 | 1817 | [[package]] 1818 | name = "password-hash" 1819 | version = "0.5.0" 1820 | source = "registry+https://github.com/rust-lang/crates.io-index" 1821 | checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" 1822 | dependencies = [ 1823 | "base64ct", 1824 | "rand_core 0.6.4", 1825 | "subtle", 1826 | ] 1827 | 1828 | [[package]] 1829 | name = "paste" 1830 | version = "1.0.15" 1831 | source = "registry+https://github.com/rust-lang/crates.io-index" 1832 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1833 | 1834 | [[package]] 1835 | name = "pathdiff" 1836 | version = "0.2.3" 1837 | source = "registry+https://github.com/rust-lang/crates.io-index" 1838 | checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" 1839 | 1840 | [[package]] 1841 | name = "pbkdf2" 1842 | version = "0.12.2" 1843 | source = "registry+https://github.com/rust-lang/crates.io-index" 1844 | checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" 1845 | dependencies = [ 1846 | "digest", 1847 | "hmac", 1848 | ] 1849 | 1850 | [[package]] 1851 | name = "pem-rfc7468" 1852 | version = "0.6.0" 1853 | source = "registry+https://github.com/rust-lang/crates.io-index" 1854 | checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" 1855 | dependencies = [ 1856 | "base64ct", 1857 | ] 1858 | 1859 | [[package]] 1860 | name = "pem-rfc7468" 1861 | version = "0.7.0" 1862 | source = "registry+https://github.com/rust-lang/crates.io-index" 1863 | checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" 1864 | dependencies = [ 1865 | "base64ct", 1866 | ] 1867 | 1868 | [[package]] 1869 | name = "percent-encoding" 1870 | version = "2.3.1" 1871 | source = "registry+https://github.com/rust-lang/crates.io-index" 1872 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1873 | 1874 | [[package]] 1875 | name = "pest" 1876 | version = "2.8.0" 1877 | source = "registry+https://github.com/rust-lang/crates.io-index" 1878 | checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" 1879 | dependencies = [ 1880 | "memchr", 1881 | "thiserror 2.0.12", 1882 | "ucd-trie", 1883 | ] 1884 | 1885 | [[package]] 1886 | name = "pest_derive" 1887 | version = "2.8.0" 1888 | source = "registry+https://github.com/rust-lang/crates.io-index" 1889 | checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" 1890 | dependencies = [ 1891 | "pest", 1892 | "pest_generator", 1893 | ] 1894 | 1895 | [[package]] 1896 | name = "pest_generator" 1897 | version = "2.8.0" 1898 | source = "registry+https://github.com/rust-lang/crates.io-index" 1899 | checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" 1900 | dependencies = [ 1901 | "pest", 1902 | "pest_meta", 1903 | "proc-macro2", 1904 | "quote", 1905 | "syn", 1906 | ] 1907 | 1908 | [[package]] 1909 | name = "pest_meta" 1910 | version = "2.8.0" 1911 | source = "registry+https://github.com/rust-lang/crates.io-index" 1912 | checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" 1913 | dependencies = [ 1914 | "once_cell", 1915 | "pest", 1916 | "sha2", 1917 | ] 1918 | 1919 | [[package]] 1920 | name = "pin-project-lite" 1921 | version = "0.2.16" 1922 | source = "registry+https://github.com/rust-lang/crates.io-index" 1923 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1924 | 1925 | [[package]] 1926 | name = "pin-utils" 1927 | version = "0.1.0" 1928 | source = "registry+https://github.com/rust-lang/crates.io-index" 1929 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1930 | 1931 | [[package]] 1932 | name = "pkcs1" 1933 | version = "0.4.1" 1934 | source = "registry+https://github.com/rust-lang/crates.io-index" 1935 | checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719" 1936 | dependencies = [ 1937 | "der 0.6.1", 1938 | "pkcs8 0.9.0", 1939 | "spki 0.6.0", 1940 | "zeroize", 1941 | ] 1942 | 1943 | [[package]] 1944 | name = "pkcs1" 1945 | version = "0.7.5" 1946 | source = "registry+https://github.com/rust-lang/crates.io-index" 1947 | checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" 1948 | dependencies = [ 1949 | "der 0.7.10", 1950 | "pkcs8 0.10.2", 1951 | "spki 0.7.3", 1952 | ] 1953 | 1954 | [[package]] 1955 | name = "pkcs8" 1956 | version = "0.9.0" 1957 | source = "registry+https://github.com/rust-lang/crates.io-index" 1958 | checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" 1959 | dependencies = [ 1960 | "der 0.6.1", 1961 | "spki 0.6.0", 1962 | ] 1963 | 1964 | [[package]] 1965 | name = "pkcs8" 1966 | version = "0.10.2" 1967 | source = "registry+https://github.com/rust-lang/crates.io-index" 1968 | checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 1969 | dependencies = [ 1970 | "der 0.7.10", 1971 | "spki 0.7.3", 1972 | ] 1973 | 1974 | [[package]] 1975 | name = "pkg-config" 1976 | version = "0.3.32" 1977 | source = "registry+https://github.com/rust-lang/crates.io-index" 1978 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 1979 | 1980 | [[package]] 1981 | name = "poly1305" 1982 | version = "0.8.0" 1983 | source = "registry+https://github.com/rust-lang/crates.io-index" 1984 | checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" 1985 | dependencies = [ 1986 | "cpufeatures", 1987 | "opaque-debug", 1988 | "universal-hash", 1989 | ] 1990 | 1991 | [[package]] 1992 | name = "ppv-lite86" 1993 | version = "0.2.21" 1994 | source = "registry+https://github.com/rust-lang/crates.io-index" 1995 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 1996 | dependencies = [ 1997 | "zerocopy", 1998 | ] 1999 | 2000 | [[package]] 2001 | name = "proc-macro2" 2002 | version = "1.0.95" 2003 | source = "registry+https://github.com/rust-lang/crates.io-index" 2004 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 2005 | dependencies = [ 2006 | "unicode-ident", 2007 | ] 2008 | 2009 | [[package]] 2010 | name = "quote" 2011 | version = "1.0.40" 2012 | source = "registry+https://github.com/rust-lang/crates.io-index" 2013 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 2014 | dependencies = [ 2015 | "proc-macro2", 2016 | ] 2017 | 2018 | [[package]] 2019 | name = "r-efi" 2020 | version = "5.2.0" 2021 | source = "registry+https://github.com/rust-lang/crates.io-index" 2022 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 2023 | 2024 | [[package]] 2025 | name = "rand" 2026 | version = "0.8.5" 2027 | source = "registry+https://github.com/rust-lang/crates.io-index" 2028 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 2029 | dependencies = [ 2030 | "libc", 2031 | "rand_chacha 0.3.1", 2032 | "rand_core 0.6.4", 2033 | ] 2034 | 2035 | [[package]] 2036 | name = "rand" 2037 | version = "0.9.1" 2038 | source = "registry+https://github.com/rust-lang/crates.io-index" 2039 | checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" 2040 | dependencies = [ 2041 | "rand_chacha 0.9.0", 2042 | "rand_core 0.9.3", 2043 | ] 2044 | 2045 | [[package]] 2046 | name = "rand_chacha" 2047 | version = "0.3.1" 2048 | source = "registry+https://github.com/rust-lang/crates.io-index" 2049 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 2050 | dependencies = [ 2051 | "ppv-lite86", 2052 | "rand_core 0.6.4", 2053 | ] 2054 | 2055 | [[package]] 2056 | name = "rand_chacha" 2057 | version = "0.9.0" 2058 | source = "registry+https://github.com/rust-lang/crates.io-index" 2059 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 2060 | dependencies = [ 2061 | "ppv-lite86", 2062 | "rand_core 0.9.3", 2063 | ] 2064 | 2065 | [[package]] 2066 | name = "rand_core" 2067 | version = "0.6.4" 2068 | source = "registry+https://github.com/rust-lang/crates.io-index" 2069 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 2070 | dependencies = [ 2071 | "getrandom 0.2.15", 2072 | ] 2073 | 2074 | [[package]] 2075 | name = "rand_core" 2076 | version = "0.9.3" 2077 | source = "registry+https://github.com/rust-lang/crates.io-index" 2078 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 2079 | dependencies = [ 2080 | "getrandom 0.3.2", 2081 | ] 2082 | 2083 | [[package]] 2084 | name = "ratatui" 2085 | version = "0.29.0" 2086 | source = "registry+https://github.com/rust-lang/crates.io-index" 2087 | checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" 2088 | dependencies = [ 2089 | "bitflags", 2090 | "cassowary", 2091 | "compact_str", 2092 | "crossterm 0.28.1", 2093 | "indoc", 2094 | "instability", 2095 | "itertools", 2096 | "lru 0.12.5", 2097 | "paste", 2098 | "strum", 2099 | "unicode-segmentation", 2100 | "unicode-truncate", 2101 | "unicode-width 0.2.0", 2102 | ] 2103 | 2104 | [[package]] 2105 | name = "redox_syscall" 2106 | version = "0.5.11" 2107 | source = "registry+https://github.com/rust-lang/crates.io-index" 2108 | checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" 2109 | dependencies = [ 2110 | "bitflags", 2111 | ] 2112 | 2113 | [[package]] 2114 | name = "redox_users" 2115 | version = "0.5.0" 2116 | source = "registry+https://github.com/rust-lang/crates.io-index" 2117 | checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" 2118 | dependencies = [ 2119 | "getrandom 0.2.15", 2120 | "libredox", 2121 | "thiserror 2.0.12", 2122 | ] 2123 | 2124 | [[package]] 2125 | name = "regex" 2126 | version = "1.11.1" 2127 | source = "registry+https://github.com/rust-lang/crates.io-index" 2128 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 2129 | dependencies = [ 2130 | "aho-corasick", 2131 | "memchr", 2132 | "regex-automata", 2133 | "regex-syntax", 2134 | ] 2135 | 2136 | [[package]] 2137 | name = "regex-automata" 2138 | version = "0.4.9" 2139 | source = "registry+https://github.com/rust-lang/crates.io-index" 2140 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 2141 | dependencies = [ 2142 | "aho-corasick", 2143 | "memchr", 2144 | "regex-syntax", 2145 | ] 2146 | 2147 | [[package]] 2148 | name = "regex-syntax" 2149 | version = "0.8.5" 2150 | source = "registry+https://github.com/rust-lang/crates.io-index" 2151 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 2152 | 2153 | [[package]] 2154 | name = "ring" 2155 | version = "0.17.14" 2156 | source = "registry+https://github.com/rust-lang/crates.io-index" 2157 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 2158 | dependencies = [ 2159 | "cc", 2160 | "cfg-if", 2161 | "getrandom 0.2.15", 2162 | "libc", 2163 | "untrusted", 2164 | "windows-sys 0.52.0", 2165 | ] 2166 | 2167 | [[package]] 2168 | name = "ron" 2169 | version = "0.8.1" 2170 | source = "registry+https://github.com/rust-lang/crates.io-index" 2171 | checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" 2172 | dependencies = [ 2173 | "base64 0.21.7", 2174 | "bitflags", 2175 | "serde", 2176 | "serde_derive", 2177 | ] 2178 | 2179 | [[package]] 2180 | name = "rsa" 2181 | version = "0.8.2" 2182 | source = "registry+https://github.com/rust-lang/crates.io-index" 2183 | checksum = "55a77d189da1fee555ad95b7e50e7457d91c0e089ec68ca69ad2989413bbdab4" 2184 | dependencies = [ 2185 | "byteorder", 2186 | "digest", 2187 | "num-bigint-dig", 2188 | "num-integer", 2189 | "num-iter", 2190 | "num-traits", 2191 | "pkcs1 0.4.1", 2192 | "pkcs8 0.9.0", 2193 | "rand_core 0.6.4", 2194 | "serde", 2195 | "signature", 2196 | "subtle", 2197 | "zeroize", 2198 | ] 2199 | 2200 | [[package]] 2201 | name = "rsa" 2202 | version = "0.9.8" 2203 | source = "registry+https://github.com/rust-lang/crates.io-index" 2204 | checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" 2205 | dependencies = [ 2206 | "const-oid", 2207 | "digest", 2208 | "num-bigint-dig", 2209 | "num-integer", 2210 | "num-traits", 2211 | "pkcs1 0.7.5", 2212 | "pkcs8 0.10.2", 2213 | "rand_core 0.6.4", 2214 | "signature", 2215 | "spki 0.7.3", 2216 | "subtle", 2217 | "zeroize", 2218 | ] 2219 | 2220 | [[package]] 2221 | name = "rust-ini" 2222 | version = "0.21.1" 2223 | source = "registry+https://github.com/rust-lang/crates.io-index" 2224 | checksum = "4e310ef0e1b6eeb79169a1171daf9abcb87a2e17c03bee2c4bb100b55c75409f" 2225 | dependencies = [ 2226 | "cfg-if", 2227 | "ordered-multimap", 2228 | "trim-in-place", 2229 | ] 2230 | 2231 | [[package]] 2232 | name = "rustc-demangle" 2233 | version = "0.1.24" 2234 | source = "registry+https://github.com/rust-lang/crates.io-index" 2235 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 2236 | 2237 | [[package]] 2238 | name = "rustix" 2239 | version = "0.38.44" 2240 | source = "registry+https://github.com/rust-lang/crates.io-index" 2241 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 2242 | dependencies = [ 2243 | "bitflags", 2244 | "errno", 2245 | "libc", 2246 | "linux-raw-sys 0.4.15", 2247 | "windows-sys 0.59.0", 2248 | ] 2249 | 2250 | [[package]] 2251 | name = "rustix" 2252 | version = "1.0.5" 2253 | source = "registry+https://github.com/rust-lang/crates.io-index" 2254 | checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" 2255 | dependencies = [ 2256 | "bitflags", 2257 | "errno", 2258 | "libc", 2259 | "linux-raw-sys 0.9.4", 2260 | "windows-sys 0.59.0", 2261 | ] 2262 | 2263 | [[package]] 2264 | name = "rustls" 2265 | version = "0.23.26" 2266 | source = "registry+https://github.com/rust-lang/crates.io-index" 2267 | checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" 2268 | dependencies = [ 2269 | "once_cell", 2270 | "ring", 2271 | "rustls-pki-types", 2272 | "rustls-webpki", 2273 | "subtle", 2274 | "zeroize", 2275 | ] 2276 | 2277 | [[package]] 2278 | name = "rustls-pki-types" 2279 | version = "1.11.0" 2280 | source = "registry+https://github.com/rust-lang/crates.io-index" 2281 | checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" 2282 | 2283 | [[package]] 2284 | name = "rustls-webpki" 2285 | version = "0.103.1" 2286 | source = "registry+https://github.com/rust-lang/crates.io-index" 2287 | checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" 2288 | dependencies = [ 2289 | "ring", 2290 | "rustls-pki-types", 2291 | "untrusted", 2292 | ] 2293 | 2294 | [[package]] 2295 | name = "rustversion" 2296 | version = "1.0.20" 2297 | source = "registry+https://github.com/rust-lang/crates.io-index" 2298 | checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" 2299 | 2300 | [[package]] 2301 | name = "ryu" 2302 | version = "1.0.20" 2303 | source = "registry+https://github.com/rust-lang/crates.io-index" 2304 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 2305 | 2306 | [[package]] 2307 | name = "salsa20" 2308 | version = "0.10.2" 2309 | source = "registry+https://github.com/rust-lang/crates.io-index" 2310 | checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" 2311 | dependencies = [ 2312 | "cipher", 2313 | ] 2314 | 2315 | [[package]] 2316 | name = "schannel" 2317 | version = "0.1.27" 2318 | source = "registry+https://github.com/rust-lang/crates.io-index" 2319 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 2320 | dependencies = [ 2321 | "windows-sys 0.59.0", 2322 | ] 2323 | 2324 | [[package]] 2325 | name = "scopeguard" 2326 | version = "1.2.0" 2327 | source = "registry+https://github.com/rust-lang/crates.io-index" 2328 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 2329 | 2330 | [[package]] 2331 | name = "scrypt" 2332 | version = "0.11.0" 2333 | source = "registry+https://github.com/rust-lang/crates.io-index" 2334 | checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" 2335 | dependencies = [ 2336 | "password-hash", 2337 | "pbkdf2", 2338 | "salsa20", 2339 | "sha2", 2340 | ] 2341 | 2342 | [[package]] 2343 | name = "secp256k1" 2344 | version = "0.29.1" 2345 | source = "registry+https://github.com/rust-lang/crates.io-index" 2346 | checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" 2347 | dependencies = [ 2348 | "rand 0.8.5", 2349 | "secp256k1-sys", 2350 | "serde", 2351 | ] 2352 | 2353 | [[package]] 2354 | name = "secp256k1-sys" 2355 | version = "0.10.1" 2356 | source = "registry+https://github.com/rust-lang/crates.io-index" 2357 | checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" 2358 | dependencies = [ 2359 | "cc", 2360 | ] 2361 | 2362 | [[package]] 2363 | name = "security-framework" 2364 | version = "2.11.1" 2365 | source = "registry+https://github.com/rust-lang/crates.io-index" 2366 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 2367 | dependencies = [ 2368 | "bitflags", 2369 | "core-foundation", 2370 | "core-foundation-sys", 2371 | "libc", 2372 | "security-framework-sys", 2373 | ] 2374 | 2375 | [[package]] 2376 | name = "security-framework-sys" 2377 | version = "2.14.0" 2378 | source = "registry+https://github.com/rust-lang/crates.io-index" 2379 | checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 2380 | dependencies = [ 2381 | "core-foundation-sys", 2382 | "libc", 2383 | ] 2384 | 2385 | [[package]] 2386 | name = "serde" 2387 | version = "1.0.219" 2388 | source = "registry+https://github.com/rust-lang/crates.io-index" 2389 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 2390 | dependencies = [ 2391 | "serde_derive", 2392 | ] 2393 | 2394 | [[package]] 2395 | name = "serde_derive" 2396 | version = "1.0.219" 2397 | source = "registry+https://github.com/rust-lang/crates.io-index" 2398 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 2399 | dependencies = [ 2400 | "proc-macro2", 2401 | "quote", 2402 | "syn", 2403 | ] 2404 | 2405 | [[package]] 2406 | name = "serde_json" 2407 | version = "1.0.140" 2408 | source = "registry+https://github.com/rust-lang/crates.io-index" 2409 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 2410 | dependencies = [ 2411 | "itoa", 2412 | "memchr", 2413 | "ryu", 2414 | "serde", 2415 | ] 2416 | 2417 | [[package]] 2418 | name = "serde_spanned" 2419 | version = "0.6.8" 2420 | source = "registry+https://github.com/rust-lang/crates.io-index" 2421 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 2422 | dependencies = [ 2423 | "serde", 2424 | ] 2425 | 2426 | [[package]] 2427 | name = "serde_urlencoded" 2428 | version = "0.7.1" 2429 | source = "registry+https://github.com/rust-lang/crates.io-index" 2430 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 2431 | dependencies = [ 2432 | "form_urlencoded", 2433 | "itoa", 2434 | "ryu", 2435 | "serde", 2436 | ] 2437 | 2438 | [[package]] 2439 | name = "sha1" 2440 | version = "0.10.6" 2441 | source = "registry+https://github.com/rust-lang/crates.io-index" 2442 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 2443 | dependencies = [ 2444 | "cfg-if", 2445 | "cpufeatures", 2446 | "digest", 2447 | ] 2448 | 2449 | [[package]] 2450 | name = "sha2" 2451 | version = "0.10.8" 2452 | source = "registry+https://github.com/rust-lang/crates.io-index" 2453 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 2454 | dependencies = [ 2455 | "cfg-if", 2456 | "cpufeatures", 2457 | "digest", 2458 | ] 2459 | 2460 | [[package]] 2461 | name = "sharded-slab" 2462 | version = "0.1.7" 2463 | source = "registry+https://github.com/rust-lang/crates.io-index" 2464 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 2465 | dependencies = [ 2466 | "lazy_static", 2467 | ] 2468 | 2469 | [[package]] 2470 | name = "shlex" 2471 | version = "1.3.0" 2472 | source = "registry+https://github.com/rust-lang/crates.io-index" 2473 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 2474 | 2475 | [[package]] 2476 | name = "signal-hook" 2477 | version = "0.3.17" 2478 | source = "registry+https://github.com/rust-lang/crates.io-index" 2479 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 2480 | dependencies = [ 2481 | "libc", 2482 | "signal-hook-registry", 2483 | ] 2484 | 2485 | [[package]] 2486 | name = "signal-hook-mio" 2487 | version = "0.2.4" 2488 | source = "registry+https://github.com/rust-lang/crates.io-index" 2489 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 2490 | dependencies = [ 2491 | "libc", 2492 | "mio", 2493 | "signal-hook", 2494 | ] 2495 | 2496 | [[package]] 2497 | name = "signal-hook-registry" 2498 | version = "1.4.5" 2499 | source = "registry+https://github.com/rust-lang/crates.io-index" 2500 | checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" 2501 | dependencies = [ 2502 | "libc", 2503 | ] 2504 | 2505 | [[package]] 2506 | name = "signature" 2507 | version = "2.2.0" 2508 | source = "registry+https://github.com/rust-lang/crates.io-index" 2509 | checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" 2510 | dependencies = [ 2511 | "digest", 2512 | "rand_core 0.6.4", 2513 | ] 2514 | 2515 | [[package]] 2516 | name = "slab" 2517 | version = "0.4.9" 2518 | source = "registry+https://github.com/rust-lang/crates.io-index" 2519 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 2520 | dependencies = [ 2521 | "autocfg", 2522 | ] 2523 | 2524 | [[package]] 2525 | name = "smallvec" 2526 | version = "1.15.0" 2527 | source = "registry+https://github.com/rust-lang/crates.io-index" 2528 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 2529 | dependencies = [ 2530 | "serde", 2531 | ] 2532 | 2533 | [[package]] 2534 | name = "socket2" 2535 | version = "0.5.9" 2536 | source = "registry+https://github.com/rust-lang/crates.io-index" 2537 | checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" 2538 | dependencies = [ 2539 | "libc", 2540 | "windows-sys 0.52.0", 2541 | ] 2542 | 2543 | [[package]] 2544 | name = "spin" 2545 | version = "0.9.8" 2546 | source = "registry+https://github.com/rust-lang/crates.io-index" 2547 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 2548 | dependencies = [ 2549 | "lock_api", 2550 | ] 2551 | 2552 | [[package]] 2553 | name = "spki" 2554 | version = "0.6.0" 2555 | source = "registry+https://github.com/rust-lang/crates.io-index" 2556 | checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" 2557 | dependencies = [ 2558 | "base64ct", 2559 | "der 0.6.1", 2560 | ] 2561 | 2562 | [[package]] 2563 | name = "spki" 2564 | version = "0.7.3" 2565 | source = "registry+https://github.com/rust-lang/crates.io-index" 2566 | checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" 2567 | dependencies = [ 2568 | "base64ct", 2569 | "der 0.7.10", 2570 | ] 2571 | 2572 | [[package]] 2573 | name = "sqlx" 2574 | version = "0.8.5" 2575 | source = "registry+https://github.com/rust-lang/crates.io-index" 2576 | checksum = "f3c3a85280daca669cfd3bcb68a337882a8bc57ec882f72c5d13a430613a738e" 2577 | dependencies = [ 2578 | "sqlx-core", 2579 | "sqlx-macros", 2580 | "sqlx-mysql", 2581 | "sqlx-postgres", 2582 | "sqlx-sqlite", 2583 | ] 2584 | 2585 | [[package]] 2586 | name = "sqlx-core" 2587 | version = "0.8.5" 2588 | source = "registry+https://github.com/rust-lang/crates.io-index" 2589 | checksum = "f743f2a3cea30a58cd479013f75550e879009e3a02f616f18ca699335aa248c3" 2590 | dependencies = [ 2591 | "base64 0.22.1", 2592 | "bytes", 2593 | "crc", 2594 | "crossbeam-queue", 2595 | "either", 2596 | "event-listener", 2597 | "futures-core", 2598 | "futures-intrusive", 2599 | "futures-io", 2600 | "futures-util", 2601 | "hashbrown 0.15.2", 2602 | "hashlink", 2603 | "indexmap", 2604 | "log", 2605 | "memchr", 2606 | "native-tls", 2607 | "once_cell", 2608 | "percent-encoding", 2609 | "serde", 2610 | "serde_json", 2611 | "sha2", 2612 | "smallvec", 2613 | "thiserror 2.0.12", 2614 | "tokio", 2615 | "tokio-stream", 2616 | "tracing", 2617 | "url", 2618 | ] 2619 | 2620 | [[package]] 2621 | name = "sqlx-macros" 2622 | version = "0.8.5" 2623 | source = "registry+https://github.com/rust-lang/crates.io-index" 2624 | checksum = "7f4200e0fde19834956d4252347c12a083bdcb237d7a1a1446bffd8768417dce" 2625 | dependencies = [ 2626 | "proc-macro2", 2627 | "quote", 2628 | "sqlx-core", 2629 | "sqlx-macros-core", 2630 | "syn", 2631 | ] 2632 | 2633 | [[package]] 2634 | name = "sqlx-macros-core" 2635 | version = "0.8.5" 2636 | source = "registry+https://github.com/rust-lang/crates.io-index" 2637 | checksum = "882ceaa29cade31beca7129b6beeb05737f44f82dbe2a9806ecea5a7093d00b7" 2638 | dependencies = [ 2639 | "dotenvy", 2640 | "either", 2641 | "heck", 2642 | "hex", 2643 | "once_cell", 2644 | "proc-macro2", 2645 | "quote", 2646 | "serde", 2647 | "serde_json", 2648 | "sha2", 2649 | "sqlx-core", 2650 | "sqlx-mysql", 2651 | "sqlx-postgres", 2652 | "sqlx-sqlite", 2653 | "syn", 2654 | "tempfile", 2655 | "tokio", 2656 | "url", 2657 | ] 2658 | 2659 | [[package]] 2660 | name = "sqlx-mysql" 2661 | version = "0.8.5" 2662 | source = "registry+https://github.com/rust-lang/crates.io-index" 2663 | checksum = "0afdd3aa7a629683c2d750c2df343025545087081ab5942593a5288855b1b7a7" 2664 | dependencies = [ 2665 | "atoi", 2666 | "base64 0.22.1", 2667 | "bitflags", 2668 | "byteorder", 2669 | "bytes", 2670 | "crc", 2671 | "digest", 2672 | "dotenvy", 2673 | "either", 2674 | "futures-channel", 2675 | "futures-core", 2676 | "futures-io", 2677 | "futures-util", 2678 | "generic-array", 2679 | "hex", 2680 | "hkdf", 2681 | "hmac", 2682 | "itoa", 2683 | "log", 2684 | "md-5", 2685 | "memchr", 2686 | "once_cell", 2687 | "percent-encoding", 2688 | "rand 0.8.5", 2689 | "rsa 0.9.8", 2690 | "serde", 2691 | "sha1", 2692 | "sha2", 2693 | "smallvec", 2694 | "sqlx-core", 2695 | "stringprep", 2696 | "thiserror 2.0.12", 2697 | "tracing", 2698 | "whoami", 2699 | ] 2700 | 2701 | [[package]] 2702 | name = "sqlx-postgres" 2703 | version = "0.8.5" 2704 | source = "registry+https://github.com/rust-lang/crates.io-index" 2705 | checksum = "a0bedbe1bbb5e2615ef347a5e9d8cd7680fb63e77d9dafc0f29be15e53f1ebe6" 2706 | dependencies = [ 2707 | "atoi", 2708 | "base64 0.22.1", 2709 | "bitflags", 2710 | "byteorder", 2711 | "crc", 2712 | "dotenvy", 2713 | "etcetera", 2714 | "futures-channel", 2715 | "futures-core", 2716 | "futures-util", 2717 | "hex", 2718 | "hkdf", 2719 | "hmac", 2720 | "home", 2721 | "itoa", 2722 | "log", 2723 | "md-5", 2724 | "memchr", 2725 | "once_cell", 2726 | "rand 0.8.5", 2727 | "serde", 2728 | "serde_json", 2729 | "sha2", 2730 | "smallvec", 2731 | "sqlx-core", 2732 | "stringprep", 2733 | "thiserror 2.0.12", 2734 | "tracing", 2735 | "whoami", 2736 | ] 2737 | 2738 | [[package]] 2739 | name = "sqlx-sqlite" 2740 | version = "0.8.5" 2741 | source = "registry+https://github.com/rust-lang/crates.io-index" 2742 | checksum = "c26083e9a520e8eb87a06b12347679b142dc2ea29e6e409f805644a7a979a5bc" 2743 | dependencies = [ 2744 | "atoi", 2745 | "flume", 2746 | "futures-channel", 2747 | "futures-core", 2748 | "futures-executor", 2749 | "futures-intrusive", 2750 | "futures-util", 2751 | "libsqlite3-sys", 2752 | "log", 2753 | "percent-encoding", 2754 | "serde", 2755 | "serde_urlencoded", 2756 | "sqlx-core", 2757 | "thiserror 2.0.12", 2758 | "tracing", 2759 | "url", 2760 | ] 2761 | 2762 | [[package]] 2763 | name = "stable_deref_trait" 2764 | version = "1.2.0" 2765 | source = "registry+https://github.com/rust-lang/crates.io-index" 2766 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 2767 | 2768 | [[package]] 2769 | name = "static_assertions" 2770 | version = "1.1.0" 2771 | source = "registry+https://github.com/rust-lang/crates.io-index" 2772 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 2773 | 2774 | [[package]] 2775 | name = "stringprep" 2776 | version = "0.1.5" 2777 | source = "registry+https://github.com/rust-lang/crates.io-index" 2778 | checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" 2779 | dependencies = [ 2780 | "unicode-bidi", 2781 | "unicode-normalization", 2782 | "unicode-properties", 2783 | ] 2784 | 2785 | [[package]] 2786 | name = "strsim" 2787 | version = "0.11.1" 2788 | source = "registry+https://github.com/rust-lang/crates.io-index" 2789 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 2790 | 2791 | [[package]] 2792 | name = "strum" 2793 | version = "0.26.3" 2794 | source = "registry+https://github.com/rust-lang/crates.io-index" 2795 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 2796 | dependencies = [ 2797 | "strum_macros", 2798 | ] 2799 | 2800 | [[package]] 2801 | name = "strum_macros" 2802 | version = "0.26.4" 2803 | source = "registry+https://github.com/rust-lang/crates.io-index" 2804 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 2805 | dependencies = [ 2806 | "heck", 2807 | "proc-macro2", 2808 | "quote", 2809 | "rustversion", 2810 | "syn", 2811 | ] 2812 | 2813 | [[package]] 2814 | name = "subtle" 2815 | version = "2.6.1" 2816 | source = "registry+https://github.com/rust-lang/crates.io-index" 2817 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 2818 | 2819 | [[package]] 2820 | name = "syn" 2821 | version = "2.0.100" 2822 | source = "registry+https://github.com/rust-lang/crates.io-index" 2823 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 2824 | dependencies = [ 2825 | "proc-macro2", 2826 | "quote", 2827 | "unicode-ident", 2828 | ] 2829 | 2830 | [[package]] 2831 | name = "synstructure" 2832 | version = "0.13.1" 2833 | source = "registry+https://github.com/rust-lang/crates.io-index" 2834 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 2835 | dependencies = [ 2836 | "proc-macro2", 2837 | "quote", 2838 | "syn", 2839 | ] 2840 | 2841 | [[package]] 2842 | name = "tempfile" 2843 | version = "3.19.1" 2844 | source = "registry+https://github.com/rust-lang/crates.io-index" 2845 | checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" 2846 | dependencies = [ 2847 | "fastrand", 2848 | "getrandom 0.3.2", 2849 | "once_cell", 2850 | "rustix 1.0.5", 2851 | "windows-sys 0.59.0", 2852 | ] 2853 | 2854 | [[package]] 2855 | name = "thiserror" 2856 | version = "1.0.69" 2857 | source = "registry+https://github.com/rust-lang/crates.io-index" 2858 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 2859 | dependencies = [ 2860 | "thiserror-impl 1.0.69", 2861 | ] 2862 | 2863 | [[package]] 2864 | name = "thiserror" 2865 | version = "2.0.12" 2866 | source = "registry+https://github.com/rust-lang/crates.io-index" 2867 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 2868 | dependencies = [ 2869 | "thiserror-impl 2.0.12", 2870 | ] 2871 | 2872 | [[package]] 2873 | name = "thiserror-impl" 2874 | version = "1.0.69" 2875 | source = "registry+https://github.com/rust-lang/crates.io-index" 2876 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 2877 | dependencies = [ 2878 | "proc-macro2", 2879 | "quote", 2880 | "syn", 2881 | ] 2882 | 2883 | [[package]] 2884 | name = "thiserror-impl" 2885 | version = "2.0.12" 2886 | source = "registry+https://github.com/rust-lang/crates.io-index" 2887 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 2888 | dependencies = [ 2889 | "proc-macro2", 2890 | "quote", 2891 | "syn", 2892 | ] 2893 | 2894 | [[package]] 2895 | name = "thread_local" 2896 | version = "1.1.8" 2897 | source = "registry+https://github.com/rust-lang/crates.io-index" 2898 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 2899 | dependencies = [ 2900 | "cfg-if", 2901 | "once_cell", 2902 | ] 2903 | 2904 | [[package]] 2905 | name = "tiny-keccak" 2906 | version = "2.0.2" 2907 | source = "registry+https://github.com/rust-lang/crates.io-index" 2908 | checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 2909 | dependencies = [ 2910 | "crunchy", 2911 | ] 2912 | 2913 | [[package]] 2914 | name = "tinystr" 2915 | version = "0.7.6" 2916 | source = "registry+https://github.com/rust-lang/crates.io-index" 2917 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 2918 | dependencies = [ 2919 | "displaydoc", 2920 | "zerovec", 2921 | ] 2922 | 2923 | [[package]] 2924 | name = "tinyvec" 2925 | version = "1.9.0" 2926 | source = "registry+https://github.com/rust-lang/crates.io-index" 2927 | checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" 2928 | dependencies = [ 2929 | "tinyvec_macros", 2930 | ] 2931 | 2932 | [[package]] 2933 | name = "tinyvec_macros" 2934 | version = "0.1.1" 2935 | source = "registry+https://github.com/rust-lang/crates.io-index" 2936 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 2937 | 2938 | [[package]] 2939 | name = "tokio" 2940 | version = "1.44.2" 2941 | source = "registry+https://github.com/rust-lang/crates.io-index" 2942 | checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" 2943 | dependencies = [ 2944 | "backtrace", 2945 | "bytes", 2946 | "libc", 2947 | "mio", 2948 | "pin-project-lite", 2949 | "socket2", 2950 | "tokio-macros", 2951 | "windows-sys 0.52.0", 2952 | ] 2953 | 2954 | [[package]] 2955 | name = "tokio-macros" 2956 | version = "2.5.0" 2957 | source = "registry+https://github.com/rust-lang/crates.io-index" 2958 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 2959 | dependencies = [ 2960 | "proc-macro2", 2961 | "quote", 2962 | "syn", 2963 | ] 2964 | 2965 | [[package]] 2966 | name = "tokio-rustls" 2967 | version = "0.26.2" 2968 | source = "registry+https://github.com/rust-lang/crates.io-index" 2969 | checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" 2970 | dependencies = [ 2971 | "rustls", 2972 | "tokio", 2973 | ] 2974 | 2975 | [[package]] 2976 | name = "tokio-socks" 2977 | version = "0.5.2" 2978 | source = "registry+https://github.com/rust-lang/crates.io-index" 2979 | checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" 2980 | dependencies = [ 2981 | "either", 2982 | "futures-util", 2983 | "thiserror 1.0.69", 2984 | "tokio", 2985 | ] 2986 | 2987 | [[package]] 2988 | name = "tokio-stream" 2989 | version = "0.1.17" 2990 | source = "registry+https://github.com/rust-lang/crates.io-index" 2991 | checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" 2992 | dependencies = [ 2993 | "futures-core", 2994 | "pin-project-lite", 2995 | "tokio", 2996 | ] 2997 | 2998 | [[package]] 2999 | name = "tokio-tungstenite" 3000 | version = "0.26.2" 3001 | source = "registry+https://github.com/rust-lang/crates.io-index" 3002 | checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" 3003 | dependencies = [ 3004 | "futures-util", 3005 | "log", 3006 | "rustls", 3007 | "rustls-pki-types", 3008 | "tokio", 3009 | "tokio-rustls", 3010 | "tungstenite", 3011 | "webpki-roots", 3012 | ] 3013 | 3014 | [[package]] 3015 | name = "toml" 3016 | version = "0.8.21" 3017 | source = "registry+https://github.com/rust-lang/crates.io-index" 3018 | checksum = "900f6c86a685850b1bc9f6223b20125115ee3f31e01207d81655bbcc0aea9231" 3019 | dependencies = [ 3020 | "serde", 3021 | "serde_spanned", 3022 | "toml_datetime", 3023 | "toml_edit", 3024 | ] 3025 | 3026 | [[package]] 3027 | name = "toml_datetime" 3028 | version = "0.6.9" 3029 | source = "registry+https://github.com/rust-lang/crates.io-index" 3030 | checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" 3031 | dependencies = [ 3032 | "serde", 3033 | ] 3034 | 3035 | [[package]] 3036 | name = "toml_edit" 3037 | version = "0.22.25" 3038 | source = "registry+https://github.com/rust-lang/crates.io-index" 3039 | checksum = "10558ed0bd2a1562e630926a2d1f0b98c827da99fabd3fe20920a59642504485" 3040 | dependencies = [ 3041 | "indexmap", 3042 | "serde", 3043 | "serde_spanned", 3044 | "toml_datetime", 3045 | "winnow", 3046 | ] 3047 | 3048 | [[package]] 3049 | name = "tracing" 3050 | version = "0.1.41" 3051 | source = "registry+https://github.com/rust-lang/crates.io-index" 3052 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 3053 | dependencies = [ 3054 | "log", 3055 | "pin-project-lite", 3056 | "tracing-attributes", 3057 | "tracing-core", 3058 | ] 3059 | 3060 | [[package]] 3061 | name = "tracing-attributes" 3062 | version = "0.1.28" 3063 | source = "registry+https://github.com/rust-lang/crates.io-index" 3064 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 3065 | dependencies = [ 3066 | "proc-macro2", 3067 | "quote", 3068 | "syn", 3069 | ] 3070 | 3071 | [[package]] 3072 | name = "tracing-core" 3073 | version = "0.1.33" 3074 | source = "registry+https://github.com/rust-lang/crates.io-index" 3075 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 3076 | dependencies = [ 3077 | "once_cell", 3078 | "valuable", 3079 | ] 3080 | 3081 | [[package]] 3082 | name = "tracing-log" 3083 | version = "0.2.0" 3084 | source = "registry+https://github.com/rust-lang/crates.io-index" 3085 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 3086 | dependencies = [ 3087 | "log", 3088 | "once_cell", 3089 | "tracing-core", 3090 | ] 3091 | 3092 | [[package]] 3093 | name = "tracing-subscriber" 3094 | version = "0.3.19" 3095 | source = "registry+https://github.com/rust-lang/crates.io-index" 3096 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 3097 | dependencies = [ 3098 | "nu-ansi-term", 3099 | "sharded-slab", 3100 | "smallvec", 3101 | "thread_local", 3102 | "tracing-core", 3103 | "tracing-log", 3104 | ] 3105 | 3106 | [[package]] 3107 | name = "trim-in-place" 3108 | version = "0.1.7" 3109 | source = "registry+https://github.com/rust-lang/crates.io-index" 3110 | checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" 3111 | 3112 | [[package]] 3113 | name = "tungstenite" 3114 | version = "0.26.2" 3115 | source = "registry+https://github.com/rust-lang/crates.io-index" 3116 | checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" 3117 | dependencies = [ 3118 | "bytes", 3119 | "data-encoding", 3120 | "http", 3121 | "httparse", 3122 | "log", 3123 | "rand 0.9.1", 3124 | "rustls", 3125 | "rustls-pki-types", 3126 | "sha1", 3127 | "thiserror 2.0.12", 3128 | "utf-8", 3129 | ] 3130 | 3131 | [[package]] 3132 | name = "typenum" 3133 | version = "1.18.0" 3134 | source = "registry+https://github.com/rust-lang/crates.io-index" 3135 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 3136 | 3137 | [[package]] 3138 | name = "ucd-trie" 3139 | version = "0.1.7" 3140 | source = "registry+https://github.com/rust-lang/crates.io-index" 3141 | checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" 3142 | 3143 | [[package]] 3144 | name = "unicode-bidi" 3145 | version = "0.3.18" 3146 | source = "registry+https://github.com/rust-lang/crates.io-index" 3147 | checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" 3148 | 3149 | [[package]] 3150 | name = "unicode-ident" 3151 | version = "1.0.18" 3152 | source = "registry+https://github.com/rust-lang/crates.io-index" 3153 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 3154 | 3155 | [[package]] 3156 | name = "unicode-normalization" 3157 | version = "0.1.22" 3158 | source = "registry+https://github.com/rust-lang/crates.io-index" 3159 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 3160 | dependencies = [ 3161 | "tinyvec", 3162 | ] 3163 | 3164 | [[package]] 3165 | name = "unicode-properties" 3166 | version = "0.1.3" 3167 | source = "registry+https://github.com/rust-lang/crates.io-index" 3168 | checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" 3169 | 3170 | [[package]] 3171 | name = "unicode-segmentation" 3172 | version = "1.12.0" 3173 | source = "registry+https://github.com/rust-lang/crates.io-index" 3174 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 3175 | 3176 | [[package]] 3177 | name = "unicode-truncate" 3178 | version = "1.1.0" 3179 | source = "registry+https://github.com/rust-lang/crates.io-index" 3180 | checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" 3181 | dependencies = [ 3182 | "itertools", 3183 | "unicode-segmentation", 3184 | "unicode-width 0.1.14", 3185 | ] 3186 | 3187 | [[package]] 3188 | name = "unicode-width" 3189 | version = "0.1.14" 3190 | source = "registry+https://github.com/rust-lang/crates.io-index" 3191 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 3192 | 3193 | [[package]] 3194 | name = "unicode-width" 3195 | version = "0.2.0" 3196 | source = "registry+https://github.com/rust-lang/crates.io-index" 3197 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 3198 | 3199 | [[package]] 3200 | name = "unicode-xid" 3201 | version = "0.2.6" 3202 | source = "registry+https://github.com/rust-lang/crates.io-index" 3203 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 3204 | 3205 | [[package]] 3206 | name = "universal-hash" 3207 | version = "0.5.1" 3208 | source = "registry+https://github.com/rust-lang/crates.io-index" 3209 | checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" 3210 | dependencies = [ 3211 | "crypto-common", 3212 | "subtle", 3213 | ] 3214 | 3215 | [[package]] 3216 | name = "untrusted" 3217 | version = "0.9.0" 3218 | source = "registry+https://github.com/rust-lang/crates.io-index" 3219 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 3220 | 3221 | [[package]] 3222 | name = "url" 3223 | version = "2.5.4" 3224 | source = "registry+https://github.com/rust-lang/crates.io-index" 3225 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 3226 | dependencies = [ 3227 | "form_urlencoded", 3228 | "idna", 3229 | "percent-encoding", 3230 | "serde", 3231 | ] 3232 | 3233 | [[package]] 3234 | name = "utf-8" 3235 | version = "0.7.6" 3236 | source = "registry+https://github.com/rust-lang/crates.io-index" 3237 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 3238 | 3239 | [[package]] 3240 | name = "utf16_iter" 3241 | version = "1.0.5" 3242 | source = "registry+https://github.com/rust-lang/crates.io-index" 3243 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 3244 | 3245 | [[package]] 3246 | name = "utf8_iter" 3247 | version = "1.0.4" 3248 | source = "registry+https://github.com/rust-lang/crates.io-index" 3249 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 3250 | 3251 | [[package]] 3252 | name = "valuable" 3253 | version = "0.1.1" 3254 | source = "registry+https://github.com/rust-lang/crates.io-index" 3255 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 3256 | 3257 | [[package]] 3258 | name = "vcpkg" 3259 | version = "0.2.15" 3260 | source = "registry+https://github.com/rust-lang/crates.io-index" 3261 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 3262 | 3263 | [[package]] 3264 | name = "version_check" 3265 | version = "0.9.5" 3266 | source = "registry+https://github.com/rust-lang/crates.io-index" 3267 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 3268 | 3269 | [[package]] 3270 | name = "voter" 3271 | version = "0.1.1" 3272 | dependencies = [ 3273 | "anyhow", 3274 | "base64 0.22.1", 3275 | "blind-rsa-signatures", 3276 | "chrono", 3277 | "config", 3278 | "crossterm 0.29.0", 3279 | "dirs", 3280 | "fern", 3281 | "futures", 3282 | "log", 3283 | "nanoid", 3284 | "nostr-sdk", 3285 | "num-bigint-dig", 3286 | "rand 0.8.5", 3287 | "ratatui", 3288 | "serde", 3289 | "serde_json", 3290 | "sha2", 3291 | "sqlx", 3292 | "tokio", 3293 | ] 3294 | 3295 | [[package]] 3296 | name = "wasi" 3297 | version = "0.11.0+wasi-snapshot-preview1" 3298 | source = "registry+https://github.com/rust-lang/crates.io-index" 3299 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 3300 | 3301 | [[package]] 3302 | name = "wasi" 3303 | version = "0.14.2+wasi-0.2.4" 3304 | source = "registry+https://github.com/rust-lang/crates.io-index" 3305 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 3306 | dependencies = [ 3307 | "wit-bindgen-rt", 3308 | ] 3309 | 3310 | [[package]] 3311 | name = "wasite" 3312 | version = "0.1.0" 3313 | source = "registry+https://github.com/rust-lang/crates.io-index" 3314 | checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" 3315 | 3316 | [[package]] 3317 | name = "wasm-bindgen" 3318 | version = "0.2.100" 3319 | source = "registry+https://github.com/rust-lang/crates.io-index" 3320 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 3321 | dependencies = [ 3322 | "cfg-if", 3323 | "once_cell", 3324 | "rustversion", 3325 | "wasm-bindgen-macro", 3326 | ] 3327 | 3328 | [[package]] 3329 | name = "wasm-bindgen-backend" 3330 | version = "0.2.100" 3331 | source = "registry+https://github.com/rust-lang/crates.io-index" 3332 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 3333 | dependencies = [ 3334 | "bumpalo", 3335 | "log", 3336 | "proc-macro2", 3337 | "quote", 3338 | "syn", 3339 | "wasm-bindgen-shared", 3340 | ] 3341 | 3342 | [[package]] 3343 | name = "wasm-bindgen-futures" 3344 | version = "0.4.50" 3345 | source = "registry+https://github.com/rust-lang/crates.io-index" 3346 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 3347 | dependencies = [ 3348 | "cfg-if", 3349 | "js-sys", 3350 | "once_cell", 3351 | "wasm-bindgen", 3352 | "web-sys", 3353 | ] 3354 | 3355 | [[package]] 3356 | name = "wasm-bindgen-macro" 3357 | version = "0.2.100" 3358 | source = "registry+https://github.com/rust-lang/crates.io-index" 3359 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 3360 | dependencies = [ 3361 | "quote", 3362 | "wasm-bindgen-macro-support", 3363 | ] 3364 | 3365 | [[package]] 3366 | name = "wasm-bindgen-macro-support" 3367 | version = "0.2.100" 3368 | source = "registry+https://github.com/rust-lang/crates.io-index" 3369 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 3370 | dependencies = [ 3371 | "proc-macro2", 3372 | "quote", 3373 | "syn", 3374 | "wasm-bindgen-backend", 3375 | "wasm-bindgen-shared", 3376 | ] 3377 | 3378 | [[package]] 3379 | name = "wasm-bindgen-shared" 3380 | version = "0.2.100" 3381 | source = "registry+https://github.com/rust-lang/crates.io-index" 3382 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 3383 | dependencies = [ 3384 | "unicode-ident", 3385 | ] 3386 | 3387 | [[package]] 3388 | name = "web-sys" 3389 | version = "0.3.77" 3390 | source = "registry+https://github.com/rust-lang/crates.io-index" 3391 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 3392 | dependencies = [ 3393 | "js-sys", 3394 | "wasm-bindgen", 3395 | ] 3396 | 3397 | [[package]] 3398 | name = "webpki-roots" 3399 | version = "0.26.8" 3400 | source = "registry+https://github.com/rust-lang/crates.io-index" 3401 | checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" 3402 | dependencies = [ 3403 | "rustls-pki-types", 3404 | ] 3405 | 3406 | [[package]] 3407 | name = "whoami" 3408 | version = "1.6.0" 3409 | source = "registry+https://github.com/rust-lang/crates.io-index" 3410 | checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" 3411 | dependencies = [ 3412 | "redox_syscall", 3413 | "wasite", 3414 | ] 3415 | 3416 | [[package]] 3417 | name = "winapi" 3418 | version = "0.3.9" 3419 | source = "registry+https://github.com/rust-lang/crates.io-index" 3420 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 3421 | dependencies = [ 3422 | "winapi-i686-pc-windows-gnu", 3423 | "winapi-x86_64-pc-windows-gnu", 3424 | ] 3425 | 3426 | [[package]] 3427 | name = "winapi-i686-pc-windows-gnu" 3428 | version = "0.4.0" 3429 | source = "registry+https://github.com/rust-lang/crates.io-index" 3430 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 3431 | 3432 | [[package]] 3433 | name = "winapi-x86_64-pc-windows-gnu" 3434 | version = "0.4.0" 3435 | source = "registry+https://github.com/rust-lang/crates.io-index" 3436 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 3437 | 3438 | [[package]] 3439 | name = "windows-core" 3440 | version = "0.61.0" 3441 | source = "registry+https://github.com/rust-lang/crates.io-index" 3442 | checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" 3443 | dependencies = [ 3444 | "windows-implement", 3445 | "windows-interface", 3446 | "windows-link", 3447 | "windows-result", 3448 | "windows-strings", 3449 | ] 3450 | 3451 | [[package]] 3452 | name = "windows-implement" 3453 | version = "0.60.0" 3454 | source = "registry+https://github.com/rust-lang/crates.io-index" 3455 | checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 3456 | dependencies = [ 3457 | "proc-macro2", 3458 | "quote", 3459 | "syn", 3460 | ] 3461 | 3462 | [[package]] 3463 | name = "windows-interface" 3464 | version = "0.59.1" 3465 | source = "registry+https://github.com/rust-lang/crates.io-index" 3466 | checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 3467 | dependencies = [ 3468 | "proc-macro2", 3469 | "quote", 3470 | "syn", 3471 | ] 3472 | 3473 | [[package]] 3474 | name = "windows-link" 3475 | version = "0.1.1" 3476 | source = "registry+https://github.com/rust-lang/crates.io-index" 3477 | checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" 3478 | 3479 | [[package]] 3480 | name = "windows-result" 3481 | version = "0.3.2" 3482 | source = "registry+https://github.com/rust-lang/crates.io-index" 3483 | checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" 3484 | dependencies = [ 3485 | "windows-link", 3486 | ] 3487 | 3488 | [[package]] 3489 | name = "windows-strings" 3490 | version = "0.4.0" 3491 | source = "registry+https://github.com/rust-lang/crates.io-index" 3492 | checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" 3493 | dependencies = [ 3494 | "windows-link", 3495 | ] 3496 | 3497 | [[package]] 3498 | name = "windows-sys" 3499 | version = "0.48.0" 3500 | source = "registry+https://github.com/rust-lang/crates.io-index" 3501 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 3502 | dependencies = [ 3503 | "windows-targets 0.48.5", 3504 | ] 3505 | 3506 | [[package]] 3507 | name = "windows-sys" 3508 | version = "0.52.0" 3509 | source = "registry+https://github.com/rust-lang/crates.io-index" 3510 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 3511 | dependencies = [ 3512 | "windows-targets 0.52.6", 3513 | ] 3514 | 3515 | [[package]] 3516 | name = "windows-sys" 3517 | version = "0.59.0" 3518 | source = "registry+https://github.com/rust-lang/crates.io-index" 3519 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 3520 | dependencies = [ 3521 | "windows-targets 0.52.6", 3522 | ] 3523 | 3524 | [[package]] 3525 | name = "windows-targets" 3526 | version = "0.48.5" 3527 | source = "registry+https://github.com/rust-lang/crates.io-index" 3528 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 3529 | dependencies = [ 3530 | "windows_aarch64_gnullvm 0.48.5", 3531 | "windows_aarch64_msvc 0.48.5", 3532 | "windows_i686_gnu 0.48.5", 3533 | "windows_i686_msvc 0.48.5", 3534 | "windows_x86_64_gnu 0.48.5", 3535 | "windows_x86_64_gnullvm 0.48.5", 3536 | "windows_x86_64_msvc 0.48.5", 3537 | ] 3538 | 3539 | [[package]] 3540 | name = "windows-targets" 3541 | version = "0.52.6" 3542 | source = "registry+https://github.com/rust-lang/crates.io-index" 3543 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 3544 | dependencies = [ 3545 | "windows_aarch64_gnullvm 0.52.6", 3546 | "windows_aarch64_msvc 0.52.6", 3547 | "windows_i686_gnu 0.52.6", 3548 | "windows_i686_gnullvm", 3549 | "windows_i686_msvc 0.52.6", 3550 | "windows_x86_64_gnu 0.52.6", 3551 | "windows_x86_64_gnullvm 0.52.6", 3552 | "windows_x86_64_msvc 0.52.6", 3553 | ] 3554 | 3555 | [[package]] 3556 | name = "windows_aarch64_gnullvm" 3557 | version = "0.48.5" 3558 | source = "registry+https://github.com/rust-lang/crates.io-index" 3559 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 3560 | 3561 | [[package]] 3562 | name = "windows_aarch64_gnullvm" 3563 | version = "0.52.6" 3564 | source = "registry+https://github.com/rust-lang/crates.io-index" 3565 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 3566 | 3567 | [[package]] 3568 | name = "windows_aarch64_msvc" 3569 | version = "0.48.5" 3570 | source = "registry+https://github.com/rust-lang/crates.io-index" 3571 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 3572 | 3573 | [[package]] 3574 | name = "windows_aarch64_msvc" 3575 | version = "0.52.6" 3576 | source = "registry+https://github.com/rust-lang/crates.io-index" 3577 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 3578 | 3579 | [[package]] 3580 | name = "windows_i686_gnu" 3581 | version = "0.48.5" 3582 | source = "registry+https://github.com/rust-lang/crates.io-index" 3583 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 3584 | 3585 | [[package]] 3586 | name = "windows_i686_gnu" 3587 | version = "0.52.6" 3588 | source = "registry+https://github.com/rust-lang/crates.io-index" 3589 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 3590 | 3591 | [[package]] 3592 | name = "windows_i686_gnullvm" 3593 | version = "0.52.6" 3594 | source = "registry+https://github.com/rust-lang/crates.io-index" 3595 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 3596 | 3597 | [[package]] 3598 | name = "windows_i686_msvc" 3599 | version = "0.48.5" 3600 | source = "registry+https://github.com/rust-lang/crates.io-index" 3601 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 3602 | 3603 | [[package]] 3604 | name = "windows_i686_msvc" 3605 | version = "0.52.6" 3606 | source = "registry+https://github.com/rust-lang/crates.io-index" 3607 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 3608 | 3609 | [[package]] 3610 | name = "windows_x86_64_gnu" 3611 | version = "0.48.5" 3612 | source = "registry+https://github.com/rust-lang/crates.io-index" 3613 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 3614 | 3615 | [[package]] 3616 | name = "windows_x86_64_gnu" 3617 | version = "0.52.6" 3618 | source = "registry+https://github.com/rust-lang/crates.io-index" 3619 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 3620 | 3621 | [[package]] 3622 | name = "windows_x86_64_gnullvm" 3623 | version = "0.48.5" 3624 | source = "registry+https://github.com/rust-lang/crates.io-index" 3625 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 3626 | 3627 | [[package]] 3628 | name = "windows_x86_64_gnullvm" 3629 | version = "0.52.6" 3630 | source = "registry+https://github.com/rust-lang/crates.io-index" 3631 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 3632 | 3633 | [[package]] 3634 | name = "windows_x86_64_msvc" 3635 | version = "0.48.5" 3636 | source = "registry+https://github.com/rust-lang/crates.io-index" 3637 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 3638 | 3639 | [[package]] 3640 | name = "windows_x86_64_msvc" 3641 | version = "0.52.6" 3642 | source = "registry+https://github.com/rust-lang/crates.io-index" 3643 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 3644 | 3645 | [[package]] 3646 | name = "winnow" 3647 | version = "0.7.7" 3648 | source = "registry+https://github.com/rust-lang/crates.io-index" 3649 | checksum = "6cb8234a863ea0e8cd7284fcdd4f145233eb00fee02bbdd9861aec44e6477bc5" 3650 | dependencies = [ 3651 | "memchr", 3652 | ] 3653 | 3654 | [[package]] 3655 | name = "wit-bindgen-rt" 3656 | version = "0.39.0" 3657 | source = "registry+https://github.com/rust-lang/crates.io-index" 3658 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 3659 | dependencies = [ 3660 | "bitflags", 3661 | ] 3662 | 3663 | [[package]] 3664 | name = "write16" 3665 | version = "1.0.0" 3666 | source = "registry+https://github.com/rust-lang/crates.io-index" 3667 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 3668 | 3669 | [[package]] 3670 | name = "writeable" 3671 | version = "0.5.5" 3672 | source = "registry+https://github.com/rust-lang/crates.io-index" 3673 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 3674 | 3675 | [[package]] 3676 | name = "yaml-rust2" 3677 | version = "0.10.1" 3678 | source = "registry+https://github.com/rust-lang/crates.io-index" 3679 | checksum = "818913695e83ece1f8d2a1c52d54484b7b46d0f9c06beeb2649b9da50d9b512d" 3680 | dependencies = [ 3681 | "arraydeque", 3682 | "encoding_rs", 3683 | "hashlink", 3684 | ] 3685 | 3686 | [[package]] 3687 | name = "yoke" 3688 | version = "0.7.5" 3689 | source = "registry+https://github.com/rust-lang/crates.io-index" 3690 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 3691 | dependencies = [ 3692 | "serde", 3693 | "stable_deref_trait", 3694 | "yoke-derive", 3695 | "zerofrom", 3696 | ] 3697 | 3698 | [[package]] 3699 | name = "yoke-derive" 3700 | version = "0.7.5" 3701 | source = "registry+https://github.com/rust-lang/crates.io-index" 3702 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 3703 | dependencies = [ 3704 | "proc-macro2", 3705 | "quote", 3706 | "syn", 3707 | "synstructure", 3708 | ] 3709 | 3710 | [[package]] 3711 | name = "zerocopy" 3712 | version = "0.8.24" 3713 | source = "registry+https://github.com/rust-lang/crates.io-index" 3714 | checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" 3715 | dependencies = [ 3716 | "zerocopy-derive", 3717 | ] 3718 | 3719 | [[package]] 3720 | name = "zerocopy-derive" 3721 | version = "0.8.24" 3722 | source = "registry+https://github.com/rust-lang/crates.io-index" 3723 | checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" 3724 | dependencies = [ 3725 | "proc-macro2", 3726 | "quote", 3727 | "syn", 3728 | ] 3729 | 3730 | [[package]] 3731 | name = "zerofrom" 3732 | version = "0.1.6" 3733 | source = "registry+https://github.com/rust-lang/crates.io-index" 3734 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 3735 | dependencies = [ 3736 | "zerofrom-derive", 3737 | ] 3738 | 3739 | [[package]] 3740 | name = "zerofrom-derive" 3741 | version = "0.1.6" 3742 | source = "registry+https://github.com/rust-lang/crates.io-index" 3743 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 3744 | dependencies = [ 3745 | "proc-macro2", 3746 | "quote", 3747 | "syn", 3748 | "synstructure", 3749 | ] 3750 | 3751 | [[package]] 3752 | name = "zeroize" 3753 | version = "1.8.1" 3754 | source = "registry+https://github.com/rust-lang/crates.io-index" 3755 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 3756 | 3757 | [[package]] 3758 | name = "zerovec" 3759 | version = "0.10.4" 3760 | source = "registry+https://github.com/rust-lang/crates.io-index" 3761 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 3762 | dependencies = [ 3763 | "yoke", 3764 | "zerofrom", 3765 | "zerovec-derive", 3766 | ] 3767 | 3768 | [[package]] 3769 | name = "zerovec-derive" 3770 | version = "0.10.3" 3771 | source = "registry+https://github.com/rust-lang/crates.io-index" 3772 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 3773 | dependencies = [ 3774 | "proc-macro2", 3775 | "quote", 3776 | "syn", 3777 | ] 3778 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "ec", 4 | "voter", 5 | ] 6 | resolver = "2" 7 | 8 | [workspace.package] 9 | authors = ["Francisco Calderón "] 10 | homepage = "https://github.com/grunch/criptocracia" 11 | repository = "https://github.com/grunch/criptocracia.git" 12 | license = "MIT" 13 | rust-version = "1.86.0" 14 | 15 | [workspace.dependencies] 16 | serde = { version = "1.0", features = ["derive"] } 17 | tokio = { version = "1", features = ["macros", "rt-multi-thread"] } 18 | anyhow = "1" 19 | nostr-sdk = { version = "0.41", features = ["nip59"] } 20 | base64 = "0.22.1" 21 | num-bigint-dig = { version = "0.8", features = ["rand"] } 22 | nanoid = "0.4.0" 23 | serde_json = "1.0.140" 24 | blind-rsa-signatures = "0.15.2" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Francisco Calderón (negrunch) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Criptocracia 2 | 3 | ![logo](logo.png) 4 | 5 | *Disclaimer: The author is NOT a cryptographer and this work has not been reviewed. This means that there is very likely a fatal flaw somewhere. Criptocracia is still experimental and not production-ready.* 6 | 7 | **Criptocracia** is an experimental, trustless open-source electronic voting system built in Rust. It leverages blind RSA signatures to ensure vote secrecy, voter anonymity and integrity, and uses the Nostr protocol for decentralized, encrypted message transport. 8 | 9 | ## Context 10 | The critical need for secure, transparent, and anonymous electronic‑voting systems is becoming ever more pressing, especially in settings where trust in central authorities is limited—addressing concerns that authoritarian regimes may use electoral systems to stay in power. The historical challenges of electoral fraud underscore the importance of exploring robust solutions. Modern cryptography provides powerful tools for building systems that can withstand manipulation and allow for public verification. 11 | 12 | ## Goal 13 | The goal of leveraging open technologies such as the Rust programming language and the Nostr protocol, along with cryptographic techniques (initially, blind signatures), to develop a fraud-resistant and publicly auditable voting system is recognized. 14 | 15 | ## Fundamental Requirements 16 | Derived from the initial consultation, the key security properties for this system are: 17 | - Vote Secrecy/Anonymity: Voter choices must remain hidden from the Electoral Commission (EC) and third parties. 18 | - Voter Authentication: Only eligible voters, identified by their Nostr public keys, are eligible to participate. 19 | - Vote Uniqueness: Each voter may cast only one valid vote. 20 | - Verifiability/Auditability: The electoral process and results must be publicly verifiable without compromising the identity of the voter, minimizing the trust required in the central tallying authority. (This central tallying authority may be comprised of a committee composed of a representative from each voting option.) 21 | - Nostr's Role: Nostr is proposed as the underlying communication layer. Its decentralized, public/private event-based features can be used for both vote transmission and the implementation of a public bulletin board. Features such as NIP-59 Gift Wrap are used to encrypt data during transmission, protecting the confidentiality of the vote in transit. 22 | 23 | ## Voters 24 | Registered users with a Nostr key pair (public and private). The public key (voter_pk) identifies the voter to the Electoral Commission. 25 | 26 | ## Electoral Commission (EC) 27 | - Maintains a record of the public keys of authorized voters. 28 | - Issues anonymous voting tokens using blind signatures. 29 | - Receives encrypted votes. 30 | - Verifies the validity of the tokens and prevents double voting. 31 | - Performs and publish in Nostr the final count 32 | 33 | ## Nostr: Communication protocol used for: 34 | - Requesting blind signatures (via NIP-59 Gift Wrap). 35 | - Casting encrypted votes (via NIP-59 Gift Wrap). 36 | 37 | ## Architecture 38 | 39 | Criptocracia is organized as a Cargo workspace containing two main binaries: 40 | 41 | * **ec**: The Electoral Commission service that registers voters, issues blind signatures on voting tokens, receives anonymized votes, verifies them, and publishes results. 42 | * **voter**: The client-side application used by registered voters to request a blind-signed token, unblind it, and cast their vote. 43 | 44 | Shared workspace dependencies include: 45 | 46 | * `blind-rsa-signatures v0.15.2` for RSA-based blind signature operations. 47 | * `nostr-sdk v0.41.0` (feature `nip59`) for Gift Wrap message encryption and transport. 48 | * `base64`, `num-bigint-dig`, `sha2`, and other utility crates for serialization and hashing. 49 | 50 | --- 51 | 52 | ## Cryptographic Protocol 53 | 54 | 1. **Blind Signature Request** 55 | 56 | * Voter generates a random nonce (128-bit BigUint) and computes `h_n = SHA256(nonce)`. 57 | * Voter blinds `h_n` using the EC's RSA public key to obtain `blinded_h_n` and a blinding factor. 58 | * `blinded_h_n` is Base64-encoded and sent to the EC via an encrypted Nostr Gift Wrap message. 59 | 60 | 2. **Blind Signature Issuance** 61 | 62 | * EC verifies the sender’s Nostr public key against the authorized voter list. 63 | * EC uses its RSA secret key to sign `blinded_h_n`, producing a blind signature. 64 | * EC encodes the blind signature in Base64 and returns it via Gift Wrap. 65 | 66 | 3. **Unblinding and Voting** 67 | 68 | * Voter decodes the blind signature, unblinds it using the stored blinding factor, and verifies the resulting token against the EC’s public RSA key. 69 | * The voter packages `(h_n, token, blinding_factor, candidate_id)` into a colon-delimited payload, Base64-encodes the first three parts, and sends via Gift Wrap with a freshly generated Nostr key pair to anonymize origin. 70 | 71 | 4. **Vote Reception and Tally** 72 | 73 | * EC receives the vote payload, decodes `h_n`, `token`, and `blinding_factor` from Base64, and parses `candidate_id`. 74 | * EC verifies the signature on `h_n` and checks that `h_n` has not been used before (prevents double voting). 75 | * Valid votes are recorded, tallied, and results published to Nostr as a custom event. 76 | 77 | --- 78 | 79 | ## Configuration and Usage 80 | 81 | Go to the directory of voter and ec for specific instructions. 82 | 83 | --- 84 | 85 | ## Limitations 86 | 87 | * **Experimental**: No formal security audit. Use only for study/demonstration. 88 | * **Single EC**: Central authority issues tokens—no threshold or multi-party setup. 89 | * **Replay Protection**: Based on one-time `h_n`, but stronger measures (timestamps, channels) may be needed. 90 | 91 | --- 92 | 93 | ## Nostr 94 | 95 | As mentioned above, both the voter and the EC communicate by sending Gift Wrap events, but there are other messages that the EC publishes. 96 | 97 | ### Election 98 | 99 | An addressable event kind `35000` with the election information in a serialized json object in the content field of the event, here an example of the json object: 100 | 101 | ```json 102 | { 103 | "id": "f5f7", 104 | "name": "Libertad 2024", 105 | "start_time": 1746611643, 106 | "status": "open", 107 | "candidates": [ 108 | { 109 | "id": 1, 110 | "name": "Donkey 🫏" 111 | }, 112 | { 113 | "id": 2, 114 | "name": "Rat 🐀" 115 | }, 116 | { 117 | "id": 3, 118 | "name": "Sheep 🐑" 119 | }, 120 | { 121 | "id": 4, 122 | "name": "Sloth 🦥" 123 | } 124 | ], 125 | "end_time": 1746615243 126 | } 127 | ``` 128 | 129 | The event would look like this: 130 | 131 | ```json 132 | [ 133 | "EVENT", 134 | "7157aabf-389e-4d3e-9656-4d818159dff2", 135 | { 136 | "tags": [ 137 | [ 138 | "d", 139 | "f5f7" 140 | ], 141 | [ 142 | "expiration", 143 | "1747043643" 144 | ] 145 | ], 146 | "content": "{\"candidates\":[{\"id\":1,\"name\":\"Donkey 🫏\"},{\"id\":2,\"name\":\"Rat 🐀\"},{\"id\":3,\"name\":\"Sheep 🐑\"},{\"id\":4,\"name\":\"Sloth 🦥\"}],\"end_time\":1746615243,\"id\":\"f5f7\",\"name\":\"Libertad 2024\",\"start_time\":1746611643,\"status\":\"open\"}", 147 | "sig": "8b5bc04003c1d20ba98d33b2fd98a536d538d58afa1c9cfa81d3b693a3a20a764b51258e28335b10945439f7a09fca1d4d2ac40135a506e1bb4a8116259c46ab", 148 | "id": "557d833876048e50068dfb06b82344a058d8104f08578e8060623ec8004c29ac", 149 | "pubkey": "0000001ace57d0da17fc18562f4658ac6d093b2cc8bb7bd44853d0c196e24a9c", 150 | "created_at": 1746611643, 151 | "kind": 35000 152 | } 153 | ] 154 | ``` 155 | 156 | ### Current status of the election 157 | 158 | After each vote is received, the EC will publish another addressable event of kind `35001`. The event’s content field will contain the current status of the election as a serialized JSON array: the first element is the candidate ID, and the second element is the number of votes received. For example, in an election with the same candidates shown above—where **Sloth 🦥** received 21 vote and **Sheep 🐑** received 35 votes—the event would look like this: 159 | 160 | ```json 161 | [ 162 | "EVENT", 163 | "7157aabf-389e-4d3e-9656-4d818159dff2", 164 | { 165 | "tags": [ 166 | [ 167 | "d", 168 | "f5f7" 169 | ], 170 | [ 171 | "expiration", 172 | "1747043706" 173 | ] 174 | ], 175 | "content": "[[4,21],[3,35]]", 176 | "sig": "3eb717f176be137d7adc0f9e6d52556c38d988bce59c2f683cbdc6f796df3a3e6d31aecf2866fa2df5d58ce7a287236f83e2c368a89015f7b8f4c5eea21e134d", 177 | "id": "7ae5c519f9e8886b70d0cef6155a69f3194e7b89cb88e589ed2012853915581e", 178 | "pubkey": "0000001ace57d0da17fc18562f4658ac6d093b2cc8bb7bd44853d0c196e24a9c", 179 | "created_at": 1746611706, 180 | "kind": 35001 181 | } 182 | ] 183 | ``` 184 | 185 | --- 186 | 187 | ## License 188 | 189 | This project is licensed under MIT. See [LICENSE](LICENSE) for details. 190 | 191 | --- 192 | 193 | ## Todo list 194 | - [x] EC publish list of candidates as a Nostr event 195 | - [x] EC: Add manually voters pubkeys 196 | - [ ] EC create a list of registration tokens to be send to voters (v0.2) 197 | - [ ] Voter creates key pair and sign the token (v0.2) 198 | - [ ] Voter send the registration token to EC in a gift wrap (v0.2) 199 | - [ ] EC receives the registration token and save the voter's pubkey (v0.2) 200 | - [x] Voter generates a nonce, hash it and send it to EC 201 | - [x] voter: Add CLI to handle arguments 202 | - [x] EC: async waiting for events and handle logs 203 | - [x] Voter: List elections on voter UI 204 | - [x] Voter: User select election and get list of candidates 205 | - [x] EC blind sign the voting token and send it back to the voter 206 | - [x] Voter cast vote 207 | - [x] EC receive vote 208 | - [x] EC Count votes and publish to Nostr 209 | - [x] EC should change the status election depending `start_time` and `end_time` 210 | - [x] voter: Show results in real time 211 | -------------------------------------------------------------------------------- /data/voters.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Alice", 4 | "pubkey": "00001001063e6bf1b28f6514ac651afef7f51b2a792f0416a5e8273daa9eea6e", 5 | "secret": "30df83c45dee3b379c91be29cbbf6ecdbcfd8e9d96979e98b9e8162505d2047a" 6 | }, 7 | { 8 | "name": "Bob", 9 | "pubkey": "00000eab0ee981184e7e5dd144360885087f6a5889b46abfe5c402bf490fcd24", 10 | "secret": "1485727f623f65dedfa38c0795623621e004986950d0167372213da0773ae2b7" 11 | }, 12 | { 13 | "name": "Charlie", 14 | "pubkey": "00000699921ac7021b7da121da5bd762d90be830c3964ba12e82b590445797a6", 15 | "secret": "4971d6cf62a8152e41d6d03c9608e1a2e9868ef8d7411d67e30c5155fedb8ef0" 16 | }, 17 | { 18 | "name": "Daniel", 19 | "pubkey": "00000217c8b36c2098604aa15a2317739de96b62c3a49cb648da7b35ddc3c683", 20 | "secret": "53d8392d02949b7d3aa05d0a9ee00fe6e0c2aae237f26f4a3ef3e05dcda3d99d" 21 | } 22 | ] -------------------------------------------------------------------------------- /ec/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ec" 3 | version = "0.1.1" 4 | edition = "2024" 5 | authors.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | rust-version.workspace = true 10 | 11 | [dependencies] 12 | nostr-sdk = { workspace = true, features = ["nip59"] } 13 | anyhow = { workspace = true } 14 | tokio = { workspace = true } 15 | serde = { workspace = true } 16 | base64 = { workspace = true } 17 | num-bigint-dig = { workspace = true } 18 | nanoid = { workspace = true } 19 | serde_json = { workspace = true } 20 | blind-rsa-signatures = { workspace = true } 21 | 22 | chrono = "0.4.40" 23 | tracing-subscriber = "0.3.19" 24 | rand = "0.8" 25 | log = "0.4.27" 26 | fern = "0.7.1" 27 | 28 | [dev-dependencies] 29 | sha2 = "0.10" -------------------------------------------------------------------------------- /ec/README.md: -------------------------------------------------------------------------------- 1 | # Criptocracia - Electoral Commission 2 | 3 | ![logo](../logo.png) 4 | 5 | *Disclaimer: The author is NOT a cryptographer and this work has not been reviewed. This means that there is very likely a fatal flaw somewhere. Criptocracia is still experimental and not production-ready.* 6 | 7 | --- 8 | 9 | ## Prerequisites 10 | 11 | * Rust toolchain (>= 1.86.0) 12 | * Nostr relay endpoint (e.g., `wss://relay.mostro.network`) 13 | 14 | Ensure you have Git and Cargo installed. Clone the repository: 15 | 16 | ```sh 17 | git clone https://github.com/grunch/criptocracia.git 18 | cd criptocracia/ec 19 | ``` 20 | 21 | --- 22 | 23 | ## Building the Project 24 | 25 | From the workspace root: 26 | 27 | ```sh 28 | # Build both binaries in release mode 29 | cargo build --release 30 | ``` 31 | 32 | The binary will be in `target/release/ec`. 33 | 34 | --- 35 | 36 | ## Configuration 37 | 38 | Manually add voters pubkeys in the file `voters_pubkeys.json`. 39 | 40 | In order to work with blind signatures we need to create a RSA private key 41 | 42 | ```sh 43 | # 1) Generate private key (RSA 2048 bits) 44 | openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \ 45 | -out ec_private.pem 46 | 47 | # 2) Extract the public key 48 | openssl rsa -in ec_private.pem -pubout -out ec_public.pem 49 | ``` 50 | 51 | Now you can share publicly the public key to all voters, they need to include it in their voter client. 52 | 53 | To simplify the testing of this project we have already created a couple of keys and included them in this repository. 54 | 55 | --- 56 | 57 | ## Usage 58 | 59 | 1. Start the EC service: 60 | 61 | ```sh 62 | target/release/ec 63 | ``` 64 | 2. The EC will publish the candidate list to Nostr and wait for blind signature requests. 65 | 3. Voter requests will be logged and served automatically. 66 | 4. Once votes arrive, EC will verify, tally, and publish results. 67 | 68 | --- 69 | 70 | ## Logging and Debugging 71 | 72 | Logs are written to `app.log` in the current working directory. Set `log_level` in settings to `debug` for verbose output. 73 | -------------------------------------------------------------------------------- /ec/ec_private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDLOuMoqXPwmnIq 3 | uqcI2vaf+JfBKAcCes3JnA4npRbhpy3EOcjd3LB46pe0ZnIiDj494SYOtNjmw/qk 4 | 3anmpUzpcaa3sMJ9K0mBoKkHQP8Fl39qv+yH1qP0BMtB7bj9QNfB4ZODNZWyTLy4 5 | JE4NzNrGEM9e/BEBAgK5k07c7FDmyzu5zVl9INL77znuryTood1udaQDLf42gayV 6 | laohAf+H20OXzN8ofkA/kxmJt2dr78/bBvPzr/y6r6EG6nHDKamJBfEsHut+N86Q 7 | tfbZaC6x6i+lsD/sh/cs2d53lS7FfUjG4XMG3DtbVEDLwHUyqUCht/kr57hfMDOX 8 | j0CpCfqzAgMBAAECggEAF4PNyuOofZtxQE5ui1DCnonmDTxzay8IZp5+6MlqV1u/ 9 | qOfCvSEO7j6+pOoBpL0fKIvHmoYEXtcoRjE7ums/9fbngnOaXV9H1w7e3+7+UwhP 10 | fuuME7+bIt33Ir6929fH3zAZoGHv2zyTzX6t5Vzhp29Ef0oNMZ+o7w4DXv6c8cc3 11 | Wpl52suNclz//p7tkGYjtHNGAabSmoFYZhBEjTHclhgPCs3hkgIiSXiT4c28O+hn 12 | pPjqjY+ALiAQ6TMCuB250kSHKHWLcTEdEqCI4nbtiCe852csBsZeYSiZ3HILZhSd 13 | 5/PLkXLRttC+HmQ946GlrCOhyFrIewvMhMT6QF12MQKBgQDYJ0+njfucgzdLJ9f2 14 | +TM/JgDmUbn2cJ114G5lvFsosQvSVjXK62QSDBWtjdLMDJkU7CQSg8amhgZPgzBu 15 | zsGRf6Mi4cb9YrXTqGJ87pPhtbHsp6K0kQtjWpk8Yy0JgDdXR9BwAnrHwnYUeupq 16 | zmaKUUUzFg9FVktLFtk+/Bj+iQKBgQDwsbDTYYJy8CUzbEyw2C6BTBpRKH7tF32N 17 | ufViHrAHfQV8tJQP0eQwTr0Uqx5vocGg7OjFVT1OUY0pXYNkHGWZCGUnldcbOCam 18 | r44zbVEk8YJC0Z9fM5G9U/U/Ost40lrGB0NlwY6d18fTjpIJ/aWM5fW6+pUSDH89 19 | FeDhdQuAWwKBgAyhz3/lRk0RRgv4WiCu05XfLLJJGGsUjb8zzH/ZkCJCpoQ2UZJ4 20 | SzLazfGElksieVfFrR3/4X4d2wSOkCgJoTpVkT0aoLxyJlomPws6Dh5kte80pMeU 21 | qmu2AbqLuTgS7CkHo2DIZFCERs5PmJ+BTHDM6xRfN6k/r8rFnRCXPwaxAoGAfj9F 22 | l2oS6USqzpEknLGXmvwW5bDO+n8SvO7oFYIxJIxf/2wcKTwXa3sxVBD5UuZOUKFS 23 | 6oZuNJEz8Jl7HFyEscMkg6HlhQJry4xTkwfowu7mOzQGWwIKlHrgLT0ikooLUMlo 24 | gYwHySTwTDgAw7rGReQsgtmCrUfeyWSbYsZotPcCgYEAmpNvn6N8dApA1Bxo+Phq 25 | iMyOqgt3twtvMeb+PgttHbN1PAidB7y/tuqHhNSugOSYnejxPL+usZovQOrR4I/2 26 | eEsAyGyHwxeg5E6IHfia87NPjMzV2dm+k+dzN08Wy3aNwAQTRgngNc8aN+dsmU7Y 27 | W+aipD7vvxPN16yBxqWo/Ts= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /ec/ec_public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyzrjKKlz8JpyKrqnCNr2 3 | n/iXwSgHAnrNyZwOJ6UW4actxDnI3dyweOqXtGZyIg4+PeEmDrTY5sP6pN2p5qVM 4 | 6XGmt7DCfStJgaCpB0D/BZd/ar/sh9aj9ATLQe24/UDXweGTgzWVsky8uCRODcza 5 | xhDPXvwRAQICuZNO3OxQ5ss7uc1ZfSDS++857q8k6KHdbnWkAy3+NoGslZWqIQH/ 6 | h9tDl8zfKH5AP5MZibdna+/P2wbz86/8uq+hBupxwympiQXxLB7rfjfOkLX22Wgu 7 | seovpbA/7If3LNned5UuxX1IxuFzBtw7W1RAy8B1MqlAobf5K+e4XzAzl49AqQn6 8 | swIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /ec/src/election.rs: -------------------------------------------------------------------------------- 1 | /*! election.rs — Electoral Commission logic 2 | Manages voter registration, issuance of blind tokens, vote reception, and counting. */ 3 | 4 | use blind_rsa_signatures::{BlindSignature, BlindedMessage, Options, SecretKey as RSASecretKey}; 5 | use nanoid::nanoid; 6 | use num_bigint_dig::BigUint; 7 | use rand::thread_rng; 8 | use serde_json::Value; 9 | use std::collections::{HashMap, HashSet}; 10 | 11 | use crate::Candidate; 12 | 13 | /// Blind signature petition made by a voter. 14 | pub struct BlindTokenRequest { 15 | pub voter_pk: String, 16 | pub blinded_h_n: BlindedMessage, 17 | } 18 | 19 | #[allow(dead_code)] 20 | #[derive(Debug, PartialEq, Clone, Copy)] 21 | pub enum Status { 22 | Open, 23 | InProgress, 24 | Finished, 25 | Canceled, 26 | } 27 | 28 | /// Commissioner of Elections (CE) manages the election process. 29 | #[derive(Debug, Clone)] 30 | pub struct Election { 31 | pub id: String, 32 | pub name: String, 33 | pub authorized_voters: HashSet, // allowed pubkeys 34 | pub used_tokens: HashSet, // h_n already used 35 | pub votes: Vec, // votes received 36 | pub candidates: Vec, 37 | pub start_time: u64, 38 | pub end_time: u64, 39 | pub status: Status, 40 | } 41 | 42 | impl Election { 43 | /// Create a new EC with a 2048-bit RSA key. 44 | pub fn new(name: String, candidates: Vec, start_time: u64, duration: u64) -> Self { 45 | let end_time = start_time + duration; 46 | let id = nanoid!( 47 | 4, 48 | &[ 49 | '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f' 50 | ] 51 | ); 52 | Self { 53 | id, 54 | name, 55 | authorized_voters: HashSet::new(), 56 | used_tokens: HashSet::new(), 57 | votes: vec![], 58 | candidates, 59 | start_time, 60 | end_time, 61 | status: Status::Open, 62 | } 63 | } 64 | 65 | pub fn register_voter(&mut self, voter_pk: &str) { 66 | if self.status != Status::Open { 67 | log::warn!("Cannot register voter: election is not open"); 68 | return; 69 | } 70 | println!("🔑 Registering voter: {}", voter_pk); 71 | // 1) Check that the pubkey is not already registered. 72 | if self.authorized_voters.contains(voter_pk) { 73 | println!("⚠️ Voter already registered"); 74 | return; 75 | } 76 | // 2) Add to the list of authorized voters. 77 | self.authorized_voters.insert(voter_pk.to_string()); 78 | } 79 | 80 | /// Blindly signs the hash submitted by a voter. 81 | pub fn issue_token( 82 | &mut self, 83 | req: BlindTokenRequest, 84 | secret_key: RSASecretKey, 85 | ) -> Result { 86 | let options = Options::default(); 87 | let rng = &mut thread_rng(); 88 | // Check that the voter is authorized and has not previously requested it. 89 | if !self.authorized_voters.remove(&req.voter_pk) { 90 | return Err("Unauthorized voter or nonce hash already issued"); 91 | } 92 | // 2) Sign it 93 | let blind_sig = secret_key 94 | .blind_sign(rng, &req.blinded_h_n, &options) 95 | .map_err(|_| "signing error")?; 96 | log::info!("Blind signature issued"); 97 | Ok(blind_sig) 98 | } 99 | 100 | /// Receives a vote along with (h_n, token) and verifies validity. 101 | pub fn receive_vote(&mut self, h_n: BigUint, vote: u8) -> Result<(), &'static str> { 102 | if self.status != Status::InProgress { 103 | return Err("Cannot receive vote: election is not in progress"); 104 | } 105 | // Avoid double voting. 106 | if !self.used_tokens.insert(h_n.clone()) { 107 | log::warn!("Duplicate token detected for h_n={}", h_n); 108 | return Err("duplicated vote"); 109 | } 110 | // Store vote (for demo purposes it will be the candidate's number). 111 | self.votes.push(vote); 112 | println!("✅ Vote received"); 113 | 114 | Ok(()) 115 | } 116 | 117 | /// Returns a map candidate → number of votes. 118 | pub fn tally(&self) -> HashMap { 119 | let mut counts = HashMap::new(); 120 | for &v in &self.votes { 121 | if let Some(c) = self.candidates.iter().find(|c| c.id == v) { 122 | *counts.entry(*c).or_insert(0) += 1; 123 | } 124 | } 125 | counts 126 | } 127 | 128 | pub fn as_json(&self) -> Value { 129 | let election_data = serde_json::json!({ 130 | "id": self.id.to_string(), 131 | "name": self.name, 132 | "start_time": self.start_time, 133 | "end_time": self.end_time, 134 | "candidates": self.candidates, 135 | "status": match self.status { 136 | Status::Open => "open", 137 | Status::InProgress => "in-progress", 138 | Status::Finished => "finished", 139 | Status::Canceled => "canceled", 140 | }, 141 | }); 142 | election_data 143 | } 144 | 145 | pub fn as_json_string(&self) -> String { 146 | let election_data = self.as_json(); 147 | serde_json::to_string(&election_data).unwrap() 148 | } 149 | } 150 | 151 | #[cfg(test)] 152 | mod tests { 153 | use super::*; 154 | use num_bigint_dig::{BigUint, RandBigInt}; 155 | use serde_json::Value; 156 | use crate::util::load_keys; 157 | use blind_rsa_signatures::{Options}; 158 | use rand::rngs::OsRng; 159 | use sha2::{Digest, Sha256}; 160 | use std::collections::HashMap; 161 | 162 | fn make_election() -> Election { 163 | // start_time = 1000, duration = 3600 164 | Election::new( 165 | "TestElect".to_string(), 166 | vec![Candidate::new(1, "Alice"), Candidate::new(2, "Bob")], 167 | 1000, 168 | 3600, 169 | ) 170 | } 171 | 172 | #[test] 173 | fn test_new_election_defaults() { 174 | let e = make_election(); 175 | assert_eq!(e.name, "TestElect"); 176 | assert_eq!(e.status, Status::Open); 177 | assert_eq!(e.start_time, 1000); 178 | assert_eq!(e.end_time, 1000 + 3600); 179 | assert!(e.authorized_voters.is_empty()); 180 | assert!(e.votes.is_empty()); 181 | assert!(e.used_tokens.is_empty()); 182 | assert_eq!(e.candidates.len(), 2); 183 | assert_eq!(e.id.len(), 4); 184 | } 185 | 186 | #[test] 187 | fn test_register_voter_and_duplicate() { 188 | let mut e = make_election(); 189 | let pk = "npub_test"; 190 | e.register_voter(pk); 191 | assert!(e.authorized_voters.contains(pk)); 192 | 193 | // Re-registration does not duplicate 194 | e.register_voter(pk); 195 | assert_eq!(e.authorized_voters.len(), 1); 196 | 197 | // Change status to InProgress and do not allow registration 198 | e.status = Status::InProgress; 199 | e.register_voter("otra"); 200 | assert_eq!(e.authorized_voters.len(), 1); 201 | } 202 | 203 | #[test] 204 | fn test_receive_vote_errors_and_success() { 205 | let mut e = make_election(); 206 | let h1 = BigUint::from(42u8); 207 | 208 | // Status Open → error 209 | assert_eq!( 210 | e.receive_vote(h1.clone(), 1).unwrap_err(), 211 | "Cannot receive vote: election is not in progress" 212 | ); 213 | 214 | // change to InProgress 215 | e.status = Status::InProgress; 216 | 217 | // First valid vote 218 | assert!(e.receive_vote(h1.clone(), 2).is_ok()); 219 | assert_eq!(e.votes, vec![2]); 220 | 221 | // Duplicated vote → error 222 | assert_eq!( 223 | e.receive_vote(h1.clone(), 2).unwrap_err(), 224 | "duplicated vote" 225 | ); 226 | } 227 | 228 | #[test] 229 | fn test_tally_counts() { 230 | let mut e = make_election(); 231 | e.status = Status::InProgress; 232 | // Valid votes to Alice(id=1) and Bob(id=2) 233 | let _ = e.receive_vote(BigUint::from(1u8), 1); 234 | let _ = e.receive_vote(BigUint::from(2u8), 2); 235 | let _ = e.receive_vote(BigUint::from(3u8), 1); 236 | 237 | let counts = e.tally(); 238 | let mut expected = HashMap::new(); 239 | expected.insert(Candidate::new(1, "Alice"), 2); 240 | expected.insert(Candidate::new(2, "Bob"), 1); 241 | assert_eq!(counts, expected); 242 | } 243 | 244 | #[test] 245 | fn test_as_json_string_and_value() { 246 | let mut e = make_election(); 247 | e.status = Status::Finished; 248 | let s = e.as_json_string(); 249 | let v: Value = serde_json::from_str(&s).unwrap(); 250 | assert_eq!(v["name"], "TestElect"); 251 | assert_eq!(v["status"], "finished"); 252 | assert_eq!(v["start_time"], 1000); 253 | assert_eq!(v["end_time"], 4600); 254 | // candidates 255 | let cands = v["candidates"].as_array().unwrap(); 256 | assert_eq!(cands.len(), 2); 257 | assert_eq!(cands[0]["id"], 1); 258 | assert_eq!(cands[1]["name"], "Bob"); 259 | } 260 | 261 | #[test] 262 | fn test_blind_signature_flow() { 263 | // Load RSA keys 264 | let (pk, sk) = 265 | load_keys("ec_private.pem", "ec_public.pem").expect("The keys could not be loaded"); 266 | 267 | // Simulates the voter: generates a random 128-bit nonce and its SHA256 hash 268 | let rng = &mut rand::thread_rng(); 269 | // Generate nonce and its hash 270 | let nonce: BigUint = OsRng.gen_biguint(128); 271 | // Hash the nonce 272 | let h_n_bytes = Sha256::digest(nonce.to_bytes_be()).to_vec(); 273 | 274 | // Voter blind the hash 275 | let options = Options::default(); 276 | let blinding_result = pk 277 | .blind(rng, &h_n_bytes, true, &options) 278 | .expect("Error blinding message"); 279 | 280 | // The server (EC) issues the blind signature 281 | let blind_sig = sk 282 | .blind_sign(rng, &blinding_result.blind_msg, &options) 283 | .expect("Blind signing error"); 284 | 285 | // The voter "unblinds" 286 | let token = pk.finalize( 287 | &blind_sig, 288 | &blinding_result.secret, 289 | blinding_result.msg_randomizer, 290 | &h_n_bytes, 291 | &options, 292 | ).expect("Error getting the token"); 293 | // Voter verify against the original hash. 294 | assert!( 295 | token.verify(&pk, blinding_result.msg_randomizer, &h_n_bytes, &options).is_ok(), 296 | "The token is not valid" 297 | ); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /ec/src/main.rs: -------------------------------------------------------------------------------- 1 | mod election; 2 | mod types; 3 | mod util; 4 | 5 | use crate::election::{Election, Status}; 6 | use crate::util::{load_keys, setup_logger}; 7 | 8 | use anyhow::Result; 9 | use base64::{Engine as _, engine::general_purpose}; 10 | use blind_rsa_signatures::{BlindedMessage, MessageRandomizer, Options, Signature as RSASignature}; 11 | use election::BlindTokenRequest; 12 | use nostr_sdk::prelude::*; 13 | use num_bigint_dig::BigUint; 14 | use std::{ 15 | fs, 16 | sync::{Arc, Mutex}, 17 | }; 18 | use tokio::{ 19 | sync::mpsc, 20 | time::{Duration, Instant, sleep_until}, 21 | }; 22 | use types::{Candidate, Message, Voter}; 23 | 24 | // Demo keys for the electoral commission: 25 | // Hex public key: 0000001ace57d0da17fc18562f4658ac6d093b2cc8bb7bd44853d0c196e24a9c 26 | // Hex private key: e3f33350728580cd51db8f4048d614910d48a5c0d7f1af6811e83c07fc865a5c 27 | // Npub public key: npub1qqqqqxkw2lgd59lurptz73jc43ksjwevezahh4zg20gvr9hzf2wq8nzqyl 28 | // Nsec private key: nsec1u0enx5rjskqv65wm3aqy34s5jyx53fwq6lc676q3aq7q0lyxtfwqph3yue 29 | 30 | /// Publish the state of the election 31 | async fn publish_election_event(client: &Client, keys: &Keys, election: &Election) -> Result<()> { 32 | log::info!( 33 | "Publishing election {} status: {:?}", 34 | election.id, 35 | election.status 36 | ); 37 | // Old election events are expired after 15 days 38 | let expire_ts = chrono::Utc::now() 39 | .checked_add_signed(chrono::Duration::days(15)) 40 | .unwrap() 41 | .timestamp() as u64; 42 | let future_ts = Timestamp::from(expire_ts); 43 | let event = EventBuilder::new(Kind::Custom(35_000), election.as_json_string()) 44 | .tag(Tag::identifier(election.id.to_string())) 45 | .tag(Tag::expiration(future_ts)) 46 | .sign(keys) 47 | .await?; 48 | 49 | client.send_event(&event).await?; 50 | log::info!( 51 | "Event with election {} status {:?} broadcast to Nostr relays!", 52 | election.id, 53 | election.status 54 | ); 55 | Ok(()) 56 | } 57 | 58 | #[tokio::main] 59 | async fn main() -> Result<()> { 60 | // Initialize logger 61 | setup_logger(log::LevelFilter::Info).expect("Can't initialize logger"); 62 | log::info!("Criptocracia started"); 63 | let keys = Keys::parse("e3f33350728580cd51db8f4048d614910d48a5c0d7f1af6811e83c07fc865a5c")?; 64 | 65 | // 1. Load the keys from PEM files 66 | let (pk, sk) = load_keys("ec_private.pem", "ec_public.pem")?; 67 | 68 | println!("🔑 Electoral Commission Public key: {}", keys.public_key()); 69 | 70 | // Build the signing client 71 | let client = Client::builder().signer(keys.clone()).build(); 72 | 73 | // Add the Mostro relay and connect 74 | client.add_relay("wss://relay.mostro.network").await?; 75 | client.connect().await; 76 | // The election starts in 60 seconds 77 | let starting_ts = chrono::Utc::now().timestamp() as u64 + 60; 78 | // Duration of the election 79 | let duration = 60 * 60; // 1 hour in seconds 80 | let election = Election::new( 81 | "Libertad 2024".to_string(), 82 | vec![ 83 | Candidate::new(1, "Donkey 🫏"), 84 | Candidate::new(2, "Rat 🐀"), 85 | Candidate::new(3, "Sheep 🐑"), 86 | Candidate::new(4, "Sloth 🦥"), 87 | ], 88 | starting_ts, 89 | duration, 90 | ); 91 | let election = Arc::new(Mutex::new(election)); 92 | // --- Timers to change status automatically --- 93 | { 94 | let election = Arc::clone(&election); 95 | let keys = keys.clone(); 96 | let client = client.clone(); 97 | tokio::spawn(async move { 98 | let start_ts = { 99 | let e = election.lock().unwrap(); 100 | e.start_time 101 | }; 102 | let now = chrono::Utc::now().timestamp() as u64; 103 | let delay = start_ts.saturating_sub(now); 104 | sleep_until(Instant::now() + Duration::from_secs(delay)).await; 105 | let e_data = { 106 | let mut e = election.lock().unwrap(); 107 | e.status = Status::InProgress; 108 | log::info!("Election {} -> InProgress", e.id); 109 | e.clone() 110 | }; 111 | // Publish the event with the new status 112 | if let Err(err) = publish_election_event(&client, &keys, &e_data).await { 113 | log::error!( 114 | "Error publishing election with status {:?}: {}", 115 | e_data.status, 116 | err 117 | ); 118 | } 119 | }); 120 | } 121 | { 122 | let election = Arc::clone(&election); 123 | let keys = keys.clone(); 124 | let client = client.clone(); 125 | tokio::spawn(async move { 126 | let end_ts = { 127 | let e = election.lock().unwrap(); 128 | e.end_time 129 | }; 130 | let now = chrono::Utc::now().timestamp() as u64; 131 | let delay = end_ts.saturating_sub(now); 132 | sleep_until(Instant::now() + Duration::from_secs(delay)).await; 133 | let e_data = { 134 | let mut e = election.lock().unwrap(); 135 | e.status = Status::Finished; 136 | log::info!("Election {} -> Finished", e.id); 137 | e.clone() 138 | }; 139 | // Publish the event with the new status 140 | if let Err(err) = publish_election_event(&client, &keys, &e_data).await { 141 | log::error!( 142 | "Error publishing election with status {:?}: {}", 143 | e_data.status, 144 | err 145 | ); 146 | } 147 | }); 148 | } 149 | let e_data = { 150 | let e = election.lock().unwrap(); 151 | e.clone() 152 | }; 153 | if let Err(err) = publish_election_event(&client, &keys, &e_data).await { 154 | log::error!( 155 | "Error publishing election with status {:?}: {}", 156 | e_data.status, 157 | err 158 | ); 159 | } 160 | 161 | // --- Register voters --- 162 | let voters: Vec = serde_json::from_str(&fs::read_to_string("voters_pubkeys.json")?)?; 163 | { 164 | let mut e = election.lock().unwrap(); 165 | for v in &voters { 166 | e.register_voter(&v.pubkey); 167 | println!("👤 Registered {}", v.name); 168 | } 169 | } 170 | let subscription = Filter::new() 171 | .pubkey(keys.public_key()) 172 | .kind(Kind::GiftWrap) 173 | .limit(0); 174 | // Client subscription 175 | client.subscribe(subscription, None).await?; 176 | // Set up channel for real-time order updates 177 | let (tx, mut rx) = mpsc::channel(100); 178 | { 179 | let client = client.clone(); 180 | let election = Arc::clone(&election); 181 | let keys = keys.clone(); 182 | let tx = tx.clone(); 183 | // Spawn a task to handle Nostr events 184 | tokio::spawn(async move { 185 | let mut notifications = client.notifications(); 186 | while let Ok(notification) = notifications.recv().await { 187 | if let RelayPoolNotification::Event { event, .. } = notification { 188 | // Validate event signature 189 | if event.verify().is_err() { 190 | log::warn!("Event failed signature verification – ignored"); 191 | continue; 192 | }; 193 | let event = match nip59::extract_rumor(&keys, &event).await { 194 | Ok(u) => u, 195 | Err(_) => { 196 | println!("Error unwrapping gift"); 197 | continue; 198 | } 199 | }; 200 | let voter = event.sender; 201 | let message = match Message::from_json(&event.rumor.content) { 202 | Ok(m) => m, 203 | Err(e) => { 204 | log::warn!("Error parsing message: {}", e); 205 | continue; 206 | } 207 | }; 208 | // Check if the message is a token request 209 | match message.kind { 210 | 1 => { 211 | log::info!("Token request received: {:#?}", message); 212 | let blinded_bytes = 213 | match general_purpose::STANDARD.decode(message.payload) { 214 | Ok(bytes) => bytes, 215 | Err(e) => { 216 | log::warn!("Error decoding content: {}", e); 217 | continue; 218 | } 219 | }; 220 | let blinded_h_n = BlindedMessage::from(blinded_bytes); 221 | let req = BlindTokenRequest { 222 | voter_pk: voter.to_string(), 223 | blinded_h_n, 224 | }; 225 | // Issue token 226 | let blind_sig = 227 | match election.lock().unwrap().issue_token(req, sk.clone()) { 228 | Ok(token) => token, 229 | Err(e) => { 230 | log::warn!("Error issuing token: {}", e); 231 | continue; 232 | } 233 | }; 234 | // Encode token to Base64 235 | let blind_sig_b64 = general_purpose::STANDARD.encode(blind_sig); 236 | let response = 237 | Message::new(message.id.clone(), 1, blind_sig_b64.clone()); 238 | // Creates a "rumor" with the hash of the nonce. 239 | let rumor: UnsignedEvent = EventBuilder::text_note(response.as_json()) 240 | .build(keys.public_key()); 241 | 242 | // Wraps the rumor in a Gift Wrap. 243 | let gift_wrap = 244 | match EventBuilder::gift_wrap(&keys, &voter, rumor, None).await { 245 | Ok(ev) => ev, 246 | Err(e) => { 247 | log::warn!("Unable to build GiftWrap for {}: {}", voter, e); 248 | continue; 249 | } 250 | }; 251 | 252 | match client.send_event(&gift_wrap).await { 253 | Ok(_) => log::info!("Blind signature sent to: {}", voter), 254 | Err(e) => log::error!("Failed to send blind signature: {}", e), 255 | } 256 | } 257 | 2 => { 258 | // Split the incoming vote message into parts. 259 | let parts: Vec<&str> = message.payload.split(':').collect(); 260 | if parts.len() != 4 { 261 | log::warn!("Invalid vote format: {}", message.payload); 262 | continue; 263 | } 264 | 265 | // Decode h_n from Base64 266 | let h_n_bytes = match general_purpose::STANDARD.decode(parts[0]) { 267 | Ok(bytes) => bytes, 268 | Err(e) => { 269 | log::warn!("Failed to decode h_n: {}", e); 270 | continue; 271 | } 272 | }; 273 | let h_n = BigUint::from_bytes_be(&h_n_bytes); 274 | 275 | // Decode token from Base64 276 | let token_bytes = match general_purpose::STANDARD.decode(parts[1]) { 277 | Ok(b) => b, 278 | Err(_) => continue, 279 | }; 280 | let token: RSASignature = RSASignature::from(token_bytes.clone()); 281 | 282 | // Decode MessageRandomizer from Base64 283 | let r_bytes = match general_purpose::STANDARD.decode(parts[2]) { 284 | Ok(b) => b, 285 | Err(_) => continue, 286 | }; 287 | let rand_arr: [u8; 32] = match <[u8; 32]>::try_from(&r_bytes[..]) { 288 | Ok(arr) => arr, 289 | Err(_) => { 290 | log::warn!("Invalid randomizer length"); 291 | continue; 292 | } 293 | }; 294 | let msg_rand = MessageRandomizer::from(rand_arr); 295 | 296 | // Parse vote as an integer 297 | let vote = match parts[3].parse::() { 298 | Ok(v) => v, 299 | Err(e) => { 300 | log::warn!("Failed to parse vote: {}", e); 301 | continue; 302 | } 303 | }; 304 | let options = Options::default(); 305 | // Verify the signature on the raw h_n_bytes 306 | if token 307 | .verify(&pk, Some(msg_rand), &h_n_bytes, &options) 308 | .is_err() 309 | { 310 | log::warn!("Invalid token signature"); 311 | continue; 312 | } 313 | 314 | if let Err(e) = election.lock().unwrap().receive_vote(h_n, vote) { 315 | log::warn!("Error receiving vote: {}", e); 316 | continue; 317 | } 318 | // Tally the votes 319 | let tally = election.lock().unwrap().tally(); 320 | let mut results = String::new(); 321 | let mut json_results: Vec<(u8, u32)> = Vec::new(); 322 | for (cand, count) in &tally { 323 | results.push_str(&format!("{}: {} vote(s)\n", cand.name, count)); 324 | json_results.push((cand.id, *count)); 325 | } 326 | let json_string = match serde_json::to_string(&json_results) { 327 | Ok(json) => json, 328 | Err(err) => { 329 | log::error!( 330 | "Failed to serialize election results to JSON: {}", 331 | err 332 | ); 333 | continue; 334 | } 335 | }; 336 | 337 | let (election_id, expire_ts) = { 338 | let e = election.lock().unwrap(); 339 | ( 340 | e.id.clone(), 341 | chrono::Utc::now() 342 | .checked_add_signed(chrono::Duration::days(5)) 343 | .unwrap() 344 | .timestamp() as u64, 345 | ) 346 | }; 347 | let future_ts = Timestamp::from(expire_ts); 348 | println!("🗳️ Election's result: \n\n{}", results); 349 | // We publish the results in a custom event with kind 35_001 350 | match EventBuilder::new(Kind::Custom(35_001), json_string) 351 | .tag(Tag::identifier(election_id.clone())) 352 | .tag(Tag::expiration(future_ts)) 353 | .sign(&keys) 354 | .await 355 | { 356 | Ok(event) => { 357 | // Publish the event to the relay 358 | match client.send_event(&event).await { 359 | Ok(_) => { 360 | log::info!("Election results published successfully") 361 | } 362 | Err(e) => log::error!("Failed to publish results: {}", e), 363 | } 364 | } 365 | Err(e) => log::error!("Failed to sign results event: {}", e), 366 | }; 367 | } 368 | _ => { 369 | log::warn!("Unknown message kind: {}", message.kind); 370 | continue; 371 | } 372 | } 373 | let _ = tx.send(event).await; 374 | } 375 | } 376 | }); 377 | } 378 | loop { 379 | // Check for new orders without blocking 380 | while let Ok(_event) = rx.try_recv() { 381 | // log::info!("New event rx: {:#?}", event); 382 | } 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /ec/src/types.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The candidates are represented by numbers 4 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] 5 | pub struct Candidate { 6 | pub id: u8, 7 | pub name: &'static str, 8 | } 9 | 10 | impl Candidate { 11 | pub fn new(id: u8, name: &'static str) -> Self { 12 | Self { id, name } 13 | } 14 | } 15 | 16 | #[derive(Debug, Serialize, Deserialize)] 17 | pub struct Voter { 18 | pub name: String, 19 | pub pubkey: String, 20 | } 21 | 22 | #[derive(Debug, serde::Serialize, serde::Deserialize)] 23 | pub struct Message { 24 | pub id: String, 25 | /// 1: Token request, 2: Vote 26 | pub kind: u8, 27 | pub payload: String, 28 | } 29 | 30 | impl Message { 31 | pub fn new(id: String, kind: u8, payload: String) -> Self { 32 | Self { id, kind, payload } 33 | } 34 | 35 | pub fn from_json(json: &str) -> Result { 36 | serde_json::from_str(json) 37 | } 38 | 39 | pub fn as_json(&self) -> String { 40 | serde_json::to_string(self).unwrap() 41 | } 42 | } 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | use super::*; 47 | 48 | #[test] 49 | fn test_candidate_new_and_eq() { 50 | let a = Candidate::new(5, "X"); 51 | let b = Candidate { id: 5, name: "X" }; 52 | assert_eq!(a, b); 53 | } 54 | 55 | #[test] 56 | fn test_message_json_roundtrip() { 57 | let msg = Message::new("abc".into(), 2, "payload".into()); 58 | let json = msg.as_json(); 59 | let parsed = Message::from_json(&json).expect("Should parse correctly"); 60 | assert_eq!(parsed.id, "abc"); 61 | assert_eq!(parsed.kind, 2); 62 | assert_eq!(parsed.payload, "payload"); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ec/src/util.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use blind_rsa_signatures::{PublicKey as RSAPublicKey, SecretKey as RSASecretKey}; 3 | use chrono::Local; 4 | use fern::Dispatch; 5 | use std::fs; 6 | use std::path::Path; 7 | 8 | /// Loads RSA keys from two PEM files and converts them 9 | /// to the `blind-rsa-signatures` types. 10 | pub fn load_keys>( 11 | priv_path: P, 12 | pub_path: P, 13 | ) -> Result<(RSAPublicKey, RSASecretKey)> { 14 | let priv_pem = fs::read_to_string(priv_path)?; 15 | let pub_pem = fs::read_to_string(pub_path)?; 16 | 17 | // Parse the PEM to RSA objects 18 | let sk = RSASecretKey::from_pem(&priv_pem)?; 19 | let pk = RSAPublicKey::from_pem(&pub_pem)?; 20 | 21 | Ok((pk, sk)) 22 | } 23 | 24 | /// Initialize logger function 25 | pub fn setup_logger(level: log::LevelFilter) -> Result<(), fern::InitError> { 26 | Dispatch::new() 27 | .format(|out, message, record| { 28 | out.finish(format_args!( 29 | "[{}] [{}] - {}", 30 | Local::now().format("%Y-%m-%d %H:%M:%S"), 31 | record.level(), 32 | message 33 | )) 34 | }) 35 | .level(level) 36 | .chain(fern::log_file("app.log")?) 37 | .apply()?; 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /ec/voters_pubkeys.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Alice", 4 | "pubkey": "00001001063e6bf1b28f6514ac651afef7f51b2a792f0416a5e8273daa9eea6e" 5 | }, 6 | { 7 | "name": "Bob", 8 | "pubkey": "00000eab0ee981184e7e5dd144360885087f6a5889b46abfe5c402bf490fcd24" 9 | }, 10 | { 11 | "name": "Charlie", 12 | "pubkey": "00000699921ac7021b7da121da5bd762d90be830c3964ba12e82b590445797a6" 13 | }, 14 | { 15 | "name": "Daniel", 16 | "pubkey": "00000217c8b36c2098604aa15a2317739de96b62c3a49cb648da7b35ddc3c683" 17 | } 18 | ] -------------------------------------------------------------------------------- /keys/negrunch.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQINBGOKeIwBEAC6vt3dVYg73Cs3OTqDp/UFQIdpax9wXNghiuZ2KHTulr+TYPnf 4 | gNVmAnJp6C8Td2UMqKYKqEwWVYuDAGQ3k15vDY/MmozZGmA+BtRV+aAaeC4Iw+ka 5 | mPNtldBdGbiG5JJud5KPLMKweoxWsqbqUzPYZhIu8OEX+SML9vKlwn0T8Mrs7B8T 6 | GKtug14rdA1FPh4vOzWj2eHmiuKatv55WM+T4Yh9gH+x9pb/btkR+ZfXYMCRAIfF 7 | aMmtspIJDVWLCS93X4pcdNFjfD+sbjJ5khU4/o94DftoBp//4t03ccxJ0Q8rBHaX 8 | yyimTu9DAeyTQSMsNkQiIv3bPTVDh3l5YSa25kLA14f+A50D23th52hdT51XkRol 9 | UlDcV9UUBkLuOVSk8kVw+X+j+goDz1csTMR3RpOEwPiN4nnXQLZrCw+HXhxWbC2r 10 | p5zNBmrdbU8t1y2l/RUyC8Qbj2+AqErI1vjOjFdTb4S3h+Oy7Ljm/tIH/ajEr/JV 11 | 2xVnXDsk3MsIHK//SF+OOZsE+dgJdHpdECH3aLx7WhHNih8WuD+Ht974/EyYrANw 12 | Eh5SnePKaV9605op1z9PDA1Xq+Q4RkuBAwdIQT2DEy6IsCboJ+feU8Dw3uIahy2h 13 | V4e45G4ZfLmyl/D/ToZWniWGzttsCwIG+LcgflM81yzI2QqbVa3ZweQNyQARAQAB 14 | tCpGcmFuY2lzY28gQ2FsZGVyw7NuIDxmamNhbGRlcm9uQGdtYWlsLmNvbT6JAk4E 15 | EwEKADgWIQQeQWMdE3uireVTRPc4UrhDZ5rW8AUCY4p4jAIbAwULCQgHAgYVCgkI 16 | CwIEFgIDAQIeAQIXgAAKCRA4UrhDZ5rW8FmYD/0ePIso547vRVmQJmBUNq1m5ipM 17 | QV7vt142QMpaa6HL37kjMibcxk/jifi2kCjQw2yK85qmjhidakqaMUFDehlFM2DN 18 | bFG8809TaTwaJY7EyCgFTA8R3xokziMBadlF+ulLFe+4GdouQm4alO3b3F2RVCIW 19 | Df03QnrtqwMjnAGHHJ27tFaI2BhgMmQJD2LzFPm8tM/fiNVXZxbn4tk6DuNeY2hB 20 | PF9C/r4/KkYQuXaxIubiFinFYky9rCNJF9uk5mY5n4MWZatV8hUX59HXC+Ra6MmY 21 | Ib4eVXe+98B8QcJ4W4eH18F2fAUeTbpFBeqYaIlwmpMGjtHi5Uz3DX4bQM13cTS6 22 | WzcNx+Z0YGPiuMT5+P1kaSZp9WXHJxKFuBpgpX28lXO8Qmq5GSjf79VPL6ojTo7Y 23 | w1blKEslCEd6qmh7aSAuBlZOYLQ4OwiaSUzaXbmHnpfEsoRB5X7BYLgb+27RrbJl 24 | aXHfJXGB0NDUDJ7daDMG/fgOy6JidTz8cCoX/eZFu0VhuIwfxVieJmo3kRhh14MY 25 | MJotp8qfrbUCl1ZB9HZpsuNBuG41Zvp/sdSnhJzOlFCPUNiZbnL64Ze+601r2ttg 26 | xuTENM0GCUf1QgR7PGZIZrf3pKWYu4Ug5HriXGxOIOrbEgAX4AV1cmNRfp5VlWMf 27 | pUSERBaxbsLcPegzTbkCDQRjiniMARAA3MjRt4MBi+xDEmOvzP26i0TH0ZSV9oOy 28 | 7QSwIVU79lzuI0pOwZRaqCgOfwLkj5GjcukowCUEhctlEjqksPWTgIb/krp9xIt8 29 | dN+p8BHAdLp7hcwL+FzPy2LXP0fykeblEfXY6C+HEm+m7rbrg/JcG9Jlplhbk6+O 30 | QjJC7Kmx50I6eH5a2yqlp0JH36Sgm9mCIV9qym2taYzcmL1lF5SL68saAITFK2XA 31 | ZU27ZUHMq6GSoZEeLKfQrTY2Z1gR9yTgVC5yYRNHeCZcSZy0IeoKy/rRSrNVBukj 32 | XXRPteYd32unR9X5ZxBE8/PXSfUn6RvvQbnlGOdargMfS8e+yHQd7Ly5Sst9CiED 33 | 3DuRTtdYo5ImxOK7gisN/qL3VGQQcgaIBOnOfHtELXJKQY2HxypG6qCO/UEljF00 34 | 941SQmpDAwuRZ76mxPkU4mF+USHt+6r4KBK6w7EnSl3D/lL1DDHkm/ePt/CN2nvC 35 | C7AhGxmtyKsLbG/ZReD74wJLszCD06A2xrNlPAchRPCnC29EOMvcqvowLJ3yPNZv 36 | R3G0ZPp3jsC9CRPBpvJvxIv/euJXpnQ/9GN3VJo1pQCBSyV4YpBApYKd6ukZQXKg 37 | sFD3gXfSNePeG5kUbexOr+By0d0IffuB6+PTEhNtc3ZBrc5cpYGNrsq2DdblF2o+ 38 | ounVaDVByzsAEQEAAYkCNgQYAQoAIBYhBB5BYx0Te6Kt5VNE9zhSuENnmtbwBQJj 39 | iniMAhsMAAoJEDhSuENnmtbwZioP/jodCUmT1Ms5acetZRKHb5uRQ1ZQynxVHYHW 40 | /LkxWFNha+T2UjIE9OmAtEuNbZkRjQ9mjFjoWjkA7RS4ODH15V5aNXc+4uswVBva 41 | 31zAJw8Dq99WGy7jau3fFb32Z4TyPpoPDFgkcanebOlVrKwe8Z92nUtwfzDyzxin 42 | 6/gw1aGoeOf1lpDU7v3Wwege9GN8yVIVxWVBolwE2JKCUecYaBiW1Y8byEcrmPu0 43 | +A9NIccHqQDcWPqS35mPMtqoWLEViMkqWFlrIfbO0hpdDyxWWl2qRiBwdYcxgeVd 44 | p/FuRxw8j/zFI5eTG6/We5HP2+kKhyzzY899u0vsn0DTywAe0yPHCqMutyjnyOva 45 | Kr7UDZzJ6mdl05wiFuIhCXc7BmncEIKtL1OPUmCMtiQ5+FhDsB0o7j0kqnRshxJ9 46 | siUHVZt8O0rV+Dua6Fg5G8s9hNrOhZSW583cPO6mOmcKurF8qeyCmeSMVGmhMWwl 47 | GLM26LwV/NN4ePg9ZW2OWjTutA3U0Pdq+ZTrZAiNK0wO74RAKOTp9jalodalhgBV 48 | U9gexmtqJJC6EofEqdtRDB1iL2OmJgd/w89dC8UoPf9pp66wUsGmUGOlGEeQ1Nd/ 49 | iPhUN//4eHygMQvNJbEib0v5LhErLbN32bxvx5R/soKiK1gFaRLzNp5m1eIPo7wM 50 | wvzJi0bq 51 | =rxmd 52 | -----END PGP PUBLIC KEY BLOCK----- 53 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grunch/criptocracia/e4fe5629237156317d99cbd5bd7e4e0a92940a6e/logo.png -------------------------------------------------------------------------------- /manifest.txt: -------------------------------------------------------------------------------- 1 | cfb0b5bf4c15c827c0f53028817bf82031c5766dd4a65c072d64dc954b921dc4 voter.exe 2 | 0106ec5909823ed691ea9461b581a2ca36c3d1a308b78e7d53a802b55cca1ee9 ec.exe 3 | -------------------------------------------------------------------------------- /manifest.txt.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grunch/criptocracia/e4fe5629237156317d99cbd5bd7e4e0a92940a6e/manifest.txt.sig -------------------------------------------------------------------------------- /voter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "voter" 3 | version = "0.1.1" 4 | edition = "2024" 5 | authors.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | rust-version.workspace = true 10 | 11 | [dependencies] 12 | nostr-sdk = { workspace = true, features = ["nip59"] } 13 | anyhow = { workspace = true } 14 | tokio = { workspace = true } 15 | base64 = { workspace = true } 16 | num-bigint-dig = { workspace = true } 17 | nanoid = { workspace = true } 18 | serde_json = { workspace = true } 19 | blind-rsa-signatures = { workspace = true } 20 | 21 | rand = "0.8" 22 | sha2 = "0.10" 23 | ratatui = "0.29.0" 24 | crossterm = { version = "0.29.0", features = ["event-stream"] } 25 | sqlx = { version = "0.8.5", features = ["sqlite", "runtime-tokio-native-tls"] } 26 | futures = "0.3" 27 | dirs = "6.0.0" 28 | fern = "0.7.1" 29 | log = "0.4.27" 30 | config = { version = "0.15.11", features = ["toml"]} 31 | serde = { version = "1.0.219", features = ["derive"] } 32 | chrono = "0.4.40" 33 | -------------------------------------------------------------------------------- /voter/README.md: -------------------------------------------------------------------------------- 1 | # Criptocracia - Voter 2 | 3 | ![logo](../logo.png) 4 | 5 | *Disclaimer: The author is NOT a cryptographer and this work has not been reviewed. This means that there is very likely a fatal flaw somewhere. Criptocracia is still experimental and not production-ready.* 6 | 7 | ## Configuration 8 | 9 | Voter use a TOML settings file (auto-initialized on first run) stored in `~/.voter/settings.toml`. Edit it to specify: 10 | 11 | ```toml 12 | # ~/.voter/settings.toml 13 | secret_key = "" 14 | ec_public_key = "" 15 | log_level = "info" 16 | relays = ["wss://relay.mostro.network"] 17 | ``` 18 | 19 | * `secret_key`: Nostr private key for signing Gift Wrap messages. 20 | * `ec_public_key`: EC’s Nostr public key (used by `voter` to encrypt requests). 21 | * `relays`: List of Nostr relays 22 | 23 | Import the RSA public key from your EC. 24 | 25 | To simplify the testing of this project we have already created a couple of keys and included them in this repository. 26 | 27 | --- 28 | 29 | ## Prerequisites 30 | 31 | * Rust toolchain (>= 1.86.0) 32 | * Nostr relay endpoint (e.g., `wss://relay.mostro.network`) 33 | 34 | Ensure you have Git and Cargo installed. Clone the repository: 35 | 36 | ```sh 37 | git clone https://github.com/grunch/criptocracia.git 38 | cd criptocracia/voter 39 | ``` 40 | 41 | --- 42 | 43 | ## Building the Project 44 | 45 | From the workspace root: 46 | 47 | ```sh 48 | # Build both binaries in release mode 49 | cargo build --release 50 | ``` 51 | 52 | The binary will be in `target/release/voter`. 53 | 54 | --- 55 | 56 | ## Usage 57 | 58 | 1. List available elections: 59 | 60 | ```sh 61 | target/release/voter 62 | ``` 63 | 2. Select an election and request a token (navigate UI with arrow keys and press Enter). 64 | 3. After receiving the blinded signature, choose your candidate and press Enter to cast your vote. 65 | 4. Vote confirmation appears in the UI, and the EC processes it asynchronously. 66 | 67 | --- 68 | 69 | ## Logging and Debugging 70 | 71 | Logs are written to `app.log` in the current working directory. Set `log_level` in settings to `debug` for verbose output. -------------------------------------------------------------------------------- /voter/ec_public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyzrjKKlz8JpyKrqnCNr2 3 | n/iXwSgHAnrNyZwOJ6UW4actxDnI3dyweOqXtGZyIg4+PeEmDrTY5sP6pN2p5qVM 4 | 6XGmt7DCfStJgaCpB0D/BZd/ar/sh9aj9ATLQe24/UDXweGTgzWVsky8uCRODcza 5 | xhDPXvwRAQICuZNO3OxQ5ss7uc1ZfSDS++857q8k6KHdbnWkAy3+NoGslZWqIQH/ 6 | h9tDl8zfKH5AP5MZibdna+/P2wbz86/8uq+hBupxwympiQXxLB7rfjfOkLX22Wgu 7 | seovpbA/7If3LNned5UuxX1IxuFzBtw7W1RAy8B1MqlAobf5K+e4XzAzl49AqQn6 8 | swIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /voter/settings.toml: -------------------------------------------------------------------------------- 1 | # Voter private key 2 | secret_key = "30df83c45dee3b379c91be29cbbf6ecdbcfd8e9d96979e98b9e8162505d2047a" 3 | # Electoral Commission Nostr public key 4 | ec_public_key = "0000001ace57d0da17fc18562f4658ac6d093b2cc8bb7bd44853d0c196e24a9c" 5 | # Relays to connect to 6 | relays = ["wss://relay.mostro.network"] 7 | log_level = "info" -------------------------------------------------------------------------------- /voter/src/election.rs: -------------------------------------------------------------------------------- 1 | use nostr_sdk::event::Event; 2 | 3 | #[derive(Debug, serde::Deserialize)] 4 | #[serde(rename_all = "kebab-case")] 5 | pub enum Status { 6 | Open, 7 | InProgress, 8 | Finished, 9 | Canceled, 10 | } 11 | 12 | #[derive(Debug, serde::Deserialize, Clone)] 13 | pub struct Candidate { 14 | pub id: u8, 15 | pub name: String, 16 | } 17 | 18 | impl Candidate { 19 | pub fn new(id: u8, name: String) -> Self { 20 | Self { id, name } 21 | } 22 | } 23 | 24 | #[derive(Debug, serde::Deserialize)] 25 | pub struct Election { 26 | pub id: String, 27 | pub name: String, 28 | pub candidates: Vec, 29 | pub start_time: u64, 30 | pub end_time: u64, 31 | pub status: Status, 32 | } 33 | 34 | impl Election { 35 | pub fn new( 36 | id: String, 37 | name: String, 38 | candidates: Vec, 39 | start_time: u64, 40 | duration: u64, 41 | ) -> Self { 42 | let end_time = start_time + duration; 43 | // Validate that ID follows expected format (4-character hex string) 44 | debug_assert!( 45 | id.len() == 4 && id.chars().all(|c| c.is_ascii_hexdigit()), 46 | "Election ID should be a 4-character hex string" 47 | ); 48 | Self { 49 | id, 50 | name, 51 | candidates, 52 | start_time, 53 | end_time, 54 | status: Status::Open, 55 | } 56 | } 57 | 58 | pub fn parse_event(event: &Event) -> Result { 59 | let data = event.content.clone(); 60 | let election = serde_json::from_str(&data); 61 | 62 | let election = match election { 63 | Ok(e) => e, 64 | Err(e) => { 65 | return Err(anyhow::anyhow!("Failed to parse election event: {}", e)); 66 | } 67 | }; 68 | 69 | Ok(election) 70 | } 71 | 72 | pub fn parse_result_event(event: &Event) -> Result, anyhow::Error> { 73 | let data = event.content.clone(); 74 | let results: Result, serde_json::Error> = serde_json::from_str(&data); 75 | 76 | let results = match results { 77 | Ok(r) => r, 78 | Err(e) => { 79 | return Err(anyhow::anyhow!("Failed to parse results event: {}", e)); 80 | } 81 | }; 82 | 83 | Ok(results) 84 | } 85 | } 86 | 87 | #[derive(Debug, serde::Serialize, serde::Deserialize)] 88 | pub struct Message { 89 | pub id: String, 90 | /// 1: Token request, 2: Vote 91 | pub kind: u8, 92 | pub payload: String, 93 | } 94 | 95 | impl Message { 96 | pub fn new(id: String, kind: u8, payload: String) -> Self { 97 | Self { id, kind, payload } 98 | } 99 | 100 | pub fn from_json(json: &str) -> Result { 101 | serde_json::from_str(json) 102 | } 103 | 104 | pub fn as_json(&self) -> String { 105 | serde_json::to_string(self).unwrap() 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /voter/src/main.rs: -------------------------------------------------------------------------------- 1 | pub mod election; 2 | pub mod settings; 3 | pub mod util; 4 | 5 | use crate::election::{Election, Message, Status}; 6 | use crate::settings::{Settings, init_settings}; 7 | use crate::util::{load_ec_pubkey, setup_logger}; 8 | 9 | use base64::engine::{Engine, general_purpose}; 10 | use blind_rsa_signatures::{BlindSignature, MessageRandomizer, Options, Secret, Signature}; 11 | use chrono::{Duration as ChronoDuration, Utc}; 12 | use crossterm::event::{Event as CEvent, EventStream, KeyCode, KeyEvent}; 13 | use crossterm::execute; 14 | use crossterm::terminal::{ 15 | EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode, 16 | }; 17 | use futures::StreamExt; 18 | use nostr_sdk::prelude::RelayPoolNotification; 19 | use nostr_sdk::prelude::*; 20 | use num_bigint_dig::{BigUint, RandBigInt}; 21 | use rand::rngs::OsRng; 22 | use ratatui::Terminal; 23 | use ratatui::backend::CrosstermBackend; 24 | use ratatui::layout::{Constraint, Direction, Layout}; 25 | use ratatui::style::{Color, Modifier, Style}; 26 | use ratatui::widgets::{Block, Borders, Cell, Paragraph, Row, Table}; 27 | use sha2::{Digest, Sha256}; 28 | use std::cmp::Reverse; 29 | use std::io::stdout; 30 | use std::str::FromStr; 31 | use std::sync::OnceLock; 32 | use std::sync::{Arc, Mutex}; 33 | use tokio::time::{Duration, interval}; 34 | 35 | /// Constructs (or copies) the configuration file and loads it. 36 | static SETTINGS: OnceLock = OnceLock::new(); 37 | 38 | // Official Mostro colors. 39 | const PRIMARY_COLOR: Color = Color::Rgb(3, 255, 254); // #03fffe 40 | const BACKGROUND_COLOR: Color = Color::Rgb(5, 35, 39); // #052327 41 | 42 | #[derive(Default)] 43 | struct App { 44 | nonce: Option, // Nonce generated by the voter 45 | h_n_bytes: Option>, // Hash of the nonce 46 | r: Option, // Randomizer used to blind the nonce 47 | token: Option, // Blind signature received from the EC 48 | secret: Option, // Secret used to blind the nonce 49 | election_id: Option, 50 | candidate_id: Option, 51 | results: Option>, // Results of the election 52 | } 53 | 54 | /// Draws the TUI interface with tabs and active content. 55 | /// The "Elections" tab shows a table of active elections and highlights the selected row. 56 | fn ui_draw( 57 | f: &mut ratatui::Frame, 58 | active_area: usize, 59 | elections: &Arc>>, 60 | app: &Arc>, 61 | selected_election_idx: usize, 62 | selected_candidate_idx: usize, 63 | ) { 64 | let app = app.lock().unwrap(); 65 | let ballot_text = if let (Some(eid), Some(cid)) = (&app.election_id, app.candidate_id) { 66 | format!("Election: {}, Candidate voted: {}", eid, cid) 67 | } else { 68 | "No vote yet".into() 69 | }; 70 | let results_text = if let Some(results) = &app.results { 71 | results 72 | .iter() 73 | .map(|(id, votes)| format!("Candidate {}: {} votes", id, votes)) 74 | .collect::>() 75 | .join("\n") 76 | } else { 77 | "No results yet".into() 78 | }; 79 | let chunks = Layout::new( 80 | Direction::Vertical, 81 | [30, 40, 30].map(Constraint::Percentage), 82 | ) 83 | .split(f.area()); 84 | 85 | // === AREA 0: Elections === 86 | let header = Row::new( 87 | ["Id", "Name", "Status", "Starts"] 88 | .iter() 89 | .map(|h| Cell::from(*h)) 90 | .collect::>(), 91 | ) 92 | .style(Style::default().add_modifier(Modifier::BOLD)); 93 | 94 | let elections_lock = elections.lock().unwrap(); 95 | let mut rows = Vec::with_capacity(elections_lock.len()); 96 | for (i, e) in elections_lock.iter().enumerate() { 97 | let mut row = Row::new(vec![ 98 | Cell::from(e.id.to_string()), 99 | Cell::from(e.name.clone()), 100 | Cell::from(match e.status { 101 | Status::Open => "Open", 102 | Status::InProgress => "In Progress", 103 | Status::Finished => "Finished", 104 | Status::Canceled => "Canceled", 105 | }), 106 | Cell::from( 107 | chrono::DateTime::from_timestamp(e.start_time as i64, 0) 108 | .map(|dt| dt.format("%Y-%m-%d %H:%M").to_string()) 109 | .unwrap_or_else(|| "Invalid".into()), 110 | ), 111 | ]); 112 | if active_area == 0 && i == selected_election_idx { 113 | row = row.style(Style::default().bg(PRIMARY_COLOR).fg(Color::Black)); 114 | } 115 | rows.push(row); 116 | } 117 | 118 | let mut block_e = Block::default() 119 | .title("Elections") 120 | .borders(Borders::ALL) 121 | .border_type(ratatui::widgets::BorderType::Rounded) 122 | .style(Style::default().bg(BACKGROUND_COLOR)); 123 | if active_area == 0 { 124 | block_e = block_e 125 | .title_style(Style::default().bg(PRIMARY_COLOR).fg(Color::Black)) 126 | .border_style(Style::default().bg(PRIMARY_COLOR).fg(Color::Black)); 127 | } 128 | 129 | let table_e = Table::new( 130 | rows, 131 | &[ 132 | Constraint::Length(5), 133 | Constraint::Min(10), 134 | Constraint::Length(12), 135 | Constraint::Length(18), 136 | ], 137 | ) 138 | .header(header) 139 | .block(block_e); 140 | f.render_widget(table_e, chunks[0]); 141 | 142 | // === AREA 1: Candidates === 143 | // If a valid election is selected, display its candidates: 144 | let mut cand_rows = Vec::new(); 145 | if let Some(e) = elections_lock.get(selected_election_idx) { 146 | for (i, c) in e.candidates.iter().enumerate() { 147 | let mut row = Row::new(vec![ 148 | Cell::from(c.id.to_string()), 149 | Cell::from(c.name.clone()), 150 | ]); 151 | if active_area == 1 && i == selected_candidate_idx { 152 | row = row.style(Style::default().bg(PRIMARY_COLOR).fg(Color::Black)); 153 | } 154 | cand_rows.push(row); 155 | } 156 | } 157 | 158 | let mut block_c = Block::default() 159 | .title("Candidates") 160 | .borders(Borders::ALL) 161 | .border_type(ratatui::widgets::BorderType::Rounded) 162 | .style(Style::default().bg(BACKGROUND_COLOR)); 163 | if active_area == 1 { 164 | block_c = block_c 165 | .title_style(Style::default().bg(PRIMARY_COLOR).fg(Color::Black)) 166 | .border_style(Style::default().bg(PRIMARY_COLOR).fg(Color::Black)); 167 | } 168 | 169 | let table_c = Table::new(cand_rows, &[Constraint::Length(5), Constraint::Min(10)]) 170 | .header( 171 | Row::new( 172 | ["Id", "Name"] 173 | .iter() 174 | .map(|h| Cell::from(*h)) 175 | .collect::>(), 176 | ) 177 | .style(Style::default().add_modifier(Modifier::BOLD)), 178 | ) 179 | .block(block_c); 180 | f.render_widget(table_c, chunks[1]); 181 | 182 | let bottom_layout = Layout::default() 183 | .direction(Direction::Horizontal) 184 | .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) 185 | .split(chunks[2]); 186 | 187 | // === AREA 2: Ballot === 188 | let block_b = Block::default() 189 | .title("Ballot") 190 | .borders(Borders::ALL) 191 | .border_type(ratatui::widgets::BorderType::Rounded) 192 | .style(Style::default().bg(BACKGROUND_COLOR)); 193 | 194 | let paragraph = Paragraph::new(ballot_text).block(block_b); 195 | f.render_widget(paragraph, bottom_layout[0]); 196 | 197 | let block_r = Block::default() 198 | .title("Results") 199 | .borders(Borders::ALL) 200 | .border_type(ratatui::widgets::BorderType::Rounded) 201 | .style(Style::default().bg(BACKGROUND_COLOR)); 202 | 203 | let paragraph = Paragraph::new(results_text).block(block_r); 204 | f.render_widget(paragraph, bottom_layout[1]); 205 | } 206 | 207 | #[tokio::main] 208 | async fn main() -> Result<(), anyhow::Error> { 209 | let settings = init_settings(); 210 | // Initialize logger 211 | setup_logger(&settings.log_level).expect("Can't initialize logger"); 212 | log::info!("Criptocracia started"); 213 | // Set the terminal in raw mode and switch to the alternate screen. 214 | enable_raw_mode()?; 215 | let mut stdout = stdout(); 216 | execute!(stdout, EnterAlternateScreen)?; 217 | let backend = CrosstermBackend::new(stdout); 218 | let mut terminal = Terminal::new(backend)?; 219 | 220 | let pk = load_ec_pubkey("ec_public.pem").expect("Failed to load EC public key"); 221 | 222 | // Shared state: elections are stored in memory. 223 | let elections: Arc>> = Arc::new(Mutex::new(Vec::new())); 224 | let app = Arc::new(Mutex::new(App::default())); 225 | let mut active_area = 0; // 0 = Elections, 1 = Candidates, 2 = Ballot 226 | let mut selected_election_idx: usize = 0; 227 | let mut selected_candidate_idx: usize = 0; 228 | 229 | // Configure Nostr client. 230 | let my_keys = Keys::parse(&settings.secret_key)?; 231 | let client = Client::new(my_keys.clone()); 232 | // Add the Mostro relay. 233 | client.add_relay("wss://relay.mostro.network").await?; 234 | client.connect().await; 235 | 236 | // EC Pubkey. 237 | let ec_pubkey = PublicKey::from_str(settings.ec_public_key.as_str()) 238 | .map_err(|e| anyhow::anyhow!("Invalid EC pubkey: {}", e))?; 239 | 240 | // Calculate timestamp for events in the last two day. 241 | let since_time = Utc::now() 242 | .checked_sub_signed(ChronoDuration::days(2)) 243 | .ok_or_else(|| anyhow::anyhow!("Failed to compute time"))? 244 | .timestamp() as u64; 245 | let timestamp = Timestamp::from(since_time); 246 | 247 | // Build the filter for NIP-69 (orders) events from Mostro. 248 | let filter = Filter::new() 249 | .kinds([Kind::Custom(35_000), Kind::Custom(35_001)]) 250 | .author(ec_pubkey) 251 | .limit(20) 252 | .since(timestamp); 253 | 254 | // Subscribe to the filter. 255 | client.subscribe(filter, None).await?; 256 | 257 | // Build the filter for NIP-59 events from the Electoral commission. 258 | let filter = Filter::new() 259 | .kind(Kind::GiftWrap) 260 | .pubkey(my_keys.public_key()) 261 | .limit(20) 262 | .since(timestamp); 263 | client.subscribe(filter, None).await?; 264 | 265 | let cloned_client = client.clone(); 266 | 267 | // Asynchronous task to handle incoming notifications. 268 | let elections_clone = Arc::clone(&elections); 269 | let app_clone = Arc::clone(&app); 270 | tokio::spawn(async move { 271 | let mut notifications = client.notifications(); 272 | while let Ok(n) = notifications.recv().await { 273 | if let RelayPoolNotification::Event { event, .. } = n { 274 | if let Kind::GiftWrap = event.kind { 275 | // Validate event signature 276 | if event.verify().is_err() { 277 | log::warn!("Invalid event signature: {}", event.id); 278 | continue; 279 | } 280 | let event = match nip59::extract_rumor(&my_keys, &event).await { 281 | Ok(u) => u, 282 | Err(_) => { 283 | log::warn!("Error unwrapping gift"); 284 | continue; 285 | } 286 | }; 287 | let message = match Message::from_json(&event.rumor.content) { 288 | Ok(m) => m, 289 | Err(e) => { 290 | log::warn!("Error reading message: {}", e); 291 | continue; 292 | } 293 | }; 294 | log::info!("Received message: {:#?}", message); 295 | match message.kind { 296 | 1 => { 297 | log::info!("Blind signature from EC received"); 298 | let mut app = app_clone.lock().unwrap(); 299 | let blind_sig_b64 = message.payload; 300 | // 1) Decode Base64 into raw bytes 301 | let blind_sig_bytes = general_purpose::STANDARD 302 | .decode(&blind_sig_b64) 303 | .expect("Invalid Base64 in blind signature"); 304 | 305 | // 2) Reconstruct the BlindSignature properly 306 | // (assumes impl From> for BlindSignature) 307 | let blind_sig = BlindSignature::from(blind_sig_bytes); 308 | 309 | // 3) Finalize to get a non-blind signature (token) 310 | let secret = match app.secret.as_ref() { 311 | Some(s) => s, 312 | None => { 313 | log::warn!("Missing secret for token finalization"); 314 | continue; 315 | } 316 | }; 317 | let msg_rand = match app.r.as_ref() { 318 | Some(r) => *r, 319 | None => { 320 | log::warn!("Missing message randomizer for token finalization"); 321 | continue; 322 | } 323 | }; 324 | // We must pass the original message bytes 325 | let h_n_bytes = app 326 | .h_n_bytes 327 | .as_ref() 328 | .expect("Missing h_n_bytes for token finalization"); 329 | 330 | let options = Options::default(); 331 | let token = match pk.finalize( 332 | &blind_sig, 333 | secret, 334 | Some(msg_rand), 335 | h_n_bytes, 336 | &options, 337 | ) { 338 | Ok(t) => t, 339 | Err(e) => { 340 | log::warn!("Error finalizing blind signature: {}", e); 341 | continue; 342 | } 343 | }; 344 | 345 | app.token = Some(token); 346 | log::info!("Token generated and stored"); 347 | } 348 | 2 => { 349 | log::info!("Voter response {}", message.payload); 350 | } 351 | _ => log::warn!("Unknown response {}", message.payload), 352 | } 353 | 354 | continue; 355 | } else if let (Kind::Custom(35_000), Ok(e)) = 356 | (event.kind, Election::parse_event(&event)) 357 | { 358 | let mut lock = elections_clone.lock().unwrap(); 359 | 360 | // If we already have the election, update it 361 | if let Some(existing) = lock.iter_mut().find(|x| x.id == e.id) { 362 | // Update the existing election 363 | *existing = e; 364 | } else { 365 | // If is a new election, add it to the list 366 | lock.push(e); 367 | } 368 | 369 | // re-order elections by start time 370 | lock.sort_by_key(|e| Reverse(e.start_time)); 371 | } else if Kind::Custom(35_001) == event.kind { 372 | // This is a result event 373 | let results = match Election::parse_result_event(&event) { 374 | Ok(r) => r, 375 | Err(e) => { 376 | log::warn!("Error parsing result event: {}", e); 377 | continue; 378 | } 379 | }; 380 | let mut app = app_clone.lock().unwrap(); 381 | if app.election_id.is_none() { 382 | continue; 383 | } 384 | app.results = Some(results); 385 | log::info!("Results received: {:?}", app.results); 386 | } else { 387 | continue; 388 | } 389 | } 390 | } 391 | }); 392 | 393 | // Event handling: keyboard input and periodic UI refresh. 394 | let mut events = EventStream::new(); 395 | let mut refresh_interval = interval(Duration::from_millis(200)); 396 | 397 | loop { 398 | tokio::select! { 399 | maybe_event = events.next() => { 400 | if let Some(Ok(CEvent::Key(KeyEvent { code, .. }))) = maybe_event { 401 | match code { 402 | KeyCode::Char('q') | KeyCode::Esc => break, 403 | KeyCode::Up => { 404 | if active_area == 0 { 405 | selected_election_idx = selected_election_idx.saturating_sub(1); 406 | } else if active_area == 1 && selected_candidate_idx > 0 { 407 | selected_candidate_idx = selected_candidate_idx.saturating_sub(1); 408 | } 409 | } 410 | KeyCode::Down => { 411 | if active_area == 0 { 412 | let len = elections.lock().unwrap().len(); 413 | if selected_election_idx + 1 < len { 414 | selected_election_idx += 1; 415 | } 416 | } else if active_area == 1 { 417 | if let Some(e) = elections.lock().unwrap().get(selected_election_idx) { 418 | if selected_candidate_idx + 1 < e.candidates.len() { 419 | selected_candidate_idx += 1; 420 | } 421 | } 422 | } 423 | } 424 | KeyCode::Enter => { 425 | let mut app = app.lock().unwrap(); 426 | if active_area == 0 { 427 | let pk = load_ec_pubkey("ec_public.pem").expect("Failed to load EC public key"); 428 | let options = Options::default(); 429 | let rng = &mut rand::thread_rng(); 430 | // 1) Generate nonce and its hash 431 | let nonce: BigUint = OsRng.gen_biguint(128); 432 | // 2) Hash the nonce 433 | let h_n_bytes = Sha256::digest(nonce.to_bytes_be()).to_vec(); 434 | 435 | // 3) Blind the hash with EC's RSA public key 436 | let blinding_result = 437 | pk.blind(rng, h_n_bytes.clone(), true, &options).expect("Blinding failed"); 438 | let blinded_h_n = blinding_result.blind_msg; 439 | let blinded_b64 = general_purpose::STANDARD.encode(blinded_h_n); 440 | app.nonce = Some(nonce); 441 | app.h_n_bytes = Some(h_n_bytes.clone()); 442 | app.secret = Some(blinding_result.secret); 443 | app.r = blinding_result.msg_randomizer; 444 | 445 | let election_id = { 446 | let elections_lock = elections.lock().unwrap(); 447 | elections_lock.get(selected_election_idx).map(|e| e.id.clone()) 448 | }; 449 | app.election_id = election_id.clone(); 450 | 451 | if let Some(election_id) = election_id { 452 | let message = Message::new( 453 | election_id, 454 | 1, 455 | blinded_b64, 456 | ); 457 | let message_json = serde_json::to_string(&message)?; 458 | log::info!("Token request content: {}", message_json); 459 | let my_keys = Keys::parse(&settings.secret_key)?; 460 | // Creates a "rumor" with the hash of the nonce. 461 | let rumor: UnsignedEvent = EventBuilder::text_note(message_json).build(my_keys.public_key()); 462 | 463 | // Wraps the rumor in a Gift Wrap. 464 | let gift_wrap: Event = EventBuilder::gift_wrap(&my_keys, &ec_pubkey, rumor, None).await?; 465 | 466 | // Send the Gift Wrap 467 | cloned_client.send_event(&gift_wrap).await?; 468 | 469 | log::info!("Token request sent!"); 470 | // Wait for the Gift Wrap to be unwrapped. 471 | } 472 | 473 | active_area = 1; 474 | selected_candidate_idx = 0; 475 | } else if active_area == 1 { 476 | // Log the selected candidate details 477 | let selected_candidate = { 478 | let elections_lock = elections.lock().unwrap(); 479 | elections_lock.get(selected_election_idx).map(|e| { 480 | let candidate = e.candidates[selected_candidate_idx].clone(); 481 | let election_id = e.id.clone(); 482 | (candidate, election_id) 483 | }) 484 | }; 485 | app.candidate_id = selected_candidate.as_ref().map(|(c, _)| c.id); 486 | 487 | if let Some((c, election_id)) = selected_candidate { 488 | log::info!("Selected candidate: {:#?}", c); 489 | let token = match app.token.as_ref() { 490 | Some(t) => t, 491 | None => { 492 | log::warn!("Token not generated"); 493 | continue; 494 | } 495 | }; 496 | let r = app.r.as_ref().unwrap(); 497 | let h_n_b64 = general_purpose::STANDARD.encode(app.h_n_bytes.as_ref().unwrap()); 498 | let token_b64 = general_purpose::STANDARD.encode(token); 499 | let r_b64 = general_purpose::STANDARD.encode(r); 500 | // h_n:token:r:candidate_id 501 | let candidate_id = c.id; 502 | let vote_payload = format!("{h_n_b64}:{token_b64}:{r_b64}:{candidate_id}"); 503 | let message = Message::new( 504 | election_id, 505 | 2, 506 | vote_payload, 507 | ); 508 | let message_json = serde_json::to_string(&message)?; 509 | log::info!("Vote to be sent: {}", message_json); 510 | // We generate a random key to keep the vote secret 511 | let random_keys = Keys::generate(); 512 | // Creates a "rumor" with the hash of the nonce. 513 | let rumor: UnsignedEvent = EventBuilder::text_note(message_json).build(random_keys.public_key()); 514 | 515 | // Wraps the rumor in a Gift Wrap. 516 | let gift_wrap: Event = EventBuilder::gift_wrap(&random_keys, &ec_pubkey, rumor, None).await?; 517 | 518 | // Send the Gift Wrap 519 | cloned_client.send_event(&gift_wrap).await?; 520 | 521 | log::info!("Vote sent!"); 522 | // Wait for the Gift Wrap to be unwrapped. 523 | } 524 | // TODO: handle candidate confirmation or switch to Ballot area 525 | } 526 | } 527 | _ => {} 528 | } 529 | } 530 | }, 531 | _ = refresh_interval.tick() => { 532 | // Refresh the UI even if there is no input. 533 | } 534 | } 535 | 536 | terminal.draw(|f| { 537 | ui_draw( 538 | f, 539 | active_area, 540 | &elections, 541 | &app, 542 | selected_election_idx, 543 | selected_candidate_idx, 544 | ) 545 | })?; 546 | } 547 | 548 | // Restore terminal to its original state. 549 | disable_raw_mode()?; 550 | execute!(terminal.backend_mut(), LeaveAlternateScreen)?; 551 | terminal.show_cursor()?; 552 | 553 | Ok(()) 554 | } 555 | -------------------------------------------------------------------------------- /voter/src/settings.rs: -------------------------------------------------------------------------------- 1 | use crate::SETTINGS; 2 | 3 | use serde::Deserialize; 4 | use std::{ 5 | env, fs, 6 | path::{Path, PathBuf}, 7 | }; 8 | 9 | #[derive(Debug, Deserialize)] 10 | pub struct Settings { 11 | pub secret_key: String, 12 | pub ec_public_key: String, 13 | pub relays: Vec, 14 | pub log_level: String, 15 | } 16 | 17 | /// Constructs (or copies) the configuration file and loads it 18 | pub fn init_settings() -> &'static Settings { 19 | SETTINGS.get_or_init(|| { 20 | // HOME and package name at compile time 21 | let home_dir = dirs::home_dir().expect("Could not find home directory"); 22 | let package_name = env!("CARGO_PKG_NAME"); 23 | let hidden_dir = home_dir.join(format!(".{package_name}")); 24 | let hidden_file = hidden_dir.join("settings.toml"); 25 | 26 | // Path to the settings.toml included in the repo (next to Cargo.toml) 27 | let default_file: PathBuf = Path::new(env!("CARGO_MANIFEST_DIR")).join("settings.toml"); 28 | 29 | // Create ~/.voter if it doesn't exist 30 | if !hidden_dir.exists() { 31 | fs::create_dir(&hidden_dir).expect("The configuration directory could not be created"); 32 | } 33 | 34 | // Copy settings.toml if it isn't already in ~/.voter 35 | if !hidden_file.exists() { 36 | fs::copy(&default_file, &hidden_file).expect("Could not copy default settings.toml"); 37 | } 38 | 39 | // Use the `config` crate to deserialize to the Settings struct 40 | let cfg = config::Config::builder() 41 | .add_source(config::File::from(hidden_file)) 42 | .build() 43 | .expect("settings.toml malformed"); 44 | 45 | cfg.try_deserialize::() 46 | .expect("Error deserializing settings.toml") 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /voter/src/util.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use blind_rsa_signatures::PublicKey as RSAPublicKey; 3 | use chrono::Local; 4 | use fern::Dispatch; 5 | use std::fs; 6 | use std::path::Path; 7 | 8 | /// Initialize logger function 9 | pub fn setup_logger(level: &str) -> Result<(), fern::InitError> { 10 | let log_level = match level.to_lowercase().as_str() { 11 | "trace" => log::LevelFilter::Trace, 12 | "debug" => log::LevelFilter::Debug, 13 | "info" => log::LevelFilter::Info, 14 | "warn" => log::LevelFilter::Warn, 15 | "error" => log::LevelFilter::Error, 16 | _ => log::LevelFilter::Info, // Default to Info for invalid values 17 | }; 18 | Dispatch::new() 19 | .format(|out, message, record| { 20 | out.finish(format_args!( 21 | "[{}] [{}] - {}", 22 | Local::now().format("%Y-%m-%d %H:%M:%S"), 23 | record.level(), 24 | message 25 | )) 26 | }) 27 | .level(log_level) 28 | .chain(fern::log_file("app.log")?) // Guarda en logs/app.log 29 | .apply()?; 30 | Ok(()) 31 | } 32 | 33 | /// Loads RSA public key from PEM files and converts it 34 | /// to the `blind-rsa-signatures` type. 35 | pub fn load_ec_pubkey>(pub_path: P) -> Result { 36 | let pub_pem = fs::read_to_string(pub_path)?; 37 | 38 | // Parse the PEM to RSA 39 | Ok(RSAPublicKey::from_pem(&pub_pem)?) 40 | } 41 | --------------------------------------------------------------------------------