mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-05 11:11:37 -07:00
Compare commits
1240 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 02f11dfded | |||
| 303d86ab7e | |||
| 50374d9396 | |||
| dfffc57536 | |||
| e46e22e21b | |||
| 36627d7d29 | |||
| f745a1c470 | |||
| 17225131f7 | |||
| c8f9af64db | |||
| 0ef0894213 | |||
| dc6a8e5ffb | |||
| 5b5106baee | |||
| 4dac577caa | |||
| 8e9716ea2f | |||
| d7f6def8ad | |||
| 50b4a63fe1 | |||
| 2cb3028086 | |||
| 88598c2c64 | |||
| 97f1d12e04 | |||
| 4e1dd4ced2 | |||
| 1617060f46 | |||
| 10d354aea8 | |||
| f030603f0d | |||
| c07a46abc9 | |||
| c751e4b2eb | |||
| 1ade62b630 | |||
| cba58924a8 | |||
| f49575f1fb | |||
| 72a5e74ef3 | |||
| 881340fd3b | |||
| 451dc645df | |||
| 8a521648b9 | |||
| 5628cfabe5 | |||
| 31a9b3f428 | |||
| e12ff63b16 | |||
| 3a0ec3d6a8 | |||
| ffded3d557 | |||
| ae63fd9230 | |||
| 6d9957d7e4 | |||
| e737122ce8 | |||
| 8db87170a9 | |||
| ba1e9c8abe | |||
| 66acbb7fed | |||
| 0514ee0410 | |||
| 7bcf33c881 | |||
| 6bbe7d88b8 | |||
| 7058d6f320 | |||
| f86bb11f84 | |||
| 5391605961 | |||
| f6e699cd22 | |||
| eeb057b76d | |||
| 692ca13ffd | |||
| 9b78061c8f | |||
| ba8672ad29 | |||
| 223c987db2 | |||
| 74524b6050 | |||
| 23852c16be | |||
| 4c6b824658 | |||
| 01ad69413a | |||
| fa6e07d976 | |||
| d91054b615 | |||
| a1b8749e74 | |||
| 38fffff06a | |||
| 74ac2667e7 | |||
| fb6248925a | |||
| 2ecf909be0 | |||
| 75f1215ed2 | |||
| bdc4daca54 | |||
| 6d163cee8a | |||
| cfd4fc835b | |||
| 8421950546 | |||
| 5027cfb618 | |||
| 149ae70be1 | |||
| 64bdb3a097 | |||
| ef7a155f9a | |||
| 58ae96c821 | |||
| cfadca04f9 | |||
| 65a4a56a83 | |||
| fb22c59d1b | |||
| b945e3e708 | |||
| 1473374bba | |||
| 3526042f1e | |||
| e1c4b9b06a | |||
| ff34970d79 | |||
| ff94ca9e42 | |||
| 590db0838a | |||
| d698d4cdae | |||
| d99e1616cf | |||
| aa72050586 | |||
| b7cecc5762 | |||
| 5c06480ce0 | |||
| dc744411c5 | |||
| e2af3652c5 | |||
| 479d301e92 | |||
| 11e57fd3fd | |||
| 2a4ddbb7be | |||
| 3b739dc5cc | |||
| 0658778a30 | |||
| 1081e9ac09 | |||
| c43d189a33 | |||
| 4328562e2c | |||
| ee79c409b4 | |||
| d3bbe29faa | |||
| 410876b36a | |||
| f411fcedb0 | |||
| 043ddd83a9 | |||
| 7dc1f114b9 | |||
| f3f08c264f | |||
| 2f35ee9671 | |||
| 856537f0ce | |||
| bc706f8b0c | |||
| a68c635c00 | |||
| f600562ca5 | |||
| 5ed93563a1 | |||
| c49a291347 | |||
| f6ef395057 | |||
| e0d3d68ce5 | |||
| 46b3e7b6fa | |||
| a80011c830 | |||
| 542c3d7561 | |||
| f641399870 | |||
| 891fefe117 | |||
| 5473758467 | |||
| 8eb7fe1859 | |||
| 8dec4f6ed3 | |||
| b6d5be8593 | |||
| 1207d64c64 | |||
| a9629679ed | |||
| c091b0d060 | |||
| 58a1bb32e5 | |||
| 5b92425e93 | |||
| 7955f4ddf1 | |||
| 98f8624447 | |||
| e08b51ed73 | |||
| cf10c6d993 | |||
| f8c4ee3d3c | |||
| 419641ad8f | |||
| 6139da3d41 | |||
| b51eb53d04 | |||
| 5a76b3c7c5 | |||
| f1b291ed62 | |||
| f42b1c4973 | |||
| ab8e3db7e9 | |||
| 575b268e88 | |||
| 7cf62f00c3 | |||
| c9b1c99a40 | |||
| 32245e93a1 | |||
| 330d6117e3 | |||
| b2dc5cc812 | |||
| a344bc4c0e | |||
| e71cd871c4 | |||
| a93bbf6f92 | |||
| f9fd8f0c25 | |||
| 59c33dd9fc | |||
| 6f9dcb4e02 | |||
| 2b43535961 | |||
| 61826594c9 | |||
| 9a487ccb6f | |||
| 453fc5182b | |||
| 450b9ceaec | |||
| 48c1822709 | |||
| 91b7cdaad2 | |||
| 361be301fa | |||
| ecd7846ec3 | |||
| e05315831f | |||
| 7734b12f9a | |||
| 20f8f9012d | |||
| 0cf9dcd314 | |||
| 8fa20fb875 | |||
| 1b593fc1ca | |||
| 215ef3d985 | |||
| 8808e5584a | |||
| 54ebfa6179 | |||
| 4bdc8548bd | |||
| 6dc0ee3877 | |||
| 8d687a959d | |||
| 8b4a8e4d80 | |||
| 90f26368d9 | |||
| e4ec7cc3f3 | |||
| 34c8739b69 | |||
| d1217340f5 | |||
| 4eab735b13 | |||
| f8d235b6f5 | |||
| 7905f0533f | |||
| 3fb0d52a74 | |||
| 07a538cd82 | |||
| facae34354 | |||
| 5541f60233 | |||
| bde8407ce8 | |||
| 6298d4a36c | |||
| 3e181af409 | |||
| 88528f7613 | |||
| 5d82f0cad5 | |||
| 971201182a | |||
| 1935811b2c | |||
| 1183f4794b | |||
| 23545c3f37 | |||
| 41b8f06f51 | |||
| 55d21f4c2f | |||
| f8581537e4 | |||
| 0f76c22c46 | |||
| 6bfaf4a093 | |||
| f650690f61 | |||
| 321e4c45b0 | |||
| 9b759f39fc | |||
| 3a3ae6d0df | |||
| ad0053b798 | |||
| 8e263de4aa | |||
| 811d53b305 | |||
| aea28d5189 | |||
| 20752d904b | |||
| 33c6f75a2e | |||
| 2183140e71 | |||
| f428e30211 | |||
| 2250eb3e78 | |||
| aebb5a5178 | |||
| b4d0b1d17d | |||
| 20d35acce1 | |||
| 6c4672c158 | |||
| 3095f46d7e | |||
| 3add85e7a7 | |||
| ad5c9469d8 | |||
| 710bb4369f | |||
| a9b94f4019 | |||
| 944fdd11f2 | |||
| 07739b48ee | |||
| b269fd00f0 | |||
| 04c391074d | |||
| a45e69e656 | |||
| b72c2edabb | |||
| 273e4c6b4c | |||
| f1491280ab | |||
| fb2549a7da | |||
| 11180e36ae | |||
| 8c111f1baf | |||
| 5e149d2cae | |||
| 41bd8cf714 | |||
| 1604f37cb8 | |||
| 935865a978 | |||
| d7fdccb08c | |||
| 98827c867d | |||
| 8a1e7bb6fb | |||
| 8f706a14f8 | |||
| e5af98eabe | |||
| 858c78b844 | |||
| a61d8daec1 | |||
| cceac781e7 | |||
| 113feacbc7 | |||
| 7bcf3b1a0d | |||
| f54977336b | |||
| b0f0e4d02a | |||
| abcbb8b47a | |||
| d85c8f0b2c | |||
| 97e9c50d11 | |||
| 9b75656400 | |||
| 60b6c798a5 | |||
| ad3675cdd6 | |||
| 9d1db48549 | |||
| b0b60fe7ed | |||
| 87b099b5aa | |||
| 77efdcd899 | |||
| 58c0abb98d | |||
| d93cc18dac | |||
| ecddf3b7f1 | |||
| a3821615dc | |||
| f9a7d2bf04 | |||
| c219ca5b1b | |||
| f64e8ffd64 | |||
| e3e04ffa22 | |||
| 043dd98e91 | |||
| b91e7f5c51 | |||
| bb58926b7a | |||
| 1d3c061add | |||
| de5ce90fa7 | |||
| 3f7a369aa1 | |||
| 94b4af5d36 | |||
| e378516011 | |||
| 2b1afa0762 | |||
| 0cec2d7827 | |||
| 8d96c68528 | |||
| 0da6a3e214 | |||
| 41d38bf964 | |||
| 05a1704898 | |||
| 1818df786d | |||
| b23f1dd5ff | |||
| 96abe8627c | |||
| 6ec7845249 | |||
| 56b62996c3 | |||
| 6cb0f5d8c5 | |||
| df53fec828 | |||
| 16fbfd0418 | |||
| a9b18c1ec0 | |||
| 8e4f9cb777 | |||
| 15f7148eae | |||
| 1e5267c8e7 | |||
| 96a4cecee5 | |||
| 8ba36ca85c | |||
| eaab17f8fb | |||
| 1165d65c94 | |||
| ee818304f3 | |||
| 30ff15e538 | |||
| 17a01a81d8 | |||
| 595726e6c5 | |||
| fed03e896c | |||
| 2201f365a1 | |||
| f2505b096d | |||
| 242307dd44 | |||
| 8aab395c70 | |||
| 8506e64345 | |||
| 1d3845abf5 | |||
| ecaa26710b | |||
| 9de9223578 | |||
| 021271091a | |||
| 7feff1c113 | |||
| 25f1d0906d | |||
| 67b786c738 | |||
| fd582015fe | |||
| 58e12f0b17 | |||
| 9ffa4a4eb1 | |||
| eb06cb5d23 | |||
| 5c1201fa42 | |||
| c0ca26ac17 | |||
| b41395e3b4 | |||
| d40c207fde | |||
| 8660718ebe | |||
| 485afb011c | |||
| 9e94e9dac1 | |||
| cebe6bd982 | |||
| c58da76562 | |||
| e896efdaeb | |||
| ff0973e0f5 | |||
| ae322739b5 | |||
| f6345d20f7 | |||
| 227aab8f39 | |||
| d6824ea607 | |||
| 2f88ded81a | |||
| bbebb4ad60 | |||
| 700f9c94bd | |||
| c712de0cd0 | |||
| 1f5f417116 | |||
| a573161abd | |||
| f1c13d6bd8 | |||
| a37826e646 | |||
| 3b4169c903 | |||
| 0eb97dba6e | |||
| fa5e6d2d84 | |||
| 4668dafa2b | |||
| cc5faecced | |||
| 0530f86a48 | |||
| 4fea5720a5 | |||
| 30cfdd73b0 | |||
| 950fd2d5ce | |||
| 74d75fb7fb | |||
| 7ee2c1084f | |||
| c0b7dd7da6 | |||
| be09584ea5 | |||
| 5ac8e8f9fd | |||
| 2eea06fc73 | |||
| 0fa269811a | |||
| 6c185a5dca | |||
| f428f26c8e | |||
| 44c9b988bb | |||
| 29987c20c0 | |||
| 51d4dde63c | |||
| 362aa82f59 | |||
| ebfbe4d260 | |||
| e71cf96bbc | |||
| 383c17e842 | |||
| 00b1a41540 | |||
| 8fd318ea7a | |||
| 1deb53cddb | |||
| 05b5b8503d | |||
| 5e1b376e22 | |||
| 309a770cbf | |||
| 393e4a9db6 | |||
| efae99971b | |||
| 4454849252 | |||
| c752d0b541 | |||
| 5969796fbf | |||
| 6520794764 | |||
| 9d3f835e31 | |||
| 70aa2ff90a | |||
| a60a7f64b9 | |||
| 2368014d06 | |||
| 92402a75f6 | |||
| 4bea5dbac1 | |||
| 2b26384b89 | |||
| c685b404ea | |||
| a3e77dcc3b | |||
| 7305478261 | |||
| 94e0707456 | |||
| f95bb77f72 | |||
| 6bbdbb4447 | |||
| 22fa420c4f | |||
| 9a008d10f3 | |||
| 3000508c02 | |||
| d3bd1c2110 | |||
| f745fff640 | |||
| c1ff7894df | |||
| 8f4e3b552e | |||
| 1ea353dbf7 | |||
| b6eaadb9a8 | |||
| f53abf736b | |||
| 4c9c0899a9 | |||
| 2b8836e29a | |||
| 101abb3004 | |||
| ce80c37c75 | |||
| 642391eb49 | |||
| 8ffe577a15 | |||
| e3d41f0a9e | |||
| 3b53c99576 | |||
| a4cec2e8b8 | |||
| e8c5bb4629 | |||
| c72a269b7c | |||
| 94d35d9918 | |||
| 448827c0e4 | |||
| 04278cd654 | |||
| 15942fbf25 | |||
| 214537b420 | |||
| c330e5b5c1 | |||
| ec5a84a537 | |||
| 5b6c96cd18 | |||
| e2fa9be7e2 | |||
| a6eb64df6c | |||
| 9cdb7b2584 | |||
| d1a1e8f646 | |||
| 75fcd50b9a | |||
| c6a446fe87 | |||
| 90369b669b | |||
| f5719ae47c | |||
| 384137d4e9 | |||
| 1b15d8b453 | |||
| 2a70faca14 | |||
| 902e877467 | |||
| caa8863c97 | |||
| ebb1a07dd0 | |||
| 0a16f7a6f1 | |||
| 3620e62ff7 | |||
| d84b29866f | |||
| 50cbb802b7 | |||
| 68f663ea85 | |||
| efc84cd390 | |||
| ea49331799 | |||
| 059175de3b | |||
| 7b95562827 | |||
| c44f618b7d | |||
| 60ef4fda4d | |||
| a23c378513 | |||
| dd95a8d11d | |||
| 1b143b9fa3 | |||
| ec7e75a57f | |||
| 20223345a4 | |||
| 3812d5e002 | |||
| 07fc07822d | |||
| 41d4dfbcd5 | |||
| 2416380f34 | |||
| 5200e3fe7a | |||
| b72d49ceb5 | |||
| 689982a38d | |||
| 334e08c750 | |||
| f84a092977 | |||
| aad41fc762 | |||
| ece78d22d2 | |||
| 5c52f3cf2f | |||
| 889cd5461c | |||
| 6127339c56 | |||
| 38640d5d9e | |||
| e296b050ee | |||
| 9bbe26f80f | |||
| 7cd2bc26f4 | |||
| 2055f2b933 | |||
| 80067a959b | |||
| 6dcf38b85b | |||
| a61ae7c861 | |||
| 4558f3992a | |||
| 8a0f9ae108 | |||
| fd1eb17771 | |||
| 3e449e93dd | |||
| 436c5dc40c | |||
| 0729f2b4fb | |||
| 76229cabfa | |||
| a7e492e2c2 | |||
| 69db9c6390 | |||
| 6def86bb4b | |||
| 38c1fd5824 | |||
| d5e6d722d3 | |||
| 2af777b6cb | |||
| 5bda2546f7 | |||
| 36644a3c09 | |||
| 7e582dd671 | |||
| c7ac28f6a1 | |||
| 4bfa1ea76c | |||
| 758ace844c | |||
| b9d7b8d616 | |||
| a294f067a9 | |||
| 2e29687cee | |||
| a0e25be196 | |||
| a0cff82320 | |||
| d510d5e49f | |||
| 9591eb2ed5 | |||
| 21969fec61 | |||
| 6ee818b394 | |||
| b22e43c335 | |||
| 6dc9fdb718 | |||
| f252084b1f | |||
| 9f97056c14 | |||
| d2ca91b830 | |||
| 59705035f9 | |||
| befb0f7868 | |||
| d5bb67e9e6 | |||
| f086b4710a | |||
| a4d16e97a1 | |||
| 6348b56c8b | |||
| 2d1260bec9 | |||
| c753711d86 | |||
| 5726b7d954 | |||
| 4240e0025e | |||
| caa344c88d | |||
| 46b9bd8c0e | |||
| 3426af2d6c | |||
| efc3f9916e | |||
| 95bc1892f5 | |||
| 4cdadeedc3 | |||
| cb0a57eaa9 | |||
| aab122ede8 | |||
| 503964c769 | |||
| b2e887be36 | |||
| f36fb6d1ef | |||
| 45d7a670ce | |||
| 86e82dc182 | |||
| b6d2ac11b8 | |||
| 6c38ae3658 | |||
| 532d477705 | |||
| 0abbf3ba0a | |||
| 330c161625 | |||
| 97db075e45 | |||
| 730f992bff | |||
| 95a38d5a96 | |||
| 13e459980b | |||
| 3b5e515a22 | |||
| 3ad95d6be3 | |||
| abd5fc606c | |||
| 6678242280 | |||
| fd487f66bc | |||
| d0dc7395e9 | |||
| 2ba2bc1fec | |||
| 97e2d8916a | |||
| 3dfd61dd4f | |||
| 0f39b9ef22 | |||
| af67cc7afe | |||
| 53558313d8 | |||
| 8a6a3e883c | |||
| af6d2480fa | |||
| c644f79573 | |||
| 219eb58c08 | |||
| 2207a01494 | |||
| 60478c0e07 | |||
| 9b496e82f3 | |||
| fa1d681e65 | |||
| 893c3bef9a | |||
| d96f8bb5c1 | |||
| 563c527419 | |||
| fec42a6d74 | |||
| 4defe88dec | |||
| 36f3001d59 | |||
| 1ce279e6a1 | |||
| c951cbdbbc | |||
| 31aba5a728 | |||
| 9a418fd27b | |||
| f9b9521bf0 | |||
| b155fdf8c9 | |||
| a43a5b053c | |||
| a14f0d8f58 | |||
| 25f781d64b | |||
| 9933ca8b56 | |||
| 9c958ee66d | |||
| a00d96c0de | |||
| ecac3aef32 | |||
| ec45504631 | |||
| 810cf22e5d | |||
| ef82f3e71e | |||
| 16dc79fc5c | |||
| 2aca370a0a | |||
| ce196529dc | |||
| 9d8bb7f4e4 | |||
| 667883b3d5 | |||
| 114ddc4a10 | |||
| 0df9c8bffc | |||
| 0a73ed0799 | |||
| a71cd07b36 | |||
| 2bb20a2c1c | |||
| 352d526b94 | |||
| b150f49c46 | |||
| d83532d29e | |||
| 83d3bc74b0 | |||
| fbf01543d1 | |||
| 8a98ee800a | |||
| f72e3f689b | |||
| cd6153a125 | |||
| fde4e8c905 | |||
| ca46484ae3 | |||
| 280adc8a85 | |||
| a45721e9ad | |||
| 8d399fa7c0 | |||
| 1422e5bc26 | |||
| 133b45d843 | |||
| 510574aa7f | |||
| 3fd3bf2d4d | |||
| fbff2b4fd6 | |||
| 7f0ca315b3 | |||
| 31407d9b1b | |||
| 83e2f5ff74 | |||
| 2375ac22a7 | |||
| b979fd4c43 | |||
| 0e5a482c42 | |||
| 1af84e082c | |||
| d248cddf90 | |||
| a7056f2b4f | |||
| 62cebe1bc9 | |||
| 99aff7e3fb | |||
| 5339e1e1b6 | |||
| 5fe0788cff | |||
| e42b16b106 | |||
| a1d9442380 | |||
| b754c1e072 | |||
| 3a0df80066 | |||
| a9bfb3ac2e | |||
| 692a47f080 | |||
| 21fcf7c874 | |||
| 05d07983c3 | |||
| fc30bde0f6 | |||
| a8d8696e2f | |||
| 66f9ef9a00 | |||
| fdd4ef3c59 | |||
| f9f6fbfe1f | |||
| 4fc96e1ca5 | |||
| cfc85fd737 | |||
| 6b7e9f0a2d | |||
| b3580fa63a | |||
| 406aa587e2 | |||
| b2f432c223 | |||
| 7c2352ea08 | |||
| 9647cb74ad | |||
| bb72ff9c35 | |||
| 397c2aa201 | |||
| b47d73c651 | |||
| 3ee9184537 | |||
| 2938def707 | |||
| 84686c70c5 | |||
| 3fa02427b3 | |||
| b862691d75 | |||
| f482cc8374 | |||
| 030b8d3f66 | |||
| 0d8dadb084 | |||
| d0674cb70f | |||
| 3975b5e736 | |||
| 2bdf5f58ef | |||
| 8e1a2094a7 | |||
| 9271372fef | |||
| 4edcaa6124 | |||
| d84d65ba45 | |||
| 28fb1ba83d | |||
| db95ed7cdd | |||
| e6a552e0ce | |||
| 915f63b3f9 | |||
| e1b545860f | |||
| 8899f42478 | |||
| 13b8a5b73d | |||
| b84a660806 | |||
| d325c8ad23 | |||
| b7fd51a251 | |||
| 7325120ca9 | |||
| 9a5a39c07d | |||
| 9b43aa3c94 | |||
| 63f30a8207 | |||
| 308e9112f2 | |||
| 58d0635f48 | |||
| 2ccb326a41 | |||
| cf9bdc134c | |||
| 29e4e85152 | |||
| a422aba578 | |||
| 7b387fd3aa | |||
| 12ec9bce7d | |||
| b378eb2df3 | |||
| 6422c090f2 | |||
| 3bc7c51325 | |||
| f57cc6beb1 | |||
| 2031d2769a | |||
| 83c9739a7d | |||
| d9ba698b7b | |||
| 19d566ebc5 | |||
| 92a5666c1c | |||
| 63d6b4752b | |||
| 6588fe35b3 | |||
| fa84272d5e | |||
| c90a6ab0d5 | |||
| 0989eac681 | |||
| b8c0a02164 | |||
| df526549e2 | |||
| 1009182f7b | |||
| 620dd2c812 | |||
| 533ba63c82 | |||
| 2165303f5e | |||
| c59f6d548b | |||
| 8eed65ad4c | |||
| bc0a2b8d39 | |||
| a405c72d4d | |||
| b26a685b76 | |||
| 3a8971c260 | |||
| ed742efc33 | |||
| 06ee9117d2 | |||
| e100d9264f | |||
| 53b02c5e6f | |||
| 50618759a6 | |||
| 600ca83386 | |||
| e5629dafd0 | |||
| a2167587ae | |||
| b2a35683a4 | |||
| dcc263c618 | |||
| 3b34cf6dbb | |||
| ae9f026f4b | |||
| 46093bad1e | |||
| c843002d07 | |||
| 6d4f30de1a | |||
| 68ff945419 | |||
| cab53e12b7 | |||
| d92767cb77 | |||
| 670fc70f1f | |||
| 223a336eb4 | |||
| 1ea1229516 | |||
| 6da6393735 | |||
| 87688936c6 | |||
| 590e52cc05 | |||
| 398af659e5 | |||
| 2e416c1a48 | |||
| 34247b2658 | |||
| 45d7dd8f27 | |||
| 09d7f1337b | |||
| 0f944bc0a1 | |||
| 07a9969a41 | |||
| 15c1e8274d | |||
| 517d7ae0b0 | |||
| efb0933965 | |||
| eb51cc6835 | |||
| 1d46adb598 | |||
| 61aa4558dc | |||
| 194d3fe6bd | |||
| 624b870f28 | |||
| eb61cf6043 | |||
| 7abb459861 | |||
| e9ac7d5fab | |||
| bb5dab342a | |||
| 7d6e9ef39c | |||
| d47ba09743 | |||
| 5c9da66595 | |||
| 9cbb9d8551 | |||
| 6b6f72e7bd | |||
| c8cdb22f0b | |||
| 6e2efce670 | |||
| 6d7afd3fa9 | |||
| 006ea11c56 | |||
| 010ffc486c | |||
| 29e5f94c2a | |||
| 6a0d2ffcac | |||
| fc587c507a | |||
| 65cb382135 | |||
| 8f0d74c08d | |||
| 800014e40c | |||
| fda283c55e | |||
| f39b34a8b7 | |||
| 1cc5e23801 | |||
| 8e0806be2d | |||
| c17adc98f5 | |||
| d381b77164 | |||
| e804fbd891 | |||
| ed5fd15f6e | |||
| 89ca15014c | |||
| a5efcfdd78 | |||
| 9c144dd24f | |||
| c0f9e8d6a3 | |||
| f312adb26a | |||
| 9c879e5e17 | |||
| b4fe9677a1 | |||
| 1951bc45a6 | |||
| ba77a9464c | |||
| b1dae2d59a | |||
| 1514d64964 | |||
| e4c502e79b | |||
| 553a244fec | |||
| 3bff922b6f | |||
| 56004c56fc | |||
| 863ee0b8d1 | |||
| 89ba8b4139 | |||
| d0b3b18e26 | |||
| cd7137af60 | |||
| 18fb665bf6 | |||
| a750fc0ba6 | |||
| a555862522 | |||
| 433109ff52 | |||
| ba428fecdb | |||
| 17cc7a2c5e | |||
| 9a2fc6cf4c | |||
| 8ff788f4ff | |||
| 80ecf5b68a | |||
| 5159f30c9c | |||
| d5daa9fda5 | |||
| 89315986d4 | |||
| 096b4f5454 | |||
| 65e68f11f8 | |||
| ec9ac59323 | |||
| 97be7feb99 | |||
| 00344e1323 | |||
| dd92d3054d | |||
| 07ef969546 | |||
| 72c15d7699 | |||
| 009b6f44e3 | |||
| ebd3065aa2 | |||
| 6cb74b63ec | |||
| effc71ca43 | |||
| 43d695a990 | |||
| 19eb2a8890 | |||
| 3875fb08e8 | |||
| 9c455b2213 | |||
| 04f0fbf23a | |||
| 51d6a254cf | |||
| 835effdef1 | |||
| c7bec2962e | |||
| ac634acd4b | |||
| 34c4389d75 | |||
| 8917a7ef88 | |||
| 45d5066029 | |||
| b47eeac414 | |||
| 5cc936245c | |||
| e68d3c8bbc | |||
| d7b1901b16 | |||
| 97d6bf6d8f | |||
| 271f84ab5b | |||
| 75b60fdb12 | |||
| 55141bdbb1 | |||
| 5f2496226f | |||
| dee66b8451 | |||
| 4aec2de7bd | |||
| ac10f504e4 | |||
| 26d107ce64 | |||
| d62c92f5a9 | |||
| 5c1646a6b3 | |||
| b539bb2693 | |||
| 7bc962b852 | |||
| f7ec628cb2 | |||
| 23f3898b4e | |||
| dd74a57c44 | |||
| 51a4d97f03 | |||
| d979129a50 | |||
| d6353daf91 | |||
| c7c6852057 | |||
| 9045c9ed0c | |||
| b543be0d15 | |||
| 47837b206e | |||
| 9a41a450e7 | |||
| f20d0202fa | |||
| 8721d03cfe | |||
| d06575dd49 | |||
| 568b07473f | |||
| 04666746d7 | |||
| b2e7eb4db4 | |||
| f88af59372 | |||
| a974c08aff | |||
| fc9d3ec5a7 | |||
| db03c86544 | |||
| 51d12acab3 | |||
| 032e193d0d | |||
| 0f83ab466f | |||
| 53ce945034 | |||
| 0e168dd292 | |||
| 5ff6a25bdc | |||
| b6b8e640f5 | |||
| 573402c97e | |||
| e01701ce0e | |||
| f9b14500be | |||
| 4bb7cd5e4b | |||
| 6d5b1800fe | |||
| 0f5328973e | |||
| 65277ddd6b | |||
| 46718d6d85 | |||
| 2a0543d2f0 | |||
| 792f06a234 | |||
| 14f7619cdc | |||
| ffa3669d02 | |||
| b1903b915b | |||
| a7bab89c93 | |||
| a1ee9e7035 | |||
| c05a92ab3e | |||
| 5789df5c0c | |||
| 78c05a49a3 | |||
| 8d496dc3d0 | |||
| 703eab2f15 | |||
| 04bfc926ee | |||
| 791c9c16ba | |||
| 6d3a57b4ac | |||
| c7b8f1fb0a | |||
| 462f3f4abd | |||
| 5eef29290c | |||
| a944aa406d | |||
| 8c6c2543da | |||
| 7ae22b7ce5 | |||
| 3e295734cb | |||
| de41c2256d | |||
| 9a5f3fed8c | |||
| 04bd3cc525 | |||
| 9fc8587e2c | |||
| 2a39c60fe4 | |||
| b11d839fb6 | |||
| 0076c07076 | |||
| a664d9804f | |||
| c4e45d509a | |||
| c1cf6c1a95 | |||
| 317931a3c5 | |||
| 759d095548 | |||
| 7854aa22d9 | |||
| 72d5b87b52 | |||
| 154e0d41c6 | |||
| 3fccc77829 | |||
| 85f1a60cf3 | |||
| cb8bb8bfd8 | |||
| 46b58ad48d | |||
| 7283b5e75c | |||
| 21e6e28a3a | |||
| d77ae9abab | |||
| 363769d4d3 | |||
| b6c47e7963 | |||
| 5533a327eb | |||
| b419615002 | |||
| c044e295ce | |||
| 87ab3f5dc8 | |||
| fa8dd5a13b | |||
| b818ebc02f | |||
| c04a53e453 | |||
| 15287a771f | |||
| 2db31cf0d5 | |||
| 550e798c1b | |||
| 290168f862 | |||
| 2f2a92866b | |||
| 116edb9052 | |||
| 9070e613a4 | |||
| 6243160ecd | |||
| 980ee3310f | |||
| c5d5d055ac | |||
| a783d4ce5b | |||
| a7b09a24b7 | |||
| 602e000379 | |||
| f93074a082 | |||
| 28f05e8aaa | |||
| 9eb1596939 | |||
| 8412352e5a | |||
| 4c35633e01 | |||
| b1371d5c7a | |||
| 3ada83a503 | |||
| 50ded324e0 | |||
| 674bd94f72 | |||
| ff24cfe314 | |||
| bc191d4f84 | |||
| 5c20676c76 | |||
| 11b7be1640 | |||
| 6ae8288142 | |||
| 7f1069cb0b | |||
| 41fbaaf1d3 | |||
| f14babe419 | |||
| 0bb2327d76 | |||
| f92042e7dd | |||
| 1861ecff86 | |||
| d4d9f55556 | |||
| 23cd64ec35 | |||
| 6734a7096a | |||
| 3263e54144 | |||
| 6805b85f89 | |||
| 88c97cbbd0 | |||
| 17b1c8d338 | |||
| 0a5c85f8e3 | |||
| 9c20df5b1b | |||
| 5d6b4eda1e | |||
| 290ada7f90 | |||
| bf37bee4f7 | |||
| 7df7fec6f7 | |||
| e423a6d692 | |||
| f4c74a9f32 | |||
| dce176fa39 | |||
| d666ff3744 | |||
| 1678391db3 | |||
| d1174f66bc | |||
| 84a7e03178 | |||
| 1bffc8baac | |||
| c558b694eb | |||
| a06bde729b | |||
| 5107bdc06b | |||
| 5137d12d36 | |||
| c6ee025063 | |||
| 826cf1508b | |||
| 51edf01162 | |||
| 87f6f3a1fe | |||
| fca691a066 | |||
| 494cac02d7 | |||
| 38c69b9691 | |||
| deb8922181 | |||
| cdda8f0eb9 | |||
| cba8cb2201 | |||
| ae67f37fc1 | |||
| 8d4bb35bcc | |||
| 180902468f | |||
| 7290c69257 | |||
| 3ebf5dcc0e | |||
| 9d4c2ba0d8 | |||
| 1d79521e81 | |||
| 31d7ef5696 | |||
| 8c621453ae | |||
| c712af3bc2 | |||
| f0a89f8d5d | |||
| 9b839621a9 | |||
| 66456714e1 | |||
| fc8e5b8a61 | |||
| 05cfc89283 | |||
| dc5cf23066 | |||
| 2e80879e88 | |||
| dfa0ccf976 | |||
| 6c16860be6 | |||
| d032cff23b | |||
| c3746d9f56 | |||
| 0fa6ba726f | |||
| b03f17dd8d | |||
| 24c8b15409 | |||
| 028a79e66c | |||
| 0a6673220b | |||
| 88add0b6b1 | |||
| 8a78b0d241 | |||
| 824c831190 | |||
| 874d6ccb60 | |||
| db3279e7da | |||
| 9251e06143 | |||
| a642ea0b28 | |||
| 00fc4c4e1b | |||
| 9ea11da00f | |||
| a8909f06ae | |||
| ee543a304a | |||
| 5c5b78cbbe | |||
| 54515af8cc | |||
| b894df2125 | |||
| 3f193d7446 | |||
| e127d21fed | |||
| 3060225f46 | |||
| c14b9d461a | |||
| 8060e3170e | |||
| 980947b355 | |||
| fd987b29ff | |||
| afcc2889ec | |||
| 5f001f6d51 | |||
| fc07211772 | |||
| c9e37a08be | |||
| dcbcf75269 | |||
| 59479b2ba6 | |||
| 007bd993a6 | |||
| 5a84a08225 | |||
| bb23648c71 | |||
| a4b8c1ea17 | |||
| 20e75cd463 | |||
| d74ddb2d91 | |||
| ec0831da70 | |||
| b12ba1508e | |||
| f3b0b82461 | |||
| fea38f6910 | |||
| 213b2fd0f8 | |||
| 42d595f6a1 | |||
| 656de10ba4 | |||
| f6f3a760bc | |||
| bafc791f1c | |||
| 1fc256dd90 | |||
| 41549b502b | |||
| f989f4df3e | |||
| 21d1c0e5a4 | |||
| d7b7804215 | |||
| 8472338bfa | |||
| 1de1d97429 | |||
| f21ead4f78 | |||
| a95cead8e7 | |||
| c276b1c0bc | |||
| b39ef29ec6 | |||
| b2b8b14955 | |||
| 7e0473dded | |||
| e6bd79c882 | |||
| 9e417592e3 | |||
| db87c34613 | |||
| 159257597a | |||
| bdd6a86d73 | |||
| 1ab3ec5731 | |||
| 004cd4933d | |||
| 14ee2181cb | |||
| b43c1f2b5b | |||
| b1e608bfba | |||
| 57c5455827 | |||
| cc185d98b4 | |||
| cd61251d39 | |||
| 43a199b57b | |||
| fa1875a8f1 | |||
| 44aae76294 | |||
| 009cebe8bf | |||
| 9399cb53b6 | |||
| bed81c8829 | |||
| 7a592cbb31 | |||
| c178f7199d | |||
| a4d3777ab2 | |||
| e931c82b9b | |||
| a7f0275093 | |||
| 9ab2fbea00 | |||
| 7fe86ed565 | |||
| 16584779fb | |||
| c5c2a4d09d | |||
| 912c326d8b | |||
| d496dbf3a0 | |||
| 88e6980b13 | |||
| 0d413b8136 | |||
| 32a4a5e601 | |||
| 91cf0826c1 | |||
| 3bc172c70b | |||
| cbfb32c34c | |||
| 5cd57a3a7f | |||
| 563ce2bd81 | |||
| 9469b90b01 | |||
| d2e1ec13f0 | |||
| 6df07a2c0a | |||
| c0d407a2b4 | |||
| f378367fe7 | |||
| c291fffce1 | |||
| 900fe95ba0 | |||
| 5dd2e38aff | |||
| 5ad8ebba9f | |||
| e7719eed65 | |||
| 5f49b8997a | |||
| d6734c85b8 | |||
| 4ca23499fa | |||
| e201a2fabb | |||
| 5c4485f657 | |||
| bae2a6fc1e | |||
| 8777fa1c04 | |||
| d771b3286d | |||
| 0e6dd56ac1 | |||
| 9127e584c5 | |||
| eab0e730a0 | |||
| 7128bc4b34 | |||
| 7b672ac72a | |||
| 8f4ab3c07d | |||
| dc5cfacc0a | |||
| 3aea2e1fff | |||
| 59933b244d | |||
| 42aca72d9f | |||
| 7034a31745 | |||
| 9d0786ded7 | |||
| 4c51706941 | |||
| a8c4f46747 | |||
| 8e367d221b | |||
| a08250ac5b | |||
| 131c71ea76 | |||
| 9db04fe446 | |||
| abaa906f34 | |||
| fa093d9b09 | |||
| 4035a226da | |||
| df5ee2dd06 | |||
| 9b4cd0743c | |||
| 3f090ed523 | |||
| 79173ef119 | |||
| 44758f2a60 | |||
| 1f0b24b735 | |||
| 327853ab92 | |||
| a6878fcd91 | |||
| 61202b5abd | |||
| 56516ee8b2 | |||
| 1cc639bc93 | |||
| 2357293e05 | |||
| 7a8b1669b0 | |||
| bc160d2eb7 | |||
| b02b85d242 | |||
| 1d151c4c92 | |||
| 899fab64d9 | |||
| 00ab3beed9 | |||
| ca10fc2d47 | |||
| 1ed6a484ac | |||
| b0675358c3 | |||
| 456dd22dc4 | |||
| 02b8fefa30 | |||
| 95808fd2e7 | |||
| e63d71c23b | |||
| 23a49ce65b | |||
| ca6300c860 | |||
| 1ebb7f8e9d | |||
| 4685cf1085 | |||
| 80a0b161b0 | |||
| 27feb5998c | |||
| e88db2818b | |||
| 19535d8721 | |||
| 8ea8fbeccb | |||
| 4e300ff90a | |||
| 7bf25a4cf2 | |||
| 5f0f9f477e | |||
| dd7c65a89c | |||
| 8620281191 | |||
| c311a60aaf | |||
| 2993490c75 | |||
| 60b324d4ed | |||
| 89a2fdd106 | |||
| 8530829ca4 | |||
| 0e5d7801dd | |||
| d5db49a3b0 | |||
| 606f11ebe8 | |||
| 07a4f4d017 | |||
| d463adfe4f | |||
| 16bd19ed43 | |||
| eb811e8341 | |||
| 8a63b6dff7 | |||
| 829654e3a2 | |||
| cec08114fd | |||
| bf90b61908 | |||
| ae0a5b1a87 | |||
| 55ff9b857e | |||
| e7122a0950 | |||
| 301b9eef31 | |||
| 36ed8fe55d | |||
| 7b9ea9eca0 | |||
| 89404147f6 | |||
| 29d4bdc80b | |||
| 594078cc1d | |||
| ab68e4c6c4 | |||
| 4956f9dad3 | |||
| fd090c6672 | |||
| b46b3010ec | |||
| f93f2dfd5c | |||
| 9ac619f4a2 | |||
| 239d4e1076 | |||
| 99825a28d7 | |||
| 144504a752 | |||
| 59aa05170d | |||
| 0b2f357bbe | |||
| 9a1c4dc56d | |||
| 7eabeda870 |
+5
-1
@@ -1,2 +1,6 @@
|
||||
*.odin linguist-language=Odin
|
||||
* text=auto
|
||||
* text=auto
|
||||
|
||||
# These files must always have *nix line-endings
|
||||
Makefile text eol=lf
|
||||
*.sh text eol=lf
|
||||
+86
-13
@@ -2,12 +2,49 @@ name: CI
|
||||
on: [push, pull_request, workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
build_netbsd:
|
||||
name: NetBSD Build, Check, and Test
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PKGSRC_BRANCH: 2024Q1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build, Check, and Test
|
||||
timeout-minutes: 25
|
||||
uses: vmactions/netbsd-vm@v1
|
||||
with:
|
||||
release: "10.0"
|
||||
envs: PKGSRC_BRANCH
|
||||
usesh: true
|
||||
copyback: false
|
||||
prepare: |
|
||||
PKG_PATH="https://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r | cut -d_ -f1)_${PKGSRC_BRANCH}/All" /usr/sbin/pkg_add pkgin
|
||||
pkgin -y in gmake git bash python311
|
||||
pkgin -y in libxml2 perl zstd
|
||||
/usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/llvm-17.0.6.tgz
|
||||
/usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/clang-17.0.6.tgz
|
||||
ln -s /usr/pkg/bin/python3.11 /usr/bin/python3
|
||||
ln -s /usr/pkg/bin/bash /bin/bash
|
||||
run: |
|
||||
git config --global --add safe.directory $(pwd)
|
||||
gmake release
|
||||
./odin version
|
||||
./odin report
|
||||
./odin check examples/all -vet -strict-style -target:netbsd_amd64
|
||||
(cd tests/core; gmake all_bsd)
|
||||
(cd tests/internal; gmake all_bsd)
|
||||
(cd tests/issues; ./run.sh)
|
||||
build_linux:
|
||||
name: Ubuntu Build, Check, and Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Download LLVM, botan
|
||||
run: sudo apt-get install llvm-11 clang-11 libbotan-2-dev botan
|
||||
- name: Download LLVM
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
echo "/usr/lib/llvm-17/bin" >> $GITHUB_PATH
|
||||
- name: build odin
|
||||
run: ./build_odin.sh release
|
||||
- name: Odin version
|
||||
@@ -46,6 +83,9 @@ jobs:
|
||||
- name: Odin check examples/all for Linux i386
|
||||
run: ./odin check examples/all -vet -strict-style -target:linux_i386
|
||||
timeout-minutes: 10
|
||||
- name: Odin check examples/all for Linux arm64
|
||||
run: ./odin check examples/all -vet -strict-style -target:linux_arm64
|
||||
timeout-minutes: 10
|
||||
- name: Odin check examples/all for FreeBSD amd64
|
||||
run: ./odin check examples/all -vet -strict-style -target:freebsd_amd64
|
||||
timeout-minutes: 10
|
||||
@@ -53,15 +93,14 @@ jobs:
|
||||
run: ./odin check examples/all -vet -strict-style -target:openbsd_amd64
|
||||
timeout-minutes: 10
|
||||
build_macOS:
|
||||
runs-on: macos-latest
|
||||
name: MacOS Build, Check, and Test
|
||||
runs-on: macos-13
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Download LLVM, botan and setup PATH
|
||||
- name: Download LLVM, and setup PATH
|
||||
run: |
|
||||
brew install llvm@13 botan
|
||||
echo "/usr/local/opt/llvm@13/bin" >> $GITHUB_PATH
|
||||
TMP_PATH=$(xcrun --show-sdk-path)/user/include
|
||||
echo "CPATH=$TMP_PATH" >> $GITHUB_ENV
|
||||
brew install llvm@17
|
||||
echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH
|
||||
- name: build odin
|
||||
run: ./build_odin.sh release
|
||||
- name: Odin version
|
||||
@@ -92,13 +131,47 @@ jobs:
|
||||
cd tests/internal
|
||||
make
|
||||
timeout-minutes: 10
|
||||
- name: Odin check examples/all for Darwin arm64
|
||||
run: ./odin check examples/all -vet -strict-style -target:darwin_arm64
|
||||
build_macOS_arm:
|
||||
name: MacOS ARM Build, Check, and Test
|
||||
runs-on: macos-14 # This is an arm/m1 runner.
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Download LLVM and setup PATH
|
||||
run: |
|
||||
brew install llvm@17
|
||||
echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH
|
||||
- name: build odin
|
||||
run: ./build_odin.sh release
|
||||
- name: Odin version
|
||||
run: ./odin version
|
||||
timeout-minutes: 1
|
||||
- name: Odin report
|
||||
run: ./odin report
|
||||
timeout-minutes: 1
|
||||
- name: Odin check
|
||||
run: ./odin check examples/demo -vet
|
||||
timeout-minutes: 10
|
||||
- name: Odin check examples/all for Linux arm64
|
||||
run: ./odin check examples/all -vet -strict-style -target:linux_arm64
|
||||
- name: Odin run
|
||||
run: ./odin run examples/demo
|
||||
timeout-minutes: 10
|
||||
- name: Odin run -debug
|
||||
run: ./odin run examples/demo -debug
|
||||
timeout-minutes: 10
|
||||
- name: Odin check examples/all
|
||||
run: ./odin check examples/all -strict-style
|
||||
timeout-minutes: 10
|
||||
- name: Core library tests
|
||||
run: |
|
||||
cd tests/core
|
||||
make
|
||||
timeout-minutes: 10
|
||||
- name: Odin internals tests
|
||||
run: |
|
||||
cd tests/internal
|
||||
make
|
||||
timeout-minutes: 10
|
||||
build_windows:
|
||||
name: Windows Build, Check, and Test
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
@@ -163,7 +236,7 @@ jobs:
|
||||
run: |
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||
cd tests\documentation
|
||||
rem call build.bat
|
||||
call build.bat
|
||||
timeout-minutes: 10
|
||||
- name: core:math/big tests
|
||||
shell: cmd
|
||||
|
||||
@@ -7,6 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build_windows:
|
||||
name: Windows Build
|
||||
if: github.repository == 'odin-lang/Odin'
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
@@ -40,12 +41,17 @@ jobs:
|
||||
name: windows_artifacts
|
||||
path: dist
|
||||
build_ubuntu:
|
||||
name: Ubuntu Build
|
||||
if: github.repository == 'odin-lang/Odin'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: (Linux) Download LLVM
|
||||
run: sudo apt-get install llvm-11 clang-11
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
echo "/usr/lib/llvm-17/bin" >> $GITHUB_PATH
|
||||
- name: build odin
|
||||
run: make nightly
|
||||
- name: Odin run
|
||||
@@ -61,27 +67,28 @@ jobs:
|
||||
cp -r core dist
|
||||
cp -r vendor dist
|
||||
cp -r examples dist
|
||||
# Zipping so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38
|
||||
zip -r dist.zip dist
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: ubuntu_artifacts
|
||||
path: dist
|
||||
path: dist.zip
|
||||
build_macos:
|
||||
name: MacOS Build
|
||||
if: github.repository == 'odin-lang/Odin'
|
||||
runs-on: macOS-latest
|
||||
runs-on: macos-13
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Download LLVM and setup PATH
|
||||
run: |
|
||||
brew install llvm@13
|
||||
echo "/usr/local/opt/llvm@13/bin" >> $GITHUB_PATH
|
||||
TMP_PATH=$(xcrun --show-sdk-path)/user/include
|
||||
echo "CPATH=$TMP_PATH" >> $GITHUB_ENV
|
||||
brew install llvm@17 dylibbundler
|
||||
echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH
|
||||
- name: build odin
|
||||
run: make nightly
|
||||
- name: Odin run
|
||||
run: ./odin run examples/demo
|
||||
- name: Copy artifacts
|
||||
# These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to
|
||||
# not link with libunwind bundled with LLVM but link with libunwind on the system.
|
||||
run: CXXFLAGS="-L/usr/lib/system -L/usr/lib" make nightly
|
||||
- name: Bundle
|
||||
run: |
|
||||
mkdir dist
|
||||
cp odin dist
|
||||
@@ -91,14 +98,53 @@ jobs:
|
||||
cp -r core dist
|
||||
cp -r vendor dist
|
||||
cp -r examples dist
|
||||
dylibbundler -b -x dist/odin -d dist/libs -od -p @executable_path/libs
|
||||
# Zipping so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38
|
||||
zip -r dist.zip dist
|
||||
- name: Odin run
|
||||
run: ./dist/odin run examples/demo
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: macos_artifacts
|
||||
path: dist
|
||||
path: dist.zip
|
||||
build_macos_arm:
|
||||
name: MacOS ARM Build
|
||||
if: github.repository == 'odin-lang/Odin'
|
||||
runs-on: macos-14 # ARM machine
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Download LLVM and setup PATH
|
||||
run: |
|
||||
brew install llvm@17 dylibbundler
|
||||
echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH
|
||||
- name: build odin
|
||||
# These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to
|
||||
# not link with libunwind bundled with LLVM but link with libunwind on the system.
|
||||
run: CXXFLAGS="-L/usr/lib/system -L/usr/lib" make nightly
|
||||
- name: Bundle
|
||||
run: |
|
||||
mkdir dist
|
||||
cp odin dist
|
||||
cp LICENSE dist
|
||||
cp -r shared dist
|
||||
cp -r base dist
|
||||
cp -r core dist
|
||||
cp -r vendor dist
|
||||
cp -r examples dist
|
||||
dylibbundler -b -x dist/odin -d dist/libs -od -p @executable_path/libs
|
||||
# Zipping so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38
|
||||
zip -r dist.zip dist
|
||||
- name: Odin run
|
||||
run: ./dist/odin run examples/demo
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: macos_arm_artifacts
|
||||
path: dist.zip
|
||||
upload_b2:
|
||||
runs-on: [ubuntu-latest]
|
||||
needs: [build_windows, build_macos, build_ubuntu]
|
||||
needs: [build_windows, build_macos, build_macos_arm, build_ubuntu]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-python@v2
|
||||
@@ -129,6 +175,11 @@ jobs:
|
||||
with:
|
||||
name: macos_artifacts
|
||||
|
||||
- name: Download macOS arm artifacts
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: macos_arm_artifacts
|
||||
|
||||
- name: Create archives and upload
|
||||
shell: bash
|
||||
env:
|
||||
@@ -138,13 +189,14 @@ jobs:
|
||||
DAYS_TO_KEEP: ${{ secrets.B2_DAYS_TO_KEEP }}
|
||||
run: |
|
||||
echo Authorizing B2 account
|
||||
b2 authorize-account "$APPID" "$APPKEY"
|
||||
b2 account authorize "$APPID" "$APPKEY"
|
||||
|
||||
echo Uploading artifcates to B2
|
||||
chmod +x ./ci/upload_create_nightly.sh
|
||||
./ci/upload_create_nightly.sh "$BUCKET" windows-amd64 windows_artifacts/
|
||||
./ci/upload_create_nightly.sh "$BUCKET" ubuntu-amd64 ubuntu_artifacts/
|
||||
./ci/upload_create_nightly.sh "$BUCKET" macos-amd64 macos_artifacts/
|
||||
./ci/upload_create_nightly.sh "$BUCKET" ubuntu-amd64 ubuntu_artifacts/dist.zip
|
||||
./ci/upload_create_nightly.sh "$BUCKET" macos-amd64 macos_artifacts/dist.zip
|
||||
./ci/upload_create_nightly.sh "$BUCKET" macos-arm64 macos_arm_artifacts/dist.zip
|
||||
|
||||
echo Deleting old artifacts in B2
|
||||
python3 ci/delete_old_binaries.py "$BUCKET" "$DAYS_TO_KEEP"
|
||||
|
||||
+10
-2
@@ -27,7 +27,10 @@ tests/documentation/all.odin-doc
|
||||
tests/internal/test_map
|
||||
tests/internal/test_pow
|
||||
tests/internal/test_rtti
|
||||
tests/core/test_base64
|
||||
tests/core/test_cbor
|
||||
tests/core/test_core_compress
|
||||
tests/core/test_core_container
|
||||
tests/core/test_core_filepath
|
||||
tests/core/test_core_fmt
|
||||
tests/core/test_core_i18n
|
||||
@@ -39,8 +42,10 @@ tests/core/test_core_net
|
||||
tests/core/test_core_os_exit
|
||||
tests/core/test_core_reflect
|
||||
tests/core/test_core_strings
|
||||
tests/core/test_crypto_hash
|
||||
tests/core/test_core_time
|
||||
tests/core/test_crypto
|
||||
tests/core/test_hash
|
||||
tests/core/test_hex
|
||||
tests/core/test_hxa
|
||||
tests/core/test_json
|
||||
tests/core/test_linalg_glsl_math
|
||||
@@ -49,6 +54,7 @@ tests/core/test_varint
|
||||
tests/core/test_xml
|
||||
tests/core/test_core_slice
|
||||
tests/core/test_core_thread
|
||||
tests/core/test_core_runtime
|
||||
tests/vendor/vendor_botan
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
@@ -316,4 +322,6 @@ build.sh
|
||||
!core/debug/
|
||||
|
||||
# RAD debugger project file
|
||||
*.raddbg
|
||||
*.raddbg
|
||||
|
||||
misc/featuregen/featuregen
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2016-2022 Ginger Bill. All rights reserved.
|
||||
Copyright (c) 2016-2024 Ginger Bill. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -126,3 +126,5 @@ clamp :: proc(value, minimum, maximum: T) -> T ---
|
||||
|
||||
soa_zip :: proc(slices: ...) -> #soa[]Struct ---
|
||||
soa_unzip :: proc(value: $S/#soa[]$E) -> (slices: ...) ---
|
||||
|
||||
unreachable :: proc() -> ! ---
|
||||
|
||||
@@ -38,9 +38,9 @@ count_leading_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_sim
|
||||
reverse_bits :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
|
||||
byte_swap :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) ---
|
||||
|
||||
overflow_add :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok ---
|
||||
overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok ---
|
||||
overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok ---
|
||||
overflow_add :: proc(lhs, rhs: $T) -> (T, bool) ---
|
||||
overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) ---
|
||||
overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) ---
|
||||
|
||||
sqrt :: proc(x: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) ---
|
||||
|
||||
@@ -167,17 +167,23 @@ type_is_matrix :: proc($T: typeid) -> bool ---
|
||||
|
||||
type_has_nil :: proc($T: typeid) -> bool ---
|
||||
|
||||
type_is_matrix_row_major :: proc($T: typeid) -> bool where type_is_matrix(T) ---
|
||||
type_is_matrix_column_major :: proc($T: typeid) -> bool where type_is_matrix(T) ---
|
||||
|
||||
type_is_specialization_of :: proc($T, $S: typeid) -> bool ---
|
||||
|
||||
type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) ---
|
||||
type_union_tag_type :: proc($T: typeid) -> typeid where type_is_union(T) ---
|
||||
type_union_tag_offset :: proc($T: typeid) -> uintptr where type_is_union(T) ---
|
||||
type_union_base_tag_value :: proc($T: typeid) -> int where type_is_union(U) ---
|
||||
type_union_variant_count :: proc($T: typeid) -> int where type_is_union(T) ---
|
||||
type_variant_type_of :: proc($T: typeid, $index: int) -> typeid where type_is_union(T) ---
|
||||
type_variant_index_of :: proc($U, $V: typeid) -> int where type_is_union(U) ---
|
||||
type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) ---
|
||||
type_union_tag_type :: proc($T: typeid) -> typeid where type_is_union(T) ---
|
||||
type_union_tag_offset :: proc($T: typeid) -> uintptr where type_is_union(T) ---
|
||||
type_union_base_tag_value :: proc($T: typeid) -> int where type_is_union(U) ---
|
||||
type_union_variant_count :: proc($T: typeid) -> int where type_is_union(T) ---
|
||||
type_variant_type_of :: proc($T: typeid, $index: int) -> typeid where type_is_union(T) ---
|
||||
type_variant_index_of :: proc($U, $V: typeid) -> int where type_is_union(U) ---
|
||||
|
||||
type_has_field :: proc($T: typeid, $name: string) -> bool ---
|
||||
type_bit_set_elem_type :: proc($T: typeid) -> typeid where type_is_bit_set(T) ---
|
||||
type_bit_set_underlying_type :: proc($T: typeid) -> typeid where type_is_bit_set(T) ---
|
||||
|
||||
type_has_field :: proc($T: typeid, $name: string) -> bool ---
|
||||
type_field_type :: proc($T: typeid, $name: string) -> typeid ---
|
||||
|
||||
type_proc_parameter_count :: proc($T: typeid) -> int where type_is_proc(T) ---
|
||||
@@ -282,6 +288,12 @@ simd_reverse :: proc(a: #simd[N]T) -> #simd[N]T ---
|
||||
simd_rotate_left :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
|
||||
simd_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
|
||||
|
||||
// Checks if the current target supports the given target features.
|
||||
//
|
||||
// Takes a constant comma-seperated string (eg: "sha512,sse4.1"), or a procedure type which has either
|
||||
// `@(require_target_feature)` or `@(enable_target_feature)` as its input and returns a boolean indicating
|
||||
// if all listed features are supported.
|
||||
has_target_feature :: proc($test: $T) -> bool where type_is_string(T) || type_is_proc(T) ---
|
||||
|
||||
// WASM targets only
|
||||
wasm_memory_grow :: proc(index, delta: uintptr) -> int ---
|
||||
@@ -293,7 +305,9 @@ wasm_memory_size :: proc(index: uintptr) -> int ---
|
||||
// 0 - indicates that the thread blocked and then was woken up
|
||||
// 1 - the loaded value from `ptr` did not match `expected`, the thread did not block
|
||||
// 2 - the thread blocked, but the timeout
|
||||
@(require_target_feature="atomics")
|
||||
wasm_memory_atomic_wait32 :: proc(ptr: ^u32, expected: u32, timeout_ns: i64) -> u32 ---
|
||||
@(require_target_feature="atomics")
|
||||
wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_up: u32) ---
|
||||
|
||||
// x86 Targets (i386, amd64)
|
||||
|
||||
+47
-12
@@ -177,10 +177,22 @@ Type_Info_Matrix :: struct {
|
||||
row_count: int,
|
||||
column_count: int,
|
||||
// Total element count = column_count * elem_stride
|
||||
layout: enum u8 {
|
||||
Column_Major, // array of column vectors
|
||||
Row_Major, // array of row vectors
|
||||
},
|
||||
}
|
||||
Type_Info_Soa_Pointer :: struct {
|
||||
elem: ^Type_Info,
|
||||
}
|
||||
Type_Info_Bit_Field :: struct {
|
||||
backing_type: ^Type_Info,
|
||||
names: []string,
|
||||
types: []^Type_Info,
|
||||
bit_sizes: []uintptr,
|
||||
bit_offsets: []uintptr,
|
||||
tags: []string,
|
||||
}
|
||||
|
||||
Type_Info_Flag :: enum u8 {
|
||||
Comparable = 0,
|
||||
@@ -223,6 +235,7 @@ Type_Info :: struct {
|
||||
Type_Info_Relative_Multi_Pointer,
|
||||
Type_Info_Matrix,
|
||||
Type_Info_Soa_Pointer,
|
||||
Type_Info_Bit_Field,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -256,21 +269,22 @@ Typeid_Kind :: enum u8 {
|
||||
Relative_Multi_Pointer,
|
||||
Matrix,
|
||||
Soa_Pointer,
|
||||
Bit_Field,
|
||||
}
|
||||
#assert(len(Typeid_Kind) < 32)
|
||||
|
||||
// Typeid_Bit_Field :: bit_field #align(align_of(uintptr)) {
|
||||
// index: 8*size_of(uintptr) - 8,
|
||||
// kind: 5, // Typeid_Kind
|
||||
// named: 1,
|
||||
// special: 1, // signed, cstring, etc
|
||||
// reserved: 1,
|
||||
// }
|
||||
// #assert(size_of(Typeid_Bit_Field) == size_of(uintptr));
|
||||
Typeid_Bit_Field :: bit_field uintptr {
|
||||
index: uintptr | 8*size_of(uintptr) - 8,
|
||||
kind: Typeid_Kind | 5, // Typeid_Kind
|
||||
named: bool | 1,
|
||||
special: bool | 1, // signed, cstring, etc
|
||||
reserved: bool | 1,
|
||||
}
|
||||
#assert(size_of(Typeid_Bit_Field) == size_of(uintptr))
|
||||
|
||||
// NOTE(bill): only the ones that are needed (not all types)
|
||||
// This will be set by the compiler
|
||||
type_table: []Type_Info
|
||||
type_table: []^Type_Info
|
||||
|
||||
args__: []cstring
|
||||
|
||||
@@ -296,6 +310,14 @@ Source_Code_Location :: struct {
|
||||
procedure: string,
|
||||
}
|
||||
|
||||
/*
|
||||
Used by the built-in directory `#load_directory(path: string) -> []Load_Directory_File`
|
||||
*/
|
||||
Load_Directory_File :: struct {
|
||||
name: string,
|
||||
data: []byte, // immutable data
|
||||
}
|
||||
|
||||
Assertion_Failure_Proc :: #type proc(prefix, message: string, loc: Source_Code_Location) -> !
|
||||
|
||||
// Allocation Stuff
|
||||
@@ -448,6 +470,15 @@ Raw_Soa_Pointer :: struct {
|
||||
index: int,
|
||||
}
|
||||
|
||||
Raw_Complex32 :: struct {real, imag: f16}
|
||||
Raw_Complex64 :: struct {real, imag: f32}
|
||||
Raw_Complex128 :: struct {real, imag: f64}
|
||||
Raw_Quaternion64 :: struct {imag, jmag, kmag: f16, real: f16}
|
||||
Raw_Quaternion128 :: struct {imag, jmag, kmag: f32, real: f32}
|
||||
Raw_Quaternion256 :: struct {imag, jmag, kmag: f64, real: f64}
|
||||
Raw_Quaternion64_Vector_Scalar :: struct {vector: [3]f16, scalar: f16}
|
||||
Raw_Quaternion128_Vector_Scalar :: struct {vector: [3]f32, scalar: f32}
|
||||
Raw_Quaternion256_Vector_Scalar :: struct {vector: [3]f64, scalar: f64}
|
||||
|
||||
|
||||
/*
|
||||
@@ -459,7 +490,9 @@ Raw_Soa_Pointer :: struct {
|
||||
Linux,
|
||||
Essence,
|
||||
FreeBSD,
|
||||
Haiku,
|
||||
OpenBSD,
|
||||
NetBSD,
|
||||
WASI,
|
||||
JS,
|
||||
Freestanding,
|
||||
@@ -486,6 +519,7 @@ Odin_Arch_Type :: type_of(ODIN_ARCH)
|
||||
Odin_Build_Mode_Type :: enum int {
|
||||
Executable,
|
||||
Dynamic,
|
||||
Static,
|
||||
Object,
|
||||
Assembly,
|
||||
LLVM_IR,
|
||||
@@ -575,8 +609,9 @@ type_info_core :: proc "contextless" (info: ^Type_Info) -> ^Type_Info {
|
||||
base := info
|
||||
loop: for {
|
||||
#partial switch i in base.variant {
|
||||
case Type_Info_Named: base = i.base
|
||||
case Type_Info_Enum: base = i.base
|
||||
case Type_Info_Named: base = i.base
|
||||
case Type_Info_Enum: base = i.base
|
||||
case Type_Info_Bit_Field: base = i.backing_type
|
||||
case: break loop
|
||||
}
|
||||
}
|
||||
@@ -591,7 +626,7 @@ __type_info_of :: proc "contextless" (id: typeid) -> ^Type_Info #no_bounds_check
|
||||
if n < 0 || n >= len(type_table) {
|
||||
n = 0
|
||||
}
|
||||
return &type_table[n]
|
||||
return type_table[n]
|
||||
}
|
||||
|
||||
when !ODIN_NO_RTTI {
|
||||
|
||||
@@ -40,7 +40,7 @@ copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int {
|
||||
}
|
||||
return n
|
||||
}
|
||||
// `copy_from_string` is a built-in procedure that copies elements from a source slice `src` to a destination string `dst`.
|
||||
// `copy_from_string` is a built-in procedure that copies elements from a source string `src` to a destination slice `dst`.
|
||||
// The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum
|
||||
// of len(src) and len(dst).
|
||||
//
|
||||
@@ -53,7 +53,7 @@ copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int
|
||||
}
|
||||
return n
|
||||
}
|
||||
// `copy` is a built-in procedure that copies elements from a source slice `src` to a destination slice/string `dst`.
|
||||
// `copy` is a built-in procedure that copies elements from a source slice/string `src` to a destination slice `dst`.
|
||||
// The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum
|
||||
// of len(src) and len(dst).
|
||||
@builtin
|
||||
@@ -122,7 +122,7 @@ pop :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bou
|
||||
// `pop_safe` trys to remove and return the end value of dynamic array `array` and reduces the length of `array` by 1.
|
||||
// If the operation is not possible, it will return false.
|
||||
@builtin
|
||||
pop_safe :: proc(array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check {
|
||||
pop_safe :: proc "contextless" (array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check {
|
||||
if len(array) == 0 {
|
||||
return
|
||||
}
|
||||
@@ -148,7 +148,7 @@ pop_front :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #
|
||||
// `pop_front_safe` trys to return and remove the first value of dynamic array `array` and reduces the length of `array` by 1.
|
||||
// If the operation is not possible, it will return false.
|
||||
@builtin
|
||||
pop_front_safe :: proc(array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check {
|
||||
pop_front_safe :: proc "contextless" (array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check {
|
||||
if len(array) == 0 {
|
||||
return
|
||||
}
|
||||
@@ -447,12 +447,12 @@ _append_elem :: #force_inline proc(array: ^$T/[dynamic]$E, arg: E, should_zero:
|
||||
}
|
||||
|
||||
@builtin
|
||||
append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
return _append_elem(array, arg, true, loc=loc)
|
||||
}
|
||||
|
||||
@builtin
|
||||
non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
return _append_elem(array, arg, false, loc=loc)
|
||||
}
|
||||
|
||||
@@ -496,12 +496,12 @@ _append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, l
|
||||
}
|
||||
|
||||
@builtin
|
||||
append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
return _append_elems(array, true, loc, ..args)
|
||||
}
|
||||
|
||||
@builtin
|
||||
non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
return _append_elems(array, false, loc, ..args)
|
||||
}
|
||||
|
||||
@@ -556,7 +556,7 @@ append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: i
|
||||
|
||||
|
||||
@builtin
|
||||
inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
|
||||
inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
|
||||
if array == nil {
|
||||
return
|
||||
}
|
||||
@@ -574,7 +574,7 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #calle
|
||||
}
|
||||
|
||||
@builtin
|
||||
inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
|
||||
inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
|
||||
if array == nil {
|
||||
return
|
||||
}
|
||||
@@ -740,6 +740,9 @@ _resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int,
|
||||
a := (^Raw_Dynamic_Array)(array)
|
||||
|
||||
if length <= a.cap {
|
||||
if should_zero && a.len < length {
|
||||
intrinsics.mem_zero(([^]E)(a.data)[a.len:], (length-a.len)*size_of(E))
|
||||
}
|
||||
a.len = max(length, 0)
|
||||
return nil
|
||||
}
|
||||
@@ -824,9 +827,25 @@ map_insert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location)
|
||||
return (^V)(__dynamic_map_set_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc))
|
||||
}
|
||||
|
||||
// Explicitly inserts a key and value into a map `m`, the same as `map_insert`, but the return values differ.
|
||||
// - `prev_key` will return the previous pointer of a key if it exists, check `found_previous` if was previously found
|
||||
// - `value_ptr` will return the pointer of the memory where the insertion happens, and `nil` if the map failed to resize
|
||||
// - `found_previous` will be true a previous key was found
|
||||
@(builtin, require_results)
|
||||
map_upsert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location) -> (prev_key: K, value_ptr: ^V, found_previous: bool) {
|
||||
key, value := key, value
|
||||
kp, vp := __dynamic_map_set_extra_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc)
|
||||
if kp != nil {
|
||||
prev_key = (^K)(kp)^
|
||||
found_previous = true
|
||||
}
|
||||
value_ptr = (^V)(vp)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@builtin
|
||||
card :: proc(s: $S/bit_set[$E; $U]) -> int {
|
||||
card :: proc "contextless" (s: $S/bit_set[$E; $U]) -> int {
|
||||
when size_of(S) == 1 {
|
||||
return int(intrinsics.count_ones(transmute(u8)s))
|
||||
} else when size_of(S) == 2 {
|
||||
|
||||
@@ -86,7 +86,6 @@ make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, alloc
|
||||
return
|
||||
}
|
||||
|
||||
array.allocator = allocator
|
||||
footer := raw_soa_footer(&array)
|
||||
if size_of(E) == 0 {
|
||||
footer.len = length
|
||||
@@ -103,7 +102,7 @@ make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, alloc
|
||||
|
||||
total_size := 0
|
||||
for i in 0..<field_count {
|
||||
type := si.types[i].variant.(Type_Info_Pointer).elem
|
||||
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
||||
total_size += type.size * length
|
||||
total_size = align_forward_int(total_size, max_align)
|
||||
}
|
||||
@@ -127,7 +126,7 @@ make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, alloc
|
||||
data := uintptr(&array)
|
||||
offset := 0
|
||||
for i in 0..<field_count {
|
||||
type := si.types[i].variant.(Type_Info_Pointer).elem
|
||||
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
||||
|
||||
offset = align_forward_int(offset, max_align)
|
||||
|
||||
@@ -227,7 +226,7 @@ reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_lo
|
||||
|
||||
max_align :: align_of(E)
|
||||
for i in 0..<field_count {
|
||||
type := si.types[i].variant.(Type_Info_Pointer).elem
|
||||
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
||||
|
||||
old_size += type.size * old_cap
|
||||
new_size += type.size * capacity
|
||||
@@ -250,7 +249,7 @@ reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_lo
|
||||
old_offset := 0
|
||||
new_offset := 0
|
||||
for i in 0..<field_count {
|
||||
type := si.types[i].variant.(Type_Info_Pointer).elem
|
||||
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
||||
|
||||
old_offset = align_forward_int(old_offset, max_align)
|
||||
new_offset = align_forward_int(new_offset, max_align)
|
||||
@@ -308,7 +307,7 @@ append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, arg: E, loc := #caller_locat
|
||||
|
||||
max_align :: align_of(E)
|
||||
for i in 0..<field_count {
|
||||
type := si.types[i].variant.(Type_Info_Pointer).elem
|
||||
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
||||
|
||||
soa_offset = align_forward_int(soa_offset, max_align)
|
||||
item_offset = align_forward_int(item_offset, type.align)
|
||||
@@ -359,7 +358,7 @@ append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, args: ..E, loc := #caller_l
|
||||
|
||||
max_align :: align_of(E)
|
||||
for i in 0..<field_count {
|
||||
type := si.types[i].variant.(Type_Info_Pointer).elem
|
||||
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
||||
|
||||
soa_offset = align_forward_int(soa_offset, max_align)
|
||||
item_offset = align_forward_int(item_offset, type.align)
|
||||
@@ -426,4 +425,97 @@ clear_soa_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) {
|
||||
@builtin
|
||||
clear_soa :: proc{
|
||||
clear_soa_dynamic_array,
|
||||
}
|
||||
}
|
||||
|
||||
// Converts soa slice into a soa dynamic array without cloning or allocating memory
|
||||
@(require_results)
|
||||
into_dynamic_soa :: proc(array: $T/#soa[]$E) -> #soa[dynamic]E {
|
||||
d: #soa[dynamic]E
|
||||
footer := raw_soa_footer_dynamic_array(&d)
|
||||
footer^ = {
|
||||
cap = len(array),
|
||||
len = 0,
|
||||
allocator = nil_allocator(),
|
||||
}
|
||||
|
||||
field_count: uintptr
|
||||
when intrinsics.type_is_array(E) {
|
||||
field_count = len(E)
|
||||
} else {
|
||||
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
||||
}
|
||||
|
||||
array := array
|
||||
dynamic_data := ([^]rawptr)(&d)[:field_count]
|
||||
slice_data := ([^]rawptr)(&array)[:field_count]
|
||||
copy(dynamic_data, slice_data)
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// `unordered_remove_soa` removed the element at the specified `index`. It does so by replacing the current end value
|
||||
// with the old value, and reducing the length of the dynamic array by 1.
|
||||
//
|
||||
// Note: This is an O(1) operation.
|
||||
// Note: If you the elements to remain in their order, use `ordered_remove_soa`.
|
||||
// Note: If the index is out of bounds, this procedure will panic.
|
||||
@builtin
|
||||
unordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, index: int, loc := #caller_location) #no_bounds_check {
|
||||
bounds_check_error_loc(loc, index, len(array))
|
||||
if index+1 < len(array) {
|
||||
ti := type_info_of(typeid_of(T))
|
||||
ti = type_info_base(ti)
|
||||
si := &ti.variant.(Type_Info_Struct)
|
||||
|
||||
field_count: uintptr
|
||||
when intrinsics.type_is_array(E) {
|
||||
field_count = len(E)
|
||||
} else {
|
||||
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
||||
}
|
||||
|
||||
data := uintptr(array)
|
||||
for i in 0..<field_count {
|
||||
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
||||
|
||||
offset := rawptr((^uintptr)(data)^ + uintptr(index*type.size))
|
||||
final := rawptr((^uintptr)(data)^ + uintptr((len(array)-1)*type.size))
|
||||
mem_copy(offset, final, type.size)
|
||||
data += size_of(rawptr)
|
||||
}
|
||||
}
|
||||
raw_soa_footer_dynamic_array(array).len -= 1
|
||||
}
|
||||
|
||||
// `ordered_remove_soa` removed the element at the specified `index` whilst keeping the order of the other elements.
|
||||
//
|
||||
// Note: This is an O(N) operation.
|
||||
// Note: If you the elements do not have to remain in their order, prefer `unordered_remove_soa`.
|
||||
// Note: If the index is out of bounds, this procedure will panic.
|
||||
@builtin
|
||||
ordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, index: int, loc := #caller_location) #no_bounds_check {
|
||||
bounds_check_error_loc(loc, index, len(array))
|
||||
if index+1 < len(array) {
|
||||
ti := type_info_of(typeid_of(T))
|
||||
ti = type_info_base(ti)
|
||||
si := &ti.variant.(Type_Info_Struct)
|
||||
|
||||
field_count: uintptr
|
||||
when intrinsics.type_is_array(E) {
|
||||
field_count = len(E)
|
||||
} else {
|
||||
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
||||
}
|
||||
|
||||
data := uintptr(array)
|
||||
for i in 0..<field_count {
|
||||
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
||||
|
||||
offset := (^uintptr)(data)^ + uintptr(index*type.size)
|
||||
length := type.size*(len(array) - index - 1)
|
||||
mem_copy(rawptr(offset), rawptr(offset + uintptr(type.size)), length)
|
||||
data += size_of(rawptr)
|
||||
}
|
||||
}
|
||||
raw_soa_footer_dynamic_array(array).len -= 1
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ Memory_Block :: struct {
|
||||
capacity: uint,
|
||||
}
|
||||
|
||||
// NOTE: This is for internal use, prefer `Arena` from `core:mem/virtual` if necessary
|
||||
Arena :: struct {
|
||||
backing_allocator: Allocator,
|
||||
curr_block: ^Memory_Block,
|
||||
|
||||
@@ -6,6 +6,9 @@ when ODIN_DEFAULT_TO_NIL_ALLOCATOR {
|
||||
} else when ODIN_DEFAULT_TO_PANIC_ALLOCATOR {
|
||||
default_allocator_proc :: panic_allocator_proc
|
||||
default_allocator :: panic_allocator
|
||||
} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
|
||||
default_allocator :: default_wasm_allocator
|
||||
default_allocator_proc :: wasm_allocator_proc
|
||||
} else {
|
||||
default_allocator :: heap_allocator
|
||||
default_allocator_proc :: heap_allocator_proc
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package runtime
|
||||
|
||||
DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, 4 * Megabyte)
|
||||
NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR
|
||||
NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_DEFAULT_TO_NIL_ALLOCATOR
|
||||
|
||||
when NO_DEFAULT_TEMP_ALLOCATOR {
|
||||
Default_Temp_Allocator :: struct {}
|
||||
|
||||
@@ -44,7 +44,7 @@ memcpy
|
||||
memove
|
||||
|
||||
|
||||
## Procedures required by the LLVM backend
|
||||
## Procedures required by the LLVM backend if u128/i128 is used
|
||||
umodti3
|
||||
udivti3
|
||||
modti3
|
||||
@@ -59,11 +59,12 @@ truncdfhf2
|
||||
gnu_h2f_ieee
|
||||
gnu_f2h_ieee
|
||||
extendhfsf2
|
||||
|
||||
## Procedures required by the LLVM backend if f16 is used
|
||||
__ashlti3 // wasm specific
|
||||
__multi3 // wasm specific
|
||||
|
||||
|
||||
|
||||
## Required an entry point is defined (i.e. 'main')
|
||||
|
||||
args__
|
||||
|
||||
@@ -333,7 +333,7 @@ map_kvh_data_values_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^
|
||||
}
|
||||
|
||||
|
||||
@(private, require_results)
|
||||
@(require_results)
|
||||
map_total_allocation_size :: #force_inline proc "contextless" (capacity: uintptr, info: ^Map_Info) -> uintptr {
|
||||
round :: #force_inline proc "contextless" (value: uintptr) -> uintptr {
|
||||
CACHE_MASK :: MAP_CACHE_LINE_SIZE - 1
|
||||
@@ -350,6 +350,12 @@ map_total_allocation_size :: #force_inline proc "contextless" (capacity: uintptr
|
||||
return size
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
map_total_allocation_size_from_value :: #force_inline proc "contextless" (m: $M/map[$K]$V) -> uintptr {
|
||||
return map_total_allocation_size(uintptr(cap(m)), map_info(M))
|
||||
}
|
||||
|
||||
|
||||
// The only procedure which needs access to the context is the one which allocates the map.
|
||||
@(require_results)
|
||||
map_alloc_dynamic :: proc "odin" (info: ^Map_Info, log2_capacity: uintptr, allocator := context.allocator, loc := #caller_location) -> (result: Raw_Map, err: Allocator_Error) {
|
||||
@@ -391,7 +397,8 @@ map_alloc_dynamic :: proc "odin" (info: ^Map_Info, log2_capacity: uintptr, alloc
|
||||
// arrays to reduce variance. This swapping can only be done with memcpy since
|
||||
// there is no type information.
|
||||
//
|
||||
// This procedure returns the address of the just inserted value.
|
||||
// This procedure returns the address of the just inserted value, and will
|
||||
// return 'nil' if there was no room to insert the entry
|
||||
@(require_results)
|
||||
map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (result: uintptr) {
|
||||
h := h
|
||||
@@ -415,6 +422,11 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^
|
||||
tv := map_cell_index_dynamic(sv, info.vs, 1)
|
||||
|
||||
swap_loop: for {
|
||||
if distance > mask {
|
||||
// Failed to find an empty slot and prevent infinite loop
|
||||
panic("unable to insert into a map")
|
||||
}
|
||||
|
||||
element_hash := hs[pos]
|
||||
|
||||
if map_hash_is_empty(element_hash) {
|
||||
@@ -841,6 +853,33 @@ __dynamic_map_get :: proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info:
|
||||
}
|
||||
}
|
||||
|
||||
__dynamic_map_get_key_and_value :: proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, key: rawptr) -> (key_ptr, value_ptr: rawptr) {
|
||||
if m.len == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
pos := map_desired_position(m^, h)
|
||||
distance := uintptr(0)
|
||||
mask := (uintptr(1) << map_log2_cap(m^)) - 1
|
||||
ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info)
|
||||
for {
|
||||
element_hash := hs[pos]
|
||||
if map_hash_is_empty(element_hash) {
|
||||
return nil, nil
|
||||
} else if distance > map_probe_distance(m^, element_hash, pos) {
|
||||
return nil, nil
|
||||
} else if element_hash == h {
|
||||
other_key := rawptr(map_cell_index_dynamic(ks, info.ks, pos))
|
||||
if info.key_equal(key, other_key) {
|
||||
key_ptr = other_key
|
||||
value_ptr = rawptr(map_cell_index_dynamic(vs, info.vs, pos))
|
||||
return
|
||||
}
|
||||
}
|
||||
pos = (pos + 1) & mask
|
||||
distance += 1
|
||||
}
|
||||
}
|
||||
|
||||
// IMPORTANT: USED WITHIN THE COMPILER
|
||||
__dynamic_map_check_grow :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (err: Allocator_Error, has_grown: bool) {
|
||||
if m.len >= map_resize_threshold(m^) {
|
||||
@@ -871,9 +910,37 @@ __dynamic_map_set :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_In
|
||||
}
|
||||
|
||||
result := map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(value))
|
||||
m.len += 1
|
||||
if result != 0 {
|
||||
m.len += 1
|
||||
}
|
||||
return rawptr(result)
|
||||
}
|
||||
__dynamic_map_set_extra_without_hash :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key, value: rawptr, loc := #caller_location) -> (prev_key_ptr, value_ptr: rawptr) {
|
||||
return __dynamic_map_set_extra(m, info, info.key_hasher(key, map_seed(m^)), key, value, loc)
|
||||
}
|
||||
|
||||
__dynamic_map_set_extra :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, hash: Map_Hash, key, value: rawptr, loc := #caller_location) -> (prev_key_ptr, value_ptr: rawptr) {
|
||||
if prev_key_ptr, value_ptr = __dynamic_map_get_key_and_value(m, info, hash, key); value_ptr != nil {
|
||||
intrinsics.mem_copy_non_overlapping(value_ptr, value, info.vs.size_of_type)
|
||||
return
|
||||
}
|
||||
|
||||
hash := hash
|
||||
err, has_grown := __dynamic_map_check_grow(m, info, loc)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
if has_grown {
|
||||
hash = info.key_hasher(key, map_seed(m^))
|
||||
}
|
||||
|
||||
result := map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(value))
|
||||
if result != 0 {
|
||||
m.len += 1
|
||||
}
|
||||
return nil, rawptr(result)
|
||||
}
|
||||
|
||||
|
||||
// IMPORTANT: USED WITHIN THE COMPILER
|
||||
@(private)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//+private
|
||||
//+build linux, darwin, freebsd, openbsd
|
||||
//+build linux, darwin, freebsd, openbsd, netbsd, haiku
|
||||
//+no-instrumentation
|
||||
package runtime
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ type_assertion_trap :: proc "contextless" () -> ! {
|
||||
}
|
||||
|
||||
|
||||
@(disabled=ODIN_NO_BOUNDS_CHECK)
|
||||
bounds_check_error :: proc "contextless" (file: string, line, column: i32, index, count: int) {
|
||||
if uint(index) < uint(count) {
|
||||
return
|
||||
@@ -61,6 +62,7 @@ multi_pointer_slice_handle_error :: proc "contextless" (file: string, line, colu
|
||||
}
|
||||
|
||||
|
||||
@(disabled=ODIN_NO_BOUNDS_CHECK)
|
||||
multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) {
|
||||
if lo <= hi {
|
||||
return
|
||||
@@ -68,6 +70,7 @@ multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column
|
||||
multi_pointer_slice_handle_error(file, line, column, lo, hi)
|
||||
}
|
||||
|
||||
@(disabled=ODIN_NO_BOUNDS_CHECK)
|
||||
slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi: int, len: int) {
|
||||
if 0 <= hi && hi <= len {
|
||||
return
|
||||
@@ -75,6 +78,7 @@ slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi:
|
||||
slice_handle_error(file, line, column, 0, hi, len)
|
||||
}
|
||||
|
||||
@(disabled=ODIN_NO_BOUNDS_CHECK)
|
||||
slice_expr_error_lo_hi :: proc "contextless" (file: string, line, column: i32, lo, hi: int, len: int) {
|
||||
if 0 <= lo && lo <= len && lo <= hi && hi <= len {
|
||||
return
|
||||
@@ -82,6 +86,7 @@ slice_expr_error_lo_hi :: proc "contextless" (file: string, line, column: i32, l
|
||||
slice_handle_error(file, line, column, lo, hi, len)
|
||||
}
|
||||
|
||||
@(disabled=ODIN_NO_BOUNDS_CHECK)
|
||||
dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) {
|
||||
if 0 <= low && low <= high && high <= max {
|
||||
return
|
||||
@@ -102,6 +107,7 @@ dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32,
|
||||
}
|
||||
|
||||
|
||||
@(disabled=ODIN_NO_BOUNDS_CHECK)
|
||||
matrix_bounds_check_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) {
|
||||
if uint(row_index) < uint(row_count) &&
|
||||
uint(column_index) < uint(column_count) {
|
||||
@@ -224,6 +230,7 @@ when ODIN_NO_RTTI {
|
||||
}
|
||||
|
||||
|
||||
@(disabled=ODIN_NO_BOUNDS_CHECK)
|
||||
make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len: int) {
|
||||
if 0 <= len {
|
||||
return
|
||||
@@ -239,6 +246,7 @@ make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_locatio
|
||||
handle_error(loc, len)
|
||||
}
|
||||
|
||||
@(disabled=ODIN_NO_BOUNDS_CHECK)
|
||||
make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len, cap: int) {
|
||||
if 0 <= len && len <= cap {
|
||||
return
|
||||
@@ -256,6 +264,7 @@ make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller
|
||||
handle_error(loc, len, cap)
|
||||
}
|
||||
|
||||
@(disabled=ODIN_NO_BOUNDS_CHECK)
|
||||
make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, cap: int) {
|
||||
if 0 <= cap {
|
||||
return
|
||||
@@ -274,19 +283,22 @@ make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_loca
|
||||
|
||||
|
||||
|
||||
|
||||
@(disabled=ODIN_NO_BOUNDS_CHECK)
|
||||
bounds_check_error_loc :: #force_inline proc "contextless" (loc := #caller_location, index, count: int) {
|
||||
bounds_check_error(loc.file_path, loc.line, loc.column, index, count)
|
||||
}
|
||||
|
||||
@(disabled=ODIN_NO_BOUNDS_CHECK)
|
||||
slice_expr_error_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, hi: int, len: int) {
|
||||
slice_expr_error_hi(loc.file_path, loc.line, loc.column, hi, len)
|
||||
}
|
||||
|
||||
@(disabled=ODIN_NO_BOUNDS_CHECK)
|
||||
slice_expr_error_lo_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, lo, hi: int, len: int) {
|
||||
slice_expr_error_lo_hi(loc.file_path, loc.line, loc.column, lo, hi, len)
|
||||
}
|
||||
|
||||
@(disabled=ODIN_NO_BOUNDS_CHECK)
|
||||
dynamic_array_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, low, high, max: int) {
|
||||
dynamic_array_expr_error(loc.file_path, loc.line, loc.column, low, high, max)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//+build linux, darwin, freebsd, openbsd
|
||||
//+build linux, darwin, freebsd, openbsd, netbsd, haiku
|
||||
//+private
|
||||
package runtime
|
||||
|
||||
@@ -35,4 +35,4 @@ _heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
|
||||
|
||||
_heap_free :: proc(ptr: rawptr) {
|
||||
_unix_free(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ RUNTIME_LINKAGE :: "strong" when (
|
||||
ODIN_BUILD_MODE == .Dynamic ||
|
||||
!ODIN_NO_CRT) &&
|
||||
!IS_WASM) else "internal"
|
||||
RUNTIME_REQUIRE :: !ODIN_TILDE
|
||||
RUNTIME_REQUIRE :: false // !ODIN_TILDE
|
||||
|
||||
@(private)
|
||||
__float16 :: f16 when __ODIN_LLVM_F16_SUPPORTED else u16
|
||||
@@ -22,7 +22,7 @@ byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byt
|
||||
return ([^]byte)(data)[:max(len, 0)]
|
||||
}
|
||||
|
||||
is_power_of_two_int :: #force_inline proc(x: int) -> bool {
|
||||
is_power_of_two_int :: #force_inline proc "contextless" (x: int) -> bool {
|
||||
if x <= 0 {
|
||||
return false
|
||||
}
|
||||
@@ -40,7 +40,25 @@ align_forward_int :: #force_inline proc(ptr, align: int) -> int {
|
||||
return p
|
||||
}
|
||||
|
||||
is_power_of_two_uintptr :: #force_inline proc(x: uintptr) -> bool {
|
||||
is_power_of_two_uint :: #force_inline proc "contextless" (x: uint) -> bool {
|
||||
if x <= 0 {
|
||||
return false
|
||||
}
|
||||
return (x & (x-1)) == 0
|
||||
}
|
||||
|
||||
align_forward_uint :: #force_inline proc(ptr, align: uint) -> uint {
|
||||
assert(is_power_of_two_uint(align))
|
||||
|
||||
p := ptr
|
||||
modulo := p & (align-1)
|
||||
if modulo != 0 {
|
||||
p += align - modulo
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
is_power_of_two_uintptr :: #force_inline proc "contextless" (x: uintptr) -> bool {
|
||||
if x <= 0 {
|
||||
return false
|
||||
}
|
||||
@@ -58,6 +76,18 @@ align_forward_uintptr :: #force_inline proc(ptr, align: uintptr) -> uintptr {
|
||||
return p
|
||||
}
|
||||
|
||||
is_power_of_two :: proc {
|
||||
is_power_of_two_int,
|
||||
is_power_of_two_uint,
|
||||
is_power_of_two_uintptr,
|
||||
}
|
||||
|
||||
align_forward :: proc {
|
||||
align_forward_int,
|
||||
align_forward_uint,
|
||||
align_forward_uintptr,
|
||||
}
|
||||
|
||||
mem_zero :: proc "contextless" (data: rawptr, len: int) -> rawptr {
|
||||
if data == nil {
|
||||
return nil
|
||||
@@ -801,6 +831,10 @@ truncsfhf2 :: proc "c" (value: f32) -> __float16 {
|
||||
}
|
||||
}
|
||||
|
||||
@(link_name="__aeabi_d2h", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
aeabi_d2h :: proc "c" (value: f64) -> __float16 {
|
||||
return truncsfhf2(f32(value))
|
||||
}
|
||||
|
||||
@(link_name="__truncdfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
truncdfhf2 :: proc "c" (value: f64) -> __float16 {
|
||||
@@ -962,9 +996,11 @@ udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
|
||||
return udivmod128(a, b, rem)
|
||||
}
|
||||
|
||||
@(link_name="__udivti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
udivti3 :: proc "c" (a, b: u128) -> u128 {
|
||||
return udivmodti4(a, b, nil)
|
||||
when !IS_WASM {
|
||||
@(link_name="__udivti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
udivti3 :: proc "c" (a, b: u128) -> u128 {
|
||||
return udivmodti4(a, b, nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1034,3 +1070,23 @@ fixdfti :: proc(a: u64) -> i128 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
__write_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) {
|
||||
for i in 0..<size {
|
||||
j := offset+i
|
||||
the_bit := byte((src[i>>3]) & (1<<(i&7)) != 0)
|
||||
dst[j>>3] &~= 1<<(j&7)
|
||||
dst[j>>3] |= the_bit<<(j&7)
|
||||
}
|
||||
}
|
||||
|
||||
__read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) {
|
||||
for j in 0..<size {
|
||||
i := offset+j
|
||||
the_bit := byte((src[i>>3]) & (1<<(i&7)) != 0)
|
||||
dst[j>>3] &~= 1<<(j&7)
|
||||
dst[j>>3] |= the_bit<<(j&7)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//+build freebsd, openbsd
|
||||
//+build freebsd, openbsd, netbsd
|
||||
//+private
|
||||
package runtime
|
||||
|
||||
@@ -9,7 +9,11 @@ foreign libc {
|
||||
@(link_name="write")
|
||||
_unix_write :: proc(fd: i32, buf: rawptr, size: int) -> int ---
|
||||
|
||||
__error :: proc() -> ^i32 ---
|
||||
when ODIN_OS == .NetBSD {
|
||||
@(link_name="__errno") __error :: proc() -> ^i32 ---
|
||||
} else {
|
||||
__error :: proc() -> ^i32 ---
|
||||
}
|
||||
}
|
||||
|
||||
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
|
||||
@@ -2,25 +2,14 @@
|
||||
//+private
|
||||
package runtime
|
||||
|
||||
foreign import libc "system:System.framework"
|
||||
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
@(link_name="__stderrp")
|
||||
_stderr: rawptr
|
||||
|
||||
@(link_name="fwrite")
|
||||
_fwrite :: proc(ptr: rawptr, size: uint, nmemb: uint, stream: rawptr) -> uint ---
|
||||
|
||||
@(link_name="__error")
|
||||
_get_errno :: proc() -> ^i32 ---
|
||||
}
|
||||
import "base:intrinsics"
|
||||
|
||||
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
ret := _fwrite(raw_data(data), 1, len(data), _stderr)
|
||||
if ret < len(data) {
|
||||
err := _get_errno()
|
||||
return int(ret), _OS_Errno(err^ if err != nil else 0)
|
||||
WRITE :: 0x2000004
|
||||
STDERR :: 2
|
||||
ret := intrinsics.syscall(WRITE, STDERR, uintptr(raw_data(data)), uintptr(len(data)))
|
||||
if ret < 0 {
|
||||
return 0, _OS_Errno(-ret)
|
||||
}
|
||||
return int(ret), 0
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
//+build haiku
|
||||
//+private
|
||||
package runtime
|
||||
|
||||
foreign import libc "system:c"
|
||||
|
||||
foreign libc {
|
||||
@(link_name="write")
|
||||
_unix_write :: proc(fd: i32, buf: rawptr, size: int) -> int ---
|
||||
|
||||
_errnop :: proc() -> ^i32 ---
|
||||
}
|
||||
|
||||
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
ret := _unix_write(2, raw_data(data), len(data))
|
||||
if ret < len(data) {
|
||||
err := _errnop()
|
||||
return int(ret), _OS_Errno(err^ if err != nil else 0)
|
||||
}
|
||||
return int(ret), 0
|
||||
}
|
||||
@@ -5,7 +5,7 @@ package runtime
|
||||
import "core:sys/wasm/wasi"
|
||||
|
||||
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
data := (wasi.ciovec_t)(data)
|
||||
n, err := wasi.fd_write(1, {data})
|
||||
data_iovec := (wasi.ciovec_t)(data)
|
||||
n, err := wasi.fd_write(1, {data_iovec})
|
||||
return int(n), _OS_Errno(err)
|
||||
}
|
||||
|
||||
+36
-16
@@ -6,7 +6,7 @@ _INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
_INTEGER_DIGITS_VAR := _INTEGER_DIGITS
|
||||
|
||||
when !ODIN_NO_RTTI {
|
||||
print_any_single :: proc "contextless" (arg: any) {
|
||||
print_any_single :: #force_no_inline proc "contextless" (arg: any) {
|
||||
x := arg
|
||||
if x.data == nil {
|
||||
print_string("nil")
|
||||
@@ -72,7 +72,7 @@ when !ODIN_NO_RTTI {
|
||||
print_string("<invalid-value>")
|
||||
}
|
||||
}
|
||||
println_any :: proc "contextless" (args: ..any) {
|
||||
println_any :: #force_no_inline proc "contextless" (args: ..any) {
|
||||
context = default_context()
|
||||
loop: for arg, i in args {
|
||||
assert(arg.id != nil)
|
||||
@@ -122,12 +122,12 @@ encode_rune :: proc "contextless" (c: rune) -> ([4]u8, int) {
|
||||
return buf, 4
|
||||
}
|
||||
|
||||
print_string :: proc "contextless" (str: string) -> (n: int) {
|
||||
print_string :: #force_no_inline proc "contextless" (str: string) -> (n: int) {
|
||||
n, _ = stderr_write(transmute([]byte)str)
|
||||
return
|
||||
}
|
||||
|
||||
print_strings :: proc "contextless" (args: ..string) -> (n: int) {
|
||||
print_strings :: #force_no_inline proc "contextless" (args: ..string) -> (n: int) {
|
||||
for str in args {
|
||||
m, err := stderr_write(transmute([]byte)str)
|
||||
n += m
|
||||
@@ -138,12 +138,12 @@ print_strings :: proc "contextless" (args: ..string) -> (n: int) {
|
||||
return
|
||||
}
|
||||
|
||||
print_byte :: proc "contextless" (b: byte) -> (n: int) {
|
||||
print_byte :: #force_no_inline proc "contextless" (b: byte) -> (n: int) {
|
||||
n, _ = stderr_write([]byte{b})
|
||||
return
|
||||
}
|
||||
|
||||
print_encoded_rune :: proc "contextless" (r: rune) {
|
||||
print_encoded_rune :: #force_no_inline proc "contextless" (r: rune) {
|
||||
print_byte('\'')
|
||||
|
||||
switch r {
|
||||
@@ -170,7 +170,7 @@ print_encoded_rune :: proc "contextless" (r: rune) {
|
||||
print_byte('\'')
|
||||
}
|
||||
|
||||
print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check {
|
||||
print_rune :: #force_no_inline proc "contextless" (r: rune) -> int #no_bounds_check {
|
||||
RUNE_SELF :: 0x80
|
||||
|
||||
if r < RUNE_SELF {
|
||||
@@ -183,7 +183,7 @@ print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check {
|
||||
}
|
||||
|
||||
|
||||
print_u64 :: proc "contextless" (x: u64) #no_bounds_check {
|
||||
print_u64 :: #force_no_inline proc "contextless" (x: u64) #no_bounds_check {
|
||||
a: [129]byte
|
||||
i := len(a)
|
||||
b := u64(10)
|
||||
@@ -198,7 +198,7 @@ print_u64 :: proc "contextless" (x: u64) #no_bounds_check {
|
||||
}
|
||||
|
||||
|
||||
print_i64 :: proc "contextless" (x: i64) #no_bounds_check {
|
||||
print_i64 :: #force_no_inline proc "contextless" (x: i64) #no_bounds_check {
|
||||
b :: i64(10)
|
||||
|
||||
u := x
|
||||
@@ -223,25 +223,29 @@ print_uint :: proc "contextless" (x: uint) { print_u64(u64(x)) }
|
||||
print_uintptr :: proc "contextless" (x: uintptr) { print_u64(u64(x)) }
|
||||
print_int :: proc "contextless" (x: int) { print_i64(i64(x)) }
|
||||
|
||||
print_caller_location :: proc "contextless" (loc: Source_Code_Location) {
|
||||
print_caller_location :: #force_no_inline proc "contextless" (loc: Source_Code_Location) {
|
||||
print_string(loc.file_path)
|
||||
when ODIN_ERROR_POS_STYLE == .Default {
|
||||
print_byte('(')
|
||||
print_u64(u64(loc.line))
|
||||
print_byte(':')
|
||||
print_u64(u64(loc.column))
|
||||
if loc.column != 0 {
|
||||
print_byte(':')
|
||||
print_u64(u64(loc.column))
|
||||
}
|
||||
print_byte(')')
|
||||
} else when ODIN_ERROR_POS_STYLE == .Unix {
|
||||
print_byte(':')
|
||||
print_u64(u64(loc.line))
|
||||
print_byte(':')
|
||||
print_u64(u64(loc.column))
|
||||
if loc.column != 0 {
|
||||
print_byte(':')
|
||||
print_u64(u64(loc.column))
|
||||
}
|
||||
print_byte(':')
|
||||
} else {
|
||||
#panic("unhandled ODIN_ERROR_POS_STYLE")
|
||||
}
|
||||
}
|
||||
print_typeid :: proc "contextless" (id: typeid) {
|
||||
print_typeid :: #force_no_inline proc "contextless" (id: typeid) {
|
||||
when ODIN_NO_RTTI {
|
||||
if id == nil {
|
||||
print_string("nil")
|
||||
@@ -257,7 +261,9 @@ print_typeid :: proc "contextless" (id: typeid) {
|
||||
}
|
||||
}
|
||||
}
|
||||
print_type :: proc "contextless" (ti: ^Type_Info) {
|
||||
|
||||
@(optimization_mode="size")
|
||||
print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
|
||||
if ti == nil {
|
||||
print_string("nil")
|
||||
return
|
||||
@@ -459,6 +465,20 @@ print_type :: proc "contextless" (ti: ^Type_Info) {
|
||||
}
|
||||
print_byte(']')
|
||||
|
||||
case Type_Info_Bit_Field:
|
||||
print_string("bit_field ")
|
||||
print_type(info.backing_type)
|
||||
print_string(" {")
|
||||
for name, i in info.names {
|
||||
if i > 0 { print_string(", ") }
|
||||
print_string(name)
|
||||
print_string(": ")
|
||||
print_type(info.types[i])
|
||||
print_string(" | ")
|
||||
print_u64(u64(info.bit_sizes[i]))
|
||||
}
|
||||
print_byte('}')
|
||||
|
||||
|
||||
case Type_Info_Simd_Vector:
|
||||
print_string("#simd[")
|
||||
|
||||
+15
-9
@@ -26,12 +26,18 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
|
||||
return dst
|
||||
}
|
||||
} else when ODIN_NO_CRT || (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) {
|
||||
// NOTE: on wasm, calls to these procs are generated (by LLVM) with type `i32` instead of `int`.
|
||||
//
|
||||
// NOTE: `#any_int` is also needed, because calls that we generate (and package code)
|
||||
// will be using `int` and need to be converted.
|
||||
int_t :: i32 when ODIN_ARCH == .wasm64p32 else int
|
||||
|
||||
@(link_name="memset", linkage="strong", require)
|
||||
memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr {
|
||||
memset :: proc "c" (ptr: rawptr, val: i32, #any_int len: int_t) -> rawptr {
|
||||
if ptr != nil && len != 0 {
|
||||
b := byte(val)
|
||||
p := ([^]byte)(ptr)
|
||||
for i := 0; i < len; i += 1 {
|
||||
for i := int_t(0); i < len; i += 1 {
|
||||
p[i] = b
|
||||
}
|
||||
}
|
||||
@@ -39,10 +45,10 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
|
||||
}
|
||||
|
||||
@(link_name="bzero", linkage="strong", require)
|
||||
bzero :: proc "c" (ptr: rawptr, len: int) -> rawptr {
|
||||
bzero :: proc "c" (ptr: rawptr, #any_int len: int_t) -> rawptr {
|
||||
if ptr != nil && len != 0 {
|
||||
p := ([^]byte)(ptr)
|
||||
for i := 0; i < len; i += 1 {
|
||||
for i := int_t(0); i < len; i += 1 {
|
||||
p[i] = 0
|
||||
}
|
||||
}
|
||||
@@ -50,7 +56,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
|
||||
}
|
||||
|
||||
@(link_name="memmove", linkage="strong", require)
|
||||
memmove :: proc "c" (dst, src: rawptr, len: int) -> rawptr {
|
||||
memmove :: proc "c" (dst, src: rawptr, #any_int len: int_t) -> rawptr {
|
||||
d, s := ([^]byte)(dst), ([^]byte)(src)
|
||||
if d == s || len == 0 {
|
||||
return dst
|
||||
@@ -63,7 +69,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
|
||||
}
|
||||
|
||||
if s > d && uintptr(s)-uintptr(d) < uintptr(len) {
|
||||
for i := 0; i < len; i += 1 {
|
||||
for i := int_t(0); i < len; i += 1 {
|
||||
d[i] = s[i]
|
||||
}
|
||||
return dst
|
||||
@@ -71,10 +77,10 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
|
||||
return memcpy(dst, src, len)
|
||||
}
|
||||
@(link_name="memcpy", linkage="strong", require)
|
||||
memcpy :: proc "c" (dst, src: rawptr, len: int) -> rawptr {
|
||||
memcpy :: proc "c" (dst, src: rawptr, #any_int len: int_t) -> rawptr {
|
||||
d, s := ([^]byte)(dst), ([^]byte)(src)
|
||||
if d != s {
|
||||
for i := 0; i < len; i += 1 {
|
||||
for i := int_t(0); i < len; i += 1 {
|
||||
d[i] = s[i]
|
||||
}
|
||||
}
|
||||
@@ -92,4 +98,4 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
|
||||
}
|
||||
return ptr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,19 +7,25 @@ ti_int :: struct #raw_union {
|
||||
all: i128,
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
ti_uint :: struct #raw_union {
|
||||
using s: struct { lo, hi: u64 },
|
||||
all: u128,
|
||||
}
|
||||
|
||||
@(link_name="__ashlti3", linkage="strong")
|
||||
__ashlti3 :: proc "contextless" (a: i128, b_: u32) -> i128 {
|
||||
__ashlti3 :: proc "contextless" (la, ha: u64, b_: u32) -> i128 {
|
||||
bits_in_dword :: size_of(u32)*8
|
||||
b := u32(b_)
|
||||
|
||||
input, result: ti_int
|
||||
input.all = a
|
||||
input.lo, input.hi = la, ha
|
||||
if b & bits_in_dword != 0 {
|
||||
result.lo = 0
|
||||
result.hi = input.lo << (b-bits_in_dword)
|
||||
} else {
|
||||
if b == 0 {
|
||||
return a
|
||||
return input.all
|
||||
}
|
||||
result.lo = input.lo<<b
|
||||
result.hi = (input.hi<<b) | (input.lo>>(bits_in_dword-b))
|
||||
@@ -29,12 +35,20 @@ __ashlti3 :: proc "contextless" (a: i128, b_: u32) -> i128 {
|
||||
|
||||
|
||||
@(link_name="__multi3", linkage="strong")
|
||||
__multi3 :: proc "contextless" (a, b: i128) -> i128 {
|
||||
__multi3 :: proc "contextless" (la, ha, lb, hb: u64) -> i128 {
|
||||
x, y, r: ti_int
|
||||
|
||||
x.all = a
|
||||
y.all = b
|
||||
|
||||
x.lo, x.hi = la, ha
|
||||
y.lo, y.hi = lb, hb
|
||||
r.all = i128(x.lo * y.lo) // TODO this is incorrect
|
||||
r.hi += x.hi*y.lo + x.lo*y.hi
|
||||
return r.all
|
||||
}
|
||||
}
|
||||
|
||||
@(link_name="__udivti3", linkage="strong")
|
||||
udivti3 :: proc "c" (la, ha, lb, hb: u64) -> u128 {
|
||||
a, b: ti_uint
|
||||
a.lo, a.hi = la, ha
|
||||
b.lo, b.hi = lb, hb
|
||||
return udivmodti4(a.all, b.all, nil)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,870 @@
|
||||
//+build wasm32, wasm64p32
|
||||
package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
/*
|
||||
Port of emmalloc, modified for use in Odin.
|
||||
|
||||
Invariants:
|
||||
- Per-allocation header overhead is 8 bytes, smallest allocated payload
|
||||
amount is 8 bytes, and a multiple of 4 bytes.
|
||||
- Acquired memory blocks are subdivided into disjoint regions that lie
|
||||
next to each other.
|
||||
- A region is either in used or free.
|
||||
Used regions may be adjacent, and a used and unused region
|
||||
may be adjacent, but not two unused ones - they would be
|
||||
merged.
|
||||
- Memory allocation takes constant time, unless the alloc needs to wasm_memory_grow()
|
||||
or memory is very close to being exhausted.
|
||||
- Free and used regions are managed inside "root regions", which are slabs
|
||||
of memory acquired via wasm_memory_grow().
|
||||
- Memory retrieved using wasm_memory_grow() can not be given back to the OS.
|
||||
Therefore, frees are internal to the allocator.
|
||||
|
||||
Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
WASM_Allocator :: struct #no_copy {
|
||||
// The minimum alignment of allocations.
|
||||
alignment: uint,
|
||||
// A region that contains as payload a single forward linked list of pointers to
|
||||
// root regions of each disjoint region blocks.
|
||||
list_of_all_regions: ^Root_Region,
|
||||
// For each of the buckets, maintain a linked list head node. The head node for each
|
||||
// free region is a sentinel node that does not actually represent any free space, but
|
||||
// the sentinel is used to avoid awkward testing against (if node == freeRegionHeadNode)
|
||||
// when adding and removing elements from the linked list, i.e. we are guaranteed that
|
||||
// the sentinel node is always fixed and there, and the actual free region list elements
|
||||
// start at free_region_buckets[i].next each.
|
||||
free_region_buckets: [NUM_FREE_BUCKETS]Region,
|
||||
// A bitmask that tracks the population status for each of the 64 distinct memory regions:
|
||||
// a zero at bit position i means that the free list bucket i is empty. This bitmask is
|
||||
// used to avoid redundant scanning of the 64 different free region buckets: instead by
|
||||
// looking at the bitmask we can find in constant time an index to a free region bucket
|
||||
// that contains free memory of desired size.
|
||||
free_region_buckets_used: BUCKET_BITMASK_T,
|
||||
// Because wasm memory can only be allocated in pages of 64k at a time, we keep any
|
||||
// spilled/unused bytes that are left from the allocated pages here, first using this
|
||||
// when bytes are needed.
|
||||
spill: []byte,
|
||||
// Mutex for thread safety, only used if the target feature "atomics" is enabled.
|
||||
mu: Mutex_State,
|
||||
}
|
||||
|
||||
// Not required to be called, called on first allocation otherwise.
|
||||
wasm_allocator_init :: proc(a: ^WASM_Allocator, alignment: uint = 8) {
|
||||
assert(is_power_of_two(alignment), "alignment must be a power of two")
|
||||
assert(alignment > 4, "alignment must be more than 4")
|
||||
|
||||
a.alignment = alignment
|
||||
|
||||
for i in 0..<NUM_FREE_BUCKETS {
|
||||
a.free_region_buckets[i].next = &a.free_region_buckets[i]
|
||||
a.free_region_buckets[i].prev = a.free_region_buckets[i].next
|
||||
}
|
||||
|
||||
if !claim_more_memory(a, 3*size_of(Region)) {
|
||||
panic("wasm_allocator: initial memory could not be allocated")
|
||||
}
|
||||
}
|
||||
|
||||
global_default_wasm_allocator_data: WASM_Allocator
|
||||
|
||||
default_wasm_allocator :: proc() -> Allocator {
|
||||
return wasm_allocator(&global_default_wasm_allocator_data)
|
||||
}
|
||||
|
||||
wasm_allocator :: proc(a: ^WASM_Allocator) -> Allocator {
|
||||
return {
|
||||
data = a,
|
||||
procedure = wasm_allocator_proc,
|
||||
}
|
||||
}
|
||||
|
||||
wasm_allocator_proc :: proc(a: rawptr, mode: Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
||||
a := (^WASM_Allocator)(a)
|
||||
if a == nil {
|
||||
a = &global_default_wasm_allocator_data
|
||||
}
|
||||
|
||||
if a.alignment == 0 {
|
||||
wasm_allocator_init(a)
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .Alloc:
|
||||
ptr := aligned_alloc(a, uint(alignment), uint(size), loc)
|
||||
if ptr == nil {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
intrinsics.mem_zero(ptr, size)
|
||||
return ([^]byte)(ptr)[:size], nil
|
||||
|
||||
case .Alloc_Non_Zeroed:
|
||||
ptr := aligned_alloc(a, uint(alignment), uint(size), loc)
|
||||
if ptr == nil {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
return ([^]byte)(ptr)[:size], nil
|
||||
|
||||
case .Resize:
|
||||
ptr := aligned_realloc(a, old_memory, uint(alignment), uint(size), loc)
|
||||
if ptr == nil {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
|
||||
bytes := ([^]byte)(ptr)[:size]
|
||||
|
||||
if size > old_size {
|
||||
new_region := raw_data(bytes[old_size:])
|
||||
intrinsics.mem_zero(new_region, size - old_size)
|
||||
}
|
||||
|
||||
return bytes, nil
|
||||
|
||||
case .Resize_Non_Zeroed:
|
||||
ptr := aligned_realloc(a, old_memory, uint(alignment), uint(size), loc)
|
||||
if ptr == nil {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
return ([^]byte)(ptr)[:size], nil
|
||||
|
||||
case .Free:
|
||||
free(a, old_memory, loc)
|
||||
return nil, nil
|
||||
|
||||
case .Free_All, .Query_Info:
|
||||
return nil, .Mode_Not_Implemented
|
||||
|
||||
case .Query_Features:
|
||||
set := (^Allocator_Mode_Set)(old_memory)
|
||||
if set != nil {
|
||||
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Resize, .Resize_Non_Zeroed, .Query_Features }
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
unreachable()
|
||||
}
|
||||
|
||||
// Returns the allocated size of the allocator (both free and used).
|
||||
// If `nil` is given, the global allocator is used.
|
||||
wasm_allocator_size :: proc(a: ^WASM_Allocator = nil) -> (size: uint) {
|
||||
a := a
|
||||
if a == nil {
|
||||
a = &global_default_wasm_allocator_data
|
||||
}
|
||||
|
||||
lock(a)
|
||||
defer unlock(a)
|
||||
|
||||
root := a.list_of_all_regions
|
||||
for root != nil {
|
||||
size += uint(uintptr(root.end_ptr) - uintptr(root))
|
||||
root = root.next
|
||||
}
|
||||
|
||||
size += len(a.spill)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Returns the amount of free memory on the allocator.
|
||||
// If `nil` is given, the global allocator is used.
|
||||
wasm_allocator_free_space :: proc(a: ^WASM_Allocator = nil) -> (free: uint) {
|
||||
a := a
|
||||
if a == nil {
|
||||
a = &global_default_wasm_allocator_data
|
||||
}
|
||||
|
||||
lock(a)
|
||||
defer unlock(a)
|
||||
|
||||
bucket_index: u64 = 0
|
||||
bucket_mask := a.free_region_buckets_used
|
||||
|
||||
for bucket_mask != 0 {
|
||||
index_add := intrinsics.count_trailing_zeros(bucket_mask)
|
||||
bucket_index += index_add
|
||||
bucket_mask >>= index_add
|
||||
for free_region := a.free_region_buckets[bucket_index].next; free_region != &a.free_region_buckets[bucket_index]; free_region = free_region.next {
|
||||
free += free_region.size - REGION_HEADER_SIZE
|
||||
}
|
||||
bucket_index += 1
|
||||
bucket_mask >>= 1
|
||||
}
|
||||
|
||||
free += len(a.spill)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
NUM_FREE_BUCKETS :: 64
|
||||
@(private="file")
|
||||
BUCKET_BITMASK_T :: u64
|
||||
|
||||
// Dynamic memory is subdivided into regions, in the format
|
||||
|
||||
// <size:u32> ..... <size:u32> | <size:u32> ..... <size:u32> | <size:u32> ..... <size:u32> | .....
|
||||
|
||||
// That is, at the bottom and top end of each memory region, the size of that region is stored. That allows traversing the
|
||||
// memory regions backwards and forwards. Because each allocation must be at least a multiple of 4 bytes, the lowest two bits of
|
||||
// each size field is unused. Free regions are distinguished by used regions by having the FREE_REGION_FLAG bit present
|
||||
// in the size field. I.e. for free regions, the size field is odd, and for used regions, the size field reads even.
|
||||
@(private="file")
|
||||
FREE_REGION_FLAG :: 0x1
|
||||
|
||||
// Attempts to alloc more than this many bytes would cause an overflow when calculating the size of a region,
|
||||
// therefore allocations larger than this are short-circuited immediately on entry.
|
||||
@(private="file")
|
||||
MAX_ALLOC_SIZE :: 0xFFFFFFC7
|
||||
|
||||
// A free region has the following structure:
|
||||
// <size:uint> <prevptr> <nextptr> ... <size:uint>
|
||||
|
||||
@(private="file")
|
||||
Region :: struct {
|
||||
size: uint,
|
||||
prev, next: ^Region,
|
||||
_at_the_end_of_this_struct_size: uint,
|
||||
}
|
||||
|
||||
// Each memory block starts with a Root_Region at the beginning.
|
||||
// The Root_Region specifies the size of the region block, and forms a linked
|
||||
// list of all Root_Regions in the program, starting with `list_of_all_regions`
|
||||
// below.
|
||||
@(private="file")
|
||||
Root_Region :: struct {
|
||||
size: u32,
|
||||
next: ^Root_Region,
|
||||
end_ptr: ^byte,
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
Mutex_State :: enum u32 {
|
||||
Unlocked = 0,
|
||||
Locked = 1,
|
||||
Waiting = 2,
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
lock :: proc(a: ^WASM_Allocator) {
|
||||
when intrinsics.has_target_feature("atomics") {
|
||||
@(cold)
|
||||
lock_slow :: proc(a: ^WASM_Allocator, curr_state: Mutex_State) {
|
||||
new_state := curr_state // Make a copy of it
|
||||
|
||||
spin_lock: for spin in 0..<i32(100) {
|
||||
state, ok := intrinsics.atomic_compare_exchange_weak_explicit(&a.mu, .Unlocked, new_state, .Acquire, .Consume)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
|
||||
if state == .Waiting {
|
||||
break spin_lock
|
||||
}
|
||||
|
||||
for i := min(spin+1, 32); i > 0; i -= 1 {
|
||||
intrinsics.cpu_relax()
|
||||
}
|
||||
}
|
||||
|
||||
// Set just in case 100 iterations did not do it
|
||||
new_state = .Waiting
|
||||
|
||||
for {
|
||||
if intrinsics.atomic_exchange_explicit(&a.mu, .Waiting, .Acquire) == .Unlocked {
|
||||
return
|
||||
}
|
||||
|
||||
assert(intrinsics.wasm_memory_atomic_wait32((^u32)(&a.mu), u32(new_state), -1) != 0)
|
||||
intrinsics.cpu_relax()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if v := intrinsics.atomic_exchange_explicit(&a.mu, .Locked, .Acquire); v != .Unlocked {
|
||||
lock_slow(a, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
unlock :: proc(a: ^WASM_Allocator) {
|
||||
when intrinsics.has_target_feature("atomics") {
|
||||
@(cold)
|
||||
unlock_slow :: proc(a: ^WASM_Allocator) {
|
||||
for {
|
||||
s := intrinsics.wasm_memory_atomic_notify32((^u32)(&a.mu), 1)
|
||||
if s >= 1 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch intrinsics.atomic_exchange_explicit(&a.mu, .Unlocked, .Release) {
|
||||
case .Unlocked:
|
||||
unreachable()
|
||||
case .Locked:
|
||||
// Okay
|
||||
case .Waiting:
|
||||
unlock_slow(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
assert_locked :: proc(a: ^WASM_Allocator) {
|
||||
when intrinsics.has_target_feature("atomics") {
|
||||
assert(intrinsics.atomic_load(&a.mu) != .Unlocked)
|
||||
}
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
has_alignment_uintptr :: proc(ptr: uintptr, #any_int alignment: uintptr) -> bool {
|
||||
return ptr & (alignment-1) == 0
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
has_alignment_uint :: proc(ptr: uint, alignment: uint) -> bool {
|
||||
return ptr & (alignment-1) == 0
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
has_alignment :: proc {
|
||||
has_alignment_uintptr,
|
||||
has_alignment_uint,
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
REGION_HEADER_SIZE :: 2*size_of(uint)
|
||||
|
||||
@(private="file")
|
||||
SMALLEST_ALLOCATION_SIZE :: 2*size_of(rawptr)
|
||||
|
||||
// Subdivide regions of free space into distinct circular doubly linked lists, where each linked list
|
||||
// represents a range of free space blocks. The following function compute_free_list_bucket() converts
|
||||
// an allocation size to the bucket index that should be looked at.
|
||||
#assert(NUM_FREE_BUCKETS == 64, "Following function is tailored specifically for the NUM_FREE_BUCKETS == 64 case")
|
||||
@(private="file")
|
||||
compute_free_list_bucket :: proc(size: uint) -> uint {
|
||||
if size < 128 { return (size >> 3) - 1 }
|
||||
|
||||
clz := intrinsics.count_leading_zeros(i32(size))
|
||||
bucket_index: i32 = ((clz > 19) \
|
||||
? 110 - (clz<<2) + ((i32)(size >> (u32)(29-clz)) ~ 4) \
|
||||
: min( 71 - (clz<<1) + ((i32)(size >> (u32)(30-clz)) ~ 2), NUM_FREE_BUCKETS-1))
|
||||
|
||||
assert(bucket_index >= 0)
|
||||
assert(bucket_index < NUM_FREE_BUCKETS)
|
||||
return uint(bucket_index)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
prev_region :: proc(region: ^Region) -> ^Region {
|
||||
prev_region_size := ([^]uint)(region)[-1]
|
||||
prev_region_size = prev_region_size & ~uint(FREE_REGION_FLAG)
|
||||
return (^Region)(uintptr(region)-uintptr(prev_region_size))
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
next_region :: proc(region: ^Region) -> ^Region {
|
||||
return (^Region)(uintptr(region)+uintptr(region.size))
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
region_ceiling_size :: proc(region: ^Region) -> uint {
|
||||
return ([^]uint)(uintptr(region)+uintptr(region.size))[-1]
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
region_is_free :: proc(r: ^Region) -> bool {
|
||||
return region_ceiling_size(r) & FREE_REGION_FLAG >= 1
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
region_is_in_use :: proc(r: ^Region) -> bool {
|
||||
return r.size == region_ceiling_size(r)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
region_payload_start_ptr :: proc(r: ^Region) -> [^]byte {
|
||||
return ([^]byte)(r)[size_of(uint):]
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
region_payload_end_ptr :: proc(r: ^Region) -> [^]byte {
|
||||
return ([^]byte)(r)[r.size-size_of(uint):]
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
create_used_region :: proc(ptr: rawptr, size: uint) {
|
||||
assert(has_alignment(uintptr(ptr), size_of(uint)))
|
||||
assert(has_alignment(size, size_of(uint)))
|
||||
assert(size >= size_of(Region))
|
||||
|
||||
uptr := ([^]uint)(ptr)
|
||||
uptr[0] = size
|
||||
uptr[size/size_of(uint)-1] = size
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
create_free_region :: proc(ptr: rawptr, size: uint) {
|
||||
assert(has_alignment(uintptr(ptr), size_of(uint)))
|
||||
assert(has_alignment(size, size_of(uint)))
|
||||
assert(size >= size_of(Region))
|
||||
|
||||
free_region := (^Region)(ptr)
|
||||
free_region.size = size
|
||||
([^]uint)(ptr)[size/size_of(uint)-1] = size | FREE_REGION_FLAG
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
prepend_to_free_list :: proc(region: ^Region, prepend_to: ^Region) {
|
||||
assert(region_is_free(region))
|
||||
region.next = prepend_to
|
||||
region.prev = prepend_to.prev
|
||||
prepend_to.prev = region
|
||||
region.prev.next = region
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
unlink_from_free_list :: proc(region: ^Region) {
|
||||
assert(region_is_free(region))
|
||||
region.prev.next = region.next
|
||||
region.next.prev = region.prev
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
link_to_free_list :: proc(a: ^WASM_Allocator, free_region: ^Region) {
|
||||
assert(free_region.size >= size_of(Region))
|
||||
bucket_index := compute_free_list_bucket(free_region.size-REGION_HEADER_SIZE)
|
||||
free_list_head := &a.free_region_buckets[bucket_index]
|
||||
free_region.prev = free_list_head
|
||||
free_region.next = free_list_head.next
|
||||
free_list_head.next = free_region
|
||||
free_region.next.prev = free_region
|
||||
a.free_region_buckets_used |= BUCKET_BITMASK_T(1) << bucket_index
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
claim_more_memory :: proc(a: ^WASM_Allocator, num_bytes: uint) -> bool {
|
||||
|
||||
PAGE_SIZE :: 64 * 1024
|
||||
|
||||
page_alloc :: proc(page_count: int) -> []byte {
|
||||
prev_page_count := intrinsics.wasm_memory_grow(0, uintptr(page_count))
|
||||
if prev_page_count < 0 { return nil }
|
||||
|
||||
ptr := ([^]byte)(uintptr(prev_page_count) * PAGE_SIZE)
|
||||
return ptr[:page_count * PAGE_SIZE]
|
||||
}
|
||||
|
||||
alloc :: proc(a: ^WASM_Allocator, num_bytes: uint) -> (bytes: [^]byte) #no_bounds_check {
|
||||
if uint(len(a.spill)) >= num_bytes {
|
||||
bytes = raw_data(a.spill[:num_bytes])
|
||||
a.spill = a.spill[num_bytes:]
|
||||
return
|
||||
}
|
||||
|
||||
pages := int((num_bytes / PAGE_SIZE) + 1)
|
||||
allocated := page_alloc(pages)
|
||||
if allocated == nil { return nil }
|
||||
|
||||
// If the allocated memory is a direct continuation of the spill from before,
|
||||
// we can just extend the spill.
|
||||
spill_end := uintptr(raw_data(a.spill)) + uintptr(len(a.spill))
|
||||
if spill_end == uintptr(raw_data(allocated)) {
|
||||
raw_spill := transmute(^Raw_Slice)(&a.spill)
|
||||
raw_spill.len += len(allocated)
|
||||
} else {
|
||||
// Otherwise, we have to "waste" the previous spill.
|
||||
// Now this is probably uncommon, and will only happen if another code path
|
||||
// is also requesting pages.
|
||||
a.spill = allocated
|
||||
}
|
||||
|
||||
bytes = raw_data(a.spill)
|
||||
a.spill = a.spill[num_bytes:]
|
||||
return
|
||||
}
|
||||
|
||||
num_bytes := num_bytes
|
||||
num_bytes = align_forward(num_bytes, a.alignment)
|
||||
|
||||
start_ptr := alloc(a, uint(num_bytes))
|
||||
if start_ptr == nil { return false }
|
||||
|
||||
assert(has_alignment(uintptr(start_ptr), align_of(uint)))
|
||||
end_ptr := start_ptr[num_bytes:]
|
||||
|
||||
end_sentinel_region := (^Region)(end_ptr[-size_of(Region):])
|
||||
create_used_region(end_sentinel_region, size_of(Region))
|
||||
|
||||
// If we are the sole user of wasm_memory_grow(), it will feed us continuous/consecutive memory addresses - take advantage
|
||||
// of that if so: instead of creating two disjoint memory regions blocks, expand the previous one to a larger size.
|
||||
prev_alloc_end_address := a.list_of_all_regions != nil ? a.list_of_all_regions.end_ptr : nil
|
||||
if start_ptr == prev_alloc_end_address {
|
||||
prev_end_sentinel := prev_region((^Region)(start_ptr))
|
||||
assert(region_is_in_use(prev_end_sentinel))
|
||||
prev_region := prev_region(prev_end_sentinel)
|
||||
|
||||
a.list_of_all_regions.end_ptr = end_ptr
|
||||
|
||||
// Two scenarios, either the last region of the previous block was in use, in which case we need to create
|
||||
// a new free region in the newly allocated space; or it was free, in which case we can extend that region
|
||||
// to cover a larger size.
|
||||
if region_is_free(prev_region) {
|
||||
new_free_region_size := uint(uintptr(end_sentinel_region) - uintptr(prev_region))
|
||||
unlink_from_free_list(prev_region)
|
||||
create_free_region(prev_region, new_free_region_size)
|
||||
link_to_free_list(a, prev_region)
|
||||
return true
|
||||
}
|
||||
|
||||
start_ptr = start_ptr[-size_of(Region):]
|
||||
} else {
|
||||
create_used_region(start_ptr, size_of(Region))
|
||||
|
||||
new_region_block := (^Root_Region)(start_ptr)
|
||||
new_region_block.next = a.list_of_all_regions
|
||||
new_region_block.end_ptr = end_ptr
|
||||
a.list_of_all_regions = new_region_block
|
||||
start_ptr = start_ptr[size_of(Region):]
|
||||
}
|
||||
|
||||
create_free_region(start_ptr, uint(uintptr(end_sentinel_region)-uintptr(start_ptr)))
|
||||
link_to_free_list(a, (^Region)(start_ptr))
|
||||
return true
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
validate_alloc_size :: proc(size: uint) -> uint {
|
||||
#assert(size_of(uint) >= size_of(uintptr))
|
||||
#assert(size_of(uint) % size_of(uintptr) == 0)
|
||||
|
||||
// NOTE: emmalloc aligns this forward on pointer size, but I think that is a mistake and will
|
||||
// do bad on wasm64p32.
|
||||
|
||||
validated_size := size > SMALLEST_ALLOCATION_SIZE ? align_forward(size, size_of(uint)) : SMALLEST_ALLOCATION_SIZE
|
||||
assert(validated_size >= size) // Assert we haven't wrapped.
|
||||
|
||||
return validated_size
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
allocate_memory :: proc(a: ^WASM_Allocator, alignment: uint, size: uint, loc := #caller_location) -> rawptr {
|
||||
|
||||
attempt_allocate :: proc(a: ^WASM_Allocator, free_region: ^Region, alignment, size: uint) -> rawptr {
|
||||
assert_locked(a)
|
||||
free_region := free_region
|
||||
|
||||
payload_start_ptr := uintptr(region_payload_start_ptr(free_region))
|
||||
payload_start_ptr_aligned := align_forward(payload_start_ptr, uintptr(alignment))
|
||||
payload_end_ptr := uintptr(region_payload_end_ptr(free_region))
|
||||
|
||||
if payload_start_ptr_aligned + uintptr(size) > payload_end_ptr {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We have enough free space, so the memory allocation will be made into this region. Remove this free region
|
||||
// from the list of free regions: whatever slop remains will be later added back to the free region pool.
|
||||
unlink_from_free_list(free_region)
|
||||
|
||||
// Before we proceed further, fix up the boundary between this and the preceding region,
|
||||
// so that the boundary between the two regions happens at a right spot for the payload to be aligned.
|
||||
if payload_start_ptr != payload_start_ptr_aligned {
|
||||
prev := prev_region(free_region)
|
||||
assert(region_is_in_use(prev))
|
||||
region_boundary_bump_amount := payload_start_ptr_aligned - payload_start_ptr
|
||||
new_this_region_size := free_region.size - uint(region_boundary_bump_amount)
|
||||
create_used_region(prev, prev.size + uint(region_boundary_bump_amount))
|
||||
free_region = (^Region)(uintptr(free_region) + region_boundary_bump_amount)
|
||||
free_region.size = new_this_region_size
|
||||
}
|
||||
|
||||
// Next, we need to decide whether this region is so large that it should be split into two regions,
|
||||
// one representing the newly used memory area, and at the high end a remaining leftover free area.
|
||||
// This splitting to two is done always if there is enough space for the high end to fit a region.
|
||||
// Carve 'size' bytes of payload off this region. So,
|
||||
// [sz prev next sz]
|
||||
// becomes
|
||||
// [sz payload sz] [sz prev next sz]
|
||||
if size_of(Region) + REGION_HEADER_SIZE + size <= free_region.size {
|
||||
new_free_region := (^Region)(uintptr(free_region) + REGION_HEADER_SIZE + uintptr(size))
|
||||
create_free_region(new_free_region, free_region.size - size - REGION_HEADER_SIZE)
|
||||
link_to_free_list(a, new_free_region)
|
||||
create_used_region(free_region, size + REGION_HEADER_SIZE)
|
||||
} else {
|
||||
// There is not enough space to split the free memory region into used+free parts, so consume the whole
|
||||
// region as used memory, not leaving a free memory region behind.
|
||||
// Initialize the free region as used by resetting the ceiling size to the same value as the size at bottom.
|
||||
([^]uint)(uintptr(free_region) + uintptr(free_region.size))[-1] = free_region.size
|
||||
}
|
||||
|
||||
return rawptr(uintptr(free_region) + size_of(uint))
|
||||
}
|
||||
|
||||
assert_locked(a)
|
||||
assert(is_power_of_two(alignment))
|
||||
assert(size <= MAX_ALLOC_SIZE, "allocation too big", loc=loc)
|
||||
|
||||
alignment := alignment
|
||||
alignment = max(alignment, a.alignment)
|
||||
|
||||
size := size
|
||||
size = validate_alloc_size(size)
|
||||
|
||||
// Attempt to allocate memory starting from smallest bucket that can contain the required amount of memory.
|
||||
// Under normal alignment conditions this should always be the first or second bucket we look at, but if
|
||||
// performing an allocation with complex alignment, we may need to look at multiple buckets.
|
||||
bucket_index := compute_free_list_bucket(size)
|
||||
bucket_mask := a.free_region_buckets_used >> bucket_index
|
||||
|
||||
// Loop through each bucket that has free regions in it, based on bits set in free_region_buckets_used bitmap.
|
||||
for bucket_mask != 0 {
|
||||
index_add := intrinsics.count_trailing_zeros(bucket_mask)
|
||||
bucket_index += uint(index_add)
|
||||
bucket_mask >>= index_add
|
||||
assert(bucket_index <= NUM_FREE_BUCKETS-1)
|
||||
assert(a.free_region_buckets_used & (BUCKET_BITMASK_T(1) << bucket_index) > 0)
|
||||
|
||||
free_region := a.free_region_buckets[bucket_index].next
|
||||
assert(free_region != nil)
|
||||
if free_region != &a.free_region_buckets[bucket_index] {
|
||||
ptr := attempt_allocate(a, free_region, alignment, size)
|
||||
if ptr != nil {
|
||||
return ptr
|
||||
}
|
||||
|
||||
// We were not able to allocate from the first region found in this bucket, so penalize
|
||||
// the region by cycling it to the end of the doubly circular linked list. (constant time)
|
||||
// This provides a randomized guarantee that when performing allocations of size k to a
|
||||
// bucket of [k-something, k+something] range, we will not always attempt to satisfy the
|
||||
// allocation from the same available region at the front of the list, but we try each
|
||||
// region in turn.
|
||||
unlink_from_free_list(free_region)
|
||||
prepend_to_free_list(free_region, &a.free_region_buckets[bucket_index])
|
||||
// But do not stick around to attempt to look at other regions in this bucket - move
|
||||
// to search the next populated bucket index if this did not fit. This gives a practical
|
||||
// "allocation in constant time" guarantee, since the next higher bucket will only have
|
||||
// regions that are all of strictly larger size than the requested allocation. Only if
|
||||
// there is a difficult alignment requirement we may fail to perform the allocation from
|
||||
// a region in the next bucket, and if so, we keep trying higher buckets until one of them
|
||||
// works.
|
||||
bucket_index += 1
|
||||
bucket_mask >>= 1
|
||||
} else {
|
||||
// This bucket was not populated after all with any regions,
|
||||
// but we just had a stale bit set to mark a populated bucket.
|
||||
// Reset the bit to update latest status so that we do not
|
||||
// redundantly look at this bucket again.
|
||||
a.free_region_buckets_used &= ~(BUCKET_BITMASK_T(1) << bucket_index)
|
||||
bucket_mask ~= 1
|
||||
}
|
||||
|
||||
assert((bucket_index == NUM_FREE_BUCKETS && bucket_mask == 0) || (bucket_mask == a.free_region_buckets_used >> bucket_index))
|
||||
}
|
||||
|
||||
// None of the buckets were able to accommodate an allocation. If this happens we are almost out of memory.
|
||||
// The largest bucket might contain some suitable regions, but we only looked at one region in that bucket, so
|
||||
// as a last resort, loop through more free regions in the bucket that represents the largest allocations available.
|
||||
// But only if the bucket representing largest allocations available is not any of the first thirty buckets,
|
||||
// these represent allocatable areas less than <1024 bytes - which could be a lot of scrap.
|
||||
// In such case, prefer to claim more memory right away.
|
||||
largest_bucket_index := NUM_FREE_BUCKETS - 1 - intrinsics.count_leading_zeros(a.free_region_buckets_used)
|
||||
// free_region will be null if there is absolutely no memory left. (all buckets are 100% used)
|
||||
free_region := a.free_region_buckets_used > 0 ? a.free_region_buckets[largest_bucket_index].next : nil
|
||||
// The 30 first free region buckets cover memory blocks < 2048 bytes, so skip looking at those here (too small)
|
||||
if a.free_region_buckets_used >> 30 > 0 {
|
||||
// Look only at a constant number of regions in this bucket max, to avoid bad worst case behavior.
|
||||
// If this many regions cannot find free space, we give up and prefer to claim more memory instead.
|
||||
max_regions_to_try_before_giving_up :: 99
|
||||
num_tries_left := max_regions_to_try_before_giving_up
|
||||
for ; free_region != &a.free_region_buckets[largest_bucket_index] && num_tries_left > 0; num_tries_left -= 1 {
|
||||
ptr := attempt_allocate(a, free_region, alignment, size)
|
||||
if ptr != nil {
|
||||
return ptr
|
||||
}
|
||||
free_region = free_region.next
|
||||
}
|
||||
}
|
||||
|
||||
// We were unable to find a free memory region. Must claim more memory!
|
||||
num_bytes_to_claim := size+size_of(Region)*3
|
||||
if alignment > a.alignment {
|
||||
num_bytes_to_claim += alignment
|
||||
}
|
||||
success := claim_more_memory(a, num_bytes_to_claim)
|
||||
if (success) {
|
||||
// Try allocate again with the newly available memory.
|
||||
return allocate_memory(a, alignment, size)
|
||||
}
|
||||
|
||||
// also claim_more_memory failed, we are really really constrained :( As a last resort, go back to looking at the
|
||||
// bucket we already looked at above, continuing where the above search left off - perhaps there are
|
||||
// regions we overlooked the first time that might be able to satisfy the allocation.
|
||||
if free_region != nil {
|
||||
for free_region != &a.free_region_buckets[largest_bucket_index] {
|
||||
ptr := attempt_allocate(a, free_region, alignment, size)
|
||||
if ptr != nil {
|
||||
return ptr
|
||||
}
|
||||
free_region = free_region.next
|
||||
}
|
||||
}
|
||||
|
||||
// Fully out of memory.
|
||||
return nil
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
aligned_alloc :: proc(a: ^WASM_Allocator, alignment, size: uint, loc := #caller_location) -> rawptr {
|
||||
lock(a)
|
||||
defer unlock(a)
|
||||
|
||||
return allocate_memory(a, alignment, size, loc)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
free :: proc(a: ^WASM_Allocator, ptr: rawptr, loc := #caller_location) {
|
||||
if ptr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
region_start_ptr := uintptr(ptr) - size_of(uint)
|
||||
region := (^Region)(region_start_ptr)
|
||||
assert(has_alignment(region_start_ptr, size_of(uint)))
|
||||
|
||||
lock(a)
|
||||
defer unlock(a)
|
||||
|
||||
size := region.size
|
||||
assert(region_is_in_use(region), "double free", loc=loc)
|
||||
|
||||
prev_region_size_field := ([^]uint)(region)[-1]
|
||||
prev_region_size := prev_region_size_field & ~uint(FREE_REGION_FLAG)
|
||||
if prev_region_size_field != prev_region_size {
|
||||
prev_region := (^Region)(uintptr(region) - uintptr(prev_region_size))
|
||||
unlink_from_free_list(prev_region)
|
||||
region_start_ptr = uintptr(prev_region)
|
||||
size += prev_region_size
|
||||
}
|
||||
|
||||
next_reg := next_region(region)
|
||||
size_at_end := (^uint)(region_payload_end_ptr(next_reg))^
|
||||
if next_reg.size != size_at_end {
|
||||
unlink_from_free_list(next_reg)
|
||||
size += next_reg.size
|
||||
}
|
||||
|
||||
create_free_region(rawptr(region_start_ptr), size)
|
||||
link_to_free_list(a, (^Region)(region_start_ptr))
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
aligned_realloc :: proc(a: ^WASM_Allocator, ptr: rawptr, alignment, size: uint, loc := #caller_location) -> rawptr {
|
||||
|
||||
attempt_region_resize :: proc(a: ^WASM_Allocator, region: ^Region, size: uint) -> bool {
|
||||
lock(a)
|
||||
defer unlock(a)
|
||||
|
||||
// First attempt to resize this region, if the next region that follows this one
|
||||
// is a free region.
|
||||
next_reg := next_region(region)
|
||||
next_region_end_ptr := uintptr(next_reg) + uintptr(next_reg.size)
|
||||
size_at_ceiling := ([^]uint)(next_region_end_ptr)[-1]
|
||||
if next_reg.size != size_at_ceiling { // Next region is free?
|
||||
assert(region_is_free(next_reg))
|
||||
new_next_region_start_ptr := uintptr(region) + uintptr(size)
|
||||
assert(has_alignment(new_next_region_start_ptr, size_of(uint)))
|
||||
// Next region does not shrink to too small size?
|
||||
if new_next_region_start_ptr + size_of(Region) <= next_region_end_ptr {
|
||||
unlink_from_free_list(next_reg)
|
||||
create_free_region(rawptr(new_next_region_start_ptr), uint(next_region_end_ptr - new_next_region_start_ptr))
|
||||
link_to_free_list(a, (^Region)(new_next_region_start_ptr))
|
||||
create_used_region(region, uint(new_next_region_start_ptr - uintptr(region)))
|
||||
return true
|
||||
}
|
||||
// If we remove the next region altogether, allocation is satisfied?
|
||||
if new_next_region_start_ptr <= next_region_end_ptr {
|
||||
unlink_from_free_list(next_reg)
|
||||
create_used_region(region, region.size + next_reg.size)
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
// Next region is an used region - we cannot change its starting address. However if we are shrinking the
|
||||
// size of this region, we can create a new free region between this and the next used region.
|
||||
if size + size_of(Region) <= region.size {
|
||||
free_region_size := region.size - size
|
||||
create_used_region(region, size)
|
||||
free_region := (^Region)(uintptr(region) + uintptr(size))
|
||||
create_free_region(free_region, free_region_size)
|
||||
link_to_free_list(a, free_region)
|
||||
return true
|
||||
} else if size <= region.size {
|
||||
// Caller was asking to shrink the size, but due to not being able to fit a full Region in the shrunk
|
||||
// area, we cannot actually do anything. This occurs if the shrink amount is really small. In such case,
|
||||
// just call it success without doing any work.
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if ptr == nil {
|
||||
return aligned_alloc(a, alignment, size, loc)
|
||||
}
|
||||
|
||||
if size == 0 {
|
||||
free(a, ptr, loc)
|
||||
return nil
|
||||
}
|
||||
|
||||
if size > MAX_ALLOC_SIZE {
|
||||
return nil
|
||||
}
|
||||
|
||||
assert(is_power_of_two(alignment))
|
||||
assert(has_alignment(uintptr(ptr), alignment), "realloc on different alignment than original allocation", loc=loc)
|
||||
|
||||
size := size
|
||||
size = validate_alloc_size(size)
|
||||
|
||||
region := (^Region)(uintptr(ptr) - size_of(uint))
|
||||
|
||||
// Attempt an in-place resize.
|
||||
if attempt_region_resize(a, region, size + REGION_HEADER_SIZE) {
|
||||
return ptr
|
||||
}
|
||||
|
||||
// Can't do it in-place, allocate new region and copy over.
|
||||
newptr := aligned_alloc(a, alignment, size, loc)
|
||||
if newptr != nil {
|
||||
intrinsics.mem_copy(newptr, ptr, min(size, region.size - REGION_HEADER_SIZE))
|
||||
free(a, ptr, loc=loc)
|
||||
}
|
||||
|
||||
return newptr
|
||||
}
|
||||
+42
-17
@@ -2,7 +2,6 @@
|
||||
set -eu
|
||||
|
||||
: ${CPPFLAGS=}
|
||||
: ${CXX=clang++}
|
||||
: ${CXXFLAGS=}
|
||||
: ${LDFLAGS=}
|
||||
: ${LLVM_CONFIG=}
|
||||
@@ -26,17 +25,19 @@ error() {
|
||||
|
||||
if [ -z "$LLVM_CONFIG" ]; then
|
||||
# darwin, linux, openbsd
|
||||
if [ -n "$(command -v llvm-config-17)" ]; then LLVM_CONFIG="llvm-config-17"
|
||||
if [ -n "$(command -v llvm-config-18)" ]; then LLVM_CONFIG="llvm-config-18"
|
||||
elif [ -n "$(command -v llvm-config-17)" ]; then LLVM_CONFIG="llvm-config-17"
|
||||
elif [ -n "$(command -v llvm-config-14)" ]; then LLVM_CONFIG="llvm-config-14"
|
||||
elif [ -n "$(command -v llvm-config-13)" ]; then LLVM_CONFIG="llvm-config-13"
|
||||
elif [ -n "$(command -v llvm-config-12)" ]; then LLVM_CONFIG="llvm-config-12"
|
||||
elif [ -n "$(command -v llvm-config-11)" ]; then LLVM_CONFIG="llvm-config-11"
|
||||
# freebsd
|
||||
elif [ -n "$(command -v llvm-config17)" ]; then LLVM_CONFIG="llvm-config-17"
|
||||
elif [ -n "$(command -v llvm-config14)" ]; then LLVM_CONFIG="llvm-config-14"
|
||||
elif [ -n "$(command -v llvm-config13)" ]; then LLVM_CONFIG="llvm-config-13"
|
||||
elif [ -n "$(command -v llvm-config12)" ]; then LLVM_CONFIG="llvm-config-12"
|
||||
elif [ -n "$(command -v llvm-config11)" ]; then LLVM_CONFIG="llvm-config-11"
|
||||
elif [ -n "$(command -v llvm-config18)" ]; then LLVM_CONFIG="llvm-config18"
|
||||
elif [ -n "$(command -v llvm-config17)" ]; then LLVM_CONFIG="llvm-config17"
|
||||
elif [ -n "$(command -v llvm-config14)" ]; then LLVM_CONFIG="llvm-config14"
|
||||
elif [ -n "$(command -v llvm-config13)" ]; then LLVM_CONFIG="llvm-config13"
|
||||
elif [ -n "$(command -v llvm-config12)" ]; then LLVM_CONFIG="llvm-config12"
|
||||
elif [ -n "$(command -v llvm-config11)" ]; then LLVM_CONFIG="llvm-config11"
|
||||
# fallback
|
||||
elif [ -n "$(command -v llvm-config)" ]; then LLVM_CONFIG="llvm-config"
|
||||
else
|
||||
@@ -44,32 +45,51 @@ if [ -z "$LLVM_CONFIG" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -x "$(which clang++)" ]; then
|
||||
: ${CXX="clang++"}
|
||||
elif [ -x "$($LLVM_CONFIG --bindir)/clang++" ]; then
|
||||
: ${CXX=$($LLVM_CONFIG --bindir)/clang++}
|
||||
else
|
||||
error "No clang++ command found. Set CXX to proceed."
|
||||
fi
|
||||
|
||||
LLVM_VERSION="$($LLVM_CONFIG --version)"
|
||||
LLVM_VERSION_MAJOR="$(echo $LLVM_VERSION | awk -F. '{print $1}')"
|
||||
LLVM_VERSION_MINOR="$(echo $LLVM_VERSION | awk -F. '{print $2}')"
|
||||
LLVM_VERSION_PATCH="$(echo $LLVM_VERSION | awk -F. '{print $3}')"
|
||||
|
||||
if [ $LLVM_VERSION_MAJOR -lt 11 ] ||
|
||||
([ $LLVM_VERSION_MAJOR -gt 14 ] && [ $LLVM_VERSION_MAJOR -lt 17 ]); then
|
||||
error "Invalid LLVM version $LLVM_VERSION: must be 11, 12, 13, 14 or 17"
|
||||
if [ $LLVM_VERSION_MAJOR -lt 11 ] || ([ $LLVM_VERSION_MAJOR -gt 14 ] && [ $LLVM_VERSION_MAJOR -lt 17 ]) || [ $LLVM_VERSION_MAJOR -gt 18 ]; then
|
||||
error "Invalid LLVM version $LLVM_VERSION: must be 11, 12, 13, 14, 17 or 18"
|
||||
fi
|
||||
|
||||
case "$OS_NAME" in
|
||||
Darwin)
|
||||
if [ "$OS_ARCH" == "arm64" ]; then
|
||||
if [ $LLVM_VERSION_MAJOR -lt 13 ] || [ $LLVM_VERSION_MAJOR -gt 17 ]; then
|
||||
error "Darwin Arm64 requires LLVM 13, 14 or 17"
|
||||
if [ "$OS_ARCH" = "arm64" ]; then
|
||||
if [ $LLVM_VERSION_MAJOR -lt 13 ]; then
|
||||
error "Invalid LLVM version $LLVM_VERSION: Darwin Arm64 requires LLVM 13, 14, 17 or 18"
|
||||
fi
|
||||
fi
|
||||
|
||||
CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
|
||||
LDFLAGS="$LDFLAGS -liconv -ldl -framework System"
|
||||
LDFLAGS="$LDFLAGS -lLLVM-C"
|
||||
darwin_sysroot=
|
||||
if [ $(which xcrun) ]; then
|
||||
darwin_sysroot="--sysroot $(xcrun --sdk macosx --show-sdk-path)"
|
||||
elif [[ -e "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" ]]; then
|
||||
darwin_sysroot="--sysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk"
|
||||
else
|
||||
echo "Warning: MacOSX.sdk not found."
|
||||
fi
|
||||
|
||||
CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags) ${darwin_sysroot}"
|
||||
LDFLAGS="$LDFLAGS -liconv -ldl -framework System -lLLVM"
|
||||
;;
|
||||
FreeBSD)
|
||||
CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
|
||||
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
|
||||
;;
|
||||
NetBSD)
|
||||
CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
|
||||
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
|
||||
;;
|
||||
Linux)
|
||||
CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
|
||||
LDFLAGS="$LDFLAGS -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)"
|
||||
@@ -83,6 +103,11 @@ OpenBSD)
|
||||
LDFLAGS="$LDFLAGS -liconv"
|
||||
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
|
||||
;;
|
||||
Haiku)
|
||||
CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags) -I/system/develop/headers/private/shared -I/system/develop/headers/private/kernel"
|
||||
LDFLAGS="$LDFLAGS -liconv"
|
||||
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
|
||||
;;
|
||||
*)
|
||||
error "Platform \"$OS_NAME\" unsupported"
|
||||
;;
|
||||
@@ -97,7 +122,7 @@ build_odin() {
|
||||
EXTRAFLAGS="-O3"
|
||||
;;
|
||||
release-native)
|
||||
if [ "$OS_ARCH" == "arm64" ]; then
|
||||
if [ "$OS_ARCH" = "arm64" ]; then
|
||||
# Use preferred flag for Arm (ie arm64 / aarch64 / etc)
|
||||
EXTRAFLAGS="-O3 -mcpu=native"
|
||||
else
|
||||
|
||||
@@ -9,11 +9,11 @@ def main():
|
||||
files_by_date = {}
|
||||
bucket = sys.argv[1]
|
||||
|
||||
files_lines = execute_cli(f"b2 ls --long {bucket} nightly").split("\n")
|
||||
files_lines = execute_cli(f"b2 ls --long b2://{bucket}/nightly/").split("\n")
|
||||
for x in files_lines:
|
||||
parts = x.split(" ", 1)
|
||||
if parts[0]:
|
||||
json_str = execute_cli(f"b2 get-file-info {parts[0]}")
|
||||
json_str = execute_cli(f"b2 file info {parts[0]}")
|
||||
data = json.loads(json_str)
|
||||
name = remove_prefix(data['fileName'], "nightly/")
|
||||
url = f"https://f001.backblazeb2.com/file/{bucket}/nightly/{urllib.parse.quote_plus(name)}"
|
||||
@@ -47,5 +47,4 @@ def execute_cli(command):
|
||||
return sb.stdout.read().decode("utf-8");
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
||||
sys.exit(main())
|
||||
@@ -11,7 +11,7 @@ def main():
|
||||
days_to_keep = int(sys.argv[2])
|
||||
print(f"Looking for binaries to delete older than {days_to_keep} days")
|
||||
|
||||
files_lines = execute_cli(f"b2 ls --long --versions {bucket} nightly").split("\n")
|
||||
files_lines = execute_cli(f"b2 ls --long --versions b2://{bucket}/nightly/").split("\n")
|
||||
for x in files_lines:
|
||||
parts = [y for y in x.split(' ') if y]
|
||||
|
||||
@@ -22,7 +22,7 @@ def main():
|
||||
|
||||
if delta.days > days_to_keep:
|
||||
print(f'Deleting {parts[5]}')
|
||||
execute_cli(f'b2 delete-file-version {parts[0]}')
|
||||
execute_cli(f'b2 rm {parts[0]}')
|
||||
|
||||
|
||||
def execute_cli(command):
|
||||
@@ -30,5 +30,4 @@ def execute_cli(command):
|
||||
return sb.stdout.read().decode("utf-8");
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
||||
sys.exit(main())
|
||||
Regular → Executable
+14
-2
@@ -1,5 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
bucket=$1
|
||||
platform=$2
|
||||
artifact=$3
|
||||
@@ -9,5 +11,15 @@ filename="odin-$platform-nightly+$now.zip"
|
||||
|
||||
echo "Creating archive $filename from $artifact and uploading to $bucket"
|
||||
|
||||
7z a -bd "output/$filename" -r "$artifact"
|
||||
b2 upload-file --noProgress "$bucket" "output/$filename" "nightly/$filename"
|
||||
# If this is already zipped up (done before artifact upload to keep permissions in tact), just move it.
|
||||
if [ "${artifact: -4}" == ".zip" ]
|
||||
then
|
||||
echo "Artifact already a zip"
|
||||
mkdir -p "output"
|
||||
mv "$artifact" "output/$filename"
|
||||
else
|
||||
echo "Artifact needs to be zipped"
|
||||
7z a -bd "output/$filename" -r "$artifact"
|
||||
fi
|
||||
|
||||
b2 file upload "$bucket" "output/$filename" "nightly/$filename"
|
||||
|
||||
@@ -81,7 +81,7 @@ _reader_read_new_chunk :: proc(b: ^Reader) -> io.Error {
|
||||
for i := b.max_consecutive_empty_reads; i > 0; i -= 1 {
|
||||
n, err := io.read(b.rd, b.buf[b.w:])
|
||||
if n < 0 {
|
||||
return .Negative_Read
|
||||
return err if err != nil else .Negative_Read
|
||||
}
|
||||
b.w += n
|
||||
if err != nil {
|
||||
@@ -189,7 +189,7 @@ reader_read :: proc(b: ^Reader, p: []byte) -> (n: int, err: io.Error) {
|
||||
if len(p) >= len(b.buf) {
|
||||
n, b.err = io.read(b.rd, p)
|
||||
if n < 0 {
|
||||
return 0, .Negative_Read
|
||||
return 0, b.err if b.err != nil else .Negative_Read
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
@@ -202,7 +202,7 @@ reader_read :: proc(b: ^Reader, p: []byte) -> (n: int, err: io.Error) {
|
||||
b.r, b.w = 0, 0
|
||||
n, b.err = io.read(b.rd, b.buf)
|
||||
if n < 0 {
|
||||
return 0, .Negative_Read
|
||||
return 0, b.err if b.err != nil else .Negative_Read
|
||||
}
|
||||
if n == 0 {
|
||||
return 0, _reader_consume_err(b)
|
||||
@@ -290,7 +290,7 @@ reader_write_to :: proc(b: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) {
|
||||
write_buf :: proc(b: ^Reader, w: io.Writer) -> (i64, io.Error) {
|
||||
n, err := io.write(w, b.buf[b.r:b.w])
|
||||
if n < 0 {
|
||||
return 0, .Negative_Write
|
||||
return 0, err if err != nil else .Negative_Write
|
||||
}
|
||||
b.r += n
|
||||
return i64(n), err
|
||||
|
||||
@@ -95,6 +95,10 @@ writer_write :: proc(b: ^Writer, p: []byte) -> (n: int, err: io.Error) {
|
||||
m: int
|
||||
if writer_buffered(b) == 0 {
|
||||
m, b.err = io.write(b.wr, p)
|
||||
if m < 0 && b.err == nil {
|
||||
b.err = .Negative_Write
|
||||
break
|
||||
}
|
||||
} else {
|
||||
m = copy(b.buf[b.n:], p)
|
||||
b.n += m
|
||||
@@ -226,7 +230,6 @@ writer_to_writer :: proc(b: ^Writer) -> (s: io.Writer) {
|
||||
|
||||
|
||||
|
||||
@(private)
|
||||
_writer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
|
||||
b := (^Writer)(stream_data)
|
||||
#partial switch mode {
|
||||
|
||||
@@ -359,7 +359,7 @@ buffer_read_from :: proc(b: ^Buffer, r: io.Reader) -> (n: i64, err: io.Error) #n
|
||||
resize(&b.buf, i)
|
||||
m, e := io.read(r, b.buf[i:cap(b.buf)])
|
||||
if m < 0 {
|
||||
err = .Negative_Read
|
||||
err = e if e != nil else .Negative_Read
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -104,3 +104,13 @@ NULL :: rawptr(uintptr(0))
|
||||
NDEBUG :: !ODIN_DEBUG
|
||||
|
||||
CHAR_BIT :: 8
|
||||
|
||||
// Since there are no types in C with an alignment larger than that of
|
||||
// max_align_t, which cannot be larger than sizeof(long double) as any other
|
||||
// exposed type wouldn't be valid C, the maximum alignment possible in a
|
||||
// strictly conformant C implementation is 16 on the platforms we care about.
|
||||
// The choice of 4096 bytes for storage of this type is more than enough on all
|
||||
// relevant platforms.
|
||||
va_list :: struct #align(16) {
|
||||
_: [4096]u8,
|
||||
}
|
||||
|
||||
@@ -1,34 +1,31 @@
|
||||
/*
|
||||
package demo
|
||||
Example:
|
||||
package demo
|
||||
|
||||
import tokenizer "core:c/frontend/tokenizer"
|
||||
import preprocessor "core:c/frontend/preprocessor"
|
||||
import "core:fmt"
|
||||
import tokenizer "core:c/frontend/tokenizer"
|
||||
import preprocessor "core:c/frontend/preprocessor"
|
||||
import "core:fmt"
|
||||
|
||||
main :: proc() {
|
||||
t := &tokenizer.Tokenizer{};
|
||||
tokenizer.init_defaults(t);
|
||||
main :: proc() {
|
||||
t := &tokenizer.Tokenizer{};
|
||||
tokenizer.init_defaults(t);
|
||||
|
||||
cpp := &preprocessor.Preprocessor{};
|
||||
cpp.warn, cpp.err = t.warn, t.err;
|
||||
preprocessor.init_lookup_tables(cpp);
|
||||
preprocessor.init_default_macros(cpp);
|
||||
cpp.include_paths = {"my/path/to/include"};
|
||||
cpp := &preprocessor.Preprocessor{};
|
||||
cpp.warn, cpp.err = t.warn, t.err;
|
||||
preprocessor.init_lookup_tables(cpp);
|
||||
preprocessor.init_default_macros(cpp);
|
||||
cpp.include_paths = {"my/path/to/include"};
|
||||
|
||||
tok := tokenizer.tokenize_file(t, "the/source/file.c", 1);
|
||||
tok := tokenizer.tokenize_file(t, "the/source/file.c", 1);
|
||||
|
||||
tok = preprocessor.preprocess(cpp, tok);
|
||||
if tok != nil {
|
||||
for t := tok; t.kind != .EOF; t = t.next {
|
||||
fmt.println(t.lit);
|
||||
tok = preprocessor.preprocess(cpp, tok);
|
||||
if tok != nil {
|
||||
for t := tok; t.kind != .EOF; t = t.next {
|
||||
fmt.println(t.lit);
|
||||
}
|
||||
}
|
||||
|
||||
fmt.println("[Done]");
|
||||
}
|
||||
|
||||
fmt.println("[Done]");
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
package c_frontend_tokenizer
|
||||
|
||||
|
||||
|
||||
+19
-1
@@ -40,7 +40,7 @@ when ODIN_OS == .FreeBSD {
|
||||
ERANGE :: 34
|
||||
}
|
||||
|
||||
when ODIN_OS == .OpenBSD {
|
||||
when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
|
||||
@(private="file")
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
@@ -80,6 +80,24 @@ when ODIN_OS == .Darwin {
|
||||
ERANGE :: 34
|
||||
}
|
||||
|
||||
when ODIN_OS == .Haiku {
|
||||
@(private="file")
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
@(link_name="_errnop")
|
||||
_get_errno :: proc() -> ^int ---
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
B_GENERAL_ERROR_BASE :: min(i32)
|
||||
@(private="file")
|
||||
B_POSIX_ERROR_BASE :: B_GENERAL_ERROR_BASE + 0x7000
|
||||
|
||||
EDOM :: B_POSIX_ERROR_BASE + 16
|
||||
EILSEQ :: B_POSIX_ERROR_BASE + 38
|
||||
ERANGE :: B_POSIX_ERROR_BASE + 17
|
||||
}
|
||||
|
||||
// Odin has no way to make an identifier "errno" behave as a function call to
|
||||
// read the value, or to produce an lvalue such that you can assign a different
|
||||
// error value to errno. To work around this, just expose it as a function like
|
||||
|
||||
@@ -4,6 +4,8 @@ package libc
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
import "core:c"
|
||||
|
||||
@(private="file")
|
||||
@(default_calling_convention="none")
|
||||
foreign _ {
|
||||
@@ -12,15 +14,7 @@ foreign _ {
|
||||
@(link_name="llvm.va_copy") _va_copy :: proc(dst, src: ^i8) ---
|
||||
}
|
||||
|
||||
// Since there are no types in C with an alignment larger than that of
|
||||
// max_align_t, which cannot be larger than sizeof(long double) as any other
|
||||
// exposed type wouldn't be valid C, the maximum alignment possible in a
|
||||
// strictly conformant C implementation is 16 on the platforms we care about.
|
||||
// The choice of 4096 bytes for storage of this type is more than enough on all
|
||||
// relevant platforms.
|
||||
va_list :: struct #align(16) {
|
||||
_: [4096]u8,
|
||||
}
|
||||
va_list :: c.va_list
|
||||
|
||||
va_start :: #force_inline proc(ap: ^va_list, _: any) {
|
||||
_va_start(cast(^i8)ap)
|
||||
|
||||
+31
-1
@@ -83,7 +83,7 @@ when ODIN_OS == .Linux {
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS == .OpenBSD {
|
||||
when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
|
||||
fpos_t :: distinct i64
|
||||
|
||||
_IOFBF :: 0
|
||||
@@ -163,6 +163,36 @@ when ODIN_OS == .Darwin {
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS == .Haiku {
|
||||
fpos_t :: distinct i64
|
||||
|
||||
_IOFBF :: 0
|
||||
_IOLBF :: 1
|
||||
_IONBF :: 2
|
||||
|
||||
BUFSIZ :: 8192
|
||||
|
||||
EOF :: int(-1)
|
||||
|
||||
FOPEN_MAX :: 128
|
||||
|
||||
FILENAME_MAX :: 256
|
||||
|
||||
L_tmpnam :: 512
|
||||
|
||||
SEEK_SET :: 0
|
||||
SEEK_CUR :: 1
|
||||
SEEK_END :: 2
|
||||
|
||||
TMP_MAX :: 32768
|
||||
|
||||
foreign libc {
|
||||
stderr: ^FILE
|
||||
stdin: ^FILE
|
||||
stdout: ^FILE
|
||||
}
|
||||
}
|
||||
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.21.4 Operations on files
|
||||
|
||||
@@ -45,7 +45,7 @@ when ODIN_OS == .Windows {
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD {
|
||||
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku {
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.27.2 Time manipulation functions
|
||||
|
||||
@@ -22,14 +22,18 @@ when ODIN_OS == .Windows {
|
||||
wctrans_t :: distinct int
|
||||
wctype_t :: distinct u32
|
||||
|
||||
} else when ODIN_OS == .OpenBSD {
|
||||
} else when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
|
||||
wctrans_t :: distinct rawptr
|
||||
wctype_t :: distinct rawptr
|
||||
|
||||
} else when ODIN_OS == .FreeBSD {
|
||||
wctrans_t :: distinct int
|
||||
wctype_t :: distinct ulong
|
||||
|
||||
|
||||
} else when ODIN_OS == .Haiku {
|
||||
wctrans_t :: distinct i32
|
||||
wctype_t :: distinct i32
|
||||
|
||||
}
|
||||
|
||||
@(default_calling_convention="c")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//+build ignore
|
||||
package gzip
|
||||
package compress_gzip
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package gzip
|
||||
package compress_gzip
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
// package shoco is an implementation of the shoco short string compressor
|
||||
package shoco
|
||||
package compress_shoco
|
||||
|
||||
DEFAULT_MODEL :: Shoco_Model {
|
||||
min_char = 39,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
// package shoco is an implementation of the shoco short string compressor
|
||||
package shoco
|
||||
package compress_shoco
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:compress"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//+build ignore
|
||||
package zlib
|
||||
package compress_zlib
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//+vet !using-param
|
||||
package zlib
|
||||
package compress_zlib
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
|
||||
@@ -0,0 +1,675 @@
|
||||
/*
|
||||
package avl implements an AVL tree.
|
||||
|
||||
The implementation is non-intrusive, and non-recursive.
|
||||
*/
|
||||
package container_avl
|
||||
|
||||
@(require) import "base:intrinsics"
|
||||
@(require) import "base:runtime"
|
||||
import "core:slice"
|
||||
|
||||
// Originally based on the CC0 implementation by Eric Biggers
|
||||
// See: https://github.com/ebiggers/avl_tree/
|
||||
|
||||
// Direction specifies the traversal direction for a tree iterator.
|
||||
Direction :: enum i8 {
|
||||
// Backward is the in-order backwards direction.
|
||||
Backward = -1,
|
||||
// Forward is the in-order forwards direction.
|
||||
Forward = 1,
|
||||
}
|
||||
|
||||
// Ordering specifies order when inserting/finding values into the tree.
|
||||
Ordering :: slice.Ordering
|
||||
|
||||
// Tree is an AVL tree.
|
||||
Tree :: struct($Value: typeid) {
|
||||
// user_data is a parameter that will be passed to the on_remove
|
||||
// callback.
|
||||
user_data: rawptr,
|
||||
// on_remove is an optional callback that can be called immediately
|
||||
// after a node is removed from the tree.
|
||||
on_remove: proc(value: Value, user_data: rawptr),
|
||||
|
||||
_root: ^Node(Value),
|
||||
_node_allocator: runtime.Allocator,
|
||||
_cmp_fn: proc(a, b: Value) -> Ordering,
|
||||
_size: int,
|
||||
}
|
||||
|
||||
// Node is an AVL tree node.
|
||||
//
|
||||
// WARNING: It is unsafe to mutate value if the node is part of a tree
|
||||
// if doing so will alter the Node's sort position relative to other
|
||||
// elements in the tree.
|
||||
Node :: struct($Value: typeid) {
|
||||
value: Value,
|
||||
|
||||
_parent: ^Node(Value),
|
||||
_left: ^Node(Value),
|
||||
_right: ^Node(Value),
|
||||
_balance: i8,
|
||||
}
|
||||
|
||||
// Iterator is a tree iterator.
|
||||
//
|
||||
// WARNING: It is unsafe to modify the tree while iterating, except via
|
||||
// the iterator_remove method.
|
||||
Iterator :: struct($Value: typeid) {
|
||||
_tree: ^Tree(Value),
|
||||
_cur: ^Node(Value),
|
||||
_next: ^Node(Value),
|
||||
_direction: Direction,
|
||||
_called_next: bool,
|
||||
}
|
||||
|
||||
// init initializes a tree.
|
||||
init :: proc {
|
||||
init_ordered,
|
||||
init_cmp,
|
||||
}
|
||||
|
||||
// init_cmp initializes a tree.
|
||||
init_cmp :: proc(
|
||||
t: ^$T/Tree($Value),
|
||||
cmp_fn: proc(a, b: Value) -> Ordering,
|
||||
node_allocator := context.allocator,
|
||||
) {
|
||||
t._root = nil
|
||||
t._node_allocator = node_allocator
|
||||
t._cmp_fn = cmp_fn
|
||||
t._size = 0
|
||||
}
|
||||
|
||||
// init_ordered initializes a tree containing ordered items, with
|
||||
// a comparison function that results in an ascending order sort.
|
||||
init_ordered :: proc(
|
||||
t: ^$T/Tree($Value),
|
||||
node_allocator := context.allocator,
|
||||
) where intrinsics.type_is_ordered_numeric(Value) {
|
||||
init_cmp(t, slice.cmp_proc(Value), node_allocator)
|
||||
}
|
||||
|
||||
// destroy de-initializes a tree.
|
||||
destroy :: proc(t: ^$T/Tree($Value), call_on_remove: bool = true) {
|
||||
iter := iterator(t, Direction.Forward)
|
||||
for _ in iterator_next(&iter) {
|
||||
iterator_remove(&iter, call_on_remove)
|
||||
}
|
||||
}
|
||||
|
||||
// len returns the number of elements in the tree.
|
||||
len :: proc "contextless" (t: ^$T/Tree($Value)) -> int {
|
||||
return t._size
|
||||
}
|
||||
|
||||
// first returns the first node in the tree (in-order) or nil iff
|
||||
// the tree is empty.
|
||||
first :: proc "contextless" (t: ^$T/Tree($Value)) -> ^Node(Value) {
|
||||
return tree_first_or_last_in_order(t, Direction.Backward)
|
||||
}
|
||||
|
||||
// last returns the last element in the tree (in-order) or nil iff
|
||||
// the tree is empty.
|
||||
last :: proc "contextless" (t: ^$T/Tree($Value)) -> ^Node(Value) {
|
||||
return tree_first_or_last_in_order(t, Direction.Forward)
|
||||
}
|
||||
|
||||
// find finds the value in the tree, and returns the corresponding
|
||||
// node or nil iff the value is not present.
|
||||
find :: proc(t: ^$T/Tree($Value), value: Value) -> ^Node(Value) {
|
||||
cur := t._root
|
||||
descend_loop: for cur != nil {
|
||||
switch t._cmp_fn(value, cur.value) {
|
||||
case .Less:
|
||||
cur = cur._left
|
||||
case .Greater:
|
||||
cur = cur._right
|
||||
case .Equal:
|
||||
break descend_loop
|
||||
}
|
||||
}
|
||||
|
||||
return cur
|
||||
}
|
||||
|
||||
// find_or_insert attempts to insert the value into the tree, and returns
|
||||
// the node, a boolean indicating if the value was inserted, and the
|
||||
// node allocator error if relevant. If the value is already
|
||||
// present, the existing node is returned un-altered.
|
||||
find_or_insert :: proc(
|
||||
t: ^$T/Tree($Value),
|
||||
value: Value,
|
||||
) -> (
|
||||
n: ^Node(Value),
|
||||
inserted: bool,
|
||||
err: runtime.Allocator_Error,
|
||||
) {
|
||||
n_ptr := &t._root
|
||||
for n_ptr^ != nil {
|
||||
n = n_ptr^
|
||||
switch t._cmp_fn(value, n.value) {
|
||||
case .Less:
|
||||
n_ptr = &n._left
|
||||
case .Greater:
|
||||
n_ptr = &n._right
|
||||
case .Equal:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
parent := n
|
||||
n = new(Node(Value), t._node_allocator) or_return
|
||||
n.value = value
|
||||
n._parent = parent
|
||||
n_ptr^ = n
|
||||
tree_rebalance_after_insert(t, n)
|
||||
|
||||
t._size += 1
|
||||
inserted = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// remove removes a node or value from the tree, and returns true iff the
|
||||
// removal was successful. While the node's value will be left intact,
|
||||
// the node itself will be freed via the tree's node allocator.
|
||||
remove :: proc {
|
||||
remove_value,
|
||||
remove_node,
|
||||
}
|
||||
|
||||
// remove_value removes a value from the tree, and returns true iff the
|
||||
// removal was successful. While the node's value will be left intact,
|
||||
// the node itself will be freed via the tree's node allocator.
|
||||
remove_value :: proc(t: ^$T/Tree($Value), value: Value, call_on_remove: bool = true) -> bool {
|
||||
n := find(t, value)
|
||||
if n == nil {
|
||||
return false
|
||||
}
|
||||
return remove_node(t, n, call_on_remove)
|
||||
}
|
||||
|
||||
// remove_node removes a node from the tree, and returns true iff the
|
||||
// removal was successful. While the node's value will be left intact,
|
||||
// the node itself will be freed via the tree's node allocator.
|
||||
remove_node :: proc(t: ^$T/Tree($Value), node: ^Node(Value), call_on_remove: bool = true) -> bool {
|
||||
if node._parent == node || (node._parent == nil && t._root != node) {
|
||||
return false
|
||||
}
|
||||
defer {
|
||||
if call_on_remove && t.on_remove != nil {
|
||||
t.on_remove(node.value, t.user_data)
|
||||
}
|
||||
free(node, t._node_allocator)
|
||||
}
|
||||
|
||||
parent: ^Node(Value)
|
||||
left_deleted: bool
|
||||
|
||||
t._size -= 1
|
||||
if node._left != nil && node._right != nil {
|
||||
parent, left_deleted = tree_swap_with_successor(t, node)
|
||||
} else {
|
||||
child := node._left
|
||||
if child == nil {
|
||||
child = node._right
|
||||
}
|
||||
parent = node._parent
|
||||
if parent != nil {
|
||||
if node == parent._left {
|
||||
parent._left = child
|
||||
left_deleted = true
|
||||
} else {
|
||||
parent._right = child
|
||||
left_deleted = false
|
||||
}
|
||||
if child != nil {
|
||||
child._parent = parent
|
||||
}
|
||||
} else {
|
||||
if child != nil {
|
||||
child._parent = parent
|
||||
}
|
||||
t._root = child
|
||||
node_reset(node)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
if left_deleted {
|
||||
parent = tree_handle_subtree_shrink(t, parent, +1, &left_deleted)
|
||||
} else {
|
||||
parent = tree_handle_subtree_shrink(t, parent, -1, &left_deleted)
|
||||
}
|
||||
if parent == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
node_reset(node)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// iterator returns a tree iterator in the specified direction.
|
||||
iterator :: proc "contextless" (t: ^$T/Tree($Value), direction: Direction) -> Iterator(Value) {
|
||||
it: Iterator(Value)
|
||||
it._tree = transmute(^Tree(Value))t
|
||||
it._direction = direction
|
||||
|
||||
iterator_first(&it)
|
||||
|
||||
return it
|
||||
}
|
||||
|
||||
// iterator_from_pos returns a tree iterator in the specified direction,
|
||||
// spanning the range [pos, last] (inclusive).
|
||||
iterator_from_pos :: proc "contextless" (
|
||||
t: ^$T/Tree($Value),
|
||||
pos: ^Node(Value),
|
||||
direction: Direction,
|
||||
) -> Iterator(Value) {
|
||||
it: Iterator(Value)
|
||||
it._tree = transmute(^Tree(Value))t
|
||||
it._direction = direction
|
||||
it._next = nil
|
||||
it._called_next = false
|
||||
|
||||
if it._cur = pos; pos != nil {
|
||||
it._next = node_next_or_prev_in_order(it._cur, it._direction)
|
||||
}
|
||||
|
||||
return it
|
||||
}
|
||||
|
||||
// iterator_get returns the node currently pointed to by the iterator,
|
||||
// or nil iff the node has been removed, the tree is empty, or the end
|
||||
// of the tree has been reached.
|
||||
iterator_get :: proc "contextless" (it: ^$I/Iterator($Value)) -> ^Node(Value) {
|
||||
return it._cur
|
||||
}
|
||||
|
||||
// iterator_remove removes the node currently pointed to by the iterator,
|
||||
// and returns true iff the removal was successful. Semantics are the
|
||||
// same as the Tree remove.
|
||||
iterator_remove :: proc(it: ^$I/Iterator($Value), call_on_remove: bool = true) -> bool {
|
||||
if it._cur == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
ok := remove_node(it._tree, it._cur, call_on_remove)
|
||||
if ok {
|
||||
it._cur = nil
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// iterator_next advances the iterator and returns the (node, true) or
|
||||
// or (nil, false) iff the end of the tree has been reached.
|
||||
//
|
||||
// Note: The first call to iterator_next will return the first node instead
|
||||
// of advancing the iterator.
|
||||
iterator_next :: proc "contextless" (it: ^$I/Iterator($Value)) -> (^Node(Value), bool) {
|
||||
// This check is needed so that the first element gets returned from
|
||||
// a brand-new iterator, and so that the somewhat contrived case where
|
||||
// iterator_remove is called before the first call to iterator_next
|
||||
// returns the correct value.
|
||||
if !it._called_next {
|
||||
it._called_next = true
|
||||
|
||||
// There can be the contrived case where iterator_remove is
|
||||
// called before ever calling iterator_next, which needs to be
|
||||
// handled as an actual call to next.
|
||||
//
|
||||
// If this happens it._cur will be nil, so only return the
|
||||
// first value, if it._cur is valid.
|
||||
if it._cur != nil {
|
||||
return it._cur, true
|
||||
}
|
||||
}
|
||||
|
||||
if it._next == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
it._cur = it._next
|
||||
it._next = node_next_or_prev_in_order(it._cur, it._direction)
|
||||
|
||||
return it._cur, true
|
||||
}
|
||||
|
||||
@(private)
|
||||
tree_first_or_last_in_order :: proc "contextless" (
|
||||
t: ^$T/Tree($Value),
|
||||
direction: Direction,
|
||||
) -> ^Node(Value) {
|
||||
first, sign := t._root, i8(direction)
|
||||
if first != nil {
|
||||
for {
|
||||
tmp := node_get_child(first, +sign)
|
||||
if tmp == nil {
|
||||
break
|
||||
}
|
||||
first = tmp
|
||||
}
|
||||
}
|
||||
|
||||
return first
|
||||
}
|
||||
|
||||
@(private)
|
||||
tree_replace_child :: proc "contextless" (
|
||||
t: ^$T/Tree($Value),
|
||||
parent, old_child, new_child: ^Node(Value),
|
||||
) {
|
||||
if parent != nil {
|
||||
if old_child == parent._left {
|
||||
parent._left = new_child
|
||||
} else {
|
||||
parent._right = new_child
|
||||
}
|
||||
} else {
|
||||
t._root = new_child
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
tree_rotate :: proc "contextless" (t: ^$T/Tree($Value), a: ^Node(Value), sign: i8) {
|
||||
b := node_get_child(a, -sign)
|
||||
e := node_get_child(b, +sign)
|
||||
p := a._parent
|
||||
|
||||
node_set_child(a, -sign, e)
|
||||
a._parent = b
|
||||
|
||||
node_set_child(b, +sign, a)
|
||||
b._parent = p
|
||||
|
||||
if e != nil {
|
||||
e._parent = a
|
||||
}
|
||||
|
||||
tree_replace_child(t, p, a, b)
|
||||
}
|
||||
|
||||
@(private)
|
||||
tree_double_rotate :: proc "contextless" (
|
||||
t: ^$T/Tree($Value),
|
||||
b, a: ^Node(Value),
|
||||
sign: i8,
|
||||
) -> ^Node(Value) {
|
||||
e := node_get_child(b, +sign)
|
||||
f := node_get_child(e, -sign)
|
||||
g := node_get_child(e, +sign)
|
||||
p := a._parent
|
||||
e_bal := e._balance
|
||||
|
||||
node_set_child(a, -sign, g)
|
||||
a_bal := -e_bal
|
||||
if sign * e_bal >= 0 {
|
||||
a_bal = 0
|
||||
}
|
||||
node_set_parent_balance(a, e, a_bal)
|
||||
|
||||
node_set_child(b, +sign, f)
|
||||
b_bal := -e_bal
|
||||
if sign * e_bal <= 0 {
|
||||
b_bal = 0
|
||||
}
|
||||
node_set_parent_balance(b, e, b_bal)
|
||||
|
||||
node_set_child(e, +sign, a)
|
||||
node_set_child(e, -sign, b)
|
||||
node_set_parent_balance(e, p, 0)
|
||||
|
||||
if g != nil {
|
||||
g._parent = a
|
||||
}
|
||||
|
||||
if f != nil {
|
||||
f._parent = b
|
||||
}
|
||||
|
||||
tree_replace_child(t, p, a, e)
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
@(private)
|
||||
tree_handle_subtree_growth :: proc "contextless" (
|
||||
t: ^$T/Tree($Value),
|
||||
node, parent: ^Node(Value),
|
||||
sign: i8,
|
||||
) -> bool {
|
||||
old_balance_factor := parent._balance
|
||||
if old_balance_factor == 0 {
|
||||
node_adjust_balance_factor(parent, sign)
|
||||
return false
|
||||
}
|
||||
|
||||
new_balance_factor := old_balance_factor + sign
|
||||
if new_balance_factor == 0 {
|
||||
node_adjust_balance_factor(parent, sign)
|
||||
return true
|
||||
}
|
||||
|
||||
if sign * node._balance > 0 {
|
||||
tree_rotate(t, parent, -sign)
|
||||
node_adjust_balance_factor(parent, -sign)
|
||||
node_adjust_balance_factor(node, -sign)
|
||||
} else {
|
||||
tree_double_rotate(t, node, parent, -sign)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@(private)
|
||||
tree_rebalance_after_insert :: proc "contextless" (t: ^$T/Tree($Value), inserted: ^Node(Value)) {
|
||||
node, parent := inserted, inserted._parent
|
||||
switch {
|
||||
case parent == nil:
|
||||
return
|
||||
case node == parent._left:
|
||||
node_adjust_balance_factor(parent, -1)
|
||||
case:
|
||||
node_adjust_balance_factor(parent, +1)
|
||||
}
|
||||
|
||||
if parent._balance == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for done := false; !done; {
|
||||
node = parent
|
||||
if parent = node._parent; parent == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if node == parent._left {
|
||||
done = tree_handle_subtree_growth(t, node, parent, -1)
|
||||
} else {
|
||||
done = tree_handle_subtree_growth(t, node, parent, +1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
tree_swap_with_successor :: proc "contextless" (
|
||||
t: ^$T/Tree($Value),
|
||||
x: ^Node(Value),
|
||||
) -> (
|
||||
^Node(Value),
|
||||
bool,
|
||||
) {
|
||||
ret: ^Node(Value)
|
||||
left_deleted: bool
|
||||
|
||||
y := x._right
|
||||
if y._left == nil {
|
||||
ret = y
|
||||
} else {
|
||||
q: ^Node(Value)
|
||||
|
||||
for {
|
||||
q = y
|
||||
if y = y._left; y._left == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if q._left = y._right; q._left != nil {
|
||||
q._left._parent = q
|
||||
}
|
||||
y._right = x._right
|
||||
x._right._parent = y
|
||||
ret = q
|
||||
left_deleted = true
|
||||
}
|
||||
|
||||
y._left = x._left
|
||||
x._left._parent = y
|
||||
|
||||
y._parent = x._parent
|
||||
y._balance = x._balance
|
||||
|
||||
tree_replace_child(t, x._parent, x, y)
|
||||
|
||||
return ret, left_deleted
|
||||
}
|
||||
|
||||
@(private)
|
||||
tree_handle_subtree_shrink :: proc "contextless" (
|
||||
t: ^$T/Tree($Value),
|
||||
parent: ^Node(Value),
|
||||
sign: i8,
|
||||
left_deleted: ^bool,
|
||||
) -> ^Node(Value) {
|
||||
old_balance_factor := parent._balance
|
||||
if old_balance_factor == 0 {
|
||||
node_adjust_balance_factor(parent, sign)
|
||||
return nil
|
||||
}
|
||||
|
||||
node: ^Node(Value)
|
||||
new_balance_factor := old_balance_factor + sign
|
||||
if new_balance_factor == 0 {
|
||||
node_adjust_balance_factor(parent, sign)
|
||||
node = parent
|
||||
} else {
|
||||
node = node_get_child(parent, sign)
|
||||
if sign * node._balance >= 0 {
|
||||
tree_rotate(t, parent, -sign)
|
||||
if node._balance == 0 {
|
||||
node_adjust_balance_factor(node, -sign)
|
||||
return nil
|
||||
}
|
||||
node_adjust_balance_factor(parent, -sign)
|
||||
node_adjust_balance_factor(node, -sign)
|
||||
} else {
|
||||
node = tree_double_rotate(t, node, parent, -sign)
|
||||
}
|
||||
}
|
||||
|
||||
parent := parent
|
||||
if parent = node._parent; parent != nil {
|
||||
left_deleted^ = node == parent._left
|
||||
}
|
||||
return parent
|
||||
}
|
||||
|
||||
@(private)
|
||||
node_reset :: proc "contextless" (n: ^Node($Value)) {
|
||||
// Mostly pointless as n will be deleted after this is called, but
|
||||
// attempt to be able to catch cases of n not being in the tree.
|
||||
n._parent = n
|
||||
n._left = nil
|
||||
n._right = nil
|
||||
n._balance = 0
|
||||
}
|
||||
|
||||
@(private)
|
||||
node_set_parent_balance :: #force_inline proc "contextless" (
|
||||
n, parent: ^Node($Value),
|
||||
balance: i8,
|
||||
) {
|
||||
n._parent = parent
|
||||
n._balance = balance
|
||||
}
|
||||
|
||||
@(private)
|
||||
node_get_child :: #force_inline proc "contextless" (n: ^Node($Value), sign: i8) -> ^Node(Value) {
|
||||
if sign < 0 {
|
||||
return n._left
|
||||
}
|
||||
return n._right
|
||||
}
|
||||
|
||||
@(private)
|
||||
node_next_or_prev_in_order :: proc "contextless" (
|
||||
n: ^Node($Value),
|
||||
direction: Direction,
|
||||
) -> ^Node(Value) {
|
||||
next, tmp: ^Node(Value)
|
||||
sign := i8(direction)
|
||||
|
||||
if next = node_get_child(n, +sign); next != nil {
|
||||
for {
|
||||
tmp = node_get_child(next, -sign)
|
||||
if tmp == nil {
|
||||
break
|
||||
}
|
||||
next = tmp
|
||||
}
|
||||
} else {
|
||||
tmp, next = n, n._parent
|
||||
for next != nil && tmp == node_get_child(next, +sign) {
|
||||
tmp, next = next, next._parent
|
||||
}
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
@(private)
|
||||
node_set_child :: #force_inline proc "contextless" (
|
||||
n: ^Node($Value),
|
||||
sign: i8,
|
||||
child: ^Node(Value),
|
||||
) {
|
||||
if sign < 0 {
|
||||
n._left = child
|
||||
} else {
|
||||
n._right = child
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
node_adjust_balance_factor :: #force_inline proc "contextless" (n: ^Node($Value), amount: i8) {
|
||||
n._balance += amount
|
||||
}
|
||||
|
||||
@(private)
|
||||
iterator_first :: proc "contextless" (it: ^Iterator($Value)) {
|
||||
// This is private because behavior when the user manually calls
|
||||
// iterator_first followed by iterator_next is unintuitive, since
|
||||
// the first call to iterator_next MUST return the first node
|
||||
// instead of advancing so that `for node in iterator_next(&next)`
|
||||
// works as expected.
|
||||
|
||||
switch it._direction {
|
||||
case .Forward:
|
||||
it._cur = tree_first_or_last_in_order(it._tree, .Backward)
|
||||
case .Backward:
|
||||
it._cur = tree_first_or_last_in_order(it._tree, .Forward)
|
||||
}
|
||||
|
||||
it._next = nil
|
||||
it._called_next = false
|
||||
|
||||
if it._cur != nil {
|
||||
it._next = node_next_or_prev_in_order(it._cur, it._direction)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package dynamic_bit_array
|
||||
package container_dynamic_bit_array
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:mem"
|
||||
|
||||
@@ -1,53 +1,52 @@
|
||||
package dynamic_bit_array
|
||||
|
||||
/*
|
||||
The Bit Array can be used in several ways:
|
||||
The Bit Array can be used in several ways:
|
||||
|
||||
-- By default you don't need to instantiate a Bit Array:
|
||||
- By default you don't need to instantiate a Bit Array:
|
||||
|
||||
package test
|
||||
package test
|
||||
|
||||
import "core:fmt"
|
||||
import "core:container/bit_array"
|
||||
import "core:fmt"
|
||||
import "core:container/bit_array"
|
||||
|
||||
main :: proc() {
|
||||
using bit_array
|
||||
main :: proc() {
|
||||
using bit_array
|
||||
|
||||
bits: Bit_Array
|
||||
bits: Bit_Array
|
||||
|
||||
// returns `true`
|
||||
fmt.println(set(&bits, 42))
|
||||
// returns `true`
|
||||
fmt.println(set(&bits, 42))
|
||||
|
||||
// returns `false`, `false`, because this Bit Array wasn't created to allow negative indices.
|
||||
was_set, was_retrieved := get(&bits, -1)
|
||||
fmt.println(was_set, was_retrieved)
|
||||
destroy(&bits)
|
||||
// returns `false`, `false`, because this Bit Array wasn't created to allow negative indices.
|
||||
was_set, was_retrieved := get(&bits, -1)
|
||||
fmt.println(was_set, was_retrieved)
|
||||
destroy(&bits)
|
||||
}
|
||||
|
||||
- A Bit Array can optionally allow for negative indices, if the minimum value was given during creation:
|
||||
|
||||
package test
|
||||
|
||||
import "core:fmt"
|
||||
import "core:container/bit_array"
|
||||
|
||||
main :: proc() {
|
||||
Foo :: enum int {
|
||||
Negative_Test = -42,
|
||||
Bar = 420,
|
||||
Leaves = 69105,
|
||||
}
|
||||
|
||||
-- A Bit Array can optionally allow for negative indices, if the mininum value was given during creation:
|
||||
using bit_array
|
||||
|
||||
package test
|
||||
bits := create(int(max(Foo)), int(min(Foo)))
|
||||
defer destroy(bits)
|
||||
|
||||
import "core:fmt"
|
||||
import "core:container/bit_array"
|
||||
|
||||
main :: proc() {
|
||||
Foo :: enum int {
|
||||
Negative_Test = -42,
|
||||
Bar = 420,
|
||||
Leaves = 69105,
|
||||
}
|
||||
|
||||
using bit_array
|
||||
|
||||
bits := create(int(max(Foo)), int(min(Foo)))
|
||||
defer destroy(bits)
|
||||
|
||||
fmt.printf("Set(Bar): %v\n", set(bits, Foo.Bar))
|
||||
fmt.printf("Get(Bar): %v, %v\n", get(bits, Foo.Bar))
|
||||
fmt.printf("Set(Negative_Test): %v\n", set(bits, Foo.Negative_Test))
|
||||
fmt.printf("Get(Leaves): %v, %v\n", get(bits, Foo.Leaves))
|
||||
fmt.printf("Get(Negative_Test): %v, %v\n", get(bits, Foo.Negative_Test))
|
||||
fmt.printf("Freed.\n")
|
||||
}
|
||||
*/
|
||||
fmt.printf("Set(Bar): %v\n", set(bits, Foo.Bar))
|
||||
fmt.printf("Get(Bar): %v, %v\n", get(bits, Foo.Bar))
|
||||
fmt.printf("Set(Negative_Test): %v\n", set(bits, Foo.Negative_Test))
|
||||
fmt.printf("Get(Leaves): %v, %v\n", get(bits, Foo.Leaves))
|
||||
fmt.printf("Get(Negative_Test): %v, %v\n", get(bits, Foo.Negative_Test))
|
||||
fmt.printf("Freed.\n")
|
||||
}
|
||||
*/
|
||||
package container_dynamic_bit_array
|
||||
|
||||
@@ -0,0 +1,568 @@
|
||||
// This package implements a red-black tree
|
||||
package container_rbtree
|
||||
|
||||
@(require) import "base:intrinsics"
|
||||
@(require) import "base:runtime"
|
||||
import "core:slice"
|
||||
|
||||
// Originally based on the CC0 implementation from literateprograms.org
|
||||
// But with API design mimicking `core:container/avl` for ease of use.
|
||||
|
||||
// Direction specifies the traversal direction for a tree iterator.
|
||||
Direction :: enum i8 {
|
||||
// Backward is the in-order backwards direction.
|
||||
Backward = -1,
|
||||
// Forward is the in-order forwards direction.
|
||||
Forward = 1,
|
||||
}
|
||||
|
||||
Ordering :: slice.Ordering
|
||||
|
||||
// Tree is a red-black tree
|
||||
Tree :: struct($Key: typeid, $Value: typeid) {
|
||||
// user_data is a parameter that will be passed to the on_remove
|
||||
// callback.
|
||||
user_data: rawptr,
|
||||
// on_remove is an optional callback that can be called immediately
|
||||
// after a node is removed from the tree.
|
||||
on_remove: proc(key: Key, value: Value, user_data: rawptr),
|
||||
|
||||
_root: ^Node(Key, Value),
|
||||
_node_allocator: runtime.Allocator,
|
||||
_cmp_fn: proc(Key, Key) -> Ordering,
|
||||
_size: int,
|
||||
}
|
||||
|
||||
// Node is a red-black tree node.
|
||||
//
|
||||
// WARNING: It is unsafe to mutate value if the node is part of a tree
|
||||
// if doing so will alter the Node's sort position relative to other
|
||||
// elements in the tree.
|
||||
Node :: struct($Key: typeid, $Value: typeid) {
|
||||
key: Key,
|
||||
value: Value,
|
||||
|
||||
_parent: ^Node(Key, Value),
|
||||
_left: ^Node(Key, Value),
|
||||
_right: ^Node(Key, Value),
|
||||
_color: Color,
|
||||
}
|
||||
|
||||
// Might store this in the node pointer in the future, but that'll require a decent amount of rework to pass ^^N instead of ^N
|
||||
Color :: enum uintptr {Black = 0, Red = 1}
|
||||
|
||||
// Iterator is a tree iterator.
|
||||
//
|
||||
// WARNING: It is unsafe to modify the tree while iterating, except via
|
||||
// the iterator_remove method.
|
||||
Iterator :: struct($Key: typeid, $Value: typeid) {
|
||||
_tree: ^Tree(Key, Value),
|
||||
_cur: ^Node(Key, Value),
|
||||
_next: ^Node(Key, Value),
|
||||
_direction: Direction,
|
||||
_called_next: bool,
|
||||
}
|
||||
|
||||
// init initializes a tree.
|
||||
init :: proc {
|
||||
init_ordered,
|
||||
init_cmp,
|
||||
}
|
||||
|
||||
// init_cmp initializes a tree.
|
||||
init_cmp :: proc(t: ^$T/Tree($Key, $Value), cmp_fn: proc(a, b: Key) -> Ordering, node_allocator := context.allocator) {
|
||||
t._root = nil
|
||||
t._node_allocator = node_allocator
|
||||
t._cmp_fn = cmp_fn
|
||||
t._size = 0
|
||||
}
|
||||
|
||||
// init_ordered initializes a tree containing ordered keys, with
|
||||
// a comparison function that results in an ascending order sort.
|
||||
init_ordered :: proc(t: ^$T/Tree($Key, $Value), node_allocator := context.allocator) where intrinsics.type_is_ordered_numeric(Key) {
|
||||
init_cmp(t, slice.cmp_proc(Key), node_allocator)
|
||||
}
|
||||
|
||||
// destroy de-initializes a tree.
|
||||
destroy :: proc(t: ^$T/Tree($Key, $Value), call_on_remove: bool = true) {
|
||||
iter := iterator(t, .Forward)
|
||||
for _ in iterator_next(&iter) {
|
||||
iterator_remove(&iter, call_on_remove)
|
||||
}
|
||||
}
|
||||
|
||||
len :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> (node_count: int) {
|
||||
return t._size
|
||||
}
|
||||
|
||||
// first returns the first node in the tree (in-order) or nil iff
|
||||
// the tree is empty.
|
||||
first :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> ^Node(Key, Value) {
|
||||
return tree_first_or_last_in_order(t, Direction.Backward)
|
||||
}
|
||||
|
||||
// last returns the last element in the tree (in-order) or nil iff
|
||||
// the tree is empty.
|
||||
last :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> ^Node(Key, Value) {
|
||||
return tree_first_or_last_in_order(t, Direction.Forward)
|
||||
}
|
||||
|
||||
// find finds the key in the tree, and returns the corresponding node, or nil iff the value is not present.
|
||||
find :: proc(t: ^$T/Tree($Key, $Value), key: Key) -> (node: ^Node(Key, Value)) {
|
||||
node = t._root
|
||||
for node != nil {
|
||||
switch t._cmp_fn(key, node.key) {
|
||||
case .Equal: return node
|
||||
case .Less: node = node._left
|
||||
case .Greater: node = node._right
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// find_value finds the key in the tree, and returns the corresponding value, or nil iff the value is not present.
|
||||
find_value :: proc(t: ^$T/Tree($Key, $Value), key: Key) -> (value: Value, ok: bool) #optional_ok {
|
||||
if n := find(t, key); n != nil {
|
||||
return n.value, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// find_or_insert attempts to insert the value into the tree, and returns
|
||||
// the node, a boolean indicating if the value was inserted, and the
|
||||
// node allocator error if relevant. If the value is already present, the existing node is updated.
|
||||
find_or_insert :: proc(t: ^$T/Tree($Key, $Value), key: Key, value: Value) -> (n: ^Node(Key, Value), inserted: bool, err: runtime.Allocator_Error) {
|
||||
n_ptr := &t._root
|
||||
for n_ptr^ != nil {
|
||||
n = n_ptr^
|
||||
switch t._cmp_fn(key, n.key) {
|
||||
case .Less:
|
||||
n_ptr = &n._left
|
||||
case .Greater:
|
||||
n_ptr = &n._right
|
||||
case .Equal:
|
||||
return
|
||||
}
|
||||
}
|
||||
_parent := n
|
||||
|
||||
n = new_clone(Node(Key, Value){key=key, value=value, _parent=_parent, _color=.Red}, t._node_allocator) or_return
|
||||
n_ptr^ = n
|
||||
insert_case1(t, n)
|
||||
t._size += 1
|
||||
return n, true, nil
|
||||
}
|
||||
|
||||
// remove removes a node or value from the tree, and returns true iff the
|
||||
// removal was successful. While the node's value will be left intact,
|
||||
// the node itself will be freed via the tree's node allocator.
|
||||
remove :: proc {
|
||||
remove_key,
|
||||
remove_node,
|
||||
}
|
||||
|
||||
// remove_value removes a value from the tree, and returns true iff the
|
||||
// removal was successful. While the node's key + value will be left intact,
|
||||
// the node itself will be freed via the tree's node allocator.
|
||||
remove_key :: proc(t: ^$T/Tree($Key, $Value), key: Key, call_on_remove := true) -> bool {
|
||||
n := find(t, key)
|
||||
if n == nil {
|
||||
return false // Key not found, nothing to do
|
||||
}
|
||||
return remove_node(t, n, call_on_remove)
|
||||
}
|
||||
|
||||
// remove_node removes a node from the tree, and returns true iff the
|
||||
// removal was successful. While the node's key + value will be left intact,
|
||||
// the node itself will be freed via the tree's node allocator.
|
||||
remove_node :: proc(t: ^$T/Tree($Key, $Value), node: ^$N/Node(Key, Value), call_on_remove := true) -> (found: bool) {
|
||||
if node._parent == node || (node._parent == nil && t._root != node) {
|
||||
return false // Don't touch self-parented or dangling nodes.
|
||||
}
|
||||
node := node
|
||||
if node._left != nil && node._right != nil {
|
||||
// Copy key + value from predecessor and delete it instead
|
||||
predecessor := maximum_node(node._left)
|
||||
node.key = predecessor.key
|
||||
node.value = predecessor.value
|
||||
node = predecessor
|
||||
}
|
||||
|
||||
child := node._right == nil ? node._left : node._right
|
||||
if node_color(node) == .Black {
|
||||
node._color = node_color(child)
|
||||
remove_case1(t, node)
|
||||
}
|
||||
replace_node(t, node, child)
|
||||
if node._parent == nil && child != nil {
|
||||
child._color = .Black // root should be black
|
||||
}
|
||||
|
||||
if call_on_remove && t.on_remove != nil {
|
||||
t.on_remove(node.key, node.value, t.user_data)
|
||||
}
|
||||
free(node, t._node_allocator)
|
||||
t._size -= 1
|
||||
return true
|
||||
}
|
||||
|
||||
// iterator returns a tree iterator in the specified direction.
|
||||
iterator :: proc "contextless" (t: ^$T/Tree($Key, $Value), direction: Direction) -> Iterator(Key, Value) {
|
||||
it: Iterator(Key, Value)
|
||||
it._tree = cast(^Tree(Key, Value))t
|
||||
it._direction = direction
|
||||
|
||||
iterator_first(&it)
|
||||
|
||||
return it
|
||||
}
|
||||
|
||||
// iterator_from_pos returns a tree iterator in the specified direction,
|
||||
// spanning the range [pos, last] (inclusive).
|
||||
iterator_from_pos :: proc "contextless" (t: ^$T/Tree($Key, $Value), pos: ^Node(Key, Value), direction: Direction) -> Iterator(Key, Value) {
|
||||
it: Iterator(Key, Value)
|
||||
it._tree = transmute(^Tree(Key, Value))t
|
||||
it._direction = direction
|
||||
it._next = nil
|
||||
it._called_next = false
|
||||
|
||||
if it._cur = pos; pos != nil {
|
||||
it._next = node_next_or_prev_in_order(it._cur, it._direction)
|
||||
}
|
||||
|
||||
return it
|
||||
}
|
||||
|
||||
// iterator_get returns the node currently pointed to by the iterator,
|
||||
// or nil iff the node has been removed, the tree is empty, or the end
|
||||
// of the tree has been reached.
|
||||
iterator_get :: proc "contextless" (it: ^$I/Iterator($Key, $Value)) -> ^Node(Key, Value) {
|
||||
return it._cur
|
||||
}
|
||||
|
||||
// iterator_remove removes the node currently pointed to by the iterator,
|
||||
// and returns true iff the removal was successful. Semantics are the
|
||||
// same as the Tree remove.
|
||||
iterator_remove :: proc(it: ^$I/Iterator($Key, $Value), call_on_remove: bool = true) -> bool {
|
||||
if it._cur == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
ok := remove_node(it._tree, it._cur , call_on_remove)
|
||||
if ok {
|
||||
it._cur = nil
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// iterator_next advances the iterator and returns the (node, true) or
|
||||
// or (nil, false) iff the end of the tree has been reached.
|
||||
//
|
||||
// Note: The first call to iterator_next will return the first node instead
|
||||
// of advancing the iterator.
|
||||
iterator_next :: proc "contextless" (it: ^$I/Iterator($Key, $Value)) -> (^Node(Key, Value), bool) {
|
||||
// This check is needed so that the first element gets returned from
|
||||
// a brand-new iterator, and so that the somewhat contrived case where
|
||||
// iterator_remove is called before the first call to iterator_next
|
||||
// returns the correct value.
|
||||
if !it._called_next {
|
||||
it._called_next = true
|
||||
|
||||
// There can be the contrived case where iterator_remove is
|
||||
// called before ever calling iterator_next, which needs to be
|
||||
// handled as an actual call to next.
|
||||
//
|
||||
// If this happens it._cur will be nil, so only return the
|
||||
// first value, if it._cur is valid.
|
||||
if it._cur != nil {
|
||||
return it._cur, true
|
||||
}
|
||||
}
|
||||
|
||||
if it._next == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
it._cur = it._next
|
||||
it._next = node_next_or_prev_in_order(it._cur, it._direction)
|
||||
|
||||
return it._cur, true
|
||||
}
|
||||
|
||||
@(private)
|
||||
tree_first_or_last_in_order :: proc "contextless" (t: ^$T/Tree($Key, $Value), direction: Direction) -> ^Node(Key, Value) {
|
||||
first, sign := t._root, i8(direction)
|
||||
if first != nil {
|
||||
for {
|
||||
tmp := node_get_child(first, sign)
|
||||
if tmp == nil {
|
||||
break
|
||||
}
|
||||
first = tmp
|
||||
}
|
||||
}
|
||||
return first
|
||||
}
|
||||
|
||||
@(private)
|
||||
node_get_child :: #force_inline proc "contextless" (n: ^Node($Key, $Value), sign: i8) -> ^Node(Key, Value) {
|
||||
if sign < 0 {
|
||||
return n._left
|
||||
}
|
||||
return n._right
|
||||
}
|
||||
|
||||
@(private)
|
||||
node_next_or_prev_in_order :: proc "contextless" (n: ^Node($Key, $Value), direction: Direction) -> ^Node(Key, Value) {
|
||||
next, tmp: ^Node(Key, Value)
|
||||
sign := i8(direction)
|
||||
|
||||
if next = node_get_child(n, +sign); next != nil {
|
||||
for {
|
||||
tmp = node_get_child(next, -sign)
|
||||
if tmp == nil {
|
||||
break
|
||||
}
|
||||
next = tmp
|
||||
}
|
||||
} else {
|
||||
tmp, next = n, n._parent
|
||||
for next != nil && tmp == node_get_child(next, +sign) {
|
||||
tmp, next = next, next._parent
|
||||
}
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
@(private)
|
||||
iterator_first :: proc "contextless" (it: ^Iterator($Key, $Value)) {
|
||||
// This is private because behavior when the user manually calls
|
||||
// iterator_first followed by iterator_next is unintuitive, since
|
||||
// the first call to iterator_next MUST return the first node
|
||||
// instead of advancing so that `for node in iterator_next(&next)`
|
||||
// works as expected.
|
||||
|
||||
switch it._direction {
|
||||
case .Forward:
|
||||
it._cur = tree_first_or_last_in_order(it._tree, .Backward)
|
||||
case .Backward:
|
||||
it._cur = tree_first_or_last_in_order(it._tree, .Forward)
|
||||
}
|
||||
|
||||
it._next = nil
|
||||
it._called_next = false
|
||||
|
||||
if it._cur != nil {
|
||||
it._next = node_next_or_prev_in_order(it._cur, it._direction)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
grand_parent :: proc(n: ^$N/Node($Key, $Value)) -> (g: ^N) {
|
||||
return n._parent._parent
|
||||
}
|
||||
|
||||
@(private)
|
||||
sibling :: proc(n: ^$N/Node($Key, $Value)) -> (s: ^N) {
|
||||
if n == n._parent._left {
|
||||
return n._parent._right
|
||||
} else {
|
||||
return n._parent._left
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
uncle :: proc(n: ^$N/Node($Key, $Value)) -> (u: ^N) {
|
||||
return sibling(n._parent)
|
||||
}
|
||||
|
||||
@(private)
|
||||
rotate__left :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
r := n._right
|
||||
replace_node(t, n, r)
|
||||
n._right = r._left
|
||||
if r._left != nil {
|
||||
r._left._parent = n
|
||||
}
|
||||
r._left = n
|
||||
n._parent = r
|
||||
}
|
||||
|
||||
@(private)
|
||||
rotate__right :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
l := n._left
|
||||
replace_node(t, n, l)
|
||||
n._left = l._right
|
||||
if l._right != nil {
|
||||
l._right._parent = n
|
||||
}
|
||||
l._right = n
|
||||
n._parent = l
|
||||
}
|
||||
|
||||
@(private)
|
||||
replace_node :: proc(t: ^$T/Tree($Key, $Value), old_n: ^$N/Node(Key, Value), new_n: ^N) {
|
||||
if old_n._parent == nil {
|
||||
t._root = new_n
|
||||
} else {
|
||||
if (old_n == old_n._parent._left) {
|
||||
old_n._parent._left = new_n
|
||||
} else {
|
||||
old_n._parent._right = new_n
|
||||
}
|
||||
}
|
||||
if new_n != nil {
|
||||
new_n._parent = old_n._parent
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
insert_case1 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
if n._parent == nil {
|
||||
n._color = .Black
|
||||
} else {
|
||||
insert_case2(t, n)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
insert_case2 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
if node_color(n._parent) == .Black {
|
||||
return // Tree is still valid
|
||||
} else {
|
||||
insert_case3(t, n)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
insert_case3 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
if node_color(uncle(n)) == .Red {
|
||||
n._parent._color = .Black
|
||||
uncle(n)._color = .Black
|
||||
grand_parent(n)._color = .Red
|
||||
insert_case1(t, grand_parent(n))
|
||||
} else {
|
||||
insert_case4(t, n)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
insert_case4 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
n := n
|
||||
if n == n._parent._right && n._parent == grand_parent(n)._left {
|
||||
rotate__left(t, n._parent)
|
||||
n = n._left
|
||||
} else if n == n._parent._left && n._parent == grand_parent(n)._right {
|
||||
rotate__right(t, n._parent)
|
||||
n = n._right
|
||||
}
|
||||
insert_case5(t, n)
|
||||
}
|
||||
|
||||
@(private)
|
||||
insert_case5 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
n._parent._color = .Black
|
||||
grand_parent(n)._color = .Red
|
||||
if n == n._parent._left && n._parent == grand_parent(n)._left {
|
||||
rotate__right(t, grand_parent(n))
|
||||
} else {
|
||||
rotate__left(t, grand_parent(n))
|
||||
}
|
||||
}
|
||||
|
||||
// The maximum_node() helper function just walks _right until it reaches the last non-leaf:
|
||||
@(private)
|
||||
maximum_node :: proc(n: ^$N/Node($Key, $Value)) -> (max_node: ^N) {
|
||||
n := n
|
||||
for n._right != nil {
|
||||
n = n._right
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@(private)
|
||||
remove_case1 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
if n._parent == nil {
|
||||
return
|
||||
} else {
|
||||
remove_case2(t, n)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
remove_case2 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
if node_color(sibling(n)) == .Red {
|
||||
n._parent._color = .Red
|
||||
sibling(n)._color = .Black
|
||||
if n == n._parent._left {
|
||||
rotate__left(t, n._parent)
|
||||
} else {
|
||||
rotate__right(t, n._parent)
|
||||
}
|
||||
}
|
||||
remove_case3(t, n)
|
||||
}
|
||||
|
||||
@(private)
|
||||
remove_case3 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
if node_color(n._parent) == .Black &&
|
||||
node_color(sibling(n)) == .Black &&
|
||||
node_color(sibling(n)._left) == .Black &&
|
||||
node_color(sibling(n)._right) == .Black {
|
||||
sibling(n)._color = .Red
|
||||
remove_case1(t, n._parent)
|
||||
} else {
|
||||
remove_case4(t, n)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
remove_case4 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
if node_color(n._parent) == .Red &&
|
||||
node_color(sibling(n)) == .Black &&
|
||||
node_color(sibling(n)._left) == .Black &&
|
||||
node_color(sibling(n)._right) == .Black {
|
||||
sibling(n)._color = .Red
|
||||
n._parent._color = .Black
|
||||
} else {
|
||||
remove_case5(t, n)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
remove_case5 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
if n == n._parent._left &&
|
||||
node_color(sibling(n)) == .Black &&
|
||||
node_color(sibling(n)._left) == .Red &&
|
||||
node_color(sibling(n)._right) == .Black {
|
||||
sibling(n)._color = .Red
|
||||
sibling(n)._left._color = .Black
|
||||
rotate__right(t, sibling(n))
|
||||
} else if n == n._parent._right &&
|
||||
node_color(sibling(n)) == .Black &&
|
||||
node_color(sibling(n)._right) == .Red &&
|
||||
node_color(sibling(n)._left) == .Black {
|
||||
sibling(n)._color = .Red
|
||||
sibling(n)._right._color = .Black
|
||||
rotate__left(t, sibling(n))
|
||||
}
|
||||
remove_case6(t, n)
|
||||
}
|
||||
|
||||
@(private)
|
||||
remove_case6 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
sibling(n)._color = node_color(n._parent)
|
||||
n._parent._color = .Black
|
||||
if n == n._parent._left {
|
||||
sibling(n)._right._color = .Black
|
||||
rotate__left(t, n._parent)
|
||||
} else {
|
||||
sibling(n)._left._color = .Black
|
||||
rotate__right(t, n._parent)
|
||||
}
|
||||
}
|
||||
|
||||
node_color :: proc(n: ^$N/Node($Key, $Value)) -> (c: Color) {
|
||||
return n == nil ? .Black : n._color
|
||||
}
|
||||
+16
-70
@@ -1,84 +1,30 @@
|
||||
# crypto
|
||||
|
||||
A cryptography library for the Odin language
|
||||
A cryptography library for the Odin language.
|
||||
|
||||
## Supported
|
||||
|
||||
This library offers various algorithms implemented in Odin.
|
||||
Please see the chart below for some of the options.
|
||||
|
||||
## Hashing algorithms
|
||||
|
||||
| Algorithm | |
|
||||
|:-------------------------------------------------------------------------------------------------------------|:-----------------|
|
||||
| [BLAKE2B](https://datatracker.ietf.org/doc/html/rfc7693) | ✔️ |
|
||||
| [BLAKE2S](https://datatracker.ietf.org/doc/html/rfc7693) | ✔️ |
|
||||
| [SHA-2](https://csrc.nist.gov/csrc/media/publications/fips/180/2/archive/2002-08-01/documents/fips180-2.pdf) | ✔️ |
|
||||
| [SHA-3](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf) | ✔️ |
|
||||
| [SHAKE](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf) | ✔️ |
|
||||
| [SM3](https://datatracker.ietf.org/doc/html/draft-sca-cfrg-sm3-02) | ✔️ |
|
||||
| legacy/[Keccak](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf) | ✔️ |
|
||||
| legacy/[MD5](https://datatracker.ietf.org/doc/html/rfc1321) | ✔️ |
|
||||
| legacy/[SHA-1](https://datatracker.ietf.org/doc/html/rfc3174) | ✔️ |
|
||||
|
||||
#### High level API
|
||||
|
||||
Each hash algorithm contains a procedure group named `hash`, or if the algorithm provides more than one digest size `hash_<size>`\*.
|
||||
Included in these groups are six procedures.
|
||||
- `hash_string` - Hash a given string and return the computed hash. Just calls `hash_bytes` internally
|
||||
- `hash_bytes` - Hash a given byte slice and return the computed hash
|
||||
- `hash_string_to_buffer` - Hash a given string and put the computed hash in the second proc parameter. Just calls `hash_bytes_to_buffer` internally
|
||||
- `hash_bytes_to_buffer` - Hash a given string and put the computed hash in the second proc parameter. The destination buffer has to be at least as big as the digest size of the hash
|
||||
- `hash_stream` - Takes a stream from io.Stream and returns the computed hash from it
|
||||
- `hash_file` - Takes a file handle and returns the computed hash from it. A second optional boolean parameter controls if the file is streamed (this is the default) or read at once (set to true)
|
||||
|
||||
\* On some algorithms there is another part to the name, since they might offer control about additional parameters.
|
||||
For instance, `SHA-2` offers different sizes.
|
||||
Computing a 512-bit hash is therefore achieved by calling `sha2.hash_512(...)`.
|
||||
|
||||
#### Low level API
|
||||
|
||||
The above mentioned procedures internally call three procedures: `init`, `update` and `final`.
|
||||
You may also directly call them, if you wish.
|
||||
|
||||
#### Example
|
||||
|
||||
```odin
|
||||
package crypto_example
|
||||
|
||||
// Import the desired package
|
||||
import "core:crypto/blake2b"
|
||||
|
||||
main :: proc() {
|
||||
input := "foo"
|
||||
|
||||
// Compute the hash, using the high level API
|
||||
computed_hash := blake2b.hash(input)
|
||||
|
||||
// Variant that takes a destination buffer, instead of returning the computed hash
|
||||
hash := make([]byte, sha2.DIGEST_SIZE) // @note: Destination buffer has to be at least as big as the digest size of the hash
|
||||
blake2b.hash(input, hash[:])
|
||||
|
||||
// Compute the hash, using the low level API
|
||||
ctx: blake2b.Context
|
||||
computed_hash_low: [blake2b.DIGEST_SIZE]byte
|
||||
blake2b.init(&ctx)
|
||||
blake2b.update(&ctx, transmute([]byte)input)
|
||||
blake2b.final(&ctx, computed_hash_low[:])
|
||||
}
|
||||
```
|
||||
For example uses of all available algorithms, please see the tests within `tests/core/crypto`.
|
||||
This package offers various algorithms implemented in Odin, along with
|
||||
useful helpers such as access to the system entropy source, and a
|
||||
constant-time byte comparison.
|
||||
|
||||
## Implementation considerations
|
||||
|
||||
- The crypto packages are not thread-safe.
|
||||
- Best-effort is make to mitigate timing side-channels on reasonable
|
||||
architectures. Architectures that are known to be unreasonable include
|
||||
architectures. Architectures that are known to be unreasonable include
|
||||
but are not limited to i386, i486, and WebAssembly.
|
||||
- Some but not all of the packages attempt to santize sensitive data,
|
||||
however this is not done consistently through the library at the moment.
|
||||
As Thomas Pornin puts it "In general, such memory cleansing is a fool's
|
||||
quest."
|
||||
- Implementations assume a 64-bit architecture (64-bit integer arithmetic
|
||||
is fast, and includes add-with-carry, sub-with-borrow, and full-result
|
||||
multiply).
|
||||
- Hardware sidechannels are explicitly out of scope for this package.
|
||||
Notable examples include but are not limited to:
|
||||
- Power/RF side-channels etc.
|
||||
- Fault injection attacks etc.
|
||||
- Hardware vulnerabilities ("apply mitigations or buy a new CPU").
|
||||
- The packages attempt to santize sensitive data, however this is, and
|
||||
will remain a "best-effort" implementation decision. As Thomas Pornin
|
||||
puts it "In general, such memory cleansing is a fool's quest."
|
||||
- All of these packages have not received independent third party review.
|
||||
|
||||
## License
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package _aes
|
||||
|
||||
// KEY_SIZE_128 is the AES-128 key size in bytes.
|
||||
KEY_SIZE_128 :: 16
|
||||
// KEY_SIZE_192 is the AES-192 key size in bytes.
|
||||
KEY_SIZE_192 :: 24
|
||||
// KEY_SIZE_256 is the AES-256 key size in bytes.
|
||||
KEY_SIZE_256 :: 32
|
||||
|
||||
// BLOCK_SIZE is the AES block size in bytes.
|
||||
BLOCK_SIZE :: 16
|
||||
|
||||
// ROUNDS_128 is the number of rounds for AES-128.
|
||||
ROUNDS_128 :: 10
|
||||
// ROUNDS_192 is the number of rounds for AES-192.
|
||||
ROUNDS_192 :: 12
|
||||
// ROUNDS_256 is the number of rounds for AES-256.
|
||||
ROUNDS_256 :: 14
|
||||
|
||||
// GHASH_KEY_SIZE is the GHASH key size in bytes.
|
||||
GHASH_KEY_SIZE :: 16
|
||||
// GHASH_BLOCK_SIZE is the GHASH block size in bytes.
|
||||
GHASH_BLOCK_SIZE :: 16
|
||||
// GHASH_TAG_SIZE is the GHASH tag size in bytes.
|
||||
GHASH_TAG_SIZE :: 16
|
||||
|
||||
// RCON is the AES keyschedule round constants.
|
||||
RCON := [10]byte{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36}
|
||||
@@ -0,0 +1,96 @@
|
||||
package aes_ct64
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:mem"
|
||||
|
||||
STRIDE :: 4
|
||||
|
||||
// Context is a keyed AES (ECB) instance.
|
||||
Context :: struct {
|
||||
_sk_exp: [120]u64,
|
||||
_num_rounds: int,
|
||||
_is_initialized: bool,
|
||||
}
|
||||
|
||||
// init initializes a context for AES with the provided key.
|
||||
init :: proc(ctx: ^Context, key: []byte) {
|
||||
skey: [30]u64 = ---
|
||||
|
||||
ctx._num_rounds = keysched(skey[:], key)
|
||||
skey_expand(ctx._sk_exp[:], skey[:], ctx._num_rounds)
|
||||
ctx._is_initialized = true
|
||||
}
|
||||
|
||||
// encrypt_block sets `dst` to `AES-ECB-Encrypt(src)`.
|
||||
encrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
q: [8]u64
|
||||
load_blockx1(&q, src)
|
||||
_encrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
|
||||
store_blockx1(dst, &q)
|
||||
}
|
||||
|
||||
// encrypt_block sets `dst` to `AES-ECB-Decrypt(src)`.
|
||||
decrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
q: [8]u64
|
||||
load_blockx1(&q, src)
|
||||
_decrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
|
||||
store_blockx1(dst, &q)
|
||||
}
|
||||
|
||||
// encrypt_blocks sets `dst` to `AES-ECB-Encrypt(src[0], .. src[n])`.
|
||||
encrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
q: [8]u64 = ---
|
||||
src, dst := src, dst
|
||||
|
||||
n := len(src)
|
||||
for n > 4 {
|
||||
load_blocks(&q, src[0:4])
|
||||
_encrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
|
||||
store_blocks(dst[0:4], &q)
|
||||
|
||||
src = src[4:]
|
||||
dst = dst[4:]
|
||||
n -= 4
|
||||
}
|
||||
if n > 0 {
|
||||
load_blocks(&q, src)
|
||||
_encrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
|
||||
store_blocks(dst, &q)
|
||||
}
|
||||
}
|
||||
|
||||
// decrypt_blocks sets dst to `AES-ECB-Decrypt(src[0], .. src[n])`.
|
||||
decrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
q: [8]u64 = ---
|
||||
src, dst := src, dst
|
||||
|
||||
n := len(src)
|
||||
for n > 4 {
|
||||
load_blocks(&q, src[0:4])
|
||||
_decrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
|
||||
store_blocks(dst[0:4], &q)
|
||||
|
||||
src = src[4:]
|
||||
dst = dst[4:]
|
||||
n -= 4
|
||||
}
|
||||
if n > 0 {
|
||||
load_blocks(&q, src)
|
||||
_decrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
|
||||
store_blocks(dst, &q)
|
||||
}
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
mem.zero_explicit(ctx, size_of(ctx))
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package aes_ct64
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
// Bitsliced AES for 64-bit general purpose (integer) registers. Each
|
||||
// invocation will process up to 4 blocks at a time. This implementation
|
||||
// is derived from the BearSSL ct64 code, and distributed under a 1-clause
|
||||
// BSD license with permission from the original author.
|
||||
//
|
||||
// WARNING: "hic sunt dracones"
|
||||
//
|
||||
// This package also deliberately exposes enough internals to be able to
|
||||
// function as a replacement for `AESENC` and `AESDEC` from AES-NI, to
|
||||
// allow the implementation of non-AES primitives that use the AES round
|
||||
// function such as AEGIS and Deoxys-II. This should ONLY be done when
|
||||
// implementing something other than AES itself.
|
||||
|
||||
sub_bytes :: proc "contextless" (q: ^[8]u64) {
|
||||
// This S-box implementation is a straightforward translation of
|
||||
// the circuit described by Boyar and Peralta in "A new
|
||||
// combinational logic minimization technique with applications
|
||||
// to cryptology" (https://eprint.iacr.org/2009/191.pdf).
|
||||
//
|
||||
// Note that variables x* (input) and s* (output) are numbered
|
||||
// in "reverse" order (x0 is the high bit, x7 is the low bit).
|
||||
|
||||
x0 := q[7]
|
||||
x1 := q[6]
|
||||
x2 := q[5]
|
||||
x3 := q[4]
|
||||
x4 := q[3]
|
||||
x5 := q[2]
|
||||
x6 := q[1]
|
||||
x7 := q[0]
|
||||
|
||||
// Top linear transformation.
|
||||
y14 := x3 ~ x5
|
||||
y13 := x0 ~ x6
|
||||
y9 := x0 ~ x3
|
||||
y8 := x0 ~ x5
|
||||
t0 := x1 ~ x2
|
||||
y1 := t0 ~ x7
|
||||
y4 := y1 ~ x3
|
||||
y12 := y13 ~ y14
|
||||
y2 := y1 ~ x0
|
||||
y5 := y1 ~ x6
|
||||
y3 := y5 ~ y8
|
||||
t1 := x4 ~ y12
|
||||
y15 := t1 ~ x5
|
||||
y20 := t1 ~ x1
|
||||
y6 := y15 ~ x7
|
||||
y10 := y15 ~ t0
|
||||
y11 := y20 ~ y9
|
||||
y7 := x7 ~ y11
|
||||
y17 := y10 ~ y11
|
||||
y19 := y10 ~ y8
|
||||
y16 := t0 ~ y11
|
||||
y21 := y13 ~ y16
|
||||
y18 := x0 ~ y16
|
||||
|
||||
// Non-linear section.
|
||||
t2 := y12 & y15
|
||||
t3 := y3 & y6
|
||||
t4 := t3 ~ t2
|
||||
t5 := y4 & x7
|
||||
t6 := t5 ~ t2
|
||||
t7 := y13 & y16
|
||||
t8 := y5 & y1
|
||||
t9 := t8 ~ t7
|
||||
t10 := y2 & y7
|
||||
t11 := t10 ~ t7
|
||||
t12 := y9 & y11
|
||||
t13 := y14 & y17
|
||||
t14 := t13 ~ t12
|
||||
t15 := y8 & y10
|
||||
t16 := t15 ~ t12
|
||||
t17 := t4 ~ t14
|
||||
t18 := t6 ~ t16
|
||||
t19 := t9 ~ t14
|
||||
t20 := t11 ~ t16
|
||||
t21 := t17 ~ y20
|
||||
t22 := t18 ~ y19
|
||||
t23 := t19 ~ y21
|
||||
t24 := t20 ~ y18
|
||||
|
||||
t25 := t21 ~ t22
|
||||
t26 := t21 & t23
|
||||
t27 := t24 ~ t26
|
||||
t28 := t25 & t27
|
||||
t29 := t28 ~ t22
|
||||
t30 := t23 ~ t24
|
||||
t31 := t22 ~ t26
|
||||
t32 := t31 & t30
|
||||
t33 := t32 ~ t24
|
||||
t34 := t23 ~ t33
|
||||
t35 := t27 ~ t33
|
||||
t36 := t24 & t35
|
||||
t37 := t36 ~ t34
|
||||
t38 := t27 ~ t36
|
||||
t39 := t29 & t38
|
||||
t40 := t25 ~ t39
|
||||
|
||||
t41 := t40 ~ t37
|
||||
t42 := t29 ~ t33
|
||||
t43 := t29 ~ t40
|
||||
t44 := t33 ~ t37
|
||||
t45 := t42 ~ t41
|
||||
z0 := t44 & y15
|
||||
z1 := t37 & y6
|
||||
z2 := t33 & x7
|
||||
z3 := t43 & y16
|
||||
z4 := t40 & y1
|
||||
z5 := t29 & y7
|
||||
z6 := t42 & y11
|
||||
z7 := t45 & y17
|
||||
z8 := t41 & y10
|
||||
z9 := t44 & y12
|
||||
z10 := t37 & y3
|
||||
z11 := t33 & y4
|
||||
z12 := t43 & y13
|
||||
z13 := t40 & y5
|
||||
z14 := t29 & y2
|
||||
z15 := t42 & y9
|
||||
z16 := t45 & y14
|
||||
z17 := t41 & y8
|
||||
|
||||
// Bottom linear transformation.
|
||||
t46 := z15 ~ z16
|
||||
t47 := z10 ~ z11
|
||||
t48 := z5 ~ z13
|
||||
t49 := z9 ~ z10
|
||||
t50 := z2 ~ z12
|
||||
t51 := z2 ~ z5
|
||||
t52 := z7 ~ z8
|
||||
t53 := z0 ~ z3
|
||||
t54 := z6 ~ z7
|
||||
t55 := z16 ~ z17
|
||||
t56 := z12 ~ t48
|
||||
t57 := t50 ~ t53
|
||||
t58 := z4 ~ t46
|
||||
t59 := z3 ~ t54
|
||||
t60 := t46 ~ t57
|
||||
t61 := z14 ~ t57
|
||||
t62 := t52 ~ t58
|
||||
t63 := t49 ~ t58
|
||||
t64 := z4 ~ t59
|
||||
t65 := t61 ~ t62
|
||||
t66 := z1 ~ t63
|
||||
s0 := t59 ~ t63
|
||||
s6 := t56 ~ ~t62
|
||||
s7 := t48 ~ ~t60
|
||||
t67 := t64 ~ t65
|
||||
s3 := t53 ~ t66
|
||||
s4 := t51 ~ t66
|
||||
s5 := t47 ~ t65
|
||||
s1 := t64 ~ ~s3
|
||||
s2 := t55 ~ ~t67
|
||||
|
||||
q[7] = s0
|
||||
q[6] = s1
|
||||
q[5] = s2
|
||||
q[4] = s3
|
||||
q[3] = s4
|
||||
q[2] = s5
|
||||
q[1] = s6
|
||||
q[0] = s7
|
||||
}
|
||||
|
||||
orthogonalize :: proc "contextless" (q: ^[8]u64) {
|
||||
CL2 :: 0x5555555555555555
|
||||
CH2 :: 0xAAAAAAAAAAAAAAAA
|
||||
q[0], q[1] = (q[0] & CL2) | ((q[1] & CL2) << 1), ((q[0] & CH2) >> 1) | (q[1] & CH2)
|
||||
q[2], q[3] = (q[2] & CL2) | ((q[3] & CL2) << 1), ((q[2] & CH2) >> 1) | (q[3] & CH2)
|
||||
q[4], q[5] = (q[4] & CL2) | ((q[5] & CL2) << 1), ((q[4] & CH2) >> 1) | (q[5] & CH2)
|
||||
q[6], q[7] = (q[6] & CL2) | ((q[7] & CL2) << 1), ((q[6] & CH2) >> 1) | (q[7] & CH2)
|
||||
|
||||
CL4 :: 0x3333333333333333
|
||||
CH4 :: 0xCCCCCCCCCCCCCCCC
|
||||
q[0], q[2] = (q[0] & CL4) | ((q[2] & CL4) << 2), ((q[0] & CH4) >> 2) | (q[2] & CH4)
|
||||
q[1], q[3] = (q[1] & CL4) | ((q[3] & CL4) << 2), ((q[1] & CH4) >> 2) | (q[3] & CH4)
|
||||
q[4], q[6] = (q[4] & CL4) | ((q[6] & CL4) << 2), ((q[4] & CH4) >> 2) | (q[6] & CH4)
|
||||
q[5], q[7] = (q[5] & CL4) | ((q[7] & CL4) << 2), ((q[5] & CH4) >> 2) | (q[7] & CH4)
|
||||
|
||||
CL8 :: 0x0F0F0F0F0F0F0F0F
|
||||
CH8 :: 0xF0F0F0F0F0F0F0F0
|
||||
q[0], q[4] = (q[0] & CL8) | ((q[4] & CL8) << 4), ((q[0] & CH8) >> 4) | (q[4] & CH8)
|
||||
q[1], q[5] = (q[1] & CL8) | ((q[5] & CL8) << 4), ((q[1] & CH8) >> 4) | (q[5] & CH8)
|
||||
q[2], q[6] = (q[2] & CL8) | ((q[6] & CL8) << 4), ((q[2] & CH8) >> 4) | (q[6] & CH8)
|
||||
q[3], q[7] = (q[3] & CL8) | ((q[7] & CL8) << 4), ((q[3] & CH8) >> 4) | (q[7] & CH8)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
interleave_in :: proc "contextless" (w: []u32) -> (q0, q1: u64) #no_bounds_check {
|
||||
if len(w) < 4 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
x0, x1, x2, x3 := u64(w[0]), u64(w[1]), u64(w[2]), u64(w[3])
|
||||
x0 |= (x0 << 16)
|
||||
x1 |= (x1 << 16)
|
||||
x2 |= (x2 << 16)
|
||||
x3 |= (x3 << 16)
|
||||
x0 &= 0x0000FFFF0000FFFF
|
||||
x1 &= 0x0000FFFF0000FFFF
|
||||
x2 &= 0x0000FFFF0000FFFF
|
||||
x3 &= 0x0000FFFF0000FFFF
|
||||
x0 |= (x0 << 8)
|
||||
x1 |= (x1 << 8)
|
||||
x2 |= (x2 << 8)
|
||||
x3 |= (x3 << 8)
|
||||
x0 &= 0x00FF00FF00FF00FF
|
||||
x1 &= 0x00FF00FF00FF00FF
|
||||
x2 &= 0x00FF00FF00FF00FF
|
||||
x3 &= 0x00FF00FF00FF00FF
|
||||
q0 = x0 | (x2 << 8)
|
||||
q1 = x1 | (x3 << 8)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
interleave_out :: proc "contextless" (q0, q1: u64) -> (w0, w1, w2, w3: u32) {
|
||||
x0 := q0 & 0x00FF00FF00FF00FF
|
||||
x1 := q1 & 0x00FF00FF00FF00FF
|
||||
x2 := (q0 >> 8) & 0x00FF00FF00FF00FF
|
||||
x3 := (q1 >> 8) & 0x00FF00FF00FF00FF
|
||||
x0 |= (x0 >> 8)
|
||||
x1 |= (x1 >> 8)
|
||||
x2 |= (x2 >> 8)
|
||||
x3 |= (x3 >> 8)
|
||||
x0 &= 0x0000FFFF0000FFFF
|
||||
x1 &= 0x0000FFFF0000FFFF
|
||||
x2 &= 0x0000FFFF0000FFFF
|
||||
x3 &= 0x0000FFFF0000FFFF
|
||||
w0 = u32(x0) | u32(x0 >> 16)
|
||||
w1 = u32(x1) | u32(x1 >> 16)
|
||||
w2 = u32(x2) | u32(x2 >> 16)
|
||||
w3 = u32(x3) | u32(x3 >> 16)
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
rotr32 :: #force_inline proc "contextless" (x: u64) -> u64 {
|
||||
return (x << 32) | (x >> 32)
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package aes_ct64
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
inv_sub_bytes :: proc "contextless" (q: ^[8]u64) {
|
||||
// AES S-box is:
|
||||
// S(x) = A(I(x)) ^ 0x63
|
||||
// where I() is inversion in GF(256), and A() is a linear
|
||||
// transform (0 is formally defined to be its own inverse).
|
||||
// Since inversion is an involution, the inverse S-box can be
|
||||
// computed from the S-box as:
|
||||
// iS(x) = B(S(B(x ^ 0x63)) ^ 0x63)
|
||||
// where B() is the inverse of A(). Indeed, for any y in GF(256):
|
||||
// iS(S(y)) = B(A(I(B(A(I(y)) ^ 0x63 ^ 0x63))) ^ 0x63 ^ 0x63) = y
|
||||
//
|
||||
// Note: we reuse the implementation of the forward S-box,
|
||||
// instead of duplicating it here, so that total code size is
|
||||
// lower. By merging the B() transforms into the S-box circuit
|
||||
// we could make faster CBC decryption, but CBC decryption is
|
||||
// already quite faster than CBC encryption because we can
|
||||
// process four blocks in parallel.
|
||||
|
||||
q0 := ~q[0]
|
||||
q1 := ~q[1]
|
||||
q2 := q[2]
|
||||
q3 := q[3]
|
||||
q4 := q[4]
|
||||
q5 := ~q[5]
|
||||
q6 := ~q[6]
|
||||
q7 := q[7]
|
||||
q[7] = q1 ~ q4 ~ q6
|
||||
q[6] = q0 ~ q3 ~ q5
|
||||
q[5] = q7 ~ q2 ~ q4
|
||||
q[4] = q6 ~ q1 ~ q3
|
||||
q[3] = q5 ~ q0 ~ q2
|
||||
q[2] = q4 ~ q7 ~ q1
|
||||
q[1] = q3 ~ q6 ~ q0
|
||||
q[0] = q2 ~ q5 ~ q7
|
||||
|
||||
sub_bytes(q)
|
||||
|
||||
q0 = ~q[0]
|
||||
q1 = ~q[1]
|
||||
q2 = q[2]
|
||||
q3 = q[3]
|
||||
q4 = q[4]
|
||||
q5 = ~q[5]
|
||||
q6 = ~q[6]
|
||||
q7 = q[7]
|
||||
q[7] = q1 ~ q4 ~ q6
|
||||
q[6] = q0 ~ q3 ~ q5
|
||||
q[5] = q7 ~ q2 ~ q4
|
||||
q[4] = q6 ~ q1 ~ q3
|
||||
q[3] = q5 ~ q0 ~ q2
|
||||
q[2] = q4 ~ q7 ~ q1
|
||||
q[1] = q3 ~ q6 ~ q0
|
||||
q[0] = q2 ~ q5 ~ q7
|
||||
}
|
||||
|
||||
inv_shift_rows :: proc "contextless" (q: ^[8]u64) {
|
||||
for x, i in q {
|
||||
q[i] =
|
||||
(x & 0x000000000000FFFF) |
|
||||
((x & 0x000000000FFF0000) << 4) |
|
||||
((x & 0x00000000F0000000) >> 12) |
|
||||
((x & 0x000000FF00000000) << 8) |
|
||||
((x & 0x0000FF0000000000) >> 8) |
|
||||
((x & 0x000F000000000000) << 12) |
|
||||
((x & 0xFFF0000000000000) >> 4)
|
||||
}
|
||||
}
|
||||
|
||||
inv_mix_columns :: proc "contextless" (q: ^[8]u64) {
|
||||
q0 := q[0]
|
||||
q1 := q[1]
|
||||
q2 := q[2]
|
||||
q3 := q[3]
|
||||
q4 := q[4]
|
||||
q5 := q[5]
|
||||
q6 := q[6]
|
||||
q7 := q[7]
|
||||
r0 := (q0 >> 16) | (q0 << 48)
|
||||
r1 := (q1 >> 16) | (q1 << 48)
|
||||
r2 := (q2 >> 16) | (q2 << 48)
|
||||
r3 := (q3 >> 16) | (q3 << 48)
|
||||
r4 := (q4 >> 16) | (q4 << 48)
|
||||
r5 := (q5 >> 16) | (q5 << 48)
|
||||
r6 := (q6 >> 16) | (q6 << 48)
|
||||
r7 := (q7 >> 16) | (q7 << 48)
|
||||
|
||||
q[0] = q5 ~ q6 ~ q7 ~ r0 ~ r5 ~ r7 ~ rotr32(q0 ~ q5 ~ q6 ~ r0 ~ r5)
|
||||
q[1] = q0 ~ q5 ~ r0 ~ r1 ~ r5 ~ r6 ~ r7 ~ rotr32(q1 ~ q5 ~ q7 ~ r1 ~ r5 ~ r6)
|
||||
q[2] = q0 ~ q1 ~ q6 ~ r1 ~ r2 ~ r6 ~ r7 ~ rotr32(q0 ~ q2 ~ q6 ~ r2 ~ r6 ~ r7)
|
||||
q[3] = q0 ~ q1 ~ q2 ~ q5 ~ q6 ~ r0 ~ r2 ~ r3 ~ r5 ~ rotr32(q0 ~ q1 ~ q3 ~ q5 ~ q6 ~ q7 ~ r0 ~ r3 ~ r5 ~ r7)
|
||||
q[4] = q1 ~ q2 ~ q3 ~ q5 ~ r1 ~ r3 ~ r4 ~ r5 ~ r6 ~ r7 ~ rotr32(q1 ~ q2 ~ q4 ~ q5 ~ q7 ~ r1 ~ r4 ~ r5 ~ r6)
|
||||
q[5] = q2 ~ q3 ~ q4 ~ q6 ~ r2 ~ r4 ~ r5 ~ r6 ~ r7 ~ rotr32(q2 ~ q3 ~ q5 ~ q6 ~ r2 ~ r5 ~ r6 ~ r7)
|
||||
q[6] = q3 ~ q4 ~ q5 ~ q7 ~ r3 ~ r5 ~ r6 ~ r7 ~ rotr32(q3 ~ q4 ~ q6 ~ q7 ~ r3 ~ r6 ~ r7)
|
||||
q[7] = q4 ~ q5 ~ q6 ~ r4 ~ r6 ~ r7 ~ rotr32(q4 ~ q5 ~ q7 ~ r4 ~ r7)
|
||||
}
|
||||
|
||||
@(private)
|
||||
_decrypt :: proc "contextless" (q: ^[8]u64, skey: []u64, num_rounds: int) {
|
||||
add_round_key(q, skey[num_rounds << 3:])
|
||||
for u := num_rounds - 1; u > 0; u -= 1 {
|
||||
inv_shift_rows(q)
|
||||
inv_sub_bytes(q)
|
||||
add_round_key(q, skey[u << 3:])
|
||||
inv_mix_columns(q)
|
||||
}
|
||||
inv_shift_rows(q)
|
||||
inv_sub_bytes(q)
|
||||
add_round_key(q, skey)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package aes_ct64
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
add_round_key :: proc "contextless" (q: ^[8]u64, sk: []u64) #no_bounds_check {
|
||||
if len(sk) < 8 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
q[0] ~= sk[0]
|
||||
q[1] ~= sk[1]
|
||||
q[2] ~= sk[2]
|
||||
q[3] ~= sk[3]
|
||||
q[4] ~= sk[4]
|
||||
q[5] ~= sk[5]
|
||||
q[6] ~= sk[6]
|
||||
q[7] ~= sk[7]
|
||||
}
|
||||
|
||||
shift_rows :: proc "contextless" (q: ^[8]u64) {
|
||||
for x, i in q {
|
||||
q[i] =
|
||||
(x & 0x000000000000FFFF) |
|
||||
((x & 0x00000000FFF00000) >> 4) |
|
||||
((x & 0x00000000000F0000) << 12) |
|
||||
((x & 0x0000FF0000000000) >> 8) |
|
||||
((x & 0x000000FF00000000) << 8) |
|
||||
((x & 0xF000000000000000) >> 12) |
|
||||
((x & 0x0FFF000000000000) << 4)
|
||||
}
|
||||
}
|
||||
|
||||
mix_columns :: proc "contextless" (q: ^[8]u64) {
|
||||
q0 := q[0]
|
||||
q1 := q[1]
|
||||
q2 := q[2]
|
||||
q3 := q[3]
|
||||
q4 := q[4]
|
||||
q5 := q[5]
|
||||
q6 := q[6]
|
||||
q7 := q[7]
|
||||
r0 := (q0 >> 16) | (q0 << 48)
|
||||
r1 := (q1 >> 16) | (q1 << 48)
|
||||
r2 := (q2 >> 16) | (q2 << 48)
|
||||
r3 := (q3 >> 16) | (q3 << 48)
|
||||
r4 := (q4 >> 16) | (q4 << 48)
|
||||
r5 := (q5 >> 16) | (q5 << 48)
|
||||
r6 := (q6 >> 16) | (q6 << 48)
|
||||
r7 := (q7 >> 16) | (q7 << 48)
|
||||
|
||||
q[0] = q7 ~ r7 ~ r0 ~ rotr32(q0 ~ r0)
|
||||
q[1] = q0 ~ r0 ~ q7 ~ r7 ~ r1 ~ rotr32(q1 ~ r1)
|
||||
q[2] = q1 ~ r1 ~ r2 ~ rotr32(q2 ~ r2)
|
||||
q[3] = q2 ~ r2 ~ q7 ~ r7 ~ r3 ~ rotr32(q3 ~ r3)
|
||||
q[4] = q3 ~ r3 ~ q7 ~ r7 ~ r4 ~ rotr32(q4 ~ r4)
|
||||
q[5] = q4 ~ r4 ~ r5 ~ rotr32(q5 ~ r5)
|
||||
q[6] = q5 ~ r5 ~ r6 ~ rotr32(q6 ~ r6)
|
||||
q[7] = q6 ~ r6 ~ r7 ~ rotr32(q7 ~ r7)
|
||||
}
|
||||
|
||||
@(private)
|
||||
_encrypt :: proc "contextless" (q: ^[8]u64, skey: []u64, num_rounds: int) {
|
||||
add_round_key(q, skey)
|
||||
for u in 1 ..< num_rounds {
|
||||
sub_bytes(q)
|
||||
shift_rows(q)
|
||||
mix_columns(q)
|
||||
add_round_key(q, skey[u << 3:])
|
||||
}
|
||||
sub_bytes(q)
|
||||
shift_rows(q)
|
||||
add_round_key(q, skey[num_rounds << 3:])
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package aes_ct64
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto/_aes"
|
||||
import "core:encoding/endian"
|
||||
import "core:mem"
|
||||
|
||||
@(private, require_results)
|
||||
sub_word :: proc "contextless" (x: u32) -> u32 {
|
||||
q := [8]u64{u64(x), 0, 0, 0, 0, 0, 0, 0}
|
||||
|
||||
orthogonalize(&q)
|
||||
sub_bytes(&q)
|
||||
orthogonalize(&q)
|
||||
ret := u32(q[0])
|
||||
|
||||
mem.zero_explicit(&q[0], size_of(u64))
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
keysched :: proc(comp_skey: []u64, key: []byte) -> int {
|
||||
num_rounds, key_len := 0, len(key)
|
||||
switch key_len {
|
||||
case _aes.KEY_SIZE_128:
|
||||
num_rounds = _aes.ROUNDS_128
|
||||
case _aes.KEY_SIZE_192:
|
||||
num_rounds = _aes.ROUNDS_192
|
||||
case _aes.KEY_SIZE_256:
|
||||
num_rounds = _aes.ROUNDS_256
|
||||
case:
|
||||
panic("crypto/aes: invalid AES key size")
|
||||
}
|
||||
|
||||
skey: [60]u32 = ---
|
||||
nk, nkf := key_len >> 2, (num_rounds + 1) << 2
|
||||
for i in 0 ..< nk {
|
||||
skey[i] = endian.unchecked_get_u32le(key[i << 2:])
|
||||
}
|
||||
tmp := skey[(key_len >> 2) - 1]
|
||||
for i, j, k := nk, 0, 0; i < nkf; i += 1 {
|
||||
if j == 0 {
|
||||
tmp = (tmp << 24) | (tmp >> 8)
|
||||
tmp = sub_word(tmp) ~ u32(_aes.RCON[k])
|
||||
} else if nk > 6 && j == 4 {
|
||||
tmp = sub_word(tmp)
|
||||
}
|
||||
tmp ~= skey[i - nk]
|
||||
skey[i] = tmp
|
||||
if j += 1; j == nk {
|
||||
j = 0
|
||||
k += 1
|
||||
}
|
||||
}
|
||||
|
||||
q: [8]u64 = ---
|
||||
for i, j := 0, 0; i < nkf; i, j = i + 4, j + 2 {
|
||||
q[0], q[4] = interleave_in(skey[i:])
|
||||
q[1] = q[0]
|
||||
q[2] = q[0]
|
||||
q[3] = q[0]
|
||||
q[5] = q[4]
|
||||
q[6] = q[4]
|
||||
q[7] = q[4]
|
||||
orthogonalize(&q)
|
||||
comp_skey[j + 0] =
|
||||
(q[0] & 0x1111111111111111) |
|
||||
(q[1] & 0x2222222222222222) |
|
||||
(q[2] & 0x4444444444444444) |
|
||||
(q[3] & 0x8888888888888888)
|
||||
comp_skey[j + 1] =
|
||||
(q[4] & 0x1111111111111111) |
|
||||
(q[5] & 0x2222222222222222) |
|
||||
(q[6] & 0x4444444444444444) |
|
||||
(q[7] & 0x8888888888888888)
|
||||
}
|
||||
|
||||
mem.zero_explicit(&skey, size_of(skey))
|
||||
mem.zero_explicit(&q, size_of(q))
|
||||
|
||||
return num_rounds
|
||||
}
|
||||
|
||||
@(private)
|
||||
skey_expand :: proc "contextless" (skey, comp_skey: []u64, num_rounds: int) {
|
||||
n := (num_rounds + 1) << 1
|
||||
for u, v := 0, 0; u < n; u, v = u + 1, v + 4 {
|
||||
x0 := comp_skey[u]
|
||||
x1, x2, x3 := x0, x0, x0
|
||||
x0 &= 0x1111111111111111
|
||||
x1 &= 0x2222222222222222
|
||||
x2 &= 0x4444444444444444
|
||||
x3 &= 0x8888888888888888
|
||||
x1 >>= 1
|
||||
x2 >>= 2
|
||||
x3 >>= 3
|
||||
skey[v + 0] = (x0 << 4) - x0
|
||||
skey[v + 1] = (x1 << 4) - x1
|
||||
skey[v + 2] = (x2 << 4) - x2
|
||||
skey[v + 3] = (x3 << 4) - x3
|
||||
}
|
||||
}
|
||||
|
||||
orthogonalize_roundkey :: proc "contextless" (qq: []u64, key: []byte) {
|
||||
if len(qq) < 8 || len(key) != 16 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
skey: [4]u32 = ---
|
||||
skey[0] = endian.unchecked_get_u32le(key[0:])
|
||||
skey[1] = endian.unchecked_get_u32le(key[4:])
|
||||
skey[2] = endian.unchecked_get_u32le(key[8:])
|
||||
skey[3] = endian.unchecked_get_u32le(key[12:])
|
||||
|
||||
q: [8]u64 = ---
|
||||
q[0], q[4] = interleave_in(skey[:])
|
||||
q[1] = q[0]
|
||||
q[2] = q[0]
|
||||
q[3] = q[0]
|
||||
q[5] = q[4]
|
||||
q[6] = q[4]
|
||||
q[7] = q[4]
|
||||
orthogonalize(&q)
|
||||
|
||||
comp_skey: [2]u64 = ---
|
||||
comp_skey[0] =
|
||||
(q[0] & 0x1111111111111111) |
|
||||
(q[1] & 0x2222222222222222) |
|
||||
(q[2] & 0x4444444444444444) |
|
||||
(q[3] & 0x8888888888888888)
|
||||
comp_skey[1] =
|
||||
(q[4] & 0x1111111111111111) |
|
||||
(q[5] & 0x2222222222222222) |
|
||||
(q[6] & 0x4444444444444444) |
|
||||
(q[7] & 0x8888888888888888)
|
||||
|
||||
for x, u in comp_skey {
|
||||
x0 := x
|
||||
x1, x2, x3 := x0, x0, x0
|
||||
x0 &= 0x1111111111111111
|
||||
x1 &= 0x2222222222222222
|
||||
x2 &= 0x4444444444444444
|
||||
x3 &= 0x8888888888888888
|
||||
x1 >>= 1
|
||||
x2 >>= 2
|
||||
x3 >>= 3
|
||||
qq[u * 4 + 0] = (x0 << 4) - x0
|
||||
qq[u * 4 + 1] = (x1 << 4) - x1
|
||||
qq[u * 4 + 2] = (x2 << 4) - x2
|
||||
qq[u * 4 + 3] = (x3 << 4) - x3
|
||||
}
|
||||
|
||||
mem.zero_explicit(&skey, size_of(skey))
|
||||
mem.zero_explicit(&q, size_of(q))
|
||||
mem.zero_explicit(&comp_skey, size_of(comp_skey))
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package aes_ct64
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto/_aes"
|
||||
import "core:encoding/endian"
|
||||
|
||||
@(private = "file")
|
||||
bmul64 :: proc "contextless" (x, y: u64) -> u64 {
|
||||
x0 := x & 0x1111111111111111
|
||||
x1 := x & 0x2222222222222222
|
||||
x2 := x & 0x4444444444444444
|
||||
x3 := x & 0x8888888888888888
|
||||
y0 := y & 0x1111111111111111
|
||||
y1 := y & 0x2222222222222222
|
||||
y2 := y & 0x4444444444444444
|
||||
y3 := y & 0x8888888888888888
|
||||
z0 := (x0 * y0) ~ (x1 * y3) ~ (x2 * y2) ~ (x3 * y1)
|
||||
z1 := (x0 * y1) ~ (x1 * y0) ~ (x2 * y3) ~ (x3 * y2)
|
||||
z2 := (x0 * y2) ~ (x1 * y1) ~ (x2 * y0) ~ (x3 * y3)
|
||||
z3 := (x0 * y3) ~ (x1 * y2) ~ (x2 * y1) ~ (x3 * y0)
|
||||
z0 &= 0x1111111111111111
|
||||
z1 &= 0x2222222222222222
|
||||
z2 &= 0x4444444444444444
|
||||
z3 &= 0x8888888888888888
|
||||
return z0 | z1 | z2 | z3
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
rev64 :: proc "contextless" (x: u64) -> u64 {
|
||||
x := x
|
||||
x = ((x & 0x5555555555555555) << 1) | ((x >> 1) & 0x5555555555555555)
|
||||
x = ((x & 0x3333333333333333) << 2) | ((x >> 2) & 0x3333333333333333)
|
||||
x = ((x & 0x0F0F0F0F0F0F0F0F) << 4) | ((x >> 4) & 0x0F0F0F0F0F0F0F0F)
|
||||
x = ((x & 0x00FF00FF00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF00FF00FF)
|
||||
x = ((x & 0x0000FFFF0000FFFF) << 16) | ((x >> 16) & 0x0000FFFF0000FFFF)
|
||||
return (x << 32) | (x >> 32)
|
||||
}
|
||||
|
||||
// ghash calculates the GHASH of data, with the key `key`, and input `dst`
|
||||
// and `data`, and stores the resulting digest in `dst`.
|
||||
//
|
||||
// Note: `dst` is both an input and an output, to support easy implementation
|
||||
// of GCM.
|
||||
ghash :: proc "contextless" (dst, key, data: []byte) {
|
||||
if len(dst) != _aes.GHASH_BLOCK_SIZE || len(key) != _aes.GHASH_BLOCK_SIZE {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
buf := data
|
||||
l := len(buf)
|
||||
|
||||
y1 := endian.unchecked_get_u64be(dst[0:])
|
||||
y0 := endian.unchecked_get_u64be(dst[8:])
|
||||
h1 := endian.unchecked_get_u64be(key[0:])
|
||||
h0 := endian.unchecked_get_u64be(key[8:])
|
||||
h0r := rev64(h0)
|
||||
h1r := rev64(h1)
|
||||
h2 := h0 ~ h1
|
||||
h2r := h0r ~ h1r
|
||||
|
||||
src: []byte
|
||||
for l > 0 {
|
||||
if l >= _aes.GHASH_BLOCK_SIZE {
|
||||
src = buf
|
||||
buf = buf[_aes.GHASH_BLOCK_SIZE:]
|
||||
l -= _aes.GHASH_BLOCK_SIZE
|
||||
} else {
|
||||
tmp: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
copy(tmp[:], buf)
|
||||
src = tmp[:]
|
||||
l = 0
|
||||
}
|
||||
y1 ~= endian.unchecked_get_u64be(src)
|
||||
y0 ~= endian.unchecked_get_u64be(src[8:])
|
||||
|
||||
y0r := rev64(y0)
|
||||
y1r := rev64(y1)
|
||||
y2 := y0 ~ y1
|
||||
y2r := y0r ~ y1r
|
||||
|
||||
z0 := bmul64(y0, h0)
|
||||
z1 := bmul64(y1, h1)
|
||||
z2 := bmul64(y2, h2)
|
||||
z0h := bmul64(y0r, h0r)
|
||||
z1h := bmul64(y1r, h1r)
|
||||
z2h := bmul64(y2r, h2r)
|
||||
z2 ~= z0 ~ z1
|
||||
z2h ~= z0h ~ z1h
|
||||
z0h = rev64(z0h) >> 1
|
||||
z1h = rev64(z1h) >> 1
|
||||
z2h = rev64(z2h) >> 1
|
||||
|
||||
v0 := z0
|
||||
v1 := z0h ~ z2
|
||||
v2 := z1 ~ z2h
|
||||
v3 := z1h
|
||||
|
||||
v3 = (v3 << 1) | (v2 >> 63)
|
||||
v2 = (v2 << 1) | (v1 >> 63)
|
||||
v1 = (v1 << 1) | (v0 >> 63)
|
||||
v0 = (v0 << 1)
|
||||
|
||||
v2 ~= v0 ~ (v0 >> 1) ~ (v0 >> 2) ~ (v0 >> 7)
|
||||
v1 ~= (v0 << 63) ~ (v0 << 62) ~ (v0 << 57)
|
||||
v3 ~= v1 ~ (v1 >> 1) ~ (v1 >> 2) ~ (v1 >> 7)
|
||||
v2 ~= (v1 << 63) ~ (v1 << 62) ~ (v1 << 57)
|
||||
|
||||
y0 = v2
|
||||
y1 = v3
|
||||
}
|
||||
|
||||
endian.unchecked_put_u64be(dst[0:], y1)
|
||||
endian.unchecked_put_u64be(dst[8:], y0)
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package aes_ct64
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto/_aes"
|
||||
import "core:encoding/endian"
|
||||
|
||||
load_blockx1 :: proc "contextless" (q: ^[8]u64, src: []byte) {
|
||||
if len(src) != _aes.BLOCK_SIZE {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
w: [4]u32 = ---
|
||||
w[0] = endian.unchecked_get_u32le(src[0:])
|
||||
w[1] = endian.unchecked_get_u32le(src[4:])
|
||||
w[2] = endian.unchecked_get_u32le(src[8:])
|
||||
w[3] = endian.unchecked_get_u32le(src[12:])
|
||||
q[0], q[4] = interleave_in(w[:])
|
||||
orthogonalize(q)
|
||||
}
|
||||
|
||||
store_blockx1 :: proc "contextless" (dst: []byte, q: ^[8]u64) {
|
||||
if len(dst) != _aes.BLOCK_SIZE {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
orthogonalize(q)
|
||||
w0, w1, w2, w3 := interleave_out(q[0], q[4])
|
||||
endian.unchecked_put_u32le(dst[0:], w0)
|
||||
endian.unchecked_put_u32le(dst[4:], w1)
|
||||
endian.unchecked_put_u32le(dst[8:], w2)
|
||||
endian.unchecked_put_u32le(dst[12:], w3)
|
||||
}
|
||||
|
||||
load_blocks :: proc "contextless" (q: ^[8]u64, src: [][]byte) {
|
||||
if n := len(src); n > STRIDE || n == 0 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
w: [4]u32 = ---
|
||||
for s, i in src {
|
||||
if len(s) != _aes.BLOCK_SIZE {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
w[0] = endian.unchecked_get_u32le(s[0:])
|
||||
w[1] = endian.unchecked_get_u32le(s[4:])
|
||||
w[2] = endian.unchecked_get_u32le(s[8:])
|
||||
w[3] = endian.unchecked_get_u32le(s[12:])
|
||||
q[i], q[i + 4] = interleave_in(w[:])
|
||||
}
|
||||
orthogonalize(q)
|
||||
}
|
||||
|
||||
store_blocks :: proc "contextless" (dst: [][]byte, q: ^[8]u64) {
|
||||
if n := len(dst); n > STRIDE || n == 0 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
orthogonalize(q)
|
||||
for d, i in dst {
|
||||
// Allow storing [0,4] blocks.
|
||||
if d == nil {
|
||||
break
|
||||
}
|
||||
if len(d) != _aes.BLOCK_SIZE {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
w0, w1, w2, w3 := interleave_out(q[i], q[i + 4])
|
||||
endian.unchecked_put_u32le(d[0:], w0)
|
||||
endian.unchecked_put_u32le(d[4:], w1)
|
||||
endian.unchecked_put_u32le(d[8:], w2)
|
||||
endian.unchecked_put_u32le(d[12:], w3)
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ package _blake2
|
||||
*/
|
||||
|
||||
import "core:encoding/endian"
|
||||
import "core:mem"
|
||||
|
||||
BLAKE2S_BLOCK_SIZE :: 64
|
||||
BLAKE2S_SIZE :: 32
|
||||
@@ -28,7 +29,6 @@ Blake2s_Context :: struct {
|
||||
is_keyed: bool,
|
||||
size: byte,
|
||||
is_last_node: bool,
|
||||
cfg: Blake2_Config,
|
||||
|
||||
is_initialized: bool,
|
||||
}
|
||||
@@ -44,7 +44,6 @@ Blake2b_Context :: struct {
|
||||
is_keyed: bool,
|
||||
size: byte,
|
||||
is_last_node: bool,
|
||||
cfg: Blake2_Config,
|
||||
|
||||
is_initialized: bool,
|
||||
}
|
||||
@@ -83,62 +82,61 @@ BLAKE2B_IV := [8]u64 {
|
||||
0x1f83d9abfb41bd6b, 0x5be0cd19137e2179,
|
||||
}
|
||||
|
||||
init :: proc(ctx: ^$T) {
|
||||
init :: proc(ctx: ^$T, cfg: ^Blake2_Config) {
|
||||
when T == Blake2s_Context {
|
||||
block_size :: BLAKE2S_BLOCK_SIZE
|
||||
max_size :: BLAKE2S_SIZE
|
||||
} else when T == Blake2b_Context {
|
||||
block_size :: BLAKE2B_BLOCK_SIZE
|
||||
max_size :: BLAKE2B_SIZE
|
||||
}
|
||||
|
||||
if ctx.cfg.size > max_size {
|
||||
if cfg.size > max_size {
|
||||
panic("blake2: requested output size exceeeds algorithm max")
|
||||
}
|
||||
|
||||
p := make([]byte, block_size)
|
||||
defer delete(p)
|
||||
// To save having to allocate a scratch buffer, use the internal
|
||||
// data buffer (`ctx.x`), as it is exactly the correct size.
|
||||
p := ctx.x[:]
|
||||
|
||||
p[0] = ctx.cfg.size
|
||||
p[1] = byte(len(ctx.cfg.key))
|
||||
p[0] = cfg.size
|
||||
p[1] = byte(len(cfg.key))
|
||||
|
||||
if ctx.cfg.salt != nil {
|
||||
if cfg.salt != nil {
|
||||
when T == Blake2s_Context {
|
||||
copy(p[16:], ctx.cfg.salt)
|
||||
copy(p[16:], cfg.salt)
|
||||
} else when T == Blake2b_Context {
|
||||
copy(p[32:], ctx.cfg.salt)
|
||||
copy(p[32:], cfg.salt)
|
||||
}
|
||||
}
|
||||
if ctx.cfg.person != nil {
|
||||
if cfg.person != nil {
|
||||
when T == Blake2s_Context {
|
||||
copy(p[24:], ctx.cfg.person)
|
||||
copy(p[24:], cfg.person)
|
||||
} else when T == Blake2b_Context {
|
||||
copy(p[48:], ctx.cfg.person)
|
||||
copy(p[48:], cfg.person)
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.cfg.tree != nil {
|
||||
p[2] = ctx.cfg.tree.(Blake2_Tree).fanout
|
||||
p[3] = ctx.cfg.tree.(Blake2_Tree).max_depth
|
||||
endian.unchecked_put_u32le(p[4:], ctx.cfg.tree.(Blake2_Tree).leaf_size)
|
||||
if cfg.tree != nil {
|
||||
p[2] = cfg.tree.(Blake2_Tree).fanout
|
||||
p[3] = cfg.tree.(Blake2_Tree).max_depth
|
||||
endian.unchecked_put_u32le(p[4:], cfg.tree.(Blake2_Tree).leaf_size)
|
||||
when T == Blake2s_Context {
|
||||
p[8] = byte(ctx.cfg.tree.(Blake2_Tree).node_offset)
|
||||
p[9] = byte(ctx.cfg.tree.(Blake2_Tree).node_offset >> 8)
|
||||
p[10] = byte(ctx.cfg.tree.(Blake2_Tree).node_offset >> 16)
|
||||
p[11] = byte(ctx.cfg.tree.(Blake2_Tree).node_offset >> 24)
|
||||
p[12] = byte(ctx.cfg.tree.(Blake2_Tree).node_offset >> 32)
|
||||
p[13] = byte(ctx.cfg.tree.(Blake2_Tree).node_offset >> 40)
|
||||
p[14] = ctx.cfg.tree.(Blake2_Tree).node_depth
|
||||
p[15] = ctx.cfg.tree.(Blake2_Tree).inner_hash_size
|
||||
p[8] = byte(cfg.tree.(Blake2_Tree).node_offset)
|
||||
p[9] = byte(cfg.tree.(Blake2_Tree).node_offset >> 8)
|
||||
p[10] = byte(cfg.tree.(Blake2_Tree).node_offset >> 16)
|
||||
p[11] = byte(cfg.tree.(Blake2_Tree).node_offset >> 24)
|
||||
p[12] = byte(cfg.tree.(Blake2_Tree).node_offset >> 32)
|
||||
p[13] = byte(cfg.tree.(Blake2_Tree).node_offset >> 40)
|
||||
p[14] = cfg.tree.(Blake2_Tree).node_depth
|
||||
p[15] = cfg.tree.(Blake2_Tree).inner_hash_size
|
||||
} else when T == Blake2b_Context {
|
||||
endian.unchecked_put_u64le(p[8:], ctx.cfg.tree.(Blake2_Tree).node_offset)
|
||||
p[16] = ctx.cfg.tree.(Blake2_Tree).node_depth
|
||||
p[17] = ctx.cfg.tree.(Blake2_Tree).inner_hash_size
|
||||
endian.unchecked_put_u64le(p[8:], cfg.tree.(Blake2_Tree).node_offset)
|
||||
p[16] = cfg.tree.(Blake2_Tree).node_depth
|
||||
p[17] = cfg.tree.(Blake2_Tree).inner_hash_size
|
||||
}
|
||||
} else {
|
||||
p[2], p[3] = 1, 1
|
||||
}
|
||||
ctx.size = ctx.cfg.size
|
||||
ctx.size = cfg.size
|
||||
for i := 0; i < 8; i += 1 {
|
||||
when T == Blake2s_Context {
|
||||
ctx.h[i] = BLAKE2S_IV[i] ~ endian.unchecked_get_u32le(p[i * 4:])
|
||||
@@ -147,11 +145,14 @@ init :: proc(ctx: ^$T) {
|
||||
ctx.h[i] = BLAKE2B_IV[i] ~ endian.unchecked_get_u64le(p[i * 8:])
|
||||
}
|
||||
}
|
||||
if ctx.cfg.tree != nil && ctx.cfg.tree.(Blake2_Tree).is_last_node {
|
||||
|
||||
mem.zero(&ctx.x, size_of(ctx.x)) // Done with the scratch space, no barrier.
|
||||
|
||||
if cfg.tree != nil && cfg.tree.(Blake2_Tree).is_last_node {
|
||||
ctx.is_last_node = true
|
||||
}
|
||||
if len(ctx.cfg.key) > 0 {
|
||||
copy(ctx.padded_key[:], ctx.cfg.key)
|
||||
if len(cfg.key) > 0 {
|
||||
copy(ctx.padded_key[:], cfg.key)
|
||||
update(ctx, ctx.padded_key[:])
|
||||
ctx.is_keyed = true
|
||||
}
|
||||
@@ -194,22 +195,40 @@ update :: proc(ctx: ^$T, p: []byte) {
|
||||
ctx.nx += copy(ctx.x[ctx.nx:], p)
|
||||
}
|
||||
|
||||
final :: proc(ctx: ^$T, hash: []byte) {
|
||||
final :: proc(ctx: ^$T, hash: []byte, finalize_clone: bool = false) {
|
||||
assert(ctx.is_initialized)
|
||||
|
||||
ctx := ctx
|
||||
if finalize_clone {
|
||||
tmp_ctx: T
|
||||
clone(&tmp_ctx, ctx)
|
||||
ctx = &tmp_ctx
|
||||
}
|
||||
defer(reset(ctx))
|
||||
|
||||
when T == Blake2s_Context {
|
||||
if len(hash) < int(ctx.cfg.size) {
|
||||
if len(hash) < int(ctx.size) {
|
||||
panic("crypto/blake2s: invalid destination digest size")
|
||||
}
|
||||
blake2s_final(ctx, hash)
|
||||
} else when T == Blake2b_Context {
|
||||
if len(hash) < int(ctx.cfg.size) {
|
||||
if len(hash) < int(ctx.size) {
|
||||
panic("crypto/blake2b: invalid destination digest size")
|
||||
}
|
||||
blake2b_final(ctx, hash)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.is_initialized = false
|
||||
clone :: proc(ctx, other: ^$T) {
|
||||
ctx^ = other^
|
||||
}
|
||||
|
||||
reset :: proc(ctx: ^$T) {
|
||||
if !ctx.is_initialized {
|
||||
return
|
||||
}
|
||||
|
||||
mem.zero_explicit(ctx, size_of(ctx^))
|
||||
}
|
||||
|
||||
@(private)
|
||||
|
||||
@@ -0,0 +1,428 @@
|
||||
package _edwards25519
|
||||
|
||||
/*
|
||||
This implements the edwards25519 composite-order group, primarily for
|
||||
the purpose of implementing X25519, Ed25519, and ristretto255. Use of
|
||||
this package for other purposes is NOT RECOMMENDED.
|
||||
|
||||
See:
|
||||
- https://eprint.iacr.org/2011/368.pdf
|
||||
- https://datatracker.ietf.org/doc/html/rfc8032
|
||||
- https://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
|
||||
*/
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto"
|
||||
import field "core:crypto/_fiat/field_curve25519"
|
||||
import "core:mem"
|
||||
|
||||
// Group_Element is an edwards25519 group element, as extended homogenous
|
||||
// coordinates, which represents the affine point `(x, y)` as `(X, Y, Z, T)`,
|
||||
// with the relations `x = X/Z`, `y = Y/Z`, and `x * y = T/Z`.
|
||||
//
|
||||
// d = -121665/121666 = 37095705934669439343138083508754565189542113879843219016388785533085940283555
|
||||
// a = -1
|
||||
//
|
||||
// Notes:
|
||||
// - There is considerable scope for optimization, however that
|
||||
// will not change the external API, and this is simple and reasonably
|
||||
// performant.
|
||||
// - The API delibarately makes it hard to create arbitrary group
|
||||
// elements that are not on the curve.
|
||||
// - The group element decoding routine takes the opinionated stance of
|
||||
// rejecting non-canonical encodings.
|
||||
|
||||
FE_D := field.Tight_Field_Element {
|
||||
929955233495203,
|
||||
466365720129213,
|
||||
1662059464998953,
|
||||
2033849074728123,
|
||||
1442794654840575,
|
||||
}
|
||||
@(private)
|
||||
FE_A := field.Tight_Field_Element {
|
||||
2251799813685228,
|
||||
2251799813685247,
|
||||
2251799813685247,
|
||||
2251799813685247,
|
||||
2251799813685247,
|
||||
}
|
||||
@(private)
|
||||
FE_D2 := field.Tight_Field_Element {
|
||||
1859910466990425,
|
||||
932731440258426,
|
||||
1072319116312658,
|
||||
1815898335770999,
|
||||
633789495995903,
|
||||
}
|
||||
@(private)
|
||||
GE_BASEPOINT := Group_Element {
|
||||
field.Tight_Field_Element {
|
||||
1738742601995546,
|
||||
1146398526822698,
|
||||
2070867633025821,
|
||||
562264141797630,
|
||||
587772402128613,
|
||||
},
|
||||
field.Tight_Field_Element {
|
||||
1801439850948184,
|
||||
1351079888211148,
|
||||
450359962737049,
|
||||
900719925474099,
|
||||
1801439850948198,
|
||||
},
|
||||
field.Tight_Field_Element{1, 0, 0, 0, 0},
|
||||
field.Tight_Field_Element {
|
||||
1841354044333475,
|
||||
16398895984059,
|
||||
755974180946558,
|
||||
900171276175154,
|
||||
1821297809914039,
|
||||
},
|
||||
}
|
||||
GE_IDENTITY := Group_Element {
|
||||
field.Tight_Field_Element{0, 0, 0, 0, 0},
|
||||
field.Tight_Field_Element{1, 0, 0, 0, 0},
|
||||
field.Tight_Field_Element{1, 0, 0, 0, 0},
|
||||
field.Tight_Field_Element{0, 0, 0, 0, 0},
|
||||
}
|
||||
|
||||
Group_Element :: struct {
|
||||
x: field.Tight_Field_Element,
|
||||
y: field.Tight_Field_Element,
|
||||
z: field.Tight_Field_Element,
|
||||
t: field.Tight_Field_Element,
|
||||
}
|
||||
|
||||
ge_clear :: proc "contextless" (ge: ^Group_Element) {
|
||||
mem.zero_explicit(ge, size_of(Group_Element))
|
||||
}
|
||||
|
||||
ge_set :: proc "contextless" (ge, a: ^Group_Element) {
|
||||
field.fe_set(&ge.x, &a.x)
|
||||
field.fe_set(&ge.y, &a.y)
|
||||
field.fe_set(&ge.z, &a.z)
|
||||
field.fe_set(&ge.t, &a.t)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
ge_set_bytes :: proc "contextless" (ge: ^Group_Element, b: []byte) -> bool {
|
||||
if len(b) != 32 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
b_ := transmute(^[32]byte)(raw_data(b))
|
||||
|
||||
// Do the work in a scratch element, so that ge is unchanged on
|
||||
// failure.
|
||||
tmp: Group_Element = ---
|
||||
defer ge_clear(&tmp)
|
||||
field.fe_one(&tmp.z) // Z = 1
|
||||
|
||||
// The encoding is the y-coordinate, with the x-coordinate polarity
|
||||
// (odd/even) encoded in the MSB.
|
||||
field.fe_from_bytes(&tmp.y, b_) // ignores high bit
|
||||
|
||||
// Recover the candidate x-coordinate via the curve equation:
|
||||
// x^2 = (y^2 - 1) / (d * y^2 + 1) (mod p)
|
||||
|
||||
fe_tmp := &tmp.t // Use this to store intermediaries.
|
||||
fe_one := &tmp.z
|
||||
|
||||
// x = num = y^2 - 1
|
||||
field.fe_carry_square(fe_tmp, field.fe_relax_cast(&tmp.y)) // fe_tmp = y^2
|
||||
field.fe_carry_sub(&tmp.x, fe_tmp, fe_one)
|
||||
|
||||
// den = d * y^2 + 1
|
||||
field.fe_carry_mul(fe_tmp, field.fe_relax_cast(fe_tmp), field.fe_relax_cast(&FE_D))
|
||||
field.fe_carry_add(fe_tmp, fe_tmp, fe_one)
|
||||
|
||||
// x = invsqrt(den/num)
|
||||
is_square := field.fe_carry_sqrt_ratio_m1(
|
||||
&tmp.x,
|
||||
field.fe_relax_cast(&tmp.x),
|
||||
field.fe_relax_cast(fe_tmp),
|
||||
)
|
||||
if is_square == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Pick the right x-coordinate.
|
||||
field.fe_cond_negate(&tmp.x, &tmp.x, int(b[31] >> 7))
|
||||
|
||||
// t = x * y
|
||||
field.fe_carry_mul(&tmp.t, field.fe_relax_cast(&tmp.x), field.fe_relax_cast(&tmp.y))
|
||||
|
||||
// Reject non-canonical encodings of ge.
|
||||
buf: [32]byte = ---
|
||||
field.fe_to_bytes(&buf, &tmp.y)
|
||||
buf[31] |= byte(field.fe_is_negative(&tmp.x)) << 7
|
||||
is_canonical := crypto.compare_constant_time(b, buf[:])
|
||||
|
||||
ge_cond_assign(ge, &tmp, is_canonical)
|
||||
|
||||
mem.zero_explicit(&buf, size_of(buf))
|
||||
|
||||
return is_canonical == 1
|
||||
}
|
||||
|
||||
ge_bytes :: proc "contextless" (ge: ^Group_Element, dst: []byte) {
|
||||
if len(dst) != 32 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
dst_ := transmute(^[32]byte)(raw_data(dst))
|
||||
|
||||
// Convert the element to affine (x, y) representation.
|
||||
x, y, z_inv: field.Tight_Field_Element = ---, ---, ---
|
||||
field.fe_carry_inv(&z_inv, field.fe_relax_cast(&ge.z))
|
||||
field.fe_carry_mul(&x, field.fe_relax_cast(&ge.x), field.fe_relax_cast(&z_inv))
|
||||
field.fe_carry_mul(&y, field.fe_relax_cast(&ge.y), field.fe_relax_cast(&z_inv))
|
||||
|
||||
// Encode the y-coordinate.
|
||||
field.fe_to_bytes(dst_, &y)
|
||||
|
||||
// Copy the least significant bit of the x-coordinate to the most
|
||||
// significant bit of the encoded y-coordinate.
|
||||
dst_[31] |= byte((x[0] & 1) << 7)
|
||||
|
||||
field.fe_clear_vec([]^field.Tight_Field_Element{&x, &y, &z_inv})
|
||||
}
|
||||
|
||||
ge_identity :: proc "contextless" (ge: ^Group_Element) {
|
||||
field.fe_zero(&ge.x)
|
||||
field.fe_one(&ge.y)
|
||||
field.fe_one(&ge.z)
|
||||
field.fe_zero(&ge.t)
|
||||
}
|
||||
|
||||
ge_generator :: proc "contextless" (ge: ^Group_Element) {
|
||||
ge_set(ge, &GE_BASEPOINT)
|
||||
}
|
||||
|
||||
@(private)
|
||||
Addend_Group_Element :: struct {
|
||||
y2_minus_x2: field.Loose_Field_Element, // t1
|
||||
y2_plus_x2: field.Loose_Field_Element, // t3
|
||||
k_times_t2: field.Tight_Field_Element, // t4
|
||||
two_times_z2: field.Loose_Field_Element, // t5
|
||||
}
|
||||
|
||||
@(private)
|
||||
ge_addend_set :: proc "contextless" (ge_a: ^Addend_Group_Element, ge: ^Group_Element) {
|
||||
field.fe_sub(&ge_a.y2_minus_x2, &ge.y, &ge.x)
|
||||
field.fe_add(&ge_a.y2_plus_x2, &ge.y, &ge.x)
|
||||
field.fe_carry_mul(&ge_a.k_times_t2, field.fe_relax_cast(&FE_D2), field.fe_relax_cast(&ge.t))
|
||||
field.fe_add(&ge_a.two_times_z2, &ge.z, &ge.z)
|
||||
}
|
||||
|
||||
@(private)
|
||||
ge_addend_conditional_assign :: proc "contextless" (ge_a, a: ^Addend_Group_Element, ctrl: int) {
|
||||
field.fe_cond_select(&ge_a.y2_minus_x2, &ge_a.y2_minus_x2, &a.y2_minus_x2, ctrl)
|
||||
field.fe_cond_select(&ge_a.y2_plus_x2, &ge_a.y2_plus_x2, &a.y2_plus_x2, ctrl)
|
||||
field.fe_cond_select(&ge_a.k_times_t2, &ge_a.k_times_t2, &a.k_times_t2, ctrl)
|
||||
field.fe_cond_select(&ge_a.two_times_z2, &ge_a.two_times_z2, &a.two_times_z2, ctrl)
|
||||
}
|
||||
|
||||
@(private)
|
||||
Add_Scratch :: struct {
|
||||
A, B, C, D: field.Tight_Field_Element,
|
||||
E, F, G, H: field.Loose_Field_Element,
|
||||
t0, t2: field.Loose_Field_Element,
|
||||
}
|
||||
|
||||
ge_add :: proc "contextless" (ge, a, b: ^Group_Element) {
|
||||
b_: Addend_Group_Element = ---
|
||||
ge_addend_set(&b_, b)
|
||||
|
||||
scratch: Add_Scratch = ---
|
||||
ge_add_addend(ge, a, &b_, &scratch)
|
||||
|
||||
mem.zero_explicit(&b_, size_of(Addend_Group_Element))
|
||||
mem.zero_explicit(&scratch, size_of(Add_Scratch))
|
||||
}
|
||||
|
||||
@(private)
|
||||
ge_add_addend :: proc "contextless" (
|
||||
ge, a: ^Group_Element,
|
||||
b: ^Addend_Group_Element,
|
||||
scratch: ^Add_Scratch,
|
||||
) {
|
||||
// https://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-3
|
||||
// Assumptions: k=2*d.
|
||||
//
|
||||
// t0 = Y1-X1
|
||||
// t1 = Y2-X2
|
||||
// A = t0*t1
|
||||
// t2 = Y1+X1
|
||||
// t3 = Y2+X2
|
||||
// B = t2*t3
|
||||
// t4 = k*T2
|
||||
// C = T1*t4
|
||||
// t5 = 2*Z2
|
||||
// D = Z1*t5
|
||||
// E = B-A
|
||||
// F = D-C
|
||||
// G = D+C
|
||||
// H = B+A
|
||||
// X3 = E*F
|
||||
// Y3 = G*H
|
||||
// T3 = E*H
|
||||
// Z3 = F*G
|
||||
//
|
||||
// In order to make the scalar multiply faster, the addend is provided
|
||||
// as a `Addend_Group_Element` with t1, t3, t4, and t5 precomputed, as
|
||||
// it is trivially obvious that those are the only values used by the
|
||||
// formula that are directly dependent on `b`, and are only dependent
|
||||
// on `b` and constants. This saves 1 sub, 2 adds, and 1 multiply,
|
||||
// each time the intermediate representation can be reused.
|
||||
|
||||
A, B, C, D := &scratch.A, &scratch.B, &scratch.C, &scratch.D
|
||||
E, F, G, H := &scratch.E, &scratch.F, &scratch.G, &scratch.H
|
||||
t0, t2 := &scratch.t0, &scratch.t2
|
||||
|
||||
field.fe_sub(t0, &a.y, &a.x)
|
||||
t1 := &b.y2_minus_x2
|
||||
field.fe_carry_mul(A, t0, t1)
|
||||
field.fe_add(t2, &a.y, &a.x)
|
||||
t3 := &b.y2_plus_x2
|
||||
field.fe_carry_mul(B, t2, t3)
|
||||
t4 := &b.k_times_t2
|
||||
field.fe_carry_mul(C, field.fe_relax_cast(&a.t), field.fe_relax_cast(t4))
|
||||
t5 := &b.two_times_z2
|
||||
field.fe_carry_mul(D, field.fe_relax_cast(&a.z), t5)
|
||||
field.fe_sub(E, B, A)
|
||||
field.fe_sub(F, D, C)
|
||||
field.fe_add(G, D, C)
|
||||
field.fe_add(H, B, A)
|
||||
field.fe_carry_mul(&ge.x, E, F)
|
||||
field.fe_carry_mul(&ge.y, G, H)
|
||||
field.fe_carry_mul(&ge.t, E, H)
|
||||
field.fe_carry_mul(&ge.z, F, G)
|
||||
}
|
||||
|
||||
@(private)
|
||||
Double_Scratch :: struct {
|
||||
A, B, C, D, G: field.Tight_Field_Element,
|
||||
t0, t2, t3: field.Tight_Field_Element,
|
||||
E, F, H: field.Loose_Field_Element,
|
||||
t1: field.Loose_Field_Element,
|
||||
}
|
||||
|
||||
ge_double :: proc "contextless" (ge, a: ^Group_Element, scratch: ^Double_Scratch = nil) {
|
||||
// https://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#doubling-dbl-2008-hwcd
|
||||
//
|
||||
// A = X1^2
|
||||
// B = Y1^2
|
||||
// t0 = Z1^2
|
||||
// C = 2*t0
|
||||
// D = a*A
|
||||
// t1 = X1+Y1
|
||||
// t2 = t1^2
|
||||
// t3 = t2-A
|
||||
// E = t3-B
|
||||
// G = D+B
|
||||
// F = G-C
|
||||
// H = D-B
|
||||
// X3 = E*F
|
||||
// Y3 = G*H
|
||||
// T3 = E*H
|
||||
// Z3 = F*G
|
||||
|
||||
sanitize, scratch := scratch == nil, scratch
|
||||
if sanitize {
|
||||
tmp: Double_Scratch = ---
|
||||
scratch = &tmp
|
||||
}
|
||||
|
||||
A, B, C, D, G := &scratch.A, &scratch.B, &scratch.C, &scratch.D, &scratch.G
|
||||
t0, t2, t3 := &scratch.t0, &scratch.t2, &scratch.t3
|
||||
E, F, H := &scratch.E, &scratch.F, &scratch.H
|
||||
t1 := &scratch.t1
|
||||
|
||||
field.fe_carry_square(A, field.fe_relax_cast(&a.x))
|
||||
field.fe_carry_square(B, field.fe_relax_cast(&a.y))
|
||||
field.fe_carry_square(t0, field.fe_relax_cast(&a.z))
|
||||
field.fe_carry_add(C, t0, t0)
|
||||
field.fe_carry_mul(D, field.fe_relax_cast(&FE_A), field.fe_relax_cast(A))
|
||||
field.fe_add(t1, &a.x, &a.y)
|
||||
field.fe_carry_square(t2, t1)
|
||||
field.fe_carry_sub(t3, t2, A)
|
||||
field.fe_sub(E, t3, B)
|
||||
field.fe_carry_add(G, D, B)
|
||||
field.fe_sub(F, G, C)
|
||||
field.fe_sub(H, D, B)
|
||||
G_ := field.fe_relax_cast(G)
|
||||
field.fe_carry_mul(&ge.x, E, F)
|
||||
field.fe_carry_mul(&ge.y, G_, H)
|
||||
field.fe_carry_mul(&ge.t, E, H)
|
||||
field.fe_carry_mul(&ge.z, F, G_)
|
||||
|
||||
if sanitize {
|
||||
mem.zero_explicit(scratch, size_of(Double_Scratch))
|
||||
}
|
||||
}
|
||||
|
||||
ge_negate :: proc "contextless" (ge, a: ^Group_Element) {
|
||||
field.fe_carry_opp(&ge.x, &a.x)
|
||||
field.fe_set(&ge.y, &a.y)
|
||||
field.fe_set(&ge.z, &a.z)
|
||||
field.fe_carry_opp(&ge.t, &a.t)
|
||||
}
|
||||
|
||||
ge_cond_negate :: proc "contextless" (ge, a: ^Group_Element, ctrl: int) {
|
||||
tmp: Group_Element = ---
|
||||
ge_negate(&tmp, a)
|
||||
ge_cond_assign(ge, &tmp, ctrl)
|
||||
|
||||
ge_clear(&tmp)
|
||||
}
|
||||
|
||||
ge_cond_assign :: proc "contextless" (ge, a: ^Group_Element, ctrl: int) {
|
||||
field.fe_cond_assign(&ge.x, &a.x, ctrl)
|
||||
field.fe_cond_assign(&ge.y, &a.y, ctrl)
|
||||
field.fe_cond_assign(&ge.z, &a.z, ctrl)
|
||||
field.fe_cond_assign(&ge.t, &a.t, ctrl)
|
||||
}
|
||||
|
||||
ge_cond_select :: proc "contextless" (ge, a, b: ^Group_Element, ctrl: int) {
|
||||
field.fe_cond_select(&ge.x, &a.x, &b.x, ctrl)
|
||||
field.fe_cond_select(&ge.y, &a.y, &b.y, ctrl)
|
||||
field.fe_cond_select(&ge.z, &a.z, &b.z, ctrl)
|
||||
field.fe_cond_select(&ge.t, &a.t, &b.t, ctrl)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
ge_equal :: proc "contextless" (a, b: ^Group_Element) -> int {
|
||||
// (x, y) ?= (x', y') -> (X/Z, Y/Z) ?= (X'/Z', Y'/Z')
|
||||
// X/Z ?= X'/Z', Y/Z ?= Y'/Z' -> X*Z' ?= X'*Z, Y*Z' ?= Y'*Z
|
||||
ax_bz, bx_az, ay_bz, by_az: field.Tight_Field_Element = ---, ---, ---, ---
|
||||
field.fe_carry_mul(&ax_bz, field.fe_relax_cast(&a.x), field.fe_relax_cast(&b.z))
|
||||
field.fe_carry_mul(&bx_az, field.fe_relax_cast(&b.x), field.fe_relax_cast(&a.z))
|
||||
field.fe_carry_mul(&ay_bz, field.fe_relax_cast(&a.y), field.fe_relax_cast(&b.z))
|
||||
field.fe_carry_mul(&by_az, field.fe_relax_cast(&b.y), field.fe_relax_cast(&a.z))
|
||||
|
||||
ret := field.fe_equal(&ax_bz, &bx_az) & field.fe_equal(&ay_bz, &by_az)
|
||||
|
||||
field.fe_clear_vec([]^field.Tight_Field_Element{&ax_bz, &ay_bz, &bx_az, &by_az})
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
ge_is_small_order :: proc "contextless" (ge: ^Group_Element) -> bool {
|
||||
tmp: Group_Element = ---
|
||||
ge_double(&tmp, ge)
|
||||
ge_double(&tmp, &tmp)
|
||||
ge_double(&tmp, &tmp)
|
||||
return ge_equal(&tmp, &GE_IDENTITY) == 1
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
ge_in_prime_order_subgroup_vartime :: proc "contextless" (ge: ^Group_Element) -> bool {
|
||||
// This is currently *very* expensive. The faster method would be
|
||||
// something like (https://eprint.iacr.org/2022/1164.pdf), however
|
||||
// that is a ~50% speedup, and a lot of added complexity for something
|
||||
// that is better solved by "just use ristretto255".
|
||||
tmp: Group_Element = ---
|
||||
_ge_scalarmult(&tmp, ge, &SC_ELL, true)
|
||||
return ge_equal(&tmp, &GE_IDENTITY) == 1
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package _edwards25519
|
||||
|
||||
import "base:intrinsics"
|
||||
import field "core:crypto/_fiat/field_scalar25519"
|
||||
import "core:mem"
|
||||
|
||||
Scalar :: field.Montgomery_Domain_Field_Element
|
||||
|
||||
// WARNING: This is non-canonical and only to be used when checking if
|
||||
// a group element is on the prime-order subgroup.
|
||||
@(private)
|
||||
SC_ELL := field.Non_Montgomery_Domain_Field_Element {
|
||||
field.ELL[0],
|
||||
field.ELL[1],
|
||||
field.ELL[2],
|
||||
field.ELL[3],
|
||||
}
|
||||
|
||||
sc_set_u64 :: proc "contextless" (sc: ^Scalar, i: u64) {
|
||||
tmp := field.Non_Montgomery_Domain_Field_Element{i, 0, 0, 0}
|
||||
field.fe_to_montgomery(sc, &tmp)
|
||||
|
||||
mem.zero_explicit(&tmp, size_of(tmp))
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
sc_set_bytes :: proc "contextless" (sc: ^Scalar, b: []byte) -> bool {
|
||||
if len(b) != 32 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
b_ := transmute(^[32]byte)(raw_data(b))
|
||||
return field.fe_from_bytes(sc, b_)
|
||||
}
|
||||
|
||||
sc_set_bytes_rfc8032 :: proc "contextless" (sc: ^Scalar, b: []byte) {
|
||||
if len(b) != 32 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
b_ := transmute(^[32]byte)(raw_data(b))
|
||||
field.fe_from_bytes_rfc8032(sc, b_)
|
||||
}
|
||||
|
||||
sc_clear :: proc "contextless" (sc: ^Scalar) {
|
||||
mem.zero_explicit(sc, size_of(Scalar))
|
||||
}
|
||||
|
||||
sc_set :: field.fe_set
|
||||
sc_set_bytes_wide :: field.fe_from_bytes_wide
|
||||
sc_bytes :: field.fe_to_bytes
|
||||
|
||||
sc_zero :: field.fe_zero
|
||||
sc_one :: field.fe_one
|
||||
|
||||
sc_add :: field.fe_add
|
||||
sc_sub :: field.fe_sub
|
||||
sc_negate :: field.fe_opp
|
||||
sc_mul :: field.fe_mul
|
||||
sc_square :: field.fe_square
|
||||
|
||||
sc_cond_assign :: field.fe_cond_assign
|
||||
sc_equal :: field.fe_equal
|
||||
@@ -0,0 +1,288 @@
|
||||
package _edwards25519
|
||||
|
||||
import field "core:crypto/_fiat/field_scalar25519"
|
||||
import "core:math/bits"
|
||||
import "core:mem"
|
||||
|
||||
// GE_BASEPOINT_TABLE is 1 * G, ... 15 * G, in precomputed format.
|
||||
//
|
||||
// Note: When generating, the values were reduced to Tight_Field_Element
|
||||
// ranges, even though that is not required.
|
||||
@(private)
|
||||
GE_BASEPOINT_TABLE := Multiply_Table {
|
||||
{
|
||||
{62697248952638, 204681361388450, 631292143396476, 338455783676468, 1213667448819585},
|
||||
{1288382639258501, 245678601348599, 269427782077623, 1462984067271730, 137412439391563},
|
||||
{301289933810280, 1259582250014073, 1422107436869536, 796239922652654, 1953934009299142},
|
||||
{2, 0, 0, 0, 0},
|
||||
},
|
||||
{
|
||||
{1519297034332653, 1098796920435767, 1823476547744119, 808144629470969, 2110930855619772},
|
||||
{338005982828284, 1667856962156925, 100399270107451, 1604566703601691, 1950338038771369},
|
||||
{1920505767731247, 1443759578976892, 1659852098357048, 1484431291070208, 275018744912646},
|
||||
{763163817085987, 2195095074806923, 2167883174351839, 1868059999999762, 911071066608705},
|
||||
},
|
||||
{
|
||||
{960627541894068, 1314966688943942, 1126875971034044, 2059608312958945, 605975666152586},
|
||||
{1714478358025626, 2209607666607510, 1600912834284834, 496072478982142, 481970031861896},
|
||||
{851735079403194, 1088965826757164, 141569479297499, 602804610059257, 2004026468601520},
|
||||
{197585529552380, 324719066578543, 564481854250498, 1173818332764578, 35452976395676},
|
||||
},
|
||||
{
|
||||
{1152980410747203, 2196804280851952, 25745194962557, 1915167295473129, 1266299690309224},
|
||||
{809905889679060, 979732230071345, 1509972345538142, 188492426534402, 818965583123815},
|
||||
{997685409185036, 1451818320876327, 2126681166774509, 2000509606057528, 235432372486854},
|
||||
{887734189279642, 1460338685162044, 877378220074262, 102436391401299, 153369156847490},
|
||||
},
|
||||
{
|
||||
{2056621900836770, 1821657694132497, 1627986892909426, 1163363868678833, 1108873376459226},
|
||||
{1187697490593623, 1066539945237335, 885654531892000, 1357534489491782, 359370291392448},
|
||||
{1509033452137525, 1305318174298508, 613642471748944, 1987256352550234, 1044283663101541},
|
||||
{220105720697037, 387661783287620, 328296827867762, 360035589590664, 795213236824054},
|
||||
},
|
||||
{
|
||||
{1820794733038396, 1612235121681074, 757405923441402, 1094031020892801, 231025333128907},
|
||||
{1639067873254194, 1484176557946322, 300800382144789, 1329915446659183, 1211704578730455},
|
||||
{641900794791527, 1711751746971612, 179044712319955, 576455585963824, 1852617592509865},
|
||||
{743549047192397, 685091042550147, 1952415336873496, 1965124675654685, 513364998442917},
|
||||
},
|
||||
{
|
||||
{1004557076870448, 1762911374844520, 1330807633622723, 384072910939787, 953849032243810},
|
||||
{2178275058221458, 257933183722891, 376684351537894, 2010189102001786, 1981824297484148},
|
||||
{1332915663881114, 1286540505502549, 1741691283561518, 977214932156314, 1764059494778091},
|
||||
{429702949064027, 1368332611650677, 2019867176450999, 2212258376161746, 526160996742554},
|
||||
},
|
||||
{
|
||||
{2098932988258576, 2203688382075948, 2120400160059479, 1748488020948146, 1203264167282624},
|
||||
{677131386735829, 1850249298025188, 672782146532031, 2144145693078904, 2088656272813787},
|
||||
{1065622343976192, 1573853211848116, 223560413590068, 333846833073379, 27832122205830},
|
||||
{1781008836504573, 917619542051793, 544322748939913, 882577394308384, 1720521246471195},
|
||||
},
|
||||
{
|
||||
{660120928379860, 2081944024858618, 1878411111349191, 424587356517195, 2111317439894005},
|
||||
{1834193977811532, 1864164086863319, 797334633289424, 150410812403062, 2085177078466389},
|
||||
{1438117271371866, 783915531014482, 388731514584658, 292113935417795, 1945855002546714},
|
||||
{1678140823166658, 679103239148744, 614102761596238, 1052962498997885, 1863983323810390},
|
||||
},
|
||||
{
|
||||
{1690309392496233, 1116333140326275, 1377242323631039, 717196888780674, 82724646713353},
|
||||
{1722370213432106, 74265192976253, 264239578448472, 1714909985012994, 2216984958602173},
|
||||
{2010482366920922, 1294036471886319, 566466395005815, 1631955803657320, 1751698647538458},
|
||||
{1073230604155753, 1159087041338551, 1664057985455483, 127472702826203, 1339591128522371},
|
||||
},
|
||||
{
|
||||
{478053307175577, 2179515791720985, 21146535423512, 1831683844029536, 462805561553981},
|
||||
{1945267486565588, 1298536818409655, 2214511796262989, 1904981051429012, 252904800782086},
|
||||
{268945954671210, 222740425595395, 1208025911856230, 1080418823003555, 75929831922483},
|
||||
{1884784014268948, 643868448202966, 978736549726821, 46385971089796, 1296884812292320},
|
||||
},
|
||||
{
|
||||
{1861159462859103, 7077532564710, 963010365896826, 1938780006785270, 766241051941647},
|
||||
{1778966986051906, 1713995999765361, 1394565822271816, 1366699246468722, 1213407027149475},
|
||||
{1978989286560907, 2135084162045594, 1951565508865477, 671788336314416, 293123929458176},
|
||||
{902608944504080, 2167765718046481, 1285718473078022, 1222562171329269, 492109027844479},
|
||||
},
|
||||
{
|
||||
{1820807832746213, 1029220580458586, 1101997555432203, 1039081975563572, 202477981158221},
|
||||
{1866134980680205, 2222325502763386, 1830284629571201, 1046966214478970, 418381946936795},
|
||||
{1783460633291322, 1719505443254998, 1810489639976220, 877049370713018, 2187801198742619},
|
||||
{197118243000763, 305493867565736, 518814410156522, 1656246186645170, 901894734874934},
|
||||
},
|
||||
{
|
||||
{225454942125915, 478410476654509, 600524586037746, 643450007230715, 1018615928259319},
|
||||
{1733330584845708, 881092297970296, 507039890129464, 496397090721598, 2230888519577628},
|
||||
{690155664737246, 1010454785646677, 753170144375012, 1651277613844874, 1622648796364156},
|
||||
{1321310321891618, 1089655277873603, 235891750867089, 815878279563688, 1709264240047556},
|
||||
},
|
||||
{
|
||||
{805027036551342, 1387174275567452, 1156538511461704, 1465897486692171, 1208567094120903},
|
||||
{2228417017817483, 202885584970535, 2182114782271881, 2077405042592934, 1029684358182774},
|
||||
{460447547653983, 627817697755692, 524899434670834, 1228019344939427, 740684787777653},
|
||||
{849757462467675, 447476306919899, 422618957298818, 302134659227815, 675831828440895},
|
||||
},
|
||||
}
|
||||
|
||||
ge_scalarmult :: proc "contextless" (ge, p: ^Group_Element, sc: ^Scalar) {
|
||||
tmp: field.Non_Montgomery_Domain_Field_Element
|
||||
field.fe_from_montgomery(&tmp, sc)
|
||||
|
||||
_ge_scalarmult(ge, p, &tmp)
|
||||
|
||||
mem.zero_explicit(&tmp, size_of(tmp))
|
||||
}
|
||||
|
||||
ge_scalarmult_basepoint :: proc "contextless" (ge: ^Group_Element, sc: ^Scalar) {
|
||||
// Something like the comb method from "Fast and compact elliptic-curve
|
||||
// cryptography" Section 3.3, would be more performant, but more
|
||||
// complex.
|
||||
//
|
||||
// - https://eprint.iacr.org/2012/309
|
||||
ge_scalarmult(ge, &GE_BASEPOINT, sc)
|
||||
}
|
||||
|
||||
ge_scalarmult_vartime :: proc "contextless" (ge, p: ^Group_Element, sc: ^Scalar) {
|
||||
tmp: field.Non_Montgomery_Domain_Field_Element
|
||||
field.fe_from_montgomery(&tmp, sc)
|
||||
|
||||
_ge_scalarmult(ge, p, &tmp, true)
|
||||
}
|
||||
|
||||
ge_double_scalarmult_basepoint_vartime :: proc "contextless" (
|
||||
ge: ^Group_Element,
|
||||
a: ^Scalar,
|
||||
A: ^Group_Element,
|
||||
b: ^Scalar,
|
||||
) {
|
||||
// Strauss-Shamir, commonly referred to as the "Shamir trick",
|
||||
// saves half the doublings, relative to doing this the naive way.
|
||||
//
|
||||
// ABGLSV-Pornin (https://eprint.iacr.org/2020/454) is faster,
|
||||
// but significantly more complex, and has incompatibilities with
|
||||
// mixed-order group elements.
|
||||
|
||||
tmp_add: Add_Scratch = ---
|
||||
tmp_addend: Addend_Group_Element = ---
|
||||
tmp_dbl: Double_Scratch = ---
|
||||
tmp: Group_Element = ---
|
||||
|
||||
A_tbl: Multiply_Table = ---
|
||||
mul_tbl_set(&A_tbl, A, &tmp_add)
|
||||
|
||||
sc_a, sc_b: field.Non_Montgomery_Domain_Field_Element
|
||||
field.fe_from_montgomery(&sc_a, a)
|
||||
field.fe_from_montgomery(&sc_b, b)
|
||||
|
||||
ge_identity(&tmp)
|
||||
for i := 31; i >= 0; i = i - 1 {
|
||||
limb := i / 8
|
||||
shift := uint(i & 7) * 8
|
||||
|
||||
limb_byte_a := sc_a[limb] >> shift
|
||||
limb_byte_b := sc_b[limb] >> shift
|
||||
|
||||
hi_a, lo_a := (limb_byte_a >> 4) & 0x0f, limb_byte_a & 0x0f
|
||||
hi_b, lo_b := (limb_byte_b >> 4) & 0x0f, limb_byte_b & 0x0f
|
||||
|
||||
if i != 31 {
|
||||
ge_double(&tmp, &tmp, &tmp_dbl)
|
||||
ge_double(&tmp, &tmp, &tmp_dbl)
|
||||
ge_double(&tmp, &tmp, &tmp_dbl)
|
||||
ge_double(&tmp, &tmp, &tmp_dbl)
|
||||
}
|
||||
mul_tbl_add(&tmp, &A_tbl, hi_a, &tmp_add, &tmp_addend, true)
|
||||
mul_tbl_add(&tmp, &GE_BASEPOINT_TABLE, hi_b, &tmp_add, &tmp_addend, true)
|
||||
|
||||
ge_double(&tmp, &tmp, &tmp_dbl)
|
||||
ge_double(&tmp, &tmp, &tmp_dbl)
|
||||
ge_double(&tmp, &tmp, &tmp_dbl)
|
||||
ge_double(&tmp, &tmp, &tmp_dbl)
|
||||
mul_tbl_add(&tmp, &A_tbl, lo_a, &tmp_add, &tmp_addend, true)
|
||||
mul_tbl_add(&tmp, &GE_BASEPOINT_TABLE, lo_b, &tmp_add, &tmp_addend, true)
|
||||
}
|
||||
|
||||
ge_set(ge, &tmp)
|
||||
}
|
||||
|
||||
@(private)
|
||||
_ge_scalarmult :: proc "contextless" (
|
||||
ge, p: ^Group_Element,
|
||||
sc: ^field.Non_Montgomery_Domain_Field_Element,
|
||||
unsafe_is_vartime := false,
|
||||
) {
|
||||
// Do the simplest possible thing that works and provides adequate,
|
||||
// performance, which is windowed add-then-multiply.
|
||||
|
||||
tmp_add: Add_Scratch = ---
|
||||
tmp_addend: Addend_Group_Element = ---
|
||||
tmp_dbl: Double_Scratch = ---
|
||||
tmp: Group_Element = ---
|
||||
|
||||
p_tbl: Multiply_Table = ---
|
||||
mul_tbl_set(&p_tbl, p, &tmp_add)
|
||||
|
||||
ge_identity(&tmp)
|
||||
for i := 31; i >= 0; i = i - 1 {
|
||||
limb := i / 8
|
||||
shift := uint(i & 7) * 8
|
||||
limb_byte := sc[limb] >> shift
|
||||
|
||||
hi, lo := (limb_byte >> 4) & 0x0f, limb_byte & 0x0f
|
||||
|
||||
if i != 31 {
|
||||
ge_double(&tmp, &tmp, &tmp_dbl)
|
||||
ge_double(&tmp, &tmp, &tmp_dbl)
|
||||
ge_double(&tmp, &tmp, &tmp_dbl)
|
||||
ge_double(&tmp, &tmp, &tmp_dbl)
|
||||
}
|
||||
mul_tbl_add(&tmp, &p_tbl, hi, &tmp_add, &tmp_addend, unsafe_is_vartime)
|
||||
|
||||
ge_double(&tmp, &tmp, &tmp_dbl)
|
||||
ge_double(&tmp, &tmp, &tmp_dbl)
|
||||
ge_double(&tmp, &tmp, &tmp_dbl)
|
||||
ge_double(&tmp, &tmp, &tmp_dbl)
|
||||
mul_tbl_add(&tmp, &p_tbl, lo, &tmp_add, &tmp_addend, unsafe_is_vartime)
|
||||
}
|
||||
|
||||
ge_set(ge, &tmp)
|
||||
|
||||
if !unsafe_is_vartime {
|
||||
ge_clear(&tmp)
|
||||
mem.zero_explicit(&tmp_add, size_of(Add_Scratch))
|
||||
mem.zero_explicit(&tmp_addend, size_of(Addend_Group_Element))
|
||||
mem.zero_explicit(&tmp_dbl, size_of(Double_Scratch))
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
Multiply_Table :: [15]Addend_Group_Element // 0 = inf, which is implicit.
|
||||
|
||||
@(private)
|
||||
mul_tbl_set :: proc "contextless" (
|
||||
tbl: ^Multiply_Table,
|
||||
ge: ^Group_Element,
|
||||
tmp_add: ^Add_Scratch,
|
||||
) {
|
||||
tmp: Group_Element = ---
|
||||
ge_set(&tmp, ge)
|
||||
|
||||
ge_addend_set(&tbl[0], ge)
|
||||
for i := 1; i < 15; i = i + 1 {
|
||||
ge_add_addend(&tmp, &tmp, &tbl[0], tmp_add)
|
||||
ge_addend_set(&tbl[i], &tmp)
|
||||
}
|
||||
|
||||
ge_clear(&tmp)
|
||||
}
|
||||
|
||||
@(private)
|
||||
mul_tbl_add :: proc "contextless" (
|
||||
ge: ^Group_Element,
|
||||
tbl: ^Multiply_Table,
|
||||
idx: u64,
|
||||
tmp_add: ^Add_Scratch,
|
||||
tmp_addend: ^Addend_Group_Element,
|
||||
unsafe_is_vartime: bool,
|
||||
) {
|
||||
// Variable time lookup, with the addition omitted entirely if idx == 0.
|
||||
if unsafe_is_vartime {
|
||||
// Skip adding the point at infinity.
|
||||
if idx != 0 {
|
||||
ge_add_addend(ge, ge, &tbl[idx - 1], tmp_add)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Constant time lookup.
|
||||
tmp_addend^ = {
|
||||
// Point at infinity (0, 1, 1, 0) in precomputed form
|
||||
{1, 0, 0, 0, 0}, // y - x
|
||||
{1, 0, 0, 0, 0}, // y + x
|
||||
{0, 0, 0, 0, 0}, // t * 2d
|
||||
{2, 0, 0, 0, 0}, // z * 2
|
||||
}
|
||||
for i := u64(1); i < 16; i = i + 1 {
|
||||
_, ctrl := bits.sub_u64(0, (i ~ idx), 0)
|
||||
ge_addend_conditional_assign(tmp_addend, &tbl[i - 1], int(~ctrl) & 1)
|
||||
}
|
||||
ge_add_addend(ge, ge, tmp_addend, tmp_add)
|
||||
}
|
||||
@@ -9,7 +9,7 @@ package fiat
|
||||
u1 :: distinct u8
|
||||
i1 :: distinct i8
|
||||
|
||||
@(optimization_mode="none")
|
||||
@(optimization_mode = "none")
|
||||
cmovznz_u64 :: proc "contextless" (arg1: u1, arg2, arg3: u64) -> (out1: u64) {
|
||||
x1 := (u64(arg1) * 0xffffffffffffffff)
|
||||
x2 := ((x1 & arg3) | ((~x1) & arg2))
|
||||
@@ -17,7 +17,7 @@ cmovznz_u64 :: proc "contextless" (arg1: u1, arg2, arg3: u64) -> (out1: u64) {
|
||||
return
|
||||
}
|
||||
|
||||
@(optimization_mode="none")
|
||||
@(optimization_mode = "none")
|
||||
cmovznz_u32 :: proc "contextless" (arg1: u1, arg2, arg3: u32) -> (out1: u32) {
|
||||
x1 := (u32(arg1) * 0xffffffff)
|
||||
x2 := ((x1 & arg3) | ((~x1) & arg2))
|
||||
|
||||
@@ -3,14 +3,32 @@ package field_curve25519
|
||||
import "core:crypto"
|
||||
import "core:mem"
|
||||
|
||||
fe_relax_cast :: #force_inline proc "contextless" (arg1: ^Tight_Field_Element) -> ^Loose_Field_Element {
|
||||
fe_relax_cast :: #force_inline proc "contextless" (
|
||||
arg1: ^Tight_Field_Element,
|
||||
) -> ^Loose_Field_Element {
|
||||
return transmute(^Loose_Field_Element)(arg1)
|
||||
}
|
||||
|
||||
fe_tighten_cast :: #force_inline proc "contextless" (arg1: ^Loose_Field_Element) -> ^Tight_Field_Element {
|
||||
fe_tighten_cast :: #force_inline proc "contextless" (
|
||||
arg1: ^Loose_Field_Element,
|
||||
) -> ^Tight_Field_Element {
|
||||
return transmute(^Tight_Field_Element)(arg1)
|
||||
}
|
||||
|
||||
fe_clear :: proc "contextless" (
|
||||
arg1: $T,
|
||||
) where T == ^Tight_Field_Element || T == ^Loose_Field_Element {
|
||||
mem.zero_explicit(arg1, size_of(arg1^))
|
||||
}
|
||||
|
||||
fe_clear_vec :: proc "contextless" (
|
||||
arg1: $T,
|
||||
) where T == []^Tight_Field_Element || T == []^Loose_Field_Element {
|
||||
for fe in arg1 {
|
||||
fe_clear(fe)
|
||||
}
|
||||
}
|
||||
|
||||
fe_from_bytes :: proc "contextless" (out1: ^Tight_Field_Element, arg1: ^[32]byte) {
|
||||
// Ignore the unused bit by copying the input and masking the bit off
|
||||
// prior to deserialization.
|
||||
@@ -23,12 +41,25 @@ fe_from_bytes :: proc "contextless" (out1: ^Tight_Field_Element, arg1: ^[32]byte
|
||||
mem.zero_explicit(&tmp1, size_of(tmp1))
|
||||
}
|
||||
|
||||
fe_is_negative :: proc "contextless" (arg1: ^Tight_Field_Element) -> int {
|
||||
tmp1: [32]byte = ---
|
||||
|
||||
fe_to_bytes(&tmp1, arg1)
|
||||
ret := tmp1[0] & 1
|
||||
|
||||
mem.zero_explicit(&tmp1, size_of(tmp1))
|
||||
|
||||
return int(ret)
|
||||
}
|
||||
|
||||
fe_equal :: proc "contextless" (arg1, arg2: ^Tight_Field_Element) -> int {
|
||||
tmp2: [32]byte = ---
|
||||
tmp1, tmp2: [32]byte = ---, ---
|
||||
|
||||
fe_to_bytes(&tmp1, arg1)
|
||||
fe_to_bytes(&tmp2, arg2)
|
||||
ret := fe_equal_bytes(arg1, &tmp2)
|
||||
ret := crypto.compare_constant_time(tmp1[:], tmp2[:])
|
||||
|
||||
mem.zero_explicit(&tmp1, size_of(tmp1))
|
||||
mem.zero_explicit(&tmp2, size_of(tmp2))
|
||||
|
||||
return ret
|
||||
@@ -46,7 +77,11 @@ fe_equal_bytes :: proc "contextless" (arg1: ^Tight_Field_Element, arg2: ^[32]byt
|
||||
return ret
|
||||
}
|
||||
|
||||
fe_carry_pow2k :: proc (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element, arg2: uint) {
|
||||
fe_carry_pow2k :: proc "contextless" (
|
||||
out1: ^Tight_Field_Element,
|
||||
arg1: ^Loose_Field_Element,
|
||||
arg2: uint,
|
||||
) {
|
||||
// Special case: `arg1^(2 * 0) = 1`, though this should never happen.
|
||||
if arg2 == 0 {
|
||||
fe_one(out1)
|
||||
@@ -54,27 +89,46 @@ fe_carry_pow2k :: proc (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element,
|
||||
}
|
||||
|
||||
fe_carry_square(out1, arg1)
|
||||
for _ in 1..<arg2 {
|
||||
for _ in 1 ..< arg2 {
|
||||
fe_carry_square(out1, fe_relax_cast(out1))
|
||||
}
|
||||
}
|
||||
|
||||
fe_carry_add :: #force_inline proc "contextless" (out1, arg1, arg2: ^Tight_Field_Element) {
|
||||
fe_add(fe_relax_cast(out1), arg1, arg2)
|
||||
fe_carry(out1, fe_relax_cast(out1))
|
||||
}
|
||||
|
||||
fe_carry_sub :: #force_inline proc "contextless" (out1, arg1, arg2: ^Tight_Field_Element) {
|
||||
fe_sub(fe_relax_cast(out1), arg1, arg2)
|
||||
fe_carry(out1, fe_relax_cast(out1))
|
||||
}
|
||||
|
||||
fe_carry_opp :: #force_inline proc "contextless" (out1, arg1: ^Tight_Field_Element) {
|
||||
fe_opp(fe_relax_cast(out1), arg1)
|
||||
fe_carry(out1, fe_relax_cast(out1))
|
||||
}
|
||||
|
||||
fe_carry_invsqrt :: proc (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element) -> int {
|
||||
// Inverse square root taken from Monocypher.
|
||||
fe_carry_abs :: #force_inline proc "contextless" (out1, arg1: ^Tight_Field_Element) {
|
||||
fe_cond_negate(out1, arg1, fe_is_negative(arg1))
|
||||
}
|
||||
|
||||
fe_carry_sqrt_ratio_m1 :: proc "contextless" (
|
||||
out1: ^Tight_Field_Element,
|
||||
arg1: ^Loose_Field_Element, // u
|
||||
arg2: ^Loose_Field_Element, // v
|
||||
) -> int {
|
||||
// SQRT_RATIO_M1(u, v) from RFC 9496 - 4.2, based on the inverse
|
||||
// square root from Monocypher.
|
||||
|
||||
w: Tight_Field_Element = ---
|
||||
fe_carry_mul(&w, arg1, arg2) // u * v
|
||||
|
||||
// r = tmp1 = u * w^((p-5)/8)
|
||||
tmp1, tmp2, tmp3: Tight_Field_Element = ---, ---, ---
|
||||
|
||||
// t0 = x^((p-5)/8)
|
||||
// Can be achieved with a simple double & add ladder,
|
||||
// but it would be slower.
|
||||
fe_carry_pow2k(&tmp1, arg1, 1)
|
||||
fe_carry_pow2k(&tmp1, fe_relax_cast(&w), 1)
|
||||
fe_carry_pow2k(&tmp2, fe_relax_cast(&tmp1), 2)
|
||||
fe_carry_mul(&tmp2, arg1, fe_relax_cast(&tmp2))
|
||||
fe_carry_mul(&tmp2, fe_relax_cast(&w), fe_relax_cast(&tmp2))
|
||||
fe_carry_mul(&tmp1, fe_relax_cast(&tmp1), fe_relax_cast(&tmp2))
|
||||
fe_carry_pow2k(&tmp1, fe_relax_cast(&tmp1), 1)
|
||||
fe_carry_mul(&tmp1, fe_relax_cast(&tmp2), fe_relax_cast(&tmp1))
|
||||
@@ -93,46 +147,121 @@ fe_carry_invsqrt :: proc (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element
|
||||
fe_carry_pow2k(&tmp2, fe_relax_cast(&tmp2), 50)
|
||||
fe_carry_mul(&tmp1, fe_relax_cast(&tmp2), fe_relax_cast(&tmp1))
|
||||
fe_carry_pow2k(&tmp1, fe_relax_cast(&tmp1), 2)
|
||||
fe_carry_mul(&tmp1, fe_relax_cast(&tmp1), arg1)
|
||||
fe_carry_mul(&tmp1, fe_relax_cast(&tmp1), fe_relax_cast(&w)) // w^((p-5)/8)
|
||||
|
||||
// quartic = x^((p-1)/4)
|
||||
quartic := &tmp2
|
||||
fe_carry_square(quartic, fe_relax_cast(&tmp1))
|
||||
fe_carry_mul(quartic, fe_relax_cast(quartic), arg1)
|
||||
fe_carry_mul(&tmp1, fe_relax_cast(&tmp1), arg1) // u * w^((p-5)/8)
|
||||
|
||||
// Serialize quartic once to save on repeated serialization/sanitization.
|
||||
quartic_buf: [32]byte = ---
|
||||
fe_to_bytes(&quartic_buf, quartic)
|
||||
check := &tmp3
|
||||
// Serialize `check` once to save on repeated serialization.
|
||||
r, check := &tmp1, &tmp2
|
||||
b: [32]byte = ---
|
||||
fe_carry_square(check, fe_relax_cast(r))
|
||||
fe_carry_mul(check, fe_relax_cast(check), arg2) // check * v
|
||||
fe_to_bytes(&b, check)
|
||||
|
||||
fe_one(check)
|
||||
p1 := fe_equal_bytes(check, &quartic_buf)
|
||||
fe_carry_opp(check, check)
|
||||
m1 := fe_equal_bytes(check, &quartic_buf)
|
||||
fe_carry_opp(check, &SQRT_M1)
|
||||
ms := fe_equal_bytes(check, &quartic_buf)
|
||||
u, neg_u, neg_u_i := &tmp3, &w, check
|
||||
fe_carry(u, arg1)
|
||||
fe_carry_opp(neg_u, u)
|
||||
fe_carry_mul(neg_u_i, fe_relax_cast(neg_u), fe_relax_cast(&FE_SQRT_M1))
|
||||
|
||||
// if quartic == -1 or sqrt(-1)
|
||||
// then isr = x^((p-1)/4) * sqrt(-1)
|
||||
// else isr = x^((p-1)/4)
|
||||
fe_carry_mul(out1, fe_relax_cast(&tmp1), fe_relax_cast(&SQRT_M1))
|
||||
fe_cond_assign(out1, &tmp1, (m1|ms) ~ 1)
|
||||
correct_sign_sqrt := fe_equal_bytes(u, &b)
|
||||
flipped_sign_sqrt := fe_equal_bytes(neg_u, &b)
|
||||
flipped_sign_sqrt_i := fe_equal_bytes(neg_u_i, &b)
|
||||
|
||||
mem.zero_explicit(&tmp1, size_of(tmp1))
|
||||
mem.zero_explicit(&tmp2, size_of(tmp2))
|
||||
mem.zero_explicit(&tmp3, size_of(tmp3))
|
||||
mem.zero_explicit(&quartic_buf, size_of(quartic_buf))
|
||||
r_prime := check
|
||||
fe_carry_mul(r_prime, fe_relax_cast(r), fe_relax_cast(&FE_SQRT_M1))
|
||||
fe_cond_assign(r, r_prime, flipped_sign_sqrt | flipped_sign_sqrt_i)
|
||||
|
||||
return p1 | m1
|
||||
// Pick the non-negative square root.
|
||||
fe_carry_abs(out1, r)
|
||||
|
||||
fe_clear_vec([]^Tight_Field_Element{&w, &tmp1, &tmp2, &tmp3})
|
||||
mem.zero_explicit(&b, size_of(b))
|
||||
|
||||
return correct_sign_sqrt | flipped_sign_sqrt
|
||||
}
|
||||
|
||||
fe_carry_inv :: proc (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element) {
|
||||
fe_carry_inv :: proc "contextless" (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element) {
|
||||
tmp1: Tight_Field_Element
|
||||
|
||||
fe_carry_square(&tmp1, arg1)
|
||||
_ = fe_carry_invsqrt(&tmp1, fe_relax_cast(&tmp1))
|
||||
_ = fe_carry_sqrt_ratio_m1(&tmp1, fe_relax_cast(&FE_ONE), fe_relax_cast(&tmp1))
|
||||
fe_carry_square(&tmp1, fe_relax_cast(&tmp1))
|
||||
fe_carry_mul(out1, fe_relax_cast(&tmp1), arg1)
|
||||
|
||||
mem.zero_explicit(&tmp1, size_of(tmp1))
|
||||
fe_clear(&tmp1)
|
||||
}
|
||||
|
||||
fe_zero :: proc "contextless" (out1: ^Tight_Field_Element) {
|
||||
out1[0] = 0
|
||||
out1[1] = 0
|
||||
out1[2] = 0
|
||||
out1[3] = 0
|
||||
out1[4] = 0
|
||||
}
|
||||
|
||||
fe_one :: proc "contextless" (out1: ^Tight_Field_Element) {
|
||||
out1[0] = 1
|
||||
out1[1] = 0
|
||||
out1[2] = 0
|
||||
out1[3] = 0
|
||||
out1[4] = 0
|
||||
}
|
||||
|
||||
fe_set :: proc "contextless" (out1, arg1: ^Tight_Field_Element) {
|
||||
x1 := arg1[0]
|
||||
x2 := arg1[1]
|
||||
x3 := arg1[2]
|
||||
x4 := arg1[3]
|
||||
x5 := arg1[4]
|
||||
out1[0] = x1
|
||||
out1[1] = x2
|
||||
out1[2] = x3
|
||||
out1[3] = x4
|
||||
out1[4] = x5
|
||||
}
|
||||
|
||||
@(optimization_mode = "none")
|
||||
fe_cond_swap :: #force_no_inline proc "contextless" (out1, out2: ^Tight_Field_Element, arg1: int) {
|
||||
mask := (u64(arg1) * 0xffffffffffffffff)
|
||||
x := (out1[0] ~ out2[0]) & mask
|
||||
x1, y1 := out1[0] ~ x, out2[0] ~ x
|
||||
x = (out1[1] ~ out2[1]) & mask
|
||||
x2, y2 := out1[1] ~ x, out2[1] ~ x
|
||||
x = (out1[2] ~ out2[2]) & mask
|
||||
x3, y3 := out1[2] ~ x, out2[2] ~ x
|
||||
x = (out1[3] ~ out2[3]) & mask
|
||||
x4, y4 := out1[3] ~ x, out2[3] ~ x
|
||||
x = (out1[4] ~ out2[4]) & mask
|
||||
x5, y5 := out1[4] ~ x, out2[4] ~ x
|
||||
out1[0], out2[0] = x1, y1
|
||||
out1[1], out2[1] = x2, y2
|
||||
out1[2], out2[2] = x3, y3
|
||||
out1[3], out2[3] = x4, y4
|
||||
out1[4], out2[4] = x5, y5
|
||||
}
|
||||
|
||||
@(optimization_mode = "none")
|
||||
fe_cond_select :: #force_no_inline proc "contextless" (
|
||||
out1, arg1, arg2: $T,
|
||||
arg3: int,
|
||||
) where T == ^Tight_Field_Element || T == ^Loose_Field_Element {
|
||||
mask := (u64(arg3) * 0xffffffffffffffff)
|
||||
x1 := ((mask & arg2[0]) | ((~mask) & arg1[0]))
|
||||
x2 := ((mask & arg2[1]) | ((~mask) & arg1[1]))
|
||||
x3 := ((mask & arg2[2]) | ((~mask) & arg1[2]))
|
||||
x4 := ((mask & arg2[3]) | ((~mask) & arg1[3]))
|
||||
x5 := ((mask & arg2[4]) | ((~mask) & arg1[4]))
|
||||
out1[0] = x1
|
||||
out1[1] = x2
|
||||
out1[2] = x3
|
||||
out1[3] = x4
|
||||
out1[4] = x5
|
||||
}
|
||||
|
||||
fe_cond_negate :: proc "contextless" (out1, arg1: ^Tight_Field_Element, ctrl: int) {
|
||||
tmp1: Tight_Field_Element = ---
|
||||
fe_carry_opp(&tmp1, arg1)
|
||||
fe_cond_select(out1, arg1, &tmp1, ctrl)
|
||||
|
||||
fe_clear(&tmp1)
|
||||
}
|
||||
|
||||
@@ -30,8 +30,6 @@ package field_curve25519
|
||||
//
|
||||
// While the base implementation is provably correct, this implementation
|
||||
// makes no such claims as the port and optimizations were done by hand.
|
||||
// At some point, it may be worth adding support to fiat-crypto for
|
||||
// generating Odin output.
|
||||
//
|
||||
// TODO:
|
||||
// * When fiat-crypto supports it, using a saturated 64-bit limbs
|
||||
@@ -44,7 +42,10 @@ import "core:math/bits"
|
||||
Loose_Field_Element :: distinct [5]u64
|
||||
Tight_Field_Element :: distinct [5]u64
|
||||
|
||||
SQRT_M1 := Tight_Field_Element{
|
||||
FE_ZERO := Tight_Field_Element{0, 0, 0, 0, 0}
|
||||
FE_ONE := Tight_Field_Element{1, 0, 0, 0, 0}
|
||||
|
||||
FE_SQRT_M1 := Tight_Field_Element {
|
||||
1718705420411056,
|
||||
234908883556509,
|
||||
2233514472574048,
|
||||
@@ -52,7 +53,13 @@ SQRT_M1 := Tight_Field_Element{
|
||||
765476049583133,
|
||||
}
|
||||
|
||||
_addcarryx_u51 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u64) -> (out1: u64, out2: fiat.u1) {
|
||||
_addcarryx_u51 :: #force_inline proc "contextless" (
|
||||
arg1: fiat.u1,
|
||||
arg2, arg3: u64,
|
||||
) -> (
|
||||
out1: u64,
|
||||
out2: fiat.u1,
|
||||
) {
|
||||
x1 := ((u64(arg1) + arg2) + arg3)
|
||||
x2 := (x1 & 0x7ffffffffffff)
|
||||
x3 := fiat.u1((x1 >> 51))
|
||||
@@ -61,7 +68,13 @@ _addcarryx_u51 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u
|
||||
return
|
||||
}
|
||||
|
||||
_subborrowx_u51 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u64) -> (out1: u64, out2: fiat.u1) {
|
||||
_subborrowx_u51 :: #force_inline proc "contextless" (
|
||||
arg1: fiat.u1,
|
||||
arg2, arg3: u64,
|
||||
) -> (
|
||||
out1: u64,
|
||||
out2: fiat.u1,
|
||||
) {
|
||||
x1 := ((i64(arg2) - i64(arg1)) - i64(arg3))
|
||||
x2 := fiat.i1((x1 >> 51))
|
||||
x3 := (u64(x1) & 0x7ffffffffffff)
|
||||
@@ -70,7 +83,7 @@ _subborrowx_u51 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3:
|
||||
return
|
||||
}
|
||||
|
||||
fe_carry_mul :: proc (out1: ^Tight_Field_Element, arg1, arg2: ^Loose_Field_Element) {
|
||||
fe_carry_mul :: proc "contextless" (out1: ^Tight_Field_Element, arg1, arg2: ^Loose_Field_Element) {
|
||||
x2, x1 := bits.mul_u64(arg1[4], (arg2[4] * 0x13))
|
||||
x4, x3 := bits.mul_u64(arg1[4], (arg2[3] * 0x13))
|
||||
x6, x5 := bits.mul_u64(arg1[4], (arg2[2] * 0x13))
|
||||
@@ -169,7 +182,7 @@ fe_carry_mul :: proc (out1: ^Tight_Field_Element, arg1, arg2: ^Loose_Field_Eleme
|
||||
out1[4] = x152
|
||||
}
|
||||
|
||||
fe_carry_square :: proc (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element) {
|
||||
fe_carry_square :: proc "contextless" (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element) {
|
||||
x1 := (arg1[4] * 0x13)
|
||||
x2 := (x1 * 0x2)
|
||||
x3 := (arg1[4] * 0x2)
|
||||
@@ -305,8 +318,11 @@ fe_opp :: proc "contextless" (out1: ^Loose_Field_Element, arg1: ^Tight_Field_Ele
|
||||
out1[4] = x5
|
||||
}
|
||||
|
||||
@(optimization_mode="none")
|
||||
fe_cond_assign :: #force_no_inline proc "contextless" (out1, arg1: ^Tight_Field_Element, arg2: int) {
|
||||
@(optimization_mode = "none")
|
||||
fe_cond_assign :: #force_no_inline proc "contextless" (
|
||||
out1, arg1: ^Tight_Field_Element,
|
||||
arg2: int,
|
||||
) {
|
||||
x1 := fiat.cmovznz_u64(fiat.u1(arg2), out1[0], arg1[0])
|
||||
x2 := fiat.cmovznz_u64(fiat.u1(arg2), out1[1], arg1[1])
|
||||
x3 := fiat.cmovznz_u64(fiat.u1(arg2), out1[2], arg1[2])
|
||||
@@ -527,7 +543,10 @@ fe_relax :: proc "contextless" (out1: ^Loose_Field_Element, arg1: ^Tight_Field_E
|
||||
out1[4] = x5
|
||||
}
|
||||
|
||||
fe_carry_scmul_121666 :: proc (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element) {
|
||||
fe_carry_scmul_121666 :: proc "contextless" (
|
||||
out1: ^Tight_Field_Element,
|
||||
arg1: ^Loose_Field_Element,
|
||||
) {
|
||||
x2, x1 := bits.mul_u64(0x1db42, arg1[4])
|
||||
x4, x3 := bits.mul_u64(0x1db42, arg1[3])
|
||||
x6, x5 := bits.mul_u64(0x1db42, arg1[2])
|
||||
@@ -565,54 +584,3 @@ fe_carry_scmul_121666 :: proc (out1: ^Tight_Field_Element, arg1: ^Loose_Field_El
|
||||
out1[3] = x27
|
||||
out1[4] = x32
|
||||
}
|
||||
|
||||
// The following routines were added by hand, and do not come from fiat-crypto.
|
||||
|
||||
fe_zero :: proc "contextless" (out1: ^Tight_Field_Element) {
|
||||
out1[0] = 0
|
||||
out1[1] = 0
|
||||
out1[2] = 0
|
||||
out1[3] = 0
|
||||
out1[4] = 0
|
||||
}
|
||||
|
||||
fe_one :: proc "contextless" (out1: ^Tight_Field_Element) {
|
||||
out1[0] = 1
|
||||
out1[1] = 0
|
||||
out1[2] = 0
|
||||
out1[3] = 0
|
||||
out1[4] = 0
|
||||
}
|
||||
|
||||
fe_set :: proc "contextless" (out1, arg1: ^Tight_Field_Element) {
|
||||
x1 := arg1[0]
|
||||
x2 := arg1[1]
|
||||
x3 := arg1[2]
|
||||
x4 := arg1[3]
|
||||
x5 := arg1[4]
|
||||
out1[0] = x1
|
||||
out1[1] = x2
|
||||
out1[2] = x3
|
||||
out1[3] = x4
|
||||
out1[4] = x5
|
||||
}
|
||||
|
||||
@(optimization_mode="none")
|
||||
fe_cond_swap :: #force_no_inline proc "contextless" (out1, out2: ^Tight_Field_Element, arg1: int) {
|
||||
mask := -u64(arg1)
|
||||
x := (out1[0] ~ out2[0]) & mask
|
||||
x1, y1 := out1[0] ~ x, out2[0] ~ x
|
||||
x = (out1[1] ~ out2[1]) & mask
|
||||
x2, y2 := out1[1] ~ x, out2[1] ~ x
|
||||
x = (out1[2] ~ out2[2]) & mask
|
||||
x3, y3 := out1[2] ~ x, out2[2] ~ x
|
||||
x = (out1[3] ~ out2[3]) & mask
|
||||
x4, y4 := out1[3] ~ x, out2[3] ~ x
|
||||
x = (out1[4] ~ out2[4]) & mask
|
||||
x5, y5 := out1[4] ~ x, out2[4] ~ x
|
||||
out1[0], out2[0] = x1, y1
|
||||
out1[1], out2[1] = x2, y2
|
||||
out1[2], out2[2] = x3, y3
|
||||
out1[3], out2[3] = x4, y4
|
||||
out1[4], out2[4] = x5, y5
|
||||
}
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
package field_poly1305
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:encoding/endian"
|
||||
import "core:mem"
|
||||
|
||||
fe_relax_cast :: #force_inline proc "contextless" (arg1: ^Tight_Field_Element) -> ^Loose_Field_Element {
|
||||
fe_relax_cast :: #force_inline proc "contextless" (
|
||||
arg1: ^Tight_Field_Element,
|
||||
) -> ^Loose_Field_Element {
|
||||
return transmute(^Loose_Field_Element)(arg1)
|
||||
}
|
||||
|
||||
fe_tighten_cast :: #force_inline proc "contextless" (arg1: ^Loose_Field_Element) -> ^Tight_Field_Element {
|
||||
fe_tighten_cast :: #force_inline proc "contextless" (
|
||||
arg1: ^Loose_Field_Element,
|
||||
) -> ^Tight_Field_Element {
|
||||
return transmute(^Tight_Field_Element)(arg1)
|
||||
}
|
||||
|
||||
fe_from_bytes :: #force_inline proc (out1: ^Tight_Field_Element, arg1: []byte, arg2: byte) {
|
||||
fe_from_bytes :: #force_inline proc "contextless" (
|
||||
out1: ^Tight_Field_Element,
|
||||
arg1: []byte,
|
||||
arg2: byte,
|
||||
) {
|
||||
// fiat-crypto's deserialization routine effectively processes a
|
||||
// single byte at a time, and wants 256-bits of input for a value
|
||||
// that will be 128-bits or 129-bits.
|
||||
@@ -20,7 +29,9 @@ fe_from_bytes :: #force_inline proc (out1: ^Tight_Field_Element, arg1: []byte, a
|
||||
// makes implementing the actual MAC block processing considerably
|
||||
// neater.
|
||||
|
||||
assert(len(arg1) == 16)
|
||||
if len(arg1) != 16 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
// While it may be unwise to do deserialization here on our
|
||||
// own when fiat-crypto provides equivalent functionality,
|
||||
@@ -51,3 +62,35 @@ fe_from_u64s :: proc "contextless" (out1: ^Tight_Field_Element, lo, hi: u64) {
|
||||
// This routine is only used to deserialize `r` which is confidential.
|
||||
mem.zero_explicit(&tmp, size_of(tmp))
|
||||
}
|
||||
|
||||
fe_zero :: proc "contextless" (out1: ^Tight_Field_Element) {
|
||||
out1[0] = 0
|
||||
out1[1] = 0
|
||||
out1[2] = 0
|
||||
}
|
||||
|
||||
fe_set :: #force_inline proc "contextless" (out1, arg1: ^Tight_Field_Element) {
|
||||
x1 := arg1[0]
|
||||
x2 := arg1[1]
|
||||
x3 := arg1[2]
|
||||
out1[0] = x1
|
||||
out1[1] = x2
|
||||
out1[2] = x3
|
||||
}
|
||||
|
||||
@(optimization_mode = "none")
|
||||
fe_cond_swap :: #force_no_inline proc "contextless" (
|
||||
out1, out2: ^Tight_Field_Element,
|
||||
arg1: bool,
|
||||
) {
|
||||
mask := (u64(arg1) * 0xffffffffffffffff)
|
||||
x := (out1[0] ~ out2[0]) & mask
|
||||
x1, y1 := out1[0] ~ x, out2[0] ~ x
|
||||
x = (out1[1] ~ out2[1]) & mask
|
||||
x2, y2 := out1[1] ~ x, out2[1] ~ x
|
||||
x = (out1[2] ~ out2[2]) & mask
|
||||
x3, y3 := out1[2] ~ x, out2[2] ~ x
|
||||
out1[0], out2[0] = x1, y1
|
||||
out1[1], out2[1] = x2, y2
|
||||
out1[2], out2[2] = x3, y3
|
||||
}
|
||||
|
||||
@@ -39,7 +39,13 @@ import "core:math/bits"
|
||||
Loose_Field_Element :: distinct [3]u64
|
||||
Tight_Field_Element :: distinct [3]u64
|
||||
|
||||
_addcarryx_u44 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u64) -> (out1: u64, out2: fiat.u1) {
|
||||
_addcarryx_u44 :: #force_inline proc "contextless" (
|
||||
arg1: fiat.u1,
|
||||
arg2, arg3: u64,
|
||||
) -> (
|
||||
out1: u64,
|
||||
out2: fiat.u1,
|
||||
) {
|
||||
x1 := ((u64(arg1) + arg2) + arg3)
|
||||
x2 := (x1 & 0xfffffffffff)
|
||||
x3 := fiat.u1((x1 >> 44))
|
||||
@@ -48,7 +54,13 @@ _addcarryx_u44 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u
|
||||
return
|
||||
}
|
||||
|
||||
_subborrowx_u44 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u64) -> (out1: u64, out2: fiat.u1) {
|
||||
_subborrowx_u44 :: #force_inline proc "contextless" (
|
||||
arg1: fiat.u1,
|
||||
arg2, arg3: u64,
|
||||
) -> (
|
||||
out1: u64,
|
||||
out2: fiat.u1,
|
||||
) {
|
||||
x1 := ((i64(arg2) - i64(arg1)) - i64(arg3))
|
||||
x2 := fiat.i1((x1 >> 44))
|
||||
x3 := (u64(x1) & 0xfffffffffff)
|
||||
@@ -57,7 +69,13 @@ _subborrowx_u44 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3:
|
||||
return
|
||||
}
|
||||
|
||||
_addcarryx_u43 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u64) -> (out1: u64, out2: fiat.u1) {
|
||||
_addcarryx_u43 :: #force_inline proc "contextless" (
|
||||
arg1: fiat.u1,
|
||||
arg2, arg3: u64,
|
||||
) -> (
|
||||
out1: u64,
|
||||
out2: fiat.u1,
|
||||
) {
|
||||
x1 := ((u64(arg1) + arg2) + arg3)
|
||||
x2 := (x1 & 0x7ffffffffff)
|
||||
x3 := fiat.u1((x1 >> 43))
|
||||
@@ -66,7 +84,13 @@ _addcarryx_u43 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u
|
||||
return
|
||||
}
|
||||
|
||||
_subborrowx_u43 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u64) -> (out1: u64, out2: fiat.u1) {
|
||||
_subborrowx_u43 :: #force_inline proc "contextless" (
|
||||
arg1: fiat.u1,
|
||||
arg2, arg3: u64,
|
||||
) -> (
|
||||
out1: u64,
|
||||
out2: fiat.u1,
|
||||
) {
|
||||
x1 := ((i64(arg2) - i64(arg1)) - i64(arg3))
|
||||
x2 := fiat.i1((x1 >> 43))
|
||||
x3 := (u64(x1) & 0x7ffffffffff)
|
||||
@@ -75,7 +99,7 @@ _subborrowx_u43 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3:
|
||||
return
|
||||
}
|
||||
|
||||
fe_carry_mul :: proc (out1: ^Tight_Field_Element, arg1, arg2: ^Loose_Field_Element) {
|
||||
fe_carry_mul :: proc "contextless" (out1: ^Tight_Field_Element, arg1, arg2: ^Loose_Field_Element) {
|
||||
x2, x1 := bits.mul_u64(arg1[2], (arg2[2] * 0x5))
|
||||
x4, x3 := bits.mul_u64(arg1[2], (arg2[1] * 0xa))
|
||||
x6, x5 := bits.mul_u64(arg1[1], (arg2[2] * 0xa))
|
||||
@@ -120,7 +144,7 @@ fe_carry_mul :: proc (out1: ^Tight_Field_Element, arg1, arg2: ^Loose_Field_Eleme
|
||||
out1[2] = x62
|
||||
}
|
||||
|
||||
fe_carry_square :: proc (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element) {
|
||||
fe_carry_square :: proc "contextless" (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element) {
|
||||
x1 := (arg1[2] * 0x5)
|
||||
x2 := (x1 * 0x2)
|
||||
x3 := (arg1[2] * 0x2)
|
||||
@@ -201,8 +225,11 @@ fe_opp :: proc "contextless" (out1: ^Loose_Field_Element, arg1: ^Tight_Field_Ele
|
||||
out1[2] = x3
|
||||
}
|
||||
|
||||
@(optimization_mode="none")
|
||||
fe_cond_assign :: #force_no_inline proc "contextless" (out1, arg1: ^Tight_Field_Element, arg2: bool) {
|
||||
@(optimization_mode = "none")
|
||||
fe_cond_assign :: #force_no_inline proc "contextless" (
|
||||
out1, arg1: ^Tight_Field_Element,
|
||||
arg2: bool,
|
||||
) {
|
||||
x1 := fiat.cmovznz_u64(fiat.u1(arg2), out1[0], arg1[0])
|
||||
x2 := fiat.cmovznz_u64(fiat.u1(arg2), out1[1], arg1[1])
|
||||
x3 := fiat.cmovznz_u64(fiat.u1(arg2), out1[2], arg1[2])
|
||||
@@ -325,34 +352,3 @@ fe_relax :: proc "contextless" (out1: ^Loose_Field_Element, arg1: ^Tight_Field_E
|
||||
out1[1] = x2
|
||||
out1[2] = x3
|
||||
}
|
||||
|
||||
// The following routines were added by hand, and do not come from fiat-crypto.
|
||||
|
||||
fe_zero :: proc "contextless" (out1: ^Tight_Field_Element) {
|
||||
out1[0] = 0
|
||||
out1[1] = 0
|
||||
out1[2] = 0
|
||||
}
|
||||
|
||||
fe_set :: #force_inline proc "contextless" (out1, arg1: ^Tight_Field_Element) {
|
||||
x1 := arg1[0]
|
||||
x2 := arg1[1]
|
||||
x3 := arg1[2]
|
||||
out1[0] = x1
|
||||
out1[1] = x2
|
||||
out1[2] = x3
|
||||
}
|
||||
|
||||
@(optimization_mode="none")
|
||||
fe_cond_swap :: #force_no_inline proc "contextless" (out1, out2: ^Tight_Field_Element, arg1: bool) {
|
||||
mask := -u64(arg1)
|
||||
x := (out1[0] ~ out2[0]) & mask
|
||||
x1, y1 := out1[0] ~ x, out2[0] ~ x
|
||||
x = (out1[1] ~ out2[1]) & mask
|
||||
x2, y2 := out1[1] ~ x, out2[1] ~ x
|
||||
x = (out1[2] ~ out2[2]) & mask
|
||||
x3, y3 := out1[2] ~ x, out2[2] ~ x
|
||||
out1[0], out2[0] = x1, y1
|
||||
out1[1], out2[1] = x2, y2
|
||||
out1[2], out2[2] = x3, y3
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
package field_scalar25519
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:encoding/endian"
|
||||
import "core:math/bits"
|
||||
import "core:mem"
|
||||
|
||||
@(private)
|
||||
_TWO_168 := Montgomery_Domain_Field_Element {
|
||||
0x5b8ab432eac74798,
|
||||
0x38afddd6de59d5d7,
|
||||
0xa2c131b399411b7c,
|
||||
0x6329a7ed9ce5a30,
|
||||
}
|
||||
@(private)
|
||||
_TWO_336 := Montgomery_Domain_Field_Element {
|
||||
0xbd3d108e2b35ecc5,
|
||||
0x5c3a3718bdf9c90b,
|
||||
0x63aa97a331b4f2ee,
|
||||
0x3d217f5be65cb5c,
|
||||
}
|
||||
|
||||
fe_clear :: proc "contextless" (arg1: ^Montgomery_Domain_Field_Element) {
|
||||
mem.zero_explicit(arg1, size_of(Montgomery_Domain_Field_Element))
|
||||
}
|
||||
|
||||
fe_from_bytes :: proc "contextless" (
|
||||
out1: ^Montgomery_Domain_Field_Element,
|
||||
arg1: ^[32]byte,
|
||||
unsafe_assume_canonical := false,
|
||||
) -> bool {
|
||||
tmp := Non_Montgomery_Domain_Field_Element {
|
||||
endian.unchecked_get_u64le(arg1[0:]),
|
||||
endian.unchecked_get_u64le(arg1[8:]),
|
||||
endian.unchecked_get_u64le(arg1[16:]),
|
||||
endian.unchecked_get_u64le(arg1[24:]),
|
||||
}
|
||||
defer mem.zero_explicit(&tmp, size_of(tmp))
|
||||
|
||||
// Check that tmp is in the the range [0, ELL).
|
||||
if !unsafe_assume_canonical {
|
||||
_, borrow := bits.sub_u64(ELL[0] - 1, tmp[0], 0)
|
||||
_, borrow = bits.sub_u64(ELL[1], tmp[1], borrow)
|
||||
_, borrow = bits.sub_u64(ELL[2], tmp[2], borrow)
|
||||
_, borrow = bits.sub_u64(ELL[3], tmp[3], borrow)
|
||||
if borrow != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fe_to_montgomery(out1, &tmp)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fe_from_bytes_rfc8032 :: proc "contextless" (
|
||||
out1: ^Montgomery_Domain_Field_Element,
|
||||
arg1: ^[32]byte,
|
||||
) {
|
||||
tmp: [64]byte
|
||||
copy(tmp[:], arg1[:])
|
||||
|
||||
// Apply "clamping" as in RFC 8032.
|
||||
tmp[0] &= 248
|
||||
tmp[31] &= 127
|
||||
tmp[31] |= 64 // Sets the 254th bit, so the encoding is non-canonical.
|
||||
|
||||
fe_from_bytes_wide(out1, &tmp)
|
||||
|
||||
mem.zero_explicit(&tmp, size_of(tmp))
|
||||
}
|
||||
|
||||
fe_from_bytes_wide :: proc "contextless" (
|
||||
out1: ^Montgomery_Domain_Field_Element,
|
||||
arg1: ^[64]byte,
|
||||
) {
|
||||
tmp: Montgomery_Domain_Field_Element
|
||||
// Use Frank Denis' trick, as documented by Filippo Valsorda
|
||||
// at https://words.filippo.io/dispatches/wide-reduction/
|
||||
//
|
||||
// x = c * 2^336 + b * 2^168 + a mod l
|
||||
_fe_from_bytes_short(out1, arg1[:21]) // a
|
||||
|
||||
_fe_from_bytes_short(&tmp, arg1[21:42]) // b
|
||||
fe_mul(&tmp, &tmp, &_TWO_168) // b * 2^168
|
||||
fe_add(out1, out1, &tmp) // a + b * 2^168
|
||||
|
||||
_fe_from_bytes_short(&tmp, arg1[42:]) // c
|
||||
fe_mul(&tmp, &tmp, &_TWO_336) // c * 2^336
|
||||
fe_add(out1, out1, &tmp) // a + b * 2^168 + c * 2^336
|
||||
|
||||
fe_clear(&tmp)
|
||||
}
|
||||
|
||||
@(private)
|
||||
_fe_from_bytes_short :: proc "contextless" (out1: ^Montgomery_Domain_Field_Element, arg1: []byte) {
|
||||
// INVARIANT: len(arg1) < 32.
|
||||
if len(arg1) >= 32 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
tmp: [32]byte
|
||||
copy(tmp[:], arg1)
|
||||
|
||||
_ = fe_from_bytes(out1, &tmp, true)
|
||||
mem.zero_explicit(&tmp, size_of(tmp))
|
||||
}
|
||||
|
||||
fe_to_bytes :: proc "contextless" (out1: []byte, arg1: ^Montgomery_Domain_Field_Element) {
|
||||
if len(out1) != 32 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
tmp: Non_Montgomery_Domain_Field_Element
|
||||
fe_from_montgomery(&tmp, arg1)
|
||||
|
||||
endian.unchecked_put_u64le(out1[0:], tmp[0])
|
||||
endian.unchecked_put_u64le(out1[8:], tmp[1])
|
||||
endian.unchecked_put_u64le(out1[16:], tmp[2])
|
||||
endian.unchecked_put_u64le(out1[24:], tmp[3])
|
||||
|
||||
mem.zero_explicit(&tmp, size_of(tmp))
|
||||
}
|
||||
|
||||
fe_equal :: proc "contextless" (arg1, arg2: ^Montgomery_Domain_Field_Element) -> int {
|
||||
tmp: Montgomery_Domain_Field_Element
|
||||
fe_sub(&tmp, arg1, arg2)
|
||||
|
||||
// This will only underflow iff arg1 == arg2, and we return the borrow,
|
||||
// which will be 1.
|
||||
_, borrow := bits.sub_u64(fe_non_zero(&tmp), 1, 0)
|
||||
|
||||
fe_clear(&tmp)
|
||||
|
||||
return int(borrow)
|
||||
}
|
||||
|
||||
fe_zero :: proc "contextless" (out1: ^Montgomery_Domain_Field_Element) {
|
||||
out1[0] = 0
|
||||
out1[1] = 0
|
||||
out1[2] = 0
|
||||
out1[3] = 0
|
||||
}
|
||||
|
||||
fe_set :: proc "contextless" (out1, arg1: ^Montgomery_Domain_Field_Element) {
|
||||
x1 := arg1[0]
|
||||
x2 := arg1[1]
|
||||
x3 := arg1[2]
|
||||
x4 := arg1[3]
|
||||
out1[0] = x1
|
||||
out1[1] = x2
|
||||
out1[2] = x3
|
||||
out1[3] = x4
|
||||
}
|
||||
@@ -0,0 +1,535 @@
|
||||
// The BSD 1-Clause License (BSD-1-Clause)
|
||||
//
|
||||
// Copyright (c) 2015-2020 the fiat-crypto authors (see the AUTHORS file)
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY the fiat-crypto authors "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Berkeley Software Design,
|
||||
// Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package field_scalar25519
|
||||
|
||||
// The file provides arithmetic on the field Z/(2^252+27742317777372353535851937790883648493)
|
||||
// using a 64-bit Montgomery form internal representation. It is derived
|
||||
// primarily from the machine generated Golang output from the fiat-crypto
|
||||
// project.
|
||||
//
|
||||
// While the base implementation is provably correct, this implementation
|
||||
// makes no such claims as the port and optimizations were done by hand.
|
||||
|
||||
import fiat "core:crypto/_fiat"
|
||||
import "core:math/bits"
|
||||
|
||||
// ELL is the saturated representation of the field order, least-significant
|
||||
// limb first.
|
||||
ELL :: [4]u64{0x5812631a5cf5d3ed, 0x14def9dea2f79cd6, 0x0, 0x1000000000000000}
|
||||
|
||||
Montgomery_Domain_Field_Element :: distinct [4]u64
|
||||
Non_Montgomery_Domain_Field_Element :: distinct [4]u64
|
||||
|
||||
fe_mul :: proc "contextless" (out1, arg1, arg2: ^Montgomery_Domain_Field_Element) {
|
||||
x1 := arg1[1]
|
||||
x2 := arg1[2]
|
||||
x3 := arg1[3]
|
||||
x4 := arg1[0]
|
||||
x6, x5 := bits.mul_u64(x4, arg2[3])
|
||||
x8, x7 := bits.mul_u64(x4, arg2[2])
|
||||
x10, x9 := bits.mul_u64(x4, arg2[1])
|
||||
x12, x11 := bits.mul_u64(x4, arg2[0])
|
||||
x13, x14 := bits.add_u64(x12, x9, u64(0x0))
|
||||
x15, x16 := bits.add_u64(x10, x7, u64(fiat.u1(x14)))
|
||||
x17, x18 := bits.add_u64(x8, x5, u64(fiat.u1(x16)))
|
||||
x19 := (u64(fiat.u1(x18)) + x6)
|
||||
_, x20 := bits.mul_u64(x11, 0xd2b51da312547e1b)
|
||||
x23, x22 := bits.mul_u64(x20, 0x1000000000000000)
|
||||
x25, x24 := bits.mul_u64(x20, 0x14def9dea2f79cd6)
|
||||
x27, x26 := bits.mul_u64(x20, 0x5812631a5cf5d3ed)
|
||||
x28, x29 := bits.add_u64(x27, x24, u64(0x0))
|
||||
x30 := (u64(fiat.u1(x29)) + x25)
|
||||
_, x32 := bits.add_u64(x11, x26, u64(0x0))
|
||||
x33, x34 := bits.add_u64(x13, x28, u64(fiat.u1(x32)))
|
||||
x35, x36 := bits.add_u64(x15, x30, u64(fiat.u1(x34)))
|
||||
x37, x38 := bits.add_u64(x17, x22, u64(fiat.u1(x36)))
|
||||
x39, x40 := bits.add_u64(x19, x23, u64(fiat.u1(x38)))
|
||||
x42, x41 := bits.mul_u64(x1, arg2[3])
|
||||
x44, x43 := bits.mul_u64(x1, arg2[2])
|
||||
x46, x45 := bits.mul_u64(x1, arg2[1])
|
||||
x48, x47 := bits.mul_u64(x1, arg2[0])
|
||||
x49, x50 := bits.add_u64(x48, x45, u64(0x0))
|
||||
x51, x52 := bits.add_u64(x46, x43, u64(fiat.u1(x50)))
|
||||
x53, x54 := bits.add_u64(x44, x41, u64(fiat.u1(x52)))
|
||||
x55 := (u64(fiat.u1(x54)) + x42)
|
||||
x56, x57 := bits.add_u64(x33, x47, u64(0x0))
|
||||
x58, x59 := bits.add_u64(x35, x49, u64(fiat.u1(x57)))
|
||||
x60, x61 := bits.add_u64(x37, x51, u64(fiat.u1(x59)))
|
||||
x62, x63 := bits.add_u64(x39, x53, u64(fiat.u1(x61)))
|
||||
x64, x65 := bits.add_u64(u64(fiat.u1(x40)), x55, u64(fiat.u1(x63)))
|
||||
_, x66 := bits.mul_u64(x56, 0xd2b51da312547e1b)
|
||||
x69, x68 := bits.mul_u64(x66, 0x1000000000000000)
|
||||
x71, x70 := bits.mul_u64(x66, 0x14def9dea2f79cd6)
|
||||
x73, x72 := bits.mul_u64(x66, 0x5812631a5cf5d3ed)
|
||||
x74, x75 := bits.add_u64(x73, x70, u64(0x0))
|
||||
x76 := (u64(fiat.u1(x75)) + x71)
|
||||
_, x78 := bits.add_u64(x56, x72, u64(0x0))
|
||||
x79, x80 := bits.add_u64(x58, x74, u64(fiat.u1(x78)))
|
||||
x81, x82 := bits.add_u64(x60, x76, u64(fiat.u1(x80)))
|
||||
x83, x84 := bits.add_u64(x62, x68, u64(fiat.u1(x82)))
|
||||
x85, x86 := bits.add_u64(x64, x69, u64(fiat.u1(x84)))
|
||||
x87 := (u64(fiat.u1(x86)) + u64(fiat.u1(x65)))
|
||||
x89, x88 := bits.mul_u64(x2, arg2[3])
|
||||
x91, x90 := bits.mul_u64(x2, arg2[2])
|
||||
x93, x92 := bits.mul_u64(x2, arg2[1])
|
||||
x95, x94 := bits.mul_u64(x2, arg2[0])
|
||||
x96, x97 := bits.add_u64(x95, x92, u64(0x0))
|
||||
x98, x99 := bits.add_u64(x93, x90, u64(fiat.u1(x97)))
|
||||
x100, x101 := bits.add_u64(x91, x88, u64(fiat.u1(x99)))
|
||||
x102 := (u64(fiat.u1(x101)) + x89)
|
||||
x103, x104 := bits.add_u64(x79, x94, u64(0x0))
|
||||
x105, x106 := bits.add_u64(x81, x96, u64(fiat.u1(x104)))
|
||||
x107, x108 := bits.add_u64(x83, x98, u64(fiat.u1(x106)))
|
||||
x109, x110 := bits.add_u64(x85, x100, u64(fiat.u1(x108)))
|
||||
x111, x112 := bits.add_u64(x87, x102, u64(fiat.u1(x110)))
|
||||
_, x113 := bits.mul_u64(x103, 0xd2b51da312547e1b)
|
||||
x116, x115 := bits.mul_u64(x113, 0x1000000000000000)
|
||||
x118, x117 := bits.mul_u64(x113, 0x14def9dea2f79cd6)
|
||||
x120, x119 := bits.mul_u64(x113, 0x5812631a5cf5d3ed)
|
||||
x121, x122 := bits.add_u64(x120, x117, u64(0x0))
|
||||
x123 := (u64(fiat.u1(x122)) + x118)
|
||||
_, x125 := bits.add_u64(x103, x119, u64(0x0))
|
||||
x126, x127 := bits.add_u64(x105, x121, u64(fiat.u1(x125)))
|
||||
x128, x129 := bits.add_u64(x107, x123, u64(fiat.u1(x127)))
|
||||
x130, x131 := bits.add_u64(x109, x115, u64(fiat.u1(x129)))
|
||||
x132, x133 := bits.add_u64(x111, x116, u64(fiat.u1(x131)))
|
||||
x134 := (u64(fiat.u1(x133)) + u64(fiat.u1(x112)))
|
||||
x136, x135 := bits.mul_u64(x3, arg2[3])
|
||||
x138, x137 := bits.mul_u64(x3, arg2[2])
|
||||
x140, x139 := bits.mul_u64(x3, arg2[1])
|
||||
x142, x141 := bits.mul_u64(x3, arg2[0])
|
||||
x143, x144 := bits.add_u64(x142, x139, u64(0x0))
|
||||
x145, x146 := bits.add_u64(x140, x137, u64(fiat.u1(x144)))
|
||||
x147, x148 := bits.add_u64(x138, x135, u64(fiat.u1(x146)))
|
||||
x149 := (u64(fiat.u1(x148)) + x136)
|
||||
x150, x151 := bits.add_u64(x126, x141, u64(0x0))
|
||||
x152, x153 := bits.add_u64(x128, x143, u64(fiat.u1(x151)))
|
||||
x154, x155 := bits.add_u64(x130, x145, u64(fiat.u1(x153)))
|
||||
x156, x157 := bits.add_u64(x132, x147, u64(fiat.u1(x155)))
|
||||
x158, x159 := bits.add_u64(x134, x149, u64(fiat.u1(x157)))
|
||||
_, x160 := bits.mul_u64(x150, 0xd2b51da312547e1b)
|
||||
x163, x162 := bits.mul_u64(x160, 0x1000000000000000)
|
||||
x165, x164 := bits.mul_u64(x160, 0x14def9dea2f79cd6)
|
||||
x167, x166 := bits.mul_u64(x160, 0x5812631a5cf5d3ed)
|
||||
x168, x169 := bits.add_u64(x167, x164, u64(0x0))
|
||||
x170 := (u64(fiat.u1(x169)) + x165)
|
||||
_, x172 := bits.add_u64(x150, x166, u64(0x0))
|
||||
x173, x174 := bits.add_u64(x152, x168, u64(fiat.u1(x172)))
|
||||
x175, x176 := bits.add_u64(x154, x170, u64(fiat.u1(x174)))
|
||||
x177, x178 := bits.add_u64(x156, x162, u64(fiat.u1(x176)))
|
||||
x179, x180 := bits.add_u64(x158, x163, u64(fiat.u1(x178)))
|
||||
x181 := (u64(fiat.u1(x180)) + u64(fiat.u1(x159)))
|
||||
x182, x183 := bits.sub_u64(x173, 0x5812631a5cf5d3ed, u64(0x0))
|
||||
x184, x185 := bits.sub_u64(x175, 0x14def9dea2f79cd6, u64(fiat.u1(x183)))
|
||||
x186, x187 := bits.sub_u64(x177, u64(0x0), u64(fiat.u1(x185)))
|
||||
x188, x189 := bits.sub_u64(x179, 0x1000000000000000, u64(fiat.u1(x187)))
|
||||
_, x191 := bits.sub_u64(x181, u64(0x0), u64(fiat.u1(x189)))
|
||||
x192 := fiat.cmovznz_u64(fiat.u1(x191), x182, x173)
|
||||
x193 := fiat.cmovznz_u64(fiat.u1(x191), x184, x175)
|
||||
x194 := fiat.cmovznz_u64(fiat.u1(x191), x186, x177)
|
||||
x195 := fiat.cmovznz_u64(fiat.u1(x191), x188, x179)
|
||||
out1[0] = x192
|
||||
out1[1] = x193
|
||||
out1[2] = x194
|
||||
out1[3] = x195
|
||||
}
|
||||
|
||||
fe_square :: proc "contextless" (out1, arg1: ^Montgomery_Domain_Field_Element) {
|
||||
x1 := arg1[1]
|
||||
x2 := arg1[2]
|
||||
x3 := arg1[3]
|
||||
x4 := arg1[0]
|
||||
x6, x5 := bits.mul_u64(x4, arg1[3])
|
||||
x8, x7 := bits.mul_u64(x4, arg1[2])
|
||||
x10, x9 := bits.mul_u64(x4, arg1[1])
|
||||
x12, x11 := bits.mul_u64(x4, arg1[0])
|
||||
x13, x14 := bits.add_u64(x12, x9, u64(0x0))
|
||||
x15, x16 := bits.add_u64(x10, x7, u64(fiat.u1(x14)))
|
||||
x17, x18 := bits.add_u64(x8, x5, u64(fiat.u1(x16)))
|
||||
x19 := (u64(fiat.u1(x18)) + x6)
|
||||
_, x20 := bits.mul_u64(x11, 0xd2b51da312547e1b)
|
||||
x23, x22 := bits.mul_u64(x20, 0x1000000000000000)
|
||||
x25, x24 := bits.mul_u64(x20, 0x14def9dea2f79cd6)
|
||||
x27, x26 := bits.mul_u64(x20, 0x5812631a5cf5d3ed)
|
||||
x28, x29 := bits.add_u64(x27, x24, u64(0x0))
|
||||
x30 := (u64(fiat.u1(x29)) + x25)
|
||||
_, x32 := bits.add_u64(x11, x26, u64(0x0))
|
||||
x33, x34 := bits.add_u64(x13, x28, u64(fiat.u1(x32)))
|
||||
x35, x36 := bits.add_u64(x15, x30, u64(fiat.u1(x34)))
|
||||
x37, x38 := bits.add_u64(x17, x22, u64(fiat.u1(x36)))
|
||||
x39, x40 := bits.add_u64(x19, x23, u64(fiat.u1(x38)))
|
||||
x42, x41 := bits.mul_u64(x1, arg1[3])
|
||||
x44, x43 := bits.mul_u64(x1, arg1[2])
|
||||
x46, x45 := bits.mul_u64(x1, arg1[1])
|
||||
x48, x47 := bits.mul_u64(x1, arg1[0])
|
||||
x49, x50 := bits.add_u64(x48, x45, u64(0x0))
|
||||
x51, x52 := bits.add_u64(x46, x43, u64(fiat.u1(x50)))
|
||||
x53, x54 := bits.add_u64(x44, x41, u64(fiat.u1(x52)))
|
||||
x55 := (u64(fiat.u1(x54)) + x42)
|
||||
x56, x57 := bits.add_u64(x33, x47, u64(0x0))
|
||||
x58, x59 := bits.add_u64(x35, x49, u64(fiat.u1(x57)))
|
||||
x60, x61 := bits.add_u64(x37, x51, u64(fiat.u1(x59)))
|
||||
x62, x63 := bits.add_u64(x39, x53, u64(fiat.u1(x61)))
|
||||
x64, x65 := bits.add_u64(u64(fiat.u1(x40)), x55, u64(fiat.u1(x63)))
|
||||
_, x66 := bits.mul_u64(x56, 0xd2b51da312547e1b)
|
||||
x69, x68 := bits.mul_u64(x66, 0x1000000000000000)
|
||||
x71, x70 := bits.mul_u64(x66, 0x14def9dea2f79cd6)
|
||||
x73, x72 := bits.mul_u64(x66, 0x5812631a5cf5d3ed)
|
||||
x74, x75 := bits.add_u64(x73, x70, u64(0x0))
|
||||
x76 := (u64(fiat.u1(x75)) + x71)
|
||||
_, x78 := bits.add_u64(x56, x72, u64(0x0))
|
||||
x79, x80 := bits.add_u64(x58, x74, u64(fiat.u1(x78)))
|
||||
x81, x82 := bits.add_u64(x60, x76, u64(fiat.u1(x80)))
|
||||
x83, x84 := bits.add_u64(x62, x68, u64(fiat.u1(x82)))
|
||||
x85, x86 := bits.add_u64(x64, x69, u64(fiat.u1(x84)))
|
||||
x87 := (u64(fiat.u1(x86)) + u64(fiat.u1(x65)))
|
||||
x89, x88 := bits.mul_u64(x2, arg1[3])
|
||||
x91, x90 := bits.mul_u64(x2, arg1[2])
|
||||
x93, x92 := bits.mul_u64(x2, arg1[1])
|
||||
x95, x94 := bits.mul_u64(x2, arg1[0])
|
||||
x96, x97 := bits.add_u64(x95, x92, u64(0x0))
|
||||
x98, x99 := bits.add_u64(x93, x90, u64(fiat.u1(x97)))
|
||||
x100, x101 := bits.add_u64(x91, x88, u64(fiat.u1(x99)))
|
||||
x102 := (u64(fiat.u1(x101)) + x89)
|
||||
x103, x104 := bits.add_u64(x79, x94, u64(0x0))
|
||||
x105, x106 := bits.add_u64(x81, x96, u64(fiat.u1(x104)))
|
||||
x107, x108 := bits.add_u64(x83, x98, u64(fiat.u1(x106)))
|
||||
x109, x110 := bits.add_u64(x85, x100, u64(fiat.u1(x108)))
|
||||
x111, x112 := bits.add_u64(x87, x102, u64(fiat.u1(x110)))
|
||||
_, x113 := bits.mul_u64(x103, 0xd2b51da312547e1b)
|
||||
x116, x115 := bits.mul_u64(x113, 0x1000000000000000)
|
||||
x118, x117 := bits.mul_u64(x113, 0x14def9dea2f79cd6)
|
||||
x120, x119 := bits.mul_u64(x113, 0x5812631a5cf5d3ed)
|
||||
x121, x122 := bits.add_u64(x120, x117, u64(0x0))
|
||||
x123 := (u64(fiat.u1(x122)) + x118)
|
||||
_, x125 := bits.add_u64(x103, x119, u64(0x0))
|
||||
x126, x127 := bits.add_u64(x105, x121, u64(fiat.u1(x125)))
|
||||
x128, x129 := bits.add_u64(x107, x123, u64(fiat.u1(x127)))
|
||||
x130, x131 := bits.add_u64(x109, x115, u64(fiat.u1(x129)))
|
||||
x132, x133 := bits.add_u64(x111, x116, u64(fiat.u1(x131)))
|
||||
x134 := (u64(fiat.u1(x133)) + u64(fiat.u1(x112)))
|
||||
x136, x135 := bits.mul_u64(x3, arg1[3])
|
||||
x138, x137 := bits.mul_u64(x3, arg1[2])
|
||||
x140, x139 := bits.mul_u64(x3, arg1[1])
|
||||
x142, x141 := bits.mul_u64(x3, arg1[0])
|
||||
x143, x144 := bits.add_u64(x142, x139, u64(0x0))
|
||||
x145, x146 := bits.add_u64(x140, x137, u64(fiat.u1(x144)))
|
||||
x147, x148 := bits.add_u64(x138, x135, u64(fiat.u1(x146)))
|
||||
x149 := (u64(fiat.u1(x148)) + x136)
|
||||
x150, x151 := bits.add_u64(x126, x141, u64(0x0))
|
||||
x152, x153 := bits.add_u64(x128, x143, u64(fiat.u1(x151)))
|
||||
x154, x155 := bits.add_u64(x130, x145, u64(fiat.u1(x153)))
|
||||
x156, x157 := bits.add_u64(x132, x147, u64(fiat.u1(x155)))
|
||||
x158, x159 := bits.add_u64(x134, x149, u64(fiat.u1(x157)))
|
||||
_, x160 := bits.mul_u64(x150, 0xd2b51da312547e1b)
|
||||
x163, x162 := bits.mul_u64(x160, 0x1000000000000000)
|
||||
x165, x164 := bits.mul_u64(x160, 0x14def9dea2f79cd6)
|
||||
x167, x166 := bits.mul_u64(x160, 0x5812631a5cf5d3ed)
|
||||
x168, x169 := bits.add_u64(x167, x164, u64(0x0))
|
||||
x170 := (u64(fiat.u1(x169)) + x165)
|
||||
_, x172 := bits.add_u64(x150, x166, u64(0x0))
|
||||
x173, x174 := bits.add_u64(x152, x168, u64(fiat.u1(x172)))
|
||||
x175, x176 := bits.add_u64(x154, x170, u64(fiat.u1(x174)))
|
||||
x177, x178 := bits.add_u64(x156, x162, u64(fiat.u1(x176)))
|
||||
x179, x180 := bits.add_u64(x158, x163, u64(fiat.u1(x178)))
|
||||
x181 := (u64(fiat.u1(x180)) + u64(fiat.u1(x159)))
|
||||
x182, x183 := bits.sub_u64(x173, 0x5812631a5cf5d3ed, u64(0x0))
|
||||
x184, x185 := bits.sub_u64(x175, 0x14def9dea2f79cd6, u64(fiat.u1(x183)))
|
||||
x186, x187 := bits.sub_u64(x177, u64(0x0), u64(fiat.u1(x185)))
|
||||
x188, x189 := bits.sub_u64(x179, 0x1000000000000000, u64(fiat.u1(x187)))
|
||||
_, x191 := bits.sub_u64(x181, u64(0x0), u64(fiat.u1(x189)))
|
||||
x192 := fiat.cmovznz_u64(fiat.u1(x191), x182, x173)
|
||||
x193 := fiat.cmovznz_u64(fiat.u1(x191), x184, x175)
|
||||
x194 := fiat.cmovznz_u64(fiat.u1(x191), x186, x177)
|
||||
x195 := fiat.cmovznz_u64(fiat.u1(x191), x188, x179)
|
||||
out1[0] = x192
|
||||
out1[1] = x193
|
||||
out1[2] = x194
|
||||
out1[3] = x195
|
||||
}
|
||||
|
||||
fe_add :: proc "contextless" (out1, arg1, arg2: ^Montgomery_Domain_Field_Element) {
|
||||
x1, x2 := bits.add_u64(arg1[0], arg2[0], u64(0x0))
|
||||
x3, x4 := bits.add_u64(arg1[1], arg2[1], u64(fiat.u1(x2)))
|
||||
x5, x6 := bits.add_u64(arg1[2], arg2[2], u64(fiat.u1(x4)))
|
||||
x7, x8 := bits.add_u64(arg1[3], arg2[3], u64(fiat.u1(x6)))
|
||||
x9, x10 := bits.sub_u64(x1, 0x5812631a5cf5d3ed, u64(0x0))
|
||||
x11, x12 := bits.sub_u64(x3, 0x14def9dea2f79cd6, u64(fiat.u1(x10)))
|
||||
x13, x14 := bits.sub_u64(x5, u64(0x0), u64(fiat.u1(x12)))
|
||||
x15, x16 := bits.sub_u64(x7, 0x1000000000000000, u64(fiat.u1(x14)))
|
||||
_, x18 := bits.sub_u64(u64(fiat.u1(x8)), u64(0x0), u64(fiat.u1(x16)))
|
||||
x19 := fiat.cmovznz_u64(fiat.u1(x18), x9, x1)
|
||||
x20 := fiat.cmovznz_u64(fiat.u1(x18), x11, x3)
|
||||
x21 := fiat.cmovznz_u64(fiat.u1(x18), x13, x5)
|
||||
x22 := fiat.cmovznz_u64(fiat.u1(x18), x15, x7)
|
||||
out1[0] = x19
|
||||
out1[1] = x20
|
||||
out1[2] = x21
|
||||
out1[3] = x22
|
||||
}
|
||||
|
||||
fe_sub :: proc "contextless" (out1, arg1, arg2: ^Montgomery_Domain_Field_Element) {
|
||||
x1, x2 := bits.sub_u64(arg1[0], arg2[0], u64(0x0))
|
||||
x3, x4 := bits.sub_u64(arg1[1], arg2[1], u64(fiat.u1(x2)))
|
||||
x5, x6 := bits.sub_u64(arg1[2], arg2[2], u64(fiat.u1(x4)))
|
||||
x7, x8 := bits.sub_u64(arg1[3], arg2[3], u64(fiat.u1(x6)))
|
||||
x9 := fiat.cmovznz_u64(fiat.u1(x8), u64(0x0), 0xffffffffffffffff)
|
||||
x10, x11 := bits.add_u64(x1, (x9 & 0x5812631a5cf5d3ed), u64(0x0))
|
||||
x12, x13 := bits.add_u64(x3, (x9 & 0x14def9dea2f79cd6), u64(fiat.u1(x11)))
|
||||
x14, x15 := bits.add_u64(x5, u64(0x0), u64(fiat.u1(x13)))
|
||||
x16, _ := bits.add_u64(x7, (x9 & 0x1000000000000000), u64(fiat.u1(x15)))
|
||||
out1[0] = x10
|
||||
out1[1] = x12
|
||||
out1[2] = x14
|
||||
out1[3] = x16
|
||||
}
|
||||
|
||||
fe_opp :: proc "contextless" (out1, arg1: ^Montgomery_Domain_Field_Element) {
|
||||
x1, x2 := bits.sub_u64(u64(0x0), arg1[0], u64(0x0))
|
||||
x3, x4 := bits.sub_u64(u64(0x0), arg1[1], u64(fiat.u1(x2)))
|
||||
x5, x6 := bits.sub_u64(u64(0x0), arg1[2], u64(fiat.u1(x4)))
|
||||
x7, x8 := bits.sub_u64(u64(0x0), arg1[3], u64(fiat.u1(x6)))
|
||||
x9 := fiat.cmovznz_u64(fiat.u1(x8), u64(0x0), 0xffffffffffffffff)
|
||||
x10, x11 := bits.add_u64(x1, (x9 & 0x5812631a5cf5d3ed), u64(0x0))
|
||||
x12, x13 := bits.add_u64(x3, (x9 & 0x14def9dea2f79cd6), u64(fiat.u1(x11)))
|
||||
x14, x15 := bits.add_u64(x5, u64(0x0), u64(fiat.u1(x13)))
|
||||
x16, _ := bits.add_u64(x7, (x9 & 0x1000000000000000), u64(fiat.u1(x15)))
|
||||
out1[0] = x10
|
||||
out1[1] = x12
|
||||
out1[2] = x14
|
||||
out1[3] = x16
|
||||
}
|
||||
|
||||
fe_one :: proc "contextless" (out1: ^Montgomery_Domain_Field_Element) {
|
||||
out1[0] = 0xd6ec31748d98951d
|
||||
out1[1] = 0xc6ef5bf4737dcf70
|
||||
out1[2] = 0xfffffffffffffffe
|
||||
out1[3] = 0xfffffffffffffff
|
||||
}
|
||||
|
||||
fe_non_zero :: proc "contextless" (arg1: ^Montgomery_Domain_Field_Element) -> u64 {
|
||||
return arg1[0] | (arg1[1] | (arg1[2] | arg1[3]))
|
||||
}
|
||||
|
||||
@(optimization_mode = "none")
|
||||
fe_cond_assign :: #force_no_inline proc "contextless" (
|
||||
out1, arg1: ^Montgomery_Domain_Field_Element,
|
||||
arg2: int,
|
||||
) {
|
||||
x1 := fiat.cmovznz_u64(fiat.u1(arg2), out1[0], arg1[0])
|
||||
x2 := fiat.cmovznz_u64(fiat.u1(arg2), out1[1], arg1[1])
|
||||
x3 := fiat.cmovznz_u64(fiat.u1(arg2), out1[2], arg1[2])
|
||||
x4 := fiat.cmovznz_u64(fiat.u1(arg2), out1[3], arg1[3])
|
||||
out1[0] = x1
|
||||
out1[1] = x2
|
||||
out1[2] = x3
|
||||
out1[3] = x4
|
||||
}
|
||||
|
||||
fe_from_montgomery :: proc "contextless" (
|
||||
out1: ^Non_Montgomery_Domain_Field_Element,
|
||||
arg1: ^Montgomery_Domain_Field_Element,
|
||||
) {
|
||||
x1 := arg1[0]
|
||||
_, x2 := bits.mul_u64(x1, 0xd2b51da312547e1b)
|
||||
x5, x4 := bits.mul_u64(x2, 0x1000000000000000)
|
||||
x7, x6 := bits.mul_u64(x2, 0x14def9dea2f79cd6)
|
||||
x9, x8 := bits.mul_u64(x2, 0x5812631a5cf5d3ed)
|
||||
x10, x11 := bits.add_u64(x9, x6, u64(0x0))
|
||||
_, x13 := bits.add_u64(x1, x8, u64(0x0))
|
||||
x14, x15 := bits.add_u64(u64(0x0), x10, u64(fiat.u1(x13)))
|
||||
x16, x17 := bits.add_u64(x14, arg1[1], u64(0x0))
|
||||
_, x18 := bits.mul_u64(x16, 0xd2b51da312547e1b)
|
||||
x21, x20 := bits.mul_u64(x18, 0x1000000000000000)
|
||||
x23, x22 := bits.mul_u64(x18, 0x14def9dea2f79cd6)
|
||||
x25, x24 := bits.mul_u64(x18, 0x5812631a5cf5d3ed)
|
||||
x26, x27 := bits.add_u64(x25, x22, u64(0x0))
|
||||
_, x29 := bits.add_u64(x16, x24, u64(0x0))
|
||||
x30, x31 := bits.add_u64(
|
||||
(u64(fiat.u1(x17)) + (u64(fiat.u1(x15)) + (u64(fiat.u1(x11)) + x7))),
|
||||
x26,
|
||||
u64(fiat.u1(x29)),
|
||||
)
|
||||
x32, x33 := bits.add_u64(x4, (u64(fiat.u1(x27)) + x23), u64(fiat.u1(x31)))
|
||||
x34, x35 := bits.add_u64(x5, x20, u64(fiat.u1(x33)))
|
||||
x36, x37 := bits.add_u64(x30, arg1[2], u64(0x0))
|
||||
x38, x39 := bits.add_u64(x32, u64(0x0), u64(fiat.u1(x37)))
|
||||
x40, x41 := bits.add_u64(x34, u64(0x0), u64(fiat.u1(x39)))
|
||||
_, x42 := bits.mul_u64(x36, 0xd2b51da312547e1b)
|
||||
x45, x44 := bits.mul_u64(x42, 0x1000000000000000)
|
||||
x47, x46 := bits.mul_u64(x42, 0x14def9dea2f79cd6)
|
||||
x49, x48 := bits.mul_u64(x42, 0x5812631a5cf5d3ed)
|
||||
x50, x51 := bits.add_u64(x49, x46, u64(0x0))
|
||||
_, x53 := bits.add_u64(x36, x48, u64(0x0))
|
||||
x54, x55 := bits.add_u64(x38, x50, u64(fiat.u1(x53)))
|
||||
x56, x57 := bits.add_u64(x40, (u64(fiat.u1(x51)) + x47), u64(fiat.u1(x55)))
|
||||
x58, x59 := bits.add_u64(
|
||||
(u64(fiat.u1(x41)) + (u64(fiat.u1(x35)) + x21)),
|
||||
x44,
|
||||
u64(fiat.u1(x57)),
|
||||
)
|
||||
x60, x61 := bits.add_u64(x54, arg1[3], u64(0x0))
|
||||
x62, x63 := bits.add_u64(x56, u64(0x0), u64(fiat.u1(x61)))
|
||||
x64, x65 := bits.add_u64(x58, u64(0x0), u64(fiat.u1(x63)))
|
||||
_, x66 := bits.mul_u64(x60, 0xd2b51da312547e1b)
|
||||
x69, x68 := bits.mul_u64(x66, 0x1000000000000000)
|
||||
x71, x70 := bits.mul_u64(x66, 0x14def9dea2f79cd6)
|
||||
x73, x72 := bits.mul_u64(x66, 0x5812631a5cf5d3ed)
|
||||
x74, x75 := bits.add_u64(x73, x70, u64(0x0))
|
||||
_, x77 := bits.add_u64(x60, x72, u64(0x0))
|
||||
x78, x79 := bits.add_u64(x62, x74, u64(fiat.u1(x77)))
|
||||
x80, x81 := bits.add_u64(x64, (u64(fiat.u1(x75)) + x71), u64(fiat.u1(x79)))
|
||||
x82, x83 := bits.add_u64(
|
||||
(u64(fiat.u1(x65)) + (u64(fiat.u1(x59)) + x45)),
|
||||
x68,
|
||||
u64(fiat.u1(x81)),
|
||||
)
|
||||
x84 := (u64(fiat.u1(x83)) + x69)
|
||||
x85, x86 := bits.sub_u64(x78, 0x5812631a5cf5d3ed, u64(0x0))
|
||||
x87, x88 := bits.sub_u64(x80, 0x14def9dea2f79cd6, u64(fiat.u1(x86)))
|
||||
x89, x90 := bits.sub_u64(x82, u64(0x0), u64(fiat.u1(x88)))
|
||||
x91, x92 := bits.sub_u64(x84, 0x1000000000000000, u64(fiat.u1(x90)))
|
||||
_, x94 := bits.sub_u64(u64(0x0), u64(0x0), u64(fiat.u1(x92)))
|
||||
x95 := fiat.cmovznz_u64(fiat.u1(x94), x85, x78)
|
||||
x96 := fiat.cmovznz_u64(fiat.u1(x94), x87, x80)
|
||||
x97 := fiat.cmovznz_u64(fiat.u1(x94), x89, x82)
|
||||
x98 := fiat.cmovznz_u64(fiat.u1(x94), x91, x84)
|
||||
out1[0] = x95
|
||||
out1[1] = x96
|
||||
out1[2] = x97
|
||||
out1[3] = x98
|
||||
}
|
||||
|
||||
fe_to_montgomery :: proc "contextless" (
|
||||
out1: ^Montgomery_Domain_Field_Element,
|
||||
arg1: ^Non_Montgomery_Domain_Field_Element,
|
||||
) {
|
||||
x1 := arg1[1]
|
||||
x2 := arg1[2]
|
||||
x3 := arg1[3]
|
||||
x4 := arg1[0]
|
||||
x6, x5 := bits.mul_u64(x4, 0x399411b7c309a3d)
|
||||
x8, x7 := bits.mul_u64(x4, 0xceec73d217f5be65)
|
||||
x10, x9 := bits.mul_u64(x4, 0xd00e1ba768859347)
|
||||
x12, x11 := bits.mul_u64(x4, 0xa40611e3449c0f01)
|
||||
x13, x14 := bits.add_u64(x12, x9, u64(0x0))
|
||||
x15, x16 := bits.add_u64(x10, x7, u64(fiat.u1(x14)))
|
||||
x17, x18 := bits.add_u64(x8, x5, u64(fiat.u1(x16)))
|
||||
_, x19 := bits.mul_u64(x11, 0xd2b51da312547e1b)
|
||||
x22, x21 := bits.mul_u64(x19, 0x1000000000000000)
|
||||
x24, x23 := bits.mul_u64(x19, 0x14def9dea2f79cd6)
|
||||
x26, x25 := bits.mul_u64(x19, 0x5812631a5cf5d3ed)
|
||||
x27, x28 := bits.add_u64(x26, x23, u64(0x0))
|
||||
_, x30 := bits.add_u64(x11, x25, u64(0x0))
|
||||
x31, x32 := bits.add_u64(x13, x27, u64(fiat.u1(x30)))
|
||||
x33, x34 := bits.add_u64(x15, (u64(fiat.u1(x28)) + x24), u64(fiat.u1(x32)))
|
||||
x35, x36 := bits.add_u64(x17, x21, u64(fiat.u1(x34)))
|
||||
x38, x37 := bits.mul_u64(x1, 0x399411b7c309a3d)
|
||||
x40, x39 := bits.mul_u64(x1, 0xceec73d217f5be65)
|
||||
x42, x41 := bits.mul_u64(x1, 0xd00e1ba768859347)
|
||||
x44, x43 := bits.mul_u64(x1, 0xa40611e3449c0f01)
|
||||
x45, x46 := bits.add_u64(x44, x41, u64(0x0))
|
||||
x47, x48 := bits.add_u64(x42, x39, u64(fiat.u1(x46)))
|
||||
x49, x50 := bits.add_u64(x40, x37, u64(fiat.u1(x48)))
|
||||
x51, x52 := bits.add_u64(x31, x43, u64(0x0))
|
||||
x53, x54 := bits.add_u64(x33, x45, u64(fiat.u1(x52)))
|
||||
x55, x56 := bits.add_u64(x35, x47, u64(fiat.u1(x54)))
|
||||
x57, x58 := bits.add_u64(
|
||||
((u64(fiat.u1(x36)) + (u64(fiat.u1(x18)) + x6)) + x22),
|
||||
x49,
|
||||
u64(fiat.u1(x56)),
|
||||
)
|
||||
_, x59 := bits.mul_u64(x51, 0xd2b51da312547e1b)
|
||||
x62, x61 := bits.mul_u64(x59, 0x1000000000000000)
|
||||
x64, x63 := bits.mul_u64(x59, 0x14def9dea2f79cd6)
|
||||
x66, x65 := bits.mul_u64(x59, 0x5812631a5cf5d3ed)
|
||||
x67, x68 := bits.add_u64(x66, x63, u64(0x0))
|
||||
_, x70 := bits.add_u64(x51, x65, u64(0x0))
|
||||
x71, x72 := bits.add_u64(x53, x67, u64(fiat.u1(x70)))
|
||||
x73, x74 := bits.add_u64(x55, (u64(fiat.u1(x68)) + x64), u64(fiat.u1(x72)))
|
||||
x75, x76 := bits.add_u64(x57, x61, u64(fiat.u1(x74)))
|
||||
x78, x77 := bits.mul_u64(x2, 0x399411b7c309a3d)
|
||||
x80, x79 := bits.mul_u64(x2, 0xceec73d217f5be65)
|
||||
x82, x81 := bits.mul_u64(x2, 0xd00e1ba768859347)
|
||||
x84, x83 := bits.mul_u64(x2, 0xa40611e3449c0f01)
|
||||
x85, x86 := bits.add_u64(x84, x81, u64(0x0))
|
||||
x87, x88 := bits.add_u64(x82, x79, u64(fiat.u1(x86)))
|
||||
x89, x90 := bits.add_u64(x80, x77, u64(fiat.u1(x88)))
|
||||
x91, x92 := bits.add_u64(x71, x83, u64(0x0))
|
||||
x93, x94 := bits.add_u64(x73, x85, u64(fiat.u1(x92)))
|
||||
x95, x96 := bits.add_u64(x75, x87, u64(fiat.u1(x94)))
|
||||
x97, x98 := bits.add_u64(
|
||||
((u64(fiat.u1(x76)) + (u64(fiat.u1(x58)) + (u64(fiat.u1(x50)) + x38))) + x62),
|
||||
x89,
|
||||
u64(fiat.u1(x96)),
|
||||
)
|
||||
_, x99 := bits.mul_u64(x91, 0xd2b51da312547e1b)
|
||||
x102, x101 := bits.mul_u64(x99, 0x1000000000000000)
|
||||
x104, x103 := bits.mul_u64(x99, 0x14def9dea2f79cd6)
|
||||
x106, x105 := bits.mul_u64(x99, 0x5812631a5cf5d3ed)
|
||||
x107, x108 := bits.add_u64(x106, x103, u64(0x0))
|
||||
_, x110 := bits.add_u64(x91, x105, u64(0x0))
|
||||
x111, x112 := bits.add_u64(x93, x107, u64(fiat.u1(x110)))
|
||||
x113, x114 := bits.add_u64(x95, (u64(fiat.u1(x108)) + x104), u64(fiat.u1(x112)))
|
||||
x115, x116 := bits.add_u64(x97, x101, u64(fiat.u1(x114)))
|
||||
x118, x117 := bits.mul_u64(x3, 0x399411b7c309a3d)
|
||||
x120, x119 := bits.mul_u64(x3, 0xceec73d217f5be65)
|
||||
x122, x121 := bits.mul_u64(x3, 0xd00e1ba768859347)
|
||||
x124, x123 := bits.mul_u64(x3, 0xa40611e3449c0f01)
|
||||
x125, x126 := bits.add_u64(x124, x121, u64(0x0))
|
||||
x127, x128 := bits.add_u64(x122, x119, u64(fiat.u1(x126)))
|
||||
x129, x130 := bits.add_u64(x120, x117, u64(fiat.u1(x128)))
|
||||
x131, x132 := bits.add_u64(x111, x123, u64(0x0))
|
||||
x133, x134 := bits.add_u64(x113, x125, u64(fiat.u1(x132)))
|
||||
x135, x136 := bits.add_u64(x115, x127, u64(fiat.u1(x134)))
|
||||
x137, x138 := bits.add_u64(
|
||||
((u64(fiat.u1(x116)) + (u64(fiat.u1(x98)) + (u64(fiat.u1(x90)) + x78))) + x102),
|
||||
x129,
|
||||
u64(fiat.u1(x136)),
|
||||
)
|
||||
_, x139 := bits.mul_u64(x131, 0xd2b51da312547e1b)
|
||||
x142, x141 := bits.mul_u64(x139, 0x1000000000000000)
|
||||
x144, x143 := bits.mul_u64(x139, 0x14def9dea2f79cd6)
|
||||
x146, x145 := bits.mul_u64(x139, 0x5812631a5cf5d3ed)
|
||||
x147, x148 := bits.add_u64(x146, x143, u64(0x0))
|
||||
_, x150 := bits.add_u64(x131, x145, u64(0x0))
|
||||
x151, x152 := bits.add_u64(x133, x147, u64(fiat.u1(x150)))
|
||||
x153, x154 := bits.add_u64(x135, (u64(fiat.u1(x148)) + x144), u64(fiat.u1(x152)))
|
||||
x155, x156 := bits.add_u64(x137, x141, u64(fiat.u1(x154)))
|
||||
x157 := ((u64(fiat.u1(x156)) + (u64(fiat.u1(x138)) + (u64(fiat.u1(x130)) + x118))) + x142)
|
||||
x158, x159 := bits.sub_u64(x151, 0x5812631a5cf5d3ed, u64(0x0))
|
||||
x160, x161 := bits.sub_u64(x153, 0x14def9dea2f79cd6, u64(fiat.u1(x159)))
|
||||
x162, x163 := bits.sub_u64(x155, u64(0x0), u64(fiat.u1(x161)))
|
||||
x164, x165 := bits.sub_u64(x157, 0x1000000000000000, u64(fiat.u1(x163)))
|
||||
_, x167 := bits.sub_u64(u64(0x0), u64(0x0), u64(fiat.u1(x165)))
|
||||
x168 := fiat.cmovznz_u64(fiat.u1(x167), x158, x151)
|
||||
x169 := fiat.cmovznz_u64(fiat.u1(x167), x160, x153)
|
||||
x170 := fiat.cmovznz_u64(fiat.u1(x167), x162, x155)
|
||||
x171 := fiat.cmovznz_u64(fiat.u1(x167), x164, x157)
|
||||
out1[0] = x168
|
||||
out1[1] = x169
|
||||
out1[2] = x170
|
||||
out1[3] = x171
|
||||
}
|
||||
+109
-78
@@ -7,50 +7,69 @@ package _sha3
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
Implementation of the Keccak hashing algorithm, standardized as SHA3 in <https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf>
|
||||
To use the original Keccak padding, set the is_keccak bool to true, otherwise it will use SHA3 padding.
|
||||
Implementation of the Keccak hashing algorithm, standardized as SHA3
|
||||
in <https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf>.
|
||||
|
||||
As the only difference between the legacy Keccak and SHA3 is the domain
|
||||
separation byte, set dsbyte to the appropriate value to pick the desired
|
||||
algorithm.
|
||||
*/
|
||||
|
||||
import "core:math/bits"
|
||||
import "core:mem"
|
||||
|
||||
ROUNDS :: 24
|
||||
|
||||
Sha3_Context :: struct {
|
||||
st: struct #raw_union {
|
||||
RATE_128 :: 1344 / 8 // ONLY for SHAKE128.
|
||||
RATE_224 :: 1152 / 8
|
||||
RATE_256 :: 1088 / 8
|
||||
RATE_384 :: 832 / 8
|
||||
RATE_512 :: 576 / 8
|
||||
|
||||
DS_KECCAK :: 0x01
|
||||
DS_SHA3 :: 0x06
|
||||
DS_SHAKE :: 0x1f
|
||||
DS_CSHAKE :: 0x04
|
||||
|
||||
Context :: struct {
|
||||
st: struct #raw_union {
|
||||
b: [200]u8,
|
||||
q: [25]u64,
|
||||
},
|
||||
pt: int,
|
||||
rsiz: int,
|
||||
mdlen: int,
|
||||
is_keccak: bool,
|
||||
|
||||
pt: int,
|
||||
rsiz: int,
|
||||
mdlen: int,
|
||||
dsbyte: byte,
|
||||
is_initialized: bool,
|
||||
is_finalized: bool, // For SHAKE (unlimited squeeze is allowed)
|
||||
}
|
||||
|
||||
@(private)
|
||||
keccakf_rndc := [?]u64 {
|
||||
0x0000000000000001, 0x0000000000008082, 0x800000000000808a,
|
||||
0x8000000080008000, 0x000000000000808b, 0x0000000080000001,
|
||||
0x8000000080008081, 0x8000000000008009, 0x000000000000008a,
|
||||
0x0000000000000088, 0x0000000080008009, 0x000000008000000a,
|
||||
0x000000008000808b, 0x800000000000008b, 0x8000000000008089,
|
||||
0x8000000000008003, 0x8000000000008002, 0x8000000000000080,
|
||||
0x000000000000800a, 0x800000008000000a, 0x8000000080008081,
|
||||
0x8000000000008080, 0x0000000080000001, 0x8000000080008008,
|
||||
}
|
||||
|
||||
@(private)
|
||||
keccakf_rotc := [?]int {
|
||||
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14,
|
||||
27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44,
|
||||
}
|
||||
|
||||
@(private)
|
||||
keccakf_piln := [?]i32 {
|
||||
10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4,
|
||||
15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1,
|
||||
}
|
||||
|
||||
@(private)
|
||||
keccakf :: proc "contextless" (st: ^[25]u64) {
|
||||
keccakf_rndc := [?]u64 {
|
||||
0x0000000000000001, 0x0000000000008082, 0x800000000000808a,
|
||||
0x8000000080008000, 0x000000000000808b, 0x0000000080000001,
|
||||
0x8000000080008081, 0x8000000000008009, 0x000000000000008a,
|
||||
0x0000000000000088, 0x0000000080008009, 0x000000008000000a,
|
||||
0x000000008000808b, 0x800000000000008b, 0x8000000000008089,
|
||||
0x8000000000008003, 0x8000000000008002, 0x8000000000000080,
|
||||
0x000000000000800a, 0x800000008000000a, 0x8000000080008081,
|
||||
0x8000000000008080, 0x0000000080000001, 0x8000000080008008,
|
||||
}
|
||||
|
||||
keccakf_rotc := [?]int {
|
||||
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14,
|
||||
27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44,
|
||||
}
|
||||
|
||||
keccakf_piln := [?]i32 {
|
||||
10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4,
|
||||
15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1,
|
||||
}
|
||||
|
||||
i, j, r: i32 = ---, ---, ---
|
||||
t: u64 = ---
|
||||
bc: [5]u64 = ---
|
||||
@@ -103,81 +122,93 @@ keccakf :: proc "contextless" (st: ^[25]u64) {
|
||||
}
|
||||
}
|
||||
|
||||
init :: proc(c: ^Sha3_Context) {
|
||||
init :: proc(ctx: ^Context) {
|
||||
for i := 0; i < 25; i += 1 {
|
||||
c.st.q[i] = 0
|
||||
ctx.st.q[i] = 0
|
||||
}
|
||||
c.rsiz = 200 - 2 * c.mdlen
|
||||
c.pt = 0
|
||||
ctx.rsiz = 200 - 2 * ctx.mdlen
|
||||
ctx.pt = 0
|
||||
|
||||
c.is_initialized = true
|
||||
c.is_finalized = false
|
||||
ctx.is_initialized = true
|
||||
ctx.is_finalized = false
|
||||
}
|
||||
|
||||
update :: proc(c: ^Sha3_Context, data: []byte) {
|
||||
assert(c.is_initialized)
|
||||
assert(!c.is_finalized)
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
assert(ctx.is_initialized)
|
||||
assert(!ctx.is_finalized)
|
||||
|
||||
j := c.pt
|
||||
j := ctx.pt
|
||||
for i := 0; i < len(data); i += 1 {
|
||||
c.st.b[j] ~= data[i]
|
||||
ctx.st.b[j] ~= data[i]
|
||||
j += 1
|
||||
if j >= c.rsiz {
|
||||
keccakf(&c.st.q)
|
||||
if j >= ctx.rsiz {
|
||||
keccakf(&ctx.st.q)
|
||||
j = 0
|
||||
}
|
||||
}
|
||||
c.pt = j
|
||||
ctx.pt = j
|
||||
}
|
||||
|
||||
final :: proc(c: ^Sha3_Context, hash: []byte) {
|
||||
assert(c.is_initialized)
|
||||
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
assert(ctx.is_initialized)
|
||||
|
||||
if len(hash) < c.mdlen {
|
||||
if c.is_keccak {
|
||||
panic("crypto/keccac: invalid destination digest size")
|
||||
}
|
||||
if len(hash) < ctx.mdlen {
|
||||
panic("crypto/sha3: invalid destination digest size")
|
||||
}
|
||||
if c.is_keccak {
|
||||
c.st.b[c.pt] ~= 0x01
|
||||
} else {
|
||||
c.st.b[c.pt] ~= 0x06
|
||||
}
|
||||
|
||||
c.st.b[c.rsiz - 1] ~= 0x80
|
||||
keccakf(&c.st.q)
|
||||
for i := 0; i < c.mdlen; i += 1 {
|
||||
hash[i] = c.st.b[i]
|
||||
ctx := ctx
|
||||
if finalize_clone {
|
||||
tmp_ctx: Context
|
||||
clone(&tmp_ctx, ctx)
|
||||
ctx = &tmp_ctx
|
||||
}
|
||||
defer (reset(ctx))
|
||||
|
||||
c.is_initialized = false // No more absorb, no more squeeze.
|
||||
ctx.st.b[ctx.pt] ~= ctx.dsbyte
|
||||
|
||||
ctx.st.b[ctx.rsiz - 1] ~= 0x80
|
||||
keccakf(&ctx.st.q)
|
||||
for i := 0; i < ctx.mdlen; i += 1 {
|
||||
hash[i] = ctx.st.b[i]
|
||||
}
|
||||
}
|
||||
|
||||
shake_xof :: proc(c: ^Sha3_Context) {
|
||||
assert(c.is_initialized)
|
||||
assert(!c.is_finalized)
|
||||
|
||||
c.st.b[c.pt] ~= 0x1F
|
||||
c.st.b[c.rsiz - 1] ~= 0x80
|
||||
keccakf(&c.st.q)
|
||||
c.pt = 0
|
||||
|
||||
c.is_finalized = true // No more absorb, unlimited squeeze.
|
||||
clone :: proc(ctx, other: ^Context) {
|
||||
ctx^ = other^
|
||||
}
|
||||
|
||||
shake_out :: proc(c: ^Sha3_Context, hash: []byte) {
|
||||
assert(c.is_initialized)
|
||||
assert(c.is_finalized)
|
||||
reset :: proc(ctx: ^Context) {
|
||||
if !ctx.is_initialized {
|
||||
return
|
||||
}
|
||||
|
||||
j := c.pt
|
||||
mem.zero_explicit(ctx, size_of(ctx^))
|
||||
}
|
||||
|
||||
shake_xof :: proc(ctx: ^Context) {
|
||||
assert(ctx.is_initialized)
|
||||
assert(!ctx.is_finalized)
|
||||
|
||||
ctx.st.b[ctx.pt] ~= ctx.dsbyte
|
||||
ctx.st.b[ctx.rsiz - 1] ~= 0x80
|
||||
keccakf(&ctx.st.q)
|
||||
ctx.pt = 0
|
||||
|
||||
ctx.is_finalized = true // No more absorb, unlimited squeeze.
|
||||
}
|
||||
|
||||
shake_out :: proc(ctx: ^Context, hash: []byte) {
|
||||
assert(ctx.is_initialized)
|
||||
assert(ctx.is_finalized)
|
||||
|
||||
j := ctx.pt
|
||||
for i := 0; i < len(hash); i += 1 {
|
||||
if j >= c.rsiz {
|
||||
keccakf(&c.st.q)
|
||||
if j >= ctx.rsiz {
|
||||
keccakf(&ctx.st.q)
|
||||
j = 0
|
||||
}
|
||||
hash[i] = c.st.b[j]
|
||||
hash[i] = ctx.st.b[j]
|
||||
j += 1
|
||||
}
|
||||
c.pt = j
|
||||
ctx.pt = j
|
||||
}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package _sha3
|
||||
|
||||
import "core:encoding/endian"
|
||||
import "core:math/bits"
|
||||
|
||||
init_cshake :: proc(ctx: ^Context, n, s: []byte, sec_strength: int) {
|
||||
ctx.mdlen = sec_strength / 8
|
||||
|
||||
// No domain separator is equivalent to vanilla SHAKE.
|
||||
if len(n) == 0 && len(s) == 0 {
|
||||
ctx.dsbyte = DS_SHAKE
|
||||
init(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.dsbyte = DS_CSHAKE
|
||||
init(ctx)
|
||||
bytepad(ctx, [][]byte{n, s}, rate_cshake(sec_strength))
|
||||
}
|
||||
|
||||
final_cshake :: proc(ctx: ^Context, dst: []byte, finalize_clone: bool = false) {
|
||||
ctx := ctx
|
||||
if finalize_clone {
|
||||
tmp_ctx: Context
|
||||
clone(&tmp_ctx, ctx)
|
||||
ctx = &tmp_ctx
|
||||
}
|
||||
defer reset(ctx)
|
||||
|
||||
encode_byte_len(ctx, len(dst), false) // right_encode
|
||||
shake_xof(ctx)
|
||||
shake_out(ctx, dst)
|
||||
}
|
||||
|
||||
rate_cshake :: #force_inline proc(sec_strength: int) -> int {
|
||||
switch sec_strength {
|
||||
case 128:
|
||||
return RATE_128
|
||||
case 256:
|
||||
return RATE_256
|
||||
}
|
||||
|
||||
panic("crypto/sha3: invalid security strength")
|
||||
}
|
||||
|
||||
// right_encode and left_encode are defined to support 0 <= x < 2^2040
|
||||
// however, the largest value we will ever need to encode is `max(int) * 8`.
|
||||
//
|
||||
// This is unfortunate as the extreme upper edge is larger than
|
||||
// `max(u64)`. While such values are impractical at present,
|
||||
// they are possible (ie: https://arxiv.org/pdf/quant-ph/9908043.pdf).
|
||||
//
|
||||
// Thus we support 0 <= x < 2^128.
|
||||
|
||||
@(private)
|
||||
_PAD: [RATE_128]byte // Biggest possible value of w per spec.
|
||||
|
||||
bytepad :: proc(ctx: ^Context, x_strings: [][]byte, w: int) {
|
||||
// 1. z = left_encode(w) || X.
|
||||
z_hi: u64
|
||||
z_lo := left_right_encode(ctx, 0, u64(w), true)
|
||||
for x in x_strings {
|
||||
// All uses of bytepad in SP 800-185 use the output from
|
||||
// one or more encode_string values for `X`.
|
||||
hi, lo := encode_string(ctx, x)
|
||||
|
||||
carry: u64
|
||||
z_lo, carry = bits.add_u64(z_lo, lo, 0)
|
||||
z_hi, carry = bits.add_u64(z_hi, hi, carry)
|
||||
|
||||
// This isn't actually possible, at least with the currently
|
||||
// defined SP 800-185 routines.
|
||||
if carry != 0 {
|
||||
panic("crypto/sha3: bytepad input length overflow")
|
||||
}
|
||||
}
|
||||
|
||||
// We skip this step as we are doing a byte-oriented implementation
|
||||
// rather than a bit oriented one.
|
||||
//
|
||||
// 2. while len(z) mod 8 ≠ 0:
|
||||
// z = z || 0
|
||||
|
||||
// 3. while (len(z)/8) mod w ≠ 0:
|
||||
// z = z || 00000000
|
||||
z_len := u128(z_hi) << 64 | u128(z_lo)
|
||||
z_rem := int(z_len % u128(w))
|
||||
pad := _PAD[:w - z_rem]
|
||||
|
||||
// We just add the padding to the state, instead of returning z.
|
||||
//
|
||||
// 4. return z.
|
||||
update(ctx, pad)
|
||||
}
|
||||
|
||||
encode_string :: #force_inline proc(ctx: ^Context, s: []byte) -> (u64, u64) {
|
||||
l := encode_byte_len(ctx, len(s), true) // left_encode
|
||||
update(ctx, s)
|
||||
|
||||
lo, hi := bits.add_u64(l, u64(len(s)), 0)
|
||||
|
||||
return hi, lo
|
||||
}
|
||||
|
||||
encode_byte_len :: #force_inline proc(ctx: ^Context, l: int, is_left: bool) -> u64 {
|
||||
hi, lo := bits.mul_u64(u64(l), 8)
|
||||
return left_right_encode(ctx, hi, lo, is_left)
|
||||
}
|
||||
|
||||
@(private)
|
||||
left_right_encode :: proc(ctx: ^Context, hi, lo: u64, is_left: bool) -> u64 {
|
||||
HI_OFFSET :: 1
|
||||
LO_OFFSET :: HI_OFFSET + 8
|
||||
RIGHT_OFFSET :: LO_OFFSET + 8
|
||||
BUF_LEN :: RIGHT_OFFSET + 1
|
||||
|
||||
buf: [BUF_LEN]byte // prefix + largest uint + postfix
|
||||
|
||||
endian.unchecked_put_u64be(buf[HI_OFFSET:], hi)
|
||||
endian.unchecked_put_u64be(buf[LO_OFFSET:], lo)
|
||||
|
||||
// 2. Strip leading `0x00` bytes.
|
||||
off: int
|
||||
for off = HI_OFFSET; off < RIGHT_OFFSET - 1; off = off + 1 {// Note: Minimum size is 1, not 0.
|
||||
if buf[off] != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
n := byte(RIGHT_OFFSET - off)
|
||||
|
||||
// 3. Prefix (left_encode) or postfix (right_encode) the length in bytes.
|
||||
b: []byte
|
||||
switch is_left {
|
||||
case true:
|
||||
buf[off - 1] = n // n | x
|
||||
b = buf[off - 1:RIGHT_OFFSET]
|
||||
case false:
|
||||
buf[RIGHT_OFFSET] = n // x | n
|
||||
b = buf[off:]
|
||||
}
|
||||
|
||||
update(ctx, b)
|
||||
|
||||
return u64(len(b))
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
package aes implements the AES block cipher and some common modes.
|
||||
|
||||
See:
|
||||
- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf
|
||||
- https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf
|
||||
- https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
|
||||
*/
|
||||
|
||||
package aes
|
||||
|
||||
import "core:crypto/_aes"
|
||||
|
||||
// KEY_SIZE_128 is the AES-128 key size in bytes.
|
||||
KEY_SIZE_128 :: _aes.KEY_SIZE_128
|
||||
// KEY_SIZE_192 is the AES-192 key size in bytes.
|
||||
KEY_SIZE_192 :: _aes.KEY_SIZE_192
|
||||
// KEY_SIZE_256 is the AES-256 key size in bytes.
|
||||
KEY_SIZE_256 :: _aes.KEY_SIZE_256
|
||||
|
||||
// BLOCK_SIZE is the AES block size in bytes.
|
||||
BLOCK_SIZE :: _aes.BLOCK_SIZE
|
||||
@@ -0,0 +1,199 @@
|
||||
package aes
|
||||
|
||||
import "core:crypto/_aes/ct64"
|
||||
import "core:encoding/endian"
|
||||
import "core:math/bits"
|
||||
import "core:mem"
|
||||
|
||||
// CTR_IV_SIZE is the size of the CTR mode IV in bytes.
|
||||
CTR_IV_SIZE :: 16
|
||||
|
||||
// Context_CTR is a keyed AES-CTR instance.
|
||||
Context_CTR :: struct {
|
||||
_impl: Context_Impl,
|
||||
_buffer: [BLOCK_SIZE]byte,
|
||||
_off: int,
|
||||
_ctr_hi: u64,
|
||||
_ctr_lo: u64,
|
||||
_is_initialized: bool,
|
||||
}
|
||||
|
||||
// init_ctr initializes a Context_CTR with the provided key and IV.
|
||||
init_ctr :: proc(ctx: ^Context_CTR, key, iv: []byte, impl := Implementation.Hardware) {
|
||||
if len(iv) != CTR_IV_SIZE {
|
||||
panic("crypto/aes: invalid CTR IV size")
|
||||
}
|
||||
|
||||
init_impl(&ctx._impl, key, impl)
|
||||
ctx._off = BLOCK_SIZE
|
||||
ctx._ctr_hi = endian.unchecked_get_u64be(iv[0:])
|
||||
ctx._ctr_lo = endian.unchecked_get_u64be(iv[8:])
|
||||
ctx._is_initialized = true
|
||||
}
|
||||
|
||||
// xor_bytes_ctr XORs each byte in src with bytes taken from the AES-CTR
|
||||
// keystream, and writes the resulting output to dst. dst and src MUST
|
||||
// alias exactly or not at all.
|
||||
xor_bytes_ctr :: proc(ctx: ^Context_CTR, dst, src: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
// TODO: Enforcing that dst and src alias exactly or not at all
|
||||
// is a good idea, though odd aliasing should be extremely uncommon.
|
||||
|
||||
src, dst := src, dst
|
||||
if dst_len := len(dst); dst_len < len(src) {
|
||||
src = src[:dst_len]
|
||||
}
|
||||
|
||||
for remaining := len(src); remaining > 0; {
|
||||
// Process multiple blocks at once
|
||||
if ctx._off == BLOCK_SIZE {
|
||||
if nr_blocks := remaining / BLOCK_SIZE; nr_blocks > 0 {
|
||||
direct_bytes := nr_blocks * BLOCK_SIZE
|
||||
ctr_blocks(ctx, dst, src, nr_blocks)
|
||||
remaining -= direct_bytes
|
||||
if remaining == 0 {
|
||||
return
|
||||
}
|
||||
dst = dst[direct_bytes:]
|
||||
src = src[direct_bytes:]
|
||||
}
|
||||
|
||||
// If there is a partial block, generate and buffer 1 block
|
||||
// worth of keystream.
|
||||
ctr_blocks(ctx, ctx._buffer[:], nil, 1)
|
||||
ctx._off = 0
|
||||
}
|
||||
|
||||
// Process partial blocks from the buffered keystream.
|
||||
to_xor := min(BLOCK_SIZE - ctx._off, remaining)
|
||||
buffered_keystream := ctx._buffer[ctx._off:]
|
||||
for i := 0; i < to_xor; i = i + 1 {
|
||||
dst[i] = buffered_keystream[i] ~ src[i]
|
||||
}
|
||||
ctx._off += to_xor
|
||||
dst = dst[to_xor:]
|
||||
src = src[to_xor:]
|
||||
remaining -= to_xor
|
||||
}
|
||||
}
|
||||
|
||||
// keystream_bytes_ctr fills dst with the raw AES-CTR keystream output.
|
||||
keystream_bytes_ctr :: proc(ctx: ^Context_CTR, dst: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
dst := dst
|
||||
for remaining := len(dst); remaining > 0; {
|
||||
// Process multiple blocks at once
|
||||
if ctx._off == BLOCK_SIZE {
|
||||
if nr_blocks := remaining / BLOCK_SIZE; nr_blocks > 0 {
|
||||
direct_bytes := nr_blocks * BLOCK_SIZE
|
||||
ctr_blocks(ctx, dst, nil, nr_blocks)
|
||||
remaining -= direct_bytes
|
||||
if remaining == 0 {
|
||||
return
|
||||
}
|
||||
dst = dst[direct_bytes:]
|
||||
}
|
||||
|
||||
// If there is a partial block, generate and buffer 1 block
|
||||
// worth of keystream.
|
||||
ctr_blocks(ctx, ctx._buffer[:], nil, 1)
|
||||
ctx._off = 0
|
||||
}
|
||||
|
||||
// Process partial blocks from the buffered keystream.
|
||||
to_copy := min(BLOCK_SIZE - ctx._off, remaining)
|
||||
buffered_keystream := ctx._buffer[ctx._off:]
|
||||
copy(dst[:to_copy], buffered_keystream[:to_copy])
|
||||
ctx._off += to_copy
|
||||
dst = dst[to_copy:]
|
||||
remaining -= to_copy
|
||||
}
|
||||
}
|
||||
|
||||
// reset_ctr sanitizes the Context_CTR. The Context_CTR must be
|
||||
// re-initialized to be used again.
|
||||
reset_ctr :: proc "contextless" (ctx: ^Context_CTR) {
|
||||
reset_impl(&ctx._impl)
|
||||
ctx._off = 0
|
||||
ctx._ctr_hi = 0
|
||||
ctx._ctr_lo = 0
|
||||
mem.zero_explicit(&ctx._buffer, size_of(ctx._buffer))
|
||||
ctx._is_initialized = false
|
||||
}
|
||||
|
||||
@(private)
|
||||
ctr_blocks :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) {
|
||||
// Use the optimized hardware implementation if available.
|
||||
if _, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
|
||||
ctr_blocks_hw(ctx, dst, src, nr_blocks)
|
||||
return
|
||||
}
|
||||
|
||||
// Portable implementation.
|
||||
ct64_inc_ctr := #force_inline proc "contextless" (dst: []byte, hi, lo: u64) -> (u64, u64) {
|
||||
endian.unchecked_put_u64be(dst[0:], hi)
|
||||
endian.unchecked_put_u64be(dst[8:], lo)
|
||||
|
||||
hi, lo := hi, lo
|
||||
carry: u64
|
||||
lo, carry = bits.add_u64(lo, 1, 0)
|
||||
hi, _ = bits.add_u64(hi, 0, carry)
|
||||
return hi, lo
|
||||
}
|
||||
|
||||
impl := &ctx._impl.(ct64.Context)
|
||||
src, dst := src, dst
|
||||
nr_blocks := nr_blocks
|
||||
ctr_hi, ctr_lo := ctx._ctr_hi, ctx._ctr_lo
|
||||
|
||||
tmp: [ct64.STRIDE][BLOCK_SIZE]byte = ---
|
||||
ctrs: [ct64.STRIDE][]byte = ---
|
||||
for i in 0 ..< ct64.STRIDE {
|
||||
ctrs[i] = tmp[i][:]
|
||||
}
|
||||
for nr_blocks > 0 {
|
||||
n := min(ct64.STRIDE, nr_blocks)
|
||||
blocks := ctrs[:n]
|
||||
|
||||
for i in 0 ..< n {
|
||||
ctr_hi, ctr_lo = ct64_inc_ctr(blocks[i], ctr_hi, ctr_lo)
|
||||
}
|
||||
ct64.encrypt_blocks(impl, blocks, blocks)
|
||||
|
||||
xor_blocks(dst, src, blocks)
|
||||
|
||||
if src != nil {
|
||||
src = src[n * BLOCK_SIZE:]
|
||||
}
|
||||
dst = dst[n * BLOCK_SIZE:]
|
||||
nr_blocks -= n
|
||||
}
|
||||
|
||||
// Write back the counter.
|
||||
ctx._ctr_hi, ctx._ctr_lo = ctr_hi, ctr_lo
|
||||
|
||||
mem.zero_explicit(&tmp, size_of(tmp))
|
||||
}
|
||||
|
||||
@(private)
|
||||
xor_blocks :: #force_inline proc "contextless" (dst, src: []byte, blocks: [][]byte) {
|
||||
// Note: This would be faster `core:simd` was used, however if
|
||||
// performance of this implementation matters to where that
|
||||
// optimization would be worth it, use chacha20poly1305, or a
|
||||
// CPU that isn't e-waste.
|
||||
if src != nil {
|
||||
#no_bounds_check {
|
||||
for i in 0 ..< len(blocks) {
|
||||
off := i * BLOCK_SIZE
|
||||
for j in 0 ..< BLOCK_SIZE {
|
||||
blocks[i][j] ~= src[off + j]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for i in 0 ..< len(blocks) {
|
||||
copy(dst[i * BLOCK_SIZE:], blocks[i])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package aes
|
||||
|
||||
import "core:crypto/_aes/ct64"
|
||||
|
||||
// Context_ECB is a keyed AES-ECB instance.
|
||||
//
|
||||
// WARNING: Using ECB mode is strongly discouraged unless it is being
|
||||
// used to implement higher level constructs.
|
||||
Context_ECB :: struct {
|
||||
_impl: Context_Impl,
|
||||
_is_initialized: bool,
|
||||
}
|
||||
|
||||
// init_ecb initializes a Context_ECB with the provided key.
|
||||
init_ecb :: proc(ctx: ^Context_ECB, key: []byte, impl := Implementation.Hardware) {
|
||||
init_impl(&ctx._impl, key, impl)
|
||||
ctx._is_initialized = true
|
||||
}
|
||||
|
||||
// encrypt_ecb encrypts the BLOCK_SIZE buffer src, and writes the result to dst.
|
||||
encrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
if len(dst) != BLOCK_SIZE || len(src) != BLOCK_SIZE {
|
||||
panic("crypto/aes: invalid buffer size(s)")
|
||||
}
|
||||
|
||||
switch &impl in ctx._impl {
|
||||
case ct64.Context:
|
||||
ct64.encrypt_block(&impl, dst, src)
|
||||
case Context_Impl_Hardware:
|
||||
encrypt_block_hw(&impl, dst, src)
|
||||
}
|
||||
}
|
||||
|
||||
// decrypt_ecb decrypts the BLOCK_SIZE buffer src, and writes the result to dst.
|
||||
decrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
if len(dst) != BLOCK_SIZE || len(src) != BLOCK_SIZE {
|
||||
panic("crypto/aes: invalid buffer size(s)")
|
||||
}
|
||||
|
||||
switch &impl in ctx._impl {
|
||||
case ct64.Context:
|
||||
ct64.decrypt_block(&impl, dst, src)
|
||||
case Context_Impl_Hardware:
|
||||
decrypt_block_hw(&impl, dst, src)
|
||||
}
|
||||
}
|
||||
|
||||
// reset_ecb sanitizes the Context_ECB. The Context_ECB must be
|
||||
// re-initialized to be used again.
|
||||
reset_ecb :: proc "contextless" (ctx: ^Context_ECB) {
|
||||
reset_impl(&ctx._impl)
|
||||
ctx._is_initialized = false
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
package aes
|
||||
|
||||
import "core:crypto"
|
||||
import "core:crypto/_aes"
|
||||
import "core:crypto/_aes/ct64"
|
||||
import "core:encoding/endian"
|
||||
import "core:mem"
|
||||
|
||||
// GCM_NONCE_SIZE is the size of the GCM nonce in bytes.
|
||||
GCM_NONCE_SIZE :: 12
|
||||
// GCM_TAG_SIZE is the size of a GCM tag in bytes.
|
||||
GCM_TAG_SIZE :: _aes.GHASH_TAG_SIZE
|
||||
|
||||
@(private)
|
||||
GCM_A_MAX :: max(u64) / 8 // 2^64 - 1 bits -> bytes
|
||||
@(private)
|
||||
GCM_P_MAX :: 0xfffffffe0 // 2^39 - 256 bits -> bytes
|
||||
|
||||
// Context_GCM is a keyed AES-GCM instance.
|
||||
Context_GCM :: struct {
|
||||
_impl: Context_Impl,
|
||||
_is_initialized: bool,
|
||||
}
|
||||
|
||||
// init_gcm initializes a Context_GCM with the provided key.
|
||||
init_gcm :: proc(ctx: ^Context_GCM, key: []byte, impl := Implementation.Hardware) {
|
||||
init_impl(&ctx._impl, key, impl)
|
||||
ctx._is_initialized = true
|
||||
}
|
||||
|
||||
// seal_gcm encrypts the plaintext and authenticates the aad and ciphertext,
|
||||
// with the provided Context_GCM and nonce, stores the output in dst and tag.
|
||||
//
|
||||
// dst and plaintext MUST alias exactly or not at all.
|
||||
seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
gcm_validate_common_slice_sizes(tag, nonce, aad, plaintext)
|
||||
if len(dst) != len(plaintext) {
|
||||
panic("crypto/aes: invalid destination ciphertext size")
|
||||
}
|
||||
|
||||
if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
|
||||
gcm_seal_hw(&impl, dst, tag, nonce, aad, plaintext)
|
||||
return
|
||||
}
|
||||
|
||||
h: [_aes.GHASH_KEY_SIZE]byte
|
||||
j0: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
s: [_aes.GHASH_TAG_SIZE]byte
|
||||
init_ghash_ct64(ctx, &h, &j0, nonce)
|
||||
|
||||
// Note: Our GHASH implementation handles appending padding.
|
||||
ct64.ghash(s[:], h[:], aad)
|
||||
gctr_ct64(ctx, dst, &s, plaintext, &h, nonce, true)
|
||||
final_ghash_ct64(&s, &h, &j0, len(aad), len(plaintext))
|
||||
copy(tag, s[:])
|
||||
|
||||
mem.zero_explicit(&h, len(h))
|
||||
mem.zero_explicit(&j0, len(j0))
|
||||
}
|
||||
|
||||
// open_gcm authenticates the aad and ciphertext, and decrypts the ciphertext,
|
||||
// with the provided Context_GCM, nonce, and tag, and stores the output in dst,
|
||||
// returning true iff the authentication was successful. If authentication
|
||||
// fails, the destination buffer will be zeroed.
|
||||
//
|
||||
// dst and plaintext MUST alias exactly or not at all.
|
||||
open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) -> bool {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
gcm_validate_common_slice_sizes(tag, nonce, aad, ciphertext)
|
||||
if len(dst) != len(ciphertext) {
|
||||
panic("crypto/aes: invalid destination plaintext size")
|
||||
}
|
||||
|
||||
if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
|
||||
return gcm_open_hw(&impl, dst, nonce, aad, ciphertext, tag)
|
||||
}
|
||||
|
||||
h: [_aes.GHASH_KEY_SIZE]byte
|
||||
j0: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
s: [_aes.GHASH_TAG_SIZE]byte
|
||||
init_ghash_ct64(ctx, &h, &j0, nonce)
|
||||
|
||||
ct64.ghash(s[:], h[:], aad)
|
||||
gctr_ct64(ctx, dst, &s, ciphertext, &h, nonce, false)
|
||||
final_ghash_ct64(&s, &h, &j0, len(aad), len(ciphertext))
|
||||
|
||||
ok := crypto.compare_constant_time(s[:], tag) == 1
|
||||
if !ok {
|
||||
mem.zero_explicit(raw_data(dst), len(dst))
|
||||
}
|
||||
|
||||
mem.zero_explicit(&h, len(h))
|
||||
mem.zero_explicit(&j0, len(j0))
|
||||
mem.zero_explicit(&s, len(s))
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// reset_ctr sanitizes the Context_GCM. The Context_GCM must be
|
||||
// re-initialized to be used again.
|
||||
reset_gcm :: proc "contextless" (ctx: ^Context_GCM) {
|
||||
reset_impl(&ctx._impl)
|
||||
ctx._is_initialized = false
|
||||
}
|
||||
|
||||
@(private)
|
||||
gcm_validate_common_slice_sizes :: proc(tag, nonce, aad, text: []byte) {
|
||||
if len(tag) != GCM_TAG_SIZE {
|
||||
panic("crypto/aes: invalid GCM tag size")
|
||||
}
|
||||
|
||||
// The specification supports nonces in the range [1, 2^64) bits
|
||||
// however per NIST SP 800-38D 5.2.1.1:
|
||||
//
|
||||
// > For IVs, it is recommended that implementations restrict support
|
||||
// > to the length of 96 bits, to promote interoperability, efficiency,
|
||||
// > and simplicity of design.
|
||||
if len(nonce) != GCM_NONCE_SIZE {
|
||||
panic("crypto/aes: invalid GCM nonce size")
|
||||
}
|
||||
|
||||
if aad_len := u64(len(aad)); aad_len > GCM_A_MAX {
|
||||
panic("crypto/aes: oversized GCM aad")
|
||||
}
|
||||
if text_len := u64(len(text)); text_len > GCM_P_MAX {
|
||||
panic("crypto/aes: oversized GCM src data")
|
||||
}
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
init_ghash_ct64 :: proc(
|
||||
ctx: ^Context_GCM,
|
||||
h: ^[_aes.GHASH_KEY_SIZE]byte,
|
||||
j0: ^[_aes.GHASH_BLOCK_SIZE]byte,
|
||||
nonce: []byte,
|
||||
) {
|
||||
impl := &ctx._impl.(ct64.Context)
|
||||
|
||||
// 1. Let H = CIPH(k, 0^128)
|
||||
ct64.encrypt_block(impl, h[:], h[:])
|
||||
|
||||
// ECB encrypt j0, so that we can just XOR with the tag. In theory
|
||||
// this could be processed along with the final GCTR block, to
|
||||
// potentially save a call to AES-ECB, but... just use AES-NI.
|
||||
copy(j0[:], nonce)
|
||||
j0[_aes.GHASH_BLOCK_SIZE - 1] = 1
|
||||
ct64.encrypt_block(impl, j0[:], j0[:])
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
final_ghash_ct64 :: proc(
|
||||
s: ^[_aes.GHASH_BLOCK_SIZE]byte,
|
||||
h: ^[_aes.GHASH_KEY_SIZE]byte,
|
||||
j0: ^[_aes.GHASH_BLOCK_SIZE]byte,
|
||||
a_len: int,
|
||||
t_len: int,
|
||||
) {
|
||||
blk: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
endian.unchecked_put_u64be(blk[0:], u64(a_len) * 8)
|
||||
endian.unchecked_put_u64be(blk[8:], u64(t_len) * 8)
|
||||
|
||||
ct64.ghash(s[:], h[:], blk[:])
|
||||
for i in 0 ..< len(s) {
|
||||
s[i] ~= j0[i]
|
||||
}
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
gctr_ct64 :: proc(
|
||||
ctx: ^Context_GCM,
|
||||
dst: []byte,
|
||||
s: ^[_aes.GHASH_BLOCK_SIZE]byte,
|
||||
src: []byte,
|
||||
h: ^[_aes.GHASH_KEY_SIZE]byte,
|
||||
nonce: []byte,
|
||||
is_seal: bool,
|
||||
) {
|
||||
ct64_inc_ctr32 := #force_inline proc "contextless" (dst: []byte, ctr: u32) -> u32 {
|
||||
endian.unchecked_put_u32be(dst[12:], ctr)
|
||||
return ctr + 1
|
||||
}
|
||||
|
||||
// 2. Define a block J_0 as follows:
|
||||
// if len(IV) = 96, then let J0 = IV || 0^31 || 1
|
||||
//
|
||||
// Note: We only support 96 bit IVs.
|
||||
tmp, tmp2: [ct64.STRIDE][BLOCK_SIZE]byte = ---, ---
|
||||
ctrs, blks: [ct64.STRIDE][]byte = ---, ---
|
||||
ctr: u32 = 2
|
||||
for i in 0 ..< ct64.STRIDE {
|
||||
// Setup scratch space for the keystream.
|
||||
blks[i] = tmp2[i][:]
|
||||
|
||||
// Pre-copy the IV to all the counter blocks.
|
||||
ctrs[i] = tmp[i][:]
|
||||
copy(ctrs[i], nonce)
|
||||
}
|
||||
|
||||
// We stitch the GCTR and GHASH operations together, so that only
|
||||
// one pass over the ciphertext is required.
|
||||
|
||||
impl := &ctx._impl.(ct64.Context)
|
||||
src, dst := src, dst
|
||||
|
||||
nr_blocks := len(src) / BLOCK_SIZE
|
||||
for nr_blocks > 0 {
|
||||
n := min(ct64.STRIDE, nr_blocks)
|
||||
l := n * BLOCK_SIZE
|
||||
|
||||
if !is_seal {
|
||||
ct64.ghash(s[:], h[:], src[:l])
|
||||
}
|
||||
|
||||
// The keystream is written to a separate buffer, as we will
|
||||
// reuse the first 96-bits of each counter.
|
||||
for i in 0 ..< n {
|
||||
ctr = ct64_inc_ctr32(ctrs[i], ctr)
|
||||
}
|
||||
ct64.encrypt_blocks(impl, blks[:n], ctrs[:n])
|
||||
|
||||
xor_blocks(dst, src, blks[:n])
|
||||
|
||||
if is_seal {
|
||||
ct64.ghash(s[:], h[:], dst[:l])
|
||||
}
|
||||
|
||||
src = src[l:]
|
||||
dst = dst[l:]
|
||||
nr_blocks -= n
|
||||
}
|
||||
if l := len(src); l > 0 {
|
||||
if !is_seal {
|
||||
ct64.ghash(s[:], h[:], src[:l])
|
||||
}
|
||||
|
||||
ct64_inc_ctr32(ctrs[0], ctr)
|
||||
ct64.encrypt_block(impl, ctrs[0], ctrs[0])
|
||||
|
||||
for i in 0 ..< l {
|
||||
dst[i] = src[i] ~ ctrs[0][i]
|
||||
}
|
||||
|
||||
if is_seal {
|
||||
ct64.ghash(s[:], h[:], dst[:l])
|
||||
}
|
||||
}
|
||||
|
||||
mem.zero_explicit(&tmp, size_of(tmp))
|
||||
mem.zero_explicit(&tmp2, size_of(tmp2))
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package aes
|
||||
|
||||
import "core:crypto/_aes/ct64"
|
||||
import "core:mem"
|
||||
import "core:reflect"
|
||||
|
||||
@(private)
|
||||
Context_Impl :: union {
|
||||
ct64.Context,
|
||||
Context_Impl_Hardware,
|
||||
}
|
||||
|
||||
// Implementation is an AES implementation. Most callers will not need
|
||||
// to use this as the package will automatically select the most performant
|
||||
// implementation available (See `is_hardware_accelerated()`).
|
||||
Implementation :: enum {
|
||||
Portable,
|
||||
Hardware,
|
||||
}
|
||||
|
||||
@(private)
|
||||
init_impl :: proc(ctx: ^Context_Impl, key: []byte, impl: Implementation) {
|
||||
impl := impl
|
||||
if !is_hardware_accelerated() {
|
||||
impl = .Portable
|
||||
}
|
||||
|
||||
switch impl {
|
||||
case .Portable:
|
||||
reflect.set_union_variant_typeid(ctx^, typeid_of(ct64.Context))
|
||||
ct64.init(&ctx.(ct64.Context), key)
|
||||
case .Hardware:
|
||||
reflect.set_union_variant_typeid(ctx^, typeid_of(Context_Impl_Hardware))
|
||||
init_impl_hw(&ctx.(Context_Impl_Hardware), key)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
reset_impl :: proc "contextless" (ctx: ^Context_Impl) {
|
||||
mem.zero_explicit(ctx, size_of(Context_Impl))
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package aes
|
||||
|
||||
@(private = "file")
|
||||
ERR_HW_NOT_SUPPORTED :: "crypto/aes: hardware implementation unsupported"
|
||||
|
||||
// is_hardware_accelerated returns true iff hardware accelerated AES
|
||||
// is supported.
|
||||
is_hardware_accelerated :: proc "contextless" () -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@(private)
|
||||
Context_Impl_Hardware :: struct {}
|
||||
|
||||
@(private)
|
||||
init_impl_hw :: proc(ctx: ^Context_Impl_Hardware, key: []byte) {
|
||||
panic(ERR_HW_NOT_SUPPORTED)
|
||||
}
|
||||
|
||||
@(private)
|
||||
encrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) {
|
||||
panic(ERR_HW_NOT_SUPPORTED)
|
||||
}
|
||||
|
||||
@(private)
|
||||
decrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) {
|
||||
panic(ERR_HW_NOT_SUPPORTED)
|
||||
}
|
||||
|
||||
@(private)
|
||||
ctr_blocks_hw :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) {
|
||||
panic(ERR_HW_NOT_SUPPORTED)
|
||||
}
|
||||
|
||||
@(private)
|
||||
gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, nonce, aad, plaintext: []byte) {
|
||||
panic(ERR_HW_NOT_SUPPORTED)
|
||||
}
|
||||
|
||||
@(private)
|
||||
gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, nonce, aad, ciphertext, tag: []byte) -> bool {
|
||||
panic(ERR_HW_NOT_SUPPORTED)
|
||||
}
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
package blake2b implements the BLAKE2b hash algorithm.
|
||||
|
||||
See:
|
||||
- https://datatracker.ietf.org/doc/html/rfc7693
|
||||
- https://www.blake2.net
|
||||
*/
|
||||
package blake2b
|
||||
|
||||
/*
|
||||
@@ -6,122 +13,47 @@ package blake2b
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
Interface for the BLAKE2b hashing algorithm.
|
||||
BLAKE2b and BLAKE2s share the implementation in the _blake2 package.
|
||||
*/
|
||||
|
||||
import "core:io"
|
||||
import "core:os"
|
||||
|
||||
import "../_blake2"
|
||||
|
||||
/*
|
||||
High level API
|
||||
*/
|
||||
|
||||
// DIGEST_SIZE is the BLAKE2b digest size in bytes.
|
||||
DIGEST_SIZE :: 64
|
||||
|
||||
// hash_string will hash the given input and return the
|
||||
// computed hash
|
||||
hash_string :: proc(data: string) -> [DIGEST_SIZE]byte {
|
||||
return hash_bytes(transmute([]byte)(data))
|
||||
}
|
||||
|
||||
// hash_bytes will hash the given input and return the
|
||||
// computed hash
|
||||
hash_bytes :: proc(data: []byte) -> [DIGEST_SIZE]byte {
|
||||
hash: [DIGEST_SIZE]byte
|
||||
ctx: Context
|
||||
cfg: _blake2.Blake2_Config
|
||||
cfg.size = _blake2.BLAKE2B_SIZE
|
||||
ctx.cfg = cfg
|
||||
init(&ctx)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash[:])
|
||||
return hash
|
||||
}
|
||||
|
||||
// hash_string_to_buffer will hash the given input and assign the
|
||||
// computed hash to the second parameter.
|
||||
// It requires that the destination buffer is at least as big as the digest size
|
||||
hash_string_to_buffer :: proc(data: string, hash: []byte) {
|
||||
hash_bytes_to_buffer(transmute([]byte)(data), hash)
|
||||
}
|
||||
|
||||
// hash_bytes_to_buffer will hash the given input and write the
|
||||
// computed hash into the second parameter.
|
||||
// It requires that the destination buffer is at least as big as the digest size
|
||||
hash_bytes_to_buffer :: proc(data, hash: []byte) {
|
||||
ctx: Context
|
||||
cfg: _blake2.Blake2_Config
|
||||
cfg.size = _blake2.BLAKE2B_SIZE
|
||||
ctx.cfg = cfg
|
||||
init(&ctx)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash)
|
||||
}
|
||||
|
||||
// hash_stream will read the stream in chunks and compute a
|
||||
// hash from its contents
|
||||
hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) {
|
||||
hash: [DIGEST_SIZE]byte
|
||||
ctx: Context
|
||||
cfg: _blake2.Blake2_Config
|
||||
cfg.size = _blake2.BLAKE2B_SIZE
|
||||
ctx.cfg = cfg
|
||||
init(&ctx)
|
||||
|
||||
buf := make([]byte, 512)
|
||||
defer delete(buf)
|
||||
|
||||
read := 1
|
||||
for read > 0 {
|
||||
read, _ = io.read(s, buf)
|
||||
if read > 0 {
|
||||
update(&ctx, buf[:read])
|
||||
}
|
||||
}
|
||||
final(&ctx, hash[:])
|
||||
return hash, true
|
||||
}
|
||||
|
||||
// hash_file will read the file provided by the given handle
|
||||
// and compute a hash
|
||||
hash_file :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE]byte, bool) {
|
||||
if !load_at_once {
|
||||
return hash_stream(os.stream_from_handle(hd))
|
||||
} else {
|
||||
if buf, ok := os.read_entire_file(hd); ok {
|
||||
return hash_bytes(buf[:]), ok
|
||||
}
|
||||
}
|
||||
return [DIGEST_SIZE]byte{}, false
|
||||
}
|
||||
|
||||
hash :: proc {
|
||||
hash_stream,
|
||||
hash_file,
|
||||
hash_bytes,
|
||||
hash_string,
|
||||
hash_bytes_to_buffer,
|
||||
hash_string_to_buffer,
|
||||
}
|
||||
|
||||
/*
|
||||
Low level API
|
||||
*/
|
||||
// BLOCK_SIZE is the BLAKE2b block size in bytes.
|
||||
BLOCK_SIZE :: _blake2.BLAKE2B_BLOCK_SIZE
|
||||
|
||||
// Context is a BLAKE2b instance.
|
||||
Context :: _blake2.Blake2b_Context
|
||||
|
||||
// init initializes a Context with the default BLAKE2b config.
|
||||
init :: proc(ctx: ^Context) {
|
||||
_blake2.init(ctx)
|
||||
cfg: _blake2.Blake2_Config
|
||||
cfg.size = _blake2.BLAKE2B_SIZE
|
||||
_blake2.init(ctx, &cfg)
|
||||
}
|
||||
|
||||
// update adds more data to the Context.
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
_blake2.update(ctx, data)
|
||||
}
|
||||
|
||||
final :: proc(ctx: ^Context, hash: []byte) {
|
||||
_blake2.final(ctx, hash)
|
||||
// final finalizes the Context, writes the digest to hash, and calls
|
||||
// reset on the Context.
|
||||
//
|
||||
// Iff finalize_clone is set, final will work on a copy of the Context,
|
||||
// which is useful for for calculating rolling digests.
|
||||
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
_blake2.final(ctx, hash, finalize_clone)
|
||||
}
|
||||
|
||||
// clone clones the Context other into ctx.
|
||||
clone :: proc(ctx, other: ^Context) {
|
||||
_blake2.clone(ctx, other)
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
_blake2.reset(ctx)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
package blake2s implements the BLAKE2s hash algorithm.
|
||||
|
||||
See:
|
||||
- https://datatracker.ietf.org/doc/html/rfc7693
|
||||
- https://www.blake2.net/
|
||||
*/
|
||||
package blake2s
|
||||
|
||||
/*
|
||||
@@ -6,122 +13,47 @@ package blake2s
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
Interface for the BLAKE2s hashing algorithm.
|
||||
BLAKE2s and BLAKE2b share the implementation in the _blake2 package.
|
||||
*/
|
||||
|
||||
import "core:io"
|
||||
import "core:os"
|
||||
|
||||
import "../_blake2"
|
||||
|
||||
/*
|
||||
High level API
|
||||
*/
|
||||
|
||||
// DIGEST_SIZE is the BLAKE2s digest size in bytes.
|
||||
DIGEST_SIZE :: 32
|
||||
|
||||
// hash_string will hash the given input and return the
|
||||
// computed hash
|
||||
hash_string :: proc(data: string) -> [DIGEST_SIZE]byte {
|
||||
return hash_bytes(transmute([]byte)(data))
|
||||
}
|
||||
|
||||
// hash_bytes will hash the given input and return the
|
||||
// computed hash
|
||||
hash_bytes :: proc(data: []byte) -> [DIGEST_SIZE]byte {
|
||||
hash: [DIGEST_SIZE]byte
|
||||
ctx: Context
|
||||
cfg: _blake2.Blake2_Config
|
||||
cfg.size = _blake2.BLAKE2S_SIZE
|
||||
ctx.cfg = cfg
|
||||
init(&ctx)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash[:])
|
||||
return hash
|
||||
}
|
||||
|
||||
// hash_string_to_buffer will hash the given input and assign the
|
||||
// computed hash to the second parameter.
|
||||
// It requires that the destination buffer is at least as big as the digest size
|
||||
hash_string_to_buffer :: proc(data: string, hash: []byte) {
|
||||
hash_bytes_to_buffer(transmute([]byte)(data), hash)
|
||||
}
|
||||
|
||||
// hash_bytes_to_buffer will hash the given input and write the
|
||||
// computed hash into the second parameter.
|
||||
// It requires that the destination buffer is at least as big as the digest size
|
||||
hash_bytes_to_buffer :: proc(data, hash: []byte) {
|
||||
ctx: Context
|
||||
cfg: _blake2.Blake2_Config
|
||||
cfg.size = _blake2.BLAKE2S_SIZE
|
||||
ctx.cfg = cfg
|
||||
init(&ctx)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash)
|
||||
}
|
||||
|
||||
// hash_stream will read the stream in chunks and compute a
|
||||
// hash from its contents
|
||||
hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) {
|
||||
hash: [DIGEST_SIZE]byte
|
||||
ctx: Context
|
||||
cfg: _blake2.Blake2_Config
|
||||
cfg.size = _blake2.BLAKE2S_SIZE
|
||||
ctx.cfg = cfg
|
||||
init(&ctx)
|
||||
|
||||
buf := make([]byte, 512)
|
||||
defer delete(buf)
|
||||
|
||||
read := 1
|
||||
for read > 0 {
|
||||
read, _ = io.read(s, buf)
|
||||
if read > 0 {
|
||||
update(&ctx, buf[:read])
|
||||
}
|
||||
}
|
||||
final(&ctx, hash[:])
|
||||
return hash, true
|
||||
}
|
||||
|
||||
// hash_file will read the file provided by the given handle
|
||||
// and compute a hash
|
||||
hash_file :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE]byte, bool) {
|
||||
if !load_at_once {
|
||||
return hash_stream(os.stream_from_handle(hd))
|
||||
} else {
|
||||
if buf, ok := os.read_entire_file(hd); ok {
|
||||
return hash_bytes(buf[:]), ok
|
||||
}
|
||||
}
|
||||
return [DIGEST_SIZE]byte{}, false
|
||||
}
|
||||
|
||||
hash :: proc {
|
||||
hash_stream,
|
||||
hash_file,
|
||||
hash_bytes,
|
||||
hash_string,
|
||||
hash_bytes_to_buffer,
|
||||
hash_string_to_buffer,
|
||||
}
|
||||
|
||||
/*
|
||||
Low level API
|
||||
*/
|
||||
// BLOCK_SIZE is the BLAKE2s block size in bytes.
|
||||
BLOCK_SIZE :: _blake2.BLAKE2S_BLOCK_SIZE
|
||||
|
||||
// Context is a BLAKE2s instance.
|
||||
Context :: _blake2.Blake2s_Context
|
||||
|
||||
// init initializes a Context with the default BLAKE2s config.
|
||||
init :: proc(ctx: ^Context) {
|
||||
_blake2.init(ctx)
|
||||
cfg: _blake2.Blake2_Config
|
||||
cfg.size = _blake2.BLAKE2S_SIZE
|
||||
_blake2.init(ctx, &cfg)
|
||||
}
|
||||
|
||||
// update adds more data to the Context.
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
_blake2.update(ctx, data)
|
||||
}
|
||||
|
||||
final :: proc(ctx: ^Context, hash: []byte) {
|
||||
_blake2.final(ctx, hash)
|
||||
// final finalizes the Context, writes the digest to hash, and calls
|
||||
// reset on the Context.
|
||||
//
|
||||
// Iff finalize_clone is set, final will work on a copy of the Context,
|
||||
// which is useful for for calculating rolling digests.
|
||||
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
_blake2.final(ctx, hash, finalize_clone)
|
||||
}
|
||||
|
||||
// clone clones the Context other into ctx.
|
||||
clone :: proc(ctx, other: ^Context) {
|
||||
_blake2.clone(ctx, other)
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
_blake2.reset(ctx)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
/*
|
||||
package chacha20 implements the ChaCha20 and XChaCha20 stream ciphers.
|
||||
|
||||
See:
|
||||
- https://datatracker.ietf.org/doc/html/rfc8439
|
||||
- https://datatracker.ietf.org/doc/draft-irtf-cfrg-xchacha/03/
|
||||
*/
|
||||
package chacha20
|
||||
|
||||
import "core:encoding/endian"
|
||||
import "core:math/bits"
|
||||
import "core:mem"
|
||||
|
||||
// KEY_SIZE is the (X)ChaCha20 key size in bytes.
|
||||
KEY_SIZE :: 32
|
||||
// NONCE_SIZE is the ChaCha20 nonce size in bytes.
|
||||
NONCE_SIZE :: 12
|
||||
// XNONCE_SIZE is the XChaCha20 nonce size in bytes.
|
||||
XNONCE_SIZE :: 24
|
||||
|
||||
@(private)
|
||||
@@ -19,25 +29,26 @@ _STATE_SIZE_U32 :: 16
|
||||
_ROUNDS :: 20
|
||||
|
||||
@(private)
|
||||
_SIGMA_0 : u32 : 0x61707865
|
||||
_SIGMA_0: u32 : 0x61707865
|
||||
@(private)
|
||||
_SIGMA_1 : u32 : 0x3320646e
|
||||
_SIGMA_1: u32 : 0x3320646e
|
||||
@(private)
|
||||
_SIGMA_2 : u32 : 0x79622d32
|
||||
_SIGMA_2: u32 : 0x79622d32
|
||||
@(private)
|
||||
_SIGMA_3 : u32 : 0x6b206574
|
||||
_SIGMA_3: u32 : 0x6b206574
|
||||
|
||||
// Context is a ChaCha20 or XChaCha20 instance.
|
||||
Context :: struct {
|
||||
_s: [_STATE_SIZE_U32]u32,
|
||||
|
||||
_buffer: [_BLOCK_SIZE]byte,
|
||||
_off: int,
|
||||
|
||||
_s: [_STATE_SIZE_U32]u32,
|
||||
_buffer: [_BLOCK_SIZE]byte,
|
||||
_off: int,
|
||||
_is_ietf_flavor: bool,
|
||||
_is_initialized: bool,
|
||||
}
|
||||
|
||||
init :: proc (ctx: ^Context, key, nonce: []byte) {
|
||||
// init inititializes a Context for ChaCha20 or XChaCha20 with the provided
|
||||
// key and nonce.
|
||||
init :: proc(ctx: ^Context, key, nonce: []byte) {
|
||||
if len(key) != KEY_SIZE {
|
||||
panic("crypto/chacha20: invalid ChaCha20 key size")
|
||||
}
|
||||
@@ -89,7 +100,8 @@ init :: proc (ctx: ^Context, key, nonce: []byte) {
|
||||
ctx._is_initialized = true
|
||||
}
|
||||
|
||||
seek :: proc (ctx: ^Context, block_nr: u64) {
|
||||
// seek seeks the (X)ChaCha20 stream counter to the specified block.
|
||||
seek :: proc(ctx: ^Context, block_nr: u64) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
if ctx._is_ietf_flavor {
|
||||
@@ -103,7 +115,10 @@ seek :: proc (ctx: ^Context, block_nr: u64) {
|
||||
ctx._off = _BLOCK_SIZE
|
||||
}
|
||||
|
||||
xor_bytes :: proc (ctx: ^Context, dst, src: []byte) {
|
||||
// xor_bytes XORs each byte in src with bytes taken from the (X)ChaCha20
|
||||
// keystream, and writes the resulting output to dst. Dst and src MUST
|
||||
// alias exactly or not at all.
|
||||
xor_bytes :: proc(ctx: ^Context, dst, src: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
// TODO: Enforcing that dst and src alias exactly or not at all
|
||||
@@ -147,7 +162,8 @@ xor_bytes :: proc (ctx: ^Context, dst, src: []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
keystream_bytes :: proc (ctx: ^Context, dst: []byte) {
|
||||
// keystream_bytes fills dst with the raw (X)ChaCha20 keystream output.
|
||||
keystream_bytes :: proc(ctx: ^Context, dst: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
dst := dst
|
||||
@@ -180,7 +196,9 @@ keystream_bytes :: proc (ctx: ^Context, dst: []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
reset :: proc (ctx: ^Context) {
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
mem.zero_explicit(&ctx._s, size_of(ctx._s))
|
||||
mem.zero_explicit(&ctx._buffer, size_of(ctx._buffer))
|
||||
|
||||
@@ -188,7 +206,7 @@ reset :: proc (ctx: ^Context) {
|
||||
}
|
||||
|
||||
@(private)
|
||||
_do_blocks :: proc (ctx: ^Context, dst, src: []byte, nr_blocks: int) {
|
||||
_do_blocks :: proc(ctx: ^Context, dst, src: []byte, nr_blocks: int) {
|
||||
// Enforce the maximum consumed keystream per nonce.
|
||||
//
|
||||
// While all modern "standard" definitions of ChaCha20 use
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
package chacha20poly1305 implements the AEAD_CHACHA20_POLY1305 Authenticated
|
||||
Encryption with Additional Data algorithm.
|
||||
|
||||
See:
|
||||
- https://www.rfc-editor.org/rfc/rfc8439
|
||||
*/
|
||||
package chacha20poly1305
|
||||
|
||||
import "core:crypto"
|
||||
@@ -6,8 +13,11 @@ import "core:crypto/poly1305"
|
||||
import "core:encoding/endian"
|
||||
import "core:mem"
|
||||
|
||||
// KEY_SIZE is the chacha20poly1305 key size in bytes.
|
||||
KEY_SIZE :: chacha20.KEY_SIZE
|
||||
// NONCE_SIZE is the chacha20poly1305 nonce size in bytes.
|
||||
NONCE_SIZE :: chacha20.NONCE_SIZE
|
||||
// TAG_SIZE is the chacha20poly1305 tag size in bytes.
|
||||
TAG_SIZE :: poly1305.TAG_SIZE
|
||||
|
||||
@(private)
|
||||
@@ -49,6 +59,8 @@ _update_mac_pad16 :: #force_inline proc (ctx: ^poly1305.Context, x_len: int) {
|
||||
}
|
||||
}
|
||||
|
||||
// encrypt encrypts the plaintext and authenticates the aad and ciphertext,
|
||||
// with the provided key and nonce, stores the output in ciphertext and tag.
|
||||
encrypt :: proc (ciphertext, tag, key, nonce, aad, plaintext: []byte) {
|
||||
_validate_common_slice_sizes(tag, key, nonce, aad, plaintext)
|
||||
if len(ciphertext) != len(plaintext) {
|
||||
@@ -95,6 +107,11 @@ encrypt :: proc (ciphertext, tag, key, nonce, aad, plaintext: []byte) {
|
||||
poly1305.final(&mac_ctx, tag) // Implicitly sanitizes context.
|
||||
}
|
||||
|
||||
// decrypt authenticates the aad and ciphertext, and decrypts the ciphertext,
|
||||
// with the provided key, nonce, and tag, and stores the output in plaintext,
|
||||
// returning true iff the authentication was successful.
|
||||
//
|
||||
// If authentication fails, the destination plaintext buffer will be zeroed.
|
||||
decrypt :: proc (plaintext, tag, key, nonce, aad, ciphertext: []byte) -> bool {
|
||||
_validate_common_slice_sizes(tag, key, nonce, aad, ciphertext)
|
||||
if len(ciphertext) != len(plaintext) {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/*
|
||||
package crypto implements a selection of cryptography algorithms and useful
|
||||
helper routines.
|
||||
*/
|
||||
package crypto
|
||||
|
||||
import "core:mem"
|
||||
@@ -45,6 +49,9 @@ compare_byte_ptrs_constant_time :: proc "contextless" (a, b: ^byte, n: int) -> i
|
||||
// the system entropy source. This routine will block if the system entropy
|
||||
// source is not ready yet. All system entropy source failures are treated
|
||||
// as catastrophic, resulting in a panic.
|
||||
//
|
||||
// Support for the system entropy source can be checked with the
|
||||
// `HAS_RAND_BYTES` boolean constant.
|
||||
rand_bytes :: proc (dst: []byte) {
|
||||
// zero-fill the buffer first
|
||||
mem.zero_explicit(raw_data(dst), len(dst))
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
package ed25519 implements the Ed25519 EdDSA signature algorithm.
|
||||
|
||||
See:
|
||||
- https://datatracker.ietf.org/doc/html/rfc8032
|
||||
- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf
|
||||
- https://eprint.iacr.org/2020/1244.pdf
|
||||
*/
|
||||
package ed25519
|
||||
|
||||
import "core:crypto"
|
||||
import grp "core:crypto/_edwards25519"
|
||||
import "core:crypto/sha2"
|
||||
import "core:mem"
|
||||
|
||||
// PRIVATE_KEY_SIZE is the byte-encoded private key size.
|
||||
PRIVATE_KEY_SIZE :: 32
|
||||
// PUBLIC_KEY_SIZE is the byte-encoded public key size.
|
||||
PUBLIC_KEY_SIZE :: 32
|
||||
// SIGNATURE_SIZE is the byte-encoded signature size.
|
||||
SIGNATURE_SIZE :: 64
|
||||
|
||||
@(private)
|
||||
NONCE_SIZE :: 32
|
||||
|
||||
// Private_Key is an Ed25519 private key.
|
||||
Private_Key :: struct {
|
||||
// WARNING: All of the members are to be treated as internal (ie:
|
||||
// the Private_Key structure is intended to be opaque). There are
|
||||
// subtle vulnerabilities that can be introduced if the internal
|
||||
// values are allowed to be altered.
|
||||
//
|
||||
// See: https://github.com/MystenLabs/ed25519-unsafe-libs
|
||||
_b: [PRIVATE_KEY_SIZE]byte,
|
||||
_s: grp.Scalar,
|
||||
_nonce: [NONCE_SIZE]byte,
|
||||
_pub_key: Public_Key,
|
||||
_is_initialized: bool,
|
||||
}
|
||||
|
||||
// Public_Key is an Ed25519 public key.
|
||||
Public_Key :: struct {
|
||||
// WARNING: All of the members are to be treated as internal (ie:
|
||||
// the Public_Key structure is intended to be opaque).
|
||||
_b: [PUBLIC_KEY_SIZE]byte,
|
||||
_neg_A: grp.Group_Element,
|
||||
_is_valid: bool,
|
||||
_is_initialized: bool,
|
||||
}
|
||||
|
||||
// private_key_set_bytes decodes a byte-encoded private key, and returns
|
||||
// true iff the operation was successful.
|
||||
private_key_set_bytes :: proc(priv_key: ^Private_Key, b: []byte) -> bool {
|
||||
if len(b) != PRIVATE_KEY_SIZE {
|
||||
return false
|
||||
}
|
||||
|
||||
// Derive the private key.
|
||||
ctx: sha2.Context_512 = ---
|
||||
h_bytes: [sha2.DIGEST_SIZE_512]byte = ---
|
||||
sha2.init_512(&ctx)
|
||||
sha2.update(&ctx, b)
|
||||
sha2.final(&ctx, h_bytes[:])
|
||||
|
||||
copy(priv_key._b[:], b)
|
||||
copy(priv_key._nonce[:], h_bytes[32:])
|
||||
grp.sc_set_bytes_rfc8032(&priv_key._s, h_bytes[:32])
|
||||
|
||||
// Derive the corresponding public key.
|
||||
A: grp.Group_Element = ---
|
||||
grp.ge_scalarmult_basepoint(&A, &priv_key._s)
|
||||
grp.ge_bytes(&A, priv_key._pub_key._b[:])
|
||||
grp.ge_negate(&priv_key._pub_key._neg_A, &A)
|
||||
priv_key._pub_key._is_valid = !grp.ge_is_small_order(&A)
|
||||
priv_key._pub_key._is_initialized = true
|
||||
|
||||
priv_key._is_initialized = true
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// private_key_bytes sets dst to byte-encoding of priv_key.
|
||||
private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
|
||||
if !priv_key._is_initialized {
|
||||
panic("crypto/ed25519: uninitialized private key")
|
||||
}
|
||||
if len(dst) != PRIVATE_KEY_SIZE {
|
||||
panic("crypto/ed25519: invalid destination size")
|
||||
}
|
||||
|
||||
copy(dst, priv_key._b[:])
|
||||
}
|
||||
|
||||
// private_key_clear clears priv_key to the uninitialized state.
|
||||
private_key_clear :: proc "contextless" (priv_key: ^Private_Key) {
|
||||
mem.zero_explicit(priv_key, size_of(Private_Key))
|
||||
}
|
||||
|
||||
// sign writes the signature by priv_key over msg to sig.
|
||||
sign :: proc(priv_key: ^Private_Key, msg, sig: []byte) {
|
||||
if !priv_key._is_initialized {
|
||||
panic("crypto/ed25519: uninitialized private key")
|
||||
}
|
||||
if len(sig) != SIGNATURE_SIZE {
|
||||
panic("crypto/ed25519: invalid destination size")
|
||||
}
|
||||
|
||||
// 1. Compute the hash of the private key d, H(d) = (h_0, h_1, ..., h_2b-1)
|
||||
// using SHA-512 for Ed25519. H(d) may be precomputed.
|
||||
//
|
||||
// 2. Using the second half of the digest hdigest2 = hb || ... || h2b-1,
|
||||
// define:
|
||||
//
|
||||
// 2.1 For Ed25519, r = SHA-512(hdigest2 || M); Interpret r as a
|
||||
// 64-octet little-endian integer.
|
||||
ctx: sha2.Context_512 = ---
|
||||
digest_bytes: [sha2.DIGEST_SIZE_512]byte = ---
|
||||
sha2.init_512(&ctx)
|
||||
sha2.update(&ctx, priv_key._nonce[:])
|
||||
sha2.update(&ctx, msg)
|
||||
sha2.final(&ctx, digest_bytes[:])
|
||||
|
||||
r: grp.Scalar = ---
|
||||
grp.sc_set_bytes_wide(&r, &digest_bytes)
|
||||
|
||||
// 3. Compute the point [r]G. The octet string R is the encoding of
|
||||
// the point [r]G.
|
||||
R: grp.Group_Element = ---
|
||||
R_bytes := sig[:32]
|
||||
grp.ge_scalarmult_basepoint(&R, &r)
|
||||
grp.ge_bytes(&R, R_bytes)
|
||||
|
||||
// 4. Derive s from H(d) as in the key pair generation algorithm.
|
||||
// Use octet strings R, Q, and M to define:
|
||||
//
|
||||
// 4.1 For Ed25519, digest = SHA-512(R || Q || M).
|
||||
// Interpret digest as a little-endian integer.
|
||||
sha2.init_512(&ctx)
|
||||
sha2.update(&ctx, R_bytes)
|
||||
sha2.update(&ctx, priv_key._pub_key._b[:]) // Q in NIST terminology.
|
||||
sha2.update(&ctx, msg)
|
||||
sha2.final(&ctx, digest_bytes[:])
|
||||
|
||||
sc: grp.Scalar = --- // `digest` in NIST terminology.
|
||||
grp.sc_set_bytes_wide(&sc, &digest_bytes)
|
||||
|
||||
// 5. Compute S = (r + digest × s) mod n. The octet string S is the
|
||||
// encoding of the resultant integer.
|
||||
grp.sc_mul(&sc, &sc, &priv_key._s)
|
||||
grp.sc_add(&sc, &sc, &r)
|
||||
|
||||
// 6. Form the signature as the concatenation of the octet strings
|
||||
// R and S.
|
||||
grp.sc_bytes(sig[32:], &sc)
|
||||
|
||||
grp.sc_clear(&r)
|
||||
}
|
||||
|
||||
// public_key_set_bytes decodes a byte-encoded public key, and returns
|
||||
// true iff the operation was successful.
|
||||
public_key_set_bytes :: proc "contextless" (pub_key: ^Public_Key, b: []byte) -> bool {
|
||||
if len(b) != PUBLIC_KEY_SIZE {
|
||||
return false
|
||||
}
|
||||
|
||||
A: grp.Group_Element = ---
|
||||
if !grp.ge_set_bytes(&A, b) {
|
||||
return false
|
||||
}
|
||||
|
||||
copy(pub_key._b[:], b)
|
||||
grp.ge_negate(&pub_key._neg_A, &A)
|
||||
pub_key._is_valid = !grp.ge_is_small_order(&A)
|
||||
pub_key._is_initialized = true
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// public_key_set_priv sets pub_key to the public component of priv_key.
|
||||
public_key_set_priv :: proc(pub_key: ^Public_Key, priv_key: ^Private_Key) {
|
||||
if !priv_key._is_initialized {
|
||||
panic("crypto/ed25519: uninitialized public key")
|
||||
}
|
||||
|
||||
src := &priv_key._pub_key
|
||||
copy(pub_key._b[:], src._b[:])
|
||||
grp.ge_set(&pub_key._neg_A, &src._neg_A)
|
||||
pub_key._is_valid = src._is_valid
|
||||
pub_key._is_initialized = src._is_initialized
|
||||
}
|
||||
|
||||
// public_key_bytes sets dst to byte-encoding of pub_key.
|
||||
public_key_bytes :: proc(pub_key: ^Public_Key, dst: []byte) {
|
||||
if !pub_key._is_initialized {
|
||||
panic("crypto/ed25519: uninitialized public key")
|
||||
}
|
||||
if len(dst) != PUBLIC_KEY_SIZE {
|
||||
panic("crypto/ed25519: invalid destination size")
|
||||
}
|
||||
|
||||
copy(dst, pub_key._b[:])
|
||||
}
|
||||
|
||||
// public_key_equal returns true iff pub_key is equal to other.
|
||||
public_key_equal :: proc(pub_key, other: ^Public_Key) -> bool {
|
||||
if !pub_key._is_initialized || !other._is_initialized {
|
||||
panic("crypto/ed25519: uninitialized public key")
|
||||
}
|
||||
|
||||
return crypto.compare_constant_time(pub_key._b[:], other._b[:]) == 1
|
||||
}
|
||||
|
||||
// verify returns true iff sig is a valid signature by pub_key over msg.
|
||||
//
|
||||
// The optional `allow_small_order_A` parameter will make this
|
||||
// implementation strictly compatible with FIPS 186-5, at the expense of
|
||||
// SBS-security. Doing so is NOT recommended, and the disallowed
|
||||
// public keys all have a known discrete-log.
|
||||
verify :: proc(pub_key: ^Public_Key, msg, sig: []byte, allow_small_order_A := false) -> bool {
|
||||
switch {
|
||||
case !pub_key._is_initialized:
|
||||
return false
|
||||
case len(sig) != SIGNATURE_SIZE:
|
||||
return false
|
||||
}
|
||||
|
||||
// TLDR: Just use ristretto255.
|
||||
//
|
||||
// While there are two "standards" for EdDSA, existing implementations
|
||||
// diverge (sometimes dramatically). This implementation opts for
|
||||
// "Algorithm 2" from "Taming the Many EdDSAs", which provides the
|
||||
// strongest notion of security (SUF-CMA + SBS).
|
||||
//
|
||||
// The relevant properties are:
|
||||
// - Reject non-canonical S.
|
||||
// - Reject non-canonical A/R.
|
||||
// - Reject small-order A (Extra non-standard check).
|
||||
// - Cofactored verification equation.
|
||||
//
|
||||
// There are 19 possible non-canonical group element encodings of
|
||||
// which:
|
||||
// - 2 are small order
|
||||
// - 10 are mixed order
|
||||
// - 7 are not on the curve
|
||||
//
|
||||
// While historical implementations have been lax about enforcing
|
||||
// that A/R are canonically encoded, that behavior is mandated by
|
||||
// both the RFC and FIPS specification. No valid key generation
|
||||
// or sign implementation will ever produce non-canonically encoded
|
||||
// public keys or signatures.
|
||||
//
|
||||
// There are 8 small-order group elements, 1 which is in the
|
||||
// prime-order sub-group, and thus the probability that a properly
|
||||
// generated A is small-order is cryptographically insignificant.
|
||||
//
|
||||
// While both the RFC and FIPS standard allow for either the
|
||||
// cofactored or non-cofactored equation. It is possible to
|
||||
// artificially produce signatures that are valid for the former
|
||||
// but not the latter. This will NEVER occur with a valid sign
|
||||
// implementation. The choice of the latter is to be compatible
|
||||
// with ABGLSV-Pornin, batch verification, and FROST (among other
|
||||
// things).
|
||||
|
||||
s_bytes, r_bytes := sig[32:], sig[:32]
|
||||
|
||||
// 1. Reject the signature if S is not in the range [0, L).
|
||||
s: grp.Scalar = ---
|
||||
if !grp.sc_set_bytes(&s, s_bytes) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 2. Reject the signature if the public key A is one of 8 small
|
||||
// order points.
|
||||
//
|
||||
// As this check is optional and not part of the standard, we allow
|
||||
// the caller to bypass it if desired. Disabling the check makes
|
||||
// the scheme NOT SBS-secure.
|
||||
if !pub_key._is_valid && !allow_small_order_A {
|
||||
return false
|
||||
}
|
||||
|
||||
// 3. Reject the signature if A or R are non-canonical.
|
||||
//
|
||||
// Note: All initialized public keys are guaranteed to be canonical.
|
||||
neg_R: grp.Group_Element = ---
|
||||
if !grp.ge_set_bytes(&neg_R, r_bytes) {
|
||||
return false
|
||||
}
|
||||
grp.ge_negate(&neg_R, &neg_R)
|
||||
|
||||
// 4. Compute the hash SHA512(R||A||M) and reduce it mod L to get a
|
||||
// scalar h.
|
||||
ctx: sha2.Context_512 = ---
|
||||
h_bytes: [sha2.DIGEST_SIZE_512]byte = ---
|
||||
sha2.init_512(&ctx)
|
||||
sha2.update(&ctx, r_bytes)
|
||||
sha2.update(&ctx, pub_key._b[:])
|
||||
sha2.update(&ctx, msg)
|
||||
sha2.final(&ctx, h_bytes[:])
|
||||
|
||||
h: grp.Scalar = ---
|
||||
grp.sc_set_bytes_wide(&h, &h_bytes)
|
||||
|
||||
// 5. Accept if 8(s * G) - 8R - 8(h * A) = 0
|
||||
//
|
||||
// > first compute V = SB − R − hA and then accept if V is one of
|
||||
// > 8 small order points (or alternatively compute 8V with 3
|
||||
// > doublings and check against the neutral element)
|
||||
V: grp.Group_Element = ---
|
||||
grp.ge_double_scalarmult_basepoint_vartime(&V, &h, &pub_key._neg_A, &s)
|
||||
grp.ge_add(&V, &V, &neg_R)
|
||||
|
||||
return grp.ge_is_small_order(&V)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
package hash provides a generic interface to the supported hash algorithms.
|
||||
|
||||
A high-level convenience procedure group `hash` is provided to easily
|
||||
accomplish common tasks.
|
||||
- `hash_string` - Hash a given string and return the digest.
|
||||
- `hash_bytes` - Hash a given byte slice and return the digest.
|
||||
- `hash_string_to_buffer` - Hash a given string and put the digest in
|
||||
the third parameter. It requires that the destination buffer
|
||||
is at least as big as the digest size.
|
||||
- `hash_bytes_to_buffer` - Hash a given string and put the computed
|
||||
digest in the third parameter. It requires that the destination
|
||||
buffer is at least as big as the digest size.
|
||||
- `hash_stream` - Incrementally fully consume a `io.Stream`, and return
|
||||
the computed digest.
|
||||
- `hash_file` - Takes a file handle and returns the computed digest.
|
||||
A third optional boolean parameter controls if the file is streamed
|
||||
(default), or or read at once.
|
||||
|
||||
```odin
|
||||
package hash_example
|
||||
|
||||
import "core:crypto/hash"
|
||||
|
||||
main :: proc() {
|
||||
input := "Feed the fire."
|
||||
|
||||
// Compute the digest, using the high level API.
|
||||
returned_digest := hash.hash(hash.Algorithm.SHA512_256, input)
|
||||
defer delete(returned_digest)
|
||||
|
||||
// Variant that takes a destination buffer, instead of returning
|
||||
// the digest.
|
||||
digest := make([]byte, hash.DIGEST_SIZES[hash.Algorithm.BLAKE2B]) // @note: Destination buffer has to be at least as big as the digest size of the hash.
|
||||
defer delete(digest)
|
||||
hash.hash(hash.Algorithm.BLAKE2B, input, digest)
|
||||
}
|
||||
```
|
||||
|
||||
A generic low level API is provided supporting the init/update/final interface
|
||||
that is typical with cryptographic hash function implementations.
|
||||
|
||||
```odin
|
||||
package hash_example
|
||||
|
||||
import "core:crypto/hash"
|
||||
|
||||
main :: proc() {
|
||||
input := "Let the cinders burn."
|
||||
|
||||
// Compute the digest, using the low level API.
|
||||
ctx: hash.Context
|
||||
digest := make([]byte, hash.DIGEST_SIZES[hash.Algorithm.SHA3_512])
|
||||
defer delete(digest)
|
||||
|
||||
hash.init(&ctx, hash.Algorithm.SHA3_512)
|
||||
hash.update(&ctx, transmute([]byte)input)
|
||||
hash.final(&ctx, digest)
|
||||
}
|
||||
```
|
||||
*/
|
||||
package crypto_hash
|
||||
@@ -0,0 +1,116 @@
|
||||
package crypto_hash
|
||||
|
||||
/*
|
||||
Copyright 2021 zhibog
|
||||
Made available under the BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
*/
|
||||
|
||||
import "core:io"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
|
||||
// hash_bytes will hash the given input and return the computed digest
|
||||
// in a newly allocated slice.
|
||||
hash_string :: proc(algorithm: Algorithm, data: string, allocator := context.allocator) -> []byte {
|
||||
return hash_bytes(algorithm, transmute([]byte)(data), allocator)
|
||||
}
|
||||
|
||||
// hash_bytes will hash the given input and return the computed digest
|
||||
// in a newly allocated slice.
|
||||
hash_bytes :: proc(algorithm: Algorithm, data: []byte, allocator := context.allocator) -> []byte {
|
||||
dst := make([]byte, DIGEST_SIZES[algorithm], allocator)
|
||||
hash_bytes_to_buffer(algorithm, data, dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
// hash_string_to_buffer will hash the given input and assign the
|
||||
// computed digest to the third parameter. It requires that the
|
||||
// destination buffer is at least as big as the digest size.
|
||||
hash_string_to_buffer :: proc(algorithm: Algorithm, data: string, hash: []byte) {
|
||||
hash_bytes_to_buffer(algorithm, transmute([]byte)(data), hash)
|
||||
}
|
||||
|
||||
// hash_bytes_to_buffer will hash the given input and write the
|
||||
// computed digest into the third parameter. It requires that the
|
||||
// destination buffer is at least as big as the digest size.
|
||||
hash_bytes_to_buffer :: proc(algorithm: Algorithm, data, hash: []byte) {
|
||||
ctx: Context
|
||||
|
||||
init(&ctx, algorithm)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash)
|
||||
}
|
||||
|
||||
// hash_stream will incrementally fully consume a stream, and return the
|
||||
// computed digest in a newly allocated slice.
|
||||
hash_stream :: proc(
|
||||
algorithm: Algorithm,
|
||||
s: io.Stream,
|
||||
allocator := context.allocator,
|
||||
) -> (
|
||||
[]byte,
|
||||
io.Error,
|
||||
) {
|
||||
ctx: Context
|
||||
|
||||
buf: [MAX_BLOCK_SIZE * 4]byte
|
||||
defer mem.zero_explicit(&buf, size_of(buf))
|
||||
|
||||
init(&ctx, algorithm)
|
||||
|
||||
loop: for {
|
||||
n, err := io.read(s, buf[:])
|
||||
if n > 0 {
|
||||
// XXX/yawning: Can io.read return n > 0 and EOF?
|
||||
update(&ctx, buf[:n])
|
||||
}
|
||||
#partial switch err {
|
||||
case .None:
|
||||
case .EOF:
|
||||
break loop
|
||||
case:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
dst := make([]byte, DIGEST_SIZES[algorithm], allocator)
|
||||
final(&ctx, dst)
|
||||
|
||||
return dst, io.Error.None
|
||||
}
|
||||
|
||||
// hash_file will read the file provided by the given handle and return the
|
||||
// computed digest in a newly allocated slice.
|
||||
hash_file :: proc(
|
||||
algorithm: Algorithm,
|
||||
hd: os.Handle,
|
||||
load_at_once := false,
|
||||
allocator := context.allocator,
|
||||
) -> (
|
||||
[]byte,
|
||||
io.Error,
|
||||
) {
|
||||
if !load_at_once {
|
||||
return hash_stream(algorithm, os.stream_from_handle(hd), allocator)
|
||||
}
|
||||
|
||||
buf, ok := os.read_entire_file(hd, allocator)
|
||||
if !ok {
|
||||
return nil, io.Error.Unknown
|
||||
}
|
||||
defer delete(buf, allocator)
|
||||
|
||||
return hash_bytes(algorithm, buf, allocator), io.Error.None
|
||||
}
|
||||
|
||||
hash :: proc {
|
||||
hash_stream,
|
||||
hash_file,
|
||||
hash_bytes,
|
||||
hash_string,
|
||||
hash_bytes_to_buffer,
|
||||
hash_string_to_buffer,
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
package crypto_hash
|
||||
|
||||
import "core:crypto/blake2b"
|
||||
import "core:crypto/blake2s"
|
||||
import "core:crypto/sha2"
|
||||
import "core:crypto/sha3"
|
||||
import "core:crypto/sm3"
|
||||
import "core:crypto/legacy/keccak"
|
||||
import "core:crypto/legacy/md5"
|
||||
import "core:crypto/legacy/sha1"
|
||||
|
||||
import "core:reflect"
|
||||
|
||||
// MAX_DIGEST_SIZE is the maximum size digest that can be returned by any
|
||||
// of the Algorithms supported via this package.
|
||||
MAX_DIGEST_SIZE :: 64
|
||||
// MAX_BLOCK_SIZE is the maximum block size used by any of Algorithms
|
||||
// supported by this package.
|
||||
MAX_BLOCK_SIZE :: sha3.BLOCK_SIZE_224
|
||||
|
||||
// Algorithm is the algorithm identifier associated with a given Context.
|
||||
Algorithm :: enum {
|
||||
Invalid,
|
||||
BLAKE2B,
|
||||
BLAKE2S,
|
||||
SHA224,
|
||||
SHA256,
|
||||
SHA384,
|
||||
SHA512,
|
||||
SHA512_256,
|
||||
SHA3_224,
|
||||
SHA3_256,
|
||||
SHA3_384,
|
||||
SHA3_512,
|
||||
SM3,
|
||||
Legacy_KECCAK_224,
|
||||
Legacy_KECCAK_256,
|
||||
Legacy_KECCAK_384,
|
||||
Legacy_KECCAK_512,
|
||||
Insecure_MD5,
|
||||
Insecure_SHA1,
|
||||
}
|
||||
|
||||
// ALGORITHM_NAMES is the Algorithm to algorithm name string.
|
||||
ALGORITHM_NAMES := [Algorithm]string {
|
||||
.Invalid = "Invalid",
|
||||
.BLAKE2B = "BLAKE2b",
|
||||
.BLAKE2S = "BLAKE2s",
|
||||
.SHA224 = "SHA-224",
|
||||
.SHA256 = "SHA-256",
|
||||
.SHA384 = "SHA-384",
|
||||
.SHA512 = "SHA-512",
|
||||
.SHA512_256 = "SHA-512/256",
|
||||
.SHA3_224 = "SHA3-224",
|
||||
.SHA3_256 = "SHA3-256",
|
||||
.SHA3_384 = "SHA3-384",
|
||||
.SHA3_512 = "SHA3-512",
|
||||
.SM3 = "SM3",
|
||||
.Legacy_KECCAK_224 = "Keccak-224",
|
||||
.Legacy_KECCAK_256 = "Keccak-256",
|
||||
.Legacy_KECCAK_384 = "Keccak-384",
|
||||
.Legacy_KECCAK_512 = "Keccak-512",
|
||||
.Insecure_MD5 = "MD5",
|
||||
.Insecure_SHA1 = "SHA-1",
|
||||
}
|
||||
|
||||
// DIGEST_SIZES is the Algorithm to digest size in bytes.
|
||||
DIGEST_SIZES := [Algorithm]int {
|
||||
.Invalid = 0,
|
||||
.BLAKE2B = blake2b.DIGEST_SIZE,
|
||||
.BLAKE2S = blake2s.DIGEST_SIZE,
|
||||
.SHA224 = sha2.DIGEST_SIZE_224,
|
||||
.SHA256 = sha2.DIGEST_SIZE_256,
|
||||
.SHA384 = sha2.DIGEST_SIZE_384,
|
||||
.SHA512 = sha2.DIGEST_SIZE_512,
|
||||
.SHA512_256 = sha2.DIGEST_SIZE_512_256,
|
||||
.SHA3_224 = sha3.DIGEST_SIZE_224,
|
||||
.SHA3_256 = sha3.DIGEST_SIZE_256,
|
||||
.SHA3_384 = sha3.DIGEST_SIZE_384,
|
||||
.SHA3_512 = sha3.DIGEST_SIZE_512,
|
||||
.SM3 = sm3.DIGEST_SIZE,
|
||||
.Legacy_KECCAK_224 = keccak.DIGEST_SIZE_224,
|
||||
.Legacy_KECCAK_256 = keccak.DIGEST_SIZE_256,
|
||||
.Legacy_KECCAK_384 = keccak.DIGEST_SIZE_384,
|
||||
.Legacy_KECCAK_512 = keccak.DIGEST_SIZE_512,
|
||||
.Insecure_MD5 = md5.DIGEST_SIZE,
|
||||
.Insecure_SHA1 = sha1.DIGEST_SIZE,
|
||||
}
|
||||
|
||||
// BLOCK_SIZES is the Algoritm to block size in bytes.
|
||||
BLOCK_SIZES := [Algorithm]int {
|
||||
.Invalid = 0,
|
||||
.BLAKE2B = blake2b.BLOCK_SIZE,
|
||||
.BLAKE2S = blake2s.BLOCK_SIZE,
|
||||
.SHA224 = sha2.BLOCK_SIZE_256,
|
||||
.SHA256 = sha2.BLOCK_SIZE_256,
|
||||
.SHA384 = sha2.BLOCK_SIZE_512,
|
||||
.SHA512 = sha2.BLOCK_SIZE_512,
|
||||
.SHA512_256 = sha2.BLOCK_SIZE_512,
|
||||
.SHA3_224 = sha3.BLOCK_SIZE_224,
|
||||
.SHA3_256 = sha3.BLOCK_SIZE_256,
|
||||
.SHA3_384 = sha3.BLOCK_SIZE_384,
|
||||
.SHA3_512 = sha3.BLOCK_SIZE_512,
|
||||
.SM3 = sm3.BLOCK_SIZE,
|
||||
.Legacy_KECCAK_224 = keccak.BLOCK_SIZE_224,
|
||||
.Legacy_KECCAK_256 = keccak.BLOCK_SIZE_256,
|
||||
.Legacy_KECCAK_384 = keccak.BLOCK_SIZE_384,
|
||||
.Legacy_KECCAK_512 = keccak.BLOCK_SIZE_512,
|
||||
.Insecure_MD5 = md5.BLOCK_SIZE,
|
||||
.Insecure_SHA1 = sha1.BLOCK_SIZE,
|
||||
}
|
||||
|
||||
// Context is a concrete instantiation of a specific hash algorithm.
|
||||
Context :: struct {
|
||||
_algo: Algorithm,
|
||||
_impl: union {
|
||||
blake2b.Context,
|
||||
blake2s.Context,
|
||||
sha2.Context_256,
|
||||
sha2.Context_512,
|
||||
sha3.Context,
|
||||
sm3.Context,
|
||||
keccak.Context,
|
||||
md5.Context,
|
||||
sha1.Context,
|
||||
},
|
||||
}
|
||||
|
||||
@(private)
|
||||
_IMPL_IDS := [Algorithm]typeid {
|
||||
.Invalid = nil,
|
||||
.BLAKE2B = typeid_of(blake2b.Context),
|
||||
.BLAKE2S = typeid_of(blake2s.Context),
|
||||
.SHA224 = typeid_of(sha2.Context_256),
|
||||
.SHA256 = typeid_of(sha2.Context_256),
|
||||
.SHA384 = typeid_of(sha2.Context_512),
|
||||
.SHA512 = typeid_of(sha2.Context_512),
|
||||
.SHA512_256 = typeid_of(sha2.Context_512),
|
||||
.SHA3_224 = typeid_of(sha3.Context),
|
||||
.SHA3_256 = typeid_of(sha3.Context),
|
||||
.SHA3_384 = typeid_of(sha3.Context),
|
||||
.SHA3_512 = typeid_of(sha3.Context),
|
||||
.SM3 = typeid_of(sm3.Context),
|
||||
.Legacy_KECCAK_224 = typeid_of(keccak.Context),
|
||||
.Legacy_KECCAK_256 = typeid_of(keccak.Context),
|
||||
.Legacy_KECCAK_384 = typeid_of(keccak.Context),
|
||||
.Legacy_KECCAK_512 = typeid_of(keccak.Context),
|
||||
.Insecure_MD5 = typeid_of(md5.Context),
|
||||
.Insecure_SHA1 = typeid_of(sha1.Context),
|
||||
}
|
||||
|
||||
// init initializes a Context with a specific hash Algorithm.
|
||||
init :: proc(ctx: ^Context, algorithm: Algorithm) {
|
||||
if ctx._impl != nil {
|
||||
reset(ctx)
|
||||
}
|
||||
|
||||
// Directly specialize the union by setting the type ID (save a copy).
|
||||
reflect.set_union_variant_typeid(
|
||||
ctx._impl,
|
||||
_IMPL_IDS[algorithm],
|
||||
)
|
||||
switch algorithm {
|
||||
case .BLAKE2B:
|
||||
blake2b.init(&ctx._impl.(blake2b.Context))
|
||||
case .BLAKE2S:
|
||||
blake2s.init(&ctx._impl.(blake2s.Context))
|
||||
case .SHA224:
|
||||
sha2.init_224(&ctx._impl.(sha2.Context_256))
|
||||
case .SHA256:
|
||||
sha2.init_256(&ctx._impl.(sha2.Context_256))
|
||||
case .SHA384:
|
||||
sha2.init_384(&ctx._impl.(sha2.Context_512))
|
||||
case .SHA512:
|
||||
sha2.init_512(&ctx._impl.(sha2.Context_512))
|
||||
case .SHA512_256:
|
||||
sha2.init_512_256(&ctx._impl.(sha2.Context_512))
|
||||
case .SHA3_224:
|
||||
sha3.init_224(&ctx._impl.(sha3.Context))
|
||||
case .SHA3_256:
|
||||
sha3.init_256(&ctx._impl.(sha3.Context))
|
||||
case .SHA3_384:
|
||||
sha3.init_384(&ctx._impl.(sha3.Context))
|
||||
case .SHA3_512:
|
||||
sha3.init_512(&ctx._impl.(sha3.Context))
|
||||
case .SM3:
|
||||
sm3.init(&ctx._impl.(sm3.Context))
|
||||
case .Legacy_KECCAK_224:
|
||||
keccak.init_224(&ctx._impl.(keccak.Context))
|
||||
case .Legacy_KECCAK_256:
|
||||
keccak.init_256(&ctx._impl.(keccak.Context))
|
||||
case .Legacy_KECCAK_384:
|
||||
keccak.init_384(&ctx._impl.(keccak.Context))
|
||||
case .Legacy_KECCAK_512:
|
||||
keccak.init_512(&ctx._impl.(keccak.Context))
|
||||
case .Insecure_MD5:
|
||||
md5.init(&ctx._impl.(md5.Context))
|
||||
case .Insecure_SHA1:
|
||||
sha1.init(&ctx._impl.(sha1.Context))
|
||||
case .Invalid:
|
||||
panic("crypto/hash: uninitialized algorithm")
|
||||
case:
|
||||
panic("crypto/hash: invalid algorithm")
|
||||
}
|
||||
|
||||
ctx._algo = algorithm
|
||||
}
|
||||
|
||||
// update adds more data to the Context.
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
switch &impl in ctx._impl {
|
||||
case blake2b.Context:
|
||||
blake2b.update(&impl, data)
|
||||
case blake2s.Context:
|
||||
blake2s.update(&impl, data)
|
||||
case sha2.Context_256:
|
||||
sha2.update(&impl, data)
|
||||
case sha2.Context_512:
|
||||
sha2.update(&impl, data)
|
||||
case sha3.Context:
|
||||
sha3.update(&impl, data)
|
||||
case sm3.Context:
|
||||
sm3.update(&impl, data)
|
||||
case keccak.Context:
|
||||
keccak.update(&impl, data)
|
||||
case md5.Context:
|
||||
md5.update(&impl, data)
|
||||
case sha1.Context:
|
||||
sha1.update(&impl, data)
|
||||
case:
|
||||
panic("crypto/hash: uninitialized algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
// final finalizes the Context, writes the digest to hash, and calls
|
||||
// reset on the Context.
|
||||
//
|
||||
// Iff finalize_clone is set, final will work on a copy of the Context,
|
||||
// which is useful for for calculating rolling digests.
|
||||
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
switch &impl in ctx._impl {
|
||||
case blake2b.Context:
|
||||
blake2b.final(&impl, hash, finalize_clone)
|
||||
case blake2s.Context:
|
||||
blake2s.final(&impl, hash, finalize_clone)
|
||||
case sha2.Context_256:
|
||||
sha2.final(&impl, hash, finalize_clone)
|
||||
case sha2.Context_512:
|
||||
sha2.final(&impl, hash, finalize_clone)
|
||||
case sha3.Context:
|
||||
sha3.final(&impl, hash, finalize_clone)
|
||||
case sm3.Context:
|
||||
sm3.final(&impl, hash, finalize_clone)
|
||||
case keccak.Context:
|
||||
keccak.final(&impl, hash, finalize_clone)
|
||||
case md5.Context:
|
||||
md5.final(&impl, hash, finalize_clone)
|
||||
case sha1.Context:
|
||||
sha1.final(&impl, hash, finalize_clone)
|
||||
case:
|
||||
panic("crypto/hash: uninitialized algorithm")
|
||||
}
|
||||
|
||||
if !finalize_clone {
|
||||
reset(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// clone clones the Context other into ctx.
|
||||
clone :: proc(ctx, other: ^Context) {
|
||||
// XXX/yawning: Maybe these cases should panic, because both cases,
|
||||
// are probably bugs.
|
||||
if ctx == other {
|
||||
return
|
||||
}
|
||||
if ctx._impl != nil {
|
||||
reset(ctx)
|
||||
}
|
||||
|
||||
ctx._algo = other._algo
|
||||
|
||||
reflect.set_union_variant_typeid(
|
||||
ctx._impl,
|
||||
reflect.union_variant_typeid(other._impl),
|
||||
)
|
||||
switch &src_impl in other._impl {
|
||||
case blake2b.Context:
|
||||
blake2b.clone(&ctx._impl.(blake2b.Context), &src_impl)
|
||||
case blake2s.Context:
|
||||
blake2s.clone(&ctx._impl.(blake2s.Context), &src_impl)
|
||||
case sha2.Context_256:
|
||||
sha2.clone(&ctx._impl.(sha2.Context_256), &src_impl)
|
||||
case sha2.Context_512:
|
||||
sha2.clone(&ctx._impl.(sha2.Context_512), &src_impl)
|
||||
case sha3.Context:
|
||||
sha3.clone(&ctx._impl.(sha3.Context), &src_impl)
|
||||
case sm3.Context:
|
||||
sm3.clone(&ctx._impl.(sm3.Context), &src_impl)
|
||||
case keccak.Context:
|
||||
keccak.clone(&ctx._impl.(keccak.Context), &src_impl)
|
||||
case md5.Context:
|
||||
md5.clone(&ctx._impl.(md5.Context), &src_impl)
|
||||
case sha1.Context:
|
||||
sha1.clone(&ctx._impl.(sha1.Context), &src_impl)
|
||||
case:
|
||||
panic("crypto/hash: uninitialized algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
switch &impl in ctx._impl {
|
||||
case blake2b.Context:
|
||||
blake2b.reset(&impl)
|
||||
case blake2s.Context:
|
||||
blake2s.reset(&impl)
|
||||
case sha2.Context_256:
|
||||
sha2.reset(&impl)
|
||||
case sha2.Context_512:
|
||||
sha2.reset(&impl)
|
||||
case sha3.Context:
|
||||
sha3.reset(&impl)
|
||||
case sm3.Context:
|
||||
sm3.reset(&impl)
|
||||
case keccak.Context:
|
||||
keccak.reset(&impl)
|
||||
case md5.Context:
|
||||
md5.reset(&impl)
|
||||
case sha1.Context:
|
||||
sha1.reset(&impl)
|
||||
case:
|
||||
// Unlike clone, calling reset repeatedly is fine.
|
||||
}
|
||||
|
||||
ctx._algo = .Invalid
|
||||
ctx._impl = nil
|
||||
}
|
||||
|
||||
// algorithm returns the Algorithm used by a Context instance.
|
||||
algorithm :: proc(ctx: ^Context) -> Algorithm {
|
||||
return ctx._algo
|
||||
}
|
||||
|
||||
// digest_size returns the digest size of a Context instance in bytes.
|
||||
digest_size :: proc(ctx: ^Context) -> int {
|
||||
return DIGEST_SIZES[ctx._algo]
|
||||
}
|
||||
|
||||
// block_size returns the block size of a Context instance in bytes.
|
||||
block_size :: proc(ctx: ^Context) -> int {
|
||||
return BLOCK_SIZES[ctx._algo]
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
package hkdf implements the HKDF HMAC-based Extract-and-Expand Key
|
||||
Derivation Function.
|
||||
|
||||
See: https://www.rfc-editor.org/rfc/rfc5869
|
||||
*/
|
||||
package hkdf
|
||||
|
||||
import "core:crypto/hash"
|
||||
import "core:crypto/hmac"
|
||||
import "core:mem"
|
||||
|
||||
// extract_and_expand derives output keying material (OKM) via the
|
||||
// HKDF-Extract and HKDF-Expand algorithms, with the specified has
|
||||
// function, salt, input keying material (IKM), and optional info.
|
||||
// The dst buffer must be less-than-or-equal to 255 HMAC tags.
|
||||
extract_and_expand :: proc(algorithm: hash.Algorithm, salt, ikm, info, dst: []byte) {
|
||||
h_len := hash.DIGEST_SIZES[algorithm]
|
||||
|
||||
tmp: [hash.MAX_DIGEST_SIZE]byte
|
||||
prk := tmp[:h_len]
|
||||
defer mem.zero_explicit(raw_data(prk), h_len)
|
||||
|
||||
extract(algorithm, salt, ikm, prk)
|
||||
expand(algorithm, prk, info, dst)
|
||||
}
|
||||
|
||||
// extract derives a pseudorandom key (PRK) via the HKDF-Extract algorithm,
|
||||
// with the specified hash function, salt, and input keying material (IKM).
|
||||
// It requires that the dst buffer be the HMAC tag size for the specified
|
||||
// hash function.
|
||||
extract :: proc(algorithm: hash.Algorithm, salt, ikm, dst: []byte) {
|
||||
// PRK = HMAC-Hash(salt, IKM)
|
||||
hmac.sum(algorithm, dst, ikm, salt)
|
||||
}
|
||||
|
||||
// expand derives output keying material (OKM) via the HKDF-Expand algorithm,
|
||||
// with the specified hash function, pseudorandom key (PRK), and optional
|
||||
// info. The dst buffer must be less-than-or-equal to 255 HMAC tags.
|
||||
expand :: proc(algorithm: hash.Algorithm, prk, info, dst: []byte) {
|
||||
h_len := hash.DIGEST_SIZES[algorithm]
|
||||
|
||||
// (<= 255*HashLen)
|
||||
dk_len := len(dst)
|
||||
switch {
|
||||
case dk_len == 0:
|
||||
return
|
||||
case dk_len > h_len * 255:
|
||||
panic("crypto/hkdf: derived key too long")
|
||||
case:
|
||||
}
|
||||
|
||||
// The output OKM is calculated as follows:
|
||||
//
|
||||
// N = ceil(L/HashLen)
|
||||
// T = T(1) | T(2) | T(3) | ... | T(N)
|
||||
// OKM = first L octets of T
|
||||
//
|
||||
// where:
|
||||
// T(0) = empty string (zero length)
|
||||
// T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
|
||||
// T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
|
||||
// T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
|
||||
// ...
|
||||
|
||||
n := dk_len / h_len
|
||||
r := dk_len % h_len
|
||||
|
||||
base: hmac.Context
|
||||
defer hmac.reset(&base)
|
||||
|
||||
hmac.init(&base, algorithm, prk)
|
||||
|
||||
dst_blk := dst
|
||||
prev: []byte
|
||||
|
||||
for i in 1 ..= n {
|
||||
_F(&base, prev, info, i, dst_blk[:h_len])
|
||||
|
||||
prev = dst_blk[:h_len]
|
||||
dst_blk = dst_blk[h_len:]
|
||||
}
|
||||
|
||||
if r > 0 {
|
||||
tmp: [hash.MAX_DIGEST_SIZE]byte
|
||||
blk := tmp[:h_len]
|
||||
defer mem.zero_explicit(raw_data(blk), h_len)
|
||||
|
||||
_F(&base, prev, info, n + 1, blk)
|
||||
copy(dst_blk, blk)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_F :: proc(base: ^hmac.Context, prev, info: []byte, i: int, dst_blk: []byte) {
|
||||
prf: hmac.Context
|
||||
|
||||
hmac.clone(&prf, base)
|
||||
hmac.update(&prf, prev)
|
||||
hmac.update(&prf, info)
|
||||
hmac.update(&prf, []byte{u8(i)})
|
||||
hmac.final(&prf, dst_blk)
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
package hmac implements the HMAC MAC algorithm.
|
||||
|
||||
See:
|
||||
- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.198-1.pdf
|
||||
*/
|
||||
package hmac
|
||||
|
||||
import "core:crypto"
|
||||
import "core:crypto/hash"
|
||||
import "core:mem"
|
||||
|
||||
// sum will compute the HMAC with the specified algorithm and key
|
||||
// over msg, and write the computed tag to dst. It requires that
|
||||
// the dst buffer is the tag size.
|
||||
sum :: proc(algorithm: hash.Algorithm, dst, msg, key: []byte) {
|
||||
ctx: Context
|
||||
|
||||
init(&ctx, algorithm, key)
|
||||
update(&ctx, msg)
|
||||
final(&ctx, dst)
|
||||
}
|
||||
|
||||
// verify will verify the HMAC tag computed with the specified algorithm
|
||||
// and key over msg and return true iff the tag is valid. It requires
|
||||
// that the tag is correctly sized.
|
||||
verify :: proc(algorithm: hash.Algorithm, tag, msg, key: []byte) -> bool {
|
||||
tag_buf: [hash.MAX_DIGEST_SIZE]byte
|
||||
|
||||
derived_tag := tag_buf[:hash.DIGEST_SIZES[algorithm]]
|
||||
sum(algorithm, derived_tag, msg, key)
|
||||
|
||||
return crypto.compare_constant_time(derived_tag, tag) == 1
|
||||
}
|
||||
|
||||
// Context is a concrete instantiation of HMAC with a specific hash
|
||||
// algorithm.
|
||||
Context :: struct {
|
||||
_o_hash: hash.Context, // H(k ^ ipad) (not finalized)
|
||||
_i_hash: hash.Context, // H(k ^ opad) (not finalized)
|
||||
_tag_sz: int,
|
||||
_is_initialized: bool,
|
||||
}
|
||||
|
||||
// init initializes a Context with a specific hash Algorithm and key.
|
||||
init :: proc(ctx: ^Context, algorithm: hash.Algorithm, key: []byte) {
|
||||
if ctx._is_initialized {
|
||||
reset(ctx)
|
||||
}
|
||||
|
||||
_init_hashes(ctx, algorithm, key)
|
||||
|
||||
ctx._tag_sz = hash.DIGEST_SIZES[algorithm]
|
||||
ctx._is_initialized = true
|
||||
}
|
||||
|
||||
// update adds more data to the Context.
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
hash.update(&ctx._i_hash, data)
|
||||
}
|
||||
|
||||
// final finalizes the Context, writes the tag to dst, and calls
|
||||
// reset on the Context.
|
||||
final :: proc(ctx: ^Context, dst: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
defer (reset(ctx))
|
||||
|
||||
if len(dst) != ctx._tag_sz {
|
||||
panic("crypto/hmac: invalid destination tag size")
|
||||
}
|
||||
|
||||
hash.final(&ctx._i_hash, dst) // H((k ^ ipad) || text)
|
||||
|
||||
hash.update(&ctx._o_hash, dst) // H((k ^ opad) || H((k ^ ipad) || text))
|
||||
hash.final(&ctx._o_hash, dst)
|
||||
}
|
||||
|
||||
// clone clones the Context other into ctx.
|
||||
clone :: proc(ctx, other: ^Context) {
|
||||
if ctx == other {
|
||||
return
|
||||
}
|
||||
|
||||
hash.clone(&ctx._o_hash, &other._o_hash)
|
||||
hash.clone(&ctx._i_hash, &other._i_hash)
|
||||
ctx._tag_sz = other._tag_sz
|
||||
ctx._is_initialized = other._is_initialized
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
if !ctx._is_initialized {
|
||||
return
|
||||
}
|
||||
|
||||
hash.reset(&ctx._o_hash)
|
||||
hash.reset(&ctx._i_hash)
|
||||
ctx._tag_sz = 0
|
||||
ctx._is_initialized = false
|
||||
}
|
||||
|
||||
// algorithm returns the Algorithm used by a Context instance.
|
||||
algorithm :: proc(ctx: ^Context) -> hash.Algorithm {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
return hash.algorithm(&ctx._i_hash)
|
||||
}
|
||||
|
||||
// tag_size returns the tag size of a Context instance in bytes.
|
||||
tag_size :: proc(ctx: ^Context) -> int {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
return ctx._tag_sz
|
||||
}
|
||||
|
||||
@(private)
|
||||
_I_PAD :: 0x36
|
||||
_O_PAD :: 0x5c
|
||||
|
||||
@(private)
|
||||
_init_hashes :: proc(ctx: ^Context, algorithm: hash.Algorithm, key: []byte) {
|
||||
K0_buf: [hash.MAX_BLOCK_SIZE]byte
|
||||
kPad_buf: [hash.MAX_BLOCK_SIZE]byte
|
||||
|
||||
kLen := len(key)
|
||||
B := hash.BLOCK_SIZES[algorithm]
|
||||
K0 := K0_buf[:B]
|
||||
defer mem.zero_explicit(raw_data(K0), B)
|
||||
|
||||
switch {
|
||||
case kLen == B, kLen < B:
|
||||
// If the length of K = B: set K0 = K.
|
||||
//
|
||||
// If the length of K < B: append zeros to the end of K to
|
||||
// create a B-byte string K0 (e.g., if K is 20 bytes in
|
||||
// length and B = 64, then K will be appended with 44 zero
|
||||
// bytes x’00’).
|
||||
//
|
||||
// K0 is zero-initialized, so the copy handles both cases.
|
||||
copy(K0, key)
|
||||
case kLen > B:
|
||||
// If the length of K > B: hash K to obtain an L byte string,
|
||||
// then append (B-L) zeros to create a B-byte string K0
|
||||
// (i.e., K0 = H(K) || 00...00).
|
||||
tmpCtx := &ctx._o_hash // Saves allocating a hash.Context.
|
||||
hash.init(tmpCtx, algorithm)
|
||||
hash.update(tmpCtx, key)
|
||||
hash.final(tmpCtx, K0)
|
||||
}
|
||||
|
||||
// Initialize the hashes, and write the padded keys:
|
||||
// - ctx._i_hash -> H(K0 ^ ipad)
|
||||
// - ctx._o_hash -> H(K0 ^ opad)
|
||||
|
||||
hash.init(&ctx._o_hash, algorithm)
|
||||
hash.init(&ctx._i_hash, algorithm)
|
||||
|
||||
kPad := kPad_buf[:B]
|
||||
defer mem.zero_explicit(raw_data(kPad), B)
|
||||
|
||||
for v, i in K0 {
|
||||
kPad[i] = v ~ _I_PAD
|
||||
}
|
||||
hash.update(&ctx._i_hash, kPad)
|
||||
|
||||
for v, i in K0 {
|
||||
kPad[i] = v ~ _O_PAD
|
||||
}
|
||||
hash.update(&ctx._o_hash, kPad)
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
package kmac implements the KMAC MAC algorithm.
|
||||
|
||||
See:
|
||||
- https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf
|
||||
*/
|
||||
package kmac
|
||||
|
||||
import "../_sha3"
|
||||
import "core:crypto"
|
||||
import "core:crypto/shake"
|
||||
|
||||
// MIN_KEY_SIZE_128 is the minimum key size for KMAC128 in bytes.
|
||||
MIN_KEY_SIZE_128 :: 128 / 8
|
||||
// MIN_KEY_SIZE_256 is the minimum key size for KMAC256 in bytes.
|
||||
MIN_KEY_SIZE_256 :: 256 / 8
|
||||
|
||||
// MIN_TAG_SIZE is the absolute minimum tag size for KMAC in bytes (8.4.2).
|
||||
// Most callers SHOULD use at least 128-bits if not 256-bits for the tag
|
||||
// size.
|
||||
MIN_TAG_SIZE :: 32 / 8
|
||||
|
||||
// sum will compute the KMAC with the specified security strength,
|
||||
// key, and domain separator over msg, and write the computed digest to
|
||||
// dst.
|
||||
sum :: proc(sec_strength: int, dst, msg, key, domain_sep: []byte) {
|
||||
ctx: Context
|
||||
|
||||
_init_kmac(&ctx, key, domain_sep, sec_strength)
|
||||
update(&ctx, msg)
|
||||
final(&ctx, dst)
|
||||
}
|
||||
|
||||
// verify will verify the KMAC tag computed with the specified security
|
||||
// strength, key and domain separator over msg and return true iff the
|
||||
// tag is valid.
|
||||
verify :: proc(sec_strength: int, tag, msg, key, domain_sep: []byte, allocator := context.temp_allocator) -> bool {
|
||||
derived_tag := make([]byte, len(tag), allocator)
|
||||
|
||||
sum(sec_strength, derived_tag, msg, key, domain_sep)
|
||||
|
||||
return crypto.compare_constant_time(derived_tag, tag) == 1
|
||||
}
|
||||
|
||||
// Context is a KMAC instance.
|
||||
Context :: distinct shake.Context
|
||||
|
||||
// init_128 initializes a Context for KMAC28. This routine will panic if
|
||||
// the key length is less than MIN_KEY_SIZE_128.
|
||||
init_128 :: proc(ctx: ^Context, key, domain_sep: []byte) {
|
||||
_init_kmac(ctx, key, domain_sep, 128)
|
||||
}
|
||||
|
||||
// init_256 initializes a Context for KMAC256. This routine will panic if
|
||||
// the key length is less than MIN_KEY_SIZE_256.
|
||||
init_256 :: proc(ctx: ^Context, key, domain_sep: []byte) {
|
||||
_init_kmac(ctx, key, domain_sep, 256)
|
||||
}
|
||||
|
||||
// update adds more data to the Context.
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
assert(ctx.is_initialized)
|
||||
|
||||
shake.write(transmute(^shake.Context)(ctx), data)
|
||||
}
|
||||
|
||||
// final finalizes the Context, writes the tag to dst, and calls reset
|
||||
// on the Context. This routine will panic if the dst length is less than
|
||||
// MIN_TAG_SIZE.
|
||||
final :: proc(ctx: ^Context, dst: []byte) {
|
||||
assert(ctx.is_initialized)
|
||||
defer reset(ctx)
|
||||
|
||||
if len(dst) < MIN_TAG_SIZE {
|
||||
panic("crypto/kmac: invalid KMAC tag_size, too short")
|
||||
}
|
||||
|
||||
_sha3.final_cshake(transmute(^_sha3.Context)(ctx), dst)
|
||||
}
|
||||
|
||||
// clone clones the Context other into ctx.
|
||||
clone :: proc(ctx, other: ^Context) {
|
||||
if ctx == other {
|
||||
return
|
||||
}
|
||||
|
||||
shake.clone(transmute(^shake.Context)(ctx), transmute(^shake.Context)(other))
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
if !ctx.is_initialized {
|
||||
return
|
||||
}
|
||||
|
||||
shake.reset(transmute(^shake.Context)(ctx))
|
||||
}
|
||||
|
||||
@(private)
|
||||
_init_kmac :: proc(ctx: ^Context, key, s: []byte, sec_strength: int) {
|
||||
if ctx.is_initialized {
|
||||
reset(ctx)
|
||||
}
|
||||
|
||||
if len(key) < sec_strength / 8 {
|
||||
panic("crypto/kmac: invalid KMAC key, too short")
|
||||
}
|
||||
|
||||
ctx_ := transmute(^_sha3.Context)(ctx)
|
||||
_sha3.init_cshake(ctx_, N_KMAC, s, sec_strength)
|
||||
_sha3.bytepad(ctx_, [][]byte{key}, _sha3.rate_cshake(sec_strength))
|
||||
}
|
||||
|
||||
@(private)
|
||||
N_KMAC := []byte{'K', 'M', 'A', 'C'}
|
||||
@@ -1,3 +1,11 @@
|
||||
/*
|
||||
package keccak implements the Keccak hash algorithm family.
|
||||
|
||||
During the SHA-3 standardization process, the padding scheme was changed
|
||||
thus Keccac and SHA-3 produce different outputs. Most users should use
|
||||
SHA-3 and/or SHAKE instead, however the legacy algorithm is provided for
|
||||
backward compatibility purposes.
|
||||
*/
|
||||
package keccak
|
||||
|
||||
/*
|
||||
@@ -6,372 +14,82 @@ package keccak
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
Interface for the Keccak hashing algorithm.
|
||||
This is done because the padding in the SHA3 standard was changed by the NIST, resulting in a different output.
|
||||
*/
|
||||
|
||||
import "core:io"
|
||||
import "core:os"
|
||||
|
||||
import "../../_sha3"
|
||||
|
||||
/*
|
||||
High level API
|
||||
*/
|
||||
|
||||
// DIGEST_SIZE_224 is the Keccak-224 digest size.
|
||||
DIGEST_SIZE_224 :: 28
|
||||
// DIGEST_SIZE_256 is the Keccak-256 digest size.
|
||||
DIGEST_SIZE_256 :: 32
|
||||
// DIGEST_SIZE_384 is the Keccak-384 digest size.
|
||||
DIGEST_SIZE_384 :: 48
|
||||
// DIGEST_SIZE_512 is the Keccak-512 digest size.
|
||||
DIGEST_SIZE_512 :: 64
|
||||
|
||||
// hash_string_224 will hash the given input and return the
|
||||
// computed hash
|
||||
hash_string_224 :: proc(data: string) -> [DIGEST_SIZE_224]byte {
|
||||
return hash_bytes_224(transmute([]byte)(data))
|
||||
}
|
||||
// BLOCK_SIZE_224 is the Keccak-224 block size in bytes.
|
||||
BLOCK_SIZE_224 :: _sha3.RATE_224
|
||||
// BLOCK_SIZE_256 is the Keccak-256 block size in bytes.
|
||||
BLOCK_SIZE_256 :: _sha3.RATE_256
|
||||
// BLOCK_SIZE_384 is the Keccak-384 block size in bytes.
|
||||
BLOCK_SIZE_384 :: _sha3.RATE_384
|
||||
// BLOCK_SIZE_512 is the Keccak-512 block size in bytes.
|
||||
BLOCK_SIZE_512 :: _sha3.RATE_512
|
||||
|
||||
// hash_bytes_224 will hash the given input and return the
|
||||
// computed hash
|
||||
hash_bytes_224 :: proc(data: []byte) -> [DIGEST_SIZE_224]byte {
|
||||
hash: [DIGEST_SIZE_224]byte
|
||||
ctx: Context
|
||||
// Context is a Keccak instance.
|
||||
Context :: distinct _sha3.Context
|
||||
|
||||
// init_224 initializes a Context for Keccak-224.
|
||||
init_224 :: proc(ctx: ^Context) {
|
||||
ctx.mdlen = DIGEST_SIZE_224
|
||||
ctx.is_keccak = true
|
||||
init(&ctx)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash[:])
|
||||
return hash
|
||||
_init(ctx)
|
||||
}
|
||||
|
||||
// hash_string_to_buffer_224 will hash the given input and assign the
|
||||
// computed hash to the second parameter.
|
||||
// It requires that the destination buffer is at least as big as the digest size
|
||||
hash_string_to_buffer_224 :: proc(data: string, hash: []byte) {
|
||||
hash_bytes_to_buffer_224(transmute([]byte)(data), hash)
|
||||
}
|
||||
|
||||
// hash_bytes_to_buffer_224 will hash the given input and write the
|
||||
// computed hash into the second parameter.
|
||||
// It requires that the destination buffer is at least as big as the digest size
|
||||
hash_bytes_to_buffer_224 :: proc(data, hash: []byte) {
|
||||
ctx: Context
|
||||
ctx.mdlen = DIGEST_SIZE_224
|
||||
ctx.is_keccak = true
|
||||
init(&ctx)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash)
|
||||
}
|
||||
|
||||
// hash_stream_224 will read the stream in chunks and compute a
|
||||
// hash from its contents
|
||||
hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) {
|
||||
hash: [DIGEST_SIZE_224]byte
|
||||
ctx: Context
|
||||
ctx.mdlen = DIGEST_SIZE_224
|
||||
ctx.is_keccak = true
|
||||
init(&ctx)
|
||||
|
||||
buf := make([]byte, 512)
|
||||
defer delete(buf)
|
||||
|
||||
read := 1
|
||||
for read > 0 {
|
||||
read, _ = io.read(s, buf)
|
||||
if read > 0 {
|
||||
update(&ctx, buf[:read])
|
||||
}
|
||||
}
|
||||
final(&ctx, hash[:])
|
||||
return hash, true
|
||||
}
|
||||
|
||||
// hash_file_224 will read the file provided by the given handle
|
||||
// and compute a hash
|
||||
hash_file_224 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_224]byte, bool) {
|
||||
if !load_at_once {
|
||||
return hash_stream_224(os.stream_from_handle(hd))
|
||||
} else {
|
||||
if buf, ok := os.read_entire_file(hd); ok {
|
||||
return hash_bytes_224(buf[:]), ok
|
||||
}
|
||||
}
|
||||
return [DIGEST_SIZE_224]byte{}, false
|
||||
}
|
||||
|
||||
hash_224 :: proc {
|
||||
hash_stream_224,
|
||||
hash_file_224,
|
||||
hash_bytes_224,
|
||||
hash_string_224,
|
||||
hash_bytes_to_buffer_224,
|
||||
hash_string_to_buffer_224,
|
||||
}
|
||||
|
||||
// hash_string_256 will hash the given input and return the
|
||||
// computed hash
|
||||
hash_string_256 :: proc(data: string) -> [DIGEST_SIZE_256]byte {
|
||||
return hash_bytes_256(transmute([]byte)(data))
|
||||
}
|
||||
|
||||
// hash_bytes_256 will hash the given input and return the
|
||||
// computed hash
|
||||
hash_bytes_256 :: proc(data: []byte) -> [DIGEST_SIZE_256]byte {
|
||||
hash: [DIGEST_SIZE_256]byte
|
||||
ctx: Context
|
||||
// init_256 initializes a Context for Keccak-256.
|
||||
init_256 :: proc(ctx: ^Context) {
|
||||
ctx.mdlen = DIGEST_SIZE_256
|
||||
ctx.is_keccak = true
|
||||
init(&ctx)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash[:])
|
||||
return hash
|
||||
_init(ctx)
|
||||
}
|
||||
|
||||
// hash_string_to_buffer_256 will hash the given input and assign the
|
||||
// computed hash to the second parameter.
|
||||
// It requires that the destination buffer is at least as big as the digest size
|
||||
hash_string_to_buffer_256 :: proc(data: string, hash: []byte) {
|
||||
hash_bytes_to_buffer_256(transmute([]byte)(data), hash)
|
||||
}
|
||||
|
||||
// hash_bytes_to_buffer_256 will hash the given input and write the
|
||||
// computed hash into the second parameter.
|
||||
// It requires that the destination buffer is at least as big as the digest size
|
||||
hash_bytes_to_buffer_256 :: proc(data, hash: []byte) {
|
||||
ctx: Context
|
||||
ctx.mdlen = DIGEST_SIZE_256
|
||||
ctx.is_keccak = true
|
||||
init(&ctx)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash)
|
||||
}
|
||||
|
||||
// hash_stream_256 will read the stream in chunks and compute a
|
||||
// hash from its contents
|
||||
hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) {
|
||||
hash: [DIGEST_SIZE_256]byte
|
||||
ctx: Context
|
||||
ctx.mdlen = DIGEST_SIZE_256
|
||||
ctx.is_keccak = true
|
||||
init(&ctx)
|
||||
|
||||
buf := make([]byte, 512)
|
||||
defer delete(buf)
|
||||
|
||||
read := 1
|
||||
for read > 0 {
|
||||
read, _ = io.read(s, buf)
|
||||
if read > 0 {
|
||||
update(&ctx, buf[:read])
|
||||
}
|
||||
}
|
||||
final(&ctx, hash[:])
|
||||
return hash, true
|
||||
}
|
||||
|
||||
// hash_file_256 will read the file provided by the given handle
|
||||
// and compute a hash
|
||||
hash_file_256 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_256]byte, bool) {
|
||||
if !load_at_once {
|
||||
return hash_stream_256(os.stream_from_handle(hd))
|
||||
} else {
|
||||
if buf, ok := os.read_entire_file(hd); ok {
|
||||
return hash_bytes_256(buf[:]), ok
|
||||
}
|
||||
}
|
||||
return [DIGEST_SIZE_256]byte{}, false
|
||||
}
|
||||
|
||||
hash_256 :: proc {
|
||||
hash_stream_256,
|
||||
hash_file_256,
|
||||
hash_bytes_256,
|
||||
hash_string_256,
|
||||
hash_bytes_to_buffer_256,
|
||||
hash_string_to_buffer_256,
|
||||
}
|
||||
|
||||
// hash_string_384 will hash the given input and return the
|
||||
// computed hash
|
||||
hash_string_384 :: proc(data: string) -> [DIGEST_SIZE_384]byte {
|
||||
return hash_bytes_384(transmute([]byte)(data))
|
||||
}
|
||||
|
||||
// hash_bytes_384 will hash the given input and return the
|
||||
// computed hash
|
||||
hash_bytes_384 :: proc(data: []byte) -> [DIGEST_SIZE_384]byte {
|
||||
hash: [DIGEST_SIZE_384]byte
|
||||
ctx: Context
|
||||
// init_384 initializes a Context for Keccak-384.
|
||||
init_384 :: proc(ctx: ^Context) {
|
||||
ctx.mdlen = DIGEST_SIZE_384
|
||||
ctx.is_keccak = true
|
||||
init(&ctx)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash[:])
|
||||
return hash
|
||||
_init(ctx)
|
||||
}
|
||||
|
||||
// hash_string_to_buffer_384 will hash the given input and assign the
|
||||
// computed hash to the second parameter.
|
||||
// It requires that the destination buffer is at least as big as the digest size
|
||||
hash_string_to_buffer_384 :: proc(data: string, hash: []byte) {
|
||||
hash_bytes_to_buffer_384(transmute([]byte)(data), hash)
|
||||
}
|
||||
|
||||
// hash_bytes_to_buffer_384 will hash the given input and write the
|
||||
// computed hash into the second parameter.
|
||||
// It requires that the destination buffer is at least as big as the digest size
|
||||
hash_bytes_to_buffer_384 :: proc(data, hash: []byte) {
|
||||
ctx: Context
|
||||
ctx.mdlen = DIGEST_SIZE_384
|
||||
ctx.is_keccak = true
|
||||
init(&ctx)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash)
|
||||
}
|
||||
|
||||
// hash_stream_384 will read the stream in chunks and compute a
|
||||
// hash from its contents
|
||||
hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) {
|
||||
hash: [DIGEST_SIZE_384]byte
|
||||
ctx: Context
|
||||
ctx.mdlen = DIGEST_SIZE_384
|
||||
ctx.is_keccak = true
|
||||
init(&ctx)
|
||||
|
||||
buf := make([]byte, 512)
|
||||
defer delete(buf)
|
||||
|
||||
read := 1
|
||||
for read > 0 {
|
||||
read, _ = io.read(s, buf)
|
||||
if read > 0 {
|
||||
update(&ctx, buf[:read])
|
||||
}
|
||||
}
|
||||
final(&ctx, hash[:])
|
||||
return hash, true
|
||||
}
|
||||
|
||||
// hash_file_384 will read the file provided by the given handle
|
||||
// and compute a hash
|
||||
hash_file_384 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_384]byte, bool) {
|
||||
if !load_at_once {
|
||||
return hash_stream_384(os.stream_from_handle(hd))
|
||||
} else {
|
||||
if buf, ok := os.read_entire_file(hd); ok {
|
||||
return hash_bytes_384(buf[:]), ok
|
||||
}
|
||||
}
|
||||
return [DIGEST_SIZE_384]byte{}, false
|
||||
}
|
||||
|
||||
hash_384 :: proc {
|
||||
hash_stream_384,
|
||||
hash_file_384,
|
||||
hash_bytes_384,
|
||||
hash_string_384,
|
||||
hash_bytes_to_buffer_384,
|
||||
hash_string_to_buffer_384,
|
||||
}
|
||||
|
||||
// hash_string_512 will hash the given input and return the
|
||||
// computed hash
|
||||
hash_string_512 :: proc(data: string) -> [DIGEST_SIZE_512]byte {
|
||||
return hash_bytes_512(transmute([]byte)(data))
|
||||
}
|
||||
|
||||
// hash_bytes_512 will hash the given input and return the
|
||||
// computed hash
|
||||
hash_bytes_512 :: proc(data: []byte) -> [DIGEST_SIZE_512]byte {
|
||||
hash: [DIGEST_SIZE_512]byte
|
||||
ctx: Context
|
||||
// init_512 initializes a Context for Keccak-512.
|
||||
init_512 :: proc(ctx: ^Context) {
|
||||
ctx.mdlen = DIGEST_SIZE_512
|
||||
ctx.is_keccak = true
|
||||
init(&ctx)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash[:])
|
||||
return hash
|
||||
_init(ctx)
|
||||
}
|
||||
|
||||
// hash_string_to_buffer_512 will hash the given input and assign the
|
||||
// computed hash to the second parameter.
|
||||
// It requires that the destination buffer is at least as big as the digest size
|
||||
hash_string_to_buffer_512 :: proc(data: string, hash: []byte) {
|
||||
hash_bytes_to_buffer_512(transmute([]byte)(data), hash)
|
||||
}
|
||||
|
||||
// hash_bytes_to_buffer_512 will hash the given input and write the
|
||||
// computed hash into the second parameter.
|
||||
// It requires that the destination buffer is at least as big as the digest size
|
||||
hash_bytes_to_buffer_512 :: proc(data, hash: []byte) {
|
||||
ctx: Context
|
||||
ctx.mdlen = DIGEST_SIZE_512
|
||||
ctx.is_keccak = true
|
||||
init(&ctx)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash)
|
||||
}
|
||||
|
||||
// hash_stream_512 will read the stream in chunks and compute a
|
||||
// hash from its contents
|
||||
hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) {
|
||||
hash: [DIGEST_SIZE_512]byte
|
||||
ctx: Context
|
||||
ctx.mdlen = DIGEST_SIZE_512
|
||||
ctx.is_keccak = true
|
||||
init(&ctx)
|
||||
|
||||
buf := make([]byte, 512)
|
||||
defer delete(buf)
|
||||
|
||||
read := 1
|
||||
for read > 0 {
|
||||
read, _ = io.read(s, buf)
|
||||
if read > 0 {
|
||||
update(&ctx, buf[:read])
|
||||
}
|
||||
}
|
||||
final(&ctx, hash[:])
|
||||
return hash, true
|
||||
}
|
||||
|
||||
// hash_file_512 will read the file provided by the given handle
|
||||
// and compute a hash
|
||||
hash_file_512 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_512]byte, bool) {
|
||||
if !load_at_once {
|
||||
return hash_stream_512(os.stream_from_handle(hd))
|
||||
} else {
|
||||
if buf, ok := os.read_entire_file(hd); ok {
|
||||
return hash_bytes_512(buf[:]), ok
|
||||
}
|
||||
}
|
||||
return [DIGEST_SIZE_512]byte{}, false
|
||||
}
|
||||
|
||||
hash_512 :: proc {
|
||||
hash_stream_512,
|
||||
hash_file_512,
|
||||
hash_bytes_512,
|
||||
hash_string_512,
|
||||
hash_bytes_to_buffer_512,
|
||||
hash_string_to_buffer_512,
|
||||
}
|
||||
|
||||
/*
|
||||
Low level API
|
||||
*/
|
||||
|
||||
Context :: _sha3.Sha3_Context
|
||||
|
||||
init :: proc(ctx: ^Context) {
|
||||
ctx.is_keccak = true
|
||||
_sha3.init(ctx)
|
||||
@(private)
|
||||
_init :: proc(ctx: ^Context) {
|
||||
ctx.dsbyte = _sha3.DS_KECCAK
|
||||
_sha3.init(transmute(^_sha3.Context)(ctx))
|
||||
}
|
||||
|
||||
// update adds more data to the Context.
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
_sha3.update(ctx, data)
|
||||
_sha3.update(transmute(^_sha3.Context)(ctx), data)
|
||||
}
|
||||
|
||||
final :: proc(ctx: ^Context, hash: []byte) {
|
||||
_sha3.final(ctx, hash)
|
||||
// final finalizes the Context, writes the digest to hash, and calls
|
||||
// reset on the Context.
|
||||
//
|
||||
// Iff finalize_clone is set, final will work on a copy of the Context,
|
||||
// which is useful for for calculating rolling digests.
|
||||
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
_sha3.final(transmute(^_sha3.Context)(ctx), hash, finalize_clone)
|
||||
}
|
||||
|
||||
// clone clones the Context other into ctx.
|
||||
clone :: proc(ctx, other: ^Context) {
|
||||
_sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other))
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
_sha3.reset(transmute(^_sha3.Context)(ctx))
|
||||
}
|
||||
|
||||
+52
-100
@@ -1,3 +1,13 @@
|
||||
/*
|
||||
package md5 implements the MD5 hash algorithm.
|
||||
|
||||
WARNING: The MD5 algorithm is known to be insecure and should only be
|
||||
used for interoperating with legacy applications.
|
||||
|
||||
See:
|
||||
- https://eprint.iacr.org/2005/075
|
||||
- https://datatracker.ietf.org/doc/html/rfc1321
|
||||
*/
|
||||
package md5
|
||||
|
||||
/*
|
||||
@@ -6,103 +16,29 @@ package md5
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
Implementation of the MD5 hashing algorithm, as defined in RFC 1321 <https://datatracker.ietf.org/doc/html/rfc1321>
|
||||
*/
|
||||
|
||||
import "core:encoding/endian"
|
||||
import "core:io"
|
||||
import "core:math/bits"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
|
||||
/*
|
||||
High level API
|
||||
*/
|
||||
|
||||
// DIGEST_SIZE is the MD5 digest size in bytes.
|
||||
DIGEST_SIZE :: 16
|
||||
|
||||
// hash_string will hash the given input and return the
|
||||
// computed hash
|
||||
hash_string :: proc(data: string) -> [DIGEST_SIZE]byte {
|
||||
return hash_bytes(transmute([]byte)(data))
|
||||
// BLOCK_SIZE is the MD5 block size in bytes.
|
||||
BLOCK_SIZE :: 64
|
||||
|
||||
// Context is a MD5 instance.
|
||||
Context :: struct {
|
||||
data: [BLOCK_SIZE]byte,
|
||||
state: [4]u32,
|
||||
bitlen: u64,
|
||||
datalen: u32,
|
||||
|
||||
is_initialized: bool,
|
||||
}
|
||||
|
||||
// hash_bytes will hash the given input and return the
|
||||
// computed hash
|
||||
hash_bytes :: proc(data: []byte) -> [DIGEST_SIZE]byte {
|
||||
hash: [DIGEST_SIZE]byte
|
||||
ctx: Context
|
||||
init(&ctx)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash[:])
|
||||
return hash
|
||||
}
|
||||
|
||||
// hash_string_to_buffer will hash the given input and assign the
|
||||
// computed hash to the second parameter.
|
||||
// It requires that the destination buffer is at least as big as the digest size
|
||||
hash_string_to_buffer :: proc(data: string, hash: []byte) {
|
||||
hash_bytes_to_buffer(transmute([]byte)(data), hash)
|
||||
}
|
||||
|
||||
// hash_bytes_to_buffer will hash the given input and write the
|
||||
// computed hash into the second parameter.
|
||||
// It requires that the destination buffer is at least as big as the digest size
|
||||
hash_bytes_to_buffer :: proc(data, hash: []byte) {
|
||||
ctx: Context
|
||||
init(&ctx)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash)
|
||||
}
|
||||
|
||||
// hash_stream will read the stream in chunks and compute a
|
||||
// hash from its contents
|
||||
hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) {
|
||||
hash: [DIGEST_SIZE]byte
|
||||
ctx: Context
|
||||
init(&ctx)
|
||||
|
||||
buf := make([]byte, 512)
|
||||
defer delete(buf)
|
||||
|
||||
read := 1
|
||||
for read > 0 {
|
||||
read, _ = io.read(s, buf)
|
||||
if read > 0 {
|
||||
update(&ctx, buf[:read])
|
||||
}
|
||||
}
|
||||
final(&ctx, hash[:])
|
||||
return hash, true
|
||||
}
|
||||
|
||||
// hash_file will read the file provided by the given handle
|
||||
// and compute a hash
|
||||
hash_file :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE]byte, bool) {
|
||||
if !load_at_once {
|
||||
return hash_stream(os.stream_from_handle(hd))
|
||||
} else {
|
||||
if buf, ok := os.read_entire_file(hd); ok {
|
||||
return hash_bytes(buf[:]), ok
|
||||
}
|
||||
}
|
||||
return [DIGEST_SIZE]byte{}, false
|
||||
}
|
||||
|
||||
hash :: proc {
|
||||
hash_stream,
|
||||
hash_file,
|
||||
hash_bytes,
|
||||
hash_string,
|
||||
hash_bytes_to_buffer,
|
||||
hash_string_to_buffer,
|
||||
}
|
||||
|
||||
/*
|
||||
Low level API
|
||||
*/
|
||||
|
||||
// init initializes a Context.
|
||||
init :: proc(ctx: ^Context) {
|
||||
ctx.state[0] = 0x67452301
|
||||
ctx.state[1] = 0xefcdab89
|
||||
@@ -115,6 +51,7 @@ init :: proc(ctx: ^Context) {
|
||||
ctx.is_initialized = true
|
||||
}
|
||||
|
||||
// update adds more data to the Context.
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
assert(ctx.is_initialized)
|
||||
|
||||
@@ -129,13 +66,26 @@ update :: proc(ctx: ^Context, data: []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
final :: proc(ctx: ^Context, hash: []byte) {
|
||||
// final finalizes the Context, writes the digest to hash, and calls
|
||||
// reset on the Context.
|
||||
//
|
||||
// Iff finalize_clone is set, final will work on a copy of the Context,
|
||||
// which is useful for for calculating rolling digests.
|
||||
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
assert(ctx.is_initialized)
|
||||
|
||||
if len(hash) < DIGEST_SIZE {
|
||||
panic("crypto/md5: invalid destination digest size")
|
||||
}
|
||||
|
||||
ctx := ctx
|
||||
if finalize_clone {
|
||||
tmp_ctx: Context
|
||||
clone(&tmp_ctx, ctx)
|
||||
ctx = &tmp_ctx
|
||||
}
|
||||
defer(reset(ctx))
|
||||
|
||||
i := ctx.datalen
|
||||
|
||||
if ctx.datalen < 56 {
|
||||
@@ -163,25 +113,27 @@ final :: proc(ctx: ^Context, hash: []byte) {
|
||||
for i = 0; i < DIGEST_SIZE / 4; i += 1 {
|
||||
endian.unchecked_put_u32le(hash[i * 4:], ctx.state[i])
|
||||
}
|
||||
}
|
||||
|
||||
ctx.is_initialized = false
|
||||
// clone clones the Context other into ctx.
|
||||
clone :: proc(ctx, other: ^$T) {
|
||||
ctx^ = other^
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^$T) {
|
||||
if !ctx.is_initialized {
|
||||
return
|
||||
}
|
||||
|
||||
mem.zero_explicit(ctx, size_of(ctx^))
|
||||
}
|
||||
|
||||
/*
|
||||
MD5 implementation
|
||||
*/
|
||||
|
||||
BLOCK_SIZE :: 64
|
||||
|
||||
Context :: struct {
|
||||
data: [BLOCK_SIZE]byte,
|
||||
state: [4]u32,
|
||||
bitlen: u64,
|
||||
datalen: u32,
|
||||
|
||||
is_initialized: bool,
|
||||
}
|
||||
|
||||
/*
|
||||
@note(zh): F, G, H and I, as mentioned in the RFC, have been inlined into FF, GG, HH
|
||||
and II respectively, instead of declaring them separately.
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
/*
|
||||
package sha1 implements the SHA1 hash algorithm.
|
||||
|
||||
WARNING: The SHA1 algorithm is known to be insecure and should only be
|
||||
used for interoperating with legacy applications.
|
||||
|
||||
See:
|
||||
- https://eprint.iacr.org/2017/190
|
||||
- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
|
||||
- https://datatracker.ietf.org/doc/html/rfc3174
|
||||
*/
|
||||
package sha1
|
||||
|
||||
/*
|
||||
@@ -6,103 +17,30 @@ package sha1
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
Implementation of the SHA1 hashing algorithm, as defined in RFC 3174 <https://datatracker.ietf.org/doc/html/rfc3174>
|
||||
*/
|
||||
|
||||
import "core:encoding/endian"
|
||||
import "core:io"
|
||||
import "core:math/bits"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
|
||||
/*
|
||||
High level API
|
||||
*/
|
||||
|
||||
// DIGEST_SIZE is the SHA1 digest size in bytes.
|
||||
DIGEST_SIZE :: 20
|
||||
|
||||
// hash_string will hash the given input and return the
|
||||
// computed hash
|
||||
hash_string :: proc(data: string) -> [DIGEST_SIZE]byte {
|
||||
return hash_bytes(transmute([]byte)(data))
|
||||
// BLOCK_SIZE is the SHA1 block size in bytes.
|
||||
BLOCK_SIZE :: 64
|
||||
|
||||
// Context is a SHA1 instance.
|
||||
Context :: struct {
|
||||
data: [BLOCK_SIZE]byte,
|
||||
state: [5]u32,
|
||||
k: [4]u32,
|
||||
bitlen: u64,
|
||||
datalen: u32,
|
||||
|
||||
is_initialized: bool,
|
||||
}
|
||||
|
||||
// hash_bytes will hash the given input and return the
|
||||
// computed hash
|
||||
hash_bytes :: proc(data: []byte) -> [DIGEST_SIZE]byte {
|
||||
hash: [DIGEST_SIZE]byte
|
||||
ctx: Context
|
||||
init(&ctx)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash[:])
|
||||
return hash
|
||||
}
|
||||
|
||||
// hash_string_to_buffer will hash the given input and assign the
|
||||
// computed hash to the second parameter.
|
||||
// It requires that the destination buffer is at least as big as the digest size
|
||||
hash_string_to_buffer :: proc(data: string, hash: []byte) {
|
||||
hash_bytes_to_buffer(transmute([]byte)(data), hash)
|
||||
}
|
||||
|
||||
// hash_bytes_to_buffer will hash the given input and write the
|
||||
// computed hash into the second parameter.
|
||||
// It requires that the destination buffer is at least as big as the digest size
|
||||
hash_bytes_to_buffer :: proc(data, hash: []byte) {
|
||||
ctx: Context
|
||||
init(&ctx)
|
||||
update(&ctx, data)
|
||||
final(&ctx, hash)
|
||||
}
|
||||
|
||||
// hash_stream will read the stream in chunks and compute a
|
||||
// hash from its contents
|
||||
hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) {
|
||||
hash: [DIGEST_SIZE]byte
|
||||
ctx: Context
|
||||
init(&ctx)
|
||||
|
||||
buf := make([]byte, 512)
|
||||
defer delete(buf)
|
||||
|
||||
read := 1
|
||||
for read > 0 {
|
||||
read, _ = io.read(s, buf)
|
||||
if read > 0 {
|
||||
update(&ctx, buf[:read])
|
||||
}
|
||||
}
|
||||
final(&ctx, hash[:])
|
||||
return hash, true
|
||||
}
|
||||
|
||||
// hash_file will read the file provided by the given handle
|
||||
// and compute a hash
|
||||
hash_file :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE]byte, bool) {
|
||||
if !load_at_once {
|
||||
return hash_stream(os.stream_from_handle(hd))
|
||||
} else {
|
||||
if buf, ok := os.read_entire_file(hd); ok {
|
||||
return hash_bytes(buf[:]), ok
|
||||
}
|
||||
}
|
||||
return [DIGEST_SIZE]byte{}, false
|
||||
}
|
||||
|
||||
hash :: proc {
|
||||
hash_stream,
|
||||
hash_file,
|
||||
hash_bytes,
|
||||
hash_string,
|
||||
hash_bytes_to_buffer,
|
||||
hash_string_to_buffer,
|
||||
}
|
||||
|
||||
/*
|
||||
Low level API
|
||||
*/
|
||||
|
||||
// init initializes a Context.
|
||||
init :: proc(ctx: ^Context) {
|
||||
ctx.state[0] = 0x67452301
|
||||
ctx.state[1] = 0xefcdab89
|
||||
@@ -120,6 +58,7 @@ init :: proc(ctx: ^Context) {
|
||||
ctx.is_initialized = true
|
||||
}
|
||||
|
||||
// update adds more data to the Context.
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
assert(ctx.is_initialized)
|
||||
|
||||
@@ -134,13 +73,26 @@ update :: proc(ctx: ^Context, data: []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
final :: proc(ctx: ^Context, hash: []byte) {
|
||||
// final finalizes the Context, writes the digest to hash, and calls
|
||||
// reset on the Context.
|
||||
//
|
||||
// Iff finalize_clone is set, final will work on a copy of the Context,
|
||||
// which is useful for for calculating rolling digests.
|
||||
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
assert(ctx.is_initialized)
|
||||
|
||||
if len(hash) < DIGEST_SIZE {
|
||||
panic("crypto/sha1: invalid destination digest size")
|
||||
}
|
||||
|
||||
ctx := ctx
|
||||
if finalize_clone {
|
||||
tmp_ctx: Context
|
||||
clone(&tmp_ctx, ctx)
|
||||
ctx = &tmp_ctx
|
||||
}
|
||||
defer(reset(ctx))
|
||||
|
||||
i := ctx.datalen
|
||||
|
||||
if ctx.datalen < 56 {
|
||||
@@ -168,26 +120,27 @@ final :: proc(ctx: ^Context, hash: []byte) {
|
||||
for i = 0; i < DIGEST_SIZE / 4; i += 1 {
|
||||
endian.unchecked_put_u32be(hash[i * 4:], ctx.state[i])
|
||||
}
|
||||
}
|
||||
|
||||
ctx.is_initialized = false
|
||||
// clone clones the Context other into ctx.
|
||||
clone :: proc(ctx, other: ^$T) {
|
||||
ctx^ = other^
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^$T) {
|
||||
if !ctx.is_initialized {
|
||||
return
|
||||
}
|
||||
|
||||
mem.zero_explicit(ctx, size_of(ctx^))
|
||||
}
|
||||
|
||||
/*
|
||||
SHA1 implementation
|
||||
*/
|
||||
|
||||
BLOCK_SIZE :: 64
|
||||
|
||||
Context :: struct {
|
||||
data: [BLOCK_SIZE]byte,
|
||||
datalen: u32,
|
||||
bitlen: u64,
|
||||
state: [5]u32,
|
||||
k: [4]u32,
|
||||
|
||||
is_initialized: bool,
|
||||
}
|
||||
|
||||
@(private)
|
||||
transform :: proc "contextless" (ctx: ^Context, data: []byte) {
|
||||
a, b, c, d, e, i, t: u32
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
package pbkdf2 implements the PBKDF2 password-based key derivation function.
|
||||
|
||||
See: https://www.rfc-editor.org/rfc/rfc2898
|
||||
*/
|
||||
package pbkdf2
|
||||
|
||||
import "core:crypto/hash"
|
||||
import "core:crypto/hmac"
|
||||
import "core:encoding/endian"
|
||||
import "core:mem"
|
||||
|
||||
// derive invokes PBKDF2-HMAC with the specified hash algorithm, password,
|
||||
// salt, iteration count, and outputs the derived key to dst.
|
||||
derive :: proc(
|
||||
hmac_hash: hash.Algorithm,
|
||||
password: []byte,
|
||||
salt: []byte,
|
||||
iterations: u32,
|
||||
dst: []byte,
|
||||
) {
|
||||
h_len := hash.DIGEST_SIZES[hmac_hash]
|
||||
|
||||
// 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long"
|
||||
// and stop.
|
||||
|
||||
dk_len := len(dst)
|
||||
switch {
|
||||
case dk_len == 0:
|
||||
return
|
||||
case u64(dk_len) > u64(max(u32)) * u64(h_len):
|
||||
// This is so beyond anything that is practical or reasonable,
|
||||
// so just panic instead of returning an error.
|
||||
panic("crypto/pbkdf2: derived key too long")
|
||||
case:
|
||||
}
|
||||
|
||||
// 2. Let l be the number of hLen-octet blocks in the derived key,
|
||||
// rounding up, and let r be the number of octets in the last block.
|
||||
|
||||
l := dk_len / h_len // Don't need to round up.
|
||||
r := dk_len % h_len
|
||||
|
||||
// 3. For each block of the derived key apply the function F defined
|
||||
// below to the password P, the salt S, the iteration count c, and
|
||||
// the block index to compute the block.
|
||||
//
|
||||
// 4. Concatenate the blocks and extract the first dkLen octets to
|
||||
// produce a derived key DK.
|
||||
//
|
||||
// 5. Output the derived key DK.
|
||||
|
||||
// Each iteration of F is always `PRF (P, ...)`, so instantiate the
|
||||
// PRF, and clone since memcpy is faster than having to re-initialize
|
||||
// HMAC repeatedly.
|
||||
|
||||
base: hmac.Context
|
||||
defer hmac.reset(&base)
|
||||
|
||||
hmac.init(&base, hmac_hash, password)
|
||||
|
||||
// Process all of the blocks that will be written directly to dst.
|
||||
dst_blk := dst
|
||||
for i in 1 ..= l { // F expects i starting at 1.
|
||||
_F(&base, salt, iterations, u32(i), dst_blk[:h_len])
|
||||
dst_blk = dst_blk[h_len:]
|
||||
}
|
||||
|
||||
// Instead of rounding l up, just proceass the one extra block iff
|
||||
// r != 0.
|
||||
if r > 0 {
|
||||
tmp: [hash.MAX_DIGEST_SIZE]byte
|
||||
blk := tmp[:h_len]
|
||||
defer mem.zero_explicit(raw_data(blk), h_len)
|
||||
|
||||
_F(&base, salt, iterations, u32(l + 1), blk)
|
||||
copy(dst_blk, blk)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_F :: proc(base: ^hmac.Context, salt: []byte, c: u32, i: u32, dst_blk: []byte) {
|
||||
h_len := len(dst_blk)
|
||||
|
||||
tmp: [hash.MAX_DIGEST_SIZE]byte
|
||||
u := tmp[:h_len]
|
||||
defer mem.zero_explicit(raw_data(u), h_len)
|
||||
|
||||
// F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
|
||||
//
|
||||
// where
|
||||
//
|
||||
// U_1 = PRF (P, S || INT (i)) ,
|
||||
// U_2 = PRF (P, U_1) ,
|
||||
// ...
|
||||
// U_c = PRF (P, U_{c-1}) .
|
||||
//
|
||||
// Here, INT (i) is a four-octet encoding of the integer i, most
|
||||
// significant octet first.
|
||||
|
||||
prf: hmac.Context
|
||||
|
||||
// U_1: PRF (P, S || INT (i))
|
||||
hmac.clone(&prf, base)
|
||||
hmac.update(&prf, salt)
|
||||
endian.unchecked_put_u32be(u, i) // Use u as scratch space.
|
||||
hmac.update(&prf, u[:4])
|
||||
hmac.final(&prf, u)
|
||||
copy(dst_blk, u)
|
||||
|
||||
// U_2 ... U_c: U_n = PRF (P, U_(n-1))
|
||||
for _ in 1 ..< c {
|
||||
hmac.clone(&prf, base)
|
||||
hmac.update(&prf, u)
|
||||
hmac.final(&prf, u)
|
||||
|
||||
// XOR dst_blk and u.
|
||||
for v, i in u {
|
||||
dst_blk[i] ~= v
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,31 @@
|
||||
/*
|
||||
package poly1305 implements the Poly1305 one-time MAC algorithm.
|
||||
|
||||
See:
|
||||
- https://datatracker.ietf.org/doc/html/rfc8439
|
||||
*/
|
||||
package poly1305
|
||||
|
||||
import "core:crypto"
|
||||
import field "core:crypto/_fiat/field_poly1305"
|
||||
import "core:encoding/endian"
|
||||
import "core:math/bits"
|
||||
import "core:mem"
|
||||
|
||||
// KEY_SIZE is the Poly1305 key size in bytes.
|
||||
KEY_SIZE :: 32
|
||||
// TAG_SIZE is the Poly1305 tag size in bytes.
|
||||
TAG_SIZE :: 16
|
||||
|
||||
@(private)
|
||||
_BLOCK_SIZE :: 16
|
||||
|
||||
sum :: proc (dst, msg, key: []byte) {
|
||||
// sum will compute the Poly1305 MAC with the key over msg, and write
|
||||
// the computed tag to dst. It requires that the dst buffer is the tag
|
||||
// size.
|
||||
//
|
||||
// The key SHOULD be unique and MUST be unpredictable for each invocation.
|
||||
sum :: proc(dst, msg, key: []byte) {
|
||||
ctx: Context = ---
|
||||
|
||||
init(&ctx, key)
|
||||
@@ -19,13 +33,12 @@ sum :: proc (dst, msg, key: []byte) {
|
||||
final(&ctx, dst)
|
||||
}
|
||||
|
||||
verify :: proc (tag, msg, key: []byte) -> bool {
|
||||
// verify will verify the Poly1305 tag computed with the key over msg and
|
||||
// return true iff the tag is valid. It requires that the tag is correctly
|
||||
// sized.
|
||||
verify :: proc(tag, msg, key: []byte) -> bool {
|
||||
ctx: Context = ---
|
||||
derived_tag: [16]byte = ---
|
||||
|
||||
if len(tag) != TAG_SIZE {
|
||||
panic("crypto/poly1305: invalid tag size")
|
||||
}
|
||||
derived_tag: [TAG_SIZE]byte = ---
|
||||
|
||||
init(&ctx, key)
|
||||
update(&ctx, msg)
|
||||
@@ -34,18 +47,19 @@ verify :: proc (tag, msg, key: []byte) -> bool {
|
||||
return crypto.compare_constant_time(derived_tag[:], tag) == 1
|
||||
}
|
||||
|
||||
// Context is a Poly1305 instance.
|
||||
Context :: struct {
|
||||
_r: field.Tight_Field_Element,
|
||||
_a: field.Tight_Field_Element,
|
||||
_s: field.Tight_Field_Element,
|
||||
|
||||
_buffer: [_BLOCK_SIZE]byte,
|
||||
_leftover: int,
|
||||
|
||||
_r: field.Tight_Field_Element,
|
||||
_a: field.Tight_Field_Element,
|
||||
_s: [2]u64,
|
||||
_buffer: [_BLOCK_SIZE]byte,
|
||||
_leftover: int,
|
||||
_is_initialized: bool,
|
||||
}
|
||||
|
||||
init :: proc (ctx: ^Context, key: []byte) {
|
||||
// init initializes a Context with the specified key. The key SHOULD be
|
||||
// unique and MUST be unpredictable for each invocation.
|
||||
init :: proc(ctx: ^Context, key: []byte) {
|
||||
if len(key) != KEY_SIZE {
|
||||
panic("crypto/poly1305: invalid key size")
|
||||
}
|
||||
@@ -53,11 +67,12 @@ init :: proc (ctx: ^Context, key: []byte) {
|
||||
// r = le_bytes_to_num(key[0..15])
|
||||
// r = clamp(r) (r &= 0xffffffc0ffffffc0ffffffc0fffffff)
|
||||
tmp_lo := endian.unchecked_get_u64le(key[0:]) & 0x0ffffffc0fffffff
|
||||
tmp_hi := endian.unchecked_get_u64le(key[8:]) & 0xffffffc0ffffffc
|
||||
tmp_hi := endian.unchecked_get_u64le(key[8:]) & 0x0ffffffc0ffffffc
|
||||
field.fe_from_u64s(&ctx._r, tmp_lo, tmp_hi)
|
||||
|
||||
// s = le_bytes_to_num(key[16..31])
|
||||
field.fe_from_bytes(&ctx._s, key[16:32], 0)
|
||||
ctx._s[0] = endian.unchecked_get_u64le(key[16:])
|
||||
ctx._s[1] = endian.unchecked_get_u64le(key[24:])
|
||||
|
||||
// a = 0
|
||||
field.fe_zero(&ctx._a)
|
||||
@@ -68,7 +83,8 @@ init :: proc (ctx: ^Context, key: []byte) {
|
||||
ctx._is_initialized = true
|
||||
}
|
||||
|
||||
update :: proc (ctx: ^Context, data: []byte) {
|
||||
// update adds more data to the Context.
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
msg := data
|
||||
@@ -105,8 +121,11 @@ update :: proc (ctx: ^Context, data: []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
final :: proc (ctx: ^Context, dst: []byte) {
|
||||
// final finalizes the Context, writes the tag to dst, and calls
|
||||
// reset on the Context.
|
||||
final :: proc(ctx: ^Context, dst: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
defer reset(ctx)
|
||||
|
||||
if len(dst) != TAG_SIZE {
|
||||
panic("poly1305: invalid destination tag size")
|
||||
@@ -121,19 +140,25 @@ final :: proc (ctx: ^Context, dst: []byte) {
|
||||
_blocks(ctx, ctx._buffer[:], true)
|
||||
}
|
||||
|
||||
// a += s
|
||||
field.fe_add(field.fe_relax_cast(&ctx._a), &ctx._a, &ctx._s) // _a unreduced
|
||||
field.fe_carry(&ctx._a, field.fe_relax_cast(&ctx._a)) // _a reduced
|
||||
|
||||
// return num_to_16_le_bytes(a)
|
||||
// a += s (NOT mod p)
|
||||
tmp: [32]byte = ---
|
||||
field.fe_to_bytes(&tmp, &ctx._a)
|
||||
copy_slice(dst, tmp[0:16])
|
||||
|
||||
reset(ctx)
|
||||
c: u64
|
||||
lo := endian.unchecked_get_u64le(tmp[0:])
|
||||
hi := endian.unchecked_get_u64le(tmp[8:])
|
||||
|
||||
lo, c = bits.add_u64(lo, ctx._s[0], 0)
|
||||
hi, _ = bits.add_u64(hi, ctx._s[1], c)
|
||||
|
||||
// return num_to_16_le_bytes(a)
|
||||
endian.unchecked_put_u64le(dst[0:], lo)
|
||||
endian.unchecked_put_u64le(dst[8:], hi)
|
||||
}
|
||||
|
||||
reset :: proc (ctx: ^Context) {
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
mem.zero_explicit(&ctx._r, size_of(ctx._r))
|
||||
mem.zero_explicit(&ctx._a, size_of(ctx._a))
|
||||
mem.zero_explicit(&ctx._s, size_of(ctx._s))
|
||||
@@ -143,7 +168,7 @@ reset :: proc (ctx: ^Context) {
|
||||
}
|
||||
|
||||
@(private)
|
||||
_blocks :: proc (ctx: ^Context, msg: []byte, final := false) {
|
||||
_blocks :: proc "contextless" (ctx: ^Context, msg: []byte, final := false) {
|
||||
n: field.Tight_Field_Element = ---
|
||||
final_byte := byte(!final)
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
//+build freebsd, openbsd, netbsd
|
||||
package crypto
|
||||
|
||||
foreign import libc "system:c"
|
||||
|
||||
HAS_RAND_BYTES :: true
|
||||
|
||||
foreign libc {
|
||||
arc4random_buf :: proc(buf: [^]byte, nbytes: uint) ---
|
||||
}
|
||||
|
||||
@(private)
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
arc4random_buf(raw_data(dst), len(dst))
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package crypto
|
||||
|
||||
import "core:fmt"
|
||||
|
||||
import CF "core:sys/darwin/CoreFoundation"
|
||||
import Sec "core:sys/darwin/Security"
|
||||
|
||||
HAS_RAND_BYTES :: true
|
||||
|
||||
@(private)
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
err := Sec.RandomCopyBytes(count=len(dst), bytes=raw_data(dst))
|
||||
if err != .Success {
|
||||
msg := CF.StringCopyToOdinString(Sec.CopyErrorMessageString(err))
|
||||
panic(fmt.tprintf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", err, msg))
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,15 @@
|
||||
//+build !linux
|
||||
//+build !windows
|
||||
//+build !openbsd
|
||||
//+build !freebsd
|
||||
//+build !netbsd
|
||||
//+build !darwin
|
||||
//+build !js
|
||||
package crypto
|
||||
|
||||
when ODIN_OS != .Linux && ODIN_OS != .OpenBSD && ODIN_OS != .Windows && ODIN_OS != .JS {
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
unimplemented("crypto: rand_bytes not supported on this OS")
|
||||
}
|
||||
HAS_RAND_BYTES :: false
|
||||
|
||||
@(private)
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
unimplemented("crypto: rand_bytes not supported on this OS")
|
||||
}
|
||||
|
||||
@@ -6,8 +6,12 @@ foreign odin_env {
|
||||
env_rand_bytes :: proc "contextless" (buf: []byte) ---
|
||||
}
|
||||
|
||||
HAS_RAND_BYTES :: true
|
||||
|
||||
@(private)
|
||||
_MAX_PER_CALL_BYTES :: 65536 // 64kiB
|
||||
|
||||
@(private)
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
dst := dst
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user