mirror of
https://github.com/vacp2p/nim-libp2p.git
synced 2026-01-10 04:08:14 -05:00
Compare commits
588 Commits
ringbuffer
...
multihash-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7eaf79fefe | ||
|
|
b517b692df | ||
|
|
7cfd26035a | ||
|
|
cd5fea53e3 | ||
|
|
d9aa393761 | ||
|
|
a4a0d9e375 | ||
|
|
c8b406d6ed | ||
|
|
f0125a62df | ||
|
|
9bf2636186 | ||
|
|
01a33ebe5c | ||
|
|
c1cd31079b | ||
|
|
9f9f38e314 | ||
|
|
f83638eb82 | ||
|
|
882cb5dfe3 | ||
|
|
81310df2a2 | ||
|
|
34110a37d7 | ||
|
|
1035e4f314 | ||
|
|
d08bad5893 | ||
|
|
7bdba4909f | ||
|
|
e71c7caf82 | ||
|
|
45476bdd6b | ||
|
|
c7ee7b950d | ||
|
|
87b3d2c864 | ||
|
|
19b4c20e2f | ||
|
|
514bd4b5f5 | ||
|
|
46d936b80c | ||
|
|
80bf27c6bb | ||
|
|
6576c5c3bf | ||
|
|
2e6b1d2738 | ||
|
|
9e6c4cb4d2 | ||
|
|
5f256049ab | ||
|
|
e29ca73386 | ||
|
|
577809750a | ||
|
|
46a5430cc2 | ||
|
|
d8b9f59c5e | ||
|
|
2951356c9d | ||
|
|
7ae21d0cbd | ||
|
|
eee8341ad2 | ||
|
|
e83bd2d582 | ||
|
|
998bb58aef | ||
|
|
c1f6dec7d3 | ||
|
|
13c613c26c | ||
|
|
45f0f9f47a | ||
|
|
b1dd0a2ec6 | ||
|
|
beecfdfadb | ||
|
|
e4faec5570 | ||
|
|
41c9bf8e8c | ||
|
|
7ae366d979 | ||
|
|
9b33cea225 | ||
|
|
f8077f7432 | ||
|
|
773fc67865 | ||
|
|
7e07ffc5a8 | ||
|
|
aa1c33ffe9 | ||
|
|
f1e220fba4 | ||
|
|
5ad656bf26 | ||
|
|
cfd631457a | ||
|
|
4f8597609b | ||
|
|
4ed72a753c | ||
|
|
2a9abbe925 | ||
|
|
ee61e234ac | ||
|
|
8f54367e3a | ||
|
|
61826a20e4 | ||
|
|
32951e1a68 | ||
|
|
1d13e405e4 | ||
|
|
729e879c1c | ||
|
|
64c9cf1b9e | ||
|
|
4d94892eb0 | ||
|
|
3ecb1744ce | ||
|
|
2f9c3fb3e2 | ||
|
|
2609c270b8 | ||
|
|
48b3e34cd3 | ||
|
|
abb2c43667 | ||
|
|
d1cfbb35d3 | ||
|
|
38a630eee0 | ||
|
|
be1a2023ce | ||
|
|
021d0c1700 | ||
|
|
f49cd377ce | ||
|
|
fc80840784 | ||
|
|
7742d06a58 | ||
|
|
e0ea1d48a4 | ||
|
|
f028ad8c12 | ||
|
|
9c153c822b | ||
|
|
d803352bd6 | ||
|
|
2eafac47e8 | ||
|
|
848fdde0a8 | ||
|
|
31e7dc68e2 | ||
|
|
08299a2059 | ||
|
|
2f3156eafb | ||
|
|
72e85101b0 | ||
|
|
d205260a3e | ||
|
|
97e576d146 | ||
|
|
888cb78331 | ||
|
|
1d4c261d2a | ||
|
|
83de0c0abd | ||
|
|
c501adc9ab | ||
|
|
f9fc24cc08 | ||
|
|
cd26244ccc | ||
|
|
cabab6aafe | ||
|
|
fb42a9b4aa | ||
|
|
141f4d9116 | ||
|
|
cb31152b53 | ||
|
|
3a7745f920 | ||
|
|
a89916fb1a | ||
|
|
c6cf46c904 | ||
|
|
b28a71ab13 | ||
|
|
95b9859bcd | ||
|
|
9e599753af | ||
|
|
2e924906bb | ||
|
|
e811c1ad32 | ||
|
|
86695b55bb | ||
|
|
8c3a4d882a | ||
|
|
4bad343ddc | ||
|
|
47b8a05c32 | ||
|
|
4e6f4af601 | ||
|
|
7275f6f9c3 | ||
|
|
c3dae6a7d4 | ||
|
|
bb404eda4a | ||
|
|
584710bd80 | ||
|
|
ad5eae9adf | ||
|
|
26fae7cd2d | ||
|
|
87d6655368 | ||
|
|
cd60b254a0 | ||
|
|
b88cdcdd4b | ||
|
|
4a5e06cb45 | ||
|
|
fff3a7ad1f | ||
|
|
05c894d487 | ||
|
|
8850e9ccd9 | ||
|
|
2746531851 | ||
|
|
2856db5490 | ||
|
|
b29e78ccae | ||
|
|
c9761c3588 | ||
|
|
e4ef21e07c | ||
|
|
61429aa0d6 | ||
|
|
c1ef011556 | ||
|
|
cd1424c09f | ||
|
|
878d627f93 | ||
|
|
1d6385ddc5 | ||
|
|
873f730b4e | ||
|
|
1c1547b137 | ||
|
|
9997f3e3d3 | ||
|
|
4d0b4ecc22 | ||
|
|
ccb24b5f1f | ||
|
|
5cb493439d | ||
|
|
24b284240a | ||
|
|
b0f77d24f9 | ||
|
|
e32ac492d3 | ||
|
|
470a7f8cc5 | ||
|
|
b269fce289 | ||
|
|
bc4febe92c | ||
|
|
b5f9bfe0f4 | ||
|
|
4ce1e8119b | ||
|
|
65136b38e2 | ||
|
|
ffc114e8d9 | ||
|
|
f2be2d6ed5 | ||
|
|
ab690a06a6 | ||
|
|
10cdaf14c5 | ||
|
|
ebbfb63c17 | ||
|
|
ac25da6cea | ||
|
|
fb41972ba3 | ||
|
|
504d1618af | ||
|
|
0f91b23f12 | ||
|
|
5ddd62a8b9 | ||
|
|
e7f13a7e73 | ||
|
|
89e825fb0d | ||
|
|
1b706e84fa | ||
|
|
5cafcb70dc | ||
|
|
8c71266058 | ||
|
|
9c986c5c13 | ||
|
|
3d0451d7f2 | ||
|
|
b1f65c97ae | ||
|
|
5584809fca | ||
|
|
7586f17b15 | ||
|
|
0e16d873c8 | ||
|
|
b11acd2118 | ||
|
|
1376f5b077 | ||
|
|
340ea05ae5 | ||
|
|
024ec51f66 | ||
|
|
efe453df87 | ||
|
|
c0f4d903ba | ||
|
|
c08d807349 | ||
|
|
bde92606ac | ||
|
|
e7f81867d4 | ||
|
|
ee83da271a | ||
|
|
a7374e827a | ||
|
|
b6d36fe646 | ||
|
|
e9a3c6c58e | ||
|
|
28f2b268ae | ||
|
|
5abb6916b6 | ||
|
|
e6aec94c0c | ||
|
|
7960a1d9d6 | ||
|
|
60a37c5eb8 | ||
|
|
9eddc7c662 | ||
|
|
028c730a4f | ||
|
|
3c93bdaf80 | ||
|
|
8bb6ff2d00 | ||
|
|
588b7d2682 | ||
|
|
285c208143 | ||
|
|
925bddb337 | ||
|
|
5623a4434a | ||
|
|
89d391afe8 | ||
|
|
e31ce69cd4 | ||
|
|
3e3b5acbd7 | ||
|
|
4c1492baa0 | ||
|
|
1be84befab | ||
|
|
037b99997e | ||
|
|
e67744bf2a | ||
|
|
5843e6fb4f | ||
|
|
f0ff7e4c69 | ||
|
|
24808ad534 | ||
|
|
c4bccef138 | ||
|
|
adf2345adb | ||
|
|
f7daad91e6 | ||
|
|
65052d7b59 | ||
|
|
b07ec5c0c6 | ||
|
|
f4c94ddba1 | ||
|
|
a7ec485ca9 | ||
|
|
86b6469e35 | ||
|
|
3e16ca724d | ||
|
|
93dd5a6768 | ||
|
|
ec43d0cb9f | ||
|
|
8469a750e7 | ||
|
|
fc6ac07ce8 | ||
|
|
79cdc31b37 | ||
|
|
be33ad6ac7 | ||
|
|
a6e45d6157 | ||
|
|
37e0f61679 | ||
|
|
5d382b6423 | ||
|
|
78a4344054 | ||
|
|
a4f0a638e7 | ||
|
|
c5aa3736f9 | ||
|
|
b0f83fd48c | ||
|
|
d6e5094095 | ||
|
|
483e1d91ba | ||
|
|
d215bb21e0 | ||
|
|
61ac0c5b95 | ||
|
|
1fa30f07e8 | ||
|
|
39d0451a10 | ||
|
|
4dc7a89f45 | ||
|
|
fd26f93b80 | ||
|
|
dd2c74d413 | ||
|
|
b7e0df127f | ||
|
|
f591e692fc | ||
|
|
8855bce085 | ||
|
|
ed5670408b | ||
|
|
97192a3c80 | ||
|
|
294d06323c | ||
|
|
a3b8729cbe | ||
|
|
6c970911f2 | ||
|
|
5d48776b02 | ||
|
|
d389d96789 | ||
|
|
09fe199b6b | ||
|
|
68306cf1f1 | ||
|
|
b37133ca43 | ||
|
|
3e3df07269 | ||
|
|
1771534030 | ||
|
|
21a444197c | ||
|
|
966996542e | ||
|
|
8070b21825 | ||
|
|
d98152f266 | ||
|
|
47a51983b5 | ||
|
|
70754cd575 | ||
|
|
a1811e7395 | ||
|
|
c6e8fadbda | ||
|
|
48846d69cb | ||
|
|
18a2e79ce2 | ||
|
|
55cc5434fe | ||
|
|
036e110a60 | ||
|
|
08bd710900 | ||
|
|
2b8db4f9d4 | ||
|
|
8df12becc3 | ||
|
|
ea6680f3cf | ||
|
|
87728e2d9c | ||
|
|
cde5ed7e8c | ||
|
|
6ec038d29a | ||
|
|
1edb317542 | ||
|
|
fdae9e4b42 | ||
|
|
a60f0c5532 | ||
|
|
62f2d85f11 | ||
|
|
e5e319c1a9 | ||
|
|
f8d4da6421 | ||
|
|
b5fb7b3a97 | ||
|
|
fa19bbbbb7 | ||
|
|
86563cbddd | ||
|
|
be801602f6 | ||
|
|
94d93cbf25 | ||
|
|
78f0855419 | ||
|
|
2195313dba | ||
|
|
100f3188ed | ||
|
|
d1d53ff369 | ||
|
|
0f27f896ab | ||
|
|
0be7144e34 | ||
|
|
fba6dc31b0 | ||
|
|
02f6e6127c | ||
|
|
1d826ee26f | ||
|
|
7498258f7c | ||
|
|
4618f4c68f | ||
|
|
3bf8a2907f | ||
|
|
96bfefc928 | ||
|
|
dc83a1e9b6 | ||
|
|
d0af3fbe85 | ||
|
|
120549e313 | ||
|
|
bccb305cf5 | ||
|
|
f9a6ef06cf | ||
|
|
8cb7dbb425 | ||
|
|
368c9765f7 | ||
|
|
d6feb1bbc2 | ||
|
|
3f5b5cee75 | ||
|
|
8a4e8a00a2 | ||
|
|
77d40c34f4 | ||
|
|
2fa2c4425f | ||
|
|
0911cb20f4 | ||
|
|
3ca49a2f40 | ||
|
|
1b91b97499 | ||
|
|
21cbe3a91a | ||
|
|
88e233db81 | ||
|
|
84659af45b | ||
|
|
aef44ed1ce | ||
|
|
02c96fc003 | ||
|
|
c4da9be32c | ||
|
|
2b5319622c | ||
|
|
5cbb473d1b | ||
|
|
b30b2656d5 | ||
|
|
89cad5a3ba | ||
|
|
09b3e11956 | ||
|
|
03f67d3db5 | ||
|
|
bb97a9de79 | ||
|
|
1a707e1264 | ||
|
|
458b0885dd | ||
|
|
a2027003cd | ||
|
|
c5db35d9b0 | ||
|
|
d1e51beb7f | ||
|
|
275d649287 | ||
|
|
467b5b4f0c | ||
|
|
fdf53d18cd | ||
|
|
48a3ac06ff | ||
|
|
49a92e5641 | ||
|
|
08a48faf41 | ||
|
|
61b299e411 | ||
|
|
ca01ee06a8 | ||
|
|
6c43ab3fce | ||
|
|
ae13a0d583 | ||
|
|
28609597d1 | ||
|
|
8294d5b9df | ||
|
|
78e83889ee | ||
|
|
7603b8de5e | ||
|
|
8cccd54125 | ||
|
|
18e00a741b | ||
|
|
ee264fdf11 | ||
|
|
9059a8aced | ||
|
|
0b753e7cf2 | ||
|
|
d43c5feab0 | ||
|
|
1609fd7197 | ||
|
|
42cd78e95b | ||
|
|
44cada9c55 | ||
|
|
6c873481ac | ||
|
|
d08ce17144 | ||
|
|
bd6ead95ef | ||
|
|
53e3825e07 | ||
|
|
e9b456162a | ||
|
|
250024f6cc | ||
|
|
fec632d28d | ||
|
|
349496e40f | ||
|
|
7faa0fac23 | ||
|
|
c5e4f8e12d | ||
|
|
fe4ff79885 | ||
|
|
aa4ebb0b3c | ||
|
|
e0f70b7177 | ||
|
|
c1dfd58772 | ||
|
|
04af0c4323 | ||
|
|
eb0890cd6f | ||
|
|
9bc5ec1566 | ||
|
|
5594bcb33e | ||
|
|
d46bcdb6ac | ||
|
|
9468bb6b4d | ||
|
|
2725be64ba | ||
|
|
b239791c56 | ||
|
|
967b458b2e | ||
|
|
a4780cf3e3 | ||
|
|
36457c9ff4 | ||
|
|
1e3b439799 | ||
|
|
dc7550638d | ||
|
|
50ce66d7d2 | ||
|
|
0af4b79daf | ||
|
|
e2f0900871 | ||
|
|
e3c967ad19 | ||
|
|
d2c98bd87d | ||
|
|
3011ba4326 | ||
|
|
c6566707fa | ||
|
|
3be681ec4d | ||
|
|
2ede0fa40c | ||
|
|
7c195ab927 | ||
|
|
3230407ffe | ||
|
|
deb72c8580 | ||
|
|
ce0685c272 | ||
|
|
1f4b090227 | ||
|
|
fb05f5ae22 | ||
|
|
e12f65f193 | ||
|
|
4b3bc4f819 | ||
|
|
6791f5e7bb | ||
|
|
08d9c84aca | ||
|
|
4e7eaba67a | ||
|
|
5f7a3ab829 | ||
|
|
ebef85c9d7 | ||
|
|
3fc1236659 | ||
|
|
fc4e9a8bb8 | ||
|
|
60f953629d | ||
|
|
18b0f726df | ||
|
|
459f6851e7 | ||
|
|
575344e2e9 | ||
|
|
75871817ee | ||
|
|
61929aed6c | ||
|
|
56599f5b9d | ||
|
|
b2eac7ecbd | ||
|
|
20b0e40f7d | ||
|
|
ff77d52851 | ||
|
|
545a31d4f0 | ||
|
|
b76bac752f | ||
|
|
c6aa085e98 | ||
|
|
e03547ea3e | ||
|
|
f80ce3133c | ||
|
|
d6263bf751 | ||
|
|
56c23a286a | ||
|
|
7a369dd1bf | ||
|
|
b784167805 | ||
|
|
440461b24b | ||
|
|
fab1340020 | ||
|
|
1721f078c7 | ||
|
|
74c402ed9d | ||
|
|
c45f9705ab | ||
|
|
81b861b34e | ||
|
|
43359dd9d1 | ||
|
|
f85d0f75ea | ||
|
|
66f9dc9167 | ||
|
|
1c4d0832ce | ||
|
|
224f92e172 | ||
|
|
5efa089196 | ||
|
|
9d4c4307de | ||
|
|
49dfa84c6f | ||
|
|
a65b7b028f | ||
|
|
67711478ce | ||
|
|
c28d8bb353 | ||
|
|
eb78292d9c | ||
|
|
3725f6a95b | ||
|
|
3640b4dd89 | ||
|
|
32085ca88a | ||
|
|
c76d1e18ef | ||
|
|
41649f0999 | ||
|
|
67102873ba | ||
|
|
d40d324160 | ||
|
|
a677b06273 | ||
|
|
6050cdef7e | ||
|
|
fedfa8e817 | ||
|
|
6887b43777 | ||
|
|
225accd11b | ||
|
|
7d6bc545e0 | ||
|
|
a1eb53b181 | ||
|
|
db629dca25 | ||
|
|
a5666789b0 | ||
|
|
b7726bf68f | ||
|
|
0221affe98 | ||
|
|
edbd35b16c | ||
|
|
80cca0ecac | ||
|
|
0041ed4cf8 | ||
|
|
95e98e8c51 | ||
|
|
4aa615c44c | ||
|
|
6b61ce8c91 | ||
|
|
53b060f8f0 | ||
|
|
af5299f26c | ||
|
|
bac754e2ad | ||
|
|
8d5ea43e2b | ||
|
|
e573238705 | ||
|
|
c1a3bd8fee | ||
|
|
ddeb7b3bd4 | ||
|
|
382b992e00 | ||
|
|
408dcf12bd | ||
|
|
0012b639c8 | ||
|
|
f7f1e89669 | ||
|
|
f14ada3dcf | ||
|
|
444b837923 | ||
|
|
f89bd0c77c | ||
|
|
e68186373b | ||
|
|
266c7b117a | ||
|
|
0e28d3b828 | ||
|
|
4ace70d53b | ||
|
|
ca19f8fdbf | ||
|
|
351bda2b56 | ||
|
|
7d9c43a5ce | ||
|
|
c11772c94e | ||
|
|
489c115132 | ||
|
|
166c0d1c87 | ||
|
|
ba451196e8 | ||
|
|
9f658c151e | ||
|
|
e304ad0f7e | ||
|
|
5e3323d43f | ||
|
|
9532bff983 | ||
|
|
676786b00e | ||
|
|
d521c57b82 | ||
|
|
63e1872516 | ||
|
|
67ef25fae0 | ||
|
|
fe7a69e389 | ||
|
|
a17cad710c | ||
|
|
3863a4cd21 | ||
|
|
64cbbe1e0a | ||
|
|
31ad4ae205 | ||
|
|
b3d9360dfc | ||
|
|
1711c204ea | ||
|
|
c43aacdc81 | ||
|
|
711609057c | ||
|
|
192cac6254 | ||
|
|
cc3c637c22 | ||
|
|
afbb1b4d3c | ||
|
|
8c2eca18dc | ||
|
|
ce371f3bb4 | ||
|
|
23338fceaa | ||
|
|
6ab6ab48ef | ||
|
|
d9305bda84 | ||
|
|
f95eda8bf6 | ||
|
|
a3e9d1ed80 | ||
|
|
c9c2f6acdb | ||
|
|
c39c1cbf68 | ||
|
|
b4f96721af | ||
|
|
4bce8f38c9 | ||
|
|
dc13ff81d3 | ||
|
|
a69301f392 | ||
|
|
4b105c6abd | ||
|
|
2e12c7ab73 | ||
|
|
a086fcba72 | ||
|
|
7b103e02f2 | ||
|
|
32233d36c8 | ||
|
|
1c99aca054 | ||
|
|
4f18dd30e9 | ||
|
|
0cd3554ce4 | ||
|
|
bcb8f5e3b6 | ||
|
|
eb78660702 | ||
|
|
103e199bc0 | ||
|
|
fa5d102370 | ||
|
|
a56c3bc296 | ||
|
|
5e7e009445 | ||
|
|
72abe822c0 | ||
|
|
a001508490 | ||
|
|
4d8b50d24c | ||
|
|
ef594e1e02 | ||
|
|
d8a9e93ff7 | ||
|
|
abbeaab684 | ||
|
|
dfbfbe6eb6 | ||
|
|
1de7508b64 | ||
|
|
3ffc03ed16 | ||
|
|
543358b262 | ||
|
|
14d2c3f51e | ||
|
|
2332813873 | ||
|
|
124a7a5ffe | ||
|
|
2d864633ea | ||
|
|
2fbe82bf9d | ||
|
|
20c02a5f23 | ||
|
|
a9a7e7eb15 | ||
|
|
34c2fb8787 | ||
|
|
1e598a0239 | ||
|
|
4ca1c2d7ed | ||
|
|
83ad890535 | ||
|
|
0b0686ee94 | ||
|
|
93ac795aef | ||
|
|
912873f8b3 | ||
|
|
78a65eebcc | ||
|
|
533e39ef94 | ||
|
|
150fafbee8 | ||
|
|
d0523fdc9d | ||
|
|
0ece5eaf12 | ||
|
|
e6440c43c2 | ||
|
|
597abddba7 | ||
|
|
5d7024f2e0 | ||
|
|
a7e335e1bb | ||
|
|
718374d890 | ||
|
|
36f3132d9a | ||
|
|
ca3f4e8701 | ||
|
|
7323ecc9c4 | ||
|
|
60becadcf9 | ||
|
|
1696d0c707 | ||
|
|
d4ff1c88e9 | ||
|
|
e536d7cb1b | ||
|
|
13503f3799 | ||
|
|
991549f391 | ||
|
|
32ca1898d9 | ||
|
|
9ba5c069c8 | ||
|
|
c97befb387 | ||
|
|
fc6b8f46f1 | ||
|
|
9973b9466d | ||
|
|
868ecab54f | ||
|
|
84cbcd8f22 |
@@ -1,52 +0,0 @@
|
||||
version: '{build}'
|
||||
|
||||
image: Visual Studio 2015
|
||||
|
||||
cache:
|
||||
- NimBinaries
|
||||
- p2pdCache
|
||||
|
||||
matrix:
|
||||
# We always want 32 and 64-bit compilation
|
||||
fast_finish: false
|
||||
|
||||
platform:
|
||||
- x86
|
||||
- x64
|
||||
|
||||
# when multiple CI builds are queued, the tested commit needs to be in the last X commits cloned with "--depth X"
|
||||
clone_depth: 10
|
||||
|
||||
install:
|
||||
- git submodule update --init --recursive
|
||||
|
||||
# use the newest versions documented here: https://www.appveyor.com/docs/windows-images-software/#mingw-msys-cygwin
|
||||
- IF "%PLATFORM%" == "x86" SET PATH=C:\mingw-w64\i686-6.3.0-posix-dwarf-rt_v5-rev1\mingw32\bin;%PATH%
|
||||
- IF "%PLATFORM%" == "x64" SET PATH=C:\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin;%PATH%
|
||||
|
||||
# build nim from our own branch - this to avoid the day-to-day churn and
|
||||
# regressions of the fast-paced Nim development while maintaining the
|
||||
# flexibility to apply patches
|
||||
- curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh
|
||||
- env MAKE="mingw32-make -j2" ARCH_OVERRIDE=%PLATFORM% bash build_nim.sh Nim csources dist/nimble NimBinaries
|
||||
- SET PATH=%CD%\Nim\bin;%PATH%
|
||||
|
||||
# set path for produced Go binaries
|
||||
- MKDIR goblin
|
||||
- CD goblin
|
||||
- SET GOPATH=%CD%
|
||||
- SET PATH=%GOPATH%\bin;%PATH%
|
||||
- CD ..
|
||||
|
||||
# install and build go-libp2p-daemon
|
||||
- bash scripts/build_p2pd.sh p2pdCache v0.3.0
|
||||
|
||||
build_script:
|
||||
- nimble install -y --depsOnly
|
||||
|
||||
test_script:
|
||||
- nimble test
|
||||
- nimble examples_build
|
||||
|
||||
deploy: off
|
||||
|
||||
1141
.assets/full-logo.svg
Normal file
1141
.assets/full-logo.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 61 KiB |
96
.assets/small-logo.svg
Normal file
96
.assets/small-logo.svg
Normal file
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:v="https://vecta.io/nano" xmlns:xlink="http://www.w3.org/1999/xlink" width="172.071" height="196.414" viewBox="0 0 45.527 51.968">
|
||||
<g transform="matrix(.2822 0 0 .2822 -212.833275 -150.656248)">
|
||||
<path d="M835.432 533.821l-12.483 9.783c-6.482-.207-19.197 1.251-26.086 3.769-6.346-4.04-11.923-8.5-11.923-8.5l-7.762 13.071c-4.444 2.375-8.906 5.046-12.883 8.58l-10.162-4.17c6.125 12.414 10.243 24.844 21.445 32.316 17.834-28.299 100.705-25.691 118.907-.16 11.764-6.165 16.339-19.429 20.965-31.674-.507.168-6.802 2.285-10.882 3.849-2.436-2.665-8.179-6.763-11.443-8.741-3.096-5.696-7.602-13.391-7.602-13.391s-5.337 3.988-11.523 8.34c-8.357-1.55-18.465-3.433-26.966-2.967-5.787-4.779-11.603-10.104-11.603-10.104z" fill="#f3d400" />
|
||||
<g opacity=".9" transform="matrix(.9375 0 0 .9375 765.1166 550.13225)">
|
||||
<path d="M99.952 106.898l.215-.107 24.755-14.248-24.97-14.535-24.97 14.374z" fill="#cc2a65" />
|
||||
<use xlink:href="#B" fill="#a21d4c" />
|
||||
<path d="M124.922 92.542l-24.755 14.248-.215.107v28.89l24.97-14.356z" fill="#b62454" />
|
||||
<path d="M50.012 106.737l.215-.125 24.755-14.248-24.97-14.517-24.97 14.356z" fill="#c8d92b" />
|
||||
<path d="M50.012 135.609v-28.872l-24.97-14.535v28.89h.018z" fill="#c2d02f" />
|
||||
<path d="M74.982 92.381l-24.755 14.23-.215.125v28.872.018h.018l24.952-14.356v-.018z" fill="#b9be33" />
|
||||
<path d="M74.982 121.253l.215-.107 24.755-14.248-24.97-14.535-24.97 14.374z" fill="#cc2a65" />
|
||||
<use xlink:href="#B" x="-24.97" y="14.356" fill="#a21d4c" />
|
||||
<path d="M99.952 106.898l-24.755 14.248-.215.107v28.89H75l24.952-14.356z" fill="#b62454" />
|
||||
<path d="M124.905 121.415l.215-.125 24.737-14.23-24.952-14.535-24.97 14.356z" fill="#a159a2" />
|
||||
<path d="M124.905 150.305v-28.89l-24.97-14.535v28.89h.018z" fill="#772a86" />
|
||||
<path d="M149.875 107.059l-24.755 14.23-.215.125v28.89l24.97-14.356z" fill="#8e3b95" />
|
||||
<path d="M74.982 92.345l.215-.125 24.737-14.248-24.952-14.517-24.97 14.356z" fill="#bec831" />
|
||||
<path d="M74.982 121.217V92.345L50.012 77.81v28.89h.018z" fill="#a1a938" />
|
||||
<path d="M99.952 77.989l-24.755 14.23-.215.125v28.872.018l24.97-14.356v-.018z" fill="#999b37" />
|
||||
<path d="M75 60.645l.197-.125 24.755-14.23L75 31.755 50.029 46.11l24.952 14.535z" fill="#bec831" />
|
||||
<path d="M74.982 89.535L75 60.645 50.029 46.11 50.012 75h.018z" fill="#a1a938" />
|
||||
<path d="M99.97 46.307L75.197 60.52l-.197.125h-.018v28.89l24.97-14.338v-.018z" fill="#999b37" />
|
||||
<path d="M99.952 75.179l.215-.107 24.755-14.23L99.97 46.306 75 60.644z" fill="#ee539a" />
|
||||
<path d="M99.952 104.069v-28.89L75 60.644l-.018 28.89H75z" fill="#d01b68" />
|
||||
<path d="M124.922 60.841l-24.755 14.23-.215.107v28.89l24.97-14.338.018-28.89z" fill="#ec0f68" />
|
||||
<path d="M124.923 89.731l.215-.125 24.755-14.23-24.952-14.535h-.018l-24.97 14.338z" fill="#a159a2" />
|
||||
<path d="M124.905 118.622l.018-28.89-24.97-14.535v28.872.018z" fill="#772a86" />
|
||||
<path d="M149.893 75.376l-24.755 14.23-.215.125-.018 28.89h.018l24.97-14.356z" fill="#8e3b95" />
|
||||
<path d="M50.03 75l.197-.125 24.755-14.23L50.03 46.109 25.06 60.447l24.952 14.535z" fill="#c8d92b" />
|
||||
<path d="M50.012 103.872L50.03 75 25.06 60.447l-.018 28.89h.018z" fill="#c2d02f" />
|
||||
<path d="M75 60.644l-24.773 14.23-.197.125-.018 28.872 24.97-14.338z" fill="#b9be33" />
|
||||
<path d="M74.982 89.534l.215-.125 24.755-14.23L75 60.644l-24.97 14.338z" fill="#f7af19" />
|
||||
<path d="M74.982 118.425v-.018.018-28.89L50.029 75l-.018 28.872.018.018z" fill="#f2901f" />
|
||||
<path d="M99.952 75.179l-24.755 14.23-.215.125v28.89l24.97-14.356.018-28.89z" fill="#f9a120" />
|
||||
<path d="M99.934 135.769l.215-.125 24.684-14.356-25.042-14.409L74.91 121.36z" fill="#833593" />
|
||||
<path d="M100.077 164.66l-.143-28.89-25.042-14.409.143 28.89h.018z" fill="#652977" />
|
||||
<path d="M124.833 121.288l-24.684 14.356-.215.125.143 28.89 24.899-14.481z" fill="#4d1f5b" />
|
||||
<path d="M99.952 104.069l.215-.107 24.755-14.23L99.97 75.179h-.018l-24.97 14.356z" fill="#a159a2" />
|
||||
<path d="M99.934 132.959l.018-28.89-24.97-14.535v28.89z" fill="#772a86" />
|
||||
<path d="M124.922 89.732l-24.755 14.23-.215.107-.018 28.89h.018l24.97-14.338z" fill="#8e3b95" />
|
||||
<path d="M25.042 121.074l.197-.125 24.755-14.248-24.952-14.517h-.018L.071 106.54l24.952 14.535z" fill="#f6dd03" />
|
||||
<path d="M25.024 149.947h.018v-28.872L.071 106.54v28.89z" fill="#f9bb1d" />
|
||||
<path d="M49.994 106.719l-24.755 14.23-.197.125v28.872.018l24.952-14.356h.018v-.018-28.872z" fill="#e9ae20" />
|
||||
<path d="M25.06 89.338l.197-.125 24.755-14.23L25.06 60.447.089 74.803l24.952 14.535z" fill="#f6dd03" />
|
||||
<path d="M25.042 118.228l.018-28.89L.089 74.803.072 103.675l.018.018z" fill="#f9bb1d" />
|
||||
<path d="M50.03 75L25.257 89.212l-.197.125-.018 28.89 24.97-14.356v.018-.018z" fill="#e9ae20" />
|
||||
<path d="M50.012 135.59l.215-.107 24.737-14.248L50.012 106.7l-24.97 14.374z" fill="#f7af19" />
|
||||
<path d="M50.012 164.481v-28.89l-24.97-14.517v28.872.018z" fill="#f2901f" />
|
||||
<path d="M74.964 121.235l-24.755 14.248-.197.107v28.89l24.97-14.356v-28.89z" fill="#f9a120" />
|
||||
<path d="M50.012 103.872l.215-.107 24.755-14.23L50.03 74.982 25.06 89.338z" fill="#f7af19" />
|
||||
<path d="M50.012 132.763v-28.89L25.06 89.338l-.018 28.89h.018z" fill="#f2901f" />
|
||||
<path d="M74.982 89.535l-24.755 14.23-.215.107v28.89l24.97-14.338.018-28.89z" fill="#f9a120" />
|
||||
<path d="M74.982 150.125l.197-.125 24.755-14.23-24.952-14.535h-.018l-24.952 14.356 24.952 14.535z" fill="#f7af19" />
|
||||
<path d="M74.964 179.015h.018v-28.89l-24.97-14.517v28.872.018z" fill="#f2901f" />
|
||||
<path d="M99.934 135.77L75.179 150l-.197.125v28.89l24.97-14.356v-28.89z" fill="#f9a120" />
|
||||
<path d="M74.982 118.425l.215-.125 24.755-14.23L75 89.535h-.018l-24.97 14.338z" fill="#31838b" />
|
||||
<path d="M74.964 147.297l.018-28.872-24.97-14.535v-.018 28.89.018z" fill="#22626c" />
|
||||
<path d="M99.952 104.069L75.197 118.3l-.215.125-.018 28.872v.018h.018l24.97-14.356z" fill="#1b4b56" />
|
||||
<path d="M74.982 28.962l.215-.125 24.737-14.248L74.982.072l-24.97 14.356 24.97 14.517z" fill="#bec831" />
|
||||
<path d="M74.982 57.834V28.962l-24.97-14.535v28.89h.018z" fill="#a1a938" />
|
||||
<path d="M99.952 14.606l-24.755 14.23-.215.125v28.872l24.97-14.356z" fill="#999b37" />
|
||||
<path d="M74.964 28.944l.215-.125 24.755-14.23L74.982.054h-.018l-24.97 14.338z" fill="#a159a2" />
|
||||
<path d="M74.946 57.835l.018-28.89-24.97-14.535v28.872.018z" fill="#772a86" />
|
||||
<path d="M99.934 14.589l-24.755 14.23-.215.125-.018 28.89h.018l24.97-14.356z" fill="#8e3b95" />
|
||||
<path d="M99.952 43.479l.215-.107 24.755-14.248-24.97-14.535-24.97 14.356z" fill="#ee539a" />
|
||||
<use xlink:href="#B" y="-63.419" fill="#d01b68" />
|
||||
<path d="M124.922 29.123l-24.755 14.248-.215.107v28.89l24.97-14.356z" fill="#ec0f68" />
|
||||
<path d="M50.03 43.317l.215-.125L75 28.961 50.048 14.427h-.018L25.06 28.765z" fill="#31838b" />
|
||||
<path d="M50.012 72.189l.018-28.872-24.97-14.535v-.018 28.89.018z" fill="#22626c" />
|
||||
<path d="M75 28.961l-24.755 14.23-.215.125-.018 28.872v.018h.018L75 57.852z" fill="#1b4b56" />
|
||||
<path d="M124.923 58.013l.215-.125 24.737-14.23-24.952-14.535-24.97 14.356z" fill="#cc2a65" />
|
||||
<use xlink:href="#B" x="24.971" y="-48.884" fill="#a21d4c" />
|
||||
<path d="M149.893 43.658l-24.755 14.23-.215.125v28.89l24.97-14.356z" fill="#b62454" />
|
||||
<path d="M74.982 57.835l.215-.107 24.755-14.248-24.97-14.535L50.012 43.3z" fill="#c8d92b" />
|
||||
<path d="M74.982 86.725v-28.89l-24.97-14.517V72.19l.018.018z" fill="#c2d02f" />
|
||||
<path d="M99.952 43.479L75.197 57.727l-.215.107v28.89H75l24.952-14.356z" fill="#b9be33" />
|
||||
<path d="M99.952 72.369l.215-.125 24.755-14.23-24.97-14.535-24.97 14.356z" fill="#33b4d7" />
|
||||
<use xlink:href="#B" y="-34.529" fill="#209ac5" />
|
||||
<path d="M124.922 58.014l-24.755 14.23-.215.125v28.89h.018l24.952-14.356z" fill="#0f8cae" />
|
||||
<path d="M25.06 57.673l.197-.125L50.012 43.3 25.06 28.783h-.018L.089 43.139l24.952 14.535z" fill="#94d6e3" />
|
||||
<path d="M25.042 86.546h.018V57.673L.089 43.139v28.89z" fill="#73ccdd" />
|
||||
<path d="M50.012 43.318l-24.755 14.23-.197.125v28.872.018l24.952-14.356h.018v-.018-28.872z" fill="#3bafbb" />
|
||||
<path d="M50.03 72.19l.215-.107 24.737-14.248L50.03 43.318 25.06 57.674z" fill="#94d6e3" />
|
||||
<path d="M50.03 101.08V72.208v-.018L25.06 57.674v28.872.018z" fill="#73ccdd" />
|
||||
<path d="M74.982 57.835L50.227 72.083l-.197.107v28.89L75 86.725v-28.89z" fill="#3bafbb" />
|
||||
<path d="M75 86.724l.197-.107 24.755-14.248L75 57.834h-.018L50.029 72.189l24.952 14.535z" fill="#33b4d7" />
|
||||
<path d="M74.982 115.614H75v-28.89l-24.97-14.517v28.872.018z" fill="#209ac5" />
|
||||
<path d="M99.952 72.368L75.197 86.617l-.197.107v28.89l24.97-14.356v-28.89z" fill="#0f8cae" />
|
||||
</g>
|
||||
<path d="M759.126 567.007s10.273 21.02 16.364 35.698c25.549 33.869 90.792 36.224 119.235.656 9.484-17.619 16.733-36.357 16.733-36.357-7.297 10.862-20.094 18.056-27.408 22.095-5.197 2.861-17.189 4.59-17.189 4.59l-31.482-16.393-31.663 16.065s-11.832-1.91-17.189-4.426c-10.811-5.799-19.735-12.549-27.401-21.928z" fill="#ffe953" />
|
||||
</g>
|
||||
<defs>
|
||||
<path id="B" d="M99.952 135.788v-28.89l-24.97-14.517v28.872l.018.018z" />
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.4 KiB |
2
.git-blame-ignore-revs
Normal file
2
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,2 @@
|
||||
# Formatted with nph 0.5.1
|
||||
dc83a1e9b68f00b3be7e09febdb1a3f877321b9a
|
||||
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* @vacp2p/p2p
|
||||
41
.github/actions/add_comment/action.yml
vendored
Normal file
41
.github/actions/add_comment/action.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Add Comment
|
||||
description: "Add or update comment in the PR"
|
||||
inputs:
|
||||
marker:
|
||||
description: "Text used to find the comment to update"
|
||||
required: true
|
||||
markdown_path:
|
||||
description: "Path to the file containing markdown"
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Add/Update Comment
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const marker = "${{ inputs.marker }}";
|
||||
const body = fs.readFileSync("${{ inputs.markdown_path }}", 'utf8');
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
});
|
||||
const existing = comments.find(c => c.body && c.body.startsWith(marker));
|
||||
if (existing) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: existing.id,
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body,
|
||||
});
|
||||
}
|
||||
49
.github/actions/discord_notify/action.yml
vendored
Normal file
49
.github/actions/discord_notify/action.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: Discord Failure Notification
|
||||
description: "Send Discord notification when CI jobs fail"
|
||||
inputs:
|
||||
webhook_url:
|
||||
description: "Discord webhook URL"
|
||||
required: true
|
||||
workflow_name:
|
||||
description: "Name of the workflow that failed"
|
||||
required: false
|
||||
default: ${{ github.workflow }}
|
||||
branch:
|
||||
description: "Branch name"
|
||||
required: false
|
||||
default: ${{ github.ref_name }}
|
||||
repository:
|
||||
description: "Repository name"
|
||||
required: false
|
||||
default: ${{ github.repository }}
|
||||
run_id:
|
||||
description: "GitHub run ID"
|
||||
required: false
|
||||
default: ${{ github.run_id }}
|
||||
server_url:
|
||||
description: "GitHub server URL"
|
||||
required: false
|
||||
default: ${{ github.server_url }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Send Discord notification
|
||||
shell: bash
|
||||
run: |
|
||||
curl -H "Content-Type: application/json" \
|
||||
-X POST \
|
||||
-d "{
|
||||
\"embeds\": [{
|
||||
\"title\": \"${{ inputs.workflow_name }} Job Failed\",
|
||||
\"url\": \"${{ inputs.server_url }}/${{ inputs.repository }}/actions/runs/${{ inputs.run_id }}\",
|
||||
\"description\": \"The workflow has failed on branch \`${{ inputs.branch }}\`\",
|
||||
\"color\": 15158332,
|
||||
\"fields\": [
|
||||
{\"name\": \"Repository\", \"value\": \"${{ inputs.repository }}\", \"inline\": true},
|
||||
{\"name\": \"Branch\", \"value\": \"${{ inputs.branch }}\", \"inline\": true}
|
||||
],
|
||||
\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\"
|
||||
}]
|
||||
}" \
|
||||
"${{ inputs.webhook_url }}"
|
||||
133
.github/actions/install_nim/action.yml
vendored
Normal file
133
.github/actions/install_nim/action.yml
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
name: Install Nim
|
||||
inputs:
|
||||
os:
|
||||
description: "Operating system to build for"
|
||||
required: true
|
||||
cpu:
|
||||
description: "CPU to build for"
|
||||
default: "amd64"
|
||||
nim_ref:
|
||||
description: "Nim version"
|
||||
default: "version-2-0"
|
||||
shell:
|
||||
description: "Shell to run commands in"
|
||||
default: "bash --noprofile --norc -e -o pipefail"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install build dependencies (Linux i386)
|
||||
shell: ${{ inputs.shell }}
|
||||
if: inputs.os == 'Linux' && inputs.cpu == 'i386'
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update -qq
|
||||
sudo DEBIAN_FRONTEND='noninteractive' apt-get install \
|
||||
--no-install-recommends -yq gcc-multilib g++-multilib \
|
||||
libssl-dev:i386
|
||||
mkdir -p external/bin
|
||||
cat << EOF > external/bin/gcc
|
||||
#!/bin/bash
|
||||
exec $(which gcc) -m32 "\$@"
|
||||
EOF
|
||||
cat << EOF > external/bin/g++
|
||||
#!/bin/bash
|
||||
exec $(which g++) -m32 "\$@"
|
||||
EOF
|
||||
chmod 755 external/bin/gcc external/bin/g++
|
||||
echo '${{ github.workspace }}/external/bin' >> $GITHUB_PATH
|
||||
|
||||
- name: MSYS2 (Windows i386)
|
||||
if: inputs.os == 'Windows' && inputs.cpu == 'i386'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
path-type: inherit
|
||||
msystem: MINGW32
|
||||
install: >-
|
||||
base-devel
|
||||
git
|
||||
mingw-w64-i686-toolchain
|
||||
|
||||
- name: MSYS2 (Windows amd64)
|
||||
if: inputs.os == 'Windows' && inputs.cpu == 'amd64'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
path-type: inherit
|
||||
install: >-
|
||||
base-devel
|
||||
git
|
||||
mingw-w64-x86_64-toolchain
|
||||
|
||||
- name: Restore Nim DLLs dependencies (Windows) from cache
|
||||
if: inputs.os == 'Windows'
|
||||
id: windows-dlls-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: external/dlls
|
||||
key: 'dlls'
|
||||
|
||||
- name: Install DLL dependencies (Windows)
|
||||
shell: ${{ inputs.shell }}
|
||||
if: >
|
||||
steps.windows-dlls-cache.outputs.cache-hit != 'true' &&
|
||||
inputs.os == 'Windows'
|
||||
run: |
|
||||
mkdir external
|
||||
curl -L "https://nim-lang.org/download/windeps.zip" -o external/windeps.zip
|
||||
7z x external/windeps.zip -oexternal/dlls
|
||||
|
||||
- name: Path to cached dependencies (Windows)
|
||||
shell: ${{ inputs.shell }}
|
||||
if: >
|
||||
inputs.os == 'Windows'
|
||||
run: |
|
||||
echo '${{ github.workspace }}'"/external/dlls" >> $GITHUB_PATH
|
||||
|
||||
- name: Derive environment variables
|
||||
shell: ${{ inputs.shell }}
|
||||
run: |
|
||||
if [[ '${{ inputs.cpu }}' == 'amd64' ]]; then
|
||||
PLATFORM=x64
|
||||
elif [[ '${{ inputs.cpu }}' == 'arm64' ]]; then
|
||||
PLATFORM=arm64
|
||||
else
|
||||
PLATFORM=x86
|
||||
fi
|
||||
echo "PLATFORM=$PLATFORM" >> $GITHUB_ENV
|
||||
|
||||
ncpu=
|
||||
MAKE_CMD="make"
|
||||
case '${{ inputs.os }}' in
|
||||
'Linux')
|
||||
ncpu=$(nproc)
|
||||
;;
|
||||
'macOS')
|
||||
ncpu=$(sysctl -n hw.ncpu)
|
||||
;;
|
||||
'Windows')
|
||||
ncpu=$NUMBER_OF_PROCESSORS
|
||||
MAKE_CMD="mingw32-make"
|
||||
;;
|
||||
esac
|
||||
[[ -z "$ncpu" || $ncpu -le 0 ]] && ncpu=1
|
||||
echo "ncpu=$ncpu" >> $GITHUB_ENV
|
||||
echo "MAKE_CMD=${MAKE_CMD}" >> $GITHUB_ENV
|
||||
echo '${{ github.workspace }}/nim/bin' >> $GITHUB_PATH
|
||||
|
||||
- name: Restore Nim from cache
|
||||
id: nim-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: '${{ github.workspace }}/nim'
|
||||
key: ${{ inputs.os }}-${{ inputs.cpu }}-nim-${{ inputs.nim_ref }}-cache-${{ env.cache_nonce }}
|
||||
|
||||
- name: Build Nim and Nimble
|
||||
shell: ${{ inputs.shell }}
|
||||
if: ${{ steps.nim-cache.outputs.cache-hit != 'true' }}
|
||||
run: |
|
||||
# We don't want partial matches of the cache restored
|
||||
rm -rf nim
|
||||
curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh
|
||||
env MAKE="${MAKE_CMD} -j${ncpu}" ARCH_OVERRIDE=${PLATFORM} NIM_COMMIT=${{ inputs.nim_ref }} \
|
||||
QUICK_AND_DIRTY_COMPILER=1 QUICK_AND_DIRTY_NIMBLE=1 CC=gcc \
|
||||
bash build_nim.sh nim csources dist/nimble NimBinaries
|
||||
12
.github/workflows/auto_assign_pr.yml
vendored
Normal file
12
.github/workflows/auto_assign_pr.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Auto Assign PR to Creator
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
|
||||
jobs:
|
||||
assign_creator:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: toshimaru/auto-author-assign@v1.6.2
|
||||
273
.github/workflows/ci.yml
vendored
273
.github/workflows/ci.yml
vendored
@@ -1,255 +1,120 @@
|
||||
name: CI
|
||||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- unstable
|
||||
pull_request:
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 90
|
||||
test:
|
||||
timeout-minutes: 40
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
platform:
|
||||
- os: linux
|
||||
cpu: amd64
|
||||
- os: linux
|
||||
cpu: i386
|
||||
- os: linux-gcc-14
|
||||
cpu: amd64
|
||||
- os: macos
|
||||
cpu: amd64
|
||||
- os: macos-14
|
||||
cpu: arm64
|
||||
- os: windows
|
||||
cpu: amd64
|
||||
#- os: windows
|
||||
#cpu: i386
|
||||
branch: [version-1-2, devel]
|
||||
nim:
|
||||
- ref: version-2-0
|
||||
memory_management: refc
|
||||
- ref: version-2-2
|
||||
memory_management: refc
|
||||
include:
|
||||
- target:
|
||||
- platform:
|
||||
os: linux
|
||||
builder: ubuntu-20.04
|
||||
builder: ubuntu-22.04
|
||||
shell: bash
|
||||
- target:
|
||||
- platform:
|
||||
os: linux-gcc-14
|
||||
builder: ubuntu-24.04
|
||||
shell: bash
|
||||
- platform:
|
||||
os: macos
|
||||
builder: macos-10.15
|
||||
builder: macos-13
|
||||
shell: bash
|
||||
- target:
|
||||
- platform:
|
||||
os: macos-14
|
||||
builder: macos-14
|
||||
shell: bash
|
||||
- platform:
|
||||
os: windows
|
||||
builder: windows-2019
|
||||
builder: windows-2022
|
||||
shell: msys2 {0}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.shell }}
|
||||
|
||||
name: '${{ matrix.target.os }}-${{ matrix.target.cpu }} (Nim ${{ matrix.branch }})'
|
||||
name: '${{ matrix.platform.os }}-${{ matrix.platform.cpu }} (Nim ${{ matrix.nim.ref }})'
|
||||
runs-on: ${{ matrix.builder }}
|
||||
continue-on-error: ${{ matrix.branch == 'version-1-6' || matrix.branch == 'devel' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install build dependencies (Linux i386)
|
||||
if: runner.os == 'Linux' && matrix.target.cpu == 'i386'
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update -qq
|
||||
sudo DEBIAN_FRONTEND='noninteractive' apt-get install \
|
||||
--no-install-recommends -yq gcc-multilib g++-multilib \
|
||||
libssl-dev:i386
|
||||
mkdir -p external/bin
|
||||
cat << EOF > external/bin/gcc
|
||||
#!/bin/bash
|
||||
exec $(which gcc) -m32 "\$@"
|
||||
EOF
|
||||
cat << EOF > external/bin/g++
|
||||
#!/bin/bash
|
||||
exec $(which g++) -m32 "\$@"
|
||||
EOF
|
||||
chmod 755 external/bin/gcc external/bin/g++
|
||||
echo '${{ github.workspace }}/external/bin' >> $GITHUB_PATH
|
||||
|
||||
- name: MSYS2 (Windows i386)
|
||||
if: runner.os == 'Windows' && matrix.target.cpu == 'i386'
|
||||
uses: msys2/setup-msys2@v2
|
||||
- name: Setup Nim
|
||||
uses: "./.github/actions/install_nim"
|
||||
with:
|
||||
path-type: inherit
|
||||
msystem: MINGW32
|
||||
install: >-
|
||||
base-devel
|
||||
git
|
||||
mingw-w64-i686-toolchain
|
||||
|
||||
- name: MSYS2 (Windows amd64)
|
||||
if: runner.os == 'Windows' && matrix.target.cpu == 'amd64'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
path-type: inherit
|
||||
install: >-
|
||||
base-devel
|
||||
git
|
||||
mingw-w64-x86_64-toolchain
|
||||
|
||||
- name: Restore Nim DLLs dependencies (Windows) from cache
|
||||
if: runner.os == 'Windows'
|
||||
id: windows-dlls-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: external/dlls
|
||||
key: 'dlls'
|
||||
|
||||
- name: Install DLL dependencies (Windows)
|
||||
if: >
|
||||
steps.windows-dlls-cache.outputs.cache-hit != 'true' &&
|
||||
runner.os == 'Windows'
|
||||
run: |
|
||||
mkdir external
|
||||
curl -L "https://nim-lang.org/download/windeps.zip" -o external/windeps.zip
|
||||
7z x external/windeps.zip -oexternal/dlls
|
||||
|
||||
- name: Path to cached dependencies (Windows)
|
||||
if: >
|
||||
runner.os == 'Windows'
|
||||
run: |
|
||||
echo '${{ github.workspace }}'"/external/dlls" >> $GITHUB_PATH
|
||||
|
||||
- name: Derive environment variables
|
||||
run: |
|
||||
if [[ '${{ matrix.target.cpu }}' == 'amd64' ]]; then
|
||||
PLATFORM=x64
|
||||
else
|
||||
PLATFORM=x86
|
||||
fi
|
||||
echo "PLATFORM=$PLATFORM" >> $GITHUB_ENV
|
||||
|
||||
ncpu=
|
||||
MAKE_CMD="make"
|
||||
case '${{ runner.os }}' in
|
||||
'Linux')
|
||||
ncpu=$(nproc)
|
||||
;;
|
||||
'macOS')
|
||||
ncpu=$(sysctl -n hw.ncpu)
|
||||
;;
|
||||
'Windows')
|
||||
ncpu=$NUMBER_OF_PROCESSORS
|
||||
MAKE_CMD="mingw32-make"
|
||||
;;
|
||||
esac
|
||||
[[ -z "$ncpu" || $ncpu -le 0 ]] && ncpu=1
|
||||
echo "ncpu=$ncpu" >> $GITHUB_ENV
|
||||
echo "MAKE_CMD=${MAKE_CMD}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Nim and Nimble
|
||||
run: |
|
||||
curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh
|
||||
env MAKE="${MAKE_CMD} -j${ncpu}" ARCH_OVERRIDE=${PLATFORM} NIM_COMMIT=${{ matrix.branch }} \
|
||||
QUICK_AND_DIRTY_COMPILER=1 QUICK_AND_DIRTY_NIMBLE=1 CC=gcc \
|
||||
bash build_nim.sh nim csources dist/nimble NimBinaries
|
||||
echo '${{ github.workspace }}/nim/bin' >> $GITHUB_PATH
|
||||
os: ${{ matrix.platform.os }}
|
||||
cpu: ${{ matrix.platform.cpu }}
|
||||
shell: ${{ matrix.shell }}
|
||||
nim_ref: ${{ matrix.nim.ref }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '^1.15.5'
|
||||
go-version: '~1.16.0' # That's the minimum Go version that works with arm.
|
||||
|
||||
- name: Install p2pd
|
||||
run: |
|
||||
V=1 bash scripts/build_p2pd.sh p2pdCache 124530a3
|
||||
|
||||
- name: Restore deps from cache
|
||||
id: deps-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: nimbledeps
|
||||
# Using nim.ref as a simple way to differentiate between nimble using the "pkgs" or "pkgs2" directories.
|
||||
# The change happened on Nimble v0.14.0. Also forcing the deps to be reinstalled on each os and cpu.
|
||||
key: nimbledeps-${{ matrix.nim.ref }}-${{ matrix.builder }}-${{ matrix.platform.cpu }}-${{ hashFiles('.pinned') }} # hashFiles returns a different value on windows
|
||||
|
||||
- name: Install deps
|
||||
if: ${{ steps.deps-cache.outputs.cache-hit != 'true' }}
|
||||
run: |
|
||||
nimble install_pinned
|
||||
|
||||
- name: Use gcc 14
|
||||
if : ${{ matrix.platform.os == 'linux-gcc-14'}}
|
||||
run: |
|
||||
# Add GCC-14 to alternatives
|
||||
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 14
|
||||
|
||||
# Set GCC-14 as the default
|
||||
sudo update-alternatives --set gcc /usr/bin/gcc-14
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
if [[ "${{ matrix.target.os }}" == "windows" ]]; then
|
||||
# https://github.com/status-im/nimbus-eth2/issues/3121
|
||||
export NIMFLAGS="-d:nimRawSetjmp"
|
||||
fi
|
||||
nim --version
|
||||
nimble --version
|
||||
nimble install_pinned
|
||||
gcc --version
|
||||
|
||||
export NIMFLAGS="${NIMFLAGS} --mm:${{ matrix.nim.memory_management }}"
|
||||
nimble test
|
||||
|
||||
bumpNBC-stable:
|
||||
if: github.ref == 'refs/heads/master'
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: status-im/github-app-token@v1
|
||||
name: Generate token
|
||||
id: generate-token
|
||||
with:
|
||||
app_id: ${{ secrets.BUMP_BOT_APP_ID }}
|
||||
private_key: ${{ secrets.BUMP_BOT_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Clone NBC
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: status-im/nimbus-eth2
|
||||
ref: unstable
|
||||
path: nbc
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout this ref
|
||||
run: |
|
||||
cd nbc/vendor/nim-libp2p
|
||||
git checkout $GITHUB_SHA
|
||||
|
||||
- name: Commit this bump
|
||||
run: |
|
||||
cd nbc
|
||||
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
|
||||
git config --global user.name = "${{ github.actor }}"
|
||||
git commit -a -m "auto-bump nim-libp2p"
|
||||
|
||||
- name: Make PR
|
||||
uses: peter-evans/create-pull-request@v3.5.0
|
||||
with:
|
||||
branch: nim-libp2p-auto-bump
|
||||
path: nbc
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
title: nim-libp2p auto bump
|
||||
|
||||
bumpNBC-unstable:
|
||||
if: github.ref == 'refs/heads/unstable'
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: status-im/github-app-token@v1
|
||||
name: Generate token
|
||||
id: generate-token
|
||||
with:
|
||||
app_id: ${{ secrets.BUMP_BOT_APP_ID }}
|
||||
private_key: ${{ secrets.BUMP_BOT_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Clone NBC
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: status-im/nimbus-eth2
|
||||
ref: unstable
|
||||
path: nbc
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout this ref
|
||||
run: |
|
||||
cd nbc/vendor/nim-libp2p
|
||||
git checkout $GITHUB_SHA
|
||||
|
||||
- name: Commit this bump
|
||||
run: |
|
||||
cd nbc
|
||||
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
|
||||
git config --global user.name = "${{ github.actor }}"
|
||||
git commit -a -m "auto-bump nim-libp2p"
|
||||
|
||||
- name: Make PR
|
||||
uses: peter-evans/create-pull-request@v3.5.0
|
||||
with:
|
||||
branch: nim-libp2p-auto-bump-unstable
|
||||
path: nbc
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
title: nim-libp2p unstable auto bump
|
||||
draft: true
|
||||
|
||||
134
.github/workflows/codecov.yml
vendored
134
.github/workflows/codecov.yml
vendored
@@ -1,134 +0,0 @@
|
||||
name: nim-libp2p codecov builds
|
||||
|
||||
on:
|
||||
#On push to common branches, this computes the "bases stats" for PRs
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
GossipSub:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
nim-options: [
|
||||
"",
|
||||
"-d:libp2p_pubsub_anonymize=true -d:libp2p_pubsub_sign=false -d:libp2p_pubsub_verify=false",
|
||||
"-d:libp2p_pubsub_sign=true -d:libp2p_pubsub_verify=true"
|
||||
]
|
||||
test-program: [
|
||||
"tests/pubsub/testpubsub",
|
||||
"tests/pubsub/testfloodsub",
|
||||
"tests/pubsub/testgossipinternal"
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y lcov build-essential git curl
|
||||
mkdir coverage
|
||||
curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh
|
||||
env MAKE="make -j${NPROC}" bash build_nim.sh Nim csources dist/nimble NimBinaries
|
||||
export PATH="$PATH:$PWD/Nim/bin"
|
||||
nimble install_pinned
|
||||
export NIM_OPTIONS="--opt:speed -d:debug --verbosity:0 --hints:off --lineDir:on -d:chronicles_log_level=INFO --warning[CaseTransition]:off --warning[ObservableStores]:off --warning[LockLevel]:off --nimcache:nimcache --passC:-fprofile-arcs --passC:-ftest-coverage --passL:-fprofile-arcs --passL:-ftest-coverage ${{ matrix.nim-options }}"
|
||||
nim c $NIM_OPTIONS -r ${{ matrix.test-program }}
|
||||
cd nimcache; rm *.c; cd ..
|
||||
lcov --capture --directory nimcache --output-file coverage/coverage.info
|
||||
shopt -s globstar
|
||||
ls `pwd`/libp2p/{*,**/*}.nim
|
||||
lcov --extract coverage/coverage.info `pwd`/libp2p/{*,**/*}.nim --output-file coverage/coverage.f.info
|
||||
export COV_UUID=`cksum <<< "${{ matrix.test-program }} $NIM_OPTIONS" | cut -f 1 -d ' '`
|
||||
genhtml coverage/coverage.f.info --output-directory coverage/$COV_UUID-output
|
||||
echo ${{ matrix.test-program }} > coverage/$COV_UUID-nim_options.txt
|
||||
echo $NIM_OPTIONS >> coverage/$COV_UUID-nim_options.txt
|
||||
bash <(curl -s https://codecov.io/bash) -f coverage/coverage.f.info || echo "Codecov did not collect coverage reports"
|
||||
- uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: coverage
|
||||
path: coverage
|
||||
|
||||
Tests:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
nim-options: [
|
||||
""
|
||||
]
|
||||
test-program: [
|
||||
"tests/testnative",
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y lcov build-essential git curl
|
||||
mkdir coverage
|
||||
curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh
|
||||
env MAKE="make -j${NPROC}" bash build_nim.sh Nim csources dist/nimble NimBinaries
|
||||
export PATH="$PATH:$PWD/Nim/bin"
|
||||
nimble install_pinned
|
||||
export NIM_OPTIONS="--opt:speed -d:debug --verbosity:0 --hints:off --lineDir:on -d:chronicles_log_level=INFO --warning[CaseTransition]:off --warning[ObservableStores]:off --warning[LockLevel]:off --nimcache:nimcache --passC:-fprofile-arcs --passC:-ftest-coverage --passL:-fprofile-arcs --passL:-ftest-coverage ${{ matrix.nim-options }} --clearNimblePath --NimblePath:nimbledeps/pkgs"
|
||||
nim c $NIM_OPTIONS -r ${{ matrix.test-program }}
|
||||
cd nimcache; rm *.c; cd ..
|
||||
lcov --capture --directory nimcache --output-file coverage/coverage.info
|
||||
shopt -s globstar
|
||||
ls `pwd`/libp2p/{*,**/*}.nim
|
||||
lcov --extract coverage/coverage.info `pwd`/libp2p/{*,**/*}.nim --output-file coverage/coverage.f.info
|
||||
export COV_UUID=`cksum <<< "${{ matrix.test-program }} $NIM_OPTIONS" | cut -f 1 -d ' '`
|
||||
genhtml coverage/coverage.f.info --output-directory coverage/$COV_UUID-output
|
||||
echo ${{ matrix.test-program }} > coverage/$COV_UUID-nim_options.txt
|
||||
echo $NIM_OPTIONS >> coverage/$COV_UUID-nim_options.txt
|
||||
bash <(curl -s https://codecov.io/bash) -f coverage/coverage.f.info || echo "Codecov did not collect coverage reports"
|
||||
- uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: coverage
|
||||
path: coverage
|
||||
|
||||
Filter:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
nim-options: [
|
||||
"",
|
||||
"-d:libp2p_pki_schemes=secp256k1",
|
||||
"-d:libp2p_pki_schemes=secp256k1;ed25519",
|
||||
"-d:libp2p_pki_schemes=secp256k1;ed25519;ecnist",
|
||||
]
|
||||
test-program: [
|
||||
"tests/testpkifilter",
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y lcov build-essential git curl
|
||||
mkdir coverage
|
||||
curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh
|
||||
env MAKE="make -j${NPROC}" bash build_nim.sh Nim csources dist/nimble NimBinaries
|
||||
export PATH="$PATH:$PWD/Nim/bin"
|
||||
nimble install_pinned
|
||||
export NIM_OPTIONS="--opt:speed -d:debug --verbosity:0 --hints:off --lineDir:on -d:chronicles_log_level=INFO --warning[CaseTransition]:off --warning[ObservableStores]:off --warning[LockLevel]:off --nimcache:nimcache --passC:-fprofile-arcs --passC:-ftest-coverage --passL:-fprofile-arcs --passL:-ftest-coverage ${{ matrix.nim-options }}"
|
||||
nim c $NIM_OPTIONS -r ${{ matrix.test-program }}
|
||||
cd nimcache; rm *.c; cd ..
|
||||
lcov --capture --directory nimcache --output-file coverage/coverage.info
|
||||
shopt -s globstar
|
||||
ls `pwd`/libp2p/{*,**/*}.nim
|
||||
lcov --extract coverage/coverage.info `pwd`/libp2p/{*,**/*}.nim --output-file coverage/coverage.f.info
|
||||
export COV_UUID=`cksum <<< "${{ matrix.test-program }} $NIM_OPTIONS" | cut -f 1 -d ' '`
|
||||
genhtml coverage/coverage.f.info --output-directory coverage/$COV_UUID-output
|
||||
echo ${{ matrix.test-program }} > coverage/$COV_UUID-nim_options.txt
|
||||
echo $NIM_OPTIONS >> coverage/$COV_UUID-nim_options.txt
|
||||
bash <(curl -s https://codecov.io/bash) -f coverage/coverage.f.info || echo "Codecov did not collect coverage reports"
|
||||
- uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: coverage
|
||||
path: coverage
|
||||
|
||||
|
||||
|
||||
70
.github/workflows/coverage.yml
vendored
Normal file
70
.github/workflows/coverage.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: Coverage
|
||||
|
||||
on:
|
||||
# On push to common branches, this computes the coverage that PRs will use for diff
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
codecov:
|
||||
name: Run coverage and upload to codecov
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
CICOV: YES
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Nim
|
||||
uses: "./.github/actions/install_nim"
|
||||
with:
|
||||
os: linux
|
||||
cpu: amd64
|
||||
shell: bash
|
||||
|
||||
- name: Restore deps from cache
|
||||
id: deps-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: nimbledeps
|
||||
key: nimbledeps-${{ hashFiles('.pinned') }}
|
||||
|
||||
- name: Install deps
|
||||
if: ${{ steps.deps-cache.outputs.cache-hit != 'true' }}
|
||||
run: |
|
||||
nimble install_pinned
|
||||
|
||||
- name: Setup coverage
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y lcov build-essential git curl
|
||||
mkdir coverage
|
||||
|
||||
- name: Run test suite with coverage flags
|
||||
run: |
|
||||
export NIMFLAGS="--lineDir:on --passC:-fprofile-arcs --passC:-ftest-coverage --passL:-fprofile-arcs --passL:-ftest-coverage"
|
||||
nimble testnative
|
||||
nimble testpubsub
|
||||
nimble testfilter
|
||||
|
||||
- name: Run coverage
|
||||
run: |
|
||||
find nimcache -name *.c -delete
|
||||
lcov --capture --directory nimcache --output-file coverage/coverage.info
|
||||
shopt -s globstar
|
||||
ls `pwd`/libp2p/{*,**/*}.nim
|
||||
lcov --extract coverage/coverage.info `pwd`/libp2p/{*,**/*}.nim --output-file coverage/coverage.f.info
|
||||
genhtml coverage/coverage.f.info --output-directory coverage/output
|
||||
|
||||
- name: Upload coverage to codecov
|
||||
run: |
|
||||
bash <(curl -s https://codecov.io/bash) -f coverage/coverage.f.info || echo "Codecov did not collect coverage reports"
|
||||
42
.github/workflows/daily_amd64.yml
vendored
Normal file
42
.github/workflows/daily_amd64.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Daily amd64
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 6 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test_amd64_latest:
|
||||
name: Daily test amd64 (latest dependencies)
|
||||
uses: ./.github/workflows/daily_common.yml
|
||||
with:
|
||||
nim: "[
|
||||
{'ref': 'version-2-0', 'memory_management': 'refc'},
|
||||
{'ref': 'version-2-2', 'memory_management': 'refc'},
|
||||
{'ref': 'devel', 'memory_management': 'refc'},
|
||||
]"
|
||||
cpu: "['amd64']"
|
||||
test_amd64_pinned:
|
||||
name: Daily test amd64 (pinned dependencies)
|
||||
uses: ./.github/workflows/daily_common.yml
|
||||
with:
|
||||
pinned_deps: true
|
||||
nim: "[
|
||||
{'ref': 'version-2-0', 'memory_management': 'refc'},
|
||||
{'ref': 'version-2-2', 'memory_management': 'refc'},
|
||||
{'ref': 'devel', 'memory_management': 'refc'},
|
||||
]"
|
||||
cpu: "['amd64']"
|
||||
notify-on-failure:
|
||||
name: Notify Discord on Failure
|
||||
needs: [test_amd64_latest, test_amd64_pinned]
|
||||
if: failure()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Discord notification
|
||||
uses: ./.github/actions/discord_notify
|
||||
with:
|
||||
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
107
.github/workflows/daily_common.yml
vendored
Normal file
107
.github/workflows/daily_common.yml
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
name: Daily Common
|
||||
# Serves as base workflow for daily tasks, it's not run by itself.
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
pinned_deps:
|
||||
description: 'Should dependencies be installed from pinned file or use latest versions'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
nim:
|
||||
description: 'Nim Configuration'
|
||||
required: true
|
||||
type: string # Following this format: [{"ref": ..., "memory_management": ...}, ...]
|
||||
cpu:
|
||||
description: 'CPU'
|
||||
required: true
|
||||
type: string
|
||||
exclude:
|
||||
description: 'Exclude matrix configurations'
|
||||
required: false
|
||||
type: string
|
||||
default: "[]"
|
||||
|
||||
jobs:
|
||||
delete_cache:
|
||||
name: Delete github action's branch cache
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: snnaplab/delete-branch-cache-action@v1
|
||||
|
||||
test:
|
||||
needs: delete_cache
|
||||
timeout-minutes: 40
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- os: linux
|
||||
builder: ubuntu-22.04
|
||||
shell: bash
|
||||
- os: macos
|
||||
builder: macos-13
|
||||
shell: bash
|
||||
- os: windows
|
||||
builder: windows-2022
|
||||
shell: msys2 {0}
|
||||
nim: ${{ fromJSON(inputs.nim) }}
|
||||
cpu: ${{ fromJSON(inputs.cpu) }}
|
||||
exclude: ${{ fromJSON(inputs.exclude) }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.platform.shell }}
|
||||
|
||||
name: '${{ matrix.platform.os }}-${{ matrix.cpu }} (Nim ${{ matrix.nim.ref }})'
|
||||
runs-on: ${{ matrix.platform.builder }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Nim
|
||||
uses: "./.github/actions/install_nim"
|
||||
with:
|
||||
os: ${{ matrix.platform.os }}
|
||||
shell: ${{ matrix.platform.shell }}
|
||||
nim_ref: ${{ matrix.nim.ref }}
|
||||
cpu: ${{ matrix.cpu }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '~1.16.0'
|
||||
cache: false
|
||||
|
||||
- name: Install p2pd
|
||||
run: |
|
||||
V=1 bash scripts/build_p2pd.sh p2pdCache 124530a3
|
||||
|
||||
- name: Install dependencies (pinned)
|
||||
if: ${{ inputs.pinned_deps }}
|
||||
run: |
|
||||
nimble install_pinned
|
||||
|
||||
- name: Install dependencies (latest)
|
||||
if: ${{ inputs.pinned_deps == false }}
|
||||
run: |
|
||||
nimble install -y --depsOnly
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
nim --version
|
||||
nimble --version
|
||||
|
||||
export NIMFLAGS="${NIMFLAGS} --mm:${{ matrix.nim.memory_management }}"
|
||||
nimble test
|
||||
|
||||
- name: Run integration tests
|
||||
if: ${{ matrix.platform.os == 'linux' && matrix.cpu == 'amd64' }}
|
||||
run: |
|
||||
nim --version
|
||||
nimble --version
|
||||
|
||||
export NIMFLAGS="${NIMFLAGS} --mm:${{ matrix.nim.memory_management }}"
|
||||
nimble testintegration
|
||||
50
.github/workflows/daily_i386.yml
vendored
Normal file
50
.github/workflows/daily_i386.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: Daily i386
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 6 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test_i386_latest:
|
||||
name: Daily i386 (latest dependencies)
|
||||
uses: ./.github/workflows/daily_common.yml
|
||||
with:
|
||||
nim: "[
|
||||
{'ref': 'version-2-0', 'memory_management': 'refc'},
|
||||
{'ref': 'version-2-2', 'memory_management': 'refc'},
|
||||
{'ref': 'devel', 'memory_management': 'refc'},
|
||||
]"
|
||||
cpu: "['i386']"
|
||||
exclude: "[
|
||||
{'platform': {'os':'macos'}},
|
||||
{'platform': {'os':'windows'}},
|
||||
]"
|
||||
test_i386_pinned:
|
||||
name: Daily i386 (pinned dependencies)
|
||||
uses: ./.github/workflows/daily_common.yml
|
||||
with:
|
||||
pinned_deps: true
|
||||
nim: "[
|
||||
{'ref': 'version-2-0', 'memory_management': 'refc'},
|
||||
{'ref': 'version-2-2', 'memory_management': 'refc'},
|
||||
{'ref': 'devel', 'memory_management': 'refc'},
|
||||
]"
|
||||
cpu: "['i386']"
|
||||
exclude: "[
|
||||
{'platform': {'os':'macos'}},
|
||||
{'platform': {'os':'windows'}},
|
||||
]"
|
||||
notify-on-failure:
|
||||
name: Notify Discord on Failure
|
||||
needs: [test_i386_latest, test_i386_pinned]
|
||||
if: failure()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Discord notification
|
||||
uses: ./.github/actions/discord_notify
|
||||
with:
|
||||
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
39
.github/workflows/daily_nimbus.yml
vendored
Normal file
39
.github/workflows/daily_nimbus.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Daily Nimbus
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 6 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
compile_nimbus:
|
||||
timeout-minutes: 80
|
||||
name: 'Compile Nimbus (linux-amd64)'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Compile nimbus using nim-libp2p
|
||||
run: |
|
||||
git clone --branch unstable --single-branch https://github.com/status-im/nimbus-eth2.git
|
||||
cd nimbus-eth2
|
||||
git submodule set-branch --branch ${{ github.sha }} vendor/nim-libp2p
|
||||
|
||||
make -j"$(nproc)"
|
||||
make -j"$(nproc)" nimbus_beacon_node
|
||||
|
||||
notify-on-failure:
|
||||
name: Notify Discord on Failure
|
||||
needs: compile_nimbus
|
||||
if: failure()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Discord notification
|
||||
uses: ./.github/actions/discord_notify
|
||||
with:
|
||||
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
|
||||
65
.github/workflows/dependencies.yml
vendored
Normal file
65
.github/workflows/dependencies.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
name: Dependencies
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
bumper:
|
||||
# Pushes new refs to interested external repositories, so they can do early testing against libp2p's newer versions
|
||||
runs-on: ubuntu-latest
|
||||
name: Bump libp2p's version for ${{ matrix.target.repository }}:${{ matrix.target.ref }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- repository: status-im/nimbus-eth2
|
||||
ref: unstable
|
||||
secret: ACTIONS_GITHUB_TOKEN_NIMBUS_ETH2
|
||||
- repository: waku-org/nwaku
|
||||
ref: master
|
||||
secret: ACTIONS_GITHUB_TOKEN_NWAKU
|
||||
- repository: codex-storage/nim-codex
|
||||
ref: master
|
||||
secret: ACTIONS_GITHUB_TOKEN_NIM_CODEX
|
||||
steps:
|
||||
- name: Clone target repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ matrix.target.repository }}
|
||||
ref: ${{ matrix.target.ref}}
|
||||
path: nbc
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets[matrix.target.secret] }}
|
||||
|
||||
- name: Checkout this ref in target repository
|
||||
run: |
|
||||
cd nbc
|
||||
git submodule update --init vendor/nim-libp2p
|
||||
cd vendor/nim-libp2p
|
||||
git checkout $GITHUB_SHA
|
||||
|
||||
- name: Push this ref to target repository
|
||||
run: |
|
||||
cd nbc
|
||||
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
|
||||
git config --global user.name = "${{ github.actor }}"
|
||||
git commit --allow-empty -a -m "auto-bump nim-libp2p"
|
||||
git branch -D nim-libp2p-auto-bump-${{ matrix.target.ref }} || true
|
||||
git switch -c nim-libp2p-auto-bump-${{ matrix.target.ref }}
|
||||
git push -f origin nim-libp2p-auto-bump-${{ matrix.target.ref }}
|
||||
notify-on-failure:
|
||||
name: Notify Discord on Failure
|
||||
needs: [bumper]
|
||||
if: failure()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Discord notification
|
||||
uses: ./.github/actions/discord_notify
|
||||
with:
|
||||
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
111
.github/workflows/documentation.yml
vendored
Normal file
111
.github/workflows/documentation.yml
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
name: Documentation Generation And Publishing
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 20
|
||||
|
||||
name: 'Generate & upload documentation'
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- uses: jiro4989/setup-nim-action@v1
|
||||
with:
|
||||
nim-version: '2.2.x'
|
||||
|
||||
- name: Generate doc
|
||||
run: |
|
||||
nim --version
|
||||
nimble --version
|
||||
nimble install_pinned
|
||||
# nim doc can "fail", but the doc is still generated
|
||||
nim doc --git.url:https://github.com/vacp2p/nim-libp2p --git.commit:${GITHUB_REF##*/} --outdir:${GITHUB_REF##*/} --project libp2p || true
|
||||
|
||||
# check that the folder exists
|
||||
ls ${GITHUB_REF##*/}
|
||||
|
||||
- name: Clone the gh-pages branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: vacp2p/nim-libp2p
|
||||
ref: gh-pages
|
||||
path: subdoc
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Commit & push
|
||||
run: |
|
||||
cd subdoc
|
||||
|
||||
# Update / create this branch doc
|
||||
rm -rf ${GITHUB_REF##*/}
|
||||
mv ../${GITHUB_REF##*/} .
|
||||
|
||||
# Remove .idx files
|
||||
# NOTE: git also uses idx files in his
|
||||
# internal folder, hence the `*` instead of `.`
|
||||
find * -name "*.idx" -delete
|
||||
git add .
|
||||
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
|
||||
git config --global user.name = "${{ github.actor }}"
|
||||
git commit -a -m "update docs for ${GITHUB_REF##*/}"
|
||||
git push origin gh-pages
|
||||
|
||||
update_site:
|
||||
name: 'Rebuild website'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: jiro4989/setup-nim-action@v1
|
||||
with:
|
||||
nim-version: 'stable'
|
||||
|
||||
- name: Generate website
|
||||
run: pip install mkdocs-material && nimble -y website
|
||||
|
||||
- name: Clone the gh-pages branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: vacp2p/nim-libp2p
|
||||
ref: gh-pages
|
||||
path: subdoc
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Commit & push
|
||||
run: |
|
||||
cd subdoc
|
||||
|
||||
# Ensure the latest changes are fetched and reset to the remote branch
|
||||
git fetch origin gh-pages
|
||||
git reset --hard origin/gh-pages
|
||||
|
||||
rm -rf docs
|
||||
mv ../site docs
|
||||
|
||||
git add .
|
||||
|
||||
if git diff-index --quiet HEAD --; then
|
||||
echo "No changes to commit"
|
||||
else
|
||||
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
|
||||
git config --global user.name "${{ github.actor }}"
|
||||
|
||||
git commit -m "update website"
|
||||
git push origin gh-pages
|
||||
fi
|
||||
60
.github/workflows/examples.yml
vendored
Normal file
60
.github/workflows/examples.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: Examples
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
examples:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
name: "Build Examples"
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Nim
|
||||
uses: "./.github/actions/install_nim"
|
||||
with:
|
||||
shell: bash
|
||||
os: linux
|
||||
cpu: amd64
|
||||
nim_ref: version-2-2
|
||||
|
||||
- name: Restore deps from cache
|
||||
id: deps-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: nimbledeps
|
||||
key: nimbledeps-${{ hashFiles('.pinned') }}
|
||||
|
||||
- name: Install deps
|
||||
if: ${{ steps.deps-cache.outputs.cache-hit != 'true' }}
|
||||
run: |
|
||||
nimble install_pinned
|
||||
|
||||
- name: Build and run examples
|
||||
run: |
|
||||
nim --version
|
||||
nimble --version
|
||||
gcc --version
|
||||
|
||||
NIMFLAGS="${NIMFLAGS} --mm:${{ matrix.nim.memory_management }}"
|
||||
nimble examples
|
||||
62
.github/workflows/interop.yml
vendored
Normal file
62
.github/workflows/interop.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
name: Interoperability Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
run-transport-interop:
|
||||
name: Run transport interoperability tests
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Free Disk Space
|
||||
# For some reason we have space issues while running this action. Likely while building the image.
|
||||
# This action will free up some space to avoid the issue.
|
||||
uses: jlumbroso/free-disk-space@v1.3.1
|
||||
with:
|
||||
tool-cache: true
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: Build image
|
||||
run: docker buildx build --load -t nim-libp2p-head -f interop/transport/Dockerfile .
|
||||
- name: Run tests
|
||||
uses: libp2p/test-plans/.github/actions/run-transport-interop-test@master
|
||||
with:
|
||||
test-filter: nim-libp2p-head
|
||||
# without suffix action fails because "hole-punching-interop" artifacts have
|
||||
# the same name as "transport-interop" artifacts
|
||||
test-results-suffix: transport-interop
|
||||
extra-versions: ${{ github.workspace }}/interop/transport/version.json
|
||||
s3-cache-bucket: ${{ vars.S3_LIBP2P_BUILD_CACHE_BUCKET_NAME }}
|
||||
s3-access-key-id: ${{ vars.S3_LIBP2P_BUILD_CACHE_AWS_ACCESS_KEY_ID }}
|
||||
s3-secret-access-key: ${{ secrets.S3_LIBP2P_BUILD_CACHE_AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.S3_LIBP2P_BUILD_CACHE_AWS_REGION }}
|
||||
|
||||
# nim-libp2p#1367: hole punching tests are temporary disabled as they keep failing
|
||||
# and issue does not seem to be on nim-libp2p side
|
||||
# run-hole-punching-interop:
|
||||
# name: Run hole-punching interoperability tests
|
||||
# runs-on: ubuntu-22.04
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - uses: docker/setup-buildx-action@v3
|
||||
# - name: Build image
|
||||
# run: docker buildx build --load -t nim-libp2p-head -f interop/hole-punching/Dockerfile .
|
||||
# - name: Run tests
|
||||
# uses: libp2p/test-plans/.github/actions/run-interop-hole-punch-test@master
|
||||
# with:
|
||||
# test-filter: nim-libp2p-head
|
||||
# extra-versions: ${{ github.workspace }}/interop/hole-punching/version.json
|
||||
# s3-cache-bucket: ${{ vars.S3_LIBP2P_BUILD_CACHE_BUCKET_NAME }}
|
||||
# s3-access-key-id: ${{ vars.S3_LIBP2P_BUILD_CACHE_AWS_ACCESS_KEY_ID }}
|
||||
# s3-secret-access-key: ${{ secrets.S3_LIBP2P_BUILD_CACHE_AWS_SECRET_ACCESS_KEY }}
|
||||
# aws-region: ${{ vars.S3_LIBP2P_BUILD_CACHE_AWS_REGION }}
|
||||
27
.github/workflows/linters.yml
vendored
Normal file
27
.github/workflows/linters.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Linters
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
nph:
|
||||
name: NPH
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2 # In PR, has extra merge commit: ^1 = PR, ^2 = base
|
||||
|
||||
- name: Check `nph` formatting
|
||||
uses: arnetheduck/nph-action@v1
|
||||
with:
|
||||
version: 0.6.1
|
||||
options: "examples libp2p tests interop tools *.nim*"
|
||||
fail: true
|
||||
suggest: true
|
||||
175
.github/workflows/multi_nim.yml
vendored
175
.github/workflows/multi_nim.yml
vendored
@@ -1,175 +0,0 @@
|
||||
name: Daily
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 6 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 120
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- os: linux
|
||||
cpu: amd64
|
||||
- os: linux
|
||||
cpu: i386
|
||||
- os: macos
|
||||
cpu: amd64
|
||||
- os: windows
|
||||
cpu: amd64
|
||||
#- os: windows
|
||||
#cpu: i386
|
||||
branch: [version-1-2, version-1-4, version-1-6, devel]
|
||||
include:
|
||||
- target:
|
||||
os: linux
|
||||
builder: ubuntu-20.04
|
||||
shell: bash
|
||||
- target:
|
||||
os: macos
|
||||
builder: macos-10.15
|
||||
shell: bash
|
||||
- target:
|
||||
os: windows
|
||||
builder: windows-2019
|
||||
shell: msys2 {0}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.shell }}
|
||||
|
||||
name: '${{ matrix.target.os }}-${{ matrix.target.cpu }} (Nim ${{ matrix.branch }})'
|
||||
runs-on: ${{ matrix.builder }}
|
||||
continue-on-error: ${{ matrix.branch == 'version-1-6' || matrix.branch == 'devel' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: unstable
|
||||
submodules: true
|
||||
|
||||
- name: Install build dependencies (Linux i386)
|
||||
if: runner.os == 'Linux' && matrix.target.cpu == 'i386'
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update -qq
|
||||
sudo DEBIAN_FRONTEND='noninteractive' apt-get install \
|
||||
--no-install-recommends -yq gcc-multilib g++-multilib \
|
||||
libssl-dev:i386
|
||||
mkdir -p external/bin
|
||||
cat << EOF > external/bin/gcc
|
||||
#!/bin/bash
|
||||
exec $(which gcc) -m32 "\$@"
|
||||
EOF
|
||||
cat << EOF > external/bin/g++
|
||||
#!/bin/bash
|
||||
exec $(which g++) -m32 "\$@"
|
||||
EOF
|
||||
chmod 755 external/bin/gcc external/bin/g++
|
||||
echo '${{ github.workspace }}/external/bin' >> $GITHUB_PATH
|
||||
|
||||
- name: MSYS2 (Windows i386)
|
||||
if: runner.os == 'Windows' && matrix.target.cpu == 'i386'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
path-type: inherit
|
||||
msystem: MINGW32
|
||||
install: >-
|
||||
base-devel
|
||||
git
|
||||
mingw-w64-i686-toolchain
|
||||
|
||||
- name: MSYS2 (Windows amd64)
|
||||
if: runner.os == 'Windows' && matrix.target.cpu == 'amd64'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
path-type: inherit
|
||||
install: >-
|
||||
base-devel
|
||||
git
|
||||
mingw-w64-x86_64-toolchain
|
||||
|
||||
- name: Restore Nim DLLs dependencies (Windows) from cache
|
||||
if: runner.os == 'Windows'
|
||||
id: windows-dlls-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: external/dlls
|
||||
key: 'dlls'
|
||||
|
||||
- name: Install DLL dependencies (Windows)
|
||||
if: >
|
||||
steps.windows-dlls-cache.outputs.cache-hit != 'true' &&
|
||||
runner.os == 'Windows'
|
||||
run: |
|
||||
mkdir external
|
||||
curl -L "https://nim-lang.org/download/windeps.zip" -o external/windeps.zip
|
||||
7z x external/windeps.zip -oexternal/dlls
|
||||
|
||||
- name: Path to cached dependencies (Windows)
|
||||
if: >
|
||||
runner.os == 'Windows'
|
||||
run: |
|
||||
echo '${{ github.workspace }}'"/external/dlls" >> $GITHUB_PATH
|
||||
|
||||
- name: Derive environment variables
|
||||
run: |
|
||||
if [[ '${{ matrix.target.cpu }}' == 'amd64' ]]; then
|
||||
PLATFORM=x64
|
||||
else
|
||||
PLATFORM=x86
|
||||
fi
|
||||
echo "PLATFORM=$PLATFORM" >> $GITHUB_ENV
|
||||
|
||||
ncpu=
|
||||
MAKE_CMD="make"
|
||||
case '${{ runner.os }}' in
|
||||
'Linux')
|
||||
ncpu=$(nproc)
|
||||
;;
|
||||
'macOS')
|
||||
ncpu=$(sysctl -n hw.ncpu)
|
||||
;;
|
||||
'Windows')
|
||||
ncpu=$NUMBER_OF_PROCESSORS
|
||||
MAKE_CMD="mingw32-make"
|
||||
;;
|
||||
esac
|
||||
[[ -z "$ncpu" || $ncpu -le 0 ]] && ncpu=1
|
||||
echo "ncpu=$ncpu" >> $GITHUB_ENV
|
||||
echo "MAKE_CMD=${MAKE_CMD}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Nim and Nimble
|
||||
run: |
|
||||
curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh
|
||||
env MAKE="${MAKE_CMD} -j${ncpu}" ARCH_OVERRIDE=${PLATFORM} NIM_COMMIT=${{ matrix.branch }} \
|
||||
QUICK_AND_DIRTY_COMPILER=1 QUICK_AND_DIRTY_NIMBLE=1 CC=gcc \
|
||||
bash build_nim.sh nim csources dist/nimble NimBinaries
|
||||
echo '${{ github.workspace }}/nim/bin' >> $GITHUB_PATH
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.15.5'
|
||||
|
||||
- name: Install p2pd
|
||||
run: |
|
||||
V=1 bash scripts/build_p2pd.sh p2pdCache 124530a3
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
if [[ "${{ matrix.target.os }}" == "windows" ]]; then
|
||||
# https://github.com/status-im/nimbus-eth2/issues/3121
|
||||
export NIMFLAGS="-d:nimRawSetjmp"
|
||||
fi
|
||||
nim --version
|
||||
nimble --version
|
||||
nimble install -y --depsOnly
|
||||
nimble test
|
||||
if [[ "${{ matrix.branch }}" == "version-1-6" || "${{ matrix.branch }}" == "devel" ]]; then
|
||||
echo -e "\nTesting with '--gc:orc':\n"
|
||||
export NIMFLAGS="${NIMFLAGS} --gc:orc"
|
||||
nimble test
|
||||
fi
|
||||
69
.github/workflows/performance.yml
vendored
Normal file
69
.github/workflows/performance.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: Performance
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
examples:
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
name: "Performance"
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build Docker Image with cache
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: performance/Dockerfile
|
||||
tags: test-node:latest
|
||||
load: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Run
|
||||
run: |
|
||||
./performance/runner.sh
|
||||
|
||||
- name: Set up Nim for aggragate script
|
||||
uses: jiro4989/setup-nim-action@v2
|
||||
with:
|
||||
nim-version: "2.x"
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Aggregate and display summary
|
||||
env:
|
||||
MARKER: "<!-- perf-summary-marker -->"
|
||||
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
COMMENT_SUMMARY_PATH: "/tmp/perf-summary.md"
|
||||
run: |
|
||||
nim c -r -d:release -o:/tmp/aggregate_stats ./performance/aggregate_stats.nim
|
||||
|
||||
- name: Post/Update PR Performance Comment
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: ./.github/actions/add_comment
|
||||
with:
|
||||
marker: "<!-- perf-summary-marker -->"
|
||||
markdown_path: "/tmp/perf-summary.md"
|
||||
35
.github/workflows/pr_lint.yml
vendored
Normal file
35
.github/workflows/pr_lint.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: "Conventional Commits"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- reopened
|
||||
- synchronize
|
||||
jobs:
|
||||
main:
|
||||
name: Validate PR title
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
id: lint_pr_title
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: marocchino/sticky-pull-request-comment@v2
|
||||
# When the previous steps fails, the workflow would stop. By adding this
|
||||
# condition you can continue the execution with the populated error message.
|
||||
if: always() && (steps.lint_pr_title.outputs.error_message != null)
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
message: |
|
||||
Pull requests titles must follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||
|
||||
# Delete a previous comment when the issue has been resolved
|
||||
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
delete: true
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -13,3 +13,15 @@ build/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
tests/pubsub/testgossipsub
|
||||
examples/*.md
|
||||
nimble.develop
|
||||
nimble.paths
|
||||
go-libp2p-daemon/
|
||||
|
||||
# Ignore all test build files in tests folder (auto generated when running tests).
|
||||
# First rule (`tests/**/test*[^.]*`) will ignore all binaries: has prefix test + does not have dot in name.
|
||||
# Second and third rules are here to un-ignores all files with extension and Docker file,
|
||||
# because it appears that vs code is skipping text search is some tests files without these rules.
|
||||
tests/**/test*[^.]*
|
||||
!tests/**/*.*
|
||||
!tests/**/Dockerfile
|
||||
41
.pinned
41
.pinned
@@ -1,17 +1,24 @@
|
||||
asynctest;https://github.com/markspanbroek/asynctest@#3882ed64ed3159578f796bc5ae0c6b13837fe798
|
||||
bearssl;https://github.com/status-im/nim-bearssl@#ba80e2a0d7ae8aab666cee013e38ff8d33a3e5e7
|
||||
chronicles;https://github.com/status-im/nim-chronicles@#2a2681b60289aaf7895b7056f22616081eb1a882
|
||||
chronos;https://github.com/status-im/nim-chronos@#87197230779002a2bfa8642f0e2ae07e2349e304
|
||||
dnsclient;https://github.com/ba0f3/dnsclient.nim@#fbb76f8af8a33ab818184a7d4406d9fee20993be
|
||||
faststreams;https://github.com/status-im/nim-faststreams@#37a183153c071539ab870f427c09a1376ba311b9
|
||||
httputils;https://github.com/status-im/nim-http-utils@#40048e8b3e69284bdb5d4daa0a16ad93402c55db
|
||||
json_serialization;https://github.com/status-im/nim-json-serialization@#4b8f487d2dfdd941df7408ceaa70b174cce02180
|
||||
metrics;https://github.com/status-im/nim-metrics@#71e0f0e354e1f4c59e3dc92153989c8b723c3440
|
||||
nimcrypto;https://github.com/cheatfate/nimcrypto@#a5742a9a214ac33f91615f3862c7b099aec43b00
|
||||
secp256k1;https://github.com/status-im/nim-secp256k1@#e092373a5cbe1fa25abfc62e0f2a5f138dc3fb13
|
||||
serialization;https://github.com/status-im/nim-serialization@#37bc0db558d85711967acb16e9bb822b06911d46
|
||||
stew;https://github.com/status-im/nim-stew@#bb705bf17b46d2c8f9bfb106d9cc7437009a2501
|
||||
testutils;https://github.com/status-im/nim-testutils@#aa6e5216f4b4ab5aa971cdcdd70e1ec1203cedf2
|
||||
unittest2;https://github.com/status-im/nim-unittest2@#4e2893eacb916c7678fdc4935ff7420f13bf3a9c
|
||||
websock;https://github.com/status-im/nim-websock@#853299e399746eff4096870067cbc61861ecd534
|
||||
zlib;https://github.com/status-im/nim-zlib@#74cdeb54b21bededb5a515d36f608bc1850555a2
|
||||
bearssl;https://github.com/status-im/nim-bearssl@#34d712933a4e0f91f5e66bc848594a581504a215
|
||||
blscurve;https://github.com/status-im/nim-blscurve@#52ae4332c749d89fa05226f5493decae568f682c
|
||||
chronicles;https://github.com/status-im/nim-chronicles@#61759a5e8df8f4d68bcd1b4b8c1adab3e72bbd8d
|
||||
chronos;https://github.com/status-im/nim-chronos@#b55e2816eb45f698ddaca8d8473e401502562db2
|
||||
dnsclient;https://github.com/ba0f3/dnsclient.nim@#23214235d4784d24aceed99bbfe153379ea557c8
|
||||
faststreams;https://github.com/status-im/nim-faststreams@#c51315d0ae5eb2594d0bf41181d0e1aca1b3c01d
|
||||
httputils;https://github.com/status-im/nim-http-utils@#79cbab1460f4c0cdde2084589d017c43a3d7b4f1
|
||||
json_serialization;https://github.com/status-im/nim-json-serialization@#2b1c5eb11df3647a2cee107cd4cce3593cbb8bcf
|
||||
metrics;https://github.com/status-im/nim-metrics@#6142e433fc8ea9b73379770a788017ac528d46ff
|
||||
ngtcp2;https://github.com/status-im/nim-ngtcp2@#9456daa178c655bccd4a3c78ad3b8cce1f0add73
|
||||
nimcrypto;https://github.com/cheatfate/nimcrypto@#19c41d6be4c00b4a2c8000583bd30cf8ceb5f4b1
|
||||
quic;https://github.com/status-im/nim-quic.git@#d9a4cbccd509f7a3ee835f75b01dec29d27a0f14
|
||||
results;https://github.com/arnetheduck/nim-results@#df8113dda4c2d74d460a8fa98252b0b771bf1f27
|
||||
secp256k1;https://github.com/status-im/nim-secp256k1@#f808ed5e7a7bfc42204ec7830f14b7a42b63c284
|
||||
serialization;https://github.com/status-im/nim-serialization@#548d0adc9797a10b2db7f788b804330306293088
|
||||
stew;https://github.com/status-im/nim-stew@#0db179256cf98eb9ce9ee7b9bc939f219e621f77
|
||||
testutils;https://github.com/status-im/nim-testutils@#9e842bd58420d23044bc55e16088e8abbe93ce51
|
||||
unittest2;https://github.com/status-im/nim-unittest2@#8b51e99b4a57fcfb31689230e75595f024543024
|
||||
websock;https://github.com/status-im/nim-websock@#d5cd89062cd2d168ef35193c7d29d2102921d97e
|
||||
zlib;https://github.com/status-im/nim-zlib@#daa8723fd32299d4ca621c837430c29a5a11e19a
|
||||
jwt;https://github.com/vacp2p/nim-jwt@#18f8378de52b241f321c1f9ea905456e89b95c6f
|
||||
bearssl_pkey_decoder;https://github.com/vacp2p/bearssl_pkey_decoder@#21dd3710df9345ed2ad8bf8f882761e07863b8e0
|
||||
constantine;https://github.com/mratsim/constantine@#v0.2.0
|
||||
poseidon2;https://github.com/codex-storage/nim-poseidon2@#e173dd817b794d2bdadaa7ed45583798aaa91f0d
|
||||
|
||||
279
README.md
279
README.md
@@ -1,13 +1,13 @@
|
||||
<h1 align="center">
|
||||
<a href="https://libp2p.io"><img width="250" src="https://github.com/libp2p/libp2p/blob/master/logo/black-bg-2.png?raw=true" alt="libp2p hex logo" /></a>
|
||||
<a href="https://libp2p.io"><img width="250" src="./.assets/full-logo.svg?raw=true" alt="nim-libp2p logo" /></a>
|
||||
</h1>
|
||||
|
||||
<h3 align="center">The Nim implementation of the libp2p Networking Stack.</h3>
|
||||
<h3 align="center">The <a href="https://nim-lang.org/">Nim</a> implementation of the <a href="https://libp2p.io/">libp2p</a> Networking Stack.</h3>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/status-im/nim-libp2p/actions"><img src="https://github.com/status-im/nim-libp2p/actions/workflows/ci.yml/badge.svg" /></a>
|
||||
<a href="https://codecov.io/gh/status-im/nim-libp2p"><img src="https://codecov.io/gh/status-im/nim-libp2p/branch/master/graph/badge.svg?token=UR5JRQ249W"/></a>
|
||||
|
||||
<a href="https://github.com/vacp2p/nim-libp2p/actions"><img src="https://github.com/vacp2p/nim-libp2p/actions/workflows/ci.yml/badge.svg" /></a>
|
||||
<a href="https://codecov.io/gh/vacp2p/nim-libp2p"><img src="https://codecov.io/gh/vacp2p/nim-libp2p/branch/master/graph/badge.svg?token=UR5JRQ249W"/></a>
|
||||
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -16,169 +16,191 @@
|
||||
<img src="https://img.shields.io/badge/nim-%3E%3D1.2.0-orange.svg?style=flat-square" />
|
||||
</p>
|
||||
|
||||
## Introduction
|
||||
|
||||
An implementation of [libp2p](https://libp2p.io/) in Nim.
|
||||
|
||||
## Project Status
|
||||
libp2p is used in production by a few projects at [Status](https://github.com/status-im), including [Nimbus](https://github.com/status-im/nimbus-eth2).
|
||||
|
||||
While far from complete, currently available components are stable.
|
||||
|
||||
Check our [examples folder](/examples) to get started!
|
||||
|
||||
# Table of Contents
|
||||
- [Background](#background)
|
||||
- [Install](#install)
|
||||
- [Prerequisite](#prerequisite)
|
||||
- [Usage](#usage)
|
||||
- [API](#api)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Tutorials and Examples](#tutorials-and-examples)
|
||||
- [Using the Go Daemon](#using-the-go-daemon)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Development](#development)
|
||||
- [Tests](#tests)
|
||||
- [Packages](#packages)
|
||||
- [Contribute](#contribute)
|
||||
- [Core Developers](#core-developers)
|
||||
- [Contribute](#contribute)
|
||||
- [Contributors](#contributors)
|
||||
- [Core Maintainers](#core-maintainers)
|
||||
- [Modules](#modules)
|
||||
- [Users](#users)
|
||||
- [Stability](#stability)
|
||||
- [License](#license)
|
||||
|
||||
## Background
|
||||
libp2p is a networking stack and library modularized out of [The IPFS Project](https://github.com/ipfs/ipfs), and bundled separately for other tools to use.
|
||||
libp2p is a [Peer-to-Peer](https://en.wikipedia.org/wiki/Peer-to-peer) networking stack, with [implementations](https://github.com/libp2p/libp2p#implementations) in multiple languages derived from the same [specifications.](https://github.com/libp2p/specs)
|
||||
|
||||
libp2p is the product of a long and arduous quest of understanding; a deep dive into the internet's network stack and the peer-to-peer protocols from the past. Building large scale peer-to-peer systems has been complex and difficult in the last 15 years and libp2p is a way to fix that. It is a "network stack", a suite of networking protocols that cleanly separates concerns and enables sophisticated applications to only use the protocols they absolutely need, without giving up interoperability and upgradeability.
|
||||
Building large scale peer-to-peer systems has been complex and difficult in the last 15 years and libp2p is a way to fix that. It strives to be a modular stack with secure defaults and useful protocols, while remaining open and extensible.
|
||||
This is a native Nim implementation, using [chronos](https://github.com/status-im/nim-chronos) for asynchronous execution. It's used in production by a few [projects](#users)
|
||||
|
||||
libp2p grew out of IPFS, but it is built so that lots of people can use it, for lots of different projects.
|
||||
|
||||
- Learn more about libp2p at [**libp2p.io**](https://libp2p.io) and follow our evolving documentation efforts at [**docs.libp2p.io**](https://docs.libp2p.io).
|
||||
- [Here](https://github.com/libp2p/libp2p#description) is an overview of libp2p and its implementations in other programming languages.
|
||||
Learn more about libp2p at [**libp2p.io**](https://libp2p.io) and follow libp2p's documentation [**docs.libp2p.io**](https://docs.libp2p.io).
|
||||
|
||||
## Install
|
||||
|
||||
> The currently supported Nim versions are 2.0 and 2.2.
|
||||
|
||||
```
|
||||
nimble install libp2p
|
||||
```
|
||||
### Prerequisite
|
||||
- [Nim](https://nim-lang.org/install.html)
|
||||
You'll find the nim-libp2p documentation [here](https://vacp2p.github.io/nim-libp2p/docs/). See [examples](./examples) for simple usage patterns.
|
||||
|
||||
## Usage
|
||||
## Getting Started
|
||||
Try out the chat example. For this you'll need to have [`go-libp2p-daemon`](examples/go-daemon/daemonapi.md) running. Full code can be found [here](https://github.com/status-im/nim-libp2p/blob/master/examples/chat.nim):
|
||||
|
||||
### API
|
||||
The specification is available in the [docs/api](docs/api) folder.
|
||||
```bash
|
||||
nim c -r --threads:on examples/directchat.nim
|
||||
```
|
||||
|
||||
### Getting Started
|
||||
Please read the [GETTING_STARTED.md](docs/GETTING_STARTED.md) guide.
|
||||
This will output a peer ID such as `QmbmHfVvouKammmQDJck4hz33WvVktNEe7pasxz2HgseRu` which you can use in another instance to connect to it.
|
||||
|
||||
### Tutorials and Examples
|
||||
Example code can be found in the [examples folder](/examples).
|
||||
```bash
|
||||
./examples/directchat
|
||||
/connect QmbmHfVvouKammmQDJck4hz33WvVktNEe7pasxz2HgseRu # change this hash by the hash you were given
|
||||
```
|
||||
|
||||
#### Direct Chat Tutorial
|
||||
- [Part I](https://our.status.im/nim-libp2p-tutorial-a-peer-to-peer-chat-example-1/): Set up the main function and use multi-thread for processing IO.
|
||||
- [Part II](https://our.status.im/nim-libp2p-tutorial-a-peer-to-peer-chat-example-2/): Dial remote peer and allow customized user input commands.
|
||||
- [Part III](https://our.status.im/nim-libp2p-tutorial-a-peer-to-peer-chat-example-3/): Configure and establish a libp2p node.
|
||||
You can now chat between the instances!
|
||||
|
||||
|
||||
### Using the Go Daemon
|
||||
Please find the installation and usage intructions in [daemonapi.md](docs/api/libp2p/daemonapi.md).
|
||||
|
||||
Examples can be found in the [examples/go-daemon folder](https://github.com/status-im/nim-libp2p/tree/readme/examples/go-daemon);
|
||||

|
||||
|
||||
## Development
|
||||
**Clone and Install dependencies:**
|
||||
|
||||
Clone the repository and install the dependencies:
|
||||
```sh
|
||||
git clone https://github.com/status-im/nim-libp2p
|
||||
git clone https://github.com/vacp2p/nim-libp2p
|
||||
cd nim-libp2p
|
||||
nimble install
|
||||
nimble install -dy
|
||||
```
|
||||
#### Run unit tests
|
||||
You can use `nix develop` to start a shell with Nim and Nimble.
|
||||
|
||||
nimble 0.20.1 is required for running `testnative`. At time of writing, this is not available in nixpkgs: If using `nix develop`, follow up with `nimble install nimble`, and use that (typically `~/.nimble/bin/nimble`).
|
||||
|
||||
### Testing
|
||||
Run unit tests:
|
||||
```sh
|
||||
# run all the unit tests
|
||||
nimble test
|
||||
```
|
||||
The code follows the [Status Nim Style Guide](https://status-im.github.io/nim-style-guide/).
|
||||
**Obs:** Running all tests requires the [`go-libp2p-daemon` to be installed and running](examples/go-daemon/daemonapi.md).
|
||||
|
||||
### Packages
|
||||
|
||||
List of packages currently in existence for nim-libp2p:
|
||||
|
||||
#### Libp2p
|
||||
- [libp2p](https://github.com/status-im/nim-libp2p)
|
||||
- [libp2p-daemon-client](https://github.com/status-im/nim-libp2p/blob/master/libp2p/daemon/daemonapi.nim)
|
||||
- [interop-libp2p](https://github.com/status-im/nim-libp2p/blob/master/tests/testinterop.nim)
|
||||
|
||||
#### Transports
|
||||
- [libp2p-tcp](https://github.com/status-im/nim-libp2p/blob/master/libp2p/transports/tcptransport.nim)
|
||||
- [libp2p-ws](https://github.com/status-im/nim-libp2p/blob/master/libp2p/transports/wstransport.nim)
|
||||
|
||||
#### Secure Channels
|
||||
- [libp2p-secio](https://github.com/status-im/nim-libp2p/blob/master/libp2p/protocols/secure/secio.nim)
|
||||
- [libp2p-noise](https://github.com/status-im/nim-libp2p/blob/master/libp2p/protocols/secure/noise.nim)
|
||||
- [libp2p-plaintext](https://github.com/status-im/nim-libp2p/blob/master/libp2p/protocols/secure/plaintext.nim)
|
||||
|
||||
#### Stream Multiplexers
|
||||
- [libp2p-mplex](https://github.com/status-im/nim-libp2p/blob/master/libp2p/muxers/mplex/mplex.nim)
|
||||
|
||||
#### Utilities
|
||||
- [libp2p-crypto](https://github.com/status-im/nim-libp2p/tree/master/libp2p/crypto)
|
||||
- [libp2p-crypto-secp256k1](https://github.com/status-im/nim-libp2p/blob/master/libp2p/crypto/secp.nim)
|
||||
|
||||
#### Data Types
|
||||
- [peer-id](https://github.com/status-im/nim-libp2p/blob/master/libp2p/peer.nim)
|
||||
- [peer-info](https://github.com/status-im/nim-libp2p/blob/master/libp2p/peerinfo.nim)
|
||||
|
||||
#### Pubsub
|
||||
- [libp2p-pubsub](https://github.com/status-im/nim-libp2p/blob/master/libp2p/protocols/pubsub/pubsub.nim)
|
||||
- [libp2p-floodsub](https://github.com/status-im/nim-libp2p/blob/master/libp2p/protocols/pubsub/floodsub.nim)
|
||||
- [libp2p-gossipsub](https://github.com/status-im/nim-libp2p/blob/master/libp2p/protocols/pubsub/gossipsub.nim)
|
||||
|
||||
|
||||
Packages that exist in the original libp2p specs and are under active development:
|
||||
- libp2p-daemon
|
||||
- libp2p-webrtc-direct
|
||||
- libp2p-webrtc-star
|
||||
- libp2p-spdy
|
||||
- libp2p-bootstrap
|
||||
- libp2p-kad-dht
|
||||
- libp2p-mdns
|
||||
- libp2p-webrtc-star
|
||||
- libp2p-delegated-content-routing
|
||||
- libp2p-delegated-peer-routing
|
||||
- libp2p-nat-mgnr
|
||||
- libp2p-utils
|
||||
|
||||
** Note that the current stack reflects the minimal requirements for the upcoming Eth2 implementation.
|
||||
|
||||
### Tips and tricks
|
||||
|
||||
#### enable expensive metrics:
|
||||
|
||||
```bash
|
||||
nim c -d:libp2p_expensive_metrics some_file.nim
|
||||
If you only want to run tests that don't require `go-libp2p-daemon`, use:
|
||||
```
|
||||
nimble testnative
|
||||
```
|
||||
|
||||
#### use identify metrics
|
||||
|
||||
```bash
|
||||
nim c -d:libp2p_agents_metrics -d:KnownLibP2PAgents=nimbus,lighthouse,prysm,teku some_file.nim
|
||||
For a list of all available test suites, use:
|
||||
```
|
||||
nimble tasks
|
||||
```
|
||||
|
||||
### specify gossipsub specific topics to measure
|
||||
|
||||
```bash
|
||||
nim c -d:KnownLibP2PTopics=topic1,topic2,topic3 some_file.nim
|
||||
```
|
||||
|
||||
## Contribute
|
||||
### Contribute
|
||||
|
||||
The libp2p implementation in Nim is a work in progress. We welcome contributors to help out! Specifically, you can:
|
||||
- Go through the modules and **check out existing issues**. This would be especially useful for modules in active development. Some knowledge of IPFS/libp2p may be required, as well as the infrastructure behind it.
|
||||
- **Perform code reviews**. Feel free to let us know if you found anything that can a) speed up the project development b) ensure better quality and c) reduce possible future bugs.
|
||||
- **Add tests**. Help nim-libp2p to be more robust by adding more tests to the [tests folder](https://github.com/status-im/nim-libp2p/tree/master/tests).
|
||||
- **Add tests**. Help nim-libp2p to be more robust by adding more tests to the [tests folder](tests/).
|
||||
- **Small PRs**. Try to keep PRs atomic and digestible. This makes the review process and pinpointing bugs easier.
|
||||
- **Code format**. Code should be formatted with [nph](https://github.com/arnetheduck/nph) and follow the [Status Nim Style Guide](https://status-im.github.io/nim-style-guide/).
|
||||
- **Join the Conversation**. Connect with other contributors in our [community channel](https://discord.com/channels/1204447718093750272/1351621032263417946). Ask questions, share ideas, get support, and stay informed about the latest updates from the maintainers.
|
||||
|
||||
The code follows the [Status Nim Style Guide](https://status-im.github.io/nim-style-guide/).
|
||||
### Contributors
|
||||
<a href="https://github.com/vacp2p/nim-libp2p/graphs/contributors"><img src="https://contrib.rocks/image?repo=vacp2p/nim-libp2p" alt="nim-libp2p contributors"></a>
|
||||
|
||||
### Core Developers
|
||||
[@cheatfate](https://github.com/cheatfate), [Dmitriy Ryajov](https://github.com/dryajov), [Tanguy](https://github.com/Menduist), [Zahary Karadjov](https://github.com/zah)
|
||||
### Core Maintainers
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/richard-ramos"><img src="https://avatars.githubusercontent.com/u/1106587?v=4?s=100" width="100px;" alt="Richard"/><br /><sub><b>Richard</b></sub></a></td>
|
||||
<td align="center"><a href="https://github.com/vladopajic"><img src="https://avatars.githubusercontent.com/u/4353513?v=4?s=100" width="100px;" alt="Vlado"/><br /><sub><b>Vlado</b></sub></a></td>
|
||||
<td align="center"><a href="https://github.com/gmelodie"><img src="https://avatars.githubusercontent.com/u/8129788?v=4?s=100" width="100px;" alt="Gabe"/><br /><sub><b>Gabe</b></sub></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Compile time flags
|
||||
|
||||
Enable quic transport support
|
||||
```bash
|
||||
nim c -d:libp2p_quic_support some_file.nim
|
||||
```
|
||||
|
||||
Enable autotls support
|
||||
```bash
|
||||
nim c -d:libp2p_autotls_support some_file.nim
|
||||
```
|
||||
|
||||
Enable expensive metrics (ie, metrics with per-peer cardinality):
|
||||
```bash
|
||||
nim c -d:libp2p_expensive_metrics some_file.nim
|
||||
```
|
||||
|
||||
Set list of known libp2p agents for metrics:
|
||||
```bash
|
||||
nim c -d:libp2p_agents_metrics -d:KnownLibP2PAgents=nimbus,lighthouse,lodestar,prysm,teku some_file.nim
|
||||
```
|
||||
|
||||
Specify gossipsub specific topics to measure in the metrics:
|
||||
```bash
|
||||
nim c -d:KnownLibP2PTopics=topic1,topic2,topic3 some_file.nim
|
||||
```
|
||||
|
||||
|
||||
## Modules
|
||||
List of packages modules implemented in nim-libp2p:
|
||||
|
||||
| Name | Description |
|
||||
| ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| **Libp2p** | |
|
||||
| [libp2p](libp2p/switch.nim) | The core of the project |
|
||||
| [connmanager](libp2p/connmanager.nim) | Connection manager |
|
||||
| [identify / push identify](libp2p/protocols/identify.nim) | [Identify](https://docs.libp2p.io/concepts/fundamentals/protocols/#identify) protocol |
|
||||
| [ping](libp2p/protocols/ping.nim) | [Ping](https://docs.libp2p.io/concepts/fundamentals/protocols/#ping) protocol |
|
||||
| [libp2p-daemon-client](libp2p/daemon/daemonapi.nim) | [go-daemon](https://github.com/libp2p/go-libp2p-daemon) nim wrapper |
|
||||
| [interop-libp2p](tests/testinterop.nim) | Interop tests |
|
||||
| **Transports** | |
|
||||
| [libp2p-tcp](libp2p/transports/tcptransport.nim) | TCP transport |
|
||||
| [libp2p-ws](libp2p/transports/wstransport.nim) | WebSocket & WebSocket Secure transport |
|
||||
| [libp2p-tor](libp2p/transports/tortransport.nim) | Tor Transport |
|
||||
| [libp2p-quic](libp2p/transports/quictransport.nim) | Quic Transport |
|
||||
| [libp2p-memory](libp2p/transports/memorytransport.nim) | Memory Transport |
|
||||
| **Secure Channels** | |
|
||||
| [libp2p-noise](libp2p/protocols/secure/noise.nim) | [Noise](https://docs.libp2p.io/concepts/secure-comm/noise/) secure channel |
|
||||
| [libp2p-plaintext](libp2p/protocols/secure/plaintext.nim) | Plain Text for development purposes |
|
||||
| **Stream Multiplexers** | |
|
||||
| [libp2p-mplex](libp2p/muxers/mplex/mplex.nim) | [MPlex](https://github.com/libp2p/specs/tree/master/mplex) multiplexer |
|
||||
| [libp2p-yamux](libp2p/muxers/yamux/yamux.nim) | [Yamux](https://docs.libp2p.io/concepts/multiplex/yamux/) multiplexer |
|
||||
| **Data Types** | |
|
||||
| [peer-id](libp2p/peerid.nim) | [Cryptographic identifiers](https://docs.libp2p.io/concepts/fundamentals/peers/#peer-id) |
|
||||
| [peer-store](libp2p/peerstore.nim) | [Address book of known peers](https://docs.libp2p.io/concepts/fundamentals/peers/#peer-store) |
|
||||
| [multiaddress](libp2p/multiaddress.nim) | [Composable network addresses](https://github.com/multiformats/multiaddr) |
|
||||
| [signed-envelope](libp2p/signed_envelope.nim) | [Signed generic data container](https://github.com/libp2p/specs/blob/master/RFC/0002-signed-envelopes.md) |
|
||||
| [routing-record](libp2p/routing_record.nim) | [Signed peer dialing informations](https://github.com/libp2p/specs/blob/master/RFC/0003-routing-records.md) |
|
||||
| [discovery manager](libp2p/discovery/discoverymngr.nim) | Discovery Manager |
|
||||
| **Utilities** | |
|
||||
| [libp2p-crypto](libp2p/crypto) | Cryptographic backend |
|
||||
| [libp2p-crypto-secp256k1](libp2p/crypto/secp.nim) | |
|
||||
| **Pubsub** | |
|
||||
| [libp2p-pubsub](libp2p/protocols/pubsub/pubsub.nim) | Pub-Sub generic interface |
|
||||
| [libp2p-floodsub](libp2p/protocols/pubsub/floodsub.nim) | FloodSub implementation |
|
||||
| [libp2p-gossipsub](libp2p/protocols/pubsub/gossipsub.nim) | [GossipSub](https://docs.libp2p.io/concepts/publish-subscribe/) implementation |
|
||||
|
||||
## Users
|
||||
|
||||
nim-libp2p is used by:
|
||||
- [Nimbus](https://github.com/status-im/nimbus-eth2), an Ethereum client
|
||||
- [nwaku](https://github.com/waku-org/nwaku), a decentralized messaging application
|
||||
- [nim-codex](https://github.com/codex-storage/nim-codex), a decentralized storage application
|
||||
- (open a pull request if you want to be included here)
|
||||
|
||||
## Stability
|
||||
nim-libp2p has been used in production for over a year in high-stake scenarios, so its core is considered stable.
|
||||
Some modules are more recent and less stable.
|
||||
|
||||
The versioning follows [semver](https://semver.org/), with some additions:
|
||||
- Some of libp2p procedures are marked as `.public.`, they will remain compatible during each `MAJOR` version
|
||||
- The rest of the procedures are considered internal, and can change at any `MINOR` version (but remain compatible for each new `PATCH`)
|
||||
|
||||
We aim to be compatible at all time with at least 2 Nim `MINOR` versions, currently `2.0 & 2.2`
|
||||
|
||||
## License
|
||||
|
||||
@@ -191,4 +213,3 @@ or
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHEv2](LICENSE-APACHEv2) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
at your option. These files may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
codecov:
|
||||
notify:
|
||||
require_ci_to_pass: true
|
||||
# must be the number of coverage report builds
|
||||
# notice that this number is for PRs;
|
||||
# like this we disabled notify on pure branches report
|
||||
# which is fine I guess
|
||||
after_n_builds: 28
|
||||
comment:
|
||||
layout: "reach, diff, flags, files"
|
||||
after_n_builds: 28 # must be the number of coverage report builds
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
@@ -16,4 +10,4 @@ coverage:
|
||||
# basic settings
|
||||
target: auto
|
||||
threshold: 5%
|
||||
base: auto
|
||||
base: auto
|
||||
|
||||
27
config.nims
27
config.nims
@@ -1,3 +1,30 @@
|
||||
# to allow locking
|
||||
if dirExists("nimbledeps/pkgs"):
|
||||
switch("NimblePath", "nimbledeps/pkgs")
|
||||
if dirExists("nimbledeps/pkgs2"):
|
||||
switch("NimblePath", "nimbledeps/pkgs2")
|
||||
|
||||
switch("warningAsError", "UnusedImport:on")
|
||||
switch("warning", "CaseTransition:off")
|
||||
switch("warning", "ObservableStores:off")
|
||||
switch("warning", "LockLevel:off")
|
||||
--styleCheck:
|
||||
usages
|
||||
switch("warningAsError", "UseBase:on")
|
||||
--excludePath:nimbledeps/
|
||||
--styleCheck:
|
||||
error
|
||||
--mm:
|
||||
refc
|
||||
# reconsider when there's a version-2-2 branch worth testing with as we might switch to orc
|
||||
|
||||
# Avoid some rare stack corruption while using exceptions with a SEH-enabled
|
||||
# toolchain: https://github.com/status-im/nimbus-eth2/issues/3121
|
||||
if defined(windows) and not defined(vcc):
|
||||
--define:
|
||||
nimRawSetjmp
|
||||
|
||||
# begin Nimble config (version 1)
|
||||
when fileExists("nimble.paths"):
|
||||
include "nimble.paths"
|
||||
# end Nimble config
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# API
|
||||
|
||||
Coming Soon...
|
||||
@@ -1,7 +0,0 @@
|
||||
# Getting Started
|
||||
Welcome to nim-libp2p!
|
||||
|
||||
|
||||
To get started, please look at the [tutorials](../examples/tutorial_1_connect.md)
|
||||
|
||||
For more concrete examples, you can look at the [hello world example](../examples/helloworld.nim) or the [direct chat](../examples/directchat.nim)
|
||||
@@ -1,29 +0,0 @@
|
||||
# Introduction
|
||||
This folder contains the documentation for each nim-libp2p module and the sample code for the tutorials.
|
||||
|
||||
# Table of Contents
|
||||
### [Getting Started](GETTING_STARTED.md)
|
||||
### Tutorials
|
||||
- P2P Chat Example
|
||||
- [part I](tutorial/directchat/start.nim)
|
||||
- [part II](tutorial/directchat/second.nim)
|
||||
### API Specifications
|
||||
- libp2p
|
||||
- [libp2p-daemon-client](api/libp2p/daemonapi.md)
|
||||
- [interop-libp2p](api/libp2p/interop.md)
|
||||
- transports
|
||||
- [libp2p-tcp](api/transports/tcptransport.md)
|
||||
- secure channels
|
||||
- [libp2p-secio](api/secure_channels/secio.md)
|
||||
- stream multiplexers
|
||||
- [libp2p-mplex](api/stream_multiplexers/mplex.md)
|
||||
- utilities
|
||||
- [libp2p-crypto](api/utilities/crypto.md)
|
||||
- [libp2p-crypto-secp256k1](api/utilities/secp256k1.md)
|
||||
- data types
|
||||
- [peer-id](api/data_types/peer.md)
|
||||
- [peer-info](api/data_types/peerinfo.md)
|
||||
- pubsub
|
||||
- [libp2p-pubsub](api/pubsub/pubsub.md)
|
||||
- [libp2p-floodsub](api/pubsub/floodsub.md)
|
||||
- [libp2p-gossipsub](api/pubsub/gossipsub.md)
|
||||
@@ -1,56 +0,0 @@
|
||||
# Table of Contents
|
||||
- [Introduction](#introduction)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [Example](#example)
|
||||
- [Getting Started](#getting-started)
|
||||
|
||||
# Introduction
|
||||
This is a libp2p-backed daemon wrapping the functionalities of go-libp2p for use in Nim. <br>
|
||||
For more information about the go daemon, check out [this repository](https://github.com/libp2p/go-libp2p-daemon).
|
||||
|
||||
# Installation
|
||||
```sh
|
||||
# clone and install dependencies
|
||||
git clone https://github.com/status-im/nim-libp2p
|
||||
cd nim-libp2p
|
||||
nimble install
|
||||
|
||||
# perform unit tests
|
||||
nimble test
|
||||
|
||||
# update the git submodule to install the go daemon
|
||||
git submodule update --init --recursive
|
||||
go version
|
||||
git clone https://github.com/libp2p/go-libp2p-daemon
|
||||
cd go-libp2p-daemon
|
||||
git checkout v0.0.1
|
||||
go install ./...
|
||||
cd ..
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
## Example
|
||||
Examples can be found in the [examples folder](https://github.com/status-im/nim-libp2p/tree/readme/examples/go-daemon)
|
||||
|
||||
## Getting Started
|
||||
Try out the chat example. Full code can be found [here](https://github.com/status-im/nim-libp2p/blob/master/examples/chat.nim):
|
||||
|
||||
```bash
|
||||
nim c -r --threads:on examples/directchat.nim
|
||||
```
|
||||
|
||||
This will output a peer ID such as `QmbmHfVvouKammmQDJck4hz33WvVktNEe7pasxz2HgseRu` which you can use in another instance to connect to it.
|
||||
|
||||
```bash
|
||||
./examples/directchat
|
||||
/connect QmbmHfVvouKammmQDJck4hz33WvVktNEe7pasxz2HgseRu
|
||||
```
|
||||
|
||||
You can now chat between the instances!
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
when not(compileOption("threads")):
|
||||
{.fatal: "Please, compile this program with the --threads:on option!".}
|
||||
|
||||
import tables, strformat, strutils
|
||||
import chronos
|
||||
import ../libp2p/[switch,
|
||||
multistream,
|
||||
crypto/crypto,
|
||||
protocols/identify,
|
||||
connection,
|
||||
transports/transport,
|
||||
transports/tcptransport,
|
||||
multiaddress,
|
||||
peerinfo,
|
||||
peerid,
|
||||
protocols/protocol,
|
||||
protocols/secure/secure,
|
||||
protocols/secure/secio,
|
||||
muxers/muxer,
|
||||
muxers/mplex/mplex]
|
||||
|
||||
const ChatCodec = "/nim-libp2p/chat/1.0.0"
|
||||
const DefaultAddr = "/ip4/127.0.0.1/tcp/55505"
|
||||
|
||||
const Help = """
|
||||
Commands: /[?|hep|connect|disconnect|exit]
|
||||
help: Prints this help
|
||||
connect: dials a remote peer
|
||||
disconnect: ends current session
|
||||
exit: closes the chat
|
||||
"""
|
||||
|
||||
type ChatProto = ref object of LPProtocol
|
||||
switch: Switch # a single entry point for dialing and listening to peer
|
||||
transp: StreamTransport # transport streams between read & write file descriptor
|
||||
conn: Connection # create and close read & write stream
|
||||
connected: bool # if the node is connected to another peer
|
||||
started: bool # if the node has started
|
||||
|
||||
# copied from https://github.com/status-im/nimbus-eth2/blob/0ed657e953740a92458f23033d47483ffa17ccb0/beacon_chain/eth2_network.nim#L109-L115
|
||||
proc initAddress(T: type MultiAddress, str: string): T =
|
||||
let address = MultiAddress.init(str)
|
||||
if IPFS.match(address) and matchPartial(multiaddress.TCP, address):
|
||||
result = address
|
||||
else:
|
||||
raise newException(MultiAddressError,
|
||||
"Invalid bootstrap node multi-address")
|
||||
|
||||
proc dialPeer(p: ChatProto, address: string) {.async.} =
|
||||
let multiAddr = MultiAddress.initAddress(address);
|
||||
let parts = address.split("/")
|
||||
let remotePeer = PeerInfo.init(parts[^1],
|
||||
[multiAddr])
|
||||
|
||||
echo &"dialing peer: {multiAddr}"
|
||||
p.conn = await p.switch.dial(remotePeer, ChatCodec)
|
||||
p.connected = true
|
||||
|
||||
proc readAndPrint(p: ChatProto) {.async.} =
|
||||
while true:
|
||||
while p.connected:
|
||||
echo cast[string](await p.conn.readLp(1024))
|
||||
await sleepAsync(100.millis)
|
||||
|
||||
proc writeAndPrint(p: ChatProto) {.async.} =
|
||||
while true:
|
||||
if not p.connected:
|
||||
echo "type an address or wait for a connection:"
|
||||
echo "type /[help|?] for help"
|
||||
|
||||
let line = await p.transp.readLine()
|
||||
if line.startsWith("/help") or line.startsWith("/?") or not p.started:
|
||||
echo Help
|
||||
continue
|
||||
|
||||
if line.startsWith("/disconnect"):
|
||||
echo "Ending current session"
|
||||
if p.connected and p.conn.closed.not:
|
||||
await p.conn.close()
|
||||
p.connected = false
|
||||
elif line.startsWith("/connect"):
|
||||
if p.connected:
|
||||
var yesno = "N"
|
||||
echo "a session is already in progress, do you want end it [y/N]?"
|
||||
yesno = await p.transp.readLine()
|
||||
if yesno.cmpIgnoreCase("y") == 0:
|
||||
await p.conn.close()
|
||||
p.connected = false
|
||||
elif yesno.cmpIgnoreCase("n") == 0:
|
||||
continue
|
||||
else:
|
||||
echo "unrecognized response"
|
||||
continue
|
||||
|
||||
echo "enter address of remote peer"
|
||||
let address = await p.transp.readLine()
|
||||
if address.len > 0:
|
||||
await p.dialPeer(address)
|
||||
|
||||
elif line.startsWith("/exit"):
|
||||
if p.connected and p.conn.closed.not:
|
||||
await p.conn.close()
|
||||
p.connected = false
|
||||
|
||||
await p.switch.stop()
|
||||
echo "quitting..."
|
||||
quit(0)
|
||||
else:
|
||||
if p.connected:
|
||||
await p.conn.writeLp(line)
|
||||
else:
|
||||
try:
|
||||
if line.startsWith("/") and "ipfs" in line:
|
||||
await p.dialPeer(line)
|
||||
except:
|
||||
echo &"unable to dial remote peer {line}"
|
||||
echo getCurrentExceptionMsg()
|
||||
|
||||
proc readWriteLoop(p: ChatProto) {.async.} =
|
||||
asyncSpawn p.writeAndPrint() # execute the async function but does not block
|
||||
asyncSpawn p.readAndPrint()
|
||||
|
||||
proc processInput(rfd: AsyncFD) {.async.} =
|
||||
let transp = fromPipe(rfd)
|
||||
while true:
|
||||
let a = await transp.readLine()
|
||||
echo "You just entered: " & a
|
||||
|
||||
proc readInput(wfd: AsyncFD) {.thread.} =
|
||||
## This procedure performs reading from `stdin` and sends data over
|
||||
## pipe to main thread.
|
||||
let transp = fromPipe(wfd)
|
||||
|
||||
while true:
|
||||
let line = stdin.readLine()
|
||||
discard waitFor transp.write(line & "\r\n")
|
||||
|
||||
proc main() {.async.} =
|
||||
let (rfd, wfd) = createAsyncPipe()
|
||||
if rfd == asyncInvalidPipe or wfd == asyncInvalidPipe:
|
||||
raise newException(ValueError, "Could not initialize pipe!")
|
||||
|
||||
var thread: Thread[AsyncFD]
|
||||
thread.createThread(readInput, wfd)
|
||||
|
||||
await processInput(rfd)
|
||||
|
||||
when isMainModule: # isMainModule = true when the module is compiled as the main file
|
||||
waitFor(main())
|
||||
@@ -1,39 +0,0 @@
|
||||
when not(compileOption("threads")):
|
||||
{.fatal: "Please, compile this program with the --threads:on option!".}
|
||||
|
||||
import chronos # an efficient library for async
|
||||
|
||||
proc processInput(rfd: AsyncFD) {.async.} =
|
||||
echo "Type something below to see if the multithread IO works:\nType 'exit' to exit."
|
||||
|
||||
let transp = fromPipe(rfd)
|
||||
while true:
|
||||
let a = await transp.readLine()
|
||||
|
||||
if a == "exit":
|
||||
quit(0);
|
||||
|
||||
echo "You just entered: " & a
|
||||
|
||||
proc readInput(wfd: AsyncFD) {.thread.} =
|
||||
## This procedure performs reading from `stdin` and sends data over
|
||||
## pipe to main thread.
|
||||
let transp = fromPipe(wfd)
|
||||
|
||||
while true:
|
||||
let line = stdin.readLine()
|
||||
discard waitFor transp.write(line & "\r\n")
|
||||
|
||||
proc main() {.async.} =
|
||||
let (rfd, wfd) = createAsyncPipe()
|
||||
if rfd == asyncInvalidPipe or wfd == asyncInvalidPipe:
|
||||
raise newException(ValueError, "Could not initialize pipe!")
|
||||
|
||||
var thread: Thread[AsyncFD]
|
||||
thread.createThread(readInput, wfd)
|
||||
|
||||
await processInput(rfd)
|
||||
|
||||
when isMainModule: # isMainModule = true when the module is compiled as the main file
|
||||
waitFor(main())
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
when not(compileOption("threads")):
|
||||
{.fatal: "Please, compile this program with the --threads:on option!".}
|
||||
|
||||
import tables, strformat, strutils, bearssl
|
||||
import chronos # an efficient library for async
|
||||
import ../libp2p/[switch, # manage transports, a single entry point for dialing and listening
|
||||
builders, # helper to build the switch object
|
||||
multistream, # tag stream with short header to identify it
|
||||
multicodec, # multicodec utilities
|
||||
crypto/crypto, # cryptographic functions
|
||||
errors, # error handling utilities
|
||||
protocols/identify, # identify the peer info of a peer
|
||||
stream/connection, # create and close stream read / write connections
|
||||
transports/transport, # listen and dial to other peers using p2p protocol
|
||||
transports/tcptransport, # listen and dial to other peers using client-server protocol
|
||||
multiaddress, # encode different addressing schemes. For example, /ip4/7.7.7.7/tcp/6543 means it is using IPv4 protocol and TCP
|
||||
peerinfo, # manage the information of a peer, such as peer ID and public / private key
|
||||
peerid, # Implement how peers interact
|
||||
protocols/protocol, # define the protocol base type
|
||||
protocols/secure/secure, # define the protocol of secure connection
|
||||
protocols/secure/secio, # define the protocol of secure input / output, allows encrypted communication that uses public keys to validate signed messages instead of a certificate authority like in TLS
|
||||
muxers/muxer, # define an interface for stream multiplexing, allowing peers to offer many protocols over a single connection
|
||||
muxers/mplex/mplex] # define some contants and message types for stream multiplexing
|
||||
|
||||
const ChatCodec = "/nim-libp2p/chat/1.0.0"
|
||||
const DefaultAddr = "/ip4/127.0.0.1/tcp/55505"
|
||||
|
||||
const Help = """
|
||||
Commands: /[?|hep|connect|disconnect|exit]
|
||||
help: Prints this help
|
||||
connect: dials a remote peer
|
||||
disconnect: ends current session
|
||||
exit: closes the chat
|
||||
"""
|
||||
|
||||
type ChatProto = ref object of LPProtocol
|
||||
switch: Switch # a single entry point for dialing and listening to peer
|
||||
transp: StreamTransport # transport streams between read & write file descriptor
|
||||
conn: Connection # create and close read & write stream
|
||||
connected: bool # if the node is connected to another peer
|
||||
started: bool # if the node has started
|
||||
|
||||
proc readAndPrint(p: ChatProto) {.async.} =
|
||||
while true:
|
||||
var strData = await p.conn.readLp(1024)
|
||||
strData &= '\0'.uint8
|
||||
var str = cast[cstring](addr strdata[0])
|
||||
echo $p.switch.peerInfo.peerId & ": " & $str
|
||||
await sleepAsync(100.millis)
|
||||
|
||||
proc dialPeer(p: ChatProto, address: string) {.async.} =
|
||||
let
|
||||
multiAddr = MultiAddress.init(address).tryGet()
|
||||
# split the peerId part /p2p/...
|
||||
peerIdBytes = multiAddr[multiCodec("p2p")]
|
||||
.tryGet()
|
||||
.protoAddress()
|
||||
.tryGet()
|
||||
remotePeer = PeerId.init(peerIdBytes).tryGet()
|
||||
# split the wire address
|
||||
ip4Addr = multiAddr[multiCodec("ip4")].tryGet()
|
||||
tcpAddr = multiAddr[multiCodec("tcp")].tryGet()
|
||||
wireAddr = ip4Addr & tcpAddr
|
||||
|
||||
echo &"dialing peer: {multiAddr}"
|
||||
p.conn = await p.switch.dial(remotePeer, @[wireAddr], ChatCodec)
|
||||
p.connected = true
|
||||
asyncSpawn p.readAndPrint()
|
||||
|
||||
proc writeAndPrint(p: ChatProto) {.async.} =
|
||||
while true:
|
||||
if not p.connected:
|
||||
echo "type an address or wait for a connection:"
|
||||
echo "type /[help|?] for help"
|
||||
|
||||
let line = await p.transp.readLine()
|
||||
if line.startsWith("/help") or line.startsWith("/?") or not p.started:
|
||||
echo Help
|
||||
continue
|
||||
|
||||
if line.startsWith("/disconnect"):
|
||||
echo "Ending current session"
|
||||
if p.connected and p.conn.closed.not:
|
||||
await p.conn.close()
|
||||
p.connected = false
|
||||
elif line.startsWith("/connect"):
|
||||
if p.connected:
|
||||
var yesno = "N"
|
||||
echo "a session is already in progress, do you want end it [y/N]?"
|
||||
yesno = await p.transp.readLine()
|
||||
if yesno.cmpIgnoreCase("y") == 0:
|
||||
await p.conn.close()
|
||||
p.connected = false
|
||||
elif yesno.cmpIgnoreCase("n") == 0:
|
||||
continue
|
||||
else:
|
||||
echo "unrecognized response"
|
||||
continue
|
||||
|
||||
echo "enter address of remote peer"
|
||||
let address = await p.transp.readLine()
|
||||
if address.len > 0:
|
||||
await p.dialPeer(address)
|
||||
|
||||
elif line.startsWith("/exit"):
|
||||
if p.connected and p.conn.closed.not:
|
||||
await p.conn.close()
|
||||
p.connected = false
|
||||
|
||||
await p.switch.stop()
|
||||
echo "quitting..."
|
||||
quit(0)
|
||||
else:
|
||||
if p.connected:
|
||||
await p.conn.writeLp(line)
|
||||
else:
|
||||
try:
|
||||
if line.startsWith("/") and "p2p" in line:
|
||||
await p.dialPeer(line)
|
||||
except:
|
||||
echo &"unable to dial remote peer {line}"
|
||||
echo getCurrentExceptionMsg()
|
||||
|
||||
proc readWriteLoop(p: ChatProto) {.async.} =
|
||||
await p.writeAndPrint()
|
||||
|
||||
proc newChatProto(switch: Switch, transp: StreamTransport): ChatProto =
|
||||
var chatproto = ChatProto(switch: switch, transp: transp, codecs: @[ChatCodec])
|
||||
|
||||
# create handler for incoming connection
|
||||
proc handle(stream: Connection, proto: string) {.async.} =
|
||||
if chatproto.connected and not chatproto.conn.closed:
|
||||
echo "a chat session is already in progress - disconnecting!"
|
||||
await stream.close()
|
||||
else:
|
||||
chatproto.conn = stream
|
||||
chatproto.connected = true
|
||||
await chatproto.readAndPrint()
|
||||
|
||||
# assign the new handler
|
||||
chatproto.handler = handle
|
||||
return chatproto
|
||||
|
||||
proc readInput(wfd: AsyncFD) {.thread.} =
|
||||
## This procedure performs reading from `stdin` and sends data over
|
||||
## pipe to main thread.
|
||||
let transp = fromPipe(wfd)
|
||||
|
||||
while true:
|
||||
let line = stdin.readLine()
|
||||
discard waitFor transp.write(line & "\r\n")
|
||||
|
||||
proc processInput(rfd: AsyncFD, rng: ref BrHmacDrbgContext) {.async.} =
|
||||
let transp = fromPipe(rfd)
|
||||
|
||||
let seckey = PrivateKey.random(RSA, rng[]).get()
|
||||
var localAddress = DefaultAddr
|
||||
while true:
|
||||
echo &"Type an address to bind to or Enter to use the default {DefaultAddr}"
|
||||
let a = await transp.readLine()
|
||||
try:
|
||||
if a.len > 0:
|
||||
localAddress = a
|
||||
break
|
||||
# uise default
|
||||
break
|
||||
except:
|
||||
echo "invalid address"
|
||||
localAddress = DefaultAddr
|
||||
continue
|
||||
|
||||
var switch = SwitchBuilder
|
||||
.init()
|
||||
.withRng(rng)
|
||||
.withPrivateKey(seckey)
|
||||
.withAddress(MultiAddress.init(localAddress).tryGet())
|
||||
.build()
|
||||
|
||||
let chatProto = newChatProto(switch, transp)
|
||||
switch.mount(chatProto)
|
||||
let libp2pFuts = await switch.start()
|
||||
chatProto.started = true
|
||||
|
||||
let id = $switch.peerInfo.peerId
|
||||
echo "PeerId: " & id
|
||||
echo "listening on: "
|
||||
for a in switch.peerInfo.addrs:
|
||||
echo &"{a}/p2p/{id}"
|
||||
|
||||
await chatProto.readWriteLoop()
|
||||
await allFuturesThrowing(libp2pFuts)
|
||||
|
||||
proc main() {.async.} =
|
||||
let rng = newRng() # Singe random number source for the whole application
|
||||
let (rfd, wfd) = createAsyncPipe()
|
||||
if rfd == asyncInvalidPipe or wfd == asyncInvalidPipe:
|
||||
raise newException(ValueError, "Could not initialize pipe!")
|
||||
|
||||
var thread: Thread[AsyncFD]
|
||||
thread.createThread(readInput, wfd)
|
||||
|
||||
await processInput(rfd, rng)
|
||||
|
||||
when isMainModule: # isMainModule = true when the module is compiled as the main file
|
||||
waitFor(main())
|
||||
@@ -1,39 +0,0 @@
|
||||
when not(compileOption("threads")):
|
||||
{.fatal: "Please, compile this program with the --threads:on option!".}
|
||||
|
||||
import chronos # an efficient library for async
|
||||
|
||||
proc processInput(rfd: AsyncFD) {.async.} =
|
||||
echo "Type something below to see if the multithread IO works:\nType 'exit' to exit."
|
||||
|
||||
let transp = fromPipe(rfd)
|
||||
while true:
|
||||
let a = await transp.readLine()
|
||||
|
||||
if a == "exit":
|
||||
quit(0);
|
||||
|
||||
echo "You just entered: " & a
|
||||
|
||||
proc readInput(wfd: AsyncFD) {.thread.} =
|
||||
## This procedure performs reading from `stdin` and sends data over
|
||||
## pipe to main thread.
|
||||
let transp = fromPipe(wfd)
|
||||
|
||||
while true:
|
||||
let line = stdin.readLine()
|
||||
discard waitFor transp.write(line & "\r\n")
|
||||
|
||||
proc main() {.async.} =
|
||||
let (rfd, wfd) = createAsyncPipe()
|
||||
if rfd == asyncInvalidPipe or wfd == asyncInvalidPipe:
|
||||
raise newException(ValueError, "Could not initialize pipe!")
|
||||
|
||||
var thread: Thread[AsyncFD]
|
||||
thread.createThread(readInput, wfd)
|
||||
|
||||
await processInput(rfd)
|
||||
|
||||
when isMainModule: # isMainModule = true when the module is compiled as the main file
|
||||
waitFor(main())
|
||||
|
||||
5
examples/README.md
Normal file
5
examples/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# nim-libp2p examples
|
||||
|
||||
In this folder, you'll find the sources of the [nim-libp2p website](https://vacp2p.github.io/nim-libp2p/docs/)
|
||||
|
||||
We recommand to follow the tutorials on the website, but feel free to grok the sources here!
|
||||
94
examples/circuitrelay.nim
Normal file
94
examples/circuitrelay.nim
Normal file
@@ -0,0 +1,94 @@
|
||||
{.used.}
|
||||
## # Circuit Relay example
|
||||
##
|
||||
## Circuit Relay can be used when a node cannot reach another node
|
||||
## directly, but can reach it through another node (the Relay).
|
||||
##
|
||||
## That may happen because of NAT, Firewalls, or incompatible transports.
|
||||
##
|
||||
## More informations [here](https://docs.libp2p.io/concepts/circuit-relay/).
|
||||
import chronos, stew/byteutils
|
||||
import libp2p, libp2p/protocols/connectivity/relay/[relay, client]
|
||||
|
||||
# Helper to create a circuit relay node
|
||||
proc createCircuitRelaySwitch(r: Relay): Switch =
|
||||
SwitchBuilder
|
||||
.new()
|
||||
.withRng(newRng())
|
||||
.withAddresses(@[MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet()])
|
||||
.withTcpTransport()
|
||||
.withMplex()
|
||||
.withNoise()
|
||||
.withCircuitRelay(r)
|
||||
.build()
|
||||
|
||||
proc main() {.async.} =
|
||||
# Create a custom protocol
|
||||
let customProtoCodec = "/test"
|
||||
var proto = new LPProtocol
|
||||
proto.codec = customProtoCodec
|
||||
proto.handler = proc(
|
||||
conn: Connection, proto: string
|
||||
) {.async: (raises: [CancelledError]).} =
|
||||
try:
|
||||
var msg = string.fromBytes(await conn.readLp(1024))
|
||||
echo "1 - Dst Received: ", msg
|
||||
assert "test1" == msg
|
||||
await conn.writeLp("test2")
|
||||
msg = string.fromBytes(await conn.readLp(1024))
|
||||
echo "2 - Dst Received: ", msg
|
||||
assert "test3" == msg
|
||||
await conn.writeLp("test4")
|
||||
except CancelledError as e:
|
||||
raise e
|
||||
except CatchableError as e:
|
||||
echo "exception in handler", e.msg
|
||||
|
||||
let
|
||||
relay = Relay.new()
|
||||
clSrc = RelayClient.new()
|
||||
clDst = RelayClient.new()
|
||||
|
||||
# Create three hosts, enable relay client on two of them.
|
||||
# The third one can relay connections for other peers.
|
||||
# RelayClient can use a relay, Relay is a relay.
|
||||
swRel = createCircuitRelaySwitch(relay)
|
||||
swSrc = createCircuitRelaySwitch(clSrc)
|
||||
swDst = createCircuitRelaySwitch(clDst)
|
||||
|
||||
swDst.mount(proto)
|
||||
|
||||
await swRel.start()
|
||||
await swSrc.start()
|
||||
await swDst.start()
|
||||
|
||||
let
|
||||
# Create a relay address to swDst using swRel as the relay
|
||||
addrs = MultiAddress
|
||||
.init(
|
||||
$swRel.peerInfo.addrs[0] & "/p2p/" & $swRel.peerInfo.peerId & "/p2p-circuit"
|
||||
)
|
||||
.get()
|
||||
|
||||
# Connect Dst to the relay
|
||||
await swDst.connect(swRel.peerInfo.peerId, swRel.peerInfo.addrs)
|
||||
|
||||
# Dst reserve a slot on the relay.
|
||||
let rsvp = await clDst.reserve(swRel.peerInfo.peerId, swRel.peerInfo.addrs)
|
||||
|
||||
# Src dial Dst using the relay
|
||||
let conn = await swSrc.dial(swDst.peerInfo.peerId, @[addrs], customProtoCodec)
|
||||
|
||||
await conn.writeLp("test1")
|
||||
var msg = string.fromBytes(await conn.readLp(1024))
|
||||
echo "1 - Src Received: ", msg
|
||||
assert "test2" == msg
|
||||
await conn.writeLp("test3")
|
||||
msg = string.fromBytes(await conn.readLp(1024))
|
||||
echo "2 - Src Received: ", msg
|
||||
assert "test4" == msg
|
||||
|
||||
await relay.stop()
|
||||
await allFutures(swSrc.stop(), swDst.stop(), swRel.stop())
|
||||
|
||||
waitFor(main())
|
||||
@@ -1,15 +1,13 @@
|
||||
when not(compileOption("threads")):
|
||||
{.used.}
|
||||
when not (compileOption("threads")):
|
||||
{.fatal: "Please, compile this program with the --threads:on option!".}
|
||||
|
||||
import
|
||||
strformat, strutils, bearssl,
|
||||
stew/byteutils,
|
||||
chronos,
|
||||
../libp2p
|
||||
import strformat, strutils, stew/byteutils, chronos, libp2p
|
||||
|
||||
const DefaultAddr = "/ip4/127.0.0.1/tcp/0"
|
||||
|
||||
const Help = """
|
||||
const Help =
|
||||
"""
|
||||
Commands: /[?|help|connect|disconnect|exit]
|
||||
help: Prints this help
|
||||
connect: dials a remote peer
|
||||
@@ -17,12 +15,11 @@ const Help = """
|
||||
exit: closes the chat
|
||||
"""
|
||||
|
||||
type
|
||||
Chat = ref object
|
||||
switch: Switch # a single entry point for dialing and listening to peer
|
||||
stdinReader: StreamTransport # transport streams between read & write file descriptor
|
||||
conn: Connection # connection to the other peer
|
||||
connected: bool # if the node is connected to another peer
|
||||
type Chat = ref object
|
||||
switch: Switch # a single entry point for dialing and listening to peer
|
||||
stdinReader: StreamTransport # transport streams between read & write file descriptor
|
||||
conn: Connection # connection to the other peer
|
||||
connected: bool # if the node is connected to another peer
|
||||
|
||||
##
|
||||
# Stdout helpers, to write the prompt
|
||||
@@ -41,19 +38,23 @@ proc writeStdout(c: Chat, str: string) =
|
||||
##
|
||||
const ChatCodec = "/nim-libp2p/chat/1.0.0"
|
||||
|
||||
type
|
||||
ChatProto = ref object of LPProtocol
|
||||
type ChatProto = ref object of LPProtocol
|
||||
|
||||
proc new(T: typedesc[ChatProto], c: Chat): T =
|
||||
let chatproto = T()
|
||||
|
||||
# create handler for incoming connection
|
||||
proc handle(stream: Connection, proto: string) {.async.} =
|
||||
if c.connected and not c.conn.closed:
|
||||
c.writeStdout "a chat session is already in progress - refusing incoming peer!"
|
||||
await stream.close()
|
||||
else:
|
||||
await c.handlePeer(stream)
|
||||
proc handle(stream: Connection, proto: string) {.async: (raises: [CancelledError]).} =
|
||||
try:
|
||||
if c.connected and not c.conn.closed:
|
||||
c.writeStdout "a chat session is already in progress - refusing incoming peer!"
|
||||
else:
|
||||
await c.handlePeer(stream)
|
||||
except CancelledError as e:
|
||||
raise e
|
||||
except CatchableError as e:
|
||||
echo "exception in handler", e.msg
|
||||
finally:
|
||||
await stream.close()
|
||||
|
||||
# assign the new handler
|
||||
@@ -77,9 +78,9 @@ proc handlePeer(c: Chat, conn: Connection) {.async.} =
|
||||
strData = await conn.readLp(1024)
|
||||
str = string.fromBytes(strData)
|
||||
c.writeStdout $conn.peerId & ": " & $str
|
||||
|
||||
except LPStreamEOFError:
|
||||
defer: c.writeStdout $conn.peerId & " disconnected"
|
||||
defer:
|
||||
c.writeStdout $conn.peerId & " disconnected"
|
||||
await c.conn.close()
|
||||
c.connected = false
|
||||
|
||||
@@ -88,10 +89,7 @@ proc dialPeer(c: Chat, address: string) {.async.} =
|
||||
let
|
||||
multiAddr = MultiAddress.init(address).tryGet()
|
||||
# split the peerId part /p2p/...
|
||||
peerIdBytes = multiAddr[multiCodec("p2p")]
|
||||
.tryGet()
|
||||
.protoAddress()
|
||||
.tryGet()
|
||||
peerIdBytes = multiAddr[multiCodec("p2p")].tryGet().protoAddress().tryGet()
|
||||
remotePeer = PeerId.init(peerIdBytes).tryGet()
|
||||
# split the wire address
|
||||
ip4Addr = multiAddr[multiCodec("ip4")].tryGet()
|
||||
@@ -124,7 +122,6 @@ proc readLoop(c: Chat) {.async.} =
|
||||
let address = await c.stdinReader.readLine()
|
||||
if address.len > 0:
|
||||
await c.dialPeer(address)
|
||||
|
||||
elif line.startsWith("/exit"):
|
||||
if c.connected and c.conn.closed.not:
|
||||
await c.conn.close()
|
||||
@@ -171,16 +168,18 @@ proc main() {.async.} =
|
||||
|
||||
var switch = SwitchBuilder
|
||||
.new()
|
||||
.withRng(rng) # Give the application RNG
|
||||
.withRng(rng)
|
||||
# Give the application RNG
|
||||
.withAddress(localAddress)
|
||||
.withTcpTransport() # Use TCP as transport
|
||||
.withMplex() # Use Mplex as muxer
|
||||
.withNoise() # Use Noise as secure manager
|
||||
.withTcpTransport()
|
||||
# Use TCP as transport
|
||||
.withMplex()
|
||||
# Use Mplex as muxer
|
||||
.withNoise()
|
||||
# Use Noise as secure manager
|
||||
.build()
|
||||
|
||||
let chat = Chat(
|
||||
switch: switch,
|
||||
stdinReader: stdinReader)
|
||||
let chat = Chat(switch: switch, stdinReader: stdinReader)
|
||||
|
||||
switch.mount(ChatProto.new(chat))
|
||||
|
||||
|
||||
3
examples/examples_build.nim
Normal file
3
examples/examples_build.nim
Normal file
@@ -0,0 +1,3 @@
|
||||
{.used.}
|
||||
|
||||
import directchat, tutorial_6_game
|
||||
5
examples/examples_run.nim
Normal file
5
examples/examples_run.nim
Normal file
@@ -0,0 +1,5 @@
|
||||
{.used.}
|
||||
|
||||
import
|
||||
helloworld, circuitrelay, tutorial_1_connect, tutorial_2_customproto,
|
||||
tutorial_3_protobuf, tutorial_4_gossipsub, tutorial_5_discovery
|
||||
@@ -2,8 +2,7 @@ import chronos, nimcrypto, strutils
|
||||
import ../../libp2p/daemon/daemonapi
|
||||
import ../hexdump
|
||||
|
||||
const
|
||||
PubSubTopic = "test-net"
|
||||
const PubSubTopic = "test-net"
|
||||
|
||||
proc dumpSubscribedPeers(api: DaemonAPI) {.async.} =
|
||||
var peers = await api.pubsubListPeers(PubSubTopic)
|
||||
@@ -37,12 +36,12 @@ proc main() {.async.} =
|
||||
|
||||
asyncSpawn monitor(api)
|
||||
|
||||
proc pubsubLogger(api: DaemonAPI,
|
||||
ticket: PubsubTicket,
|
||||
message: PubSubMessage): Future[bool] {.async.} =
|
||||
proc pubsubLogger(
|
||||
api: DaemonAPI, ticket: PubsubTicket, message: PubSubMessage
|
||||
): Future[bool] {.async.} =
|
||||
let msglen = len(message.data)
|
||||
echo "= Recieved pubsub message with length ", msglen,
|
||||
" bytes from peer ", message.peer.pretty()
|
||||
echo "= Recieved pubsub message with length ",
|
||||
msglen, " bytes from peer ", message.peer.pretty()
|
||||
echo dumpHex(message.data)
|
||||
await api.dumpSubscribedPeers()
|
||||
result = true
|
||||
|
||||
@@ -2,18 +2,16 @@ import chronos, nimcrypto, strutils
|
||||
import ../../libp2p/daemon/daemonapi
|
||||
|
||||
## nim c -r --threads:on chat.nim
|
||||
when not(compileOption("threads")):
|
||||
when not (compileOption("threads")):
|
||||
{.fatal: "Please, compile this program with the --threads:on option!".}
|
||||
|
||||
const
|
||||
ServerProtocols = @["/test-chat-stream"]
|
||||
const ServerProtocols = @["/test-chat-stream"]
|
||||
|
||||
type
|
||||
CustomData = ref object
|
||||
api: DaemonAPI
|
||||
remotes: seq[StreamTransport]
|
||||
consoleFd: AsyncFD
|
||||
serveFut: Future[void]
|
||||
type CustomData = ref object
|
||||
api: DaemonAPI
|
||||
remotes: seq[StreamTransport]
|
||||
consoleFd: AsyncFD
|
||||
serveFut: Future[void]
|
||||
|
||||
proc threadMain(wfd: AsyncFD) {.thread.} =
|
||||
## This procedure performs reading from `stdin` and sends data over
|
||||
@@ -82,7 +80,7 @@ proc serveThread(udata: CustomData) {.async.} =
|
||||
relay = true
|
||||
break
|
||||
if relay:
|
||||
echo peer.pretty(), " * ", " [", addresses.join(", "), "]"
|
||||
echo peer.pretty(), " * ", " [", addresses.join(", "), "]"
|
||||
else:
|
||||
echo peer.pretty(), " [", addresses.join(", "), "]"
|
||||
elif line.startsWith("/exit"):
|
||||
@@ -95,8 +93,8 @@ proc serveThread(udata: CustomData) {.async.} =
|
||||
pending.add(item.write(msg))
|
||||
if len(pending) > 0:
|
||||
var results = await all(pending)
|
||||
except:
|
||||
echo getCurrentException().msg
|
||||
except CatchableError as err:
|
||||
echo err.msg
|
||||
|
||||
proc main() {.async.} =
|
||||
var data = new CustomData
|
||||
|
||||
43
examples/go-daemon/daemonapi.md
Normal file
43
examples/go-daemon/daemonapi.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Table of Contents
|
||||
- [Introduction](#introduction)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Installation](#installation)
|
||||
- [Script](#script)
|
||||
- [Examples](#examples)
|
||||
|
||||
# Introduction
|
||||
This is a libp2p-backed daemon wrapping the functionalities of go-libp2p for use in Nim. <br>
|
||||
For more information about the go daemon, check out [this repository](https://github.com/libp2p/go-libp2p-daemon).
|
||||
> **Required only** for running the tests.
|
||||
|
||||
# Prerequisites
|
||||
Go with version `1.16.0`
|
||||
> You will *likely* be able to build `go-libp2p-daemon` with different Go versions, but **they haven't been tested**.
|
||||
|
||||
# Installation
|
||||
Run the build script while having the `go` command pointing to the correct Go version.
|
||||
```sh
|
||||
./scripts/build_p2pd.sh
|
||||
```
|
||||
`build_p2pd.sh` will not rebuild unless needed. If you already have the newest binary and you want to force the rebuild, use:
|
||||
```sh
|
||||
./scripts/build_p2pd.sh -f
|
||||
```
|
||||
Or:
|
||||
```sh
|
||||
./scripts/build_p2pd.sh --force
|
||||
```
|
||||
|
||||
If everything goes correctly, the binary (`p2pd`) should be built and placed in the `$GOPATH/bin` directory.
|
||||
If you're having issues, head into [our discord](https://discord.com/channels/864066763682218004/1115526869769535629) and ask for assistance.
|
||||
|
||||
After successfully building the binary, remember to add it to your path so it can be found. You can do that by running:
|
||||
```sh
|
||||
export PATH="$PATH:$HOME/go/bin"
|
||||
```
|
||||
> **Tip:** To make this change permanent, add the command above to your `.bashrc` file.
|
||||
|
||||
# Examples
|
||||
Examples can be found in the [examples folder](https://github.com/status-im/nim-libp2p/tree/readme/examples/go-daemon)
|
||||
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
import chronos, nimcrypto, strutils, os
|
||||
import ../../libp2p/daemon/daemonapi
|
||||
|
||||
const
|
||||
PubSubTopic = "test-net"
|
||||
const PubSubTopic = "test-net"
|
||||
|
||||
proc main(bn: string) {.async.} =
|
||||
echo "= Starting P2P node"
|
||||
var bootnodes = bn.split(",")
|
||||
var api = await newDaemonApi({DHTFull, PSGossipSub, WaitBootstrap},
|
||||
bootstrapNodes = bootnodes,
|
||||
peersRequired = 1)
|
||||
var api = await newDaemonApi(
|
||||
{DHTFull, PSGossipSub, WaitBootstrap}, bootstrapNodes = bootnodes, peersRequired = 1
|
||||
)
|
||||
var id = await api.identity()
|
||||
echo "= P2P node ", id.peer.pretty(), " started:"
|
||||
for item in id.addresses:
|
||||
echo item
|
||||
|
||||
proc pubsubLogger(api: DaemonAPI,
|
||||
ticket: PubsubTicket,
|
||||
message: PubSubMessage): Future[bool] {.async.} =
|
||||
proc pubsubLogger(
|
||||
api: DaemonAPI, ticket: PubsubTicket, message: PubSubMessage
|
||||
): Future[bool] {.async.} =
|
||||
let msglen = len(message.data)
|
||||
echo "= Recieved pubsub message with length ", msglen,
|
||||
" bytes from peer ", message.peer.pretty(), ": "
|
||||
echo "= Recieved pubsub message with length ",
|
||||
msglen, " bytes from peer ", message.peer.pretty(), ": "
|
||||
var strdata = cast[string](message.data)
|
||||
echo strdata
|
||||
result = true
|
||||
|
||||
@@ -1,39 +1,48 @@
|
||||
import bearssl
|
||||
import chronos # an efficient library for async
|
||||
import stew/byteutils # various utils
|
||||
import ../libp2p # when installed through nimble, just use `import libp2p`
|
||||
{.used.}
|
||||
|
||||
import chronos # an efficient library for async
|
||||
import stew/byteutils # various utils
|
||||
import libp2p
|
||||
|
||||
##
|
||||
# Create our custom protocol
|
||||
##
|
||||
const TestCodec = "/test/proto/1.0.0" # custom protocol string identifier
|
||||
|
||||
type
|
||||
TestProto = ref object of LPProtocol # declare a custom protocol
|
||||
type TestProto = ref object of LPProtocol # declare a custom protocol
|
||||
|
||||
proc new(T: typedesc[TestProto]): T =
|
||||
|
||||
# every incoming connections will be in handled in this closure
|
||||
proc handle(conn: Connection, proto: string) {.async, gcsafe.} =
|
||||
echo "Got from remote - ", string.fromBytes(await conn.readLp(1024))
|
||||
await conn.writeLp("Roger p2p!")
|
||||
proc handle(conn: Connection, proto: string) {.async: (raises: [CancelledError]).} =
|
||||
try:
|
||||
echo "Got from remote - ", string.fromBytes(await conn.readLp(1024))
|
||||
await conn.writeLp("Roger p2p!")
|
||||
except CancelledError as e:
|
||||
raise e
|
||||
except CatchableError as e:
|
||||
echo "exception in handler", e.msg
|
||||
finally:
|
||||
# We must close the connections ourselves when we're done with it
|
||||
await conn.close()
|
||||
|
||||
# We must close the connections ourselves when we're done with it
|
||||
await conn.close()
|
||||
|
||||
return T(codecs: @[TestCodec], handler: handle)
|
||||
return T.new(codecs = @[TestCodec], handler = handle)
|
||||
|
||||
##
|
||||
# Helper to create a switch/node
|
||||
##
|
||||
proc createSwitch(ma: MultiAddress, rng: ref BrHmacDrbgContext): Switch =
|
||||
proc createSwitch(ma: MultiAddress, rng: ref HmacDrbgContext): Switch =
|
||||
var switch = SwitchBuilder
|
||||
.new()
|
||||
.withRng(rng) # Give the application RNG
|
||||
.withAddress(ma) # Our local address(es)
|
||||
.withTcpTransport() # Use TCP as transport
|
||||
.withMplex() # Use Mplex as muxer
|
||||
.withNoise() # Use Noise as secure manager
|
||||
.withRng(rng)
|
||||
# Give the application RNG
|
||||
.withAddress(ma)
|
||||
# Our local address(es)
|
||||
.withTcpTransport()
|
||||
# Use TCP as transport
|
||||
.withMplex()
|
||||
# Use Mplex as muxer
|
||||
.withNoise()
|
||||
# Use Noise as secure manager
|
||||
.build()
|
||||
|
||||
result = switch
|
||||
@@ -41,7 +50,7 @@ proc createSwitch(ma: MultiAddress, rng: ref BrHmacDrbgContext): Switch =
|
||||
##
|
||||
# The actual application
|
||||
##
|
||||
proc main() {.async, gcsafe.} =
|
||||
proc main() {.async.} =
|
||||
let
|
||||
rng = newRng() # Single random number source for the whole application
|
||||
# port 0 will take a random available port
|
||||
@@ -74,7 +83,8 @@ proc main() {.async, gcsafe.} =
|
||||
# use the second node to dial the first node
|
||||
# using the first node peerid and address
|
||||
# and specify our custom protocol codec
|
||||
let conn = await switch2.dial(switch1.peerInfo.peerId, switch1.peerInfo.addrs, TestCodec)
|
||||
let conn =
|
||||
await switch2.dial(switch1.peerInfo.peerId, switch1.peerInfo.addrs, TestCodec)
|
||||
|
||||
# conn is now a fully setup connection, we talk directly to the node1 custom protocol handler
|
||||
await conn.writeLp("Hello p2p!") # writeLp send a length prefixed buffer over the wire
|
||||
@@ -85,6 +95,7 @@ proc main() {.async, gcsafe.} =
|
||||
# We must close the connection ourselves when we're done with it
|
||||
await conn.close()
|
||||
|
||||
await allFutures(switch1.stop(), switch2.stop()) # close connections and shutdown all transports
|
||||
await allFutures(switch1.stop(), switch2.stop())
|
||||
# close connections and shutdown all transports
|
||||
|
||||
waitFor(main())
|
||||
|
||||
@@ -35,26 +35,24 @@ proc dumpHex*(pbytes: pointer, nbytes: int, items = 1, ascii = true): string =
|
||||
var asciiText = ""
|
||||
while i < nbytes:
|
||||
if i %% 16 == 0:
|
||||
result = result & toHex(cast[BiggestInt](slider),
|
||||
sizeof(BiggestInt) * 2) & ": "
|
||||
result = result & toHex(cast[BiggestInt](slider), sizeof(BiggestInt) * 2) & ": "
|
||||
var k = 0
|
||||
while k < items:
|
||||
var ch = cast[ptr char](cast[uint](slider) + k.uint)[]
|
||||
if ord(ch) > 31 and ord(ch) < 127: asciiText &= ch else: asciiText &= "."
|
||||
if ord(ch) > 31 and ord(ch) < 127:
|
||||
asciiText &= ch
|
||||
else:
|
||||
asciiText &= "."
|
||||
inc(k)
|
||||
case items:
|
||||
case items
|
||||
of 1:
|
||||
result = result & toHex(cast[BiggestInt](cast[ptr uint8](slider)[]),
|
||||
hexSize)
|
||||
result = result & toHex(cast[BiggestInt](cast[ptr uint8](slider)[]), hexSize)
|
||||
of 2:
|
||||
result = result & toHex(cast[BiggestInt](cast[ptr uint16](slider)[]),
|
||||
hexSize)
|
||||
result = result & toHex(cast[BiggestInt](cast[ptr uint16](slider)[]), hexSize)
|
||||
of 4:
|
||||
result = result & toHex(cast[BiggestInt](cast[ptr uint32](slider)[]),
|
||||
hexSize)
|
||||
result = result & toHex(cast[BiggestInt](cast[ptr uint32](slider)[]), hexSize)
|
||||
of 8:
|
||||
result = result & toHex(cast[BiggestInt](cast[ptr uint64](slider)[]),
|
||||
hexSize)
|
||||
result = result & toHex(cast[BiggestInt](cast[ptr uint64](slider)[]), hexSize)
|
||||
else:
|
||||
raise newException(ValueError, "Wrong items size!")
|
||||
result = result & " "
|
||||
|
||||
6
examples/index.md
Normal file
6
examples/index.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# nim-libp2p documentation
|
||||
|
||||
Welcome to the nim-libp2p documentation!
|
||||
|
||||
Here, you'll find [tutorials](tutorial_1_connect.md) to help you get started, as well as
|
||||
the [full reference](https://vacp2p.github.io/nim-libp2p/master/libp2p.html).
|
||||
@@ -1,106 +0,0 @@
|
||||
Hi all, welcome to the first article of the nim-libp2p's tutorial series!
|
||||
|
||||
_This tutorial is for everyone who is interested in building peer-to-peer chatting applications. No Nim programming experience is needed._
|
||||
|
||||
To give you a quick overview, **Nim** is the programming language we are using and **nim-libp2p** is the Nim implementation of [libp2p](https://libp2p.io/), a modular library that enables the development of peer-to-peer network applications.
|
||||
|
||||
Hope you'll find it helpful in your journey of learning. Happy coding! ;)
|
||||
|
||||
# Before you start
|
||||
The only prerequisite here is [Nim](https://nim-lang.org/), the programming language with a Python-like syntax and a performance similar to C. Detailed information can be found [here](https://nim-lang.org/docs/tut1.html).
|
||||
|
||||
Install Nim via their official website: [https://nim-lang.org/install.html](https://nim-lang.org/install.html)
|
||||
Check Nim's installation via `nim --version` and its package manager Nimble via `nimble --version`.
|
||||
|
||||
You can now install the latest version of `nim-libp2p`:
|
||||
```bash
|
||||
nimble install libp2p@#master
|
||||
```
|
||||
|
||||
# A simple ping application
|
||||
We'll start by creating a simple application, which is starting two libp2p [switch](https://docs.libp2p.io/concepts/stream-multiplexing/#switch-swarm), and pinging each other using the [Ping](https://docs.libp2p.io/concepts/protocols/#ping) protocol.
|
||||
|
||||
_TIP: You can extract the code from this tutorial by running `nim c -r tools/markdown_runner.nim examples/tutorial_1_connect.md` in the libp2p folder!_
|
||||
|
||||
Let's create a `part1.nim`, and import our dependencies:
|
||||
```nim
|
||||
import bearssl
|
||||
import chronos
|
||||
|
||||
import libp2p
|
||||
import libp2p/protocols/ping
|
||||
```
|
||||
[bearssl](https://github.com/status-im/nim-bearssl) is used as a [cryptographic pseudorandom number generator](https://en.wikipedia.org/wiki/Cryptographically-secure_pseudorandom_number_generator)
|
||||
[chronos](https://github.com/status-im/nim-chronos) the asynchronous framework used by `nim-libp2p`
|
||||
|
||||
Next, we'll create an helper procedure to create our switches. A switch needs a bit of configuration, and it will be easier to do this configuration only once:
|
||||
```nim
|
||||
proc createSwitch(ma: MultiAddress, rng: ref BrHmacDrbgContext): Switch =
|
||||
var switch = SwitchBuilder
|
||||
.new()
|
||||
.withRng(rng) # Give the application RNG
|
||||
.withAddress(ma) # Our local address(es)
|
||||
.withTcpTransport() # Use TCP as transport
|
||||
.withMplex() # Use Mplex as muxer
|
||||
.withNoise() # Use Noise as secure manager
|
||||
.build()
|
||||
|
||||
return switch
|
||||
```
|
||||
This will create a switch using [Mplex](https://docs.libp2p.io/concepts/stream-multiplexing/) as a multiplexer, Noise to secure the communication, and TCP as an underlying transport.
|
||||
|
||||
You can of course tweak this, to use a different or multiple transport, or tweak the configuration of Mplex and Noise, but this is some sane defaults that we'll use going forward.
|
||||
|
||||
|
||||
Let's now start to create our main procedure:
|
||||
```nim
|
||||
proc main() {.async, gcsafe.} =
|
||||
let
|
||||
rng = newRng()
|
||||
localAddress = MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet()
|
||||
pingProtocol = Ping.new(rng=rng)
|
||||
```
|
||||
We created some variables that we'll need for the rest of the application: the global `rng` instance, our `localAddress`, and an instance of the `Ping` protocol.
|
||||
The address is in the [MultiAddress](https://github.com/multiformats/multiaddr) format. The port `0` means "take any port available".
|
||||
|
||||
`tryGet` is procedure which is part of the [nim-result](https://github.com/arnetheduck/nim-result/), that will throw an exception if the supplied MultiAddress is not valid.
|
||||
|
||||
We can now create our two switches:
|
||||
```nim
|
||||
let
|
||||
switch1 = createSwitch(localAddress, rng)
|
||||
switch2 = createSwitch(localAddress, rng)
|
||||
|
||||
switch1.mount(pingProtocol)
|
||||
|
||||
await switch1.start()
|
||||
await switch2.start()
|
||||
```
|
||||
We've **mounted** the `pingProtocol` on our first switch. This means that the first switch will actually listen for any ping requests coming in, and handle them accordingly.
|
||||
|
||||
Now that we've started the nodes, they are listening for incoming peers.
|
||||
We can find out which port was attributed, and the resulting local addresses, by using `switch1.peerInfo.addrs`.
|
||||
|
||||
We'll **dial** the first switch from the second one, by specifying it's **Peer ID**, it's **MultiAddress** and the **`Ping` protocol codec**:
|
||||
```nim
|
||||
let conn = await switch2.dial(switch1.peerInfo.peerId, switch1.peerInfo.addrs, PingCodec)
|
||||
```
|
||||
We now have a `Ping` connection setup between the second and the first switch, we can use it to actually ping the node:
|
||||
```nim
|
||||
# ping the other node and echo the ping duration
|
||||
echo "ping: ", await pingProtocol.ping(conn)
|
||||
|
||||
# We must close the connection ourselves when we're done with it
|
||||
await conn.close()
|
||||
```
|
||||
|
||||
And that's it! Just a little bit of cleanup: shutting down the switches, waiting for them to stop, and we'll call our `main` procedure:
|
||||
```nim
|
||||
await allFutures(switch1.stop(), switch2.stop()) # close connections and shutdown all transports
|
||||
|
||||
waitFor(main())
|
||||
```
|
||||
|
||||
You can now run this program using `nim c -r part1.nim`, and you should see the dialing sequence, ending with a ping output.
|
||||
|
||||
In the [next tutorial](tutorial_2_customproto.md), we'll look at how to create our own custom protocol.
|
||||
103
examples/tutorial_1_connect.nim
Normal file
103
examples/tutorial_1_connect.nim
Normal file
@@ -0,0 +1,103 @@
|
||||
{.used.}
|
||||
## # Simple ping tutorial
|
||||
##
|
||||
## Hi all, welcome to the first nim-libp2p tutorial!
|
||||
##
|
||||
## !!! tips ""
|
||||
## This tutorial is for everyone who is interested in building peer-to-peer applications. No Nim programming experience is needed.
|
||||
##
|
||||
## To give you a quick overview, **Nim** is the programming language we are using and **nim-libp2p** is the Nim implementation of [libp2p](https://libp2p.io/), a modular library that enables the development of peer-to-peer network applications.
|
||||
##
|
||||
## Hope you'll find it helpful in your journey of learning. Happy coding! ;)
|
||||
##
|
||||
## ## Before you start
|
||||
## The only prerequisite here is [Nim](https://nim-lang.org/), the programming language with a Python-like syntax and a performance similar to C. Detailed information can be found [here](https://nim-lang.org/docs/tut1.html).
|
||||
##
|
||||
## Install Nim via their [official website](https://nim-lang.org/install.html).
|
||||
## Check Nim's installation via `nim --version` and its package manager Nimble via `nimble --version`.
|
||||
##
|
||||
## You can now install the latest version of `nim-libp2p`:
|
||||
## ```bash
|
||||
## nimble install libp2p@#master
|
||||
## ```
|
||||
##
|
||||
## ## A simple ping application
|
||||
## We'll start by creating a simple application, which is starting two libp2p [switch](https://docs.libp2p.io/concepts/stream-multiplexing/#switch-swarm), and pinging each other using the [Ping](https://docs.libp2p.io/concepts/protocols/#ping) protocol.
|
||||
##
|
||||
## !!! tips ""
|
||||
## You can find the source of this tutorial (and other tutorials) in the [libp2p/examples](https://github.com/status-im/nim-libp2p/tree/master/examples) folder!
|
||||
##
|
||||
## Let's create a `part1.nim`, and import our dependencies:
|
||||
import chronos
|
||||
|
||||
import libp2p
|
||||
import libp2p/protocols/ping
|
||||
|
||||
## [chronos](https://github.com/status-im/nim-chronos) the asynchronous framework used by `nim-libp2p`
|
||||
##
|
||||
## Next, we'll create a helper procedure to create our switches. A switch needs a bit of configuration, and it will be easier to do this configuration only once:
|
||||
proc createSwitch(ma: MultiAddress, rng: ref HmacDrbgContext): Switch =
|
||||
var switch = SwitchBuilder
|
||||
.new()
|
||||
.withRng(rng)
|
||||
# Give the application RNG
|
||||
.withAddress(ma)
|
||||
# Our local address(es)
|
||||
.withTcpTransport()
|
||||
# Use TCP as transport
|
||||
.withMplex()
|
||||
# Use Mplex as muxer
|
||||
.withNoise()
|
||||
# Use Noise as secure manager
|
||||
.build()
|
||||
|
||||
return switch
|
||||
|
||||
## This will create a switch using [Mplex](https://docs.libp2p.io/concepts/stream-multiplexing/) as a multiplexer, Noise to secure the communication, and TCP as an underlying transport.
|
||||
##
|
||||
## You can of course tweak this, to use a different or multiple transport, or tweak the configuration of Mplex and Noise, but this is some sane defaults that we'll use going forward.
|
||||
##
|
||||
##
|
||||
## Let's now start to create our main procedure:
|
||||
proc main() {.async.} =
|
||||
let
|
||||
rng = newRng()
|
||||
localAddress = MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet()
|
||||
pingProtocol = Ping.new(rng = rng)
|
||||
## We created some variables that we'll need for the rest of the application: the global `rng` instance, our `localAddress`, and an instance of the `Ping` protocol.
|
||||
## The address is in the [MultiAddress](https://github.com/multiformats/multiaddr) format. The port `0` means "take any port available".
|
||||
##
|
||||
## `tryGet` is procedure which is part of [nim-result](https://github.com/arnetheduck/nim-result/), that will throw an exception if the supplied MultiAddress is invalid.
|
||||
##
|
||||
## We can now create our two switches:
|
||||
let
|
||||
switch1 = createSwitch(localAddress, rng)
|
||||
switch2 = createSwitch(localAddress, rng)
|
||||
|
||||
switch1.mount(pingProtocol)
|
||||
|
||||
await switch1.start()
|
||||
await switch2.start()
|
||||
## We've **mounted** the `pingProtocol` on our first switch. This means that the first switch will actually listen for any ping requests coming in, and handle them accordingly.
|
||||
##
|
||||
## Now that we've started the nodes, they are listening for incoming peers.
|
||||
## We can find out which port was attributed, and the resulting local addresses, by using `switch1.peerInfo.addrs`.
|
||||
##
|
||||
## We'll **dial** the first switch from the second one, by specifying its **Peer ID**, its **MultiAddress** and the **`Ping` protocol codec**:
|
||||
let conn =
|
||||
await switch2.dial(switch1.peerInfo.peerId, switch1.peerInfo.addrs, PingCodec)
|
||||
## We now have a `Ping` connection setup between the second and the first switch, we can use it to actually ping the node:
|
||||
# ping the other node and echo the ping duration
|
||||
echo "ping: ", await pingProtocol.ping(conn)
|
||||
|
||||
# We must close the connection ourselves when we're done with it
|
||||
await conn.close()
|
||||
## And that's it! Just a little bit of cleanup: shutting down the switches, waiting for them to stop, and we'll call our `main` procedure:
|
||||
await allFutures(switch1.stop(), switch2.stop())
|
||||
# close connections and shutdown all transports
|
||||
|
||||
waitFor(main())
|
||||
|
||||
## You can now run this program using `nim c -r part1.nim`, and you should see the dialing sequence, ending with a ping output.
|
||||
##
|
||||
## In the [next tutorial](tutorial_2_customproto.md), we'll look at how to create our own custom protocol.
|
||||
@@ -1,80 +0,0 @@
|
||||
In the [previous tutorial](tutorial_1_connect.md), we've looked at how to create a simple ping program using the `nim-libp2p`.
|
||||
|
||||
We'll now look at how to create a custom protocol inside the libp2p
|
||||
|
||||
# Custom protocol in libp2p
|
||||
Let's create a `part2.nim`, and import our dependencies:
|
||||
```nim
|
||||
import bearssl
|
||||
import chronos
|
||||
import stew/byteutils
|
||||
|
||||
import libp2p
|
||||
```
|
||||
This is similar to the first tutorial, except we don't need to import the `Ping` protocol.
|
||||
|
||||
Next, we'll declare our custom protocol
|
||||
```nim
|
||||
const TestCodec = "/test/proto/1.0.0"
|
||||
|
||||
type TestProto = ref object of LPProtocol
|
||||
```
|
||||
|
||||
We've set a [protocol ID](https://docs.libp2p.io/concepts/protocols/#protocol-ids), and created a custom `LPProtocol`. In a more complex protocol, we could use this structure to store interesting variables.
|
||||
|
||||
A protocol generally has two part: and handling/server part, and a dialing/client part.
|
||||
Theses two parts can be identical, but in our trivial protocol, the server will wait for a message from the client, and the client will send a message, so we have to handle the two cases separately.
|
||||
|
||||
Let's start with the server part:
|
||||
```nim
|
||||
proc new(T: typedesc[TestProto]): T =
|
||||
# every incoming connections will in be handled in this closure
|
||||
proc handle(conn: Connection, proto: string) {.async, gcsafe.} =
|
||||
echo "Got from remote - ", string.fromBytes(await conn.readLp(1024))
|
||||
# We must close the connections ourselves when we're done with it
|
||||
await conn.close()
|
||||
|
||||
return T(codecs: @[TestCodec], handler: handle)
|
||||
```
|
||||
This is a constructor for our `TestProto`, that will specify our `codecs` and a `handler`, which will be called for each incoming peer asking for this protocol.
|
||||
In our handle, we simply read a message from the connection and `echo` it.
|
||||
|
||||
We can now create our client part:
|
||||
```nim
|
||||
proc hello(p: TestProto, conn: Connection) {.async.} =
|
||||
await conn.writeLp("Hello p2p!")
|
||||
```
|
||||
Again, pretty straight-forward, we just send a message on the connection.
|
||||
|
||||
We can now create our main procedure:
|
||||
```nim
|
||||
proc main() {.async, gcsafe.} =
|
||||
let
|
||||
rng = newRng()
|
||||
testProto = TestProto.new()
|
||||
switch1 = newStandardSwitch(rng=rng)
|
||||
switch2 = newStandardSwitch(rng=rng)
|
||||
|
||||
switch1.mount(testProto)
|
||||
|
||||
await switch1.start()
|
||||
await switch2.start()
|
||||
|
||||
let conn = await switch2.dial(switch1.peerInfo.peerId, switch1.peerInfo.addrs, TestCodec)
|
||||
|
||||
await testProto.hello(conn)
|
||||
|
||||
# We must close the connection ourselves when we're done with it
|
||||
await conn.close()
|
||||
|
||||
await allFutures(switch1.stop(), switch2.stop()) # close connections and shutdown all transports
|
||||
```
|
||||
|
||||
This is very similar to the first tutorial's `main`, the only noteworthy difference is that we use `newStandardSwitch`, which is similar to `createSwitch` but is bundled directly in libp2p
|
||||
|
||||
We can now wrap our program by calling our main proc:
|
||||
```nim
|
||||
waitFor(main())
|
||||
```
|
||||
|
||||
And that's it!
|
||||
82
examples/tutorial_2_customproto.nim
Normal file
82
examples/tutorial_2_customproto.nim
Normal file
@@ -0,0 +1,82 @@
|
||||
{.used.}
|
||||
## # Custom protocol in libp2p
|
||||
##
|
||||
## In the [previous tutorial](tutorial_1_connect.md), we've looked at how to create a simple ping program using the `nim-libp2p`.
|
||||
##
|
||||
## We'll now look at how to create a custom protocol inside the libp2p
|
||||
##
|
||||
## Let's create a `part2.nim`, and import our dependencies:
|
||||
import chronos
|
||||
import stew/byteutils
|
||||
|
||||
import libp2p
|
||||
## This is similar to the first tutorial, except we don't need to import the `Ping` protocol.
|
||||
##
|
||||
## Next, we'll declare our custom protocol
|
||||
const TestCodec = "/test/proto/1.0.0"
|
||||
|
||||
type TestProto = ref object of LPProtocol
|
||||
|
||||
## We've set a [protocol ID](https://docs.libp2p.io/concepts/protocols/#protocol-ids), and created a custom `LPProtocol`. In a more complex protocol, we could use this structure to store interesting variables.
|
||||
##
|
||||
## A protocol generally has two parts: a handling/server part, and a dialing/client part.
|
||||
## These two parts can be identical, but in our trivial protocol, the server will wait for a message from the client, and the client will send a message, so we have to handle the two cases separately.
|
||||
##
|
||||
## Let's start with the server part:
|
||||
|
||||
proc new(T: typedesc[TestProto]): T =
|
||||
# every incoming connections will in be handled in this closure
|
||||
proc handle(conn: Connection, proto: string) {.async: (raises: [CancelledError]).} =
|
||||
# Read up to 1024 bytes from this connection, and transform them into
|
||||
# a string
|
||||
try:
|
||||
echo "Got from remote - ", string.fromBytes(await conn.readLp(1024))
|
||||
except CancelledError as e:
|
||||
raise e
|
||||
except CatchableError as e:
|
||||
echo "exception in handler", e.msg
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
return T.new(codecs = @[TestCodec], handler = handle)
|
||||
|
||||
## This is a constructor for our `TestProto`, that will specify our `codecs` and a `handler`, which will be called for each incoming peer asking for this protocol.
|
||||
## In our handle, we simply read a message from the connection and `echo` it.
|
||||
##
|
||||
## We can now create our client part:
|
||||
proc hello(p: TestProto, conn: Connection) {.async.} =
|
||||
await conn.writeLp("Hello p2p!")
|
||||
|
||||
## Again, pretty straightforward, we just send a message on the connection.
|
||||
##
|
||||
## We can now create our main procedure:
|
||||
proc main() {.async.} =
|
||||
let
|
||||
rng = newRng()
|
||||
testProto = TestProto.new()
|
||||
switch1 = newStandardSwitch(rng = rng)
|
||||
switch2 = newStandardSwitch(rng = rng)
|
||||
|
||||
switch1.mount(testProto)
|
||||
|
||||
await switch1.start()
|
||||
await switch2.start()
|
||||
|
||||
let conn =
|
||||
await switch2.dial(switch1.peerInfo.peerId, switch1.peerInfo.addrs, TestCodec)
|
||||
|
||||
await testProto.hello(conn)
|
||||
|
||||
# We must close the connection ourselves when we're done with it
|
||||
await conn.close()
|
||||
|
||||
await allFutures(switch1.stop(), switch2.stop())
|
||||
# close connections and shutdown all transports
|
||||
|
||||
## This is very similar to the first tutorial's `main`, the only noteworthy difference is that we use `newStandardSwitch`, which is similar to the `createSwitch` of the first tutorial, but is bundled directly in libp2p
|
||||
##
|
||||
## We can now wrap our program by calling our main proc:
|
||||
waitFor(main())
|
||||
|
||||
## And that's it!
|
||||
## In the [next tutorial](tutorial_3_protobuf.md), we'll create a more complex protocol using Protobuf.
|
||||
172
examples/tutorial_3_protobuf.nim
Normal file
172
examples/tutorial_3_protobuf.nim
Normal file
@@ -0,0 +1,172 @@
|
||||
{.used.}
|
||||
## # Protobuf usage
|
||||
##
|
||||
## In the [previous tutorial](tutorial_2_customproto.md), we created a simple "ping" protocol.
|
||||
## Most real protocol want their messages to be structured and extensible, which is why
|
||||
## most real protocols use [protobuf](https://developers.google.com/protocol-buffers) to
|
||||
## define their message structures.
|
||||
##
|
||||
## Here, we'll create a slightly more complex protocol, which parses & generate protobuf
|
||||
## messages. Let's start by importing our dependencies, as usual:
|
||||
import chronos
|
||||
import stew/results # for Opt[T]
|
||||
|
||||
import libp2p
|
||||
|
||||
## ## Protobuf encoding & decoding
|
||||
## This will be the structure of our messages:
|
||||
## ```protobuf
|
||||
## message MetricList {
|
||||
## message Metric {
|
||||
## string name = 1;
|
||||
## float value = 2;
|
||||
## }
|
||||
##
|
||||
## repeated Metric metrics = 2;
|
||||
## }
|
||||
## ```
|
||||
## We'll create our protobuf types, encoders & decoders, according to this format.
|
||||
## To create the encoders & decoders, we are going to use minprotobuf
|
||||
## (included in libp2p).
|
||||
##
|
||||
## While more modern technics
|
||||
## (such as [nim-protobuf-serialization](https://github.com/status-im/nim-protobuf-serialization))
|
||||
## exists, minprotobuf is currently the recommended method to handle protobuf, since it has
|
||||
## been used in production extensively, and audited.
|
||||
type
|
||||
Metric = object
|
||||
name: string
|
||||
value: float
|
||||
|
||||
MetricList = object
|
||||
metrics: seq[Metric]
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
proc encode(m: Metric): ProtoBuffer =
|
||||
result = initProtoBuffer()
|
||||
result.write(1, m.name)
|
||||
result.write(2, m.value)
|
||||
result.finish()
|
||||
|
||||
proc decode(_: type Metric, buf: seq[byte]): Result[Metric, ProtoError] =
|
||||
var res: Metric
|
||||
let pb = initProtoBuffer(buf)
|
||||
# "getField" will return a Result[bool, ProtoError].
|
||||
# The Result will hold an error if the protobuf is invalid.
|
||||
# The Result will hold "false" if the field is missing
|
||||
#
|
||||
# We are just checking the error, and ignoring whether the value
|
||||
# is present or not (default values are valid).
|
||||
discard ?pb.getField(1, res.name)
|
||||
discard ?pb.getField(2, res.value)
|
||||
ok(res)
|
||||
|
||||
proc encode(m: MetricList): ProtoBuffer =
|
||||
result = initProtoBuffer()
|
||||
for metric in m.metrics:
|
||||
result.write(1, metric.encode())
|
||||
result.finish()
|
||||
|
||||
proc decode(_: type MetricList, buf: seq[byte]): Result[MetricList, ProtoError] =
|
||||
var
|
||||
res: MetricList
|
||||
metrics: seq[seq[byte]]
|
||||
let pb = initProtoBuffer(buf)
|
||||
discard ?pb.getRepeatedField(1, metrics)
|
||||
|
||||
for metric in metrics:
|
||||
res.metrics &= ?Metric.decode(metric)
|
||||
ok(res)
|
||||
|
||||
## ## Results instead of exceptions
|
||||
## As you can see, this part of the program also uses Results instead of exceptions for error handling.
|
||||
## We start by `{.push raises: [].}`, which will prevent every non-async function from raising
|
||||
## exceptions.
|
||||
##
|
||||
## Then, we use [nim-result](https://github.com/arnetheduck/nim-result) to convey
|
||||
## errors to function callers. A `Result[T, E]` will either hold a valid result of type
|
||||
## T, or an error of type E.
|
||||
##
|
||||
## You can check if the call succeeded by using `res.isOk`, and then get the
|
||||
## value using `res.value` or the error by using `res.error`.
|
||||
##
|
||||
## Another useful tool is `?`, which will unpack a Result if it succeeded,
|
||||
## or if it failed, exit the current procedure returning the error.
|
||||
##
|
||||
## nim-result is packed with other functionalities that you'll find in the
|
||||
## nim-result repository.
|
||||
##
|
||||
## Results and exception are generally interchangeable, but have different semantics
|
||||
## that you may or may not prefer.
|
||||
##
|
||||
## ## Creating the protocol
|
||||
## We'll next create a protocol, like in the last tutorial, to request these metrics from our host
|
||||
type
|
||||
MetricCallback = proc(): Future[MetricList] {.raises: [], gcsafe.}
|
||||
MetricProto = ref object of LPProtocol
|
||||
metricGetter: MetricCallback
|
||||
|
||||
proc new(_: typedesc[MetricProto], cb: MetricCallback): MetricProto =
|
||||
var res: MetricProto
|
||||
proc handle(conn: Connection, proto: string) {.async: (raises: [CancelledError]).} =
|
||||
try:
|
||||
let
|
||||
metrics = await res.metricGetter()
|
||||
asProtobuf = metrics.encode()
|
||||
await conn.writeLp(asProtobuf.buffer)
|
||||
except CancelledError as e:
|
||||
raise e
|
||||
except CatchableError as e:
|
||||
echo "exception in handler", e.msg
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
res = MetricProto.new(@["/metric-getter/1.0.0"], handle)
|
||||
res.metricGetter = cb
|
||||
return res
|
||||
|
||||
proc fetch(p: MetricProto, conn: Connection): Future[MetricList] {.async.} =
|
||||
let protobuf = await conn.readLp(2048)
|
||||
# tryGet will raise an exception if the Result contains an error.
|
||||
# It's useful to bridge between exception-world and result-world
|
||||
return MetricList.decode(protobuf).tryGet()
|
||||
|
||||
## We can now create our main procedure:
|
||||
proc main() {.async.} =
|
||||
let rng = newRng()
|
||||
proc randomMetricGenerator(): Future[MetricList] {.async.} =
|
||||
let metricCount = rng[].generate(uint32) mod 16
|
||||
for i in 0 ..< metricCount + 1:
|
||||
result.metrics.add(
|
||||
Metric(name: "metric_" & $i, value: float(rng[].generate(uint16)) / 1000.0)
|
||||
)
|
||||
return result
|
||||
|
||||
let
|
||||
metricProto1 = MetricProto.new(randomMetricGenerator)
|
||||
metricProto2 = MetricProto.new(randomMetricGenerator)
|
||||
switch1 = newStandardSwitch(rng = rng)
|
||||
switch2 = newStandardSwitch(rng = rng)
|
||||
|
||||
switch1.mount(metricProto1)
|
||||
|
||||
await switch1.start()
|
||||
await switch2.start()
|
||||
|
||||
let
|
||||
conn = await switch2.dial(
|
||||
switch1.peerInfo.peerId, switch1.peerInfo.addrs, metricProto2.codecs
|
||||
)
|
||||
metrics = await metricProto2.fetch(conn)
|
||||
await conn.close()
|
||||
|
||||
for metric in metrics.metrics:
|
||||
echo metric.name, " = ", metric.value
|
||||
|
||||
await allFutures(switch1.stop(), switch2.stop())
|
||||
# close connections and shutdown all transports
|
||||
|
||||
waitFor(main())
|
||||
|
||||
## If you run this program, you should see random metrics being sent from the switch1 to the switch2.
|
||||
169
examples/tutorial_4_gossipsub.nim
Normal file
169
examples/tutorial_4_gossipsub.nim
Normal file
@@ -0,0 +1,169 @@
|
||||
{.used.}
|
||||
## # GossipSub
|
||||
##
|
||||
## In this tutorial, we'll build a simple GossipSub network
|
||||
## to broadcast the metrics we built in the previous tutorial.
|
||||
##
|
||||
## GossipSub is used to broadcast some messages in a network,
|
||||
## and allows to balance between latency, bandwidth usage,
|
||||
## privacy and attack resistance.
|
||||
##
|
||||
## You'll find a good explanation of how GossipSub works
|
||||
## [here.](https://docs.libp2p.io/concepts/publish-subscribe/) There are a lot
|
||||
## of parameters you can tweak to adjust how GossipSub behaves but here we'll
|
||||
## use the sane defaults shipped with libp2p.
|
||||
##
|
||||
## We'll start by creating our metric structure like previously
|
||||
|
||||
import chronos
|
||||
import stew/results
|
||||
|
||||
import libp2p
|
||||
import libp2p/protocols/pubsub/rpc/messages
|
||||
|
||||
type
|
||||
Metric = object
|
||||
name: string
|
||||
value: float
|
||||
|
||||
MetricList = object
|
||||
hostname: string
|
||||
metrics: seq[Metric]
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
proc encode(m: Metric): ProtoBuffer =
|
||||
result = initProtoBuffer()
|
||||
result.write(1, m.name)
|
||||
result.write(2, m.value)
|
||||
result.finish()
|
||||
|
||||
proc decode(_: type Metric, buf: seq[byte]): Result[Metric, ProtoError] =
|
||||
var res: Metric
|
||||
let pb = initProtoBuffer(buf)
|
||||
discard ?pb.getField(1, res.name)
|
||||
discard ?pb.getField(2, res.value)
|
||||
ok(res)
|
||||
|
||||
proc encode(m: MetricList): ProtoBuffer =
|
||||
result = initProtoBuffer()
|
||||
for metric in m.metrics:
|
||||
result.write(1, metric.encode())
|
||||
result.write(2, m.hostname)
|
||||
result.finish()
|
||||
|
||||
proc decode(_: type MetricList, buf: seq[byte]): Result[MetricList, ProtoError] =
|
||||
var
|
||||
res: MetricList
|
||||
metrics: seq[seq[byte]]
|
||||
let pb = initProtoBuffer(buf)
|
||||
discard ?pb.getRepeatedField(1, metrics)
|
||||
|
||||
for metric in metrics:
|
||||
res.metrics &= ?Metric.decode(metric)
|
||||
?pb.getRequiredField(2, res.hostname)
|
||||
ok(res)
|
||||
|
||||
## This is exactly like the previous structure, except that we added
|
||||
## a `hostname` to distinguish where the metric is coming from.
|
||||
##
|
||||
## Now we'll create a small GossipSub network to broadcast the metrics,
|
||||
## and collect them on one of the node.
|
||||
|
||||
type Node = tuple[switch: Switch, gossip: GossipSub, hostname: string]
|
||||
|
||||
proc oneNode(node: Node, rng: ref HmacDrbgContext) {.async.} =
|
||||
# This procedure will handle one of the node of the network
|
||||
node.gossip.addValidator(
|
||||
["metrics"],
|
||||
proc(topic: string, message: Message): Future[ValidationResult] {.async.} =
|
||||
let decoded = MetricList.decode(message.data)
|
||||
if decoded.isErr:
|
||||
return ValidationResult.Reject
|
||||
return ValidationResult.Accept,
|
||||
)
|
||||
# This "validator" will attach to the `metrics` topic and make sure
|
||||
# that every message in this topic is valid. This allows us to stop
|
||||
# propagation of invalid messages quickly in the network, and punish
|
||||
# peers sending them.
|
||||
|
||||
# `John` will be responsible to log the metrics, the rest of the nodes
|
||||
# will just forward them in the network
|
||||
if node.hostname == "John":
|
||||
node.gossip.subscribe(
|
||||
"metrics",
|
||||
proc(topic: string, data: seq[byte]) {.async.} =
|
||||
let m = MetricList.decode(data).expect("metric can be decoded")
|
||||
echo m
|
||||
,
|
||||
)
|
||||
else:
|
||||
node.gossip.subscribe("metrics", nil)
|
||||
|
||||
# Create random metrics 10 times and broadcast them
|
||||
for _ in 0 ..< 10:
|
||||
await sleepAsync(500.milliseconds)
|
||||
var metricList = MetricList(hostname: node.hostname)
|
||||
let metricCount = rng[].generate(uint32) mod 4
|
||||
for i in 0 ..< metricCount + 1:
|
||||
metricList.metrics.add(
|
||||
Metric(name: "metric_" & $i, value: float(rng[].generate(uint16)) / 1000.0)
|
||||
)
|
||||
|
||||
discard await node.gossip.publish("metrics", encode(metricList).buffer)
|
||||
await node.switch.stop()
|
||||
|
||||
## For our main procedure, we'll create a few nodes, and connect them together.
|
||||
## Note that they are not all interconnected, but GossipSub will take care of
|
||||
## broadcasting to the full network nonetheless.
|
||||
proc main() {.async.} =
|
||||
let rng = newRng()
|
||||
var nodes: seq[Node]
|
||||
|
||||
for hostname in ["John", "Walter", "David", "Thuy", "Amy"]:
|
||||
let
|
||||
switch = newStandardSwitch(rng = rng)
|
||||
gossip = GossipSub.init(switch = switch, triggerSelf = true)
|
||||
switch.mount(gossip)
|
||||
await switch.start()
|
||||
|
||||
nodes.add((switch, gossip, hostname))
|
||||
|
||||
for index, node in nodes:
|
||||
# Connect to a few neighbors
|
||||
for otherNodeIdx in index - 1 .. index + 2:
|
||||
if otherNodeIdx notin 0 ..< nodes.len or otherNodeIdx == index:
|
||||
continue
|
||||
let otherNode = nodes[otherNodeIdx]
|
||||
await node.switch.connect(
|
||||
otherNode.switch.peerInfo.peerId, otherNode.switch.peerInfo.addrs
|
||||
)
|
||||
|
||||
var allFuts: seq[Future[void]]
|
||||
for node in nodes:
|
||||
allFuts.add(oneNode(node, rng))
|
||||
|
||||
await allFutures(allFuts)
|
||||
|
||||
waitFor(main())
|
||||
|
||||
## If you run this program, you should see something like:
|
||||
## ```
|
||||
## (hostname: "John", metrics: @[(name: "metric_0", value: 42.097), (name: "metric_1", value: 50.99), (name: "metric_2", value: 47.86), (name: "metric_3", value: 5.368)])
|
||||
## (hostname: "Walter", metrics: @[(name: "metric_0", value: 39.452), (name: "metric_1", value: 15.606), (name: "metric_2", value: 14.059), (name: "metric_3", value: 6.68)])
|
||||
## (hostname: "David", metrics: @[(name: "metric_0", value: 9.82), (name: "metric_1", value: 2.862), (name: "metric_2", value: 15.514)])
|
||||
## (hostname: "Thuy", metrics: @[(name: "metric_0", value: 59.038)])
|
||||
## (hostname: "Amy", metrics: @[(name: "metric_0", value: 55.616), (name: "metric_1", value: 23.52), (name: "metric_2", value: 59.081), (name: "metric_3", value: 2.516)])
|
||||
## ```
|
||||
##
|
||||
## This is John receiving & logging everyone's metrics.
|
||||
##
|
||||
## ## Going further
|
||||
## Building efficient & safe GossipSub networks is a tricky subject. By tweaking the [gossip params](https://vacp2p.github.io/nim-libp2p/master/libp2p/protocols/pubsub/gossipsub/types.html#GossipSubParams)
|
||||
## and [topic params](https://vacp2p.github.io/nim-libp2p/master/libp2p/protocols/pubsub/gossipsub/types.html#TopicParams),
|
||||
## you can achieve very different properties.
|
||||
##
|
||||
## Also see reports for [GossipSub v1.1](https://gateway.ipfs.io/ipfs/QmRAFP5DBnvNjdYSbWhEhVRJJDFCLpPyvew5GwCCB4VxM4)
|
||||
##
|
||||
## If you are interested in broadcasting for your application, you may want to use [Waku](https://waku.org/), which builds on top of GossipSub,
|
||||
## and adds features such as history, spam protection, and light node friendliness.
|
||||
141
examples/tutorial_5_discovery.nim
Normal file
141
examples/tutorial_5_discovery.nim
Normal file
@@ -0,0 +1,141 @@
|
||||
{.used.}
|
||||
## # Discovery Manager
|
||||
##
|
||||
## In the [previous tutorial](tutorial_4_gossipsub.md), we built a custom protocol using [protobuf](https://developers.google.com/protocol-buffers) and
|
||||
## spread informations (some metrics) on the network using gossipsub.
|
||||
## For this tutorial, on the other hand, we'll go back to a simple example
|
||||
## we'll try to discover a specific peers to greet on the network.
|
||||
##
|
||||
## First, as usual, we import the dependencies:
|
||||
import sequtils
|
||||
import chronos
|
||||
import stew/byteutils
|
||||
|
||||
import libp2p
|
||||
import libp2p/protocols/rendezvous
|
||||
import libp2p/discovery/rendezvousinterface
|
||||
import libp2p/discovery/discoverymngr
|
||||
|
||||
## We'll not use newStandardSwitch this time as we need the discovery protocol
|
||||
## [RendezVous](https://github.com/libp2p/specs/blob/master/rendezvous/README.md) to be mounted on the switch using withRendezVous.
|
||||
##
|
||||
## Note that other discovery methods such as [Kademlia](https://github.com/libp2p/specs/blob/master/kad-dht/README.md) or [discv5](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md) exist.
|
||||
proc createSwitch(rdv: RendezVous = RendezVous.new()): Switch =
|
||||
SwitchBuilder
|
||||
.new()
|
||||
.withRng(newRng())
|
||||
.withAddresses(@[MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet()])
|
||||
.withTcpTransport()
|
||||
.withYamux()
|
||||
.withNoise()
|
||||
.withRendezVous(rdv)
|
||||
.build()
|
||||
|
||||
# Create a really simple protocol to log one message received then close the stream
|
||||
const DumbCodec = "/dumb/proto/1.0.0"
|
||||
type DumbProto = ref object of LPProtocol
|
||||
proc new(T: typedesc[DumbProto], nodeNumber: int): T =
|
||||
proc handle(conn: Connection, proto: string) {.async: (raises: [CancelledError]).} =
|
||||
try:
|
||||
echo "Node", nodeNumber, " received: ", string.fromBytes(await conn.readLp(1024))
|
||||
except CancelledError as e:
|
||||
raise e
|
||||
except CatchableError as e:
|
||||
echo "exception in handler", e.msg
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
return T.new(codecs = @[DumbCodec], handler = handle)
|
||||
|
||||
## ## Bootnodes
|
||||
## The first time a p2p program is ran, he needs to know how to join
|
||||
## its network. This is generally done by hard-coding a list of stable
|
||||
## nodes in the binary, called "bootnodes". These bootnodes are a
|
||||
## critical part of a p2p network, since they are used by every new
|
||||
## user to onboard the network.
|
||||
##
|
||||
## By using libp2p, we can use any node supporting our discovery protocol
|
||||
## (rendezvous in this case) as a bootnode. For this example, we'll
|
||||
## create a bootnode, and then every peer will advertise itself on the
|
||||
## bootnode, and use it to find other peers
|
||||
proc main() {.async.} =
|
||||
let bootNode = createSwitch()
|
||||
await bootNode.start()
|
||||
|
||||
# Create 5 nodes in the network
|
||||
var
|
||||
switches: seq[Switch] = @[]
|
||||
discManagers: seq[DiscoveryManager] = @[]
|
||||
|
||||
for i in 0 .. 5:
|
||||
let rdv = RendezVous.new()
|
||||
# Create a remote future to await at the end of the program
|
||||
let switch = createSwitch(rdv)
|
||||
switch.mount(DumbProto.new(i))
|
||||
switches.add(switch)
|
||||
|
||||
# A discovery manager is a simple tool, you can set it up by adding discovery
|
||||
# interfaces (such as RendezVousInterface) then you can use it to advertise
|
||||
# something on the network or to request something from it.
|
||||
let dm = DiscoveryManager()
|
||||
# A RendezVousInterface is a RendezVous protocol wrapped to be usable by the
|
||||
# DiscoveryManager.
|
||||
dm.add(RendezVousInterface.new(rdv))
|
||||
discManagers.add(dm)
|
||||
|
||||
# We can now start the switch and connect to the bootnode
|
||||
await switch.start()
|
||||
await switch.connect(bootNode.peerInfo.peerId, bootNode.peerInfo.addrs)
|
||||
|
||||
# Each nodes of the network will advertise on some topics (EvenGang or OddClub)
|
||||
dm.advertise(RdvNamespace(if i mod 2 == 0: "EvenGang" else: "OddClub"))
|
||||
|
||||
## We can now create the newcomer. This peer will connect to the boot node, and use
|
||||
## it to discover peers & greet them.
|
||||
let
|
||||
rdv = RendezVous.new()
|
||||
newcomer = createSwitch(rdv)
|
||||
dm = DiscoveryManager()
|
||||
await newcomer.start()
|
||||
await newcomer.connect(bootNode.peerInfo.peerId, bootNode.peerInfo.addrs)
|
||||
dm.add(RendezVousInterface.new(rdv, ttr = 250.milliseconds))
|
||||
|
||||
# Use the discovery manager to find peers on the OddClub topic to greet them
|
||||
let queryOddClub = dm.request(RdvNamespace("OddClub"))
|
||||
for _ in 0 .. 2:
|
||||
let
|
||||
# getPeer give you a PeerAttribute containing informations about the peer.
|
||||
res = await queryOddClub.getPeer()
|
||||
# Here we will use the PeerId and the MultiAddress to greet him
|
||||
conn = await newcomer.dial(res[PeerId], res.getAll(MultiAddress), DumbCodec)
|
||||
await conn.writeLp("Odd Club suuuucks! Even Gang is better!")
|
||||
# Uh-oh!
|
||||
await conn.close()
|
||||
# Wait for the peer to close the stream
|
||||
await conn.join()
|
||||
# Queries will run in a loop, so we must stop them when we are done
|
||||
queryOddClub.stop()
|
||||
|
||||
# Maybe it was because he wanted to join the EvenGang
|
||||
let queryEvenGang = dm.request(RdvNamespace("EvenGang"))
|
||||
for _ in 0 .. 2:
|
||||
let
|
||||
res = await queryEvenGang.getPeer()
|
||||
conn = await newcomer.dial(res[PeerId], res.getAll(MultiAddress), DumbCodec)
|
||||
await conn.writeLp("Even Gang is sooo laaame! Odd Club rocks!")
|
||||
# Or maybe not...
|
||||
await conn.close()
|
||||
await conn.join()
|
||||
queryEvenGang.stop()
|
||||
# What can I say, some people just want to watch the world burn... Anyway
|
||||
|
||||
# Stop all the discovery managers
|
||||
for d in discManagers:
|
||||
d.stop()
|
||||
dm.stop()
|
||||
|
||||
# Stop all the switches
|
||||
await allFutures(switches.mapIt(it.stop()))
|
||||
await allFutures(bootNode.stop(), newcomer.stop())
|
||||
|
||||
waitFor(main())
|
||||
285
examples/tutorial_6_game.nim
Normal file
285
examples/tutorial_6_game.nim
Normal file
@@ -0,0 +1,285 @@
|
||||
{.used.}
|
||||
## # Tron example
|
||||
##
|
||||
## In this tutorial, we will create a video game based on libp2p, using
|
||||
## all of the features we talked about in the last tutorials.
|
||||
##
|
||||
## We will:
|
||||
## - Discover peers using the Discovery Manager
|
||||
## - Use GossipSub to find a play mate
|
||||
## - Create a custom protocol to play with him
|
||||
##
|
||||
## While this may look like a daunting project, it's less than 150 lines of code.
|
||||
##
|
||||
## The game will be a simple Tron. We will use [nico](https://github.com/ftsf/nico)
|
||||
## as a game engine. (you need to run `nimble install nico` to have it available)
|
||||
##
|
||||
## 
|
||||
##
|
||||
## We will start by importing our dependencies and creating our types
|
||||
import os
|
||||
import nico, chronos, stew/byteutils, stew/endians2
|
||||
import libp2p
|
||||
import libp2p/protocols/rendezvous
|
||||
import libp2p/discovery/rendezvousinterface
|
||||
import libp2p/discovery/discoverymngr
|
||||
|
||||
const
|
||||
directions = @[(K_UP, 0, -1), (K_LEFT, -1, 0), (K_DOWN, 0, 1), (K_RIGHT, 1, 0)]
|
||||
mapSize = 32
|
||||
tickPeriod = 0.2
|
||||
|
||||
type
|
||||
Player = ref object
|
||||
x, y: int
|
||||
currentDir, nextDir: int
|
||||
lost: bool
|
||||
color: int
|
||||
|
||||
Game = ref object
|
||||
gameMap: array[mapSize * mapSize, int]
|
||||
tickTime: float
|
||||
localPlayer, remotePlayer: Player
|
||||
peerFound: Future[Connection]
|
||||
hasCandidate: bool
|
||||
tickFinished: Future[int]
|
||||
|
||||
GameProto = ref object of LPProtocol
|
||||
|
||||
proc new(_: type[Game]): Game =
|
||||
# Default state of a game
|
||||
result = Game(
|
||||
tickTime: -3.0, # 3 seconds of "warm-up" time
|
||||
localPlayer: Player(x: 4, y: 16, currentDir: 3, nextDir: 3, color: 8),
|
||||
remotePlayer: Player(x: 27, y: 16, currentDir: 1, nextDir: 1, color: 12),
|
||||
peerFound: newFuture[Connection](),
|
||||
)
|
||||
for pos in 0 .. result.gameMap.high:
|
||||
if pos mod mapSize in [0, mapSize - 1] or pos div mapSize in [0, mapSize - 1]:
|
||||
result.gameMap[pos] = 7
|
||||
|
||||
## ## Game Logic
|
||||
## The networking during the game will work like this:
|
||||
##
|
||||
## * Each player will have `tickPeriod` (0.1) seconds to choose
|
||||
## a direction that he wants to go to (default to current direction)
|
||||
## * After `tickPeriod`, we will send our choosen direction to the peer,
|
||||
## and wait for his direction
|
||||
## * Once we have both direction, we will "tick" the game, and restart the
|
||||
## loop, as long as both player are alive.
|
||||
##
|
||||
## This is a very simplistic scheme, but creating proper networking for
|
||||
## video games is an [art](https://developer.valvesoftware.com/wiki/Latency_Compensating_Methods_in_Client/Server_In-game_Protocol_Design_and_Optimization)
|
||||
##
|
||||
## The main drawback of this scheme is that the more ping you have with
|
||||
## the peer, the slower the game will run. Or invertedly, the less ping you
|
||||
## have, the faster it runs!
|
||||
proc update(g: Game, dt: float32) =
|
||||
# Will be called at each frame of the game.
|
||||
#
|
||||
# Because both Nico and Chronos have a main loop,
|
||||
# they must share the control of the main thread.
|
||||
# This is a hacky way to make this happen
|
||||
waitFor(sleepAsync(1.milliseconds))
|
||||
# Don't do anything if we are still waiting for an opponent
|
||||
if not (g.peerFound.finished()) or isNil(g.tickFinished):
|
||||
return
|
||||
g.tickTime += dt
|
||||
|
||||
# Update the wanted direction, making sure we can't go backward
|
||||
for i in 0 .. directions.high:
|
||||
if i != (g.localPlayer.currentDir + 2 mod 4) and keyp(directions[i][0]):
|
||||
g.localPlayer.nextDir = i
|
||||
|
||||
if g.tickTime > tickPeriod and not g.tickFinished.finished():
|
||||
# We choosen our next direction, let the networking know
|
||||
g.localPlayer.currentDir = g.localPlayer.nextDir
|
||||
g.tickFinished.complete(g.localPlayer.currentDir)
|
||||
|
||||
proc tick(g: Game, p: Player) =
|
||||
# Move player and check if he lost
|
||||
p.x += directions[p.currentDir][1]
|
||||
p.y += directions[p.currentDir][2]
|
||||
if g.gameMap[p.y * mapSize + p.x] != 0:
|
||||
p.lost = true
|
||||
g.gameMap[p.y * mapSize + p.x] = p.color
|
||||
|
||||
proc mainLoop(g: Game, peer: Connection) {.async.} =
|
||||
while not (g.localPlayer.lost or g.remotePlayer.lost):
|
||||
if g.tickTime > 0.0:
|
||||
g.tickTime = 0
|
||||
g.tickFinished = newFuture[int]()
|
||||
|
||||
# Wait for a choosen direction
|
||||
let dir = await g.tickFinished
|
||||
# Send it
|
||||
await peer.writeLp(toBytes(uint32(dir)))
|
||||
|
||||
# Get the one from the peer
|
||||
g.remotePlayer.currentDir = int uint32.fromBytes(await peer.readLp(8))
|
||||
# Tick the players & restart
|
||||
g.tick(g.remotePlayer)
|
||||
g.tick(g.localPlayer)
|
||||
|
||||
## We'll draw the map & put some texts when necessary:
|
||||
proc draw(g: Game) =
|
||||
for pos, color in g.gameMap:
|
||||
setColor(color)
|
||||
boxFill(pos mod 32 * 4, pos div 32 * 4, 4, 4)
|
||||
let text =
|
||||
if not (g.peerFound.finished()):
|
||||
"Matchmaking.."
|
||||
elif g.tickTime < -1.5:
|
||||
"Welcome to Etron"
|
||||
elif g.tickTime < 0.0:
|
||||
"- " & $(int(abs(g.tickTime) / 0.5) + 1) & " -"
|
||||
elif g.remotePlayer.lost and g.localPlayer.lost:
|
||||
"DEUCE"
|
||||
elif g.localPlayer.lost:
|
||||
"YOU LOOSE"
|
||||
elif g.remotePlayer.lost:
|
||||
"YOU WON"
|
||||
else:
|
||||
""
|
||||
printc(text, screenWidth div 2, screenHeight div 2)
|
||||
|
||||
## ## Matchmaking
|
||||
## To find an opponent, we will broadcast our address on a
|
||||
## GossipSub topic, and wait for someone to connect to us.
|
||||
## We will also listen to that topic, and connect to anyone
|
||||
## broadcasting his address.
|
||||
##
|
||||
## If we are looking for a game, we'll send `ok` to let the
|
||||
## peer know that we are available, check that he is also available,
|
||||
## and launch the game.
|
||||
proc new(T: typedesc[GameProto], g: Game): T =
|
||||
proc handle(conn: Connection, proto: string) {.async: (raises: [CancelledError]).} =
|
||||
defer:
|
||||
await conn.closeWithEof()
|
||||
try:
|
||||
if g.peerFound.finished or g.hasCandidate:
|
||||
await conn.close()
|
||||
return
|
||||
g.hasCandidate = true
|
||||
await conn.writeLp("ok")
|
||||
if "ok" != string.fromBytes(await conn.readLp(1024)):
|
||||
g.hasCandidate = false
|
||||
return
|
||||
g.peerFound.complete(conn)
|
||||
# The handler of a protocol must wait for the stream to
|
||||
# be finished before returning
|
||||
await conn.join()
|
||||
except CancelledError as e:
|
||||
raise e
|
||||
except CatchableError as e:
|
||||
echo "exception in handler", e.msg
|
||||
|
||||
return T.new(codecs = @["/tron/1.0.0"], handler = handle)
|
||||
|
||||
proc networking(g: Game) {.async.} =
|
||||
# Create our switch, similar to the GossipSub example and
|
||||
# the Discovery examples combined
|
||||
let
|
||||
rdv = RendezVous.new()
|
||||
switch = SwitchBuilder
|
||||
.new()
|
||||
.withRng(newRng())
|
||||
.withAddresses(@[MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet()])
|
||||
.withTcpTransport()
|
||||
.withYamux()
|
||||
.withNoise()
|
||||
.withRendezVous(rdv)
|
||||
.build()
|
||||
dm = DiscoveryManager()
|
||||
gameProto = GameProto.new(g)
|
||||
gossip = GossipSub.init(switch = switch, triggerSelf = false)
|
||||
dm.add(RendezVousInterface.new(rdv))
|
||||
|
||||
switch.mount(gossip)
|
||||
switch.mount(gameProto)
|
||||
|
||||
gossip.subscribe(
|
||||
"/tron/matchmaking",
|
||||
proc(topic: string, data: seq[byte]) {.async.} =
|
||||
# If we are still looking for an opponent,
|
||||
# try to match anyone broadcasting its address
|
||||
if g.peerFound.finished or g.hasCandidate:
|
||||
return
|
||||
g.hasCandidate = true
|
||||
|
||||
try:
|
||||
let
|
||||
(peerId, multiAddress) = parseFullAddress(data).tryGet()
|
||||
stream = await switch.dial(peerId, @[multiAddress], gameProto.codec)
|
||||
|
||||
await stream.writeLp("ok")
|
||||
if (await stream.readLp(10)) != "ok".toBytes:
|
||||
g.hasCandidate = false
|
||||
return
|
||||
g.peerFound.complete(stream)
|
||||
# We are "player 2"
|
||||
swap(g.localPlayer, g.remotePlayer)
|
||||
except CatchableError as exc:
|
||||
discard,
|
||||
)
|
||||
|
||||
await switch.start()
|
||||
defer:
|
||||
await switch.stop()
|
||||
|
||||
# As explained in the last tutorial, we need a bootnode to be able
|
||||
# to find peers. We could use any libp2p running rendezvous (or any
|
||||
# node running tron). We will take it's MultiAddress from the command
|
||||
# line parameters
|
||||
if paramCount() > 0:
|
||||
let (peerId, multiAddress) = paramStr(1).parseFullAddress().tryGet()
|
||||
await switch.connect(peerId, @[multiAddress])
|
||||
else:
|
||||
echo "No bootnode provided, listening on: ", switch.peerInfo.fullAddrs.tryGet()
|
||||
|
||||
# Discover peers from the bootnode, and connect to them
|
||||
dm.advertise(RdvNamespace("tron"))
|
||||
let discoveryQuery = dm.request(RdvNamespace("tron"))
|
||||
discoveryQuery.forEach:
|
||||
try:
|
||||
await switch.connect(peer[PeerId], peer.getAll(MultiAddress))
|
||||
except CatchableError as exc:
|
||||
echo "Failed to dial a peer: ", exc.msg
|
||||
|
||||
# We will try to publish our address multiple times, in case
|
||||
# it takes time to establish connections with other GossipSub peers
|
||||
var published = false
|
||||
while not published:
|
||||
await sleepAsync(500.milliseconds)
|
||||
for fullAddr in switch.peerInfo.fullAddrs.tryGet():
|
||||
if (await gossip.publish("/tron/matchmaking", fullAddr.bytes)) == 0:
|
||||
published = false
|
||||
break
|
||||
published = true
|
||||
|
||||
discoveryQuery.stop()
|
||||
|
||||
# We now wait for someone to connect to us (or for us to connect to someone)
|
||||
let peerConn = await g.peerFound
|
||||
defer:
|
||||
await peerConn.closeWithEof()
|
||||
|
||||
await g.mainLoop(peerConn)
|
||||
|
||||
let
|
||||
game = Game.new()
|
||||
netFut = networking(game)
|
||||
nico.init("Status", "Tron")
|
||||
nico.createWindow("Tron", mapSize * 4, mapSize * 4, 4, false)
|
||||
nico.run(
|
||||
proc() =
|
||||
discard,
|
||||
proc(dt: float32) =
|
||||
game.update(dt),
|
||||
proc() =
|
||||
game.draw(),
|
||||
)
|
||||
waitFor(netFut.cancelAndWait())
|
||||
|
||||
## And that's it! If you want to run this code locally, the simplest way is to use the
|
||||
## first node as a boot node for the second one. But you can also use any rendezvous node
|
||||
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1752620740,
|
||||
"narHash": "sha256-f3pO+9lg66mV7IMmmIqG4PL3223TYMlnlw+pnpelbss=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "32a4e87942101f1c9f9865e04dc3ddb175f5f32e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-25.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
34
flake.nix
Normal file
34
flake.nix
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
description = "nim-libp2p dev shell flake";
|
||||
|
||||
nixConfig = {
|
||||
extra-substituters = [ "https://nix-cache.status.im/" ];
|
||||
extra-trusted-public-keys = [ "nix-cache.status.im-1:x/93lOfLU+duPplwMSBR+OlY4+mo+dCN7n0mr4oPwgY=" ];
|
||||
};
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let
|
||||
stableSystems = [
|
||||
"x86_64-linux" "aarch64-linux" "armv7a-linux"
|
||||
"x86_64-darwin" "aarch64-darwin"
|
||||
"x86_64-windows"
|
||||
];
|
||||
forEach = nixpkgs.lib.genAttrs;
|
||||
forAllSystems = forEach stableSystems;
|
||||
pkgsFor = forEach stableSystems (
|
||||
system: import nixpkgs { inherit system; }
|
||||
);
|
||||
in rec {
|
||||
devShells = forAllSystems (system: {
|
||||
default = pkgsFor.${system}.mkShell {
|
||||
nativeBuildInputs = with pkgsFor.${system}; [
|
||||
nim-2_2 nimble openssl.dev
|
||||
];
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
5
funding.json
Normal file
5
funding.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"opRetro": {
|
||||
"projectId": "0xc9561ba3e4eca5483b40f8b1a254a73c91fefe4f8aee32dc20c0d96dcf33fe80"
|
||||
}
|
||||
}
|
||||
19
interop/hole-punching/Dockerfile
Normal file
19
interop/hole-punching/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
# syntax=docker/dockerfile:1.5-labs
|
||||
FROM nimlang/nim:latest as builder
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
COPY .pinned libp2p.nimble nim-libp2p/
|
||||
|
||||
RUN --mount=type=cache,target=/var/cache/apt apt-get update && apt-get install -y libssl-dev
|
||||
|
||||
RUN cd nim-libp2p && nimble install_pinned && nimble install redis -y
|
||||
|
||||
COPY . nim-libp2p/
|
||||
|
||||
RUN cd nim-libp2p && nim c --skipParentCfg --NimblePath:./nimbledeps/pkgs2 --mm:refc -d:chronicles_log_level=DEBUG -d:chronicles_default_output_device=stderr -d:release --threads:off --skipProjCfg -o:hole-punching-tests ./interop/hole-punching/hole_punching.nim
|
||||
|
||||
FROM --platform=linux/amd64 debian:bullseye-slim
|
||||
RUN --mount=type=cache,target=/var/cache/apt apt-get update && apt-get install -y dnsutils jq curl tcpdump iproute2 libssl-dev
|
||||
COPY --from=builder /workspace/nim-libp2p/hole-punching-tests /usr/bin/hole-punch-client
|
||||
ENV RUST_BACKTRACE=1
|
||||
138
interop/hole-punching/hole_punching.nim
Normal file
138
interop/hole-punching/hole_punching.nim
Normal file
@@ -0,0 +1,138 @@
|
||||
import std/[os, options, strformat, sequtils]
|
||||
import redis
|
||||
import chronos, chronicles
|
||||
import
|
||||
../../libp2p/[
|
||||
builders,
|
||||
switch,
|
||||
multicodec,
|
||||
observedaddrmanager,
|
||||
services/hpservice,
|
||||
services/autorelayservice,
|
||||
protocols/connectivity/autonat/client as aclient,
|
||||
protocols/connectivity/relay/client as rclient,
|
||||
protocols/connectivity/relay/relay,
|
||||
protocols/connectivity/autonat/service,
|
||||
protocols/ping,
|
||||
]
|
||||
import ../../tests/[stubs/autonatclientstub, errorhelpers]
|
||||
|
||||
logScope:
|
||||
topics = "hp interop node"
|
||||
|
||||
proc createSwitch(r: Relay = nil, hpService: Service = nil): Switch =
|
||||
let rng = newRng()
|
||||
var builder = SwitchBuilder
|
||||
.new()
|
||||
.withRng(rng)
|
||||
.withAddresses(@[MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet()])
|
||||
.withObservedAddrManager(ObservedAddrManager.new(maxSize = 1, minCount = 1))
|
||||
.withTcpTransport({ServerFlags.TcpNoDelay})
|
||||
.withYamux()
|
||||
.withAutonat()
|
||||
.withNoise()
|
||||
|
||||
if hpService != nil:
|
||||
builder = builder.withServices(@[hpService])
|
||||
|
||||
if r != nil:
|
||||
builder = builder.withCircuitRelay(r)
|
||||
|
||||
let s = builder.build()
|
||||
s.mount(Ping.new(rng = rng))
|
||||
return s
|
||||
|
||||
proc main() {.async.} =
|
||||
let relayClient = RelayClient.new()
|
||||
let autoRelayService = AutoRelayService.new(1, relayClient, nil, newRng())
|
||||
let autonatClientStub = AutonatClientStub.new(expectedDials = 1)
|
||||
autonatClientStub.answer = NotReachable
|
||||
let autonatService = AutonatService.new(autonatClientStub, newRng(), maxQueueSize = 1)
|
||||
let hpservice = HPService.new(autonatService, autoRelayService)
|
||||
|
||||
let
|
||||
isListener = getEnv("MODE") == "listen"
|
||||
switch = createSwitch(relayClient, hpservice)
|
||||
auxSwitch = createSwitch()
|
||||
redisClient = open("redis", 6379.Port)
|
||||
|
||||
debug "Connected to redis"
|
||||
|
||||
await switch.start()
|
||||
await auxSwitch.start()
|
||||
|
||||
let relayAddr =
|
||||
try:
|
||||
redisClient.bLPop(@["RELAY_TCP_ADDRESS"], 0)
|
||||
except Exception as e:
|
||||
raise newException(CatchableError, e.msg)
|
||||
|
||||
debug "All relay addresses", relayAddr
|
||||
|
||||
# This is necessary to make the autonat service work. It will ask this peer for our reachability which the autonat
|
||||
# client stub will answer NotReachable.
|
||||
await switch.connect(auxSwitch.peerInfo.peerId, auxSwitch.peerInfo.addrs)
|
||||
|
||||
# Wait for autonat to be NotReachable
|
||||
while autonatService.networkReachability != NetworkReachability.NotReachable:
|
||||
await sleepAsync(100.milliseconds)
|
||||
|
||||
# This will trigger the autonat relay service to make a reservation.
|
||||
let relayMA = MultiAddress.init(relayAddr[1]).tryGet()
|
||||
|
||||
try:
|
||||
debug "Dialing relay...", relayMA
|
||||
let relayId = await switch.connect(relayMA).wait(30.seconds)
|
||||
debug "Connected to relay", relayId
|
||||
except AsyncTimeoutError as e:
|
||||
raise newException(CatchableError, "Connection to relay timed out: " & e.msg, e)
|
||||
|
||||
# Wait for our relay address to be published
|
||||
while not switch.peerInfo.addrs.anyIt(it.contains(multiCodec("p2p-circuit")).tryGet()):
|
||||
await sleepAsync(100.milliseconds)
|
||||
|
||||
if isListener:
|
||||
let listenerPeerId = switch.peerInfo.peerId
|
||||
discard redisClient.rPush("LISTEN_CLIENT_PEER_ID", $listenerPeerId)
|
||||
debug "Pushed listener client peer id to redis", listenerPeerId
|
||||
|
||||
# Nothing to do anymore, wait to be killed
|
||||
await sleepAsync(2.minutes)
|
||||
else:
|
||||
let listenerId =
|
||||
try:
|
||||
PeerId.init(redisClient.bLPop(@["LISTEN_CLIENT_PEER_ID"], 0)[1]).tryGet()
|
||||
except Exception as e:
|
||||
raise newException(CatchableError, "Exception init peer: " & e.msg, e)
|
||||
|
||||
debug "Got listener peer id", listenerId
|
||||
let listenerRelayAddr = MultiAddress.init($relayMA & "/p2p-circuit").tryGet()
|
||||
|
||||
debug "Dialing listener relay address", listenerRelayAddr
|
||||
await switch.connect(listenerId, @[listenerRelayAddr])
|
||||
|
||||
# wait for hole-punching to complete in the background
|
||||
await sleepAsync(5000.milliseconds)
|
||||
|
||||
let conn = switch.connManager.selectMuxer(listenerId).connection
|
||||
let channel = await switch.dial(listenerId, @[listenerRelayAddr], PingCodec)
|
||||
let delay = await Ping.new().ping(channel)
|
||||
await allFuturesThrowing(
|
||||
channel.close(), conn.close(), switch.stop(), auxSwitch.stop()
|
||||
)
|
||||
echo &"""{{"rtt_to_holepunched_peer_millis":{delay.millis}}}"""
|
||||
|
||||
try:
|
||||
proc mainAsync(): Future[string] {.async.} =
|
||||
# mainAsync wraps main and returns some value, as otherwise
|
||||
# 'waitFor(fut)' has no type (or is ambiguous)
|
||||
await main()
|
||||
return "done"
|
||||
|
||||
discard waitFor(mainAsync().wait(4.minutes))
|
||||
except AsyncTimeoutError as e:
|
||||
error "Program execution timed out", description = e.msg
|
||||
quit(-1)
|
||||
except CatchableError as e:
|
||||
error "Unexpected error", description = e.msg
|
||||
quit(-1)
|
||||
7
interop/hole-punching/version.json
Normal file
7
interop/hole-punching/version.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"id": "nim-libp2p-head",
|
||||
"containerImageID": "nim-libp2p-head",
|
||||
"transports": [
|
||||
"tcp"
|
||||
]
|
||||
}
|
||||
18
interop/transport/Dockerfile
Normal file
18
interop/transport/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
# syntax=docker/dockerfile:1.5-labs
|
||||
FROM nimlang/nim:latest as builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY .pinned libp2p.nimble nim-libp2p/
|
||||
|
||||
RUN --mount=type=cache,target=/var/cache/apt apt-get update && apt-get install -y libssl-dev
|
||||
|
||||
RUN cd nim-libp2p && nimble install_pinned && nimble install redis -y
|
||||
|
||||
COPY . nim-libp2p/
|
||||
|
||||
RUN \
|
||||
cd nim-libp2p && \
|
||||
nim c --skipProjCfg --skipParentCfg --NimblePath:./nimbledeps/pkgs2 -p:nim-libp2p --mm:refc -d:libp2p_quic_support -d:chronicles_log_level=WARN -d:chronicles_default_output_device=stderr --threads:off ./interop/transport/main.nim
|
||||
|
||||
ENTRYPOINT ["/app/nim-libp2p/interop/transport/main"]
|
||||
113
interop/transport/main.nim
Normal file
113
interop/transport/main.nim
Normal file
@@ -0,0 +1,113 @@
|
||||
import std/[os, strutils, sequtils], chronos, redis, serialization, json_serialization
|
||||
import ../../libp2p/[builders, protocols/ping, transports/wstransport]
|
||||
|
||||
type ResultJson = object
|
||||
handshakePlusOneRTTMillis: float
|
||||
pingRTTMilllis: float
|
||||
|
||||
let testTimeout =
|
||||
try:
|
||||
seconds(parseInt(getEnv("test_timeout_seconds")))
|
||||
except CatchableError:
|
||||
3.minutes
|
||||
|
||||
proc main() {.async.} =
|
||||
let
|
||||
transport = getEnv("transport")
|
||||
muxer = getEnv("muxer")
|
||||
secureChannel = getEnv("security")
|
||||
isDialer = getEnv("is_dialer") == "true"
|
||||
envIp = getEnv("ip", "0.0.0.0")
|
||||
ip =
|
||||
# nim-libp2p doesn't do snazzy ip expansion
|
||||
if envIp == "0.0.0.0":
|
||||
block:
|
||||
let addresses =
|
||||
getInterfaces().filterIt(it.name == "eth0").mapIt(it.addresses)
|
||||
if addresses.len < 1 or addresses[0].len < 1:
|
||||
quit "Can't find local ip!"
|
||||
($addresses[0][0].host).split(":")[0]
|
||||
else:
|
||||
envIp
|
||||
redisAddr = getEnv("redis_addr", "redis:6379").split(":")
|
||||
|
||||
# using synchronous redis because async redis is based on
|
||||
# asyncdispatch instead of chronos
|
||||
redisClient = open(redisAddr[0], Port(parseInt(redisAddr[1])))
|
||||
|
||||
switchBuilder = SwitchBuilder.new()
|
||||
|
||||
case transport
|
||||
of "tcp":
|
||||
discard switchBuilder.withTcpTransport().withAddress(
|
||||
MultiAddress.init("/ip4/" & ip & "/tcp/0").tryGet()
|
||||
)
|
||||
of "quic-v1":
|
||||
discard switchBuilder.withQuicTransport().withAddress(
|
||||
MultiAddress.init("/ip4/" & ip & "/udp/0/quic-v1").tryGet()
|
||||
)
|
||||
of "ws":
|
||||
discard switchBuilder.withWsTransport().withAddress(
|
||||
MultiAddress.init("/ip4/" & ip & "/tcp/0/ws").tryGet()
|
||||
)
|
||||
else:
|
||||
doAssert false
|
||||
|
||||
case secureChannel
|
||||
of "noise":
|
||||
discard switchBuilder.withNoise()
|
||||
|
||||
case muxer
|
||||
of "yamux":
|
||||
discard switchBuilder.withYamux()
|
||||
of "mplex":
|
||||
discard switchBuilder.withMplex()
|
||||
|
||||
let
|
||||
rng = newRng()
|
||||
switch = switchBuilder.withRng(rng).build()
|
||||
pingProtocol = Ping.new(rng = rng)
|
||||
switch.mount(pingProtocol)
|
||||
await switch.start()
|
||||
defer:
|
||||
await switch.stop()
|
||||
|
||||
if not isDialer:
|
||||
discard redisClient.rPush("listenerAddr", $switch.peerInfo.fullAddrs.tryGet()[0])
|
||||
await sleepAsync(100.hours) # will get cancelled
|
||||
else:
|
||||
let listenerAddr =
|
||||
try:
|
||||
redisClient.bLPop(@["listenerAddr"], testTimeout.seconds.int)[1]
|
||||
except Exception as e:
|
||||
raise newException(CatchableError, "Exception calling bLPop: " & e.msg, e)
|
||||
let
|
||||
remoteAddr = MultiAddress.init(listenerAddr).tryGet()
|
||||
dialingStart = Moment.now()
|
||||
remotePeerId = await switch.connect(remoteAddr)
|
||||
stream = await switch.dial(remotePeerId, PingCodec)
|
||||
pingDelay = await pingProtocol.ping(stream)
|
||||
totalDelay = Moment.now() - dialingStart
|
||||
await stream.close()
|
||||
|
||||
echo Json.encode(
|
||||
ResultJson(
|
||||
handshakePlusOneRTTMillis: float(totalDelay.milliseconds),
|
||||
pingRTTMilllis: float(pingDelay.milliseconds),
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
proc mainAsync(): Future[string] {.async.} =
|
||||
# mainAsync wraps main and returns some value, as otherwise
|
||||
# 'waitFor(fut)' has no type (or is ambiguous)
|
||||
await main()
|
||||
return "done"
|
||||
|
||||
discard waitFor(mainAsync().wait(testTimeout))
|
||||
except AsyncTimeoutError as e:
|
||||
error "Program execution timed out", description = e.msg
|
||||
quit(-1)
|
||||
except CatchableError as e:
|
||||
error "Unexpected error", description = e.msg
|
||||
quit(-1)
|
||||
16
interop/transport/version.json
Normal file
16
interop/transport/version.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "nim-libp2p-head",
|
||||
"containerImageID": "nim-libp2p-head",
|
||||
"transports": [
|
||||
"tcp",
|
||||
"ws",
|
||||
"quic-v1"
|
||||
],
|
||||
"secureChannels": [
|
||||
"noise"
|
||||
],
|
||||
"muxers": [
|
||||
"mplex",
|
||||
"yamux"
|
||||
]
|
||||
}
|
||||
110
libp2p.nim
110
libp2p.nim
@@ -1,40 +1,76 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
import
|
||||
libp2p/[protobuf/minprotobuf,
|
||||
muxers/muxer,
|
||||
muxers/mplex/mplex,
|
||||
stream/lpstream,
|
||||
stream/bufferstream,
|
||||
stream/connection,
|
||||
transports/transport,
|
||||
transports/tcptransport,
|
||||
protocols/secure/noise,
|
||||
cid,
|
||||
multihash,
|
||||
multibase,
|
||||
multicodec,
|
||||
errors,
|
||||
switch,
|
||||
peerid,
|
||||
peerinfo,
|
||||
multiaddress,
|
||||
builders,
|
||||
crypto/crypto,
|
||||
protocols/pubsub]
|
||||
when defined(nimdoc):
|
||||
## Welcome to the nim-libp2p reference!
|
||||
##
|
||||
## On the left, you'll find a switch that allows you to see private
|
||||
## procedures. By default, you'll only see the public one (marked with `{.public.}`)
|
||||
##
|
||||
## The difference between public and private procedures is that public procedure
|
||||
## stay backward compatible during the Major version, whereas private ones can
|
||||
## change at each new Minor version.
|
||||
##
|
||||
## If you're new to nim-libp2p, you can find a tutorial `here<https://vacp2p.github.io/nim-libp2p/docs/tutorial_1_connect/>`_
|
||||
## that can help you get started.
|
||||
|
||||
import bearssl
|
||||
# Import stuff for doc
|
||||
import
|
||||
libp2p/[
|
||||
protobuf/minprotobuf,
|
||||
switch,
|
||||
stream/lpstream,
|
||||
builders,
|
||||
transports/tcptransport,
|
||||
transports/wstransport,
|
||||
protocols/ping,
|
||||
protocols/pubsub,
|
||||
peerid,
|
||||
peerinfo,
|
||||
peerstore,
|
||||
multiaddress,
|
||||
]
|
||||
|
||||
export
|
||||
minprotobuf, switch, peerid, peerinfo,
|
||||
connection, multiaddress, crypto, lpstream,
|
||||
bufferstream, bearssl, muxer, mplex, transport,
|
||||
tcptransport, noise, errors, cid, multihash,
|
||||
multicodec, builders, pubsub
|
||||
proc dummyPrivateProc*() =
|
||||
## A private proc example
|
||||
discard
|
||||
|
||||
else:
|
||||
import
|
||||
libp2p/[
|
||||
protobuf/minprotobuf,
|
||||
muxers/muxer,
|
||||
muxers/mplex/mplex,
|
||||
stream/lpstream,
|
||||
stream/bufferstream,
|
||||
stream/connection,
|
||||
transports/transport,
|
||||
transports/tcptransport,
|
||||
protocols/secure/noise,
|
||||
cid,
|
||||
multihash,
|
||||
multicodec,
|
||||
errors,
|
||||
switch,
|
||||
peerid,
|
||||
peerinfo,
|
||||
multiaddress,
|
||||
builders,
|
||||
crypto/crypto,
|
||||
protocols/pubsub,
|
||||
]
|
||||
|
||||
export
|
||||
minprotobuf, switch, peerid, peerinfo, connection, multiaddress, crypto, lpstream,
|
||||
bufferstream, muxer, mplex, transport, tcptransport, noise, errors, cid, multihash,
|
||||
multicodec, builders, pubsub
|
||||
|
||||
when defined(libp2p_quic_support):
|
||||
import libp2p/transports/quictransport
|
||||
export quictransport
|
||||
|
||||
150
libp2p.nimble
150
libp2p.nimble
@@ -1,57 +1,51 @@
|
||||
mode = ScriptMode.Verbose
|
||||
|
||||
packageName = "libp2p"
|
||||
version = "0.0.2"
|
||||
author = "Status Research & Development GmbH"
|
||||
description = "LibP2P implementation"
|
||||
license = "MIT"
|
||||
skipDirs = @["tests", "examples", "Nim", "tools", "scripts", "docs"]
|
||||
packageName = "libp2p"
|
||||
version = "1.12.0"
|
||||
author = "Status Research & Development GmbH"
|
||||
description = "LibP2P implementation"
|
||||
license = "MIT"
|
||||
skipDirs = @["tests", "examples", "Nim", "tools", "scripts", "docs"]
|
||||
|
||||
requires "nim >= 1.2.0",
|
||||
"nimcrypto >= 0.4.1",
|
||||
"dnsclient >= 0.1.2",
|
||||
"bearssl >= 0.1.4",
|
||||
"chronicles >= 0.10.2",
|
||||
"chronos >= 3.0.6",
|
||||
"metrics",
|
||||
"secp256k1",
|
||||
"stew#head",
|
||||
"websock"
|
||||
requires "nim >= 2.0.0",
|
||||
"nimcrypto >= 0.6.0 & < 0.7.0", "dnsclient >= 0.3.0 & < 0.4.0", "bearssl >= 0.2.5",
|
||||
"chronicles >= 0.11.0 & < 0.12.0", "chronos >= 4.0.4", "metrics", "secp256k1",
|
||||
"stew >= 0.4.0", "websock >= 0.2.0", "unittest2", "blscurve >= 0.0.1", "results",
|
||||
"quic >= 0.2.10",
|
||||
"https://github.com/vacp2p/nim-jwt.git#18f8378de52b241f321c1f9ea905456e89b95c6f"
|
||||
|
||||
const nimflags =
|
||||
"--verbosity:0 --hints:off " &
|
||||
"--warning[CaseTransition]:off --warning[ObservableStores]:off " &
|
||||
"--warning[LockLevel]:off " &
|
||||
"-d:chronosStrictException " &
|
||||
"--styleCheck:usages --styleCheck:hint "
|
||||
let nimc = getEnv("NIMC", "nim") # Which nim compiler to use
|
||||
let lang = getEnv("NIMLANG", "c") # Which backend (c/cpp/js)
|
||||
let flags = getEnv("NIMFLAGS", "") # Extra flags for the compiler
|
||||
let verbose = getEnv("V", "") notin ["", "0"]
|
||||
|
||||
proc runTest(filename: string, verify: bool = true, sign: bool = true,
|
||||
moreoptions: string = "") =
|
||||
var excstr = "nim c --opt:speed -d:debug -d:libp2p_agents_metrics -d:libp2p_protobuf_metrics -d:libp2p_network_protocols_metrics "
|
||||
excstr.add(" " & getEnv("NIMFLAGS") & " ")
|
||||
excstr.add(" " & nimflags & " ")
|
||||
excstr.add(" -d:libp2p_pubsub_sign=" & $sign)
|
||||
excstr.add(" -d:libp2p_pubsub_verify=" & $verify)
|
||||
let cfg =
|
||||
" --styleCheck:usages --styleCheck:error" &
|
||||
(if verbose: "" else: " --verbosity:0 --hints:off") & " --skipUserCfg -f" &
|
||||
" --threads:on --opt:speed"
|
||||
|
||||
import hashes, strutils
|
||||
|
||||
proc runTest(filename: string, moreoptions: string = "") =
|
||||
var excstr = nimc & " " & lang & " -d:debug " & cfg & " " & flags
|
||||
excstr.add(" " & moreoptions & " ")
|
||||
if verify and sign:
|
||||
# build it with TRACE and JSON logs
|
||||
exec excstr & " -d:chronicles_log_level=TRACE -d:chronicles_sinks:json" & " tests/" & filename
|
||||
# build it again, to run it with less verbose logs
|
||||
exec excstr & " -d:chronicles_log_level=INFO -r" & " tests/" & filename
|
||||
if getEnv("CICOV").len > 0:
|
||||
excstr &= " --nimcache:nimcache/" & filename & "-" & $excstr.hash
|
||||
exec excstr & " -r -d:libp2p_quic_support -d:libp2p_autotls_support tests/" & filename
|
||||
rmFile "tests/" & filename.toExe
|
||||
|
||||
proc buildSample(filename: string, run = false) =
|
||||
var excstr = "nim c --opt:speed --threads:on -d:debug "
|
||||
excstr.add(" " & nimflags & " ")
|
||||
proc buildSample(filename: string, run = false, extraFlags = "") =
|
||||
var excstr = nimc & " " & lang & " " & cfg & " " & flags & " -p:. " & extraFlags
|
||||
excstr.add(" examples/" & filename)
|
||||
exec excstr
|
||||
if run:
|
||||
exec "./examples/" & filename.toExe
|
||||
rmFile "examples/" & filename.toExe
|
||||
|
||||
proc buildTutorial(filename: string) =
|
||||
discard gorge "cat " & filename & " | nim c -r --hints:off tools/markdown_runner.nim | " &
|
||||
" nim " & nimflags & " c -"
|
||||
proc tutorialToMd(filename: string) =
|
||||
let markdown = gorge "cat " & filename & " | " & nimc & " " & lang &
|
||||
" -r --verbosity:0 --hints:off tools/markdown_builder.nim "
|
||||
writeFile(filename.replace(".nim", ".md"), markdown)
|
||||
|
||||
task testnative, "Runs libp2p native tests":
|
||||
runTest("testnative")
|
||||
@@ -63,44 +57,35 @@ task testinterop, "Runs interop tests":
|
||||
runTest("testinterop")
|
||||
|
||||
task testpubsub, "Runs pubsub tests":
|
||||
runTest("pubsub/testgossipinternal", sign = false, verify = false, moreoptions = "-d:pubsub_internal_testing")
|
||||
runTest("pubsub/testpubsub")
|
||||
runTest("pubsub/testpubsub", sign = false, verify = false)
|
||||
runTest("pubsub/testpubsub", sign = false, verify = false, moreoptions = "-d:libp2p_pubsub_anonymize=true")
|
||||
|
||||
task testpubsub_slim, "Runs pubsub tests":
|
||||
runTest("pubsub/testgossipinternal", sign = false, verify = false, moreoptions = "-d:pubsub_internal_testing")
|
||||
runTest("pubsub/testpubsub")
|
||||
runTest("pubsub/testpubsub", "-d:libp2p_gossipsub_1_4")
|
||||
|
||||
task testfilter, "Run PKI filter test":
|
||||
runTest("testpkifilter",
|
||||
moreoptions = "-d:libp2p_pki_schemes=\"secp256k1\"")
|
||||
runTest("testpkifilter",
|
||||
moreoptions = "-d:libp2p_pki_schemes=\"secp256k1;ed25519\"")
|
||||
runTest("testpkifilter",
|
||||
moreoptions = "-d:libp2p_pki_schemes=\"secp256k1;ed25519;ecnist\"")
|
||||
runTest("testpkifilter",
|
||||
moreoptions = "-d:libp2p_pki_schemes=")
|
||||
runTest("testpkifilter")
|
||||
runTest("testpkifilter", moreoptions = "-d:libp2p_pki_schemes=")
|
||||
|
||||
task testintegration, "Runs integraion tests":
|
||||
runTest("testintegration")
|
||||
|
||||
task test, "Runs the test suite":
|
||||
exec "nimble testnative"
|
||||
exec "nimble testpubsub"
|
||||
exec "nimble testdaemon"
|
||||
exec "nimble testinterop"
|
||||
runTest("testall")
|
||||
exec "nimble testfilter"
|
||||
exec "nimble examples_build"
|
||||
|
||||
task test_slim, "Runs the (slimmed down) test suite":
|
||||
exec "nimble testnative"
|
||||
exec "nimble testpubsub_slim"
|
||||
exec "nimble testfilter"
|
||||
exec "nimble examples_build"
|
||||
task website, "Build the website":
|
||||
tutorialToMd("examples/tutorial_1_connect.nim")
|
||||
tutorialToMd("examples/tutorial_2_customproto.nim")
|
||||
tutorialToMd("examples/tutorial_3_protobuf.nim")
|
||||
tutorialToMd("examples/tutorial_4_gossipsub.nim")
|
||||
tutorialToMd("examples/tutorial_5_discovery.nim")
|
||||
tutorialToMd("examples/tutorial_6_game.nim")
|
||||
tutorialToMd("examples/circuitrelay.nim")
|
||||
exec "mkdocs build"
|
||||
|
||||
task examples_build, "Build the samples":
|
||||
buildSample("directchat")
|
||||
buildSample("helloworld", true)
|
||||
buildTutorial("examples/tutorial_1_connect.md")
|
||||
buildTutorial("examples/tutorial_2_customproto.md")
|
||||
task examples, "Build and run examples":
|
||||
exec "nimble install -y nimpng"
|
||||
exec "nimble install -y nico --passNim=--skipParentCfg"
|
||||
buildSample("examples_build", false, "--styleCheck:off") # build only
|
||||
|
||||
buildSample("examples_run", true)
|
||||
|
||||
# pin system
|
||||
# while nimble lockfile
|
||||
@@ -111,12 +96,14 @@ task pin, "Create a lockfile":
|
||||
# pinner.nim was originally here
|
||||
# but you can't read output from
|
||||
# a command in a nimscript
|
||||
exec "nim c -r tools/pinner.nim"
|
||||
exec nimc & " c -r tools/pinner.nim"
|
||||
|
||||
import sequtils
|
||||
import os
|
||||
task install_pinned, "Reads the lockfile":
|
||||
let toInstall = readFile(PinFile).splitWhitespace().mapIt((it.split(";", 1)[0], it.split(";", 1)[1]))
|
||||
let toInstall = readFile(PinFile).splitWhitespace().mapIt(
|
||||
(it.split(";", 1)[0], it.split(";", 1)[1])
|
||||
)
|
||||
# [('packageName', 'packageFullUri')]
|
||||
|
||||
rmDir("nimbledeps")
|
||||
@@ -125,9 +112,20 @@ task install_pinned, "Reads the lockfile":
|
||||
|
||||
# Remove the automatically installed deps
|
||||
# (inefficient you say?)
|
||||
let allowedDirectories = toInstall.mapIt(it[0] & "-" & it[1].split('@')[1])
|
||||
for dependency in listDirs("nimbledeps/pkgs"):
|
||||
if dependency.extractFilename notin allowedDirectories:
|
||||
let nimblePkgs =
|
||||
if system.dirExists("nimbledeps/pkgs"): "nimbledeps/pkgs" else: "nimbledeps/pkgs2"
|
||||
for dependency in listDirs(nimblePkgs):
|
||||
let
|
||||
fileName = dependency.extractFilename
|
||||
fileContent = readFile(dependency & "/nimblemeta.json")
|
||||
packageName = fileName.split('-')[0]
|
||||
|
||||
if toInstall.anyIt(
|
||||
it[0] == packageName and (
|
||||
it[1].split('#')[^1] in fileContent or # nimble for nim 2.X
|
||||
fileName.endsWith(it[1].split('#')[^1]) # nimble for nim 1.X
|
||||
)
|
||||
) == false or fileName.split('-')[^1].len < 20: # safegard for nimble for nim 1.X
|
||||
rmDir(dependency)
|
||||
|
||||
task unpin, "Restore global package use":
|
||||
|
||||
533
libp2p/autotls/acme/api.nim
Normal file
533
libp2p/autotls/acme/api.nim
Normal file
@@ -0,0 +1,533 @@
|
||||
import json, uri
|
||||
from times import DateTime, parse
|
||||
import chronos/apps/http/httpclient, results, chronicles
|
||||
|
||||
import ./utils
|
||||
import ../../crypto/crypto
|
||||
import ../../crypto/rsa
|
||||
|
||||
export ACMEError
|
||||
|
||||
logScope:
|
||||
topics = "libp2p acme api"
|
||||
|
||||
const
|
||||
LetsEncryptURL* = "https://acme-v02.api.letsencrypt.org"
|
||||
LetsEncryptURLStaging* = "https://acme-staging-v02.api.letsencrypt.org"
|
||||
Alg = "RS256"
|
||||
DefaultChalCompletedRetries = 10
|
||||
DefaultChalCompletedRetryTime = 1.seconds
|
||||
DefaultFinalizeRetries = 10
|
||||
DefaultFinalizeRetryTime = 1.seconds
|
||||
DefaultRandStringSize = 256
|
||||
ACMEHttpHeaders = [("Content-Type", "application/jose+json")]
|
||||
|
||||
type Authorization* = string
|
||||
type Domain* = string
|
||||
type Kid* = string
|
||||
type Nonce* = string
|
||||
|
||||
type ACMEDirectory* = object
|
||||
newNonce*: string
|
||||
newOrder*: string
|
||||
newAccount*: string
|
||||
|
||||
type ACMEApi* = ref object of RootObj
|
||||
directory: Opt[ACMEDirectory]
|
||||
session: HttpSessionRef
|
||||
acmeServerURL*: Uri
|
||||
|
||||
type HTTPResponse* = object
|
||||
body*: JsonNode
|
||||
headers*: HttpTable
|
||||
|
||||
type JWK = object
|
||||
kty: string
|
||||
n: string
|
||||
e: string
|
||||
|
||||
# whether the request uses Kid or not
|
||||
type ACMERequestType = enum
|
||||
ACMEJwkRequest
|
||||
ACMEKidRequest
|
||||
|
||||
type ACMERequestHeader = object
|
||||
alg: string
|
||||
typ: string
|
||||
nonce: Nonce
|
||||
url: string
|
||||
case kind: ACMERequestType
|
||||
of ACMEJwkRequest:
|
||||
jwk: JWK
|
||||
of ACMEKidRequest:
|
||||
kid: Kid
|
||||
|
||||
type Email = string
|
||||
|
||||
type ACMERegisterRequest* = object
|
||||
termsOfServiceAgreed: bool
|
||||
contact: seq[Email]
|
||||
|
||||
type ACMEAccountStatus = enum
|
||||
valid = "valid"
|
||||
deactivated = "deactivated"
|
||||
revoked = "revoked"
|
||||
|
||||
type ACMERegisterResponseBody = object
|
||||
status*: ACMEAccountStatus
|
||||
|
||||
type ACMERegisterResponse* = object
|
||||
kid*: Kid
|
||||
status*: ACMEAccountStatus
|
||||
|
||||
type ACMEChallengeStatus* {.pure.} = enum
|
||||
PENDING = "pending"
|
||||
PROCESSING = "processing"
|
||||
VALID = "valid"
|
||||
INVALID = "invalid"
|
||||
|
||||
type ACMEOrderStatus* {.pure.} = enum
|
||||
PENDING = "pending"
|
||||
READY = "ready"
|
||||
PROCESSING = "processing"
|
||||
VALID = "valid"
|
||||
INVALID = "invalid"
|
||||
|
||||
type ACMEChallengeType* {.pure.} = enum
|
||||
DNS01 = "dns-01"
|
||||
HTTP01 = "http-01"
|
||||
TLSALPN01 = "tls-alpn-01"
|
||||
|
||||
type ACMEChallengeToken* = string
|
||||
|
||||
type ACMEChallenge* = object
|
||||
url*: string
|
||||
`type`*: ACMEChallengeType
|
||||
status*: ACMEChallengeStatus
|
||||
token*: ACMEChallengeToken
|
||||
|
||||
type ACMEChallengeIdentifier = object
|
||||
`type`: string
|
||||
value: string
|
||||
|
||||
type ACMEChallengeRequest = object
|
||||
identifiers: seq[ACMEChallengeIdentifier]
|
||||
|
||||
type ACMEChallengeResponseBody = object
|
||||
status: ACMEOrderStatus
|
||||
authorizations: seq[Authorization]
|
||||
finalize: string
|
||||
|
||||
type ACMEChallengeResponse* = object
|
||||
status*: ACMEOrderStatus
|
||||
authorizations*: seq[Authorization]
|
||||
finalize*: string
|
||||
order*: string
|
||||
|
||||
type ACMEChallengeResponseWrapper* = object
|
||||
finalize*: string
|
||||
order*: string
|
||||
dns01*: ACMEChallenge
|
||||
|
||||
type ACMEAuthorizationsResponse* = object
|
||||
challenges*: seq[ACMEChallenge]
|
||||
|
||||
type ACMECompletedResponse* = object
|
||||
url: string
|
||||
|
||||
type ACMECheckKind* = enum
|
||||
ACMEOrderCheck
|
||||
ACMEChallengeCheck
|
||||
|
||||
type ACMECheckResponse* = object
|
||||
case kind: ACMECheckKind
|
||||
of ACMEOrderCheck:
|
||||
orderStatus: ACMEOrderStatus
|
||||
of ACMEChallengeCheck:
|
||||
chalStatus: ACMEChallengeStatus
|
||||
retryAfter: Duration
|
||||
|
||||
type ACMEFinalizeResponse* = object
|
||||
status: ACMEOrderStatus
|
||||
|
||||
type ACMEOrderResponse* = object
|
||||
certificate: string
|
||||
expires: string
|
||||
|
||||
type ACMECertificateResponse* = object
|
||||
rawCertificate*: string
|
||||
certificateExpiry*: DateTime
|
||||
|
||||
type ACMECertificate* = object
|
||||
rawCertificate*: string
|
||||
certificateExpiry*: DateTime
|
||||
certKeyPair*: KeyPair
|
||||
|
||||
when defined(libp2p_autotls_support):
|
||||
import options, sequtils, strutils, jwt, bearssl/pem
|
||||
|
||||
template handleError*(msg: string, body: untyped): untyped =
|
||||
try:
|
||||
body
|
||||
except ACMEError as exc:
|
||||
raise exc
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except JsonKindError as exc:
|
||||
raise newException(ACMEError, msg & ": Failed to decode JSON", exc)
|
||||
except ValueError as exc:
|
||||
raise newException(ACMEError, msg & ": Failed to decode JSON", exc)
|
||||
except HttpError as exc:
|
||||
raise newException(ACMEError, msg & ": Failed to connect to ACME server", exc)
|
||||
except CatchableError as exc:
|
||||
raise newException(ACMEError, msg & ": Unexpected error", exc)
|
||||
|
||||
method post*(
|
||||
self: ACMEApi, uri: Uri, payload: string
|
||||
): Future[HTTPResponse] {.
|
||||
async: (raises: [ACMEError, HttpError, CancelledError]), base
|
||||
.}
|
||||
|
||||
method get*(
|
||||
self: ACMEApi, uri: Uri
|
||||
): Future[HTTPResponse] {.
|
||||
async: (raises: [ACMEError, HttpError, CancelledError]), base
|
||||
.}
|
||||
|
||||
proc new*(
|
||||
T: typedesc[ACMEApi], acmeServerURL: Uri = parseUri(LetsEncryptURL)
|
||||
): ACMEApi =
|
||||
let session = HttpSessionRef.new()
|
||||
|
||||
ACMEApi(
|
||||
session: session, directory: Opt.none(ACMEDirectory), acmeServerURL: acmeServerURL
|
||||
)
|
||||
|
||||
proc getDirectory(
|
||||
self: ACMEApi
|
||||
): Future[ACMEDirectory] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
handleError("getDirectory"):
|
||||
self.directory.valueOr:
|
||||
let acmeResponse = await self.get(self.acmeServerURL / "directory")
|
||||
let directory = acmeResponse.body.to(ACMEDirectory)
|
||||
self.directory = Opt.some(directory)
|
||||
directory
|
||||
|
||||
method requestNonce*(
|
||||
self: ACMEApi
|
||||
): Future[Nonce] {.async: (raises: [ACMEError, CancelledError]), base.} =
|
||||
handleError("requestNonce"):
|
||||
let acmeResponse = await self.get(parseUri((await self.getDirectory()).newNonce))
|
||||
Nonce(acmeResponse.headers.keyOrError("Replay-Nonce"))
|
||||
|
||||
# TODO: save n and e in account so we don't have to recalculate every time
|
||||
proc acmeHeader(
|
||||
self: ACMEApi, uri: Uri, key: KeyPair, needsJwk: bool, kid: Opt[Kid]
|
||||
): Future[ACMERequestHeader] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
if not needsJwk and kid.isNone():
|
||||
raise newException(ACMEError, "kid not set")
|
||||
|
||||
if key.pubkey.scheme != PKScheme.RSA or key.seckey.scheme != PKScheme.RSA:
|
||||
raise newException(ACMEError, "Unsupported signing key type")
|
||||
|
||||
let newNonce = await self.requestNonce()
|
||||
if needsJwk:
|
||||
let pubkey = key.pubkey.rsakey
|
||||
let nArray = @(getArray(pubkey.buffer, pubkey.key.n, pubkey.key.nlen))
|
||||
let eArray = @(getArray(pubkey.buffer, pubkey.key.e, pubkey.key.elen))
|
||||
ACMERequestHeader(
|
||||
kind: ACMEJwkRequest,
|
||||
alg: Alg,
|
||||
typ: "JWT",
|
||||
nonce: newNonce,
|
||||
url: $uri,
|
||||
jwk: JWK(kty: "RSA", n: base64UrlEncode(nArray), e: base64UrlEncode(eArray)),
|
||||
)
|
||||
else:
|
||||
ACMERequestHeader(
|
||||
kind: ACMEKidRequest,
|
||||
alg: Alg,
|
||||
typ: "JWT",
|
||||
nonce: newNonce,
|
||||
url: $uri,
|
||||
kid: kid.get(),
|
||||
)
|
||||
|
||||
method post*(
|
||||
self: ACMEApi, uri: Uri, payload: string
|
||||
): Future[HTTPResponse] {.
|
||||
async: (raises: [ACMEError, HttpError, CancelledError]), base
|
||||
.} =
|
||||
let rawResponse = await HttpClientRequestRef
|
||||
.post(self.session, $uri, body = payload, headers = ACMEHttpHeaders)
|
||||
.get()
|
||||
.send()
|
||||
let body = await rawResponse.getResponseBody()
|
||||
HTTPResponse(body: body, headers: rawResponse.headers)
|
||||
|
||||
method get*(
|
||||
self: ACMEApi, uri: Uri
|
||||
): Future[HTTPResponse] {.
|
||||
async: (raises: [ACMEError, HttpError, CancelledError]), base
|
||||
.} =
|
||||
let rawResponse = await HttpClientRequestRef.get(self.session, $uri).get().send()
|
||||
let body = await rawResponse.getResponseBody()
|
||||
HTTPResponse(body: body, headers: rawResponse.headers)
|
||||
|
||||
proc createSignedAcmeRequest(
|
||||
self: ACMEApi,
|
||||
uri: Uri,
|
||||
payload: auto,
|
||||
key: KeyPair,
|
||||
needsJwk: bool = false,
|
||||
kid: Opt[Kid] = Opt.none(Kid),
|
||||
): Future[string] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
if key.pubkey.scheme != PKScheme.RSA or key.seckey.scheme != PKScheme.RSA:
|
||||
raise newException(ACMEError, "Unsupported signing key type")
|
||||
|
||||
let acmeHeader = await self.acmeHeader(uri, key, needsJwk, kid)
|
||||
handleError("createSignedAcmeRequest"):
|
||||
var token = toJWT(%*{"header": acmeHeader, "claims": payload})
|
||||
let derPrivKey = key.seckey.rsakey.getBytes.get
|
||||
let pemPrivKey: string = pemEncode(derPrivKey, "PRIVATE KEY")
|
||||
token.sign(pemPrivKey)
|
||||
$token.toFlattenedJson()
|
||||
|
||||
proc requestRegister*(
|
||||
self: ACMEApi, key: KeyPair
|
||||
): Future[ACMERegisterResponse] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
let registerRequest = ACMERegisterRequest(termsOfServiceAgreed: true)
|
||||
handleError("acmeRegister"):
|
||||
let payload = await self.createSignedAcmeRequest(
|
||||
parseUri((await self.getDirectory()).newAccount),
|
||||
registerRequest,
|
||||
key,
|
||||
needsJwk = true,
|
||||
)
|
||||
let acmeResponse =
|
||||
await self.post(parseUri((await self.getDirectory()).newAccount), payload)
|
||||
let acmeResponseBody = acmeResponse.body.to(ACMERegisterResponseBody)
|
||||
|
||||
ACMERegisterResponse(
|
||||
status: acmeResponseBody.status,
|
||||
kid: acmeResponse.headers.keyOrError("location"),
|
||||
)
|
||||
|
||||
proc requestNewOrder*(
|
||||
self: ACMEApi, domains: seq[Domain], key: KeyPair, kid: Kid
|
||||
): Future[ACMEChallengeResponse] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
# request challenge from ACME server
|
||||
let orderRequest = ACMEChallengeRequest(
|
||||
identifiers: domains.mapIt(ACMEChallengeIdentifier(`type`: "dns", value: it))
|
||||
)
|
||||
handleError("requestNewOrder"):
|
||||
let payload = await self.createSignedAcmeRequest(
|
||||
parseUri((await self.getDirectory()).newOrder),
|
||||
orderRequest,
|
||||
key,
|
||||
kid = Opt.some(kid),
|
||||
)
|
||||
let acmeResponse =
|
||||
await self.post(parseUri((await self.getDirectory()).newOrder), payload)
|
||||
let challengeResponseBody = acmeResponse.body.to(ACMEChallengeResponseBody)
|
||||
if challengeResponseBody.authorizations.len == 0:
|
||||
raise newException(ACMEError, "Authorizations field is empty")
|
||||
ACMEChallengeResponse(
|
||||
status: challengeResponseBody.status,
|
||||
authorizations: challengeResponseBody.authorizations,
|
||||
finalize: challengeResponseBody.finalize,
|
||||
order: acmeResponse.headers.keyOrError("location"),
|
||||
)
|
||||
|
||||
proc requestAuthorizations*(
|
||||
self: ACMEApi, authorizations: seq[Authorization], key: KeyPair, kid: Kid
|
||||
): Future[ACMEAuthorizationsResponse] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
handleError("requestAuthorizations"):
|
||||
doAssert authorizations.len > 0
|
||||
let acmeResponse = await self.get(parseUri(authorizations[0]))
|
||||
acmeResponse.body.to(ACMEAuthorizationsResponse)
|
||||
|
||||
proc requestChallenge*(
|
||||
self: ACMEApi, domains: seq[Domain], key: KeyPair, kid: Kid
|
||||
): Future[ACMEChallengeResponseWrapper] {.
|
||||
async: (raises: [ACMEError, CancelledError])
|
||||
.} =
|
||||
let orderResponse = await self.requestNewOrder(domains, key, kid)
|
||||
if orderResponse.status != ACMEOrderStatus.PENDING and
|
||||
orderResponse.status != ACMEOrderStatus.READY:
|
||||
# ready is a valid status when renewing certs before expiry
|
||||
raise
|
||||
newException(ACMEError, "Invalid new order status: " & $orderResponse.status)
|
||||
|
||||
let authorizationsResponse =
|
||||
await self.requestAuthorizations(orderResponse.authorizations, key, kid)
|
||||
if authorizationsResponse.challenges.len == 0:
|
||||
raise newException(ACMEError, "No challenges received")
|
||||
|
||||
return ACMEChallengeResponseWrapper(
|
||||
finalize: orderResponse.finalize,
|
||||
order: orderResponse.order,
|
||||
dns01: authorizationsResponse.challenges.filterIt(
|
||||
it.`type` == ACMEChallengeType.DNS01
|
||||
)[0],
|
||||
# getting the first element is safe since we checked that authorizationsResponse.challenges.len != 0
|
||||
)
|
||||
|
||||
proc requestCheck*(
|
||||
self: ACMEApi, checkURL: Uri, checkKind: ACMECheckKind, key: KeyPair, kid: Kid
|
||||
): Future[ACMECheckResponse] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
handleError("requestCheck"):
|
||||
let acmeResponse = await self.get(checkURL)
|
||||
let retryAfter =
|
||||
try:
|
||||
parseInt(acmeResponse.headers.keyOrError("Retry-After")).seconds
|
||||
except ValueError:
|
||||
DefaultChalCompletedRetryTime
|
||||
|
||||
case checkKind
|
||||
of ACMEOrderCheck:
|
||||
try:
|
||||
ACMECheckResponse(
|
||||
kind: checkKind,
|
||||
orderStatus: parseEnum[ACMEOrderStatus](acmeResponse.body["status"].getStr),
|
||||
retryAfter: retryAfter,
|
||||
)
|
||||
except ValueError:
|
||||
raise newException(
|
||||
ACMEError, "Invalid order status: " & acmeResponse.body["status"].getStr
|
||||
)
|
||||
of ACMEChallengeCheck:
|
||||
try:
|
||||
ACMECheckResponse(
|
||||
kind: checkKind,
|
||||
chalStatus:
|
||||
parseEnum[ACMEChallengeStatus](acmeResponse.body["status"].getStr),
|
||||
retryAfter: retryAfter,
|
||||
)
|
||||
except ValueError:
|
||||
raise newException(
|
||||
ACMEError, "Invalid order status: " & acmeResponse.body["status"].getStr
|
||||
)
|
||||
|
||||
proc sendChallengeCompleted*(
|
||||
self: ACMEApi, chalURL: Uri, key: KeyPair, kid: Kid
|
||||
): Future[ACMECompletedResponse] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
handleError("sendChallengeCompleted"):
|
||||
let payload =
|
||||
await self.createSignedAcmeRequest(chalURL, %*{}, key, kid = Opt.some(kid))
|
||||
let acmeResponse = await self.post(chalURL, payload)
|
||||
acmeResponse.body.to(ACMECompletedResponse)
|
||||
|
||||
proc checkChallengeCompleted*(
|
||||
self: ACMEApi,
|
||||
checkURL: Uri,
|
||||
key: KeyPair,
|
||||
kid: Kid,
|
||||
retries: int = DefaultChalCompletedRetries,
|
||||
): Future[bool] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
for i in 0 .. retries:
|
||||
let checkResponse =
|
||||
await self.requestCheck(checkURL, ACMEChallengeCheck, key, kid)
|
||||
case checkResponse.chalStatus
|
||||
of ACMEChallengeStatus.PENDING:
|
||||
await sleepAsync(checkResponse.retryAfter) # try again after some delay
|
||||
of ACMEChallengeStatus.VALID:
|
||||
return true
|
||||
else:
|
||||
raise newException(
|
||||
ACMEError,
|
||||
"Failed challenge completion: expected 'valid', got '" &
|
||||
$checkResponse.chalStatus & "'",
|
||||
)
|
||||
return false
|
||||
|
||||
proc completeChallenge*(
|
||||
self: ACMEApi,
|
||||
chalURL: Uri,
|
||||
key: KeyPair,
|
||||
kid: Kid,
|
||||
retries: int = DefaultChalCompletedRetries,
|
||||
): Future[bool] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
let completedResponse = await self.sendChallengeCompleted(chalURL, key, kid)
|
||||
# check until acme server is done (poll validation)
|
||||
return await self.checkChallengeCompleted(chalURL, key, kid, retries = retries)
|
||||
|
||||
proc requestFinalize*(
|
||||
self: ACMEApi,
|
||||
domain: Domain,
|
||||
finalize: Uri,
|
||||
certKeyPair: KeyPair,
|
||||
key: KeyPair,
|
||||
kid: Kid,
|
||||
): Future[ACMEFinalizeResponse] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
handleError("requestFinalize"):
|
||||
let payload = await self.createSignedAcmeRequest(
|
||||
finalize, %*{"csr": createCSR(domain, certKeyPair)}, key, kid = Opt.some(kid)
|
||||
)
|
||||
let acmeResponse = await self.post(finalize, payload)
|
||||
# server responds with updated order response
|
||||
acmeResponse.body.to(ACMEFinalizeResponse)
|
||||
|
||||
proc checkCertFinalized*(
|
||||
self: ACMEApi,
|
||||
order: Uri,
|
||||
key: KeyPair,
|
||||
kid: Kid,
|
||||
retries: int = DefaultChalCompletedRetries,
|
||||
): Future[bool] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
for i in 0 .. retries:
|
||||
let checkResponse = await self.requestCheck(order, ACMEOrderCheck, key, kid)
|
||||
case checkResponse.orderStatus
|
||||
of ACMEOrderStatus.VALID:
|
||||
return true
|
||||
of ACMEOrderStatus.PROCESSING:
|
||||
await sleepAsync(checkResponse.retryAfter) # try again after some delay
|
||||
else:
|
||||
error "Failed certificate finalization",
|
||||
description = "expected 'valid', got '" & $checkResponse.orderStatus & "'"
|
||||
return false # do not try again
|
||||
|
||||
return false
|
||||
|
||||
proc certificateFinalized*(
|
||||
self: ACMEApi,
|
||||
domain: Domain,
|
||||
finalize: Uri,
|
||||
order: Uri,
|
||||
certKeyPair: KeyPair,
|
||||
key: KeyPair,
|
||||
kid: Kid,
|
||||
retries: int = DefaultFinalizeRetries,
|
||||
): Future[bool] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
let finalizeResponse =
|
||||
await self.requestFinalize(domain, finalize, certKeyPair, key, kid)
|
||||
# keep checking order until cert is valid (done)
|
||||
return await self.checkCertFinalized(order, key, kid, retries = retries)
|
||||
|
||||
proc requestGetOrder*(
|
||||
self: ACMEApi, order: Uri
|
||||
): Future[ACMEOrderResponse] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
handleError("requestGetOrder"):
|
||||
let acmeResponse = await self.get(order)
|
||||
acmeResponse.body.to(ACMEOrderResponse)
|
||||
|
||||
proc downloadCertificate*(
|
||||
self: ACMEApi, order: Uri
|
||||
): Future[ACMECertificateResponse] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
let orderResponse = await self.requestGetOrder(order)
|
||||
|
||||
handleError("downloadCertificate"):
|
||||
let rawResponse = await HttpClientRequestRef
|
||||
.get(self.session, orderResponse.certificate)
|
||||
.get()
|
||||
.send()
|
||||
ACMECertificateResponse(
|
||||
rawCertificate: bytesToString(await rawResponse.getBodyBytes()),
|
||||
certificateExpiry: parse(orderResponse.expires, "yyyy-MM-dd'T'HH:mm:ss'Z'"),
|
||||
)
|
||||
|
||||
proc close*(self: ACMEApi) {.async: (raises: [CancelledError]).} =
|
||||
await self.session.closeWait()
|
||||
|
||||
else:
|
||||
{.hint: "autotls disabled. Use -d:libp2p_autotls_support".}
|
||||
98
libp2p/autotls/acme/client.nim
Normal file
98
libp2p/autotls/acme/client.nim
Normal file
@@ -0,0 +1,98 @@
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2025 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import chronicles
|
||||
import ../../crypto/crypto
|
||||
import ./api
|
||||
|
||||
export api
|
||||
|
||||
type KeyAuthorization* = string
|
||||
|
||||
type ACMEClient* = ref object
|
||||
api: ACMEApi
|
||||
key*: KeyPair
|
||||
kid*: Kid
|
||||
|
||||
logScope:
|
||||
topics = "libp2p acme client"
|
||||
|
||||
when defined(libp2p_autotls_support):
|
||||
import uri
|
||||
import chronos, results, stew/byteutils
|
||||
import ../../crypto/rsa
|
||||
import ./utils
|
||||
|
||||
proc new*(
|
||||
T: typedesc[ACMEClient],
|
||||
rng: ref HmacDrbgContext = newRng(),
|
||||
api: ACMEApi = ACMEApi.new(acmeServerURL = parseUri(LetsEncryptURL)),
|
||||
key: Opt[KeyPair] = Opt.none(KeyPair),
|
||||
kid: Kid = Kid(""),
|
||||
): T {.raises: [].} =
|
||||
let key = key.valueOr:
|
||||
KeyPair.random(PKScheme.RSA, rng[]).get()
|
||||
T(api: api, key: key, kid: kid)
|
||||
|
||||
proc getOrInitKid*(
|
||||
self: ACMEClient
|
||||
): Future[Kid] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
if self.kid.len == 0:
|
||||
let registerResponse = await self.api.requestRegister(self.key)
|
||||
self.kid = registerResponse.kid
|
||||
return self.kid
|
||||
|
||||
proc genKeyAuthorization*(self: ACMEClient, token: string): KeyAuthorization =
|
||||
base64UrlEncode(@(sha256.digest((token & "." & thumbprint(self.key)).toBytes).data))
|
||||
|
||||
proc getChallenge*(
|
||||
self: ACMEClient, domains: seq[api.Domain]
|
||||
): Future[ACMEChallengeResponseWrapper] {.
|
||||
async: (raises: [ACMEError, CancelledError])
|
||||
.} =
|
||||
await self.api.requestChallenge(domains, self.key, await self.getOrInitKid())
|
||||
|
||||
proc getCertificate*(
|
||||
self: ACMEClient,
|
||||
domain: api.Domain,
|
||||
certKeyPair: KeyPair,
|
||||
challenge: ACMEChallengeResponseWrapper,
|
||||
): Future[ACMECertificateResponse] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
let chalURL = parseUri(challenge.dns01.url)
|
||||
let orderURL = parseUri(challenge.order)
|
||||
let finalizeURL = parseUri(challenge.finalize)
|
||||
trace "Sending challenge completed notification"
|
||||
discard await self.api.sendChallengeCompleted(
|
||||
chalURL, self.key, await self.getOrInitKid()
|
||||
)
|
||||
|
||||
trace "Checking for completed challenge"
|
||||
let completed = await self.api.checkChallengeCompleted(
|
||||
chalURL, self.key, await self.getOrInitKid()
|
||||
)
|
||||
if not completed:
|
||||
raise newException(
|
||||
ACMEError, "Failed to signal ACME server about challenge completion"
|
||||
)
|
||||
|
||||
trace "Waiting for certificate to be finalized"
|
||||
let finalized = await self.api.certificateFinalized(
|
||||
domain, finalizeURL, orderURL, certKeyPair, self.key, await self.getOrInitKid()
|
||||
)
|
||||
if not finalized:
|
||||
raise
|
||||
newException(ACMEError, "Failed to finalize certificate for domain " & domain)
|
||||
|
||||
trace "Downloading certificate"
|
||||
await self.api.downloadCertificate(orderURL)
|
||||
|
||||
proc close*(self: ACMEClient) {.async: (raises: [CancelledError]).} =
|
||||
await self.api.close()
|
||||
40
libp2p/autotls/acme/mockapi.nim
Normal file
40
libp2p/autotls/acme/mockapi.nim
Normal file
@@ -0,0 +1,40 @@
|
||||
import uri
|
||||
import chronos, chronos/apps/http/httpclient, json
|
||||
import ./api, ./utils
|
||||
|
||||
export api
|
||||
|
||||
type MockACMEApi* = ref object of ACMEApi
|
||||
mockedResponses*: seq[HTTPResponse]
|
||||
|
||||
proc new*(
|
||||
T: typedesc[MockACMEApi]
|
||||
): Future[T] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
let directory = ACMEDirectory(
|
||||
newNonce: LetsEncryptURL & "/new-nonce",
|
||||
newOrder: LetsEncryptURL & "/new-order",
|
||||
newAccount: LetsEncryptURL & "/new-account",
|
||||
)
|
||||
MockACMEApi(
|
||||
session: HttpSessionRef.new(),
|
||||
directory: Opt.some(directory),
|
||||
acmeServerURL: parseUri(LetsEncryptURL),
|
||||
)
|
||||
|
||||
when defined(libp2p_autotls_support):
|
||||
method requestNonce*(
|
||||
self: MockACMEApi
|
||||
): Future[Nonce] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
return $self.acmeServerURL & "/acme/1234"
|
||||
|
||||
method post*(
|
||||
self: MockACMEApi, uri: Uri, payload: string
|
||||
): Future[HTTPResponse] {.async: (raises: [ACMEError, HttpError, CancelledError]).} =
|
||||
result = self.mockedResponses[0]
|
||||
self.mockedResponses.delete(0)
|
||||
|
||||
method get*(
|
||||
self: MockACMEApi, uri: Uri
|
||||
): Future[HTTPResponse] {.async: (raises: [ACMEError, HttpError, CancelledError]).} =
|
||||
result = self.mockedResponses[0]
|
||||
self.mockedResponses.delete(0)
|
||||
73
libp2p/autotls/acme/utils.nim
Normal file
73
libp2p/autotls/acme/utils.nim
Normal file
@@ -0,0 +1,73 @@
|
||||
import ../../errors
|
||||
|
||||
type ACMEError* = object of LPError
|
||||
|
||||
when defined(libp2p_autotls_support):
|
||||
import base64, strutils, chronos/apps/http/httpclient, json
|
||||
import ../../transports/tls/certificate_ffi
|
||||
import ../../transports/tls/certificate
|
||||
import ../../crypto/crypto
|
||||
import ../../crypto/rsa
|
||||
|
||||
proc keyOrError*(table: HttpTable, key: string): string {.raises: [ValueError].} =
|
||||
if not table.contains(key):
|
||||
raise newException(ValueError, "key " & key & " not present in headers")
|
||||
table.getString(key)
|
||||
|
||||
proc base64UrlEncode*(data: seq[byte]): string =
|
||||
## Encodes data using base64url (RFC 4648 §5) — no padding, URL-safe
|
||||
var encoded = base64.encode(data, safe = true)
|
||||
encoded.removeSuffix("=")
|
||||
encoded.removeSuffix("=")
|
||||
return encoded
|
||||
|
||||
proc thumbprint*(key: KeyPair): string =
|
||||
doAssert key.seckey.scheme == PKScheme.RSA, "unsupported keytype"
|
||||
let pubkey = key.pubkey.rsakey
|
||||
let nArray = @(getArray(pubkey.buffer, pubkey.key.n, pubkey.key.nlen))
|
||||
let eArray = @(getArray(pubkey.buffer, pubkey.key.e, pubkey.key.elen))
|
||||
|
||||
let n = base64UrlEncode(nArray)
|
||||
let e = base64UrlEncode(eArray)
|
||||
let keyJson = %*{"e": e, "kty": "RSA", "n": n}
|
||||
let digest = sha256.digest($keyJson)
|
||||
return base64UrlEncode(@(digest.data))
|
||||
|
||||
proc getResponseBody*(
|
||||
response: HttpClientResponseRef
|
||||
): Future[JsonNode] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
try:
|
||||
let bodyBytes = await response.getBodyBytes()
|
||||
if bodyBytes.len > 0:
|
||||
return bytesToString(bodyBytes).parseJson()
|
||||
return %*{} # empty body
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
raise newException(
|
||||
ACMEError, "Unexpected error occurred while getting body bytes", exc
|
||||
)
|
||||
except Exception as exc: # this is required for nim 1.6
|
||||
raise newException(
|
||||
ACMEError, "Unexpected error occurred while getting body bytes", exc
|
||||
)
|
||||
|
||||
proc createCSR*(
|
||||
domain: string, certKeyPair: KeyPair
|
||||
): string {.raises: [ACMEError].} =
|
||||
var certKey: cert_key_t
|
||||
var certCtx: cert_context_t
|
||||
var derCSR: ptr cert_buffer = nil
|
||||
|
||||
# convert KeyPair to cert_key_t
|
||||
let rawSeckey: seq[byte] = certKeyPair.seckey.getRawBytes.valueOr:
|
||||
raise newException(ACMEError, "Failed to get seckey raw bytes (DER)")
|
||||
let seckeyBuffer = rawSeckey.toCertBuffer()
|
||||
if cert_new_key_t(seckeyBuffer.unsafeAddr, certKey.addr) != CERT_SUCCESS:
|
||||
raise newException(ACMEError, "Failed to convert key pair to cert_key_t")
|
||||
|
||||
# create CSR
|
||||
if cert_signing_req(domain.cstring, certKey, derCSR.addr) != CERT_SUCCESS:
|
||||
raise newException(ACMEError, "Failed to create CSR")
|
||||
|
||||
base64.encode(derCSR.toSeq, safe = true)
|
||||
33
libp2p/autotls/mockservice.nim
Normal file
33
libp2p/autotls/mockservice.nim
Normal file
@@ -0,0 +1,33 @@
|
||||
when defined(libp2p_autotls_support):
|
||||
import ./service, ./acme/client, ../peeridauth/client
|
||||
|
||||
import ../crypto/crypto, ../crypto/rsa, websock/websock
|
||||
|
||||
type MockAutotlsService* = ref object of AutotlsService
|
||||
mockedCert*: TLSCertificate
|
||||
mockedKey*: TLSPrivateKey
|
||||
|
||||
proc new*(
|
||||
T: typedesc[MockAutotlsService],
|
||||
rng: ref HmacDrbgContext = newRng(),
|
||||
config: AutotlsConfig = AutotlsConfig.new(),
|
||||
): T =
|
||||
T(
|
||||
acmeClient:
|
||||
ACMEClient.new(api = ACMEApi.new(acmeServerURL = config.acmeServerURL)),
|
||||
brokerClient: PeerIDAuthClient.new(),
|
||||
bearer: Opt.none(BearerToken),
|
||||
cert: Opt.none(AutotlsCert),
|
||||
certReady: newAsyncEvent(),
|
||||
running: newAsyncEvent(),
|
||||
config: config,
|
||||
rng: rng,
|
||||
)
|
||||
|
||||
method getCertWhenReady*(
|
||||
self: MockAutotlsService
|
||||
): Future[AutotlsCert] {.async: (raises: [AutoTLSError, CancelledError]).} =
|
||||
AutotlsCert.new(self.mockedCert, self.mockedKey, Moment.now)
|
||||
|
||||
method setup*(self: MockAutotlsService) {.base, async.} =
|
||||
self.running.fire()
|
||||
291
libp2p/autotls/service.nim
Normal file
291
libp2p/autotls/service.nim
Normal file
@@ -0,0 +1,291 @@
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2025 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
{.push public.}
|
||||
|
||||
import chronos, chronicles, net, results
|
||||
import chronos/apps/http/httpclient, bearssl/rand
|
||||
|
||||
import
|
||||
./acme/client,
|
||||
./utils,
|
||||
../crypto/crypto,
|
||||
../nameresolving/nameresolver,
|
||||
../peeridauth/client,
|
||||
../switch,
|
||||
../peerinfo,
|
||||
../wire
|
||||
|
||||
logScope:
|
||||
topics = "libp2p autotls"
|
||||
|
||||
export LetsEncryptURL, AutoTLSError
|
||||
|
||||
const
|
||||
DefaultDnsServers* =
|
||||
@[
|
||||
initTAddress("1.1.1.1:53"),
|
||||
initTAddress("1.0.0.1:53"),
|
||||
initTAddress("[2606:4700:4700::1111]:53"),
|
||||
]
|
||||
DefaultRenewCheckTime* = 1.hours
|
||||
DefaultRenewBufferTime = 1.hours
|
||||
|
||||
DefaultIssueRetries = 3
|
||||
DefaultIssueRetryTime = 1.seconds
|
||||
|
||||
AutoTLSBroker* = "registration.libp2p.direct"
|
||||
AutoTLSDNSServer* = "libp2p.direct"
|
||||
HttpOk* = 200
|
||||
HttpCreated* = 201
|
||||
# NoneIp is needed because nim 1.6.16 can't do proper generic inference
|
||||
NoneIp = Opt.none(IpAddress)
|
||||
|
||||
type SigParam = object
|
||||
k: string
|
||||
v: seq[byte]
|
||||
|
||||
type AutotlsCert* = ref object
|
||||
cert*: TLSCertificate
|
||||
privkey*: TLSPrivateKey
|
||||
expiry*: Moment
|
||||
|
||||
type AutotlsConfig* = ref object
|
||||
acmeServerURL*: Uri
|
||||
nameResolver*: NameResolver
|
||||
ipAddress: Opt[IpAddress]
|
||||
renewCheckTime*: Duration
|
||||
renewBufferTime*: Duration
|
||||
issueRetries*: int
|
||||
issueRetryTime*: Duration
|
||||
|
||||
type AutotlsService* = ref object of Service
|
||||
acmeClient*: ACMEClient
|
||||
brokerClient*: PeerIDAuthClient
|
||||
bearer*: Opt[BearerToken]
|
||||
cert*: Opt[AutotlsCert]
|
||||
certReady*: AsyncEvent
|
||||
running*: AsyncEvent
|
||||
config*: AutotlsConfig
|
||||
managerFut: Future[void]
|
||||
peerInfo: PeerInfo
|
||||
rng*: ref HmacDrbgContext
|
||||
|
||||
when defined(libp2p_autotls_support):
|
||||
import json, sequtils, bearssl/pem
|
||||
|
||||
import
|
||||
../crypto/rsa,
|
||||
../utils/heartbeat,
|
||||
../transports/transport,
|
||||
../transports/tcptransport,
|
||||
../nameresolving/dnsresolver
|
||||
|
||||
proc new*(
|
||||
T: typedesc[AutotlsCert],
|
||||
cert: TLSCertificate,
|
||||
privkey: TLSPrivateKey,
|
||||
expiry: Moment,
|
||||
): T =
|
||||
T(cert: cert, privkey: privkey, expiry: expiry)
|
||||
|
||||
method getCertWhenReady*(
|
||||
self: AutotlsService
|
||||
): Future[AutotlsCert] {.base, async: (raises: [AutoTLSError, CancelledError]).} =
|
||||
await self.certReady.wait()
|
||||
return self.cert.get
|
||||
|
||||
proc new*(
|
||||
T: typedesc[AutotlsConfig],
|
||||
ipAddress: Opt[IpAddress] = NoneIp,
|
||||
nameServers: seq[TransportAddress] = DefaultDnsServers,
|
||||
acmeServerURL: Uri = parseUri(LetsEncryptURL),
|
||||
renewCheckTime: Duration = DefaultRenewCheckTime,
|
||||
renewBufferTime: Duration = DefaultRenewBufferTime,
|
||||
issueRetries: int = DefaultIssueRetries,
|
||||
issueRetryTime: Duration = DefaultIssueRetryTime,
|
||||
): T =
|
||||
T(
|
||||
nameResolver: DnsResolver.new(nameServers),
|
||||
acmeServerURL: acmeServerURL,
|
||||
ipAddress: ipAddress,
|
||||
renewCheckTime: renewCheckTime,
|
||||
renewBufferTime: renewBufferTime,
|
||||
issueRetries: issueRetries,
|
||||
issueRetryTime: issueRetryTime,
|
||||
)
|
||||
|
||||
proc new*(
|
||||
T: typedesc[AutotlsService],
|
||||
rng: ref HmacDrbgContext = newRng(),
|
||||
config: AutotlsConfig = AutotlsConfig.new(),
|
||||
): T =
|
||||
T(
|
||||
acmeClient:
|
||||
ACMEClient.new(api = ACMEApi.new(acmeServerURL = config.acmeServerURL)),
|
||||
brokerClient: PeerIDAuthClient.new(),
|
||||
bearer: Opt.none(BearerToken),
|
||||
cert: Opt.none(AutotlsCert),
|
||||
certReady: newAsyncEvent(),
|
||||
running: newAsyncEvent(),
|
||||
config: config,
|
||||
managerFut: nil,
|
||||
peerInfo: nil,
|
||||
rng: rng,
|
||||
)
|
||||
|
||||
method setup*(
|
||||
self: AutotlsService, switch: Switch
|
||||
): Future[bool] {.async: (raises: [CancelledError]).} =
|
||||
trace "Setting up AutotlsService"
|
||||
let hasBeenSetup = await procCall Service(self).setup(switch)
|
||||
if hasBeenSetup:
|
||||
if self.config.ipAddress.isNone():
|
||||
try:
|
||||
self.config.ipAddress = Opt.some(getPublicIPAddress())
|
||||
except AutoTLSError as exc:
|
||||
error "Failed to get public IP address", err = exc.msg
|
||||
return false
|
||||
self.managerFut = self.run(switch)
|
||||
return hasBeenSetup
|
||||
|
||||
method issueCertificate(
|
||||
self: AutotlsService
|
||||
): Future[bool] {.
|
||||
base, async: (raises: [AutoTLSError, ACMEError, PeerIDAuthError, CancelledError])
|
||||
.} =
|
||||
trace "Issuing certificate"
|
||||
|
||||
if self.peerInfo.isNil():
|
||||
error "Cannot issue new certificate: peerInfo not set"
|
||||
return false
|
||||
|
||||
# generate autotls domain string: "*.{peerID}.libp2p.direct"
|
||||
let baseDomain =
|
||||
api.Domain(encodePeerId(self.peerInfo.peerId) & "." & AutoTLSDNSServer)
|
||||
let domain = api.Domain("*." & baseDomain)
|
||||
|
||||
let acmeClient = self.acmeClient
|
||||
|
||||
trace "Requesting ACME challenge"
|
||||
let dns01Challenge = await acmeClient.getChallenge(@[domain])
|
||||
trace "Generating key authorization"
|
||||
let keyAuth = acmeClient.genKeyAuthorization(dns01Challenge.dns01.token)
|
||||
|
||||
let addrs = await self.peerInfo.expandAddrs()
|
||||
if addrs.len == 0:
|
||||
error "Unable to authenticate with broker: no addresses"
|
||||
return false
|
||||
|
||||
let strMultiaddresses: seq[string] = addrs.mapIt($it)
|
||||
let payload = %*{"value": keyAuth, "addresses": strMultiaddresses}
|
||||
let registrationURL = parseUri("https://" & AutoTLSBroker & "/v1/_acme-challenge")
|
||||
|
||||
trace "Sending challenge to AutoTLS broker"
|
||||
let (bearer, response) =
|
||||
await self.brokerClient.send(registrationURL, self.peerInfo, payload, self.bearer)
|
||||
if self.bearer.isNone():
|
||||
# save bearer token for future
|
||||
self.bearer = Opt.some(bearer)
|
||||
if response.status != HttpOk:
|
||||
error "Failed to authenticate with AutoTLS Broker at " & AutoTLSBroker
|
||||
debug "Broker message",
|
||||
body = bytesToString(response.body), peerinfo = self.peerInfo
|
||||
return false
|
||||
|
||||
let dashedIpAddr = ($self.config.ipAddress.get()).replace(".", "-")
|
||||
let acmeChalDomain = api.Domain("_acme-challenge." & baseDomain)
|
||||
let ip4Domain = api.Domain(dashedIpAddr & "." & baseDomain)
|
||||
debug "Waiting for DNS record to be set", ip = ip4Domain, acme = acmeChalDomain
|
||||
let dnsSet = await checkDNSRecords(
|
||||
self.config.nameResolver, self.config.ipAddress.get(), baseDomain, keyAuth
|
||||
)
|
||||
if not dnsSet:
|
||||
error "DNS records not set"
|
||||
return false
|
||||
|
||||
trace "Notifying challenge completion to ACME and downloading cert"
|
||||
let certKeyPair = KeyPair.random(PKScheme.RSA, self.rng[]).get()
|
||||
|
||||
let certificate =
|
||||
await acmeClient.getCertificate(domain, certKeyPair, dns01Challenge)
|
||||
|
||||
let derPrivKey = certKeyPair.seckey.rsakey.getBytes.valueOr:
|
||||
raise newException(AutoTLSError, "Unable to get TLS private key")
|
||||
let pemPrivKey: string = derPrivKey.pemEncode("PRIVATE KEY")
|
||||
debug "autotls cert", pemPrivKey = pemPrivKey, cert = certificate.rawCertificate
|
||||
|
||||
trace "Installing certificate"
|
||||
let newCert =
|
||||
try:
|
||||
AutotlsCert.new(
|
||||
TLSCertificate.init(certificate.rawCertificate),
|
||||
TLSPrivateKey.init(pemPrivKey),
|
||||
asMoment(certificate.certificateExpiry),
|
||||
)
|
||||
except TLSStreamProtocolError:
|
||||
error "Could not parse downloaded certificates"
|
||||
return false
|
||||
self.cert = Opt.some(newCert)
|
||||
self.certReady.fire()
|
||||
trace "Certificate installed"
|
||||
true
|
||||
|
||||
proc hasTcpStarted(switch: Switch): bool =
|
||||
switch.transports.filterIt(it of TcpTransport and it.running).len == 0
|
||||
|
||||
proc tryIssueCertificate(self: AutotlsService) {.async: (raises: [CancelledError]).} =
|
||||
for _ in 0 ..< self.config.issueRetries:
|
||||
try:
|
||||
if await self.issueCertificate():
|
||||
return
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
error "Failed to issue certificate", err = exc.msg
|
||||
await sleepAsync(self.config.issueRetryTime)
|
||||
error "Failed to issue certificate"
|
||||
|
||||
method run*(
|
||||
self: AutotlsService, switch: Switch
|
||||
) {.async: (raises: [CancelledError]).} =
|
||||
trace "Starting Autotls management"
|
||||
self.running.fire()
|
||||
self.peerInfo = switch.peerInfo
|
||||
|
||||
# ensure that there's at least one TcpTransport running
|
||||
# for communicating with autotls broker
|
||||
if switch.hasTcpStarted():
|
||||
error "Could not find a running TcpTransport in switch"
|
||||
return
|
||||
|
||||
heartbeat "Certificate Management", self.config.renewCheckTime:
|
||||
if self.cert.isNone():
|
||||
await self.tryIssueCertificate()
|
||||
|
||||
# AutotlsService will renew the cert 1h before it expires
|
||||
let cert = self.cert.get
|
||||
let waitTime = cert.expiry - Moment.now - self.config.renewBufferTime
|
||||
if waitTime <= self.config.renewBufferTime:
|
||||
await self.tryIssueCertificate()
|
||||
|
||||
method stop*(
|
||||
self: AutotlsService, switch: Switch
|
||||
): Future[bool] {.async: (raises: [CancelledError]).} =
|
||||
let hasBeenStopped = await procCall Service(self).stop(switch)
|
||||
if hasBeenStopped:
|
||||
if not self.acmeClient.isNil():
|
||||
await self.acmeClient.close()
|
||||
if not self.brokerClient.isNil():
|
||||
await self.brokerClient.close()
|
||||
if not self.managerFut.isNil():
|
||||
await self.managerFut.cancelAndWait()
|
||||
self.managerFut = nil
|
||||
return hasBeenStopped
|
||||
112
libp2p/autotls/utils.nim
Normal file
112
libp2p/autotls/utils.nim
Normal file
@@ -0,0 +1,112 @@
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2025 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
{.push raises: [].}
|
||||
{.push public.}
|
||||
|
||||
import chronos, chronicles
|
||||
import ../errors
|
||||
|
||||
logScope:
|
||||
topics = "libp2p utils"
|
||||
|
||||
const
|
||||
DefaultDnsRetries = 3
|
||||
DefaultDnsRetryTime = 1.seconds
|
||||
|
||||
type AutoTLSError* = object of LPError
|
||||
|
||||
when defined(libp2p_autotls_support):
|
||||
import net, strutils
|
||||
from times import DateTime, toTime, toUnix
|
||||
import stew/base36
|
||||
import
|
||||
../peerid,
|
||||
../multihash,
|
||||
../cid,
|
||||
../multicodec,
|
||||
../nameresolving/nameresolver,
|
||||
./acme/client
|
||||
|
||||
proc checkedGetPrimaryIPAddr*(): IpAddress {.raises: [AutoTLSError].} =
|
||||
# This is so that we don't need to catch Exceptions directly
|
||||
# since we support 1.6.16 and getPrimaryIPAddr before nim 2 didn't have explicit .raises. pragmas
|
||||
try:
|
||||
return getPrimaryIPAddr()
|
||||
except Exception as exc:
|
||||
raise newException(AutoTLSError, "Error while getting primary IP address", exc)
|
||||
|
||||
proc isIPv4*(ip: IpAddress): bool =
|
||||
ip.family == IpAddressFamily.IPv4
|
||||
|
||||
proc isPublic*(ip: IpAddress): bool {.raises: [AutoTLSError].} =
|
||||
let ip = $ip
|
||||
try:
|
||||
not (
|
||||
ip.startsWith("10.") or
|
||||
(ip.startsWith("172.") and parseInt(ip.split(".")[1]) in 16 .. 31) or
|
||||
ip.startsWith("192.168.") or ip.startsWith("127.") or ip.startsWith("169.254.")
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise newException(AutoTLSError, "Failed to parse IP address", exc)
|
||||
|
||||
proc getPublicIPAddress*(): IpAddress {.raises: [AutoTLSError].} =
|
||||
let ip = checkedGetPrimaryIPAddr()
|
||||
if not ip.isIPv4():
|
||||
raise newException(AutoTLSError, "Host does not have an IPv4 address")
|
||||
if not ip.isPublic():
|
||||
raise newException(AutoTLSError, "Host does not have a public IPv4 address")
|
||||
return ip
|
||||
|
||||
proc asMoment*(dt: DateTime): Moment =
|
||||
let unixTime: int64 = dt.toTime.toUnix
|
||||
return Moment.init(unixTime, Second)
|
||||
|
||||
proc encodePeerId*(peerId: PeerId): string {.raises: [AutoTLSError].} =
|
||||
var mh: MultiHash
|
||||
let decodeResult = MultiHash.decode(peerId.data, mh)
|
||||
if decodeResult.isErr() or decodeResult.get() == -1:
|
||||
raise
|
||||
newException(AutoTLSError, "Failed to decode PeerId: invalid multihash format")
|
||||
|
||||
let cidResult = Cid.init(CIDv1, multiCodec("libp2p-key"), mh)
|
||||
if cidResult.isErr():
|
||||
raise newException(AutoTLSError, "Failed to initialize CID from multihash")
|
||||
|
||||
return Base36.encode(cidResult.get().data.buffer)
|
||||
|
||||
proc checkDNSRecords*(
|
||||
nameResolver: NameResolver,
|
||||
ipAddress: IpAddress,
|
||||
baseDomain: api.Domain,
|
||||
keyAuth: KeyAuthorization,
|
||||
retries: int = DefaultDnsRetries,
|
||||
): Future[bool] {.async: (raises: [AutoTLSError, CancelledError]).} =
|
||||
# if my ip address is 100.10.10.3 then the ip4Domain will be:
|
||||
# 100-10-10-3.{peerIdBase36}.libp2p.direct
|
||||
# and acme challenge TXT domain will be:
|
||||
# _acme-challenge.{peerIdBase36}.libp2p.direct
|
||||
let dashedIpAddr = ($ipAddress).replace(".", "-")
|
||||
let acmeChalDomain = api.Domain("_acme-challenge." & baseDomain)
|
||||
let ip4Domain = api.Domain(dashedIpAddr & "." & baseDomain)
|
||||
|
||||
var txt: seq[string]
|
||||
var ip4: seq[TransportAddress]
|
||||
for _ in 0 .. retries:
|
||||
txt = await nameResolver.resolveTxt(acmeChalDomain)
|
||||
try:
|
||||
ip4 = await nameResolver.resolveIp(ip4Domain, 0.Port)
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
error "Failed to resolve IP", description = exc.msg # retry
|
||||
if txt.len > 0 and txt[0] == keyAuth and ip4.len > 0:
|
||||
return true
|
||||
await sleepAsync(DefaultDnsRetryTime)
|
||||
|
||||
return false
|
||||
@@ -1,45 +1,69 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2020 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
## This module contains a Switch Building helper.
|
||||
runnableExamples:
|
||||
let switch = SwitchBuilder.new().withRng(rng).withAddresses(multiaddress)
|
||||
# etc
|
||||
.build()
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import options, tables, chronos, chronicles, sequtils
|
||||
import
|
||||
options, tables, chronos, chronicles, bearssl,
|
||||
switch, peerid, peerinfo, stream/connection, multiaddress,
|
||||
crypto/crypto, transports/[transport, tcptransport],
|
||||
muxers/[muxer, mplex/mplex],
|
||||
protocols/[identify, secure/secure, secure/noise],
|
||||
connmanager, upgrademngrs/muxedupgrade,
|
||||
switch,
|
||||
peerid,
|
||||
peerinfo,
|
||||
stream/connection,
|
||||
multiaddress,
|
||||
crypto/crypto,
|
||||
transports/[transport, tcptransport, wstransport, memorytransport],
|
||||
muxers/[muxer, mplex/mplex, yamux/yamux],
|
||||
protocols/[identify, secure/secure, secure/noise, rendezvous],
|
||||
protocols/connectivity/[autonat/server, relay/relay, relay/client, relay/rtransport],
|
||||
connmanager,
|
||||
upgrademngrs/muxedupgrade,
|
||||
observedaddrmanager,
|
||||
autotls/service,
|
||||
nameresolving/nameresolver,
|
||||
errors
|
||||
errors,
|
||||
utility
|
||||
import services/wildcardresolverservice
|
||||
|
||||
export
|
||||
switch, peerid, peerinfo, connection, multiaddress, crypto, errors
|
||||
switch, peerid, peerinfo, connection, multiaddress, crypto, errors, TLSPrivateKey,
|
||||
TLSCertificate, TLSFlags, ServerFlags
|
||||
|
||||
const MemoryAutoAddress* = memorytransport.MemoryAutoAddress
|
||||
|
||||
type
|
||||
TransportProvider* = proc(upgr: Upgrade): Transport {.gcsafe, raises: [Defect].}
|
||||
TransportProvider* {.deprecated: "Use TransportBuilder instead".} =
|
||||
proc(upgr: Upgrade, privateKey: PrivateKey): Transport {.gcsafe, raises: [].}
|
||||
|
||||
TransportBuilder* {.public.} =
|
||||
proc(config: TransportConfig): Transport {.gcsafe, raises: [].}
|
||||
|
||||
TransportConfig* = ref object
|
||||
upgr*: Upgrade
|
||||
privateKey*: PrivateKey
|
||||
autotls*: AutotlsService
|
||||
|
||||
SecureProtocol* {.pure.} = enum
|
||||
Noise,
|
||||
Secio {.deprecated.}
|
||||
|
||||
MplexOpts = object
|
||||
enable: bool
|
||||
newMuxer: MuxerConstructor
|
||||
Noise
|
||||
|
||||
SwitchBuilder* = ref object
|
||||
privKey: Option[PrivateKey]
|
||||
addresses: seq[MultiAddress]
|
||||
secureManagers: seq[SecureProtocol]
|
||||
mplexOpts: MplexOpts
|
||||
transports: seq[TransportProvider]
|
||||
rng: ref BrHmacDrbgContext
|
||||
muxers: seq[MuxerProvider]
|
||||
transports: seq[TransportBuilder]
|
||||
rng: ref HmacDrbgContext
|
||||
maxConnections: int
|
||||
maxIn: int
|
||||
sendSignedPeerRecord: bool
|
||||
@@ -48,12 +72,20 @@ type
|
||||
protoVersion: string
|
||||
agentVersion: string
|
||||
nameResolver: NameResolver
|
||||
peerStoreCapacity: Opt[int]
|
||||
autonat: bool
|
||||
autotls: AutotlsService
|
||||
circuitRelay: Relay
|
||||
rdv: RendezVous
|
||||
services: seq[Service]
|
||||
observedAddrManager: ObservedAddrManager
|
||||
enableWildcardResolver: bool
|
||||
|
||||
proc new*(T: type[SwitchBuilder]): T =
|
||||
proc new*(T: type[SwitchBuilder]): T {.public.} =
|
||||
## Creates a SwitchBuilder
|
||||
|
||||
let address = MultiAddress
|
||||
.init("/ip4/127.0.0.1/tcp/0")
|
||||
.expect("Should initialize to default")
|
||||
let address =
|
||||
MultiAddress.init("/ip4/127.0.0.1/tcp/0").expect("Should initialize to default")
|
||||
|
||||
SwitchBuilder(
|
||||
privKey: none(PrivateKey),
|
||||
@@ -64,122 +96,260 @@ proc new*(T: type[SwitchBuilder]): T =
|
||||
maxOut: -1,
|
||||
maxConnsPerPeer: MaxConnectionsPerPeer,
|
||||
protoVersion: ProtoVersion,
|
||||
agentVersion: AgentVersion)
|
||||
agentVersion: AgentVersion,
|
||||
enableWildcardResolver: true,
|
||||
)
|
||||
|
||||
proc withPrivateKey*(
|
||||
b: SwitchBuilder, privateKey: PrivateKey
|
||||
): SwitchBuilder {.public.} =
|
||||
## Set the private key of the switch. Will be used to
|
||||
## generate a PeerId
|
||||
|
||||
proc withPrivateKey*(b: SwitchBuilder, privateKey: PrivateKey): SwitchBuilder =
|
||||
b.privKey = some(privateKey)
|
||||
b
|
||||
|
||||
proc withAddress*(b: SwitchBuilder, address: MultiAddress): SwitchBuilder =
|
||||
b.addresses = @[address]
|
||||
b
|
||||
|
||||
proc withAddresses*(b: SwitchBuilder, addresses: seq[MultiAddress]): SwitchBuilder =
|
||||
proc withAddresses*(
|
||||
b: SwitchBuilder, addresses: seq[MultiAddress], enableWildcardResolver: bool = true
|
||||
): SwitchBuilder {.public.} =
|
||||
## | Set the listening addresses of the switch
|
||||
## | Calling it multiple time will override the value
|
||||
b.addresses = addresses
|
||||
b.enableWildcardResolver = enableWildcardResolver
|
||||
b
|
||||
|
||||
proc withSignedPeerRecord*(b: SwitchBuilder, sendIt = true): SwitchBuilder =
|
||||
proc withAddress*(
|
||||
b: SwitchBuilder, address: MultiAddress, enableWildcardResolver: bool = true
|
||||
): SwitchBuilder {.public.} =
|
||||
## | Set the listening address of the switch
|
||||
## | Calling it multiple time will override the value
|
||||
b.withAddresses(@[address], enableWildcardResolver)
|
||||
|
||||
proc withSignedPeerRecord*(b: SwitchBuilder, sendIt = true): SwitchBuilder {.public.} =
|
||||
b.sendSignedPeerRecord = sendIt
|
||||
b
|
||||
|
||||
proc withMplex*(b: SwitchBuilder, inTimeout = 5.minutes, outTimeout = 5.minutes): SwitchBuilder =
|
||||
proc withMplex*(
|
||||
b: SwitchBuilder, inTimeout = 5.minutes, outTimeout = 5.minutes, maxChannCount = 200
|
||||
): SwitchBuilder {.public.} =
|
||||
## | Uses `Mplex <https://docs.libp2p.io/concepts/stream-multiplexing/#mplex>`_ as a multiplexer
|
||||
## | `Timeout` is the duration after which a inactive connection will be closed
|
||||
proc newMuxer(conn: Connection): Muxer =
|
||||
Mplex.new(
|
||||
conn,
|
||||
inTimeout = inTimeout,
|
||||
outTimeout = outTimeout)
|
||||
|
||||
b.mplexOpts = MplexOpts(
|
||||
enable: true,
|
||||
newMuxer: newMuxer,
|
||||
)
|
||||
Mplex.new(conn, inTimeout, outTimeout, maxChannCount)
|
||||
|
||||
assert b.muxers.countIt(it.codec == MplexCodec) == 0, "Mplex build multiple times"
|
||||
b.muxers.add(MuxerProvider.new(newMuxer, MplexCodec))
|
||||
b
|
||||
|
||||
proc withNoise*(b: SwitchBuilder): SwitchBuilder =
|
||||
proc withYamux*(
|
||||
b: SwitchBuilder,
|
||||
windowSize: int = YamuxDefaultWindowSize,
|
||||
inTimeout: Duration = 5.minutes,
|
||||
outTimeout: Duration = 5.minutes,
|
||||
): SwitchBuilder =
|
||||
proc newMuxer(conn: Connection): Muxer =
|
||||
Yamux.new(conn, windowSize, inTimeout = inTimeout, outTimeout = outTimeout)
|
||||
|
||||
assert b.muxers.countIt(it.codec == YamuxCodec) == 0, "Yamux build multiple times"
|
||||
b.muxers.add(MuxerProvider.new(newMuxer, YamuxCodec))
|
||||
b
|
||||
|
||||
proc withNoise*(b: SwitchBuilder): SwitchBuilder {.public.} =
|
||||
b.secureManagers.add(SecureProtocol.Noise)
|
||||
b
|
||||
|
||||
proc withTransport*(b: SwitchBuilder, prov: TransportProvider): SwitchBuilder =
|
||||
proc withTransport*(
|
||||
b: SwitchBuilder, prov: TransportBuilder
|
||||
): SwitchBuilder {.public.} =
|
||||
## Use a custom transport
|
||||
runnableExamples:
|
||||
let switch = SwitchBuilder
|
||||
.new()
|
||||
.withTransport(
|
||||
proc(config: TransportConfig): Transport =
|
||||
TcpTransport.new(flags, config.upgr)
|
||||
)
|
||||
.build()
|
||||
b.transports.add(prov)
|
||||
b
|
||||
|
||||
proc withTcpTransport*(b: SwitchBuilder, flags: set[ServerFlags] = {}): SwitchBuilder =
|
||||
b.withTransport(proc(upgr: Upgrade): Transport = TcpTransport.new(flags, upgr))
|
||||
proc withTransport*(
|
||||
b: SwitchBuilder, prov: TransportProvider
|
||||
): SwitchBuilder {.deprecated: "Use TransportBuilder instead".} =
|
||||
## Use a custom transport
|
||||
runnableExamples:
|
||||
let switch = SwitchBuilder
|
||||
.new()
|
||||
.withTransport(
|
||||
proc(upgr: Upgrade, privateKey: PrivateKey): Transport =
|
||||
TcpTransport.new(flags, upgr)
|
||||
)
|
||||
.build()
|
||||
let tBuilder: TransportBuilder = proc(config: TransportConfig): Transport =
|
||||
prov(config.upgr, config.privateKey)
|
||||
b.withTransport(tBuilder)
|
||||
|
||||
proc withRng*(b: SwitchBuilder, rng: ref BrHmacDrbgContext): SwitchBuilder =
|
||||
proc withTcpTransport*(
|
||||
b: SwitchBuilder, flags: set[ServerFlags] = {}
|
||||
): SwitchBuilder {.public.} =
|
||||
b.withTransport(
|
||||
proc(config: TransportConfig): Transport =
|
||||
TcpTransport.new(flags, config.upgr)
|
||||
)
|
||||
|
||||
proc withWsTransport*(
|
||||
b: SwitchBuilder,
|
||||
tlsPrivateKey: TLSPrivateKey = nil,
|
||||
tlsCertificate: TLSCertificate = nil,
|
||||
tlsFlags: set[TLSFlags] = {},
|
||||
flags: set[ServerFlags] = {},
|
||||
): SwitchBuilder =
|
||||
b.withTransport(
|
||||
proc(config: TransportConfig): Transport =
|
||||
WsTransport.new(
|
||||
config.upgr, tlsPrivateKey, tlsCertificate, config.autotls, tlsFlags, flags
|
||||
)
|
||||
)
|
||||
|
||||
when defined(libp2p_quic_support):
|
||||
import transports/quictransport
|
||||
|
||||
proc withQuicTransport*(b: SwitchBuilder): SwitchBuilder {.public.} =
|
||||
b.withTransport(
|
||||
proc(config: TransportConfig): Transport =
|
||||
QuicTransport.new(config.upgr, config.privateKey)
|
||||
)
|
||||
|
||||
proc withMemoryTransport*(b: SwitchBuilder): SwitchBuilder {.public.} =
|
||||
b.withTransport(
|
||||
proc(config: TransportConfig): Transport =
|
||||
MemoryTransport.new(config.upgr)
|
||||
)
|
||||
|
||||
proc withRng*(b: SwitchBuilder, rng: ref HmacDrbgContext): SwitchBuilder {.public.} =
|
||||
b.rng = rng
|
||||
b
|
||||
|
||||
proc withMaxConnections*(b: SwitchBuilder, maxConnections: int): SwitchBuilder =
|
||||
proc withMaxConnections*(
|
||||
b: SwitchBuilder, maxConnections: int
|
||||
): SwitchBuilder {.public.} =
|
||||
## Maximum concurrent connections of the switch. You should either use this, or
|
||||
## `withMaxIn <#withMaxIn,SwitchBuilder,int>`_ & `withMaxOut<#withMaxOut,SwitchBuilder,int>`_
|
||||
b.maxConnections = maxConnections
|
||||
b
|
||||
|
||||
proc withMaxIn*(b: SwitchBuilder, maxIn: int): SwitchBuilder =
|
||||
proc withMaxIn*(b: SwitchBuilder, maxIn: int): SwitchBuilder {.public.} =
|
||||
## Maximum concurrent incoming connections. Should be used with `withMaxOut<#withMaxOut,SwitchBuilder,int>`_
|
||||
b.maxIn = maxIn
|
||||
b
|
||||
|
||||
proc withMaxOut*(b: SwitchBuilder, maxOut: int): SwitchBuilder =
|
||||
proc withMaxOut*(b: SwitchBuilder, maxOut: int): SwitchBuilder {.public.} =
|
||||
## Maximum concurrent outgoing connections. Should be used with `withMaxIn<#withMaxIn,SwitchBuilder,int>`_
|
||||
b.maxOut = maxOut
|
||||
b
|
||||
|
||||
proc withMaxConnsPerPeer*(b: SwitchBuilder, maxConnsPerPeer: int): SwitchBuilder =
|
||||
proc withMaxConnsPerPeer*(
|
||||
b: SwitchBuilder, maxConnsPerPeer: int
|
||||
): SwitchBuilder {.public.} =
|
||||
b.maxConnsPerPeer = maxConnsPerPeer
|
||||
b
|
||||
|
||||
proc withProtoVersion*(b: SwitchBuilder, protoVersion: string): SwitchBuilder =
|
||||
proc withPeerStore*(b: SwitchBuilder, capacity: int): SwitchBuilder {.public.} =
|
||||
b.peerStoreCapacity = Opt.some(capacity)
|
||||
b
|
||||
|
||||
proc withProtoVersion*(
|
||||
b: SwitchBuilder, protoVersion: string
|
||||
): SwitchBuilder {.public.} =
|
||||
b.protoVersion = protoVersion
|
||||
b
|
||||
|
||||
proc withAgentVersion*(b: SwitchBuilder, agentVersion: string): SwitchBuilder =
|
||||
proc withAgentVersion*(
|
||||
b: SwitchBuilder, agentVersion: string
|
||||
): SwitchBuilder {.public.} =
|
||||
b.agentVersion = agentVersion
|
||||
b
|
||||
|
||||
proc withNameResolver*(b: SwitchBuilder, nameResolver: NameResolver): SwitchBuilder =
|
||||
proc withNameResolver*(
|
||||
b: SwitchBuilder, nameResolver: NameResolver
|
||||
): SwitchBuilder {.public.} =
|
||||
b.nameResolver = nameResolver
|
||||
b
|
||||
|
||||
proc build*(b: SwitchBuilder): Switch
|
||||
{.raises: [Defect, LPError].} =
|
||||
proc withAutonat*(b: SwitchBuilder): SwitchBuilder =
|
||||
b.autonat = true
|
||||
b
|
||||
|
||||
when defined(libp2p_autotls_support):
|
||||
proc withAutotls*(
|
||||
b: SwitchBuilder, config: AutotlsConfig = AutotlsConfig.new()
|
||||
): SwitchBuilder {.public.} =
|
||||
b.autotls = AutotlsService.new(config = config)
|
||||
b
|
||||
|
||||
proc withCircuitRelay*(b: SwitchBuilder, r: Relay = Relay.new()): SwitchBuilder =
|
||||
b.circuitRelay = r
|
||||
b
|
||||
|
||||
proc withRendezVous*(
|
||||
b: SwitchBuilder, rdv: RendezVous = RendezVous.new()
|
||||
): SwitchBuilder =
|
||||
b.rdv = rdv
|
||||
b
|
||||
|
||||
proc withServices*(b: SwitchBuilder, services: seq[Service]): SwitchBuilder =
|
||||
b.services = services
|
||||
b
|
||||
|
||||
proc withObservedAddrManager*(
|
||||
b: SwitchBuilder, observedAddrManager: ObservedAddrManager
|
||||
): SwitchBuilder =
|
||||
b.observedAddrManager = observedAddrManager
|
||||
b
|
||||
|
||||
proc build*(b: SwitchBuilder): Switch {.raises: [LPError], public.} =
|
||||
if b.rng == nil: # newRng could fail
|
||||
raise newException(Defect, "Cannot initialize RNG")
|
||||
|
||||
let pkRes = PrivateKey.random(b.rng[])
|
||||
let
|
||||
seckey = b.privKey.get(otherwise = pkRes.expect("Expected default Private Key"))
|
||||
let seckey = b.privKey.get(otherwise = pkRes.expect("Expected default Private Key"))
|
||||
|
||||
var
|
||||
secureManagerInstances: seq[Secure]
|
||||
if b.secureManagers.len == 0:
|
||||
debug "no secure managers defined. Adding noise by default"
|
||||
b.secureManagers.add(SecureProtocol.Noise)
|
||||
|
||||
var secureManagerInstances: seq[Secure]
|
||||
if SecureProtocol.Noise in b.secureManagers:
|
||||
secureManagerInstances.add(Noise.new(b.rng, seckey).Secure)
|
||||
|
||||
let
|
||||
peerInfo = PeerInfo.new(
|
||||
seckey,
|
||||
b.addresses,
|
||||
protoVersion = b.protoVersion,
|
||||
agentVersion = b.agentVersion)
|
||||
let peerInfo = PeerInfo.new(
|
||||
seckey, b.addresses, protoVersion = b.protoVersion, agentVersion = b.agentVersion
|
||||
)
|
||||
|
||||
let identify =
|
||||
if b.observedAddrManager != nil:
|
||||
Identify.new(peerInfo, b.sendSignedPeerRecord, b.observedAddrManager)
|
||||
else:
|
||||
Identify.new(peerInfo, b.sendSignedPeerRecord)
|
||||
|
||||
let
|
||||
muxers = block:
|
||||
var muxers: Table[string, MuxerProvider]
|
||||
if b.mplexOpts.enable:
|
||||
muxers[MplexCodec] = MuxerProvider.new(b.mplexOpts.newMuxer, MplexCodec)
|
||||
muxers
|
||||
|
||||
let
|
||||
identify = Identify.new(peerInfo, b.sendSignedPeerRecord)
|
||||
connManager = ConnManager.new(b.maxConnsPerPeer, b.maxConnections, b.maxIn, b.maxOut)
|
||||
connManager =
|
||||
ConnManager.new(b.maxConnsPerPeer, b.maxConnections, b.maxIn, b.maxOut)
|
||||
ms = MultistreamSelect.new()
|
||||
muxedUpgrade = MuxedUpgrade.new(identify, muxers, secureManagerInstances, connManager, ms)
|
||||
muxedUpgrade = MuxedUpgrade.new(b.muxers, secureManagerInstances, ms)
|
||||
|
||||
let
|
||||
transports = block:
|
||||
var transports: seq[Transport]
|
||||
for tProvider in b.transports:
|
||||
transports.add(tProvider(muxedUpgrade))
|
||||
transports
|
||||
if not b.autotls.isNil():
|
||||
b.services.insert(b.autotls, 0)
|
||||
|
||||
let transports = block:
|
||||
var transports: seq[Transport]
|
||||
for tProvider in b.transports:
|
||||
transports.add(
|
||||
tProvider(
|
||||
TransportConfig(upgr: muxedUpgrade, privateKey: seckey, autotls: b.autotls)
|
||||
)
|
||||
)
|
||||
transports
|
||||
|
||||
if b.secureManagers.len == 0:
|
||||
b.secureManagers &= SecureProtocol.Noise
|
||||
@@ -187,39 +357,67 @@ proc build*(b: SwitchBuilder): Switch
|
||||
if isNil(b.rng):
|
||||
b.rng = newRng()
|
||||
|
||||
let peerStore = block:
|
||||
b.peerStoreCapacity.withValue(capacity):
|
||||
PeerStore.new(identify, capacity)
|
||||
else:
|
||||
PeerStore.new(identify)
|
||||
|
||||
if b.enableWildcardResolver:
|
||||
b.services.insert(WildcardAddressResolverService.new(), 0)
|
||||
|
||||
let switch = newSwitch(
|
||||
peerInfo = peerInfo,
|
||||
transports = transports,
|
||||
identity = identify,
|
||||
muxers = muxers,
|
||||
secureManagers = secureManagerInstances,
|
||||
connManager = connManager,
|
||||
ms = ms,
|
||||
nameResolver = b.nameResolver)
|
||||
nameResolver = b.nameResolver,
|
||||
peerStore = peerStore,
|
||||
services = b.services,
|
||||
)
|
||||
|
||||
switch.mount(identify)
|
||||
|
||||
if b.autonat:
|
||||
let autonat = Autonat.new(switch)
|
||||
switch.mount(autonat)
|
||||
|
||||
if not isNil(b.circuitRelay):
|
||||
if b.circuitRelay of RelayClient:
|
||||
switch.addTransport(RelayTransport.new(RelayClient(b.circuitRelay), muxedUpgrade))
|
||||
b.circuitRelay.setup(switch)
|
||||
switch.mount(b.circuitRelay)
|
||||
|
||||
if not isNil(b.rdv):
|
||||
b.rdv.setup(switch)
|
||||
switch.mount(b.rdv)
|
||||
|
||||
return switch
|
||||
|
||||
proc newStandardSwitch*(
|
||||
privKey = none(PrivateKey),
|
||||
addrs: MultiAddress | seq[MultiAddress] = MultiAddress.init("/ip4/127.0.0.1/tcp/0").tryGet(),
|
||||
secureManagers: openArray[SecureProtocol] = [
|
||||
SecureProtocol.Noise,
|
||||
],
|
||||
transportFlags: set[ServerFlags] = {},
|
||||
rng = newRng(),
|
||||
inTimeout: Duration = 5.minutes,
|
||||
outTimeout: Duration = 5.minutes,
|
||||
maxConnections = MaxConnections,
|
||||
maxIn = -1,
|
||||
maxOut = -1,
|
||||
maxConnsPerPeer = MaxConnectionsPerPeer,
|
||||
nameResolver: NameResolver = nil,
|
||||
sendSignedPeerRecord = false): Switch
|
||||
{.raises: [Defect, LPError].} =
|
||||
if SecureProtocol.Secio in secureManagers:
|
||||
quit("Secio is deprecated!") # use of secio is unsafe
|
||||
|
||||
let addrs = when addrs is MultiAddress: @[addrs] else: addrs
|
||||
privKey = none(PrivateKey),
|
||||
addrs: MultiAddress | seq[MultiAddress] =
|
||||
MultiAddress.init("/ip4/127.0.0.1/tcp/0").expect("valid address"),
|
||||
secureManagers: openArray[SecureProtocol] = [SecureProtocol.Noise],
|
||||
transportFlags: set[ServerFlags] = {},
|
||||
rng = newRng(),
|
||||
inTimeout: Duration = 5.minutes,
|
||||
outTimeout: Duration = 5.minutes,
|
||||
maxConnections = MaxConnections,
|
||||
maxIn = -1,
|
||||
maxOut = -1,
|
||||
maxConnsPerPeer = MaxConnectionsPerPeer,
|
||||
nameResolver: NameResolver = nil,
|
||||
sendSignedPeerRecord = false,
|
||||
peerStoreCapacity = 1000,
|
||||
): Switch {.raises: [LPError], public.} =
|
||||
## Helper for common switch configurations.
|
||||
let addrs =
|
||||
when addrs is MultiAddress:
|
||||
@[addrs]
|
||||
else:
|
||||
addrs
|
||||
var b = SwitchBuilder
|
||||
.new()
|
||||
.withAddresses(addrs)
|
||||
@@ -229,12 +427,13 @@ proc newStandardSwitch*(
|
||||
.withMaxIn(maxIn)
|
||||
.withMaxOut(maxOut)
|
||||
.withMaxConnsPerPeer(maxConnsPerPeer)
|
||||
.withPeerStore(capacity = peerStoreCapacity)
|
||||
.withMplex(inTimeout, outTimeout)
|
||||
.withTcpTransport(transportFlags)
|
||||
.withNameResolver(nameResolver)
|
||||
.withNoise()
|
||||
|
||||
if privKey.isSome():
|
||||
b = b.withPrivateKey(privKey.get())
|
||||
privKey.withValue(pkey):
|
||||
b = b.withPrivateKey(pkey)
|
||||
|
||||
b.build()
|
||||
|
||||
166
libp2p/cid.nim
166
libp2p/cid.nim
@@ -1,28 +1,36 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implementes CID (Content IDentifier).
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
{.used.}
|
||||
|
||||
import tables, hashes
|
||||
import multibase, multicodec, multihash, vbuffer, varint
|
||||
import stew/[base58, results]
|
||||
import multibase, multicodec, multihash, vbuffer, varint, results
|
||||
import stew/base58
|
||||
import ./utils/sequninit
|
||||
|
||||
export results
|
||||
|
||||
type
|
||||
CidError* {.pure.} = enum
|
||||
Error, Incorrect, Unsupported, Overrun
|
||||
Error
|
||||
Incorrect
|
||||
Unsupported
|
||||
Overrun
|
||||
|
||||
CidVersion* = enum
|
||||
CIDvIncorrect, CIDv0, CIDv1, CIDvReserved
|
||||
CIDvIncorrect
|
||||
CIDv0
|
||||
CIDv1
|
||||
CIDvReserved
|
||||
|
||||
Cid* = object
|
||||
cidver*: CidVersion
|
||||
@@ -30,54 +38,58 @@ type
|
||||
hpos*: int
|
||||
data*: VBuffer
|
||||
|
||||
const
|
||||
ContentIdsList = [
|
||||
multiCodec("raw"),
|
||||
multiCodec("dag-pb"),
|
||||
multiCodec("dag-cbor"),
|
||||
multiCodec("dag-json"),
|
||||
multiCodec("git-raw"),
|
||||
multiCodec("eth-block"),
|
||||
multiCodec("eth-block-list"),
|
||||
multiCodec("eth-tx-trie"),
|
||||
multiCodec("eth-tx"),
|
||||
multiCodec("eth-tx-receipt-trie"),
|
||||
multiCodec("eth-tx-receipt"),
|
||||
multiCodec("eth-state-trie"),
|
||||
multiCodec("eth-account-snapshot"),
|
||||
multiCodec("eth-storage-trie"),
|
||||
multiCodec("bitcoin-block"),
|
||||
multiCodec("bitcoin-tx"),
|
||||
multiCodec("zcash-block"),
|
||||
multiCodec("zcash-tx"),
|
||||
multiCodec("stellar-block"),
|
||||
multiCodec("stellar-tx"),
|
||||
multiCodec("decred-block"),
|
||||
multiCodec("decred-tx"),
|
||||
multiCodec("dash-block"),
|
||||
multiCodec("dash-tx"),
|
||||
multiCodec("torrent-info"),
|
||||
multiCodec("torrent-file"),
|
||||
multiCodec("ed25519-pub")
|
||||
]
|
||||
const ContentIdsList* = [
|
||||
multiCodec("raw"),
|
||||
multiCodec("dag-pb"),
|
||||
multiCodec("dag-cbor"),
|
||||
multiCodec("dag-json"),
|
||||
multiCodec("libp2p-key"),
|
||||
multiCodec("git-raw"),
|
||||
multiCodec("eth-block"),
|
||||
multiCodec("eth-block-list"),
|
||||
multiCodec("eth-tx-trie"),
|
||||
multiCodec("eth-tx"),
|
||||
multiCodec("eth-tx-receipt-trie"),
|
||||
multiCodec("eth-tx-receipt"),
|
||||
multiCodec("eth-state-trie"),
|
||||
multiCodec("eth-account-snapshot"),
|
||||
multiCodec("eth-storage-trie"),
|
||||
multiCodec("bitcoin-block"),
|
||||
multiCodec("bitcoin-tx"),
|
||||
multiCodec("zcash-block"),
|
||||
multiCodec("zcash-tx"),
|
||||
multiCodec("stellar-block"),
|
||||
multiCodec("stellar-tx"),
|
||||
multiCodec("decred-block"),
|
||||
multiCodec("decred-tx"),
|
||||
multiCodec("dash-block"),
|
||||
multiCodec("dash-tx"),
|
||||
multiCodec("torrent-info"),
|
||||
multiCodec("torrent-file"),
|
||||
multiCodec("ed25519-pub"),
|
||||
multiCodec("codex-root"),
|
||||
multiCodec("codex-manifest"),
|
||||
multiCodec("codex-block"),
|
||||
multiCodec("codex-slot-root"),
|
||||
multiCodec("codex-proving-root"),
|
||||
multiCodec("codex-slot-cell"),
|
||||
]
|
||||
|
||||
proc initCidCodeTable(): Table[int, MultiCodec] {.compileTime.} =
|
||||
for item in ContentIdsList:
|
||||
result[int(item)] = item
|
||||
|
||||
const
|
||||
CodeContentIds = initCidCodeTable()
|
||||
const CodeContentIds = initCidCodeTable()
|
||||
|
||||
template orError*(exp: untyped, err: untyped): untyped =
|
||||
(exp.mapErr do (_: auto) -> auto: err)
|
||||
exp.mapErr do(_: auto) -> auto:
|
||||
err
|
||||
|
||||
proc decode(data: openArray[byte]): Result[Cid, CidError] =
|
||||
if len(data) == 34 and data[0] == 0x12'u8 and data[1] == 0x20'u8:
|
||||
ok(Cid(
|
||||
cidver: CIDv0,
|
||||
mcodec: multiCodec("dag-pb"),
|
||||
hpos: 0,
|
||||
data: initVBuffer(data)))
|
||||
ok(
|
||||
Cid(cidver: CIDv0, mcodec: multiCodec("dag-pb"), hpos: 0, data: initVBuffer(data))
|
||||
)
|
||||
else:
|
||||
var version, codec: uint64
|
||||
var res, offset: int
|
||||
@@ -98,21 +110,18 @@ proc decode(data: openArray[byte]): Result[Cid, CidError] =
|
||||
err(CidError.Incorrect)
|
||||
else:
|
||||
offset += res
|
||||
var mcodec = CodeContentIds.getOrDefault(cast[int](codec),
|
||||
InvalidMultiCodec)
|
||||
var mcodec =
|
||||
CodeContentIds.getOrDefault(cast[int](codec), InvalidMultiCodec)
|
||||
if mcodec == InvalidMultiCodec:
|
||||
err(CidError.Incorrect)
|
||||
else:
|
||||
if not MultiHash.validate(vb.buffer.toOpenArray(vb.offset,
|
||||
vb.buffer.high)):
|
||||
if not MultiHash.validate(
|
||||
vb.buffer.toOpenArray(vb.offset, vb.buffer.high)
|
||||
):
|
||||
err(CidError.Incorrect)
|
||||
else:
|
||||
vb.finish()
|
||||
ok(Cid(
|
||||
cidver: CIDv1,
|
||||
mcodec: mcodec,
|
||||
hpos: offset,
|
||||
data: vb))
|
||||
ok(Cid(cidver: CIDv1, mcodec: mcodec, hpos: offset, data: vb))
|
||||
|
||||
proc decode(data: openArray[char]): Result[Cid, CidError] =
|
||||
var buffer: seq[byte]
|
||||
@@ -121,7 +130,7 @@ proc decode(data: openArray[char]): Result[Cid, CidError] =
|
||||
return err(CidError.Incorrect)
|
||||
if len(data) == 46:
|
||||
if data[0] == 'Q' and data[1] == 'm':
|
||||
buffer = newSeq[byte](BTCBase58.decodedLength(len(data)))
|
||||
buffer = newSeqUninit[byte](BTCBase58.decodedLength(len(data)))
|
||||
if BTCBase58.decode(data, buffer, plen) != Base58Status.Success:
|
||||
return err(CidError.Incorrect)
|
||||
buffer.setLen(plen)
|
||||
@@ -129,7 +138,7 @@ proc decode(data: openArray[char]): Result[Cid, CidError] =
|
||||
let length = MultiBase.decodedLength(data[0], len(data))
|
||||
if length == -1:
|
||||
return err(CidError.Incorrect)
|
||||
buffer = newSeq[byte](length)
|
||||
buffer = newSeqUninit[byte](length)
|
||||
if MultiBase.decode(data, buffer, plen) != MultiBaseStatus.Success:
|
||||
return err(CidError.Incorrect)
|
||||
buffer.setLen(plen)
|
||||
@@ -172,7 +181,9 @@ proc mhash*(cid: Cid): Result[MultiHash, CidError] =
|
||||
if cid.cidver notin {CIDv0, CIDv1}:
|
||||
err(CidError.Incorrect)
|
||||
else:
|
||||
MultiHash.init(cid.data.buffer.toOpenArray(cid.hpos, cid.data.high)).orError(CidError.Incorrect)
|
||||
MultiHash.init(cid.data.buffer.toOpenArray(cid.hpos, cid.data.high)).orError(
|
||||
CidError.Incorrect
|
||||
)
|
||||
|
||||
proc contentType*(cid: Cid): Result[MultiCodec, CidError] =
|
||||
## Returns content type part of CID
|
||||
@@ -185,12 +196,15 @@ proc version*(cid: Cid): CidVersion =
|
||||
## Returns CID version
|
||||
result = cid.cidver
|
||||
|
||||
proc init*[T: char|byte](ctype: typedesc[Cid], data: openArray[T]): Result[Cid, CidError] =
|
||||
proc init*[T: char | byte](
|
||||
ctype: typedesc[Cid], data: openArray[T]
|
||||
): Result[Cid, CidError] =
|
||||
## Create new content identifier using array of bytes or string ``data``.
|
||||
decode(data)
|
||||
|
||||
proc init*(ctype: typedesc[Cid], version: CidVersion, content: MultiCodec,
|
||||
hash: MultiHash): Result[Cid, CidError] =
|
||||
proc init*(
|
||||
ctype: typedesc[Cid], version: CidVersion, content: MultiCodec, hash: MultiHash
|
||||
): Result[Cid, CidError] =
|
||||
## Create new content identifier using content type ``content`` and
|
||||
## MultiHash ``hash`` using version ``version``.
|
||||
##
|
||||
@@ -213,8 +227,7 @@ proc init*(ctype: typedesc[Cid], version: CidVersion, content: MultiCodec,
|
||||
res.data.finish()
|
||||
return ok(res)
|
||||
elif version == CIDv1:
|
||||
let mcodec = CodeContentIds.getOrDefault(cast[int](content),
|
||||
InvalidMultiCodec)
|
||||
let mcodec = CodeContentIds.getOrDefault(cast[int](content), InvalidMultiCodec)
|
||||
if mcodec == InvalidMultiCodec:
|
||||
return err(CidError.Incorrect)
|
||||
res.mcodec = mcodec
|
||||
@@ -233,11 +246,9 @@ proc `==`*(a: Cid, b: Cid): bool =
|
||||
## are equal, ``false`` otherwise.
|
||||
if a.mcodec == b.mcodec:
|
||||
var ah, bh: MultiHash
|
||||
if MultiHash.decode(
|
||||
a.data.buffer.toOpenArray(a.hpos, a.data.high), ah).isErr:
|
||||
if MultiHash.decode(a.data.buffer.toOpenArray(a.hpos, a.data.high), ah).isErr:
|
||||
return false
|
||||
if MultiHash.decode(
|
||||
b.data.buffer.toOpenArray(b.hpos, b.data.high), bh).isErr:
|
||||
if MultiHash.decode(b.data.buffer.toOpenArray(b.hpos, b.data.high), bh).isErr:
|
||||
return false
|
||||
result = (ah == bh)
|
||||
|
||||
@@ -261,12 +272,6 @@ proc write*(vb: var VBuffer, cid: Cid) {.inline.} =
|
||||
## Write CID value ``cid`` to buffer ``vb``.
|
||||
vb.writeArray(cid.data.buffer)
|
||||
|
||||
proc encode*(mbtype: typedesc[MultiBase], encoding: string,
|
||||
cid: Cid): string {.inline.} =
|
||||
## Get MultiBase encoded representation of ``cid`` using encoding
|
||||
## ``encoding``.
|
||||
result = MultiBase.encode(encoding, cid.data.buffer).tryGet()
|
||||
|
||||
proc hash*(cid: Cid): Hash {.inline.} =
|
||||
hash(cid.data.buffer)
|
||||
|
||||
@@ -276,9 +281,6 @@ proc `$`*(cid: Cid): string =
|
||||
BTCBase58.encode(cid.data.buffer)
|
||||
elif cid.cidver == CIDv1:
|
||||
let res = MultiBase.encode("base58btc", cid.data.buffer)
|
||||
if res.isOk():
|
||||
res.get()
|
||||
else:
|
||||
""
|
||||
res.get("")
|
||||
else:
|
||||
""
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2020 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[options, tables, sequtils, sets]
|
||||
import std/[tables, sequtils, sets]
|
||||
import pkg/[chronos, chronicles, metrics]
|
||||
import peerinfo,
|
||||
peerstore,
|
||||
stream/connection,
|
||||
muxers/muxer,
|
||||
utils/semaphore,
|
||||
errors
|
||||
import peerinfo, peerstore, stream/connection, muxers/muxer, utils/semaphore, errors
|
||||
|
||||
logScope:
|
||||
topics = "libp2p connmanager"
|
||||
@@ -29,17 +24,16 @@ const
|
||||
|
||||
type
|
||||
TooManyConnectionsError* = object of LPError
|
||||
|
||||
ConnProvider* = proc(): Future[Connection]
|
||||
{.gcsafe, closure, raises: [Defect].}
|
||||
AlreadyExpectingConnectionError* = object of LPError
|
||||
|
||||
ConnEventKind* {.pure.} = enum
|
||||
Connected, # A connection was made and securely upgraded - there may be
|
||||
# more than one concurrent connection thus more than one upgrade
|
||||
# event per peer.
|
||||
|
||||
Disconnected # Peer disconnected - this event is fired once per upgrade
|
||||
# when the associated connection is terminated.
|
||||
Connected
|
||||
# A connection was made and securely upgraded - there may be
|
||||
# more than one concurrent connection thus more than one upgrade
|
||||
# event per peer.
|
||||
Disconnected
|
||||
# Peer disconnected - this event is fired once per upgrade
|
||||
# when the associated connection is terminated.
|
||||
|
||||
ConnEvent* = object
|
||||
case kind*: ConnEventKind
|
||||
@@ -48,47 +42,50 @@ type
|
||||
else:
|
||||
discard
|
||||
|
||||
ConnEventHandler* =
|
||||
proc(peerId: PeerId, event: ConnEvent): Future[void]
|
||||
{.gcsafe, raises: [Defect].}
|
||||
ConnEventHandler* = proc(peerId: PeerId, event: ConnEvent): Future[void] {.
|
||||
gcsafe, async: (raises: [CancelledError])
|
||||
.}
|
||||
|
||||
PeerEventKind* {.pure.} = enum
|
||||
Left,
|
||||
Identified,
|
||||
Left
|
||||
Joined
|
||||
Identified
|
||||
|
||||
PeerEvent* = object
|
||||
case kind*: PeerEventKind
|
||||
of PeerEventKind.Joined:
|
||||
initiator*: bool
|
||||
else:
|
||||
discard
|
||||
of PeerEventKind.Joined, PeerEventKind.Identified:
|
||||
initiator*: bool
|
||||
else:
|
||||
discard
|
||||
|
||||
PeerEventHandler* =
|
||||
proc(peerId: PeerId, event: PeerEvent): Future[void] {.gcsafe, raises: [Defect].}
|
||||
|
||||
MuxerHolder = object
|
||||
muxer: Muxer
|
||||
handle: Future[void]
|
||||
PeerEventHandler* = proc(peerId: PeerId, event: PeerEvent): Future[void] {.
|
||||
gcsafe, async: (raises: [CancelledError])
|
||||
.}
|
||||
|
||||
ConnManager* = ref object of RootObj
|
||||
maxConnsPerPeer: int
|
||||
inSema*: AsyncSemaphore
|
||||
outSema*: AsyncSemaphore
|
||||
conns: Table[PeerId, HashSet[Connection]]
|
||||
muxed: Table[Connection, MuxerHolder]
|
||||
muxed: Table[PeerId, seq[Muxer]]
|
||||
connEvents: array[ConnEventKind, OrderedSet[ConnEventHandler]]
|
||||
peerEvents: array[PeerEventKind, OrderedSet[PeerEventHandler]]
|
||||
expectedConnectionsOverLimit*: Table[(PeerId, Direction), Future[Muxer]]
|
||||
peerStore*: PeerStore
|
||||
|
||||
ConnectionSlot* = object
|
||||
connManager: ConnManager
|
||||
direction: Direction
|
||||
|
||||
proc newTooManyConnectionsError(): ref TooManyConnectionsError {.inline.} =
|
||||
result = newException(TooManyConnectionsError, "Too many connections")
|
||||
|
||||
proc new*(C: type ConnManager,
|
||||
maxConnsPerPeer = MaxConnectionsPerPeer,
|
||||
maxConnections = MaxConnections,
|
||||
maxIn = -1,
|
||||
maxOut = -1): ConnManager =
|
||||
proc new*(
|
||||
C: type ConnManager,
|
||||
maxConnsPerPeer = MaxConnectionsPerPeer,
|
||||
maxConnections = MaxConnections,
|
||||
maxIn = -1,
|
||||
maxOut = -1,
|
||||
): ConnManager =
|
||||
var inSema, outSema: AsyncSemaphore
|
||||
if maxIn > 0 or maxOut > 0:
|
||||
inSema = newAsyncSemaphore(maxIn)
|
||||
@@ -99,44 +96,38 @@ proc new*(C: type ConnManager,
|
||||
else:
|
||||
raiseAssert "Invalid connection counts!"
|
||||
|
||||
C(maxConnsPerPeer: maxConnsPerPeer,
|
||||
inSema: inSema,
|
||||
outSema: outSema)
|
||||
C(maxConnsPerPeer: maxConnsPerPeer, inSema: inSema, outSema: outSema)
|
||||
|
||||
proc connCount*(c: ConnManager, peerId: PeerId): int =
|
||||
c.conns.getOrDefault(peerId).len
|
||||
c.muxed.getOrDefault(peerId).len
|
||||
|
||||
proc addConnEventHandler*(c: ConnManager,
|
||||
handler: ConnEventHandler,
|
||||
kind: ConnEventKind) =
|
||||
proc connectedPeers*(c: ConnManager, dir: Direction): seq[PeerId] =
|
||||
var peers = newSeq[PeerId]()
|
||||
for peerId, mux in c.muxed:
|
||||
if mux.anyIt(it.connection.dir == dir):
|
||||
peers.add(peerId)
|
||||
return peers
|
||||
|
||||
proc getConnections*(c: ConnManager): Table[PeerId, seq[Muxer]] =
|
||||
return c.muxed
|
||||
|
||||
proc addConnEventHandler*(
|
||||
c: ConnManager, handler: ConnEventHandler, kind: ConnEventKind
|
||||
) =
|
||||
## Add peer event handler - handlers must not raise exceptions!
|
||||
##
|
||||
if isNil(handler):
|
||||
return
|
||||
c.connEvents[kind].incl(handler)
|
||||
|
||||
try:
|
||||
if isNil(handler): return
|
||||
c.connEvents[kind].incl(handler)
|
||||
except Exception as exc:
|
||||
# TODO: there is an Exception being raised
|
||||
# somewhere in the depths of the std.
|
||||
# Might be related to https://github.com/nim-lang/Nim/issues/17382
|
||||
proc removeConnEventHandler*(
|
||||
c: ConnManager, handler: ConnEventHandler, kind: ConnEventKind
|
||||
) =
|
||||
c.connEvents[kind].excl(handler)
|
||||
|
||||
raiseAssert exc.msg
|
||||
|
||||
proc removeConnEventHandler*(c: ConnManager,
|
||||
handler: ConnEventHandler,
|
||||
kind: ConnEventKind) =
|
||||
try:
|
||||
c.connEvents[kind].excl(handler)
|
||||
except Exception as exc:
|
||||
# TODO: there is an Exception being raised
|
||||
# somewhere in the depths of the std.
|
||||
# Might be related to https://github.com/nim-lang/Nim/issues/17382
|
||||
|
||||
raiseAssert exc.msg
|
||||
|
||||
proc triggerConnEvent*(c: ConnManager,
|
||||
peerId: PeerId,
|
||||
event: ConnEvent) {.async, gcsafe.} =
|
||||
proc triggerConnEvent*(
|
||||
c: ConnManager, peerId: PeerId, event: ConnEvent
|
||||
) {.async: (raises: [CancelledError]).} =
|
||||
try:
|
||||
trace "About to trigger connection events", peer = peerId
|
||||
if c.connEvents[event.kind].len() > 0:
|
||||
@@ -149,54 +140,32 @@ proc triggerConnEvent*(c: ConnManager,
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
warn "Exception in triggerConnEvents",
|
||||
msg = exc.msg, peer = peerId, event = $event
|
||||
warn "Exception in triggerConnEvent",
|
||||
description = exc.msg, peer = peerId, event = $event
|
||||
|
||||
proc addPeerEventHandler*(c: ConnManager,
|
||||
handler: PeerEventHandler,
|
||||
kind: PeerEventKind) =
|
||||
proc addPeerEventHandler*(
|
||||
c: ConnManager, handler: PeerEventHandler, kind: PeerEventKind
|
||||
) =
|
||||
## Add peer event handler - handlers must not raise exceptions!
|
||||
##
|
||||
|
||||
if isNil(handler): return
|
||||
try:
|
||||
c.peerEvents[kind].incl(handler)
|
||||
except Exception as exc:
|
||||
# TODO: there is an Exception being raised
|
||||
# somewhere in the depths of the std.
|
||||
# Might be related to https://github.com/nim-lang/Nim/issues/17382
|
||||
if isNil(handler):
|
||||
return
|
||||
c.peerEvents[kind].incl(handler)
|
||||
|
||||
raiseAssert exc.msg
|
||||
|
||||
proc removePeerEventHandler*(c: ConnManager,
|
||||
handler: PeerEventHandler,
|
||||
kind: PeerEventKind) =
|
||||
try:
|
||||
c.peerEvents[kind].excl(handler)
|
||||
except Exception as exc:
|
||||
# TODO: there is an Exception being raised
|
||||
# somewhere in the depths of the std.
|
||||
# Might be related to https://github.com/nim-lang/Nim/issues/17382
|
||||
|
||||
raiseAssert exc.msg
|
||||
|
||||
proc triggerPeerEvents*(c: ConnManager,
|
||||
peerId: PeerId,
|
||||
event: PeerEvent) {.async, gcsafe.} =
|
||||
proc removePeerEventHandler*(
|
||||
c: ConnManager, handler: PeerEventHandler, kind: PeerEventKind
|
||||
) =
|
||||
c.peerEvents[kind].excl(handler)
|
||||
|
||||
proc triggerPeerEvents*(
|
||||
c: ConnManager, peerId: PeerId, event: PeerEvent
|
||||
) {.async: (raises: [CancelledError]).} =
|
||||
trace "About to trigger peer events", peer = peerId
|
||||
if c.peerEvents[event.kind].len == 0:
|
||||
return
|
||||
|
||||
try:
|
||||
let count = c.connCount(peerId)
|
||||
if event.kind == PeerEventKind.Joined and count != 1:
|
||||
trace "peer already joined", peer = peerId, event = $event
|
||||
return
|
||||
elif event.kind == PeerEventKind.Left and count != 0:
|
||||
trace "peer still connected or already left", peer = peerId, event = $event
|
||||
return
|
||||
|
||||
trace "triggering peer events", peer = peerId, event = $event
|
||||
|
||||
var peerEvents: seq[Future[void]]
|
||||
@@ -207,20 +176,29 @@ proc triggerPeerEvents*(c: ConnManager,
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except CatchableError as exc: # handlers should not raise!
|
||||
warn "Exception in triggerPeerEvents", exc = exc.msg, peer = peerId
|
||||
warn "Exception in triggerPeerEvents", description = exc.msg, peer = peerId
|
||||
|
||||
proc contains*(c: ConnManager, conn: Connection): bool =
|
||||
## checks if a connection is being tracked by the
|
||||
## connection manager
|
||||
##
|
||||
proc expectConnection*(
|
||||
c: ConnManager, p: PeerId, dir: Direction
|
||||
): Future[Muxer] {.async: (raises: [AlreadyExpectingConnectionError, CancelledError]).} =
|
||||
## Wait for a peer to connect to us. This will bypass the `MaxConnectionsPerPeer`
|
||||
let key = (p, dir)
|
||||
if key in c.expectedConnectionsOverLimit:
|
||||
raise newException(
|
||||
AlreadyExpectingConnectionError,
|
||||
"Already expecting an incoming connection from that peer: " & shortLog(p),
|
||||
)
|
||||
|
||||
if isNil(conn):
|
||||
return
|
||||
let future = Future[Muxer].Raising([CancelledError]).init()
|
||||
c.expectedConnectionsOverLimit[key] = future
|
||||
|
||||
return conn in c.conns.getOrDefault(conn.peerId)
|
||||
try:
|
||||
return await future
|
||||
finally:
|
||||
c.expectedConnectionsOverLimit.del(key)
|
||||
|
||||
proc contains*(c: ConnManager, peerId: PeerId): bool =
|
||||
peerId in c.conns
|
||||
peerId in c.muxed
|
||||
|
||||
proc contains*(c: ConnManager, muxer: Muxer): bool =
|
||||
## checks if a muxer is being tracked by the connection
|
||||
@@ -228,356 +206,230 @@ proc contains*(c: ConnManager, muxer: Muxer): bool =
|
||||
##
|
||||
|
||||
if isNil(muxer):
|
||||
return
|
||||
return false
|
||||
|
||||
let conn = muxer.connection
|
||||
if conn notin c:
|
||||
return
|
||||
return muxer in c.muxed.getOrDefault(conn.peerId)
|
||||
|
||||
if conn notin c.muxed:
|
||||
return
|
||||
proc closeMuxer(muxer: Muxer) {.async: (raises: [CancelledError]).} =
|
||||
trace "Cleaning up muxer", m = muxer
|
||||
|
||||
return muxer == c.muxed.getOrDefault(conn).muxer
|
||||
|
||||
proc closeMuxerHolder(muxerHolder: MuxerHolder) {.async.} =
|
||||
trace "Cleaning up muxer", m = muxerHolder.muxer
|
||||
|
||||
await muxerHolder.muxer.close()
|
||||
if not(isNil(muxerHolder.handle)):
|
||||
await muxer.close()
|
||||
if not (isNil(muxer.handler)):
|
||||
try:
|
||||
await muxerHolder.handle # TODO noraises?
|
||||
await muxer.handler
|
||||
except CatchableError as exc:
|
||||
trace "Exception in close muxer handler", exc = exc.msg
|
||||
trace "Cleaned up muxer", m = muxerHolder.muxer
|
||||
|
||||
proc delConn(c: ConnManager, conn: Connection) =
|
||||
let peerId = conn.peerId
|
||||
c.conns.withValue(peerId, peerConns):
|
||||
peerConns[].excl(conn)
|
||||
|
||||
if peerConns[].len == 0:
|
||||
c.conns.del(peerId) # invalidates `peerConns`
|
||||
|
||||
libp2p_peers.set(c.conns.len.int64)
|
||||
trace "Removed connection", conn
|
||||
|
||||
proc cleanupConn(c: ConnManager, conn: Connection) {.async.} =
|
||||
## clean connection's resources such as muxers and streams
|
||||
|
||||
if isNil(conn):
|
||||
trace "Wont cleanup a nil connection"
|
||||
return
|
||||
|
||||
# Remove connection from all tables without async breaks
|
||||
var muxer = some(MuxerHolder())
|
||||
if not c.muxed.pop(conn, muxer.get()):
|
||||
muxer = none(MuxerHolder)
|
||||
|
||||
delConn(c, conn)
|
||||
trace "Exception in close muxer handler", description = exc.msg
|
||||
trace "Cleaned up muxer", m = muxer
|
||||
|
||||
proc muxCleanup(c: ConnManager, mux: Muxer) {.async: (raises: []).} =
|
||||
try:
|
||||
if muxer.isSome:
|
||||
await closeMuxerHolder(muxer.get())
|
||||
finally:
|
||||
await conn.close()
|
||||
trace "Triggering disconnect events", mux
|
||||
let peerId = mux.connection.peerId
|
||||
|
||||
trace "Connection cleaned up", conn
|
||||
let muxers = c.muxed.getOrDefault(peerId).filterIt(it != mux)
|
||||
if muxers.len > 0:
|
||||
c.muxed[peerId] = muxers
|
||||
else:
|
||||
c.muxed.del(peerId)
|
||||
libp2p_peers.set(c.muxed.len.int64)
|
||||
await c.triggerPeerEvents(peerId, PeerEvent(kind: PeerEventKind.Left))
|
||||
|
||||
proc onConnUpgraded(c: ConnManager, conn: Connection) {.async.} =
|
||||
try:
|
||||
trace "Triggering connect events", conn
|
||||
conn.upgrade()
|
||||
if not (c.peerStore.isNil):
|
||||
c.peerStore.cleanup(peerId)
|
||||
|
||||
let peerId = conn.peerId
|
||||
await c.triggerPeerEvents(
|
||||
peerId, PeerEvent(kind: PeerEventKind.Joined, initiator: conn.dir == Direction.Out))
|
||||
|
||||
await c.triggerConnEvent(
|
||||
peerId, ConnEvent(kind: ConnEventKind.Connected, incoming: conn.dir == Direction.In))
|
||||
await c.triggerConnEvent(peerId, ConnEvent(kind: ConnEventKind.Disconnected))
|
||||
except CatchableError as exc:
|
||||
# This is top-level procedure which will work as separate task, so it
|
||||
# do not need to propagate CancelledError and should handle other errors
|
||||
warn "Unexpected exception in switch peer connection cleanup",
|
||||
conn, msg = exc.msg
|
||||
warn "Unexpected exception peer cleanup handler", mux, description = exc.msg
|
||||
|
||||
proc peerCleanup(c: ConnManager, conn: Connection) {.async.} =
|
||||
try:
|
||||
trace "Triggering disconnect events", conn
|
||||
let peerId = conn.peerId
|
||||
await c.triggerConnEvent(
|
||||
peerId, ConnEvent(kind: ConnEventKind.Disconnected))
|
||||
await c.triggerPeerEvents(peerId, PeerEvent(kind: PeerEventKind.Left))
|
||||
except CatchableError as exc:
|
||||
# This is top-level procedure which will work as separate task, so it
|
||||
# do not need to propagate CancelledError and should handle other errors
|
||||
warn "Unexpected exception peer cleanup handler",
|
||||
conn, msg = exc.msg
|
||||
|
||||
proc onClose(c: ConnManager, conn: Connection) {.async.} =
|
||||
proc onClose(c: ConnManager, mux: Muxer) {.async: (raises: []).} =
|
||||
## connection close even handler
|
||||
##
|
||||
## triggers the connections resource cleanup
|
||||
##
|
||||
try:
|
||||
await conn.join()
|
||||
trace "Connection closed, cleaning up", conn
|
||||
await c.cleanupConn(conn)
|
||||
except CancelledError:
|
||||
# This is top-level procedure which will work as separate task, so it
|
||||
# do not need to propagate CancelledError.
|
||||
debug "Unexpected cancellation in connection manager's cleanup", conn
|
||||
await mux.connection.join()
|
||||
trace "Connection closed, cleaning up", mux
|
||||
except CatchableError as exc:
|
||||
debug "Unexpected exception in connection manager's cleanup",
|
||||
errMsg = exc.msg, conn
|
||||
description = exc.msg, mux
|
||||
finally:
|
||||
trace "Triggering peerCleanup", conn
|
||||
asyncSpawn c.peerCleanup(conn)
|
||||
await c.muxCleanup(mux)
|
||||
|
||||
proc selectConn*(c: ConnManager,
|
||||
peerId: PeerId,
|
||||
dir: Direction): Connection =
|
||||
proc selectMuxer*(c: ConnManager, peerId: PeerId, dir: Direction): Muxer =
|
||||
## Select a connection for the provided peer and direction
|
||||
##
|
||||
let conns = toSeq(
|
||||
c.conns.getOrDefault(peerId))
|
||||
.filterIt( it.dir == dir )
|
||||
let conns = toSeq(c.muxed.getOrDefault(peerId)).filterIt(it.connection.dir == dir)
|
||||
|
||||
if conns.len > 0:
|
||||
return conns[0]
|
||||
|
||||
proc selectConn*(c: ConnManager, peerId: PeerId): Connection =
|
||||
proc selectMuxer*(c: ConnManager, peerId: PeerId): Muxer =
|
||||
## Select a connection for the provided giving priority
|
||||
## to outgoing connections
|
||||
##
|
||||
|
||||
var conn = c.selectConn(peerId, Direction.Out)
|
||||
if isNil(conn):
|
||||
conn = c.selectConn(peerId, Direction.In)
|
||||
if isNil(conn):
|
||||
var mux = c.selectMuxer(peerId, Direction.Out)
|
||||
if isNil(mux):
|
||||
mux = c.selectMuxer(peerId, Direction.In)
|
||||
if isNil(mux):
|
||||
trace "connection not found", peerId
|
||||
return mux
|
||||
|
||||
return conn
|
||||
|
||||
proc selectMuxer*(c: ConnManager, conn: Connection): Muxer =
|
||||
## select the muxer for the provided connection
|
||||
##
|
||||
|
||||
if isNil(conn):
|
||||
return
|
||||
|
||||
if conn in c.muxed:
|
||||
return c.muxed.getOrDefault(conn).muxer
|
||||
else:
|
||||
debug "no muxer for connection", conn
|
||||
|
||||
proc storeConn*(c: ConnManager, conn: Connection)
|
||||
{.raises: [Defect, LPError].} =
|
||||
## store a connection
|
||||
##
|
||||
|
||||
if isNil(conn):
|
||||
raise newException(LPError, "Connection cannot be nil")
|
||||
|
||||
if conn.closed or conn.atEof:
|
||||
raise newException(LPError, "Connection closed or EOF")
|
||||
|
||||
let peerId = conn.peerId
|
||||
if c.conns.getOrDefault(peerId).len > c.maxConnsPerPeer:
|
||||
debug "Too many connections for peer",
|
||||
conn, conns = c.conns.getOrDefault(peerId).len
|
||||
|
||||
raise newTooManyConnectionsError()
|
||||
|
||||
c.conns.mgetOrPut(peerId, HashSet[Connection]()).incl(conn)
|
||||
libp2p_peers.set(c.conns.len.int64)
|
||||
|
||||
# Launch on close listener
|
||||
# All the errors are handled inside `onClose()` procedure.
|
||||
asyncSpawn c.onClose(conn)
|
||||
|
||||
trace "Stored connection",
|
||||
conn, direction = $conn.dir, connections = c.conns.len
|
||||
|
||||
proc trackConn(c: ConnManager,
|
||||
provider: ConnProvider,
|
||||
sema: AsyncSemaphore):
|
||||
Future[Connection] {.async.} =
|
||||
var conn: Connection
|
||||
try:
|
||||
conn = await provider()
|
||||
|
||||
if isNil(conn):
|
||||
return
|
||||
|
||||
trace "Got connection", conn
|
||||
|
||||
proc semaphoreMonitor() {.async.} =
|
||||
try:
|
||||
await conn.join()
|
||||
except CatchableError as exc:
|
||||
trace "Exception in semaphore monitor, ignoring", exc = exc.msg
|
||||
|
||||
sema.release()
|
||||
|
||||
asyncSpawn semaphoreMonitor()
|
||||
except CatchableError as exc:
|
||||
trace "Exception tracking connection", exc = exc.msg
|
||||
if not isNil(conn):
|
||||
await conn.close()
|
||||
|
||||
raise exc
|
||||
|
||||
return conn
|
||||
|
||||
proc trackIncomingConn*(c: ConnManager,
|
||||
provider: ConnProvider):
|
||||
Future[Connection] {.async.} =
|
||||
## await for a connection slot before attempting
|
||||
## to call the connection provider
|
||||
##
|
||||
|
||||
var conn: Connection
|
||||
try:
|
||||
trace "Tracking incoming connection"
|
||||
await c.inSema.acquire()
|
||||
conn = await c.trackConn(provider, c.inSema)
|
||||
if isNil(conn):
|
||||
trace "Couldn't acquire connection, releasing semaphore slot", dir = $Direction.In
|
||||
c.inSema.release()
|
||||
|
||||
return conn
|
||||
except CatchableError as exc:
|
||||
trace "Exception tracking connection", exc = exc.msg
|
||||
c.inSema.release()
|
||||
raise exc
|
||||
|
||||
proc trackOutgoingConn*(c: ConnManager,
|
||||
provider: ConnProvider,
|
||||
forceDial = false):
|
||||
Future[Connection] {.async.} =
|
||||
## try acquiring a connection if all slots
|
||||
## are already taken, raise TooManyConnectionsError
|
||||
## exception
|
||||
##
|
||||
|
||||
trace "Tracking outgoing connection", count = c.outSema.count,
|
||||
max = c.outSema.size
|
||||
|
||||
if forceDial:
|
||||
c.outSema.forceAcquire()
|
||||
elif not c.outSema.tryAcquire():
|
||||
trace "Too many outgoing connections!", count = c.outSema.count,
|
||||
max = c.outSema.size
|
||||
raise newTooManyConnectionsError()
|
||||
|
||||
var conn: Connection
|
||||
try:
|
||||
conn = await c.trackConn(provider, c.outSema)
|
||||
if isNil(conn):
|
||||
trace "Couldn't acquire connection, releasing semaphore slot", dir = $Direction.Out
|
||||
c.outSema.release()
|
||||
|
||||
return conn
|
||||
except CatchableError as exc:
|
||||
trace "Exception tracking connection", exc = exc.msg
|
||||
c.outSema.release()
|
||||
raise exc
|
||||
|
||||
proc storeMuxer*(c: ConnManager,
|
||||
muxer: Muxer,
|
||||
handle: Future[void] = nil)
|
||||
{.raises: [Defect, CatchableError].} =
|
||||
proc storeMuxer*(c: ConnManager, muxer: Muxer) {.raises: [LPError].} =
|
||||
## store the connection and muxer
|
||||
##
|
||||
|
||||
if isNil(muxer):
|
||||
raise newException(CatchableError, "muxer cannot be nil")
|
||||
raise newException(LPError, "muxer cannot be nil")
|
||||
|
||||
if isNil(muxer.connection):
|
||||
raise newException(CatchableError, "muxer's connection cannot be nil")
|
||||
raise newException(LPError, "muxer's connection cannot be nil")
|
||||
|
||||
if muxer.connection notin c:
|
||||
raise newException(CatchableError, "cant add muxer for untracked connection")
|
||||
if muxer.connection.closed or muxer.connection.atEof:
|
||||
raise newException(LPError, "Connection closed or EOF")
|
||||
|
||||
c.muxed[muxer.connection] = MuxerHolder(
|
||||
muxer: muxer,
|
||||
handle: handle)
|
||||
let
|
||||
peerId = muxer.connection.peerId
|
||||
dir = muxer.connection.dir
|
||||
|
||||
trace "Stored muxer",
|
||||
muxer, handle = not handle.isNil, connections = c.conns.len
|
||||
# we use getOrDefault in the if below instead of [] to avoid the KeyError
|
||||
if c.muxed.getOrDefault(peerId).len > c.maxConnsPerPeer:
|
||||
let key = (peerId, dir)
|
||||
let expectedConn = c.expectedConnectionsOverLimit.getOrDefault(key)
|
||||
if expectedConn != nil and not expectedConn.finished:
|
||||
expectedConn.complete(muxer)
|
||||
else:
|
||||
debug "Too many connections for peer",
|
||||
conns = c.muxed.getOrDefault(peerId).len, peerId, dir
|
||||
|
||||
asyncSpawn c.onConnUpgraded(muxer.connection)
|
||||
raise newTooManyConnectionsError()
|
||||
|
||||
proc getStream*(c: ConnManager,
|
||||
peerId: PeerId,
|
||||
dir: Direction): Future[Connection] {.async, gcsafe.} =
|
||||
## get a muxed stream for the provided peer
|
||||
## with the given direction
|
||||
var newPeer = false
|
||||
c.muxed.withValue(peerId, muxers):
|
||||
doAssert muxers[].len > 0
|
||||
doAssert muxer notin muxers[]
|
||||
muxers[].add(muxer)
|
||||
do:
|
||||
c.muxed[peerId] = @[muxer]
|
||||
newPeer = true
|
||||
libp2p_peers.set(c.muxed.len.int64)
|
||||
|
||||
asyncSpawn c.triggerConnEvent(
|
||||
peerId, ConnEvent(kind: ConnEventKind.Connected, incoming: dir == Direction.In)
|
||||
)
|
||||
|
||||
if newPeer:
|
||||
asyncSpawn c.triggerPeerEvents(
|
||||
peerId, PeerEvent(kind: PeerEventKind.Joined, initiator: dir == Direction.Out)
|
||||
)
|
||||
|
||||
asyncSpawn c.onClose(muxer)
|
||||
|
||||
trace "Stored muxer", muxer, direction = $muxer.connection.dir, peers = c.muxed.len
|
||||
|
||||
proc getIncomingSlot*(
|
||||
c: ConnManager
|
||||
): Future[ConnectionSlot] {.async: (raises: [CancelledError]).} =
|
||||
await c.inSema.acquire()
|
||||
return ConnectionSlot(connManager: c, direction: In)
|
||||
|
||||
proc getOutgoingSlot*(
|
||||
c: ConnManager, forceDial = false
|
||||
): ConnectionSlot {.raises: [TooManyConnectionsError].} =
|
||||
if forceDial:
|
||||
c.outSema.forceAcquire()
|
||||
elif not c.outSema.tryAcquire():
|
||||
trace "Too many outgoing connections!",
|
||||
available = c.outSema.count, max = c.outSema.size
|
||||
raise newTooManyConnectionsError()
|
||||
return ConnectionSlot(connManager: c, direction: Out)
|
||||
|
||||
func semaphore(c: ConnManager, dir: Direction): AsyncSemaphore {.inline.} =
|
||||
return if dir == In: c.inSema else: c.outSema
|
||||
|
||||
proc slotsAvailable*(c: ConnManager, dir: Direction): int =
|
||||
return semaphore(c, dir).count
|
||||
|
||||
proc release*(cs: ConnectionSlot) =
|
||||
semaphore(cs.connManager, cs.direction).release()
|
||||
|
||||
proc trackConnection*(cs: ConnectionSlot, conn: Connection) =
|
||||
if isNil(conn):
|
||||
cs.release()
|
||||
return
|
||||
|
||||
proc semaphoreMonitor() {.async: (raises: [CancelledError]).} =
|
||||
try:
|
||||
await conn.join()
|
||||
except CatchableError as exc:
|
||||
trace "Exception in semaphore monitor, ignoring", description = exc.msg
|
||||
|
||||
cs.release()
|
||||
|
||||
asyncSpawn semaphoreMonitor()
|
||||
|
||||
proc trackMuxer*(cs: ConnectionSlot, mux: Muxer) =
|
||||
if isNil(mux):
|
||||
cs.release()
|
||||
return
|
||||
cs.trackConnection(mux.connection)
|
||||
|
||||
proc getStream*(
|
||||
c: ConnManager, muxer: Muxer
|
||||
): Future[Connection] {.async: (raises: [LPStreamError, MuxerError, CancelledError]).} =
|
||||
## get a muxed stream for the passed muxer
|
||||
##
|
||||
|
||||
let muxer = c.selectMuxer(c.selectConn(peerId, dir))
|
||||
if not(isNil(muxer)):
|
||||
if not (isNil(muxer)):
|
||||
return await muxer.newStream()
|
||||
|
||||
proc getStream*(c: ConnManager,
|
||||
peerId: PeerId): Future[Connection] {.async, gcsafe.} =
|
||||
proc getStream*(
|
||||
c: ConnManager, peerId: PeerId
|
||||
): Future[Connection] {.async: (raises: [LPStreamError, MuxerError, CancelledError]).} =
|
||||
## get a muxed stream for the passed peer from any connection
|
||||
##
|
||||
|
||||
let muxer = c.selectMuxer(c.selectConn(peerId))
|
||||
if not(isNil(muxer)):
|
||||
return await muxer.newStream()
|
||||
return await c.getStream(c.selectMuxer(peerId))
|
||||
|
||||
proc getStream*(c: ConnManager,
|
||||
conn: Connection): Future[Connection] {.async, gcsafe.} =
|
||||
## get a muxed stream for the passed connection
|
||||
proc getStream*(
|
||||
c: ConnManager, peerId: PeerId, dir: Direction
|
||||
): Future[Connection] {.async: (raises: [LPStreamError, MuxerError, CancelledError]).} =
|
||||
## get a muxed stream for the passed peer from a connection with `dir`
|
||||
##
|
||||
|
||||
let muxer = c.selectMuxer(conn)
|
||||
if not(isNil(muxer)):
|
||||
return await muxer.newStream()
|
||||
return await c.getStream(c.selectMuxer(peerId, dir))
|
||||
|
||||
proc dropPeer*(c: ConnManager, peerId: PeerId) {.async.} =
|
||||
proc dropPeer*(c: ConnManager, peerId: PeerId) {.async: (raises: [CancelledError]).} =
|
||||
## drop connections and cleanup resources for peer
|
||||
##
|
||||
trace "Dropping peer", peerId
|
||||
let conns = c.conns.getOrDefault(peerId)
|
||||
for conn in conns:
|
||||
trace "Removing connection", conn
|
||||
delConn(c, conn)
|
||||
|
||||
var muxers: seq[MuxerHolder]
|
||||
for conn in conns:
|
||||
if conn in c.muxed:
|
||||
muxers.add c.muxed[conn]
|
||||
c.muxed.del(conn)
|
||||
let muxers = c.muxed.getOrDefault(peerId)
|
||||
|
||||
for muxer in muxers:
|
||||
await closeMuxerHolder(muxer)
|
||||
|
||||
for conn in conns:
|
||||
await conn.close()
|
||||
trace "Dropped peer", peerId
|
||||
await closeMuxer(muxer)
|
||||
|
||||
trace "Peer dropped", peerId
|
||||
|
||||
proc close*(c: ConnManager) {.async.} =
|
||||
proc close*(c: ConnManager) {.async: (raises: [CancelledError]).} =
|
||||
## cleanup resources for the connection
|
||||
## manager
|
||||
##
|
||||
|
||||
trace "Closing ConnManager"
|
||||
let conns = c.conns
|
||||
c.conns.clear()
|
||||
|
||||
let muxed = c.muxed
|
||||
c.muxed.clear()
|
||||
|
||||
for _, muxer in muxed:
|
||||
await closeMuxerHolder(muxer)
|
||||
let expected = c.expectedConnectionsOverLimit
|
||||
c.expectedConnectionsOverLimit.clear()
|
||||
|
||||
for _, conns2 in conns:
|
||||
for conn in conns2:
|
||||
await conn.close()
|
||||
for _, fut in expected:
|
||||
await fut.cancelAndWait()
|
||||
|
||||
for _, muxers in muxed:
|
||||
for mux in muxers:
|
||||
await closeMuxer(mux)
|
||||
|
||||
trace "Closed ConnManager"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2020 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module integrates BearSSL ChaCha20+Poly1305
|
||||
##
|
||||
@@ -15,16 +15,11 @@
|
||||
|
||||
# RFC @ https://tools.ietf.org/html/rfc7539
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import bearssl
|
||||
|
||||
# have to do this due to a nim bug and raises[] on callbacks
|
||||
# https://github.com/nim-lang/Nim/issues/13905
|
||||
proc ourPoly1305CtmulRun*(key: pointer; iv: pointer; data: pointer; len: int;
|
||||
aad: pointer; aadLen: int; tag: pointer; ichacha: pointer;
|
||||
encrypt: cint) {.cdecl, importc: "br_poly1305_ctmul_run",
|
||||
header: "bearssl_block.h".}
|
||||
import bearssl/blockx
|
||||
from stew/assign2 import assign
|
||||
from stew/ptrops import baseAddr
|
||||
|
||||
const
|
||||
ChaChaPolyKeySize = 32
|
||||
@@ -39,62 +34,70 @@ type
|
||||
|
||||
proc intoChaChaPolyKey*(s: openArray[byte]): ChaChaPolyKey =
|
||||
assert s.len == ChaChaPolyKeySize
|
||||
copyMem(addr result[0], unsafeAddr s[0], ChaChaPolyKeySize)
|
||||
assign(result, s)
|
||||
|
||||
proc intoChaChaPolyNonce*(s: openArray[byte]): ChaChaPolyNonce =
|
||||
assert s.len == ChaChaPolyNonceSize
|
||||
copyMem(addr result[0], unsafeAddr s[0], ChaChaPolyNonceSize)
|
||||
assign(result, s)
|
||||
|
||||
proc intoChaChaPolyTag*(s: openArray[byte]): ChaChaPolyTag =
|
||||
assert s.len == ChaChaPolyTagSize
|
||||
copyMem(addr result[0], unsafeAddr s[0], ChaChaPolyTagSize)
|
||||
assign(result, s)
|
||||
|
||||
# bearssl allows us to use optimized versions
|
||||
# this is reconciled at runtime
|
||||
# we do this in the global scope / module init
|
||||
|
||||
proc encrypt*(_: type[ChaChaPoly],
|
||||
key: ChaChaPolyKey,
|
||||
nonce: ChaChaPolyNonce,
|
||||
tag: var ChaChaPolyTag,
|
||||
data: var openArray[byte],
|
||||
aad: openArray[byte]) =
|
||||
let
|
||||
ad = if aad.len > 0:
|
||||
unsafeAddr aad[0]
|
||||
else:
|
||||
nil
|
||||
proc encrypt*(
|
||||
_: type[ChaChaPoly],
|
||||
key: ChaChaPolyKey,
|
||||
nonce: ChaChaPolyNonce,
|
||||
tag: var ChaChaPolyTag,
|
||||
data: var openArray[byte],
|
||||
aad: openArray[byte],
|
||||
) =
|
||||
let ad =
|
||||
if aad.len > 0:
|
||||
unsafeAddr aad[0]
|
||||
else:
|
||||
nil
|
||||
|
||||
ourPoly1305CtmulRun(
|
||||
poly1305CtmulRun(
|
||||
unsafeAddr key[0],
|
||||
unsafeAddr nonce[0],
|
||||
addr data[0],
|
||||
data.len,
|
||||
baseAddr(data),
|
||||
uint(data.len),
|
||||
ad,
|
||||
aad.len,
|
||||
addr tag[0],
|
||||
chacha20CtRun,
|
||||
#[encrypt]# 1.cint)
|
||||
uint(aad.len),
|
||||
baseAddr(tag),
|
||||
# cast is required to workaround https://github.com/nim-lang/Nim/issues/13905
|
||||
cast[Chacha20Run](chacha20CtRun), #[encrypt]#
|
||||
1.cint,
|
||||
)
|
||||
|
||||
proc decrypt*(_: type[ChaChaPoly],
|
||||
key: ChaChaPolyKey,
|
||||
nonce: ChaChaPolyNonce,
|
||||
tag: var ChaChaPolyTag,
|
||||
data: var openArray[byte],
|
||||
aad: openArray[byte]) =
|
||||
let
|
||||
ad = if aad.len > 0:
|
||||
unsafeAddr aad[0]
|
||||
else:
|
||||
nil
|
||||
proc decrypt*(
|
||||
_: type[ChaChaPoly],
|
||||
key: ChaChaPolyKey,
|
||||
nonce: ChaChaPolyNonce,
|
||||
tag: var ChaChaPolyTag,
|
||||
data: var openArray[byte],
|
||||
aad: openArray[byte],
|
||||
) =
|
||||
let ad =
|
||||
if aad.len > 0:
|
||||
unsafeAddr aad[0]
|
||||
else:
|
||||
nil
|
||||
|
||||
ourPoly1305CtmulRun(
|
||||
poly1305CtmulRun(
|
||||
unsafeAddr key[0],
|
||||
unsafeAddr nonce[0],
|
||||
addr data[0],
|
||||
data.len,
|
||||
baseAddr(data),
|
||||
uint(data.len),
|
||||
ad,
|
||||
aad.len,
|
||||
addr tag[0],
|
||||
chacha20CtRun,
|
||||
#[decrypt]# 0.cint)
|
||||
uint(aad.len),
|
||||
baseAddr(tag),
|
||||
# cast is required to workaround https://github.com/nim-lang/Nim/issues/13905
|
||||
cast[Chacha20Run](chacha20CtRun), #[decrypt]#
|
||||
0.cint,
|
||||
)
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implements Public Key and Private Key interface for libp2p.
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
from strutils import split, strip, cmpIgnoreCase
|
||||
import ../utils/sequninit
|
||||
|
||||
const libp2p_pki_schemes* {.strdefine.} = "rsa,ed25519,secp256k1,ecnist"
|
||||
|
||||
type
|
||||
PKScheme* = enum
|
||||
RSA = 0,
|
||||
Ed25519,
|
||||
Secp256k1,
|
||||
ECDSA
|
||||
type PKScheme* = enum
|
||||
RSA = 0
|
||||
Ed25519
|
||||
Secp256k1
|
||||
ECDSA
|
||||
|
||||
proc initSupportedSchemes(list: static string): set[PKScheme] =
|
||||
var res: set[PKScheme]
|
||||
@@ -65,31 +65,29 @@ when supported(PKScheme.Ed25519):
|
||||
import ed25519/ed25519
|
||||
when supported(PKScheme.Secp256k1):
|
||||
import secp
|
||||
when supported(PKScheme.ECDSA):
|
||||
import ecnist
|
||||
|
||||
# We are still importing `ecnist` because, it is used for SECIO handshake,
|
||||
# but it will be impossible to create ECNIST keys or import ECNIST keys.
|
||||
# These used to be declared in `crypto` itself
|
||||
export ecnist.ephemeral, ecnist.ECDHEScheme
|
||||
|
||||
import ecnist, bearssl
|
||||
import bearssl/rand, bearssl/hash as bhash
|
||||
import ../protobuf/minprotobuf, ../vbuffer, ../multihash, ../multicodec
|
||||
import nimcrypto/[rijndael, twofish, sha2, hash, hmac]
|
||||
# We use `ncrutils` for constant-time hexadecimal encoding/decoding procedures.
|
||||
import nimcrypto/utils as ncrutils
|
||||
import ../utility
|
||||
import stew/results
|
||||
export results
|
||||
import results
|
||||
export results, utility
|
||||
|
||||
# This is workaround for Nim's `import` bug
|
||||
export rijndael, twofish, sha2, hash, hmac, ncrutils
|
||||
|
||||
from strutils import split
|
||||
export rijndael, twofish, sha2, hash, hmac, ncrutils, rand
|
||||
|
||||
type
|
||||
DigestSheme* = enum
|
||||
Sha256,
|
||||
Sha256
|
||||
Sha512
|
||||
|
||||
ECDHEScheme* = EcCurveKind
|
||||
|
||||
PublicKey* = object
|
||||
case scheme*: PKScheme
|
||||
of PKScheme.RSA:
|
||||
@@ -150,36 +148,37 @@ type
|
||||
data*: seq[byte]
|
||||
|
||||
CryptoError* = enum
|
||||
KeyError,
|
||||
SigError,
|
||||
HashError,
|
||||
KeyError
|
||||
SigError
|
||||
HashError
|
||||
SchemeError
|
||||
|
||||
CryptoResult*[T] = Result[T, CryptoError]
|
||||
|
||||
template orError*(exp: untyped, err: untyped): untyped =
|
||||
(exp.mapErr do (_: auto) -> auto: err)
|
||||
exp.mapErr do(_: auto) -> auto:
|
||||
err
|
||||
|
||||
proc newRng*(): ref BrHmacDrbgContext =
|
||||
proc newRng*(): ref HmacDrbgContext =
|
||||
# You should only create one instance of the RNG per application / library
|
||||
# Ref is used so that it can be shared between components
|
||||
# TODO consider moving to bearssl
|
||||
var seeder = brPrngSeederSystem(nil)
|
||||
var seeder = prngSeederSystem(nil)
|
||||
if seeder == nil:
|
||||
return nil
|
||||
|
||||
var rng = (ref BrHmacDrbgContext)()
|
||||
brHmacDrbgInit(addr rng[], addr sha256Vtable, nil, 0)
|
||||
var rng = (ref HmacDrbgContext)()
|
||||
hmacDrbgInit(rng[], addr sha256Vtable, nil, 0)
|
||||
if seeder(addr rng.vtable) == 0:
|
||||
return nil
|
||||
rng
|
||||
|
||||
proc shuffle*[T](
|
||||
rng: ref BrHmacDrbgContext,
|
||||
x: var openArray[T]) =
|
||||
proc shuffle*[T](rng: ref HmacDrbgContext, x: var openArray[T]) =
|
||||
if x.len == 0:
|
||||
return
|
||||
|
||||
var randValues = newSeqUninitialized[byte](len(x) * 2)
|
||||
brHmacDrbgGenerate(rng[], randValues)
|
||||
var randValues = newSeqUninit[byte](len(x) * 2)
|
||||
hmacDrbgGenerate(rng[], randValues)
|
||||
|
||||
for i in countdown(x.high, 1):
|
||||
let
|
||||
@@ -187,9 +186,12 @@ proc shuffle*[T](
|
||||
y = rand mod i
|
||||
swap(x[i], x[y])
|
||||
|
||||
proc random*(T: typedesc[PrivateKey], scheme: PKScheme,
|
||||
rng: var BrHmacDrbgContext,
|
||||
bits = RsaDefaultKeySize): CryptoResult[PrivateKey] =
|
||||
proc random*(
|
||||
T: typedesc[PrivateKey],
|
||||
scheme: PKScheme,
|
||||
rng: var HmacDrbgContext,
|
||||
bits = RsaDefaultKeySize,
|
||||
): CryptoResult[PrivateKey] =
|
||||
## Generate random private key for scheme ``scheme``.
|
||||
##
|
||||
## ``bits`` is number of bits for RSA key, ``bits`` value must be in
|
||||
@@ -197,7 +199,7 @@ proc random*(T: typedesc[PrivateKey], scheme: PKScheme,
|
||||
case scheme
|
||||
of PKScheme.RSA:
|
||||
when supported(PKScheme.RSA):
|
||||
let rsakey = ? RsaPrivateKey.random(rng, bits).orError(KeyError)
|
||||
let rsakey = ?RsaPrivateKey.random(rng, bits).orError(CryptoError.KeyError)
|
||||
ok(PrivateKey(scheme: scheme, rsakey: rsakey))
|
||||
else:
|
||||
err(SchemeError)
|
||||
@@ -209,7 +211,8 @@ proc random*(T: typedesc[PrivateKey], scheme: PKScheme,
|
||||
err(SchemeError)
|
||||
of PKScheme.ECDSA:
|
||||
when supported(PKScheme.ECDSA):
|
||||
let eckey = ? ecnist.EcPrivateKey.random(Secp256r1, rng).orError(KeyError)
|
||||
let eckey =
|
||||
?ecnist.EcPrivateKey.random(Secp256r1, rng).orError(CryptoError.KeyError)
|
||||
ok(PrivateKey(scheme: scheme, eckey: eckey))
|
||||
else:
|
||||
err(SchemeError)
|
||||
@@ -220,8 +223,9 @@ proc random*(T: typedesc[PrivateKey], scheme: PKScheme,
|
||||
else:
|
||||
err(SchemeError)
|
||||
|
||||
proc random*(T: typedesc[PrivateKey], rng: var BrHmacDrbgContext,
|
||||
bits = RsaDefaultKeySize): CryptoResult[PrivateKey] =
|
||||
proc random*(
|
||||
T: typedesc[PrivateKey], rng: var HmacDrbgContext, bits = RsaDefaultKeySize
|
||||
): CryptoResult[PrivateKey] =
|
||||
## Generate random private key using default public-key cryptography scheme.
|
||||
##
|
||||
## Default public-key cryptography schemes are following order:
|
||||
@@ -235,17 +239,21 @@ proc random*(T: typedesc[PrivateKey], rng: var BrHmacDrbgContext,
|
||||
let skkey = SkPrivateKey.random(rng)
|
||||
ok(PrivateKey(scheme: PKScheme.Secp256k1, skkey: skkey))
|
||||
elif supported(PKScheme.RSA):
|
||||
let rsakey = ? RsaPrivateKey.random(rng, bits).orError(KeyError)
|
||||
let rsakey = ?RsaPrivateKey.random(rng, bits).orError(CryptoError.KeyError)
|
||||
ok(PrivateKey(scheme: PKScheme.RSA, rsakey: rsakey))
|
||||
elif supported(PKScheme.ECDSA):
|
||||
let eckey = ? ecnist.EcPrivateKey.random(Secp256r1, rng).orError(KeyError)
|
||||
let eckey =
|
||||
?ecnist.EcPrivateKey.random(Secp256r1, rng).orError(CryptoError.KeyError)
|
||||
ok(PrivateKey(scheme: PKScheme.ECDSA, eckey: eckey))
|
||||
else:
|
||||
err(SchemeError)
|
||||
|
||||
proc random*(T: typedesc[KeyPair], scheme: PKScheme,
|
||||
rng: var BrHmacDrbgContext,
|
||||
bits = RsaDefaultKeySize): CryptoResult[KeyPair] =
|
||||
proc random*(
|
||||
T: typedesc[KeyPair],
|
||||
scheme: PKScheme,
|
||||
rng: var HmacDrbgContext,
|
||||
bits = RsaDefaultKeySize,
|
||||
): CryptoResult[KeyPair] =
|
||||
## Generate random key pair for scheme ``scheme``.
|
||||
##
|
||||
## ``bits`` is number of bits for RSA key, ``bits`` value must be in
|
||||
@@ -253,39 +261,52 @@ proc random*(T: typedesc[KeyPair], scheme: PKScheme,
|
||||
case scheme
|
||||
of PKScheme.RSA:
|
||||
when supported(PKScheme.RSA):
|
||||
let pair = ? RsaKeyPair.random(rng, bits).orError(KeyError)
|
||||
ok(KeyPair(
|
||||
seckey: PrivateKey(scheme: scheme, rsakey: pair.seckey),
|
||||
pubkey: PublicKey(scheme: scheme, rsakey: pair.pubkey)))
|
||||
let pair = ?RsaKeyPair.random(rng, bits).orError(CryptoError.KeyError)
|
||||
ok(
|
||||
KeyPair(
|
||||
seckey: PrivateKey(scheme: scheme, rsakey: pair.seckey),
|
||||
pubkey: PublicKey(scheme: scheme, rsakey: pair.pubkey),
|
||||
)
|
||||
)
|
||||
else:
|
||||
err(SchemeError)
|
||||
of PKScheme.Ed25519:
|
||||
when supported(PKScheme.Ed25519):
|
||||
let pair = EdKeyPair.random(rng)
|
||||
ok(KeyPair(
|
||||
seckey: PrivateKey(scheme: scheme, edkey: pair.seckey),
|
||||
pubkey: PublicKey(scheme: scheme, edkey: pair.pubkey)))
|
||||
ok(
|
||||
KeyPair(
|
||||
seckey: PrivateKey(scheme: scheme, edkey: pair.seckey),
|
||||
pubkey: PublicKey(scheme: scheme, edkey: pair.pubkey),
|
||||
)
|
||||
)
|
||||
else:
|
||||
err(SchemeError)
|
||||
of PKScheme.ECDSA:
|
||||
when supported(PKScheme.ECDSA):
|
||||
let pair = ? EcKeyPair.random(Secp256r1, rng).orError(KeyError)
|
||||
ok(KeyPair(
|
||||
seckey: PrivateKey(scheme: scheme, eckey: pair.seckey),
|
||||
pubkey: PublicKey(scheme: scheme, eckey: pair.pubkey)))
|
||||
let pair = ?EcKeyPair.random(Secp256r1, rng).orError(CryptoError.KeyError)
|
||||
ok(
|
||||
KeyPair(
|
||||
seckey: PrivateKey(scheme: scheme, eckey: pair.seckey),
|
||||
pubkey: PublicKey(scheme: scheme, eckey: pair.pubkey),
|
||||
)
|
||||
)
|
||||
else:
|
||||
err(SchemeError)
|
||||
of PKScheme.Secp256k1:
|
||||
when supported(PKScheme.Secp256k1):
|
||||
let pair = SkKeyPair.random(rng)
|
||||
ok(KeyPair(
|
||||
seckey: PrivateKey(scheme: scheme, skkey: pair.seckey),
|
||||
pubkey: PublicKey(scheme: scheme, skkey: pair.pubkey)))
|
||||
ok(
|
||||
KeyPair(
|
||||
seckey: PrivateKey(scheme: scheme, skkey: pair.seckey),
|
||||
pubkey: PublicKey(scheme: scheme, skkey: pair.pubkey),
|
||||
)
|
||||
)
|
||||
else:
|
||||
err(SchemeError)
|
||||
|
||||
proc random*(T: typedesc[KeyPair], rng: var BrHmacDrbgContext,
|
||||
bits = RsaDefaultKeySize): CryptoResult[KeyPair] =
|
||||
proc random*(
|
||||
T: typedesc[KeyPair], rng: var HmacDrbgContext, bits = RsaDefaultKeySize
|
||||
): CryptoResult[KeyPair] =
|
||||
## Generate random private pair of keys using default public-key cryptography
|
||||
## scheme.
|
||||
##
|
||||
@@ -295,24 +316,36 @@ proc random*(T: typedesc[KeyPair], rng: var BrHmacDrbgContext,
|
||||
## So will be used first available (supported) method.
|
||||
when supported(PKScheme.Ed25519):
|
||||
let pair = EdKeyPair.random(rng)
|
||||
ok(KeyPair(
|
||||
seckey: PrivateKey(scheme: PKScheme.Ed25519, edkey: pair.seckey),
|
||||
pubkey: PublicKey(scheme: PKScheme.Ed25519, edkey: pair.pubkey)))
|
||||
ok(
|
||||
KeyPair(
|
||||
seckey: PrivateKey(scheme: PKScheme.Ed25519, edkey: pair.seckey),
|
||||
pubkey: PublicKey(scheme: PKScheme.Ed25519, edkey: pair.pubkey),
|
||||
)
|
||||
)
|
||||
elif supported(PKScheme.Secp256k1):
|
||||
let pair = SkKeyPair.random(rng)
|
||||
ok(KeyPair(
|
||||
seckey: PrivateKey(scheme: PKScheme.Secp256k1, skkey: pair.seckey),
|
||||
pubkey: PublicKey(scheme: PKScheme.Secp256k1, skkey: pair.pubkey)))
|
||||
ok(
|
||||
KeyPair(
|
||||
seckey: PrivateKey(scheme: PKScheme.Secp256k1, skkey: pair.seckey),
|
||||
pubkey: PublicKey(scheme: PKScheme.Secp256k1, skkey: pair.pubkey),
|
||||
)
|
||||
)
|
||||
elif supported(PKScheme.RSA):
|
||||
let pair = ? RsaKeyPair.random(rng, bits).orError(KeyError)
|
||||
ok(KeyPair(
|
||||
seckey: PrivateKey(scheme: PKScheme.RSA, rsakey: pair.seckey),
|
||||
pubkey: PublicKey(scheme: PKScheme.RSA, rsakey: pair.pubkey)))
|
||||
let pair = ?RsaKeyPair.random(rng, bits).orError(KeyError)
|
||||
ok(
|
||||
KeyPair(
|
||||
seckey: PrivateKey(scheme: PKScheme.RSA, rsakey: pair.seckey),
|
||||
pubkey: PublicKey(scheme: PKScheme.RSA, rsakey: pair.pubkey),
|
||||
)
|
||||
)
|
||||
elif supported(PKScheme.ECDSA):
|
||||
let pair = ? EcKeyPair.random(Secp256r1, rng).orError(KeyError)
|
||||
ok(KeyPair(
|
||||
seckey: PrivateKey(scheme: PKScheme.ECDSA, eckey: pair.seckey),
|
||||
pubkey: PublicKey(scheme: PKScheme.ECDSA, eckey: pair.pubkey)))
|
||||
let pair = ?EcKeyPair.random(Secp256r1, rng).orError(KeyError)
|
||||
ok(
|
||||
KeyPair(
|
||||
seckey: PrivateKey(scheme: PKScheme.ECDSA, eckey: pair.seckey),
|
||||
pubkey: PublicKey(scheme: PKScheme.ECDSA, eckey: pair.pubkey),
|
||||
)
|
||||
)
|
||||
else:
|
||||
err(SchemeError)
|
||||
|
||||
@@ -333,7 +366,7 @@ proc getPublicKey*(key: PrivateKey): CryptoResult[PublicKey] =
|
||||
err(SchemeError)
|
||||
of PKScheme.ECDSA:
|
||||
when supported(PKScheme.ECDSA):
|
||||
let eckey = ? key.eckey.getPublicKey().orError(KeyError)
|
||||
let eckey = ?key.eckey.getPublicKey().orError(KeyError)
|
||||
ok(PublicKey(scheme: ECDSA, eckey: eckey))
|
||||
else:
|
||||
err(SchemeError)
|
||||
@@ -344,8 +377,9 @@ proc getPublicKey*(key: PrivateKey): CryptoResult[PublicKey] =
|
||||
else:
|
||||
err(SchemeError)
|
||||
|
||||
proc toRawBytes*(key: PrivateKey | PublicKey,
|
||||
data: var openArray[byte]): CryptoResult[int] =
|
||||
proc toRawBytes*(
|
||||
key: PrivateKey | PublicKey, data: var openArray[byte]
|
||||
): CryptoResult[int] =
|
||||
## Serialize private key ``key`` (using scheme's own serialization) and store
|
||||
## it to ``data``.
|
||||
##
|
||||
@@ -404,7 +438,7 @@ proc toBytes*(key: PrivateKey, data: var openArray[byte]): CryptoResult[int] =
|
||||
## Returns number of bytes (octets) needed to store private key ``key``.
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, uint64(key.scheme))
|
||||
msg.write(2, ? key.getRawBytes())
|
||||
msg.write(2, ?key.getRawBytes())
|
||||
msg.finish()
|
||||
var blen = len(msg.buffer)
|
||||
if len(data) >= blen:
|
||||
@@ -418,7 +452,7 @@ proc toBytes*(key: PublicKey, data: var openArray[byte]): CryptoResult[int] =
|
||||
## Returns number of bytes (octets) needed to store public key ``key``.
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, uint64(key.scheme))
|
||||
msg.write(2, ? key.getRawBytes())
|
||||
msg.write(2, ?key.getRawBytes())
|
||||
msg.finish()
|
||||
var blen = len(msg.buffer)
|
||||
if len(data) >= blen and blen > 0:
|
||||
@@ -438,7 +472,7 @@ proc getBytes*(key: PrivateKey): CryptoResult[seq[byte]] =
|
||||
## serialization).
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, uint64(key.scheme))
|
||||
msg.write(2, ? key.getRawBytes())
|
||||
msg.write(2, ?key.getRawBytes())
|
||||
msg.finish()
|
||||
ok(msg.buffer)
|
||||
|
||||
@@ -447,7 +481,7 @@ proc getBytes*(key: PublicKey): CryptoResult[seq[byte]] =
|
||||
## serialization).
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, uint64(key.scheme))
|
||||
msg.write(2, ? key.getRawBytes())
|
||||
msg.write(2, ?key.getRawBytes())
|
||||
msg.finish()
|
||||
ok(msg.buffer)
|
||||
|
||||
@@ -455,7 +489,7 @@ proc getBytes*(sig: Signature): seq[byte] =
|
||||
## Return signature ``sig`` in binary form.
|
||||
result = sig.data
|
||||
|
||||
proc init*[T: PrivateKey|PublicKey](key: var T, data: openArray[byte]): bool =
|
||||
template initImpl[T: PrivateKey | PublicKey](key: var T, data: openArray[byte]): bool =
|
||||
## Initialize private key ``key`` from libp2p's protobuf serialized raw
|
||||
## binary form.
|
||||
##
|
||||
@@ -468,7 +502,7 @@ proc init*[T: PrivateKey|PublicKey](key: var T, data: openArray[byte]): bool =
|
||||
var pb = initProtoBuffer(@data)
|
||||
let r1 = pb.getField(1, id)
|
||||
let r2 = pb.getField(2, buffer)
|
||||
if not(r1.isOk() and r1.get() and r2.isOk() and r2.get()):
|
||||
if not (r1.get(false) and r2.get(false)):
|
||||
false
|
||||
else:
|
||||
if cast[int8](id) notin SupportedSchemesInt or len(buffer) <= 0:
|
||||
@@ -479,7 +513,7 @@ proc init*[T: PrivateKey|PublicKey](key: var T, data: openArray[byte]): bool =
|
||||
var nkey = PrivateKey(scheme: scheme)
|
||||
else:
|
||||
var nkey = PublicKey(scheme: scheme)
|
||||
case scheme:
|
||||
case scheme
|
||||
of PKScheme.RSA:
|
||||
when supported(PKScheme.RSA):
|
||||
if init(nkey.rsakey, buffer).isOk:
|
||||
@@ -517,6 +551,15 @@ proc init*[T: PrivateKey|PublicKey](key: var T, data: openArray[byte]): bool =
|
||||
else:
|
||||
false
|
||||
|
||||
{.push warning[ProveField]: off.} # https://github.com/nim-lang/Nim/issues/22060
|
||||
proc init*(key: var PrivateKey, data: openArray[byte]): bool =
|
||||
initImpl(key, data)
|
||||
|
||||
proc init*(key: var PublicKey, data: openArray[byte]): bool =
|
||||
initImpl(key, data)
|
||||
|
||||
{.pop.}
|
||||
|
||||
proc init*(sig: var Signature, data: openArray[byte]): bool =
|
||||
## Initialize signature ``sig`` from raw binary form.
|
||||
##
|
||||
@@ -525,7 +568,7 @@ proc init*(sig: var Signature, data: openArray[byte]): bool =
|
||||
sig.data = @data
|
||||
result = true
|
||||
|
||||
proc init*[T: PrivateKey|PublicKey](key: var T, data: string): bool =
|
||||
proc init*[T: PrivateKey | PublicKey](key: var T, data: string): bool =
|
||||
## Initialize private/public key ``key`` from libp2p's protobuf serialized
|
||||
## hexadecimal string representation.
|
||||
##
|
||||
@@ -539,26 +582,23 @@ proc init*(sig: var Signature, data: string): bool =
|
||||
## Returns ``true`` on success.
|
||||
sig.init(ncrutils.fromHex(data))
|
||||
|
||||
proc init*(t: typedesc[PrivateKey],
|
||||
data: openArray[byte]): CryptoResult[PrivateKey] =
|
||||
proc init*(t: typedesc[PrivateKey], data: openArray[byte]): CryptoResult[PrivateKey] =
|
||||
## Create new private key from libp2p's protobuf serialized binary form.
|
||||
var res: t
|
||||
if not res.init(data):
|
||||
err(KeyError)
|
||||
err(CryptoError.KeyError)
|
||||
else:
|
||||
ok(res)
|
||||
|
||||
proc init*(t: typedesc[PublicKey],
|
||||
data: openArray[byte]): CryptoResult[PublicKey] =
|
||||
proc init*(t: typedesc[PublicKey], data: openArray[byte]): CryptoResult[PublicKey] =
|
||||
## Create new public key from libp2p's protobuf serialized binary form.
|
||||
var res: t
|
||||
if not res.init(data):
|
||||
err(KeyError)
|
||||
err(CryptoError.KeyError)
|
||||
else:
|
||||
ok(res)
|
||||
|
||||
proc init*(t: typedesc[Signature],
|
||||
data: openArray[byte]): CryptoResult[Signature] =
|
||||
proc init*(t: typedesc[Signature], data: openArray[byte]): CryptoResult[Signature] =
|
||||
## Create new public key from libp2p's protobuf serialized binary form.
|
||||
var res: t
|
||||
if not res.init(data):
|
||||
@@ -574,24 +614,28 @@ proc init*(t: typedesc[PrivateKey], data: string): CryptoResult[PrivateKey] =
|
||||
when supported(PKScheme.RSA):
|
||||
proc init*(t: typedesc[PrivateKey], key: rsa.RsaPrivateKey): PrivateKey =
|
||||
PrivateKey(scheme: RSA, rsakey: key)
|
||||
|
||||
proc init*(t: typedesc[PublicKey], key: rsa.RsaPublicKey): PublicKey =
|
||||
PublicKey(scheme: RSA, rsakey: key)
|
||||
|
||||
when supported(PKScheme.Ed25519):
|
||||
proc init*(t: typedesc[PrivateKey], key: EdPrivateKey): PrivateKey =
|
||||
PrivateKey(scheme: Ed25519, edkey: key)
|
||||
|
||||
proc init*(t: typedesc[PublicKey], key: EdPublicKey): PublicKey =
|
||||
PublicKey(scheme: Ed25519, edkey: key)
|
||||
|
||||
when supported(PKScheme.Secp256k1):
|
||||
proc init*(t: typedesc[PrivateKey], key: SkPrivateKey): PrivateKey =
|
||||
PrivateKey(scheme: Secp256k1, skkey: key)
|
||||
|
||||
proc init*(t: typedesc[PublicKey], key: SkPublicKey): PublicKey =
|
||||
PublicKey(scheme: Secp256k1, skkey: key)
|
||||
|
||||
when supported(PKScheme.ECDSA):
|
||||
proc init*(t: typedesc[PrivateKey], key: ecnist.EcPrivateKey): PrivateKey =
|
||||
PrivateKey(scheme: ECDSA, eckey: key)
|
||||
|
||||
proc init*(t: typedesc[PublicKey], key: ecnist.EcPublicKey): PublicKey =
|
||||
PublicKey(scheme: ECDSA, eckey: key)
|
||||
|
||||
@@ -660,9 +704,9 @@ proc `==`*(key1, key2: PrivateKey): bool =
|
||||
else:
|
||||
false
|
||||
|
||||
proc `$`*(key: PrivateKey|PublicKey): string =
|
||||
proc `$`*(key: PrivateKey | PublicKey): string =
|
||||
## Get string representation of private/public key ``key``.
|
||||
case key.scheme:
|
||||
case key.scheme
|
||||
of PKScheme.RSA:
|
||||
when supported(PKScheme.RSA):
|
||||
$(key.rsakey)
|
||||
@@ -684,9 +728,9 @@ proc `$`*(key: PrivateKey|PublicKey): string =
|
||||
else:
|
||||
"unsupported secp256k1 key"
|
||||
|
||||
func shortLog*(key: PrivateKey|PublicKey): string =
|
||||
func shortLog*(key: PrivateKey | PublicKey): string =
|
||||
## Get short string representation of private/public key ``key``.
|
||||
case key.scheme:
|
||||
case key.scheme
|
||||
of PKScheme.RSA:
|
||||
when supported(PKScheme.RSA):
|
||||
($key.rsakey).shortLog
|
||||
@@ -712,16 +756,15 @@ proc `$`*(sig: Signature): string =
|
||||
## Get string representation of signature ``sig``.
|
||||
result = ncrutils.toHex(sig.data)
|
||||
|
||||
proc sign*(key: PrivateKey,
|
||||
data: openArray[byte]): CryptoResult[Signature] {.gcsafe.} =
|
||||
proc sign*(key: PrivateKey, data: openArray[byte]): CryptoResult[Signature] {.gcsafe.} =
|
||||
## Sign message ``data`` using private key ``key`` and return generated
|
||||
## signature in raw binary form.
|
||||
var res: Signature
|
||||
case key.scheme:
|
||||
case key.scheme
|
||||
of PKScheme.RSA:
|
||||
when supported(PKScheme.RSA):
|
||||
let sig = ? key.rsakey.sign(data).orError(SigError)
|
||||
res.data = ? sig.getBytes().orError(SigError)
|
||||
let sig = ?key.rsakey.sign(data).orError(SigError)
|
||||
res.data = ?sig.getBytes().orError(SigError)
|
||||
ok(res)
|
||||
else:
|
||||
err(SchemeError)
|
||||
@@ -734,8 +777,8 @@ proc sign*(key: PrivateKey,
|
||||
err(SchemeError)
|
||||
of PKScheme.ECDSA:
|
||||
when supported(PKScheme.ECDSA):
|
||||
let sig = ? key.eckey.sign(data).orError(SigError)
|
||||
res.data = ? sig.getBytes().orError(SigError)
|
||||
let sig = ?key.eckey.sign(data).orError(SigError)
|
||||
res.data = ?sig.getBytes().orError(SigError)
|
||||
ok(res)
|
||||
else:
|
||||
err(SchemeError)
|
||||
@@ -750,7 +793,7 @@ proc sign*(key: PrivateKey,
|
||||
proc verify*(sig: Signature, message: openArray[byte], key: PublicKey): bool =
|
||||
## Verify signature ``sig`` using message ``message`` and public key ``key``.
|
||||
## Return ``true`` if message signature is valid.
|
||||
case key.scheme:
|
||||
case key.scheme
|
||||
of PKScheme.RSA:
|
||||
when supported(PKScheme.RSA):
|
||||
var signature: RsaSignature
|
||||
@@ -788,12 +831,12 @@ proc verify*(sig: Signature, message: openArray[byte], key: PublicKey): bool =
|
||||
else:
|
||||
false
|
||||
|
||||
template makeSecret(buffer, hmactype, secret, seed: untyped) {.dirty.}=
|
||||
template makeSecret(buffer, hmactype, secret, seed: untyped) {.dirty.} =
|
||||
var ctx: hmactype
|
||||
var j = 0
|
||||
# We need to strip leading zeros, because Go bigint serialization do it.
|
||||
var offset = 0
|
||||
for i in 0..<len(secret):
|
||||
for i in 0 ..< len(secret):
|
||||
if secret[i] != 0x00'u8:
|
||||
break
|
||||
inc(offset)
|
||||
@@ -814,8 +857,9 @@ template makeSecret(buffer, hmactype, secret, seed: untyped) {.dirty.}=
|
||||
ctx.update(a.data)
|
||||
a = ctx.finish()
|
||||
|
||||
proc stretchKeys*(cipherType: string, hashType: string,
|
||||
sharedSecret: seq[byte]): Secret =
|
||||
proc stretchKeys*(
|
||||
cipherType: string, hashType: string, sharedSecret: seq[byte]
|
||||
): Secret =
|
||||
## Expand shared secret to cryptographic keys.
|
||||
if cipherType == "AES-128":
|
||||
result.ivsize = aes128.sizeBlock
|
||||
@@ -830,7 +874,7 @@ proc stretchKeys*(cipherType: string, hashType: string,
|
||||
var seed = "key expansion"
|
||||
result.macsize = 20
|
||||
let length = result.ivsize + result.keysize + result.macsize
|
||||
result.data = newSeq[byte](2 * length)
|
||||
result.data = newSeqUninit[byte](2 * length)
|
||||
|
||||
if hashType == "SHA256":
|
||||
makeSecret(result.data, HMAC[sha256], sharedSecret, seed)
|
||||
@@ -841,65 +885,57 @@ template goffset*(secret, id, o: untyped): untyped =
|
||||
id * (len(secret.data) shr 1) + o
|
||||
|
||||
template ivOpenArray*(secret: Secret, id: int): untyped =
|
||||
toOpenArray(secret.data, goffset(secret, id, 0),
|
||||
goffset(secret, id, secret.ivsize - 1))
|
||||
toOpenArray(
|
||||
secret.data, goffset(secret, id, 0), goffset(secret, id, secret.ivsize - 1)
|
||||
)
|
||||
|
||||
template keyOpenArray*(secret: Secret, id: int): untyped =
|
||||
toOpenArray(secret.data, goffset(secret, id, secret.ivsize),
|
||||
goffset(secret, id, secret.ivsize + secret.keysize - 1))
|
||||
toOpenArray(
|
||||
secret.data,
|
||||
goffset(secret, id, secret.ivsize),
|
||||
goffset(secret, id, secret.ivsize + secret.keysize - 1),
|
||||
)
|
||||
|
||||
template macOpenArray*(secret: Secret, id: int): untyped =
|
||||
toOpenArray(secret.data, goffset(secret, id, secret.ivsize + secret.keysize),
|
||||
goffset(secret, id, secret.ivsize + secret.keysize + secret.macsize - 1))
|
||||
toOpenArray(
|
||||
secret.data,
|
||||
goffset(secret, id, secret.ivsize + secret.keysize),
|
||||
goffset(secret, id, secret.ivsize + secret.keysize + secret.macsize - 1),
|
||||
)
|
||||
|
||||
proc iv*(secret: Secret, id: int): seq[byte] {.inline.} =
|
||||
## Get array of bytes with with initial vector.
|
||||
result = newSeq[byte](secret.ivsize)
|
||||
var offset = if id == 0: 0 else: (len(secret.data) div 2)
|
||||
result = newSeqUninit[byte](secret.ivsize)
|
||||
var offset =
|
||||
if id == 0:
|
||||
0
|
||||
else:
|
||||
(len(secret.data) div 2)
|
||||
copyMem(addr result[0], unsafeAddr secret.data[offset], secret.ivsize)
|
||||
|
||||
proc key*(secret: Secret, id: int): seq[byte] {.inline.} =
|
||||
result = newSeq[byte](secret.keysize)
|
||||
var offset = if id == 0: 0 else: (len(secret.data) div 2)
|
||||
result = newSeqUninit[byte](secret.keysize)
|
||||
var offset =
|
||||
if id == 0:
|
||||
0
|
||||
else:
|
||||
(len(secret.data) div 2)
|
||||
offset += secret.ivsize
|
||||
copyMem(addr result[0], unsafeAddr secret.data[offset], secret.keysize)
|
||||
|
||||
proc mac*(secret: Secret, id: int): seq[byte] {.inline.} =
|
||||
result = newSeq[byte](secret.macsize)
|
||||
var offset = if id == 0: 0 else: (len(secret.data) div 2)
|
||||
result = newSeqUninit[byte](secret.macsize)
|
||||
var offset =
|
||||
if id == 0:
|
||||
0
|
||||
else:
|
||||
(len(secret.data) div 2)
|
||||
offset += secret.ivsize + secret.keysize
|
||||
copyMem(addr result[0], unsafeAddr secret.data[offset], secret.macsize)
|
||||
|
||||
proc ephemeral*(
|
||||
scheme: ECDHEScheme,
|
||||
rng: var BrHmacDrbgContext): CryptoResult[EcKeyPair] =
|
||||
## Generate ephemeral keys used to perform ECDHE.
|
||||
var keypair: EcKeyPair
|
||||
if scheme == Secp256r1:
|
||||
keypair = ? EcKeyPair.random(Secp256r1, rng).orError(KeyError)
|
||||
elif scheme == Secp384r1:
|
||||
keypair = ? EcKeyPair.random(Secp384r1, rng).orError(KeyError)
|
||||
elif scheme == Secp521r1:
|
||||
keypair = ? EcKeyPair.random(Secp521r1, rng).orError(KeyError)
|
||||
ok(keypair)
|
||||
|
||||
proc ephemeral*(
|
||||
scheme: string, rng: var BrHmacDrbgContext): CryptoResult[EcKeyPair] =
|
||||
## Generate ephemeral keys used to perform ECDHE using string encoding.
|
||||
##
|
||||
## Currently supported encoding strings are P-256, P-384, P-521, if encoding
|
||||
## string is not supported P-521 key will be generated.
|
||||
if scheme == "P-256":
|
||||
ephemeral(Secp256r1, rng)
|
||||
elif scheme == "P-384":
|
||||
ephemeral(Secp384r1, rng)
|
||||
elif scheme == "P-521":
|
||||
ephemeral(Secp521r1, rng)
|
||||
else:
|
||||
ephemeral(Secp521r1, rng)
|
||||
|
||||
proc getOrder*(remotePubkey, localNonce: openArray[byte],
|
||||
localPubkey, remoteNonce: openArray[byte]): CryptoResult[int] =
|
||||
proc getOrder*(
|
||||
remotePubkey, localNonce: openArray[byte], localPubkey, remoteNonce: openArray[byte]
|
||||
): CryptoResult[int] =
|
||||
## Compare values and calculate `order` parameter.
|
||||
var ctx: sha256
|
||||
ctx.init()
|
||||
@@ -910,9 +946,9 @@ proc getOrder*(remotePubkey, localNonce: openArray[byte],
|
||||
ctx.update(localPubkey)
|
||||
ctx.update(remoteNonce)
|
||||
var digest2 = ctx.finish()
|
||||
var mh1 = ? MultiHash.init(multiCodec("sha2-256"), digest1).orError(HashError)
|
||||
var mh2 = ? MultiHash.init(multiCodec("sha2-256"), digest2).orError(HashError)
|
||||
var res = 0;
|
||||
var mh1 = ?MultiHash.init(multiCodec("sha2-256"), digest1).orError(HashError)
|
||||
var mh2 = ?MultiHash.init(multiCodec("sha2-256"), digest2).orError(HashError)
|
||||
var res = 0
|
||||
for i in 0 ..< len(mh1.data.buffer):
|
||||
res = int(mh1.data.buffer[i]) - int(mh2.data.buffer[i])
|
||||
if res != 0:
|
||||
@@ -943,96 +979,45 @@ proc selectBest*(order: int, p1, p2: string): string =
|
||||
if felement == selement:
|
||||
return felement
|
||||
|
||||
proc createProposal*(nonce, pubkey: openArray[byte],
|
||||
exchanges, ciphers, hashes: string): seq[byte] =
|
||||
## Create SecIO proposal message using random ``nonce``, local public key
|
||||
## ``pubkey``, comma-delimieted list of supported exchange schemes
|
||||
## ``exchanges``, comma-delimeted list of supported ciphers ``ciphers`` and
|
||||
## comma-delimeted list of supported hashes ``hashes``.
|
||||
var msg = initProtoBuffer({WithUint32BeLength})
|
||||
msg.write(1, nonce)
|
||||
msg.write(2, pubkey)
|
||||
msg.write(3, exchanges)
|
||||
msg.write(4, ciphers)
|
||||
msg.write(5, hashes)
|
||||
msg.finish()
|
||||
msg.buffer
|
||||
|
||||
proc decodeProposal*(message: seq[byte], nonce, pubkey: var seq[byte],
|
||||
exchanges, ciphers, hashes: var string): bool =
|
||||
## Parse incoming proposal message and decode remote random nonce ``nonce``,
|
||||
## remote public key ``pubkey``, comma-delimieted list of supported exchange
|
||||
## schemes ``exchanges``, comma-delimeted list of supported ciphers
|
||||
## ``ciphers`` and comma-delimeted list of supported hashes ``hashes``.
|
||||
##
|
||||
## Procedure returns ``true`` on success and ``false`` on error.
|
||||
var pb = initProtoBuffer(message)
|
||||
let r1 = pb.getField(1, nonce)
|
||||
let r2 = pb.getField(2, pubkey)
|
||||
let r3 = pb.getField(3, exchanges)
|
||||
let r4 = pb.getField(4, ciphers)
|
||||
let r5 = pb.getField(5, hashes)
|
||||
|
||||
r1.isOk() and r1.get() and r2.isOk() and r2.get() and
|
||||
r3.isOk() and r3.get() and r4.isOk() and r4.get() and
|
||||
r5.isOk() and r5.get()
|
||||
|
||||
proc createExchange*(epubkey, signature: openArray[byte]): seq[byte] =
|
||||
## Create SecIO exchange message using ephemeral public key ``epubkey`` and
|
||||
## signature of proposal blocks ``signature``.
|
||||
var msg = initProtoBuffer({WithUint32BeLength})
|
||||
msg.write(1, epubkey)
|
||||
msg.write(2, signature)
|
||||
msg.finish()
|
||||
msg.buffer
|
||||
|
||||
proc decodeExchange*(message: seq[byte],
|
||||
pubkey, signature: var seq[byte]): bool =
|
||||
## Parse incoming exchange message and decode remote ephemeral public key
|
||||
## ``pubkey`` and signature ``signature``.
|
||||
##
|
||||
## Procedure returns ``true`` on success and ``false`` on error.
|
||||
var pb = initProtoBuffer(message)
|
||||
let r1 = pb.getField(1, pubkey)
|
||||
let r2 = pb.getField(2, signature)
|
||||
r1.isOk() and r1.get() and r2.isOk() and r2.get()
|
||||
|
||||
## Serialization/Deserialization helpers
|
||||
|
||||
proc write*(vb: var VBuffer, pubkey: PublicKey) {.
|
||||
inline, raises: [Defect, ResultError[CryptoError]].} =
|
||||
proc write*(
|
||||
vb: var VBuffer, pubkey: PublicKey
|
||||
) {.inline, raises: [ResultError[CryptoError]].} =
|
||||
## Write PublicKey value ``pubkey`` to buffer ``vb``.
|
||||
vb.writeSeq(pubkey.getBytes().tryGet())
|
||||
|
||||
proc write*(vb: var VBuffer, seckey: PrivateKey) {.
|
||||
inline, raises: [Defect, ResultError[CryptoError]].} =
|
||||
proc write*(
|
||||
vb: var VBuffer, seckey: PrivateKey
|
||||
) {.inline, raises: [ResultError[CryptoError]].} =
|
||||
## Write PrivateKey value ``seckey`` to buffer ``vb``.
|
||||
vb.writeSeq(seckey.getBytes().tryGet())
|
||||
|
||||
proc write*(vb: var VBuffer, sig: PrivateKey) {.
|
||||
inline, raises: [Defect, ResultError[CryptoError]].} =
|
||||
proc write*(
|
||||
vb: var VBuffer, sig: PrivateKey
|
||||
) {.inline, raises: [ResultError[CryptoError]].} =
|
||||
## Write Signature value ``sig`` to buffer ``vb``.
|
||||
vb.writeSeq(sig.getBytes().tryGet())
|
||||
|
||||
proc write*[T: PublicKey|PrivateKey](pb: var ProtoBuffer, field: int,
|
||||
key: T) {.
|
||||
inline, raises: [Defect, ResultError[CryptoError]].} =
|
||||
proc write*[T: PublicKey | PrivateKey](
|
||||
pb: var ProtoBuffer, field: int, key: T
|
||||
) {.inline, raises: [ResultError[CryptoError]].} =
|
||||
write(pb, field, key.getBytes().tryGet())
|
||||
|
||||
proc write*(pb: var ProtoBuffer, field: int, sig: Signature) {.
|
||||
inline, raises: [Defect].} =
|
||||
proc write*(pb: var ProtoBuffer, field: int, sig: Signature) {.inline, raises: [].} =
|
||||
write(pb, field, sig.getBytes())
|
||||
|
||||
proc getField*[T: PublicKey|PrivateKey](pb: ProtoBuffer, field: int,
|
||||
value: var T): ProtoResult[bool] =
|
||||
proc getField*[T: PublicKey | PrivateKey](
|
||||
pb: ProtoBuffer, field: int, value: var T
|
||||
): ProtoResult[bool] =
|
||||
## Deserialize public/private key from protobuf's message ``pb`` using field
|
||||
## index ``field``.
|
||||
##
|
||||
## On success deserialized key will be stored in ``value``.
|
||||
var buffer: seq[byte]
|
||||
var key: T
|
||||
let res = ? pb.getField(field, buffer)
|
||||
if not(res):
|
||||
let res = ?pb.getField(field, buffer)
|
||||
if not (res):
|
||||
ok(false)
|
||||
else:
|
||||
if key.init(buffer):
|
||||
@@ -1041,16 +1026,15 @@ proc getField*[T: PublicKey|PrivateKey](pb: ProtoBuffer, field: int,
|
||||
else:
|
||||
err(ProtoError.IncorrectBlob)
|
||||
|
||||
proc getField*(pb: ProtoBuffer, field: int,
|
||||
value: var Signature): ProtoResult[bool] =
|
||||
proc getField*(pb: ProtoBuffer, field: int, value: var Signature): ProtoResult[bool] =
|
||||
## Deserialize signature from protobuf's message ``pb`` using field index
|
||||
## ``field``.
|
||||
##
|
||||
## On success deserialized signature will be stored in ``value``.
|
||||
var buffer: seq[byte]
|
||||
var sig: Signature
|
||||
let res = ? pb.getField(field, buffer)
|
||||
if not(res):
|
||||
let res = ?pb.getField(field, buffer)
|
||||
if not (res):
|
||||
ok(false)
|
||||
else:
|
||||
if sig.init(buffer):
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2020 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module integrates BearSSL Cyrve25519 mul and mulgen
|
||||
##
|
||||
@@ -15,75 +15,69 @@
|
||||
|
||||
# RFC @ https://tools.ietf.org/html/rfc7748
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import bearssl
|
||||
import stew/results
|
||||
import bearssl/[ec, rand]
|
||||
import results
|
||||
from stew/assign2 import assign
|
||||
export results
|
||||
|
||||
const
|
||||
Curve25519KeySize* = 32
|
||||
const Curve25519KeySize* = 32
|
||||
|
||||
type
|
||||
Curve25519* = object
|
||||
Curve25519Key* = array[Curve25519KeySize, byte]
|
||||
pcuchar = ptr cuchar
|
||||
Curve25519Error* = enum
|
||||
Curver25519GenError
|
||||
|
||||
proc intoCurve25519Key*(s: openArray[byte]): Curve25519Key =
|
||||
assert s.len == Curve25519KeySize
|
||||
copyMem(addr result[0], unsafeAddr s[0], Curve25519KeySize)
|
||||
assign(result, s)
|
||||
|
||||
proc getBytes*(key: Curve25519Key): seq[byte] = @key
|
||||
proc getBytes*(key: Curve25519Key): seq[byte] =
|
||||
@key
|
||||
|
||||
proc byteswap(buf: var Curve25519Key) {.inline.} =
|
||||
for i in 0..<16:
|
||||
let
|
||||
x = buf[i]
|
||||
for i in 0 ..< 16:
|
||||
let x = buf[i]
|
||||
buf[i] = buf[31 - i]
|
||||
buf[31 - i] = x
|
||||
|
||||
proc mul*(_: type[Curve25519], point: var Curve25519Key, multiplier: Curve25519Key) =
|
||||
let defaultBrEc = brEcGetDefault()
|
||||
let defaultBrEc = ecGetDefault()
|
||||
|
||||
# multiplier needs to be big-endian
|
||||
var
|
||||
multiplierBs = multiplier
|
||||
var multiplierBs = multiplier
|
||||
multiplierBs.byteswap()
|
||||
let
|
||||
res = defaultBrEc.mul(
|
||||
cast[pcuchar](addr point[0]),
|
||||
Curve25519KeySize,
|
||||
cast[pcuchar](addr multiplierBs[0]),
|
||||
Curve25519KeySize,
|
||||
EC_curve25519)
|
||||
let res = defaultBrEc.mul(
|
||||
addr point[0],
|
||||
Curve25519KeySize,
|
||||
addr multiplierBs[0],
|
||||
Curve25519KeySize,
|
||||
EC_curve25519,
|
||||
)
|
||||
assert res == 1
|
||||
|
||||
proc mulgen(_: type[Curve25519], dst: var Curve25519Key, point: Curve25519Key) =
|
||||
let defaultBrEc = brEcGetDefault()
|
||||
let defaultBrEc = ecGetDefault()
|
||||
|
||||
var
|
||||
rpoint = point
|
||||
var rpoint = point
|
||||
rpoint.byteswap()
|
||||
|
||||
let
|
||||
size = defaultBrEc.mulgen(
|
||||
cast[pcuchar](addr dst[0]),
|
||||
cast[pcuchar](addr rpoint[0]),
|
||||
Curve25519KeySize,
|
||||
EC_curve25519)
|
||||
|
||||
let size =
|
||||
defaultBrEc.mulgen(addr dst[0], addr rpoint[0], Curve25519KeySize, EC_curve25519)
|
||||
|
||||
assert size == Curve25519KeySize
|
||||
|
||||
proc public*(private: Curve25519Key): Curve25519Key =
|
||||
Curve25519.mulgen(result, private)
|
||||
|
||||
proc random*(_: type[Curve25519Key], rng: var BrHmacDrbgContext): Curve25519Key =
|
||||
proc random*(_: type[Curve25519Key], rng: var HmacDrbgContext): Curve25519Key =
|
||||
var res: Curve25519Key
|
||||
let defaultBrEc = brEcGetDefault()
|
||||
let len = brEcKeygen(
|
||||
addr rng.vtable, defaultBrEc, nil, addr res[0], EC_curve25519)
|
||||
let defaultBrEc = ecGetDefault()
|
||||
let len = ecKeygen(
|
||||
PrngClassPointerConst(addr rng.vtable), defaultBrEc, nil, addr res[0], EC_curve25519
|
||||
)
|
||||
# Per bearssl documentation, the keygen only fails if the curve is
|
||||
# unrecognised -
|
||||
doAssert len == Curve25519KeySize, "Could not generate curve"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implements constant-time ECDSA and ECDHE for NIST elliptic
|
||||
## curves secp256r1, secp384r1 and secp521r1.
|
||||
@@ -14,14 +14,19 @@
|
||||
## BearSSL library <https://bearssl.org/>
|
||||
## Copyright(C) 2018 Thomas Pornin <pornin@bolet.org>.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import bearssl
|
||||
import bearssl/[ec, rand, hash]
|
||||
# We use `ncrutils` for constant-time hexadecimal encoding/decoding procedures.
|
||||
import nimcrypto/utils as ncrutils
|
||||
import minasn1
|
||||
export minasn1.Asn1Error
|
||||
import stew/[results, ctops]
|
||||
import stew/ctops
|
||||
import results
|
||||
import ../utils/sequninit
|
||||
|
||||
import ../utility
|
||||
|
||||
export results
|
||||
|
||||
const
|
||||
@@ -40,12 +45,12 @@ const
|
||||
|
||||
type
|
||||
EcPrivateKey* = ref object
|
||||
buffer*: array[BR_EC_KBUF_PRIV_MAX_SIZE, byte]
|
||||
key*: BrEcPrivateKey
|
||||
buffer*: array[EC_KBUF_PRIV_MAX_SIZE, byte]
|
||||
key*: ec.EcPrivateKey
|
||||
|
||||
EcPublicKey* = ref object
|
||||
buffer*: array[BR_EC_KBUF_PUB_MAX_SIZE, byte]
|
||||
key*: BrEcPublicKey
|
||||
buffer*: array[EC_KBUF_PUB_MAX_SIZE, byte]
|
||||
key*: ec.EcPublicKey
|
||||
|
||||
EcKeyPair* = object
|
||||
seckey*: EcPrivateKey
|
||||
@@ -55,23 +60,22 @@ type
|
||||
buffer*: seq[byte]
|
||||
|
||||
EcCurveKind* = enum
|
||||
Secp256r1 = BR_EC_SECP256R1,
|
||||
Secp384r1 = BR_EC_SECP384R1,
|
||||
Secp521r1 = BR_EC_SECP521R1
|
||||
Secp256r1 = EC_secp256r1
|
||||
Secp384r1 = EC_secp384r1
|
||||
Secp521r1 = EC_secp521r1
|
||||
|
||||
EcPKI* = EcPrivateKey | EcPublicKey | EcSignature
|
||||
|
||||
EcError* = enum
|
||||
EcRngError,
|
||||
EcKeyGenError,
|
||||
EcPublicKeyError,
|
||||
EcKeyIncorrectError,
|
||||
EcRngError
|
||||
EcKeyGenError
|
||||
EcPublicKeyError
|
||||
EcKeyIncorrectError
|
||||
EcSignatureError
|
||||
|
||||
EcResult*[T] = Result[T, EcError]
|
||||
|
||||
const
|
||||
EcSupportedCurvesCint* = {cint(Secp256r1), cint(Secp384r1), cint(Secp521r1)}
|
||||
const EcSupportedCurvesCint* = @[cint(Secp256r1), cint(Secp384r1), cint(Secp521r1)]
|
||||
|
||||
proc `-`(x: uint32): uint32 {.inline.} =
|
||||
result = (0xFFFF_FFFF'u32 - x) + 1'u32
|
||||
@@ -85,7 +89,7 @@ proc CMP(x, y: uint32): int32 {.inline.} =
|
||||
|
||||
proc EQ0(x: int32): uint32 {.inline.} =
|
||||
var q = cast[uint32](x)
|
||||
result = not(q or -q) shr 31
|
||||
result = not (q or -q) shr 31
|
||||
|
||||
proc NEQ(x, y: uint32): uint32 {.inline.} =
|
||||
var q = cast[uint32](x xor y)
|
||||
@@ -101,16 +105,16 @@ proc checkScalar(scalar: openArray[byte], curve: cint): uint32 =
|
||||
## - ``scalar`` is lower than the curve ``order``.
|
||||
##
|
||||
## Otherwise, return ``0``.
|
||||
var impl = brEcGetDefault()
|
||||
var orderlen = 0
|
||||
var order = cast[ptr UncheckedArray[byte]](impl.order(curve, addr orderlen))
|
||||
var impl = ecGetDefault()
|
||||
var orderlen: uint = 0
|
||||
var order = cast[ptr UncheckedArray[byte]](impl.order(curve, orderlen))
|
||||
|
||||
var z = 0'u32
|
||||
var c = 0'i32
|
||||
for u in scalar:
|
||||
z = z or u
|
||||
if len(scalar) == orderlen:
|
||||
for i in 0..<len(scalar):
|
||||
if len(scalar) == int(orderlen):
|
||||
for i in 0 ..< len(scalar):
|
||||
c = c or (-(cast[int32](EQ0(c))) and CMP(scalar[i], order[i]))
|
||||
else:
|
||||
c = -1
|
||||
@@ -119,12 +123,11 @@ proc checkScalar(scalar: openArray[byte], curve: cint): uint32 =
|
||||
proc checkPublic(key: openArray[byte], curve: cint): uint32 =
|
||||
## Return ``1`` if public key ``key`` is on curve.
|
||||
var ckey = @key
|
||||
var x = [0x00'u8, 0x01'u8]
|
||||
var impl = brEcGetDefault()
|
||||
var orderlen = 0
|
||||
discard impl.order(curve, addr orderlen)
|
||||
result = impl.mul(cast[ptr cuchar](unsafeAddr ckey[0]), len(ckey),
|
||||
cast[ptr cuchar](addr x[0]), len(x), curve)
|
||||
var x = [byte 0x00, 0x01]
|
||||
var impl = ecGetDefault()
|
||||
var orderlen: uint = 0
|
||||
discard impl.order(curve, orderlen)
|
||||
result = impl.mul(unsafeAddr ckey[0], uint(len(ckey)), addr x[0], uint(len(x)), curve)
|
||||
|
||||
proc getOffset(pubkey: EcPublicKey): int {.inline.} =
|
||||
let o = cast[uint](pubkey.key.q) - cast[uint](unsafeAddr pubkey.buffer[0])
|
||||
@@ -142,21 +145,15 @@ proc getOffset(seckey: EcPrivateKey): int {.inline.} =
|
||||
|
||||
template getPublicKeyLength*(curve: EcCurveKind): int =
|
||||
case curve
|
||||
of Secp256r1:
|
||||
PubKey256Length
|
||||
of Secp384r1:
|
||||
PubKey384Length
|
||||
of Secp521r1:
|
||||
PubKey521Length
|
||||
of Secp256r1: PubKey256Length
|
||||
of Secp384r1: PubKey384Length
|
||||
of Secp521r1: PubKey521Length
|
||||
|
||||
template getPrivateKeyLength*(curve: EcCurveKind): int =
|
||||
case curve
|
||||
of Secp256r1:
|
||||
SecKey256Length
|
||||
of Secp384r1:
|
||||
SecKey384Length
|
||||
of Secp521r1:
|
||||
SecKey521Length
|
||||
of Secp256r1: SecKey256Length
|
||||
of Secp384r1: SecKey384Length
|
||||
of Secp521r1: SecKey521Length
|
||||
|
||||
proc copy*[T: EcPKI](dst: var T, src: T): bool =
|
||||
## Copy EC `private key`, `public key` or `signature` ``src`` to ``dst``.
|
||||
@@ -174,7 +171,7 @@ proc copy*[T: EcPKI](dst: var T, src: T): bool =
|
||||
dst.buffer = src.buffer
|
||||
dst.key.curve = src.key.curve
|
||||
dst.key.xlen = length
|
||||
dst.key.x = cast[ptr cuchar](addr dst.buffer[offset])
|
||||
dst.key.x = addr dst.buffer[offset]
|
||||
result = true
|
||||
elif T is EcPublicKey:
|
||||
let length = src.key.qlen
|
||||
@@ -184,7 +181,7 @@ proc copy*[T: EcPKI](dst: var T, src: T): bool =
|
||||
dst.buffer = src.buffer
|
||||
dst.key.curve = src.key.curve
|
||||
dst.key.qlen = length
|
||||
dst.key.q = cast[ptr cuchar](addr dst.buffer[offset])
|
||||
dst.key.q = addr dst.buffer[offset]
|
||||
result = true
|
||||
else:
|
||||
let length = len(src.buffer)
|
||||
@@ -198,7 +195,7 @@ proc copy*[T: EcPKI](src: T): T {.inline.} =
|
||||
if not copy(result, src):
|
||||
raise newException(EcKeyIncorrectError, "Incorrect key or signature")
|
||||
|
||||
proc clear*[T: EcPKI|EcKeyPair](pki: var T) =
|
||||
proc clear*[T: EcPKI | EcKeyPair](pki: var T) =
|
||||
## Wipe and clear EC `private key`, `public key` or `signature` object.
|
||||
doAssert(not isNil(pki))
|
||||
when T is EcPrivateKey:
|
||||
@@ -229,18 +226,22 @@ proc clear*[T: EcPKI|EcKeyPair](pki: var T) =
|
||||
pki.pubkey.key.curve = 0
|
||||
|
||||
proc random*(
|
||||
T: typedesc[EcPrivateKey], kind: EcCurveKind,
|
||||
rng: var BrHmacDrbgContext): EcResult[EcPrivateKey] =
|
||||
T: typedesc[EcPrivateKey], kind: EcCurveKind, rng: var HmacDrbgContext
|
||||
): EcResult[EcPrivateKey] =
|
||||
## Generate new random EC private key using BearSSL's HMAC-SHA256-DRBG
|
||||
## algorithm.
|
||||
##
|
||||
## ``kind`` elliptic curve kind of your choice (secp256r1, secp384r1 or
|
||||
## secp521r1).
|
||||
var ecimp = brEcGetDefault()
|
||||
var ecimp = ecGetDefault()
|
||||
var res = new EcPrivateKey
|
||||
if brEcKeygen(addr rng.vtable, ecimp,
|
||||
addr res.key, addr res.buffer[0],
|
||||
cast[cint](kind)) == 0:
|
||||
if ecKeygen(
|
||||
PrngClassPointerConst(addr rng.vtable),
|
||||
ecimp,
|
||||
addr res.key,
|
||||
addr res.buffer[0],
|
||||
safeConvert[cint](kind),
|
||||
) == 0:
|
||||
err(EcKeyGenError)
|
||||
else:
|
||||
ok(res)
|
||||
@@ -250,12 +251,11 @@ proc getPublicKey*(seckey: EcPrivateKey): EcResult[EcPublicKey] =
|
||||
if isNil(seckey):
|
||||
return err(EcKeyIncorrectError)
|
||||
|
||||
var ecimp = brEcGetDefault()
|
||||
var ecimp = ecGetDefault()
|
||||
if seckey.key.curve in EcSupportedCurvesCint:
|
||||
var length = getPublicKeyLength(cast[EcCurveKind](seckey.key.curve))
|
||||
var res = new EcPublicKey
|
||||
if brEcComputePublicKey(ecimp, addr res.key,
|
||||
addr res.buffer[0], unsafeAddr seckey.key) == 0:
|
||||
assert res.buffer.len > getPublicKeyLength(cast[EcCurveKind](seckey.key.curve))
|
||||
if ecComputePub(ecimp, addr res.key, addr res.buffer[0], unsafeAddr seckey.key) == 0:
|
||||
err(EcKeyIncorrectError)
|
||||
else:
|
||||
ok(res)
|
||||
@@ -263,23 +263,23 @@ proc getPublicKey*(seckey: EcPrivateKey): EcResult[EcPublicKey] =
|
||||
err(EcKeyIncorrectError)
|
||||
|
||||
proc random*(
|
||||
T: typedesc[EcKeyPair], kind: EcCurveKind,
|
||||
rng: var BrHmacDrbgContext): EcResult[T] =
|
||||
T: typedesc[EcKeyPair], kind: EcCurveKind, rng: var HmacDrbgContext
|
||||
): EcResult[T] =
|
||||
## Generate new random EC private and public keypair using BearSSL's
|
||||
## HMAC-SHA256-DRBG algorithm.
|
||||
##
|
||||
## ``kind`` elliptic curve kind of your choice (secp256r1, secp384r1 or
|
||||
## secp521r1).
|
||||
let
|
||||
seckey = ? EcPrivateKey.random(kind, rng)
|
||||
pubkey = ? seckey.getPublicKey()
|
||||
seckey = ?EcPrivateKey.random(kind, rng)
|
||||
pubkey = ?seckey.getPublicKey()
|
||||
key = EcKeyPair(seckey: seckey, pubkey: pubkey)
|
||||
ok(key)
|
||||
|
||||
proc `$`*(seckey: EcPrivateKey): string =
|
||||
## Return string representation of EC private key.
|
||||
if isNil(seckey) or seckey.key.curve == 0 or seckey.key.xlen == 0 or
|
||||
len(seckey.buffer) == 0:
|
||||
len(seckey.buffer) == 0:
|
||||
result = "Empty or uninitialized ECNIST key"
|
||||
else:
|
||||
if seckey.key.curve notin EcSupportedCurvesCint:
|
||||
@@ -295,7 +295,7 @@ proc `$`*(seckey: EcPrivateKey): string =
|
||||
proc `$`*(pubkey: EcPublicKey): string =
|
||||
## Return string representation of EC public key.
|
||||
if isNil(pubkey) or pubkey.key.curve == 0 or pubkey.key.qlen == 0 or
|
||||
len(pubkey.buffer) == 0:
|
||||
len(pubkey.buffer) == 0:
|
||||
result = "Empty or uninitialized ECNIST key"
|
||||
else:
|
||||
if pubkey.key.curve notin EcSupportedCurvesCint:
|
||||
@@ -368,32 +368,30 @@ proc toBytes*(seckey: EcPrivateKey, data: var openArray[byte]): EcResult[int] =
|
||||
return err(EcKeyIncorrectError)
|
||||
if seckey.key.curve in EcSupportedCurvesCint:
|
||||
var offset, length: int
|
||||
var pubkey = ? seckey.getPublicKey()
|
||||
var pubkey = ?seckey.getPublicKey()
|
||||
var b = Asn1Buffer.init()
|
||||
var p = Asn1Composite.init(Asn1Tag.Sequence)
|
||||
var c0 = Asn1Composite.init(0)
|
||||
var c1 = Asn1Composite.init(1)
|
||||
if seckey.key.curve == BR_EC_SECP256R1:
|
||||
if seckey.key.curve == EC_secp256r1:
|
||||
c0.write(Asn1Tag.Oid, Asn1OidSecp256r1)
|
||||
elif seckey.key.curve == BR_EC_SECP384R1:
|
||||
elif seckey.key.curve == EC_secp384r1:
|
||||
c0.write(Asn1Tag.Oid, Asn1OidSecp384r1)
|
||||
elif seckey.key.curve == BR_EC_SECP521R1:
|
||||
elif seckey.key.curve == EC_secp521r1:
|
||||
c0.write(Asn1Tag.Oid, Asn1OidSecp521r1)
|
||||
c0.finish()
|
||||
offset = pubkey.getOffset()
|
||||
if offset < 0:
|
||||
return err(EcKeyIncorrectError)
|
||||
length = pubkey.key.qlen
|
||||
c1.write(Asn1Tag.BitString,
|
||||
pubkey.buffer.toOpenArray(offset, offset + length - 1))
|
||||
length = int(pubkey.key.qlen)
|
||||
c1.write(Asn1Tag.BitString, pubkey.buffer.toOpenArray(offset, offset + length - 1))
|
||||
c1.finish()
|
||||
offset = seckey.getOffset()
|
||||
if offset < 0:
|
||||
return err(EcKeyIncorrectError)
|
||||
length = seckey.key.xlen
|
||||
length = int(seckey.key.xlen)
|
||||
p.write(1'u64)
|
||||
p.write(Asn1Tag.OctetString,
|
||||
seckey.buffer.toOpenArray(offset, offset + length - 1))
|
||||
p.write(Asn1Tag.OctetString, seckey.buffer.toOpenArray(offset, offset + length - 1))
|
||||
p.write(c0)
|
||||
p.write(c1)
|
||||
p.finish()
|
||||
@@ -407,7 +405,6 @@ proc toBytes*(seckey: EcPrivateKey, data: var openArray[byte]): EcResult[int] =
|
||||
else:
|
||||
err(EcKeyIncorrectError)
|
||||
|
||||
|
||||
proc toBytes*(pubkey: EcPublicKey, data: var openArray[byte]): EcResult[int] =
|
||||
## Serialize EC public key ``pubkey`` to ASN.1 DER binary form and store it
|
||||
## to ``data``.
|
||||
@@ -421,20 +418,19 @@ proc toBytes*(pubkey: EcPublicKey, data: var openArray[byte]): EcResult[int] =
|
||||
var p = Asn1Composite.init(Asn1Tag.Sequence)
|
||||
var c = Asn1Composite.init(Asn1Tag.Sequence)
|
||||
c.write(Asn1Tag.Oid, Asn1OidEcPublicKey)
|
||||
if pubkey.key.curve == BR_EC_SECP256R1:
|
||||
if pubkey.key.curve == EC_secp256r1:
|
||||
c.write(Asn1Tag.Oid, Asn1OidSecp256r1)
|
||||
elif pubkey.key.curve == BR_EC_SECP384R1:
|
||||
elif pubkey.key.curve == EC_secp384r1:
|
||||
c.write(Asn1Tag.Oid, Asn1OidSecp384r1)
|
||||
elif pubkey.key.curve == BR_EC_SECP521R1:
|
||||
elif pubkey.key.curve == EC_secp521r1:
|
||||
c.write(Asn1Tag.Oid, Asn1OidSecp521r1)
|
||||
c.finish()
|
||||
p.write(c)
|
||||
let offset = getOffset(pubkey)
|
||||
if offset < 0:
|
||||
return err(EcKeyIncorrectError)
|
||||
let length = pubkey.key.qlen
|
||||
p.write(Asn1Tag.BitString,
|
||||
pubkey.buffer.toOpenArray(offset, offset + length - 1))
|
||||
let length = int(pubkey.key.qlen)
|
||||
p.write(Asn1Tag.BitString, pubkey.buffer.toOpenArray(offset, offset + length - 1))
|
||||
p.finish()
|
||||
b.write(p)
|
||||
b.finish()
|
||||
@@ -463,10 +459,10 @@ proc getBytes*(seckey: EcPrivateKey): EcResult[seq[byte]] =
|
||||
if isNil(seckey):
|
||||
return err(EcKeyIncorrectError)
|
||||
if seckey.key.curve in EcSupportedCurvesCint:
|
||||
var res = newSeq[byte]()
|
||||
let length = ? seckey.toBytes(res)
|
||||
var res = newSeqUninit[byte](0)
|
||||
let length = ?seckey.toBytes(res)
|
||||
res.setLen(length)
|
||||
discard ? seckey.toBytes(res)
|
||||
discard ?seckey.toBytes(res)
|
||||
ok(res)
|
||||
else:
|
||||
err(EcKeyIncorrectError)
|
||||
@@ -476,10 +472,10 @@ proc getBytes*(pubkey: EcPublicKey): EcResult[seq[byte]] =
|
||||
if isNil(pubkey):
|
||||
return err(EcKeyIncorrectError)
|
||||
if pubkey.key.curve in EcSupportedCurvesCint:
|
||||
var res = newSeq[byte]()
|
||||
let length = ? pubkey.toBytes(res)
|
||||
var res = newSeqUninit[byte](0)
|
||||
let length = ?pubkey.toBytes(res)
|
||||
res.setLen(length)
|
||||
discard ? pubkey.toBytes(res)
|
||||
discard ?pubkey.toBytes(res)
|
||||
ok(res)
|
||||
else:
|
||||
err(EcKeyIncorrectError)
|
||||
@@ -488,10 +484,10 @@ proc getBytes*(sig: EcSignature): EcResult[seq[byte]] =
|
||||
## Serialize EC signature ``sig`` to ASN.1 DER binary form and return it.
|
||||
if isNil(sig):
|
||||
return err(EcSignatureError)
|
||||
var res = newSeq[byte]()
|
||||
let length = ? sig.toBytes(res)
|
||||
var res = newSeqUninit[byte](0)
|
||||
let length = ?sig.toBytes(res)
|
||||
res.setLen(length)
|
||||
discard ? sig.toBytes(res)
|
||||
discard ?sig.toBytes(res)
|
||||
ok(res)
|
||||
|
||||
proc getRawBytes*(seckey: EcPrivateKey): EcResult[seq[byte]] =
|
||||
@@ -499,10 +495,10 @@ proc getRawBytes*(seckey: EcPrivateKey): EcResult[seq[byte]] =
|
||||
if isNil(seckey):
|
||||
return err(EcKeyIncorrectError)
|
||||
if seckey.key.curve in EcSupportedCurvesCint:
|
||||
var res = newSeq[byte]()
|
||||
let length = ? seckey.toRawBytes(res)
|
||||
var res = newSeqUninit[byte](0)
|
||||
let length = ?seckey.toRawBytes(res)
|
||||
res.setLen(length)
|
||||
discard ? seckey.toRawBytes(res)
|
||||
discard ?seckey.toRawBytes(res)
|
||||
ok(res)
|
||||
else:
|
||||
err(EcKeyIncorrectError)
|
||||
@@ -512,10 +508,10 @@ proc getRawBytes*(pubkey: EcPublicKey): EcResult[seq[byte]] =
|
||||
if isNil(pubkey):
|
||||
return err(EcKeyIncorrectError)
|
||||
if pubkey.key.curve in EcSupportedCurvesCint:
|
||||
var res = newSeq[byte]()
|
||||
let length = ? pubkey.toRawBytes(res)
|
||||
var res = newSeqUninit[byte](0)
|
||||
let length = ?pubkey.toRawBytes(res)
|
||||
res.setLen(length)
|
||||
discard ? pubkey.toRawBytes(res)
|
||||
discard ?pubkey.toRawBytes(res)
|
||||
return ok(res)
|
||||
else:
|
||||
return err(EcKeyIncorrectError)
|
||||
@@ -524,10 +520,10 @@ proc getRawBytes*(sig: EcSignature): EcResult[seq[byte]] =
|
||||
## Serialize EC signature ``sig`` to raw binary form and return it.
|
||||
if isNil(sig):
|
||||
return err(EcSignatureError)
|
||||
var res = newSeq[byte]()
|
||||
let length = ? sig.toBytes(res)
|
||||
var res = newSeqUninit[byte](0)
|
||||
let length = ?sig.toBytes(res)
|
||||
res.setLen(length)
|
||||
discard ? sig.toBytes(res)
|
||||
discard ?sig.toBytes(res)
|
||||
ok(res)
|
||||
|
||||
proc `==`*(pubkey1, pubkey2: EcPublicKey): bool =
|
||||
@@ -547,8 +543,10 @@ proc `==`*(pubkey1, pubkey2: EcPublicKey): bool =
|
||||
let op2 = pubkey2.getOffset()
|
||||
if op1 == -1 or op2 == -1:
|
||||
return false
|
||||
return CT.isEqual(pubkey1.buffer.toOpenArray(op1, pubkey1.key.qlen - 1),
|
||||
pubkey2.buffer.toOpenArray(op2, pubkey2.key.qlen - 1))
|
||||
return CT.isEqual(
|
||||
pubkey1.buffer.toOpenArray(op1, pubkey1.key.qlen - 1),
|
||||
pubkey2.buffer.toOpenArray(op2, pubkey2.key.qlen - 1),
|
||||
)
|
||||
|
||||
proc `==`*(seckey1, seckey2: EcPrivateKey): bool =
|
||||
## Returns ``true`` if both keys ``seckey1`` and ``seckey2`` are equal.
|
||||
@@ -567,8 +565,10 @@ proc `==`*(seckey1, seckey2: EcPrivateKey): bool =
|
||||
let op2 = seckey2.getOffset()
|
||||
if op1 == -1 or op2 == -1:
|
||||
return false
|
||||
return CT.isEqual(seckey1.buffer.toOpenArray(op1, seckey1.key.xlen - 1),
|
||||
seckey2.buffer.toOpenArray(op2, seckey2.key.xlen - 1))
|
||||
return CT.isEqual(
|
||||
seckey1.buffer.toOpenArray(op1, seckey1.key.xlen - 1),
|
||||
seckey2.buffer.toOpenArray(op2, seckey2.key.xlen - 1),
|
||||
)
|
||||
|
||||
proc `==`*(a, b: EcSignature): bool =
|
||||
## Return ``true`` if both signatures ``sig1`` and ``sig2`` are equal.
|
||||
@@ -602,44 +602,44 @@ proc init*(key: var EcPrivateKey, data: openArray[byte]): Result[void, Asn1Error
|
||||
|
||||
var ab = Asn1Buffer.init(data)
|
||||
|
||||
field = ? ab.read()
|
||||
field = ?ab.read()
|
||||
|
||||
if field.kind != Asn1Tag.Sequence:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
var ib = field.getBuffer()
|
||||
|
||||
field = ? ib.read()
|
||||
field = ?ib.read()
|
||||
|
||||
if field.kind != Asn1Tag.Integer:
|
||||
return err(Asn1Error.Incorrect)
|
||||
if field.vint != 1'u64:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
raw = ? ib.read()
|
||||
raw = ?ib.read()
|
||||
|
||||
if raw.kind != Asn1Tag.OctetString:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
oid = ? ib.read()
|
||||
oid = ?ib.read()
|
||||
|
||||
if oid.kind != Asn1Tag.Oid:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
if oid == Asn1OidSecp256r1:
|
||||
curve = cast[cint](Secp256r1)
|
||||
curve = safeConvert[cint](Secp256r1)
|
||||
elif oid == Asn1OidSecp384r1:
|
||||
curve = cast[cint](Secp384r1)
|
||||
curve = safeConvert[cint](Secp384r1)
|
||||
elif oid == Asn1OidSecp521r1:
|
||||
curve = cast[cint](Secp521r1)
|
||||
curve = safeConvert[cint](Secp521r1)
|
||||
else:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
if checkScalar(raw.toOpenArray(), curve) == 1'u32:
|
||||
key = new EcPrivateKey
|
||||
copyMem(addr key.buffer[0], addr raw.buffer[raw.offset], raw.length)
|
||||
key.key.x = cast[ptr cuchar](addr key.buffer[0])
|
||||
key.key.xlen = raw.length
|
||||
key.key.x = addr key.buffer[0]
|
||||
key.key.xlen = uint(raw.length)
|
||||
key.key.curve = curve
|
||||
ok()
|
||||
else:
|
||||
@@ -655,19 +655,19 @@ proc init*(pubkey: var EcPublicKey, data: openArray[byte]): Result[void, Asn1Err
|
||||
|
||||
var ab = Asn1Buffer.init(data)
|
||||
|
||||
field = ? ab.read()
|
||||
field = ?ab.read()
|
||||
|
||||
if field.kind != Asn1Tag.Sequence:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
var ib = field.getBuffer()
|
||||
field = ? ib.read()
|
||||
field = ?ib.read()
|
||||
|
||||
if field.kind != Asn1Tag.Sequence:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
var ob = field.getBuffer()
|
||||
oid = ? ob.read()
|
||||
oid = ?ob.read()
|
||||
|
||||
if oid.kind != Asn1Tag.Oid:
|
||||
return err(Asn1Error.Incorrect)
|
||||
@@ -675,21 +675,21 @@ proc init*(pubkey: var EcPublicKey, data: openArray[byte]): Result[void, Asn1Err
|
||||
if oid != Asn1OidEcPublicKey:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
oid = ? ob.read()
|
||||
oid = ?ob.read()
|
||||
|
||||
if oid.kind != Asn1Tag.Oid:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
if oid == Asn1OidSecp256r1:
|
||||
curve = cast[cint](Secp256r1)
|
||||
curve = safeConvert[cint](Secp256r1)
|
||||
elif oid == Asn1OidSecp384r1:
|
||||
curve = cast[cint](Secp384r1)
|
||||
curve = safeConvert[cint](Secp384r1)
|
||||
elif oid == Asn1OidSecp521r1:
|
||||
curve = cast[cint](Secp521r1)
|
||||
curve = safeConvert[cint](Secp521r1)
|
||||
else:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
raw = ? ib.read()
|
||||
raw = ?ib.read()
|
||||
|
||||
if raw.kind != Asn1Tag.BitString:
|
||||
return err(Asn1Error.Incorrect)
|
||||
@@ -697,8 +697,8 @@ proc init*(pubkey: var EcPublicKey, data: openArray[byte]): Result[void, Asn1Err
|
||||
if checkPublic(raw.toOpenArray(), curve) != 0:
|
||||
pubkey = new EcPublicKey
|
||||
copyMem(addr pubkey.buffer[0], addr raw.buffer[raw.offset], raw.length)
|
||||
pubkey.key.q = cast[ptr cuchar](addr pubkey.buffer[0])
|
||||
pubkey.key.qlen = raw.length
|
||||
pubkey.key.q = addr pubkey.buffer[0]
|
||||
pubkey.key.qlen = uint(raw.length)
|
||||
pubkey.key.curve = curve
|
||||
ok()
|
||||
else:
|
||||
@@ -715,16 +715,14 @@ proc init*(sig: var EcSignature, data: openArray[byte]): Result[void, Asn1Error]
|
||||
else:
|
||||
err(Asn1Error.Incorrect)
|
||||
|
||||
proc init*[T: EcPKI](sospk: var T,
|
||||
data: string): Result[void, Asn1Error] {.inline.} =
|
||||
proc init*[T: EcPKI](sospk: var T, data: string): Result[void, Asn1Error] {.inline.} =
|
||||
## Initialize EC `private key`, `public key` or `signature` ``sospk`` from
|
||||
## ASN.1 DER hexadecimal string representation ``data``.
|
||||
##
|
||||
## Procedure returns ``Asn1Status``.
|
||||
sospk.init(ncrutils.fromHex(data))
|
||||
|
||||
proc init*(t: typedesc[EcPrivateKey],
|
||||
data: openArray[byte]): EcResult[EcPrivateKey] =
|
||||
proc init*(t: typedesc[EcPrivateKey], data: openArray[byte]): EcResult[EcPrivateKey] =
|
||||
## Initialize EC private key from ASN.1 DER binary representation ``data`` and
|
||||
## return constructed object.
|
||||
var key: EcPrivateKey
|
||||
@@ -734,8 +732,7 @@ proc init*(t: typedesc[EcPrivateKey],
|
||||
else:
|
||||
ok(key)
|
||||
|
||||
proc init*(t: typedesc[EcPublicKey],
|
||||
data: openArray[byte]): EcResult[EcPublicKey] =
|
||||
proc init*(t: typedesc[EcPublicKey], data: openArray[byte]): EcResult[EcPublicKey] =
|
||||
## Initialize EC public key from ASN.1 DER binary representation ``data`` and
|
||||
## return constructed object.
|
||||
var key: EcPublicKey
|
||||
@@ -745,8 +742,7 @@ proc init*(t: typedesc[EcPublicKey],
|
||||
else:
|
||||
ok(key)
|
||||
|
||||
proc init*(t: typedesc[EcSignature],
|
||||
data: openArray[byte]): EcResult[EcSignature] =
|
||||
proc init*(t: typedesc[EcSignature], data: openArray[byte]): EcResult[EcSignature] =
|
||||
## Initialize EC signature from raw binary representation ``data`` and
|
||||
## return constructed object.
|
||||
var sig: EcSignature
|
||||
@@ -771,13 +767,13 @@ proc initRaw*(key: var EcPrivateKey, data: openArray[byte]): bool =
|
||||
## Procedure returns ``true`` on success, ``false`` otherwise.
|
||||
var curve: cint
|
||||
if len(data) == SecKey256Length:
|
||||
curve = cast[cint](Secp256r1)
|
||||
curve = safeConvert[cint](Secp256r1)
|
||||
result = true
|
||||
elif len(data) == SecKey384Length:
|
||||
curve = cast[cint](Secp384r1)
|
||||
curve = safeConvert[cint](Secp384r1)
|
||||
result = true
|
||||
elif len(data) == SecKey521Length:
|
||||
curve = cast[cint](Secp521r1)
|
||||
curve = safeConvert[cint](Secp521r1)
|
||||
result = true
|
||||
if result:
|
||||
result = false
|
||||
@@ -785,8 +781,8 @@ proc initRaw*(key: var EcPrivateKey, data: openArray[byte]): bool =
|
||||
let length = len(data)
|
||||
key = new EcPrivateKey
|
||||
copyMem(addr key.buffer[0], unsafeAddr data[0], length)
|
||||
key.key.x = cast[ptr cuchar](addr key.buffer[0])
|
||||
key.key.xlen = length
|
||||
key.key.x = addr key.buffer[0]
|
||||
key.key.xlen = uint(length)
|
||||
key.key.curve = curve
|
||||
result = true
|
||||
|
||||
@@ -802,13 +798,13 @@ proc initRaw*(pubkey: var EcPublicKey, data: openArray[byte]): bool =
|
||||
if len(data) > 0:
|
||||
if data[0] == 0x04'u8:
|
||||
if len(data) == PubKey256Length:
|
||||
curve = cast[cint](Secp256r1)
|
||||
curve = safeConvert[cint](Secp256r1)
|
||||
result = true
|
||||
elif len(data) == PubKey384Length:
|
||||
curve = cast[cint](Secp384r1)
|
||||
curve = safeConvert[cint](Secp384r1)
|
||||
result = true
|
||||
elif len(data) == PubKey521Length:
|
||||
curve = cast[cint](Secp521r1)
|
||||
curve = safeConvert[cint](Secp521r1)
|
||||
result = true
|
||||
if result:
|
||||
result = false
|
||||
@@ -816,8 +812,8 @@ proc initRaw*(pubkey: var EcPublicKey, data: openArray[byte]): bool =
|
||||
let length = len(data)
|
||||
pubkey = new EcPublicKey
|
||||
copyMem(addr pubkey.buffer[0], unsafeAddr data[0], length)
|
||||
pubkey.key.q = cast[ptr cuchar](addr pubkey.buffer[0])
|
||||
pubkey.key.qlen = length
|
||||
pubkey.key.q = addr pubkey.buffer[0]
|
||||
pubkey.key.qlen = uint(length)
|
||||
pubkey.key.curve = curve
|
||||
result = true
|
||||
|
||||
@@ -829,8 +825,7 @@ proc initRaw*(sig: var EcSignature, data: openArray[byte]): bool =
|
||||
##
|
||||
## Procedure returns ``true`` on success, ``false`` otherwise.
|
||||
let length = len(data)
|
||||
if (length == Sig256Length) or (length == Sig384Length) or
|
||||
(length == Sig521Length):
|
||||
if (length == Sig256Length) or (length == Sig384Length) or (length == Sig521Length):
|
||||
result = true
|
||||
if result:
|
||||
sig = new EcSignature
|
||||
@@ -843,8 +838,9 @@ proc initRaw*[T: EcPKI](sospk: var T, data: string): bool {.inline.} =
|
||||
## Procedure returns ``true`` on success, ``false`` otherwise.
|
||||
result = sospk.initRaw(ncrutils.fromHex(data))
|
||||
|
||||
proc initRaw*(t: typedesc[EcPrivateKey],
|
||||
data: openArray[byte]): EcResult[EcPrivateKey] =
|
||||
proc initRaw*(
|
||||
t: typedesc[EcPrivateKey], data: openArray[byte]
|
||||
): EcResult[EcPrivateKey] =
|
||||
## Initialize EC private key from raw binary representation ``data`` and
|
||||
## return constructed object.
|
||||
var res: EcPrivateKey
|
||||
@@ -853,8 +849,7 @@ proc initRaw*(t: typedesc[EcPrivateKey],
|
||||
else:
|
||||
ok(res)
|
||||
|
||||
proc initRaw*(t: typedesc[EcPublicKey],
|
||||
data: openArray[byte]): EcResult[EcPublicKey] =
|
||||
proc initRaw*(t: typedesc[EcPublicKey], data: openArray[byte]): EcResult[EcPublicKey] =
|
||||
## Initialize EC public key from raw binary representation ``data`` and
|
||||
## return constructed object.
|
||||
var res: EcPublicKey
|
||||
@@ -863,8 +858,7 @@ proc initRaw*(t: typedesc[EcPublicKey],
|
||||
else:
|
||||
ok(res)
|
||||
|
||||
proc initRaw*(t: typedesc[EcSignature],
|
||||
data: openArray[byte]): EcResult[EcSignature] =
|
||||
proc initRaw*(t: typedesc[EcSignature], data: openArray[byte]): EcResult[EcSignature] =
|
||||
## Initialize EC signature from raw binary representation ``data`` and
|
||||
## return constructed object.
|
||||
var res: EcSignature
|
||||
@@ -883,7 +877,7 @@ proc scalarMul*(pub: EcPublicKey, sec: EcPrivateKey): EcPublicKey =
|
||||
##
|
||||
## Returns point in curve as ``pub * sec`` or ``nil`` otherwise.
|
||||
doAssert((not isNil(pub)) and (not isNil(sec)))
|
||||
var impl = brEcGetDefault()
|
||||
var impl = ecGetDefault()
|
||||
if sec.key.curve in EcSupportedCurvesCint:
|
||||
if pub.key.curve == sec.key.curve:
|
||||
var key = new EcPublicKey
|
||||
@@ -891,16 +885,19 @@ proc scalarMul*(pub: EcPublicKey, sec: EcPrivateKey): EcPublicKey =
|
||||
let poffset = key.getOffset()
|
||||
let soffset = sec.getOffset()
|
||||
if poffset >= 0 and soffset >= 0:
|
||||
let res = impl.mul(cast[ptr cuchar](addr key.buffer[poffset]),
|
||||
key.key.qlen,
|
||||
cast[ptr cuchar](unsafeAddr sec.buffer[soffset]),
|
||||
sec.key.xlen,
|
||||
key.key.curve)
|
||||
let res = impl.mul(
|
||||
addr key.buffer[poffset],
|
||||
key.key.qlen,
|
||||
unsafeAddr sec.buffer[soffset],
|
||||
sec.key.xlen,
|
||||
key.key.curve,
|
||||
)
|
||||
if res != 0:
|
||||
result = key
|
||||
|
||||
proc toSecret*(pubkey: EcPublicKey, seckey: EcPrivateKey,
|
||||
data: var openArray[byte]): int =
|
||||
proc toSecret*(
|
||||
pubkey: EcPublicKey, seckey: EcPrivateKey, data: var openArray[byte]
|
||||
): int =
|
||||
## Calculate ECDHE shared secret using Go's elliptic/curve approach, using
|
||||
## remote public key ``pubkey`` and local private key ``seckey`` and store
|
||||
## shared secret to ``data``.
|
||||
@@ -913,11 +910,11 @@ proc toSecret*(pubkey: EcPublicKey, seckey: EcPrivateKey,
|
||||
doAssert((not isNil(pubkey)) and (not isNil(seckey)))
|
||||
var mult = scalarMul(pubkey, seckey)
|
||||
if not isNil(mult):
|
||||
if seckey.key.curve == BR_EC_SECP256R1:
|
||||
if seckey.key.curve == EC_secp256r1:
|
||||
result = Secret256Length
|
||||
elif seckey.key.curve == BR_EC_SECP384R1:
|
||||
elif seckey.key.curve == EC_secp384r1:
|
||||
result = Secret384Length
|
||||
elif seckey.key.curve == BR_EC_SECP521R1:
|
||||
elif seckey.key.curve == EC_secp521r1:
|
||||
result = Secret521Length
|
||||
if len(data) >= result:
|
||||
var qplus1 = cast[pointer](cast[uint](mult.key.q) + 1'u)
|
||||
@@ -933,29 +930,30 @@ proc getSecret*(pubkey: EcPublicKey, seckey: EcPrivateKey): seq[byte] =
|
||||
var data: array[Secret521Length, byte]
|
||||
let res = toSecret(pubkey, seckey, data)
|
||||
if res > 0:
|
||||
result = newSeq[byte](res)
|
||||
result = newSeqUninit[byte](res)
|
||||
copyMem(addr result[0], addr data[0], res)
|
||||
|
||||
proc sign*[T: byte|char](seckey: EcPrivateKey,
|
||||
message: openArray[T]): EcResult[EcSignature] {.gcsafe.} =
|
||||
proc sign*[T: byte | char](
|
||||
seckey: EcPrivateKey, message: openArray[T]
|
||||
): EcResult[EcSignature] {.gcsafe.} =
|
||||
## Get ECDSA signature of data ``message`` using private key ``seckey``.
|
||||
if isNil(seckey):
|
||||
return err(EcKeyIncorrectError)
|
||||
var hc: BrHashCompatContext
|
||||
var hc: HashCompatContext
|
||||
var hash: array[32, byte]
|
||||
var impl = brEcGetDefault()
|
||||
var impl = ecGetDefault()
|
||||
if seckey.key.curve in EcSupportedCurvesCint:
|
||||
var sig = new EcSignature
|
||||
sig.buffer = newSeq[byte](256)
|
||||
sig.buffer = newSeqUninit[byte](256)
|
||||
var kv = addr sha256Vtable
|
||||
kv.init(addr hc.vtable)
|
||||
if len(message) > 0:
|
||||
kv.update(addr hc.vtable, unsafeAddr message[0], len(message))
|
||||
kv.update(addr hc.vtable, unsafeAddr message[0], uint(len(message)))
|
||||
else:
|
||||
kv.update(addr hc.vtable, nil, 0)
|
||||
kv.output(addr hc.vtable, addr hash[0])
|
||||
let res = brEcdsaSignAsn1(impl, kv, addr hash[0], addr seckey.key,
|
||||
addr sig.buffer[0])
|
||||
kv.out(addr hc.vtable, addr hash[0])
|
||||
let res =
|
||||
ecdsaI31SignAsn1(impl, kv, addr hash[0], addr seckey.key, addr sig.buffer[0])
|
||||
# Clear context with initial value
|
||||
kv.init(addr hc.vtable)
|
||||
if res != 0:
|
||||
@@ -966,28 +964,61 @@ proc sign*[T: byte|char](seckey: EcPrivateKey,
|
||||
else:
|
||||
err(EcKeyIncorrectError)
|
||||
|
||||
proc verify*[T: byte|char](sig: EcSignature, message: openArray[T],
|
||||
pubkey: EcPublicKey): bool {.inline.} =
|
||||
proc verify*[T: byte | char](
|
||||
sig: EcSignature, message: openArray[T], pubkey: EcPublicKey
|
||||
): bool {.inline.} =
|
||||
## Verify ECDSA signature ``sig`` using public key ``pubkey`` and data
|
||||
## ``message``.
|
||||
##
|
||||
## Return ``true`` if message verification succeeded, ``false`` if
|
||||
## verification failed.
|
||||
doAssert((not isNil(sig)) and (not isNil(pubkey)))
|
||||
var hc: BrHashCompatContext
|
||||
var hc: HashCompatContext
|
||||
var hash: array[32, byte]
|
||||
var impl = brEcGetDefault()
|
||||
var impl = ecGetDefault()
|
||||
if pubkey.key.curve in EcSupportedCurvesCint:
|
||||
var kv = addr sha256Vtable
|
||||
kv.init(addr hc.vtable)
|
||||
if len(message) > 0:
|
||||
kv.update(addr hc.vtable, unsafeAddr message[0], len(message))
|
||||
kv.update(addr hc.vtable, unsafeAddr message[0], uint(len(message)))
|
||||
else:
|
||||
kv.update(addr hc.vtable, nil, 0)
|
||||
kv.output(addr hc.vtable, addr hash[0])
|
||||
let res = brEcdsaVerifyAsn1(impl, addr hash[0], len(hash),
|
||||
unsafeAddr pubkey.key,
|
||||
addr sig.buffer[0], len(sig.buffer))
|
||||
kv.out(addr hc.vtable, addr hash[0])
|
||||
let res = ecdsaI31VrfyAsn1(
|
||||
impl,
|
||||
addr hash[0],
|
||||
uint(len(hash)),
|
||||
unsafeAddr pubkey.key,
|
||||
addr sig.buffer[0],
|
||||
uint(len(sig.buffer)),
|
||||
)
|
||||
# Clear context with initial value
|
||||
kv.init(addr hc.vtable)
|
||||
result = (res == 1)
|
||||
|
||||
type ECDHEScheme* = EcCurveKind
|
||||
|
||||
proc ephemeral*(scheme: ECDHEScheme, rng: var HmacDrbgContext): EcResult[EcKeyPair] =
|
||||
## Generate ephemeral keys used to perform ECDHE.
|
||||
var keypair: EcKeyPair
|
||||
if scheme == Secp256r1:
|
||||
keypair = ?EcKeyPair.random(Secp256r1, rng)
|
||||
elif scheme == Secp384r1:
|
||||
keypair = ?EcKeyPair.random(Secp384r1, rng)
|
||||
elif scheme == Secp521r1:
|
||||
keypair = ?EcKeyPair.random(Secp521r1, rng)
|
||||
ok(keypair)
|
||||
|
||||
proc ephemeral*(scheme: string, rng: var HmacDrbgContext): EcResult[EcKeyPair] =
|
||||
## Generate ephemeral keys used to perform ECDHE using string encoding.
|
||||
##
|
||||
## Currently supported encoding strings are P-256, P-384, P-521, if encoding
|
||||
## string is not supported P-521 key will be generated.
|
||||
if scheme == "P-256":
|
||||
ephemeral(Secp256r1, rng)
|
||||
elif scheme == "P-384":
|
||||
ephemeral(Secp384r1, rng)
|
||||
elif scheme == "P-521":
|
||||
ephemeral(Secp521r1, rng)
|
||||
else:
|
||||
ephemeral(Secp521r1, rng)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,40 +1,53 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2020 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
# https://tools.ietf.org/html/rfc5869
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import nimcrypto
|
||||
import bearssl
|
||||
import bearssl/[kdf, hash]
|
||||
|
||||
type
|
||||
BearHKDFContext {.importc: "br_hkdf_context", header: "bearssl_kdf.h".} = object
|
||||
HKDFResult*[len: static int] = array[len, byte]
|
||||
type HkdfResult*[len: static int] = array[len, byte]
|
||||
|
||||
proc br_hkdf_init(ctx: ptr BearHKDFContext; hashClass: ptr HashClass; salt: pointer; len: csize_t) {.importc: "br_hkdf_init", header: "bearssl_kdf.h", raises: [].}
|
||||
proc br_hkdf_inject(ctx: ptr BearHKDFContext; ikm: pointer; len: csize_t) {.importc: "br_hkdf_inject", header: "bearssl_kdf.h", raises: [].}
|
||||
proc br_hkdf_flip(ctx: ptr BearHKDFContext) {.importc: "br_hkdf_flip", header: "bearssl_kdf.h", raises: [].}
|
||||
proc br_hkdf_produce(ctx: ptr BearHKDFContext; info: pointer; infoLen: csize_t; output: pointer; outputLen: csize_t) {.importc: "br_hkdf_produce", header: "bearssl_kdf.h", raises: [].}
|
||||
|
||||
proc hkdf*[T: sha256; len: static int](_: type[T]; salt, ikm, info: openArray[byte]; outputs: var openArray[HKDFResult[len]]) =
|
||||
var
|
||||
ctx: BearHKDFContext
|
||||
br_hkdf_init(
|
||||
addr ctx, addr sha256Vtable,
|
||||
if salt.len > 0: unsafeAddr salt[0] else: nil, csize_t(salt.len))
|
||||
br_hkdf_inject(
|
||||
addr ctx, if ikm.len > 0: unsafeAddr ikm[0] else: nil, csize_t(ikm.len))
|
||||
br_hkdf_flip(addr ctx)
|
||||
for i in 0..outputs.high:
|
||||
br_hkdf_produce(
|
||||
addr ctx,
|
||||
if info.len > 0: unsafeAddr info[0]
|
||||
else: nil, csize_t(info.len),
|
||||
addr outputs[i][0], csize_t(outputs[i].len))
|
||||
proc hkdf*[T: sha256, len: static int](
|
||||
_: type[T],
|
||||
salt, ikm, info: openArray[byte],
|
||||
outputs: var openArray[HkdfResult[len]],
|
||||
) =
|
||||
var ctx: HkdfContext
|
||||
hkdfInit(
|
||||
ctx,
|
||||
addr sha256Vtable,
|
||||
if salt.len > 0:
|
||||
unsafeAddr salt[0]
|
||||
else:
|
||||
nil,
|
||||
csize_t(salt.len),
|
||||
)
|
||||
hkdfInject(
|
||||
ctx,
|
||||
if ikm.len > 0:
|
||||
unsafeAddr ikm[0]
|
||||
else:
|
||||
nil,
|
||||
csize_t(ikm.len),
|
||||
)
|
||||
hkdfFlip(ctx)
|
||||
for i in 0 .. outputs.high:
|
||||
discard hkdfProduce(
|
||||
ctx,
|
||||
if info.len > 0:
|
||||
unsafeAddr info[0]
|
||||
else:
|
||||
nil,
|
||||
csize_t(info.len),
|
||||
addr outputs[i][0],
|
||||
csize_t(outputs[i].len),
|
||||
)
|
||||
|
||||
@@ -1,52 +1,54 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implements minimal ASN.1 encoding/decoding primitives.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import stew/[endians2, results, ctops]
|
||||
import stew/[endians2, ctops]
|
||||
import results
|
||||
export results
|
||||
# We use `ncrutils` for constant-time hexadecimal encoding/decoding procedures.
|
||||
import nimcrypto/utils as ncrutils
|
||||
import ../utility
|
||||
import ../utils/sequninit
|
||||
|
||||
type
|
||||
Asn1Error* {.pure.} = enum
|
||||
Overflow,
|
||||
Incomplete,
|
||||
Indefinite,
|
||||
Incorrect,
|
||||
NoSupport,
|
||||
Overflow
|
||||
Incomplete
|
||||
Indefinite
|
||||
Incorrect
|
||||
NoSupport
|
||||
Overrun
|
||||
|
||||
Asn1Result*[T] = Result[T, Asn1Error]
|
||||
|
||||
Asn1Class* {.pure.} = enum
|
||||
Universal = 0x00,
|
||||
Universal = 0x00
|
||||
Application = 0x01
|
||||
ContextSpecific = 0x02
|
||||
Private = 0x03
|
||||
|
||||
Asn1Tag* {.pure.} = enum
|
||||
## Protobuf's field types enum
|
||||
NoSupport,
|
||||
Boolean,
|
||||
Integer,
|
||||
BitString,
|
||||
OctetString,
|
||||
Null,
|
||||
Oid,
|
||||
Sequence,
|
||||
NoSupport
|
||||
Boolean
|
||||
Integer
|
||||
BitString
|
||||
OctetString
|
||||
Null
|
||||
Oid
|
||||
Sequence
|
||||
Context
|
||||
|
||||
Asn1Buffer* = object of RootObj
|
||||
## ASN.1's message representation object
|
||||
Asn1Buffer* = object of RootObj ## ASN.1's message representation object
|
||||
buffer*: seq[byte]
|
||||
offset*: int
|
||||
length*: int
|
||||
@@ -72,37 +74,23 @@ type
|
||||
idx*: int
|
||||
|
||||
const
|
||||
Asn1OidSecp256r1* = [
|
||||
0x2A'u8, 0x86'u8, 0x48'u8, 0xCE'u8, 0x3D'u8, 0x03'u8, 0x01'u8, 0x07'u8
|
||||
]
|
||||
Asn1OidSecp256r1* =
|
||||
[0x2A'u8, 0x86'u8, 0x48'u8, 0xCE'u8, 0x3D'u8, 0x03'u8, 0x01'u8, 0x07'u8]
|
||||
## Encoded OID for `secp256r1` curve (1.2.840.10045.3.1.7)
|
||||
Asn1OidSecp384r1* = [
|
||||
0x2B'u8, 0x81'u8, 0x04'u8, 0x00'u8, 0x22'u8
|
||||
]
|
||||
Asn1OidSecp384r1* = [0x2B'u8, 0x81'u8, 0x04'u8, 0x00'u8, 0x22'u8]
|
||||
## Encoded OID for `secp384r1` curve (1.3.132.0.34)
|
||||
Asn1OidSecp521r1* = [
|
||||
0x2B'u8, 0x81'u8, 0x04'u8, 0x00'u8, 0x23'u8
|
||||
]
|
||||
Asn1OidSecp521r1* = [0x2B'u8, 0x81'u8, 0x04'u8, 0x00'u8, 0x23'u8]
|
||||
## Encoded OID for `secp521r1` curve (1.3.132.0.35)
|
||||
Asn1OidSecp256k1* = [
|
||||
0x2B'u8, 0x81'u8, 0x04'u8, 0x00'u8, 0x0A'u8
|
||||
]
|
||||
Asn1OidSecp256k1* = [0x2B'u8, 0x81'u8, 0x04'u8, 0x00'u8, 0x0A'u8]
|
||||
## Encoded OID for `secp256k1` curve (1.3.132.0.10)
|
||||
Asn1OidEcPublicKey* = [
|
||||
0x2A'u8, 0x86'u8, 0x48'u8, 0xCE'u8, 0x3D'u8, 0x02'u8, 0x01'u8
|
||||
]
|
||||
Asn1OidEcPublicKey* = [0x2A'u8, 0x86'u8, 0x48'u8, 0xCE'u8, 0x3D'u8, 0x02'u8, 0x01'u8]
|
||||
## Encoded OID for Elliptic Curve Public Key (1.2.840.10045.2.1)
|
||||
Asn1OidRsaEncryption* = [
|
||||
0x2A'u8, 0x86'u8, 0x48'u8, 0x86'u8, 0xF7'u8, 0x0D'u8, 0x01'u8,
|
||||
0x01'u8, 0x01'u8
|
||||
]
|
||||
Asn1OidRsaEncryption* =
|
||||
[0x2A'u8, 0x86'u8, 0x48'u8, 0x86'u8, 0xF7'u8, 0x0D'u8, 0x01'u8, 0x01'u8, 0x01'u8]
|
||||
## Encoded OID for RSA Encryption (1.2.840.113549.1.1.1)
|
||||
Asn1True* = [0x01'u8, 0x01'u8, 0xFF'u8]
|
||||
## Encoded boolean ``TRUE``.
|
||||
Asn1False* = [0x01'u8, 0x01'u8, 0x00'u8]
|
||||
## Encoded boolean ``FALSE``.
|
||||
Asn1Null* = [0x05'u8, 0x00'u8]
|
||||
## Encoded ``NULL`` value.
|
||||
Asn1True* = [0x01'u8, 0x01'u8, 0xFF'u8] ## Encoded boolean ``TRUE``.
|
||||
Asn1False* = [0x01'u8, 0x01'u8, 0x00'u8] ## Encoded boolean ``FALSE``.
|
||||
Asn1Null* = [0x05'u8, 0x00'u8] ## Encoded ``NULL`` value.
|
||||
|
||||
template toOpenArray*(ab: Asn1Buffer): untyped =
|
||||
toOpenArray(ab.buffer, ab.offset, ab.buffer.high)
|
||||
@@ -116,10 +104,10 @@ template toOpenArray*(af: Asn1Field): untyped =
|
||||
template isEmpty*(ab: Asn1Buffer): bool =
|
||||
ab.offset >= len(ab.buffer)
|
||||
|
||||
template isEnough*(ab: Asn1Buffer, length: int): bool =
|
||||
template isEnough*(ab: Asn1Buffer, length: int64): bool =
|
||||
len(ab.buffer) >= ab.offset + length
|
||||
|
||||
proc len*[T: Asn1Buffer|Asn1Composite](abc: T): int {.inline.} =
|
||||
proc len*[T: Asn1Buffer | Asn1Composite](abc: T): int {.inline.} =
|
||||
len(abc.buffer) - abc.offset
|
||||
|
||||
proc len*(field: Asn1Field): int {.inline.} =
|
||||
@@ -128,31 +116,22 @@ proc len*(field: Asn1Field): int {.inline.} =
|
||||
template getPtr*(field: untyped): pointer =
|
||||
cast[pointer](unsafeAddr field.buffer[field.offset])
|
||||
|
||||
proc extend*[T: Asn1Buffer|Asn1Composite](abc: var T, length: int) {.inline.} =
|
||||
proc extend*[T: Asn1Buffer | Asn1Composite](abc: var T, length: int) {.inline.} =
|
||||
## Extend buffer or composite's internal buffer by ``length`` octets.
|
||||
abc.buffer.setLen(len(abc.buffer) + length)
|
||||
|
||||
proc code*(tag: Asn1Tag): byte {.inline.} =
|
||||
## Converts Nim ``tag`` enum to ASN.1 tag code.
|
||||
case tag:
|
||||
of Asn1Tag.NoSupport:
|
||||
0x00'u8
|
||||
of Asn1Tag.Boolean:
|
||||
0x01'u8
|
||||
of Asn1Tag.Integer:
|
||||
0x02'u8
|
||||
of Asn1Tag.BitString:
|
||||
0x03'u8
|
||||
of Asn1Tag.OctetString:
|
||||
0x04'u8
|
||||
of Asn1Tag.Null:
|
||||
0x05'u8
|
||||
of Asn1Tag.Oid:
|
||||
0x06'u8
|
||||
of Asn1Tag.Sequence:
|
||||
0x30'u8
|
||||
of Asn1Tag.Context:
|
||||
0xA0'u8
|
||||
case tag
|
||||
of Asn1Tag.NoSupport: 0x00'u8
|
||||
of Asn1Tag.Boolean: 0x01'u8
|
||||
of Asn1Tag.Integer: 0x02'u8
|
||||
of Asn1Tag.BitString: 0x03'u8
|
||||
of Asn1Tag.OctetString: 0x04'u8
|
||||
of Asn1Tag.Null: 0x05'u8
|
||||
of Asn1Tag.Oid: 0x06'u8
|
||||
of Asn1Tag.Sequence: 0x30'u8
|
||||
of Asn1Tag.Context: 0xA0'u8
|
||||
|
||||
proc asn1EncodeLength*(dest: var openArray[byte], length: uint64): int =
|
||||
## Encode ASN.1 DER length part of TLV triple and return number of bytes
|
||||
@@ -181,8 +160,7 @@ proc asn1EncodeLength*(dest: var openArray[byte], length: uint64): int =
|
||||
# then 9, so it is safe to convert it to `int`.
|
||||
int(res)
|
||||
|
||||
proc asn1EncodeInteger*(dest: var openArray[byte],
|
||||
value: openArray[byte]): int =
|
||||
proc asn1EncodeInteger*(dest: var openArray[byte], value: openArray[byte]): int =
|
||||
## Encode big-endian binary representation of integer as ASN.1 DER `INTEGER`
|
||||
## and return number of bytes (octets) used.
|
||||
##
|
||||
@@ -192,17 +170,16 @@ proc asn1EncodeInteger*(dest: var openArray[byte],
|
||||
var buffer: array[16, byte]
|
||||
var lenlen = 0
|
||||
|
||||
let offset =
|
||||
block:
|
||||
var o = 0
|
||||
for i in 0 ..< len(value):
|
||||
if value[o] != 0x00:
|
||||
break
|
||||
inc(o)
|
||||
if o < len(value):
|
||||
o
|
||||
else:
|
||||
o - 1
|
||||
let offset = block:
|
||||
var o = 0
|
||||
for i in 0 ..< len(value):
|
||||
if value[o] != 0x00:
|
||||
break
|
||||
inc(o)
|
||||
if o < len(value):
|
||||
o
|
||||
else:
|
||||
o - 1
|
||||
|
||||
let destlen =
|
||||
if len(value) > 0:
|
||||
@@ -224,12 +201,10 @@ proc asn1EncodeInteger*(dest: var openArray[byte],
|
||||
if value[offset] >= 0x80'u8:
|
||||
dest[1 + lenlen] = 0x00'u8
|
||||
shift = 2
|
||||
copyMem(addr dest[shift + lenlen], unsafeAddr value[offset],
|
||||
len(value) - offset)
|
||||
copyMem(addr dest[shift + lenlen], unsafeAddr value[offset], len(value) - offset)
|
||||
destlen
|
||||
|
||||
proc asn1EncodeInteger*[T: SomeUnsignedInt](dest: var openArray[byte],
|
||||
value: T): int =
|
||||
proc asn1EncodeInteger*[T: SomeUnsignedInt](dest: var openArray[byte], value: T): int =
|
||||
## Encode Nim's unsigned integer as ASN.1 DER `INTEGER` and return number of
|
||||
## bytes (octets) used.
|
||||
##
|
||||
@@ -264,8 +239,7 @@ proc asn1EncodeNull*(dest: var openArray[byte]): int =
|
||||
dest[1] = 0x00'u8
|
||||
res
|
||||
|
||||
proc asn1EncodeOctetString*(dest: var openArray[byte],
|
||||
value: openArray[byte]): int =
|
||||
proc asn1EncodeOctetString*(dest: var openArray[byte], value: openArray[byte]): int =
|
||||
## Encode array of bytes as ASN.1 DER `OCTET STRING` and return number of
|
||||
## bytes (octets) used.
|
||||
##
|
||||
@@ -282,8 +256,9 @@ proc asn1EncodeOctetString*(dest: var openArray[byte],
|
||||
copyMem(addr dest[1 + lenlen], unsafeAddr value[0], len(value))
|
||||
res
|
||||
|
||||
proc asn1EncodeBitString*(dest: var openArray[byte],
|
||||
value: openArray[byte], bits = 0): int =
|
||||
proc asn1EncodeBitString*(
|
||||
dest: var openArray[byte], value: openArray[byte], bits = 0
|
||||
): int =
|
||||
## Encode array of bytes as ASN.1 DER `BIT STRING` and return number of bytes
|
||||
## (octets) used.
|
||||
##
|
||||
@@ -304,7 +279,7 @@ proc asn1EncodeBitString*(dest: var openArray[byte],
|
||||
let bytelen = (bitlen + 7) shr 3
|
||||
# Number of unused bits
|
||||
let unused = (8 - (bitlen and 7)) and 7
|
||||
let mask = not((1'u8 shl unused) - 1'u8)
|
||||
let mask = not ((1'u8 shl unused) - 1'u8)
|
||||
var lenlen = asn1EncodeLength(buffer, uint64(bytelen + 1))
|
||||
let res = 1 + lenlen + 1 + len(value)
|
||||
if len(dest) >= res:
|
||||
@@ -318,55 +293,6 @@ proc asn1EncodeBitString*(dest: var openArray[byte],
|
||||
dest[2 + lenlen + bytelen - 1] = lastbyte and mask
|
||||
res
|
||||
|
||||
proc asn1EncodeTag[T: SomeUnsignedInt](dest: var openArray[byte],
|
||||
value: T): int =
|
||||
var v = value
|
||||
if value <= cast[T](0x7F):
|
||||
if len(dest) >= 1:
|
||||
dest[0] = cast[byte](value)
|
||||
1
|
||||
else:
|
||||
var s = 0
|
||||
var res = 0
|
||||
while v != 0:
|
||||
v = v shr 7
|
||||
s += 7
|
||||
inc(res)
|
||||
if len(dest) >= res:
|
||||
var k = 0
|
||||
while s != 0:
|
||||
s -= 7
|
||||
dest[k] = cast[byte](((value shr s) and cast[T](0x7F)) or cast[T](0x80))
|
||||
inc(k)
|
||||
dest[k - 1] = dest[k - 1] and 0x7F'u8
|
||||
res
|
||||
|
||||
proc asn1EncodeOid*(dest: var openArray[byte], value: openArray[int]): int =
|
||||
## Encode array of integers ``value`` as ASN.1 DER `OBJECT IDENTIFIER` and
|
||||
## return number of bytes (octets) used.
|
||||
##
|
||||
## If length of ``dest`` is less then number of required bytes to encode
|
||||
## ``value``, then result of encoding will not be stored in ``dest``
|
||||
## but number of bytes (octets) required will be returned.
|
||||
var buffer: array[16, byte]
|
||||
var res = 1
|
||||
var oidlen = 1
|
||||
for i in 2..<len(value):
|
||||
oidlen += asn1EncodeTag(buffer, cast[uint64](value[i]))
|
||||
res += asn1EncodeLength(buffer, uint64(oidlen))
|
||||
res += oidlen
|
||||
if len(dest) >= res:
|
||||
let last = dest.high
|
||||
var offset = 1
|
||||
dest[0] = Asn1Tag.Oid.code()
|
||||
offset += asn1EncodeLength(dest.toOpenArray(offset, last), uint64(oidlen))
|
||||
dest[offset] = cast[byte](value[0] * 40 + value[1])
|
||||
offset += 1
|
||||
for i in 2..<len(value):
|
||||
offset += asn1EncodeTag(dest.toOpenArray(offset, last),
|
||||
cast[uint64](value[i]))
|
||||
res
|
||||
|
||||
proc asn1EncodeOid*(dest: var openArray[byte], value: openArray[byte]): int =
|
||||
## Encode array of bytes ``value`` as ASN.1 DER `OBJECT IDENTIFIER` and return
|
||||
## number of bytes (octets) used.
|
||||
@@ -386,8 +312,7 @@ proc asn1EncodeOid*(dest: var openArray[byte], value: openArray[byte]): int =
|
||||
copyMem(addr dest[1 + lenlen], unsafeAddr value[0], len(value))
|
||||
res
|
||||
|
||||
proc asn1EncodeSequence*(dest: var openArray[byte],
|
||||
value: openArray[byte]): int =
|
||||
proc asn1EncodeSequence*(dest: var openArray[byte], value: openArray[byte]): int =
|
||||
## Encode ``value`` as ASN.1 DER `SEQUENCE` and return number of bytes
|
||||
## (octets) used.
|
||||
##
|
||||
@@ -403,8 +328,7 @@ proc asn1EncodeSequence*(dest: var openArray[byte],
|
||||
copyMem(addr dest[1 + lenlen], unsafeAddr value[0], len(value))
|
||||
res
|
||||
|
||||
proc asn1EncodeComposite*(dest: var openArray[byte],
|
||||
value: Asn1Composite): int =
|
||||
proc asn1EncodeComposite*(dest: var openArray[byte], value: Asn1Composite): int =
|
||||
## Encode composite value and return number of bytes (octets) used.
|
||||
##
|
||||
## If length of ``dest`` is less then number of required bytes to encode
|
||||
@@ -416,12 +340,12 @@ proc asn1EncodeComposite*(dest: var openArray[byte],
|
||||
if len(dest) >= res:
|
||||
dest[0] = value.tag.code()
|
||||
copyMem(addr dest[1], addr buffer[0], lenlen)
|
||||
copyMem(addr dest[1 + lenlen], unsafeAddr value.buffer[0],
|
||||
len(value.buffer))
|
||||
copyMem(addr dest[1 + lenlen], unsafeAddr value.buffer[0], len(value.buffer))
|
||||
res
|
||||
|
||||
proc asn1EncodeContextTag*(dest: var openArray[byte], value: openArray[byte],
|
||||
tag: int): int =
|
||||
proc asn1EncodeContextTag*(
|
||||
dest: var openArray[byte], value: openArray[byte], tag: int
|
||||
): int =
|
||||
## Encode ASN.1 DER `CONTEXT SPECIFIC TAG` ``tag`` for value ``value`` and
|
||||
## return number of bytes (octets) used.
|
||||
##
|
||||
@@ -440,26 +364,29 @@ proc asn1EncodeContextTag*(dest: var openArray[byte], value: openArray[byte],
|
||||
copyMem(addr dest[1 + lenlen], unsafeAddr value[0], len(value))
|
||||
res
|
||||
|
||||
proc getLength(ab: var Asn1Buffer): Asn1Result[uint64] =
|
||||
proc getLength(ab: var Asn1Buffer): Asn1Result[int] =
|
||||
## Decode length part of ASN.1 TLV triplet.
|
||||
if not ab.isEmpty():
|
||||
let b = ab.buffer[ab.offset]
|
||||
if (b and 0x80'u8) == 0x00'u8:
|
||||
let length = cast[uint64](b)
|
||||
let length = safeConvert[int](b)
|
||||
ab.offset += 1
|
||||
return ok(length)
|
||||
if b == 0x80'u8:
|
||||
return err(Asn1Error.Indefinite)
|
||||
if b == 0xFF'u8:
|
||||
return err(Asn1Error.Incorrect)
|
||||
let octets = cast[uint64](b and 0x7F'u8)
|
||||
if octets > 8'u64:
|
||||
let octets = safeConvert[int](b and 0x7F'u8)
|
||||
if octets > 8:
|
||||
return err(Asn1Error.Overflow)
|
||||
if ab.isEnough(int(octets)):
|
||||
var length: uint64 = 0
|
||||
for i in 0..<int(octets):
|
||||
length = (length shl 8) or cast[uint64](ab.buffer[ab.offset + i + 1])
|
||||
ab.offset = ab.offset + int(octets) + 1
|
||||
if ab.isEnough(octets):
|
||||
var lengthU: uint64 = 0
|
||||
for i in 0 ..< octets:
|
||||
lengthU = (lengthU shl 8) or safeConvert[uint64](ab.buffer[ab.offset + i + 1])
|
||||
if lengthU > uint64(int64.high):
|
||||
return err(Asn1Error.Overflow)
|
||||
let length = int(lengthU)
|
||||
ab.offset = ab.offset + octets + 1
|
||||
return ok(length)
|
||||
else:
|
||||
return err(Asn1Error.Incomplete)
|
||||
@@ -471,8 +398,8 @@ proc getTag(ab: var Asn1Buffer, tag: var int): Asn1Result[Asn1Class] =
|
||||
if not ab.isEmpty():
|
||||
let
|
||||
b = ab.buffer[ab.offset]
|
||||
c = int((b and 0xC0'u8) shr 6)
|
||||
tag = int(b and 0x3F)
|
||||
c = safeConvert[int]((b and 0xC0'u8) shr 6)
|
||||
tag = safeConvert[int](b and 0x3F)
|
||||
ab.offset += 1
|
||||
if c >= 0 and c < 4:
|
||||
ok(cast[Asn1Class](c))
|
||||
@@ -486,14 +413,14 @@ proc read*(ab: var Asn1Buffer): Asn1Result[Asn1Field] =
|
||||
var
|
||||
field: Asn1Field
|
||||
tag, ttag, offset: int
|
||||
length, tlength: uint64
|
||||
length, tlength: int
|
||||
aclass: Asn1Class
|
||||
inclass: bool
|
||||
|
||||
inclass = false
|
||||
while true:
|
||||
offset = ab.offset
|
||||
aclass = ? ab.getTag(tag)
|
||||
aclass = ?ab.getTag(tag)
|
||||
|
||||
case aclass
|
||||
of Asn1Class.ContextSpecific:
|
||||
@@ -502,9 +429,9 @@ proc read*(ab: var Asn1Buffer): Asn1Result[Asn1Field] =
|
||||
else:
|
||||
inclass = true
|
||||
ttag = tag
|
||||
tlength = ? ab.getLength()
|
||||
tlength = ?ab.getLength()
|
||||
of Asn1Class.Universal:
|
||||
length = ? ab.getLength()
|
||||
length = ?ab.getLength()
|
||||
|
||||
if inclass:
|
||||
if length >= tlength:
|
||||
@@ -516,32 +443,35 @@ proc read*(ab: var Asn1Buffer): Asn1Result[Asn1Field] =
|
||||
if length != 1:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
if not ab.isEnough(int(length)):
|
||||
if not ab.isEnough(length):
|
||||
return err(Asn1Error.Incomplete)
|
||||
|
||||
let b = ab.buffer[ab.offset]
|
||||
if b != 0xFF'u8 and b != 0x00'u8:
|
||||
return err(Asn1Error.Incorrect)
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
field = Asn1Field(kind: Asn1Tag.Boolean, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset),
|
||||
length: 1)
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
field = Asn1Field(
|
||||
kind: Asn1Tag.Boolean,
|
||||
klass: aclass,
|
||||
index: ttag,
|
||||
offset: ab.offset,
|
||||
length: 1,
|
||||
buffer: ab.buffer,
|
||||
)
|
||||
field.vbool = (b == 0xFF'u8)
|
||||
ab.offset += 1
|
||||
return ok(field)
|
||||
|
||||
of Asn1Tag.Integer.code():
|
||||
# INTEGER
|
||||
if length == 0:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
if not ab.isEnough(int(length)):
|
||||
return err(Asn1Error.Incomplete)
|
||||
if not ab.isEnough(length):
|
||||
return err(Asn1Error.Incomplete)
|
||||
|
||||
# Count number of leading zeroes
|
||||
var zc = 0
|
||||
while (zc < int(length)) and (ab.buffer[ab.offset + zc] == 0x00'u8):
|
||||
while (zc < length) and (ab.buffer[ab.offset + zc] == 0x00'u8):
|
||||
inc(zc)
|
||||
|
||||
if zc > 1:
|
||||
@@ -549,71 +479,87 @@ proc read*(ab: var Asn1Buffer): Asn1Result[Asn1Field] =
|
||||
|
||||
if zc == 0:
|
||||
# Negative or Positive integer
|
||||
field = Asn1Field(kind: Asn1Tag.Integer, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset),
|
||||
length: int(length))
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
field = Asn1Field(
|
||||
kind: Asn1Tag.Integer,
|
||||
klass: aclass,
|
||||
index: ttag,
|
||||
offset: ab.offset,
|
||||
length: length,
|
||||
buffer: ab.buffer,
|
||||
)
|
||||
if (ab.buffer[ab.offset] and 0x80'u8) == 0x80'u8:
|
||||
# Negative integer
|
||||
if length <= 8:
|
||||
# We need this transformation because our field.vint is uint64.
|
||||
for i in 0 ..< 8:
|
||||
if i < 8 - int(length):
|
||||
if i < 8 - length:
|
||||
field.vint = (field.vint shl 8) or 0xFF'u64
|
||||
else:
|
||||
let offset = ab.offset + i - (8 - int(length))
|
||||
field.vint = (field.vint shl 8) or uint64(ab.buffer[offset])
|
||||
let offset = ab.offset + i - (8 - length)
|
||||
field.vint =
|
||||
(field.vint shl 8) or safeConvert[uint64](ab.buffer[offset])
|
||||
else:
|
||||
# Positive integer
|
||||
if length <= 8:
|
||||
for i in 0 ..< int(length):
|
||||
field.vint = (field.vint shl 8) or
|
||||
uint64(ab.buffer[ab.offset + i])
|
||||
ab.offset += int(length)
|
||||
for i in 0 ..< length:
|
||||
field.vint =
|
||||
(field.vint shl 8) or safeConvert[uint64](ab.buffer[ab.offset + i])
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
else:
|
||||
if length == 1:
|
||||
# Zero value integer
|
||||
field = Asn1Field(kind: Asn1Tag.Integer, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset),
|
||||
length: int(length), vint: 0'u64)
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
ab.offset += int(length)
|
||||
field = Asn1Field(
|
||||
kind: Asn1Tag.Integer,
|
||||
klass: aclass,
|
||||
index: ttag,
|
||||
offset: ab.offset,
|
||||
length: length,
|
||||
vint: 0'u64,
|
||||
buffer: ab.buffer,
|
||||
)
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
else:
|
||||
# Positive integer with leading zero
|
||||
field = Asn1Field(kind: Asn1Tag.Integer, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset) + 1,
|
||||
length: int(length) - 1)
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
field = Asn1Field(
|
||||
kind: Asn1Tag.Integer,
|
||||
klass: aclass,
|
||||
index: ttag,
|
||||
offset: ab.offset + 1,
|
||||
length: length - 1,
|
||||
buffer: ab.buffer,
|
||||
)
|
||||
if length <= 9:
|
||||
for i in 1 ..< int(length):
|
||||
field.vint = (field.vint shl 8) or
|
||||
uint64(ab.buffer[ab.offset + i])
|
||||
ab.offset += int(length)
|
||||
for i in 1 ..< length:
|
||||
field.vint =
|
||||
(field.vint shl 8) or safeConvert[uint64](ab.buffer[ab.offset + i])
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
|
||||
of Asn1Tag.BitString.code():
|
||||
# BIT STRING
|
||||
if length == 0:
|
||||
# BIT STRING should include `unused` bits field, so length should be
|
||||
# bigger then 1.
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
elif length == 1:
|
||||
if ab.buffer[ab.offset] != 0x00'u8:
|
||||
return err(Asn1Error.Incorrect)
|
||||
else:
|
||||
# Zero-length BIT STRING.
|
||||
field = Asn1Field(kind: Asn1Tag.BitString, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset + 1),
|
||||
length: 0, ubits: 0)
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
ab.offset += int(length)
|
||||
field = Asn1Field(
|
||||
kind: Asn1Tag.BitString,
|
||||
klass: aclass,
|
||||
index: ttag,
|
||||
offset: ab.offset + 1,
|
||||
length: 0,
|
||||
ubits: 0,
|
||||
buffer: ab.buffer,
|
||||
)
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
|
||||
else:
|
||||
if not ab.isEnough(int(length)):
|
||||
if not ab.isEnough(length):
|
||||
return err(Asn1Error.Incomplete)
|
||||
|
||||
let unused = ab.buffer[ab.offset]
|
||||
@@ -621,70 +567,84 @@ proc read*(ab: var Asn1Buffer): Asn1Result[Asn1Field] =
|
||||
# Number of unused bits should not be bigger then `7`.
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
let mask = (1'u8 shl int(unused)) - 1'u8
|
||||
if (ab.buffer[ab.offset + int(length) - 1] and mask) != 0x00'u8:
|
||||
let mask = (1'u8 shl safeConvert[int](unused)) - 1'u8
|
||||
if (ab.buffer[ab.offset + length - 1] and mask) != 0x00'u8:
|
||||
## All unused bits should be set to `0`.
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
field = Asn1Field(kind: Asn1Tag.BitString, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset + 1),
|
||||
length: int(length - 1), ubits: int(unused))
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
ab.offset += int(length)
|
||||
field = Asn1Field(
|
||||
kind: Asn1Tag.BitString,
|
||||
klass: aclass,
|
||||
index: ttag,
|
||||
offset: ab.offset + 1,
|
||||
length: length - 1,
|
||||
ubits: safeConvert[int](unused),
|
||||
buffer: ab.buffer,
|
||||
)
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
|
||||
of Asn1Tag.OctetString.code():
|
||||
# OCTET STRING
|
||||
if not ab.isEnough(int(length)):
|
||||
if not ab.isEnough(length):
|
||||
return err(Asn1Error.Incomplete)
|
||||
|
||||
field = Asn1Field(kind: Asn1Tag.OctetString, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset),
|
||||
length: int(length))
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
ab.offset += int(length)
|
||||
field = Asn1Field(
|
||||
kind: Asn1Tag.OctetString,
|
||||
klass: aclass,
|
||||
index: ttag,
|
||||
offset: ab.offset,
|
||||
length: length,
|
||||
buffer: ab.buffer,
|
||||
)
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
|
||||
of Asn1Tag.Null.code():
|
||||
# NULL
|
||||
if length != 0:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
field = Asn1Field(kind: Asn1Tag.Null, klass: aclass, index: ttag,
|
||||
offset: int(ab.offset), length: 0)
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
ab.offset += int(length)
|
||||
field = Asn1Field(
|
||||
kind: Asn1Tag.Null,
|
||||
klass: aclass,
|
||||
index: ttag,
|
||||
offset: ab.offset,
|
||||
length: 0,
|
||||
buffer: ab.buffer,
|
||||
)
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
|
||||
of Asn1Tag.Oid.code():
|
||||
# OID
|
||||
if not ab.isEnough(int(length)):
|
||||
if not ab.isEnough(length):
|
||||
return err(Asn1Error.Incomplete)
|
||||
|
||||
field = Asn1Field(kind: Asn1Tag.Oid, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset),
|
||||
length: int(length))
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
ab.offset += int(length)
|
||||
field = Asn1Field(
|
||||
kind: Asn1Tag.Oid,
|
||||
klass: aclass,
|
||||
index: ttag,
|
||||
offset: ab.offset,
|
||||
length: length,
|
||||
buffer: ab.buffer,
|
||||
)
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
|
||||
of Asn1Tag.Sequence.code():
|
||||
# SEQUENCE
|
||||
if not ab.isEnough(int(length)):
|
||||
if not ab.isEnough(length):
|
||||
return err(Asn1Error.Incomplete)
|
||||
|
||||
field = Asn1Field(kind: Asn1Tag.Sequence, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset),
|
||||
length: int(length))
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
ab.offset += int(length)
|
||||
field = Asn1Field(
|
||||
kind: Asn1Tag.Sequence,
|
||||
klass: aclass,
|
||||
index: ttag,
|
||||
offset: ab.offset,
|
||||
length: length,
|
||||
buffer: ab.buffer,
|
||||
)
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
|
||||
else:
|
||||
return err(Asn1Error.NoSupport)
|
||||
|
||||
inclass = false
|
||||
ttag = 0
|
||||
else:
|
||||
return err(Asn1Error.NoSupport)
|
||||
|
||||
@@ -702,9 +662,9 @@ proc `==`*(field: Asn1Field, data: openArray[byte]): bool =
|
||||
if length > 0:
|
||||
if field.length == len(data):
|
||||
CT.isEqual(
|
||||
field.buffer.toOpenArray(field.offset,
|
||||
field.offset + field.length - 1),
|
||||
data.toOpenArray(0, field.length - 1))
|
||||
field.buffer.toOpenArray(field.offset, field.offset + field.length - 1),
|
||||
data.toOpenArray(0, field.length - 1),
|
||||
)
|
||||
else:
|
||||
false
|
||||
else:
|
||||
@@ -720,15 +680,15 @@ proc init*(t: typedesc[Asn1Buffer], data: string): Asn1Buffer =
|
||||
|
||||
proc init*(t: typedesc[Asn1Buffer]): Asn1Buffer =
|
||||
## Initialize empty ``Asn1Buffer``.
|
||||
Asn1Buffer(buffer: newSeq[byte]())
|
||||
Asn1Buffer(buffer: newSeqUninit[byte](0))
|
||||
|
||||
proc init*(t: typedesc[Asn1Composite], tag: Asn1Tag): Asn1Composite =
|
||||
## Initialize ``Asn1Composite`` with tag ``tag``.
|
||||
Asn1Composite(tag: tag, buffer: newSeq[byte]())
|
||||
Asn1Composite(tag: tag, buffer: newSeqUninit[byte](0))
|
||||
|
||||
proc init*(t: typedesc[Asn1Composite], idx: int): Asn1Composite =
|
||||
## Initialize ``Asn1Composite`` with tag context-specific id ``id``.
|
||||
Asn1Composite(tag: Asn1Tag.Context, idx: idx, buffer: newSeq[byte]())
|
||||
Asn1Composite(tag: Asn1Tag.Context, idx: idx, buffer: newSeqUninit[byte](0))
|
||||
|
||||
proc `$`*(buffer: Asn1Buffer): string =
|
||||
## Return string representation of ``buffer``.
|
||||
@@ -782,13 +742,14 @@ proc `$`*(field: Asn1Field): string =
|
||||
res.add(ncrutils.toHex(field.toOpenArray()))
|
||||
res
|
||||
|
||||
proc write*[T: Asn1Buffer|Asn1Composite](abc: var T, tag: Asn1Tag) =
|
||||
proc write*[T: Asn1Buffer | Asn1Composite](abc: var T, tag: Asn1Tag) =
|
||||
## Write empty value to buffer or composite with ``tag``.
|
||||
##
|
||||
## This procedure must be used to write `NULL`, `0` or empty `BIT STRING`,
|
||||
## `OCTET STRING` types.
|
||||
doAssert(tag in {Asn1Tag.Null, Asn1Tag.Integer, Asn1Tag.BitString,
|
||||
Asn1Tag.OctetString})
|
||||
doAssert(
|
||||
tag in {Asn1Tag.Null, Asn1Tag.Integer, Asn1Tag.BitString, Asn1Tag.OctetString}
|
||||
)
|
||||
var length: int
|
||||
if tag == Asn1Tag.Null:
|
||||
length = asn1EncodeNull(abc.toOpenArray())
|
||||
@@ -810,22 +771,23 @@ proc write*[T: Asn1Buffer|Asn1Composite](abc: var T, tag: Asn1Tag) =
|
||||
discard asn1EncodeOctetString(abc.toOpenArray(), tmp.toOpenArray(0, -1))
|
||||
abc.offset += length
|
||||
|
||||
proc write*[T: Asn1Buffer|Asn1Composite](abc: var T, value: uint64) =
|
||||
proc write*[T: Asn1Buffer | Asn1Composite](abc: var T, value: uint64) =
|
||||
## Write uint64 ``value`` to buffer or composite as ASN.1 `INTEGER`.
|
||||
let length = asn1EncodeInteger(abc.toOpenArray(), value)
|
||||
abc.extend(length)
|
||||
discard asn1EncodeInteger(abc.toOpenArray(), value)
|
||||
abc.offset += length
|
||||
|
||||
proc write*[T: Asn1Buffer|Asn1Composite](abc: var T, value: bool) =
|
||||
proc write*[T: Asn1Buffer | Asn1Composite](abc: var T, value: bool) =
|
||||
## Write bool ``value`` to buffer or composite as ASN.1 `BOOLEAN`.
|
||||
let length = asn1EncodeBoolean(abc.toOpenArray(), value)
|
||||
abc.extend(length)
|
||||
discard asn1EncodeBoolean(abc.toOpenArray(), value)
|
||||
abc.offset += length
|
||||
|
||||
proc write*[T: Asn1Buffer|Asn1Composite](abc: var T, tag: Asn1Tag,
|
||||
value: openArray[byte], bits = 0) =
|
||||
proc write*[T: Asn1Buffer | Asn1Composite](
|
||||
abc: var T, tag: Asn1Tag, value: openArray[byte], bits = 0
|
||||
) =
|
||||
## Write array ``value`` using ``tag``.
|
||||
##
|
||||
## This procedure is used to write ASN.1 `INTEGER`, `OCTET STRING`,
|
||||
@@ -833,8 +795,9 @@ proc write*[T: Asn1Buffer|Asn1Composite](abc: var T, tag: Asn1Tag,
|
||||
##
|
||||
## For `BIT STRING` you can use ``bits`` argument to specify number of used
|
||||
## bits.
|
||||
doAssert(tag in {Asn1Tag.Integer, Asn1Tag.OctetString, Asn1Tag.BitString,
|
||||
Asn1Tag.Oid})
|
||||
doAssert(
|
||||
tag in {Asn1Tag.Integer, Asn1Tag.OctetString, Asn1Tag.BitString, Asn1Tag.Oid}
|
||||
)
|
||||
var length: int
|
||||
if tag == Asn1Tag.Integer:
|
||||
length = asn1EncodeInteger(abc.toOpenArray(), value)
|
||||
@@ -854,7 +817,7 @@ proc write*[T: Asn1Buffer|Asn1Composite](abc: var T, tag: Asn1Tag,
|
||||
discard asn1EncodeOid(abc.toOpenArray(), value)
|
||||
abc.offset += length
|
||||
|
||||
proc write*[T: Asn1Buffer|Asn1Composite](abc: var T, value: Asn1Composite) =
|
||||
proc write*[T: Asn1Buffer | Asn1Composite](abc: var T, value: Asn1Composite) =
|
||||
doAssert(len(value) > 0, "Composite value not finished")
|
||||
var length: int
|
||||
if value.tag == Asn1Tag.Sequence:
|
||||
@@ -871,6 +834,6 @@ proc write*[T: Asn1Buffer|Asn1Composite](abc: var T, value: Asn1Composite) =
|
||||
discard asn1EncodeContextTag(abc.toOpenArray(), value.buffer, value.idx)
|
||||
abc.offset += length
|
||||
|
||||
proc finish*[T: Asn1Buffer|Asn1Composite](abc: var T) {.inline.} =
|
||||
proc finish*[T: Asn1Buffer | Asn1Composite](abc: var T) {.inline.} =
|
||||
## Finishes buffer or composite and prepares it for writing.
|
||||
abc.offset = 0
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implements constant-time RSA PKCS#1.5 DSA.
|
||||
##
|
||||
@@ -13,12 +13,15 @@
|
||||
## BearSSL library <https://bearssl.org/>
|
||||
## Copyright(C) 2018 Thomas Pornin <pornin@bolet.org>.
|
||||
|
||||
{.push raises: Defect.}
|
||||
import bearssl
|
||||
{.push raises: [].}
|
||||
|
||||
import bearssl/[rsa, rand, hash]
|
||||
import minasn1
|
||||
import stew/[results, ctops]
|
||||
import results
|
||||
import stew/ctops
|
||||
# We use `ncrutils` for constant-time hexadecimal encoding/decoding procedures.
|
||||
import nimcrypto/utils as ncrutils
|
||||
import ../utils/sequninit
|
||||
|
||||
export Asn1Error, results
|
||||
|
||||
@@ -29,45 +32,30 @@ const
|
||||
MinKeySize* = 2048
|
||||
## Minimal allowed RSA key size in bits.
|
||||
## https://github.com/libp2p/go-libp2p-core/blob/master/crypto/rsa_common.go#L13
|
||||
DefaultKeySize* = 3072
|
||||
## Default RSA key size in bits.
|
||||
DefaultKeySize* = 3072 ## Default RSA key size in bits.
|
||||
|
||||
RsaOidSha1* = [
|
||||
0x05'u8, 0x2B'u8, 0x0E'u8, 0x03'u8, 0x02'u8, 0x1A'u8
|
||||
]
|
||||
RsaOidSha1* = [byte 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A]
|
||||
## RSA PKCS#1.5 SHA-1 hash object identifier.
|
||||
RsaOidSha224* = [
|
||||
0x09'u8, 0x60'u8, 0x86'u8, 0x48'u8, 0x01'u8, 0x65'u8, 0x03'u8, 0x04'u8,
|
||||
0x02'u8, 0x04'u8
|
||||
]
|
||||
RsaOidSha224* = [byte 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04]
|
||||
## RSA PKCS#1.5 SHA-224 hash object identifier.
|
||||
RsaOidSha256* = [
|
||||
0x09'u8, 0x60'u8, 0x86'u8, 0x48'u8, 0x01'u8, 0x65'u8, 0x03'u8, 0x04'u8,
|
||||
0x02'u8, 0x01'u8
|
||||
]
|
||||
RsaOidSha256* = [byte 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]
|
||||
## RSA PKCS#1.5 SHA-256 hash object identifier.
|
||||
RsaOidSha384* = [
|
||||
0x09'u8, 0x60'u8, 0x86'u8, 0x48'u8, 0x01'u8, 0x65'u8, 0x03'u8, 0x04'u8,
|
||||
0x02'u8, 0x02'u8
|
||||
]
|
||||
RsaOidSha384* = [byte 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02]
|
||||
## RSA PKCS#1.5 SHA-384 hash object identifier.
|
||||
RsaOidSha512* = [
|
||||
0x09'u8, 0x60'u8, 0x86'u8, 0x48'u8, 0x01'u8, 0x65'u8, 0x03'u8, 0x04'u8,
|
||||
0x02'u8, 0x03'u8
|
||||
]
|
||||
RsaOidSha512* = [byte 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03]
|
||||
## RSA PKCS#1.5 SHA-512 hash object identifier.
|
||||
|
||||
type
|
||||
RsaPrivateKey* = ref object
|
||||
buffer*: seq[byte]
|
||||
seck*: BrRsaPrivateKey
|
||||
pubk*: BrRsaPublicKey
|
||||
pexp*: ptr cuchar
|
||||
pexplen*: int
|
||||
seck*: rsa.RsaPrivateKey
|
||||
pubk*: rsa.RsaPublicKey
|
||||
pexp*: ptr byte
|
||||
pexplen*: uint
|
||||
|
||||
RsaPublicKey* = ref object
|
||||
buffer*: seq[byte]
|
||||
key*: BrRsaPublicKey
|
||||
key*: rsa.RsaPublicKey
|
||||
|
||||
RsaKeyPair* = RsaPrivateKey
|
||||
|
||||
@@ -78,9 +66,9 @@ type
|
||||
RsaKP* = RsaPrivateKey | RsaKeyPair
|
||||
|
||||
RsaError* = enum
|
||||
RsaGenError,
|
||||
RsaKeyIncorrectError,
|
||||
RsaSignatureError,
|
||||
RsaGenError
|
||||
RsaKeyIncorrectError
|
||||
RsaSignatureError
|
||||
RsaLowSecurityError
|
||||
|
||||
RsaResult*[T] = Result[T, RsaError]
|
||||
@@ -99,8 +87,8 @@ template getFinish(bs, os, ls: untyped): untyped =
|
||||
var eo = -1
|
||||
if p >= s:
|
||||
let so = cast[int](p - s)
|
||||
if so + ls <= len(bs):
|
||||
eo = so + ls - 1
|
||||
if so + int(ls) <= len(bs):
|
||||
eo = so + int(ls) - 1
|
||||
eo
|
||||
|
||||
template getArray*(bs, os, ls: untyped): untyped =
|
||||
@@ -108,15 +96,18 @@ template getArray*(bs, os, ls: untyped): untyped =
|
||||
|
||||
template trimZeroes(b: seq[byte], pt, ptlen: untyped) =
|
||||
var length = ptlen
|
||||
for i in 0..<length:
|
||||
if pt[] != cast[cuchar](0x00'u8):
|
||||
for i in 0 ..< length:
|
||||
if pt[] != byte(0x00):
|
||||
break
|
||||
pt = cast[ptr cuchar](cast[uint](pt) + 1)
|
||||
pt = cast[ptr byte](cast[uint](pt) + 1)
|
||||
ptlen -= 1
|
||||
|
||||
proc random*[T: RsaKP](t: typedesc[T], rng: var BrHmacDrbgContext,
|
||||
bits = DefaultKeySize,
|
||||
pubexp = DefaultPublicExponent): RsaResult[T] =
|
||||
proc random*[T: RsaKP](
|
||||
t: typedesc[T],
|
||||
rng: var HmacDrbgContext,
|
||||
bits = DefaultKeySize,
|
||||
pubexp = DefaultPublicExponent,
|
||||
): RsaResult[T] =
|
||||
## Generate new random RSA private key using BearSSL's HMAC-SHA256-DRBG
|
||||
## algorithm.
|
||||
##
|
||||
@@ -129,28 +120,33 @@ proc random*[T: RsaKP](t: typedesc[T], rng: var BrHmacDrbgContext,
|
||||
|
||||
let
|
||||
sko = 0
|
||||
pko = brRsaPrivateKeyBufferSize(bits)
|
||||
eko = pko + brRsaPublicKeyBufferSize(bits)
|
||||
pko = rsaKbufPrivSize(bits)
|
||||
eko = pko + rsaKbufPubSize(bits)
|
||||
length = eko + ((bits + 7) shr 3)
|
||||
|
||||
let res = new T
|
||||
res.buffer = newSeq[byte](length)
|
||||
res.buffer = newSeqUninit[byte](length)
|
||||
|
||||
var keygen = brRsaKeygenGetDefault()
|
||||
var keygen = rsaKeygenGetDefault()
|
||||
|
||||
if keygen(addr rng.vtable,
|
||||
addr res.seck, addr res.buffer[sko],
|
||||
addr res.pubk, addr res.buffer[pko],
|
||||
cuint(bits), pubexp) == 0:
|
||||
if keygen(
|
||||
addr rng.vtable,
|
||||
addr res.seck,
|
||||
addr res.buffer[sko],
|
||||
addr res.pubk,
|
||||
addr res.buffer[pko],
|
||||
cuint(bits),
|
||||
pubexp,
|
||||
) == 0:
|
||||
return err(RsaGenError)
|
||||
|
||||
let
|
||||
compute = brRsaComputePrivexpGetDefault()
|
||||
compute = rsaComputePrivexpGetDefault()
|
||||
computed = compute(addr res.buffer[eko], addr res.seck, pubexp)
|
||||
if computed == 0:
|
||||
return err(RsaGenError)
|
||||
|
||||
res.pexp = cast[ptr cuchar](addr res.buffer[eko])
|
||||
res.pexp = addr res.buffer[eko]
|
||||
res.pexplen = computed
|
||||
|
||||
trimZeroes(res.buffer, res.seck.p, res.seck.plen)
|
||||
@@ -169,12 +165,13 @@ proc copy*[T: RsaPKI](key: T): T =
|
||||
doAssert(not isNil(key))
|
||||
when T is RsaPrivateKey:
|
||||
if len(key.buffer) > 0:
|
||||
let length = key.seck.plen + key.seck.qlen + key.seck.dplen +
|
||||
key.seck.dqlen + key.seck.iqlen + key.pubk.nlen +
|
||||
key.pubk.elen + key.pexplen
|
||||
let length =
|
||||
key.seck.plen.uint + key.seck.qlen.uint + key.seck.dplen.uint +
|
||||
key.seck.dqlen.uint + key.seck.iqlen.uint + key.pubk.nlen.uint +
|
||||
key.pubk.elen.uint + key.pexplen.uint
|
||||
result = new RsaPrivateKey
|
||||
result.buffer = newSeq[byte](length)
|
||||
let po = 0
|
||||
result.buffer = newSeqUninit[byte](length)
|
||||
let po: uint = 0
|
||||
let qo = po + key.seck.plen
|
||||
let dpo = qo + key.seck.qlen
|
||||
let dqo = dpo + key.seck.dplen
|
||||
@@ -190,14 +187,14 @@ proc copy*[T: RsaPKI](key: T): T =
|
||||
copyMem(addr result.buffer[no], key.pubk.n, key.pubk.nlen)
|
||||
copyMem(addr result.buffer[eo], key.pubk.e, key.pubk.elen)
|
||||
copyMem(addr result.buffer[peo], key.pexp, key.pexplen)
|
||||
result.seck.p = cast[ptr cuchar](addr result.buffer[po])
|
||||
result.seck.q = cast[ptr cuchar](addr result.buffer[qo])
|
||||
result.seck.dp = cast[ptr cuchar](addr result.buffer[dpo])
|
||||
result.seck.dq = cast[ptr cuchar](addr result.buffer[dqo])
|
||||
result.seck.iq = cast[ptr cuchar](addr result.buffer[iqo])
|
||||
result.pubk.n = cast[ptr cuchar](addr result.buffer[no])
|
||||
result.pubk.e = cast[ptr cuchar](addr result.buffer[eo])
|
||||
result.pexp = cast[ptr cuchar](addr result.buffer[peo])
|
||||
result.seck.p = addr result.buffer[po]
|
||||
result.seck.q = addr result.buffer[qo]
|
||||
result.seck.dp = addr result.buffer[dpo]
|
||||
result.seck.dq = addr result.buffer[dqo]
|
||||
result.seck.iq = addr result.buffer[iqo]
|
||||
result.pubk.n = addr result.buffer[no]
|
||||
result.pubk.e = addr result.buffer[eo]
|
||||
result.pexp = addr result.buffer[peo]
|
||||
result.seck.plen = key.seck.plen
|
||||
result.seck.qlen = key.seck.qlen
|
||||
result.seck.dplen = key.seck.dplen
|
||||
@@ -211,13 +208,13 @@ proc copy*[T: RsaPKI](key: T): T =
|
||||
if len(key.buffer) > 0:
|
||||
let length = key.key.nlen + key.key.elen
|
||||
result = new RsaPublicKey
|
||||
result.buffer = newSeq[byte](length)
|
||||
result.buffer = newSeqUninit[byte](length)
|
||||
let no = 0
|
||||
let eo = no + key.key.nlen
|
||||
copyMem(addr result.buffer[no], key.key.n, key.key.nlen)
|
||||
copyMem(addr result.buffer[eo], key.key.e, key.key.elen)
|
||||
result.key.n = cast[ptr cuchar](addr result.buffer[no])
|
||||
result.key.e = cast[ptr cuchar](addr result.buffer[eo])
|
||||
result.key.n = cast[ptr char](addr result.buffer[no])
|
||||
result.key.e = cast[ptr char](addr result.buffer[eo])
|
||||
result.key.nlen = key.key.nlen
|
||||
result.key.elen = key.key.elen
|
||||
elif T is RsaSignature:
|
||||
@@ -230,12 +227,11 @@ proc getPublicKey*(key: RsaPrivateKey): RsaPublicKey =
|
||||
doAssert(not isNil(key))
|
||||
let length = key.pubk.nlen + key.pubk.elen
|
||||
result = new RsaPublicKey
|
||||
result.buffer = newSeq[byte](length)
|
||||
result.key.n = cast[ptr cuchar](addr result.buffer[0])
|
||||
result.key.e = cast[ptr cuchar](addr result.buffer[key.pubk.nlen])
|
||||
result.buffer = newSeqUninit[byte](length)
|
||||
result.key.n = addr result.buffer[0]
|
||||
result.key.e = addr result.buffer[key.pubk.nlen]
|
||||
copyMem(addr result.buffer[0], cast[pointer](key.pubk.n), key.pubk.nlen)
|
||||
copyMem(addr result.buffer[key.pubk.nlen], cast[pointer](key.pubk.e),
|
||||
key.pubk.elen)
|
||||
copyMem(addr result.buffer[key.pubk.nlen], cast[pointer](key.pubk.e), key.pubk.elen)
|
||||
result.key.nlen = key.pubk.nlen
|
||||
result.key.elen = key.pubk.elen
|
||||
|
||||
@@ -247,7 +243,7 @@ proc pubkey*(pair: RsaKeyPair): RsaPublicKey {.inline.} =
|
||||
## Get RSA public key from pair ``pair``.
|
||||
result = cast[RsaPrivateKey](pair).getPublicKey()
|
||||
|
||||
proc clear*[T: RsaPKI|RsaKeyPair](pki: var T) =
|
||||
proc clear*[T: RsaPKI | RsaKeyPair](pki: var T) =
|
||||
## Wipe and clear EC private key, public key or scalar object.
|
||||
doAssert(not isNil(pki))
|
||||
when T is RsaPrivateKey:
|
||||
@@ -291,21 +287,14 @@ proc toBytes*(key: RsaPrivateKey, data: var openArray[byte]): RsaResult[int] =
|
||||
var b = Asn1Buffer.init()
|
||||
var p = Asn1Composite.init(Asn1Tag.Sequence)
|
||||
p.write(0'u64)
|
||||
p.write(Asn1Tag.Integer, getArray(key.buffer, key.pubk.n,
|
||||
key.pubk.nlen))
|
||||
p.write(Asn1Tag.Integer, getArray(key.buffer, key.pubk.e,
|
||||
key.pubk.elen))
|
||||
p.write(Asn1Tag.Integer, getArray(key.buffer, key.pubk.n, key.pubk.nlen))
|
||||
p.write(Asn1Tag.Integer, getArray(key.buffer, key.pubk.e, key.pubk.elen))
|
||||
p.write(Asn1Tag.Integer, getArray(key.buffer, key.pexp, key.pexplen))
|
||||
p.write(Asn1Tag.Integer, getArray(key.buffer, key.seck.p,
|
||||
key.seck.plen))
|
||||
p.write(Asn1Tag.Integer, getArray(key.buffer, key.seck.q,
|
||||
key.seck.qlen))
|
||||
p.write(Asn1Tag.Integer, getArray(key.buffer, key.seck.dp,
|
||||
key.seck.dplen))
|
||||
p.write(Asn1Tag.Integer, getArray(key.buffer, key.seck.dq,
|
||||
key.seck.dqlen))
|
||||
p.write(Asn1Tag.Integer, getArray(key.buffer, key.seck.iq,
|
||||
key.seck.iqlen))
|
||||
p.write(Asn1Tag.Integer, getArray(key.buffer, key.seck.p, key.seck.plen))
|
||||
p.write(Asn1Tag.Integer, getArray(key.buffer, key.seck.q, key.seck.qlen))
|
||||
p.write(Asn1Tag.Integer, getArray(key.buffer, key.seck.dp, key.seck.dplen))
|
||||
p.write(Asn1Tag.Integer, getArray(key.buffer, key.seck.dq, key.seck.dqlen))
|
||||
p.write(Asn1Tag.Integer, getArray(key.buffer, key.seck.iq, key.seck.iqlen))
|
||||
p.finish()
|
||||
b.write(p)
|
||||
b.finish()
|
||||
@@ -369,8 +358,8 @@ proc getBytes*(key: RsaPrivateKey): RsaResult[seq[byte]] =
|
||||
## return it.
|
||||
if isNil(key):
|
||||
return err(RsaKeyIncorrectError)
|
||||
var res = newSeq[byte](4096)
|
||||
let length = ? key.toBytes(res)
|
||||
var res = newSeqUninit[byte](4096)
|
||||
let length = ?key.toBytes(res)
|
||||
if length > 0:
|
||||
res.setLen(length)
|
||||
ok(res)
|
||||
@@ -382,8 +371,8 @@ proc getBytes*(key: RsaPublicKey): RsaResult[seq[byte]] =
|
||||
## return it.
|
||||
if isNil(key):
|
||||
return err(RsaKeyIncorrectError)
|
||||
var res = newSeq[byte](4096)
|
||||
let length = ? key.toBytes(res)
|
||||
var res = newSeqUninit[byte](4096)
|
||||
let length = ?key.toBytes(res)
|
||||
if length > 0:
|
||||
res.setLen(length)
|
||||
ok(res)
|
||||
@@ -394,8 +383,8 @@ proc getBytes*(sig: RsaSignature): RsaResult[seq[byte]] =
|
||||
## Serialize RSA signature ``sig`` to raw binary form and return it.
|
||||
if isNil(sig):
|
||||
return err(RsaSignatureError)
|
||||
var res = newSeq[byte](4096)
|
||||
let length = ? sig.toBytes(res)
|
||||
var res = newSeqUninit[byte](4096)
|
||||
let length = ?sig.toBytes(res)
|
||||
if length > 0:
|
||||
res.setLen(length)
|
||||
ok(res)
|
||||
@@ -407,20 +396,19 @@ proc init*(key: var RsaPrivateKey, data: openArray[byte]): Result[void, Asn1Erro
|
||||
## ``data``.
|
||||
##
|
||||
## Procedure returns ``Asn1Status``.
|
||||
var
|
||||
field, rawn, rawpube, rawprie, rawp, rawq, rawdp, rawdq, rawiq: Asn1Field
|
||||
var field, rawn, rawpube, rawprie, rawp, rawq, rawdp, rawdq, rawiq: Asn1Field
|
||||
|
||||
# Asn1Field is not trivial so avoid too much Result
|
||||
|
||||
var ab = Asn1Buffer.init(data)
|
||||
field = ? ab.read()
|
||||
field = ?ab.read()
|
||||
|
||||
if field.kind != Asn1Tag.Sequence:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
var ib = field.getBuffer()
|
||||
|
||||
field = ? ib.read()
|
||||
field = ?ib.read()
|
||||
|
||||
if field.kind != Asn1Tag.Integer:
|
||||
return err(Asn1Error.Incorrect)
|
||||
@@ -428,66 +416,66 @@ proc init*(key: var RsaPrivateKey, data: openArray[byte]): Result[void, Asn1Erro
|
||||
if field.vint != 0'u64:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
rawn = ? ib.read()
|
||||
rawn = ?ib.read()
|
||||
|
||||
if rawn.kind != Asn1Tag.Integer:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
rawpube = ? ib.read()
|
||||
rawpube = ?ib.read()
|
||||
|
||||
if rawpube.kind != Asn1Tag.Integer:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
rawprie = ? ib.read()
|
||||
rawprie = ?ib.read()
|
||||
|
||||
if rawprie.kind != Asn1Tag.Integer:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
rawp = ? ib.read()
|
||||
rawp = ?ib.read()
|
||||
|
||||
if rawp.kind != Asn1Tag.Integer:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
rawq = ? ib.read()
|
||||
rawq = ?ib.read()
|
||||
|
||||
if rawq.kind != Asn1Tag.Integer:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
rawdp = ? ib.read()
|
||||
rawdp = ?ib.read()
|
||||
|
||||
if rawdp.kind != Asn1Tag.Integer:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
rawdq = ? ib.read()
|
||||
rawdq = ?ib.read()
|
||||
|
||||
if rawdq.kind != Asn1Tag.Integer:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
rawiq = ? ib.read()
|
||||
rawiq = ?ib.read()
|
||||
|
||||
if rawiq.kind != Asn1Tag.Integer:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
if len(rawn) >= (MinKeySize shr 3) and len(rawp) > 0 and len(rawq) > 0 and
|
||||
len(rawdp) > 0 and len(rawdq) > 0 and len(rawiq) > 0:
|
||||
len(rawdp) > 0 and len(rawdq) > 0 and len(rawiq) > 0:
|
||||
key = new RsaPrivateKey
|
||||
key.buffer = @data
|
||||
key.pubk.n = cast[ptr cuchar](addr key.buffer[rawn.offset])
|
||||
key.pubk.e = cast[ptr cuchar](addr key.buffer[rawpube.offset])
|
||||
key.seck.p = cast[ptr cuchar](addr key.buffer[rawp.offset])
|
||||
key.seck.q = cast[ptr cuchar](addr key.buffer[rawq.offset])
|
||||
key.seck.dp = cast[ptr cuchar](addr key.buffer[rawdp.offset])
|
||||
key.seck.dq = cast[ptr cuchar](addr key.buffer[rawdq.offset])
|
||||
key.seck.iq = cast[ptr cuchar](addr key.buffer[rawiq.offset])
|
||||
key.pexp = cast[ptr cuchar](addr key.buffer[rawprie.offset])
|
||||
key.pubk.nlen = len(rawn)
|
||||
key.pubk.elen = len(rawpube)
|
||||
key.seck.plen = len(rawp)
|
||||
key.seck.qlen = len(rawq)
|
||||
key.seck.dplen = len(rawdp)
|
||||
key.seck.dqlen = len(rawdq)
|
||||
key.seck.iqlen = len(rawiq)
|
||||
key.pexplen = len(rawprie)
|
||||
key.pubk.n = addr key.buffer[rawn.offset]
|
||||
key.pubk.e = addr key.buffer[rawpube.offset]
|
||||
key.seck.p = addr key.buffer[rawp.offset]
|
||||
key.seck.q = addr key.buffer[rawq.offset]
|
||||
key.seck.dp = addr key.buffer[rawdp.offset]
|
||||
key.seck.dq = addr key.buffer[rawdq.offset]
|
||||
key.seck.iq = addr key.buffer[rawiq.offset]
|
||||
key.pexp = addr key.buffer[rawprie.offset]
|
||||
key.pubk.nlen = uint(len(rawn))
|
||||
key.pubk.elen = uint(len(rawpube))
|
||||
key.seck.plen = uint(len(rawp))
|
||||
key.seck.qlen = uint(len(rawq))
|
||||
key.seck.dplen = uint(len(rawdp))
|
||||
key.seck.dqlen = uint(len(rawdq))
|
||||
key.seck.iqlen = uint(len(rawiq))
|
||||
key.pexplen = uint(len(rawprie))
|
||||
key.seck.nBitlen = cast[uint32](len(rawn) shl 3)
|
||||
ok()
|
||||
else:
|
||||
@@ -501,52 +489,52 @@ proc init*(key: var RsaPublicKey, data: openArray[byte]): Result[void, Asn1Error
|
||||
var field, rawn, rawe: Asn1Field
|
||||
var ab = Asn1Buffer.init(data)
|
||||
|
||||
field = ? ab.read()
|
||||
field = ?ab.read()
|
||||
|
||||
if field.kind != Asn1Tag.Sequence:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
var ib = field.getBuffer()
|
||||
|
||||
field = ? ib.read()
|
||||
field = ?ib.read()
|
||||
|
||||
if field.kind != Asn1Tag.Sequence:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
var ob = field.getBuffer()
|
||||
|
||||
field = ? ob.read()
|
||||
field = ?ob.read()
|
||||
|
||||
if field.kind != Asn1Tag.Oid:
|
||||
return err(Asn1Error.Incorrect)
|
||||
elif field != Asn1OidRsaEncryption:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
field = ? ob.read()
|
||||
field = ?ob.read()
|
||||
|
||||
if field.kind != Asn1Tag.Null:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
field = ? ib.read()
|
||||
field = ?ib.read()
|
||||
|
||||
if field.kind != Asn1Tag.BitString:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
var vb = field.getBuffer()
|
||||
|
||||
field = ? vb.read()
|
||||
field = ?vb.read()
|
||||
|
||||
if field.kind != Asn1Tag.Sequence:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
var sb = field.getBuffer()
|
||||
|
||||
rawn = ? sb.read()
|
||||
rawn = ?sb.read()
|
||||
|
||||
if rawn.kind != Asn1Tag.Integer:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
rawe = ? sb.read()
|
||||
rawe = ?sb.read()
|
||||
|
||||
if rawe.kind != Asn1Tag.Integer:
|
||||
return err(Asn1Error.Incorrect)
|
||||
@@ -554,10 +542,10 @@ proc init*(key: var RsaPublicKey, data: openArray[byte]): Result[void, Asn1Error
|
||||
if len(rawn) >= (MinKeySize shr 3) and len(rawe) > 0:
|
||||
key = new RsaPublicKey
|
||||
key.buffer = @data
|
||||
key.key.n = cast[ptr cuchar](addr key.buffer[rawn.offset])
|
||||
key.key.e = cast[ptr cuchar](addr key.buffer[rawe.offset])
|
||||
key.key.nlen = len(rawn)
|
||||
key.key.elen = len(rawe)
|
||||
key.key.n = addr key.buffer[rawn.offset]
|
||||
key.key.e = addr key.buffer[rawe.offset]
|
||||
key.key.nlen = uint(len(rawn))
|
||||
key.key.elen = uint(len(rawe))
|
||||
ok()
|
||||
else:
|
||||
err(Asn1Error.Incorrect)
|
||||
@@ -574,16 +562,16 @@ proc init*(sig: var RsaSignature, data: openArray[byte]): Result[void, Asn1Error
|
||||
else:
|
||||
err(Asn1Error.Incorrect)
|
||||
|
||||
proc init*[T: RsaPKI](sospk: var T,
|
||||
data: string): Result[void, Asn1Error] {.inline.} =
|
||||
proc init*[T: RsaPKI](sospk: var T, data: string): Result[void, Asn1Error] {.inline.} =
|
||||
## Initialize EC `private key`, `public key` or `scalar` ``sospk`` from
|
||||
## hexadecimal string representation ``data``.
|
||||
##
|
||||
## Procedure returns ``Result[void, Asn1Status]``.
|
||||
sospk.init(ncrutils.fromHex(data))
|
||||
|
||||
proc init*(t: typedesc[RsaPrivateKey],
|
||||
data: openArray[byte]): RsaResult[RsaPrivateKey] =
|
||||
proc init*(
|
||||
t: typedesc[RsaPrivateKey], data: openArray[byte]
|
||||
): RsaResult[RsaPrivateKey] =
|
||||
## Initialize RSA private key from ASN.1 DER binary representation ``data``
|
||||
## and return constructed object.
|
||||
var res: RsaPrivateKey
|
||||
@@ -592,8 +580,7 @@ proc init*(t: typedesc[RsaPrivateKey],
|
||||
else:
|
||||
ok(res)
|
||||
|
||||
proc init*(t: typedesc[RsaPublicKey],
|
||||
data: openArray[byte]): RsaResult[RsaPublicKey] =
|
||||
proc init*(t: typedesc[RsaPublicKey], data: openArray[byte]): RsaResult[RsaPublicKey] =
|
||||
## Initialize RSA public key from ASN.1 DER binary representation ``data``
|
||||
## and return constructed object.
|
||||
var res: RsaPublicKey
|
||||
@@ -602,8 +589,7 @@ proc init*(t: typedesc[RsaPublicKey],
|
||||
else:
|
||||
ok(res)
|
||||
|
||||
proc init*(t: typedesc[RsaSignature],
|
||||
data: openArray[byte]): RsaResult[RsaSignature] =
|
||||
proc init*(t: typedesc[RsaSignature], data: openArray[byte]): RsaResult[RsaSignature] =
|
||||
## Initialize RSA signature from raw binary representation ``data`` and
|
||||
## return constructed object.
|
||||
var res: RsaSignature
|
||||
@@ -630,14 +616,11 @@ proc `$`*(key: RsaPrivateKey): string =
|
||||
result.add("\nq = ")
|
||||
result.add(ncrutils.toHex(getArray(key.buffer, key.seck.q, key.seck.qlen)))
|
||||
result.add("\ndp = ")
|
||||
result.add(ncrutils.toHex(getArray(key.buffer, key.seck.dp,
|
||||
key.seck.dplen)))
|
||||
result.add(ncrutils.toHex(getArray(key.buffer, key.seck.dp, key.seck.dplen)))
|
||||
result.add("\ndq = ")
|
||||
result.add(ncrutils.toHex(getArray(key.buffer, key.seck.dq,
|
||||
key.seck.dqlen)))
|
||||
result.add(ncrutils.toHex(getArray(key.buffer, key.seck.dq, key.seck.dqlen)))
|
||||
result.add("\niq = ")
|
||||
result.add(ncrutils.toHex(getArray(key.buffer, key.seck.iq,
|
||||
key.seck.iqlen)))
|
||||
result.add(ncrutils.toHex(getArray(key.buffer, key.seck.iq, key.seck.iqlen)))
|
||||
result.add("\npre = ")
|
||||
result.add(ncrutils.toHex(getArray(key.buffer, key.pexp, key.pexplen)))
|
||||
result.add("\nm = ")
|
||||
@@ -682,23 +665,38 @@ proc `==`*(a, b: RsaPrivateKey): bool =
|
||||
false
|
||||
else:
|
||||
if a.seck.nBitlen == b.seck.nBitlen:
|
||||
if cast[int](a.seck.nBitlen) > 0:
|
||||
let r1 = CT.isEqual(getArray(a.buffer, a.seck.p, a.seck.plen),
|
||||
getArray(b.buffer, b.seck.p, b.seck.plen))
|
||||
let r2 = CT.isEqual(getArray(a.buffer, a.seck.q, a.seck.qlen),
|
||||
getArray(b.buffer, b.seck.q, b.seck.qlen))
|
||||
let r3 = CT.isEqual(getArray(a.buffer, a.seck.dp, a.seck.dplen),
|
||||
getArray(b.buffer, b.seck.dp, b.seck.dplen))
|
||||
let r4 = CT.isEqual(getArray(a.buffer, a.seck.dq, a.seck.dqlen),
|
||||
getArray(b.buffer, b.seck.dq, b.seck.dqlen))
|
||||
let r5 = CT.isEqual(getArray(a.buffer, a.seck.iq, a.seck.iqlen),
|
||||
getArray(b.buffer, b.seck.iq, b.seck.iqlen))
|
||||
let r6 = CT.isEqual(getArray(a.buffer, a.pexp, a.pexplen),
|
||||
getArray(b.buffer, b.pexp, b.pexplen))
|
||||
let r7 = CT.isEqual(getArray(a.buffer, a.pubk.n, a.pubk.nlen),
|
||||
getArray(b.buffer, b.pubk.n, b.pubk.nlen))
|
||||
let r8 = CT.isEqual(getArray(a.buffer, a.pubk.e, a.pubk.elen),
|
||||
getArray(b.buffer, b.pubk.e, b.pubk.elen))
|
||||
if a.seck.nBitlen > 0'u:
|
||||
let r1 = CT.isEqual(
|
||||
getArray(a.buffer, a.seck.p, a.seck.plen),
|
||||
getArray(b.buffer, b.seck.p, b.seck.plen),
|
||||
)
|
||||
let r2 = CT.isEqual(
|
||||
getArray(a.buffer, a.seck.q, a.seck.qlen),
|
||||
getArray(b.buffer, b.seck.q, b.seck.qlen),
|
||||
)
|
||||
let r3 = CT.isEqual(
|
||||
getArray(a.buffer, a.seck.dp, a.seck.dplen),
|
||||
getArray(b.buffer, b.seck.dp, b.seck.dplen),
|
||||
)
|
||||
let r4 = CT.isEqual(
|
||||
getArray(a.buffer, a.seck.dq, a.seck.dqlen),
|
||||
getArray(b.buffer, b.seck.dq, b.seck.dqlen),
|
||||
)
|
||||
let r5 = CT.isEqual(
|
||||
getArray(a.buffer, a.seck.iq, a.seck.iqlen),
|
||||
getArray(b.buffer, b.seck.iq, b.seck.iqlen),
|
||||
)
|
||||
let r6 = CT.isEqual(
|
||||
getArray(a.buffer, a.pexp, a.pexplen), getArray(b.buffer, b.pexp, b.pexplen)
|
||||
)
|
||||
let r7 = CT.isEqual(
|
||||
getArray(a.buffer, a.pubk.n, a.pubk.nlen),
|
||||
getArray(b.buffer, b.pubk.n, b.pubk.nlen),
|
||||
)
|
||||
let r8 = CT.isEqual(
|
||||
getArray(a.buffer, a.pubk.e, a.pubk.elen),
|
||||
getArray(b.buffer, b.pubk.e, b.pubk.elen),
|
||||
)
|
||||
r1 and r2 and r3 and r4 and r5 and r6 and r7 and r8
|
||||
else:
|
||||
true
|
||||
@@ -736,42 +734,45 @@ proc `==`*(a, b: RsaPublicKey): bool =
|
||||
elif isNil(b) and (not isNil(a)):
|
||||
false
|
||||
else:
|
||||
let r1 = CT.isEqual(getArray(a.buffer, a.key.n, a.key.nlen),
|
||||
getArray(b.buffer, b.key.n, b.key.nlen))
|
||||
let r2 = CT.isEqual(getArray(a.buffer, a.key.e, a.key.elen),
|
||||
getArray(b.buffer, b.key.e, b.key.elen))
|
||||
let r1 = CT.isEqual(
|
||||
getArray(a.buffer, a.key.n, a.key.nlen), getArray(b.buffer, b.key.n, b.key.nlen)
|
||||
)
|
||||
let r2 = CT.isEqual(
|
||||
getArray(a.buffer, a.key.e, a.key.elen), getArray(b.buffer, b.key.e, b.key.elen)
|
||||
)
|
||||
(r1 and r2)
|
||||
|
||||
proc sign*[T: byte|char](key: RsaPrivateKey,
|
||||
message: openArray[T]): RsaResult[RsaSignature] {.gcsafe.} =
|
||||
proc sign*[T: byte | char](
|
||||
key: RsaPrivateKey, message: openArray[T]
|
||||
): RsaResult[RsaSignature] {.gcsafe.} =
|
||||
## Get RSA PKCS1.5 signature of data ``message`` using SHA256 and private
|
||||
## key ``key``.
|
||||
if isNil(key):
|
||||
return err(RsaKeyIncorrectError)
|
||||
|
||||
var hc: BrHashCompatContext
|
||||
var hc: HashCompatContext
|
||||
var hash: array[32, byte]
|
||||
let impl = BrRsaPkcs1SignGetDefault()
|
||||
let impl = rsaPkcs1SignGetDefault()
|
||||
var res = new RsaSignature
|
||||
res.buffer = newSeq[byte]((key.seck.nBitlen + 7) shr 3)
|
||||
res.buffer = newSeqUninit[byte]((key.seck.nBitlen + 7) shr 3)
|
||||
var kv = addr sha256Vtable
|
||||
kv.init(addr hc.vtable)
|
||||
if len(message) > 0:
|
||||
kv.update(addr hc.vtable, unsafeAddr message[0], len(message))
|
||||
kv.update(addr hc.vtable, unsafeAddr message[0], uint(len(message)))
|
||||
else:
|
||||
kv.update(addr hc.vtable, nil, 0)
|
||||
kv.output(addr hc.vtable, addr hash[0])
|
||||
kv.out(addr hc.vtable, addr hash[0])
|
||||
var oid = RsaOidSha256
|
||||
let implRes = impl(cast[ptr cuchar](addr oid[0]),
|
||||
cast[ptr cuchar](addr hash[0]), len(hash),
|
||||
addr key.seck, cast[ptr cuchar](addr res.buffer[0]))
|
||||
let implRes =
|
||||
impl(addr oid[0], addr hash[0], uint(len(hash)), addr key.seck, addr res.buffer[0])
|
||||
if implRes == 0:
|
||||
err(RsaSignatureError)
|
||||
else:
|
||||
ok(res)
|
||||
|
||||
proc verify*[T: byte|char](sig: RsaSignature, message: openArray[T],
|
||||
pubkey: RsaPublicKey): bool {.inline.} =
|
||||
proc verify*[T: byte | char](
|
||||
sig: RsaSignature, message: openArray[T], pubkey: RsaPublicKey
|
||||
): bool {.inline.} =
|
||||
## Verify RSA signature ``sig`` using public key ``pubkey`` and data
|
||||
## ``message``.
|
||||
##
|
||||
@@ -779,20 +780,25 @@ proc verify*[T: byte|char](sig: RsaSignature, message: openArray[T],
|
||||
## verification failed.
|
||||
doAssert((not isNil(sig)) and (not isNil(pubkey)))
|
||||
if len(sig.buffer) > 0:
|
||||
var hc: BrHashCompatContext
|
||||
var hc: HashCompatContext
|
||||
var hash: array[32, byte]
|
||||
var check: array[32, byte]
|
||||
var impl = BrRsaPkcs1VrfyGetDefault()
|
||||
var impl = rsaPkcs1VrfyGetDefault()
|
||||
var kv = addr sha256Vtable
|
||||
kv.init(addr hc.vtable)
|
||||
if len(message) > 0:
|
||||
kv.update(addr hc.vtable, unsafeAddr message[0], len(message))
|
||||
kv.update(addr hc.vtable, unsafeAddr message[0], uint(len(message)))
|
||||
else:
|
||||
kv.update(addr hc.vtable, nil, 0)
|
||||
kv.output(addr hc.vtable, addr hash[0])
|
||||
kv.out(addr hc.vtable, addr hash[0])
|
||||
var oid = RsaOidSha256
|
||||
let res = impl(cast[ptr cuchar](addr sig.buffer[0]), len(sig.buffer),
|
||||
cast[ptr cuchar](addr oid[0]),
|
||||
len(check), addr pubkey.key, cast[ptr cuchar](addr check[0]))
|
||||
let res = impl(
|
||||
addr sig.buffer[0],
|
||||
uint(len(sig.buffer)),
|
||||
addr oid[0],
|
||||
uint(len(check)),
|
||||
addr pubkey.key,
|
||||
addr check[0],
|
||||
)
|
||||
if res == 1:
|
||||
result = equalMem(addr check[0], addr hash[0], len(hash))
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
secp256k1, bearssl,
|
||||
stew/[byteutils, results],
|
||||
nimcrypto/[hash, sha2]
|
||||
import bearssl/rand
|
||||
import secp256k1, results, stew/byteutils, nimcrypto/[hash, sha2]
|
||||
import ../utils/sequninit
|
||||
|
||||
export sha2, results
|
||||
export sha2, results, rand
|
||||
|
||||
const
|
||||
SkRawPrivateKeySize* = 256 div 8
|
||||
## Size of private key in octets (bytes)
|
||||
SkRawPrivateKeySize* = 256 div 8 ## Size of private key in octets (bytes)
|
||||
SkRawSignatureSize* = SkRawPrivateKeySize * 2 + 1
|
||||
## Size of signature in octets (bytes)
|
||||
SkRawPublicKeySize* = SkRawPrivateKeySize + 1
|
||||
## Size of public key in octets (bytes)
|
||||
SkRawPublicKeySize* = SkRawPrivateKeySize + 1 ## Size of public key in octets (bytes)
|
||||
|
||||
# This is extremely confusing but it's to avoid.. confusion between Eth standard and Secp standard
|
||||
type
|
||||
@@ -31,20 +28,18 @@ type
|
||||
SkSignature* = distinct secp256k1.SkSignature
|
||||
SkKeyPair* = distinct secp256k1.SkKeyPair
|
||||
|
||||
template pubkey*(v: SkKeyPair): SkPublicKey = SkPublicKey(secp256k1.SkKeyPair(v).pubkey)
|
||||
template seckey*(v: SkKeyPair): SkPrivateKey = SkPrivateKey(secp256k1.SkKeyPair(v).seckey)
|
||||
|
||||
proc random*(t: typedesc[SkPrivateKey], rng: var BrHmacDrbgContext): SkPrivateKey =
|
||||
let rngPtr = unsafeAddr rng # doesn't escape
|
||||
proc random*(t: typedesc[SkPrivateKey], rng: var HmacDrbgContext): SkPrivateKey =
|
||||
#TODO is there a better way?
|
||||
var rngPtr = addr rng
|
||||
proc callRng(data: var openArray[byte]) =
|
||||
brHmacDrbgGenerate(rngPtr[], data)
|
||||
hmacDrbgGenerate(rngPtr[], data)
|
||||
|
||||
SkPrivateKey(SkSecretKey.random(callRng))
|
||||
|
||||
proc random*(t: typedesc[SkKeyPair], rng: var BrHmacDrbgContext): SkKeyPair =
|
||||
let rngPtr = unsafeAddr rng # doesn't escape
|
||||
proc random*(t: typedesc[SkKeyPair], rng: var HmacDrbgContext): SkKeyPair =
|
||||
let rngPtr = addr rng
|
||||
proc callRng(data: var openArray[byte]) =
|
||||
brHmacDrbgGenerate(rngPtr[], data)
|
||||
hmacDrbgGenerate(rngPtr[], data)
|
||||
|
||||
SkKeyPair(secp256k1.SkKeyPair.random(callRng))
|
||||
|
||||
@@ -57,31 +52,31 @@ template pubkey*(v: SkKeyPair): SkPublicKey =
|
||||
proc init*(key: var SkPrivateKey, data: openArray[byte]): SkResult[void] =
|
||||
## Initialize Secp256k1 `private key` ``key`` from raw binary
|
||||
## representation ``data``.
|
||||
key = SkPrivateKey(? secp256k1.SkSecretKey.fromRaw(data))
|
||||
key = SkPrivateKey(?secp256k1.SkSecretKey.fromRaw(data))
|
||||
ok()
|
||||
|
||||
proc init*(key: var SkPrivateKey, data: string): SkResult[void] =
|
||||
## Initialize Secp256k1 `private key` ``key`` from hexadecimal string
|
||||
## representation ``data``.
|
||||
key = SkPrivateKey(? secp256k1.SkSecretKey.fromHex(data))
|
||||
key = SkPrivateKey(?secp256k1.SkSecretKey.fromHex(data))
|
||||
ok()
|
||||
|
||||
proc init*(key: var SkPublicKey, data: openArray[byte]): SkResult[void] =
|
||||
## Initialize Secp256k1 `public key` ``key`` from raw binary
|
||||
## representation ``data``.
|
||||
key = SkPublicKey(? secp256k1.SkPublicKey.fromRaw(data))
|
||||
key = SkPublicKey(?secp256k1.SkPublicKey.fromRaw(data))
|
||||
ok()
|
||||
|
||||
proc init*(key: var SkPublicKey, data: string): SkResult[void] =
|
||||
## Initialize Secp256k1 `public key` ``key`` from hexadecimal string
|
||||
## representation ``data``.
|
||||
key = SkPublicKey(? secp256k1.SkPublicKey.fromHex(data))
|
||||
key = SkPublicKey(?secp256k1.SkPublicKey.fromHex(data))
|
||||
ok()
|
||||
|
||||
proc init*(sig: var SkSignature, data: openArray[byte]): SkResult[void] =
|
||||
## Initialize Secp256k1 `signature` ``sig`` from raw binary
|
||||
## representation ``data``.
|
||||
sig = SkSignature(? secp256k1.SkSignature.fromDer(data))
|
||||
sig = SkSignature(?secp256k1.SkSignature.fromDer(data))
|
||||
ok()
|
||||
|
||||
proc init*(sig: var SkSignature, data: string): SkResult[void] =
|
||||
@@ -91,8 +86,9 @@ proc init*(sig: var SkSignature, data: string): SkResult[void] =
|
||||
var buffer: seq[byte]
|
||||
try:
|
||||
buffer = hexToSeqByte(data)
|
||||
except ValueError:
|
||||
return err("secp: Hex to bytes failed")
|
||||
except ValueError as e:
|
||||
let errMsg = "secp: Hex to bytes failed: " & e.msg
|
||||
return err(errMsg.cstring)
|
||||
init(sig, buffer)
|
||||
|
||||
proc init*(t: typedesc[SkPrivateKey], data: openArray[byte]): SkResult[SkPrivateKey] =
|
||||
@@ -152,7 +148,7 @@ proc toBytes*(key: SkPrivateKey, data: var openArray[byte]): SkResult[int] =
|
||||
## Procedure returns number of bytes (octets) needed to store
|
||||
## Secp256k1 private key.
|
||||
if len(data) >= SkRawPrivateKeySize:
|
||||
data[0..<SkRawPrivateKeySize] = SkSecretKey(key).toRaw()
|
||||
data[0 ..< SkRawPrivateKeySize] = SkSecretKey(key).toRaw()
|
||||
ok(SkRawPrivateKeySize)
|
||||
else:
|
||||
err("secp: Not enough bytes")
|
||||
@@ -164,7 +160,7 @@ proc toBytes*(key: SkPublicKey, data: var openArray[byte]): SkResult[int] =
|
||||
## Procedure returns number of bytes (octets) needed to store
|
||||
## Secp256k1 public key.
|
||||
if len(data) >= SkRawPublicKeySize:
|
||||
data[0..<SkRawPublicKeySize] = secp256k1.SkPublicKey(key).toRawCompressed()
|
||||
data[0 ..< SkRawPublicKeySize] = secp256k1.SkPublicKey(key).toRawCompressed()
|
||||
ok(SkRawPublicKeySize)
|
||||
else:
|
||||
err("secp: Not enough bytes")
|
||||
@@ -187,26 +183,32 @@ proc getBytes*(key: SkPublicKey): seq[byte] {.inline.} =
|
||||
|
||||
proc getBytes*(sig: SkSignature): seq[byte] {.inline.} =
|
||||
## Serialize Secp256k1 `signature` and return it.
|
||||
result = newSeq[byte](72)
|
||||
result = newSeqUninit[byte](72)
|
||||
let length = toBytes(sig, result)
|
||||
result.setLen(length)
|
||||
|
||||
proc sign*[T: byte|char](key: SkPrivateKey, msg: openArray[T]): SkSignature =
|
||||
proc sign*[T: byte | char](key: SkPrivateKey, msg: openArray[T]): SkSignature =
|
||||
## Sign message `msg` using private key `key` and return signature object.
|
||||
let h = sha256.digest(msg)
|
||||
SkSignature(sign(SkSecretKey(key), SkMessage(h.data)))
|
||||
|
||||
proc verify*[T: byte|char](sig: SkSignature, msg: openArray[T],
|
||||
key: SkPublicKey): bool =
|
||||
proc verify*[T: byte | char](
|
||||
sig: SkSignature, msg: openArray[T], key: SkPublicKey
|
||||
): bool =
|
||||
let h = sha256.digest(msg)
|
||||
verify(secp256k1.SkSignature(sig), SkMessage(h.data), secp256k1.SkPublicKey(key))
|
||||
|
||||
func clear*(key: var SkPrivateKey) = clear(secp256k1.SkSecretKey(key))
|
||||
func clear*(key: var SkPrivateKey) =
|
||||
clear(secp256k1.SkSecretKey(key))
|
||||
|
||||
func `$`*(key: SkPrivateKey): string = $secp256k1.SkSecretKey(key)
|
||||
func `$`*(key: SkPublicKey): string = $secp256k1.SkPublicKey(key)
|
||||
func `$`*(key: SkSignature): string = $secp256k1.SkSignature(key)
|
||||
func `$`*(key: SkKeyPair): string = $secp256k1.SkKeyPair(key)
|
||||
func `$`*(key: SkPrivateKey): string =
|
||||
$secp256k1.SkSecretKey(key)
|
||||
func `$`*(key: SkPublicKey): string =
|
||||
$secp256k1.SkPublicKey(key)
|
||||
func `$`*(key: SkSignature): string =
|
||||
$secp256k1.SkSignature(key)
|
||||
func `$`*(key: SkKeyPair): string =
|
||||
$secp256k1.SkKeyPair(key)
|
||||
|
||||
func `==`*(a, b: SkPrivateKey): bool =
|
||||
secp256k1.SkSecretKey(a) == secp256k1.SkSecretKey(b)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user