diff options
52 files changed, 2365 insertions, 186 deletions
@@ -1,5 +1,7 @@ | |||
1 | /target | 1 | log/ |
2 | target/ | ||
2 | blocks/ | 3 | blocks/ |
3 | users/ | 4 | users/ |
5 | public/ | ||
4 | tags.lock | 6 | tags.lock |
5 | tags.temp | 7 | tags.temp |
diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3c79e9c --- /dev/null +++ b/.gitmodules | |||
@@ -0,0 +1,3 @@ | |||
1 | [submodule "site/themes/juice"] | ||
2 | path = site/themes/juice | ||
3 | url = https://github.com/huhu/juice | ||
@@ -41,14 +41,67 @@ dependencies = [ | |||
41 | ] | 41 | ] |
42 | 42 | ||
43 | [[package]] | 43 | [[package]] |
44 | name = "atty" | 44 | name = "anyhow" |
45 | version = "0.2.14" | 45 | version = "1.0.40" |
46 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
47 | checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" | ||
48 | |||
49 | [[package]] | ||
50 | name = "arc-swap" | ||
51 | version = "0.4.8" | ||
46 | source = "registry+https://github.com/rust-lang/crates.io-index" | 52 | source = "registry+https://github.com/rust-lang/crates.io-index" |
47 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" | 53 | checksum = "dabe5a181f83789739c194cbe5a897dde195078fac08568d09221fd6137a7ba8" |
54 | |||
55 | [[package]] | ||
56 | name = "arrayvec" | ||
57 | version = "0.5.2" | ||
58 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
59 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" | ||
60 | |||
61 | [[package]] | ||
62 | name = "askama" | ||
63 | version = "0.10.5" | ||
64 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
65 | checksum = "d298738b6e47e1034e560e5afe63aa488fea34e25ec11b855a76f0d7b8e73134" | ||
48 | dependencies = [ | 66 | dependencies = [ |
49 | "hermit-abi", | 67 | "askama_derive", |
50 | "libc", | 68 | "askama_escape", |
51 | "winapi 0.3.9", | 69 | "askama_shared", |
70 | ] | ||
71 | |||
72 | [[package]] | ||
73 | name = "askama_derive" | ||
74 | version = "0.10.5" | ||
75 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
76 | checksum = "ca2925c4c290382f9d2fa3d1c1b6a63fa1427099721ecca4749b154cc9c25522" | ||
77 | dependencies = [ | ||
78 | "askama_shared", | ||
79 | "proc-macro2", | ||
80 | "syn", | ||
81 | ] | ||
82 | |||
83 | [[package]] | ||
84 | name = "askama_escape" | ||
85 | version = "0.10.1" | ||
86 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
87 | checksum = "90c108c1a94380c89d2215d0ac54ce09796823cca0fd91b299cfff3b33e346fb" | ||
88 | |||
89 | [[package]] | ||
90 | name = "askama_shared" | ||
91 | version = "0.11.1" | ||
92 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
93 | checksum = "2582b77e0f3c506ec4838a25fa8a5f97b9bed72bb6d3d272ea1c031d8bd373bc" | ||
94 | dependencies = [ | ||
95 | "askama_escape", | ||
96 | "humansize", | ||
97 | "nom", | ||
98 | "num-traits", | ||
99 | "percent-encoding", | ||
100 | "proc-macro2", | ||
101 | "quote", | ||
102 | "serde", | ||
103 | "syn", | ||
104 | "toml", | ||
52 | ] | 105 | ] |
53 | 106 | ||
54 | [[package]] | 107 | [[package]] |
@@ -82,6 +135,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
82 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" | 135 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" |
83 | 136 | ||
84 | [[package]] | 137 | [[package]] |
138 | name = "bitvec" | ||
139 | version = "0.19.5" | ||
140 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
141 | checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" | ||
142 | dependencies = [ | ||
143 | "funty", | ||
144 | "radium", | ||
145 | "tap", | ||
146 | "wyz", | ||
147 | ] | ||
148 | |||
149 | [[package]] | ||
85 | name = "blake2" | 150 | name = "blake2" |
86 | version = "0.9.1" | 151 | version = "0.9.1" |
87 | source = "registry+https://github.com/rust-lang/crates.io-index" | 152 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -218,6 +283,17 @@ dependencies = [ | |||
218 | ] | 283 | ] |
219 | 284 | ||
220 | [[package]] | 285 | [[package]] |
286 | name = "derivative" | ||
287 | version = "2.2.0" | ||
288 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
289 | checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" | ||
290 | dependencies = [ | ||
291 | "proc-macro2", | ||
292 | "quote", | ||
293 | "syn", | ||
294 | ] | ||
295 | |||
296 | [[package]] | ||
221 | name = "digest" | 297 | name = "digest" |
222 | version = "0.9.0" | 298 | version = "0.9.0" |
223 | source = "registry+https://github.com/rust-lang/crates.io-index" | 299 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -233,19 +309,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
233 | checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" | 309 | checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" |
234 | 310 | ||
235 | [[package]] | 311 | [[package]] |
236 | name = "env_logger" | ||
237 | version = "0.6.2" | ||
238 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
239 | checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" | ||
240 | dependencies = [ | ||
241 | "atty", | ||
242 | "humantime", | ||
243 | "log", | ||
244 | "regex", | ||
245 | "termcolor", | ||
246 | ] | ||
247 | |||
248 | [[package]] | ||
249 | name = "fnv" | 312 | name = "fnv" |
250 | version = "1.0.7" | 313 | version = "1.0.7" |
251 | source = "registry+https://github.com/rust-lang/crates.io-index" | 314 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -278,6 +341,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
278 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" | 341 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" |
279 | 342 | ||
280 | [[package]] | 343 | [[package]] |
344 | name = "funty" | ||
345 | version = "1.1.0" | ||
346 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
347 | checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" | ||
348 | |||
349 | [[package]] | ||
281 | name = "futures" | 350 | name = "futures" |
282 | version = "0.3.14" | 351 | version = "0.3.14" |
283 | source = "registry+https://github.com/rust-lang/crates.io-index" | 352 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -376,6 +445,7 @@ name = "gradecoin" | |||
376 | version = "0.1.0" | 445 | version = "0.1.0" |
377 | dependencies = [ | 446 | dependencies = [ |
378 | "aes", | 447 | "aes", |
448 | "askama", | ||
379 | "base64 0.13.0", | 449 | "base64 0.13.0", |
380 | "blake2", | 450 | "blake2", |
381 | "block-modes", | 451 | "block-modes", |
@@ -384,9 +454,9 @@ dependencies = [ | |||
384 | "jsonwebtoken", | 454 | "jsonwebtoken", |
385 | "lazy_static", | 455 | "lazy_static", |
386 | "log", | 456 | "log", |
457 | "log4rs", | ||
387 | "md-5", | 458 | "md-5", |
388 | "parking_lot", | 459 | "parking_lot 0.10.2", |
389 | "pretty_env_logger", | ||
390 | "rsa", | 460 | "rsa", |
391 | "serde", | 461 | "serde", |
392 | "serde_json", | 462 | "serde_json", |
@@ -448,15 +518,6 @@ dependencies = [ | |||
448 | ] | 518 | ] |
449 | 519 | ||
450 | [[package]] | 520 | [[package]] |
451 | name = "hermit-abi" | ||
452 | version = "0.1.18" | ||
453 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
454 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" | ||
455 | dependencies = [ | ||
456 | "libc", | ||
457 | ] | ||
458 | |||
459 | [[package]] | ||
460 | name = "hex-literal" | 521 | name = "hex-literal" |
461 | version = "0.3.1" | 522 | version = "0.3.1" |
462 | source = "registry+https://github.com/rust-lang/crates.io-index" | 523 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -496,13 +557,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
496 | checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" | 557 | checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" |
497 | 558 | ||
498 | [[package]] | 559 | [[package]] |
560 | name = "humansize" | ||
561 | version = "1.1.0" | ||
562 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
563 | checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e" | ||
564 | |||
565 | [[package]] | ||
499 | name = "humantime" | 566 | name = "humantime" |
500 | version = "1.3.0" | 567 | version = "2.1.0" |
501 | source = "registry+https://github.com/rust-lang/crates.io-index" | 568 | source = "registry+https://github.com/rust-lang/crates.io-index" |
502 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" | 569 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" |
503 | dependencies = [ | ||
504 | "quick-error", | ||
505 | ] | ||
506 | 570 | ||
507 | [[package]] | 571 | [[package]] |
508 | name = "hyper" | 572 | name = "hyper" |
@@ -559,6 +623,15 @@ dependencies = [ | |||
559 | ] | 623 | ] |
560 | 624 | ||
561 | [[package]] | 625 | [[package]] |
626 | name = "instant" | ||
627 | version = "0.1.9" | ||
628 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
629 | checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" | ||
630 | dependencies = [ | ||
631 | "cfg-if 1.0.0", | ||
632 | ] | ||
633 | |||
634 | [[package]] | ||
562 | name = "iovec" | 635 | name = "iovec" |
563 | version = "0.1.4" | 636 | version = "0.1.4" |
564 | source = "registry+https://github.com/rust-lang/crates.io-index" | 637 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -616,6 +689,19 @@ dependencies = [ | |||
616 | ] | 689 | ] |
617 | 690 | ||
618 | [[package]] | 691 | [[package]] |
692 | name = "lexical-core" | ||
693 | version = "0.7.5" | ||
694 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
695 | checksum = "21f866863575d0e1d654fbeeabdc927292fdf862873dc3c96c6f753357e13374" | ||
696 | dependencies = [ | ||
697 | "arrayvec", | ||
698 | "bitflags", | ||
699 | "cfg-if 1.0.0", | ||
700 | "ryu", | ||
701 | "static_assertions", | ||
702 | ] | ||
703 | |||
704 | [[package]] | ||
619 | name = "libc" | 705 | name = "libc" |
620 | version = "0.2.93" | 706 | version = "0.2.93" |
621 | source = "registry+https://github.com/rust-lang/crates.io-index" | 707 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -628,6 +714,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
628 | checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" | 714 | checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" |
629 | 715 | ||
630 | [[package]] | 716 | [[package]] |
717 | name = "linked-hash-map" | ||
718 | version = "0.5.4" | ||
719 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
720 | checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" | ||
721 | |||
722 | [[package]] | ||
631 | name = "lock_api" | 723 | name = "lock_api" |
632 | version = "0.3.4" | 724 | version = "0.3.4" |
633 | source = "registry+https://github.com/rust-lang/crates.io-index" | 725 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -637,12 +729,55 @@ dependencies = [ | |||
637 | ] | 729 | ] |
638 | 730 | ||
639 | [[package]] | 731 | [[package]] |
732 | name = "lock_api" | ||
733 | version = "0.4.3" | ||
734 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
735 | checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176" | ||
736 | dependencies = [ | ||
737 | "scopeguard", | ||
738 | ] | ||
739 | |||
740 | [[package]] | ||
640 | name = "log" | 741 | name = "log" |
641 | version = "0.4.14" | 742 | version = "0.4.14" |
642 | source = "registry+https://github.com/rust-lang/crates.io-index" | 743 | source = "registry+https://github.com/rust-lang/crates.io-index" |
643 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" | 744 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" |
644 | dependencies = [ | 745 | dependencies = [ |
645 | "cfg-if 1.0.0", | 746 | "cfg-if 1.0.0", |
747 | "serde", | ||
748 | ] | ||
749 | |||
750 | [[package]] | ||
751 | name = "log-mdc" | ||
752 | version = "0.1.0" | ||
753 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
754 | checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" | ||
755 | |||
756 | [[package]] | ||
757 | name = "log4rs" | ||
758 | version = "1.0.0" | ||
759 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
760 | checksum = "d1572a880d1115ff867396eee7ae2bc924554225e67a0d3c85c745b3e60ca211" | ||
761 | dependencies = [ | ||
762 | "anyhow", | ||
763 | "arc-swap", | ||
764 | "chrono", | ||
765 | "derivative", | ||
766 | "fnv", | ||
767 | "humantime", | ||
768 | "libc", | ||
769 | "log", | ||
770 | "log-mdc", | ||
771 | "parking_lot 0.11.1", | ||
772 | "regex", | ||
773 | "serde", | ||
774 | "serde-value", | ||
775 | "serde_json", | ||
776 | "serde_yaml", | ||
777 | "thiserror", | ||
778 | "thread-id", | ||
779 | "typemap", | ||
780 | "winapi 0.3.9", | ||
646 | ] | 781 | ] |
647 | 782 | ||
648 | [[package]] | 783 | [[package]] |
@@ -745,6 +880,19 @@ dependencies = [ | |||
745 | ] | 880 | ] |
746 | 881 | ||
747 | [[package]] | 882 | [[package]] |
883 | name = "nom" | ||
884 | version = "6.1.2" | ||
885 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
886 | checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" | ||
887 | dependencies = [ | ||
888 | "bitvec", | ||
889 | "funty", | ||
890 | "lexical-core", | ||
891 | "memchr", | ||
892 | "version_check", | ||
893 | ] | ||
894 | |||
895 | [[package]] | ||
748 | name = "num-bigint" | 896 | name = "num-bigint" |
749 | version = "0.2.6" | 897 | version = "0.2.6" |
750 | source = "registry+https://github.com/rust-lang/crates.io-index" | 898 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -828,13 +976,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
828 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" | 976 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" |
829 | 977 | ||
830 | [[package]] | 978 | [[package]] |
979 | name = "ordered-float" | ||
980 | version = "2.1.1" | ||
981 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
982 | checksum = "766f840da25490628d8e63e529cd21c014f6600c6b8517add12a6fa6167a6218" | ||
983 | dependencies = [ | ||
984 | "num-traits", | ||
985 | ] | ||
986 | |||
987 | [[package]] | ||
831 | name = "parking_lot" | 988 | name = "parking_lot" |
832 | version = "0.10.2" | 989 | version = "0.10.2" |
833 | source = "registry+https://github.com/rust-lang/crates.io-index" | 990 | source = "registry+https://github.com/rust-lang/crates.io-index" |
834 | checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" | 991 | checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" |
835 | dependencies = [ | 992 | dependencies = [ |
836 | "lock_api", | 993 | "lock_api 0.3.4", |
837 | "parking_lot_core", | 994 | "parking_lot_core 0.7.2", |
995 | ] | ||
996 | |||
997 | [[package]] | ||
998 | name = "parking_lot" | ||
999 | version = "0.11.1" | ||
1000 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1001 | checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" | ||
1002 | dependencies = [ | ||
1003 | "instant", | ||
1004 | "lock_api 0.4.3", | ||
1005 | "parking_lot_core 0.8.3", | ||
838 | ] | 1006 | ] |
839 | 1007 | ||
840 | [[package]] | 1008 | [[package]] |
@@ -852,6 +1020,20 @@ dependencies = [ | |||
852 | ] | 1020 | ] |
853 | 1021 | ||
854 | [[package]] | 1022 | [[package]] |
1023 | name = "parking_lot_core" | ||
1024 | version = "0.8.3" | ||
1025 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1026 | checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" | ||
1027 | dependencies = [ | ||
1028 | "cfg-if 1.0.0", | ||
1029 | "instant", | ||
1030 | "libc", | ||
1031 | "redox_syscall 0.2.6", | ||
1032 | "smallvec", | ||
1033 | "winapi 0.3.9", | ||
1034 | ] | ||
1035 | |||
1036 | [[package]] | ||
855 | name = "pem" | 1037 | name = "pem" |
856 | version = "0.8.3" | 1038 | version = "0.8.3" |
857 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -933,17 +1115,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
933 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" | 1115 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" |
934 | 1116 | ||
935 | [[package]] | 1117 | [[package]] |
936 | name = "pretty_env_logger" | ||
937 | version = "0.3.1" | ||
938 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
939 | checksum = "717ee476b1690853d222af4634056d830b5197ffd747726a9a1eee6da9f49074" | ||
940 | dependencies = [ | ||
941 | "chrono", | ||
942 | "env_logger", | ||
943 | "log", | ||
944 | ] | ||
945 | |||
946 | [[package]] | ||
947 | name = "proc-macro2" | 1118 | name = "proc-macro2" |
948 | version = "1.0.26" | 1119 | version = "1.0.26" |
949 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -968,6 +1139,12 @@ dependencies = [ | |||
968 | ] | 1139 | ] |
969 | 1140 | ||
970 | [[package]] | 1141 | [[package]] |
1142 | name = "radium" | ||
1143 | version = "0.5.3" | ||
1144 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1145 | checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" | ||
1146 | |||
1147 | [[package]] | ||
971 | name = "rand" | 1148 | name = "rand" |
972 | version = "0.7.3" | 1149 | version = "0.7.3" |
973 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1150 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1158,6 +1335,16 @@ dependencies = [ | |||
1158 | ] | 1335 | ] |
1159 | 1336 | ||
1160 | [[package]] | 1337 | [[package]] |
1338 | name = "serde-value" | ||
1339 | version = "0.7.0" | ||
1340 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1341 | checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" | ||
1342 | dependencies = [ | ||
1343 | "ordered-float", | ||
1344 | "serde", | ||
1345 | ] | ||
1346 | |||
1347 | [[package]] | ||
1161 | name = "serde_derive" | 1348 | name = "serde_derive" |
1162 | version = "1.0.125" | 1349 | version = "1.0.125" |
1163 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1350 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1201,6 +1388,18 @@ dependencies = [ | |||
1201 | ] | 1388 | ] |
1202 | 1389 | ||
1203 | [[package]] | 1390 | [[package]] |
1391 | name = "serde_yaml" | ||
1392 | version = "0.8.17" | ||
1393 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1394 | checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23" | ||
1395 | dependencies = [ | ||
1396 | "dtoa", | ||
1397 | "linked-hash-map", | ||
1398 | "serde", | ||
1399 | "yaml-rust", | ||
1400 | ] | ||
1401 | |||
1402 | [[package]] | ||
1204 | name = "sha-1" | 1403 | name = "sha-1" |
1205 | version = "0.9.4" | 1404 | version = "0.9.4" |
1206 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1405 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1279,6 +1478,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
1279 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" | 1478 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" |
1280 | 1479 | ||
1281 | [[package]] | 1480 | [[package]] |
1481 | name = "static_assertions" | ||
1482 | version = "1.1.0" | ||
1483 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1484 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" | ||
1485 | |||
1486 | [[package]] | ||
1282 | name = "subtle" | 1487 | name = "subtle" |
1283 | version = "2.4.0" | 1488 | version = "2.4.0" |
1284 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1489 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1308,6 +1513,12 @@ dependencies = [ | |||
1308 | ] | 1513 | ] |
1309 | 1514 | ||
1310 | [[package]] | 1515 | [[package]] |
1516 | name = "tap" | ||
1517 | version = "1.0.1" | ||
1518 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1519 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" | ||
1520 | |||
1521 | [[package]] | ||
1311 | name = "tempfile" | 1522 | name = "tempfile" |
1312 | version = "3.2.0" | 1523 | version = "3.2.0" |
1313 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1524 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1322,15 +1533,6 @@ dependencies = [ | |||
1322 | ] | 1533 | ] |
1323 | 1534 | ||
1324 | [[package]] | 1535 | [[package]] |
1325 | name = "termcolor" | ||
1326 | version = "1.1.2" | ||
1327 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1328 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" | ||
1329 | dependencies = [ | ||
1330 | "winapi-util", | ||
1331 | ] | ||
1332 | |||
1333 | [[package]] | ||
1334 | name = "thiserror" | 1536 | name = "thiserror" |
1335 | version = "1.0.24" | 1537 | version = "1.0.24" |
1336 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1538 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1351,6 +1553,17 @@ dependencies = [ | |||
1351 | ] | 1553 | ] |
1352 | 1554 | ||
1353 | [[package]] | 1555 | [[package]] |
1556 | name = "thread-id" | ||
1557 | version = "3.3.0" | ||
1558 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1559 | checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1" | ||
1560 | dependencies = [ | ||
1561 | "libc", | ||
1562 | "redox_syscall 0.1.57", | ||
1563 | "winapi 0.3.9", | ||
1564 | ] | ||
1565 | |||
1566 | [[package]] | ||
1354 | name = "time" | 1567 | name = "time" |
1355 | version = "0.1.43" | 1568 | version = "0.1.43" |
1356 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1569 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1432,6 +1645,15 @@ dependencies = [ | |||
1432 | ] | 1645 | ] |
1433 | 1646 | ||
1434 | [[package]] | 1647 | [[package]] |
1648 | name = "toml" | ||
1649 | version = "0.5.8" | ||
1650 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1651 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" | ||
1652 | dependencies = [ | ||
1653 | "serde", | ||
1654 | ] | ||
1655 | |||
1656 | [[package]] | ||
1435 | name = "tower-service" | 1657 | name = "tower-service" |
1436 | version = "0.3.1" | 1658 | version = "0.3.1" |
1437 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1659 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1469,6 +1691,12 @@ dependencies = [ | |||
1469 | ] | 1691 | ] |
1470 | 1692 | ||
1471 | [[package]] | 1693 | [[package]] |
1694 | name = "traitobject" | ||
1695 | version = "0.1.0" | ||
1696 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1697 | checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" | ||
1698 | |||
1699 | [[package]] | ||
1472 | name = "try-lock" | 1700 | name = "try-lock" |
1473 | version = "0.2.3" | 1701 | version = "0.2.3" |
1474 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1702 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1503,6 +1731,15 @@ dependencies = [ | |||
1503 | ] | 1731 | ] |
1504 | 1732 | ||
1505 | [[package]] | 1733 | [[package]] |
1734 | name = "typemap" | ||
1735 | version = "0.3.3" | ||
1736 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1737 | checksum = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6" | ||
1738 | dependencies = [ | ||
1739 | "unsafe-any", | ||
1740 | ] | ||
1741 | |||
1742 | [[package]] | ||
1506 | name = "typenum" | 1743 | name = "typenum" |
1507 | version = "1.13.0" | 1744 | version = "1.13.0" |
1508 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1745 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1542,6 +1779,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
1542 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" | 1779 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" |
1543 | 1780 | ||
1544 | [[package]] | 1781 | [[package]] |
1782 | name = "unsafe-any" | ||
1783 | version = "0.4.2" | ||
1784 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1785 | checksum = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f" | ||
1786 | dependencies = [ | ||
1787 | "traitobject", | ||
1788 | ] | ||
1789 | |||
1790 | [[package]] | ||
1545 | name = "untrusted" | 1791 | name = "untrusted" |
1546 | version = "0.7.1" | 1792 | version = "0.7.1" |
1547 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1793 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1720,15 +1966,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
1720 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" | 1966 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" |
1721 | 1967 | ||
1722 | [[package]] | 1968 | [[package]] |
1723 | name = "winapi-util" | ||
1724 | version = "0.1.5" | ||
1725 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1726 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" | ||
1727 | dependencies = [ | ||
1728 | "winapi 0.3.9", | ||
1729 | ] | ||
1730 | |||
1731 | [[package]] | ||
1732 | name = "winapi-x86_64-pc-windows-gnu" | 1969 | name = "winapi-x86_64-pc-windows-gnu" |
1733 | version = "0.4.0" | 1970 | version = "0.4.0" |
1734 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1971 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1745,6 +1982,21 @@ dependencies = [ | |||
1745 | ] | 1982 | ] |
1746 | 1983 | ||
1747 | [[package]] | 1984 | [[package]] |
1985 | name = "wyz" | ||
1986 | version = "0.2.0" | ||
1987 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1988 | checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" | ||
1989 | |||
1990 | [[package]] | ||
1991 | name = "yaml-rust" | ||
1992 | version = "0.4.5" | ||
1993 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1994 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" | ||
1995 | dependencies = [ | ||
1996 | "linked-hash-map", | ||
1997 | ] | ||
1998 | |||
1999 | [[package]] | ||
1748 | name = "zeroize" | 2000 | name = "zeroize" |
1749 | version = "1.2.0" | 2001 | version = "1.2.0" |
1750 | source = "registry+https://github.com/rust-lang/crates.io-index" | 2002 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -12,7 +12,7 @@ tokio = { version = "0.2.9", features = ["macros"] } | |||
12 | serde = { version = "1.0.104", features = ["derive"] } | 12 | serde = { version = "1.0.104", features = ["derive"] } |
13 | chrono = { version = "0.4.10", features = ["serde"] } | 13 | chrono = { version = "0.4.10", features = ["serde"] } |
14 | log = "0.4.8" | 14 | log = "0.4.8" |
15 | pretty_env_logger = "0.3.1" | 15 | log4rs = "1.0.0" |
16 | parking_lot = "0.10.0" | 16 | parking_lot = "0.10.0" |
17 | serde_json = "1.0.59" | 17 | serde_json = "1.0.59" |
18 | lazy_static = "1.4.0" | 18 | lazy_static = "1.4.0" |
@@ -25,6 +25,7 @@ base64 = "0.13.0" | |||
25 | sha2 = "0.9.3" | 25 | sha2 = "0.9.3" |
26 | block-modes = "0.7.0" | 26 | block-modes = "0.7.0" |
27 | aes = "0.6.0" | 27 | aes = "0.6.0" |
28 | askama = "0.10.5" | ||
28 | 29 | ||
29 | [dev-dependencies] | 30 | [dev-dependencies] |
30 | serde_test = "1.0.117" | 31 | serde_test = "1.0.117" |
@@ -1,5 +1,32 @@ | |||
1 | # TODO | 1 | # TODO |
2 | 2 | ||
3 | - [ ] IV is array, should be hex, implement checks | ||
4 | - [ ] Restore students from disk | ||
5 | |||
6 | ## Office Hour/Recitation | ||
7 | - [ ] Should give a little pointers but not too much, I think at first this is going to seem hard to many students but it should become fairly easy after some little pointers | ||
8 | |||
9 | ## Docs | ||
10 | - [ ] Make a better explanation of authorization schema | ||
11 | - [ ] register: give the register message schema(passwd is missing) | ||
12 | - [ ] how to bank works | ||
13 | - [ ] register should have AuthRequest in the explanation | ||
14 | - [ ] link all types in schema.rs to the docs, they need to understand why we have them | ||
15 | - [ ] explain hash type(MD5 is missing in Claims) | ||
16 | - [ ] Initial auth request needs more explanation | ||
17 | - [ ] Explain JSON Wrapped | ||
18 | - [ ] Give links to the functions, their docs are very good. For example, it seems impossible to understand authentication from the first page, but when you go to handlers::authenticate_user many things are clarified. | ||
19 | - [ ] authorized_propose_transaction and authorized_propose_block may have more explanation as in the case of | ||
20 | - [x] how to start(possibly some pointers and links -- blockchain, rest, jwt, rsa, public key) | ||
21 | - [x] There is todo at handlers::authorized_propose_transaction, fix that | ||
22 | - [x] gradecoin: give narrative explanation | ||
23 | - [x] bank public key | ||
24 | - [X] delete CONSTANTS | ||
25 | |||
26 | ### Authorization | ||
27 | - [x] Pointer to JWT | ||
28 | - [x] Pointer to Public Key Sign | ||
29 | |||
3 | ## Tests | 30 | ## Tests |
4 | - [ ] User Authentication/Authentication Tests | 31 | - [ ] User Authentication/Authentication Tests |
5 | - [ ] Route Tests | 32 | - [ ] Route Tests |
@@ -7,11 +34,9 @@ | |||
7 | - [ ] Valid JSON with missing fields | 34 | - [ ] Valid JSON with missing fields |
8 | - [ ] Valid JSON with extra fields | 35 | - [ ] Valid JSON with extra fields |
9 | 36 | ||
10 | ## Please | ||
11 | - [ ] use [juice](https://www.getzola.org/themes/juice/) theme with [template rendering](https://blog.logrocket.com/template-rendering-in-rust/) to create a landing page. I want it to look handmade & _scammy_, I'm talking [verylegit](https://verylegit.link) shortened urls, botched [this person does not exist](https://www.thispersondoesnotexist.com/) user stories etc. | ||
12 | |||
13 | ## Testnet | 37 | ## Testnet |
14 | - [ ] CHAOS MODE, 3 different coins, combine them to make 1 gradecoin | 38 | - [ ] CHAOS MODE, 3 different coins, combine them to make 1 gradecoin |
39 | - [ ] [SQLite](https://unixsheikh.com/articles/sqlite-the-only-database-you-will-ever-need-in-most-cases.html), not PostgreSQL | ||
15 | 40 | ||
16 | ## Done & Brag | 41 | ## Done & Brag |
17 | - [x] Switch to RwLock (parking_lot) (done at 2021-04-07 03:43, two possible schemes to represent inner Db (ledger) in code) | 42 | - [x] Switch to RwLock (parking_lot) (done at 2021-04-07 03:43, two possible schemes to represent inner Db (ledger) in code) |
@@ -34,3 +59,4 @@ | |||
34 | - [X] Blocks should "play out" the transactions and execute transactions (2021-04-14 21:29) | 59 | - [X] Blocks should "play out" the transactions and execute transactions (2021-04-14 21:29) |
35 | - [X] "Coinbase" ("by" of the first transaction of the block) should get rewarded for their efforts (2021-04-14 21:48) | 60 | - [X] "Coinbase" ("by" of the first transaction of the block) should get rewarded for their efforts (2021-04-14 21:48) |
36 | - [X] Implemented Bank Account (2021-04-14 23:28) | 61 | - [X] Implemented Bank Account (2021-04-14 23:28) |
62 | - [x] use [juice](https://www.getzola.org/themes/juice/) theme ~~with [template rendering](https://blog.logrocket.com/template-rendering-in-rust/)~~ zola to create a landing page. (done at 2021-04-15 03:41, in the most hilarious way possible) | ||
diff --git a/log.conf.yml b/log.conf.yml new file mode 100644 index 0000000..1940669 --- /dev/null +++ b/log.conf.yml | |||
@@ -0,0 +1,41 @@ | |||
1 | # Scan this file for changes every 2 minutes | ||
2 | refresh_rate: 2 minutes | ||
3 | |||
4 | appenders: | ||
5 | # An appender named "stdout" that writes to stdout with pretty colours | ||
6 | stdout: | ||
7 | kind: console | ||
8 | encoder: | ||
9 | pattern: "[{d(%Y-%m-%d %H:%M:%S)} {h({l})}] - {m}\n" | ||
10 | |||
11 | # And this guy writes to file, also rolls the files (when they get too large) | ||
12 | gradecoin: | ||
13 | kind: rolling_file | ||
14 | path: "log/gradecoin.log" | ||
15 | append: true | ||
16 | encoder: | ||
17 | kind: pattern | ||
18 | pattern: "[{d(%Y-%m-%d %H:%M:%S)} {l}] - {m}\n" | ||
19 | |||
20 | policy: | ||
21 | kind: compound | ||
22 | |||
23 | trigger: | ||
24 | kind: size | ||
25 | limit: 4 mb | ||
26 | |||
27 | roller: | ||
28 | kind: fixed_window | ||
29 | pattern: "log/gradecoin.{}.old.log" | ||
30 | count: 5 | ||
31 | base: 1 | ||
32 | |||
33 | loggers: | ||
34 | gradecoin: | ||
35 | level: info | ||
36 | appenders: | ||
37 | - stdout | ||
38 | - gradecoin | ||
39 | additive: false | ||
40 | |||
41 | |||
diff --git a/site/config.toml b/site/config.toml new file mode 100644 index 0000000..070b762 --- /dev/null +++ b/site/config.toml | |||
@@ -0,0 +1,28 @@ | |||
1 | # The URL the site will be built for | ||
2 | base_url = "https://gradecoin.xyz" | ||
3 | |||
4 | theme = "juice" | ||
5 | |||
6 | title = "Gradecoin" | ||
7 | description = "Mine Your Grades" | ||
8 | |||
9 | # Whether to automatically compile all Sass files in the sass directory | ||
10 | compile_sass = true | ||
11 | |||
12 | # Whether to build a search index to be used later on by a JavaScript library | ||
13 | build_search_index = true | ||
14 | |||
15 | [markdown] | ||
16 | # Whether to do syntax highlighting | ||
17 | # Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola | ||
18 | highlight_code = true | ||
19 | highlight_theme = "subway-moscow" | ||
20 | |||
21 | [extra] | ||
22 | # Put all your custom variables here | ||
23 | juice_logo_name = "Gradecoin" | ||
24 | juice_logo_path = "gradecoin.png" | ||
25 | juice_extra_menu = [ | ||
26 | { title = "why?", link = "https://github.com/zhuowei/nft_ptr#why"} | ||
27 | ] | ||
28 | |||
diff --git a/site/content/JWT.md b/site/content/JWT.md new file mode 100644 index 0000000..46da1a2 --- /dev/null +++ b/site/content/JWT.md | |||
@@ -0,0 +1,41 @@ | |||
1 | +++ | ||
2 | title = "JWT" | ||
3 | description = "JSON Web Token Documentation" | ||
4 | weight = 4 | ||
5 | +++ | ||
6 | |||
7 | > JSON Web Tokens are representations of claims, or authorization proofs that fit into the `Header` of HTTP requests. | ||
8 | |||
9 | # How? | ||
10 | |||
11 | JWTs are used as the [MAC](https://en.wikipedia.org/wiki/Message_authentication_code) of operations that require authorization: | ||
12 | - block proposal | ||
13 | - transaction proposal. | ||
14 | |||
15 | They are send alongside the JSON request body in the `Header`; | ||
16 | |||
17 | ```html | ||
18 | Authorization: Bearer aaaaaa.bbbbbb.ccccc | ||
19 | ``` | ||
20 | |||
21 | Gradecoin uses 3 fields for the JWTs; | ||
22 | |||
23 | ```json | ||
24 | { | ||
25 | "tha": "Hash of the payload, check invididual references", | ||
26 | "iat": "Issued At, Unix Time", | ||
27 | "exp": "Expiration Time, epoch" | ||
28 | } | ||
29 | ``` | ||
30 | |||
31 | - `tha` is explained in [blocks](@/block_docs.md) and [transactions](@/transaction_docs.md) documentations. | ||
32 | - `iat` when the JWT was created in [Unix Time](https://en.wikipedia.org/wiki/Unix_time) format | ||
33 | - `exp` when the JWT will expire & be rejected in [Unix Time](https://en.wikipedia.org/wiki/Unix_time) | ||
34 | |||
35 | # Algorithm | ||
36 | We are using [RS256](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.1), `RSASSA-PKCS1-v1_5 using SHA-256`. The JWTs you encode with your private RSA key will be decoded using the public key you have authenticated with. You can see how the process works [here](https://jwt.io/). | ||
37 | |||
38 | # References | ||
39 | - [RFC, the ultimate reference](https://tools.ietf.org/html/rfc7519) | ||
40 | - [JWT Debugger](https://jwt.io/) | ||
41 | |||
diff --git a/site/content/_index.md b/site/content/_index.md new file mode 100644 index 0000000..4ad0544 --- /dev/null +++ b/site/content/_index.md | |||
@@ -0,0 +1,85 @@ | |||
1 | +++ | ||
2 | title = "Gradecoin" | ||
3 | sort_by = "weight" | ||
4 | +++ | ||
5 | |||
6 | # Welcome to Gradecoin! | ||
7 | |||
8 | Blockchains are incredibly simple yet can appear very complicated, we will see how they work and practice programming _production_ cryptography code. | ||
9 | |||
10 | This server is the sandbox for the PA1, it's currently running the Gradecoin application. Gradecoin is the faux currency we will use to simulate a blockchain network. At the end of the simulation, the amount of Gradecoin you hold will be your PA1 grade. | ||
11 | |||
12 | **A quick summary**: authenticate yourself to the system using public key encryption. | ||
13 | Craft [Transaction](@/transaction_docs.md) proposals and tag them using [JWTs](@/JWT.md). | ||
14 | When there are enough transactions then you can propose [Blocks](@/block_docs.md) in the same way. | ||
15 | Blocks need to be _mined_ beforehand using Proof-of-work, or brute force. | ||
16 | |||
17 | Gradecoin offers 3 endpoints at [/register](/register), [/block](/block) and [/transaction](/transaction). You can only send GET requests to /block and /transaction without authorization. | ||
18 | The server is programmed in [RESTful](https://www.service-architecture.com/articles/web-services/representational_state_transfer_rest.html) architecture, there are no `DELETE`, `PUT` or `UPDATE` operations, though. | ||
19 | |||
20 | Gradecoin uses a Proof-of-work block accepting mechanism. It uses single round [Blake2s](https://www.blake2.net/) hashing which produces 256-bit (64 hexadecimal characters) output. The [target](https://wiki.bitcoinsv.io/index.php/Target) hash is _24 bits_ or _6 hexadecimal characters_ of 0. During testing, I could mine a block on average around 2-7 minutes. | ||
21 | |||
22 | > We're expecting you to use existing tools and implementations. Standards are hard. [Don't roll your own crypto](https://www.reddit.com/r/crypto/comments/2coqsy/dont_roll_your_own/). Feel free to ask questions. Collaborate. | ||
23 | |||
24 | You might ask, | ||
25 | |||
26 | > But if nobody has any Gradecoin then how do we have transactions? | ||
27 | |||
28 | There is a bank! Their public key is `31415926535897932384626433832795028841971693993751058209749445923` and they have some amount of Gradecoin preloaded. It's also the only account that you can send transactions requests _to_ yourself. | ||
29 | |||
30 | # Coinbase | ||
31 | The first transactions of a block is called the `coinbase`. They are the **author** of the block proposal and if the block is accepted then they get compensated for their efforts with some Gradecoin. | ||
32 | |||
33 | # Public Key Signatures | ||
34 | Gradecoin uses 2048 bit RSA keyspairs. | ||
35 | |||
36 | # Services | ||
37 | ## /register | ||
38 | - Student creates their own 2048 bit RSA `keypair` | ||
39 | - Downloads `Gradecoin`'s Public Key from [Moodle](https://odtuclass.metu.edu.tr/my/) | ||
40 | - Encrypts their JSON wrapped `Public Key`, `Student ID` and one time `passwd` using Gradecoin's Public Key | ||
41 | - Their public key is now in our database and can be used to sign their JWT's during requests | ||
42 | |||
43 | ## /transaction | ||
44 | - You can offer a [Transaction](/transaction) - POST request | ||
45 | - The request should have `Authorization` | ||
46 | - The request header should be signed by the Public Key of the `by` field in the transaction | ||
47 | - fetch the list of `Transaction`s - GET request | ||
48 | |||
49 | ## /block | ||
50 | - offer a [`schema::Block`] - POST request | ||
51 | - The request should have `Authorization` | ||
52 | - The [`schema::Block::transaction_list`] of the block should be a subset of [`schema::Db::pending_transactions`] | ||
53 | - fetch the last accepted [`schema::Block`] - GET request | ||
54 | |||
55 | `Authorization`: The request header should have Bearer JWT.Token signed with Student Public Key | ||
56 | |||
57 | # Questions | ||
58 | ## This all sound complicated! | ||
59 | - I've drawn inspiration from [actual Bitcoin transactions](https://explorer.bitcoin.com/btc) and [warp](https://github.com/seanmonstar/warp/blob/master/examples/todos.rs). The simplicity of the system is how little interfaces it has. | ||
60 | - Don't know where to start? Gradecoin uses RESTful API; simple `curl` commands or even your browser will work! [This website can help as well](https://curl.trillworks.com/). | ||
61 | - [JWT Debugger](https://jwt.io) and the corresponding [RFC](https://tools.ietf.org/html/rfc7519) | ||
62 | - Remember that you are absolutely encouraged to grab off-the-shelf implementations for every cryptography primitive you will use. You can start by finding a code snippet to generate a RSA keypair? | ||
63 | |||
64 | ## I found a bug! | ||
65 | Thank you! Please [let me know](mailto:yigit@ceng.metu.edu.tr) so we can solve it. | ||
66 | |||
67 | ## I hacked the server! | ||
68 | That wasn't supposed to happen :( I did not place any intentional vulnerabilities to the system so if you cracked something, it was not intended. Please don't abuse it and let me know so I can patch it. | ||
69 | |||
70 | ## Submission? | ||
71 | At the end of the _simulation_, your Gradecoin balance will be your grade. I will also expect a unique client programmed in either; | ||
72 | - c | ||
73 | - c++ | ||
74 | - perl | ||
75 | - rust | ||
76 | - python | ||
77 | - random assortment of bash scripts | ||
78 | |||
79 | If your favourite programming language is missing please let me know 🤷? | ||
80 | |||
81 | ## Can my friends play? | ||
82 | Sadly, no. Student's who are enrolled to the class will receive one-time-passwords for authentication. | ||
83 | |||
84 | ## How and or Why? | ||
85 | - [Built](https://xkcd.com/2314/), [with](https://lofi.cafe/) [Rust](https://xkcd.com/2418/) | ||
diff --git a/site/content/block_docs.md b/site/content/block_docs.md new file mode 100644 index 0000000..c1d61e9 --- /dev/null +++ b/site/content/block_docs.md | |||
@@ -0,0 +1,30 @@ | |||
1 | +++ | ||
2 | title = "Blocks" | ||
3 | description = "Block Documentation" | ||
4 | weight = 10 | ||
5 | +++ | ||
6 | |||
7 | A block that was proposed to commit Transactions in `transaction_list` to the | ||
8 | ledger with a nonce that made `hash` valid; 6 zeroes at the left hand side of the | ||
9 | hash (24 bytes). | ||
10 | |||
11 | We are _mining_ using [blake2s](https://www.blake2.net/) algorithm, which produces 256 bit hashes. Hash/second is roughly 20x10^3 on my machine, a new block can be mined in around 4-6 minutes. | ||
12 | |||
13 | # Requests | ||
14 | |||
15 | ## GET | ||
16 | A HTTP `GET` request to [/block](/block) endpoint will return the latest mined block. | ||
17 | |||
18 | ## POST | ||
19 | |||
20 | A HTTP `POST` request with Authorization using JWT will allow you to propose your own blocks. | ||
21 | |||
22 | # Fields | ||
23 | ``` | ||
24 | transaction_list: [array of Fingerprints] | ||
25 | nonce: unsigned 32-bit integer | ||
26 | timestamp: ISO 8601 <date>T<time> | ||
27 | hash: String | ||
28 | ``` | ||
29 | |||
30 | [ISO 8601 Reference](https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations) | ||
diff --git a/site/content/register_docs.md b/site/content/register_docs.md new file mode 100644 index 0000000..83aef7f --- /dev/null +++ b/site/content/register_docs.md | |||
@@ -0,0 +1,39 @@ | |||
1 | +++ | ||
2 | title = "Register" | ||
3 | description = "Register Documentation" | ||
4 | weight = 3 | ||
5 | +++ | ||
6 | |||
7 | POST request to /register endpoint | ||
8 | |||
9 | Lets a user to authenticate themselves to the system. | ||
10 | Only people who are enrolled to the class can open Gradecoin accounts. | ||
11 | This is enforced with your Student ID and a one time password you will receive. | ||
12 | |||
13 | # Authentication Process | ||
14 | - Gradecoin's Public Key (`gradecoin_public_key`) is listed on our Moodle page. | ||
15 | - You pick a short temporary key (`k_temp`) | ||
16 | - Create a JSON object (`auth_plaintext`) with your `metu_id` and `public key` in base64 (PEM) format (`S_PK`) [reference](https://tls.mbed.org/kb/cryptography/asn1-key-structures-in-der-and-pem) | ||
17 | ```json | ||
18 | { | ||
19 | "student_id": "e12345", | ||
20 | "passwd": "15 char secret", | ||
21 | "public_key": "---BEGIN PUBLIC KEY..." | ||
22 | } | ||
23 | ``` | ||
24 | |||
25 | - Pick a random IV. | ||
26 | - Encrypt the serialized string of `auth_plaintext` with 128 bit block AES in CBC mode with Pkcs7 padding using the temporary key (`k_temp`), the result is `auth_ciphertext`. Encode this with base64. | ||
27 | - The temporary key you have picked `k_temp` is encrypted using RSA with OAEP padding scheme | ||
28 | using SHA-256 with `gradecoin_public_key`, giving us `key_ciphertext`. Encode this with base 64. | ||
29 | - The payload JSON object (`auth_request`) can be serialized now: | ||
30 | |||
31 | ```json | ||
32 | { | ||
33 | "c": "auth_ciphertext", | ||
34 | "iv": "hexadecimal", | ||
35 | "key": "key_ciphertext" | ||
36 | } | ||
37 | ``` | ||
38 | |||
39 | If your authentication process was valid, you will be given access and your public key fingerprint that is your address. | ||
diff --git a/site/content/transaction_docs.md b/site/content/transaction_docs.md new file mode 100644 index 0000000..820c35f --- /dev/null +++ b/site/content/transaction_docs.md | |||
@@ -0,0 +1,26 @@ | |||
1 | +++ | ||
2 | title = "Transactions" | ||
3 | description = "Transaction documentation" | ||
4 | weight = 6 | ||
5 | +++ | ||
6 | |||
7 | A transaction request between `source` and `target` to move `amount` Gradecoin. | ||
8 | |||
9 | # Requests | ||
10 | |||
11 | ## GET | ||
12 | A HTTP `GET` request to [/transaction](/transaction) endpoint will return the current list of pending transactions. | ||
13 | |||
14 | ## POST | ||
15 | |||
16 | A HTTP `POST` request with Authorization using JWT to [/transaction](/transactions) will allow you to propose your own transactions. | ||
17 | |||
18 | |||
19 | # Fields | ||
20 | ``` | ||
21 | by: Fingerprint | ||
22 | source: Fingerprint | ||
23 | target: Fingerprint | ||
24 | amount: unsigned 16 bit integer | ||
25 | timestamp: ISO 8601 <date>T<time> | ||
26 | ``` | ||
diff --git a/site/public/404.html b/site/public/404.html new file mode 100644 index 0000000..f8414f0 --- /dev/null +++ b/site/public/404.html | |||
@@ -0,0 +1,3 @@ | |||
1 | <!doctype html> | ||
2 | <title>404 Not Found</title> | ||
3 | <h1>404 Not Found</h1> | ||
diff --git a/static/android-chrome-192x192.png b/site/public/android-chrome-192x192.png index 023ddbd..023ddbd 100644 --- a/static/android-chrome-192x192.png +++ b/site/public/android-chrome-192x192.png | |||
Binary files differ | |||
diff --git a/static/android-chrome-512x512.png b/site/public/android-chrome-512x512.png index 4251933..4251933 100644 --- a/static/android-chrome-512x512.png +++ b/site/public/android-chrome-512x512.png | |||
Binary files differ | |||
diff --git a/static/apple-touch-icon.png b/site/public/apple-touch-icon.png index cd8e4c8..cd8e4c8 100644 --- a/static/apple-touch-icon.png +++ b/site/public/apple-touch-icon.png | |||
Binary files differ | |||
diff --git a/site/public/block-docs/index.html b/site/public/block-docs/index.html new file mode 100644 index 0000000..de4d4e2 --- /dev/null +++ b/site/public/block-docs/index.html | |||
@@ -0,0 +1,168 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html lang="en"> | ||
3 | |||
4 | <head> | ||
5 | <meta charset="UTF-8"> | ||
6 | <title>Blocks | Gradecoin </title> | ||
7 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||
8 | <style> | ||
9 | :root { | ||
10 | /* Primary theme color */ | ||
11 | --primary-color: #F8D12F; | ||
12 | /* Primary theme text color */ | ||
13 | --primary-text-color: #1E2329; | ||
14 | /* Primary theme link color */ | ||
15 | --primary-link-color: #2F57F7; | ||
16 | /* Secondary color: the background body color */ | ||
17 | --secondary-color: #FAFAFA; | ||
18 | --secondary-text-color: #303030; | ||
19 | /* Highlight text color of table of content */ | ||
20 | --toc-highlight-text-color: #d46e13; | ||
21 | } | ||
22 | </style> | ||
23 | |||
24 | <link href="https://fonts.googleapis.com/css?family=Alfa+Slab+One&display=swap" rel="stylesheet"> | ||
25 | <link href="https://fonts.googleapis.com/css?family=Fira+Sans:400,500,600&display=swap" rel="stylesheet"> | ||
26 | <link rel="stylesheet" href="/normalize.css"> | ||
27 | <link rel="stylesheet" href="https://gradecoin.xyz/juice.css"> | ||
28 | |||
29 | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" /> | ||
30 | |||
31 | </head> | ||
32 | |||
33 | <body> | ||
34 | |||
35 | <header class="box-shadow"> | ||
36 | |||
37 | |||
38 | <a href="https://gradecoin.xyz/"> | ||
39 | <div class="logo"> | ||
40 | <img src="https://gradecoin.xyz/gradecoin.png" alt="logo"> | ||
41 | Gradecoin | ||
42 | </div> | ||
43 | </a> | ||
44 | |||
45 | <nav> | ||
46 | |||
47 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/register-docs/">Register</a> | ||
48 | |||
49 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/jwt/">JWT</a> | ||
50 | |||
51 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/transaction-docs/">Transactions</a> | ||
52 | |||
53 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/block-docs/">Blocks</a> | ||
54 | |||
55 | |||
56 | |||
57 | <a class="nav-item subtitle-text" href="https://github.com/zhuowei/nft_ptr#why">why?</a> | ||
58 | |||
59 | |||
60 | </nav> | ||
61 | |||
62 | </header> | ||
63 | |||
64 | |||
65 | <main> | ||
66 | |||
67 | |||
68 | |||
69 | |||
70 | |||
71 | <div class="toc"> | ||
72 | <div class="toc-sticky"> | ||
73 | |||
74 | <div class="toc-item"> | ||
75 | <a class="subtext" href="https://gradecoin.xyz/block-docs/#requests">Requests</a> | ||
76 | </div> | ||
77 | |||
78 | |||
79 | <div class="toc-item-child"> | ||
80 | <a class="subtext" href="https://gradecoin.xyz/block-docs/#get"><small>- GET</small></a> | ||
81 | </div> | ||
82 | |||
83 | <div class="toc-item-child"> | ||
84 | <a class="subtext" href="https://gradecoin.xyz/block-docs/#post"><small>- POST</small></a> | ||
85 | </div> | ||
86 | |||
87 | |||
88 | |||
89 | <div class="toc-item"> | ||
90 | <a class="subtext" href="https://gradecoin.xyz/block-docs/#fields">Fields</a> | ||
91 | </div> | ||
92 | |||
93 | |||
94 | </div> | ||
95 | </div> | ||
96 | |||
97 | |||
98 | |||
99 | <div class="content text"> | ||
100 | |||
101 | <div class="heading-text">Block Documentation</div> | ||
102 | <p>A block that was proposed to commit Transactions in <code>transaction_list</code> to the | ||
103 | ledger with a nonce that made <code>hash</code> valid; 6 zeroes at the left hand side of the | ||
104 | hash (24 bytes).</p> | ||
105 | <p>We are <em>mining</em> using <a href="https://www.blake2.net/">blake2s</a> algorithm, which produces 256 bit hashes. Hash/second is roughly 20x10^3 on my machine, a new block can be mined in around 4-6 minutes.</p> | ||
106 | <h1 id="requests">Requests</h1> | ||
107 | <h2 id="get">GET</h2> | ||
108 | <p>A HTTP <code>GET</code> request to <a href="/block">/block</a> endpoint will return the latest mined block.</p> | ||
109 | <h2 id="post">POST</h2> | ||
110 | <p>A HTTP <code>POST</code> request with Authorization using JWT will allow you to propose your own blocks.</p> | ||
111 | <h1 id="fields">Fields</h1> | ||
112 | <pre style="background-color:#ffffff;"> | ||
113 | <code><span style="color:#545052;">transaction_list: [array of Fingerprints] | ||
114 | nonce: unsigned 32-bit integer | ||
115 | timestamp: ISO 8601 <date>T<time> | ||
116 | hash: String | ||
117 | </span></code></pre> | ||
118 | <p><a href="https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations">ISO 8601 Reference</a></p> | ||
119 | |||
120 | |||
121 | </div> | ||
122 | |||
123 | |||
124 | |||
125 | </main> | ||
126 | |||
127 | |||
128 | <footer> | ||
129 | Built For ⁂ CENG489 ⁂ Introduction to Computer Security | ||
130 | </footer> | ||
131 | |||
132 | </body> | ||
133 | <script> | ||
134 | function highlightNav(heading) { | ||
135 | let pathname = location.pathname; | ||
136 | document.querySelectorAll(".toc a").forEach((item) => { | ||
137 | item.classList.remove("active"); | ||
138 | }); | ||
139 | document.querySelector(".toc a[href$='" + pathname + "#" + heading + "']").classList.add("active"); | ||
140 | } | ||
141 | |||
142 | let currentHeading = ""; | ||
143 | window.onscroll = function () { | ||
144 | let h = document.querySelectorAll("h1,h2,h3,h4,h5,h6"); | ||
145 | let elementArr = []; | ||
146 | |||
147 | h.forEach(item => { | ||
148 | if (item.id !== "") { | ||
149 | elementArr[item.id] = item.getBoundingClientRect().top; | ||
150 | } | ||
151 | }); | ||
152 | elementArr.sort(); | ||
153 | for (let key in elementArr) { | ||
154 | if (!elementArr.hasOwnProperty(key)) { | ||
155 | continue; | ||
156 | } | ||
157 | if (elementArr[key] > 0 && elementArr[key] < 300) { | ||
158 | if (currentHeading !== key) { | ||
159 | highlightNav(key); | ||
160 | currentHeading = key; | ||
161 | } | ||
162 | break; | ||
163 | } | ||
164 | } | ||
165 | } | ||
166 | </script> | ||
167 | |||
168 | </html> | ||
diff --git a/site/public/elasticlunr.min.js b/site/public/elasticlunr.min.js new file mode 100644 index 0000000..79dad65 --- /dev/null +++ b/site/public/elasticlunr.min.js | |||
@@ -0,0 +1,10 @@ | |||
1 | /** | ||
2 | * elasticlunr - http://weixsong.github.io | ||
3 | * Lightweight full-text search engine in Javascript for browser search and offline search. - 0.9.6 | ||
4 | * | ||
5 | * Copyright (C) 2017 Oliver Nightingale | ||
6 | * Copyright (C) 2017 Wei Song | ||
7 | * MIT Licensed | ||
8 | * @license | ||
9 | */ | ||
10 | !function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u<s.length;u++){var a=s[u];r[a]=this.pipeline.run(t.tokenizer(e[a]))}var l={};for(var c in o){var d=r[c]||r.any;if(d){var f=this.fieldSearch(d,c,o),h=o[c].boost;for(var p in f)f[p]=f[p]*h;for(var p in f)p in l?l[p]+=f[p]:l[p]=f[p]}}var v,g=[];for(var p in l)v={ref:p,score:l[p]},this.documentStore.hasDoc(p)&&(v.doc=this.documentStore.getDoc(p)),g.push(v);return g.sort(function(e,t){return t.score-e.score}),g},t.Index.prototype.fieldSearch=function(e,t,n){var i=n[t].bool,o=n[t].expand,r=n[t].boost,s=null,u={};return 0!==r?(e.forEach(function(e){var n=[e];1==o&&(n=this.index[t].expandToken(e));var r={};n.forEach(function(n){var o=this.index[t].getDocs(n),a=this.idf(n,t);if(s&&"AND"==i){var l={};for(var c in s)c in o&&(l[c]=o[c]);o=l}n==e&&this.fieldSearchStats(u,n,o);for(var c in o){var d=this.index[t].getTermFrequency(n,c),f=this.documentStore.getFieldLength(c,t),h=1;0!=f&&(h=1/Math.sqrt(f));var p=1;n!=e&&(p=.15*(1-(n.length-e.length)/n.length));var v=d*a*h*p;c in r?r[c]+=v:r[c]=v}},this),s=this.mergeScores(s,r,i)},this),s=this.coordNorm(s,u,e.length)):void 0},t.Index.prototype.mergeScores=function(e,t,n){if(!e)return t;if("AND"==n){var i={};for(var o in t)o in e&&(i[o]=e[o]+t[o]);return i}for(var o in t)o in e?e[o]+=t[o]:e[o]=t[o];return e},t.Index.prototype.fieldSearchStats=function(e,t,n){for(var i in n)i in e?e[i].push(t):e[i]=[t]},t.Index.prototype.coordNorm=function(e,t,n){for(var i in e)if(i in t){var o=t[i].length;e[i]=e[i]*o/n}return e},t.Index.prototype.toJSON=function(){var e={};return this._fields.forEach(function(t){e[t]=this.index[t].toJSON()},this),{version:t.version,fields:this._fields,ref:this._ref,documentStore:this.documentStore.toJSON(),index:e,pipeline:this.pipeline.toJSON()}},t.Index.prototype.use=function(e){var t=Array.prototype.slice.call(arguments,1);t.unshift(this),e.apply(this,t)},t.DocumentStore=function(e){this._save=null===e||void 0===e?!0:e,this.docs={},this.docInfo={},this.length=0},t.DocumentStore.load=function(e){var t=new this;return t.length=e.length,t.docs=e.docs,t.docInfo=e.docInfo,t._save=e.save,t},t.DocumentStore.prototype.isDocStored=function(){return this._save},t.DocumentStore.prototype.addDoc=function(t,n){this.hasDoc(t)||this.length++,this.docs[t]=this._save===!0?e(n):null},t.DocumentStore.prototype.getDoc=function(e){return this.hasDoc(e)===!1?null:this.docs[e]},t.DocumentStore.prototype.hasDoc=function(e){return e in this.docs},t.DocumentStore.prototype.removeDoc=function(e){this.hasDoc(e)&&(delete this.docs[e],delete this.docInfo[e],this.length--)},t.DocumentStore.prototype.addFieldLength=function(e,t,n){null!==e&&void 0!==e&&0!=this.hasDoc(e)&&(this.docInfo[e]||(this.docInfo[e]={}),this.docInfo[e][t]=n)},t.DocumentStore.prototype.updateFieldLength=function(e,t,n){null!==e&&void 0!==e&&0!=this.hasDoc(e)&&this.addFieldLength(e,t,n)},t.DocumentStore.prototype.getFieldLength=function(e,t){return null===e||void 0===e?0:e in this.docs&&t in this.docInfo[e]?this.docInfo[e][t]:0},t.DocumentStore.prototype.toJSON=function(){return{docs:this.docs,docInfo:this.docInfo,length:this.length,save:this._save}},t.stemmer=function(){var e={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},t={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",o=n+"[^aeiouy]*",r=i+"[aeiou]*",s="^("+o+")?"+r+o,u="^("+o+")?"+r+o+"("+r+")?$",a="^("+o+")?"+r+o+r+o,l="^("+o+")?"+i,c=new RegExp(s),d=new RegExp(a),f=new RegExp(u),h=new RegExp(l),p=/^(.+?)(ss|i)es$/,v=/^(.+?)([^s])s$/,g=/^(.+?)eed$/,m=/^(.+?)(ed|ing)$/,y=/.$/,S=/(at|bl|iz)$/,x=new RegExp("([^aeiouylsz])\\1$"),w=new RegExp("^"+o+i+"[^aeiouwxy]$"),I=/^(.+?[^aeiou])y$/,b=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,E=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,D=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,F=/^(.+?)(s|t)(ion)$/,_=/^(.+?)e$/,P=/ll$/,k=new RegExp("^"+o+i+"[^aeiouwxy]$"),z=function(n){var i,o,r,s,u,a,l;if(n.length<3)return n;if(r=n.substr(0,1),"y"==r&&(n=r.toUpperCase()+n.substr(1)),s=p,u=v,s.test(n)?n=n.replace(s,"$1$2"):u.test(n)&&(n=n.replace(u,"$1$2")),s=g,u=m,s.test(n)){var z=s.exec(n);s=c,s.test(z[1])&&(s=y,n=n.replace(s,""))}else if(u.test(n)){var z=u.exec(n);i=z[1],u=h,u.test(i)&&(n=i,u=S,a=x,l=w,u.test(n)?n+="e":a.test(n)?(s=y,n=n.replace(s,"")):l.test(n)&&(n+="e"))}if(s=I,s.test(n)){var z=s.exec(n);i=z[1],n=i+"i"}if(s=b,s.test(n)){var z=s.exec(n);i=z[1],o=z[2],s=c,s.test(i)&&(n=i+e[o])}if(s=E,s.test(n)){var z=s.exec(n);i=z[1],o=z[2],s=c,s.test(i)&&(n=i+t[o])}if(s=D,u=F,s.test(n)){var z=s.exec(n);i=z[1],s=d,s.test(i)&&(n=i)}else if(u.test(n)){var z=u.exec(n);i=z[1]+z[2],u=d,u.test(i)&&(n=i)}if(s=_,s.test(n)){var z=s.exec(n);i=z[1],s=d,u=f,a=k,(s.test(i)||u.test(i)&&!a.test(i))&&(n=i)}return s=P,u=d,s.test(n)&&u.test(n)&&(s=y,n=n.replace(s,"")),"y"==r&&(n=r.toLowerCase()+n.substr(1)),n};return z}(),t.Pipeline.registerFunction(t.stemmer,"stemmer"),t.stopWordFilter=function(e){return e&&t.stopWordFilter.stopWords[e]!==!0?e:void 0},t.clearStopWords=function(){t.stopWordFilter.stopWords={}},t.addStopWords=function(e){null!=e&&Array.isArray(e)!==!1&&e.forEach(function(e){t.stopWordFilter.stopWords[e]=!0},this)},t.resetStopWords=function(){t.stopWordFilter.stopWords=t.defaultStopWords},t.defaultStopWords={"":!0,a:!0,able:!0,about:!0,across:!0,after:!0,all:!0,almost:!0,also:!0,am:!0,among:!0,an:!0,and:!0,any:!0,are:!0,as:!0,at:!0,be:!0,because:!0,been:!0,but:!0,by:!0,can:!0,cannot:!0,could:!0,dear:!0,did:!0,"do":!0,does:!0,either:!0,"else":!0,ever:!0,every:!0,"for":!0,from:!0,get:!0,got:!0,had:!0,has:!0,have:!0,he:!0,her:!0,hers:!0,him:!0,his:!0,how:!0,however:!0,i:!0,"if":!0,"in":!0,into:!0,is:!0,it:!0,its:!0,just:!0,least:!0,let:!0,like:!0,likely:!0,may:!0,me:!0,might:!0,most:!0,must:!0,my:!0,neither:!0,no:!0,nor:!0,not:!0,of:!0,off:!0,often:!0,on:!0,only:!0,or:!0,other:!0,our:!0,own:!0,rather:!0,said:!0,say:!0,says:!0,she:!0,should:!0,since:!0,so:!0,some:!0,than:!0,that:!0,the:!0,their:!0,them:!0,then:!0,there:!0,these:!0,they:!0,"this":!0,tis:!0,to:!0,too:!0,twas:!0,us:!0,wants:!0,was:!0,we:!0,were:!0,what:!0,when:!0,where:!0,which:!0,"while":!0,who:!0,whom:!0,why:!0,will:!0,"with":!0,would:!0,yet:!0,you:!0,your:!0},t.stopWordFilter.stopWords=t.defaultStopWords,t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter"),t.trimmer=function(e){if(null===e||void 0===e)throw new Error("token should not be undefined");return e.replace(/^\W+/,"").replace(/\W+$/,"")},t.Pipeline.registerFunction(t.trimmer,"trimmer"),t.InvertedIndex=function(){this.root={docs:{},df:0}},t.InvertedIndex.load=function(e){var t=new this;return t.root=e.root,t},t.InvertedIndex.prototype.addToken=function(e,t,n){for(var n=n||this.root,i=0;i<=e.length-1;){var o=e[i];o in n||(n[o]={docs:{},df:0}),i+=1,n=n[o]}var r=t.ref;n.docs[r]?n.docs[r]={tf:t.tf}:(n.docs[r]={tf:t.tf},n.df+=1)},t.InvertedIndex.prototype.hasToken=function(e){if(!e)return!1;for(var t=this.root,n=0;n<e.length;n++){if(!t[e[n]])return!1;t=t[e[n]]}return!0},t.InvertedIndex.prototype.getNode=function(e){if(!e)return null;for(var t=this.root,n=0;n<e.length;n++){if(!t[e[n]])return null;t=t[e[n]]}return t},t.InvertedIndex.prototype.getDocs=function(e){var t=this.getNode(e);return null==t?{}:t.docs},t.InvertedIndex.prototype.getTermFrequency=function(e,t){var n=this.getNode(e);return null==n?0:t in n.docs?n.docs[t].tf:0},t.InvertedIndex.prototype.getDocFreq=function(e){var t=this.getNode(e);return null==t?0:t.df},t.InvertedIndex.prototype.removeToken=function(e,t){if(e){var n=this.getNode(e);null!=n&&t in n.docs&&(delete n.docs[t],n.df-=1)}},t.InvertedIndex.prototype.expandToken=function(e,t,n){if(null==e||""==e)return[];var t=t||[];if(void 0==n&&(n=this.getNode(e),null==n))return t;n.df>0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e<arguments.length;e++)t=arguments[e],~this.indexOf(t)||this.elements.splice(this.locationFor(t),0,t);this.length=this.elements.length},lunr.SortedSet.prototype.toArray=function(){return this.elements.slice()},lunr.SortedSet.prototype.map=function(e,t){return this.elements.map(e,t)},lunr.SortedSet.prototype.forEach=function(e,t){return this.elements.forEach(e,t)},lunr.SortedSet.prototype.indexOf=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]<u[i]?n++:s[n]>u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o<r.length;o++)i.add(r[o]);return i},lunr.SortedSet.prototype.toJSON=function(){return this.toArray()},function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():e.elasticlunr=t()}(this,function(){return t})}(); | ||
diff --git a/static/favicon-16x16.png b/site/public/favicon-16x16.png index bd63d34..bd63d34 100644 --- a/static/favicon-16x16.png +++ b/site/public/favicon-16x16.png | |||
Binary files differ | |||
diff --git a/static/favicon-32x32.png b/site/public/favicon-32x32.png index e343587..e343587 100644 --- a/static/favicon-32x32.png +++ b/site/public/favicon-32x32.png | |||
Binary files differ | |||
diff --git a/static/favicon.ico b/site/public/favicon.ico index 45d8bfe..45d8bfe 100644 --- a/static/favicon.ico +++ b/site/public/favicon.ico | |||
Binary files differ | |||
diff --git a/static/gradecoin.png b/site/public/gradecoin.png index eeb670c..eeb670c 100644 --- a/static/gradecoin.png +++ b/site/public/gradecoin.png | |||
Binary files differ | |||
diff --git a/site/public/index.html b/site/public/index.html new file mode 100644 index 0000000..8986841 --- /dev/null +++ b/site/public/index.html | |||
@@ -0,0 +1,319 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html lang="en"> | ||
3 | |||
4 | <head> | ||
5 | <meta charset="UTF-8"> | ||
6 | <title>Gradecoin</title> | ||
7 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||
8 | <style> | ||
9 | :root { | ||
10 | /* Primary theme color */ | ||
11 | --primary-color: #F8D12F; | ||
12 | /* Primary theme text color */ | ||
13 | --primary-text-color: #1E2329; | ||
14 | /* Primary theme link color */ | ||
15 | --primary-link-color: #2F57F7; | ||
16 | /* Secondary color: the background body color */ | ||
17 | --secondary-color: #FAFAFA; | ||
18 | --secondary-text-color: #303030; | ||
19 | /* Highlight text color of table of content */ | ||
20 | --toc-highlight-text-color: #d46e13; | ||
21 | } | ||
22 | </style> | ||
23 | |||
24 | <link href="https://fonts.googleapis.com/css?family=Alfa+Slab+One&display=swap" rel="stylesheet"> | ||
25 | <link href="https://fonts.googleapis.com/css?family=Fira+Sans:400,500,600&display=swap" rel="stylesheet"> | ||
26 | <link rel="stylesheet" href="/normalize.css"> | ||
27 | <link rel="stylesheet" href="https://gradecoin.xyz/juice.css"> | ||
28 | |||
29 | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" /> | ||
30 | |||
31 | </head> | ||
32 | |||
33 | <body> | ||
34 | |||
35 | |||
36 | <header class="pos-absolute" style="background-color: transparent"> | ||
37 | |||
38 | |||
39 | <a href="https://gradecoin.xyz/"> | ||
40 | <div class="logo"> | ||
41 | <img src="https://gradecoin.xyz/gradecoin.png" alt="logo"> | ||
42 | Gradecoin | ||
43 | </div> | ||
44 | </a> | ||
45 | |||
46 | <nav> | ||
47 | |||
48 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/register-docs/">Register</a> | ||
49 | |||
50 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/jwt/">JWT</a> | ||
51 | |||
52 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/transaction-docs/">Transactions</a> | ||
53 | |||
54 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/block-docs/">Blocks</a> | ||
55 | |||
56 | |||
57 | |||
58 | <a class="nav-item subtitle-text" href="https://github.com/zhuowei/nft_ptr#why">why?</a> | ||
59 | |||
60 | |||
61 | </nav> | ||
62 | |||
63 | </header> | ||
64 | |||
65 | <div class="hero"> | ||
66 | |||
67 | <section class="text-center"> | ||
68 | <h1 class="heading-text animate__animated animate__jackInTheBox" style="font-size: 50px"> | ||
69 | Mine your own grades | ||
70 | </h1> | ||
71 | <h3 class="title-text"> | ||
72 | <b>Gradecoin</b> is the latest cutting edge blockchain technology agile grading framework that drives organic engagement and other buzzwords, with big data mining search engine optimization | ||
73 | </h3> | ||
74 | <div> | ||
75 | </div> | ||
76 | </section> | ||
77 | <img class="hero-image" style="width: 40%" src="https://gradecoin.xyz/gradecoin.png"> | ||
78 | |||
79 | <div class="explore-more text" | ||
80 | onclick="document.getElementById('features').scrollIntoView({behavior: 'smooth'})"> | ||
81 | ⇩ Learn How ⇩ | ||
82 | </div> | ||
83 | <style> | ||
84 | |||
85 | .hero section { | ||
86 | padding: 0 5rem; | ||
87 | } | ||
88 | |||
89 | @media screen and (max-width: 768px) { | ||
90 | .hero section { | ||
91 | padding: 0 2rem; | ||
92 | } | ||
93 | |||
94 | .hero-image { | ||
95 | display: none | ||
96 | } | ||
97 | |||
98 | } | ||
99 | footer { | ||
100 | color: #8b8b8b; | ||
101 | } | ||
102 | </style> | ||
103 | |||
104 | </div> | ||
105 | |||
106 | |||
107 | |||
108 | <main> | ||
109 | |||
110 | |||
111 | |||
112 | |||
113 | |||
114 | <div class="toc"> | ||
115 | <div class="toc-sticky"> | ||
116 | |||
117 | <div class="toc-item"> | ||
118 | <a class="subtext" href="https://gradecoin.xyz/#welcome-to-gradecoin">Welcome to Gradecoin!</a> | ||
119 | </div> | ||
120 | |||
121 | |||
122 | <div class="toc-item"> | ||
123 | <a class="subtext" href="https://gradecoin.xyz/#coinbase">Coinbase</a> | ||
124 | </div> | ||
125 | |||
126 | |||
127 | <div class="toc-item"> | ||
128 | <a class="subtext" href="https://gradecoin.xyz/#public-key-signatures">Public Key Signatures</a> | ||
129 | </div> | ||
130 | |||
131 | |||
132 | <div class="toc-item"> | ||
133 | <a class="subtext" href="https://gradecoin.xyz/#services">Services</a> | ||
134 | </div> | ||
135 | |||
136 | |||
137 | <div class="toc-item-child"> | ||
138 | <a class="subtext" href="https://gradecoin.xyz/#register"><small>- /register</small></a> | ||
139 | </div> | ||
140 | |||
141 | <div class="toc-item-child"> | ||
142 | <a class="subtext" href="https://gradecoin.xyz/#transaction"><small>- /transaction</small></a> | ||
143 | </div> | ||
144 | |||
145 | <div class="toc-item-child"> | ||
146 | <a class="subtext" href="https://gradecoin.xyz/#block"><small>- /block</small></a> | ||
147 | </div> | ||
148 | |||
149 | |||
150 | |||
151 | <div class="toc-item"> | ||
152 | <a class="subtext" href="https://gradecoin.xyz/#questions">Questions</a> | ||
153 | </div> | ||
154 | |||
155 | |||
156 | <div class="toc-item-child"> | ||
157 | <a class="subtext" href="https://gradecoin.xyz/#this-all-sound-complicated"><small>- This all sound complicated!</small></a> | ||
158 | </div> | ||
159 | |||
160 | <div class="toc-item-child"> | ||
161 | <a class="subtext" href="https://gradecoin.xyz/#i-found-a-bug"><small>- I found a bug!</small></a> | ||
162 | </div> | ||
163 | |||
164 | <div class="toc-item-child"> | ||
165 | <a class="subtext" href="https://gradecoin.xyz/#i-hacked-the-server"><small>- I hacked the server!</small></a> | ||
166 | </div> | ||
167 | |||
168 | <div class="toc-item-child"> | ||
169 | <a class="subtext" href="https://gradecoin.xyz/#submission"><small>- Submission?</small></a> | ||
170 | </div> | ||
171 | |||
172 | <div class="toc-item-child"> | ||
173 | <a class="subtext" href="https://gradecoin.xyz/#can-my-friends-play"><small>- Can my friends play?</small></a> | ||
174 | </div> | ||
175 | |||
176 | <div class="toc-item-child"> | ||
177 | <a class="subtext" href="https://gradecoin.xyz/#how-and-or-why"><small>- How and or Why?</small></a> | ||
178 | </div> | ||
179 | |||
180 | |||
181 | |||
182 | </div> | ||
183 | </div> | ||
184 | |||
185 | |||
186 | |||
187 | <div class="content text"> | ||
188 | |||
189 | <div id="features" class="heading-text">Overview</div> | ||
190 | <h1 id="welcome-to-gradecoin">Welcome to Gradecoin!</h1> | ||
191 | <p>Blockchains are incredibly simple yet can appear very complicated, we will see how they work and practice programming <em>production</em> cryptography code.</p> | ||
192 | <p>This server is the sandbox for the PA1, it's currently running the Gradecoin application. Gradecoin is the faux currency we will use to simulate a blockchain network. At the end of the simulation, the amount of Gradecoin you hold will be your PA1 grade.</p> | ||
193 | <p><strong>A quick summary</strong>: authenticate yourself to the system using public key encryption. | ||
194 | Craft <a href="https://gradecoin.xyz/transaction-docs/">Transaction</a> proposals and tag them using <a href="https://gradecoin.xyz/jwt/">JWTs</a>. | ||
195 | When there are enough transactions then you can propose <a href="https://gradecoin.xyz/block-docs/">Blocks</a> in the same way. | ||
196 | Blocks need to be <em>mined</em> beforehand using Proof-of-work, or brute force.</p> | ||
197 | <p>Gradecoin offers 3 endpoints at <a href="/register">/register</a>, <a href="/block">/block</a> and <a href="/transaction">/transaction</a>. You can only send GET requests to /block and /transaction without authorization. | ||
198 | The server is programmed in <a href="https://www.service-architecture.com/articles/web-services/representational_state_transfer_rest.html">RESTful</a> architecture, there are no <code>DELETE</code>, <code>PUT</code> or <code>UPDATE</code> operations, though.</p> | ||
199 | <p>Gradecoin uses a Proof-of-work block accepting mechanism. It uses single round <a href="https://www.blake2.net/">Blake2s</a> hashing which produces 256-bit (64 hexadecimal characters) output. The <a href="https://wiki.bitcoinsv.io/index.php/Target">target</a> hash is <em>24 bits</em> or <em>6 hexadecimal characters</em> of 0. During testing, I could mine a block on average around 2-7 minutes.</p> | ||
200 | <blockquote> | ||
201 | <p>We're expecting you to use existing tools and implementations. Standards are hard. <a href="https://www.reddit.com/r/crypto/comments/2coqsy/dont_roll_your_own/">Don't roll your own crypto</a>. Feel free to ask questions. Collaborate.</p> | ||
202 | </blockquote> | ||
203 | <p>You might ask,</p> | ||
204 | <blockquote> | ||
205 | <p>But if nobody has any Gradecoin then how do we have transactions?</p> | ||
206 | </blockquote> | ||
207 | <p>There is a bank! Their public key is <code>31415926535897932384626433832795028841971693993751058209749445923</code> and they have some amount of Gradecoin preloaded. It's also the only account that you can send transactions requests <em>to</em> yourself.</p> | ||
208 | <h1 id="coinbase">Coinbase</h1> | ||
209 | <p>The first transactions of a block is called the <code>coinbase</code>. They are the <strong>author</strong> of the block proposal and if the block is accepted then they get compensated for their efforts with some Gradecoin.</p> | ||
210 | <h1 id="public-key-signatures">Public Key Signatures</h1> | ||
211 | <p>Gradecoin uses 2048 bit RSA keyspairs.</p> | ||
212 | <h1 id="services">Services</h1> | ||
213 | <h2 id="register">/register</h2> | ||
214 | <ul> | ||
215 | <li>Student creates their own 2048 bit RSA <code>keypair</code></li> | ||
216 | <li>Downloads <code>Gradecoin</code>'s Public Key from <a href="https://odtuclass.metu.edu.tr/my/">Moodle</a></li> | ||
217 | <li>Encrypts their JSON wrapped <code>Public Key</code>, <code>Student ID</code> and one time <code>passwd</code> using Gradecoin's Public Key</li> | ||
218 | <li>Their public key is now in our database and can be used to sign their JWT's during requests</li> | ||
219 | </ul> | ||
220 | <h2 id="transaction">/transaction</h2> | ||
221 | <ul> | ||
222 | <li>You can offer a <a href="/transaction">Transaction</a> - POST request | ||
223 | <ul> | ||
224 | <li>The request should have <code>Authorization</code></li> | ||
225 | <li>The request header should be signed by the Public Key of the <code>by</code> field in the transaction</li> | ||
226 | </ul> | ||
227 | </li> | ||
228 | <li>fetch the list of <code>Transaction</code>s - GET request</li> | ||
229 | </ul> | ||
230 | <h2 id="block">/block</h2> | ||
231 | <ul> | ||
232 | <li>offer a [<code>schema::Block</code>] - POST request | ||
233 | <ul> | ||
234 | <li>The request should have <code>Authorization</code></li> | ||
235 | <li>The [<code>schema::Block::transaction_list</code>] of the block should be a subset of [<code>schema::Db::pending_transactions</code>]</li> | ||
236 | </ul> | ||
237 | </li> | ||
238 | <li>fetch the last accepted [<code>schema::Block</code>] - GET request</li> | ||
239 | </ul> | ||
240 | <p><code>Authorization</code>: The request header should have Bearer JWT.Token signed with Student Public Key</p> | ||
241 | <h1 id="questions">Questions</h1> | ||
242 | <h2 id="this-all-sound-complicated">This all sound complicated!</h2> | ||
243 | <ul> | ||
244 | <li>I've drawn inspiration from <a href="https://explorer.bitcoin.com/btc">actual Bitcoin transactions</a> and <a href="https://github.com/seanmonstar/warp/blob/master/examples/todos.rs">warp</a>. The simplicity of the system is how little interfaces it has.</li> | ||
245 | <li>Don't know where to start? Gradecoin uses RESTful API; simple <code>curl</code> commands or even your browser will work! <a href="https://curl.trillworks.com/">This website can help as well</a>.</li> | ||
246 | <li><a href="https://jwt.io">JWT Debugger</a> and the corresponding <a href="https://tools.ietf.org/html/rfc7519">RFC</a></li> | ||
247 | <li>Remember that you are absolutely encouraged to grab off-the-shelf implementations for every cryptography primitive you will use. You can start by finding a code snippet to generate a RSA keypair?</li> | ||
248 | </ul> | ||
249 | <h2 id="i-found-a-bug">I found a bug!</h2> | ||
250 | <p>Thank you! Please <a href="mailto:yigit@ceng.metu.edu.tr">let me know</a> so we can solve it.</p> | ||
251 | <h2 id="i-hacked-the-server">I hacked the server!</h2> | ||
252 | <p>That wasn't supposed to happen :( I did not place any intentional vulnerabilities to the system so if you cracked something, it was not intended. Please don't abuse it and let me know so I can patch it.</p> | ||
253 | <h2 id="submission">Submission?</h2> | ||
254 | <p>At the end of the <em>simulation</em>, your Gradecoin balance will be your grade. I will also expect a unique client programmed in either;</p> | ||
255 | <ul> | ||
256 | <li>c</li> | ||
257 | <li>c++</li> | ||
258 | <li>perl</li> | ||
259 | <li>rust</li> | ||
260 | <li>python</li> | ||
261 | <li>random assortment of bash scripts</li> | ||
262 | </ul> | ||
263 | <p>If your favourite programming language is missing please let me know 🤷?</p> | ||
264 | <h2 id="can-my-friends-play">Can my friends play?</h2> | ||
265 | <p>Sadly, no. Student's who are enrolled to the class will receive one-time-passwords for authentication.</p> | ||
266 | <h2 id="how-and-or-why">How and or Why?</h2> | ||
267 | <ul> | ||
268 | <li><a href="https://xkcd.com/2314/">Built</a>, <a href="https://lofi.cafe/">with</a> <a href="https://xkcd.com/2418/">Rust</a></li> | ||
269 | </ul> | ||
270 | |||
271 | |||
272 | </div> | ||
273 | |||
274 | |||
275 | |||
276 | </main> | ||
277 | |||
278 | |||
279 | <footer> | ||
280 | Built For ⁂ CENG489 ⁂ Introduction to Computer Security | ||
281 | </footer> | ||
282 | |||
283 | </body> | ||
284 | <script> | ||
285 | function highlightNav(heading) { | ||
286 | let pathname = location.pathname; | ||
287 | document.querySelectorAll(".toc a").forEach((item) => { | ||
288 | item.classList.remove("active"); | ||
289 | }); | ||
290 | document.querySelector(".toc a[href$='" + pathname + "#" + heading + "']").classList.add("active"); | ||
291 | } | ||
292 | |||
293 | let currentHeading = ""; | ||
294 | window.onscroll = function () { | ||
295 | let h = document.querySelectorAll("h1,h2,h3,h4,h5,h6"); | ||
296 | let elementArr = []; | ||
297 | |||
298 | h.forEach(item => { | ||
299 | if (item.id !== "") { | ||
300 | elementArr[item.id] = item.getBoundingClientRect().top; | ||
301 | } | ||
302 | }); | ||
303 | elementArr.sort(); | ||
304 | for (let key in elementArr) { | ||
305 | if (!elementArr.hasOwnProperty(key)) { | ||
306 | continue; | ||
307 | } | ||
308 | if (elementArr[key] > 0 && elementArr[key] < 300) { | ||
309 | if (currentHeading !== key) { | ||
310 | highlightNav(key); | ||
311 | currentHeading = key; | ||
312 | } | ||
313 | break; | ||
314 | } | ||
315 | } | ||
316 | } | ||
317 | </script> | ||
318 | |||
319 | </html> | ||
diff --git a/site/public/juice.css b/site/public/juice.css new file mode 100644 index 0000000..219f864 --- /dev/null +++ b/site/public/juice.css | |||
@@ -0,0 +1 @@ | |||
.text-center{text-align:center}.pos-absolute{right:0;left:0;position:absolute}.box-shadow{box-shadow:0 2px 10px 2px #ddd}.heading-text{font-family:"Fira Sans", sans-serif;font-size:32px;font-weight:600;padding:10px 0 25px 0;color:var(--primary-text-color)}h1,.title-text{font-family:"Fira Sans", sans-serif;font-size:25px;font-weight:500;color:var(--primary-text-color);border-left:var(--primary-color) 8px solid;padding-left:10px}h2,.subtitle-text{font-family:"Fira Sans", sans-serif;font-size:20px;font-weight:500;color:var(--primary-text-color)}.text{font-family:"Fira Sans", sans-serif;font-size:18px;font-weight:400;line-height:26px;letter-spacing:0.2px;color:var(--primary-text-color)}.subtext{font-family:"Fira Sans", sans-serif;font-size:16px;font-weight:400;letter-spacing:0.1px}.content{padding:0 40px;display:flex;flex-direction:column;justify-content:center;overflow-x:auto}.content pre{overflow-x:auto;padding:1.25em 1.5em;white-space:pre;word-wrap:normal;background-color:white;color:#4a4a4a;font-size:.875em;font-family:monospace}.content code{background-color:white;color:#4a4a4a;font-size:.875em;font-weight:normal;padding:0.25em 0.5em;font-family:monospace}.content pre code{padding:0}.content a{color:var(--primary-link-color)}.content a:hover{text-decoration:underline}.content blockquote{border-left:#e2dede 8px solid;margin:0;background-color:#f2f1f0;padding:0 20px}body{padding:0;margin:0;box-sizing:border-box;background-color:var(--secondary-color)}a{text-decoration:none}ul{margin-top:0.5rem}ul>li{padding:0.3rem 0}p>img{width:100%;height:auto}header{background-color:var(--primary-color);color:black;padding:20px 50px;display:flex;align-items:center;justify-content:space-between}.logo{font-family:"Alfa Slab One", serif;font-size:32px;color:var(--primary-text-color);display:flex;align-items:center;margin:0 40px}.logo img{width:60px;margin:0 25px}.nav-item{margin:0 10px;text-decoration:none;font-size:18px;font-weight:bold}.nav-item:hover{color:#000;text-decoration:underline}.hero{display:flex;align-items:center;justify-content:space-evenly;height:100vh;background-color:var(--primary-color);overflow-x:hidden;padding:0 40px}.hero .explore-more{position:absolute;bottom:20px;cursor:pointer}main{display:flex;padding:50px 100px}main .toc{max-width:260px;min-width:240px}main .toc-item{padding:10px 20px;color:#424242}main .toc-item a,main .toc-item-child a{color:var(--secondary-text-color)}main .toc-item a:hover,main .toc-item-child a:hover{cursor:pointer;text-decoration:underline}main .toc-item a.active,main .toc-item-child a.active{color:var(--toc-highlight-text-color)}main .toc-item-child{padding:0 30px 5px;color:#424242}.toc-sticky{border-radius:3px;border-top:5px solid var(--primary-color);background-color:white;position:sticky;position:-webkit-sticky;position:-moz-sticky;position:-ms-sticky;position:-o-sticky;top:10px;padding:10px 0}footer{padding:50px;display:flex;flex-direction:column;justify-content:center;align-items:center;background-color:#202020;color:#fcfcfc}footer a{color:#fcfcfc;text-decoration:underline}@media screen and (min-width: 1280px){.content{max-width:60%;min-width:800px}}@media screen and (max-width: 768px){header{padding:10px 30px;flex-direction:column;align-items:center;justify-content:center}.logo{font-size:28px;margin:10px}.logo img{width:45px;margin:0 10px 0 0}.nav-item{margin:0 5px;font-size:14px}.hero{padding:40px 30px}main{padding:30px}.content{padding:0}.explore-more,.toc{display:none}} | |||
diff --git a/site/public/jwt/index.html b/site/public/jwt/index.html new file mode 100644 index 0000000..d06d45a --- /dev/null +++ b/site/public/jwt/index.html | |||
@@ -0,0 +1,179 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html lang="en"> | ||
3 | |||
4 | <head> | ||
5 | <meta charset="UTF-8"> | ||
6 | <title>JWT | Gradecoin </title> | ||
7 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||
8 | <style> | ||
9 | :root { | ||
10 | /* Primary theme color */ | ||
11 | --primary-color: #F8D12F; | ||
12 | /* Primary theme text color */ | ||
13 | --primary-text-color: #1E2329; | ||
14 | /* Primary theme link color */ | ||
15 | --primary-link-color: #2F57F7; | ||
16 | /* Secondary color: the background body color */ | ||
17 | --secondary-color: #FAFAFA; | ||
18 | --secondary-text-color: #303030; | ||
19 | /* Highlight text color of table of content */ | ||
20 | --toc-highlight-text-color: #d46e13; | ||
21 | } | ||
22 | </style> | ||
23 | |||
24 | <link href="https://fonts.googleapis.com/css?family=Alfa+Slab+One&display=swap" rel="stylesheet"> | ||
25 | <link href="https://fonts.googleapis.com/css?family=Fira+Sans:400,500,600&display=swap" rel="stylesheet"> | ||
26 | <link rel="stylesheet" href="/normalize.css"> | ||
27 | <link rel="stylesheet" href="https://gradecoin.xyz/juice.css"> | ||
28 | |||
29 | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" /> | ||
30 | |||
31 | </head> | ||
32 | |||
33 | <body> | ||
34 | |||
35 | <header class="box-shadow"> | ||
36 | |||
37 | |||
38 | <a href="https://gradecoin.xyz/"> | ||
39 | <div class="logo"> | ||
40 | <img src="https://gradecoin.xyz/gradecoin.png" alt="logo"> | ||
41 | Gradecoin | ||
42 | </div> | ||
43 | </a> | ||
44 | |||
45 | <nav> | ||
46 | |||
47 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/register-docs/">Register</a> | ||
48 | |||
49 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/jwt/">JWT</a> | ||
50 | |||
51 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/transaction-docs/">Transactions</a> | ||
52 | |||
53 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/block-docs/">Blocks</a> | ||
54 | |||
55 | |||
56 | |||
57 | <a class="nav-item subtitle-text" href="https://github.com/zhuowei/nft_ptr#why">why?</a> | ||
58 | |||
59 | |||
60 | </nav> | ||
61 | |||
62 | </header> | ||
63 | |||
64 | |||
65 | <main> | ||
66 | |||
67 | |||
68 | |||
69 | |||
70 | |||
71 | <div class="toc"> | ||
72 | <div class="toc-sticky"> | ||
73 | |||
74 | <div class="toc-item"> | ||
75 | <a class="subtext" href="https://gradecoin.xyz/jwt/#how">How?</a> | ||
76 | </div> | ||
77 | |||
78 | |||
79 | <div class="toc-item"> | ||
80 | <a class="subtext" href="https://gradecoin.xyz/jwt/#algorithm">Algorithm</a> | ||
81 | </div> | ||
82 | |||
83 | |||
84 | <div class="toc-item"> | ||
85 | <a class="subtext" href="https://gradecoin.xyz/jwt/#references">References</a> | ||
86 | </div> | ||
87 | |||
88 | |||
89 | </div> | ||
90 | </div> | ||
91 | |||
92 | |||
93 | |||
94 | <div class="content text"> | ||
95 | |||
96 | <div class="heading-text">JSON Web Token Documentation</div> | ||
97 | <blockquote> | ||
98 | <p>JSON Web Tokens are representations of claims, or authorization proofs that fit into the <code>Header</code> of HTTP requests.</p> | ||
99 | </blockquote> | ||
100 | <h1 id="how">How?</h1> | ||
101 | <p>JWTs are used as the <a href="https://en.wikipedia.org/wiki/Message_authentication_code">MAC</a> of operations that require authorization:</p> | ||
102 | <ul> | ||
103 | <li>block proposal</li> | ||
104 | <li>transaction proposal.</li> | ||
105 | </ul> | ||
106 | <p>They are send alongside the JSON request body in the <code>Header</code>;</p> | ||
107 | <pre style="background-color:#ffffff;"> | ||
108 | <code class="language-html" data-lang="html"><span style="color:#545052;">Authorization: Bearer aaaaaa.bbbbbb.ccccc | ||
109 | </span></code></pre> | ||
110 | <p>Gradecoin uses 3 fields for the JWTs;</p> | ||
111 | <pre style="background-color:#ffffff;"> | ||
112 | <code class="language-json" data-lang="json"><span style="color:#545052;">{ | ||
113 | "</span><span style="color:#009854;">tha</span><span style="color:#545052;">": "</span><span style="color:#009854;">Hash of the payload, check invididual references</span><span style="color:#545052;">", | ||
114 | "</span><span style="color:#009854;">iat</span><span style="color:#545052;">": "</span><span style="color:#009854;">Issued At, Unix Time</span><span style="color:#545052;">", | ||
115 | "</span><span style="color:#009854;">exp</span><span style="color:#545052;">": "</span><span style="color:#009854;">Expiration Time, epoch</span><span style="color:#545052;">" | ||
116 | } | ||
117 | </span></code></pre> | ||
118 | <ul> | ||
119 | <li><code>tha</code> is explained in <a href="https://gradecoin.xyz/block-docs/">blocks</a> and <a href="https://gradecoin.xyz/transaction-docs/">transactions</a> documentations.</li> | ||
120 | <li><code>iat</code> when the JWT was created in <a href="https://en.wikipedia.org/wiki/Unix_time">Unix Time</a> format</li> | ||
121 | <li><code>exp</code> when the JWT will expire & be rejected in <a href="https://en.wikipedia.org/wiki/Unix_time">Unix Time</a></li> | ||
122 | </ul> | ||
123 | <h1 id="algorithm">Algorithm</h1> | ||
124 | <p>We are using <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-3.1">RS256</a>, <code>RSASSA-PKCS1-v1_5 using SHA-256</code>. The JWTs you encode with your private RSA key will be decoded using the public key you have authenticated with. You can see how the process works <a href="https://jwt.io/">here</a>.</p> | ||
125 | <h1 id="references">References</h1> | ||
126 | <ul> | ||
127 | <li><a href="https://tools.ietf.org/html/rfc7519">RFC, the ultimate reference</a></li> | ||
128 | <li><a href="https://jwt.io/">JWT Debugger</a></li> | ||
129 | </ul> | ||
130 | |||
131 | |||
132 | </div> | ||
133 | |||
134 | |||
135 | |||
136 | </main> | ||
137 | |||
138 | |||
139 | <footer> | ||
140 | Built For ⁂ CENG489 ⁂ Introduction to Computer Security | ||
141 | </footer> | ||
142 | |||
143 | </body> | ||
144 | <script> | ||
145 | function highlightNav(heading) { | ||
146 | let pathname = location.pathname; | ||
147 | document.querySelectorAll(".toc a").forEach((item) => { | ||
148 | item.classList.remove("active"); | ||
149 | }); | ||
150 | document.querySelector(".toc a[href$='" + pathname + "#" + heading + "']").classList.add("active"); | ||
151 | } | ||
152 | |||
153 | let currentHeading = ""; | ||
154 | window.onscroll = function () { | ||
155 | let h = document.querySelectorAll("h1,h2,h3,h4,h5,h6"); | ||
156 | let elementArr = []; | ||
157 | |||
158 | h.forEach(item => { | ||
159 | if (item.id !== "") { | ||
160 | elementArr[item.id] = item.getBoundingClientRect().top; | ||
161 | } | ||
162 | }); | ||
163 | elementArr.sort(); | ||
164 | for (let key in elementArr) { | ||
165 | if (!elementArr.hasOwnProperty(key)) { | ||
166 | continue; | ||
167 | } | ||
168 | if (elementArr[key] > 0 && elementArr[key] < 300) { | ||
169 | if (currentHeading !== key) { | ||
170 | highlightNav(key); | ||
171 | currentHeading = key; | ||
172 | } | ||
173 | break; | ||
174 | } | ||
175 | } | ||
176 | } | ||
177 | </script> | ||
178 | |||
179 | </html> | ||
diff --git a/site/public/normalize.css b/site/public/normalize.css new file mode 100644 index 0000000..192eb9c --- /dev/null +++ b/site/public/normalize.css | |||
@@ -0,0 +1,349 @@ | |||
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ | ||
2 | |||
3 | /* Document | ||
4 | ========================================================================== */ | ||
5 | |||
6 | /** | ||
7 | * 1. Correct the line height in all browsers. | ||
8 | * 2. Prevent adjustments of font size after orientation changes in iOS. | ||
9 | */ | ||
10 | |||
11 | html { | ||
12 | line-height: 1.15; /* 1 */ | ||
13 | -webkit-text-size-adjust: 100%; /* 2 */ | ||
14 | } | ||
15 | |||
16 | /* Sections | ||
17 | ========================================================================== */ | ||
18 | |||
19 | /** | ||
20 | * Remove the margin in all browsers. | ||
21 | */ | ||
22 | |||
23 | body { | ||
24 | margin: 0; | ||
25 | } | ||
26 | |||
27 | /** | ||
28 | * Render the `main` element consistently in IE. | ||
29 | */ | ||
30 | |||
31 | main { | ||
32 | display: block; | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * Correct the font size and margin on `h1` elements within `section` and | ||
37 | * `article` contexts in Chrome, Firefox, and Safari. | ||
38 | */ | ||
39 | |||
40 | h1 { | ||
41 | font-size: 2em; | ||
42 | margin: 0.67em 0; | ||
43 | } | ||
44 | |||
45 | /* Grouping content | ||
46 | ========================================================================== */ | ||
47 | |||
48 | /** | ||
49 | * 1. Add the correct box sizing in Firefox. | ||
50 | * 2. Show the overflow in Edge and IE. | ||
51 | */ | ||
52 | |||
53 | hr { | ||
54 | box-sizing: content-box; /* 1 */ | ||
55 | height: 0; /* 1 */ | ||
56 | overflow: visible; /* 2 */ | ||
57 | } | ||
58 | |||
59 | /** | ||
60 | * 1. Correct the inheritance and scaling of font size in all browsers. | ||
61 | * 2. Correct the odd `em` font sizing in all browsers. | ||
62 | */ | ||
63 | |||
64 | pre { | ||
65 | font-family: monospace, monospace; /* 1 */ | ||
66 | font-size: 1em; /* 2 */ | ||
67 | } | ||
68 | |||
69 | /* Text-level semantics | ||
70 | ========================================================================== */ | ||
71 | |||
72 | /** | ||
73 | * Remove the gray background on active links in IE 10. | ||
74 | */ | ||
75 | |||
76 | a { | ||
77 | background-color: transparent; | ||
78 | } | ||
79 | |||
80 | /** | ||
81 | * 1. Remove the bottom border in Chrome 57- | ||
82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. | ||
83 | */ | ||
84 | |||
85 | abbr[title] { | ||
86 | border-bottom: none; /* 1 */ | ||
87 | text-decoration: underline; /* 2 */ | ||
88 | text-decoration: underline dotted; /* 2 */ | ||
89 | } | ||
90 | |||
91 | /** | ||
92 | * Add the correct font weight in Chrome, Edge, and Safari. | ||
93 | */ | ||
94 | |||
95 | b, | ||
96 | strong { | ||
97 | font-weight: bolder; | ||
98 | } | ||
99 | |||
100 | /** | ||
101 | * 1. Correct the inheritance and scaling of font size in all browsers. | ||
102 | * 2. Correct the odd `em` font sizing in all browsers. | ||
103 | */ | ||
104 | |||
105 | code, | ||
106 | kbd, | ||
107 | samp { | ||
108 | font-family: monospace, monospace; /* 1 */ | ||
109 | font-size: 1em; /* 2 */ | ||
110 | } | ||
111 | |||
112 | /** | ||
113 | * Add the correct font size in all browsers. | ||
114 | */ | ||
115 | |||
116 | small { | ||
117 | font-size: 80%; | ||
118 | } | ||
119 | |||
120 | /** | ||
121 | * Prevent `sub` and `sup` elements from affecting the line height in | ||
122 | * all browsers. | ||
123 | */ | ||
124 | |||
125 | sub, | ||
126 | sup { | ||
127 | font-size: 75%; | ||
128 | line-height: 0; | ||
129 | position: relative; | ||
130 | vertical-align: baseline; | ||
131 | } | ||
132 | |||
133 | sub { | ||
134 | bottom: -0.25em; | ||
135 | } | ||
136 | |||
137 | sup { | ||
138 | top: -0.5em; | ||
139 | } | ||
140 | |||
141 | /* Embedded content | ||
142 | ========================================================================== */ | ||
143 | |||
144 | /** | ||
145 | * Remove the border on images inside links in IE 10. | ||
146 | */ | ||
147 | |||
148 | img { | ||
149 | border-style: none; | ||
150 | } | ||
151 | |||
152 | /* Forms | ||
153 | ========================================================================== */ | ||
154 | |||
155 | /** | ||
156 | * 1. Change the font styles in all browsers. | ||
157 | * 2. Remove the margin in Firefox and Safari. | ||
158 | */ | ||
159 | |||
160 | button, | ||
161 | input, | ||
162 | optgroup, | ||
163 | select, | ||
164 | textarea { | ||
165 | font-family: inherit; /* 1 */ | ||
166 | font-size: 100%; /* 1 */ | ||
167 | line-height: 1.15; /* 1 */ | ||
168 | margin: 0; /* 2 */ | ||
169 | } | ||
170 | |||
171 | /** | ||
172 | * Show the overflow in IE. | ||
173 | * 1. Show the overflow in Edge. | ||
174 | */ | ||
175 | |||
176 | button, | ||
177 | input { /* 1 */ | ||
178 | overflow: visible; | ||
179 | } | ||
180 | |||
181 | /** | ||
182 | * Remove the inheritance of text transform in Edge, Firefox, and IE. | ||
183 | * 1. Remove the inheritance of text transform in Firefox. | ||
184 | */ | ||
185 | |||
186 | button, | ||
187 | select { /* 1 */ | ||
188 | text-transform: none; | ||
189 | } | ||
190 | |||
191 | /** | ||
192 | * Correct the inability to style clickable types in iOS and Safari. | ||
193 | */ | ||
194 | |||
195 | button, | ||
196 | [type="button"], | ||
197 | [type="reset"], | ||
198 | [type="submit"] { | ||
199 | -webkit-appearance: button; | ||
200 | } | ||
201 | |||
202 | /** | ||
203 | * Remove the inner border and padding in Firefox. | ||
204 | */ | ||
205 | |||
206 | button::-moz-focus-inner, | ||
207 | [type="button"]::-moz-focus-inner, | ||
208 | [type="reset"]::-moz-focus-inner, | ||
209 | [type="submit"]::-moz-focus-inner { | ||
210 | border-style: none; | ||
211 | padding: 0; | ||
212 | } | ||
213 | |||
214 | /** | ||
215 | * Restore the focus styles unset by the previous rule. | ||
216 | */ | ||
217 | |||
218 | button:-moz-focusring, | ||
219 | [type="button"]:-moz-focusring, | ||
220 | [type="reset"]:-moz-focusring, | ||
221 | [type="submit"]:-moz-focusring { | ||
222 | outline: 1px dotted ButtonText; | ||
223 | } | ||
224 | |||
225 | /** | ||
226 | * Correct the padding in Firefox. | ||
227 | */ | ||
228 | |||
229 | fieldset { | ||
230 | padding: 0.35em 0.75em 0.625em; | ||
231 | } | ||
232 | |||
233 | /** | ||
234 | * 1. Correct the text wrapping in Edge and IE. | ||
235 | * 2. Correct the color inheritance from `fieldset` elements in IE. | ||
236 | * 3. Remove the padding so developers are not caught out when they zero out | ||
237 | * `fieldset` elements in all browsers. | ||
238 | */ | ||
239 | |||
240 | legend { | ||
241 | box-sizing: border-box; /* 1 */ | ||
242 | color: inherit; /* 2 */ | ||
243 | display: table; /* 1 */ | ||
244 | max-width: 100%; /* 1 */ | ||
245 | padding: 0; /* 3 */ | ||
246 | white-space: normal; /* 1 */ | ||
247 | } | ||
248 | |||
249 | /** | ||
250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. | ||
251 | */ | ||
252 | |||
253 | progress { | ||
254 | vertical-align: baseline; | ||
255 | } | ||
256 | |||
257 | /** | ||
258 | * Remove the default vertical scrollbar in IE 10+. | ||
259 | */ | ||
260 | |||
261 | textarea { | ||
262 | overflow: auto; | ||
263 | } | ||
264 | |||
265 | /** | ||
266 | * 1. Add the correct box sizing in IE 10. | ||
267 | * 2. Remove the padding in IE 10. | ||
268 | */ | ||
269 | |||
270 | [type="checkbox"], | ||
271 | [type="radio"] { | ||
272 | box-sizing: border-box; /* 1 */ | ||
273 | padding: 0; /* 2 */ | ||
274 | } | ||
275 | |||
276 | /** | ||
277 | * Correct the cursor style of increment and decrement buttons in Chrome. | ||
278 | */ | ||
279 | |||
280 | [type="number"]::-webkit-inner-spin-button, | ||
281 | [type="number"]::-webkit-outer-spin-button { | ||
282 | height: auto; | ||
283 | } | ||
284 | |||
285 | /** | ||
286 | * 1. Correct the odd appearance in Chrome and Safari. | ||
287 | * 2. Correct the outline style in Safari. | ||
288 | */ | ||
289 | |||
290 | [type="search"] { | ||
291 | -webkit-appearance: textfield; /* 1 */ | ||
292 | outline-offset: -2px; /* 2 */ | ||
293 | } | ||
294 | |||
295 | /** | ||
296 | * Remove the inner padding in Chrome and Safari on macOS. | ||
297 | */ | ||
298 | |||
299 | [type="search"]::-webkit-search-decoration { | ||
300 | -webkit-appearance: none; | ||
301 | } | ||
302 | |||
303 | /** | ||
304 | * 1. Correct the inability to style clickable types in iOS and Safari. | ||
305 | * 2. Change font properties to `inherit` in Safari. | ||
306 | */ | ||
307 | |||
308 | ::-webkit-file-upload-button { | ||
309 | -webkit-appearance: button; /* 1 */ | ||
310 | font: inherit; /* 2 */ | ||
311 | } | ||
312 | |||
313 | /* Interactive | ||
314 | ========================================================================== */ | ||
315 | |||
316 | /* | ||
317 | * Add the correct display in Edge, IE 10+, and Firefox. | ||
318 | */ | ||
319 | |||
320 | details { | ||
321 | display: block; | ||
322 | } | ||
323 | |||
324 | /* | ||
325 | * Add the correct display in all browsers. | ||
326 | */ | ||
327 | |||
328 | summary { | ||
329 | display: list-item; | ||
330 | } | ||
331 | |||
332 | /* Misc | ||
333 | ========================================================================== */ | ||
334 | |||
335 | /** | ||
336 | * Add the correct display in IE 10+. | ||
337 | */ | ||
338 | |||
339 | template { | ||
340 | display: none; | ||
341 | } | ||
342 | |||
343 | /** | ||
344 | * Add the correct display in IE 10. | ||
345 | */ | ||
346 | |||
347 | [hidden] { | ||
348 | display: none; | ||
349 | } | ||
diff --git a/site/public/register-docs/index.html b/site/public/register-docs/index.html new file mode 100644 index 0000000..fdc5237 --- /dev/null +++ b/site/public/register-docs/index.html | |||
@@ -0,0 +1,168 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html lang="en"> | ||
3 | |||
4 | <head> | ||
5 | <meta charset="UTF-8"> | ||
6 | <title>Register | Gradecoin </title> | ||
7 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||
8 | <style> | ||
9 | :root { | ||
10 | /* Primary theme color */ | ||
11 | --primary-color: #F8D12F; | ||
12 | /* Primary theme text color */ | ||
13 | --primary-text-color: #1E2329; | ||
14 | /* Primary theme link color */ | ||
15 | --primary-link-color: #2F57F7; | ||
16 | /* Secondary color: the background body color */ | ||
17 | --secondary-color: #FAFAFA; | ||
18 | --secondary-text-color: #303030; | ||
19 | /* Highlight text color of table of content */ | ||
20 | --toc-highlight-text-color: #d46e13; | ||
21 | } | ||
22 | </style> | ||
23 | |||
24 | <link href="https://fonts.googleapis.com/css?family=Alfa+Slab+One&display=swap" rel="stylesheet"> | ||
25 | <link href="https://fonts.googleapis.com/css?family=Fira+Sans:400,500,600&display=swap" rel="stylesheet"> | ||
26 | <link rel="stylesheet" href="/normalize.css"> | ||
27 | <link rel="stylesheet" href="https://gradecoin.xyz/juice.css"> | ||
28 | |||
29 | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" /> | ||
30 | |||
31 | </head> | ||
32 | |||
33 | <body> | ||
34 | |||
35 | <header class="box-shadow"> | ||
36 | |||
37 | |||
38 | <a href="https://gradecoin.xyz/"> | ||
39 | <div class="logo"> | ||
40 | <img src="https://gradecoin.xyz/gradecoin.png" alt="logo"> | ||
41 | Gradecoin | ||
42 | </div> | ||
43 | </a> | ||
44 | |||
45 | <nav> | ||
46 | |||
47 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/register-docs/">Register</a> | ||
48 | |||
49 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/jwt/">JWT</a> | ||
50 | |||
51 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/transaction-docs/">Transactions</a> | ||
52 | |||
53 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/block-docs/">Blocks</a> | ||
54 | |||
55 | |||
56 | |||
57 | <a class="nav-item subtitle-text" href="https://github.com/zhuowei/nft_ptr#why">why?</a> | ||
58 | |||
59 | |||
60 | </nav> | ||
61 | |||
62 | </header> | ||
63 | |||
64 | |||
65 | <main> | ||
66 | |||
67 | |||
68 | |||
69 | |||
70 | |||
71 | <div class="toc"> | ||
72 | <div class="toc-sticky"> | ||
73 | |||
74 | <div class="toc-item"> | ||
75 | <a class="subtext" href="https://gradecoin.xyz/register-docs/#authentication-process">Authentication Process</a> | ||
76 | </div> | ||
77 | |||
78 | |||
79 | </div> | ||
80 | </div> | ||
81 | |||
82 | |||
83 | |||
84 | <div class="content text"> | ||
85 | |||
86 | <div class="heading-text">Register Documentation</div> | ||
87 | <p>POST request to /register endpoint</p> | ||
88 | <p>Lets a user to authenticate themselves to the system. | ||
89 | Only people who are enrolled to the class can open Gradecoin accounts. | ||
90 | This is enforced with your Student ID and a one time password you will receive.</p> | ||
91 | <h1 id="authentication-process">Authentication Process</h1> | ||
92 | <ul> | ||
93 | <li>Gradecoin's Public Key (<code>gradecoin_public_key</code>) is listed on our Moodle page.</li> | ||
94 | <li>You pick a short temporary key (<code>k_temp</code>)</li> | ||
95 | <li>Create a JSON object (<code>auth_plaintext</code>) with your <code>metu_id</code> and <code>public key</code> in base64 (PEM) format (<code>S_PK</code>) <a href="https://tls.mbed.org/kb/cryptography/asn1-key-structures-in-der-and-pem">reference</a></li> | ||
96 | </ul> | ||
97 | <pre style="background-color:#ffffff;"> | ||
98 | <code class="language-json" data-lang="json"><span style="color:#545052;">{ | ||
99 | "</span><span style="color:#009854;">student_id</span><span style="color:#545052;">": "</span><span style="color:#009854;">e12345</span><span style="color:#545052;">", | ||
100 | "</span><span style="color:#009854;">passwd</span><span style="color:#545052;">": "</span><span style="color:#009854;">15 char secret</span><span style="color:#545052;">", | ||
101 | "</span><span style="color:#009854;">public_key</span><span style="color:#545052;">": "</span><span style="color:#009854;">---BEGIN PUBLIC KEY...</span><span style="color:#545052;">" | ||
102 | } | ||
103 | </span></code></pre> | ||
104 | <ul> | ||
105 | <li>Pick a random IV.</li> | ||
106 | <li>Encrypt the serialized string of <code>auth_plaintext</code> with 128 bit block AES in CBC mode with Pkcs7 padding using the temporary key (<code>k_temp</code>), the result is <code>auth_ciphertext</code>. Encode this with base64.</li> | ||
107 | <li>The temporary key you have picked <code>k_temp</code> is encrypted using RSA with OAEP padding scheme | ||
108 | using SHA-256 with <code>gradecoin_public_key</code>, giving us <code>key_ciphertext</code>. Encode this with base 64.</li> | ||
109 | <li>The payload JSON object (<code>auth_request</code>) can be serialized now:</li> | ||
110 | </ul> | ||
111 | <pre style="background-color:#ffffff;"> | ||
112 | <code class="language-json" data-lang="json"><span style="color:#545052;">{ | ||
113 | "</span><span style="color:#009854;">c</span><span style="color:#545052;">": "</span><span style="color:#009854;">auth_ciphertext</span><span style="color:#545052;">", | ||
114 | "</span><span style="color:#009854;">iv</span><span style="color:#545052;">": "</span><span style="color:#009854;">hexadecimal</span><span style="color:#545052;">", | ||
115 | "</span><span style="color:#009854;">key</span><span style="color:#545052;">": "</span><span style="color:#009854;">key_ciphertext</span><span style="color:#545052;">" | ||
116 | } | ||
117 | </span></code></pre> | ||
118 | <p>If your authentication process was valid, you will be given access and your public key fingerprint that is your address.</p> | ||
119 | |||
120 | |||
121 | </div> | ||
122 | |||
123 | |||
124 | |||
125 | </main> | ||
126 | |||
127 | |||
128 | <footer> | ||
129 | Built For ⁂ CENG489 ⁂ Introduction to Computer Security | ||
130 | </footer> | ||
131 | |||
132 | </body> | ||
133 | <script> | ||
134 | function highlightNav(heading) { | ||
135 | let pathname = location.pathname; | ||
136 | document.querySelectorAll(".toc a").forEach((item) => { | ||
137 | item.classList.remove("active"); | ||
138 | }); | ||
139 | document.querySelector(".toc a[href$='" + pathname + "#" + heading + "']").classList.add("active"); | ||
140 | } | ||
141 | |||
142 | let currentHeading = ""; | ||
143 | window.onscroll = function () { | ||
144 | let h = document.querySelectorAll("h1,h2,h3,h4,h5,h6"); | ||
145 | let elementArr = []; | ||
146 | |||
147 | h.forEach(item => { | ||
148 | if (item.id !== "") { | ||
149 | elementArr[item.id] = item.getBoundingClientRect().top; | ||
150 | } | ||
151 | }); | ||
152 | elementArr.sort(); | ||
153 | for (let key in elementArr) { | ||
154 | if (!elementArr.hasOwnProperty(key)) { | ||
155 | continue; | ||
156 | } | ||
157 | if (elementArr[key] > 0 && elementArr[key] < 300) { | ||
158 | if (currentHeading !== key) { | ||
159 | highlightNav(key); | ||
160 | currentHeading = key; | ||
161 | } | ||
162 | break; | ||
163 | } | ||
164 | } | ||
165 | } | ||
166 | </script> | ||
167 | |||
168 | </html> | ||
diff --git a/site/public/robots.txt b/site/public/robots.txt new file mode 100644 index 0000000..7b7f367 --- /dev/null +++ b/site/public/robots.txt | |||
@@ -0,0 +1,3 @@ | |||
1 | User-agent: * | ||
2 | Allow: / | ||
3 | Sitemap: https://gradecoin.xyz/sitemap.xml | ||
diff --git a/site/public/search_index.en.js b/site/public/search_index.en.js new file mode 100644 index 0000000..a5eeec0 --- /dev/null +++ b/site/public/search_index.en.js | |||
@@ -0,0 +1 @@ | |||
window.searchIndex = {"fields":["title","body"],"pipeline":["trimmer","stopWordFilter","stemmer"],"ref":"id","version":"0.9.5","index":{"body":{"root":{"docs":{},"df":0,"0":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1},"1":{"docs":{},"df":0,"2":{"docs":{},"df":0,"8":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}},"5":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1},"6":{"docs":{"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":1}},"2":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1,"0":{"docs":{},"df":0,"4":{"docs":{},"df":0,"8":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1}},"x":{"docs":{},"df":0,"1":{"docs":{},"df":0,"0":{"docs":{},"df":0,"^":{"docs":{},"df":0,"3":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1}}}}}},"4":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":2},"5":{"docs":{},"df":0,"6":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/block-docs/":{"tf":1.0},"https://gradecoin.xyz/jwt/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":4}}},"3":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":2,"1":{"docs":{},"df":0,"4":{"docs":{},"df":0,"1":{"docs":{},"df":0,"5":{"docs":{},"df":0,"9":{"docs":{},"df":0,"2":{"docs":{},"df":0,"6":{"docs":{},"df":0,"5":{"docs":{},"df":0,"3":{"docs":{},"df":0,"5":{"docs":{},"df":0,"8":{"docs":{},"df":0,"9":{"docs":{},"df":0,"7":{"docs":{},"df":0,"9":{"docs":{},"df":0,"3":{"docs":{},"df":0,"2":{"docs":{},"df":0,"3":{"docs":{},"df":0,"8":{"docs":{},"df":0,"4":{"docs":{},"df":0,"6":{"docs":{},"df":0,"2":{"docs":{},"df":0,"6":{"docs":{},"df":0,"4":{"docs":{},"df":0,"3":{"docs":{},"df":0,"3":{"docs":{},"df":0,"8":{"docs":{},"df":0,"3":{"docs":{},"df":0,"2":{"docs":{},"df":0,"7":{"docs":{},"df":0,"9":{"docs":{},"df":0,"5":{"docs":{},"df":0,"0":{"docs":{},"df":0,"2":{"docs":{},"df":0,"8":{"docs":{},"df":0,"8":{"docs":{},"df":0,"4":{"docs":{},"df":0,"1":{"docs":{},"df":0,"9":{"docs":{},"df":0,"7":{"docs":{},"df":0,"1":{"docs":{},"df":0,"6":{"docs":{},"df":0,"9":{"docs":{},"df":0,"3":{"docs":{},"df":0,"9":{"docs":{},"df":0,"9":{"docs":{},"df":0,"3":{"docs":{},"df":0,"7":{"docs":{},"df":0,"5":{"docs":{},"df":0,"1":{"docs":{},"df":0,"0":{"docs":{},"df":0,"5":{"docs":{},"df":0,"8":{"docs":{},"df":0,"2":{"docs":{},"df":0,"0":{"docs":{},"df":0,"9":{"docs":{},"df":0,"7":{"docs":{},"df":0,"4":{"docs":{},"df":0,"9":{"docs":{},"df":0,"4":{"docs":{},"df":0,"4":{"docs":{},"df":0,"5":{"docs":{},"df":0,"9":{"docs":{},"df":0,"2":{"docs":{},"df":0,"3":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}},"2":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1}},"4":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1},"6":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/block-docs/":{"tf":1.4142135623730951}},"df":2,"4":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2}},"7":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1},"8":{"docs":{},"df":0,"6":{"docs":{},"df":0,"0":{"docs":{},"df":0,"1":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.4142135623730951},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":2}}}},"a":{"docs":{},"df":0,"a":{"docs":{},"df":0,"a":{"docs":{},"df":0,"a":{"docs":{},"df":0,"a":{"docs":{},"df":0,"a":{"docs":{},"df":0,".":{"docs":{},"df":0,"b":{"docs":{},"df":0,"b":{"docs":{},"df":0,"b":{"docs":{},"df":0,"b":{"docs":{},"df":0,"b":{"docs":{},"df":0,"b":{"docs":{},"df":0,".":{"docs":{},"df":0,"c":{"docs":{},"df":0,"c":{"docs":{},"df":0,"c":{"docs":{},"df":0,"c":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}}}}}}}}}}}}}}}}},"b":{"docs":{},"df":0,"s":{"docs":{},"df":0,"o":{"docs":{},"df":0,"l":{"docs":{},"df":0,"u":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"u":{"docs":{},"df":0,"s":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"c":{"docs":{},"df":0,"c":{"docs":{},"df":0,"e":{"docs":{},"df":0,"p":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.7320508075688772}},"df":1}},"s":{"docs":{},"df":0,"s":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}},"o":{"docs":{},"df":0,"u":{"docs":{},"df":0,"n":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2}}}}},"t":{"docs":{},"df":0,"u":{"docs":{},"df":0,"a":{"docs":{},"df":0,"l":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"d":{"docs":{},"df":0,"d":{"docs":{},"df":0,"r":{"docs":{},"df":0,"e":{"docs":{},"df":0,"s":{"docs":{},"df":0,"s":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}}}}},"e":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1},"l":{"docs":{},"df":0,"g":{"docs":{},"df":0,"o":{"docs":{},"df":0,"r":{"docs":{},"df":0,"i":{"docs":{},"df":0,"t":{"docs":{},"df":0,"h":{"docs":{},"df":0,"m":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0},"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":2}}}}}}},"l":{"docs":{},"df":0,"o":{"docs":{},"df":0,"w":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":2}}},"o":{"docs":{},"df":0,"n":{"docs":{},"df":0,"g":{"docs":{},"df":0,"s":{"docs":{},"df":0,"i":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}}}}}},"m":{"docs":{},"df":0,"o":{"docs":{},"df":0,"u":{"docs":{},"df":0,"n":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951},"https://gradecoin.xyz/transaction-docs/":{"tf":1.4142135623730951}},"df":2}}}},"p":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}},"p":{"docs":{},"df":0,"i":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1},"p":{"docs":{},"df":0,"e":{"docs":{},"df":0,"a":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"l":{"docs":{},"df":0,"i":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"r":{"docs":{},"df":0,"c":{"docs":{},"df":0,"h":{"docs":{},"df":0,"i":{"docs":{},"df":0,"t":{"docs":{},"df":0,"e":{"docs":{},"df":0,"c":{"docs":{},"df":0,"t":{"docs":{},"df":0,"u":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}}}}},"o":{"docs":{},"df":0,"u":{"docs":{},"df":0,"n":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":2}}}},"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"y":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1}}}},"s":{"docs":{},"df":0,"k":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1},"s":{"docs":{},"df":0,"o":{"docs":{},"df":0,"r":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"u":{"docs":{},"df":0,"t":{"docs":{},"df":0,"h":{"docs":{},"df":0,"_":{"docs":{},"df":0,"c":{"docs":{},"df":0,"i":{"docs":{},"df":0,"p":{"docs":{},"df":0,"h":{"docs":{},"df":0,"e":{"docs":{},"df":0,"r":{"docs":{},"df":0,"t":{"docs":{},"df":0,"e":{"docs":{},"df":0,"x":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.4142135623730951}},"df":1}}}}}}}}}},"p":{"docs":{},"df":0,"l":{"docs":{},"df":0,"a":{"docs":{},"df":0,"i":{"docs":{},"df":0,"n":{"docs":{},"df":0,"t":{"docs":{},"df":0,"e":{"docs":{},"df":0,"x":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.4142135623730951}},"df":1}}}}}}}}},"r":{"docs":{},"df":0,"e":{"docs":{},"df":0,"q":{"docs":{},"df":0,"u":{"docs":{},"df":0,"e":{"docs":{},"df":0,"s":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}}}}}}},"e":{"docs":{},"df":0,"n":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951},"https://gradecoin.xyz/jwt/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.7320508075688772}},"df":3}}},"o":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/":{"tf":2.23606797749979},"https://gradecoin.xyz/block-docs/":{"tf":1.0},"https://gradecoin.xyz/jwt/":{"tf":1.7320508075688772},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":4}}}}},"v":{"docs":{},"df":0,"e":{"docs":{},"df":0,"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"g":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}},"b":{"docs":{},"df":0,"a":{"docs":{},"df":0,"l":{"docs":{},"df":0,"a":{"docs":{},"df":0,"n":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"n":{"docs":{},"df":0,"k":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"s":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1,"6":{"docs":{},"df":0,"4":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.4142135623730951}},"df":1}}},"h":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"e":{"docs":{},"df":0,"a":{"docs":{},"df":0,"r":{"docs":{},"df":0,"e":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":2}}}},"f":{"docs":{},"df":0,"o":{"docs":{},"df":0,"r":{"docs":{},"df":0,"e":{"docs":{},"df":0,"h":{"docs":{},"df":0,"a":{"docs":{},"df":0,"n":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}}}},"g":{"docs":{},"df":0,"i":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}},"t":{"docs":{},"df":0,"w":{"docs":{},"df":0,"e":{"docs":{},"df":0,"e":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":1}}}}}},"i":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":2.0},"https://gradecoin.xyz/block-docs/":{"tf":1.4142135623730951},"https://gradecoin.xyz/register-docs/":{"tf":1.0},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":4,"c":{"docs":{},"df":0,"o":{"docs":{},"df":0,"i":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}},"l":{"docs":{},"df":0,"a":{"docs":{},"df":0,"k":{"docs":{},"df":0,"e":{"docs":{},"df":0,"2":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":2}}}},"o":{"docs":{},"df":0,"c":{"docs":{},"df":0,"k":{"docs":{"https://gradecoin.xyz/":{"tf":3.3166247903554},"https://gradecoin.xyz/block-docs/":{"tf":2.449489742783178},"https://gradecoin.xyz/jwt/":{"tf":1.4142135623730951},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":4,"c":{"docs":{},"df":0,"h":{"docs":{},"df":0,"a":{"docs":{},"df":0,"i":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1}}}}}}}}},"o":{"docs":{},"df":0,"d":{"docs":{},"df":0,"i":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}},"r":{"docs":{},"df":0,"o":{"docs":{},"df":0,"w":{"docs":{},"df":0,"s":{"docs":{},"df":0,"e":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"u":{"docs":{},"df":0,"t":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"u":{"docs":{},"df":0,"g":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1},"i":{"docs":{},"df":0,"l":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"y":{"docs":{},"df":0,"t":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1}}}},"c":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2,"a":{"docs":{},"df":0,"l":{"docs":{},"df":0,"l":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"b":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}},"h":{"docs":{},"df":0,"a":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1,"a":{"docs":{},"df":0,"c":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1}}}}},"e":{"docs":{},"df":0,"c":{"docs":{},"df":0,"k":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}}},"l":{"docs":{},"df":0,"a":{"docs":{},"df":0,"i":{"docs":{},"df":0,"m":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}},"s":{"docs":{},"df":0,"s":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2}}},"i":{"docs":{},"df":0,"e":{"docs":{},"df":0,"n":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"o":{"docs":{},"df":0,"d":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1}},"i":{"docs":{},"df":0,"n":{"docs":{},"df":0,"b":{"docs":{},"df":0,"a":{"docs":{},"df":0,"s":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1}}}}},"l":{"docs":{},"df":0,"l":{"docs":{},"df":0,"a":{"docs":{},"df":0,"b":{"docs":{},"df":0,"o":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}},"m":{"docs":{},"df":0,"m":{"docs":{},"df":0,"a":{"docs":{},"df":0,"n":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"i":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1}}},"p":{"docs":{},"df":0,"e":{"docs":{},"df":0,"n":{"docs":{},"df":0,"s":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"l":{"docs":{},"df":0,"i":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1}}}}},"r":{"docs":{},"df":0,"r":{"docs":{},"df":0,"e":{"docs":{},"df":0,"s":{"docs":{},"df":0,"p":{"docs":{},"df":0,"o":{"docs":{},"df":0,"n":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}}}}},"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"c":{"docs":{},"df":0,"k":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"f":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"e":{"docs":{},"df":0,"a":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/jwt/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":3}}},"y":{"docs":{},"df":0,"p":{"docs":{},"df":0,"t":{"docs":{},"df":0,"o":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1,"g":{"docs":{},"df":0,"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"p":{"docs":{},"df":0,"h":{"docs":{},"df":0,"i":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1}}}}}}}}}}},"u":{"docs":{},"df":0,"r":{"docs":{},"df":0,"l":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1},"r":{"docs":{},"df":0,"e":{"docs":{},"df":0,"n":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1},"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":2}}}}}}},"d":{"docs":{},"df":0,"a":{"docs":{},"df":0,"t":{"docs":{},"df":0,"a":{"docs":{},"df":0,"b":{"docs":{},"df":0,"a":{"docs":{},"df":0,"s":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}},"e":{"docs":{},"df":0,"b":{"docs":{},"df":0,"u":{"docs":{},"df":0,"g":{"docs":{},"df":0,"g":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":2}}}},"c":{"docs":{},"df":0,"o":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}},"l":{"docs":{},"df":0,"e":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"o":{"docs":{},"df":0,"c":{"docs":{},"df":0,"u":{"docs":{},"df":0,"m":{"docs":{},"df":0,"e":{"docs":{},"df":0,"n":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}}}}},"n":{"docs":{},"df":0,"'":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.7320508075688772}},"df":1}}},"w":{"docs":{},"df":0,"n":{"docs":{},"df":0,"l":{"docs":{},"df":0,"o":{"docs":{},"df":0,"a":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}}},"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"w":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"u":{"docs":{},"df":0,"r":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1}}}},"e":{"docs":{},"df":0,"1":{"docs":{},"df":0,"2":{"docs":{},"df":0,"3":{"docs":{},"df":0,"4":{"docs":{},"df":0,"5":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}}}},"f":{"docs":{},"df":0,"f":{"docs":{},"df":0,"o":{"docs":{},"df":0,"r":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"n":{"docs":{},"df":0,"c":{"docs":{},"df":0,"o":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.4142135623730951}},"df":2},"u":{"docs":{},"df":0,"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"g":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"r":{"docs":{},"df":0,"y":{"docs":{},"df":0,"p":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951},"https://gradecoin.xyz/register-docs/":{"tf":1.4142135623730951}},"df":2}}}}},"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1,"p":{"docs":{},"df":0,"o":{"docs":{},"df":0,"i":{"docs":{},"df":0,"n":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/block-docs/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":4}}}}}},"f":{"docs":{},"df":0,"o":{"docs":{},"df":0,"r":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}}},"o":{"docs":{},"df":0,"u":{"docs":{},"df":0,"g":{"docs":{},"df":0,"h":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"r":{"docs":{},"df":0,"o":{"docs":{},"df":0,"l":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2}}}},"p":{"docs":{},"df":0,"o":{"docs":{},"df":0,"c":{"docs":{},"df":0,"h":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}}},"v":{"docs":{},"df":0,"e":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"x":{"docs":{},"df":0,"i":{"docs":{},"df":0,"s":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"p":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.4142135623730951}},"df":1,"e":{"docs":{},"df":0,"c":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1}}},"i":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.4142135623730951}},"df":1}},"l":{"docs":{},"df":0,"a":{"docs":{},"df":0,"i":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}}}}}},"f":{"docs":{},"df":0,"a":{"docs":{},"df":0,"u":{"docs":{},"df":0,"x":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"v":{"docs":{},"df":0,"o":{"docs":{},"df":0,"u":{"docs":{},"df":0,"r":{"docs":{},"df":0,"i":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}}},"e":{"docs":{},"df":0,"e":{"docs":{},"df":0,"l":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"t":{"docs":{},"df":0,"c":{"docs":{},"df":0,"h":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1}}}},"i":{"docs":{},"df":0,"e":{"docs":{},"df":0,"l":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/block-docs/":{"tf":1.0},"https://gradecoin.xyz/jwt/":{"tf":1.0},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":4}}},"n":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1},"g":{"docs":{},"df":0,"e":{"docs":{},"df":0,"r":{"docs":{},"df":0,"p":{"docs":{},"df":0,"r":{"docs":{},"df":0,"i":{"docs":{},"df":0,"n":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0},"https://gradecoin.xyz/transaction-docs/":{"tf":1.7320508075688772}},"df":3}}}}}}}}},"r":{"docs":{},"df":0,"s":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"t":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}},"o":{"docs":{},"df":0,"r":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1},"m":{"docs":{},"df":0,"a":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2}}}},"u":{"docs":{},"df":0,"n":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"r":{"docs":{},"df":0,"e":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"i":{"docs":{},"df":0,"e":{"docs":{},"df":0,"n":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}},"g":{"docs":{},"df":0,"e":{"docs":{},"df":0,"n":{"docs":{},"df":0,"e":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"i":{"docs":{},"df":0,"v":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1,"n":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}}},"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"b":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1},"d":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1,"c":{"docs":{},"df":0,"o":{"docs":{},"df":0,"i":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/":{"tf":3.605551275463989},"https://gradecoin.xyz/jwt/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":4,"'":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2},"_":{"docs":{},"df":0,"p":{"docs":{},"df":0,"u":{"docs":{},"df":0,"b":{"docs":{},"df":0,"l":{"docs":{},"df":0,"i":{"docs":{},"df":0,"c":{"docs":{},"df":0,"_":{"docs":{},"df":0,"k":{"docs":{},"df":0,"e":{"docs":{},"df":0,"y":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.4142135623730951}},"df":1}}}}}}}}}}}}}}}}}}}},"h":{"docs":{},"df":0,"a":{"docs":{},"df":0,"c":{"docs":{},"df":0,"k":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"n":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1}},"p":{"docs":{},"df":0,"p":{"docs":{},"df":0,"e":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"r":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"s":{"docs":{},"df":0,"h":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951},"https://gradecoin.xyz/block-docs/":{"tf":2.0},"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":3,"/":{"docs":{},"df":0,"s":{"docs":{},"df":0,"e":{"docs":{},"df":0,"c":{"docs":{},"df":0,"o":{"docs":{},"df":0,"n":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1}}}}}}}}}},"e":{"docs":{},"df":0,"a":{"docs":{},"df":0,"d":{"docs":{},"df":0,"e":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951},"https://gradecoin.xyz/jwt/":{"tf":1.4142135623730951}},"df":2}}}},"l":{"docs":{},"df":0,"p":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"r":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}},"x":{"docs":{},"df":0,"a":{"docs":{},"df":0,"d":{"docs":{},"df":0,"e":{"docs":{},"df":0,"c":{"docs":{},"df":0,"i":{"docs":{},"df":0,"m":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2}}}}}}}},"o":{"docs":{},"df":0,"l":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"t":{"docs":{},"df":0,"t":{"docs":{},"df":0,"p":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.4142135623730951},"https://gradecoin.xyz/jwt/":{"tf":1.0},"https://gradecoin.xyz/transaction-docs/":{"tf":1.4142135623730951}},"df":3}}}},"i":{"docs":{},"df":0,"'":{"docs":{},"df":0,"v":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"a":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.4142135623730951}},"df":1}},"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2},"m":{"docs":{},"df":0,"p":{"docs":{},"df":0,"l":{"docs":{},"df":0,"e":{"docs":{},"df":0,"m":{"docs":{},"df":0,"e":{"docs":{},"df":0,"n":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1}}}}}}}},"n":{"docs":{},"df":0,"c":{"docs":{},"df":0,"r":{"docs":{},"df":0,"e":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"s":{"docs":{},"df":0,"p":{"docs":{},"df":0,"i":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"t":{"docs":{},"df":0,"e":{"docs":{},"df":0,"g":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":2},"n":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1},"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"r":{"docs":{},"df":0,"f":{"docs":{},"df":0,"a":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}},"v":{"docs":{},"df":0,"i":{"docs":{},"df":0,"d":{"docs":{},"df":0,"i":{"docs":{},"df":0,"d":{"docs":{},"df":0,"u":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}}}}}},"s":{"docs":{},"df":0,"o":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.4142135623730951},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":2},"s":{"docs":{},"df":0,"u":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}},"t":{"docs":{},"df":0,"'":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1}},"v":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.4142135623730951}},"df":1}},"j":{"docs":{},"df":0,"s":{"docs":{},"df":0,"o":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/jwt/":{"tf":1.4142135623730951},"https://gradecoin.xyz/register-docs/":{"tf":1.4142135623730951}},"df":3}}},"w":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951},"https://gradecoin.xyz/block-docs/":{"tf":1.0},"https://gradecoin.xyz/jwt/":{"tf":2.6457513110645907},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":4,"'":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1},".":{"docs":{},"df":0,"t":{"docs":{},"df":0,"o":{"docs":{},"df":0,"k":{"docs":{},"df":0,"e":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}}}}},"k":{"docs":{},"df":0,"_":{"docs":{},"df":0,"t":{"docs":{},"df":0,"e":{"docs":{},"df":0,"m":{"docs":{},"df":0,"p":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.7320508075688772}},"df":1}}}}},"e":{"docs":{},"df":0,"y":{"docs":{"https://gradecoin.xyz/":{"tf":3.0},"https://gradecoin.xyz/jwt/":{"tf":1.4142135623730951},"https://gradecoin.xyz/register-docs/":{"tf":2.8284271247461903}},"df":3,"_":{"docs":{},"df":0,"c":{"docs":{},"df":0,"i":{"docs":{},"df":0,"p":{"docs":{},"df":0,"h":{"docs":{},"df":0,"e":{"docs":{},"df":0,"r":{"docs":{},"df":0,"t":{"docs":{},"df":0,"e":{"docs":{},"df":0,"x":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.4142135623730951}},"df":1}}}}}}}}}}},"p":{"docs":{},"df":0,"a":{"docs":{},"df":0,"i":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1}}}},"s":{"docs":{},"df":0,"p":{"docs":{},"df":0,"a":{"docs":{},"df":0,"i":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}}},"n":{"docs":{},"df":0,"o":{"docs":{},"df":0,"w":{"docs":{"https://gradecoin.xyz/":{"tf":2.0}},"df":1}}}},"l":{"docs":{},"df":0,"a":{"docs":{},"df":0,"n":{"docs":{},"df":0,"g":{"docs":{},"df":0,"u":{"docs":{},"df":0,"a":{"docs":{},"df":0,"g":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"s":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"t":{"docs":{},"df":0,"e":{"docs":{},"df":0,"s":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1}}}}},"e":{"docs":{},"df":0,"d":{"docs":{},"df":0,"g":{"docs":{},"df":0,"e":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1}}}},"f":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1}},"t":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}},"i":{"docs":{},"df":0,"s":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":3}},"t":{"docs":{},"df":0,"t":{"docs":{},"df":0,"l":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"t":{"docs":{},"df":0,";":{"docs":{},"df":0,"d":{"docs":{},"df":0,"a":{"docs":{},"df":0,"t":{"docs":{},"df":0,"e":{"docs":{},"df":0,"&":{"docs":{},"df":0,"g":{"docs":{},"df":0,"t":{"docs":{},"df":0,";":{"docs":{},"df":0,"t":{"docs":{},"df":0,"&":{"docs":{},"df":0,"l":{"docs":{},"df":0,"t":{"docs":{},"df":0,";":{"docs":{},"df":0,"t":{"docs":{},"df":0,"i":{"docs":{},"df":0,"m":{"docs":{},"df":0,"e":{"docs":{},"df":0,"&":{"docs":{},"df":0,"g":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":2}}}}}}}}}}}}}}}}}}}}}}},"m":{"docs":{},"df":0,"a":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1,"h":{"docs":{},"df":0,"i":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1}}}},"d":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1}}},"e":{"docs":{},"df":0,"c":{"docs":{},"df":0,"h":{"docs":{},"df":0,"a":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"t":{"docs":{},"df":0,"u":{"docs":{},"df":0,"_":{"docs":{},"df":0,"i":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}}}}},"i":{"docs":{},"df":0,"n":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951},"https://gradecoin.xyz/block-docs/":{"tf":1.7320508075688772}},"df":2},"u":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":2}}},"s":{"docs":{},"df":0,"s":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"o":{"docs":{},"df":0,"d":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}},"o":{"docs":{},"df":0,"d":{"docs":{},"df":0,"l":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2}}},"v":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":1}}}},"n":{"docs":{},"df":0,"e":{"docs":{},"df":0,"e":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"t":{"docs":{},"df":0,"w":{"docs":{},"df":0,"o":{"docs":{},"df":0,"r":{"docs":{},"df":0,"k":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"w":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1}},"o":{"docs":{},"df":0,"b":{"docs":{},"df":0,"o":{"docs":{},"df":0,"d":{"docs":{},"df":0,"i":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"n":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.4142135623730951}},"df":1}},"w":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2}}},"o":{"docs":{},"df":0,"a":{"docs":{},"df":0,"e":{"docs":{},"df":0,"p":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}},"b":{"docs":{},"df":0,"j":{"docs":{},"df":0,"e":{"docs":{},"df":0,"c":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.4142135623730951}},"df":1}}}}},"f":{"docs":{},"df":0,"f":{"docs":{},"df":0,"e":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/":{"tf":1.7320508075688772}},"df":1}}}},"n":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2},"p":{"docs":{},"df":0,"e":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1},"r":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":2}}},"u":{"docs":{},"df":0,"t":{"docs":{},"df":0,"p":{"docs":{},"df":0,"u":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}},"p":{"docs":{},"df":0,"a":{"docs":{},"df":0,"1":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1},"d":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.4142135623730951}},"df":1},"g":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}},"s":{"docs":{},"df":0,"s":{"docs":{},"df":0,"w":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2},"o":{"docs":{},"df":0,"r":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2}}}}}},"t":{"docs":{},"df":0,"c":{"docs":{},"df":0,"h":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"y":{"docs":{},"df":0,"l":{"docs":{},"df":0,"o":{"docs":{},"df":0,"a":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2}}}}}},"e":{"docs":{},"df":0,"m":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1},"n":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":1}},"o":{"docs":{},"df":0,"p":{"docs":{},"df":0,"l":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}},"r":{"docs":{},"df":0,"l":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"i":{"docs":{},"df":0,"c":{"docs":{},"df":0,"k":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.7320508075688772}},"df":1}}},"k":{"docs":{},"df":0,"c":{"docs":{},"df":0,"s":{"docs":{},"df":0,"1":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1},"7":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}}},"l":{"docs":{},"df":0,"a":{"docs":{},"df":0,"c":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"y":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"e":{"docs":{},"df":0,"a":{"docs":{},"df":0,"s":{"docs":{"https://gradecoin.xyz/":{"tf":1.7320508075688772}},"df":1}}}},"o":{"docs":{},"df":0,"s":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951},"https://gradecoin.xyz/block-docs/":{"tf":1.4142135623730951},"https://gradecoin.xyz/register-docs/":{"tf":1.0},"https://gradecoin.xyz/transaction-docs/":{"tf":1.4142135623730951}},"df":4}}},"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"c":{"docs":{},"df":0,"t":{"docs":{},"df":0,"i":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"e":{"docs":{},"df":0,"l":{"docs":{},"df":0,"o":{"docs":{},"df":0,"a":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"i":{"docs":{},"df":0,"m":{"docs":{},"df":0,"i":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"v":{"docs":{},"df":0,"a":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}}},"o":{"docs":{},"df":0,"c":{"docs":{},"df":0,"e":{"docs":{},"df":0,"s":{"docs":{},"df":0,"s":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.4142135623730951}},"df":2}}}},"d":{"docs":{},"df":0,"u":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":2,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"g":{"docs":{},"df":0,"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"m":{"docs":{"https://gradecoin.xyz/":{"tf":2.0}},"df":1}}}},"o":{"docs":{},"df":0,"f":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951},"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":2}},"p":{"docs":{},"df":0,"o":{"docs":{},"df":0,"s":{"docs":{"https://gradecoin.xyz/":{"tf":1.7320508075688772},"https://gradecoin.xyz/block-docs/":{"tf":1.4142135623730951},"https://gradecoin.xyz/jwt/":{"tf":1.4142135623730951},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":4}}}}},"u":{"docs":{},"df":0,"b":{"docs":{},"df":0,"l":{"docs":{},"df":0,"i":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/":{"tf":3.0},"https://gradecoin.xyz/jwt/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":2.0}},"df":3,"_":{"docs":{},"df":0,"k":{"docs":{},"df":0,"e":{"docs":{},"df":0,"y":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}}}}}}},"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"y":{"docs":{},"df":0,"t":{"docs":{},"df":0,"h":{"docs":{},"df":0,"o":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}},"q":{"docs":{},"df":0,"u":{"docs":{},"df":0,"e":{"docs":{},"df":0,"s":{"docs":{},"df":0,"t":{"docs":{},"df":0,"i":{"docs":{},"df":0,"o":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1}}}}}},"i":{"docs":{},"df":0,"c":{"docs":{},"df":0,"k":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"n":{"docs":{},"df":0,"d":{"docs":{},"df":0,"o":{"docs":{},"df":0,"m":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2}}}}},"e":{"docs":{},"df":0,"c":{"docs":{},"df":0,"e":{"docs":{},"df":0,"i":{"docs":{},"df":0,"v":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2}}}},"f":{"docs":{},"df":0,"e":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0},"https://gradecoin.xyz/jwt/":{"tf":1.7320508075688772},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":3}}},"g":{"docs":{},"df":0,"i":{"docs":{},"df":0,"s":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951},"https://gradecoin.xyz/register-docs/":{"tf":1.4142135623730951}},"df":2}}}},"j":{"docs":{},"df":0,"e":{"docs":{},"df":0,"c":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}}},"m":{"docs":{},"df":0,"e":{"docs":{},"df":0,"m":{"docs":{},"df":0,"b":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"p":{"docs":{},"df":0,"r":{"docs":{},"df":0,"e":{"docs":{},"df":0,"s":{"docs":{},"df":0,"e":{"docs":{},"df":0,"n":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}}}}}},"q":{"docs":{},"df":0,"u":{"docs":{},"df":0,"e":{"docs":{},"df":0,"s":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":3.3166247903554},"https://gradecoin.xyz/block-docs/":{"tf":1.7320508075688772},"https://gradecoin.xyz/jwt/":{"tf":1.4142135623730951},"https://gradecoin.xyz/register-docs/":{"tf":1.0},"https://gradecoin.xyz/transaction-docs/":{"tf":2.0}},"df":5}}},"i":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}}},"s":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1},"u":{"docs":{},"df":0,"l":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}}},"t":{"docs":{},"df":0,"u":{"docs":{},"df":0,"r":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":2}}}}},"f":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":2}},"o":{"docs":{},"df":0,"l":{"docs":{},"df":0,"l":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"u":{"docs":{},"df":0,"g":{"docs":{},"df":0,"h":{"docs":{},"df":0,"l":{"docs":{},"df":0,"i":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1}}}},"n":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"s":{"docs":{},"df":0,"2":{"docs":{},"df":0,"5":{"docs":{},"df":0,"6":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}},"a":{"docs":{"https://gradecoin.xyz/":{"tf":1.7320508075688772},"https://gradecoin.xyz/jwt/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":3,"s":{"docs":{},"df":0,"s":{"docs":{},"df":0,"a":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}}}},"u":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1},"s":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1}}}},"s":{"docs":{},"df":0,"_":{"docs":{},"df":0,"p":{"docs":{},"df":0,"k":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}},"a":{"docs":{},"df":0,"d":{"docs":{},"df":0,"l":{"docs":{},"df":0,"i":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"m":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"n":{"docs":{},"df":0,"d":{"docs":{},"df":0,"b":{"docs":{},"df":0,"o":{"docs":{},"df":0,"x":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}},"c":{"docs":{},"df":0,"h":{"docs":{},"df":0,"e":{"docs":{},"df":0,"m":{"docs":{},"df":0,"a":{"docs":{},"df":0,":":{"docs":{},"df":0,":":{"docs":{},"df":0,"b":{"docs":{},"df":0,"l":{"docs":{},"df":0,"o":{"docs":{},"df":0,"c":{"docs":{},"df":0,"k":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1,":":{"docs":{},"df":0,":":{"docs":{},"df":0,"t":{"docs":{},"df":0,"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"n":{"docs":{},"df":0,"s":{"docs":{},"df":0,"a":{"docs":{},"df":0,"c":{"docs":{},"df":0,"t":{"docs":{},"df":0,"i":{"docs":{},"df":0,"o":{"docs":{},"df":0,"n":{"docs":{},"df":0,"_":{"docs":{},"df":0,"l":{"docs":{},"df":0,"i":{"docs":{},"df":0,"s":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}}}}}}}}}}}}}}}}}}},"d":{"docs":{},"df":0,"b":{"docs":{},"df":0,":":{"docs":{},"df":0,":":{"docs":{},"df":0,"p":{"docs":{},"df":0,"e":{"docs":{},"df":0,"n":{"docs":{},"df":0,"d":{"docs":{},"df":0,"i":{"docs":{},"df":0,"n":{"docs":{},"df":0,"g":{"docs":{},"df":0,"_":{"docs":{},"df":0,"t":{"docs":{},"df":0,"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"n":{"docs":{},"df":0,"s":{"docs":{},"df":0,"a":{"docs":{},"df":0,"c":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}}}}}}}}}}}}}}}}}}},"e":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}}},"r":{"docs":{},"df":0,"i":{"docs":{},"df":0,"p":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"e":{"docs":{},"df":0,"c":{"docs":{},"df":0,"r":{"docs":{},"df":0,"e":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}}},"e":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":2},"n":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951},"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":2}},"r":{"docs":{},"df":0,"i":{"docs":{},"df":0,"a":{"docs":{},"df":0,"l":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.4142135623730951}},"df":1}}},"v":{"docs":{},"df":0,"e":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/":{"tf":1.7320508075688772}},"df":1}},"i":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"h":{"docs":{},"df":0,"a":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2},"e":{"docs":{},"df":0,"l":{"docs":{},"df":0,"f":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"o":{"docs":{},"df":0,"r":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}}},"i":{"docs":{},"df":0,"d":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1}},"g":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/":{"tf":1.7320508075688772}},"df":1,"a":{"docs":{},"df":0,"t":{"docs":{},"df":0,"u":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}},"m":{"docs":{},"df":0,"p":{"docs":{},"df":0,"l":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1,"i":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"u":{"docs":{},"df":0,"l":{"docs":{"https://gradecoin.xyz/":{"tf":1.7320508075688772}},"df":1}}},"n":{"docs":{},"df":0,"g":{"docs":{},"df":0,"l":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"n":{"docs":{},"df":0,"i":{"docs":{},"df":0,"p":{"docs":{},"df":0,"p":{"docs":{},"df":0,"e":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}},"o":{"docs":{},"df":0,"l":{"docs":{},"df":0,"v":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"m":{"docs":{},"df":0,"e":{"docs":{},"df":0,"t":{"docs":{},"df":0,"h":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"u":{"docs":{},"df":0,"n":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"r":{"docs":{},"df":0,"c":{"docs":{"https://gradecoin.xyz/transaction-docs/":{"tf":1.4142135623730951}},"df":1}}}},"t":{"docs":{},"df":0,"a":{"docs":{},"df":0,"n":{"docs":{},"df":0,"d":{"docs":{},"df":0,"a":{"docs":{},"df":0,"r":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"r":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1}}},"r":{"docs":{},"df":0,"i":{"docs":{},"df":0,"n":{"docs":{},"df":0,"g":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2}}}},"u":{"docs":{},"df":0,"d":{"docs":{},"df":0,"e":{"docs":{},"df":0,"n":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.7320508075688772},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2,"'":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1},"_":{"docs":{},"df":0,"i":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}}}}}}}},"u":{"docs":{},"df":0,"b":{"docs":{},"df":0,"m":{"docs":{},"df":0,"i":{"docs":{},"df":0,"s":{"docs":{},"df":0,"s":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"s":{"docs":{},"df":0,"e":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"m":{"docs":{},"df":0,"m":{"docs":{},"df":0,"a":{"docs":{},"df":0,"r":{"docs":{},"df":0,"i":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"p":{"docs":{},"df":0,"p":{"docs":{},"df":0,"o":{"docs":{},"df":0,"s":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"y":{"docs":{},"df":0,"s":{"docs":{},"df":0,"t":{"docs":{},"df":0,"e":{"docs":{},"df":0,"m":{"docs":{"https://gradecoin.xyz/":{"tf":1.7320508075688772},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2}}}}}},"t":{"docs":{},"df":0,"a":{"docs":{},"df":0,"g":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1},"r":{"docs":{},"df":0,"g":{"docs":{},"df":0,"e":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0},"https://gradecoin.xyz/transaction-docs/":{"tf":1.4142135623730951}},"df":2}}}}},"e":{"docs":{},"df":0,"m":{"docs":{},"df":0,"p":{"docs":{},"df":0,"o":{"docs":{},"df":0,"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"r":{"docs":{},"df":0,"i":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.7320508075688772}},"df":1}}}}}}},"s":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"h":{"docs":{},"df":0,"a":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.4142135623730951}},"df":1,"n":{"docs":{},"df":0,"k":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"e":{"docs":{},"df":0,"m":{"docs":{},"df":0,"s":{"docs":{},"df":0,"e":{"docs":{},"df":0,"l":{"docs":{},"df":0,"v":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}}}}},"o":{"docs":{},"df":0,"u":{"docs":{},"df":0,"g":{"docs":{},"df":0,"h":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}},"i":{"docs":{},"df":0,"m":{"docs":{},"df":0,"e":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951},"https://gradecoin.xyz/jwt/":{"tf":2.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":3,"s":{"docs":{},"df":0,"t":{"docs":{},"df":0,"a":{"docs":{},"df":0,"m":{"docs":{},"df":0,"p":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":2}}}}}}}},"o":{"docs":{},"df":0,"k":{"docs":{},"df":0,"e":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}},"o":{"docs":{},"df":0,"l":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"n":{"docs":{},"df":0,"s":{"docs":{},"df":0,"a":{"docs":{},"df":0,"c":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":3.4641016151377544},"https://gradecoin.xyz/block-docs/":{"tf":1.0},"https://gradecoin.xyz/jwt/":{"tf":1.4142135623730951},"https://gradecoin.xyz/transaction-docs/":{"tf":2.449489742783178}},"df":4,"i":{"docs":{},"df":0,"o":{"docs":{},"df":0,"n":{"docs":{},"df":0,"_":{"docs":{},"df":0,"l":{"docs":{},"df":0,"i":{"docs":{},"df":0,"s":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.4142135623730951}},"df":1}}}}}}}}}}}}}}}},"u":{"docs":{},"df":0,"l":{"docs":{},"df":0,"t":{"docs":{},"df":0,"i":{"docs":{},"df":0,"m":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}}},"n":{"docs":{},"df":0,"i":{"docs":{},"df":0,"q":{"docs":{},"df":0,"u":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"x":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.7320508075688772}},"df":1}},"s":{"docs":{},"df":0,"i":{"docs":{},"df":0,"g":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":2}}}}},"p":{"docs":{},"df":0,"d":{"docs":{},"df":0,"a":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"s":{"docs":{"https://gradecoin.xyz/":{"tf":3.4641016151377544},"https://gradecoin.xyz/block-docs/":{"tf":1.4142135623730951},"https://gradecoin.xyz/jwt/":{"tf":2.23606797749979},"https://gradecoin.xyz/register-docs/":{"tf":1.7320508075688772},"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":5,"e":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}}},"v":{"docs":{},"df":0,"1":{"docs":{},"df":0,"_":{"docs":{},"df":0,"5":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}},"a":{"docs":{},"df":0,"l":{"docs":{},"df":0,"i":{"docs":{},"df":0,"d":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0},"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":2}}}},"e":{"docs":{},"df":0,"r":{"docs":{},"df":0,"i":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"u":{"docs":{},"df":0,"l":{"docs":{},"df":0,"n":{"docs":{},"df":0,"e":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}},"w":{"docs":{},"df":0,"a":{"docs":{},"df":0,"r":{"docs":{},"df":0,"p":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"s":{"docs":{},"df":0,"n":{"docs":{},"df":0,"'":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"y":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"e":{"docs":{},"df":0,"'":{"docs":{},"df":0,"r":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}},"b":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1,"s":{"docs":{},"df":0,"i":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"l":{"docs":{},"df":0,"c":{"docs":{},"df":0,"o":{"docs":{},"df":0,"m":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"l":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}},"i":{"docs":{},"df":0,"t":{"docs":{},"df":0,"h":{"docs":{},"df":0,"o":{"docs":{},"df":0,"u":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}},"o":{"docs":{},"df":0,"r":{"docs":{},"df":0,"k":{"docs":{"https://gradecoin.xyz/":{"tf":2.0},"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":2}}},"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"p":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}},"y":{"docs":{},"df":0,"o":{"docs":{},"df":0,"u":{"docs":{},"df":0,"r":{"docs":{},"df":0,"s":{"docs":{},"df":0,"e":{"docs":{},"df":0,"l":{"docs":{},"df":0,"f":{"docs":{"https://gradecoin.xyz/":{"tf":1.4142135623730951}},"df":1}}}}}}}},"z":{"docs":{},"df":0,"e":{"docs":{},"df":0,"r":{"docs":{},"df":0,"o":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1}}}}}},"title":{"root":{"docs":{},"df":0,"b":{"docs":{},"df":0,"l":{"docs":{},"df":0,"o":{"docs":{},"df":0,"c":{"docs":{},"df":0,"k":{"docs":{"https://gradecoin.xyz/block-docs/":{"tf":1.0}},"df":1}}}}},"g":{"docs":{},"df":0,"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"d":{"docs":{},"df":0,"e":{"docs":{},"df":0,"c":{"docs":{},"df":0,"o":{"docs":{},"df":0,"i":{"docs":{},"df":0,"n":{"docs":{"https://gradecoin.xyz/":{"tf":1.0}},"df":1}}}}}}}}},"j":{"docs":{},"df":0,"w":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/jwt/":{"tf":1.0}},"df":1}}},"r":{"docs":{},"df":0,"e":{"docs":{},"df":0,"g":{"docs":{},"df":0,"i":{"docs":{},"df":0,"s":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/register-docs/":{"tf":1.0}},"df":1}}}}}},"t":{"docs":{},"df":0,"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"n":{"docs":{},"df":0,"s":{"docs":{},"df":0,"a":{"docs":{},"df":0,"c":{"docs":{},"df":0,"t":{"docs":{"https://gradecoin.xyz/transaction-docs/":{"tf":1.0}},"df":1}}}}}}}}}}},"documentStore":{"save":true,"docs":{"https://gradecoin.xyz/":{"body":"Welcome to Gradecoin!\nBlockchains are incredibly simple yet can appear very complicated, we will see how they work and practice programming production cryptography code.\nThis server is the sandbox for the PA1, it's currently running the Gradecoin application. Gradecoin is the faux currency we will use to simulate a blockchain network. At the end of the simulation, the amount of Gradecoin you hold will be your PA1 grade.\nA quick summary: authenticate yourself to the system using public key encryption.\nCraft Transaction proposals and tag them using JWTs.\nWhen there are enough transactions then you can propose Blocks in the same way.\nBlocks need to be mined beforehand using Proof-of-work, or brute force.\nGradecoin offers 3 endpoints at /register, /block and /transaction. You can only send GET requests to /block and /transaction without authorization.\nThe server is programmed in RESTful architecture, there are no DELETE, PUT or UPDATE operations, though.\nGradecoin uses a Proof-of-work block accepting mechanism. It uses single round Blake2s hashing which produces 256-bit (64 hexadecimal characters) output. The target hash is 24 bits or 6 hexadecimal characters of 0. During testing, I could mine a block on average around 2-7 minutes.\n\nWe're expecting you to use existing tools and implementations. Standards are hard. Don't roll your own crypto. Feel free to ask questions. Collaborate.\n\nYou might ask,\n\nBut if nobody has any Gradecoin then how do we have transactions?\n\nThere is a bank! Their public key is 31415926535897932384626433832795028841971693993751058209749445923 and they have some amount of Gradecoin preloaded. It's also the only account that you can send transactions requests to yourself.\nCoinbase\nThe first transactions of a block is called the coinbase. They are the author of the block proposal and if the block is accepted then they get compensated for their efforts with some Gradecoin.\nPublic Key Signatures\nGradecoin uses 2048 bit RSA keyspairs.\nServices\n/register\n\nStudent creates their own 2048 bit RSA keypair\nDownloads Gradecoin's Public Key from Moodle\nEncrypts their JSON wrapped Public Key, Student ID and one time passwd using Gradecoin's Public Key\nTheir public key is now in our database and can be used to sign their JWT's during requests\n\n/transaction\n\nYou can offer a Transaction - POST request\n\nThe request should have Authorization\nThe request header should be signed by the Public Key of the by field in the transaction\n\n\nfetch the list of Transactions - GET request\n\n/block\n\noffer a [schema::Block] - POST request\n\nThe request should have Authorization\nThe [schema::Block::transaction_list] of the block should be a subset of [schema::Db::pending_transactions]\n\n\nfetch the last accepted [schema::Block] - GET request\n\nAuthorization: The request header should have Bearer JWT.Token signed with Student Public Key\nQuestions\nThis all sound complicated!\n\nI've drawn inspiration from actual Bitcoin transactions and warp. The simplicity of the system is how little interfaces it has.\nDon't know where to start? Gradecoin uses RESTful API; simple curl commands or even your browser will work! This website can help as well.\nJWT Debugger and the corresponding RFC\nRemember that you are absolutely encouraged to grab off-the-shelf implementations for every cryptography primitive you will use. You can start by finding a code snippet to generate a RSA keypair?\n\nI found a bug!\nThank you! Please let me know so we can solve it.\nI hacked the server!\nThat wasn't supposed to happen :( I did not place any intentional vulnerabilities to the system so if you cracked something, it was not intended. Please don't abuse it and let me know so I can patch it.\nSubmission?\nAt the end of the simulation, your Gradecoin balance will be your grade. I will also expect a unique client programmed in either;\n\nc\nc++\nperl\nrust\npython\nrandom assortment of bash scripts\n\nIf your favourite programming language is missing please let me know 🤷?\nCan my friends play?\nSadly, no. Student's who are enrolled to the class will receive one-time-passwords for authentication.\nHow and or Why?\n\nBuilt, with Rust\n\n","id":"https://gradecoin.xyz/","title":"Gradecoin"},"https://gradecoin.xyz/block-docs/":{"body":"A block that was proposed to commit Transactions in transaction_list to the\nledger with a nonce that made hash valid; 6 zeroes at the left hand side of the\nhash (24 bytes).\nWe are mining using blake2s algorithm, which produces 256 bit hashes. Hash/second is roughly 20x10^3 on my machine, a new block can be mined in around 4-6 minutes.\nRequests\nGET\nA HTTP GET request to /block endpoint will return the latest mined block.\nPOST\nA HTTP POST request with Authorization using JWT will allow you to propose your own blocks.\nFields\ntransaction_list: [array of Fingerprints]\nnonce: unsigned 32-bit integer\ntimestamp: ISO 8601 <date>T<time>\nhash: String\n\nISO 8601 Reference\n","id":"https://gradecoin.xyz/block-docs/","title":"Blocks"},"https://gradecoin.xyz/jwt/":{"body":"\nJSON Web Tokens are representations of claims, or authorization proofs that fit into the Header of HTTP requests.\n\nHow?\nJWTs are used as the MAC of operations that require authorization:\n\nblock proposal\ntransaction proposal.\n\nThey are send alongside the JSON request body in the Header;\nAuthorization: Bearer aaaaaa.bbbbbb.ccccc\n\nGradecoin uses 3 fields for the JWTs;\n{\n\"tha\": \"Hash of the payload, check invididual references\",\n\"iat\": \"Issued At, Unix Time\",\n\"exp\": \"Expiration Time, epoch\"\n}\n\n\ntha is explained in blocks and transactions documentations.\niat when the JWT was created in Unix Time format\nexp when the JWT will expire & be rejected in Unix Time\n\nAlgorithm\nWe are using RS256, RSASSA-PKCS1-v1_5 using SHA-256. The JWTs you encode with your private RSA key will be decoded using the public key you have authenticated with. You can see how the process works here.\nReferences\n\nRFC, the ultimate reference\nJWT Debugger\n\n","id":"https://gradecoin.xyz/jwt/","title":"JWT"},"https://gradecoin.xyz/register-docs/":{"body":"POST request to /register endpoint\nLets a user to authenticate themselves to the system.\nOnly people who are enrolled to the class can open Gradecoin accounts.\nThis is enforced with your Student ID and a one time password you will receive.\nAuthentication Process\n\nGradecoin's Public Key (gradecoin_public_key) is listed on our Moodle page.\nYou pick a short temporary key (k_temp)\nCreate a JSON object (auth_plaintext) with your metu_id and public key in base64 (PEM) format (S_PK) reference\n\n{\n \"student_id\": \"e12345\",\n \"passwd\": \"15 char secret\",\n \"public_key\": \"---BEGIN PUBLIC KEY...\"\n}\n\n\nPick a random IV.\nEncrypt the serialized string of auth_plaintext with 128 bit block AES in CBC mode with Pkcs7 padding using the temporary key (k_temp), the result is auth_ciphertext. Encode this with base64.\nThe temporary key you have picked k_temp is encrypted using RSA with OAEP padding scheme\nusing SHA-256 with gradecoin_public_key, giving us key_ciphertext. Encode this with base 64.\nThe payload JSON object (auth_request) can be serialized now:\n\n{\n \"c\": \"auth_ciphertext\",\n \"iv\": \"hexadecimal\",\n \"key\": \"key_ciphertext\"\n}\n\nIf your authentication process was valid, you will be given access and your public key fingerprint that is your address.\n","id":"https://gradecoin.xyz/register-docs/","title":"Register"},"https://gradecoin.xyz/transaction-docs/":{"body":"A transaction request between source and target to move amount Gradecoin.\nRequests\nGET\nA HTTP GET request to /transaction endpoint will return the current list of pending transactions.\nPOST\nA HTTP POST request with Authorization using JWT to /transaction will allow you to propose your own transactions.\nFields\nby: Fingerprint\nsource: Fingerprint\ntarget: Fingerprint\namount: unsigned 16 bit integer\ntimestamp: ISO 8601 <date>T<time>\n","id":"https://gradecoin.xyz/transaction-docs/","title":"Transactions"}},"docInfo":{"https://gradecoin.xyz/":{"body":371,"title":1},"https://gradecoin.xyz/block-docs/":{"body":74,"title":1},"https://gradecoin.xyz/jwt/":{"body":96,"title":1},"https://gradecoin.xyz/register-docs/":{"body":121,"title":1},"https://gradecoin.xyz/transaction-docs/":{"body":44,"title":1}},"length":5},"lang":"English"}; \ No newline at end of file | |||
diff --git a/site/public/sitemap.xml b/site/public/sitemap.xml new file mode 100644 index 0000000..1f2e2d9 --- /dev/null +++ b/site/public/sitemap.xml | |||
@@ -0,0 +1,18 @@ | |||
1 | <?xml version="1.0" encoding="UTF-8"?> | ||
2 | <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> | ||
3 | <url> | ||
4 | <loc>https://gradecoin.xyz/</loc> | ||
5 | </url> | ||
6 | <url> | ||
7 | <loc>https://gradecoin.xyz/block-docs/</loc> | ||
8 | </url> | ||
9 | <url> | ||
10 | <loc>https://gradecoin.xyz/jwt/</loc> | ||
11 | </url> | ||
12 | <url> | ||
13 | <loc>https://gradecoin.xyz/register-docs/</loc> | ||
14 | </url> | ||
15 | <url> | ||
16 | <loc>https://gradecoin.xyz/transaction-docs/</loc> | ||
17 | </url> | ||
18 | </urlset> | ||
diff --git a/site/public/transaction-docs/index.html b/site/public/transaction-docs/index.html new file mode 100644 index 0000000..67b9505 --- /dev/null +++ b/site/public/transaction-docs/index.html | |||
@@ -0,0 +1,164 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html lang="en"> | ||
3 | |||
4 | <head> | ||
5 | <meta charset="UTF-8"> | ||
6 | <title>Transactions | Gradecoin </title> | ||
7 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||
8 | <style> | ||
9 | :root { | ||
10 | /* Primary theme color */ | ||
11 | --primary-color: #F8D12F; | ||
12 | /* Primary theme text color */ | ||
13 | --primary-text-color: #1E2329; | ||
14 | /* Primary theme link color */ | ||
15 | --primary-link-color: #2F57F7; | ||
16 | /* Secondary color: the background body color */ | ||
17 | --secondary-color: #FAFAFA; | ||
18 | --secondary-text-color: #303030; | ||
19 | /* Highlight text color of table of content */ | ||
20 | --toc-highlight-text-color: #d46e13; | ||
21 | } | ||
22 | </style> | ||
23 | |||
24 | <link href="https://fonts.googleapis.com/css?family=Alfa+Slab+One&display=swap" rel="stylesheet"> | ||
25 | <link href="https://fonts.googleapis.com/css?family=Fira+Sans:400,500,600&display=swap" rel="stylesheet"> | ||
26 | <link rel="stylesheet" href="/normalize.css"> | ||
27 | <link rel="stylesheet" href="https://gradecoin.xyz/juice.css"> | ||
28 | |||
29 | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" /> | ||
30 | |||
31 | </head> | ||
32 | |||
33 | <body> | ||
34 | |||
35 | <header class="box-shadow"> | ||
36 | |||
37 | |||
38 | <a href="https://gradecoin.xyz/"> | ||
39 | <div class="logo"> | ||
40 | <img src="https://gradecoin.xyz/gradecoin.png" alt="logo"> | ||
41 | Gradecoin | ||
42 | </div> | ||
43 | </a> | ||
44 | |||
45 | <nav> | ||
46 | |||
47 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/register-docs/">Register</a> | ||
48 | |||
49 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/jwt/">JWT</a> | ||
50 | |||
51 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/transaction-docs/">Transactions</a> | ||
52 | |||
53 | <a class="nav-item subtitle-text" href="https://gradecoin.xyz/block-docs/">Blocks</a> | ||
54 | |||
55 | |||
56 | |||
57 | <a class="nav-item subtitle-text" href="https://github.com/zhuowei/nft_ptr#why">why?</a> | ||
58 | |||
59 | |||
60 | </nav> | ||
61 | |||
62 | </header> | ||
63 | |||
64 | |||
65 | <main> | ||
66 | |||
67 | |||
68 | |||
69 | |||
70 | |||
71 | <div class="toc"> | ||
72 | <div class="toc-sticky"> | ||
73 | |||
74 | <div class="toc-item"> | ||
75 | <a class="subtext" href="https://gradecoin.xyz/transaction-docs/#requests">Requests</a> | ||
76 | </div> | ||
77 | |||
78 | |||
79 | <div class="toc-item-child"> | ||
80 | <a class="subtext" href="https://gradecoin.xyz/transaction-docs/#get"><small>- GET</small></a> | ||
81 | </div> | ||
82 | |||
83 | <div class="toc-item-child"> | ||
84 | <a class="subtext" href="https://gradecoin.xyz/transaction-docs/#post"><small>- POST</small></a> | ||
85 | </div> | ||
86 | |||
87 | |||
88 | |||
89 | <div class="toc-item"> | ||
90 | <a class="subtext" href="https://gradecoin.xyz/transaction-docs/#fields">Fields</a> | ||
91 | </div> | ||
92 | |||
93 | |||
94 | </div> | ||
95 | </div> | ||
96 | |||
97 | |||
98 | |||
99 | <div class="content text"> | ||
100 | |||
101 | <div class="heading-text">Transaction documentation</div> | ||
102 | <p>A transaction request between <code>source</code> and <code>target</code> to move <code>amount</code> Gradecoin.</p> | ||
103 | <h1 id="requests">Requests</h1> | ||
104 | <h2 id="get">GET</h2> | ||
105 | <p>A HTTP <code>GET</code> request to <a href="/transaction">/transaction</a> endpoint will return the current list of pending transactions.</p> | ||
106 | <h2 id="post">POST</h2> | ||
107 | <p>A HTTP <code>POST</code> request with Authorization using JWT to <a href="/transactions">/transaction</a> will allow you to propose your own transactions.</p> | ||
108 | <h1 id="fields">Fields</h1> | ||
109 | <pre style="background-color:#ffffff;"> | ||
110 | <code><span style="color:#545052;">by: Fingerprint | ||
111 | source: Fingerprint | ||
112 | target: Fingerprint | ||
113 | amount: unsigned 16 bit integer | ||
114 | timestamp: ISO 8601 <date>T<time> | ||
115 | </span></code></pre> | ||
116 | |||
117 | </div> | ||
118 | |||
119 | |||
120 | |||
121 | </main> | ||
122 | |||
123 | |||
124 | <footer> | ||
125 | Built For ⁂ CENG489 ⁂ Introduction to Computer Security | ||
126 | </footer> | ||
127 | |||
128 | </body> | ||
129 | <script> | ||
130 | function highlightNav(heading) { | ||
131 | let pathname = location.pathname; | ||
132 | document.querySelectorAll(".toc a").forEach((item) => { | ||
133 | item.classList.remove("active"); | ||
134 | }); | ||
135 | document.querySelector(".toc a[href$='" + pathname + "#" + heading + "']").classList.add("active"); | ||
136 | } | ||
137 | |||
138 | let currentHeading = ""; | ||
139 | window.onscroll = function () { | ||
140 | let h = document.querySelectorAll("h1,h2,h3,h4,h5,h6"); | ||
141 | let elementArr = []; | ||
142 | |||
143 | h.forEach(item => { | ||
144 | if (item.id !== "") { | ||
145 | elementArr[item.id] = item.getBoundingClientRect().top; | ||
146 | } | ||
147 | }); | ||
148 | elementArr.sort(); | ||
149 | for (let key in elementArr) { | ||
150 | if (!elementArr.hasOwnProperty(key)) { | ||
151 | continue; | ||
152 | } | ||
153 | if (elementArr[key] > 0 && elementArr[key] < 300) { | ||
154 | if (currentHeading !== key) { | ||
155 | highlightNav(key); | ||
156 | currentHeading = key; | ||
157 | } | ||
158 | break; | ||
159 | } | ||
160 | } | ||
161 | } | ||
162 | </script> | ||
163 | |||
164 | </html> | ||
diff --git a/site/static/android-chrome-192x192.png b/site/static/android-chrome-192x192.png new file mode 100644 index 0000000..023ddbd --- /dev/null +++ b/site/static/android-chrome-192x192.png | |||
Binary files differ | |||
diff --git a/site/static/android-chrome-512x512.png b/site/static/android-chrome-512x512.png new file mode 100644 index 0000000..4251933 --- /dev/null +++ b/site/static/android-chrome-512x512.png | |||
Binary files differ | |||
diff --git a/site/static/apple-touch-icon.png b/site/static/apple-touch-icon.png new file mode 100644 index 0000000..cd8e4c8 --- /dev/null +++ b/site/static/apple-touch-icon.png | |||
Binary files differ | |||
diff --git a/site/static/favicon-16x16.png b/site/static/favicon-16x16.png new file mode 100644 index 0000000..bd63d34 --- /dev/null +++ b/site/static/favicon-16x16.png | |||
Binary files differ | |||
diff --git a/site/static/favicon-32x32.png b/site/static/favicon-32x32.png new file mode 100644 index 0000000..e343587 --- /dev/null +++ b/site/static/favicon-32x32.png | |||
Binary files differ | |||
diff --git a/site/static/favicon.ico b/site/static/favicon.ico new file mode 100644 index 0000000..45d8bfe --- /dev/null +++ b/site/static/favicon.ico | |||
Binary files differ | |||
diff --git a/site/static/gradecoin.png b/site/static/gradecoin.png new file mode 100644 index 0000000..eeb670c --- /dev/null +++ b/site/static/gradecoin.png | |||
Binary files differ | |||
diff --git a/site/templates/_variables.html b/site/templates/_variables.html new file mode 100644 index 0000000..3fd05b4 --- /dev/null +++ b/site/templates/_variables.html | |||
@@ -0,0 +1,15 @@ | |||
1 | <style> | ||
2 | :root { | ||
3 | /* Primary theme color */ | ||
4 | --primary-color: #F8D12F; | ||
5 | /* Primary theme text color */ | ||
6 | --primary-text-color: #1E2329; | ||
7 | /* Primary theme link color */ | ||
8 | --primary-link-color: #2F57F7; | ||
9 | /* Secondary color: the background body color */ | ||
10 | --secondary-color: #FAFAFA; | ||
11 | --secondary-text-color: #303030; | ||
12 | /* Highlight text color of table of content */ | ||
13 | --toc-highlight-text-color: #d46e13; | ||
14 | } | ||
15 | </style> | ||
diff --git a/site/templates/index.html b/site/templates/index.html new file mode 100644 index 0000000..1256b2a --- /dev/null +++ b/site/templates/index.html | |||
@@ -0,0 +1,50 @@ | |||
1 | {% extends "juice/templates/index.html" %} | ||
2 | |||
3 | {% block head %} | ||
4 | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" /> | ||
5 | {% endblock head %} | ||
6 | |||
7 | {% block hero %} | ||
8 | <section class="text-center"> | ||
9 | <h1 class="heading-text animate__animated animate__jackInTheBox" style="font-size: 50px"> | ||
10 | Mine your own grades | ||
11 | </h1> | ||
12 | <h3 class="title-text"> | ||
13 | <b>Gradecoin</b> is the latest cutting edge blockchain technology agile grading framework that drives organic engagement and other buzzwords, with big data mining search engine optimization | ||
14 | </h3> | ||
15 | <div> | ||
16 | </div> | ||
17 | </section> | ||
18 | <img class="hero-image" style="width: 40%" src="{{ get_url(path="gradecoin.png") }}"> | ||
19 | |||
20 | <div class="explore-more text" | ||
21 | onclick="document.getElementById('features').scrollIntoView({behavior: 'smooth'})"> | ||
22 | ⇩ Learn How ⇩ | ||
23 | </div> | ||
24 | <style> | ||
25 | |||
26 | .hero section { | ||
27 | padding: 0 5rem; | ||
28 | } | ||
29 | |||
30 | @media screen and (max-width: 768px) { | ||
31 | .hero section { | ||
32 | padding: 0 2rem; | ||
33 | } | ||
34 | |||
35 | .hero-image { | ||
36 | display: none | ||
37 | } | ||
38 | |||
39 | } | ||
40 | footer { | ||
41 | color: #8b8b8b; | ||
42 | } | ||
43 | </style> | ||
44 | {% endblock hero %} | ||
45 | |||
46 | {% block footer %} | ||
47 | <footer> | ||
48 | Built For ⁂ CENG489 ⁂ Introduction to Computer Security | ||
49 | </footer> | ||
50 | {% endblock footer %} | ||
diff --git a/site/themes/juice b/site/themes/juice new file mode 160000 | |||
Subproject 07310323f85e7851c0a1498021b0a02cfb2e215 | |||
diff --git a/src/bin/main.rs b/src/bin/main.rs index 8b61e5c..882fdc6 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs | |||
@@ -10,11 +10,10 @@ use gradecoin::schema::create_database; | |||
10 | #[tokio::main] | 10 | #[tokio::main] |
11 | async fn main() { | 11 | async fn main() { |
12 | // Show debug logs by default by setting `RUST_LOG=gradecoin=debug` | 12 | // Show debug logs by default by setting `RUST_LOG=gradecoin=debug` |
13 | // TODO: write logs to file? <13-04-21, yigit> // | ||
14 | if env::var_os("RUST_LOG").is_none() { | 13 | if env::var_os("RUST_LOG").is_none() { |
15 | env::set_var("RUST_LOG", "gradecoin=debug"); | 14 | env::set_var("RUST_LOG", "gradecoin=debug"); |
16 | } | 15 | } |
17 | pretty_env_logger::init(); | 16 | log4rs::init_file("log.conf.yml", Default::default()).unwrap(); |
18 | 17 | ||
19 | let db = create_database(); | 18 | let db = create_database(); |
20 | 19 | ||
diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 7339a06..0000000 --- a/src/error.rs +++ /dev/null | |||
@@ -1,38 +0,0 @@ | |||
1 | use log::warn; | ||
2 | use serde::Serialize; | ||
3 | use std::convert::Infallible; | ||
4 | use warp::{http::StatusCode, Rejection, Reply}; | ||
5 | |||
6 | #[derive(Serialize)] | ||
7 | struct ErrorResponse { | ||
8 | message: String, | ||
9 | } | ||
10 | |||
11 | pub async fn handle_rejection(err: Rejection) -> std::result::Result<impl Reply, Infallible> { | ||
12 | let code; | ||
13 | let message; | ||
14 | |||
15 | if err.is_not_found() { | ||
16 | code = StatusCode::NOT_FOUND; | ||
17 | message = "Requested resource is not found"; | ||
18 | } else if let Some(_) = err.find::<warp::filters::body::BodyDeserializeError>() { | ||
19 | code = StatusCode::BAD_REQUEST; | ||
20 | message = "Error: JSON body is not formatted correctly, check your payload"; | ||
21 | } else if let Some(_) = err.find::<warp::reject::MissingHeader>() { | ||
22 | code = StatusCode::METHOD_NOT_ALLOWED; | ||
23 | message = "Error: Authorization header missing, cannot authorize"; | ||
24 | } else if let Some(_) = err.find::<warp::reject::MethodNotAllowed>() { | ||
25 | code = StatusCode::METHOD_NOT_ALLOWED; | ||
26 | message = "Error: method not allowed on this endpoint"; | ||
27 | } else { | ||
28 | warn!("unhandled error: {:?}", err); | ||
29 | code = StatusCode::INTERNAL_SERVER_ERROR; | ||
30 | message = "Internal Server Error"; | ||
31 | } | ||
32 | |||
33 | let json = warp::reply::json(&ErrorResponse { | ||
34 | message: message.to_owned(), | ||
35 | }); | ||
36 | |||
37 | Ok(warp::reply::with_status(json, code)) | ||
38 | } | ||
diff --git a/src/handlers.rs b/src/handlers.rs index a8c9947..fe60ded 100644 --- a/src/handlers.rs +++ b/src/handlers.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | /// API handlers, the ends of each filter chain | ||
2 | use aes::Aes128; | 1 | use aes::Aes128; |
3 | use base64; | 2 | /// API handlers, the ends of each filter chain |
3 | use askama::Template; | ||
4 | use blake2::{Blake2s, Digest}; | 4 | use blake2::{Blake2s, Digest}; |
5 | use block_modes::block_padding::Pkcs7; | 5 | use block_modes::block_padding::Pkcs7; |
6 | use block_modes::{BlockMode, Cbc}; | 6 | use block_modes::{BlockMode, Cbc}; |
@@ -8,10 +8,8 @@ use jsonwebtoken::errors::ErrorKind; | |||
8 | use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; | 8 | use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; |
9 | use log::{debug, warn}; | 9 | use log::{debug, warn}; |
10 | use md5::Md5; | 10 | use md5::Md5; |
11 | use parking_lot::RwLockUpgradableReadGuard; | ||
12 | use rsa::{PaddingScheme, RSAPrivateKey}; | 11 | use rsa::{PaddingScheme, RSAPrivateKey}; |
13 | use serde::Serialize; | 12 | use serde::Serialize; |
14 | use serde_json; | ||
15 | use sha2::Sha256; | 13 | use sha2::Sha256; |
16 | use std::collections::HashMap; | 14 | use std::collections::HashMap; |
17 | use std::convert::Infallible; | 15 | use std::convert::Infallible; |
@@ -39,6 +37,7 @@ enum ResponseType { | |||
39 | 37 | ||
40 | use crate::schema::{ | 38 | use crate::schema::{ |
41 | AuthRequest, Block, Claims, Db, InitialAuthRequest, MetuId, NakedBlock, Transaction, User, | 39 | AuthRequest, Block, Claims, Db, InitialAuthRequest, MetuId, NakedBlock, Transaction, User, |
40 | UserAtRest, | ||
42 | }; | 41 | }; |
43 | 42 | ||
44 | const BEARER: &str = "Bearer "; | 43 | const BEARER: &str = "Bearer "; |
@@ -61,9 +60,9 @@ const BEARER: &str = "Bearer "; | |||
61 | /// public_key: "---BEGIN PUBLIC KEY..." | 60 | /// public_key: "---BEGIN PUBLIC KEY..." |
62 | /// } | 61 | /// } |
63 | /// | 62 | /// |
64 | /// - Encrypts the serialized string of `auth_plaintext` with 128 bit block AES in CBC mode with Pkcs7 padding using the temporary key (`k_temp`), the result is `auth_ciphertext` TODO should this be base64'd? | 63 | /// - Encrypts the serialized string of `auth_plaintext` with 128 bit block AES in CBC mode with Pkcs7 padding using the temporary key (`k_temp`), the result is `auth_ciphertext` |
65 | /// - The temporary key student has picked `k_temp` is encrypted using RSA with OAEP padding scheme | 64 | /// - The temporary key student has picked `k_temp` is encrypted using RSA with OAEP padding scheme |
66 | /// using sha256 with `gradecoin_public_key` (TODO base64? same as above), giving us `key_ciphertext` | 65 | /// using sha256 with `gradecoin_public_key`, giving us `key_ciphertext` |
67 | /// - The payload JSON object (`auth_request`) can be JSON serialized now: | 66 | /// - The payload JSON object (`auth_request`) can be JSON serialized now: |
68 | /// { | 67 | /// { |
69 | /// c: "auth_ciphertext" | 68 | /// c: "auth_ciphertext" |
@@ -92,7 +91,7 @@ pub async fn authenticate_user( | |||
92 | // Load our RSA Private Key as DER | 91 | // Load our RSA Private Key as DER |
93 | let der_encoded = PRIVATE_KEY | 92 | let der_encoded = PRIVATE_KEY |
94 | .lines() | 93 | .lines() |
95 | .filter(|line| !line.starts_with("-")) | 94 | .filter(|line| !line.starts_with('-')) |
96 | .fold(String::new(), |mut data, line| { | 95 | .fold(String::new(), |mut data, line| { |
97 | data.push_str(&line); | 96 | data.push_str(&line); |
98 | data | 97 | data |
@@ -104,18 +103,126 @@ pub async fn authenticate_user( | |||
104 | let gradecoin_private_key = RSAPrivateKey::from_pkcs1(&der_bytes).expect("failed to parse key"); | 103 | let gradecoin_private_key = RSAPrivateKey::from_pkcs1(&der_bytes).expect("failed to parse key"); |
105 | 104 | ||
106 | let padding = PaddingScheme::new_oaep::<sha2::Sha256>(); | 105 | let padding = PaddingScheme::new_oaep::<sha2::Sha256>(); |
107 | let temp_key = gradecoin_private_key | ||
108 | .decrypt(padding, &request.key.as_bytes()) | ||
109 | .expect("failed to decrypt"); | ||
110 | 106 | ||
111 | // decrypt c using key dec_key | 107 | let key_ciphertext = match base64::decode(&request.key) { |
112 | let cipher = Aes128Cbc::new_var(&temp_key, &request.iv).unwrap(); | 108 | Ok(c) => c, |
113 | let auth_plaintext = cipher | 109 | Err(err) => { |
114 | .decrypt_vec(&base64::decode(request.c).unwrap()) | 110 | debug!( |
115 | .unwrap(); | 111 | "The ciphertext of the key was not base64 encoded {}, {}", |
112 | &request.key, err | ||
113 | ); | ||
114 | |||
115 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
116 | res: ResponseType::Error, | ||
117 | message: "The ciphertext of the key was not base64 encoded {}, {}".to_owned(), | ||
118 | }); | ||
119 | |||
120 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
121 | } | ||
122 | }; | ||
123 | |||
124 | let temp_key = match gradecoin_private_key.decrypt(padding, &key_ciphertext) { | ||
125 | Ok(k) => k, | ||
126 | Err(err) => { | ||
127 | debug!( | ||
128 | "Failed to decrypt ciphertext {:?}, {}", | ||
129 | &key_ciphertext, err | ||
130 | ); | ||
131 | |||
132 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
133 | res: ResponseType::Error, | ||
134 | message: "Failed to decrypt the ciphertext of the temporary key".to_owned(), | ||
135 | }); | ||
136 | |||
137 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
138 | } | ||
139 | }; | ||
140 | |||
141 | let cipher = match Aes128Cbc::new_var(&temp_key, &request.iv.as_bytes()) { | ||
142 | Ok(c) => c, | ||
143 | Err(err) => { | ||
144 | debug!( | ||
145 | "Could not create a cipher from temp_key and request.iv {:?}, {}, {}", | ||
146 | &temp_key, &request.iv, err | ||
147 | ); | ||
148 | |||
149 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
150 | res: ResponseType::Error, | ||
151 | message: "Given IV has invalid length".to_owned(), | ||
152 | }); | ||
153 | |||
154 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
155 | } | ||
156 | }; | ||
116 | 157 | ||
117 | let request: AuthRequest = | 158 | let auth_packet = match base64::decode(&request.c) { |
118 | serde_json::from_str(&String::from_utf8(auth_plaintext).unwrap()).unwrap(); | 159 | Ok(a) => a, |
160 | |||
161 | Err(err) => { | ||
162 | debug!( | ||
163 | "The auth_packet (c field) did not base64 decode {} {}", | ||
164 | &request.c, err | ||
165 | ); | ||
166 | |||
167 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
168 | res: ResponseType::Error, | ||
169 | message: "The c field was not correctly base64 encoded".to_owned(), | ||
170 | }); | ||
171 | |||
172 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
173 | } | ||
174 | }; | ||
175 | |||
176 | let auth_plaintext = match cipher.decrypt_vec(&auth_packet) { | ||
177 | Ok(p) => p, | ||
178 | Err(err) => { | ||
179 | debug!( | ||
180 | "Base64 decoded auth request did not decrypt correctly {:?} {}", | ||
181 | &auth_packet, err | ||
182 | ); | ||
183 | |||
184 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
185 | res: ResponseType::Error, | ||
186 | message: "The Bas64 decoded auth request did not decrypt correctly".to_owned(), | ||
187 | }); | ||
188 | |||
189 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
190 | } | ||
191 | }; | ||
192 | |||
193 | let utf8_auth_plaintext = match String::from_utf8(auth_plaintext.clone()) { | ||
194 | Ok(text) => text, | ||
195 | Err(err) => { | ||
196 | debug!( | ||
197 | "Auth plaintext did not convert into utf8 {:?} {}", | ||
198 | &auth_plaintext, err | ||
199 | ); | ||
200 | |||
201 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
202 | res: ResponseType::Error, | ||
203 | message: "Auth plaintext couldn't get converted to UTF-8".to_owned(), | ||
204 | }); | ||
205 | |||
206 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
207 | } | ||
208 | }; | ||
209 | |||
210 | let request: AuthRequest = match serde_json::from_str(&utf8_auth_plaintext) { | ||
211 | Ok(req) => req, | ||
212 | Err(err) => { | ||
213 | debug!( | ||
214 | "Auth plaintext did not serialize correctly {:?} {}", | ||
215 | &utf8_auth_plaintext, err | ||
216 | ); | ||
217 | |||
218 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
219 | res: ResponseType::Error, | ||
220 | message: "The auth request JSON did not serialize correctly".to_owned(), | ||
221 | }); | ||
222 | |||
223 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
224 | } | ||
225 | }; | ||
119 | 226 | ||
120 | let provided_id = request.student_id.clone(); | 227 | let provided_id = request.student_id.clone(); |
121 | 228 | ||
@@ -131,33 +238,24 @@ pub async fn authenticate_user( | |||
131 | } | 238 | } |
132 | }; | 239 | }; |
133 | 240 | ||
134 | let userlist = db.users.upgradable_read(); | 241 | { |
135 | 242 | let userlist = db.users.read(); | |
136 | if userlist.contains_key(&provided_id) { | ||
137 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
138 | res: ResponseType::Error, | ||
139 | message: | ||
140 | "This user is already authenticated, do you think this is a mistake? Contact me" | ||
141 | .to_owned(), | ||
142 | }); | ||
143 | |||
144 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
145 | } | ||
146 | 243 | ||
147 | // We're using this as the validator | 244 | if userlist.contains_key(&provided_id) { |
148 | // I hate myself | 245 | let res_json = warp::reply::json(&GradeCoinResponse { |
149 | if let Err(_) = DecodingKey::from_rsa_pem(request.public_key.as_bytes()) { | 246 | res: ResponseType::Error, |
150 | let res_json = warp::reply::json(&GradeCoinResponse { | 247 | message: |
151 | res: ResponseType::Error, | 248 | "This user is already authenticated, do you think this is a mistake? Contact me" |
152 | message: "The supplied RSA public key is not in valid PEM format".to_owned(), | 249 | .to_owned(), |
153 | }); | 250 | }); |
154 | 251 | ||
155 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | 252 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); |
253 | } | ||
156 | } | 254 | } |
157 | 255 | ||
158 | // We're using this as the validator | 256 | // We're using this as the validator |
159 | // I hate myself | 257 | // I hate myself |
160 | if let Err(_) = DecodingKey::from_rsa_pem(request.public_key.as_bytes()) { | 258 | if DecodingKey::from_rsa_pem(request.public_key.as_bytes()).is_err() { |
161 | let res_json = warp::reply::json(&GradeCoinResponse { | 259 | let res_json = warp::reply::json(&GradeCoinResponse { |
162 | res: ResponseType::Error, | 260 | res: ResponseType::Error, |
163 | message: "The supplied RSA public key is not in valid PEM format".to_owned(), | 261 | message: "The supplied RSA public key is not in valid PEM format".to_owned(), |
@@ -174,11 +272,19 @@ pub async fn authenticate_user( | |||
174 | balance: 0, | 272 | balance: 0, |
175 | }; | 273 | }; |
176 | 274 | ||
177 | let user_json = serde_json::to_string(&new_user).unwrap(); | 275 | let user_at_rest_json = serde_json::to_string(&UserAtRest { |
276 | user: User { | ||
277 | user_id: new_user.user_id.clone(), | ||
278 | public_key: new_user.public_key.clone(), | ||
279 | balance: 0, | ||
280 | }, | ||
281 | fingerprint: fingerprint.clone(), | ||
282 | }) | ||
283 | .unwrap(); | ||
178 | 284 | ||
179 | fs::write(format!("users/{}.guy", new_user.user_id), user_json).unwrap(); | 285 | fs::write(format!("users/{}.guy", new_user.user_id), user_at_rest_json).unwrap(); |
180 | 286 | ||
181 | let mut userlist = RwLockUpgradableReadGuard::upgrade(userlist); | 287 | let mut userlist = db.users.write(); |
182 | 288 | ||
183 | userlist.insert(fingerprint.clone(), new_user); | 289 | userlist.insert(fingerprint.clone(), new_user); |
184 | 290 | ||
@@ -193,17 +299,6 @@ pub async fn authenticate_user( | |||
193 | Ok(warp::reply::with_status(res_json, StatusCode::CREATED)) | 299 | Ok(warp::reply::with_status(res_json, StatusCode::CREATED)) |
194 | } | 300 | } |
195 | 301 | ||
196 | // fn shed_pem_header_footer(maybe_key: String) -> Result<Vec<u8>, String> { | ||
197 | // let der_encoded = maybe_key | ||
198 | // .lines() | ||
199 | // .filter(|line| !line.starts_with("-")) | ||
200 | // .fold(String::new(), |mut data, line| { | ||
201 | // data.push_str(&line); | ||
202 | // data | ||
203 | // }); | ||
204 | // Ok(base64::decode(&der_encoded).expect("failed to decode base64 content")) | ||
205 | // } | ||
206 | |||
207 | /// GET /transaction | 302 | /// GET /transaction |
208 | /// Returns JSON array of transactions | 303 | /// Returns JSON array of transactions |
209 | /// Cannot fail | 304 | /// Cannot fail |
@@ -241,7 +336,7 @@ pub async fn authorized_propose_block( | |||
241 | 336 | ||
242 | println!("{:?}", &new_block); | 337 | println!("{:?}", &new_block); |
243 | 338 | ||
244 | if new_block.transaction_list.len() < 1 { | 339 | if new_block.transaction_list.is_empty() { |
245 | let res_json = warp::reply::json(&GradeCoinResponse { | 340 | let res_json = warp::reply::json(&GradeCoinResponse { |
246 | res: ResponseType::Error, | 341 | res: ResponseType::Error, |
247 | message: format!( | 342 | message: format!( |
@@ -322,8 +417,8 @@ pub async fn authorized_propose_block( | |||
322 | 417 | ||
323 | let naked_block = NakedBlock { | 418 | let naked_block = NakedBlock { |
324 | transaction_list: new_block.transaction_list.clone(), | 419 | transaction_list: new_block.transaction_list.clone(), |
325 | nonce: new_block.nonce.clone(), | 420 | nonce: new_block.nonce, |
326 | timestamp: new_block.timestamp.clone(), | 421 | timestamp: new_block.timestamp, |
327 | }; | 422 | }; |
328 | 423 | ||
329 | let naked_block_flat = serde_json::to_vec(&naked_block).unwrap(); | 424 | let naked_block_flat = serde_json::to_vec(&naked_block).unwrap(); |
@@ -556,7 +651,7 @@ pub async fn list_blocks(db: Db) -> Result<impl warp::Reply, Infallible> { | |||
556 | /// *[`jwt_token`]: The raw JWT token, "Bearer aaa.bbb.ccc" | 651 | /// *[`jwt_token`]: The raw JWT token, "Bearer aaa.bbb.ccc" |
557 | /// *[`user_pem`]: User Public Key, "BEGIN RSA" | 652 | /// *[`user_pem`]: User Public Key, "BEGIN RSA" |
558 | /// NOT async, might look into it if this becomes a bottleneck | 653 | /// NOT async, might look into it if this becomes a bottleneck |
559 | fn authorize_proposer(jwt_token: String, user_pem: &String) -> Result<TokenData<Claims>, String> { | 654 | fn authorize_proposer(jwt_token: String, user_pem: &str) -> Result<TokenData<Claims>, String> { |
560 | // Throw away the "Bearer " part | 655 | // Throw away the "Bearer " part |
561 | let raw_jwt = jwt_token.trim_start_matches(BEARER).to_owned(); | 656 | let raw_jwt = jwt_token.trim_start_matches(BEARER).to_owned(); |
562 | debug!("raw_jwt: {:?}", raw_jwt); | 657 | debug!("raw_jwt: {:?}", raw_jwt); |
@@ -599,3 +694,19 @@ fn authorize_proposer(jwt_token: String, user_pem: &String) -> Result<TokenData< | |||
599 | 694 | ||
600 | Ok(token_payload) | 695 | Ok(token_payload) |
601 | } | 696 | } |
697 | |||
698 | #[derive(Template)] | ||
699 | #[template(path = "welcome.html")] | ||
700 | struct WelcomeTemplate<'a> { | ||
701 | title: &'a str, | ||
702 | body: &'a str, | ||
703 | } | ||
704 | |||
705 | pub async fn welcome_handler() -> Result<impl warp::Reply, warp::Rejection> { | ||
706 | let template = WelcomeTemplate { | ||
707 | title: "Welcome", | ||
708 | body: "To The Bookstore!", | ||
709 | }; | ||
710 | let res = template.render().unwrap(); | ||
711 | Ok(warp::reply::html(res)) | ||
712 | } | ||
@@ -22,12 +22,11 @@ | |||
22 | //! `Authorization`: The request header should have Bearer JWT.Token signed with Student Public Key | 22 | //! `Authorization`: The request header should have Bearer JWT.Token signed with Student Public Key |
23 | 23 | ||
24 | pub mod custom_filters; | 24 | pub mod custom_filters; |
25 | pub mod error; | ||
26 | pub mod handlers; | 25 | pub mod handlers; |
27 | pub mod routes; | 26 | pub mod routes; |
28 | pub mod schema; | 27 | pub mod schema; |
29 | 28 | ||
30 | pub const PRIVATE_KEY: &'static str = "-----BEGIN RSA PRIVATE KEY----- | 29 | pub const PRIVATE_KEY: &str = "-----BEGIN RSA PRIVATE KEY----- |
31 | MIIEogIBAAKCAQEAyGuqiCPGcguy+Y9TH7Bl7XlEsalyqb9bYlzpbV0dnqZ3lPkE | 30 | MIIEogIBAAKCAQEAyGuqiCPGcguy+Y9TH7Bl7XlEsalyqb9bYlzpbV0dnqZ3lPkE |
32 | PkuOhkN+GcuiV6iXtSwyh7nB+xTRXKJFRUBO/jbN8jfcxVwBu0JxjF3v1YRBxbOH | 31 | PkuOhkN+GcuiV6iXtSwyh7nB+xTRXKJFRUBO/jbN8jfcxVwBu0JxjF3v1YRBxbOH |
33 | hz2A295mbKD9xHQCKxkfYBNkUXxj8gd+GaDvQiSW5NdrX/lEkvqfGtdEX1m2+Hdc | 32 | hz2A295mbKD9xHQCKxkfYBNkUXxj8gd+GaDvQiSW5NdrX/lEkvqfGtdEX1m2+Hdc |
@@ -55,7 +54,7 @@ PDYHM9dfQ8xn51U0fTeaXjy/8Km8fyX2Jtxntlm6puyhSTJ8AX+FEgJkC4ajNEvA | |||
55 | mJ1Gsy2fXKUyyZdI2b74MLqOpzr9cvS60tmTIScuiHFzg/SJgiA= | 54 | mJ1Gsy2fXKUyyZdI2b74MLqOpzr9cvS60tmTIScuiHFzg/SJgiA= |
56 | -----END RSA PRIVATE KEY-----"; | 55 | -----END RSA PRIVATE KEY-----"; |
57 | 56 | ||
58 | pub const PUB_KEY: &'static str = "-----BEGIN PUBLIC KEY----- | 57 | pub const PUB_KEY: &str = "-----BEGIN PUBLIC KEY----- |
59 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyGuqiCPGcguy+Y9TH7Bl | 58 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyGuqiCPGcguy+Y9TH7Bl |
60 | 7XlEsalyqb9bYlzpbV0dnqZ3lPkEPkuOhkN+GcuiV6iXtSwyh7nB+xTRXKJFRUBO | 59 | 7XlEsalyqb9bYlzpbV0dnqZ3lPkEPkuOhkN+GcuiV6iXtSwyh7nB+xTRXKJFRUBO |
61 | /jbN8jfcxVwBu0JxjF3v1YRBxbOHhz2A295mbKD9xHQCKxkfYBNkUXxj8gd+GaDv | 60 | /jbN8jfcxVwBu0JxjF3v1YRBxbOHhz2A295mbKD9xHQCKxkfYBNkUXxj8gd+GaDv |
diff --git a/src/routes.rs b/src/routes.rs index 280de35..52d357a 100644 --- a/src/routes.rs +++ b/src/routes.rs | |||
@@ -7,11 +7,19 @@ use crate::schema::Db; | |||
7 | 7 | ||
8 | /// Every route combined | 8 | /// Every route combined |
9 | pub fn consensus_routes(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { | 9 | pub fn consensus_routes(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { |
10 | // Remember when we wanted to implement templating | ||
11 | // Why would we? Just put a staic webpage under /public (next to Cargo.toml) and place it and | ||
12 | // the end of the filter chain | ||
13 | |||
14 | // Fully fledged website support, phew! | ||
15 | let static_route = warp::any().and(warp::fs::dir("public")); | ||
16 | |||
10 | transaction_list(db.clone()) | 17 | transaction_list(db.clone()) |
11 | .or(register_user(db.clone())) | 18 | .or(register_user(db.clone())) |
12 | .or(auth_transaction_propose(db.clone())) | 19 | .or(auth_transaction_propose(db.clone())) |
13 | .or(auth_block_propose(db.clone())) | 20 | .or(auth_block_propose(db.clone())) |
14 | .or(block_list(db.clone())) | 21 | .or(block_list(db)) |
22 | .or(static_route) | ||
15 | } | 23 | } |
16 | 24 | ||
17 | /// POST /register warp route | 25 | /// POST /register warp route |
@@ -60,4 +68,3 @@ pub fn auth_block_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = R | |||
60 | .and(custom_filters::with_db(db)) | 68 | .and(custom_filters::with_db(db)) |
61 | .and_then(handlers::authorized_propose_block) | 69 | .and_then(handlers::authorized_propose_block) |
62 | } | 70 | } |
63 | |||
diff --git a/src/schema.rs b/src/schema.rs index 264d2bd..cb353e4 100644 --- a/src/schema.rs +++ b/src/schema.rs | |||
@@ -9,6 +9,7 @@ | |||
9 | //! Users are held in memory and they're also backed up to text files | 9 | //! Users are held in memory and they're also backed up to text files |
10 | use chrono::{NaiveDate, NaiveDateTime}; | 10 | use chrono::{NaiveDate, NaiveDateTime}; |
11 | use lazy_static::lazy_static; | 11 | use lazy_static::lazy_static; |
12 | use log::debug; | ||
12 | use parking_lot::RwLock; | 13 | use parking_lot::RwLock; |
13 | use serde::{Deserialize, Serialize}; | 14 | use serde::{Deserialize, Serialize}; |
14 | use std::collections::{HashMap, HashSet}; | 15 | use std::collections::{HashMap, HashSet}; |
@@ -23,8 +24,6 @@ use std::vec::Vec; | |||
23 | 24 | ||
24 | pub type Fingerprint = String; | 25 | pub type Fingerprint = String; |
25 | 26 | ||
26 | // TODO: start by reading users from file too <14-04-21, yigit> // | ||
27 | |||
28 | fn block_parser(path: String) -> u64 { | 27 | fn block_parser(path: String) -> u64 { |
29 | let end_pos = path.find(".block").unwrap(); | 28 | let end_pos = path.find(".block").unwrap(); |
30 | let block_str = path[9..end_pos].to_string(); | 29 | let block_str = path[9..end_pos].to_string(); |
@@ -63,16 +62,49 @@ fn read_block_name() -> io::Result<Vec<PathBuf>> { | |||
63 | Ok(entries) | 62 | Ok(entries) |
64 | } | 63 | } |
65 | 64 | ||
66 | fn create_db_with_last_block(path: String) -> Db { | 65 | fn read_users() -> io::Result<Vec<PathBuf>> { |
66 | let entries = fs::read_dir("./users")? | ||
67 | .map(|res| res.map(|e| e.path())) | ||
68 | .collect::<Result<Vec<_>, io::Error>>()?; | ||
69 | |||
70 | Ok(entries) | ||
71 | } | ||
72 | |||
73 | fn populate_db_with_last_block(db: &mut Db, path: String) -> &mut Db { | ||
74 | debug!("Populating db with last block {}", path); | ||
67 | let file = fs::read(path).unwrap(); | 75 | let file = fs::read(path).unwrap(); |
68 | let json = std::str::from_utf8(&file).unwrap(); | 76 | let json = std::str::from_utf8(&file).unwrap(); |
69 | let block: Block = serde_json::from_str(json).unwrap(); | 77 | let block: Block = serde_json::from_str(json).unwrap(); |
70 | let db = Db::new(); | ||
71 | *db.blockchain.write() = block; | 78 | *db.blockchain.write() = block; |
72 | return db; | 79 | |
80 | db | ||
81 | } | ||
82 | |||
83 | #[derive(Debug, Serialize, Deserialize, PartialEq)] | ||
84 | pub struct UserAtRest { | ||
85 | pub fingerprint: Fingerprint, | ||
86 | pub user: User, | ||
73 | } | 87 | } |
74 | 88 | ||
75 | /// Creates a new database, uses the previous last block if one exists | 89 | fn populate_db_with_users(db: &mut Db, files: Vec<PathBuf>) -> &mut Db { |
90 | for fs in files { | ||
91 | if let Ok(file_content) = fs::read(fs) { | ||
92 | let json = | ||
93 | String::from_utf8(file_content).expect("we have written a malformed user file"); | ||
94 | let user_at_rest: UserAtRest = serde_json::from_str(&json).unwrap(); | ||
95 | |||
96 | debug!("Populating db with user: {:?}", user_at_rest); | ||
97 | db.users | ||
98 | .write() | ||
99 | .insert(user_at_rest.fingerprint, user_at_rest.user); | ||
100 | } | ||
101 | } | ||
102 | |||
103 | db | ||
104 | } | ||
105 | |||
106 | /// Creates a new database, uses the previous last block if one exists and attempts the populate | ||
107 | /// the users | ||
76 | pub fn create_database() -> Db { | 108 | pub fn create_database() -> Db { |
77 | fs::create_dir_all("blocks").unwrap(); | 109 | fs::create_dir_all("blocks").unwrap(); |
78 | fs::create_dir_all("users").unwrap(); | 110 | fs::create_dir_all("users").unwrap(); |
@@ -82,6 +114,12 @@ pub fn create_database() -> Db { | |||
82 | } else { | 114 | } else { |
83 | return Db::new(); | 115 | return Db::new(); |
84 | } | 116 | } |
117 | |||
118 | if let Ok(users_path) = read_users() { | ||
119 | populate_db_with_users(&mut db, users_path); | ||
120 | } | ||
121 | |||
122 | db | ||
85 | } | 123 | } |
86 | 124 | ||
87 | /// A JWT Payload/Claims representation | 125 | /// A JWT Payload/Claims representation |
@@ -181,13 +219,23 @@ impl Block { | |||
181 | Block { | 219 | Block { |
182 | transaction_list: vec!["gradecoin_bank".to_owned()], | 220 | transaction_list: vec!["gradecoin_bank".to_owned()], |
183 | nonce: 0, | 221 | nonce: 0, |
184 | timestamp: NaiveDate::from_ymd(2021, 04, 11).and_hms(20, 45, 00), | 222 | timestamp: NaiveDate::from_ymd(2021, 4, 11).and_hms(20, 45, 00), |
185 | hash: String::from("not_actually_mined"), | 223 | hash: String::from("not_actually_mined"), |
186 | } | 224 | } |
187 | } | 225 | } |
188 | } | 226 | } |
189 | 227 | ||
190 | /// Simply a Student | 228 | impl Default for Block { |
229 | fn default() -> Self { | ||
230 | Self::new() | ||
231 | } | ||
232 | } | ||
233 | |||
234 | /// A Student | ||
235 | /// | ||
236 | /// * [`user_id`]: Can only be one of the repopulated | ||
237 | /// * [`public_key`]: A PEM format public key "---- BEGIN" and all | ||
238 | /// * [`balance`]: User's current Gradecoin amount | ||
191 | #[derive(Serialize, Deserialize, Debug, PartialEq)] | 239 | #[derive(Serialize, Deserialize, Debug, PartialEq)] |
192 | pub struct User { | 240 | pub struct User { |
193 | pub user_id: MetuId, | 241 | pub user_id: MetuId, |
@@ -196,7 +244,7 @@ pub struct User { | |||
196 | } | 244 | } |
197 | 245 | ||
198 | /// The values are hard coded in [`OUR_STUDENTS`] so MetuId::new() can accept/reject values based on that | 246 | /// The values are hard coded in [`OUR_STUDENTS`] so MetuId::new() can accept/reject values based on that |
199 | #[derive(Serialize, Deserialize, Debug, PartialEq)] | 247 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] |
200 | pub struct MetuId { | 248 | pub struct MetuId { |
201 | id: String, | 249 | id: String, |
202 | passwd: String, | 250 | passwd: String, |
@@ -214,7 +262,7 @@ pub struct AuthRequest { | |||
214 | #[derive(Serialize, Deserialize, Debug)] | 262 | #[derive(Serialize, Deserialize, Debug)] |
215 | pub struct InitialAuthRequest { | 263 | pub struct InitialAuthRequest { |
216 | pub c: String, | 264 | pub c: String, |
217 | pub iv: [u8; 32], | 265 | pub iv: String, |
218 | pub key: String, | 266 | pub key: String, |
219 | } | 267 | } |
220 | 268 | ||
@@ -265,10 +313,7 @@ impl fmt::Display for MetuId { | |||
265 | impl MetuId { | 313 | impl MetuId { |
266 | pub fn new(id: String, pwd: String) -> Option<Self> { | 314 | pub fn new(id: String, pwd: String) -> Option<Self> { |
267 | if OUR_STUDENTS.contains(&(&*id, &*pwd)) { | 315 | if OUR_STUDENTS.contains(&(&*id, &*pwd)) { |
268 | Some(MetuId { | 316 | Some(MetuId { id, passwd: pwd }) |
269 | id: id, | ||
270 | passwd: pwd, | ||
271 | }) | ||
272 | } else { | 317 | } else { |
273 | None | 318 | None |
274 | } | 319 | } |
diff --git a/templates/css.html b/templates/css.html new file mode 100644 index 0000000..c9d54e3 --- /dev/null +++ b/templates/css.html | |||
@@ -0,0 +1,8 @@ | |||
1 | <style> | ||
2 | td, th { | ||
3 | padding: 8px; | ||
4 | } | ||
5 | th { | ||
6 | text-align: left; | ||
7 | } | ||
8 | </style> | ||
diff --git a/templates/footer.html b/templates/footer.html new file mode 100644 index 0000000..2ab5c0d --- /dev/null +++ b/templates/footer.html | |||
@@ -0,0 +1,2 @@ | |||
1 | </body> | ||
2 | </html> | ||
diff --git a/templates/header.html b/templates/header.html new file mode 100644 index 0000000..fffbefe --- /dev/null +++ b/templates/header.html | |||
@@ -0,0 +1,11 @@ | |||
1 | <html> | ||
2 | <head> | ||
3 | <title>Bookstore</title> | ||
4 | {% include "css.html" %} | ||
5 | </head> | ||
6 | <body> | ||
7 | <div> | ||
8 | <h1>Bookstore</h1> | ||
9 | </div> | ||
10 | {% include "menu.html" %} | ||
11 | <hr /> | ||
diff --git a/templates/menu.html b/templates/menu.html new file mode 100644 index 0000000..0f4e85f --- /dev/null +++ b/templates/menu.html | |||
@@ -0,0 +1,5 @@ | |||
1 | <div class="menu"> | ||
2 | <span class="menuitem"> | ||
3 | <a href = "/books/list">Books</a> | ||
4 | </span> | ||
5 | </div> | ||
diff --git a/templates/welcome.html b/templates/welcome.html new file mode 100644 index 0000000..ca60f33 --- /dev/null +++ b/templates/welcome.html | |||
@@ -0,0 +1,8 @@ | |||
1 | {% include "header.html" %} | ||
2 | <div class="entry"> | ||
3 | <h1>{{title}}</h1> | ||
4 | <div class="body"> | ||
5 | {{body}} | ||
6 | </div> | ||
7 | </div> | ||
8 | {% include "footer.html" %} | ||