Compare commits
1058 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15b5581894 | ||
|
|
05ac033907 | ||
|
|
cbf0b00c1d | ||
|
|
71bc62609e | ||
|
|
7336511246 | ||
|
|
eb9041691e | ||
|
|
51d8ac4219 | ||
|
|
64eb48ef39 | ||
|
|
0138e5c71f | ||
|
|
cf76d41bf4 | ||
|
|
2161bff690 | ||
|
|
07a7281e26 | ||
|
|
fb975731bb | ||
|
|
1bce9fa76d | ||
|
|
8f794afdb5 | ||
|
|
f9f536cd08 | ||
|
|
bfb65140d2 | ||
|
|
5e2cadd9c7 | ||
|
|
12afb13e9b | ||
|
|
4435e6645c | ||
|
|
90e5eeefb8 | ||
|
|
c33d7fbb45 | ||
|
|
3bbb647e5f | ||
|
|
007207d8ca | ||
|
|
c8aea223ec | ||
|
|
d52272983a | ||
|
|
905d76b436 | ||
|
|
e69d2009cf | ||
|
|
a95b2de645 | ||
|
|
6ecc6ddcdf | ||
|
|
6ecb66eff9 | ||
|
|
94a784bfe9 | ||
|
|
2072341eca | ||
|
|
36d250a420 | ||
|
|
6f7548f596 | ||
|
|
1ccbe5cfd4 | ||
|
|
a8242ffef9 | ||
|
|
fb071acb09 | ||
|
|
51817baecd | ||
|
|
15ebc98877 | ||
|
|
b6a25ba30a | ||
|
|
abceaf3832 | ||
|
|
25c2f7e7fd | ||
|
|
27a0142af1 | ||
|
|
698fa6e8f6 | ||
|
|
ae312bcb01 | ||
|
|
d17ee979b8 | ||
|
|
4fb4ef6d24 | ||
|
|
1c82a0733f | ||
|
|
b54ddd027d | ||
|
|
61e56aba41 | ||
|
|
aecc6fc862 | ||
|
|
dd38573a28 | ||
|
|
c84d82580a | ||
|
|
506cee52e8 | ||
|
|
c58b52c21c | ||
|
|
f8326b6e27 | ||
|
|
ea0c13c0a4 | ||
|
|
a8668b371c | ||
|
|
32ea4d69a3 | ||
|
|
defd24c40e | ||
|
|
194101ed00 | ||
|
|
2ab8c72ce3 | ||
|
|
a75ba3620c | ||
|
|
54afb33333 | ||
|
|
3f42b6178f | ||
|
|
1460f6cc27 | ||
|
|
751210c963 | ||
|
|
7dfa57999b | ||
|
|
11fb967c6a | ||
|
|
c270599a23 | ||
|
|
412d0307cb | ||
|
|
33b5b36f44 | ||
|
|
ef7a82835a | ||
|
|
ac8f65dbbf | ||
|
|
725073a683 | ||
|
|
c5aed5bab1 | ||
|
|
cacbf256fe | ||
|
|
0cd972326c | ||
|
|
239cf91594 | ||
|
|
1700873f48 | ||
|
|
4adb44155d | ||
|
|
82b37bfa0d | ||
|
|
544725a018 | ||
|
|
26e253e554 | ||
|
|
610d9baba4 | ||
|
|
253ff3f37b | ||
|
|
54c2d6167f | ||
|
|
29ebddc64f | ||
|
|
fc129b4a26 | ||
|
|
a071d81fe9 | ||
|
|
9f94473eaf | ||
|
|
42437fbc57 | ||
|
|
f3cdd7bf21 | ||
|
|
5ee54b7298 | ||
|
|
06c7096054 | ||
|
|
b2277f65e4 | ||
|
|
c52a6c8fb7 | ||
|
|
7c7b181ca0 | ||
|
|
24a4fdf405 | ||
|
|
45f27cec34 | ||
|
|
6bd7db96fe | ||
|
|
ff398fe7ff | ||
|
|
3ebc769757 | ||
|
|
d60e6384d7 | ||
|
|
3fd58bdcbd | ||
|
|
ecbafb2390 | ||
|
|
adabd8198c | ||
|
|
c2487cfe07 | ||
|
|
e0141f8324 | ||
|
|
b52ac20660 | ||
|
|
3c85f29f11 | ||
|
|
d9673b0d6b | ||
|
|
89f828be1c | ||
|
|
ec56b1f09d | ||
|
|
95236d25b2 | ||
|
|
7b2afdfc8c | ||
|
|
440e52f410 | ||
|
|
8840a293dd | ||
|
|
89d627769e | ||
|
|
4e2e88a620 | ||
|
|
ebf51c0be0 | ||
|
|
04c6867660 | ||
|
|
0199acbece | ||
|
|
e4c2fe9e72 | ||
|
|
407de5e8c4 | ||
|
|
7d26a82232 | ||
|
|
3b23817936 | ||
|
|
aa8487c1d0 | ||
|
|
9cb8606103 | ||
|
|
6cf3ba7efd | ||
|
|
023e511f83 | ||
|
|
17042e9c32 | ||
|
|
f2c34f7ca2 | ||
|
|
375a8daeb4 | ||
|
|
b700ff3501 | ||
|
|
9519493e32 | ||
|
|
037fd1b309 | ||
|
|
78a534633d | ||
|
|
effead9ba5 | ||
|
|
a8ee3c97e6 | ||
|
|
fb461659c7 | ||
|
|
a574df3132 | ||
|
|
d83143d0ba | ||
|
|
f875175325 | ||
|
|
c9db8ea21d | ||
|
|
a16bad4175 | ||
|
|
595dac6c3f | ||
|
|
82a148a99b | ||
|
|
4320c9bc4f | ||
|
|
23d977ecce | ||
|
|
ab27848dc4 | ||
|
|
742a6007fe | ||
|
|
91933d857d | ||
|
|
3e1d73126c | ||
|
|
7014642815 | ||
|
|
1bd4564216 | ||
|
|
97cb010df8 | ||
|
|
ed18c7b54c | ||
|
|
e71598d876 | ||
|
|
3d0ce10fa6 | ||
|
|
cfc8df156b | ||
|
|
94cb3b6e0e | ||
|
|
fefec000fb | ||
|
|
c7ded6a785 | ||
|
|
2fbb952cdd | ||
|
|
e2ab3e4f5b | ||
|
|
1871275ecd | ||
|
|
afc1b72611 | ||
|
|
c5c3fb6a75 | ||
|
|
bceb883d99 | ||
|
|
fcccbf3b75 | ||
|
|
9ad71b7baa | ||
|
|
4311d43497 | ||
|
|
0815cc3b83 | ||
|
|
b21844b371 | ||
|
|
f825048efa | ||
|
|
2cbffe36e2 | ||
|
|
6bb106a784 | ||
|
|
4e0ec4e66b | ||
|
|
b58a3ed0ad | ||
|
|
b803f57db6 | ||
|
|
b457f46c81 | ||
|
|
b4520c5886 | ||
|
|
ca67dc251f | ||
|
|
aea289e79e | ||
|
|
ef0eda0c39 | ||
|
|
af7dc3676b | ||
|
|
fa570f297e | ||
|
|
9cce6a47d4 | ||
|
|
bff0bedfa9 | ||
|
|
d6c80c1672 | ||
|
|
512343003d | ||
|
|
3adb640d2b | ||
|
|
ea589a17a4 | ||
|
|
a3883ca5d9 | ||
|
|
5be0163cde | ||
|
|
55454b2f2d | ||
|
|
a4dc85543b | ||
|
|
d8394bded7 | ||
|
|
d89b8d904f | ||
|
|
8e866ee551 | ||
|
|
6dfc1bd474 | ||
|
|
c84e5745fa | ||
|
|
751d15e4be | ||
|
|
8b6c5be6a9 | ||
|
|
3502b36232 | ||
|
|
046b8bbc8a | ||
|
|
6fa4b2dec5 | ||
|
|
83bcb819da | ||
|
|
b7e8621846 | ||
|
|
d5c19f8719 | ||
|
|
8fa56add47 | ||
|
|
e45ee6cd72 | ||
|
|
e344286c32 | ||
|
|
45d7c60608 | ||
|
|
a3e39c9858 | ||
|
|
8ab6d89810 | ||
|
|
1b3b9b2887 | ||
|
|
89ae0f0ea0 | ||
|
|
06cae8a535 | ||
|
|
12ac992ffb | ||
|
|
3b23c2e86d | ||
|
|
0f9b2218da | ||
|
|
12c42bce9b | ||
|
|
eb3d33ed6f | ||
|
|
a139c6d216 | ||
|
|
50f5ccc4f2 | ||
|
|
94db961975 | ||
|
|
38f3747cde | ||
|
|
4fec2f2fc2 | ||
|
|
c96d8a742b | ||
|
|
c2de31e586 | ||
|
|
4b11d79829 | ||
|
|
c35abe4196 | ||
|
|
a9a278a6e1 | ||
|
|
fb07ffa676 | ||
|
|
940a88fa4e | ||
|
|
708e495c28 | ||
|
|
460681ead9 | ||
|
|
2a7ed457dc | ||
|
|
35bbf11ba5 | ||
|
|
4f1e1e8870 | ||
|
|
cf505139f1 | ||
|
|
cdba22a2cb | ||
|
|
1df6361753 | ||
|
|
a6c6c7c070 | ||
|
|
ed6f2877d7 | ||
|
|
53c408f549 | ||
|
|
64704c456f | ||
|
|
0edf8e3f1b | ||
|
|
53a04e1686 | ||
|
|
d356c6640d | ||
|
|
e8e087fc37 | ||
|
|
181738a736 | ||
|
|
1ac1224cd3 | ||
|
|
3687df8da2 | ||
|
|
67eee6aeb7 | ||
|
|
83ba9df85b | ||
|
|
83f706186f | ||
|
|
eea5f6f232 | ||
|
|
38af7f35fc | ||
|
|
eea3de00c1 | ||
|
|
4d66e8d06d | ||
|
|
010cfb7d67 | ||
|
|
f9a8aac036 | ||
|
|
3d28823be7 | ||
|
|
492317abd7 | ||
|
|
ab22d5e278 | ||
|
|
ce5f704dd5 | ||
|
|
696dc05dda | ||
|
|
589083ad7a | ||
|
|
1fda997370 | ||
|
|
7c335e8764 | ||
|
|
5b1d33d5fa | ||
|
|
e435407080 | ||
|
|
b180be7526 | ||
|
|
90fb473d87 | ||
|
|
3650f2d51c | ||
|
|
3090324366 | ||
|
|
130901e820 | ||
|
|
801b8191ef | ||
|
|
469f9c26e7 | ||
|
|
b5f510ead7 | ||
|
|
b4f8bba843 | ||
|
|
2bb79614f6 | ||
|
|
c077dc652d | ||
|
|
1b6f2a3e92 | ||
|
|
9136c81f05 | ||
|
|
912e5599d9 | ||
|
|
ff0095ac5e | ||
|
|
bf9fedc4ee | ||
|
|
78ba54da6b | ||
|
|
c90e279ab5 | ||
|
|
215602de08 | ||
|
|
13b832f959 | ||
|
|
789bb9c852 | ||
|
|
aada1f547b | ||
|
|
60718225ac | ||
|
|
1a2fc03083 | ||
|
|
61e8baee0c | ||
|
|
dbc5f55da9 | ||
|
|
81b7698428 | ||
|
|
4ce4bad383 | ||
|
|
23d25a079b | ||
|
|
d19bdb642e | ||
|
|
2e0246a6c2 | ||
|
|
349098e7b2 | ||
|
|
dc3b1ecdd0 | ||
|
|
7fb33796b1 | ||
|
|
ec886ddefb | ||
|
|
4ded4181a6 | ||
|
|
60691d03e0 | ||
|
|
869fa399b1 | ||
|
|
7af953fd62 | ||
|
|
5acbcb0d57 | ||
|
|
7000c394b2 | ||
|
|
7c0d894ccf | ||
|
|
f092c10de5 | ||
|
|
7f5e6b3a0a | ||
|
|
5e9dc185a5 | ||
|
|
0e94ac0111 | ||
|
|
0b5bbf6048 | ||
|
|
14c9cbd40e | ||
|
|
c4466878cf | ||
|
|
c584221fa2 | ||
|
|
70ba050c06 | ||
|
|
f2281dc38a | ||
|
|
18a2e6265d | ||
|
|
0d593199d0 | ||
|
|
832c4a7565 | ||
|
|
34d22a35dd | ||
|
|
c880716f16 | ||
|
|
f20f4c74d2 | ||
|
|
1fdfa1e6c6 | ||
|
|
4ee49f03df | ||
|
|
52d5278a62 | ||
|
|
35a2b87174 | ||
|
|
664bb2becd | ||
|
|
8865b95818 | ||
|
|
03600dce97 | ||
|
|
8b607efc40 | ||
|
|
9f1af6b3e8 | ||
|
|
c0952ba44b | ||
|
|
9475a2e474 | ||
|
|
5baed0c158 | ||
|
|
97c259858c | ||
|
|
0f90696528 | ||
|
|
ac974a180d | ||
|
|
052380b8de | ||
|
|
23a29439c0 | ||
|
|
a8617e2862 | ||
|
|
6945a6b320 | ||
|
|
b375c9adbf | ||
|
|
88a727739b | ||
|
|
5f3ae4dee3 | ||
|
|
5cbe834024 | ||
|
|
eea5b86cc4 | ||
|
|
09c512abaa | ||
|
|
d42d816e7f | ||
|
|
17effd15ab | ||
|
|
9982eba188 | ||
|
|
aafe527d4a | ||
|
|
e4c53a460e | ||
|
|
ca01596a65 | ||
|
|
4f2415b483 | ||
|
|
f680eacdf2 | ||
|
|
f6dcca3ebb | ||
|
|
1a642fc624 | ||
|
|
c4469514db | ||
|
|
59daf95859 | ||
|
|
3a6e04ba15 | ||
|
|
b26f86d6ff | ||
|
|
570215acae | ||
|
|
a511264433 | ||
|
|
06f25d2b27 | ||
|
|
a3bd355b16 | ||
|
|
1c1cd8b164 | ||
|
|
a898109522 | ||
|
|
3694b10e22 | ||
|
|
39f25d20a8 | ||
|
|
61c1646b43 | ||
|
|
1b9396dcbb | ||
|
|
05be4b5b0e | ||
|
|
f8f8bf0fea | ||
|
|
6643b6a306 | ||
|
|
7036ab26d7 | ||
|
|
9b55169251 | ||
|
|
ff13c9c186 | ||
|
|
879e7305ca | ||
|
|
1ff827b2ea | ||
|
|
e7f1204fa4 | ||
|
|
9a2c4a34ee | ||
|
|
f4c421f77a | ||
|
|
fe84718b55 | ||
|
|
15d71a01d5 | ||
|
|
abe30f2578 | ||
|
|
fdae48547b | ||
|
|
68ca8df22f | ||
|
|
b8da38f4e4 | ||
|
|
1f103ab7f1 | ||
|
|
da6cf33aac | ||
|
|
73f08b376f | ||
|
|
ed34d1fee7 | ||
|
|
e9f4f1d13c | ||
|
|
e5bb330be5 | ||
|
|
e23e22cb81 | ||
|
|
7434026f5f | ||
|
|
5319d33bc6 | ||
|
|
702311b6b2 | ||
|
|
2511bc20e0 | ||
|
|
ce5a6eabae | ||
|
|
581cd97ba1 | ||
|
|
fa2f99641f | ||
|
|
c40f7db1ab | ||
|
|
be1363e943 | ||
|
|
c313039526 | ||
|
|
73db5dda8c | ||
|
|
5a8fb77fb2 | ||
|
|
4b6ebcbb61 | ||
|
|
bc056c41bc | ||
|
|
5b54442a48 | ||
|
|
dc14cce7a9 | ||
|
|
4f278ba715 | ||
|
|
1036fcca36 | ||
|
|
6b286a5dee | ||
|
|
c8ae675b95 | ||
|
|
4186467129 | ||
|
|
726884afcb | ||
|
|
5a2f3e700b | ||
|
|
911fc74346 | ||
|
|
1388f4d27e | ||
|
|
5b87327a43 | ||
|
|
2fc1679886 | ||
|
|
7ba5652bea | ||
|
|
1e9aebbc86 | ||
|
|
61ec361182 | ||
|
|
7781cbbc57 | ||
|
|
f3b806b471 | ||
|
|
6496405f30 | ||
|
|
85a6dc5e8c | ||
|
|
856bbfc9c8 | ||
|
|
ebaa43fa4c | ||
|
|
2d44b3ebea | ||
|
|
984493db30 | ||
|
|
47848769ff | ||
|
|
a8f6f5b974 | ||
|
|
1e13f474cb | ||
|
|
c69c3a9a46 | ||
|
|
3216b143c2 | ||
|
|
cc963d42a0 | ||
|
|
3f95a62e4f | ||
|
|
7f9a9e2e82 | ||
|
|
375ecd4ada | ||
|
|
8875dccd11 | ||
|
|
4c2301fdf6 | ||
|
|
346594c406 | ||
|
|
2609e2db5c | ||
|
|
692d5b4e08 | ||
|
|
c4af7464e5 | ||
|
|
5a1dc3eb8a | ||
|
|
adcbfcf8d6 | ||
|
|
b57607e7d3 | ||
|
|
2f159d4f45 | ||
|
|
93e1040d07 | ||
|
|
403c07c305 | ||
|
|
6253308e2e | ||
|
|
33a014013a | ||
|
|
4637215ab2 | ||
|
|
ceaedbbd7f | ||
|
|
52f8b1a1d7 | ||
|
|
e6d1250185 | ||
|
|
d04e5e49d0 | ||
|
|
a72436f330 | ||
|
|
467403f437 | ||
|
|
bbaac0c6a9 | ||
|
|
753fd4bda3 | ||
|
|
57d2c8c94a | ||
|
|
870ec89e9a | ||
|
|
0c3b70f2fb | ||
|
|
7a367698fe | ||
|
|
c77e99814b | ||
|
|
4ddd69cc55 | ||
|
|
ef4a61c769 | ||
|
|
7dfb5ff5bd | ||
|
|
6b391312ab | ||
|
|
6e62d77e4d | ||
|
|
0281cca9af | ||
|
|
018e2403b1 | ||
|
|
61f4534e2a | ||
|
|
69667317c1 | ||
|
|
530aac0682 | ||
|
|
6e7d8f90b5 | ||
|
|
65bf1086a2 | ||
|
|
98220442b4 | ||
|
|
3c3d2ef2b9 | ||
|
|
67066eb32a | ||
|
|
631df0fe56 | ||
|
|
485870296d | ||
|
|
6e5a1c00dc | ||
|
|
ba487eaaca | ||
|
|
eb0986c3f9 | ||
|
|
baf5be09dc | ||
|
|
f3aed42dd6 | ||
|
|
44531e3009 | ||
|
|
2177a0179e | ||
|
|
e1f333021f | ||
|
|
4040d73c60 | ||
|
|
61bd47ccc1 | ||
|
|
915c366056 | ||
|
|
6888a968f9 | ||
|
|
4e8e5bbb86 | ||
|
|
5752869824 | ||
|
|
39a12622ae | ||
|
|
dca88ff85c | ||
|
|
ab9843cb00 | ||
|
|
72fd1fa58d | ||
|
|
51eac1926f | ||
|
|
c1aed4af45 | ||
|
|
c12447c7c5 | ||
|
|
502bacea82 | ||
|
|
da5ba4ccc8 | ||
|
|
1f2426226b | ||
|
|
c0eb30b604 | ||
|
|
caca3a8048 | ||
|
|
f929419676 | ||
|
|
bc53e78a04 | ||
|
|
3dbea1a7a1 | ||
|
|
644d1db5ef | ||
|
|
07d97d5b26 | ||
|
|
c658b46fe1 | ||
|
|
8a5c9a8c70 | ||
|
|
3e4a525520 | ||
|
|
74ce0f738e | ||
|
|
1073d56245 | ||
|
|
7aa4484a03 | ||
|
|
6c96b60a63 | ||
|
|
ac1900a0fc | ||
|
|
bd3bc5c168 | ||
|
|
9ed43230ca | ||
|
|
7abe9ec4cc | ||
|
|
5ed2527663 | ||
|
|
b87c7cac54 | ||
|
|
0b34579b04 | ||
|
|
31291ebd35 | ||
|
|
e8e0b08f17 | ||
|
|
491ce61a76 | ||
|
|
e12a26dac5 | ||
|
|
7bf1889af1 | ||
|
|
139a2b7b0d | ||
|
|
f230c719d8 | ||
|
|
8703449dfe | ||
|
|
108590d924 | ||
|
|
b2cf5d2958 | ||
|
|
ee5ed44761 | ||
|
|
346913b7f6 | ||
|
|
18a8d2f67f | ||
|
|
3ae10915e4 | ||
|
|
949b9c85ca | ||
|
|
c21c19bd6c | ||
|
|
29125e830b | ||
|
|
1ac4676f4b | ||
|
|
fe5301cebf | ||
|
|
7262601123 | ||
|
|
fcb6cc1e76 | ||
|
|
ff716e7799 | ||
|
|
9c595aff95 | ||
|
|
98dbd1fdc7 | ||
|
|
a6df452841 | ||
|
|
4eab8672b8 | ||
|
|
6e2821b4db | ||
|
|
1564fae011 | ||
|
|
30e40079ca | ||
|
|
65a1d88907 | ||
|
|
e590874a81 | ||
|
|
b28e742683 | ||
|
|
62fdfb937a | ||
|
|
8814195122 | ||
|
|
e2095d4a5d | ||
|
|
78a38e9825 | ||
|
|
712101b63d | ||
|
|
1f18dbb17c | ||
|
|
0d8001adea | ||
|
|
1e44a62494 | ||
|
|
e8b13cb77e | ||
|
|
3f2fd610d9 | ||
|
|
25604dc577 | ||
|
|
0cfc6bf2a6 | ||
|
|
90d7823acb | ||
|
|
b4ae1b6528 | ||
|
|
f442665c46 | ||
|
|
c22498066b | ||
|
|
637883f52b | ||
|
|
f0c0da8551 | ||
|
|
10f42a2b39 | ||
|
|
29baccd857 | ||
|
|
f9814381a7 | ||
|
|
771ea47d37 | ||
|
|
c54beba932 | ||
|
|
a809621f63 | ||
|
|
7ad556346e | ||
|
|
5737c21340 | ||
|
|
77a5ce6bf3 | ||
|
|
fb503f523b | ||
|
|
bf7677ce69 | ||
|
|
1ce8c1cf82 | ||
|
|
4ddccfa5e5 | ||
|
|
5795d332c8 | ||
|
|
823054dc34 | ||
|
|
2ce15f429b | ||
|
|
143411aaf0 | ||
|
|
7d9548400d | ||
|
|
1b1941a896 | ||
|
|
4848c384cd | ||
|
|
d4f38d3894 | ||
|
|
e6acbc5a58 | ||
|
|
8dd4db5d85 | ||
|
|
d436eddc6a | ||
|
|
faf8e9ec6a | ||
|
|
7e6a571cba | ||
|
|
6bee9cd1e4 | ||
|
|
fb2b77e991 | ||
|
|
b914912c06 | ||
|
|
0ce04845de | ||
|
|
073365d5d9 | ||
|
|
3c27587d83 | ||
|
|
dbccc700f1 | ||
|
|
9f90ff2e59 | ||
|
|
4d9c8926b1 | ||
|
|
070695b348 | ||
|
|
c6e97d4dc5 | ||
|
|
a24e78fa92 | ||
|
|
21e0cd7781 | ||
|
|
ed45760425 | ||
|
|
0ca4250bd4 | ||
|
|
5059619947 | ||
|
|
edb228839e | ||
|
|
a9c93c797d | ||
|
|
f826ac35e3 | ||
|
|
9dc512349a | ||
|
|
3ecf16a492 | ||
|
|
584a76ab70 | ||
|
|
e339cde790 | ||
|
|
82a6a0848a | ||
|
|
441cb7980b | ||
|
|
5f523f6966 | ||
|
|
993650f3d6 | ||
|
|
ab330301eb | ||
|
|
bab85dd789 | ||
|
|
acae643a4a | ||
|
|
01f825b0e1 | ||
|
|
796637b31a | ||
|
|
484ecfaf47 | ||
|
|
c082af6f74 | ||
|
|
494eb4ab6b | ||
|
|
fe678dcd2f | ||
|
|
da5de4a6ff | ||
|
|
526c61e2c0 | ||
|
|
c7f608ec74 | ||
|
|
6816f8b489 | ||
|
|
cd58b5ff1f | ||
|
|
bca100cdb0 | ||
|
|
c63452e25d | ||
|
|
7e779327eb | ||
|
|
9f1e08663d | ||
|
|
91122d9193 | ||
|
|
c7d21841a4 | ||
|
|
701692b7d3 | ||
|
|
787a30cd8e | ||
|
|
ca6edcaf71 | ||
|
|
8d171a37f8 | ||
|
|
e9bd41b3f1 | ||
|
|
aa67bd5d00 | ||
|
|
802d684994 | ||
|
|
434e018584 | ||
|
|
4839294c86 | ||
|
|
71bbcd54ff | ||
|
|
3db353f356 | ||
|
|
f4c5ea8378 | ||
|
|
6a97a4a11e | ||
|
|
79f754e6ac | ||
|
|
e3e2fcc285 | ||
|
|
00e61a9100 | ||
|
|
f3107214f4 | ||
|
|
78e001284f | ||
|
|
9caa868603 | ||
|
|
202c54d423 | ||
|
|
1051004aee | ||
|
|
a1a002f4da | ||
|
|
5ef41ed53e | ||
|
|
67721f3413 | ||
|
|
34bf2452c3 | ||
|
|
8fbb98fb3f | ||
|
|
1792725651 | ||
|
|
47ff3a9bee | ||
|
|
ac3a706f0d | ||
|
|
9b5483a71b | ||
|
|
38c31c880f | ||
|
|
0bed5976e3 | ||
|
|
19a6725430 | ||
|
|
48a54efcb2 | ||
|
|
c0b0dc5219 | ||
|
|
683a26c830 | ||
|
|
1a16f335fa | ||
|
|
4145be863b | ||
|
|
e9f1b38984 | ||
|
|
edd90f153c | ||
|
|
dec390a89f | ||
|
|
41348dead4 | ||
|
|
c473ab97c7 | ||
|
|
0cd1de769b | ||
|
|
5ae8e8a9ca | ||
|
|
796d72f48e | ||
|
|
86a8584252 | ||
|
|
6509e90c36 | ||
|
|
09ecd0c583 | ||
|
|
a6d6adc57f | ||
|
|
6be5744be4 | ||
|
|
31b4437b93 | ||
|
|
458afcd230 | ||
|
|
cf5ae81ced | ||
|
|
08e46432c5 | ||
|
|
6f11621734 | ||
|
|
6004c394d6 | ||
|
|
609d83f92c | ||
|
|
4ef71f8a82 | ||
|
|
b47218521f | ||
|
|
c9cee8fd52 | ||
|
|
75e779029f | ||
|
|
3016d54f13 | ||
|
|
05e7c133fb | ||
|
|
fd57100190 | ||
|
|
fc86b1799a | ||
|
|
9546e0c8c2 | ||
|
|
4fe6c148ef | ||
|
|
9e872788c7 | ||
|
|
da95f77996 | ||
|
|
6f758bc7b1 | ||
|
|
b37b3cd1fc | ||
|
|
e81069ab3d | ||
|
|
bf25f17880 | ||
|
|
6201532c64 | ||
|
|
3094ae9df9 | ||
|
|
59f6931e06 | ||
|
|
95a601d053 | ||
|
|
5d7ef54d02 | ||
|
|
b34d46cbc8 | ||
|
|
0bb78814a6 | ||
|
|
4938d5dde5 | ||
|
|
4817b94d0b | ||
|
|
2a470deeaf | ||
|
|
861d829420 | ||
|
|
09ee6e1477 | ||
|
|
10fc74eb81 | ||
|
|
986f485b3e | ||
|
|
af1088ef61 | ||
|
|
13bc922e54 | ||
|
|
1f39a1bd76 | ||
|
|
13efb6586d | ||
|
|
a48ed02433 | ||
|
|
24c43513a6 | ||
|
|
a6d267abc0 | ||
|
|
c8877d4098 | ||
|
|
1318f536c9 | ||
|
|
bc2f86e806 | ||
|
|
107f0fa4c6 | ||
|
|
a5190449da | ||
|
|
114d4433a9 | ||
|
|
bd4498cffc | ||
|
|
6771539a90 | ||
|
|
123b6ae62e | ||
|
|
5d6a0d4dae | ||
|
|
6ecc63002b | ||
|
|
b3ea766bd5 | ||
|
|
e2a90ce159 | ||
|
|
8057e8df43 | ||
|
|
98b7c07171 | ||
|
|
3d76417353 | ||
|
|
2060ea5de3 | ||
|
|
79eb8f7ace | ||
|
|
0ebcc200c3 | ||
|
|
27f3f42ce2 | ||
|
|
dab967ace8 | ||
|
|
485d4aa8f3 | ||
|
|
799ae29ac4 | ||
|
|
00b209a29e | ||
|
|
581e510c2d | ||
|
|
9053c0dfd9 | ||
|
|
d32ce37484 | ||
|
|
1020560af6 | ||
|
|
118588ef6c | ||
|
|
6b9314eaa9 | ||
|
|
686bf443e6 | ||
|
|
93054ef24b | ||
|
|
1620eabd9d | ||
|
|
7fcb082cad | ||
|
|
68d16b723a | ||
|
|
ec9c6e9783 | ||
|
|
3b083d545d | ||
|
|
5400447395 | ||
|
|
ed8425b8b9 | ||
|
|
7d7d4822a5 | ||
|
|
80528c5344 | ||
|
|
6c24d9897a | ||
|
|
e346f3058e | ||
|
|
dc8da3743d | ||
|
|
6563576ae9 | ||
|
|
db9397890e | ||
|
|
bd1e3fc606 | ||
|
|
fe5f5b28d9 | ||
|
|
ee41fa6f30 | ||
|
|
84ecab0eaf | ||
|
|
3fde07b2e1 | ||
|
|
3c2f13f88b | ||
|
|
52f10242e2 | ||
|
|
9e719f088f | ||
|
|
6552256981 | ||
|
|
d1934363e7 | ||
|
|
cdbcfaa7de | ||
|
|
6ecc855c34 | ||
|
|
30ecd045fa | ||
|
|
f9e64e0965 | ||
|
|
0ecbb3ec02 | ||
|
|
3bb5ad7204 | ||
|
|
8e078b1d83 | ||
|
|
0c4d2fbc70 | ||
|
|
e4614e49ae | ||
|
|
8f56606cac | ||
|
|
af90fbfb33 | ||
|
|
938c3707fc | ||
|
|
1b95af18ff | ||
|
|
272ebc1ef7 | ||
|
|
cfd50538bb | ||
|
|
08abf67a51 | ||
|
|
981554cf74 | ||
|
|
979af3d314 | ||
|
|
57d1f2b4d3 | ||
|
|
1fa95ac236 | ||
|
|
25dc1c2155 | ||
|
|
61ec02248a | ||
|
|
ecd7caa93d | ||
|
|
aea26dcc64 | ||
|
|
1b8f299df2 | ||
|
|
6e2e08628a | ||
|
|
fb0e6cf0d0 | ||
|
|
a57c45bb1c | ||
|
|
b2cc1c54f8 | ||
|
|
21dad1d41e | ||
|
|
1d42b7f0d1 | ||
|
|
cddc33bb24 | ||
|
|
cb7ad2366d | ||
|
|
25536c5ffb | ||
|
|
842b75c0e6 | ||
|
|
8b0ba97cf2 | ||
|
|
b84682168d | ||
|
|
f20f9f376e | ||
|
|
24838bbcd3 | ||
|
|
7a00d97977 | ||
|
|
ac3de4c5fc | ||
|
|
28c731881f | ||
|
|
a7a46f4253 | ||
|
|
528497b86a | ||
|
|
32c32ecfda | ||
|
|
e30839e85f | ||
|
|
a4c7843a07 | ||
|
|
f0c6d17ad8 | ||
|
|
66c0638f3b | ||
|
|
eec7364760 | ||
|
|
b10094508f | ||
|
|
8e8679807d | ||
|
|
629da8f8bf | ||
|
|
10a346476a | ||
|
|
d1f5284fe6 | ||
|
|
849f5d9a44 | ||
|
|
7a76916143 | ||
|
|
2a829b1f1a | ||
|
|
44c68221a8 | ||
|
|
e36c4ecc98 | ||
|
|
01d399ad66 | ||
|
|
9376796bdb | ||
|
|
f135c09b36 | ||
|
|
4e85015836 | ||
|
|
f56c065f57 | ||
|
|
adc3457a89 | ||
|
|
fa261ebd02 | ||
|
|
7fdb87ad38 | ||
|
|
baeda93474 | ||
|
|
7a28f72abc | ||
|
|
1ab06ce2d2 | ||
|
|
b0cc44aabb | ||
|
|
c983efe895 | ||
|
|
650948feed | ||
|
|
4e76cce0b7 | ||
|
|
9e9045ab94 | ||
|
|
d86333af6b | ||
|
|
28b5906d9e | ||
|
|
24f6d8296b | ||
|
|
9c5e35548d | ||
|
|
92b5b5644f | ||
|
|
8fb47b52f5 | ||
|
|
14361cc7b1 | ||
|
|
2257bc8e2f | ||
|
|
461b2d4853 | ||
|
|
941f2380c4 | ||
|
|
cb31220a4a | ||
|
|
9e09d5b6bf | ||
|
|
a04c5b180a | ||
|
|
db7a8c4ac7 | ||
|
|
fac356c701 | ||
|
|
90eb61091a | ||
|
|
f6998cb04e | ||
|
|
897a94641e | ||
|
|
3f677c6168 | ||
|
|
1cd93e4ceb | ||
|
|
03b96a7c0a | ||
|
|
869fa6fda8 | ||
|
|
bc2f5586ee | ||
|
|
16f435b906 | ||
|
|
0a170efaa5 | ||
|
|
1356d6d8d5 | ||
|
|
0a9114203b | ||
|
|
2b05eb43a9 | ||
|
|
dd2c27958c | ||
|
|
6737484eda | ||
|
|
9fe991db1c | ||
|
|
6cf441fc9e | ||
|
|
99e37844fd | ||
|
|
8348147a4f | ||
|
|
bd7273061e | ||
|
|
4f447c50b2 | ||
|
|
692cd10d4a | ||
|
|
86446d713a | ||
|
|
d1487b3177 | ||
|
|
8e7a08f865 | ||
|
|
0d75d01c84 | ||
|
|
1c75e8eb47 | ||
|
|
431b27d3e1 | ||
|
|
d97cf34138 | ||
|
|
e6f75bccfe | ||
|
|
c039ab79b5 | ||
|
|
9436e88d27 | ||
|
|
652f5032a2 | ||
|
|
d7a7f034db | ||
|
|
27c32d3629 | ||
|
|
0d2993e46d | ||
|
|
e4168c2985 | ||
|
|
9d3771458d | ||
|
|
ff0ce70169 | ||
|
|
811d165458 | ||
|
|
d57944ffb9 | ||
|
|
991f0cac6e | ||
|
|
b2d338bf35 | ||
|
|
ef80698deb | ||
|
|
e12a5159a8 | ||
|
|
0455aa96bf | ||
|
|
9aaedad64f | ||
|
|
ea6b0ec164 | ||
|
|
679d6c6f2b | ||
|
|
ec96091369 | ||
|
|
0a535bf485 | ||
|
|
dcaff6f4c8 | ||
|
|
5e7d42e4a4 | ||
|
|
99a13c4e66 | ||
|
|
060205ab27 | ||
|
|
9f775a7673 | ||
|
|
d477670cb9 | ||
|
|
54c9c382e7 | ||
|
|
9108a3bb50 | ||
|
|
c1cced3662 | ||
|
|
5ea4c22868 | ||
|
|
f1b7d8ab34 | ||
|
|
9c9ddaccec | ||
|
|
68a08df9c3 | ||
|
|
1e6d889fc7 | ||
|
|
443115f885 | ||
|
|
cab073ba00 | ||
|
|
7f9589d678 | ||
|
|
899c1a4b0e | ||
|
|
e3f7b70c38 | ||
|
|
6e49ff1d98 | ||
|
|
0c7f998c25 | ||
|
|
23ea4798e0 | ||
|
|
2632310b6a | ||
|
|
c4dd156113 | ||
|
|
06a7ca221c | ||
|
|
04931618ed | ||
|
|
dbc4d85f0a | ||
|
|
bd45cd4a5e | ||
|
|
8c02211dc3 | ||
|
|
eb18e7ade7 | ||
|
|
8ddd508a44 | ||
|
|
857809f693 | ||
|
|
ca0045482c | ||
|
|
1c2c683ae3 | ||
|
|
7c8d404cf8 | ||
|
|
d293f94a6f | ||
|
|
a60d0e440d | ||
|
|
9e294d577a | ||
|
|
6d5d419e14 | ||
|
|
1b4d9c2d39 | ||
|
|
a91e562021 | ||
|
|
d7f690c8ba | ||
|
|
7f1ff152ca | ||
|
|
beb6438ef5 | ||
|
|
e25a660a11 | ||
|
|
a7887fa9f1 | ||
|
|
a6915778bb | ||
|
|
249f81cbc5 | ||
|
|
373036bb7a | ||
|
|
ed296312aa | ||
|
|
d27c36cf75 | ||
|
|
c737056000 | ||
|
|
6f5bd845ad | ||
|
|
fed4864939 | ||
|
|
acd7192b17 | ||
|
|
21cb08586b | ||
|
|
4d1cb01da0 | ||
|
|
0e548ce7c5 | ||
|
|
236692cfd5 | ||
|
|
322dfbcd78 | ||
|
|
1bad20ae38 | ||
|
|
113c35af0a | ||
|
|
5c47116889 | ||
|
|
23fc5517b5 | ||
|
|
7e948a088f | ||
|
|
102bc7809f | ||
|
|
c2bd1a0e76 | ||
|
|
de72643898 | ||
|
|
e0f71b0c17 | ||
|
|
66f3c50842 | ||
|
|
373fdb65e9 | ||
|
|
58a1671076 | ||
|
|
e0a21e5aae | ||
|
|
3f33370e7d | ||
|
|
06fa900504 | ||
|
|
29d7a0e3c9 | ||
|
|
e8106016c8 | ||
|
|
d716bae3d5 | ||
|
|
f48b09b76e | ||
|
|
f54944bbfc | ||
|
|
ff722cac72 | ||
|
|
eeee90def5 | ||
|
|
47a02480c4 | ||
|
|
bbefdec973 | ||
|
|
f864f72bb5 | ||
|
|
6623de4e61 | ||
|
|
33284e988e | ||
|
|
9d7d6f7108 | ||
|
|
81b52c7336 | ||
|
|
fb17ae7751 | ||
|
|
9bcb589785 | ||
|
|
2e00f656d0 | ||
|
|
87f893265d | ||
|
|
d4205da484 | ||
|
|
ca0d1f5eb7 | ||
|
|
e76a545970 | ||
|
|
ec7282d05c | ||
|
|
ea3083bd3b |
@@ -1,3 +1,3 @@
|
|||||||
[target.x86_64-pc-windows-msvc]
|
[target.x86_64-pc-windows-msvc]
|
||||||
# Link the C runtime statically ; https://github.com/paritytech/parity/issues/6643
|
# Link the C runtime statically ; https://github.com/openethereum/openethereum/issues/6643
|
||||||
rustflags = ["-Ctarget-feature=+crt-static"]
|
rustflags = ["-Ctarget-feature=+crt-static"]
|
||||||
|
|||||||
2
.git-blame-ignore-revs
Normal file
2
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Reformat the source code
|
||||||
|
610d9baba4af83b5767c659ca2ccfed337af1056
|
||||||
8
.github/CODE_OF_CONDUCT.md
vendored
8
.github/CODE_OF_CONDUCT.md
vendored
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
## 1. Purpose
|
## 1. Purpose
|
||||||
|
|
||||||
A primary goal of Parity is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof).
|
A primary goal of OpenEthereum is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof).
|
||||||
|
|
||||||
This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior.
|
This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior.
|
||||||
|
|
||||||
We invite all those who participate in Parity to help us create safe and positive experiences for everyone.
|
We invite all those who participate in OpenEthereum to help us create safe and positive experiences for everyone.
|
||||||
|
|
||||||
## 2. Open Source Citizenship
|
## 2. Open Source Citizenship
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ Additionally, community organizers are available to help community members engag
|
|||||||
|
|
||||||
## 7. Addressing Grievances
|
## 7. Addressing Grievances
|
||||||
|
|
||||||
If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify Parity Technologies with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies.
|
If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify OpenEthereum Technologies with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies.
|
||||||
|
|
||||||
## 8. Scope
|
## 8. Scope
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ This code of conduct and its related procedures also applies to unacceptable beh
|
|||||||
|
|
||||||
## 9. Contact info
|
## 9. Contact info
|
||||||
|
|
||||||
You can contact Parity via Email: community@parity.io
|
You can contact OpenEthereum via Email: community@parity.io
|
||||||
|
|
||||||
## 10. License and attribution
|
## 10. License and attribution
|
||||||
|
|
||||||
|
|||||||
49
.github/CONTRIBUTING.md
vendored
49
.github/CONTRIBUTING.md
vendored
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Do you have a question?
|
## Do you have a question?
|
||||||
|
|
||||||
Check out our [Basic Usage](https://github.com/paritytech/parity/wiki/Basic-Usage), [Configuration](https://github.com/paritytech/parity/wiki/Configuring-Parity), and [FAQ](https://github.com/paritytech/parity/wiki/FAQ) articles on our [wiki](https://github.com/paritytech/parity/wiki)!
|
Check out our [Beginner Introduction](https://openethereum.github.io/Beginner-Introduction), [Configuration](https://openethereum.github.io//Configuring-OpenEthereum), and [FAQ](https://openethereum.github.io/FAQ) articles on our [wiki](https://openethereum.github.io/)!
|
||||||
|
|
||||||
See also frequently asked questions [tagged with `parity`](https://ethereum.stackexchange.com/questions/tagged/parity?sort=votes&pageSize=50) on Stack Exchange.
|
See also frequently asked questions [tagged with `parity`](https://ethereum.stackexchange.com/questions/tagged/parity?sort=votes&pageSize=50) on Stack Exchange.
|
||||||
|
|
||||||
@@ -10,11 +10,11 @@ See also frequently asked questions [tagged with `parity`](https://ethereum.stac
|
|||||||
|
|
||||||
Do **not** open an issue on Github if you think your discovered bug could be a **security-relevant vulnerability**. Please, read our [security policy](../SECURITY.md) instead.
|
Do **not** open an issue on Github if you think your discovered bug could be a **security-relevant vulnerability**. Please, read our [security policy](../SECURITY.md) instead.
|
||||||
|
|
||||||
Otherwise, just create a [new issue](https://github.com/paritytech/parity/issues/new) in our repository and state:
|
Otherwise, just create a [new issue](https://github.com/openethereum/openethereum/issues/new) in our repository and state:
|
||||||
|
|
||||||
- What's your Parity version?
|
- What's your OpenEthereum version?
|
||||||
- What's your operating system and version?
|
- What's your operating system and version?
|
||||||
- How did you install parity?
|
- How did you install OpenEthereum?
|
||||||
- Is your node fully synchronized?
|
- Is your node fully synchronized?
|
||||||
- Did you try turning it off and on again?
|
- Did you try turning it off and on again?
|
||||||
|
|
||||||
@@ -22,12 +22,47 @@ Also, try to include **steps to reproduce** the issue and expand on the **actual
|
|||||||
|
|
||||||
## Contribute!
|
## Contribute!
|
||||||
|
|
||||||
If you would like to contribute to Parity, please **fork it**, fix bugs or implement features, and [propose a pull request](https://github.com/paritytech/parity/compare).
|
If you would like to contribute to OpenEthereum, please **fork it**, fix bugs or implement features, and [propose a pull request](https://github.com/openethereum/openethereum/compare).
|
||||||
|
|
||||||
Please, refer to the [Coding Guide](https://github.com/paritytech/parity/wiki/Coding-guide) in our wiki for more details about hacking on Parity.
|
### Labels & Milestones
|
||||||
|
|
||||||
|
We use [labels](https://github.com/openethereum/openethereum/labels) to manage PRs and issues and communicate the state of a PR. Please familiarize yourself with them. Furthermore we are organizing issues in [milestones](https://github.com/openethereum/openethereum/milestones). Best way to get started is to a pick a ticket from the current milestone tagged [`easy`](https://github.com/openethereum/openethereum/labels/Q2-easy%20%F0%9F%92%83) and get going, or [`mentor`](https://github.com/openethereum/openethereum/labels/Q1-mentor%20%F0%9F%95%BA) and get in contact with the mentor offering their support on that larger task.
|
||||||
|
|
||||||
|
### Rules
|
||||||
|
|
||||||
|
There are a few basic ground-rules for contributors (including the maintainer(s) of the project):
|
||||||
|
|
||||||
|
* **No pushing directly to the master branch**.
|
||||||
|
* **All modifications** must be made in a **pull-request** to solicit feedback from other contributors.
|
||||||
|
* Pull-requests cannot be merged before CI runs green and two reviewers have given their approval.
|
||||||
|
* All code changed should be formated by running `cargo fmt -- --config=merge_imports=true`
|
||||||
|
|
||||||
|
### Recommendations
|
||||||
|
|
||||||
|
* **Non-master branch names** *should* be prefixed with a short name moniker, followed by the associated Github Issue ID (if any), and a brief description of the task using the format `<GITHUB_USERNAME>-<ISSUE_ID>-<BRIEF_DESCRIPTION>` (e.g. `gavin-123-readme`). The name moniker helps people to inquiry about their unfinished work, and the GitHub Issue ID helps your future self and other developers (particularly those who are onboarding) find out about and understand the original scope of the task, and where it fits into Parity Ethereum [Projects](https://github.com/openethereum/openethereum/projects).
|
||||||
|
* **Remove stale branches periodically**
|
||||||
|
|
||||||
|
### Preparing Pull Requests
|
||||||
|
|
||||||
|
* If your PR does not alter any logic (e.g. comments, dependencies, docs), then it may be tagged [`insubstantial`](https://github.com/openethereum/openethereum/pulls?q=is%3Aopen+is%3Apr+label%3A%22A2-insubstantial+%F0%9F%91%B6%22).
|
||||||
|
|
||||||
|
* Once a PR is ready for review please add the [`pleasereview`](https://github.com/openethereum/openethereum/pulls?utf8=%E2%9C%93&q=is%3Aopen+is%3Apr+label%3A%22A0-pleasereview+%F0%9F%A4%93%22+) label.
|
||||||
|
|
||||||
|
### Reviewing Pull Requests*:
|
||||||
|
|
||||||
|
* At least two reviewers are required to review PRs (even for PRs tagged [`insubstantial`](https://github.com/openethereum/openethereum/pulls?q=is%3Aopen+is%3Apr+label%3A%22A2-insubstantial+%F0%9F%91%B6%22)).
|
||||||
|
|
||||||
|
When doing a review, make sure to look for any:
|
||||||
|
|
||||||
|
* Buggy behavior.
|
||||||
|
* Undue maintenance burden.
|
||||||
|
* Breaking with house coding style.
|
||||||
|
* Pessimization (i.e. reduction of speed as measured in the projects benchmarks).
|
||||||
|
* Breaking changes should be carefuly reviewed and tagged as such so they end up in the [changelog](../CHANGELOG.md).
|
||||||
|
* Uselessness (i.e. it does not strictly add a feature or fix a known issue).
|
||||||
|
|
||||||
## License.
|
## License.
|
||||||
|
|
||||||
By contributing to Parity, you agree that your contributions will be licensed under the [GPLv3 License](../LICENSE).
|
By contributing to Parity Ethereum, you agree that your contributions will be licensed under the [GPLv3 License](../LICENSE).
|
||||||
|
|
||||||
Each contributor has to sign our Contributor License Agreement. The purpose of the CLA is to ensure that the guardian of a project's outputs has the necessary ownership or grants of rights over all contributions to allow them to distribute under the chosen license. You can read and sign our full Contributor License Agreement at [cla.parity.io](https://cla.parity.io) before submitting a pull request.
|
Each contributor has to sign our Contributor License Agreement. The purpose of the CLA is to ensure that the guardian of a project's outputs has the necessary ownership or grants of rights over all contributions to allow them to distribute under the chosen license. You can read and sign our full Contributor License Agreement at [cla.parity.io](https://cla.parity.io) before submitting a pull request.
|
||||||
|
|||||||
16
.github/ISSUE_TEMPLATE.md
vendored
16
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,13 +1,13 @@
|
|||||||
|
For questions please use https://discord.io/openethereum, issues are for bugs and feature requests.
|
||||||
|
|
||||||
_Before filing a new issue, please **provide the following information**._
|
_Before filing a new issue, please **provide the following information**._
|
||||||
|
|
||||||
> I'm running:
|
- **OpenEthereum version (>=3.1.0)**: 0.0.0
|
||||||
>
|
- **Operating system**: Windows / MacOS / Linux
|
||||||
> - **Which Parity version?**: 0.0.0
|
- **Installation**: homebrew / one-line installer / built from source
|
||||||
> - **Which operating system?**: Windows / MacOS / Linux
|
- **Fully synchronized**: no / yes
|
||||||
> - **How installed?**: via installer / homebrew / binaries / from source
|
- **Network**: ethereum / ropsten / kovan / ...
|
||||||
> - **Are you fully synchronized?**: no / yes
|
- **Restarted**: no / yes
|
||||||
> - **Which network are you connected to?**: ethereum / ropsten / kovan / ...
|
|
||||||
> - **Did you try to restart the node?**: no / yes
|
|
||||||
|
|
||||||
_Your issue description goes here below. Try to include **actual** vs. **expected behavior** and **steps to reproduce** the issue._
|
_Your issue description goes here below. Try to include **actual** vs. **expected behavior** and **steps to reproduce** the issue._
|
||||||
|
|
||||||
|
|||||||
35
.github/workflows/build-test.yml
vendored
Normal file
35
.github/workflows/build-test.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
name: Build and Test Suite
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- dev
|
||||||
|
jobs:
|
||||||
|
build-tests:
|
||||||
|
name: Test and Build
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- ubuntu-16.04
|
||||||
|
- macos-latest
|
||||||
|
toolchain:
|
||||||
|
- stable
|
||||||
|
runs-on: ${{ matrix.platform }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@main
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- name: Install toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: ${{ matrix.toolchain }}
|
||||||
|
profile: minimal
|
||||||
|
override: true
|
||||||
|
- name: Build tests
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: test
|
||||||
|
args: --locked --all --release --features "json-tests" --verbose --no-run
|
||||||
285
.github/workflows/build.yml
vendored
Normal file
285
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
name: Build Release Suite
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v*
|
||||||
|
|
||||||
|
# Global vars
|
||||||
|
env:
|
||||||
|
AWS_REGION: "us-east-1"
|
||||||
|
AWS_S3_ARTIFACTS_BUCKET: "openethereum-releases"
|
||||||
|
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build Release
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- ubuntu-16.04
|
||||||
|
- macos-latest
|
||||||
|
toolchain:
|
||||||
|
- stable
|
||||||
|
runs-on: ${{ matrix.platform }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@main
|
||||||
|
- name: Install toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: ${{ matrix.toolchain }}
|
||||||
|
profile: minimal
|
||||||
|
override: true
|
||||||
|
|
||||||
|
# ==============================
|
||||||
|
# Windows Build
|
||||||
|
# ==============================
|
||||||
|
|
||||||
|
# - name: Install LLVM for Windows
|
||||||
|
# if: matrix.platform == 'windows2019'
|
||||||
|
# run: choco install llvm
|
||||||
|
|
||||||
|
# - name: Build OpenEthereum for Windows
|
||||||
|
# if: matrix.platform == 'windows2019'
|
||||||
|
# run: sh scripts/actions/build-windows.sh ${{matrix.platform}}
|
||||||
|
|
||||||
|
# - name: Upload Windows build
|
||||||
|
# uses: actions/upload-artifact@v2
|
||||||
|
# if: matrix.platform == 'windows2019'
|
||||||
|
# with:
|
||||||
|
# name: windows-artifacts
|
||||||
|
# path: artifacts
|
||||||
|
|
||||||
|
# ==============================
|
||||||
|
# Linux/Macos Build
|
||||||
|
# ==============================
|
||||||
|
|
||||||
|
- name: Build OpenEthereum for ${{matrix.platform}}
|
||||||
|
if: matrix.platform != 'windows2019'
|
||||||
|
run: sh scripts/actions/build-linux.sh ${{matrix.platform}}
|
||||||
|
|
||||||
|
- name: Upload Linux build
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
if: matrix.platform == 'ubuntu-16.04'
|
||||||
|
with:
|
||||||
|
name: linux-artifacts
|
||||||
|
path: artifacts
|
||||||
|
|
||||||
|
- name: Upload MacOS build
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
if: matrix.platform == 'macos-latest'
|
||||||
|
with:
|
||||||
|
name: macos-artifacts
|
||||||
|
path: artifacts
|
||||||
|
|
||||||
|
zip-artifacts-creator:
|
||||||
|
name: Create zip artifacts
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-16.04
|
||||||
|
steps:
|
||||||
|
- name: Set env
|
||||||
|
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# ==============================
|
||||||
|
# Create ZIP files
|
||||||
|
# ==============================
|
||||||
|
|
||||||
|
# - name: Download Windows artifacts
|
||||||
|
# uses: actions/download-artifact@v2
|
||||||
|
# with:
|
||||||
|
# name: windows-artifacts
|
||||||
|
# path: windows-artifacts
|
||||||
|
|
||||||
|
- name: Download Linux artifacts
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: linux-artifacts
|
||||||
|
path: linux-artifacts
|
||||||
|
|
||||||
|
- name: Download MacOS artifacts
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: macos-artifacts
|
||||||
|
path: macos-artifacts
|
||||||
|
|
||||||
|
- name: Display structure of downloaded files
|
||||||
|
run: ls
|
||||||
|
|
||||||
|
- name: Create zip Linux
|
||||||
|
id: create_zip_linux
|
||||||
|
run: |
|
||||||
|
cd linux-artifacts/
|
||||||
|
zip -rT openethereum-linux-${{ env.RELEASE_VERSION }}.zip *
|
||||||
|
ls openethereum-linux-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
cd ..
|
||||||
|
mv linux-artifacts/openethereum-linux-${{ env.RELEASE_VERSION }}.zip .
|
||||||
|
|
||||||
|
echo "Setting outputs..."
|
||||||
|
echo ::set-output name=LINUX_ARTIFACT::openethereum-linux-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
echo ::set-output name=LINUX_SHASUM::$(shasum -a 256 openethereum-linux-${{ env.RELEASE_VERSION }}.zip | awk '{print $1}')
|
||||||
|
|
||||||
|
- name: Create zip MacOS
|
||||||
|
id: create_zip_macos
|
||||||
|
run: |
|
||||||
|
cd macos-artifacts/
|
||||||
|
zip -rT openethereum-macos-${{ env.RELEASE_VERSION }}.zip *
|
||||||
|
ls openethereum-macos-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
cd ..
|
||||||
|
mv macos-artifacts/openethereum-macos-${{ env.RELEASE_VERSION }}.zip .
|
||||||
|
|
||||||
|
echo "Setting outputs..."
|
||||||
|
echo ::set-output name=MACOS_ARTIFACT::openethereum-macos-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
echo ::set-output name=MACOS_SHASUM::$(shasum -a 256 openethereum-macos-${{ env.RELEASE_VERSION }}.zip | awk '{print $1}')
|
||||||
|
|
||||||
|
# - name: Create zip Windows
|
||||||
|
# id: create_zip_windows
|
||||||
|
# run: |
|
||||||
|
# cd windows-artifacts/
|
||||||
|
# zip -rT openethereum-windows-${{ env.RELEASE_VERSION }}.zip *
|
||||||
|
# ls openethereum-windows-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
# cd ..
|
||||||
|
# mv windows-artifacts/openethereum-windows-${{ env.RELEASE_VERSION }}.zip .
|
||||||
|
|
||||||
|
# echo "Setting outputs..."
|
||||||
|
# echo ::set-output name=WINDOWS_ARTIFACT::openethereum-windows-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
# echo ::set-output name=WINDOWS_SHASUM::$(shasum -a 256 openethereum-windows-${{ env.RELEASE_VERSION }}.zip | awk '{print $1}')
|
||||||
|
|
||||||
|
# =======================================================================
|
||||||
|
# Upload artifacts
|
||||||
|
# This is required to share artifacts between different jobs
|
||||||
|
# =======================================================================
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: openethereum-linux-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
path: openethereum-linux-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: openethereum-macos-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
path: openethereum-macos-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
|
||||||
|
# - name: Upload artifacts
|
||||||
|
# uses: actions/upload-artifact@v2
|
||||||
|
# with:
|
||||||
|
# name: openethereum-windows-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
# path: openethereum-windows-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
|
||||||
|
# =======================================================================
|
||||||
|
# Upload artifacts to S3
|
||||||
|
# This is required by some software distribution systems which require
|
||||||
|
# artifacts to be downloadable, like Brew on MacOS.
|
||||||
|
# =======================================================================
|
||||||
|
- name: Configure AWS credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@v1
|
||||||
|
with:
|
||||||
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
|
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
aws-region: ${{ env.AWS_REGION }}
|
||||||
|
|
||||||
|
- name: Copy files to S3 with the AWS CLI
|
||||||
|
run: |
|
||||||
|
# Deploy zip artifacts to S3 bucket to a directory whose name is the tagged release version.
|
||||||
|
# Deploy macos binary artifact (if required, add more `aws s3 cp` commands to deploy specific OS versions)
|
||||||
|
aws s3 cp macos-artifacts/openethereum s3://${{ env.AWS_S3_ARTIFACTS_BUCKET }}/${{ env.RELEASE_VERSION }}/macos/ --region ${{ env.AWS_REGION }}
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
linux-artifact: ${{ steps.create_zip_linux.outputs.LINUX_ARTIFACT }}
|
||||||
|
linux-shasum: ${{ steps.create_zip_linux.outputs.LINUX_SHASUM }}
|
||||||
|
macos-artifact: ${{ steps.create_zip_macos.outputs.MACOS_ARTIFACT }}
|
||||||
|
macos-shasum: ${{ steps.create_zip_macos.outputs.MACOS_SHASUM }}
|
||||||
|
# windows-artifact: ${{ steps.create_zip_windows.outputs.WINDOWS_ARTIFACT }}
|
||||||
|
# windows-shasum: ${{ steps.create_zip_windows.outputs.WINDOWS_SHASUM }}
|
||||||
|
|
||||||
|
draft-release:
|
||||||
|
name: Draft Release
|
||||||
|
needs: zip-artifacts-creator
|
||||||
|
runs-on: ubuntu-16.04
|
||||||
|
steps:
|
||||||
|
- name: Set env
|
||||||
|
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# ==============================
|
||||||
|
# Download artifacts
|
||||||
|
# ==============================
|
||||||
|
|
||||||
|
- name: Download artifacts
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: openethereum-linux-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
|
||||||
|
- name: Download artifacts
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: openethereum-macos-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
|
||||||
|
# - name: Download artifacts
|
||||||
|
# uses: actions/download-artifact@v2
|
||||||
|
# with:
|
||||||
|
# name: openethereum-windows-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
|
||||||
|
- name: Display structure of downloaded files
|
||||||
|
run: ls
|
||||||
|
|
||||||
|
# ==============================
|
||||||
|
# Create release draft
|
||||||
|
# ==============================
|
||||||
|
|
||||||
|
- name: Create Release Draft
|
||||||
|
id: create_release_draft
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref }}
|
||||||
|
release_name: OpenEthereum ${{ github.ref }}
|
||||||
|
body: |
|
||||||
|
This release contains <ADD_TEXT>
|
||||||
|
|
||||||
|
| System | Architecture | Binary | Sha256 Checksum |
|
||||||
|
|:---:|:---:|:---:|:---|
|
||||||
|
| <img src="https://gist.github.com/5chdn/1fce888fde1d773761f809b607757f76/raw/44c4f0fc63f1ea8e61a9513af5131ef65eaa6c75/apple.png" alt="Apple Icon by Pixel Perfect from https://www.flaticon.com/authors/pixel-perfect" style="width: 32px;"/> | x64 | [${{ needs.zip-artifacts-creator.outputs.macos-artifact }}](https://github.com/openethereum/openethereum/releases/download/${{ env.RELEASE_VERSION }}/${{ needs.zip-artifacts-creator.outputs.macos-artifact }}) | `${{ needs.zip-artifacts-creator.outputs.macos-shasum }}` |
|
||||||
|
| <img src="https://gist.github.com/5chdn/1fce888fde1d773761f809b607757f76/raw/44c4f0fc63f1ea8e61a9513af5131ef65eaa6c75/linux.png" alt="Linux Icon by Pixel Perfect from https://www.flaticon.com/authors/pixel-perfect" style="width: 32px;"/> | x64 | [${{ needs.zip-artifacts-creator.outputs.linux-artifact }}](https://github.com/openethereum/openethereum/releases/download/${{ env.RELEASE_VERSION }}/${{ needs.zip-artifacts-creator.outputs.linux-artifact }}) | `${{ needs.zip-artifacts-creator.outputs.linux-shasum }}` |
|
||||||
|
| <img src="https://gist.github.com/5chdn/1fce888fde1d773761f809b607757f76/raw/44c4f0fc63f1ea8e61a9513af5131ef65eaa6c75/windows.png" alt="Windows Icon by Pixel Perfect from https://www.flaticon.com/authors/pixel-perfect" style="width: 32px;"/> | x64 | [${{ needs.zip-artifacts-creator.outputs.windows-artifact }}](https://github.com/openethereum/openethereum/releases/download/${{ env.RELEASE_VERSION }}/${{ needs.zip-artifacts-creator.outputs.windows-artifact }}) | `${{ needs.zip-artifacts-creator.outputs.windows-shasum }}` |
|
||||||
|
| | | | |
|
||||||
|
| **System** | **Option** | - | **Resource** |
|
||||||
|
| <img src="https://gist.github.com/5chdn/1fce888fde1d773761f809b607757f76/raw/44c4f0fc63f1ea8e61a9513af5131ef65eaa6c75/settings.png" alt="Settings Icon by Pixel Perfect from https://www.flaticon.com/authors/pixel-perfect" style="width: 32px;"/> | Docker | - | [hub.docker.com/r/openethereum/openethereum](https://hub.docker.com/r/openethereum/openethereum) |
|
||||||
|
|
||||||
|
draft: true
|
||||||
|
prerelease: true
|
||||||
|
|
||||||
|
- name: Upload Release Asset - Linux
|
||||||
|
id: upload_release_asset_linux
|
||||||
|
uses: actions/upload-release-asset@v1.0.1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release_draft.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||||
|
asset_path: ./openethereum-linux-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
asset_name: openethereum-linux-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
asset_content_type: application/zip
|
||||||
|
|
||||||
|
- name: Upload Release Asset - MacOS
|
||||||
|
id: upload_release_asset_macos
|
||||||
|
uses: actions/upload-release-asset@v1.0.1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release_draft.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||||
|
asset_path: ./openethereum-macos-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
asset_name: openethereum-macos-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
asset_content_type: application/zip
|
||||||
|
|
||||||
|
# - name: Upload Release Asset - Windows
|
||||||
|
# id: upload_release_asset_windows
|
||||||
|
# uses: actions/upload-release-asset@v1
|
||||||
|
# env:
|
||||||
|
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
# with:
|
||||||
|
# upload_url: ${{ steps.create_release_draft.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||||
|
# asset_path: ./openethereum-windows-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
# asset_name: openethereum-windows-${{ env.RELEASE_VERSION }}.zip
|
||||||
|
# asset_content_type: application/zip
|
||||||
50
.github/workflows/check.yml
vendored
Normal file
50
.github/workflows/check.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
name: Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- dev
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: Check
|
||||||
|
runs-on: ubuntu-16.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@main
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- name: Install stable toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
profile: minimal
|
||||||
|
override: true
|
||||||
|
- name: Run cargo check 1/3
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: check
|
||||||
|
args: --locked --no-default-features --verbose
|
||||||
|
- name: Run cargo check 2/3
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: check
|
||||||
|
args: --locked --manifest-path util/io/Cargo.toml --no-default-features --verbose
|
||||||
|
- name: Run cargo check 3/3
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: check
|
||||||
|
args: --locked --manifest-path util/io/Cargo.toml --features "mio" --verbose
|
||||||
|
- name: Run cargo check evmbin
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: check
|
||||||
|
args: --locked -p evmbin --verbose
|
||||||
|
- name: Run cargo check benches
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: check
|
||||||
|
args: --locked --all --benches --verbose
|
||||||
|
- name: Run validate chainspecs
|
||||||
|
run: ./scripts/actions/validate-chainspecs.sh
|
||||||
29
.github/workflows/deploy-docker-nightly.yml
vendored
Normal file
29
.github/workflows/deploy-docker-nightly.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
name: Docker Image Nightly Release
|
||||||
|
|
||||||
|
# Run "nightly" build on each commit to "dev" branch.
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-docker:
|
||||||
|
name: Build Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@master
|
||||||
|
- name: Install toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
profile: minimal
|
||||||
|
override: true
|
||||||
|
- name: Deploy to docker hub
|
||||||
|
uses: elgohr/Publish-Docker-Github-Action@master
|
||||||
|
with:
|
||||||
|
name: openethereum/openethereum
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
dockerfile: scripts/docker/alpine/Dockerfile
|
||||||
|
tags: "nightly"
|
||||||
30
.github/workflows/deploy-docker-tag.yml
vendored
Normal file
30
.github/workflows/deploy-docker-tag.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: Docker Image Tag and Latest Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-docker:
|
||||||
|
name: Build Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@master
|
||||||
|
- name: Set env
|
||||||
|
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
|
- name: Install toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
profile: minimal
|
||||||
|
override: true
|
||||||
|
- name: Deploy to docker hub
|
||||||
|
uses: elgohr/Publish-Docker-Github-Action@master
|
||||||
|
with:
|
||||||
|
name: openethereum/openethereum
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
dockerfile: scripts/docker/alpine/Dockerfile
|
||||||
|
tags: "latest,${{ env.RELEASE_VERSION }}"
|
||||||
30
.github/workflows/deploy-docker.yml
vendored
Normal file
30
.github/workflows/deploy-docker.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: Docker Image Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
tags:
|
||||||
|
- v*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-docker:
|
||||||
|
name: Build Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@master
|
||||||
|
- name: Install toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
profile: minimal
|
||||||
|
override: true
|
||||||
|
- name: Deploy to docker hub
|
||||||
|
uses: elgohr/Publish-Docker-Github-Action@master
|
||||||
|
with:
|
||||||
|
name: openethereum/openethereum
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
dockerfile: scripts/docker/alpine/Dockerfile
|
||||||
|
tag_names: true
|
||||||
20
.github/workflows/fmt.yml
vendored
Normal file
20
.github/workflows/fmt.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
name: rustfmt
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
fmt:
|
||||||
|
name: Rustfmt
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
- run: rustup component add rustfmt
|
||||||
|
- uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: fmt
|
||||||
|
args: --all -- --check --config merge_imports=true
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -40,5 +40,8 @@ node_modules
|
|||||||
out/
|
out/
|
||||||
|
|
||||||
.vscode
|
.vscode
|
||||||
|
rls/
|
||||||
/parity.*
|
/parity.*
|
||||||
|
|
||||||
|
# cargo remote artifacts
|
||||||
|
remote-target
|
||||||
|
|||||||
245
.gitlab-ci.yml
245
.gitlab-ci.yml
@@ -1,245 +0,0 @@
|
|||||||
stages:
|
|
||||||
- test
|
|
||||||
- push-release
|
|
||||||
- build
|
|
||||||
variables:
|
|
||||||
RUST_BACKTRACE: "1"
|
|
||||||
RUSTFLAGS: ""
|
|
||||||
CARGOFLAGS: ""
|
|
||||||
CI_SERVER_NAME: "GitLab CI"
|
|
||||||
LIBSSL: "libssl1.0.0 (>=1.0.0)"
|
|
||||||
CARGO_HOME: $CI_PROJECT_DIR/cargo
|
|
||||||
cache:
|
|
||||||
key: "$CI_BUILD_STAGE-$CI_BUILD_REF_NAME"
|
|
||||||
paths:
|
|
||||||
- target/
|
|
||||||
- cargo/
|
|
||||||
untracked: true
|
|
||||||
linux-stable:
|
|
||||||
stage: build
|
|
||||||
image: parity/rust:gitlab-ci
|
|
||||||
only:
|
|
||||||
- beta
|
|
||||||
- tags
|
|
||||||
- stable
|
|
||||||
- triggers
|
|
||||||
script:
|
|
||||||
- rustup default stable
|
|
||||||
# ARGUMENTS: 1. BUILD_PLATFORM (target for binaries) 2. PLATFORM (target for cargo) 3. ARC (architecture) 4. & 5. CC & CXX flags 6. binary identifier
|
|
||||||
- scripts/gitlab-build.sh x86_64-unknown-linux-gnu x86_64-unknown-linux-gnu amd64 gcc g++ ubuntu
|
|
||||||
tags:
|
|
||||||
- rust-stable
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- parity.zip
|
|
||||||
name: "stable-x86_64-unknown-linux-gnu_parity"
|
|
||||||
linux-stable-debian:
|
|
||||||
stage: build
|
|
||||||
image: parity/rust-debian:gitlab-ci
|
|
||||||
only:
|
|
||||||
- beta
|
|
||||||
- tags
|
|
||||||
- stable
|
|
||||||
- triggers
|
|
||||||
script:
|
|
||||||
- export LIBSSL="libssl1.1 (>=1.1.0)"
|
|
||||||
- scripts/gitlab-build.sh x86_64-unknown-debian-gnu x86_64-unknown-linux-gnu amd64 gcc g++ debian
|
|
||||||
tags:
|
|
||||||
- rust-debian
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- parity.zip
|
|
||||||
name: "stable-x86_64-unknown-debian-gnu_parity"
|
|
||||||
linux-centos:
|
|
||||||
stage: build
|
|
||||||
image: parity/rust-centos:gitlab-ci
|
|
||||||
only:
|
|
||||||
- beta
|
|
||||||
- tags
|
|
||||||
- stable
|
|
||||||
- triggers
|
|
||||||
script:
|
|
||||||
- scripts/gitlab-build.sh x86_64-unknown-centos-gnu x86_64-unknown-linux-gnu x86_64 gcc g++ centos
|
|
||||||
tags:
|
|
||||||
- rust-centos
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- parity.zip
|
|
||||||
name: "x86_64-unknown-centos-gnu_parity"
|
|
||||||
linux-i686:
|
|
||||||
stage: build
|
|
||||||
image: parity/rust-i686:gitlab-ci
|
|
||||||
only:
|
|
||||||
- beta
|
|
||||||
- tags
|
|
||||||
- stable
|
|
||||||
- triggers
|
|
||||||
script:
|
|
||||||
- scripts/gitlab-build.sh i686-unknown-linux-gnu i686-unknown-linux-gnu i386 gcc g++ ubuntu
|
|
||||||
tags:
|
|
||||||
- rust-i686
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- parity.zip
|
|
||||||
name: "i686-unknown-linux-gnu"
|
|
||||||
linux-armv7:
|
|
||||||
stage: build
|
|
||||||
image: parity/rust-armv7:gitlab-ci
|
|
||||||
only:
|
|
||||||
- beta
|
|
||||||
- tags
|
|
||||||
- stable
|
|
||||||
- triggers
|
|
||||||
script:
|
|
||||||
- scripts/gitlab-build.sh armv7-unknown-linux-gnueabihf armv7-unknown-linux-gnueabihf armhf arm-linux-gnueabihf-gcc arm-linux-gnueabihf-g++ ubuntu
|
|
||||||
tags:
|
|
||||||
- rust-arm
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- parity.zip
|
|
||||||
name: "armv7_unknown_linux_gnueabihf_parity"
|
|
||||||
linux-arm:
|
|
||||||
stage: build
|
|
||||||
image: parity/rust-arm:gitlab-ci
|
|
||||||
only:
|
|
||||||
- beta
|
|
||||||
- tags
|
|
||||||
- stable
|
|
||||||
- triggers
|
|
||||||
script:
|
|
||||||
- scripts/gitlab-build.sh arm-unknown-linux-gnueabihf arm-unknown-linux-gnueabihf armhf arm-linux-gnueabihf-gcc arm-linux-gnueabihf-g++ ubuntu
|
|
||||||
tags:
|
|
||||||
- rust-arm
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- parity.zip
|
|
||||||
name: "arm-unknown-linux-gnueabihf_parity"
|
|
||||||
linux-aarch64:
|
|
||||||
stage: build
|
|
||||||
image: parity/rust-arm64:gitlab-ci
|
|
||||||
only:
|
|
||||||
- beta
|
|
||||||
- tags
|
|
||||||
- stable
|
|
||||||
- triggers
|
|
||||||
script:
|
|
||||||
- scripts/gitlab-build.sh aarch64-unknown-linux-gnu aarch64-unknown-linux-gnu arm64 aarch64-linux-gnu-gcc aarch64-linux-gnu-g++ ubuntu
|
|
||||||
tags:
|
|
||||||
- rust-arm
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- parity.zip
|
|
||||||
name: "aarch64-unknown-linux-gnu_parity"
|
|
||||||
linux-snap:
|
|
||||||
stage: build
|
|
||||||
image: snapcore/snapcraft:stable
|
|
||||||
only:
|
|
||||||
- stable
|
|
||||||
- beta
|
|
||||||
- tags
|
|
||||||
- triggers
|
|
||||||
script:
|
|
||||||
- scripts/gitlab-build.sh x86_64-unknown-snap-gnu x86_64-unknown-linux-gnu amd64 gcc g++ snap
|
|
||||||
tags:
|
|
||||||
- rust-stable
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- parity.zip
|
|
||||||
name: "stable-x86_64-unknown-snap-gnu_parity"
|
|
||||||
darwin:
|
|
||||||
stage: build
|
|
||||||
only:
|
|
||||||
- beta
|
|
||||||
- tags
|
|
||||||
- stable
|
|
||||||
- triggers
|
|
||||||
script:
|
|
||||||
- scripts/gitlab-build.sh x86_64-apple-darwin x86_64-apple-darwin macos gcc g++ macos
|
|
||||||
tags:
|
|
||||||
- osx
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- parity.zip
|
|
||||||
name: "x86_64-apple-darwin_parity"
|
|
||||||
windows:
|
|
||||||
cache:
|
|
||||||
key: "%CI_BUILD_STAGE%-%CI_BUILD_REF_NAME%"
|
|
||||||
untracked: true
|
|
||||||
stage: build
|
|
||||||
only:
|
|
||||||
- beta
|
|
||||||
- tags
|
|
||||||
- stable
|
|
||||||
- triggers
|
|
||||||
script:
|
|
||||||
- sh scripts/gitlab-build.sh x86_64-pc-windows-msvc x86_64-pc-windows-msvc installer "" "" windows
|
|
||||||
tags:
|
|
||||||
- rust-windows
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- parity.zip
|
|
||||||
name: "x86_64-pc-windows-msvc_parity"
|
|
||||||
docker-build:
|
|
||||||
stage: build
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
- triggers
|
|
||||||
before_script:
|
|
||||||
- docker info
|
|
||||||
script:
|
|
||||||
- if [ "$CI_BUILD_REF_NAME" == "beta-release" ]; then DOCKER_TAG="latest"; else DOCKER_TAG=$CI_BUILD_REF_NAME; fi
|
|
||||||
- echo "Tag:" $DOCKER_TAG
|
|
||||||
- docker login -u $Docker_Hub_User_Parity -p $Docker_Hub_Pass_Parity
|
|
||||||
- scripts/docker-build.sh $DOCKER_TAG
|
|
||||||
- docker logout
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
test-coverage:
|
|
||||||
stage: test
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
script:
|
|
||||||
- scripts/gitlab-test.sh test-coverage
|
|
||||||
tags:
|
|
||||||
- kcov
|
|
||||||
allow_failure: true
|
|
||||||
test-rust-stable:
|
|
||||||
stage: test
|
|
||||||
image: parity/rust:gitlab-ci
|
|
||||||
script:
|
|
||||||
- scripts/gitlab-test.sh stable
|
|
||||||
tags:
|
|
||||||
- rust-stable
|
|
||||||
test-rust-beta:
|
|
||||||
stage: test
|
|
||||||
only:
|
|
||||||
- triggers
|
|
||||||
- master
|
|
||||||
image: parity/rust:gitlab-ci
|
|
||||||
script:
|
|
||||||
- scripts/gitlab-test.sh beta
|
|
||||||
tags:
|
|
||||||
- rust-beta
|
|
||||||
allow_failure: true
|
|
||||||
test-rust-nightly:
|
|
||||||
stage: test
|
|
||||||
only:
|
|
||||||
- triggers
|
|
||||||
- master
|
|
||||||
image: parity/rust:gitlab-ci
|
|
||||||
script:
|
|
||||||
- scripts/gitlab-test.sh nightly
|
|
||||||
tags:
|
|
||||||
- rust
|
|
||||||
- rust-nightly
|
|
||||||
allow_failure: true
|
|
||||||
push-release:
|
|
||||||
stage: push-release
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
- triggers
|
|
||||||
image: parity/rust:gitlab-ci
|
|
||||||
script:
|
|
||||||
- scripts/gitlab-push-release.sh
|
|
||||||
tags:
|
|
||||||
- curl
|
|
||||||
430
CHANGELOG.md
430
CHANGELOG.md
@@ -1,392 +1,56 @@
|
|||||||
## Parity [v1.9.3](https://github.com/paritytech/parity/releases/tag/v1.9.3) (2018-02-20)
|
## OpenEthereum v3.1.1
|
||||||
|
|
||||||
Parity 1.9.3 is a bug-fix release to improve performance and stability.
|
Bug fixes:
|
||||||
|
* Ancient target set. InvalidStateRoot bug (#69) (#149)
|
||||||
|
* Update linked-hash-map to 0.5.3
|
||||||
|
|
||||||
The full list of included changes:
|
Enhancements:
|
||||||
|
* Added additional Sg-1,Ca-2,Ca-3 OE bootnodes
|
||||||
|
* Add --ws-max-paxload (#155)
|
||||||
|
* Add flag to disable storage output in openethereum-evm tool #97 (#115)
|
||||||
|
* ethstore - remove unnecessary dir & tiny-keccak dependencies from the lib (#107)
|
||||||
|
* Sync block verification (#74)
|
||||||
|
* Add `wasmDisableTransition` spec option (#60)
|
||||||
|
* EIP2929 with journaling + Yolov3 (#79)
|
||||||
|
* EIP2565 impl (#82)
|
||||||
|
* TypedTransaction (EIP-2718) and Optional access list (EIP-2930) (#135)
|
||||||
|
|
||||||
- Backports ([#7945](https://github.com/paritytech/parity/pull/7945))
|
DevOps:
|
||||||
- ECIP 1041 - Remove Difficulty Bomb ([#7905](https://github.com/paritytech/parity/pull/7905))
|
* Add custom windows runner (#162)
|
||||||
- spec: Validate required divisor fields are not 0 ([#7933](https://github.com/paritytech/parity/pull/7933))
|
* Remove sscache (#138)
|
||||||
- Kovan WASM fork code ([#7849](https://github.com/paritytech/parity/pull/7849))
|
* Fix deprecated set-env declaration (#106)
|
||||||
- Gitlab Cargo Cache ([#7944](https://github.com/paritytech/parity/pull/7944))
|
|
||||||
- Bump react-qr-reader ([#7943](https://github.com/paritytech/parity/pull/7943))
|
|
||||||
- Update react-qr-reader
|
|
||||||
- Explicit webrtc-adapter dependency (package-lock workaround)
|
|
||||||
- Iframe with allow (QR, new Chrome policy)
|
|
||||||
- Backport of [#7844](https://github.com/paritytech/parity/pull/7844) and [#7917](https://github.com/paritytech/parity/pull/7917) to beta ([#7940](https://github.com/paritytech/parity/pull/7940))
|
|
||||||
- Randomize the peer we dispatch to
|
|
||||||
- Fix a division by zero in light client RPC handler
|
|
||||||
- Wallet allowJsEval: true ([#7913](https://github.com/paritytech/parity/pull/7913))
|
|
||||||
- Wallet allowJsEval: true
|
|
||||||
- Fix unsafe wallet.
|
|
||||||
- Enable unsafe-eval for all dapps.
|
|
||||||
- Fix CSP for dapps that require eval. ([#7867](https://github.com/paritytech/parity/pull/7867)) ([#7903](https://github.com/paritytech/parity/pull/7903))
|
|
||||||
- Add allowJsEval to manifest.
|
|
||||||
- Enable 'unsafe-eval' if requested in manifest.
|
|
||||||
- Fix snap build beta ([#7895](https://github.com/paritytech/parity/pull/7895))
|
|
||||||
- Fix snapcraft grade to stable ([#7894](https://github.com/paritytech/parity/pull/7894))
|
|
||||||
- Backport Master CI PRs to Beta ([#7890](https://github.com/paritytech/parity/pull/7890))
|
|
||||||
- Add binary identifiers and sha256sum to builds ([#7830](https://github.com/paritytech/parity/pull/7830))
|
|
||||||
- Fix checksums and auto-update push ([#7846](https://github.com/paritytech/parity/pull/7846))
|
|
||||||
- Update gitlab-build.sh ([#7855](https://github.com/paritytech/parity/pull/7855))
|
|
||||||
- Fix installer binary names for macos and windows ([#7881](https://github.com/paritytech/parity/pull/7881))
|
|
||||||
- Update gitlab-test.sh ([#7883](https://github.com/paritytech/parity/pull/7883))
|
|
||||||
- Fix snapcraft nightly ([#7884](https://github.com/paritytech/parity/pull/7884))
|
|
||||||
- Backport Core PRs to beta ([#7891](https://github.com/paritytech/parity/pull/7891))
|
|
||||||
- Update back-references more aggressively after answering from cache ([#7578](https://github.com/paritytech/parity/pull/7578))
|
|
||||||
- Updated WASM Runtime & new interpreter (wasmi) ([#7796](https://github.com/paritytech/parity/pull/7796))
|
|
||||||
- Adjust storage update evm-style ([#7812](https://github.com/paritytech/parity/pull/7812))
|
|
||||||
- Add new EF ropstens nodes ([#7824](https://github.com/paritytech/parity/pull/7824))
|
|
||||||
- Store updater metadata in a single place ([#7832](https://github.com/paritytech/parity/pull/7832))
|
|
||||||
- WASM: Disable internal memory ([#7842](https://github.com/paritytech/parity/pull/7842))
|
|
||||||
- Add a timeout for light client sync requests ([#7848](https://github.com/paritytech/parity/pull/7848))
|
|
||||||
- Flush keyfiles. Resolves [#7632](https://github.com/paritytech/parity/issues/7632) ([#7868](https://github.com/paritytech/parity/pull/7868))
|
|
||||||
- Fix wallet import ([#7873](https://github.com/paritytech/parity/pull/7873))
|
|
||||||
|
|
||||||
## Parity [v1.9.2](https://github.com/paritytech/parity/releases/tag/v1.9.2) (2018-02-02)
|
|
||||||
|
|
||||||
Parity 1.9.2 is a bug-fix release to improve performance and stability. It adds additional bootnodes for the Ropsten test network.
|
## OpenEthereum v3.1.0
|
||||||
|
|
||||||
The full list of included changes:
|
OpenEthereum 3.1.0 is a release based on v2.5.13 which is the last stable version known of the client that does not include any of the issues introduced in v2.7. It removes non core features like Ethereum Classic, Private Transactions, Light Client, Updater, IPFS and Swarm support, currently deprecated flags such as expanse, kotti, mordor testnets.
|
||||||
|
|
||||||
- Backports beta ([#7780](https://github.com/paritytech/parity/pull/7780))
|
Database migration utility currently in beta: https://github.com/openethereum/3.1-db-upgrade-tool
|
||||||
- Bump beta to 1.9.2
|
|
||||||
- Update ropsten.json ([#7776](https://github.com/paritytech/parity/pull/7776))
|
|
||||||
- Snapcraft push beta
|
|
||||||
|
|
||||||
## Parity [v1.9.1](https://github.com/paritytech/parity/releases/tag/v1.9.1) (2018-02-01)
|
The full list of included changes from v2.5.13 to v3.1.0:
|
||||||
|
|
||||||
Parity 1.9.1 is a bug-fix release to improve performance and stability. It restores ERC-20 token balances, improves networking, fixes database corruptions on client shutdown, and fixes issues with the `--password` command-line flag. Happy syncing, fellow Ethereans!
|
* Use ubuntu-16.04 for glibc compatibility (#11888) (#73)
|
||||||
|
* Remove classic, kotti, mordor, expanse (#52)
|
||||||
In addition, this stabilizes Kovan and other Proof-of-Authority networks. If you run a network with AuRa engine, updating is highly encouraged!
|
* Added bad block header hash for ropsten (#49)
|
||||||
|
* Remove accounts bloom (#33)
|
||||||
The full list of included changes:
|
* Bump jsonrpc-- to v15
|
||||||
|
* Implement eth/64, remove eth/62 (#46)
|
||||||
- Beta Backports ([#7756](https://github.com/paritytech/parity/pull/7756))
|
* No snapshotting by default (#11814)
|
||||||
- Filter-out nodes.json ([#7716](https://github.com/paritytech/parity/pull/7716))
|
* Update Ellaism chainspec
|
||||||
- Filter-out nodes.json
|
* Prometheus, heavy memory calls removed (#27)
|
||||||
- network: sort node table nodes by failure ratio
|
* Update ethereum/tests
|
||||||
- network: fix node table tests
|
* Implement JSON test suite (#11801)
|
||||||
- network: fit node failure percentage into buckets of 5%
|
* Fix issues during block sync (#11265)
|
||||||
- network: consider number of attempts in sorting of node table
|
* Fix race same block (#11400)
|
||||||
- network: fix node table grumbles
|
* EIP-2537: Precompile for BLS12-381 curve operations (#11707)
|
||||||
- Fix client not being dropped on shutdown ([#7695](https://github.com/paritytech/parity/pull/7695))
|
* Remove private transactions
|
||||||
- parity: wait for client to drop on shutdown
|
* Remove GetNodeData
|
||||||
- parity: fix grumbles in shutdown wait
|
* Remove IPFS integration (#11532)
|
||||||
- parity: increase shutdown timeouts
|
* Remove updater
|
||||||
- Wrap --help output to 120 characters ([#7626](https://github.com/paritytech/parity/pull/7626))
|
* Remove light client
|
||||||
- Update Clap dependency and remove workarounds
|
* Remove C and Java bindings (#11346)
|
||||||
- WIP
|
* Remove whisper (#10855)
|
||||||
- Remove line breaks in help messages for now
|
* EIP-2315: Simple Subroutines for the EVM (#11629)
|
||||||
- Multiple values can only be separated by commas (closes [#7428](https://github.com/paritytech/parity/issues/7428))
|
* Remove deprecated flags (removal of --geth flag)
|
||||||
- Grumbles; refactor repeating code; add constant
|
* Remove support for hardware wallets (#10678)
|
||||||
- Use a single Wrapper rather than allocate a new one for each call
|
* Update bootnodes
|
||||||
- Wrap --help to 120 characters rather than 100 characte
|
|
||||||
- Token filter balances (throttle) ([#7742](https://github.com/paritytech/parity/pull/7742))
|
|
||||||
- Token filter balances (throttle)
|
|
||||||
- Cleanups
|
|
||||||
- Remove unused uniq
|
|
||||||
- Update @parity/shared to 2.2.23
|
|
||||||
- Remove unused code paths
|
|
||||||
- Bump beta to 1.9.1 ([#7751](https://github.com/paritytech/parity/pull/7751))
|
|
||||||
- Explicitly add branch name ([#7754](https://github.com/paritytech/parity/pull/7754))
|
|
||||||
- Explicitly add branch name
|
|
||||||
- Fix cargo update branch to beta
|
|
||||||
- Revert revert revert ([#7715](https://github.com/paritytech/parity/pull/7715))
|
|
||||||
- This reverts commit 568dc33.
|
|
||||||
|
|
||||||
## Parity [v1.9.0](https://github.com/paritytech/parity/releases/tag/v1.9.0) "Velocity" (2018-01-25)
|
|
||||||
|
|
||||||
We are happy to announce our newest Parity 1.9 release. Among others, it enables the following features:
|
|
||||||
|
|
||||||
- It integrates the fully reworked Parity Wallet and DApps browser (a.k.a. "UI 2.0", [#6819](https://github.com/paritytech/parity/pull/6819)).
|
|
||||||
- It enables devp2p snappy compression ([#6683](https://github.com/paritytech/parity/pull/6683)).
|
|
||||||
- AuRa Proof-of-Authority chains now disable uncles by default ([#7006](https://github.com/paritytech/parity/pull/7006)). Existing PoA chains can go through a "maximum uncle count transition" to achieve more stability ([#7196](https://github.com/paritytech/parity/pull/7196)).
|
|
||||||
- Added Expanse's Byzantium hard-fork ([#7463](https://github.com/paritytech/parity/pull/7463)).
|
|
||||||
- Added support for Ellaism chain ([#7222](https://github.com/paritytech/parity/pull/7222)).
|
|
||||||
|
|
||||||
Further, users upgrading from 1.8 should acknowledge the following changes:
|
|
||||||
|
|
||||||
- Fixed DELEGATECALL's from/to field ([#7568](https://github.com/paritytech/parity/pull/7568)).
|
|
||||||
- Set zero nonce and gas price for calls by default ([#6954](https://github.com/paritytech/parity/pull/6954)).
|
|
||||||
- Create pending blocks with all transactions from the queue ([#6942](https://github.com/paritytech/parity/pull/6942)).
|
|
||||||
- Remove RPC parameter leniency now that Mist formats correctly ([#6651](https://github.com/paritytech/parity/pull/6651)). Parity stops accepting decimal-formatted block numbers and stops parsing the empty string as empty bytes.
|
|
||||||
- Public nodes do not support the user interface anymore. If you are running a public node, please stay on the 1.8 branch of the stable releases.
|
|
||||||
|
|
||||||
Additional noteworthy changes:
|
|
||||||
|
|
||||||
- `ethstore` and `ethkey` have been significantly improved ([#6961](https://github.com/paritytech/parity/pull/6961)):
|
|
||||||
- `ethstore` now supports brute forcing pre-sale wallets given a password list for recovery.
|
|
||||||
- `ethkey` now supports multi-threaded generation of prefix-matching addresses.
|
|
||||||
- `ethkey` now supports prefix-matching brain wallets.
|
|
||||||
- `ethkey` now supports brain-wallets recovery-phrases lookup. This helps to find a correct phrase if you know the address you want to get yet you made a typo backing the phrase up, or forgot a word.
|
|
||||||
|
|
||||||
Read more about Parity 1.9 in our [blog post](http://paritytech.io/velocity-the-fastest-parity-released/).
|
|
||||||
|
|
||||||
The full list of included changes:
|
|
||||||
|
|
||||||
- Add scroll when when too many accounts ([#7677](https://github.com/paritytech/parity/pull/7677)) ([#7679](https://github.com/paritytech/parity/pull/7679))
|
|
||||||
- Update installer.nsi
|
|
||||||
- Fix conditions in gitlab-test ([#7676](https://github.com/paritytech/parity/pull/7676))
|
|
||||||
- Fix conditions in gitlab-test
|
|
||||||
- Update gitlab-test.sh
|
|
||||||
- Remove cargo cache
|
|
||||||
- Backports to beta ([#7660](https://github.com/paritytech/parity/pull/7660))
|
|
||||||
- Improve handling of RocksDB corruption ([#7630](https://github.com/paritytech/parity/pull/7630))
|
|
||||||
- Kvdb-rocksdb: update rust-rocksdb version
|
|
||||||
- Kvdb-rocksdb: mark corruptions and attempt repair on db open
|
|
||||||
- Kvdb-rocksdb: better corruption detection on open
|
|
||||||
- Kvdb-rocksdb: add corruption_file_name const
|
|
||||||
- Kvdb-rocksdb: rename mark_corruption to check_for_corruption
|
|
||||||
- Hardening of CSP ([#7621](https://github.com/paritytech/parity/pull/7621))
|
|
||||||
- Fixed delegatecall's from/to ([#7568](https://github.com/paritytech/parity/pull/7568))
|
|
||||||
- Fixed delegatecall's from/to, closes [#7166](https://github.com/paritytech/parity/issues/7166)
|
|
||||||
- Added tests for delegatecall traces, [#7167](https://github.com/paritytech/parity/issues/7167)
|
|
||||||
- Light client RPCs ([#7603](https://github.com/paritytech/parity/pull/7603))
|
|
||||||
- Implement registrar.
|
|
||||||
- Implement eth_getCode
|
|
||||||
- Don't wait for providers.
|
|
||||||
- Don't wait for providers.
|
|
||||||
- Fix linting and wasm tests.
|
|
||||||
- Problem: AttachedProtocols don't get registered ([#7610](https://github.com/paritytech/parity/pull/7610))
|
|
||||||
- Fix Temporarily Invalid blocks handling ([#7613](https://github.com/paritytech/parity/pull/7613))
|
|
||||||
- Handle temporarily invalid blocks in sync.
|
|
||||||
- Fix tests.
|
|
||||||
- Add docker build for beta ([#7671](https://github.com/paritytech/parity/pull/7671))
|
|
||||||
- Add docker build for beta
|
|
||||||
- Add cargo cache
|
|
||||||
- Fix snapcraft build for beta ([#7670](https://github.com/paritytech/parity/pull/7670))
|
|
||||||
- Update Parity.pkgproj
|
|
||||||
- update gitlab build from master
|
|
||||||
- Update references to dapp sources ([#7634](https://github.com/paritytech/parity/pull/7634)) ([#7636](https://github.com/paritytech/parity/pull/7636))
|
|
||||||
- Update tokenreg ([#7618](https://github.com/paritytech/parity/pull/7618)) ([#7619](https://github.com/paritytech/parity/pull/7619))
|
|
||||||
- Fix cache:key ([#7598](https://github.com/paritytech/parity/pull/7598))
|
|
||||||
- Make 1.9 beta ([#7533](https://github.com/paritytech/parity/pull/7533))
|
|
||||||
- Trigger js-precompiled ([#7535](https://github.com/paritytech/parity/pull/7535))
|
|
||||||
- RocksDB fix ([#7512](https://github.com/paritytech/parity/pull/7512))
|
|
||||||
- Update js-api ([#7510](https://github.com/paritytech/parity/pull/7510))
|
|
||||||
- Expose default gas price percentile configuration in CLI ([#7497](https://github.com/paritytech/parity/pull/7497))
|
|
||||||
- Use https connection ([#7503](https://github.com/paritytech/parity/pull/7503))
|
|
||||||
- More thorough changes detection ([#7472](https://github.com/paritytech/parity/pull/7472))
|
|
||||||
- Fix small layout issues ([#7500](https://github.com/paritytech/parity/pull/7500))
|
|
||||||
- Show all accounts on Topbar ([#7498](https://github.com/paritytech/parity/pull/7498))
|
|
||||||
- Update Parity Mainnet Bootnodes ([#7476](https://github.com/paritytech/parity/pull/7476))
|
|
||||||
- Fixed panic when io is not available for export block ([#7495](https://github.com/paritytech/parity/pull/7495))
|
|
||||||
- Advance AuRa step as far as we can and prevent invalid blocks. ([#7451](https://github.com/paritytech/parity/pull/7451))
|
|
||||||
- Update package-lock in js-old ([#7494](https://github.com/paritytech/parity/pull/7494))
|
|
||||||
- Update issue template and readme ([#7450](https://github.com/paritytech/parity/pull/7450))
|
|
||||||
- Update package-lock.json pinned versions ([#7492](https://github.com/paritytech/parity/pull/7492))
|
|
||||||
- Explicit pre-precompiled push checkout ([#7474](https://github.com/paritytech/parity/pull/7474))
|
|
||||||
- Trigger js-precompiled ([#7473](https://github.com/paritytech/parity/pull/7473))
|
|
||||||
- Expanse Byzantium update w/ correct metropolis difficulty increment divisor ([#7463](https://github.com/paritytech/parity/pull/7463))
|
|
||||||
- Updated icons ([#7469](https://github.com/paritytech/parity/pull/7469))
|
|
||||||
- Cleanup certifications ([#7454](https://github.com/paritytech/parity/pull/7454))
|
|
||||||
- Fix css lint (updated stylelint) ([#7471](https://github.com/paritytech/parity/pull/7471))
|
|
||||||
- Upgrade markdown-loader & marked ([#7467](https://github.com/paritytech/parity/pull/7467))
|
|
||||||
- Remove JS test for removed code ([#7461](https://github.com/paritytech/parity/pull/7461))
|
|
||||||
- Pull in dapp-status ([#7457](https://github.com/paritytech/parity/pull/7457))
|
|
||||||
- Bump openssl crate ([#7455](https://github.com/paritytech/parity/pull/7455))
|
|
||||||
- Signer updates from global Redux state ([#7452](https://github.com/paritytech/parity/pull/7452))
|
|
||||||
- Remove expanse chain ([#7437](https://github.com/paritytech/parity/pull/7437))
|
|
||||||
- Store tokens with repeatable id ([#7435](https://github.com/paritytech/parity/pull/7435))
|
|
||||||
- Strict config parsing ([#7433](https://github.com/paritytech/parity/pull/7433))
|
|
||||||
- Upgrade to RocksDB 5.8.8 and tune settings to reduce space amplification ([#7348](https://github.com/paritytech/parity/pull/7348))
|
|
||||||
- Fix status layout ([#7432](https://github.com/paritytech/parity/pull/7432))
|
|
||||||
- Fix tracing failed calls. ([#7412](https://github.com/paritytech/parity/pull/7412))
|
|
||||||
- Problem: sending any Whisper message fails ([#7421](https://github.com/paritytech/parity/pull/7421))
|
|
||||||
- Wait for future blocks in AuRa ([#7368](https://github.com/paritytech/parity/pull/7368))
|
|
||||||
- Fix final feature. ([#7426](https://github.com/paritytech/parity/pull/7426))
|
|
||||||
- Use RwLock for state DB ([#7425](https://github.com/paritytech/parity/pull/7425))
|
|
||||||
- Update branding on UI ([#7370](https://github.com/paritytech/parity/pull/7370))
|
|
||||||
- Changelog for 1.8.5 and 1.7.11 ([#7401](https://github.com/paritytech/parity/pull/7401))
|
|
||||||
- Added checking tx-type using transactions permission contract for miners ([#7359](https://github.com/paritytech/parity/pull/7359))
|
|
||||||
- Standalone dir crate, replaces [#7383](https://github.com/paritytech/parity/issues/7383) ([#7409](https://github.com/paritytech/parity/pull/7409))
|
|
||||||
- SecretStore: secretstore_signRawHash method ([#7336](https://github.com/paritytech/parity/pull/7336))
|
|
||||||
- SecretStore: return error 404 when there's no key shares for given key on all nodes ([#7331](https://github.com/paritytech/parity/pull/7331))
|
|
||||||
- SecretStore: PoA integration initial version ([#7101](https://github.com/paritytech/parity/pull/7101))
|
|
||||||
- Update bootnodes ([#7363](https://github.com/paritytech/parity/pull/7363))
|
|
||||||
- Fix default CORS settings. ([#7387](https://github.com/paritytech/parity/pull/7387))
|
|
||||||
- Fix version ([#7390](https://github.com/paritytech/parity/pull/7390))
|
|
||||||
- Wasm runtime update ([#7356](https://github.com/paritytech/parity/pull/7356))
|
|
||||||
- Parity-version pr reopen ([#7136](https://github.com/paritytech/parity/pull/7136))
|
|
||||||
- Get rid of clippy remainings. ([#7355](https://github.com/paritytech/parity/pull/7355))
|
|
||||||
- Avoid using ok_or with allocated argument ([#7357](https://github.com/paritytech/parity/pull/7357))
|
|
||||||
- Make accounts refresh time configurable. ([#7345](https://github.com/paritytech/parity/pull/7345))
|
|
||||||
- Enable traces for DEV chain ([#7327](https://github.com/paritytech/parity/pull/7327))
|
|
||||||
- Problem: AuRa's unsafeties around step duration ([#7282](https://github.com/paritytech/parity/pull/7282))
|
|
||||||
- Problem: Cargo.toml file contains [project] key ([#7346](https://github.com/paritytech/parity/pull/7346))
|
|
||||||
- Fix broken flex modal layouts ([#7343](https://github.com/paritytech/parity/pull/7343))
|
|
||||||
- Fix dappIcon & Fix Signer Pending ([#7338](https://github.com/paritytech/parity/pull/7338))
|
|
||||||
- Fix wallet token/badge icons not showing up ([#7333](https://github.com/paritytech/parity/pull/7333))
|
|
||||||
- Add Ellaism coin in chain config ([#7222](https://github.com/paritytech/parity/pull/7222))
|
|
||||||
- Update bootnodes ([#7296](https://github.com/paritytech/parity/pull/7296))
|
|
||||||
- Adds `personal_signTransaction` RPC method ([#6991](https://github.com/paritytech/parity/pull/6991))
|
|
||||||
- Fix double initialization of embeded providers. ([#7326](https://github.com/paritytech/parity/pull/7326))
|
|
||||||
- Transaction Pool re-implementation ([#6994](https://github.com/paritytech/parity/pull/6994))
|
|
||||||
- UI package bump ([#7318](https://github.com/paritytech/parity/pull/7318))
|
|
||||||
- Test framework and basic test for whisper ([#7011](https://github.com/paritytech/parity/pull/7011))
|
|
||||||
- CI js-precompiled trigger ([#7316](https://github.com/paritytech/parity/pull/7316))
|
|
||||||
- Fix inject.js & Signer store duplication ([#7299](https://github.com/paritytech/parity/pull/7299))
|
|
||||||
- Detect different node, same-key signing in aura ([#7245](https://github.com/paritytech/parity/pull/7245))
|
|
||||||
- New warp enodes ([#7287](https://github.com/paritytech/parity/pull/7287))
|
|
||||||
- CSS fixes for v1 ([#7285](https://github.com/paritytech/parity/pull/7285))
|
|
||||||
- Wallet subscriptions & refresh ([#7283](https://github.com/paritytech/parity/pull/7283))
|
|
||||||
- Update inject web3 dependencies ([#7286](https://github.com/paritytech/parity/pull/7286))
|
|
||||||
- Some padding around dapp image ([#7276](https://github.com/paritytech/parity/pull/7276))
|
|
||||||
- Expand available middleware methods ([#7275](https://github.com/paritytech/parity/pull/7275))
|
|
||||||
- Inject parity script to all dapps // Expand dapps to any ZIP file ([#7260](https://github.com/paritytech/parity/pull/7260))
|
|
||||||
- New Homepage ([#7266](https://github.com/paritytech/parity/pull/7266))
|
|
||||||
- Update kovan HF block number. ([#7259](https://github.com/paritytech/parity/pull/7259))
|
|
||||||
- CHANGELOG for 1.7.10 and 1.8.4 ([#7265](https://github.com/paritytech/parity/pull/7265))
|
|
||||||
- Remove extraneous id hashing ([#7269](https://github.com/paritytech/parity/pull/7269))
|
|
||||||
- Simplify status + content display overlaps/page fixing ([#7264](https://github.com/paritytech/parity/pull/7264))
|
|
||||||
- UI redirect to 127.0.0.1 when localhost requested ([#7236](https://github.com/paritytech/parity/pull/7236))
|
|
||||||
- Usability improvements to security token Dialog [#7112](https://github.com/paritytech/parity/issues/7112) ([#7134](https://github.com/paritytech/parity/pull/7134))
|
|
||||||
- Don't display unneeded notifications ([#7237](https://github.com/paritytech/parity/pull/7237))
|
|
||||||
- Reduce max block timestamp drift to 15 seconds ([#7240](https://github.com/paritytech/parity/pull/7240))
|
|
||||||
- Increase allowed time drift to 10s. ([#7238](https://github.com/paritytech/parity/pull/7238))
|
|
||||||
- Improve building from source ([#7239](https://github.com/paritytech/parity/pull/7239))
|
|
||||||
- Fix/Update method permissions ([#7233](https://github.com/paritytech/parity/pull/7233))
|
|
||||||
- Fix aura difficulty race ([#7198](https://github.com/paritytech/parity/pull/7198))
|
|
||||||
- Dependency updates ([#7226](https://github.com/paritytech/parity/pull/7226))
|
|
||||||
- Display all dapps (shell) & wallet tabs (v1) by default ([#7213](https://github.com/paritytech/parity/pull/7213))
|
|
||||||
- Rework dapps list ([#7206](https://github.com/paritytech/parity/pull/7206))
|
|
||||||
- Add contributing guidelines and code of conduct. ([#7157](https://github.com/paritytech/parity/pull/7157))
|
|
||||||
- Make Signing Requests more visible ([#7204](https://github.com/paritytech/parity/pull/7204))
|
|
||||||
- Send each log as a separate notification ([#7175](https://github.com/paritytech/parity/pull/7175))
|
|
||||||
- Deleting a mistake comment in calc difficulty ([#7154](https://github.com/paritytech/parity/pull/7154))
|
|
||||||
- Maximum uncle count transition ([#7196](https://github.com/paritytech/parity/pull/7196))
|
|
||||||
- Update FirstRun for UI-2 ([#7195](https://github.com/paritytech/parity/pull/7195))
|
|
||||||
- Update mocha import stubs ([#7191](https://github.com/paritytech/parity/pull/7191))
|
|
||||||
- Escape inifinite loop in estimte_gas ([#7075](https://github.com/paritytech/parity/pull/7075))
|
|
||||||
- New account selector UI in top bar ([#7179](https://github.com/paritytech/parity/pull/7179))
|
|
||||||
- Removed ethcore-util dependency from ethcore-network ([#7180](https://github.com/paritytech/parity/pull/7180))
|
|
||||||
- WASM test runner utility upgrade ([#7147](https://github.com/paritytech/parity/pull/7147))
|
|
||||||
- React 16 ([#7174](https://github.com/paritytech/parity/pull/7174))
|
|
||||||
- Assorted improvements for ethstore and ethkey ([#6961](https://github.com/paritytech/parity/pull/6961))
|
|
||||||
- Delete unused package.json (dist bundles) ([#7173](https://github.com/paritytech/parity/pull/7173))
|
|
||||||
- Remove *.css.map & *.js.map ([#7168](https://github.com/paritytech/parity/pull/7168))
|
|
||||||
- Use git flag to remove old js artifacts ([#7165](https://github.com/paritytech/parity/pull/7165))
|
|
||||||
- Cleanup JS build artifacts ([#7164](https://github.com/paritytech/parity/pull/7164))
|
|
||||||
- Fixes typo in user config path ([#7159](https://github.com/paritytech/parity/pull/7159))
|
|
||||||
- Pull in new dapp-{methods,visible} dapps ([#7150](https://github.com/paritytech/parity/pull/7150))
|
|
||||||
- WASM test runner utility ([#7142](https://github.com/paritytech/parity/pull/7142))
|
|
||||||
- WASM Remove blockhash error ([#7121](https://github.com/paritytech/parity/pull/7121))
|
|
||||||
- ECIP-1039: Monetary policy rounding specification ([#7067](https://github.com/paritytech/parity/pull/7067))
|
|
||||||
- Fixed `RotatingLogger` after migrating to new arrayvec ([#7129](https://github.com/paritytech/parity/pull/7129))
|
|
||||||
- Push to correct shell branch ([#7135](https://github.com/paritytech/parity/pull/7135))
|
|
||||||
- Update js-precompiled ref, trigger JS build ([#7132](https://github.com/paritytech/parity/pull/7132))
|
|
||||||
- Fixed build && test ([#7128](https://github.com/paritytech/parity/pull/7128))
|
|
||||||
- Update packages, pull in compiled-only repos ([#7125](https://github.com/paritytech/parity/pull/7125))
|
|
||||||
- Cleanup top bar, add Home icon for navigation ([#7118](https://github.com/paritytech/parity/pull/7118))
|
|
||||||
- WASM storage_read and storage_write don't return anything ([#7110](https://github.com/paritytech/parity/pull/7110))
|
|
||||||
- Local dapp development URL ([#7100](https://github.com/paritytech/parity/pull/7100))
|
|
||||||
- Remove unused and duplicated files in js-old ([#7082](https://github.com/paritytech/parity/pull/7082))
|
|
||||||
- Optimize & group dapp requests ([#7083](https://github.com/paritytech/parity/pull/7083))
|
|
||||||
- WASM parse payload from panics ([#7097](https://github.com/paritytech/parity/pull/7097))
|
|
||||||
- Fix no-default-features. ([#7096](https://github.com/paritytech/parity/pull/7096))
|
|
||||||
- Updated eth-secp256k1 ([#7090](https://github.com/paritytech/parity/pull/7090))
|
|
||||||
- Improve Github Issue Template ([#7099](https://github.com/paritytech/parity/pull/7099))
|
|
||||||
- Changes necessary to upload crates to crates.io ([#7020](https://github.com/paritytech/parity/pull/7020))
|
|
||||||
- Reopened 6860 - iterate over both buffered and unbuffered database entries ([#7048](https://github.com/paritytech/parity/pull/7048))
|
|
||||||
- SecretStore: servers set change session api ([#6925](https://github.com/paritytech/parity/pull/6925))
|
|
||||||
- Disable uncles by default ([#7006](https://github.com/paritytech/parity/pull/7006))
|
|
||||||
- Squashed ethcore-network changes which introduce error-chain ([#7040](https://github.com/paritytech/parity/pull/7040))
|
|
||||||
- Removed redundant imports ([#7057](https://github.com/paritytech/parity/pull/7057))
|
|
||||||
- CHANGELOG for 1.7.8, 1.7.9, 1.8.2, and 1.8.3 ([#7055](https://github.com/paritytech/parity/pull/7055))
|
|
||||||
- Properly display Signer errors (Snackbar display popup) ([#7053](https://github.com/paritytech/parity/pull/7053))
|
|
||||||
- Add the desktop file for the snap ([#7059](https://github.com/paritytech/parity/pull/7059))
|
|
||||||
- Small performance gain in allocations ([#7054](https://github.com/paritytech/parity/pull/7054))
|
|
||||||
- Bump JSON-RPC version ([#7051](https://github.com/paritytech/parity/pull/7051))
|
|
||||||
- Fix nonce reservation ([#7025](https://github.com/paritytech/parity/pull/7025))
|
|
||||||
- Fixed ethstore-cli output ([#7052](https://github.com/paritytech/parity/pull/7052))
|
|
||||||
- Add mui for embed compilation ([#7049](https://github.com/paritytech/parity/pull/7049))
|
|
||||||
- Update the snap metadata to keep working strictly confined ([#6993](https://github.com/paritytech/parity/pull/6993))
|
|
||||||
- Remove unused js packages (dapp cleanups) ([#7046](https://github.com/paritytech/parity/pull/7046))
|
|
||||||
- Gitlog location update ([#7042](https://github.com/paritytech/parity/pull/7042))
|
|
||||||
- Move git logging to .git-release.log ([#7041](https://github.com/paritytech/parity/pull/7041))
|
|
||||||
- Start from rust root in release update step ([#7039](https://github.com/paritytech/parity/pull/7039))
|
|
||||||
- Complete token merge, remove unused files ([#7037](https://github.com/paritytech/parity/pull/7037))
|
|
||||||
- Add missing cargo-push.sh shell variable ([#7036](https://github.com/paritytech/parity/pull/7036))
|
|
||||||
- Fix npm start script ([#7034](https://github.com/paritytech/parity/pull/7034))
|
|
||||||
- Update executable flags on release scripts ([#7035](https://github.com/paritytech/parity/pull/7035))
|
|
||||||
- Fix v1 precompiled ([#7033](https://github.com/paritytech/parity/pull/7033))
|
|
||||||
- Push precompiled to correct branch (v1) ([#7031](https://github.com/paritytech/parity/pull/7031))
|
|
||||||
- Update v1 Wallet Dapp ([#6935](https://github.com/paritytech/parity/pull/6935))
|
|
||||||
- WASM tests update ([#7018](https://github.com/paritytech/parity/pull/7018))
|
|
||||||
- Events in WASM runtime ([#6967](https://github.com/paritytech/parity/pull/6967))
|
|
||||||
- Adds validate_node_url() and refactors boot node check ([#6907](https://github.com/paritytech/parity/pull/6907)) ([#6970](https://github.com/paritytech/parity/pull/6970))
|
|
||||||
- Fix windows build (with ui rebuild) ([#7016](https://github.com/paritytech/parity/pull/7016))
|
|
||||||
- Make CLI arguments parsing more backwards compatible ([#7004](https://github.com/paritytech/parity/pull/7004))
|
|
||||||
- Fixes for parity-extension ([#6990](https://github.com/paritytech/parity/pull/6990))
|
|
||||||
- Update ethcore-bigint ([#6992](https://github.com/paritytech/parity/pull/6992))
|
|
||||||
- Get local transactions by hash in the light client ([#6874](https://github.com/paritytech/parity/pull/6874))
|
|
||||||
- Warn when blacklisted account present in store ([#6875](https://github.com/paritytech/parity/pull/6875))
|
|
||||||
- Skip nonce check for gas estimation ([#6997](https://github.com/paritytech/parity/pull/6997))
|
|
||||||
- Creating pending block with all transactions from the queue ([#6942](https://github.com/paritytech/parity/pull/6942))
|
|
||||||
- Removes `MAX_TX_TO_IMPORT` from `ChainSync` ([#6976](https://github.com/paritytech/parity/pull/6976))
|
|
||||||
- SecretStore: versioned keys ([#6910](https://github.com/paritytech/parity/pull/6910))
|
|
||||||
- Removes `FUTURE_QUEUE_LIMITS_SHIFT` ([#6962](https://github.com/paritytech/parity/pull/6962))
|
|
||||||
- Set zero nonce and gas price for calls by default ([#6954](https://github.com/paritytech/parity/pull/6954))
|
|
||||||
- Add hint in ActionParams for splitting code/data ([#6957](https://github.com/paritytech/parity/pull/6957))
|
|
||||||
- Return decoded seal fields. ([#6932](https://github.com/paritytech/parity/pull/6932))
|
|
||||||
- Fix serialization of status in transaction receipts. ([#6926](https://github.com/paritytech/parity/pull/6926))
|
|
||||||
- Reserve nonces for signing ([#6834](https://github.com/paritytech/parity/pull/6834))
|
|
||||||
- Windows fixes ([#6921](https://github.com/paritytech/parity/pull/6921))
|
|
||||||
- Don't add {css,js}.map from dapps ([#6931](https://github.com/paritytech/parity/pull/6931))
|
|
||||||
- Fix JSON tracing for sub-calls. ([#6842](https://github.com/paritytech/parity/pull/6842))
|
|
||||||
- Shell updates (bonds, updated Dapps) ([#6897](https://github.com/paritytech/parity/pull/6897))
|
|
||||||
- Fix [#6228](https://github.com/paritytech/parity/issues/6228): do not display eth price in cli for etc ([#6877](https://github.com/paritytech/parity/pull/6877))
|
|
||||||
- Fix mining help ([#6885](https://github.com/paritytech/parity/pull/6885))
|
|
||||||
- Refactor static context check in CREATE. ([#6886](https://github.com/paritytech/parity/pull/6886))
|
|
||||||
- Cleanup some configuration options ([#6878](https://github.com/paritytech/parity/pull/6878))
|
|
||||||
- Fix serialization of non-localized transactions ([#6868](https://github.com/paritytech/parity/pull/6868))
|
|
||||||
- Updated ntp to version 0.3 ([#6854](https://github.com/paritytech/parity/pull/6854))
|
|
||||||
- Align README with 1.8 and prepare CHANGELOG with 1.8.1 ([#6833](https://github.com/paritytech/parity/pull/6833))
|
|
||||||
- Return error on timed unlock ([#6777](https://github.com/paritytech/parity/pull/6777))
|
|
||||||
- Fix dapps tests in master ([#6866](https://github.com/paritytech/parity/pull/6866))
|
|
||||||
- Ethstore optimizations ([#6827](https://github.com/paritytech/parity/pull/6827))
|
|
||||||
- Add ECIP1017 to Morden config ([#6810](https://github.com/paritytech/parity/pull/6810))
|
|
||||||
- Remove all package publishing to npm ([#6838](https://github.com/paritytech/parity/pull/6838))
|
|
||||||
- Util crates use tempdir crate instead of devtools to create temp path ([#6807](https://github.com/paritytech/parity/pull/6807))
|
|
||||||
- Trigger js build ([#6836](https://github.com/paritytech/parity/pull/6836))
|
|
||||||
- Clean-up scripts. ([#6832](https://github.com/paritytech/parity/pull/6832))
|
|
||||||
- Tweaked snapshot sync threshold ([#6829](https://github.com/paritytech/parity/pull/6829))
|
|
||||||
- Integrate UI 2 ([#6819](https://github.com/paritytech/parity/pull/6819))
|
|
||||||
- Refresh cached tokens based on registry info & random balances ([#6818](https://github.com/paritytech/parity/pull/6818))
|
|
||||||
- Change keypath derivation logic ([#6815](https://github.com/paritytech/parity/pull/6815))
|
|
||||||
- Refactors journaldb as a separate crate ([#6801](https://github.com/paritytech/parity/pull/6801))
|
|
||||||
- Trigger UI build. ([#6817](https://github.com/paritytech/parity/pull/6817))
|
|
||||||
- Bumped more crate versions ([#6809](https://github.com/paritytech/parity/pull/6809))
|
|
||||||
- Fix RPC compilation warnings. ([#6808](https://github.com/paritytech/parity/pull/6808))
|
|
||||||
- Remove internal ipc ([#6795](https://github.com/paritytech/parity/pull/6795))
|
|
||||||
- Consistent KeyValueDB errors ([#6792](https://github.com/paritytech/parity/pull/6792))
|
|
||||||
- Squash remaining warnings ([#6789](https://github.com/paritytech/parity/pull/6789))
|
|
||||||
- Forward-port [#6754](https://github.com/paritytech/parity/issues/6754) [#6755](https://github.com/paritytech/parity/issues/6755) ([#6785](https://github.com/paritytech/parity/pull/6785))
|
|
||||||
- Removed duplicated versions of clippy ([#6776](https://github.com/paritytech/parity/pull/6776))
|
|
||||||
- Updated ethabi to version 4.0 ([#6742](https://github.com/paritytech/parity/pull/6742))
|
|
||||||
- Updated rpc_cli and parity to rpassword 1.0 ([#6774](https://github.com/paritytech/parity/pull/6774))
|
|
||||||
- Fix sign data typo ([#6750](https://github.com/paritytech/parity/pull/6750))
|
|
||||||
- Refactoring/cache 6693 ([#6772](https://github.com/paritytech/parity/pull/6772))
|
|
||||||
- Fix CHANGLOG for 1.8.0 ([#6751](https://github.com/paritytech/parity/pull/6751))
|
|
||||||
- Removes redundant `mut` in service.rs.in ([#6775](https://github.com/paritytech/parity/pull/6775))
|
|
||||||
- Remove redundant `mut` ([#6773](https://github.com/paritytech/parity/pull/6773))
|
|
||||||
- Fixed kovan chain validation ([#6758](https://github.com/paritytech/parity/pull/6758))
|
|
||||||
- Removed redundant evm deps ([#6757](https://github.com/paritytech/parity/pull/6757))
|
|
||||||
- Fixed modexp gas calculation overflow ([#6741](https://github.com/paritytech/parity/pull/6741))
|
|
||||||
- Use cc 1.0 instead of gcc ([#6733](https://github.com/paritytech/parity/pull/6733))
|
|
||||||
- Version bump to 1.9.0 ([#6727](https://github.com/paritytech/parity/pull/6727))
|
|
||||||
- Fix badges not showing up ([#6730](https://github.com/paritytech/parity/pull/6730))
|
|
||||||
|
|
||||||
### Previous releases
|
|
||||||
|
|
||||||
- [CHANGELOG-1.8](docs/CHANGELOG-1.8.md) (_stable_)
|
|
||||||
- [CHANGELOG-1.7](docs/CHANGELOG-1.7.md) (EOL: 2018-01-25)
|
|
||||||
- [CHANGELOG-1.6](docs/CHANGELOG-1.6.md) (EOL: 2017-10-15)
|
|
||||||
- [CHANGELOG-1.5](docs/CHANGELOG-1.5.md) (EOL: 2017-07-28)
|
|
||||||
- [CHANGELOG-1.4](docs/CHANGELOG-1.4.md) (EOL: 2017-03-13)
|
|
||||||
- [CHANGELOG-1.3](docs/CHANGELOG-1.3.md) (EOL: 2017-01-19)
|
|
||||||
- [CHANGELOG-1.2](docs/CHANGELOG-1.2.md) (EOL: 2016-11-07)
|
|
||||||
- [CHANGELOG-1.1](docs/CHANGELOG-1.1.md) (EOL: 2016-08-12)
|
|
||||||
- [CHANGELOG-1.0](docs/CHANGELOG-1.0.md) (EOL: 2016-06-24)
|
|
||||||
- [CHANGELOG-0.9](docs/CHANGELOG-0.9.md) (EOL: 2016-05-02)
|
|
||||||
|
|||||||
6264
Cargo.lock
generated
6264
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
159
Cargo.toml
159
Cargo.toml
@@ -1,76 +1,78 @@
|
|||||||
[package]
|
[package]
|
||||||
description = "Parity Ethereum client"
|
description = "OpenEthereum"
|
||||||
name = "parity"
|
name = "openethereum"
|
||||||
# NOTE Make sure to update util/version/Cargo.toml as well
|
# NOTE Make sure to update util/version/Cargo.toml as well
|
||||||
version = "1.10.0"
|
version = "3.1.1"
|
||||||
license = "GPL-3.0"
|
license = "GPL-3.0"
|
||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
authors = [
|
||||||
|
"OpenEthereum developers",
|
||||||
|
"Parity Technologies <admin@parity.io>"
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.3"
|
blooms-db = { path = "util/blooms-db" }
|
||||||
env_logger = "0.4"
|
log = "0.4"
|
||||||
rustc-hex = "1.0"
|
rustc-hex = "1.0"
|
||||||
docopt = "0.8"
|
docopt = "1.0"
|
||||||
clap = "2"
|
clap = "2"
|
||||||
term_size = "0.3"
|
term_size = "0.3"
|
||||||
textwrap = "0.9"
|
textwrap = "0.9"
|
||||||
time = "0.1"
|
|
||||||
num_cpus = "1.2"
|
num_cpus = "1.2"
|
||||||
number_prefix = "0.2"
|
number_prefix = "0.2"
|
||||||
rpassword = "1.0"
|
rpassword = "1.0"
|
||||||
semver = "0.6"
|
semver = "0.9"
|
||||||
ansi_term = "0.10"
|
ansi_term = "0.10"
|
||||||
parking_lot = "0.5"
|
parking_lot = "0.7"
|
||||||
regex = "0.2"
|
regex = "1.0"
|
||||||
isatty = "0.1"
|
atty = "0.2.8"
|
||||||
toml = "0.4"
|
toml = "0.4"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
app_dirs = "1.1.1"
|
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
futures-cpupool = "0.1"
|
hyper = { version = "0.12" }
|
||||||
fdlimit = "0.1"
|
fdlimit = "0.1"
|
||||||
ws2_32-sys = "0.2"
|
|
||||||
ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }
|
ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }
|
||||||
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.10" }
|
jsonrpc-core = "15.0.0"
|
||||||
ethsync = { path = "sync" }
|
parity-bytes = "0.1"
|
||||||
ethcore = { path = "ethcore" }
|
common-types = { path = "ethcore/types" }
|
||||||
ethcore-bytes = { path = "util/bytes" }
|
ethcore = { path = "ethcore", features = ["parity"] }
|
||||||
|
ethcore-accounts = { path = "accounts", optional = true }
|
||||||
|
ethcore-blockchain = { path = "ethcore/blockchain" }
|
||||||
|
ethcore-call-contract = { path = "ethcore/call-contract"}
|
||||||
|
ethcore-db = { path = "ethcore/db" }
|
||||||
ethcore-io = { path = "util/io" }
|
ethcore-io = { path = "util/io" }
|
||||||
ethcore-light = { path = "ethcore/light" }
|
ethcore-logger = { path = "parity/logger" }
|
||||||
ethcore-logger = { path = "logger" }
|
|
||||||
ethcore-migrations = { path = "ethcore/migrations" }
|
|
||||||
ethcore-miner = { path = "miner" }
|
ethcore-miner = { path = "miner" }
|
||||||
ethcore-network = { path = "util/network" }
|
ethcore-network = { path = "util/network" }
|
||||||
ethcore-stratum = { path = "stratum" }
|
ethcore-service = { path = "ethcore/service" }
|
||||||
ethcore-transaction = { path = "ethcore/transaction" }
|
ethcore-sync = { path = "ethcore/sync" }
|
||||||
ethereum-types = "0.2"
|
ethereum-types = "0.4"
|
||||||
node-filter = { path = "ethcore/node_filter" }
|
ethkey = { path = "accounts/ethkey" }
|
||||||
ethkey = { path = "ethkey" }
|
ethstore = { path = "accounts/ethstore" }
|
||||||
node-health = { path = "dapps/node-health" }
|
fetch = { path = "util/fetch" }
|
||||||
rlp = { path = "util/rlp" }
|
node-filter = { path = "ethcore/node-filter" }
|
||||||
rpc-cli = { path = "rpc_cli" }
|
rlp = { version = "0.3.0", features = ["ethereum"] }
|
||||||
parity-hash-fetch = { path = "hash-fetch" }
|
cli-signer= { path = "cli-signer" }
|
||||||
parity-ipfs-api = { path = "ipfs" }
|
parity-daemonize = "0.3"
|
||||||
parity-local-store = { path = "local-store" }
|
parity-local-store = { path = "miner/local-store" }
|
||||||
parity-reactor = { path = "util/reactor" }
|
parity-runtime = { path = "util/runtime" }
|
||||||
parity-rpc = { path = "rpc" }
|
parity-rpc = { path = "rpc" }
|
||||||
parity-rpc-client = { path = "rpc_client" }
|
|
||||||
parity-updater = { path = "updater" }
|
|
||||||
parity-version = { path = "util/version" }
|
parity-version = { path = "util/version" }
|
||||||
parity-whisper = { path = "whisper" }
|
parity-path = "0.1"
|
||||||
path = { path = "util/path" }
|
|
||||||
dir = { path = "util/dir" }
|
dir = { path = "util/dir" }
|
||||||
panic_hook = { path = "util/panic_hook" }
|
panic_hook = { path = "util/panic-hook" }
|
||||||
keccak-hash = { path = "util/hash" }
|
keccak-hash = "0.1"
|
||||||
migration = { path = "util/migration" }
|
migration-rocksdb = { path = "util/migration-rocksdb" }
|
||||||
kvdb = { path = "util/kvdb" }
|
kvdb = "0.1"
|
||||||
kvdb-rocksdb = { path = "util/kvdb-rocksdb" }
|
kvdb-rocksdb = "0.1.3"
|
||||||
journaldb = { path = "util/journaldb" }
|
journaldb = { path = "util/journaldb" }
|
||||||
|
stats = { path = "util/stats" }
|
||||||
|
prometheus = "0.9.0"
|
||||||
|
|
||||||
parity-dapps = { path = "dapps", optional = true }
|
ethcore-secretstore = { path = "secret-store", optional = true }
|
||||||
ethcore-secretstore = { path = "secret_store", optional = true }
|
|
||||||
|
registrar = { path = "util/registrar" }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
rustc_version = "0.2"
|
rustc_version = "0.2"
|
||||||
@@ -79,57 +81,64 @@ rustc_version = "0.2"
|
|||||||
pretty_assertions = "0.1"
|
pretty_assertions = "0.1"
|
||||||
ipnetwork = "0.12.6"
|
ipnetwork = "0.12.6"
|
||||||
tempdir = "0.3"
|
tempdir = "0.3"
|
||||||
|
fake-fetch = { path = "util/fake-fetch" }
|
||||||
|
lazy_static = "1.2.0"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = "0.2"
|
winapi = { version = "0.3.4", features = ["winsock2", "winuser", "shellapi"] }
|
||||||
|
|
||||||
[target.'cfg(not(windows))'.dependencies]
|
|
||||||
daemonize = "0.2"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["ui-precompiled"]
|
default = ["accounts"]
|
||||||
ui = [
|
accounts = ["ethcore-accounts", "parity-rpc/accounts"]
|
||||||
"ui-enabled",
|
miner-debug = ["ethcore/miner-debug"]
|
||||||
"parity-dapps/ui",
|
|
||||||
]
|
|
||||||
ui-precompiled = [
|
|
||||||
"ui-enabled",
|
|
||||||
"parity-dapps/ui-precompiled",
|
|
||||||
]
|
|
||||||
ui-enabled = ["dapps"]
|
|
||||||
dapps = ["parity-dapps"]
|
|
||||||
jit = ["ethcore/jit"]
|
|
||||||
json-tests = ["ethcore/json-tests"]
|
json-tests = ["ethcore/json-tests"]
|
||||||
|
ci-skip-tests = ["ethcore/ci-skip-tests"]
|
||||||
test-heavy = ["ethcore/test-heavy"]
|
test-heavy = ["ethcore/test-heavy"]
|
||||||
evm-debug = ["ethcore/evm-debug"]
|
evm-debug = ["ethcore/evm-debug"]
|
||||||
evm-debug-tests = ["ethcore/evm-debug-tests"]
|
evm-debug-tests = ["ethcore/evm-debug-tests"]
|
||||||
slow-blocks = ["ethcore/slow-blocks"]
|
slow-blocks = ["ethcore/slow-blocks"]
|
||||||
secretstore = ["ethcore-secretstore"]
|
secretstore = ["ethcore-secretstore", "ethcore-secretstore/accounts"]
|
||||||
final = ["parity-version/final"]
|
final = ["parity-version/final"]
|
||||||
|
deadlock_detection = ["parking_lot/deadlock_detection"]
|
||||||
|
# to create a memory profile (requires nightly rust), use e.g.
|
||||||
|
# `heaptrack /path/to/parity <parity params>`,
|
||||||
|
# to visualize a memory profile, use `heaptrack_gui`
|
||||||
|
# or
|
||||||
|
# `valgrind --tool=massif /path/to/parity <parity params>`
|
||||||
|
# and `massif-visualizer` for visualization
|
||||||
|
memory_profiling = []
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "parity/lib.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
path = "parity/main.rs"
|
path = "parity/main.rs"
|
||||||
name = "parity"
|
name = "openethereum"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.test]
|
||||||
panic = "abort"
|
lto = false
|
||||||
|
opt-level = 3 # makes tests slower to compile, but faster to run
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = false
|
debug = false
|
||||||
lto = false
|
lto = true
|
||||||
panic = "abort"
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
|
# This should only list projects that are not
|
||||||
|
# in the dependency tree in any other way
|
||||||
|
# (i.e. pretty much only standalone CLI tools)
|
||||||
members = [
|
members = [
|
||||||
|
"accounts/ethkey/cli",
|
||||||
|
"accounts/ethstore/cli",
|
||||||
"chainspec",
|
"chainspec",
|
||||||
"dapps/js-glue",
|
|
||||||
"ethcore/wasm/run",
|
"ethcore/wasm/run",
|
||||||
"ethcore/types",
|
|
||||||
"ethkey/cli",
|
|
||||||
"ethstore/cli",
|
|
||||||
"evmbin",
|
"evmbin",
|
||||||
"miner",
|
"util/triehash-ethereum",
|
||||||
"transaction-pool",
|
"util/keccak-hasher",
|
||||||
"whisper",
|
"util/patricia-trie-ethereum",
|
||||||
"util/rlp_compress"
|
"util/fastmap",
|
||||||
|
"util/time-utils"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
heapsize = { git = "https://github.com/cheme/heapsize.git", branch = "ec-macfix" }
|
||||||
|
|||||||
371
README.md
371
README.md
@@ -1,116 +1,102 @@
|
|||||||
# Parity - fast, light, and robust Ethereum client
|
# OpenEthereum
|
||||||
|
|
||||||
## [» Download the latest release «](https://github.com/paritytech/parity/releases/latest)
|
Fast and feature-rich multi-network Ethereum client.
|
||||||
|
|
||||||
[](https://gitlab.parity.io/parity/parity/commits/master)
|
[» Download the latest release «](https://github.com/openethereum/openethereum/releases/latest)
|
||||||
[](https://codecov.io/gh/paritytech/parity)
|
|
||||||
[](https://build.snapcraft.io/user/paritytech/parity)
|
[![GPL licensed][license-badge]][license-url]
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
[![Build Status][ci-badge]][ci-url]
|
||||||
|
[![Discord chat][chat-badge]][chat-url]
|
||||||
|
|
||||||
|
[license-badge]: https://img.shields.io/badge/license-GPL_v3-green.svg
|
||||||
|
[license-url]: LICENSE
|
||||||
|
[ci-badge]: https://github.com/openethereum/openethereum/workflows/Build%20and%20Test%20Suite/badge.svg
|
||||||
|
[ci-url]: https://github.com/openethereum/openethereum/actions
|
||||||
|
[chat-badge]: https://img.shields.io/discord/669192218728202270.svg?logo=discord
|
||||||
|
[chat-url]: https://discord.io/openethereum
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Description](#chapter-001)
|
||||||
|
2. [Technical Overview](#chapter-002)
|
||||||
|
3. [Building](#chapter-003)<br>
|
||||||
|
3.1 [Building Dependencies](#chapter-0031)<br>
|
||||||
|
3.2 [Building from Source Code](#chapter-0032)<br>
|
||||||
|
3.3 [Starting OpenEthereum](#chapter-0034)
|
||||||
|
4. [Testing](#chapter-004)
|
||||||
|
5. [Documentation](#chapter-005)
|
||||||
|
6. [Toolchain](#chapter-006)
|
||||||
|
7. [Contributing](#chapter-008)
|
||||||
|
8. [License](#chapter-009)
|
||||||
|
|
||||||
|
|
||||||
### Join the chat!
|
## 1. Description <a id="chapter-001"></a>
|
||||||
|
|
||||||
Get in touch with us on Gitter:
|
**Built for mission-critical use**: Miners, service providers, and exchanges need fast synchronisation and maximum uptime. OpenEthereum provides the core infrastructure essential for speedy and reliable services.
|
||||||
[](https://gitter.im/paritytech/parity)
|
|
||||||
[](https://gitter.im/paritytech/parity.js)
|
|
||||||
[](https://gitter.im/paritytech/parity/miners)
|
|
||||||
[](https://gitter.im/paritytech/parity-poa)
|
|
||||||
|
|
||||||
Or join our community on Matrix:
|
- Clean, modular codebase for easy customisation
|
||||||
[](https://riot.im/app/#/group/+parity:matrix.parity.io)
|
- Advanced CLI-based client
|
||||||
|
- Minimal memory and storage footprint
|
||||||
|
- Synchronise in hours, not days with Warp Sync
|
||||||
|
- Modular for light integration into your service or product
|
||||||
|
|
||||||
Official website: https://parity.io
|
## 2. Technical Overview <a id="chapter-002"></a>
|
||||||
|
|
||||||
Be sure to check out [our wiki](https://paritytech.github.io/wiki/) and the [internal documentation](https://paritytech.github.io/parity/ethcore/index.html) for more information.
|
OpenEthereum's goal is to be the fastest, lightest, and most secure Ethereum client. We are developing OpenEthereum using the **Rust programming language**. OpenEthereum is licensed under the GPLv3 and can be used for all your Ethereum needs.
|
||||||
|
|
||||||
----
|
By default, OpenEthereum runs a JSON-RPC HTTP server on port `:8545` and a Web-Sockets server on port `:8546`. This is fully configurable and supports a number of APIs.
|
||||||
|
|
||||||
## About Parity
|
If you run into problems while using OpenEthereum, check out the [old wiki for documentation](https://openethereum.github.io/), feel free to [file an issue in this repository](https://github.com/openethereum/openethereum/issues/new), or hop on our [Discord](https://discord.io/openethereum) chat room to ask a question. We are glad to help!
|
||||||
|
|
||||||
Parity's goal is to be the fastest, lightest, and most secure Ethereum client. We are developing Parity using the sophisticated and cutting-edge Rust programming language. Parity is licensed under the GPLv3, and can be used for all your Ethereum needs.
|
You can download OpenEthereum's latest release at [the releases page](https://github.com/openethereum/openethereum/releases) or follow the instructions below to build from source. Read the [CHANGELOG.md](CHANGELOG.md) for a list of all changes between different versions.
|
||||||
|
|
||||||
Parity comes with a built-in wallet. To access [Parity Wallet](http://web3.site/) simply go to http://web3.site/ (if you don't have access to the internet, but still want to use the service, you can also use http://127.0.0.1:8180/). It includes various functionality allowing you to:
|
## 3. Building <a id="chapter-003"></a>
|
||||||
|
|
||||||
- create and manage your Ethereum accounts;
|
### 3.1 Build Dependencies <a id="chapter-0031"></a>
|
||||||
- manage your Ether and any Ethereum tokens;
|
|
||||||
- create and register your own tokens;
|
|
||||||
- and much more.
|
|
||||||
|
|
||||||
By default, Parity will also run a JSONRPC server on `127.0.0.1:8545` and a websockets server on `127.0.0.1:8546`. This is fully configurable and supports a number of APIs.
|
OpenEthereum requires **latest stable Rust version** to build.
|
||||||
|
|
||||||
If you run into an issue while using Parity, feel free to file one in this repository or hop on our [Gitter](https://gitter.im/paritytech/parity) or [Riot](https://riot.im/app/#/group/+parity:matrix.parity.io) chat room to ask a question. We are glad to help!
|
We recommend installing Rust through [rustup](https://www.rustup.rs/). If you don't already have `rustup`, you can install it like this:
|
||||||
|
|
||||||
**For security-critical issues**, please refer to the security policy outlined in [SECURITY.MD](SECURITY.md).
|
|
||||||
|
|
||||||
Parity's current release is 1.9. You can download it at https://github.com/paritytech/parity/releases or follow the instructions below to build from source.
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
## Build dependencies
|
|
||||||
|
|
||||||
**Parity requires Rust version 1.23.0 to build**
|
|
||||||
|
|
||||||
We recommend installing Rust through [rustup](https://www.rustup.rs/). If you don't already have rustup, you can install it like this:
|
|
||||||
|
|
||||||
- Linux:
|
- Linux:
|
||||||
```bash
|
|
||||||
$ curl https://sh.rustup.rs -sSf | sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Parity also requires `gcc`, `g++`, `libssl-dev`/`openssl`, `libudev-dev` and `pkg-config` packages to be installed.
|
|
||||||
|
|
||||||
- OSX:
|
|
||||||
```bash
|
|
||||||
$ curl https://sh.rustup.rs -sSf | sh
|
|
||||||
```
|
|
||||||
|
|
||||||
`clang` is required. It comes with Xcode command line tools or can be installed with homebrew.
|
|
||||||
|
|
||||||
- Windows
|
|
||||||
Make sure you have Visual Studio 2015 with C++ support installed. Next, download and run the rustup installer from
|
|
||||||
https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe, start "VS2015 x64 Native Tools Command Prompt", and use the following command to install and set up the msvc toolchain:
|
|
||||||
```bash
|
```bash
|
||||||
$ rustup default stable-x86_64-pc-windows-msvc
|
$ curl https://sh.rustup.rs -sSf | sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Once you have rustup, install Parity or download and build from source
|
OpenEthereum also requires `clang` (>= 9.0), `clang++`, `pkg-config`, `file`, `make`, and `cmake` packages to be installed.
|
||||||
|
|
||||||
----
|
- OSX:
|
||||||
|
```bash
|
||||||
|
$ curl https://sh.rustup.rs -sSf | sh
|
||||||
|
```
|
||||||
|
|
||||||
## Install from the snap store
|
`clang` is required. It comes with Xcode command line tools or can be installed with homebrew.
|
||||||
|
|
||||||
In any of the [supported Linux distros](https://snapcraft.io/docs/core/install):
|
- Windows:
|
||||||
|
Make sure you have Visual Studio 2015 with C++ support installed. Next, download and run the `rustup` installer from
|
||||||
|
https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe, start "VS2015 x64 Native Tools Command Prompt", and use the following command to install and set up the `msvc` toolchain:
|
||||||
|
```bash
|
||||||
|
$ rustup default stable-x86_64-pc-windows-msvc
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you have `rustup` installed, then you need to install:
|
||||||
|
* [Perl](https://www.perl.org)
|
||||||
|
* [Yasm](https://yasm.tortall.net)
|
||||||
|
|
||||||
|
Make sure that these binaries are in your `PATH`. After that, you should be able to build OpenEthereum from source.
|
||||||
|
|
||||||
|
### 3.2 Build from Source Code <a id="chapter-0032"></a>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo snap install parity
|
# download OpenEthereum code
|
||||||
```
|
$ git clone https://github.com/openethereum/openethereum
|
||||||
|
$ cd openethereum
|
||||||
Or, if you want to contribute testing the upcoming release:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo snap install parity --beta
|
|
||||||
```
|
|
||||||
|
|
||||||
And to test the latest code landed into the master branch:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo snap install parity --edge
|
|
||||||
```
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
## Build from source
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# download Parity code
|
|
||||||
$ git clone https://github.com/paritytech/parity
|
|
||||||
$ cd parity
|
|
||||||
|
|
||||||
# build in release mode
|
# build in release mode
|
||||||
$ cargo build --release
|
$ cargo build --release --features final
|
||||||
```
|
```
|
||||||
|
|
||||||
This will produce an executable in the `./target/release` subdirectory.
|
This produces an executable in the `./target/release` subdirectory.
|
||||||
|
|
||||||
Note: if cargo fails to parse manifest try:
|
Note: if cargo fails to parse manifest try:
|
||||||
|
|
||||||
@@ -118,62 +104,215 @@ Note: if cargo fails to parse manifest try:
|
|||||||
$ ~/.cargo/bin/cargo build --release
|
$ ~/.cargo/bin/cargo build --release
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: When compiling a crate and you receive the following error:
|
Note, when compiling a crate and you receive errors, it's in most cases your outdated version of Rust, or some of your crates have to be recompiled. Cleaning the repository will most likely solve the issue if you are on the latest stable version of Rust, try:
|
||||||
|
|
||||||
```
|
|
||||||
error: the crate is compiled with the panic strategy `abort` which is incompatible with this crate's strategy of `unwind`
|
|
||||||
```
|
|
||||||
|
|
||||||
Cleaning the repository will most likely solve the issue, try:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cargo clean
|
$ cargo clean
|
||||||
```
|
```
|
||||||
|
|
||||||
This will always compile the latest nightly builds. If you want to build stable or beta, do a
|
This always compiles the latest nightly builds. If you want to build stable, do a
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ git checkout stable
|
$ git checkout stable
|
||||||
```
|
```
|
||||||
|
|
||||||
or
|
### 3.3 Starting OpenEthereum <a id="chapter-0034"></a>
|
||||||
|
|
||||||
|
#### Manually
|
||||||
|
|
||||||
|
To start OpenEthereum manually, just run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ git checkout beta
|
$ ./target/release/openethereum
|
||||||
```
|
```
|
||||||
|
|
||||||
first.
|
so OpenEthereum begins syncing the Ethereum blockchain.
|
||||||
|
|
||||||
----
|
#### Using `systemd` service file
|
||||||
|
|
||||||
## Simple one-line installer for Mac and Ubuntu
|
To start OpenEthereum as a regular user using `systemd` init:
|
||||||
|
|
||||||
```bash
|
1. Copy `./scripts/openethereum.service` to your
|
||||||
bash <(curl https://get.parity.io -Lk)
|
`systemd` user directory (usually `~/.config/systemd/user`).
|
||||||
```
|
2. Copy release to bin folder, write `sudo install ./target/release/openethereum /usr/bin/openethereum`
|
||||||
|
3. To configure OpenEthereum, see [our wiki](https://openethereum.github.io/Configuring-OpenEthereum) for details.
|
||||||
|
|
||||||
The one-line installer always defaults to the latest beta release. To install a stable release, run:
|
## 4. Testing <a id="chapter-004"></a>
|
||||||
|
|
||||||
```bash
|
Download the required test files: `git submodule update --init --recursive`. You can run tests with the following commands:
|
||||||
bash <(curl https://get.parity.io -Lk) -r stable
|
|
||||||
```
|
|
||||||
|
|
||||||
## Start Parity
|
* **All** packages
|
||||||
|
```
|
||||||
|
cargo test --all
|
||||||
|
```
|
||||||
|
|
||||||
### Manually
|
* Specific package
|
||||||
|
```
|
||||||
|
cargo test --package <spec>
|
||||||
|
```
|
||||||
|
|
||||||
To start Parity manually, just run
|
Replace `<spec>` with one of the packages from the [package list](#package-list) (e.g. `cargo test --package evmbin`).
|
||||||
|
|
||||||
```bash
|
You can show your logs in the test output by passing `--nocapture` (i.e. `cargo test --package evmbin -- --nocapture`)
|
||||||
$ ./target/release/parity
|
|
||||||
```
|
|
||||||
|
|
||||||
and Parity will begin syncing the Ethereum blockchain.
|
## 5. Documentation <a id="chapter-005"></a>
|
||||||
|
|
||||||
### Using systemd service file
|
Be sure to [check out our wiki](https://openethereum.github.io/) for more information.
|
||||||
|
|
||||||
To start Parity as a regular user using systemd init:
|
### Viewing documentation for OpenEthereum packages
|
||||||
|
|
||||||
1. Copy `./scripts/parity.service` to your
|
You can generate documentation for OpenEthereum Rust packages that automatically opens in your web browser using [rustdoc with Cargo](https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html#using-rustdoc-with-cargo) (of the The Rustdoc Book), by running the the following commands:
|
||||||
systemd user directory (usually `~/.config/systemd/user`).
|
|
||||||
2. To configure Parity, write a `/etc/parity/config.toml` config file, see [Configuring Parity](https://paritytech.github.io/wiki/Configuring-Parity) for details.
|
* **All** packages
|
||||||
|
```
|
||||||
|
cargo doc --document-private-items --open
|
||||||
|
```
|
||||||
|
|
||||||
|
* Specific package
|
||||||
|
```
|
||||||
|
cargo doc --package <spec> -- --document-private-items --open
|
||||||
|
```
|
||||||
|
|
||||||
|
Use`--document-private-items` to also view private documentation and `--no-deps` to exclude building documentation for dependencies.
|
||||||
|
|
||||||
|
Replacing `<spec>` with one of the following from the details section below (i.e. `cargo doc --package openethereum --open`):
|
||||||
|
|
||||||
|
<a id="package-list"></a>
|
||||||
|
**Package List**
|
||||||
|
<details><p>
|
||||||
|
|
||||||
|
* OpenEthereum Client Application
|
||||||
|
```bash
|
||||||
|
openethereum
|
||||||
|
```
|
||||||
|
* OpenEthereum Account Management, Key Management Tool, and Keys Generator
|
||||||
|
```bash
|
||||||
|
ethcore-accounts, ethkey-cli, ethstore, ethstore-cli
|
||||||
|
```
|
||||||
|
* OpenEthereum Chain Specification
|
||||||
|
```bash
|
||||||
|
chainspec
|
||||||
|
```
|
||||||
|
* OpenEthereum CLI Signer Tool & RPC Client
|
||||||
|
```bash
|
||||||
|
cli-signer parity-rpc-client
|
||||||
|
```
|
||||||
|
* OpenEthereum Ethash & ProgPoW Implementations
|
||||||
|
```bash
|
||||||
|
ethash
|
||||||
|
```
|
||||||
|
* EthCore Library
|
||||||
|
```bash
|
||||||
|
ethcore
|
||||||
|
```
|
||||||
|
* OpenEthereum Blockchain Database, Test Generator, Configuration,
|
||||||
|
Caching, Importing Blocks, and Block Information
|
||||||
|
```bash
|
||||||
|
ethcore-blockchain
|
||||||
|
```
|
||||||
|
* OpenEthereum Contract Calls and Blockchain Service & Registry Information
|
||||||
|
```bash
|
||||||
|
ethcore-call-contract
|
||||||
|
```
|
||||||
|
* OpenEthereum Database Access & Utilities, Database Cache Manager
|
||||||
|
```bash
|
||||||
|
ethcore-db
|
||||||
|
```
|
||||||
|
* OpenEthereum Virtual Machine (EVM) Rust Implementation
|
||||||
|
```bash
|
||||||
|
evm
|
||||||
|
```
|
||||||
|
* OpenEthereum Light Client Implementation
|
||||||
|
```bash
|
||||||
|
ethcore-light
|
||||||
|
```
|
||||||
|
* Smart Contract based Node Filter, Manage Permissions of Network Connections
|
||||||
|
```bash
|
||||||
|
node-filter
|
||||||
|
```
|
||||||
|
* OpenEthereum Client & Network Service Creation & Registration with the I/O Subsystem
|
||||||
|
```bash
|
||||||
|
ethcore-service
|
||||||
|
```
|
||||||
|
* OpenEthereum Blockchain Synchronization
|
||||||
|
```bash
|
||||||
|
ethcore-sync
|
||||||
|
```
|
||||||
|
* OpenEthereum Common Types
|
||||||
|
```bash
|
||||||
|
common-types
|
||||||
|
```
|
||||||
|
* OpenEthereum Virtual Machines (VM) Support Library
|
||||||
|
```bash
|
||||||
|
vm
|
||||||
|
```
|
||||||
|
* OpenEthereum WASM Interpreter
|
||||||
|
```bash
|
||||||
|
wasm
|
||||||
|
```
|
||||||
|
* OpenEthereum WASM Test Runner
|
||||||
|
```bash
|
||||||
|
pwasm-run-test
|
||||||
|
```
|
||||||
|
* OpenEthereum EVM Implementation
|
||||||
|
```bash
|
||||||
|
evmbin
|
||||||
|
```
|
||||||
|
* OpenEthereum JSON Deserialization
|
||||||
|
```bash
|
||||||
|
ethjson
|
||||||
|
```
|
||||||
|
* OpenEthereum State Machine Generalization for Consensus Engines
|
||||||
|
```bash
|
||||||
|
parity-machine
|
||||||
|
```
|
||||||
|
* OpenEthereum Miner Interface
|
||||||
|
```bash
|
||||||
|
ethcore-miner parity-local-store price-info ethcore-stratum using_queue
|
||||||
|
```
|
||||||
|
* OpenEthereum Logger Implementation
|
||||||
|
```bash
|
||||||
|
ethcore-logger
|
||||||
|
```
|
||||||
|
* OpenEthereum JSON-RPC Servers
|
||||||
|
```bash
|
||||||
|
parity-rpc
|
||||||
|
```
|
||||||
|
* OpenEthereum Updater Service
|
||||||
|
```bash
|
||||||
|
parity-updater parity-hash-fetch
|
||||||
|
```
|
||||||
|
* OpenEthereum Core Libraries (`util`)
|
||||||
|
```bash
|
||||||
|
accounts-bloom blooms-db dir eip-712 fake-fetch fastmap fetch ethcore-io
|
||||||
|
journaldb keccak-hasher len-caching-lock memory-cache memzero
|
||||||
|
migration-rocksdb ethcore-network ethcore-network-devp2p panic_hook
|
||||||
|
patricia-trie-ethereum registrar rlp_compress stats
|
||||||
|
time-utils triehash-ethereum unexpected parity-version
|
||||||
|
```
|
||||||
|
|
||||||
|
</p></details>
|
||||||
|
|
||||||
|
## 6. Toolchain <a id="chapter-006"></a>
|
||||||
|
|
||||||
|
In addition to the OpenEthereum client, there are additional tools in this repository available:
|
||||||
|
|
||||||
|
- [evmbin](./evmbin) - OpenEthereum EVM Implementation.
|
||||||
|
- [ethstore](./accounts/ethstore) - OpenEthereum Key Management.
|
||||||
|
- [ethkey](./accounts/ethkey) - OpenEthereum Keys Generator.
|
||||||
|
|
||||||
|
The following tools are available in a separate repository:
|
||||||
|
- [ethabi](https://github.com/openethereum/ethabi) - OpenEthereum Encoding of Function Calls. [Docs here](https://crates.io/crates/ethabi)
|
||||||
|
- [whisper](https://github.com/openethereum/whisper) - OpenEthereum Whisper-v2 PoC Implementation.
|
||||||
|
|
||||||
|
## 7. Contributing <a id="chapter-007"></a>
|
||||||
|
|
||||||
|
An introduction has been provided in the ["So You Want to be a Core Developer" presentation slides by Hernando Castano](http://tiny.cc/contrib-to-parity-eth). Additional guidelines are provided in [CONTRIBUTING](./.github/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
### Contributor Code of Conduct
|
||||||
|
|
||||||
|
[CODE_OF_CONDUCT](./.github/CODE_OF_CONDUCT.md)
|
||||||
|
|
||||||
|
## 8. License <a id="chapter-008"></a>
|
||||||
|
|
||||||
|
[LICENSE](./LICENSE)
|
||||||
|
|||||||
80
SECURITY.md
80
SECURITY.md
@@ -1,80 +0,0 @@
|
|||||||
# Security Policy
|
|
||||||
|
|
||||||
Parity Technologies is committed to resolving security vulnerabilities in our software quickly and carefully. We take the necessary steps to minimize risk, provide timely information, and deliver vulnerability fixes and mitigations required to address security issues.
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
Security vulnerabilities in Parity software should be reported by email to security@parity.io. If you think your report might be eligible for the Parity Bug Bounty Program, your email should be send to bugbounty@parity.io.
|
|
||||||
|
|
||||||
Your report should include the following:
|
|
||||||
|
|
||||||
- your name
|
|
||||||
- description of the vulnerability
|
|
||||||
- attack scenario (if any)
|
|
||||||
- components
|
|
||||||
- reproduction
|
|
||||||
- other details
|
|
||||||
|
|
||||||
Try to include as much information in your report as you can, including a description of the vulnerability, its potential impact, and steps for reproducing it. Be sure to use a descriptive subject line.
|
|
||||||
|
|
||||||
You'll receive a response to your email within two business days indicating the next steps in handling your report. We encourage finders to use encrypted communication channels to protect the confidentiality of vulnerability reports. You can encrypt your report using our public key. This key is [on MIT's key server](https://pgp.mit.edu/pks/lookup?op=get&search=0x5D0F03018D07DE73) server and reproduced below.
|
|
||||||
|
|
||||||
After the initial reply to your report, our team will endeavor to keep you informed of the progress being made towards a fix. These updates will be sent at least every five business days.
|
|
||||||
|
|
||||||
Thank you for taking the time to responsibly disclose any vulnerabilities you find.
|
|
||||||
|
|
||||||
## Responsible Investigation and Reporting
|
|
||||||
|
|
||||||
Responsible investigation and reporting includes, but isn't limited to, the following:
|
|
||||||
|
|
||||||
- Don't violate the privacy of other users, destroy data, etc.
|
|
||||||
- Don’t defraud or harm Parity Technologies Ltd or its users during your research; you should make a good faith effort to not interrupt or degrade our services.
|
|
||||||
- Don't target our physical security measures, or attempt to use social engineering, spam, distributed denial of service (DDOS) attacks, etc.
|
|
||||||
- Initially report the bug only to us and not to anyone else.
|
|
||||||
- Give us a reasonable amount of time to fix the bug before disclosing it to anyone else, and give us adequate written warning before disclosing it to anyone else.
|
|
||||||
- In general, please investigate and report bugs in a way that makes a reasonable, good faith effort not to be disruptive or harmful to us or our users. Otherwise your actions might be interpreted as an attack rather than an effort to be helpful.
|
|
||||||
|
|
||||||
## Bug Bounty Program
|
|
||||||
|
|
||||||
Our Bug Bounty Program allows us to recognise and reward members of the Parity community for helping us find and address significant bugs, in accordance with the terms of the Parity Bug Bounty Program. A detailed description on eligibility, rewards, legal information and terms & conditions for contributors can be found on [our website](https://paritytech.io/bug-bounty.html).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Plaintext PGP Key
|
|
||||||
|
|
||||||
```
|
|
||||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
||||||
|
|
||||||
mQENBFlyIAwBCACe0keNPjgYzZ1Oy/8t3zj/Qw9bHHqrzx7FWy8NbXnYBM19NqOZ
|
|
||||||
DIP7Oe0DvCaf/uruBskCS0iVstHlEFQ2AYe0Ei0REt9lQdy61GylU/DEB3879IG+
|
|
||||||
6FO0SnFeYeerv1/hFI2K6uv8v7PyyVDiiJSW0I1KIs2OBwJicTKmWxLAeQsRgx9G
|
|
||||||
yRGalrVk4KP+6pWTA7k3DxmDZKZyfYV/Ej10NtuzmsemwDbv98HKeomp/kgFOfSy
|
|
||||||
3AZjeCpctlsNqpjUuXa0/HudmH2WLxZ0fz8XeoRh8XM9UudNIecjrDqmAFrt/btQ
|
|
||||||
/3guvlzhFCdhYPVGsUusKMECk/JG+Xx1/1ZjABEBAAG0LFBhcml0eSBTZWN1cml0
|
|
||||||
eSBDb250YWN0IDxzZWN1cml0eUBwYXJpdHkuaW8+iQFUBBMBCAA+FiEE2uUVYCjP
|
|
||||||
N6B8aTiDXQ8DAY0H3nMFAllyIAwCGwMFCQPCZwAFCwkIBwIGFQgJCgsCBBYCAwEC
|
|
||||||
HgECF4AACgkQXQ8DAY0H3nM60wgAkS3A36Zc+upiaxU7tumcGv+an17j7gin0sif
|
|
||||||
+0ELSjVfrXInM6ovai+NhUdcLkJ7tCrKS90fvlaELK5Sg9CXBWCTFccKN4A/B7ey
|
|
||||||
rOg2NPXUecnyBB/XqQgKYH7ujYlOlqBDXMfz6z8Hj6WToxg9PPMGGomyMGh8AWxM
|
|
||||||
3yRPFs5RKt0VKgN++5N00oly5Y8ri5pgCidDvCLYMGTVDHFKwkuc9w6BlWlu1R1e
|
|
||||||
/hXFWUFAP1ffTAul3QwyKhjPn2iotCdxXjvt48KaU8DN4iL7aMBN/ZBKqGS7yRdF
|
|
||||||
D/JbJyaaJ0ZRvFSTSXy/sWY3z1B5mtCPBxco8hqqNfRkCwuZ6LkBDQRZciAMAQgA
|
|
||||||
8BP8xrwe12TOUTqL/Vrbxv/FLdhKh53J6TrPKvC2TEEKOrTNo5ahRq+XOS5E7G2N
|
|
||||||
x3b+fq8gR9BzFcldAx0XWUtGs/Wv++ulaSNqTBxj13J3G3WGsUfMKxRgj//piCUD
|
|
||||||
bCFLQfGZdKk0M1o9QkPVARwwmvCNiNB/l++xGqPtfc44H5jWj3GoGvL2MkShPzrN
|
|
||||||
yN/bJ+m+R5gtFGdInqa5KXBuxxuW25eDKJ+LzjbgUgeC76wNcfOiQHTdMkcupjdO
|
|
||||||
bbGFwo10hcbRAOcZEv6//Zrlmk/6nPxEd2hN20St2bSN0+FqfZ267mWEu3ejsgF8
|
|
||||||
ArdCpv5h4fBvJyNwiTZwIQARAQABiQE8BBgBCAAmFiEE2uUVYCjPN6B8aTiDXQ8D
|
|
||||||
AY0H3nMFAllyIAwCGwwFCQPCZwAACgkQXQ8DAY0H3nNisggAl4fqhRlA34wIb190
|
|
||||||
sqXHVxiCuzPaqS6krE9xAa1+gncX485OtcJNqnjugHm2rFE48lv7oasviuPXuInE
|
|
||||||
/OgVFnXYv9d/Xx2JUeDs+bFTLouCDRY2Unh7KJZasfqnMcCHWcxHx5FvRNZRssaB
|
|
||||||
WTZVo6sizPurGUtbpYe4/OLFhadBqAE0EUmVRFEUMc1YTnu4eLaRBzoWN4d2UWwi
|
|
||||||
LN25RSrVSke7LTSFbgn9ntQrQ2smXSR+cdNkkfRCjFcpUaecvFl9HwIqoyVbT4Ym
|
|
||||||
0hbpbbX/cJdc91tKa+psa29uMeGL/cgL9fAu19yNFRyOTMxjZnvql1X/WE1pLmoP
|
|
||||||
ETBD1Q==
|
|
||||||
=K9Qw
|
|
||||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
||||||
```
|
|
||||||
22
accounts/Cargo.toml
Normal file
22
accounts/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
description = "OpenEthereum Account Management"
|
||||||
|
homepage = "https://github.com/openethereum/openethereum"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
name = "ethcore-accounts"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
common-types = { path = "../ethcore/types" }
|
||||||
|
ethkey = { path = "ethkey" }
|
||||||
|
ethstore = { path = "ethstore" }
|
||||||
|
log = "0.4"
|
||||||
|
parking_lot = "0.7"
|
||||||
|
serde = "1.0"
|
||||||
|
serde_derive = "1.0"
|
||||||
|
serde_json = "1.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
ethereum-types = "0.4"
|
||||||
|
tempdir = "0.3"
|
||||||
21
accounts/ethkey/Cargo.toml
Normal file
21
accounts/ethkey/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
description = "Parity Ethereum Keys Generator"
|
||||||
|
name = "ethkey"
|
||||||
|
version = "0.3.0"
|
||||||
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
edit-distance = "2.0"
|
||||||
|
parity-crypto = "0.3.0"
|
||||||
|
eth-secp256k1 = { git = "https://github.com/paritytech/rust-secp256k1", rev = "ccc06e7480148b723eb44ac56cf4d20eec380b6f" }
|
||||||
|
ethereum-types = "0.4"
|
||||||
|
lazy_static = "1.0"
|
||||||
|
log = "0.4"
|
||||||
|
memzero = { path = "../../util/memzero" }
|
||||||
|
parity-wordlist = "1.3"
|
||||||
|
quick-error = "1.2.2"
|
||||||
|
rand = "0.4"
|
||||||
|
rustc-hex = "1.0"
|
||||||
|
serde = "1.0"
|
||||||
|
serde_derive = "1.0"
|
||||||
|
tiny-keccak = "1.4"
|
||||||
@@ -1,19 +1,12 @@
|
|||||||
# ethkey
|
## ethkey-cli
|
||||||
|
|
||||||
[![Build Status][travis-image]][travis-url]
|
Parity Ethereum keys generator.
|
||||||
|
|
||||||
[travis-image]: https://travis-ci.org/paritytech/ethkey.svg?branch=master
|
|
||||||
[travis-url]: https://travis-ci.org/paritytech/ethkey
|
|
||||||
|
|
||||||
Ethereum keys generator.
|
|
||||||
|
|
||||||
[Documentation](http://paritytech.github.io/ethkey/ethkey/index.html)
|
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
Ethereum keys generator.
|
Parity Ethereum Keys Generator.
|
||||||
Copyright 2016, 2017 Parity Technologies (UK) Ltd
|
Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
ethkey info <secret-or-phrase> [options]
|
ethkey info <secret-or-phrase> [options]
|
||||||
@@ -27,17 +20,17 @@ Usage:
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help Display this message and exit.
|
-h, --help Display this message and exit.
|
||||||
-s, --secret Display only the secret.
|
-s, --secret Display only the secret key.
|
||||||
-p, --public Display only the public.
|
-p, --public Display only the public key.
|
||||||
-a, --address Display only the address.
|
-a, --address Display only the address.
|
||||||
-b, --brain Use parity brain wallet algorithm.
|
-b, --brain Use parity brain wallet algorithm. Not recommended.
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
info Display public and address of the secret.
|
info Display public key and address of the secret.
|
||||||
generate random Generates new random ethereum key.
|
generate random Generates new random Ethereum key.
|
||||||
generate prefix Random generation, but address must start with a prefix.
|
generate prefix Random generation, but address must start with a prefix ("vanity address").
|
||||||
sign Sign message using secret.
|
sign Sign message using a secret key.
|
||||||
verify Verify signer of the signature.
|
verify Verify signer of the signature by public key or address.
|
||||||
recover Try to find brain phrase matching given address from partial phrase.
|
recover Try to find brain phrase matching given address from partial phrase.
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -218,10 +211,10 @@ public: 4e19a5fdae82596e1485c69b687c9cc52b5078e5b0668ef3ce8543cd90e712cb00df822
|
|||||||
address: 00cf3711cbd3a1512570639280758118ba0b2bcb
|
address: 00cf3711cbd3a1512570639280758118ba0b2bcb
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Parity Ethereum toolchain
|
||||||
|
_This project is a part of the Parity Ethereum toolchain._
|
||||||
|
|
||||||
# Parity toolchain
|
- [evmbin](https://github.com/paritytech/parity-ethereum/blob/master/evmbin/) - EVM implementation for Parity Ethereum.
|
||||||
*this project is a part of the parity toolchain*
|
- [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum function calls encoding.
|
||||||
|
- [ethstore](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethstore) - Parity Ethereum key management.
|
||||||
- [**ethkey**](https://github.com/paritytech/ethkey) - Ethereum keys generator and signer.
|
- [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator.
|
||||||
- [**ethstore**](https://github.com/paritytech/ethstore) - Ethereum key management.
|
|
||||||
- [**ethabi**](https://github.com/paritytech/ethabi) - Ethereum function calls encoding.
|
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
[package]
|
[package]
|
||||||
|
description = "Parity Ethereum Keys Generator CLI"
|
||||||
name = "ethkey-cli"
|
name = "ethkey-cli"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
docopt = "0.8"
|
docopt = "1.0"
|
||||||
env_logger = "0.4"
|
env_logger = "0.5"
|
||||||
ethkey = { path = "../" }
|
ethkey = { path = "../" }
|
||||||
panic_hook = { path = "../../util/panic_hook" }
|
panic_hook = { path = "../../../util/panic-hook" }
|
||||||
parity-wordlist="1.2"
|
parity-wordlist="1.3"
|
||||||
rustc-hex = "1.0"
|
rustc-hex = "1.0"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
494
accounts/ethkey/cli/src/main.rs
Normal file
494
accounts/ethkey/cli/src/main.rs
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
extern crate docopt;
|
||||||
|
extern crate env_logger;
|
||||||
|
extern crate ethkey;
|
||||||
|
extern crate panic_hook;
|
||||||
|
extern crate parity_wordlist;
|
||||||
|
extern crate rustc_hex;
|
||||||
|
extern crate serde;
|
||||||
|
extern crate threadpool;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
use std::{env, fmt, io, num::ParseIntError, process, sync};
|
||||||
|
|
||||||
|
use docopt::Docopt;
|
||||||
|
use ethkey::{
|
||||||
|
brain_recover, sign, verify_address, verify_public, Brain, BrainPrefix, Error as EthkeyError,
|
||||||
|
Generator, KeyPair, Prefix, Random,
|
||||||
|
};
|
||||||
|
use rustc_hex::{FromHex, FromHexError};
|
||||||
|
|
||||||
|
const USAGE: &'static str = r#"
|
||||||
|
Parity Ethereum keys generator.
|
||||||
|
Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
ethkey info <secret-or-phrase> [options]
|
||||||
|
ethkey generate random [options]
|
||||||
|
ethkey generate prefix <prefix> [options]
|
||||||
|
ethkey sign <secret> <message>
|
||||||
|
ethkey verify public <public> <signature> <message>
|
||||||
|
ethkey verify address <address> <signature> <message>
|
||||||
|
ethkey recover <address> <known-phrase>
|
||||||
|
ethkey [-h | --help]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help Display this message and exit.
|
||||||
|
-s, --secret Display only the secret key.
|
||||||
|
-p, --public Display only the public key.
|
||||||
|
-a, --address Display only the address.
|
||||||
|
-b, --brain Use parity brain wallet algorithm. Not recommended.
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
info Display public key and address of the secret.
|
||||||
|
generate random Generates new random Ethereum key.
|
||||||
|
generate prefix Random generation, but address must start with a prefix ("vanity address").
|
||||||
|
sign Sign message using a secret key.
|
||||||
|
verify Verify signer of the signature by public key or address.
|
||||||
|
recover Try to find brain phrase matching given address from partial phrase.
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Args {
|
||||||
|
cmd_info: bool,
|
||||||
|
cmd_generate: bool,
|
||||||
|
cmd_random: bool,
|
||||||
|
cmd_prefix: bool,
|
||||||
|
cmd_sign: bool,
|
||||||
|
cmd_verify: bool,
|
||||||
|
cmd_public: bool,
|
||||||
|
cmd_address: bool,
|
||||||
|
cmd_recover: bool,
|
||||||
|
arg_prefix: String,
|
||||||
|
arg_secret: String,
|
||||||
|
arg_secret_or_phrase: String,
|
||||||
|
arg_known_phrase: String,
|
||||||
|
arg_message: String,
|
||||||
|
arg_public: String,
|
||||||
|
arg_address: String,
|
||||||
|
arg_signature: String,
|
||||||
|
flag_secret: bool,
|
||||||
|
flag_public: bool,
|
||||||
|
flag_address: bool,
|
||||||
|
flag_brain: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Error {
|
||||||
|
Ethkey(EthkeyError),
|
||||||
|
FromHex(FromHexError),
|
||||||
|
ParseInt(ParseIntError),
|
||||||
|
Docopt(docopt::Error),
|
||||||
|
Io(io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EthkeyError> for Error {
|
||||||
|
fn from(err: EthkeyError) -> Self {
|
||||||
|
Error::Ethkey(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FromHexError> for Error {
|
||||||
|
fn from(err: FromHexError) -> Self {
|
||||||
|
Error::FromHex(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ParseIntError> for Error {
|
||||||
|
fn from(err: ParseIntError) -> Self {
|
||||||
|
Error::ParseInt(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<docopt::Error> for Error {
|
||||||
|
fn from(err: docopt::Error) -> Self {
|
||||||
|
Error::Docopt(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for Error {
|
||||||
|
fn from(err: io::Error) -> Self {
|
||||||
|
Error::Io(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
match *self {
|
||||||
|
Error::Ethkey(ref e) => write!(f, "{}", e),
|
||||||
|
Error::FromHex(ref e) => write!(f, "{}", e),
|
||||||
|
Error::ParseInt(ref e) => write!(f, "{}", e),
|
||||||
|
Error::Docopt(ref e) => write!(f, "{}", e),
|
||||||
|
Error::Io(ref e) => write!(f, "{}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DisplayMode {
|
||||||
|
KeyPair,
|
||||||
|
Secret,
|
||||||
|
Public,
|
||||||
|
Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DisplayMode {
|
||||||
|
fn new(args: &Args) -> Self {
|
||||||
|
if args.flag_secret {
|
||||||
|
DisplayMode::Secret
|
||||||
|
} else if args.flag_public {
|
||||||
|
DisplayMode::Public
|
||||||
|
} else if args.flag_address {
|
||||||
|
DisplayMode::Address
|
||||||
|
} else {
|
||||||
|
DisplayMode::KeyPair
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
panic_hook::set_abort();
|
||||||
|
env_logger::try_init().expect("Logger initialized only once.");
|
||||||
|
|
||||||
|
match execute(env::args()) {
|
||||||
|
Ok(ok) => println!("{}", ok),
|
||||||
|
Err(Error::Docopt(ref e)) => e.exit(),
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display(result: (KeyPair, Option<String>), mode: DisplayMode) -> String {
|
||||||
|
let keypair = result.0;
|
||||||
|
match mode {
|
||||||
|
DisplayMode::KeyPair => match result.1 {
|
||||||
|
Some(extra_data) => format!("{}\n{}", extra_data, keypair),
|
||||||
|
None => format!("{}", keypair),
|
||||||
|
},
|
||||||
|
DisplayMode::Secret => format!("{:x}", keypair.secret()),
|
||||||
|
DisplayMode::Public => format!("{:x}", keypair.public()),
|
||||||
|
DisplayMode::Address => format!("{:x}", keypair.address()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute<S, I>(command: I) -> Result<String, Error>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = S>,
|
||||||
|
S: AsRef<str>,
|
||||||
|
{
|
||||||
|
let args: Args = Docopt::new(USAGE).and_then(|d| d.argv(command).deserialize())?;
|
||||||
|
|
||||||
|
return if args.cmd_info {
|
||||||
|
let display_mode = DisplayMode::new(&args);
|
||||||
|
|
||||||
|
let result = if args.flag_brain {
|
||||||
|
let phrase = args.arg_secret_or_phrase;
|
||||||
|
let phrase_info = validate_phrase(&phrase);
|
||||||
|
let keypair = Brain::new(phrase)
|
||||||
|
.generate()
|
||||||
|
.expect("Brain wallet generator is infallible; qed");
|
||||||
|
(keypair, Some(phrase_info))
|
||||||
|
} else {
|
||||||
|
let secret = args
|
||||||
|
.arg_secret_or_phrase
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| EthkeyError::InvalidSecret)?;
|
||||||
|
(KeyPair::from_secret(secret)?, None)
|
||||||
|
};
|
||||||
|
Ok(display(result, display_mode))
|
||||||
|
} else if args.cmd_generate {
|
||||||
|
let display_mode = DisplayMode::new(&args);
|
||||||
|
let result = if args.cmd_random {
|
||||||
|
if args.flag_brain {
|
||||||
|
let mut brain = BrainPrefix::new(vec![0], usize::max_value(), BRAIN_WORDS);
|
||||||
|
let keypair = brain.generate()?;
|
||||||
|
let phrase = format!("recovery phrase: {}", brain.phrase());
|
||||||
|
(keypair, Some(phrase))
|
||||||
|
} else {
|
||||||
|
(Random.generate()?, None)
|
||||||
|
}
|
||||||
|
} else if args.cmd_prefix {
|
||||||
|
let prefix = args.arg_prefix.from_hex()?;
|
||||||
|
let brain = args.flag_brain;
|
||||||
|
in_threads(move || {
|
||||||
|
let iterations = 1024;
|
||||||
|
let prefix = prefix.clone();
|
||||||
|
move || {
|
||||||
|
let prefix = prefix.clone();
|
||||||
|
let res = if brain {
|
||||||
|
let mut brain = BrainPrefix::new(prefix, iterations, BRAIN_WORDS);
|
||||||
|
let result = brain.generate();
|
||||||
|
let phrase = format!("recovery phrase: {}", brain.phrase());
|
||||||
|
result.map(|keypair| (keypair, Some(phrase)))
|
||||||
|
} else {
|
||||||
|
let result = Prefix::new(prefix, iterations).generate();
|
||||||
|
result.map(|res| (res, None))
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(res.map(Some).unwrap_or(None))
|
||||||
|
}
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
return Ok(format!("{}", USAGE));
|
||||||
|
};
|
||||||
|
Ok(display(result, display_mode))
|
||||||
|
} else if args.cmd_sign {
|
||||||
|
let secret = args
|
||||||
|
.arg_secret
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| EthkeyError::InvalidSecret)?;
|
||||||
|
let message = args
|
||||||
|
.arg_message
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| EthkeyError::InvalidMessage)?;
|
||||||
|
let signature = sign(&secret, &message)?;
|
||||||
|
Ok(format!("{}", signature))
|
||||||
|
} else if args.cmd_verify {
|
||||||
|
let signature = args
|
||||||
|
.arg_signature
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| EthkeyError::InvalidSignature)?;
|
||||||
|
let message = args
|
||||||
|
.arg_message
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| EthkeyError::InvalidMessage)?;
|
||||||
|
let ok = if args.cmd_public {
|
||||||
|
let public = args
|
||||||
|
.arg_public
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| EthkeyError::InvalidPublic)?;
|
||||||
|
verify_public(&public, &signature, &message)?
|
||||||
|
} else if args.cmd_address {
|
||||||
|
let address = args
|
||||||
|
.arg_address
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| EthkeyError::InvalidAddress)?;
|
||||||
|
verify_address(&address, &signature, &message)?
|
||||||
|
} else {
|
||||||
|
return Ok(format!("{}", USAGE));
|
||||||
|
};
|
||||||
|
Ok(format!("{}", ok))
|
||||||
|
} else if args.cmd_recover {
|
||||||
|
let display_mode = DisplayMode::new(&args);
|
||||||
|
let known_phrase = args.arg_known_phrase;
|
||||||
|
let address = args
|
||||||
|
.arg_address
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| EthkeyError::InvalidAddress)?;
|
||||||
|
let (phrase, keypair) = in_threads(move || {
|
||||||
|
let mut it =
|
||||||
|
brain_recover::PhrasesIterator::from_known_phrase(&known_phrase, BRAIN_WORDS);
|
||||||
|
move || {
|
||||||
|
let mut i = 0;
|
||||||
|
while let Some(phrase) = it.next() {
|
||||||
|
i += 1;
|
||||||
|
|
||||||
|
let keypair = Brain::new(phrase.clone()).generate().unwrap();
|
||||||
|
if keypair.address() == address {
|
||||||
|
return Ok(Some((phrase, keypair)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if i >= 1024 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(EthkeyError::Custom("Couldn't find any results.".into()))
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
Ok(display((keypair, Some(phrase)), display_mode))
|
||||||
|
} else {
|
||||||
|
Ok(format!("{}", USAGE))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const BRAIN_WORDS: usize = 12;
|
||||||
|
|
||||||
|
fn validate_phrase(phrase: &str) -> String {
|
||||||
|
match Brain::validate_phrase(phrase, BRAIN_WORDS) {
|
||||||
|
Ok(()) => format!("The recovery phrase looks correct.\n"),
|
||||||
|
Err(err) => format!("The recover phrase was not generated by Parity: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn in_threads<F, X, O>(prepare: F) -> Result<O, EthkeyError>
|
||||||
|
where
|
||||||
|
O: Send + 'static,
|
||||||
|
X: Send + 'static,
|
||||||
|
F: Fn() -> X,
|
||||||
|
X: FnMut() -> Result<Option<O>, EthkeyError>,
|
||||||
|
{
|
||||||
|
let pool = threadpool::Builder::new().build();
|
||||||
|
|
||||||
|
let (tx, rx) = sync::mpsc::sync_channel(1);
|
||||||
|
let is_done = sync::Arc::new(sync::atomic::AtomicBool::default());
|
||||||
|
|
||||||
|
for _ in 0..pool.max_count() {
|
||||||
|
let is_done = is_done.clone();
|
||||||
|
let tx = tx.clone();
|
||||||
|
let mut task = prepare();
|
||||||
|
pool.execute(move || {
|
||||||
|
loop {
|
||||||
|
if is_done.load(sync::atomic::Ordering::SeqCst) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = match task() {
|
||||||
|
Ok(None) => continue,
|
||||||
|
Ok(Some(v)) => Ok(v),
|
||||||
|
Err(err) => Err(err),
|
||||||
|
};
|
||||||
|
|
||||||
|
// We are interested only in the first response.
|
||||||
|
let _ = tx.send(res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(solution) = rx.recv() {
|
||||||
|
is_done.store(true, sync::atomic::Ordering::SeqCst);
|
||||||
|
return solution;
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(EthkeyError::Custom("No results found.".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::execute;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn info() {
|
||||||
|
let command = vec![
|
||||||
|
"ethkey",
|
||||||
|
"info",
|
||||||
|
"17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55",
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
let expected =
|
||||||
|
"secret: 17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55
|
||||||
|
public: 689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124
|
||||||
|
address: 26d1ec50b4e62c1d1a40d16e7cacc6a6580757d5".to_owned();
|
||||||
|
assert_eq!(execute(command).unwrap(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn brain() {
|
||||||
|
let command = vec!["ethkey", "info", "--brain", "this is sparta"]
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
let expected =
|
||||||
|
"The recover phrase was not generated by Parity: The word 'this' does not come from the dictionary.
|
||||||
|
|
||||||
|
secret: aa22b54c0cb43ee30a014afe5ef3664b1cde299feabca46cd3167a85a57c39f2
|
||||||
|
public: c4c5398da6843632c123f543d714d2d2277716c11ff612b2a2f23c6bda4d6f0327c31cd58c55a9572c3cc141dade0c32747a13b7ef34c241b26c84adbb28fcf4
|
||||||
|
address: 006e27b6a72e1f34c626762f3c4761547aff1421".to_owned();
|
||||||
|
assert_eq!(execute(command).unwrap(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secret() {
|
||||||
|
let command = vec!["ethkey", "info", "--brain", "this is sparta", "--secret"]
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
let expected =
|
||||||
|
"aa22b54c0cb43ee30a014afe5ef3664b1cde299feabca46cd3167a85a57c39f2".to_owned();
|
||||||
|
assert_eq!(execute(command).unwrap(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public() {
|
||||||
|
let command = vec!["ethkey", "info", "--brain", "this is sparta", "--public"]
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
let expected = "c4c5398da6843632c123f543d714d2d2277716c11ff612b2a2f23c6bda4d6f0327c31cd58c55a9572c3cc141dade0c32747a13b7ef34c241b26c84adbb28fcf4".to_owned();
|
||||||
|
assert_eq!(execute(command).unwrap(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn address() {
|
||||||
|
let command = vec!["ethkey", "info", "-b", "this is sparta", "--address"]
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
let expected = "006e27b6a72e1f34c626762f3c4761547aff1421".to_owned();
|
||||||
|
assert_eq!(execute(command).unwrap(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign() {
|
||||||
|
let command = vec![
|
||||||
|
"ethkey",
|
||||||
|
"sign",
|
||||||
|
"17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55",
|
||||||
|
"bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec987",
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
let expected = "c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200".to_owned();
|
||||||
|
assert_eq!(execute(command).unwrap(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_valid_public() {
|
||||||
|
let command = vec!["ethkey", "verify", "public", "689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124", "c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200", "bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec987"]
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
let expected = "true".to_owned();
|
||||||
|
assert_eq!(execute(command).unwrap(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_valid_address() {
|
||||||
|
let command = vec!["ethkey", "verify", "address", "26d1ec50b4e62c1d1a40d16e7cacc6a6580757d5", "c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200", "bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec987"]
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
let expected = "true".to_owned();
|
||||||
|
assert_eq!(execute(command).unwrap(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_invalid() {
|
||||||
|
let command = vec!["ethkey", "verify", "public", "689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124", "c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200", "bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec986"]
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
let expected = "false".to_owned();
|
||||||
|
assert_eq!(execute(command).unwrap(), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
74
accounts/ethkey/src/brain.rs
Normal file
74
accounts/ethkey/src/brain.rs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::{Generator, KeyPair, Secret};
|
||||||
|
use keccak::Keccak256;
|
||||||
|
use parity_wordlist;
|
||||||
|
|
||||||
|
/// Simple brainwallet.
|
||||||
|
pub struct Brain(String);
|
||||||
|
|
||||||
|
impl Brain {
|
||||||
|
pub fn new(s: String) -> Self {
|
||||||
|
Brain(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_phrase(phrase: &str, expected_words: usize) -> Result<(), ::WordlistError> {
|
||||||
|
parity_wordlist::validate_phrase(phrase, expected_words)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Generator for Brain {
|
||||||
|
type Error = ::Void;
|
||||||
|
|
||||||
|
fn generate(&mut self) -> Result<KeyPair, Self::Error> {
|
||||||
|
let seed = self.0.clone();
|
||||||
|
let mut secret = seed.into_bytes().keccak256();
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
loop {
|
||||||
|
secret = secret.keccak256();
|
||||||
|
|
||||||
|
match i > 16384 {
|
||||||
|
false => i += 1,
|
||||||
|
true => {
|
||||||
|
if let Ok(pair) =
|
||||||
|
Secret::from_unsafe_slice(&secret).and_then(KeyPair::from_secret)
|
||||||
|
{
|
||||||
|
if pair.address()[0] == 0 {
|
||||||
|
trace!("Testing: {}, got: {:?}", self.0, pair.address());
|
||||||
|
return Ok(pair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use Brain;
|
||||||
|
use Generator;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_brain() {
|
||||||
|
let words = "this is sparta!".to_owned();
|
||||||
|
let first_keypair = Brain::new(words.clone()).generate().unwrap();
|
||||||
|
let second_keypair = Brain::new(words.clone()).generate().unwrap();
|
||||||
|
assert_eq!(first_keypair.secret(), second_keypair.secret());
|
||||||
|
}
|
||||||
|
}
|
||||||
73
accounts/ethkey/src/brain_prefix.rs
Normal file
73
accounts/ethkey/src/brain_prefix.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::{Brain, Error, Generator, KeyPair};
|
||||||
|
use parity_wordlist as wordlist;
|
||||||
|
|
||||||
|
/// Tries to find brain-seed keypair with address starting with given prefix.
|
||||||
|
pub struct BrainPrefix {
|
||||||
|
prefix: Vec<u8>,
|
||||||
|
iterations: usize,
|
||||||
|
no_of_words: usize,
|
||||||
|
last_phrase: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BrainPrefix {
|
||||||
|
pub fn new(prefix: Vec<u8>, iterations: usize, no_of_words: usize) -> Self {
|
||||||
|
BrainPrefix {
|
||||||
|
prefix,
|
||||||
|
iterations,
|
||||||
|
no_of_words,
|
||||||
|
last_phrase: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn phrase(&self) -> &str {
|
||||||
|
&self.last_phrase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Generator for BrainPrefix {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn generate(&mut self) -> Result<KeyPair, Error> {
|
||||||
|
for _ in 0..self.iterations {
|
||||||
|
let phrase = wordlist::random_phrase(self.no_of_words);
|
||||||
|
let keypair = Brain::new(phrase.clone()).generate().unwrap();
|
||||||
|
if keypair.address().starts_with(&self.prefix) {
|
||||||
|
self.last_phrase = phrase;
|
||||||
|
return Ok(keypair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::Custom("Could not find keypair".into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use BrainPrefix;
|
||||||
|
use Generator;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prefix_generator() {
|
||||||
|
let prefix = vec![0x00u8];
|
||||||
|
let keypair = BrainPrefix::new(prefix.clone(), usize::max_value(), 12)
|
||||||
|
.generate()
|
||||||
|
.unwrap();
|
||||||
|
assert!(keypair.address().starts_with(&prefix));
|
||||||
|
}
|
||||||
|
}
|
||||||
178
accounts/ethkey/src/brain_recover.rs
Normal file
178
accounts/ethkey/src/brain_recover.rs
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use edit_distance::edit_distance;
|
||||||
|
use parity_wordlist;
|
||||||
|
|
||||||
|
use super::{Address, Brain, Generator};
|
||||||
|
|
||||||
|
/// Tries to find a phrase for address, given the number
|
||||||
|
/// of expected words and a partial phrase.
|
||||||
|
///
|
||||||
|
/// Returns `None` if phrase couldn't be found.
|
||||||
|
pub fn brain_recover(
|
||||||
|
address: &Address,
|
||||||
|
known_phrase: &str,
|
||||||
|
expected_words: usize,
|
||||||
|
) -> Option<String> {
|
||||||
|
let it = PhrasesIterator::from_known_phrase(known_phrase, expected_words);
|
||||||
|
for phrase in it {
|
||||||
|
let keypair = Brain::new(phrase.clone())
|
||||||
|
.generate()
|
||||||
|
.expect("Brain wallets are infallible; qed");
|
||||||
|
trace!("Testing: {}, got: {:?}", phrase, keypair.address());
|
||||||
|
if &keypair.address() == address {
|
||||||
|
return Some(phrase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_substitutions(word: &str) -> Vec<&'static str> {
|
||||||
|
let mut words = parity_wordlist::WORDS
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|w| (edit_distance(w, word), w))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
words.sort_by(|a, b| a.0.cmp(&b.0));
|
||||||
|
|
||||||
|
words.into_iter().map(|pair| pair.1).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over possible
|
||||||
|
pub struct PhrasesIterator {
|
||||||
|
words: Vec<Vec<&'static str>>,
|
||||||
|
combinations: u64,
|
||||||
|
indexes: Vec<usize>,
|
||||||
|
has_next: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PhrasesIterator {
|
||||||
|
pub fn from_known_phrase(known_phrase: &str, expected_words: usize) -> Self {
|
||||||
|
let known_words = parity_wordlist::WORDS
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.collect::<HashSet<_>>();
|
||||||
|
let mut words = known_phrase
|
||||||
|
.split(' ')
|
||||||
|
.map(|word| match known_words.get(word) {
|
||||||
|
None => {
|
||||||
|
info!(
|
||||||
|
"Invalid word '{}', looking for potential substitutions.",
|
||||||
|
word
|
||||||
|
);
|
||||||
|
let substitutions = generate_substitutions(word);
|
||||||
|
info!("Closest words: {:?}", &substitutions[..10]);
|
||||||
|
substitutions
|
||||||
|
}
|
||||||
|
Some(word) => vec![*word],
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// add missing words
|
||||||
|
if words.len() < expected_words {
|
||||||
|
let to_add = expected_words - words.len();
|
||||||
|
info!("Number of words is insuficcient adding {} more.", to_add);
|
||||||
|
for _ in 0..to_add {
|
||||||
|
words.push(parity_wordlist::WORDS.iter().cloned().collect());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start searching
|
||||||
|
PhrasesIterator::new(words)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(words: Vec<Vec<&'static str>>) -> Self {
|
||||||
|
let combinations = words.iter().fold(1u64, |acc, x| acc * x.len() as u64);
|
||||||
|
let indexes = words.iter().map(|_| 0).collect();
|
||||||
|
info!("Starting to test {} possible combinations.", combinations);
|
||||||
|
|
||||||
|
PhrasesIterator {
|
||||||
|
words,
|
||||||
|
combinations,
|
||||||
|
indexes,
|
||||||
|
has_next: combinations > 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn combinations(&self) -> u64 {
|
||||||
|
self.combinations
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current(&self) -> String {
|
||||||
|
let mut s = self.words[0][self.indexes[0]].to_owned();
|
||||||
|
for i in 1..self.indexes.len() {
|
||||||
|
s.push(' ');
|
||||||
|
s.push_str(self.words[i][self.indexes[i]]);
|
||||||
|
}
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_index(&mut self) -> bool {
|
||||||
|
let mut pos = self.indexes.len();
|
||||||
|
while pos > 0 {
|
||||||
|
pos -= 1;
|
||||||
|
self.indexes[pos] += 1;
|
||||||
|
if self.indexes[pos] >= self.words[pos].len() {
|
||||||
|
self.indexes[pos] = 0;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for PhrasesIterator {
|
||||||
|
type Item = String;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<String> {
|
||||||
|
if !self.has_next {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let phrase = self.current();
|
||||||
|
self.has_next = self.next_index();
|
||||||
|
Some(phrase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::PhrasesIterator;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_generate_possible_combinations() {
|
||||||
|
let mut it =
|
||||||
|
PhrasesIterator::new(vec![vec!["1", "2", "3"], vec!["test"], vec!["a", "b", "c"]]);
|
||||||
|
|
||||||
|
assert_eq!(it.combinations(), 9);
|
||||||
|
assert_eq!(it.next(), Some("1 test a".to_owned()));
|
||||||
|
assert_eq!(it.next(), Some("1 test b".to_owned()));
|
||||||
|
assert_eq!(it.next(), Some("1 test c".to_owned()));
|
||||||
|
assert_eq!(it.next(), Some("2 test a".to_owned()));
|
||||||
|
assert_eq!(it.next(), Some("2 test b".to_owned()));
|
||||||
|
assert_eq!(it.next(), Some("2 test c".to_owned()));
|
||||||
|
assert_eq!(it.next(), Some("3 test a".to_owned()));
|
||||||
|
assert_eq!(it.next(), Some("3 test b".to_owned()));
|
||||||
|
assert_eq!(it.next(), Some("3 test c".to_owned()));
|
||||||
|
assert_eq!(it.next(), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
202
accounts/ethkey/src/crypto.rs
Normal file
202
accounts/ethkey/src/crypto.rs
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#![allow(deprecated)]
|
||||||
|
|
||||||
|
use parity_crypto::error::SymmError;
|
||||||
|
use secp256k1;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
quick_error! {
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
Secp(e: secp256k1::Error) {
|
||||||
|
display("secp256k1 error: {}", e)
|
||||||
|
cause(e)
|
||||||
|
from()
|
||||||
|
}
|
||||||
|
Io(e: io::Error) {
|
||||||
|
display("i/o error: {}", e)
|
||||||
|
cause(e)
|
||||||
|
from()
|
||||||
|
}
|
||||||
|
InvalidMessage {
|
||||||
|
display("invalid message")
|
||||||
|
}
|
||||||
|
Symm(e: SymmError) {
|
||||||
|
cause(e)
|
||||||
|
from()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ECDH functions
|
||||||
|
pub mod ecdh {
|
||||||
|
use super::Error;
|
||||||
|
use secp256k1::{self, ecdh, key};
|
||||||
|
use Public;
|
||||||
|
use Secret;
|
||||||
|
use SECP256K1;
|
||||||
|
|
||||||
|
/// Agree on a shared secret
|
||||||
|
pub fn agree(secret: &Secret, public: &Public) -> Result<Secret, Error> {
|
||||||
|
let context = &SECP256K1;
|
||||||
|
let pdata = {
|
||||||
|
let mut temp = [4u8; 65];
|
||||||
|
(&mut temp[1..65]).copy_from_slice(&public[0..64]);
|
||||||
|
temp
|
||||||
|
};
|
||||||
|
|
||||||
|
let publ = key::PublicKey::from_slice(context, &pdata)?;
|
||||||
|
let sec = key::SecretKey::from_slice(context, &secret)?;
|
||||||
|
let shared = ecdh::SharedSecret::new_raw(context, &publ, &sec);
|
||||||
|
|
||||||
|
Secret::from_unsafe_slice(&shared[0..32])
|
||||||
|
.map_err(|_| Error::Secp(secp256k1::Error::InvalidSecretKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ECIES function
|
||||||
|
pub mod ecies {
|
||||||
|
use super::{ecdh, Error};
|
||||||
|
use ethereum_types::H128;
|
||||||
|
use parity_crypto::{aes, digest, hmac, is_equal};
|
||||||
|
use Generator;
|
||||||
|
use Public;
|
||||||
|
use Random;
|
||||||
|
use Secret;
|
||||||
|
|
||||||
|
/// Encrypt a message with a public key, writing an HMAC covering both
|
||||||
|
/// the plaintext and authenticated data.
|
||||||
|
///
|
||||||
|
/// Authenticated data may be empty.
|
||||||
|
pub fn encrypt(public: &Public, auth_data: &[u8], plain: &[u8]) -> Result<Vec<u8>, Error> {
|
||||||
|
let r = Random.generate()?;
|
||||||
|
let z = ecdh::agree(r.secret(), public)?;
|
||||||
|
let mut key = [0u8; 32];
|
||||||
|
kdf(&z, &[0u8; 0], &mut key);
|
||||||
|
|
||||||
|
let ekey = &key[0..16];
|
||||||
|
let mkey = hmac::SigKey::sha256(&digest::sha256(&key[16..32]));
|
||||||
|
|
||||||
|
let mut msg = vec![0u8; 1 + 64 + 16 + plain.len() + 32];
|
||||||
|
msg[0] = 0x04u8;
|
||||||
|
{
|
||||||
|
let msgd = &mut msg[1..];
|
||||||
|
msgd[0..64].copy_from_slice(r.public());
|
||||||
|
let iv = H128::random();
|
||||||
|
msgd[64..80].copy_from_slice(&iv);
|
||||||
|
{
|
||||||
|
let cipher = &mut msgd[(64 + 16)..(64 + 16 + plain.len())];
|
||||||
|
aes::encrypt_128_ctr(ekey, &iv, plain, cipher)?;
|
||||||
|
}
|
||||||
|
let mut hmac = hmac::Signer::with(&mkey);
|
||||||
|
{
|
||||||
|
let cipher_iv = &msgd[64..(64 + 16 + plain.len())];
|
||||||
|
hmac.update(cipher_iv);
|
||||||
|
}
|
||||||
|
hmac.update(auth_data);
|
||||||
|
let sig = hmac.sign();
|
||||||
|
msgd[(64 + 16 + plain.len())..].copy_from_slice(&sig);
|
||||||
|
}
|
||||||
|
Ok(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypt a message with a secret key, checking HMAC for ciphertext
|
||||||
|
/// and authenticated data validity.
|
||||||
|
pub fn decrypt(secret: &Secret, auth_data: &[u8], encrypted: &[u8]) -> Result<Vec<u8>, Error> {
|
||||||
|
let meta_len = 1 + 64 + 16 + 32;
|
||||||
|
if encrypted.len() < meta_len || encrypted[0] < 2 || encrypted[0] > 4 {
|
||||||
|
return Err(Error::InvalidMessage); //invalid message: publickey
|
||||||
|
}
|
||||||
|
|
||||||
|
let e = &encrypted[1..];
|
||||||
|
let p = Public::from_slice(&e[0..64]);
|
||||||
|
let z = ecdh::agree(secret, &p)?;
|
||||||
|
let mut key = [0u8; 32];
|
||||||
|
kdf(&z, &[0u8; 0], &mut key);
|
||||||
|
|
||||||
|
let ekey = &key[0..16];
|
||||||
|
let mkey = hmac::SigKey::sha256(&digest::sha256(&key[16..32]));
|
||||||
|
|
||||||
|
let clen = encrypted.len() - meta_len;
|
||||||
|
let cipher_with_iv = &e[64..(64 + 16 + clen)];
|
||||||
|
let cipher_iv = &cipher_with_iv[0..16];
|
||||||
|
let cipher_no_iv = &cipher_with_iv[16..];
|
||||||
|
let msg_mac = &e[(64 + 16 + clen)..];
|
||||||
|
|
||||||
|
// Verify tag
|
||||||
|
let mut hmac = hmac::Signer::with(&mkey);
|
||||||
|
hmac.update(cipher_with_iv);
|
||||||
|
hmac.update(auth_data);
|
||||||
|
let mac = hmac.sign();
|
||||||
|
|
||||||
|
if !is_equal(&mac.as_ref()[..], msg_mac) {
|
||||||
|
return Err(Error::InvalidMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut msg = vec![0u8; clen];
|
||||||
|
aes::decrypt_128_ctr(ekey, cipher_iv, cipher_no_iv, &mut msg[..])?;
|
||||||
|
Ok(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kdf(secret: &Secret, s1: &[u8], dest: &mut [u8]) {
|
||||||
|
// SEC/ISO/Shoup specify counter size SHOULD be equivalent
|
||||||
|
// to size of hash output, however, it also notes that
|
||||||
|
// the 4 bytes is okay. NIST specifies 4 bytes.
|
||||||
|
let mut ctr = 1u32;
|
||||||
|
let mut written = 0usize;
|
||||||
|
while written < dest.len() {
|
||||||
|
let mut hasher = digest::Hasher::sha256();
|
||||||
|
let ctrs = [
|
||||||
|
(ctr >> 24) as u8,
|
||||||
|
(ctr >> 16) as u8,
|
||||||
|
(ctr >> 8) as u8,
|
||||||
|
ctr as u8,
|
||||||
|
];
|
||||||
|
hasher.update(&ctrs);
|
||||||
|
hasher.update(secret);
|
||||||
|
hasher.update(s1);
|
||||||
|
let d = hasher.finish();
|
||||||
|
&mut dest[written..(written + 32)].copy_from_slice(&d);
|
||||||
|
written += 32;
|
||||||
|
ctr += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::ecies;
|
||||||
|
use Generator;
|
||||||
|
use Random;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ecies_shared() {
|
||||||
|
let kp = Random.generate().unwrap();
|
||||||
|
let message = b"So many books, so little time";
|
||||||
|
|
||||||
|
let shared = b"shared";
|
||||||
|
let wrong_shared = b"incorrect";
|
||||||
|
let encrypted = ecies::encrypt(kp.public(), shared, message).unwrap();
|
||||||
|
assert!(encrypted[..] != message[..]);
|
||||||
|
assert_eq!(encrypted[0], 0x04);
|
||||||
|
|
||||||
|
assert!(ecies::decrypt(kp.secret(), wrong_shared, &encrypted).is_err());
|
||||||
|
let decrypted = ecies::decrypt(kp.secret(), shared, &encrypted).unwrap();
|
||||||
|
assert_eq!(decrypted[..message.len()], message[..]);
|
||||||
|
}
|
||||||
|
}
|
||||||
81
accounts/ethkey/src/error.rs
Normal file
81
accounts/ethkey/src/error.rs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::{error, fmt};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Crypto error
|
||||||
|
pub enum Error {
|
||||||
|
/// Invalid secret key
|
||||||
|
InvalidSecret,
|
||||||
|
/// Invalid public key
|
||||||
|
InvalidPublic,
|
||||||
|
/// Invalid address
|
||||||
|
InvalidAddress,
|
||||||
|
/// Invalid EC signature
|
||||||
|
InvalidSignature,
|
||||||
|
/// Invalid AES message
|
||||||
|
InvalidMessage,
|
||||||
|
/// IO Error
|
||||||
|
Io(::std::io::Error),
|
||||||
|
/// Custom
|
||||||
|
Custom(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let msg = match *self {
|
||||||
|
Error::InvalidSecret => "Invalid secret".into(),
|
||||||
|
Error::InvalidPublic => "Invalid public".into(),
|
||||||
|
Error::InvalidAddress => "Invalid address".into(),
|
||||||
|
Error::InvalidSignature => "Invalid EC signature".into(),
|
||||||
|
Error::InvalidMessage => "Invalid AES message".into(),
|
||||||
|
Error::Io(ref err) => format!("I/O error: {}", err),
|
||||||
|
Error::Custom(ref s) => s.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
f.write_fmt(format_args!("Crypto error ({})", msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for Error {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Crypto error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<String> for Error {
|
||||||
|
fn into(self) -> String {
|
||||||
|
format!("{}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<::secp256k1::Error> for Error {
|
||||||
|
fn from(e: ::secp256k1::Error) -> Error {
|
||||||
|
match e {
|
||||||
|
::secp256k1::Error::InvalidMessage => Error::InvalidMessage,
|
||||||
|
::secp256k1::Error::InvalidPublicKey => Error::InvalidPublic,
|
||||||
|
::secp256k1::Error::InvalidSecretKey => Error::InvalidSecret,
|
||||||
|
_ => Error::InvalidSignature,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<::std::io::Error> for Error {
|
||||||
|
fn from(err: ::std::io::Error) -> Error {
|
||||||
|
Error::Io(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
589
accounts/ethkey/src/extended.rs
Normal file
589
accounts/ethkey/src/extended.rs
Normal file
@@ -0,0 +1,589 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Extended keys
|
||||||
|
|
||||||
|
pub use self::derivation::Error as DerivationError;
|
||||||
|
use ethereum_types::H256;
|
||||||
|
use secret::Secret;
|
||||||
|
use Public;
|
||||||
|
|
||||||
|
/// Represents label that can be stored as a part of key derivation
|
||||||
|
pub trait Label {
|
||||||
|
/// Length of the data that label occupies
|
||||||
|
fn len() -> usize;
|
||||||
|
|
||||||
|
/// Store label data to the key derivation sequence
|
||||||
|
/// Must not use more than `len()` bytes from slice
|
||||||
|
fn store(&self, target: &mut [u8]);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Label for u32 {
|
||||||
|
fn len() -> usize {
|
||||||
|
4
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store(&self, target: &mut [u8]) {
|
||||||
|
let bytes = self.to_be_bytes();
|
||||||
|
target[0..4].copy_from_slice(&bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Key derivation over generic label `T`
|
||||||
|
pub enum Derivation<T: Label> {
|
||||||
|
/// Soft key derivation (allow proof of parent)
|
||||||
|
Soft(T),
|
||||||
|
/// Hard key derivation (does not allow proof of parent)
|
||||||
|
Hard(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for Derivation<u32> {
|
||||||
|
fn from(index: u32) -> Self {
|
||||||
|
if index < (2 << 30) {
|
||||||
|
Derivation::Soft(index)
|
||||||
|
} else {
|
||||||
|
Derivation::Hard(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Label for H256 {
|
||||||
|
fn len() -> usize {
|
||||||
|
32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store(&self, target: &mut [u8]) {
|
||||||
|
self.copy_to(&mut target[0..32]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extended secret key, allows deterministic derivation of subsequent keys.
|
||||||
|
pub struct ExtendedSecret {
|
||||||
|
secret: Secret,
|
||||||
|
chain_code: H256,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtendedSecret {
|
||||||
|
/// New extended key from given secret and chain code.
|
||||||
|
pub fn with_code(secret: Secret, chain_code: H256) -> ExtendedSecret {
|
||||||
|
ExtendedSecret {
|
||||||
|
secret: secret,
|
||||||
|
chain_code: chain_code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// New extended key from given secret with the random chain code.
|
||||||
|
pub fn new_random(secret: Secret) -> ExtendedSecret {
|
||||||
|
ExtendedSecret::with_code(secret, H256::random())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// New extended key from given secret.
|
||||||
|
/// Chain code will be derived from the secret itself (in a deterministic way).
|
||||||
|
pub fn new(secret: Secret) -> ExtendedSecret {
|
||||||
|
let chain_code = derivation::chain_code(*secret);
|
||||||
|
ExtendedSecret::with_code(secret, chain_code)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derive new private key
|
||||||
|
pub fn derive<T>(&self, index: Derivation<T>) -> ExtendedSecret
|
||||||
|
where
|
||||||
|
T: Label,
|
||||||
|
{
|
||||||
|
let (derived_key, next_chain_code) =
|
||||||
|
derivation::private(*self.secret, self.chain_code, index);
|
||||||
|
|
||||||
|
let derived_secret = Secret::from(derived_key.0);
|
||||||
|
|
||||||
|
ExtendedSecret::with_code(derived_secret, next_chain_code)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Private key component of the extended key.
|
||||||
|
pub fn as_raw(&self) -> &Secret {
|
||||||
|
&self.secret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extended public key, allows deterministic derivation of subsequent keys.
|
||||||
|
pub struct ExtendedPublic {
|
||||||
|
public: Public,
|
||||||
|
chain_code: H256,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtendedPublic {
|
||||||
|
/// New extended public key from known parent and chain code
|
||||||
|
pub fn new(public: Public, chain_code: H256) -> Self {
|
||||||
|
ExtendedPublic {
|
||||||
|
public: public,
|
||||||
|
chain_code: chain_code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create new extended public key from known secret
|
||||||
|
pub fn from_secret(secret: &ExtendedSecret) -> Result<Self, DerivationError> {
|
||||||
|
Ok(ExtendedPublic::new(
|
||||||
|
derivation::point(**secret.as_raw())?,
|
||||||
|
secret.chain_code.clone(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derive new public key
|
||||||
|
/// Operation is defined only for index belongs [0..2^31)
|
||||||
|
pub fn derive<T>(&self, index: Derivation<T>) -> Result<Self, DerivationError>
|
||||||
|
where
|
||||||
|
T: Label,
|
||||||
|
{
|
||||||
|
let (derived_key, next_chain_code) =
|
||||||
|
derivation::public(self.public, self.chain_code, index)?;
|
||||||
|
Ok(ExtendedPublic::new(derived_key, next_chain_code))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn public(&self) -> &Public {
|
||||||
|
&self.public
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ExtendedKeyPair {
|
||||||
|
secret: ExtendedSecret,
|
||||||
|
public: ExtendedPublic,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtendedKeyPair {
|
||||||
|
pub fn new(secret: Secret) -> Self {
|
||||||
|
let extended_secret = ExtendedSecret::new(secret);
|
||||||
|
let extended_public = ExtendedPublic::from_secret(&extended_secret)
|
||||||
|
.expect("Valid `Secret` always produces valid public; qed");
|
||||||
|
ExtendedKeyPair {
|
||||||
|
secret: extended_secret,
|
||||||
|
public: extended_public,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_code(secret: Secret, public: Public, chain_code: H256) -> Self {
|
||||||
|
ExtendedKeyPair {
|
||||||
|
secret: ExtendedSecret::with_code(secret, chain_code.clone()),
|
||||||
|
public: ExtendedPublic::new(public, chain_code),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_secret(secret: Secret, chain_code: H256) -> Self {
|
||||||
|
let extended_secret = ExtendedSecret::with_code(secret, chain_code);
|
||||||
|
let extended_public = ExtendedPublic::from_secret(&extended_secret)
|
||||||
|
.expect("Valid `Secret` always produces valid public; qed");
|
||||||
|
ExtendedKeyPair {
|
||||||
|
secret: extended_secret,
|
||||||
|
public: extended_public,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_seed(seed: &[u8]) -> Result<ExtendedKeyPair, DerivationError> {
|
||||||
|
let (master_key, chain_code) = derivation::seed_pair(seed);
|
||||||
|
Ok(ExtendedKeyPair::with_secret(
|
||||||
|
Secret::from_unsafe_slice(&*master_key).map_err(|_| DerivationError::InvalidSeed)?,
|
||||||
|
chain_code,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn secret(&self) -> &ExtendedSecret {
|
||||||
|
&self.secret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn public(&self) -> &ExtendedPublic {
|
||||||
|
&self.public
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn derive<T>(&self, index: Derivation<T>) -> Result<Self, DerivationError>
|
||||||
|
where
|
||||||
|
T: Label,
|
||||||
|
{
|
||||||
|
let derived = self.secret.derive(index);
|
||||||
|
|
||||||
|
Ok(ExtendedKeyPair {
|
||||||
|
public: ExtendedPublic::from_secret(&derived)?,
|
||||||
|
secret: derived,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derivation functions for private and public keys
|
||||||
|
// Work is based on BIP0032
|
||||||
|
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
|
||||||
|
mod derivation {
|
||||||
|
use super::{Derivation, Label};
|
||||||
|
use ethereum_types::{H256, H512, U256, U512};
|
||||||
|
use keccak;
|
||||||
|
use math::curve_order;
|
||||||
|
use parity_crypto::hmac;
|
||||||
|
use secp256k1::key::{PublicKey, SecretKey};
|
||||||
|
use SECP256K1;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
InvalidHardenedUse,
|
||||||
|
InvalidPoint,
|
||||||
|
MissingIndex,
|
||||||
|
InvalidSeed,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deterministic derivation of the key using secp256k1 elliptic curve.
|
||||||
|
// Derivation can be either hardened or not.
|
||||||
|
// For hardened derivation, pass u32 index at least 2^31 or custom Derivation::Hard(T) enum
|
||||||
|
//
|
||||||
|
// Can panic if passed `private_key` is not a valid secp256k1 private key
|
||||||
|
// (outside of (0..curve_order()]) field
|
||||||
|
pub fn private<T>(private_key: H256, chain_code: H256, index: Derivation<T>) -> (H256, H256)
|
||||||
|
where
|
||||||
|
T: Label,
|
||||||
|
{
|
||||||
|
match index {
|
||||||
|
Derivation::Soft(index) => private_soft(private_key, chain_code, index),
|
||||||
|
Derivation::Hard(index) => private_hard(private_key, chain_code, index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hmac_pair(data: &[u8], private_key: H256, chain_code: H256) -> (H256, H256) {
|
||||||
|
let private: U256 = private_key.into();
|
||||||
|
|
||||||
|
// produces 512-bit derived hmac (I)
|
||||||
|
let skey = hmac::SigKey::sha512(&*chain_code);
|
||||||
|
let i_512 = hmac::sign(&skey, &data[..]);
|
||||||
|
|
||||||
|
// left most 256 bits are later added to original private key
|
||||||
|
let hmac_key: U256 = H256::from_slice(&i_512[0..32]).into();
|
||||||
|
// right most 256 bits are new chain code for later derivations
|
||||||
|
let next_chain_code = H256::from(&i_512[32..64]);
|
||||||
|
|
||||||
|
let child_key = private_add(hmac_key, private).into();
|
||||||
|
(child_key, next_chain_code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can panic if passed `private_key` is not a valid secp256k1 private key
|
||||||
|
// (outside of (0..curve_order()]) field
|
||||||
|
fn private_soft<T>(private_key: H256, chain_code: H256, index: T) -> (H256, H256)
|
||||||
|
where
|
||||||
|
T: Label,
|
||||||
|
{
|
||||||
|
let mut data = vec![0u8; 33 + T::len()];
|
||||||
|
|
||||||
|
let sec_private = SecretKey::from_slice(&SECP256K1, &*private_key)
|
||||||
|
.expect("Caller should provide valid private key");
|
||||||
|
let sec_public = PublicKey::from_secret_key(&SECP256K1, &sec_private)
|
||||||
|
.expect("Caller should provide valid private key");
|
||||||
|
let public_serialized = sec_public.serialize_vec(&SECP256K1, true);
|
||||||
|
|
||||||
|
// curve point (compressed public key) -- index
|
||||||
|
// 0.33 -- 33..end
|
||||||
|
data[0..33].copy_from_slice(&public_serialized);
|
||||||
|
index.store(&mut data[33..]);
|
||||||
|
|
||||||
|
hmac_pair(&data, private_key, chain_code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deterministic derivation of the key using secp256k1 elliptic curve
|
||||||
|
// This is hardened derivation and does not allow to associate
|
||||||
|
// corresponding public keys of the original and derived private keys
|
||||||
|
fn private_hard<T>(private_key: H256, chain_code: H256, index: T) -> (H256, H256)
|
||||||
|
where
|
||||||
|
T: Label,
|
||||||
|
{
|
||||||
|
let mut data: Vec<u8> = vec![0u8; 33 + T::len()];
|
||||||
|
let private: U256 = private_key.into();
|
||||||
|
|
||||||
|
// 0x00 (padding) -- private_key -- index
|
||||||
|
// 0 -- 1..33 -- 33..end
|
||||||
|
private.to_big_endian(&mut data[1..33]);
|
||||||
|
index.store(&mut data[33..(33 + T::len())]);
|
||||||
|
|
||||||
|
hmac_pair(&data, private_key, chain_code)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn private_add(k1: U256, k2: U256) -> U256 {
|
||||||
|
let sum = U512::from(k1) + U512::from(k2);
|
||||||
|
modulo(sum, curve_order())
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: surely can be optimized
|
||||||
|
fn modulo(u1: U512, u2: U256) -> U256 {
|
||||||
|
let dv = u1 / U512::from(u2);
|
||||||
|
let md = u1 - (dv * U512::from(u2));
|
||||||
|
md.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn public<T>(
|
||||||
|
public_key: H512,
|
||||||
|
chain_code: H256,
|
||||||
|
derivation: Derivation<T>,
|
||||||
|
) -> Result<(H512, H256), Error>
|
||||||
|
where
|
||||||
|
T: Label,
|
||||||
|
{
|
||||||
|
let index = match derivation {
|
||||||
|
Derivation::Soft(index) => index,
|
||||||
|
Derivation::Hard(_) => {
|
||||||
|
return Err(Error::InvalidHardenedUse);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut public_sec_raw = [0u8; 65];
|
||||||
|
public_sec_raw[0] = 4;
|
||||||
|
public_sec_raw[1..65].copy_from_slice(&*public_key);
|
||||||
|
let public_sec =
|
||||||
|
PublicKey::from_slice(&SECP256K1, &public_sec_raw).map_err(|_| Error::InvalidPoint)?;
|
||||||
|
let public_serialized = public_sec.serialize_vec(&SECP256K1, true);
|
||||||
|
|
||||||
|
let mut data = vec![0u8; 33 + T::len()];
|
||||||
|
// curve point (compressed public key) -- index
|
||||||
|
// 0.33 -- 33..end
|
||||||
|
data[0..33].copy_from_slice(&public_serialized);
|
||||||
|
index.store(&mut data[33..(33 + T::len())]);
|
||||||
|
|
||||||
|
// HMAC512SHA produces [derived private(256); new chain code(256)]
|
||||||
|
let skey = hmac::SigKey::sha512(&*chain_code);
|
||||||
|
let i_512 = hmac::sign(&skey, &data[..]);
|
||||||
|
|
||||||
|
let new_private = H256::from(&i_512[0..32]);
|
||||||
|
let new_chain_code = H256::from(&i_512[32..64]);
|
||||||
|
|
||||||
|
// Generated private key can (extremely rarely) be out of secp256k1 key field
|
||||||
|
if curve_order() <= new_private.clone().into() {
|
||||||
|
return Err(Error::MissingIndex);
|
||||||
|
}
|
||||||
|
let new_private_sec = SecretKey::from_slice(&SECP256K1, &*new_private)
|
||||||
|
.expect("Private key belongs to the field [0..CURVE_ORDER) (checked above); So initializing can never fail; qed");
|
||||||
|
let mut new_public = PublicKey::from_secret_key(&SECP256K1, &new_private_sec)
|
||||||
|
.expect("Valid private key produces valid public key");
|
||||||
|
|
||||||
|
// Adding two points on the elliptic curves (combining two public keys)
|
||||||
|
new_public
|
||||||
|
.add_assign(&SECP256K1, &public_sec)
|
||||||
|
.expect("Addition of two valid points produce valid point");
|
||||||
|
|
||||||
|
let serialized = new_public.serialize_vec(&SECP256K1, false);
|
||||||
|
|
||||||
|
Ok((H512::from(&serialized[1..65]), new_chain_code))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sha3(slc: &[u8]) -> H256 {
|
||||||
|
keccak::Keccak256::keccak256(slc).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn chain_code(secret: H256) -> H256 {
|
||||||
|
// 10,000 rounds of sha3
|
||||||
|
let mut running_sha3 = sha3(&*secret);
|
||||||
|
for _ in 0..99999 {
|
||||||
|
running_sha3 = sha3(&*running_sha3);
|
||||||
|
}
|
||||||
|
running_sha3
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn point(secret: H256) -> Result<H512, Error> {
|
||||||
|
let sec = SecretKey::from_slice(&SECP256K1, &*secret).map_err(|_| Error::InvalidPoint)?;
|
||||||
|
let public_sec =
|
||||||
|
PublicKey::from_secret_key(&SECP256K1, &sec).map_err(|_| Error::InvalidPoint)?;
|
||||||
|
let serialized = public_sec.serialize_vec(&SECP256K1, false);
|
||||||
|
Ok(H512::from(&serialized[1..65]))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn seed_pair(seed: &[u8]) -> (H256, H256) {
|
||||||
|
let skey = hmac::SigKey::sha512(b"Bitcoin seed");
|
||||||
|
let i_512 = hmac::sign(&skey, seed);
|
||||||
|
|
||||||
|
let master_key = H256::from_slice(&i_512[0..32]);
|
||||||
|
let chain_code = H256::from_slice(&i_512[32..64]);
|
||||||
|
|
||||||
|
(master_key, chain_code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{derivation, Derivation, ExtendedKeyPair, ExtendedPublic, ExtendedSecret};
|
||||||
|
use ethereum_types::{H128, H256};
|
||||||
|
use secret::Secret;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
fn master_chain_basic() -> (H256, H256) {
|
||||||
|
let seed = H128::from_str("000102030405060708090a0b0c0d0e0f")
|
||||||
|
.expect("Seed should be valid H128")
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
derivation::seed_pair(&*seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_extended<F>(f: F, test_private: H256)
|
||||||
|
where
|
||||||
|
F: Fn(ExtendedSecret) -> ExtendedSecret,
|
||||||
|
{
|
||||||
|
let (private_seed, chain_code) = master_chain_basic();
|
||||||
|
let extended_secret = ExtendedSecret::with_code(Secret::from(private_seed.0), chain_code);
|
||||||
|
let derived = f(extended_secret);
|
||||||
|
assert_eq!(**derived.as_raw(), test_private);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn smoky() {
|
||||||
|
let secret =
|
||||||
|
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||||
|
.unwrap();
|
||||||
|
let extended_secret = ExtendedSecret::with_code(secret.clone(), 0u64.into());
|
||||||
|
|
||||||
|
// hardened
|
||||||
|
assert_eq!(&**extended_secret.as_raw(), &*secret);
|
||||||
|
assert_eq!(
|
||||||
|
&**extended_secret.derive(2147483648.into()).as_raw(),
|
||||||
|
&"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&**extended_secret.derive(2147483649.into()).as_raw(),
|
||||||
|
&"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into()
|
||||||
|
);
|
||||||
|
|
||||||
|
// normal
|
||||||
|
assert_eq!(
|
||||||
|
&**extended_secret.derive(0.into()).as_raw(),
|
||||||
|
&"bf6a74e3f7b36fc4c96a1e12f31abc817f9f5904f5a8fc27713163d1f0b713f6".into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&**extended_secret.derive(1.into()).as_raw(),
|
||||||
|
&"bd4fca9eb1f9c201e9448c1eecd66e302d68d4d313ce895b8c134f512205c1bc".into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&**extended_secret.derive(2.into()).as_raw(),
|
||||||
|
&"86932b542d6cab4d9c65490c7ef502d89ecc0e2a5f4852157649e3251e2a3268".into()
|
||||||
|
);
|
||||||
|
|
||||||
|
let extended_public = ExtendedPublic::from_secret(&extended_secret)
|
||||||
|
.expect("Extended public should be created");
|
||||||
|
let derived_public = extended_public
|
||||||
|
.derive(0.into())
|
||||||
|
.expect("First derivation of public should succeed");
|
||||||
|
assert_eq!(&*derived_public.public(), &"f7b3244c96688f92372bfd4def26dc4151529747bab9f188a4ad34e141d47bd66522ff048bc6f19a0a4429b04318b1a8796c000265b4fa200dae5f6dda92dd94".into());
|
||||||
|
|
||||||
|
let keypair = ExtendedKeyPair::with_secret(
|
||||||
|
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||||
|
.unwrap(),
|
||||||
|
064.into(),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&**keypair
|
||||||
|
.derive(2147483648u32.into())
|
||||||
|
.expect("Derivation of keypair should succeed")
|
||||||
|
.secret()
|
||||||
|
.as_raw(),
|
||||||
|
&"edef54414c03196557cf73774bc97a645c9a1df2164ed34f0c2a78d1375a930c".into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn h256_soft_match() {
|
||||||
|
let secret =
|
||||||
|
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||||
|
.unwrap();
|
||||||
|
let derivation_secret =
|
||||||
|
H256::from_str("51eaf04f9dbbc1417dc97e789edd0c37ecda88bac490434e367ea81b71b7b015")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let extended_secret = ExtendedSecret::with_code(secret.clone(), 0u64.into());
|
||||||
|
let extended_public = ExtendedPublic::from_secret(&extended_secret)
|
||||||
|
.expect("Extended public should be created");
|
||||||
|
|
||||||
|
let derived_secret0 = extended_secret.derive(Derivation::Soft(derivation_secret));
|
||||||
|
let derived_public0 = extended_public
|
||||||
|
.derive(Derivation::Soft(derivation_secret))
|
||||||
|
.expect("First derivation of public should succeed");
|
||||||
|
|
||||||
|
let public_from_secret0 = ExtendedPublic::from_secret(&derived_secret0)
|
||||||
|
.expect("Extended public should be created");
|
||||||
|
|
||||||
|
assert_eq!(public_from_secret0.public(), derived_public0.public());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn h256_hard() {
|
||||||
|
let secret =
|
||||||
|
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||||
|
.unwrap();
|
||||||
|
let derivation_secret =
|
||||||
|
H256::from_str("51eaf04f9dbbc1417dc97e789edd0c37ecda88bac490434e367ea81b71b7b015")
|
||||||
|
.unwrap();
|
||||||
|
let extended_secret = ExtendedSecret::with_code(secret.clone(), 1u64.into());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
&**extended_secret
|
||||||
|
.derive(Derivation::Hard(derivation_secret))
|
||||||
|
.as_raw(),
|
||||||
|
&"2bc2d696fb744d77ff813b4a1ef0ad64e1e5188b622c54ba917acc5ebc7c5486".into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn match_() {
|
||||||
|
let secret =
|
||||||
|
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||||
|
.unwrap();
|
||||||
|
let extended_secret = ExtendedSecret::with_code(secret.clone(), 1.into());
|
||||||
|
let extended_public = ExtendedPublic::from_secret(&extended_secret)
|
||||||
|
.expect("Extended public should be created");
|
||||||
|
|
||||||
|
let derived_secret0 = extended_secret.derive(0.into());
|
||||||
|
let derived_public0 = extended_public
|
||||||
|
.derive(0.into())
|
||||||
|
.expect("First derivation of public should succeed");
|
||||||
|
|
||||||
|
let public_from_secret0 = ExtendedPublic::from_secret(&derived_secret0)
|
||||||
|
.expect("Extended public should be created");
|
||||||
|
|
||||||
|
assert_eq!(public_from_secret0.public(), derived_public0.public());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_seeds() {
|
||||||
|
let seed = H128::from_str("000102030405060708090a0b0c0d0e0f")
|
||||||
|
.expect("Seed should be valid H128")
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
// private key from bitcoin test vector
|
||||||
|
// xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs
|
||||||
|
let test_private =
|
||||||
|
H256::from_str("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
|
||||||
|
.expect("Private should be decoded ok");
|
||||||
|
|
||||||
|
let (private_seed, _) = derivation::seed_pair(&*seed);
|
||||||
|
|
||||||
|
assert_eq!(private_seed, test_private);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vector_1() {
|
||||||
|
// xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7
|
||||||
|
// H(0)
|
||||||
|
test_extended(
|
||||||
|
|secret| secret.derive(2147483648.into()),
|
||||||
|
H256::from_str("edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea")
|
||||||
|
.expect("Private should be decoded ok"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vector_2() {
|
||||||
|
// xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs
|
||||||
|
// H(0)/1
|
||||||
|
test_extended(
|
||||||
|
|secret| secret.derive(2147483648.into()).derive(1.into()),
|
||||||
|
H256::from_str("3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368")
|
||||||
|
.expect("Private should be decoded ok"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
accounts/ethkey/src/keccak.rs
Normal file
33
accounts/ethkey/src/keccak.rs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use tiny_keccak::Keccak;
|
||||||
|
|
||||||
|
pub trait Keccak256<T> {
|
||||||
|
fn keccak256(&self) -> T
|
||||||
|
where
|
||||||
|
T: Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Keccak256<[u8; 32]> for [u8] {
|
||||||
|
fn keccak256(&self) -> [u8; 32] {
|
||||||
|
let mut keccak = Keccak::new_keccak256();
|
||||||
|
let mut result = [0u8; 32];
|
||||||
|
keccak.update(self);
|
||||||
|
keccak.finalize(&mut result);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
120
accounts/ethkey/src/keypair.rs
Normal file
120
accounts/ethkey/src/keypair.rs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::{Address, Error, Public, Secret, SECP256K1};
|
||||||
|
use keccak::Keccak256;
|
||||||
|
use rustc_hex::ToHex;
|
||||||
|
use secp256k1::key;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
pub fn public_to_address(public: &Public) -> Address {
|
||||||
|
let hash = public.keccak256();
|
||||||
|
let mut result = Address::default();
|
||||||
|
result.copy_from_slice(&hash[12..]);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
/// secp256k1 key pair
|
||||||
|
pub struct KeyPair {
|
||||||
|
secret: Secret,
|
||||||
|
public: Public,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for KeyPair {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
writeln!(f, "secret: {}", self.secret.to_hex())?;
|
||||||
|
writeln!(f, "public: {}", self.public.to_hex())?;
|
||||||
|
write!(f, "address: {}", self.address().to_hex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyPair {
|
||||||
|
/// Create a pair from secret key
|
||||||
|
pub fn from_secret(secret: Secret) -> Result<KeyPair, Error> {
|
||||||
|
let context = &SECP256K1;
|
||||||
|
let s: key::SecretKey = key::SecretKey::from_slice(context, &secret[..])?;
|
||||||
|
let pub_key = key::PublicKey::from_secret_key(context, &s)?;
|
||||||
|
let serialized = pub_key.serialize_vec(context, false);
|
||||||
|
|
||||||
|
let mut public = Public::default();
|
||||||
|
public.copy_from_slice(&serialized[1..65]);
|
||||||
|
|
||||||
|
let keypair = KeyPair {
|
||||||
|
secret: secret,
|
||||||
|
public: public,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(keypair)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_secret_slice(slice: &[u8]) -> Result<KeyPair, Error> {
|
||||||
|
Self::from_secret(Secret::from_unsafe_slice(slice)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_keypair(sec: key::SecretKey, publ: key::PublicKey) -> Self {
|
||||||
|
let context = &SECP256K1;
|
||||||
|
let serialized = publ.serialize_vec(context, false);
|
||||||
|
let secret = Secret::from(sec);
|
||||||
|
let mut public = Public::default();
|
||||||
|
public.copy_from_slice(&serialized[1..65]);
|
||||||
|
|
||||||
|
KeyPair {
|
||||||
|
secret: secret,
|
||||||
|
public: public,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn secret(&self) -> &Secret {
|
||||||
|
&self.secret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn public(&self) -> &Public {
|
||||||
|
&self.public
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn address(&self) -> Address {
|
||||||
|
public_to_address(&self.public)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::str::FromStr;
|
||||||
|
use KeyPair;
|
||||||
|
use Secret;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_secret() {
|
||||||
|
let secret =
|
||||||
|
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||||
|
.unwrap();
|
||||||
|
let _ = KeyPair::from_secret(secret).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keypair_display() {
|
||||||
|
let expected =
|
||||||
|
"secret: a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65
|
||||||
|
public: 8ce0db0b0359ffc5866ba61903cc2518c3675ef2cf380a7e54bde7ea20e6fa1ab45b7617346cd11b7610001ee6ae5b0155c41cad9527cbcdff44ec67848943a4
|
||||||
|
address: 5b073e9233944b5e729e46d618f0d8edf3d9c34a".to_owned();
|
||||||
|
let secret =
|
||||||
|
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||||
|
.unwrap();
|
||||||
|
let kp = KeyPair::from_secret(secret).unwrap();
|
||||||
|
assert_eq!(format!("{}", kp), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
89
accounts/ethkey/src/lib.rs
Normal file
89
accounts/ethkey/src/lib.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// #![warn(missing_docs)]
|
||||||
|
|
||||||
|
extern crate edit_distance;
|
||||||
|
extern crate ethereum_types;
|
||||||
|
extern crate memzero;
|
||||||
|
extern crate parity_crypto;
|
||||||
|
extern crate parity_wordlist;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate quick_error;
|
||||||
|
extern crate rand;
|
||||||
|
extern crate rustc_hex;
|
||||||
|
extern crate secp256k1;
|
||||||
|
extern crate serde;
|
||||||
|
extern crate tiny_keccak;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
mod brain;
|
||||||
|
mod brain_prefix;
|
||||||
|
mod error;
|
||||||
|
mod extended;
|
||||||
|
mod keccak;
|
||||||
|
mod keypair;
|
||||||
|
mod password;
|
||||||
|
mod prefix;
|
||||||
|
mod random;
|
||||||
|
mod secret;
|
||||||
|
mod signature;
|
||||||
|
|
||||||
|
pub mod brain_recover;
|
||||||
|
pub mod crypto;
|
||||||
|
pub mod math;
|
||||||
|
|
||||||
|
pub use self::{
|
||||||
|
brain::Brain,
|
||||||
|
brain_prefix::BrainPrefix,
|
||||||
|
error::Error,
|
||||||
|
extended::{Derivation, DerivationError, ExtendedKeyPair, ExtendedPublic, ExtendedSecret},
|
||||||
|
keypair::{public_to_address, KeyPair},
|
||||||
|
math::public_is_valid,
|
||||||
|
parity_wordlist::Error as WordlistError,
|
||||||
|
password::Password,
|
||||||
|
prefix::Prefix,
|
||||||
|
random::Random,
|
||||||
|
secret::Secret,
|
||||||
|
signature::{recover, sign, verify_address, verify_public, Signature},
|
||||||
|
};
|
||||||
|
|
||||||
|
use ethereum_types::H256;
|
||||||
|
|
||||||
|
pub use ethereum_types::{Address, Public};
|
||||||
|
pub type Message = H256;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref SECP256K1: secp256k1::Secp256k1 = secp256k1::Secp256k1::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Uninstantiatable error type for infallible generators.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Void {}
|
||||||
|
|
||||||
|
/// Generates new keypair.
|
||||||
|
pub trait Generator {
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
/// Should be called to generate new keypair.
|
||||||
|
fn generate(&mut self) -> Result<KeyPair, Self::Error>;
|
||||||
|
}
|
||||||
134
accounts/ethkey/src/math.rs
Normal file
134
accounts/ethkey/src/math.rs
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::{Error, Public, Secret, SECP256K1};
|
||||||
|
use ethereum_types::{H256, U256};
|
||||||
|
use secp256k1::{
|
||||||
|
constants::{CURVE_ORDER, GENERATOR_X, GENERATOR_Y},
|
||||||
|
key,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Whether the public key is valid.
|
||||||
|
pub fn public_is_valid(public: &Public) -> bool {
|
||||||
|
to_secp256k1_public(public)
|
||||||
|
.ok()
|
||||||
|
.map_or(false, |p| p.is_valid())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inplace multiply public key by secret key (EC point * scalar)
|
||||||
|
pub fn public_mul_secret(public: &mut Public, secret: &Secret) -> Result<(), Error> {
|
||||||
|
let key_secret = secret.to_secp256k1_secret()?;
|
||||||
|
let mut key_public = to_secp256k1_public(public)?;
|
||||||
|
key_public.mul_assign(&SECP256K1, &key_secret)?;
|
||||||
|
set_public(public, &key_public);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inplace add one public key to another (EC point + EC point)
|
||||||
|
pub fn public_add(public: &mut Public, other: &Public) -> Result<(), Error> {
|
||||||
|
let mut key_public = to_secp256k1_public(public)?;
|
||||||
|
let other_public = to_secp256k1_public(other)?;
|
||||||
|
key_public.add_assign(&SECP256K1, &other_public)?;
|
||||||
|
set_public(public, &key_public);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inplace sub one public key from another (EC point - EC point)
|
||||||
|
pub fn public_sub(public: &mut Public, other: &Public) -> Result<(), Error> {
|
||||||
|
let mut key_neg_other = to_secp256k1_public(other)?;
|
||||||
|
key_neg_other.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
|
||||||
|
|
||||||
|
let mut key_public = to_secp256k1_public(public)?;
|
||||||
|
key_public.add_assign(&SECP256K1, &key_neg_other)?;
|
||||||
|
set_public(public, &key_public);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replace public key with its negation (EC point = - EC point)
|
||||||
|
pub fn public_negate(public: &mut Public) -> Result<(), Error> {
|
||||||
|
let mut key_public = to_secp256k1_public(public)?;
|
||||||
|
key_public.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
|
||||||
|
set_public(public, &key_public);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return base point of secp256k1
|
||||||
|
pub fn generation_point() -> Public {
|
||||||
|
let mut public_sec_raw = [0u8; 65];
|
||||||
|
public_sec_raw[0] = 4;
|
||||||
|
public_sec_raw[1..33].copy_from_slice(&GENERATOR_X);
|
||||||
|
public_sec_raw[33..65].copy_from_slice(&GENERATOR_Y);
|
||||||
|
|
||||||
|
let public_key = key::PublicKey::from_slice(&SECP256K1, &public_sec_raw)
|
||||||
|
.expect("constructing using predefined constants; qed");
|
||||||
|
let mut public = Public::default();
|
||||||
|
set_public(&mut public, &public_key);
|
||||||
|
public
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return secp256k1 elliptic curve order
|
||||||
|
pub fn curve_order() -> U256 {
|
||||||
|
H256::from_slice(&CURVE_ORDER).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_secp256k1_public(public: &Public) -> Result<key::PublicKey, Error> {
|
||||||
|
let public_data = {
|
||||||
|
let mut temp = [4u8; 65];
|
||||||
|
(&mut temp[1..65]).copy_from_slice(&public[0..64]);
|
||||||
|
temp
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(key::PublicKey::from_slice(&SECP256K1, &public_data)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_public(public: &mut Public, key_public: &key::PublicKey) {
|
||||||
|
let key_public_serialized = key_public.serialize_vec(&SECP256K1, false);
|
||||||
|
public.copy_from_slice(&key_public_serialized[1..65]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{
|
||||||
|
super::{Generator, Random},
|
||||||
|
public_add, public_sub,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_addition_is_commutative() {
|
||||||
|
let public1 = Random.generate().unwrap().public().clone();
|
||||||
|
let public2 = Random.generate().unwrap().public().clone();
|
||||||
|
|
||||||
|
let mut left = public1.clone();
|
||||||
|
public_add(&mut left, &public2).unwrap();
|
||||||
|
|
||||||
|
let mut right = public2.clone();
|
||||||
|
public_add(&mut right, &public1).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_addition_is_reversible_with_subtraction() {
|
||||||
|
let public1 = Random.generate().unwrap().public().clone();
|
||||||
|
let public2 = Random.generate().unwrap().public().clone();
|
||||||
|
|
||||||
|
let mut sum = public1.clone();
|
||||||
|
public_add(&mut sum, &public2).unwrap();
|
||||||
|
public_sub(&mut sum, &public2).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(sum, public1);
|
||||||
|
}
|
||||||
|
}
|
||||||
59
accounts/ethkey/src/password.rs
Normal file
59
accounts/ethkey/src/password.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::{fmt, ptr};
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct Password(String);
|
||||||
|
|
||||||
|
impl fmt::Debug for Password {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "Password(******)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Password {
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
self.0.as_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
self.0.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom drop impl to zero out memory.
|
||||||
|
impl Drop for Password {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
for byte_ref in self.0.as_mut_vec() {
|
||||||
|
ptr::write_volatile(byte_ref, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Password {
|
||||||
|
fn from(s: String) -> Password {
|
||||||
|
Password(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for Password {
|
||||||
|
fn from(s: &'a str) -> Password {
|
||||||
|
Password::from(String::from(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
62
accounts/ethkey/src/prefix.rs
Normal file
62
accounts/ethkey/src/prefix.rs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::{Error, Generator, KeyPair, Random};
|
||||||
|
|
||||||
|
/// Tries to find keypair with address starting with given prefix.
|
||||||
|
pub struct Prefix {
|
||||||
|
prefix: Vec<u8>,
|
||||||
|
iterations: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Prefix {
|
||||||
|
pub fn new(prefix: Vec<u8>, iterations: usize) -> Self {
|
||||||
|
Prefix {
|
||||||
|
prefix: prefix,
|
||||||
|
iterations: iterations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Generator for Prefix {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn generate(&mut self) -> Result<KeyPair, Error> {
|
||||||
|
for _ in 0..self.iterations {
|
||||||
|
let keypair = Random.generate()?;
|
||||||
|
if keypair.address().starts_with(&self.prefix) {
|
||||||
|
return Ok(keypair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::Custom("Could not find keypair".into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use Generator;
|
||||||
|
use Prefix;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prefix_generator() {
|
||||||
|
let prefix = vec![0xffu8];
|
||||||
|
let keypair = Prefix::new(prefix.clone(), usize::max_value())
|
||||||
|
.generate()
|
||||||
|
.unwrap();
|
||||||
|
assert!(keypair.address().starts_with(&prefix));
|
||||||
|
}
|
||||||
|
}
|
||||||
45
accounts/ethkey/src/random.rs
Normal file
45
accounts/ethkey/src/random.rs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::{Generator, KeyPair, SECP256K1};
|
||||||
|
use rand::os::OsRng;
|
||||||
|
|
||||||
|
/// Randomly generates new keypair, instantiating the RNG each time.
|
||||||
|
pub struct Random;
|
||||||
|
|
||||||
|
impl Generator for Random {
|
||||||
|
type Error = ::std::io::Error;
|
||||||
|
|
||||||
|
fn generate(&mut self) -> Result<KeyPair, Self::Error> {
|
||||||
|
let mut rng = OsRng::new()?;
|
||||||
|
match rng.generate() {
|
||||||
|
Ok(pair) => Ok(pair),
|
||||||
|
Err(void) => match void {}, // LLVM unreachable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Generator for OsRng {
|
||||||
|
type Error = ::Void;
|
||||||
|
|
||||||
|
fn generate(&mut self) -> Result<KeyPair, Self::Error> {
|
||||||
|
let (sec, publ) = SECP256K1
|
||||||
|
.generate_keypair(self)
|
||||||
|
.expect("context always created with full capabilities; qed");
|
||||||
|
|
||||||
|
Ok(KeyPair::from_keypair(sec, publ))
|
||||||
|
}
|
||||||
|
}
|
||||||
322
accounts/ethkey/src/secret.rs
Normal file
322
accounts/ethkey/src/secret.rs
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use ethereum_types::H256;
|
||||||
|
use memzero::Memzero;
|
||||||
|
use rustc_hex::ToHex;
|
||||||
|
use secp256k1::{constants::SECRET_KEY_SIZE as SECP256K1_SECRET_KEY_SIZE, key};
|
||||||
|
use std::{fmt, ops::Deref, str::FromStr};
|
||||||
|
use Error;
|
||||||
|
use SECP256K1;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
|
pub struct Secret {
|
||||||
|
inner: Memzero<H256>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToHex for Secret {
|
||||||
|
fn to_hex(&self) -> String {
|
||||||
|
format!("{:x}", *self.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::LowerHex for Secret {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.inner.fmt(fmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Secret {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.inner.fmt(fmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Secret {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
fmt,
|
||||||
|
"Secret: 0x{:x}{:x}..{:x}{:x}",
|
||||||
|
self.inner[0], self.inner[1], self.inner[30], self.inner[31]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Secret {
|
||||||
|
/// Creates a `Secret` from the given slice, returning `None` if the slice length != 32.
|
||||||
|
pub fn from_slice(key: &[u8]) -> Option<Self> {
|
||||||
|
if key.len() != 32 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut h = H256::default();
|
||||||
|
h.copy_from_slice(&key[0..32]);
|
||||||
|
Some(Secret {
|
||||||
|
inner: Memzero::from(h),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates zero key, which is invalid for crypto operations, but valid for math operation.
|
||||||
|
pub fn zero() -> Self {
|
||||||
|
Secret {
|
||||||
|
inner: Memzero::from(H256::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Imports and validates the key.
|
||||||
|
pub fn from_unsafe_slice(key: &[u8]) -> Result<Self, Error> {
|
||||||
|
let secret = key::SecretKey::from_slice(&super::SECP256K1, key)?;
|
||||||
|
Ok(secret.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks validity of this key.
|
||||||
|
pub fn check_validity(&self) -> Result<(), Error> {
|
||||||
|
self.to_secp256k1_secret().map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inplace add one secret key to another (scalar + scalar)
|
||||||
|
pub fn add(&mut self, other: &Secret) -> Result<(), Error> {
|
||||||
|
match (self.is_zero(), other.is_zero()) {
|
||||||
|
(true, true) | (false, true) => Ok(()),
|
||||||
|
(true, false) => {
|
||||||
|
*self = other.clone();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
(false, false) => {
|
||||||
|
let mut key_secret = self.to_secp256k1_secret()?;
|
||||||
|
let other_secret = other.to_secp256k1_secret()?;
|
||||||
|
key_secret.add_assign(&SECP256K1, &other_secret)?;
|
||||||
|
|
||||||
|
*self = key_secret.into();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inplace subtract one secret key from another (scalar - scalar)
|
||||||
|
pub fn sub(&mut self, other: &Secret) -> Result<(), Error> {
|
||||||
|
match (self.is_zero(), other.is_zero()) {
|
||||||
|
(true, true) | (false, true) => Ok(()),
|
||||||
|
(true, false) => {
|
||||||
|
*self = other.clone();
|
||||||
|
self.neg()
|
||||||
|
}
|
||||||
|
(false, false) => {
|
||||||
|
let mut key_secret = self.to_secp256k1_secret()?;
|
||||||
|
let mut other_secret = other.to_secp256k1_secret()?;
|
||||||
|
other_secret.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
|
||||||
|
key_secret.add_assign(&SECP256K1, &other_secret)?;
|
||||||
|
|
||||||
|
*self = key_secret.into();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inplace decrease secret key (scalar - 1)
|
||||||
|
pub fn dec(&mut self) -> Result<(), Error> {
|
||||||
|
match self.is_zero() {
|
||||||
|
true => {
|
||||||
|
*self = key::MINUS_ONE_KEY.into();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
let mut key_secret = self.to_secp256k1_secret()?;
|
||||||
|
key_secret.add_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
|
||||||
|
|
||||||
|
*self = key_secret.into();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inplace multiply one secret key to another (scalar * scalar)
|
||||||
|
pub fn mul(&mut self, other: &Secret) -> Result<(), Error> {
|
||||||
|
match (self.is_zero(), other.is_zero()) {
|
||||||
|
(true, true) | (true, false) => Ok(()),
|
||||||
|
(false, true) => {
|
||||||
|
*self = Self::zero();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
(false, false) => {
|
||||||
|
let mut key_secret = self.to_secp256k1_secret()?;
|
||||||
|
let other_secret = other.to_secp256k1_secret()?;
|
||||||
|
key_secret.mul_assign(&SECP256K1, &other_secret)?;
|
||||||
|
|
||||||
|
*self = key_secret.into();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inplace negate secret key (-scalar)
|
||||||
|
pub fn neg(&mut self) -> Result<(), Error> {
|
||||||
|
match self.is_zero() {
|
||||||
|
true => Ok(()),
|
||||||
|
false => {
|
||||||
|
let mut key_secret = self.to_secp256k1_secret()?;
|
||||||
|
key_secret.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
|
||||||
|
|
||||||
|
*self = key_secret.into();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inplace inverse secret key (1 / scalar)
|
||||||
|
pub fn inv(&mut self) -> Result<(), Error> {
|
||||||
|
let mut key_secret = self.to_secp256k1_secret()?;
|
||||||
|
key_secret.inv_assign(&SECP256K1)?;
|
||||||
|
|
||||||
|
*self = key_secret.into();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute power of secret key inplace (secret ^ pow).
|
||||||
|
/// This function is not intended to be used with large powers.
|
||||||
|
pub fn pow(&mut self, pow: usize) -> Result<(), Error> {
|
||||||
|
if self.is_zero() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
match pow {
|
||||||
|
0 => *self = key::ONE_KEY.into(),
|
||||||
|
1 => (),
|
||||||
|
_ => {
|
||||||
|
let c = self.clone();
|
||||||
|
for _ in 1..pow {
|
||||||
|
self.mul(&c)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create `secp256k1::key::SecretKey` based on this secret
|
||||||
|
pub fn to_secp256k1_secret(&self) -> Result<key::SecretKey, Error> {
|
||||||
|
Ok(key::SecretKey::from_slice(&SECP256K1, &self[..])?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Secret {
|
||||||
|
type Err = Error;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(H256::from_str(s)
|
||||||
|
.map_err(|e| Error::Custom(format!("{:?}", e)))?
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<[u8; 32]> for Secret {
|
||||||
|
fn from(k: [u8; 32]) -> Self {
|
||||||
|
Secret {
|
||||||
|
inner: Memzero::from(H256(k)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<H256> for Secret {
|
||||||
|
fn from(s: H256) -> Self {
|
||||||
|
s.0.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for Secret {
|
||||||
|
fn from(s: &'static str) -> Self {
|
||||||
|
s.parse().expect(&format!(
|
||||||
|
"invalid string literal for {}: '{}'",
|
||||||
|
stringify!(Self),
|
||||||
|
s
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<key::SecretKey> for Secret {
|
||||||
|
fn from(key: key::SecretKey) -> Self {
|
||||||
|
let mut a = [0; SECP256K1_SECRET_KEY_SIZE];
|
||||||
|
a.copy_from_slice(&key[0..SECP256K1_SECRET_KEY_SIZE]);
|
||||||
|
a.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Secret {
|
||||||
|
type Target = H256;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{
|
||||||
|
super::{Generator, Random},
|
||||||
|
Secret,
|
||||||
|
};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiplicating_secret_inversion_with_secret_gives_one() {
|
||||||
|
let secret = Random.generate().unwrap().secret().clone();
|
||||||
|
let mut inversion = secret.clone();
|
||||||
|
inversion.inv().unwrap();
|
||||||
|
inversion.mul(&secret).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
inversion,
|
||||||
|
Secret::from_str("0000000000000000000000000000000000000000000000000000000000000001")
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secret_inversion_is_reversible_with_inversion() {
|
||||||
|
let secret = Random.generate().unwrap().secret().clone();
|
||||||
|
let mut inversion = secret.clone();
|
||||||
|
inversion.inv().unwrap();
|
||||||
|
inversion.inv().unwrap();
|
||||||
|
assert_eq!(inversion, secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secret_pow() {
|
||||||
|
let secret = Random.generate().unwrap().secret().clone();
|
||||||
|
|
||||||
|
let mut pow0 = secret.clone();
|
||||||
|
pow0.pow(0).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
pow0,
|
||||||
|
Secret::from_str("0000000000000000000000000000000000000000000000000000000000000001")
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut pow1 = secret.clone();
|
||||||
|
pow1.pow(1).unwrap();
|
||||||
|
assert_eq!(pow1, secret);
|
||||||
|
|
||||||
|
let mut pow2 = secret.clone();
|
||||||
|
pow2.pow(2).unwrap();
|
||||||
|
let mut pow2_expected = secret.clone();
|
||||||
|
pow2_expected.mul(&secret).unwrap();
|
||||||
|
assert_eq!(pow2, pow2_expected);
|
||||||
|
|
||||||
|
let mut pow3 = secret.clone();
|
||||||
|
pow3.pow(3).unwrap();
|
||||||
|
let mut pow3_expected = secret.clone();
|
||||||
|
pow3_expected.mul(&secret).unwrap();
|
||||||
|
pow3_expected.mul(&secret).unwrap();
|
||||||
|
assert_eq!(pow3, pow3_expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
325
accounts/ethkey/src/signature.rs
Normal file
325
accounts/ethkey/src/signature.rs
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use ethereum_types::{H256, H520};
|
||||||
|
use public_to_address;
|
||||||
|
use rustc_hex::{FromHex, ToHex};
|
||||||
|
use secp256k1::{
|
||||||
|
key::{PublicKey, SecretKey},
|
||||||
|
Error as SecpError, Message as SecpMessage, RecoverableSignature, RecoveryId,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
cmp::PartialEq,
|
||||||
|
fmt,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
use Address;
|
||||||
|
use Error;
|
||||||
|
use Message;
|
||||||
|
use Public;
|
||||||
|
use Secret;
|
||||||
|
use SECP256K1;
|
||||||
|
|
||||||
|
/// Signature encoded as RSV components
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Signature([u8; 65]);
|
||||||
|
|
||||||
|
impl Signature {
|
||||||
|
/// Get a slice into the 'r' portion of the data.
|
||||||
|
pub fn r(&self) -> &[u8] {
|
||||||
|
&self.0[0..32]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a slice into the 's' portion of the data.
|
||||||
|
pub fn s(&self) -> &[u8] {
|
||||||
|
&self.0[32..64]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the recovery byte.
|
||||||
|
pub fn v(&self) -> u8 {
|
||||||
|
self.0[64]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encode the signature into RSV array (V altered to be in "Electrum" notation).
|
||||||
|
pub fn into_electrum(mut self) -> [u8; 65] {
|
||||||
|
self.0[64] += 27;
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse bytes as a signature encoded as RSV (V in "Electrum" notation).
|
||||||
|
/// May return empty (invalid) signature if given data has invalid length.
|
||||||
|
pub fn from_electrum(data: &[u8]) -> Self {
|
||||||
|
if data.len() != 65 || data[64] < 27 {
|
||||||
|
// fallback to empty (invalid) signature
|
||||||
|
return Signature::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sig = [0u8; 65];
|
||||||
|
sig.copy_from_slice(data);
|
||||||
|
sig[64] -= 27;
|
||||||
|
Signature(sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a signature object from the sig.
|
||||||
|
pub fn from_rsv(r: &H256, s: &H256, v: u8) -> Self {
|
||||||
|
let mut sig = [0u8; 65];
|
||||||
|
sig[0..32].copy_from_slice(&r);
|
||||||
|
sig[32..64].copy_from_slice(&s);
|
||||||
|
sig[64] = v;
|
||||||
|
Signature(sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if this is a "low" signature.
|
||||||
|
pub fn is_low_s(&self) -> bool {
|
||||||
|
H256::from_slice(self.s())
|
||||||
|
<= "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if each component of the signature is in range.
|
||||||
|
pub fn is_valid(&self) -> bool {
|
||||||
|
self.v() <= 1
|
||||||
|
&& H256::from_slice(self.r())
|
||||||
|
< "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141".into()
|
||||||
|
&& H256::from_slice(self.r()) >= 1.into()
|
||||||
|
&& H256::from_slice(self.s())
|
||||||
|
< "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141".into()
|
||||||
|
&& H256::from_slice(self.s()) >= 1.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// manual implementation large arrays don't have trait impls by default.
|
||||||
|
// remove when integer generics exist
|
||||||
|
impl PartialEq for Signature {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
&self.0[..] == &other.0[..]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// manual implementation required in Rust 1.13+, see `std::cmp::AssertParamIsEq`.
|
||||||
|
impl Eq for Signature {}
|
||||||
|
|
||||||
|
// also manual for the same reason, but the pretty printing might be useful.
|
||||||
|
impl fmt::Debug for Signature {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
f.debug_struct("Signature")
|
||||||
|
.field("r", &self.0[0..32].to_hex())
|
||||||
|
.field("s", &self.0[32..64].to_hex())
|
||||||
|
.field("v", &self.0[64..65].to_hex())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Signature {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
write!(f, "{}", self.to_hex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Signature {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s.from_hex() {
|
||||||
|
Ok(ref hex) if hex.len() == 65 => {
|
||||||
|
let mut data = [0; 65];
|
||||||
|
data.copy_from_slice(&hex[0..65]);
|
||||||
|
Ok(Signature(data))
|
||||||
|
}
|
||||||
|
_ => Err(Error::InvalidSignature),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Signature {
|
||||||
|
fn default() -> Self {
|
||||||
|
Signature([0; 65])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for Signature {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
H520::from(self.0).hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Signature {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Signature(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<[u8; 65]> for Signature {
|
||||||
|
fn from(s: [u8; 65]) -> Self {
|
||||||
|
Signature(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<[u8; 65]> for Signature {
|
||||||
|
fn into(self) -> [u8; 65] {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Signature> for H520 {
|
||||||
|
fn from(s: Signature) -> Self {
|
||||||
|
H520::from(s.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<H520> for Signature {
|
||||||
|
fn from(bytes: H520) -> Self {
|
||||||
|
Signature(bytes.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Signature {
|
||||||
|
type Target = [u8; 65];
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for Signature {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign(secret: &Secret, message: &Message) -> Result<Signature, Error> {
|
||||||
|
let context = &SECP256K1;
|
||||||
|
let sec = SecretKey::from_slice(context, &secret)?;
|
||||||
|
let s = context.sign_recoverable(&SecpMessage::from_slice(&message[..])?, &sec)?;
|
||||||
|
let (rec_id, data) = s.serialize_compact(context);
|
||||||
|
let mut data_arr = [0; 65];
|
||||||
|
|
||||||
|
// no need to check if s is low, it always is
|
||||||
|
data_arr[0..64].copy_from_slice(&data[0..64]);
|
||||||
|
data_arr[64] = rec_id.to_i32() as u8;
|
||||||
|
Ok(Signature(data_arr))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_public(
|
||||||
|
public: &Public,
|
||||||
|
signature: &Signature,
|
||||||
|
message: &Message,
|
||||||
|
) -> Result<bool, Error> {
|
||||||
|
let context = &SECP256K1;
|
||||||
|
let rsig = RecoverableSignature::from_compact(
|
||||||
|
context,
|
||||||
|
&signature[0..64],
|
||||||
|
RecoveryId::from_i32(signature[64] as i32)?,
|
||||||
|
)?;
|
||||||
|
let sig = rsig.to_standard(context);
|
||||||
|
|
||||||
|
let pdata: [u8; 65] = {
|
||||||
|
let mut temp = [4u8; 65];
|
||||||
|
temp[1..65].copy_from_slice(&**public);
|
||||||
|
temp
|
||||||
|
};
|
||||||
|
|
||||||
|
let publ = PublicKey::from_slice(context, &pdata)?;
|
||||||
|
match context.verify(&SecpMessage::from_slice(&message[..])?, &sig, &publ) {
|
||||||
|
Ok(_) => Ok(true),
|
||||||
|
Err(SecpError::IncorrectSignature) => Ok(false),
|
||||||
|
Err(x) => Err(Error::from(x)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_address(
|
||||||
|
address: &Address,
|
||||||
|
signature: &Signature,
|
||||||
|
message: &Message,
|
||||||
|
) -> Result<bool, Error> {
|
||||||
|
let public = recover(signature, message)?;
|
||||||
|
let recovered_address = public_to_address(&public);
|
||||||
|
Ok(address == &recovered_address)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recover(signature: &Signature, message: &Message) -> Result<Public, Error> {
|
||||||
|
let context = &SECP256K1;
|
||||||
|
let rsig = RecoverableSignature::from_compact(
|
||||||
|
context,
|
||||||
|
&signature[0..64],
|
||||||
|
RecoveryId::from_i32(signature[64] as i32)?,
|
||||||
|
)?;
|
||||||
|
let pubkey = context.recover(&SecpMessage::from_slice(&message[..])?, &rsig)?;
|
||||||
|
let serialized = pubkey.serialize_vec(context, false);
|
||||||
|
|
||||||
|
let mut public = Public::default();
|
||||||
|
public.copy_from_slice(&serialized[1..65]);
|
||||||
|
Ok(public)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{recover, sign, verify_address, verify_public, Signature};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use Generator;
|
||||||
|
use Message;
|
||||||
|
use Random;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vrs_conversion() {
|
||||||
|
// given
|
||||||
|
let keypair = Random.generate().unwrap();
|
||||||
|
let message = Message::default();
|
||||||
|
let signature = sign(keypair.secret(), &message).unwrap();
|
||||||
|
|
||||||
|
// when
|
||||||
|
let vrs = signature.clone().into_electrum();
|
||||||
|
let from_vrs = Signature::from_electrum(&vrs);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(signature, from_vrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signature_to_and_from_str() {
|
||||||
|
let keypair = Random.generate().unwrap();
|
||||||
|
let message = Message::default();
|
||||||
|
let signature = sign(keypair.secret(), &message).unwrap();
|
||||||
|
let string = format!("{}", signature);
|
||||||
|
let deserialized = Signature::from_str(&string).unwrap();
|
||||||
|
assert_eq!(signature, deserialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_and_recover_public() {
|
||||||
|
let keypair = Random.generate().unwrap();
|
||||||
|
let message = Message::default();
|
||||||
|
let signature = sign(keypair.secret(), &message).unwrap();
|
||||||
|
assert_eq!(keypair.public(), &recover(&signature, &message).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_and_verify_public() {
|
||||||
|
let keypair = Random.generate().unwrap();
|
||||||
|
let message = Message::default();
|
||||||
|
let signature = sign(keypair.secret(), &message).unwrap();
|
||||||
|
assert!(verify_public(keypair.public(), &signature, &message).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_and_verify_address() {
|
||||||
|
let keypair = Random.generate().unwrap();
|
||||||
|
let message = Message::default();
|
||||||
|
let signature = sign(keypair.secret(), &message).unwrap();
|
||||||
|
assert!(verify_address(&keypair.address(), &signature, &message).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
22
ethstore/Cargo.toml → accounts/ethstore/Cargo.toml
Executable file → Normal file
22
ethstore/Cargo.toml → accounts/ethstore/Cargo.toml
Executable file → Normal file
@@ -1,10 +1,11 @@
|
|||||||
[package]
|
[package]
|
||||||
|
description = "Parity Ethereum Key Management"
|
||||||
name = "ethstore"
|
name = "ethstore"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.3"
|
log = "0.4"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
rand = "0.4"
|
rand = "0.4"
|
||||||
ethkey = { path = "../ethkey" }
|
ethkey = { path = "../ethkey" }
|
||||||
@@ -12,16 +13,17 @@ serde = "1.0"
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
rustc-hex = "1.0"
|
rustc-hex = "1.0"
|
||||||
rust-crypto = "0.2.36"
|
|
||||||
tiny-keccak = "1.3"
|
|
||||||
time = "0.1.34"
|
time = "0.1.34"
|
||||||
itertools = "0.5"
|
itertools = "0.5"
|
||||||
parking_lot = "0.5"
|
parking_lot = "0.7"
|
||||||
ethcrypto = { path = "../ethcrypto" }
|
parity-crypto = "0.3.0"
|
||||||
ethereum-types = "0.2"
|
ethereum-types = "0.4"
|
||||||
dir = { path = "../util/dir" }
|
smallvec = "0.6"
|
||||||
smallvec = "0.4"
|
parity-wordlist = "1.3"
|
||||||
parity-wordlist = "1.0"
|
|
||||||
tempdir = "0.3"
|
tempdir = "0.3"
|
||||||
|
lazy_static = "1.2.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
matches = "0.1"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
@@ -1,19 +1,12 @@
|
|||||||
# ethstore
|
## ethstore-cli
|
||||||
|
|
||||||
[![Build Status][travis-image]][travis-url]
|
Parity Ethereum key management.
|
||||||
|
|
||||||
[travis-image]: https://travis-ci.org/paritytech/ethstore.svg?branch=master
|
|
||||||
[travis-url]: https://travis-ci.org/paritytech/ethstore
|
|
||||||
|
|
||||||
Ethereum key management.
|
|
||||||
|
|
||||||
[Documentation](http://paritytech.github.io/ethstore/ethstore/index.html)
|
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
Ethereum key management.
|
Parity Ethereum key management tool.
|
||||||
Copyright 2016, 2017 Parity Technologies (UK) Ltd
|
Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
ethstore insert <secret> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
ethstore insert <secret> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
@@ -35,19 +28,19 @@ Usage:
|
|||||||
Options:
|
Options:
|
||||||
-h, --help Display this message and exit.
|
-h, --help Display this message and exit.
|
||||||
--dir DIR Specify the secret store directory. It may be either
|
--dir DIR Specify the secret store directory. It may be either
|
||||||
parity, parity-test, geth, geth-test
|
parity, parity-(chain), geth, geth-test
|
||||||
or a path [default: parity].
|
or a path [default: parity].
|
||||||
--vault VAULT Specify vault to use in this operation.
|
--vault VAULT Specify vault to use in this operation.
|
||||||
--vault-pwd VAULTPWD Specify vault password to use in this operation. Please note
|
--vault-pwd VAULTPWD Specify vault password to use in this operation. Please note
|
||||||
that this option is required when vault option is set.
|
that this option is required when vault option is set.
|
||||||
Otherwise it is ignored.
|
Otherwise it is ignored.
|
||||||
--src DIR Specify import source. It may be either
|
--src DIR Specify import source. It may be either
|
||||||
parity, parity-test, get, geth-test
|
parity, parity-(chain), geth, geth-test
|
||||||
or a path [default: geth].
|
or a path [default: geth].
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
insert Save account with password.
|
insert Save account with password.
|
||||||
change-pwd Change account password.
|
change-pwd Change password.
|
||||||
list List accounts.
|
list List accounts.
|
||||||
import Import accounts from src.
|
import Import accounts from src.
|
||||||
import-wallet Import presale wallet.
|
import-wallet Import presale wallet.
|
||||||
@@ -59,7 +52,7 @@ Commands:
|
|||||||
create-vault Create new vault.
|
create-vault Create new vault.
|
||||||
change-vault-pwd Change vault password.
|
change-vault-pwd Change vault password.
|
||||||
move-to-vault Move account to vault from another vault/root directory.
|
move-to-vault Move account to vault from another vault/root directory.
|
||||||
move-from-vault Move account to root directory from given vault or root.
|
move-from-vault Move account to root directory from given vault.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
@@ -337,11 +330,10 @@ ethstore move-from-vault 00e63fdb87ceb815ec96ae185b8f7381a0b4a5ea vault1 vault1_
|
|||||||
OK
|
OK
|
||||||
```
|
```
|
||||||
|
|
||||||
--
|
## Parity Ethereum toolchain
|
||||||
|
_This project is a part of the Parity Ethereum toolchain._
|
||||||
|
|
||||||
# Parity toolchain
|
- [evmbin](https://github.com/paritytech/parity-ethereum/blob/master/evmbin/) - EVM implementation for Parity Ethereum.
|
||||||
*this project is a part of the parity toolchain*
|
- [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum function calls encoding.
|
||||||
|
- [ethstore](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethstore) - Parity Ethereum key management.
|
||||||
- [**ethkey**](https://github.com/paritytech/ethkey) - Ethereum keys generator and signer.
|
- [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator.
|
||||||
- [**ethstore**](https://github.com/paritytech/ethstore) - Ethereum key management.
|
|
||||||
- [**ethabi**](https://github.com/paritytech/ethabi) - Ethereum function calls encoding.
|
|
||||||
@@ -1,20 +1,25 @@
|
|||||||
[package]
|
[package]
|
||||||
|
description = "Parity Ethereum Key Management CLI"
|
||||||
name = "ethstore-cli"
|
name = "ethstore-cli"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
docopt = "0.8"
|
docopt = "1.0"
|
||||||
|
env_logger = "0.5"
|
||||||
num_cpus = "1.6"
|
num_cpus = "1.6"
|
||||||
rustc-hex = "1.0"
|
rustc-hex = "1.0"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
parking_lot = "0.5"
|
parking_lot = "0.7"
|
||||||
ethstore = { path = "../" }
|
ethstore = { path = "../" }
|
||||||
dir = { path = '../../util/dir' }
|
dir = { path = '../../../util/dir' }
|
||||||
panic_hook = { path = "../../util/panic_hook" }
|
panic_hook = { path = "../../../util/panic-hook" }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "ethstore"
|
name = "ethstore"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
doc = false
|
doc = false
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempdir = "0.3.5"
|
||||||
66
accounts/ethstore/cli/src/crack.rs
Normal file
66
accounts/ethstore/cli/src/crack.rs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::{cmp, collections::VecDeque, sync::Arc, thread};
|
||||||
|
|
||||||
|
use ethstore::{ethkey::Password, Error, PresaleWallet};
|
||||||
|
use num_cpus;
|
||||||
|
|
||||||
|
pub fn run(passwords: VecDeque<Password>, wallet_path: &str) -> Result<(), Error> {
|
||||||
|
let passwords = Arc::new(Mutex::new(passwords));
|
||||||
|
|
||||||
|
let mut handles = Vec::new();
|
||||||
|
|
||||||
|
for _ in 0..num_cpus::get() {
|
||||||
|
let passwords = passwords.clone();
|
||||||
|
let wallet = PresaleWallet::open(&wallet_path)?;
|
||||||
|
handles.push(thread::spawn(move || {
|
||||||
|
look_for_password(passwords, wallet);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
for handle in handles {
|
||||||
|
handle
|
||||||
|
.join()
|
||||||
|
.map_err(|err| Error::Custom(format!("Error finishing thread: {:?}", err)))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn look_for_password(passwords: Arc<Mutex<VecDeque<Password>>>, wallet: PresaleWallet) {
|
||||||
|
let mut counter = 0;
|
||||||
|
while !passwords.lock().is_empty() {
|
||||||
|
let package = {
|
||||||
|
let mut passwords = passwords.lock();
|
||||||
|
let len = passwords.len();
|
||||||
|
passwords.split_off(cmp::min(len, 32))
|
||||||
|
};
|
||||||
|
for pass in package {
|
||||||
|
counter += 1;
|
||||||
|
match wallet.decrypt(&pass) {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("Found password: {}", pass.as_str());
|
||||||
|
passwords.lock().clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ if counter % 100 == 0 => print!("."),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
363
accounts/ethstore/cli/src/main.rs
Normal file
363
accounts/ethstore/cli/src/main.rs
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
extern crate dir;
|
||||||
|
extern crate docopt;
|
||||||
|
extern crate ethstore;
|
||||||
|
extern crate num_cpus;
|
||||||
|
extern crate panic_hook;
|
||||||
|
extern crate parking_lot;
|
||||||
|
extern crate rustc_hex;
|
||||||
|
extern crate serde;
|
||||||
|
|
||||||
|
extern crate env_logger;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
use std::{collections::VecDeque, env, fmt, fs, io::Read, process};
|
||||||
|
|
||||||
|
use docopt::Docopt;
|
||||||
|
use ethstore::{
|
||||||
|
accounts_dir::{KeyDirectory, RootDiskDirectory},
|
||||||
|
ethkey::{Address, Password},
|
||||||
|
import_accounts, EthStore, PresaleWallet, SecretStore, SecretVaultRef, SimpleSecretStore,
|
||||||
|
StoreAccountRef,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod crack;
|
||||||
|
|
||||||
|
pub const USAGE: &'static str = r#"
|
||||||
|
Parity Ethereum key management tool.
|
||||||
|
Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
ethstore insert <secret> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
|
ethstore change-pwd <address> <old-pwd> <new-pwd> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
|
ethstore list [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
|
ethstore import [<password>] [--src DIR] [--dir DIR]
|
||||||
|
ethstore import-wallet <path> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
|
ethstore find-wallet-pass <path> <password>
|
||||||
|
ethstore remove <address> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
|
ethstore sign <address> <password> <message> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
|
ethstore public <address> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
|
ethstore list-vaults [--dir DIR]
|
||||||
|
ethstore create-vault <vault> <password> [--dir DIR]
|
||||||
|
ethstore change-vault-pwd <vault> <old-pwd> <new-pwd> [--dir DIR]
|
||||||
|
ethstore move-to-vault <address> <vault> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
|
ethstore move-from-vault <address> <vault> <password> [--dir DIR]
|
||||||
|
ethstore [-h | --help]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help Display this message and exit.
|
||||||
|
--dir DIR Specify the secret store directory. It may be either
|
||||||
|
parity, parity-(chain), geth, geth-test
|
||||||
|
or a path [default: parity].
|
||||||
|
--vault VAULT Specify vault to use in this operation.
|
||||||
|
--vault-pwd VAULTPWD Specify vault password to use in this operation. Please note
|
||||||
|
that this option is required when vault option is set.
|
||||||
|
Otherwise it is ignored.
|
||||||
|
--src DIR Specify import source. It may be either
|
||||||
|
parity, parity-(chain), geth, geth-test
|
||||||
|
or a path [default: geth].
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
insert Save account with password.
|
||||||
|
change-pwd Change password.
|
||||||
|
list List accounts.
|
||||||
|
import Import accounts from src.
|
||||||
|
import-wallet Import presale wallet.
|
||||||
|
find-wallet-pass Tries to open a wallet with list of passwords given.
|
||||||
|
remove Remove account.
|
||||||
|
sign Sign message.
|
||||||
|
public Displays public key for an address.
|
||||||
|
list-vaults List vaults.
|
||||||
|
create-vault Create new vault.
|
||||||
|
change-vault-pwd Change vault password.
|
||||||
|
move-to-vault Move account to vault from another vault/root directory.
|
||||||
|
move-from-vault Move account to root directory from given vault.
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Args {
|
||||||
|
cmd_insert: bool,
|
||||||
|
cmd_change_pwd: bool,
|
||||||
|
cmd_list: bool,
|
||||||
|
cmd_import: bool,
|
||||||
|
cmd_import_wallet: bool,
|
||||||
|
cmd_find_wallet_pass: bool,
|
||||||
|
cmd_remove: bool,
|
||||||
|
cmd_sign: bool,
|
||||||
|
cmd_public: bool,
|
||||||
|
cmd_list_vaults: bool,
|
||||||
|
cmd_create_vault: bool,
|
||||||
|
cmd_change_vault_pwd: bool,
|
||||||
|
cmd_move_to_vault: bool,
|
||||||
|
cmd_move_from_vault: bool,
|
||||||
|
arg_secret: String,
|
||||||
|
arg_password: String,
|
||||||
|
arg_old_pwd: String,
|
||||||
|
arg_new_pwd: String,
|
||||||
|
arg_address: String,
|
||||||
|
arg_message: String,
|
||||||
|
arg_path: String,
|
||||||
|
arg_vault: String,
|
||||||
|
flag_src: String,
|
||||||
|
flag_dir: String,
|
||||||
|
flag_vault: String,
|
||||||
|
flag_vault_pwd: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Error {
|
||||||
|
Ethstore(ethstore::Error),
|
||||||
|
Docopt(docopt::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ethstore::Error> for Error {
|
||||||
|
fn from(err: ethstore::Error) -> Self {
|
||||||
|
Error::Ethstore(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<docopt::Error> for Error {
|
||||||
|
fn from(err: docopt::Error) -> Self {
|
||||||
|
Error::Docopt(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
Error::Ethstore(ref err) => fmt::Display::fmt(err, f),
|
||||||
|
Error::Docopt(ref err) => fmt::Display::fmt(err, f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
panic_hook::set_abort();
|
||||||
|
if env::var("RUST_LOG").is_err() {
|
||||||
|
env::set_var("RUST_LOG", "warn")
|
||||||
|
}
|
||||||
|
env_logger::try_init().expect("Logger initialized only once.");
|
||||||
|
|
||||||
|
match execute(env::args()) {
|
||||||
|
Ok(result) => println!("{}", result),
|
||||||
|
Err(Error::Docopt(ref e)) => e.exit(),
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_dir(location: &str, password: Option<Password>) -> Result<Box<dyn KeyDirectory>, Error> {
|
||||||
|
let dir: RootDiskDirectory = match location {
|
||||||
|
path if path.starts_with("parity") => {
|
||||||
|
let chain = path.split('-').nth(1).unwrap_or("ethereum");
|
||||||
|
let mut path = dir::default_data_pathbuf();
|
||||||
|
path.push("keys");
|
||||||
|
path.push(chain);
|
||||||
|
RootDiskDirectory::create(path)?
|
||||||
|
}
|
||||||
|
path => RootDiskDirectory::create(path)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Box::new(dir.with_password(password)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_args_vault(store: &EthStore, args: &Args) -> Result<SecretVaultRef, Error> {
|
||||||
|
if args.flag_vault.is_empty() {
|
||||||
|
return Ok(SecretVaultRef::Root);
|
||||||
|
}
|
||||||
|
|
||||||
|
let vault_pwd = load_password(&args.flag_vault_pwd)?;
|
||||||
|
store.open_vault(&args.flag_vault, &vault_pwd)?;
|
||||||
|
Ok(SecretVaultRef::Vault(args.flag_vault.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_args_vault_account(
|
||||||
|
store: &EthStore,
|
||||||
|
address: Address,
|
||||||
|
args: &Args,
|
||||||
|
) -> Result<StoreAccountRef, Error> {
|
||||||
|
match open_args_vault(store, args)? {
|
||||||
|
SecretVaultRef::Root => Ok(StoreAccountRef::root(address)),
|
||||||
|
SecretVaultRef::Vault(name) => Ok(StoreAccountRef::vault(&name, address)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_accounts(accounts: &[Address]) -> String {
|
||||||
|
accounts
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, a)| format!("{:2}: 0x{:x}", i, a))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_vaults(vaults: &[String]) -> String {
|
||||||
|
vaults.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_password(path: &str) -> Result<Password, Error> {
|
||||||
|
let mut file = fs::File::open(path).map_err(|e| {
|
||||||
|
ethstore::Error::Custom(format!("Error opening password file '{}': {}", path, e))
|
||||||
|
})?;
|
||||||
|
let mut password = String::new();
|
||||||
|
file.read_to_string(&mut password).map_err(|e| {
|
||||||
|
ethstore::Error::Custom(format!("Error reading password file '{}': {}", path, e))
|
||||||
|
})?;
|
||||||
|
// drop EOF
|
||||||
|
let _ = password.pop();
|
||||||
|
Ok(password.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute<S, I>(command: I) -> Result<String, Error>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = S>,
|
||||||
|
S: AsRef<str>,
|
||||||
|
{
|
||||||
|
let args: Args = Docopt::new(USAGE).and_then(|d| d.argv(command).deserialize())?;
|
||||||
|
|
||||||
|
let store = EthStore::open(key_dir(&args.flag_dir, None)?)?;
|
||||||
|
|
||||||
|
return if args.cmd_insert {
|
||||||
|
let secret = args
|
||||||
|
.arg_secret
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ethstore::Error::InvalidSecret)?;
|
||||||
|
let password = load_password(&args.arg_password)?;
|
||||||
|
let vault_ref = open_args_vault(&store, &args)?;
|
||||||
|
let account_ref = store.insert_account(vault_ref, secret, &password)?;
|
||||||
|
Ok(format!("0x{:x}", account_ref.address))
|
||||||
|
} else if args.cmd_change_pwd {
|
||||||
|
let address = args
|
||||||
|
.arg_address
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||||
|
let old_pwd = load_password(&args.arg_old_pwd)?;
|
||||||
|
let new_pwd = load_password(&args.arg_new_pwd)?;
|
||||||
|
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||||
|
let ok = store
|
||||||
|
.change_password(&account_ref, &old_pwd, &new_pwd)
|
||||||
|
.is_ok();
|
||||||
|
Ok(format!("{}", ok))
|
||||||
|
} else if args.cmd_list {
|
||||||
|
let vault_ref = open_args_vault(&store, &args)?;
|
||||||
|
let accounts = store.accounts()?;
|
||||||
|
let accounts: Vec<_> = accounts
|
||||||
|
.into_iter()
|
||||||
|
.filter(|a| &a.vault == &vault_ref)
|
||||||
|
.map(|a| a.address)
|
||||||
|
.collect();
|
||||||
|
Ok(format_accounts(&accounts))
|
||||||
|
} else if args.cmd_import {
|
||||||
|
let password = match args.arg_password.as_ref() {
|
||||||
|
"" => None,
|
||||||
|
_ => Some(load_password(&args.arg_password)?),
|
||||||
|
};
|
||||||
|
let src = key_dir(&args.flag_src, password)?;
|
||||||
|
let dst = key_dir(&args.flag_dir, None)?;
|
||||||
|
|
||||||
|
let accounts = import_accounts(&*src, &*dst)?;
|
||||||
|
Ok(format_accounts(&accounts))
|
||||||
|
} else if args.cmd_import_wallet {
|
||||||
|
let wallet = PresaleWallet::open(&args.arg_path)?;
|
||||||
|
let password = load_password(&args.arg_password)?;
|
||||||
|
let kp = wallet.decrypt(&password)?;
|
||||||
|
let vault_ref = open_args_vault(&store, &args)?;
|
||||||
|
let account_ref = store.insert_account(vault_ref, kp.secret().clone(), &password)?;
|
||||||
|
Ok(format!("0x{:x}", account_ref.address))
|
||||||
|
} else if args.cmd_find_wallet_pass {
|
||||||
|
let passwords = load_password(&args.arg_password)?;
|
||||||
|
let passwords = passwords
|
||||||
|
.as_str()
|
||||||
|
.lines()
|
||||||
|
.map(|line| str::to_owned(line).into())
|
||||||
|
.collect::<VecDeque<_>>();
|
||||||
|
crack::run(passwords, &args.arg_path)?;
|
||||||
|
Ok(format!("Password not found."))
|
||||||
|
} else if args.cmd_remove {
|
||||||
|
let address = args
|
||||||
|
.arg_address
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||||
|
let password = load_password(&args.arg_password)?;
|
||||||
|
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||||
|
let ok = store.remove_account(&account_ref, &password).is_ok();
|
||||||
|
Ok(format!("{}", ok))
|
||||||
|
} else if args.cmd_sign {
|
||||||
|
let address = args
|
||||||
|
.arg_address
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||||
|
let message = args
|
||||||
|
.arg_message
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ethstore::Error::InvalidMessage)?;
|
||||||
|
let password = load_password(&args.arg_password)?;
|
||||||
|
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||||
|
let signature = store.sign(&account_ref, &password, &message)?;
|
||||||
|
Ok(format!("0x{}", signature))
|
||||||
|
} else if args.cmd_public {
|
||||||
|
let address = args
|
||||||
|
.arg_address
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||||
|
let password = load_password(&args.arg_password)?;
|
||||||
|
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||||
|
let public = store.public(&account_ref, &password)?;
|
||||||
|
Ok(format!("0x{:x}", public))
|
||||||
|
} else if args.cmd_list_vaults {
|
||||||
|
let vaults = store.list_vaults()?;
|
||||||
|
Ok(format_vaults(&vaults))
|
||||||
|
} else if args.cmd_create_vault {
|
||||||
|
let password = load_password(&args.arg_password)?;
|
||||||
|
store.create_vault(&args.arg_vault, &password)?;
|
||||||
|
Ok("OK".to_owned())
|
||||||
|
} else if args.cmd_change_vault_pwd {
|
||||||
|
let old_pwd = load_password(&args.arg_old_pwd)?;
|
||||||
|
let new_pwd = load_password(&args.arg_new_pwd)?;
|
||||||
|
store.open_vault(&args.arg_vault, &old_pwd)?;
|
||||||
|
store.change_vault_password(&args.arg_vault, &new_pwd)?;
|
||||||
|
Ok("OK".to_owned())
|
||||||
|
} else if args.cmd_move_to_vault {
|
||||||
|
let address = args
|
||||||
|
.arg_address
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||||
|
let password = load_password(&args.arg_password)?;
|
||||||
|
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||||
|
store.open_vault(&args.arg_vault, &password)?;
|
||||||
|
store.change_account_vault(SecretVaultRef::Vault(args.arg_vault), account_ref)?;
|
||||||
|
Ok("OK".to_owned())
|
||||||
|
} else if args.cmd_move_from_vault {
|
||||||
|
let address = args
|
||||||
|
.arg_address
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||||
|
let password = load_password(&args.arg_password)?;
|
||||||
|
store.open_vault(&args.arg_vault, &password)?;
|
||||||
|
store.change_account_vault(
|
||||||
|
SecretVaultRef::Root,
|
||||||
|
StoreAccountRef::vault(&args.arg_vault, address),
|
||||||
|
)?;
|
||||||
|
Ok("OK".to_owned())
|
||||||
|
} else {
|
||||||
|
Ok(format!("{}", USAGE))
|
||||||
|
};
|
||||||
|
}
|
||||||
57
accounts/ethstore/src/account/cipher.rs
Normal file
57
accounts/ethstore/src/account/cipher.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use json;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub struct Aes128Ctr {
|
||||||
|
pub iv: [u8; 16],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub enum Cipher {
|
||||||
|
Aes128Ctr(Aes128Ctr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<json::Aes128Ctr> for Aes128Ctr {
|
||||||
|
fn from(json: json::Aes128Ctr) -> Self {
|
||||||
|
Aes128Ctr { iv: json.iv.into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<json::Aes128Ctr> for Aes128Ctr {
|
||||||
|
fn into(self) -> json::Aes128Ctr {
|
||||||
|
json::Aes128Ctr {
|
||||||
|
iv: From::from(self.iv),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<json::Cipher> for Cipher {
|
||||||
|
fn from(json: json::Cipher) -> Self {
|
||||||
|
match json {
|
||||||
|
json::Cipher::Aes128Ctr(params) => Cipher::Aes128Ctr(From::from(params)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<json::Cipher> for Cipher {
|
||||||
|
fn into(self) -> json::Cipher {
|
||||||
|
match self {
|
||||||
|
Cipher::Aes128Ctr(params) => json::Cipher::Aes128Ctr(params.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
234
accounts/ethstore/src/account/crypto.rs
Normal file
234
accounts/ethstore/src/account/crypto.rs
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use account::{Aes128Ctr, Cipher, Kdf, Pbkdf2, Prf};
|
||||||
|
use crypto::{self, Keccak256};
|
||||||
|
use ethkey::{Password, Secret};
|
||||||
|
use json;
|
||||||
|
use random::Random;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
use std::{num::NonZeroU32, str};
|
||||||
|
use Error;
|
||||||
|
|
||||||
|
/// Encrypted data
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub struct Crypto {
|
||||||
|
/// Encryption parameters
|
||||||
|
pub cipher: Cipher,
|
||||||
|
/// Encrypted data buffer
|
||||||
|
pub ciphertext: Vec<u8>,
|
||||||
|
/// Key derivation function parameters
|
||||||
|
pub kdf: Kdf,
|
||||||
|
/// Message authentication code
|
||||||
|
pub mac: [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<json::Crypto> for Crypto {
|
||||||
|
fn from(json: json::Crypto) -> Self {
|
||||||
|
Crypto {
|
||||||
|
cipher: json.cipher.into(),
|
||||||
|
ciphertext: json.ciphertext.into(),
|
||||||
|
kdf: json.kdf.into(),
|
||||||
|
mac: json.mac.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Crypto> for json::Crypto {
|
||||||
|
fn from(c: Crypto) -> Self {
|
||||||
|
json::Crypto {
|
||||||
|
cipher: c.cipher.into(),
|
||||||
|
ciphertext: c.ciphertext.into(),
|
||||||
|
kdf: c.kdf.into(),
|
||||||
|
mac: c.mac.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl str::FromStr for Crypto {
|
||||||
|
type Err = <json::Crypto as str::FromStr>::Err;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
s.parse::<json::Crypto>().map(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Crypto> for String {
|
||||||
|
fn from(c: Crypto) -> Self {
|
||||||
|
json::Crypto::from(c).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Crypto {
|
||||||
|
/// Encrypt account secret
|
||||||
|
pub fn with_secret(
|
||||||
|
secret: &Secret,
|
||||||
|
password: &Password,
|
||||||
|
iterations: NonZeroU32,
|
||||||
|
) -> Result<Self, crypto::Error> {
|
||||||
|
Crypto::with_plain(&*secret, password, iterations)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypt custom plain data
|
||||||
|
pub fn with_plain(
|
||||||
|
plain: &[u8],
|
||||||
|
password: &Password,
|
||||||
|
iterations: NonZeroU32,
|
||||||
|
) -> Result<Self, crypto::Error> {
|
||||||
|
let salt: [u8; 32] = Random::random();
|
||||||
|
let iv: [u8; 16] = Random::random();
|
||||||
|
|
||||||
|
// two parts of derived key
|
||||||
|
// DK = [ DK[0..15] DK[16..31] ] = [derived_left_bits, derived_right_bits]
|
||||||
|
let (derived_left_bits, derived_right_bits) =
|
||||||
|
crypto::derive_key_iterations(password.as_bytes(), &salt, iterations);
|
||||||
|
|
||||||
|
// preallocated (on-stack in case of `Secret`) buffer to hold cipher
|
||||||
|
// length = length(plain) as we are using CTR-approach
|
||||||
|
let plain_len = plain.len();
|
||||||
|
let mut ciphertext: SmallVec<[u8; 32]> = SmallVec::from_vec(vec![0; plain_len]);
|
||||||
|
|
||||||
|
// aes-128-ctr with initial vector of iv
|
||||||
|
crypto::aes::encrypt_128_ctr(&derived_left_bits, &iv, plain, &mut *ciphertext)?;
|
||||||
|
|
||||||
|
// KECCAK(DK[16..31] ++ <ciphertext>), where DK[16..31] - derived_right_bits
|
||||||
|
let mac = crypto::derive_mac(&derived_right_bits, &*ciphertext).keccak256();
|
||||||
|
|
||||||
|
Ok(Crypto {
|
||||||
|
cipher: Cipher::Aes128Ctr(Aes128Ctr { iv: iv }),
|
||||||
|
ciphertext: ciphertext.into_vec(),
|
||||||
|
kdf: Kdf::Pbkdf2(Pbkdf2 {
|
||||||
|
dklen: crypto::KEY_LENGTH as u32,
|
||||||
|
salt: salt.to_vec(),
|
||||||
|
c: iterations,
|
||||||
|
prf: Prf::HmacSha256,
|
||||||
|
}),
|
||||||
|
mac: mac,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to decrypt and convert result to account secret
|
||||||
|
pub fn secret(&self, password: &Password) -> Result<Secret, Error> {
|
||||||
|
if self.ciphertext.len() > 32 {
|
||||||
|
return Err(Error::InvalidSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
let secret = self.do_decrypt(password, 32)?;
|
||||||
|
Ok(Secret::from_unsafe_slice(&secret)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to decrypt and return result as is
|
||||||
|
pub fn decrypt(&self, password: &Password) -> Result<Vec<u8>, Error> {
|
||||||
|
let expected_len = self.ciphertext.len();
|
||||||
|
self.do_decrypt(password, expected_len)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_decrypt(&self, password: &Password, expected_len: usize) -> Result<Vec<u8>, Error> {
|
||||||
|
let (derived_left_bits, derived_right_bits) = match self.kdf {
|
||||||
|
Kdf::Pbkdf2(ref params) => {
|
||||||
|
crypto::derive_key_iterations(password.as_bytes(), ¶ms.salt, params.c)
|
||||||
|
}
|
||||||
|
Kdf::Scrypt(ref params) => crypto::scrypt::derive_key(
|
||||||
|
password.as_bytes(),
|
||||||
|
¶ms.salt,
|
||||||
|
params.n,
|
||||||
|
params.p,
|
||||||
|
params.r,
|
||||||
|
)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mac = crypto::derive_mac(&derived_right_bits, &self.ciphertext).keccak256();
|
||||||
|
|
||||||
|
if !crypto::is_equal(&mac, &self.mac) {
|
||||||
|
return Err(Error::InvalidPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut plain: SmallVec<[u8; 32]> = SmallVec::from_vec(vec![0; expected_len]);
|
||||||
|
|
||||||
|
match self.cipher {
|
||||||
|
Cipher::Aes128Ctr(ref params) => {
|
||||||
|
// checker by callers
|
||||||
|
debug_assert!(expected_len >= self.ciphertext.len());
|
||||||
|
|
||||||
|
let from = expected_len - self.ciphertext.len();
|
||||||
|
crypto::aes::decrypt_128_ctr(
|
||||||
|
&derived_left_bits,
|
||||||
|
¶ms.iv,
|
||||||
|
&self.ciphertext,
|
||||||
|
&mut plain[from..],
|
||||||
|
)?;
|
||||||
|
Ok(plain.into_iter().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{Crypto, Error, NonZeroU32};
|
||||||
|
use ethkey::{Generator, Random};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(10240).expect("10240 > 0; qed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn crypto_with_secret_create() {
|
||||||
|
let keypair = Random.generate().unwrap();
|
||||||
|
let passwd = "this is sparta".into();
|
||||||
|
let crypto = Crypto::with_secret(keypair.secret(), &passwd, *ITERATIONS).unwrap();
|
||||||
|
let secret = crypto.secret(&passwd).unwrap();
|
||||||
|
assert_eq!(keypair.secret(), &secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn crypto_with_secret_invalid_password() {
|
||||||
|
let keypair = Random.generate().unwrap();
|
||||||
|
let crypto =
|
||||||
|
Crypto::with_secret(keypair.secret(), &"this is sparta".into(), *ITERATIONS).unwrap();
|
||||||
|
assert_matches!(
|
||||||
|
crypto.secret(&"this is sparta!".into()),
|
||||||
|
Err(Error::InvalidPassword)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn crypto_with_null_plain_data() {
|
||||||
|
let original_data = b"";
|
||||||
|
let passwd = "this is sparta".into();
|
||||||
|
let crypto = Crypto::with_plain(&original_data[..], &passwd, *ITERATIONS).unwrap();
|
||||||
|
let decrypted_data = crypto.decrypt(&passwd).unwrap();
|
||||||
|
assert_eq!(original_data[..], *decrypted_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn crypto_with_tiny_plain_data() {
|
||||||
|
let original_data = b"{}";
|
||||||
|
let passwd = "this is sparta".into();
|
||||||
|
let crypto = Crypto::with_plain(&original_data[..], &passwd, *ITERATIONS).unwrap();
|
||||||
|
let decrypted_data = crypto.decrypt(&passwd).unwrap();
|
||||||
|
assert_eq!(original_data[..], *decrypted_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn crypto_with_huge_plain_data() {
|
||||||
|
let original_data: Vec<_> = (1..65536).map(|i| (i % 256) as u8).collect();
|
||||||
|
let passwd = "this is sparta".into();
|
||||||
|
let crypto = Crypto::with_plain(&original_data, &passwd, *ITERATIONS).unwrap();
|
||||||
|
let decrypted_data = crypto.decrypt(&passwd).unwrap();
|
||||||
|
assert_eq!(&original_data, &decrypted_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
126
accounts/ethstore/src/account/kdf.rs
Normal file
126
accounts/ethstore/src/account/kdf.rs
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use json;
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub enum Prf {
|
||||||
|
HmacSha256,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub struct Pbkdf2 {
|
||||||
|
pub c: NonZeroU32,
|
||||||
|
pub dklen: u32,
|
||||||
|
pub prf: Prf,
|
||||||
|
pub salt: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub struct Scrypt {
|
||||||
|
pub dklen: u32,
|
||||||
|
pub p: u32,
|
||||||
|
pub n: u32,
|
||||||
|
pub r: u32,
|
||||||
|
pub salt: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub enum Kdf {
|
||||||
|
Pbkdf2(Pbkdf2),
|
||||||
|
Scrypt(Scrypt),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<json::Prf> for Prf {
|
||||||
|
fn from(json: json::Prf) -> Self {
|
||||||
|
match json {
|
||||||
|
json::Prf::HmacSha256 => Prf::HmacSha256,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<json::Prf> for Prf {
|
||||||
|
fn into(self) -> json::Prf {
|
||||||
|
match self {
|
||||||
|
Prf::HmacSha256 => json::Prf::HmacSha256,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<json::Pbkdf2> for Pbkdf2 {
|
||||||
|
fn from(json: json::Pbkdf2) -> Self {
|
||||||
|
Pbkdf2 {
|
||||||
|
c: json.c,
|
||||||
|
dklen: json.dklen,
|
||||||
|
prf: From::from(json.prf),
|
||||||
|
salt: json.salt.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<json::Pbkdf2> for Pbkdf2 {
|
||||||
|
fn into(self) -> json::Pbkdf2 {
|
||||||
|
json::Pbkdf2 {
|
||||||
|
c: self.c,
|
||||||
|
dklen: self.dklen,
|
||||||
|
prf: self.prf.into(),
|
||||||
|
salt: From::from(self.salt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<json::Scrypt> for Scrypt {
|
||||||
|
fn from(json: json::Scrypt) -> Self {
|
||||||
|
Scrypt {
|
||||||
|
dklen: json.dklen,
|
||||||
|
p: json.p,
|
||||||
|
n: json.n,
|
||||||
|
r: json.r,
|
||||||
|
salt: json.salt.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<json::Scrypt> for Scrypt {
|
||||||
|
fn into(self) -> json::Scrypt {
|
||||||
|
json::Scrypt {
|
||||||
|
dklen: self.dklen,
|
||||||
|
p: self.p,
|
||||||
|
n: self.n,
|
||||||
|
r: self.r,
|
||||||
|
salt: From::from(self.salt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<json::Kdf> for Kdf {
|
||||||
|
fn from(json: json::Kdf) -> Self {
|
||||||
|
match json {
|
||||||
|
json::Kdf::Pbkdf2(params) => Kdf::Pbkdf2(From::from(params)),
|
||||||
|
json::Kdf::Scrypt(params) => Kdf::Scrypt(From::from(params)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<json::Kdf> for Kdf {
|
||||||
|
fn into(self) -> json::Kdf {
|
||||||
|
match self {
|
||||||
|
Kdf::Pbkdf2(params) => json::Kdf::Pbkdf2(params.into()),
|
||||||
|
Kdf::Scrypt(params) => json::Kdf::Scrypt(params.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
ethstore/src/account/mod.rs → accounts/ethstore/src/account/mod.rs
Executable file → Normal file
23
ethstore/src/account/mod.rs → accounts/ethstore/src/account/mod.rs
Executable file → Normal file
@@ -1,18 +1,18 @@
|
|||||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License as published by
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
|
|
||||||
// Parity is distributed in the hope that it will be useful,
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
mod cipher;
|
mod cipher;
|
||||||
mod crypto;
|
mod crypto;
|
||||||
@@ -20,9 +20,10 @@ mod kdf;
|
|||||||
mod safe_account;
|
mod safe_account;
|
||||||
mod version;
|
mod version;
|
||||||
|
|
||||||
pub use self::cipher::{Cipher, Aes128Ctr};
|
pub use self::{
|
||||||
pub use self::crypto::Crypto;
|
cipher::{Aes128Ctr, Cipher},
|
||||||
pub use self::kdf::{Kdf, Pbkdf2, Scrypt, Prf};
|
crypto::Crypto,
|
||||||
pub use self::safe_account::SafeAccount;
|
kdf::{Kdf, Pbkdf2, Prf, Scrypt},
|
||||||
pub use self::version::Version;
|
safe_account::SafeAccount,
|
||||||
|
version::Version,
|
||||||
|
};
|
||||||
286
accounts/ethstore/src/account/safe_account.rs
Normal file
286
accounts/ethstore/src/account/safe_account.rs
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::crypto::Crypto;
|
||||||
|
use account::Version;
|
||||||
|
use crypto;
|
||||||
|
use ethkey::{
|
||||||
|
self, crypto::ecdh::agree, sign, Address, KeyPair, Message, Password, Public, Secret, Signature,
|
||||||
|
};
|
||||||
|
use json;
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
use Error;
|
||||||
|
|
||||||
|
/// Account representation.
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub struct SafeAccount {
|
||||||
|
/// Account ID
|
||||||
|
pub id: [u8; 16],
|
||||||
|
/// Account version
|
||||||
|
pub version: Version,
|
||||||
|
/// Account address
|
||||||
|
pub address: Address,
|
||||||
|
/// Account private key derivation definition.
|
||||||
|
pub crypto: Crypto,
|
||||||
|
/// Account filename
|
||||||
|
pub filename: Option<String>,
|
||||||
|
/// Account name
|
||||||
|
pub name: String,
|
||||||
|
/// Account metadata
|
||||||
|
pub meta: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<json::KeyFile> for SafeAccount {
|
||||||
|
fn into(self) -> json::KeyFile {
|
||||||
|
json::KeyFile {
|
||||||
|
id: From::from(self.id),
|
||||||
|
version: self.version.into(),
|
||||||
|
address: Some(self.address.into()),
|
||||||
|
crypto: self.crypto.into(),
|
||||||
|
name: Some(self.name.into()),
|
||||||
|
meta: Some(self.meta.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SafeAccount {
|
||||||
|
/// Create a new account
|
||||||
|
pub fn create(
|
||||||
|
keypair: &KeyPair,
|
||||||
|
id: [u8; 16],
|
||||||
|
password: &Password,
|
||||||
|
iterations: NonZeroU32,
|
||||||
|
name: String,
|
||||||
|
meta: String,
|
||||||
|
) -> Result<Self, crypto::Error> {
|
||||||
|
Ok(SafeAccount {
|
||||||
|
id: id,
|
||||||
|
version: Version::V3,
|
||||||
|
crypto: Crypto::with_secret(keypair.secret(), password, iterations)?,
|
||||||
|
address: keypair.address(),
|
||||||
|
filename: None,
|
||||||
|
name: name,
|
||||||
|
meta: meta,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `SafeAccount` from the given `json`; if it was read from a
|
||||||
|
/// file, the `filename` should be `Some` name. If it is as yet anonymous, then it
|
||||||
|
/// can be left `None`.
|
||||||
|
/// In case `password` is provided, we will attempt to read the secret from the keyfile
|
||||||
|
/// and derive the address from it instead of reading it directly.
|
||||||
|
/// Providing password is required for `json::KeyFile`s with no address.
|
||||||
|
pub fn from_file(
|
||||||
|
json: json::KeyFile,
|
||||||
|
filename: Option<String>,
|
||||||
|
password: &Option<Password>,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let crypto = Crypto::from(json.crypto);
|
||||||
|
let address = match (password, &json.address) {
|
||||||
|
(None, Some(json_address)) => json_address.into(),
|
||||||
|
(None, None) => Err(Error::Custom(
|
||||||
|
"This keystore does not contain address. You need to provide password to import it"
|
||||||
|
.into(),
|
||||||
|
))?,
|
||||||
|
(Some(password), json_address) => {
|
||||||
|
let derived_address = KeyPair::from_secret(
|
||||||
|
crypto
|
||||||
|
.secret(&password)
|
||||||
|
.map_err(|_| Error::InvalidPassword)?,
|
||||||
|
)?
|
||||||
|
.address();
|
||||||
|
|
||||||
|
match json_address {
|
||||||
|
Some(json_address) => {
|
||||||
|
let json_address = json_address.into();
|
||||||
|
if derived_address != json_address {
|
||||||
|
warn!("Detected address mismatch when opening an account. Derived: {:?}, in json got: {:?}",
|
||||||
|
derived_address, json_address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
derived_address
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(SafeAccount {
|
||||||
|
id: json.id.into(),
|
||||||
|
version: json.version.into(),
|
||||||
|
address,
|
||||||
|
crypto,
|
||||||
|
filename,
|
||||||
|
name: json.name.unwrap_or(String::new()),
|
||||||
|
meta: json.meta.unwrap_or("{}".to_owned()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `SafeAccount` from the given vault `json`; if it was read from a
|
||||||
|
/// file, the `filename` should be `Some` name. If it is as yet anonymous, then it
|
||||||
|
/// can be left `None`.
|
||||||
|
pub fn from_vault_file(
|
||||||
|
password: &Password,
|
||||||
|
json: json::VaultKeyFile,
|
||||||
|
filename: Option<String>,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let meta_crypto: Crypto = json.metacrypto.into();
|
||||||
|
let meta_plain = meta_crypto.decrypt(password)?;
|
||||||
|
let meta_plain =
|
||||||
|
json::VaultKeyMeta::load(&meta_plain).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||||
|
|
||||||
|
SafeAccount::from_file(
|
||||||
|
json::KeyFile {
|
||||||
|
id: json.id,
|
||||||
|
version: json.version,
|
||||||
|
crypto: json.crypto,
|
||||||
|
address: Some(meta_plain.address),
|
||||||
|
name: meta_plain.name,
|
||||||
|
meta: meta_plain.meta,
|
||||||
|
},
|
||||||
|
filename,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `VaultKeyFile` from the given `self`
|
||||||
|
pub fn into_vault_file(
|
||||||
|
self,
|
||||||
|
iterations: NonZeroU32,
|
||||||
|
password: &Password,
|
||||||
|
) -> Result<json::VaultKeyFile, Error> {
|
||||||
|
let meta_plain = json::VaultKeyMeta {
|
||||||
|
address: self.address.into(),
|
||||||
|
name: Some(self.name),
|
||||||
|
meta: Some(self.meta),
|
||||||
|
};
|
||||||
|
let meta_plain = meta_plain
|
||||||
|
.write()
|
||||||
|
.map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||||
|
let meta_crypto = Crypto::with_plain(&meta_plain, password, iterations)?;
|
||||||
|
|
||||||
|
Ok(json::VaultKeyFile {
|
||||||
|
id: self.id.into(),
|
||||||
|
version: self.version.into(),
|
||||||
|
crypto: self.crypto.into(),
|
||||||
|
metacrypto: meta_crypto.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sign a message.
|
||||||
|
pub fn sign(&self, password: &Password, message: &Message) -> Result<Signature, Error> {
|
||||||
|
let secret = self.crypto.secret(password)?;
|
||||||
|
sign(&secret, message).map_err(From::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypt a message.
|
||||||
|
pub fn decrypt(
|
||||||
|
&self,
|
||||||
|
password: &Password,
|
||||||
|
shared_mac: &[u8],
|
||||||
|
message: &[u8],
|
||||||
|
) -> Result<Vec<u8>, Error> {
|
||||||
|
let secret = self.crypto.secret(password)?;
|
||||||
|
ethkey::crypto::ecies::decrypt(&secret, shared_mac, message).map_err(From::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Agree on shared key.
|
||||||
|
pub fn agree(&self, password: &Password, other: &Public) -> Result<Secret, Error> {
|
||||||
|
let secret = self.crypto.secret(password)?;
|
||||||
|
agree(&secret, other).map_err(From::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derive public key.
|
||||||
|
pub fn public(&self, password: &Password) -> Result<Public, Error> {
|
||||||
|
let secret = self.crypto.secret(password)?;
|
||||||
|
Ok(KeyPair::from_secret(secret)?.public().clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change account's password.
|
||||||
|
pub fn change_password(
|
||||||
|
&self,
|
||||||
|
old_password: &Password,
|
||||||
|
new_password: &Password,
|
||||||
|
iterations: NonZeroU32,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let secret = self.crypto.secret(old_password)?;
|
||||||
|
let result = SafeAccount {
|
||||||
|
id: self.id.clone(),
|
||||||
|
version: self.version.clone(),
|
||||||
|
crypto: Crypto::with_secret(&secret, new_password, iterations)?,
|
||||||
|
address: self.address.clone(),
|
||||||
|
filename: self.filename.clone(),
|
||||||
|
name: self.name.clone(),
|
||||||
|
meta: self.meta.clone(),
|
||||||
|
};
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if password matches the account.
|
||||||
|
pub fn check_password(&self, password: &Password) -> bool {
|
||||||
|
self.crypto.secret(password).is_ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{NonZeroU32, SafeAccount};
|
||||||
|
use ethkey::{verify_public, Generator, Message, Random};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(10240).expect("10240 > 0; qed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_and_verify_public() {
|
||||||
|
let keypair = Random.generate().unwrap();
|
||||||
|
let password = "hello world".into();
|
||||||
|
let message = Message::default();
|
||||||
|
let account = SafeAccount::create(
|
||||||
|
&keypair,
|
||||||
|
[0u8; 16],
|
||||||
|
&password,
|
||||||
|
*ITERATIONS,
|
||||||
|
"Test".to_owned(),
|
||||||
|
"{}".to_owned(),
|
||||||
|
);
|
||||||
|
let signature = account.unwrap().sign(&password, &message).unwrap();
|
||||||
|
assert!(verify_public(keypair.public(), &signature, &message).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn change_password() {
|
||||||
|
let keypair = Random.generate().unwrap();
|
||||||
|
let first_password = "hello world".into();
|
||||||
|
let sec_password = "this is sparta".into();
|
||||||
|
let message = Message::default();
|
||||||
|
let account = SafeAccount::create(
|
||||||
|
&keypair,
|
||||||
|
[0u8; 16],
|
||||||
|
&first_password,
|
||||||
|
*ITERATIONS,
|
||||||
|
"Test".to_owned(),
|
||||||
|
"{}".to_owned(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let new_account = account
|
||||||
|
.change_password(&first_password, &sec_password, *ITERATIONS)
|
||||||
|
.unwrap();
|
||||||
|
assert!(account.sign(&first_password, &message).is_ok());
|
||||||
|
assert!(account.sign(&sec_password, &message).is_err());
|
||||||
|
assert!(new_account.sign(&first_password, &message).is_err());
|
||||||
|
assert!(new_account.sign(&sec_password, &message).is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,38 +1,38 @@
|
|||||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License as published by
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
|
|
||||||
// Parity is distributed in the hope that it will be useful,
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use json;
|
use json;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum Version {
|
pub enum Version {
|
||||||
V3,
|
V3,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<json::Version> for Version {
|
impl From<json::Version> for Version {
|
||||||
fn from(json: json::Version) -> Self {
|
fn from(json: json::Version) -> Self {
|
||||||
match json {
|
match json {
|
||||||
json::Version::V3 => Version::V3,
|
json::Version::V3 => Version::V3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<json::Version> for Version {
|
impl Into<json::Version> for Version {
|
||||||
fn into(self) -> json::Version {
|
fn into(self) -> json::Version {
|
||||||
match self {
|
match self {
|
||||||
Version::V3 => json::Version::V3,
|
Version::V3 => json::Version::V3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
608
accounts/ethstore/src/accounts_dir/disk.rs
Normal file
608
accounts/ethstore/src/accounts_dir/disk.rs
Normal file
@@ -0,0 +1,608 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
vault::{VaultDiskDirectory, VAULT_FILE_NAME},
|
||||||
|
KeyDirectory, VaultKey, VaultKeyDirectory, VaultKeyDirectoryProvider,
|
||||||
|
};
|
||||||
|
use ethkey::Password;
|
||||||
|
use json::{self, Uuid};
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fs, io,
|
||||||
|
io::Write,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
use time;
|
||||||
|
use Error;
|
||||||
|
use SafeAccount;
|
||||||
|
|
||||||
|
const IGNORED_FILES: &'static [&'static str] = &[
|
||||||
|
"thumbs.db",
|
||||||
|
"address_book.json",
|
||||||
|
"dapps_policy.json",
|
||||||
|
"dapps_accounts.json",
|
||||||
|
"dapps_history.json",
|
||||||
|
"vault.json",
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Find a unique filename that does not exist using four-letter random suffix.
|
||||||
|
pub fn find_unique_filename_using_random_suffix(
|
||||||
|
parent_path: &Path,
|
||||||
|
original_filename: &str,
|
||||||
|
) -> io::Result<String> {
|
||||||
|
let mut path = parent_path.join(original_filename);
|
||||||
|
let mut deduped_filename = original_filename.to_string();
|
||||||
|
|
||||||
|
if path.exists() {
|
||||||
|
const MAX_RETRIES: usize = 500;
|
||||||
|
let mut retries = 0;
|
||||||
|
|
||||||
|
while path.exists() {
|
||||||
|
if retries >= MAX_RETRIES {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"Exceeded maximum retries when deduplicating filename.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let suffix = ::random::random_string(4);
|
||||||
|
deduped_filename = format!("{}-{}", original_filename, suffix);
|
||||||
|
path.set_file_name(&deduped_filename);
|
||||||
|
retries += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(deduped_filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new file and restrict permissions to owner only. It errors if the file already exists.
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn create_new_file_with_permissions_to_owner(file_path: &Path) -> io::Result<fs::File> {
|
||||||
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
|
|
||||||
|
fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create_new(true)
|
||||||
|
.mode((libc::S_IWUSR | libc::S_IRUSR) as u32)
|
||||||
|
.open(file_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new file and restrict permissions to owner only. It errors if the file already exists.
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
pub fn create_new_file_with_permissions_to_owner(file_path: &Path) -> io::Result<fs::File> {
|
||||||
|
fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create_new(true)
|
||||||
|
.open(file_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new file and restrict permissions to owner only. It replaces the existing file if it already exists.
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn replace_file_with_permissions_to_owner(file_path: &Path) -> io::Result<fs::File> {
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
|
let file = fs::File::create(file_path)?;
|
||||||
|
let mut permissions = file.metadata()?.permissions();
|
||||||
|
permissions.set_mode((libc::S_IWUSR | libc::S_IRUSR) as u32);
|
||||||
|
file.set_permissions(permissions)?;
|
||||||
|
|
||||||
|
Ok(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new file and restrict permissions to owner only. It replaces the existing file if it already exists.
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
pub fn replace_file_with_permissions_to_owner(file_path: &Path) -> io::Result<fs::File> {
|
||||||
|
fs::File::create(file_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Root keys directory implementation
|
||||||
|
pub type RootDiskDirectory = DiskDirectory<DiskKeyFileManager>;
|
||||||
|
|
||||||
|
/// Disk directory key file manager
|
||||||
|
pub trait KeyFileManager: Send + Sync {
|
||||||
|
/// Read `SafeAccount` from given key file stream
|
||||||
|
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error>
|
||||||
|
where
|
||||||
|
T: io::Read;
|
||||||
|
|
||||||
|
/// Write `SafeAccount` to given key file stream
|
||||||
|
fn write<T>(&self, account: SafeAccount, writer: &mut T) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: io::Write;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disk-based keys directory implementation
|
||||||
|
pub struct DiskDirectory<T>
|
||||||
|
where
|
||||||
|
T: KeyFileManager,
|
||||||
|
{
|
||||||
|
path: PathBuf,
|
||||||
|
key_manager: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Keys file manager for root keys directory
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct DiskKeyFileManager {
|
||||||
|
password: Option<Password>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RootDiskDirectory {
|
||||||
|
pub fn create<P>(path: P) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
fs::create_dir_all(&path)?;
|
||||||
|
Ok(Self::at(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// allows to read keyfiles with given password (needed for keyfiles w/o address)
|
||||||
|
pub fn with_password(&self, password: Option<Password>) -> Self {
|
||||||
|
DiskDirectory::new(&self.path, DiskKeyFileManager { password })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn at<P>(path: P) -> Self
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
DiskDirectory::new(path, DiskKeyFileManager::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DiskDirectory<T>
|
||||||
|
where
|
||||||
|
T: KeyFileManager,
|
||||||
|
{
|
||||||
|
/// Create new disk directory instance
|
||||||
|
pub fn new<P>(path: P, key_manager: T) -> Self
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
DiskDirectory {
|
||||||
|
path: path.as_ref().to_path_buf(),
|
||||||
|
key_manager: key_manager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn files(&self) -> Result<Vec<PathBuf>, Error> {
|
||||||
|
Ok(fs::read_dir(&self.path)?
|
||||||
|
.flat_map(Result::ok)
|
||||||
|
.filter(|entry| {
|
||||||
|
let metadata = entry.metadata().ok();
|
||||||
|
let file_name = entry.file_name();
|
||||||
|
let name = file_name.to_string_lossy();
|
||||||
|
// filter directories
|
||||||
|
metadata.map_or(false, |m| !m.is_dir()) &&
|
||||||
|
// hidden files
|
||||||
|
!name.starts_with(".") &&
|
||||||
|
// other ignored files
|
||||||
|
!IGNORED_FILES.contains(&&*name)
|
||||||
|
})
|
||||||
|
.map(|entry| entry.path())
|
||||||
|
.collect::<Vec<PathBuf>>())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn files_hash(&self) -> Result<u64, Error> {
|
||||||
|
use std::{collections::hash_map::DefaultHasher, hash::Hasher};
|
||||||
|
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
let files = self.files()?;
|
||||||
|
for file in files {
|
||||||
|
hasher.write(file.to_str().unwrap_or("").as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(hasher.finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last_modification_date(&self) -> Result<u64, Error> {
|
||||||
|
use std::time::{Duration, UNIX_EPOCH};
|
||||||
|
let duration = fs::metadata(&self.path)?
|
||||||
|
.modified()?
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap_or(Duration::default());
|
||||||
|
let timestamp = duration.as_secs() ^ (duration.subsec_nanos() as u64);
|
||||||
|
Ok(timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// all accounts found in keys directory
|
||||||
|
fn files_content(&self) -> Result<HashMap<PathBuf, SafeAccount>, Error> {
|
||||||
|
// it's not done using one iterator cause
|
||||||
|
// there is an issue with rustc and it takes tooo much time to compile
|
||||||
|
let paths = self.files()?;
|
||||||
|
Ok(paths
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|path| {
|
||||||
|
let filename = Some(
|
||||||
|
path.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.expect("Keys have valid UTF8 names only.")
|
||||||
|
.to_owned(),
|
||||||
|
);
|
||||||
|
fs::File::open(path.clone())
|
||||||
|
.map_err(Into::into)
|
||||||
|
.and_then(|file| self.key_manager.read(filename, file))
|
||||||
|
.map_err(|err| {
|
||||||
|
warn!("Invalid key file: {:?} ({})", path, err);
|
||||||
|
err
|
||||||
|
})
|
||||||
|
.map(|account| (path, account))
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// insert account with given filename. if the filename is a duplicate of any stored account and dedup is set to
|
||||||
|
/// true, a random suffix is appended to the filename.
|
||||||
|
pub fn insert_with_filename(
|
||||||
|
&self,
|
||||||
|
account: SafeAccount,
|
||||||
|
mut filename: String,
|
||||||
|
dedup: bool,
|
||||||
|
) -> Result<SafeAccount, Error> {
|
||||||
|
if dedup {
|
||||||
|
filename = find_unique_filename_using_random_suffix(&self.path, &filename)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// path to keyfile
|
||||||
|
let keyfile_path = self.path.join(filename.as_str());
|
||||||
|
|
||||||
|
// update account filename
|
||||||
|
let original_account = account.clone();
|
||||||
|
let mut account = account;
|
||||||
|
account.filename = Some(filename);
|
||||||
|
|
||||||
|
{
|
||||||
|
// save the file
|
||||||
|
let mut file = if dedup {
|
||||||
|
create_new_file_with_permissions_to_owner(&keyfile_path)?
|
||||||
|
} else {
|
||||||
|
replace_file_with_permissions_to_owner(&keyfile_path)?
|
||||||
|
};
|
||||||
|
|
||||||
|
// write key content
|
||||||
|
self.key_manager
|
||||||
|
.write(original_account, &mut file)
|
||||||
|
.map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||||
|
|
||||||
|
file.flush()?;
|
||||||
|
file.sync_all()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get key file manager referece
|
||||||
|
pub fn key_manager(&self) -> &T {
|
||||||
|
&self.key_manager
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> KeyDirectory for DiskDirectory<T>
|
||||||
|
where
|
||||||
|
T: KeyFileManager,
|
||||||
|
{
|
||||||
|
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
||||||
|
let accounts = self
|
||||||
|
.files_content()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, account)| account)
|
||||||
|
.collect();
|
||||||
|
Ok(accounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
|
// Disk store handles updates correctly iff filename is the same
|
||||||
|
let filename = account_filename(&account);
|
||||||
|
self.insert_with_filename(account, filename, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
|
let filename = account_filename(&account);
|
||||||
|
self.insert_with_filename(account, filename, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
|
// enumerate all entries in keystore
|
||||||
|
// and find entry with given address
|
||||||
|
let to_remove = self
|
||||||
|
.files_content()?
|
||||||
|
.into_iter()
|
||||||
|
.find(|&(_, ref acc)| acc.id == account.id && acc.address == account.address);
|
||||||
|
|
||||||
|
// remove it
|
||||||
|
match to_remove {
|
||||||
|
None => Err(Error::InvalidAccount),
|
||||||
|
Some((path, _)) => fs::remove_file(path).map_err(From::from),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> Option<&PathBuf> {
|
||||||
|
Some(&self.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_vault_provider(&self) -> Option<&dyn VaultKeyDirectoryProvider> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unique_repr(&self) -> Result<u64, Error> {
|
||||||
|
self.last_modification_date()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> VaultKeyDirectoryProvider for DiskDirectory<T>
|
||||||
|
where
|
||||||
|
T: KeyFileManager,
|
||||||
|
{
|
||||||
|
fn create(&self, name: &str, key: VaultKey) -> Result<Box<dyn VaultKeyDirectory>, Error> {
|
||||||
|
let vault_dir = VaultDiskDirectory::create(&self.path, name, key)?;
|
||||||
|
Ok(Box::new(vault_dir))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open(&self, name: &str, key: VaultKey) -> Result<Box<dyn VaultKeyDirectory>, Error> {
|
||||||
|
let vault_dir = VaultDiskDirectory::at(&self.path, name, key)?;
|
||||||
|
Ok(Box::new(vault_dir))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_vaults(&self) -> Result<Vec<String>, Error> {
|
||||||
|
Ok(fs::read_dir(&self.path)?
|
||||||
|
.filter_map(|e| e.ok().map(|e| e.path()))
|
||||||
|
.filter_map(|path| {
|
||||||
|
let mut vault_file_path = path.clone();
|
||||||
|
vault_file_path.push(VAULT_FILE_NAME);
|
||||||
|
if vault_file_path.is_file() {
|
||||||
|
path.file_name()
|
||||||
|
.and_then(|f| f.to_str())
|
||||||
|
.map(|f| f.to_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vault_meta(&self, name: &str) -> Result<String, Error> {
|
||||||
|
VaultDiskDirectory::meta_at(&self.path, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyFileManager for DiskKeyFileManager {
|
||||||
|
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error>
|
||||||
|
where
|
||||||
|
T: io::Read,
|
||||||
|
{
|
||||||
|
let key_file =
|
||||||
|
json::KeyFile::load(reader).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||||
|
SafeAccount::from_file(key_file, filename, &self.password)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<T>(&self, mut account: SafeAccount, writer: &mut T) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: io::Write,
|
||||||
|
{
|
||||||
|
// when account is moved back to root directory from vault
|
||||||
|
// => remove vault field from meta
|
||||||
|
account.meta = json::remove_vault_name_from_json_meta(&account.meta)
|
||||||
|
.map_err(|err| Error::Custom(format!("{:?}", err)))?;
|
||||||
|
|
||||||
|
let key_file: json::KeyFile = account.into();
|
||||||
|
key_file
|
||||||
|
.write(writer)
|
||||||
|
.map_err(|e| Error::Custom(format!("{:?}", e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn account_filename(account: &SafeAccount) -> String {
|
||||||
|
// build file path
|
||||||
|
account.filename.clone().unwrap_or_else(|| {
|
||||||
|
let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc())
|
||||||
|
.expect("Time-format string is valid.");
|
||||||
|
format!("UTC--{}Z--{}", timestamp, Uuid::from(account.id))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
extern crate tempdir;
|
||||||
|
|
||||||
|
use self::tempdir::TempDir;
|
||||||
|
use super::{KeyDirectory, RootDiskDirectory, VaultKey};
|
||||||
|
use account::SafeAccount;
|
||||||
|
use ethkey::{Generator, Random};
|
||||||
|
use std::{env, fs, num::NonZeroU32};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(1024).expect("1024 > 0; qed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_create_new_account() {
|
||||||
|
// given
|
||||||
|
let mut dir = env::temp_dir();
|
||||||
|
dir.push("ethstore_should_create_new_account");
|
||||||
|
let keypair = Random.generate().unwrap();
|
||||||
|
let password = "hello world".into();
|
||||||
|
let directory = RootDiskDirectory::create(dir.clone()).unwrap();
|
||||||
|
|
||||||
|
// when
|
||||||
|
let account = SafeAccount::create(
|
||||||
|
&keypair,
|
||||||
|
[0u8; 16],
|
||||||
|
&password,
|
||||||
|
*ITERATIONS,
|
||||||
|
"Test".to_owned(),
|
||||||
|
"{}".to_owned(),
|
||||||
|
);
|
||||||
|
let res = directory.insert(account.unwrap());
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert!(res.is_ok(), "Should save account succesfuly.");
|
||||||
|
assert!(
|
||||||
|
res.unwrap().filename.is_some(),
|
||||||
|
"Filename has been assigned."
|
||||||
|
);
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
let _ = fs::remove_dir_all(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_handle_duplicate_filenames() {
|
||||||
|
// given
|
||||||
|
let mut dir = env::temp_dir();
|
||||||
|
dir.push("ethstore_should_handle_duplicate_filenames");
|
||||||
|
let keypair = Random.generate().unwrap();
|
||||||
|
let password = "hello world".into();
|
||||||
|
let directory = RootDiskDirectory::create(dir.clone()).unwrap();
|
||||||
|
|
||||||
|
// when
|
||||||
|
let account = SafeAccount::create(
|
||||||
|
&keypair,
|
||||||
|
[0u8; 16],
|
||||||
|
&password,
|
||||||
|
*ITERATIONS,
|
||||||
|
"Test".to_owned(),
|
||||||
|
"{}".to_owned(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let filename = "test".to_string();
|
||||||
|
let dedup = true;
|
||||||
|
|
||||||
|
directory
|
||||||
|
.insert_with_filename(account.clone(), "foo".to_string(), dedup)
|
||||||
|
.unwrap();
|
||||||
|
let file1 = directory
|
||||||
|
.insert_with_filename(account.clone(), filename.clone(), dedup)
|
||||||
|
.unwrap()
|
||||||
|
.filename
|
||||||
|
.unwrap();
|
||||||
|
let file2 = directory
|
||||||
|
.insert_with_filename(account.clone(), filename.clone(), dedup)
|
||||||
|
.unwrap()
|
||||||
|
.filename
|
||||||
|
.unwrap();
|
||||||
|
let file3 = directory
|
||||||
|
.insert_with_filename(account.clone(), filename.clone(), dedup)
|
||||||
|
.unwrap()
|
||||||
|
.filename
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// then
|
||||||
|
// the first file should have the original names
|
||||||
|
assert_eq!(file1, filename);
|
||||||
|
|
||||||
|
// the following duplicate files should have a suffix appended
|
||||||
|
assert!(file2 != file3);
|
||||||
|
assert_eq!(file2.len(), filename.len() + 5);
|
||||||
|
assert_eq!(file3.len(), filename.len() + 5);
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
let _ = fs::remove_dir_all(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_manage_vaults() {
|
||||||
|
// given
|
||||||
|
let mut dir = env::temp_dir();
|
||||||
|
dir.push("should_create_new_vault");
|
||||||
|
let directory = RootDiskDirectory::create(dir.clone()).unwrap();
|
||||||
|
let vault_name = "vault";
|
||||||
|
let password = "password".into();
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert!(directory.as_vault_provider().is_some());
|
||||||
|
|
||||||
|
// and when
|
||||||
|
let before_root_items_count = fs::read_dir(&dir).unwrap().count();
|
||||||
|
let vault = directory
|
||||||
|
.as_vault_provider()
|
||||||
|
.unwrap()
|
||||||
|
.create(vault_name, VaultKey::new(&password, *ITERATIONS));
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert!(vault.is_ok());
|
||||||
|
let after_root_items_count = fs::read_dir(&dir).unwrap().count();
|
||||||
|
assert!(after_root_items_count > before_root_items_count);
|
||||||
|
|
||||||
|
// and when
|
||||||
|
let vault = directory
|
||||||
|
.as_vault_provider()
|
||||||
|
.unwrap()
|
||||||
|
.open(vault_name, VaultKey::new(&password, *ITERATIONS));
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert!(vault.is_ok());
|
||||||
|
let after_root_items_count2 = fs::read_dir(&dir).unwrap().count();
|
||||||
|
assert!(after_root_items_count == after_root_items_count2);
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
let _ = fs::remove_dir_all(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_list_vaults() {
|
||||||
|
// given
|
||||||
|
let temp_path = TempDir::new("").unwrap();
|
||||||
|
let directory = RootDiskDirectory::create(&temp_path).unwrap();
|
||||||
|
let vault_provider = directory.as_vault_provider().unwrap();
|
||||||
|
let iter = NonZeroU32::new(1).expect("1 > 0; qed");
|
||||||
|
vault_provider
|
||||||
|
.create("vault1", VaultKey::new(&"password1".into(), iter))
|
||||||
|
.unwrap();
|
||||||
|
vault_provider
|
||||||
|
.create("vault2", VaultKey::new(&"password2".into(), iter))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// then
|
||||||
|
let vaults = vault_provider.list_vaults().unwrap();
|
||||||
|
assert_eq!(vaults.len(), 2);
|
||||||
|
assert!(vaults.iter().any(|v| &*v == "vault1"));
|
||||||
|
assert!(vaults.iter().any(|v| &*v == "vault2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hash_of_files() {
|
||||||
|
let temp_path = TempDir::new("").unwrap();
|
||||||
|
let directory = RootDiskDirectory::create(&temp_path).unwrap();
|
||||||
|
|
||||||
|
let hash = directory
|
||||||
|
.files_hash()
|
||||||
|
.expect("Files hash should be calculated ok");
|
||||||
|
assert_eq!(hash, 15130871412783076140);
|
||||||
|
|
||||||
|
let keypair = Random.generate().unwrap();
|
||||||
|
let password = "test pass".into();
|
||||||
|
let account = SafeAccount::create(
|
||||||
|
&keypair,
|
||||||
|
[0u8; 16],
|
||||||
|
&password,
|
||||||
|
*ITERATIONS,
|
||||||
|
"Test".to_owned(),
|
||||||
|
"{}".to_owned(),
|
||||||
|
);
|
||||||
|
directory
|
||||||
|
.insert(account.unwrap())
|
||||||
|
.expect("Account should be inserted ok");
|
||||||
|
|
||||||
|
let new_hash = directory
|
||||||
|
.files_hash()
|
||||||
|
.expect("New files hash should be calculated ok");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
new_hash != hash,
|
||||||
|
"hash of the file list should change once directory content changed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
77
accounts/ethstore/src/accounts_dir/memory.rs
Normal file
77
accounts/ethstore/src/accounts_dir/memory.rs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use ethkey::Address;
|
||||||
|
use itertools;
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use super::KeyDirectory;
|
||||||
|
use Error;
|
||||||
|
use SafeAccount;
|
||||||
|
|
||||||
|
/// Accounts in-memory storage.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct MemoryDirectory {
|
||||||
|
accounts: RwLock<HashMap<Address, Vec<SafeAccount>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyDirectory for MemoryDirectory {
|
||||||
|
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
||||||
|
Ok(itertools::Itertools::flatten(self.accounts.read().values().cloned()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
|
let mut lock = self.accounts.write();
|
||||||
|
let accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new);
|
||||||
|
// If the filename is the same we just need to replace the entry
|
||||||
|
accounts.retain(|acc| acc.filename != account.filename);
|
||||||
|
accounts.push(account.clone());
|
||||||
|
Ok(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
|
let mut lock = self.accounts.write();
|
||||||
|
let accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new);
|
||||||
|
accounts.push(account.clone());
|
||||||
|
Ok(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
|
let mut accounts = self.accounts.write();
|
||||||
|
let is_empty = if let Some(accounts) = accounts.get_mut(&account.address) {
|
||||||
|
if let Some(position) = accounts.iter().position(|acc| acc == account) {
|
||||||
|
accounts.remove(position);
|
||||||
|
}
|
||||||
|
accounts.is_empty()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
if is_empty {
|
||||||
|
accounts.remove(&account.address);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unique_repr(&self) -> Result<u64, Error> {
|
||||||
|
let mut val = 0u64;
|
||||||
|
let accounts = self.accounts.read();
|
||||||
|
for acc in accounts.keys() {
|
||||||
|
val = val ^ acc.low_u64()
|
||||||
|
}
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
112
accounts/ethstore/src/accounts_dir/mod.rs
Normal file
112
accounts/ethstore/src/accounts_dir/mod.rs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Accounts Directory
|
||||||
|
|
||||||
|
use ethkey::Password;
|
||||||
|
use std::{num::NonZeroU32, path::PathBuf};
|
||||||
|
use Error;
|
||||||
|
use SafeAccount;
|
||||||
|
|
||||||
|
mod disk;
|
||||||
|
mod memory;
|
||||||
|
mod vault;
|
||||||
|
|
||||||
|
/// `VaultKeyDirectory::set_key` error
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum SetKeyError {
|
||||||
|
/// Error is fatal and directory is probably in inconsistent state
|
||||||
|
Fatal(Error),
|
||||||
|
/// Error is non fatal, directory is reverted to pre-operation state
|
||||||
|
NonFatalOld(Error),
|
||||||
|
/// Error is non fatal, directory is consistent with new key
|
||||||
|
NonFatalNew(Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Vault key
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
|
pub struct VaultKey {
|
||||||
|
/// Vault password
|
||||||
|
pub password: Password,
|
||||||
|
/// Number of iterations to produce a derived key from password
|
||||||
|
pub iterations: NonZeroU32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Keys directory
|
||||||
|
pub trait KeyDirectory: Send + Sync {
|
||||||
|
/// Read keys from directory
|
||||||
|
fn load(&self) -> Result<Vec<SafeAccount>, Error>;
|
||||||
|
/// Insert new key to directory
|
||||||
|
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error>;
|
||||||
|
/// Update key in the directory
|
||||||
|
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error>;
|
||||||
|
/// Remove key from directory
|
||||||
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error>;
|
||||||
|
/// Get directory filesystem path, if available
|
||||||
|
fn path(&self) -> Option<&PathBuf> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
/// Return vault provider, if available
|
||||||
|
fn as_vault_provider(&self) -> Option<&dyn VaultKeyDirectoryProvider> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
/// Unique representation of directory account collection
|
||||||
|
fn unique_repr(&self) -> Result<u64, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Vaults provider
|
||||||
|
pub trait VaultKeyDirectoryProvider {
|
||||||
|
/// Create new vault with given key
|
||||||
|
fn create(&self, name: &str, key: VaultKey) -> Result<Box<dyn VaultKeyDirectory>, Error>;
|
||||||
|
/// Open existing vault with given key
|
||||||
|
fn open(&self, name: &str, key: VaultKey) -> Result<Box<dyn VaultKeyDirectory>, Error>;
|
||||||
|
/// List all vaults
|
||||||
|
fn list_vaults(&self) -> Result<Vec<String>, Error>;
|
||||||
|
/// Get vault meta
|
||||||
|
fn vault_meta(&self, name: &str) -> Result<String, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Vault directory
|
||||||
|
pub trait VaultKeyDirectory: KeyDirectory {
|
||||||
|
/// Cast to `KeyDirectory`
|
||||||
|
fn as_key_directory(&self) -> &dyn KeyDirectory;
|
||||||
|
/// Vault name
|
||||||
|
fn name(&self) -> &str;
|
||||||
|
/// Get vault key
|
||||||
|
fn key(&self) -> VaultKey;
|
||||||
|
/// Set new key for vault
|
||||||
|
fn set_key(&self, key: VaultKey) -> Result<(), SetKeyError>;
|
||||||
|
/// Get vault meta
|
||||||
|
fn meta(&self) -> String;
|
||||||
|
/// Set vault meta
|
||||||
|
fn set_meta(&self, meta: &str) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use self::{
|
||||||
|
disk::{DiskKeyFileManager, KeyFileManager, RootDiskDirectory},
|
||||||
|
memory::MemoryDirectory,
|
||||||
|
vault::VaultDiskDirectory,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl VaultKey {
|
||||||
|
/// Create new vault key
|
||||||
|
pub fn new(password: &Password, iterations: NonZeroU32) -> Self {
|
||||||
|
VaultKey {
|
||||||
|
password: password.clone(),
|
||||||
|
iterations: iterations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
527
accounts/ethstore/src/accounts_dir/vault.rs
Normal file
527
accounts/ethstore/src/accounts_dir/vault.rs
Normal file
@@ -0,0 +1,527 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
super::account::Crypto,
|
||||||
|
disk::{self, DiskDirectory, KeyFileManager},
|
||||||
|
KeyDirectory, SetKeyError, VaultKey, VaultKeyDirectory,
|
||||||
|
};
|
||||||
|
use crypto::Keccak256;
|
||||||
|
use json;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::{
|
||||||
|
fs, io,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
use Error;
|
||||||
|
use SafeAccount;
|
||||||
|
|
||||||
|
/// Name of vault metadata file
|
||||||
|
pub const VAULT_FILE_NAME: &'static str = "vault.json";
|
||||||
|
/// Name of temporary vault metadata file
|
||||||
|
pub const VAULT_TEMP_FILE_NAME: &'static str = "vault_temp.json";
|
||||||
|
|
||||||
|
/// Vault directory implementation
|
||||||
|
pub type VaultDiskDirectory = DiskDirectory<VaultKeyFileManager>;
|
||||||
|
|
||||||
|
/// Vault key file manager
|
||||||
|
pub struct VaultKeyFileManager {
|
||||||
|
name: String,
|
||||||
|
key: VaultKey,
|
||||||
|
meta: Mutex<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VaultDiskDirectory {
|
||||||
|
/// Create new vault directory with given key
|
||||||
|
pub fn create<P>(root: P, name: &str, key: VaultKey) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
// check that vault directory does not exists
|
||||||
|
let vault_dir_path = make_vault_dir_path(root, name, true)?;
|
||||||
|
if vault_dir_path.exists() {
|
||||||
|
return Err(Error::CreationFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create vault && vault file
|
||||||
|
let vault_meta = "{}";
|
||||||
|
fs::create_dir_all(&vault_dir_path)?;
|
||||||
|
if let Err(err) = create_vault_file(&vault_dir_path, &key, vault_meta) {
|
||||||
|
let _ = fs::remove_dir_all(&vault_dir_path); // can't do anything with this
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiskDirectory::new(
|
||||||
|
vault_dir_path,
|
||||||
|
VaultKeyFileManager::new(name, key, vault_meta),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open existing vault directory with given key
|
||||||
|
pub fn at<P>(root: P, name: &str, key: VaultKey) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
// check that vault directory exists
|
||||||
|
let vault_dir_path = make_vault_dir_path(root, name, true)?;
|
||||||
|
if !vault_dir_path.is_dir() {
|
||||||
|
return Err(Error::CreationFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that passed key matches vault file
|
||||||
|
let meta = read_vault_file(&vault_dir_path, Some(&key))?;
|
||||||
|
|
||||||
|
Ok(DiskDirectory::new(
|
||||||
|
vault_dir_path,
|
||||||
|
VaultKeyFileManager::new(name, key, &meta),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read vault meta without actually opening the vault
|
||||||
|
pub fn meta_at<P>(root: P, name: &str) -> Result<String, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
// check that vault directory exists
|
||||||
|
let vault_dir_path = make_vault_dir_path(root, name, true)?;
|
||||||
|
if !vault_dir_path.is_dir() {
|
||||||
|
return Err(Error::VaultNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that passed key matches vault file
|
||||||
|
read_vault_file(&vault_dir_path, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_temp_vault(&self, key: VaultKey) -> Result<VaultDiskDirectory, Error> {
|
||||||
|
let original_path = self
|
||||||
|
.path()
|
||||||
|
.expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed");
|
||||||
|
let mut path: PathBuf = original_path.clone();
|
||||||
|
let name = self.name();
|
||||||
|
|
||||||
|
path.push(name); // to jump to the next level
|
||||||
|
|
||||||
|
let mut index = 0;
|
||||||
|
loop {
|
||||||
|
let name = format!("{}_temp_{}", name, index);
|
||||||
|
path.set_file_name(&name);
|
||||||
|
if !path.exists() {
|
||||||
|
return VaultDiskDirectory::create(original_path, &name, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy_to_vault(&self, vault: &VaultDiskDirectory) -> Result<(), Error> {
|
||||||
|
for account in self.load()? {
|
||||||
|
let filename = account.filename.clone().expect(
|
||||||
|
"self is instance of DiskDirectory; DiskDirectory fills filename in load; qed",
|
||||||
|
);
|
||||||
|
vault.insert_with_filename(account, filename, true)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(&self) -> Result<(), Error> {
|
||||||
|
let path = self
|
||||||
|
.path()
|
||||||
|
.expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed");
|
||||||
|
fs::remove_dir_all(path).map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VaultKeyDirectory for VaultDiskDirectory {
|
||||||
|
fn as_key_directory(&self) -> &dyn KeyDirectory {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
&self.key_manager().name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key(&self) -> VaultKey {
|
||||||
|
self.key_manager().key.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_key(&self, new_key: VaultKey) -> Result<(), SetKeyError> {
|
||||||
|
let temp_vault = VaultDiskDirectory::create_temp_vault(self, new_key.clone())
|
||||||
|
.map_err(|err| SetKeyError::NonFatalOld(err))?;
|
||||||
|
let mut source_path = temp_vault
|
||||||
|
.path()
|
||||||
|
.expect(
|
||||||
|
"temp_vault is instance of DiskDirectory; DiskDirectory always returns path; qed",
|
||||||
|
)
|
||||||
|
.clone();
|
||||||
|
let mut target_path = self
|
||||||
|
.path()
|
||||||
|
.expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed")
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
// preserve meta
|
||||||
|
temp_vault
|
||||||
|
.set_meta(&self.meta())
|
||||||
|
.map_err(SetKeyError::NonFatalOld)?;
|
||||||
|
|
||||||
|
// jump to next fs level
|
||||||
|
source_path.push("next");
|
||||||
|
target_path.push("next");
|
||||||
|
|
||||||
|
let temp_accounts = self
|
||||||
|
.copy_to_vault(&temp_vault)
|
||||||
|
.and_then(|_| temp_vault.load())
|
||||||
|
.map_err(|err| {
|
||||||
|
// ignore error, as we already processing error
|
||||||
|
let _ = temp_vault.delete();
|
||||||
|
SetKeyError::NonFatalOld(err)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// we can't just delete temp vault until all files moved, because
|
||||||
|
// original vault content has already been partially replaced
|
||||||
|
// => when error or crash happens here, we can't do anything
|
||||||
|
for temp_account in temp_accounts {
|
||||||
|
let filename = temp_account.filename.expect(
|
||||||
|
"self is instance of DiskDirectory; DiskDirectory fills filename in load; qed",
|
||||||
|
);
|
||||||
|
source_path.set_file_name(&filename);
|
||||||
|
target_path.set_file_name(&filename);
|
||||||
|
fs::rename(&source_path, &target_path).map_err(|err| SetKeyError::Fatal(err.into()))?;
|
||||||
|
}
|
||||||
|
source_path.set_file_name(VAULT_FILE_NAME);
|
||||||
|
target_path.set_file_name(VAULT_FILE_NAME);
|
||||||
|
fs::rename(source_path, target_path).map_err(|err| SetKeyError::Fatal(err.into()))?;
|
||||||
|
|
||||||
|
temp_vault
|
||||||
|
.delete()
|
||||||
|
.map_err(|err| SetKeyError::NonFatalNew(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn meta(&self) -> String {
|
||||||
|
self.key_manager().meta.lock().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_meta(&self, meta: &str) -> Result<(), Error> {
|
||||||
|
let key_manager = self.key_manager();
|
||||||
|
let vault_path = self
|
||||||
|
.path()
|
||||||
|
.expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed");
|
||||||
|
create_vault_file(vault_path, &key_manager.key, meta)?;
|
||||||
|
*key_manager.meta.lock() = meta.to_owned();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VaultKeyFileManager {
|
||||||
|
pub fn new(name: &str, key: VaultKey, meta: &str) -> Self {
|
||||||
|
VaultKeyFileManager {
|
||||||
|
name: name.into(),
|
||||||
|
key: key,
|
||||||
|
meta: Mutex::new(meta.to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyFileManager for VaultKeyFileManager {
|
||||||
|
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error>
|
||||||
|
where
|
||||||
|
T: io::Read,
|
||||||
|
{
|
||||||
|
let vault_file =
|
||||||
|
json::VaultKeyFile::load(reader).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||||
|
let mut safe_account =
|
||||||
|
SafeAccount::from_vault_file(&self.key.password, vault_file, filename.clone())?;
|
||||||
|
|
||||||
|
safe_account.meta = json::insert_vault_name_to_json_meta(&safe_account.meta, &self.name)
|
||||||
|
.map_err(|err| Error::Custom(format!("{:?}", err)))?;
|
||||||
|
Ok(safe_account)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<T>(&self, mut account: SafeAccount, writer: &mut T) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: io::Write,
|
||||||
|
{
|
||||||
|
account.meta = json::remove_vault_name_from_json_meta(&account.meta)
|
||||||
|
.map_err(|err| Error::Custom(format!("{:?}", err)))?;
|
||||||
|
|
||||||
|
let vault_file: json::VaultKeyFile =
|
||||||
|
account.into_vault_file(self.key.iterations, &self.key.password)?;
|
||||||
|
vault_file
|
||||||
|
.write(writer)
|
||||||
|
.map_err(|e| Error::Custom(format!("{:?}", e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes path to vault directory, checking that vault name is appropriate
|
||||||
|
fn make_vault_dir_path<P>(root: P, name: &str, check_name: bool) -> Result<PathBuf, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
// check vault name
|
||||||
|
if check_name && !check_vault_name(name) {
|
||||||
|
return Err(Error::InvalidVaultName);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut vault_dir_path: PathBuf = root.as_ref().into();
|
||||||
|
vault_dir_path.push(name);
|
||||||
|
Ok(vault_dir_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Every vault must have unique name => we rely on filesystem to check this
|
||||||
|
/// => vault name must not contain any fs-special characters to avoid directory traversal
|
||||||
|
/// => we only allow alphanumeric + separator characters in vault name.
|
||||||
|
fn check_vault_name(name: &str) -> bool {
|
||||||
|
!name.is_empty()
|
||||||
|
&& name
|
||||||
|
.chars()
|
||||||
|
.all(|c| c.is_alphanumeric() || c.is_whitespace() || c == '-' || c == '_')
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Vault can be empty, but still must be pluggable => we store vault password in separate file
|
||||||
|
fn create_vault_file<P>(vault_dir_path: P, key: &VaultKey, meta: &str) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let password_hash = key.password.as_bytes().keccak256();
|
||||||
|
let crypto = Crypto::with_plain(&password_hash, &key.password, key.iterations)?;
|
||||||
|
|
||||||
|
let vault_file_path = vault_dir_path.as_ref().join(VAULT_FILE_NAME);
|
||||||
|
let temp_vault_file_name = disk::find_unique_filename_using_random_suffix(
|
||||||
|
vault_dir_path.as_ref(),
|
||||||
|
&VAULT_TEMP_FILE_NAME,
|
||||||
|
)?;
|
||||||
|
let temp_vault_file_path = vault_dir_path.as_ref().join(&temp_vault_file_name);
|
||||||
|
|
||||||
|
// this method is used to rewrite existing vault file
|
||||||
|
// => write to temporary file first, then rename temporary file to vault file
|
||||||
|
let mut vault_file = disk::create_new_file_with_permissions_to_owner(&temp_vault_file_path)?;
|
||||||
|
let vault_file_contents = json::VaultFile {
|
||||||
|
crypto: crypto.into(),
|
||||||
|
meta: Some(meta.to_owned()),
|
||||||
|
};
|
||||||
|
vault_file_contents
|
||||||
|
.write(&mut vault_file)
|
||||||
|
.map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||||
|
drop(vault_file);
|
||||||
|
fs::rename(&temp_vault_file_path, &vault_file_path)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When vault is opened => we must check that password matches && read metadata
|
||||||
|
fn read_vault_file<P>(vault_dir_path: P, key: Option<&VaultKey>) -> Result<String, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let mut vault_file_path: PathBuf = vault_dir_path.as_ref().into();
|
||||||
|
vault_file_path.push(VAULT_FILE_NAME);
|
||||||
|
|
||||||
|
let vault_file = fs::File::open(vault_file_path)?;
|
||||||
|
let vault_file_contents =
|
||||||
|
json::VaultFile::load(vault_file).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||||
|
let vault_file_meta = vault_file_contents.meta.unwrap_or("{}".to_owned());
|
||||||
|
let vault_file_crypto: Crypto = vault_file_contents.crypto.into();
|
||||||
|
|
||||||
|
if let Some(key) = key {
|
||||||
|
let password_bytes = vault_file_crypto.decrypt(&key.password)?;
|
||||||
|
let password_hash = key.password.as_bytes().keccak256();
|
||||||
|
if password_hash != password_bytes.as_slice() {
|
||||||
|
return Err(Error::InvalidPassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(vault_file_meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
extern crate tempdir;
|
||||||
|
|
||||||
|
use self::tempdir::TempDir;
|
||||||
|
use super::{
|
||||||
|
check_vault_name, create_vault_file, make_vault_dir_path, read_vault_file,
|
||||||
|
VaultDiskDirectory, VaultKey, VAULT_FILE_NAME,
|
||||||
|
};
|
||||||
|
use std::{fs, io::Write, num::NonZeroU32, path::PathBuf};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(1024).expect("1024 > 0; qed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_vault_name_succeeds() {
|
||||||
|
assert!(check_vault_name("vault"));
|
||||||
|
assert!(check_vault_name("vault with spaces"));
|
||||||
|
assert!(check_vault_name("vault with tabs"));
|
||||||
|
assert!(check_vault_name("vault_with_underscores"));
|
||||||
|
assert!(check_vault_name("vault-with-dashes"));
|
||||||
|
assert!(check_vault_name("vault-with-digits-123"));
|
||||||
|
assert!(check_vault_name("vault中文名字"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_vault_name_fails() {
|
||||||
|
assert!(!check_vault_name(""));
|
||||||
|
assert!(!check_vault_name("."));
|
||||||
|
assert!(!check_vault_name("*"));
|
||||||
|
assert!(!check_vault_name("../.bash_history"));
|
||||||
|
assert!(!check_vault_name("/etc/passwd"));
|
||||||
|
assert!(!check_vault_name("c:\\windows"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn make_vault_dir_path_succeeds() {
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
&make_vault_dir_path("/home/user/parity", "vault", true).unwrap(),
|
||||||
|
&Path::new("/home/user/parity/vault")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&make_vault_dir_path("/home/user/parity", "*bad-name*", false).unwrap(),
|
||||||
|
&Path::new("/home/user/parity/*bad-name*")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn make_vault_dir_path_fails() {
|
||||||
|
assert!(make_vault_dir_path("/home/user/parity", "*bad-name*", true).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_vault_file_succeeds() {
|
||||||
|
// given
|
||||||
|
let temp_path = TempDir::new("").unwrap();
|
||||||
|
let key = VaultKey::new(&"password".into(), *ITERATIONS);
|
||||||
|
let mut vault_dir: PathBuf = temp_path.path().into();
|
||||||
|
vault_dir.push("vault");
|
||||||
|
fs::create_dir_all(&vault_dir).unwrap();
|
||||||
|
|
||||||
|
// when
|
||||||
|
let result = create_vault_file(&vault_dir, &key, "{}");
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let mut vault_file_path = vault_dir.clone();
|
||||||
|
vault_file_path.push(VAULT_FILE_NAME);
|
||||||
|
assert!(vault_file_path.exists() && vault_file_path.is_file());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_vault_file_succeeds() {
|
||||||
|
// given
|
||||||
|
let temp_path = TempDir::new("").unwrap();
|
||||||
|
let key = VaultKey::new(&"password".into(), *ITERATIONS);
|
||||||
|
let vault_file_contents = r#"{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"758696c8dc6378ab9b25bb42790da2f5"},"ciphertext":"54eb50683717d41caaeb12ea969f2c159daada5907383f26f327606a37dc7168","kdf":"pbkdf2","kdfparams":{"c":1024,"dklen":32,"prf":"hmac-sha256","salt":"3c320fa566a1a7963ac8df68a19548d27c8f40bf92ef87c84594dcd5bbc402b6"},"mac":"9e5c2314c2a0781962db85611417c614bd6756666b6b1e93840f5b6ed895f003"}}"#;
|
||||||
|
let dir: PathBuf = temp_path.path().into();
|
||||||
|
let mut vault_file_path: PathBuf = dir.clone();
|
||||||
|
vault_file_path.push(VAULT_FILE_NAME);
|
||||||
|
{
|
||||||
|
let mut vault_file = fs::File::create(vault_file_path).unwrap();
|
||||||
|
vault_file
|
||||||
|
.write_all(vault_file_contents.as_bytes())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
let result = read_vault_file(&dir, Some(&key));
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_vault_file_fails() {
|
||||||
|
// given
|
||||||
|
let temp_path = TempDir::new("").unwrap();
|
||||||
|
let key = VaultKey::new(&"password1".into(), *ITERATIONS);
|
||||||
|
let dir: PathBuf = temp_path.path().into();
|
||||||
|
let mut vault_file_path: PathBuf = dir.clone();
|
||||||
|
vault_file_path.push(VAULT_FILE_NAME);
|
||||||
|
|
||||||
|
// when
|
||||||
|
let result = read_vault_file(&dir, Some(&key));
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
// and when given
|
||||||
|
let vault_file_contents = r#"{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"0155e3690be19fbfbecabcd440aa284b"},"ciphertext":"4d6938a1f49b7782","kdf":"pbkdf2","kdfparams":{"c":1024,"dklen":32,"prf":"hmac-sha256","salt":"b6a9338a7ccd39288a86dba73bfecd9101b4f3db9c9830e7c76afdbd4f6872e5"},"mac":"16381463ea11c6eb2239a9f339c2e780516d29d234ce30ac5f166f9080b5a262"}}"#;
|
||||||
|
{
|
||||||
|
let mut vault_file = fs::File::create(vault_file_path).unwrap();
|
||||||
|
vault_file
|
||||||
|
.write_all(vault_file_contents.as_bytes())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
let result = read_vault_file(&dir, Some(&key));
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vault_directory_can_be_created() {
|
||||||
|
// given
|
||||||
|
let temp_path = TempDir::new("").unwrap();
|
||||||
|
let key = VaultKey::new(&"password".into(), *ITERATIONS);
|
||||||
|
let dir: PathBuf = temp_path.path().into();
|
||||||
|
|
||||||
|
// when
|
||||||
|
let vault = VaultDiskDirectory::create(&dir, "vault", key.clone());
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert!(vault.is_ok());
|
||||||
|
|
||||||
|
// and when
|
||||||
|
let vault = VaultDiskDirectory::at(&dir, "vault", key);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert!(vault.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vault_directory_cannot_be_created_if_already_exists() {
|
||||||
|
// given
|
||||||
|
let temp_path = TempDir::new("").unwrap();
|
||||||
|
let key = VaultKey::new(&"password".into(), *ITERATIONS);
|
||||||
|
let dir: PathBuf = temp_path.path().into();
|
||||||
|
let mut vault_dir = dir.clone();
|
||||||
|
vault_dir.push("vault");
|
||||||
|
fs::create_dir_all(&vault_dir).unwrap();
|
||||||
|
|
||||||
|
// when
|
||||||
|
let vault = VaultDiskDirectory::create(&dir, "vault", key);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert!(vault.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vault_directory_cannot_be_opened_if_not_exists() {
|
||||||
|
// given
|
||||||
|
let temp_path = TempDir::new("").unwrap();
|
||||||
|
let key = VaultKey::new(&"password".into(), *ITERATIONS);
|
||||||
|
let dir: PathBuf = temp_path.path().into();
|
||||||
|
|
||||||
|
// when
|
||||||
|
let vault = VaultDiskDirectory::at(&dir, "vault", key);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert!(vault.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
126
accounts/ethstore/src/error.rs
Normal file
126
accounts/ethstore/src/error.rs
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use crypto::{self, Error as EthCryptoError};
|
||||||
|
use ethkey::{self, DerivationError, Error as EthKeyError};
|
||||||
|
use std::{fmt, io::Error as IoError};
|
||||||
|
|
||||||
|
/// Account-related errors.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
/// IO error
|
||||||
|
Io(IoError),
|
||||||
|
/// Invalid Password
|
||||||
|
InvalidPassword,
|
||||||
|
/// Account's secret is invalid.
|
||||||
|
InvalidSecret,
|
||||||
|
/// Invalid Vault Crypto meta.
|
||||||
|
InvalidCryptoMeta,
|
||||||
|
/// Invalid Account.
|
||||||
|
InvalidAccount,
|
||||||
|
/// Invalid Message.
|
||||||
|
InvalidMessage,
|
||||||
|
/// Invalid Key File
|
||||||
|
InvalidKeyFile(String),
|
||||||
|
/// Vaults are not supported.
|
||||||
|
VaultsAreNotSupported,
|
||||||
|
/// Unsupported vault
|
||||||
|
UnsupportedVault,
|
||||||
|
/// Invalid vault name
|
||||||
|
InvalidVaultName,
|
||||||
|
/// Vault not found
|
||||||
|
VaultNotFound,
|
||||||
|
/// Account creation failed.
|
||||||
|
CreationFailed,
|
||||||
|
/// `EthKey` error
|
||||||
|
EthKey(EthKeyError),
|
||||||
|
/// `ethkey::crypto::Error`
|
||||||
|
EthKeyCrypto(ethkey::crypto::Error),
|
||||||
|
/// `EthCrypto` error
|
||||||
|
EthCrypto(EthCryptoError),
|
||||||
|
/// Derivation error
|
||||||
|
Derivation(DerivationError),
|
||||||
|
/// Custom error
|
||||||
|
Custom(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
let s = match *self {
|
||||||
|
Error::Io(ref err) => err.to_string(),
|
||||||
|
Error::InvalidPassword => "Invalid password".into(),
|
||||||
|
Error::InvalidSecret => "Invalid secret".into(),
|
||||||
|
Error::InvalidCryptoMeta => "Invalid crypted metadata".into(),
|
||||||
|
Error::InvalidAccount => "Invalid account".into(),
|
||||||
|
Error::InvalidMessage => "Invalid message".into(),
|
||||||
|
Error::InvalidKeyFile(ref reason) => format!("Invalid key file: {}", reason),
|
||||||
|
Error::VaultsAreNotSupported => "Vaults are not supported".into(),
|
||||||
|
Error::UnsupportedVault => "Vault is not supported for this operation".into(),
|
||||||
|
Error::InvalidVaultName => "Invalid vault name".into(),
|
||||||
|
Error::VaultNotFound => "Vault not found".into(),
|
||||||
|
Error::CreationFailed => "Account creation failed".into(),
|
||||||
|
Error::EthKey(ref err) => err.to_string(),
|
||||||
|
Error::EthKeyCrypto(ref err) => err.to_string(),
|
||||||
|
Error::EthCrypto(ref err) => err.to_string(),
|
||||||
|
Error::Derivation(ref err) => format!("Derivation error: {:?}", err),
|
||||||
|
Error::Custom(ref s) => s.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<IoError> for Error {
|
||||||
|
fn from(err: IoError) -> Self {
|
||||||
|
Error::Io(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EthKeyError> for Error {
|
||||||
|
fn from(err: EthKeyError) -> Self {
|
||||||
|
Error::EthKey(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ethkey::crypto::Error> for Error {
|
||||||
|
fn from(err: ethkey::crypto::Error) -> Self {
|
||||||
|
Error::EthKeyCrypto(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EthCryptoError> for Error {
|
||||||
|
fn from(err: EthCryptoError) -> Self {
|
||||||
|
Error::EthCrypto(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crypto::error::ScryptError> for Error {
|
||||||
|
fn from(err: crypto::error::ScryptError) -> Self {
|
||||||
|
Error::EthCrypto(err.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crypto::error::SymmError> for Error {
|
||||||
|
fn from(err: crypto::error::SymmError) -> Self {
|
||||||
|
Error::EthCrypto(err.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DerivationError> for Error {
|
||||||
|
fn from(err: DerivationError) -> Self {
|
||||||
|
Error::Derivation(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
41
accounts/ethstore/src/ethkey.rs
Normal file
41
accounts/ethstore/src/ethkey.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! ethkey reexport to make documentation look pretty.
|
||||||
|
pub use _ethkey::*;
|
||||||
|
use json;
|
||||||
|
|
||||||
|
impl Into<json::H160> for Address {
|
||||||
|
fn into(self) -> json::H160 {
|
||||||
|
let a: [u8; 20] = self.into();
|
||||||
|
From::from(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<json::H160> for Address {
|
||||||
|
fn from(json: json::H160) -> Self {
|
||||||
|
let a: [u8; 20] = json.into();
|
||||||
|
From::from(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a json::H160> for Address {
|
||||||
|
fn from(json: &'a json::H160) -> Self {
|
||||||
|
let mut a = [0u8; 20];
|
||||||
|
a.copy_from_slice(json);
|
||||||
|
From::from(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
1523
accounts/ethstore/src/ethstore.rs
Normal file
1523
accounts/ethstore/src/ethstore.rs
Normal file
File diff suppressed because it is too large
Load Diff
67
accounts/ethstore/src/import.rs
Normal file
67
accounts/ethstore/src/import.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::{collections::HashSet, fs, path::Path};
|
||||||
|
|
||||||
|
use accounts_dir::{DiskKeyFileManager, KeyDirectory, KeyFileManager};
|
||||||
|
use ethkey::Address;
|
||||||
|
use Error;
|
||||||
|
|
||||||
|
/// Import an account from a file.
|
||||||
|
pub fn import_account(path: &Path, dst: &dyn KeyDirectory) -> Result<Address, Error> {
|
||||||
|
let key_manager = DiskKeyFileManager::default();
|
||||||
|
let existing_accounts = dst
|
||||||
|
.load()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| a.address)
|
||||||
|
.collect::<HashSet<_>>();
|
||||||
|
let filename = path
|
||||||
|
.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.map(|f| f.to_owned());
|
||||||
|
let account = fs::File::open(&path)
|
||||||
|
.map_err(Into::into)
|
||||||
|
.and_then(|file| key_manager.read(filename, file))?;
|
||||||
|
|
||||||
|
let address = account.address.clone();
|
||||||
|
if !existing_accounts.contains(&address) {
|
||||||
|
dst.insert(account)?;
|
||||||
|
}
|
||||||
|
Ok(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Import all accounts from one directory to the other.
|
||||||
|
pub fn import_accounts(
|
||||||
|
src: &dyn KeyDirectory,
|
||||||
|
dst: &dyn KeyDirectory,
|
||||||
|
) -> Result<Vec<Address>, Error> {
|
||||||
|
let accounts = src.load()?;
|
||||||
|
let existing_accounts = dst
|
||||||
|
.load()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| a.address)
|
||||||
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
|
accounts
|
||||||
|
.into_iter()
|
||||||
|
.filter(|a| !existing_accounts.contains(&a.address))
|
||||||
|
.map(|a| {
|
||||||
|
let address = a.address.clone();
|
||||||
|
dst.insert(a)?;
|
||||||
|
Ok(address)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
82
accounts/ethstore/src/json/bytes.rs
Normal file
82
accounts/ethstore/src/json/bytes.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use rustc_hex::{FromHex, FromHexError, ToHex};
|
||||||
|
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
use std::{ops, str};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Bytes(Vec<u8>);
|
||||||
|
|
||||||
|
impl ops::Deref for Bytes {
|
||||||
|
type Target = [u8];
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deserialize<'a> for Bytes {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'a>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
let data = s
|
||||||
|
.from_hex()
|
||||||
|
.map_err(|e| Error::custom(format!("Invalid hex value {}", e)))?;
|
||||||
|
Ok(Bytes(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Bytes {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&self.0.to_hex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl str::FromStr for Bytes {
|
||||||
|
type Err = FromHexError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
s.from_hex().map(Bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for Bytes {
|
||||||
|
fn from(s: &'static str) -> Self {
|
||||||
|
s.parse().expect(&format!(
|
||||||
|
"invalid string literal for {}: '{}'",
|
||||||
|
stringify!(Self),
|
||||||
|
s
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<u8>> for Bytes {
|
||||||
|
fn from(v: Vec<u8>) -> Self {
|
||||||
|
Bytes(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Bytes> for Vec<u8> {
|
||||||
|
fn from(b: Bytes) -> Self {
|
||||||
|
b.0
|
||||||
|
}
|
||||||
|
}
|
||||||
112
accounts/ethstore/src/json/cipher.rs
Normal file
112
accounts/ethstore/src/json/cipher.rs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::{Error, H128};
|
||||||
|
use serde::{
|
||||||
|
de::{Error as SerdeError, Visitor},
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum CipherSer {
|
||||||
|
Aes128Ctr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for CipherSer {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
CipherSer::Aes128Ctr => serializer.serialize_str("aes-128-ctr"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deserialize<'a> for CipherSer {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'a>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_any(CipherSerVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CipherSerVisitor;
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for CipherSerVisitor {
|
||||||
|
type Value = CipherSer;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "a valid cipher identifier")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
match value {
|
||||||
|
"aes-128-ctr" => Ok(CipherSer::Aes128Ctr),
|
||||||
|
_ => Err(SerdeError::custom(Error::UnsupportedCipher)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
self.visit_str(value.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Aes128Ctr {
|
||||||
|
pub iv: H128,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum CipherSerParams {
|
||||||
|
Aes128Ctr(Aes128Ctr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for CipherSerParams {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
CipherSerParams::Aes128Ctr(ref params) => params.serialize(serializer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deserialize<'a> for CipherSerParams {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'a>,
|
||||||
|
{
|
||||||
|
Aes128Ctr::deserialize(deserializer)
|
||||||
|
.map(CipherSerParams::Aes128Ctr)
|
||||||
|
.map_err(|_| Error::InvalidCipherParams)
|
||||||
|
.map_err(SerdeError::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Cipher {
|
||||||
|
Aes128Ctr(Aes128Ctr),
|
||||||
|
}
|
||||||
218
accounts/ethstore/src/json/crypto.rs
Normal file
218
accounts/ethstore/src/json/crypto.rs
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::{Bytes, Cipher, CipherSer, CipherSerParams, Kdf, KdfSer, KdfSerParams, H256};
|
||||||
|
use serde::{
|
||||||
|
de::{Error, MapAccess, Visitor},
|
||||||
|
ser::SerializeStruct,
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
};
|
||||||
|
use serde_json;
|
||||||
|
use std::{fmt, str};
|
||||||
|
|
||||||
|
pub type CipherText = Bytes;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Crypto {
|
||||||
|
pub cipher: Cipher,
|
||||||
|
pub ciphertext: CipherText,
|
||||||
|
pub kdf: Kdf,
|
||||||
|
pub mac: H256,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl str::FromStr for Crypto {
|
||||||
|
type Err = serde_json::error::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
serde_json::from_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Crypto> for String {
|
||||||
|
fn from(c: Crypto) -> Self {
|
||||||
|
serde_json::to_string(&c)
|
||||||
|
.expect("serialization cannot fail, cause all crypto keys are strings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CryptoField {
|
||||||
|
Cipher,
|
||||||
|
CipherParams,
|
||||||
|
CipherText,
|
||||||
|
Kdf,
|
||||||
|
KdfParams,
|
||||||
|
Mac,
|
||||||
|
Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deserialize<'a> for CryptoField {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<CryptoField, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'a>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_any(CryptoFieldVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CryptoFieldVisitor;
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for CryptoFieldVisitor {
|
||||||
|
type Value = CryptoField;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "a valid crypto struct description")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: Error,
|
||||||
|
{
|
||||||
|
match value {
|
||||||
|
"cipher" => Ok(CryptoField::Cipher),
|
||||||
|
"cipherparams" => Ok(CryptoField::CipherParams),
|
||||||
|
"ciphertext" => Ok(CryptoField::CipherText),
|
||||||
|
"kdf" => Ok(CryptoField::Kdf),
|
||||||
|
"kdfparams" => Ok(CryptoField::KdfParams),
|
||||||
|
"mac" => Ok(CryptoField::Mac),
|
||||||
|
"version" => Ok(CryptoField::Version),
|
||||||
|
_ => Err(Error::custom(format!("Unknown field: '{}'", value))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deserialize<'a> for Crypto {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Crypto, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'a>,
|
||||||
|
{
|
||||||
|
static FIELDS: &'static [&'static str] = &["id", "version", "crypto", "Crypto", "address"];
|
||||||
|
deserializer.deserialize_struct("Crypto", FIELDS, CryptoVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CryptoVisitor;
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for CryptoVisitor {
|
||||||
|
type Value = Crypto;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "a valid vault crypto object")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
|
||||||
|
where
|
||||||
|
V: MapAccess<'a>,
|
||||||
|
{
|
||||||
|
let mut cipher = None;
|
||||||
|
let mut cipherparams = None;
|
||||||
|
let mut ciphertext = None;
|
||||||
|
let mut kdf = None;
|
||||||
|
let mut kdfparams = None;
|
||||||
|
let mut mac = None;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match visitor.next_key()? {
|
||||||
|
Some(CryptoField::Cipher) => {
|
||||||
|
cipher = Some(visitor.next_value()?);
|
||||||
|
}
|
||||||
|
Some(CryptoField::CipherParams) => {
|
||||||
|
cipherparams = Some(visitor.next_value()?);
|
||||||
|
}
|
||||||
|
Some(CryptoField::CipherText) => {
|
||||||
|
ciphertext = Some(visitor.next_value()?);
|
||||||
|
}
|
||||||
|
Some(CryptoField::Kdf) => {
|
||||||
|
kdf = Some(visitor.next_value()?);
|
||||||
|
}
|
||||||
|
Some(CryptoField::KdfParams) => {
|
||||||
|
kdfparams = Some(visitor.next_value()?);
|
||||||
|
}
|
||||||
|
Some(CryptoField::Mac) => {
|
||||||
|
mac = Some(visitor.next_value()?);
|
||||||
|
}
|
||||||
|
// skip not required version field (it appears in pyethereum generated keystores)
|
||||||
|
Some(CryptoField::Version) => visitor.next_value().unwrap_or(()),
|
||||||
|
None => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cipher = match (cipher, cipherparams) {
|
||||||
|
(Some(CipherSer::Aes128Ctr), Some(CipherSerParams::Aes128Ctr(params))) => {
|
||||||
|
Cipher::Aes128Ctr(params)
|
||||||
|
}
|
||||||
|
(None, _) => return Err(V::Error::missing_field("cipher")),
|
||||||
|
(Some(_), None) => return Err(V::Error::missing_field("cipherparams")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let ciphertext = match ciphertext {
|
||||||
|
Some(ciphertext) => ciphertext,
|
||||||
|
None => return Err(V::Error::missing_field("ciphertext")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let kdf = match (kdf, kdfparams) {
|
||||||
|
(Some(KdfSer::Pbkdf2), Some(KdfSerParams::Pbkdf2(params))) => Kdf::Pbkdf2(params),
|
||||||
|
(Some(KdfSer::Scrypt), Some(KdfSerParams::Scrypt(params))) => Kdf::Scrypt(params),
|
||||||
|
(Some(_), Some(_)) => return Err(V::Error::custom("Invalid cipherparams")),
|
||||||
|
(None, _) => return Err(V::Error::missing_field("kdf")),
|
||||||
|
(Some(_), None) => return Err(V::Error::missing_field("kdfparams")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mac = match mac {
|
||||||
|
Some(mac) => mac,
|
||||||
|
None => return Err(V::Error::missing_field("mac")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Crypto {
|
||||||
|
cipher: cipher,
|
||||||
|
ciphertext: ciphertext,
|
||||||
|
kdf: kdf,
|
||||||
|
mac: mac,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Crypto {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let mut crypto = serializer.serialize_struct("Crypto", 6)?;
|
||||||
|
match self.cipher {
|
||||||
|
Cipher::Aes128Ctr(ref params) => {
|
||||||
|
crypto.serialize_field("cipher", &CipherSer::Aes128Ctr)?;
|
||||||
|
crypto.serialize_field("cipherparams", params)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crypto.serialize_field("ciphertext", &self.ciphertext)?;
|
||||||
|
match self.kdf {
|
||||||
|
Kdf::Pbkdf2(ref params) => {
|
||||||
|
crypto.serialize_field("kdf", &KdfSer::Pbkdf2)?;
|
||||||
|
crypto.serialize_field("kdfparams", params)?;
|
||||||
|
}
|
||||||
|
Kdf::Scrypt(ref params) => {
|
||||||
|
crypto.serialize_field("kdf", &KdfSer::Scrypt)?;
|
||||||
|
crypto.serialize_field("kdfparams", params)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
crypto.serialize_field("mac", &self.mac)?;
|
||||||
|
crypto.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
50
accounts/ethstore/src/json/error.rs
Normal file
50
accounts/ethstore/src/json/error.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Error {
|
||||||
|
UnsupportedCipher,
|
||||||
|
InvalidCipherParams,
|
||||||
|
UnsupportedKdf,
|
||||||
|
InvalidUuid,
|
||||||
|
UnsupportedVersion,
|
||||||
|
InvalidCiphertext,
|
||||||
|
InvalidH256,
|
||||||
|
InvalidPrf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
match *self {
|
||||||
|
Error::InvalidUuid => write!(f, "Invalid Uuid"),
|
||||||
|
Error::UnsupportedVersion => write!(f, "Unsupported version"),
|
||||||
|
Error::UnsupportedKdf => write!(f, "Unsupported kdf"),
|
||||||
|
Error::InvalidCiphertext => write!(f, "Invalid ciphertext"),
|
||||||
|
Error::UnsupportedCipher => write!(f, "Unsupported cipher"),
|
||||||
|
Error::InvalidCipherParams => write!(f, "Invalid cipher params"),
|
||||||
|
Error::InvalidH256 => write!(f, "Invalid hash"),
|
||||||
|
Error::InvalidPrf => write!(f, "Invalid prf"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<String> for Error {
|
||||||
|
fn into(self) -> String {
|
||||||
|
format!("{}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
135
accounts/ethstore/src/json/hash.rs
Normal file
135
accounts/ethstore/src/json/hash.rs
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::Error;
|
||||||
|
use rustc_hex::{FromHex, ToHex};
|
||||||
|
use serde::{
|
||||||
|
de::{Error as SerdeError, Visitor},
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
};
|
||||||
|
use std::{fmt, ops, str};
|
||||||
|
|
||||||
|
macro_rules! impl_hash {
|
||||||
|
($name: ident, $size: expr) => {
|
||||||
|
pub struct $name([u8; $size]);
|
||||||
|
|
||||||
|
impl fmt::Debug for $name {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
let self_ref: &[u8] = &self.0;
|
||||||
|
write!(f, "{:?}", self_ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for $name {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
let self_ref: &[u8] = &self.0;
|
||||||
|
let other_ref: &[u8] = &other.0;
|
||||||
|
self_ref == other_ref
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::Deref for $name {
|
||||||
|
type Target = [u8];
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for $name {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&self.0.to_hex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deserialize<'a> for $name {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'a>,
|
||||||
|
{
|
||||||
|
struct HashVisitor;
|
||||||
|
|
||||||
|
impl<'b> Visitor<'b> for HashVisitor {
|
||||||
|
type Value = $name;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "a hex-encoded {}", stringify!($name))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
value.parse().map_err(SerdeError::custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
self.visit_str(value.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_any(HashVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl str::FromStr for $name {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||||
|
match value.from_hex() {
|
||||||
|
Ok(ref hex) if hex.len() == $size => {
|
||||||
|
let mut hash = [0u8; $size];
|
||||||
|
hash.clone_from_slice(hex);
|
||||||
|
Ok($name(hash))
|
||||||
|
}
|
||||||
|
_ => Err(Error::InvalidH256),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for $name {
|
||||||
|
fn from(s: &'static str) -> Self {
|
||||||
|
s.parse().expect(&format!(
|
||||||
|
"invalid string literal for {}: '{}'",
|
||||||
|
stringify!($name),
|
||||||
|
s
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<[u8; $size]> for $name {
|
||||||
|
fn from(bytes: [u8; $size]) -> Self {
|
||||||
|
$name(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<[u8; $size]> for $name {
|
||||||
|
fn into(self) -> [u8; $size] {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_hash!(H128, 16);
|
||||||
|
impl_hash!(H160, 20);
|
||||||
|
impl_hash!(H256, 32);
|
||||||
179
accounts/ethstore/src/json/id.rs
Normal file
179
accounts/ethstore/src/json/id.rs
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Universaly unique identifier.
|
||||||
|
use super::Error;
|
||||||
|
use rustc_hex::{FromHex, ToHex};
|
||||||
|
use serde::{
|
||||||
|
de::{Error as SerdeError, Visitor},
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
};
|
||||||
|
use std::{fmt, str};
|
||||||
|
|
||||||
|
/// Universaly unique identifier.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Uuid([u8; 16]);
|
||||||
|
|
||||||
|
impl From<[u8; 16]> for Uuid {
|
||||||
|
fn from(uuid: [u8; 16]) -> Self {
|
||||||
|
Uuid(uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Into<String> for &'a Uuid {
|
||||||
|
fn into(self) -> String {
|
||||||
|
let d1 = &self.0[0..4];
|
||||||
|
let d2 = &self.0[4..6];
|
||||||
|
let d3 = &self.0[6..8];
|
||||||
|
let d4 = &self.0[8..10];
|
||||||
|
let d5 = &self.0[10..16];
|
||||||
|
[d1, d2, d3, d4, d5]
|
||||||
|
.iter()
|
||||||
|
.map(|d| d.to_hex())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("-")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<String> for Uuid {
|
||||||
|
fn into(self) -> String {
|
||||||
|
Into::into(&self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<[u8; 16]> for Uuid {
|
||||||
|
fn into(self) -> [u8; 16] {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Uuid {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
let s: String = (self as &Uuid).into();
|
||||||
|
write!(f, "{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy_into(from: &str, into: &mut [u8]) -> Result<(), Error> {
|
||||||
|
let from = from.from_hex().map_err(|_| Error::InvalidUuid)?;
|
||||||
|
|
||||||
|
if from.len() != into.len() {
|
||||||
|
return Err(Error::InvalidUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
into.copy_from_slice(&from);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl str::FromStr for Uuid {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let parts: Vec<&str> = s.split("-").collect();
|
||||||
|
|
||||||
|
if parts.len() != 5 {
|
||||||
|
return Err(Error::InvalidUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut uuid = [0u8; 16];
|
||||||
|
|
||||||
|
copy_into(parts[0], &mut uuid[0..4])?;
|
||||||
|
copy_into(parts[1], &mut uuid[4..6])?;
|
||||||
|
copy_into(parts[2], &mut uuid[6..8])?;
|
||||||
|
copy_into(parts[3], &mut uuid[8..10])?;
|
||||||
|
copy_into(parts[4], &mut uuid[10..16])?;
|
||||||
|
|
||||||
|
Ok(Uuid(uuid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for Uuid {
|
||||||
|
fn from(s: &'static str) -> Self {
|
||||||
|
s.parse().expect(&format!(
|
||||||
|
"invalid string literal for {}: '{}'",
|
||||||
|
stringify!(Self),
|
||||||
|
s
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Uuid {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let s: String = self.into();
|
||||||
|
serializer.serialize_str(&s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deserialize<'a> for Uuid {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'a>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_any(UuidVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UuidVisitor;
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for UuidVisitor {
|
||||||
|
type Value = Uuid;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "a valid hex-encoded UUID")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
value.parse().map_err(SerdeError::custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
self.visit_str(value.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Uuid;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn uuid_from_str() {
|
||||||
|
let uuid: Uuid = "3198bc9c-6672-5ab3-d995-4942343ae5b6".into();
|
||||||
|
assert_eq!(
|
||||||
|
uuid,
|
||||||
|
Uuid::from([
|
||||||
|
0x31, 0x98, 0xbc, 0x9c, 0x66, 0x72, 0x5a, 0xb3, 0xd9, 0x95, 0x49, 0x42, 0x34, 0x3a,
|
||||||
|
0xe5, 0xb6
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn uuid_from_and_to_str() {
|
||||||
|
let from = "3198bc9c-6672-5ab3-d995-4942343ae5b6";
|
||||||
|
let uuid: Uuid = from.into();
|
||||||
|
let to: String = uuid.into();
|
||||||
|
assert_eq!(from, &to);
|
||||||
|
}
|
||||||
|
}
|
||||||
186
accounts/ethstore/src/json/kdf.rs
Normal file
186
accounts/ethstore/src/json/kdf.rs
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::{Bytes, Error};
|
||||||
|
use serde::{
|
||||||
|
de::{Error as SerdeError, Visitor},
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
};
|
||||||
|
use std::{fmt, num::NonZeroU32};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum KdfSer {
|
||||||
|
Pbkdf2,
|
||||||
|
Scrypt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for KdfSer {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
KdfSer::Pbkdf2 => serializer.serialize_str("pbkdf2"),
|
||||||
|
KdfSer::Scrypt => serializer.serialize_str("scrypt"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deserialize<'a> for KdfSer {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'a>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_any(KdfSerVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct KdfSerVisitor;
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for KdfSerVisitor {
|
||||||
|
type Value = KdfSer;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "a kdf algorithm identifier")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
match value {
|
||||||
|
"pbkdf2" => Ok(KdfSer::Pbkdf2),
|
||||||
|
"scrypt" => Ok(KdfSer::Scrypt),
|
||||||
|
_ => Err(SerdeError::custom(Error::UnsupportedKdf)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
self.visit_str(value.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Prf {
|
||||||
|
HmacSha256,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Prf {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
Prf::HmacSha256 => serializer.serialize_str("hmac-sha256"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deserialize<'a> for Prf {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'a>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_any(PrfVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PrfVisitor;
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for PrfVisitor {
|
||||||
|
type Value = Prf;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "a prf algorithm identifier")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
match value {
|
||||||
|
"hmac-sha256" => Ok(Prf::HmacSha256),
|
||||||
|
_ => Err(SerdeError::custom(Error::InvalidPrf)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
self.visit_str(value.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Pbkdf2 {
|
||||||
|
pub c: NonZeroU32,
|
||||||
|
pub dklen: u32,
|
||||||
|
pub prf: Prf,
|
||||||
|
pub salt: Bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Scrypt {
|
||||||
|
pub dklen: u32,
|
||||||
|
pub p: u32,
|
||||||
|
pub n: u32,
|
||||||
|
pub r: u32,
|
||||||
|
pub salt: Bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum KdfSerParams {
|
||||||
|
Pbkdf2(Pbkdf2),
|
||||||
|
Scrypt(Scrypt),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for KdfSerParams {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
KdfSerParams::Pbkdf2(ref params) => params.serialize(serializer),
|
||||||
|
KdfSerParams::Scrypt(ref params) => params.serialize(serializer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deserialize<'a> for KdfSerParams {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'a>,
|
||||||
|
{
|
||||||
|
use serde_json::{from_value, Value};
|
||||||
|
|
||||||
|
let v: Value = Deserialize::deserialize(deserializer)?;
|
||||||
|
|
||||||
|
from_value(v.clone())
|
||||||
|
.map(KdfSerParams::Pbkdf2)
|
||||||
|
.or_else(|_| from_value(v).map(KdfSerParams::Scrypt))
|
||||||
|
.map_err(|_| D::Error::custom("Invalid KDF algorithm"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Kdf {
|
||||||
|
Pbkdf2(Pbkdf2),
|
||||||
|
Scrypt(Scrypt),
|
||||||
|
}
|
||||||
359
accounts/ethstore/src/json/key_file.rs
Normal file
359
accounts/ethstore/src/json/key_file.rs
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::{Crypto, Uuid, Version, H160};
|
||||||
|
use serde::{
|
||||||
|
de::{DeserializeOwned, Error, MapAccess, Visitor},
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
};
|
||||||
|
use serde_json;
|
||||||
|
use std::{
|
||||||
|
fmt,
|
||||||
|
io::{Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Public opaque type representing serializable `KeyFile`.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct OpaqueKeyFile {
|
||||||
|
key_file: KeyFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for OpaqueKeyFile {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
self.key_file.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<T> for OpaqueKeyFile
|
||||||
|
where
|
||||||
|
T: Into<KeyFile>,
|
||||||
|
{
|
||||||
|
fn from(val: T) -> Self {
|
||||||
|
OpaqueKeyFile {
|
||||||
|
key_file: val.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize)]
|
||||||
|
pub struct KeyFile {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub version: Version,
|
||||||
|
pub crypto: Crypto,
|
||||||
|
pub address: Option<H160>,
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub meta: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum KeyFileField {
|
||||||
|
Id,
|
||||||
|
Version,
|
||||||
|
Crypto,
|
||||||
|
Address,
|
||||||
|
Name,
|
||||||
|
Meta,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deserialize<'a> for KeyFileField {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<KeyFileField, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'a>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_any(KeyFileFieldVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct KeyFileFieldVisitor;
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for KeyFileFieldVisitor {
|
||||||
|
type Value = KeyFileField;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "a valid key file field")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: Error,
|
||||||
|
{
|
||||||
|
match value {
|
||||||
|
"id" => Ok(KeyFileField::Id),
|
||||||
|
"version" => Ok(KeyFileField::Version),
|
||||||
|
"crypto" => Ok(KeyFileField::Crypto),
|
||||||
|
"Crypto" => Ok(KeyFileField::Crypto),
|
||||||
|
"address" => Ok(KeyFileField::Address),
|
||||||
|
"name" => Ok(KeyFileField::Name),
|
||||||
|
"meta" => Ok(KeyFileField::Meta),
|
||||||
|
_ => Err(Error::custom(format!("Unknown field: '{}'", value))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deserialize<'a> for KeyFile {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<KeyFile, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'a>,
|
||||||
|
{
|
||||||
|
static FIELDS: &'static [&'static str] = &["id", "version", "crypto", "Crypto", "address"];
|
||||||
|
deserializer.deserialize_struct("KeyFile", FIELDS, KeyFileVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn none_if_empty<'a, T>(v: Option<serde_json::Value>) -> Option<T>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
v.and_then(|v| {
|
||||||
|
if v.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
serde_json::from_value(v).ok()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
struct KeyFileVisitor;
|
||||||
|
impl<'a> Visitor<'a> for KeyFileVisitor {
|
||||||
|
type Value = KeyFile;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "a valid key object")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
|
||||||
|
where
|
||||||
|
V: MapAccess<'a>,
|
||||||
|
{
|
||||||
|
let mut id = None;
|
||||||
|
let mut version = None;
|
||||||
|
let mut crypto = None;
|
||||||
|
let mut address = None;
|
||||||
|
let mut name = None;
|
||||||
|
let mut meta = None;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match visitor.next_key()? {
|
||||||
|
Some(KeyFileField::Id) => {
|
||||||
|
id = Some(visitor.next_value()?);
|
||||||
|
}
|
||||||
|
Some(KeyFileField::Version) => {
|
||||||
|
version = Some(visitor.next_value()?);
|
||||||
|
}
|
||||||
|
Some(KeyFileField::Crypto) => {
|
||||||
|
crypto = Some(visitor.next_value()?);
|
||||||
|
}
|
||||||
|
Some(KeyFileField::Address) => {
|
||||||
|
address = Some(visitor.next_value()?);
|
||||||
|
}
|
||||||
|
Some(KeyFileField::Name) => name = none_if_empty(visitor.next_value().ok()),
|
||||||
|
Some(KeyFileField::Meta) => meta = none_if_empty(visitor.next_value().ok()),
|
||||||
|
None => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = match id {
|
||||||
|
Some(id) => id,
|
||||||
|
None => return Err(V::Error::missing_field("id")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let version = match version {
|
||||||
|
Some(version) => version,
|
||||||
|
None => return Err(V::Error::missing_field("version")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let crypto = match crypto {
|
||||||
|
Some(crypto) => crypto,
|
||||||
|
None => return Err(V::Error::missing_field("crypto")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = KeyFile {
|
||||||
|
id: id,
|
||||||
|
version: version,
|
||||||
|
crypto: crypto,
|
||||||
|
address: address,
|
||||||
|
name: name,
|
||||||
|
meta: meta,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyFile {
|
||||||
|
pub fn load<R>(reader: R) -> Result<Self, serde_json::Error>
|
||||||
|
where
|
||||||
|
R: Read,
|
||||||
|
{
|
||||||
|
serde_json::from_reader(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<W>(&self, writer: &mut W) -> Result<(), serde_json::Error>
|
||||||
|
where
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
serde_json::to_writer(writer, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use json::{Aes128Ctr, Cipher, Crypto, Kdf, KeyFile, Scrypt, Uuid, Version};
|
||||||
|
use serde_json;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_keyfile() {
|
||||||
|
let json = r#"
|
||||||
|
{
|
||||||
|
"address": "6edddfc6349aff20bc6467ccf276c5b52487f7a8",
|
||||||
|
"crypto": {
|
||||||
|
"cipher": "aes-128-ctr",
|
||||||
|
"ciphertext": "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc",
|
||||||
|
"cipherparams": {
|
||||||
|
"iv": "b5a7ec855ec9e2c405371356855fec83"
|
||||||
|
},
|
||||||
|
"kdf": "scrypt",
|
||||||
|
"kdfparams": {
|
||||||
|
"dklen": 32,
|
||||||
|
"n": 262144,
|
||||||
|
"p": 1,
|
||||||
|
"r": 8,
|
||||||
|
"salt": "1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209"
|
||||||
|
},
|
||||||
|
"mac": "46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f"
|
||||||
|
},
|
||||||
|
"id": "8777d9f6-7860-4b9b-88b7-0b57ee6b3a73",
|
||||||
|
"version": 3,
|
||||||
|
"name": "Test",
|
||||||
|
"meta": "{}"
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let expected = KeyFile {
|
||||||
|
id: Uuid::from_str("8777d9f6-7860-4b9b-88b7-0b57ee6b3a73").unwrap(),
|
||||||
|
version: Version::V3,
|
||||||
|
address: Some("6edddfc6349aff20bc6467ccf276c5b52487f7a8".into()),
|
||||||
|
crypto: Crypto {
|
||||||
|
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||||
|
iv: "b5a7ec855ec9e2c405371356855fec83".into(),
|
||||||
|
}),
|
||||||
|
ciphertext: "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc"
|
||||||
|
.into(),
|
||||||
|
kdf: Kdf::Scrypt(Scrypt {
|
||||||
|
n: 262144,
|
||||||
|
dklen: 32,
|
||||||
|
p: 1,
|
||||||
|
r: 8,
|
||||||
|
salt: "1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209".into(),
|
||||||
|
}),
|
||||||
|
mac: "46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f".into(),
|
||||||
|
},
|
||||||
|
name: Some("Test".to_owned()),
|
||||||
|
meta: Some("{}".to_owned()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let keyfile: KeyFile = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(keyfile, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn capital_crypto_keyfile() {
|
||||||
|
let json = r#"
|
||||||
|
{
|
||||||
|
"address": "6edddfc6349aff20bc6467ccf276c5b52487f7a8",
|
||||||
|
"Crypto": {
|
||||||
|
"cipher": "aes-128-ctr",
|
||||||
|
"ciphertext": "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc",
|
||||||
|
"cipherparams": {
|
||||||
|
"iv": "b5a7ec855ec9e2c405371356855fec83"
|
||||||
|
},
|
||||||
|
"kdf": "scrypt",
|
||||||
|
"kdfparams": {
|
||||||
|
"dklen": 32,
|
||||||
|
"n": 262144,
|
||||||
|
"p": 1,
|
||||||
|
"r": 8,
|
||||||
|
"salt": "1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209"
|
||||||
|
},
|
||||||
|
"mac": "46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f"
|
||||||
|
},
|
||||||
|
"id": "8777d9f6-7860-4b9b-88b7-0b57ee6b3a73",
|
||||||
|
"version": 3
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let expected = KeyFile {
|
||||||
|
id: "8777d9f6-7860-4b9b-88b7-0b57ee6b3a73".into(),
|
||||||
|
version: Version::V3,
|
||||||
|
address: Some("6edddfc6349aff20bc6467ccf276c5b52487f7a8".into()),
|
||||||
|
crypto: Crypto {
|
||||||
|
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||||
|
iv: "b5a7ec855ec9e2c405371356855fec83".into(),
|
||||||
|
}),
|
||||||
|
ciphertext: "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc"
|
||||||
|
.into(),
|
||||||
|
kdf: Kdf::Scrypt(Scrypt {
|
||||||
|
n: 262144,
|
||||||
|
dklen: 32,
|
||||||
|
p: 1,
|
||||||
|
r: 8,
|
||||||
|
salt: "1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209".into(),
|
||||||
|
}),
|
||||||
|
mac: "46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f".into(),
|
||||||
|
},
|
||||||
|
name: None,
|
||||||
|
meta: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let keyfile: KeyFile = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(keyfile, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_and_from_json() {
|
||||||
|
let file = KeyFile {
|
||||||
|
id: "8777d9f6-7860-4b9b-88b7-0b57ee6b3a73".into(),
|
||||||
|
version: Version::V3,
|
||||||
|
address: Some("6edddfc6349aff20bc6467ccf276c5b52487f7a8".into()),
|
||||||
|
crypto: Crypto {
|
||||||
|
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||||
|
iv: "b5a7ec855ec9e2c405371356855fec83".into(),
|
||||||
|
}),
|
||||||
|
ciphertext: "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc"
|
||||||
|
.into(),
|
||||||
|
kdf: Kdf::Scrypt(Scrypt {
|
||||||
|
n: 262144,
|
||||||
|
dklen: 32,
|
||||||
|
p: 1,
|
||||||
|
r: 8,
|
||||||
|
salt: "1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209".into(),
|
||||||
|
}),
|
||||||
|
mac: "46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f".into(),
|
||||||
|
},
|
||||||
|
name: Some("Test".to_owned()),
|
||||||
|
meta: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&file).unwrap();
|
||||||
|
println!("{}", serialized);
|
||||||
|
let deserialized = serde_json::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(file, deserialized);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
accounts/ethstore/src/json/mod.rs
Normal file
48
accounts/ethstore/src/json/mod.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Contract interface specification.
|
||||||
|
|
||||||
|
mod bytes;
|
||||||
|
mod cipher;
|
||||||
|
mod crypto;
|
||||||
|
mod error;
|
||||||
|
mod hash;
|
||||||
|
mod id;
|
||||||
|
mod kdf;
|
||||||
|
mod key_file;
|
||||||
|
mod presale;
|
||||||
|
mod vault_file;
|
||||||
|
mod vault_key_file;
|
||||||
|
mod version;
|
||||||
|
|
||||||
|
pub use self::{
|
||||||
|
bytes::Bytes,
|
||||||
|
cipher::{Aes128Ctr, Cipher, CipherSer, CipherSerParams},
|
||||||
|
crypto::{CipherText, Crypto},
|
||||||
|
error::Error,
|
||||||
|
hash::{H128, H160, H256},
|
||||||
|
id::Uuid,
|
||||||
|
kdf::{Kdf, KdfSer, KdfSerParams, Pbkdf2, Prf, Scrypt},
|
||||||
|
key_file::{KeyFile, OpaqueKeyFile},
|
||||||
|
presale::{Encseed, PresaleWallet},
|
||||||
|
vault_file::VaultFile,
|
||||||
|
vault_key_file::{
|
||||||
|
insert_vault_name_to_json_meta, remove_vault_name_from_json_meta, VaultKeyFile,
|
||||||
|
VaultKeyMeta,
|
||||||
|
},
|
||||||
|
version::Version,
|
||||||
|
};
|
||||||
@@ -1,31 +1,50 @@
|
|||||||
use std::io::Read;
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::{Bytes, H160};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use super::{H160, Bytes};
|
use std::io::Read;
|
||||||
|
|
||||||
pub type Encseed = Bytes;
|
pub type Encseed = Bytes;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Deserialize)]
|
#[derive(Debug, PartialEq, Deserialize)]
|
||||||
pub struct PresaleWallet {
|
pub struct PresaleWallet {
|
||||||
pub encseed: Encseed,
|
pub encseed: Encseed,
|
||||||
#[serde(rename = "ethaddr")]
|
#[serde(rename = "ethaddr")]
|
||||||
pub address: H160,
|
pub address: H160,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PresaleWallet {
|
impl PresaleWallet {
|
||||||
pub fn load<R>(reader: R) -> Result<Self, serde_json::Error> where R: Read {
|
pub fn load<R>(reader: R) -> Result<Self, serde_json::Error>
|
||||||
serde_json::from_reader(reader)
|
where
|
||||||
}
|
R: Read,
|
||||||
|
{
|
||||||
|
serde_json::from_reader(reader)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::str::FromStr;
|
use json::{PresaleWallet, H160};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use json::{PresaleWallet, H160};
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn presale_wallet() {
|
fn presale_wallet() {
|
||||||
let json = r#"
|
let json = r#"
|
||||||
{
|
{
|
||||||
"encseed": "137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066",
|
"encseed": "137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066",
|
||||||
"ethaddr": "ede84640d1a1d3e06902048e67aa7db8d52c2ce1",
|
"ethaddr": "ede84640d1a1d3e06902048e67aa7db8d52c2ce1",
|
||||||
@@ -33,18 +52,18 @@ mod tests {
|
|||||||
"btcaddr": "1JvqEc6WLhg6GnyrLBe2ztPAU28KRfuseH"
|
"btcaddr": "1JvqEc6WLhg6GnyrLBe2ztPAU28KRfuseH"
|
||||||
} "#;
|
} "#;
|
||||||
|
|
||||||
let expected = PresaleWallet {
|
let expected = PresaleWallet {
|
||||||
encseed: "137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066".into(),
|
encseed: "137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066".into(),
|
||||||
address: H160::from_str("ede84640d1a1d3e06902048e67aa7db8d52c2ce1").unwrap(),
|
address: H160::from_str("ede84640d1a1d3e06902048e67aa7db8d52c2ce1").unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let wallet: PresaleWallet = serde_json::from_str(json).unwrap();
|
let wallet: PresaleWallet = serde_json::from_str(json).unwrap();
|
||||||
assert_eq!(expected, wallet);
|
assert_eq!(expected, wallet);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn long_presale_wallet() {
|
fn long_presale_wallet() {
|
||||||
let json = r#"
|
let json = r#"
|
||||||
{
|
{
|
||||||
"encseed":
|
"encseed":
|
||||||
"137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0d",
|
"137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0d",
|
||||||
@@ -53,12 +72,12 @@ mod tests {
|
|||||||
"btcaddr": "1JvqEc6WLhg6GnyrLBe2ztPAU28KRfuseH"
|
"btcaddr": "1JvqEc6WLhg6GnyrLBe2ztPAU28KRfuseH"
|
||||||
} "#;
|
} "#;
|
||||||
|
|
||||||
let expected = PresaleWallet {
|
let expected = PresaleWallet {
|
||||||
encseed: "137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0d".into(),
|
encseed: "137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0d".into(),
|
||||||
address: H160::from_str("ede84640d1a1d3e06902048e67aa7db8d52c2ce1").unwrap(),
|
address: H160::from_str("ede84640d1a1d3e06902048e67aa7db8d52c2ce1").unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let wallet: PresaleWallet = serde_json::from_str(json).unwrap();
|
let wallet: PresaleWallet = serde_json::from_str(json).unwrap();
|
||||||
assert_eq!(expected, wallet);
|
assert_eq!(expected, wallet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
105
accounts/ethstore/src/json/vault_file.rs
Normal file
105
accounts/ethstore/src/json/vault_file.rs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::Crypto;
|
||||||
|
use serde_json;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
/// Vault meta file
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct VaultFile {
|
||||||
|
/// Vault password, encrypted with vault password
|
||||||
|
pub crypto: Crypto,
|
||||||
|
/// Vault metadata string
|
||||||
|
pub meta: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VaultFile {
|
||||||
|
pub fn load<R>(reader: R) -> Result<Self, serde_json::Error>
|
||||||
|
where
|
||||||
|
R: Read,
|
||||||
|
{
|
||||||
|
serde_json::from_reader(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<W>(&self, writer: &mut W) -> Result<(), serde_json::Error>
|
||||||
|
where
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
serde_json::to_writer(writer, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use json::{Aes128Ctr, Cipher, Crypto, Kdf, Pbkdf2, Prf, VaultFile};
|
||||||
|
use serde_json;
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(1024).expect("1024 > 0; qed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_and_from_json() {
|
||||||
|
let file = VaultFile {
|
||||||
|
crypto: Crypto {
|
||||||
|
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||||
|
iv: "0155e3690be19fbfbecabcd440aa284b".into(),
|
||||||
|
}),
|
||||||
|
ciphertext: "4d6938a1f49b7782".into(),
|
||||||
|
kdf: Kdf::Pbkdf2(Pbkdf2 {
|
||||||
|
c: *ITERATIONS,
|
||||||
|
dklen: 32,
|
||||||
|
prf: Prf::HmacSha256,
|
||||||
|
salt: "b6a9338a7ccd39288a86dba73bfecd9101b4f3db9c9830e7c76afdbd4f6872e5".into(),
|
||||||
|
}),
|
||||||
|
mac: "16381463ea11c6eb2239a9f339c2e780516d29d234ce30ac5f166f9080b5a262".into(),
|
||||||
|
},
|
||||||
|
meta: Some("{}".into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&file).unwrap();
|
||||||
|
let deserialized = serde_json::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(file, deserialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_and_from_json_no_meta() {
|
||||||
|
let file = VaultFile {
|
||||||
|
crypto: Crypto {
|
||||||
|
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||||
|
iv: "0155e3690be19fbfbecabcd440aa284b".into(),
|
||||||
|
}),
|
||||||
|
ciphertext: "4d6938a1f49b7782".into(),
|
||||||
|
kdf: Kdf::Pbkdf2(Pbkdf2 {
|
||||||
|
c: *ITERATIONS,
|
||||||
|
dklen: 32,
|
||||||
|
prf: Prf::HmacSha256,
|
||||||
|
salt: "b6a9338a7ccd39288a86dba73bfecd9101b4f3db9c9830e7c76afdbd4f6872e5".into(),
|
||||||
|
}),
|
||||||
|
mac: "16381463ea11c6eb2239a9f339c2e780516d29d234ce30ac5f166f9080b5a262".into(),
|
||||||
|
},
|
||||||
|
meta: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&file).unwrap();
|
||||||
|
let deserialized = serde_json::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(file, deserialized);
|
||||||
|
}
|
||||||
|
}
|
||||||
205
accounts/ethstore/src/json/vault_key_file.rs
Normal file
205
accounts/ethstore/src/json/vault_key_file.rs
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::{Crypto, Uuid, Version, H160};
|
||||||
|
use serde::de::Error;
|
||||||
|
use serde_json::{self, error, value::Value};
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
/// Meta key name for vault field
|
||||||
|
const VAULT_NAME_META_KEY: &'static str = "vault";
|
||||||
|
|
||||||
|
/// Key file as stored in vaults
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct VaultKeyFile {
|
||||||
|
/// Key id
|
||||||
|
pub id: Uuid,
|
||||||
|
/// Key version
|
||||||
|
pub version: Version,
|
||||||
|
/// Secret, encrypted with account password
|
||||||
|
pub crypto: Crypto,
|
||||||
|
/// Serialized `VaultKeyMeta`, encrypted with vault password
|
||||||
|
pub metacrypto: Crypto,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Data, stored in `VaultKeyFile::metacrypto`
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct VaultKeyMeta {
|
||||||
|
/// Key address
|
||||||
|
pub address: H160,
|
||||||
|
/// Key name
|
||||||
|
pub name: Option<String>,
|
||||||
|
/// Key metadata
|
||||||
|
pub meta: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert vault name to the JSON meta field
|
||||||
|
pub fn insert_vault_name_to_json_meta(
|
||||||
|
meta: &str,
|
||||||
|
vault_name: &str,
|
||||||
|
) -> Result<String, error::Error> {
|
||||||
|
let mut meta = if meta.is_empty() {
|
||||||
|
Value::Object(serde_json::Map::new())
|
||||||
|
} else {
|
||||||
|
serde_json::from_str(meta)?
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(meta_obj) = meta.as_object_mut() {
|
||||||
|
meta_obj.insert(
|
||||||
|
VAULT_NAME_META_KEY.to_owned(),
|
||||||
|
Value::String(vault_name.to_owned()),
|
||||||
|
);
|
||||||
|
serde_json::to_string(meta_obj)
|
||||||
|
} else {
|
||||||
|
Err(error::Error::custom(
|
||||||
|
"Meta is expected to be a serialized JSON object",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove vault name from the JSON meta field
|
||||||
|
pub fn remove_vault_name_from_json_meta(meta: &str) -> Result<String, error::Error> {
|
||||||
|
let mut meta = if meta.is_empty() {
|
||||||
|
Value::Object(serde_json::Map::new())
|
||||||
|
} else {
|
||||||
|
serde_json::from_str(meta)?
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(meta_obj) = meta.as_object_mut() {
|
||||||
|
meta_obj.remove(VAULT_NAME_META_KEY);
|
||||||
|
serde_json::to_string(meta_obj)
|
||||||
|
} else {
|
||||||
|
Err(error::Error::custom(
|
||||||
|
"Meta is expected to be a serialized JSON object",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VaultKeyFile {
|
||||||
|
pub fn load<R>(reader: R) -> Result<Self, serde_json::Error>
|
||||||
|
where
|
||||||
|
R: Read,
|
||||||
|
{
|
||||||
|
serde_json::from_reader(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<W>(&self, writer: &mut W) -> Result<(), serde_json::Error>
|
||||||
|
where
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
serde_json::to_writer(writer, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VaultKeyMeta {
|
||||||
|
pub fn load(bytes: &[u8]) -> Result<Self, serde_json::Error> {
|
||||||
|
serde_json::from_slice(&bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&self) -> Result<Vec<u8>, serde_json::Error> {
|
||||||
|
let s = serde_json::to_string(self)?;
|
||||||
|
Ok(s.as_bytes().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use json::{
|
||||||
|
insert_vault_name_to_json_meta, remove_vault_name_from_json_meta, Aes128Ctr, Cipher,
|
||||||
|
Crypto, Kdf, Pbkdf2, Prf, VaultKeyFile, Version,
|
||||||
|
};
|
||||||
|
use serde_json;
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(10240).expect("10240 > 0; qed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_and_from_json() {
|
||||||
|
let file = VaultKeyFile {
|
||||||
|
id: "08d82c39-88e3-7a71-6abb-89c8f36c3ceb".into(),
|
||||||
|
version: Version::V3,
|
||||||
|
crypto: Crypto {
|
||||||
|
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||||
|
iv: "fecb968bbc8c7e608a89ebcfe53a41d0".into(),
|
||||||
|
}),
|
||||||
|
ciphertext: "4befe0a66d9a4b6fec8e39eb5c90ac5dafdeaab005fff1af665fd1f9af925c91".into(),
|
||||||
|
kdf: Kdf::Pbkdf2(Pbkdf2 {
|
||||||
|
c: *ITERATIONS,
|
||||||
|
dklen: 32,
|
||||||
|
prf: Prf::HmacSha256,
|
||||||
|
salt: "f17731e84ecac390546692dbd4ccf6a3a2720dc9652984978381e61c28a471b2".into(),
|
||||||
|
}),
|
||||||
|
mac: "7c7c3daafb24cf11eb3079dfb9064a11e92f309a0ee1dd676486bab119e686b7".into(),
|
||||||
|
},
|
||||||
|
metacrypto: Crypto {
|
||||||
|
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||||
|
iv: "9c353fb3f894fc05946843616c26bb3f".into(),
|
||||||
|
}),
|
||||||
|
ciphertext: "fef0d113d7576c1702daf380ad6f4c5408389e57991cae2a174facd74bd549338e1014850bddbab7eb486ff5f5c9c5532800c6a6d4db2be2212cd5cd3769244ab230e1f369e8382a9e6d7c0a".into(),
|
||||||
|
kdf: Kdf::Pbkdf2(Pbkdf2 {
|
||||||
|
c: *ITERATIONS,
|
||||||
|
dklen: 32,
|
||||||
|
prf: Prf::HmacSha256,
|
||||||
|
salt: "aca82865174a82249a198814b263f43a631f272cbf7ed329d0f0839d259c652a".into(),
|
||||||
|
}),
|
||||||
|
mac: "b7413946bfe459d2801268dc331c04b3a84d92be11ef4dd9a507f895e8d9b5bd".into(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&file).unwrap();
|
||||||
|
let deserialized = serde_json::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(file, deserialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vault_name_inserted_to_json_meta() {
|
||||||
|
assert_eq!(
|
||||||
|
insert_vault_name_to_json_meta(r#""#, "MyVault").unwrap(),
|
||||||
|
r#"{"vault":"MyVault"}"#
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
insert_vault_name_to_json_meta(r#"{"tags":["kalabala"]}"#, "MyVault").unwrap(),
|
||||||
|
r#"{"tags":["kalabala"],"vault":"MyVault"}"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vault_name_not_inserted_to_json_meta() {
|
||||||
|
assert!(insert_vault_name_to_json_meta(r#"///3533"#, "MyVault").is_err());
|
||||||
|
assert!(insert_vault_name_to_json_meta(r#""string""#, "MyVault").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vault_name_removed_from_json_meta() {
|
||||||
|
assert_eq!(
|
||||||
|
remove_vault_name_from_json_meta(r#"{"vault":"MyVault"}"#).unwrap(),
|
||||||
|
r#"{}"#
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
remove_vault_name_from_json_meta(r#"{"tags":["kalabala"],"vault":"MyVault"}"#).unwrap(),
|
||||||
|
r#"{"tags":["kalabala"]}"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vault_name_not_removed_from_json_meta() {
|
||||||
|
assert!(remove_vault_name_from_json_meta(r#"///3533"#).is_err());
|
||||||
|
assert!(remove_vault_name_from_json_meta(r#""string""#).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
67
accounts/ethstore/src/json/version.rs
Normal file
67
accounts/ethstore/src/json/version.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::Error;
|
||||||
|
use serde::{
|
||||||
|
de::{Error as SerdeError, Visitor},
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Version {
|
||||||
|
V3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Version {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
Version::V3 => serializer.serialize_u64(3),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deserialize<'a> for Version {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Version, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'a>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_any(VersionVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VersionVisitor;
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for VersionVisitor {
|
||||||
|
type Value = Version;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "a valid key version identifier")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
match value {
|
||||||
|
3 => Ok(Version::V3),
|
||||||
|
_ => Err(SerdeError::custom(Error::UnsupportedVersion)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
ethstore/src/lib.rs → accounts/ethstore/src/lib.rs
Executable file → Normal file
47
ethstore/src/lib.rs → accounts/ethstore/src/lib.rs
Executable file → Normal file
@@ -1,25 +1,23 @@
|
|||||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License as published by
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
|
|
||||||
// Parity is distributed in the hope that it will be useful,
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//! Ethereum key-management.
|
//! Ethereum key-management.
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
extern crate crypto as rcrypto;
|
|
||||||
extern crate dir;
|
|
||||||
extern crate itertools;
|
extern crate itertools;
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
extern crate parking_lot;
|
extern crate parking_lot;
|
||||||
@@ -28,20 +26,25 @@ extern crate rustc_hex;
|
|||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
extern crate smallvec;
|
extern crate smallvec;
|
||||||
extern crate time;
|
|
||||||
extern crate tiny_keccak;
|
|
||||||
extern crate tempdir;
|
extern crate tempdir;
|
||||||
|
extern crate time;
|
||||||
|
|
||||||
extern crate ethcrypto as crypto;
|
|
||||||
extern crate ethereum_types;
|
extern crate ethereum_types;
|
||||||
extern crate ethkey as _ethkey;
|
extern crate ethkey as _ethkey;
|
||||||
|
extern crate parity_crypto as crypto;
|
||||||
extern crate parity_wordlist;
|
extern crate parity_wordlist;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate matches;
|
||||||
|
|
||||||
pub mod accounts_dir;
|
pub mod accounts_dir;
|
||||||
pub mod ethkey;
|
pub mod ethkey;
|
||||||
|
|
||||||
@@ -55,18 +58,20 @@ mod presale;
|
|||||||
mod random;
|
mod random;
|
||||||
mod secret_store;
|
mod secret_store;
|
||||||
|
|
||||||
pub use self::account::{SafeAccount, Crypto};
|
pub use self::{
|
||||||
pub use self::error::Error;
|
account::{Crypto, SafeAccount},
|
||||||
pub use self::ethstore::{EthStore, EthMultiStore};
|
error::Error,
|
||||||
pub use self::import::{import_account, import_accounts, read_geth_accounts};
|
ethstore::{EthMultiStore, EthStore},
|
||||||
pub use self::json::OpaqueKeyFile as KeyFile;
|
import::{import_account, import_accounts},
|
||||||
pub use self::presale::PresaleWallet;
|
json::OpaqueKeyFile as KeyFile,
|
||||||
pub use self::secret_store::{
|
parity_wordlist::random_phrase,
|
||||||
SecretVaultRef, StoreAccountRef, SimpleSecretStore, SecretStore,
|
presale::PresaleWallet,
|
||||||
Derivation, IndexDerivation,
|
random::random_string,
|
||||||
|
secret_store::{
|
||||||
|
Derivation, IndexDerivation, SecretStore, SecretVaultRef, SimpleSecretStore,
|
||||||
|
StoreAccountRef,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
pub use self::random::random_string;
|
|
||||||
pub use self::parity_wordlist::random_phrase;
|
|
||||||
|
|
||||||
/// An opaque wrapper for secret.
|
/// An opaque wrapper for secret.
|
||||||
pub struct OpaqueSecret(::ethkey::Secret);
|
pub struct OpaqueSecret(::ethkey::Secret);
|
||||||
103
accounts/ethstore/src/presale.rs
Normal file
103
accounts/ethstore/src/presale.rs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use crypto::{self, pbkdf2, Keccak256};
|
||||||
|
use ethkey::{Address, KeyPair, Password, Secret};
|
||||||
|
use json;
|
||||||
|
use std::{fs, num::NonZeroU32, path::Path};
|
||||||
|
use Error;
|
||||||
|
|
||||||
|
/// Pre-sale wallet.
|
||||||
|
pub struct PresaleWallet {
|
||||||
|
iv: [u8; 16],
|
||||||
|
ciphertext: Vec<u8>,
|
||||||
|
address: Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<json::PresaleWallet> for PresaleWallet {
|
||||||
|
fn from(wallet: json::PresaleWallet) -> Self {
|
||||||
|
let mut iv = [0u8; 16];
|
||||||
|
iv.copy_from_slice(&wallet.encseed[..16]);
|
||||||
|
|
||||||
|
let mut ciphertext = vec![];
|
||||||
|
ciphertext.extend_from_slice(&wallet.encseed[16..]);
|
||||||
|
|
||||||
|
PresaleWallet {
|
||||||
|
iv: iv,
|
||||||
|
ciphertext: ciphertext,
|
||||||
|
address: Address::from(wallet.address),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PresaleWallet {
|
||||||
|
/// Open a pre-sale wallet.
|
||||||
|
pub fn open<P>(path: P) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let file = fs::File::open(path)?;
|
||||||
|
let presale =
|
||||||
|
json::PresaleWallet::load(file).map_err(|e| Error::InvalidKeyFile(format!("{}", e)))?;
|
||||||
|
Ok(PresaleWallet::from(presale))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypt the wallet.
|
||||||
|
pub fn decrypt(&self, password: &Password) -> Result<KeyPair, Error> {
|
||||||
|
let mut derived_key = [0u8; 32];
|
||||||
|
let salt = pbkdf2::Salt(password.as_bytes());
|
||||||
|
let sec = pbkdf2::Secret(password.as_bytes());
|
||||||
|
let iter = NonZeroU32::new(2000).expect("2000 > 0; qed");
|
||||||
|
pbkdf2::sha256(iter, salt, sec, &mut derived_key);
|
||||||
|
|
||||||
|
let mut key = vec![0; self.ciphertext.len()];
|
||||||
|
let len =
|
||||||
|
crypto::aes::decrypt_128_cbc(&derived_key[0..16], &self.iv, &self.ciphertext, &mut key)
|
||||||
|
.map_err(|_| Error::InvalidPassword)?;
|
||||||
|
let unpadded = &key[..len];
|
||||||
|
|
||||||
|
let secret = Secret::from_unsafe_slice(&unpadded.keccak256())?;
|
||||||
|
if let Ok(kp) = KeyPair::from_secret(secret) {
|
||||||
|
if kp.address() == self.address {
|
||||||
|
return Ok(kp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::InvalidPassword)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::PresaleWallet;
|
||||||
|
use json;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
let json = r#"
|
||||||
|
{
|
||||||
|
"encseed": "137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066",
|
||||||
|
"ethaddr": "ede84640d1a1d3e06902048e67aa7db8d52c2ce1",
|
||||||
|
"email": "123@gmail.com",
|
||||||
|
"btcaddr": "1JvqEc6WLhg6GnyrLBe2ztPAU28KRfuseH"
|
||||||
|
} "#;
|
||||||
|
|
||||||
|
let wallet = json::PresaleWallet::load(json.as_bytes()).unwrap();
|
||||||
|
let wallet = PresaleWallet::from(wallet);
|
||||||
|
assert!(wallet.decrypt(&"123".into()).is_ok());
|
||||||
|
assert!(wallet.decrypt(&"124".into()).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
47
accounts/ethstore/src/random.rs
Normal file
47
accounts/ethstore/src/random.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use rand::{OsRng, Rng};
|
||||||
|
|
||||||
|
pub trait Random {
|
||||||
|
fn random() -> Self
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Random for [u8; 16] {
|
||||||
|
fn random() -> Self {
|
||||||
|
let mut result = [0u8; 16];
|
||||||
|
let mut rng = OsRng::new().unwrap();
|
||||||
|
rng.fill_bytes(&mut result);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Random for [u8; 32] {
|
||||||
|
fn random() -> Self {
|
||||||
|
let mut result = [0u8; 32];
|
||||||
|
let mut rng = OsRng::new().unwrap();
|
||||||
|
rng.fill_bytes(&mut result);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a random string of given length.
|
||||||
|
pub fn random_string(length: usize) -> String {
|
||||||
|
let mut rng = OsRng::new().expect("Not able to operate without random source.");
|
||||||
|
rng.gen_ascii_chars().take(length).collect()
|
||||||
|
}
|
||||||
268
accounts/ethstore/src/secret_store.rs
Normal file
268
accounts/ethstore/src/secret_store.rs
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use ethereum_types::H256;
|
||||||
|
use ethkey::{Address, Message, Password, Public, Secret, Signature};
|
||||||
|
use json::{OpaqueKeyFile, Uuid};
|
||||||
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
use Error;
|
||||||
|
use OpaqueSecret;
|
||||||
|
|
||||||
|
/// Key directory reference
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum SecretVaultRef {
|
||||||
|
/// Reference to key in root directory
|
||||||
|
Root,
|
||||||
|
/// Referenc to key in specific vault
|
||||||
|
Vault(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stored account reference
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Ord)]
|
||||||
|
pub struct StoreAccountRef {
|
||||||
|
/// Account address
|
||||||
|
pub address: Address,
|
||||||
|
/// Vault reference
|
||||||
|
pub vault: SecretVaultRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for StoreAccountRef {
|
||||||
|
fn partial_cmp(&self, other: &StoreAccountRef) -> Option<Ordering> {
|
||||||
|
Some(
|
||||||
|
self.address
|
||||||
|
.cmp(&other.address)
|
||||||
|
.then_with(|| self.vault.cmp(&other.vault)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::borrow::Borrow<Address> for StoreAccountRef {
|
||||||
|
fn borrow(&self) -> &Address {
|
||||||
|
&self.address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple Secret Store API
|
||||||
|
pub trait SimpleSecretStore: Send + Sync {
|
||||||
|
/// Inserts new accounts to the store (or vault) with given password.
|
||||||
|
fn insert_account(
|
||||||
|
&self,
|
||||||
|
vault: SecretVaultRef,
|
||||||
|
secret: Secret,
|
||||||
|
password: &Password,
|
||||||
|
) -> Result<StoreAccountRef, Error>;
|
||||||
|
/// Inserts new derived account to the store (or vault) with given password.
|
||||||
|
fn insert_derived(
|
||||||
|
&self,
|
||||||
|
vault: SecretVaultRef,
|
||||||
|
account_ref: &StoreAccountRef,
|
||||||
|
password: &Password,
|
||||||
|
derivation: Derivation,
|
||||||
|
) -> Result<StoreAccountRef, Error>;
|
||||||
|
/// Changes accounts password.
|
||||||
|
fn change_password(
|
||||||
|
&self,
|
||||||
|
account: &StoreAccountRef,
|
||||||
|
old_password: &Password,
|
||||||
|
new_password: &Password,
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
/// Exports key details for account.
|
||||||
|
fn export_account(
|
||||||
|
&self,
|
||||||
|
account: &StoreAccountRef,
|
||||||
|
password: &Password,
|
||||||
|
) -> Result<OpaqueKeyFile, Error>;
|
||||||
|
/// Entirely removes account from the store and underlying storage.
|
||||||
|
fn remove_account(&self, account: &StoreAccountRef, password: &Password) -> Result<(), Error>;
|
||||||
|
/// Generates new derived account.
|
||||||
|
fn generate_derived(
|
||||||
|
&self,
|
||||||
|
account_ref: &StoreAccountRef,
|
||||||
|
password: &Password,
|
||||||
|
derivation: Derivation,
|
||||||
|
) -> Result<Address, Error>;
|
||||||
|
/// Sign a message with given account.
|
||||||
|
fn sign(
|
||||||
|
&self,
|
||||||
|
account: &StoreAccountRef,
|
||||||
|
password: &Password,
|
||||||
|
message: &Message,
|
||||||
|
) -> Result<Signature, Error>;
|
||||||
|
/// Sign a message with derived account.
|
||||||
|
fn sign_derived(
|
||||||
|
&self,
|
||||||
|
account_ref: &StoreAccountRef,
|
||||||
|
password: &Password,
|
||||||
|
derivation: Derivation,
|
||||||
|
message: &Message,
|
||||||
|
) -> Result<Signature, Error>;
|
||||||
|
/// Decrypt a messages with given account.
|
||||||
|
fn decrypt(
|
||||||
|
&self,
|
||||||
|
account: &StoreAccountRef,
|
||||||
|
password: &Password,
|
||||||
|
shared_mac: &[u8],
|
||||||
|
message: &[u8],
|
||||||
|
) -> Result<Vec<u8>, Error>;
|
||||||
|
/// Agree on shared key.
|
||||||
|
fn agree(
|
||||||
|
&self,
|
||||||
|
account: &StoreAccountRef,
|
||||||
|
password: &Password,
|
||||||
|
other: &Public,
|
||||||
|
) -> Result<Secret, Error>;
|
||||||
|
|
||||||
|
/// Returns all accounts in this secret store.
|
||||||
|
fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error>;
|
||||||
|
/// Get reference to some account with given address.
|
||||||
|
/// This method could be removed if we will guarantee that there is max(1) account for given address.
|
||||||
|
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error>;
|
||||||
|
|
||||||
|
/// Create new vault with given password
|
||||||
|
fn create_vault(&self, name: &str, password: &Password) -> Result<(), Error>;
|
||||||
|
/// Open vault with given password
|
||||||
|
fn open_vault(&self, name: &str, password: &Password) -> Result<(), Error>;
|
||||||
|
/// Close vault
|
||||||
|
fn close_vault(&self, name: &str) -> Result<(), Error>;
|
||||||
|
/// List all vaults
|
||||||
|
fn list_vaults(&self) -> Result<Vec<String>, Error>;
|
||||||
|
/// List all currently opened vaults
|
||||||
|
fn list_opened_vaults(&self) -> Result<Vec<String>, Error>;
|
||||||
|
/// Change vault password
|
||||||
|
fn change_vault_password(&self, name: &str, new_password: &Password) -> Result<(), Error>;
|
||||||
|
/// Cnage account' vault
|
||||||
|
fn change_account_vault(
|
||||||
|
&self,
|
||||||
|
vault: SecretVaultRef,
|
||||||
|
account: StoreAccountRef,
|
||||||
|
) -> Result<StoreAccountRef, Error>;
|
||||||
|
/// Get vault metadata string.
|
||||||
|
fn get_vault_meta(&self, name: &str) -> Result<String, Error>;
|
||||||
|
/// Set vault metadata string.
|
||||||
|
fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Secret Store API
|
||||||
|
pub trait SecretStore: SimpleSecretStore {
|
||||||
|
/// Returns a raw opaque Secret that can be later used to sign a message.
|
||||||
|
fn raw_secret(
|
||||||
|
&self,
|
||||||
|
account: &StoreAccountRef,
|
||||||
|
password: &Password,
|
||||||
|
) -> Result<OpaqueSecret, Error>;
|
||||||
|
|
||||||
|
/// Signs a message with raw secret.
|
||||||
|
fn sign_with_secret(
|
||||||
|
&self,
|
||||||
|
secret: &OpaqueSecret,
|
||||||
|
message: &Message,
|
||||||
|
) -> Result<Signature, Error> {
|
||||||
|
Ok(::ethkey::sign(&secret.0, message)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Imports presale wallet
|
||||||
|
fn import_presale(
|
||||||
|
&self,
|
||||||
|
vault: SecretVaultRef,
|
||||||
|
json: &[u8],
|
||||||
|
password: &Password,
|
||||||
|
) -> Result<StoreAccountRef, Error>;
|
||||||
|
/// Imports existing JSON wallet
|
||||||
|
fn import_wallet(
|
||||||
|
&self,
|
||||||
|
vault: SecretVaultRef,
|
||||||
|
json: &[u8],
|
||||||
|
password: &Password,
|
||||||
|
gen_id: bool,
|
||||||
|
) -> Result<StoreAccountRef, Error>;
|
||||||
|
/// Copies account between stores and vaults.
|
||||||
|
fn copy_account(
|
||||||
|
&self,
|
||||||
|
new_store: &dyn SimpleSecretStore,
|
||||||
|
new_vault: SecretVaultRef,
|
||||||
|
account: &StoreAccountRef,
|
||||||
|
password: &Password,
|
||||||
|
new_password: &Password,
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
/// Checks if password matches given account.
|
||||||
|
fn test_password(&self, account: &StoreAccountRef, password: &Password) -> Result<bool, Error>;
|
||||||
|
|
||||||
|
/// Returns a public key for given account.
|
||||||
|
fn public(&self, account: &StoreAccountRef, password: &Password) -> Result<Public, Error>;
|
||||||
|
|
||||||
|
/// Returns uuid of an account.
|
||||||
|
fn uuid(&self, account: &StoreAccountRef) -> Result<Uuid, Error>;
|
||||||
|
/// Returns account's name.
|
||||||
|
fn name(&self, account: &StoreAccountRef) -> Result<String, Error>;
|
||||||
|
/// Returns account's metadata.
|
||||||
|
fn meta(&self, account: &StoreAccountRef) -> Result<String, Error>;
|
||||||
|
|
||||||
|
/// Modifies account metadata.
|
||||||
|
fn set_name(&self, account: &StoreAccountRef, name: String) -> Result<(), Error>;
|
||||||
|
/// Modifies account name.
|
||||||
|
fn set_meta(&self, account: &StoreAccountRef, meta: String) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Returns local path of the store.
|
||||||
|
fn local_path(&self) -> PathBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StoreAccountRef {
|
||||||
|
/// Create reference to root account with given address
|
||||||
|
pub fn root(address: Address) -> Self {
|
||||||
|
StoreAccountRef::new(SecretVaultRef::Root, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create reference to vault account with given address
|
||||||
|
pub fn vault(vault_name: &str, address: Address) -> Self {
|
||||||
|
StoreAccountRef::new(SecretVaultRef::Vault(vault_name.to_owned()), address)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create new account reference
|
||||||
|
pub fn new(vault_ref: SecretVaultRef, address: Address) -> Self {
|
||||||
|
StoreAccountRef {
|
||||||
|
vault: vault_ref,
|
||||||
|
address: address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for StoreAccountRef {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.address.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Node in hierarchical derivation.
|
||||||
|
pub struct IndexDerivation {
|
||||||
|
/// Node is soft (allows proof of parent from parent node).
|
||||||
|
pub soft: bool,
|
||||||
|
/// Index sequence of the node.
|
||||||
|
pub index: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derivation scheme for keys
|
||||||
|
pub enum Derivation {
|
||||||
|
/// Hierarchical derivation
|
||||||
|
Hierarchical(Vec<IndexDerivation>),
|
||||||
|
/// Hash derivation, soft.
|
||||||
|
SoftHash(H256),
|
||||||
|
/// Hash derivation, hard.
|
||||||
|
HardHash(H256),
|
||||||
|
}
|
||||||
197
accounts/ethstore/tests/api.rs
Normal file
197
accounts/ethstore/tests/api.rs
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
extern crate ethstore;
|
||||||
|
extern crate rand;
|
||||||
|
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
use ethstore::{
|
||||||
|
accounts_dir::RootDiskDirectory,
|
||||||
|
ethkey::{verify_address, Generator, KeyPair, Random, Secret},
|
||||||
|
EthStore, SecretVaultRef, SimpleSecretStore, StoreAccountRef,
|
||||||
|
};
|
||||||
|
use util::TransientDir;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secret_store_create() {
|
||||||
|
let dir = TransientDir::create().unwrap();
|
||||||
|
let _ = EthStore::open(Box::new(dir)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn secret_store_open_not_existing() {
|
||||||
|
let dir = TransientDir::open();
|
||||||
|
let _ = EthStore::open(Box::new(dir)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn random_secret() -> Secret {
|
||||||
|
Random.generate().unwrap().secret().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secret_store_create_account() {
|
||||||
|
let dir = TransientDir::create().unwrap();
|
||||||
|
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||||
|
assert_eq!(store.accounts().unwrap().len(), 0);
|
||||||
|
assert!(store
|
||||||
|
.insert_account(SecretVaultRef::Root, random_secret(), &"".into())
|
||||||
|
.is_ok());
|
||||||
|
assert_eq!(store.accounts().unwrap().len(), 1);
|
||||||
|
assert!(store
|
||||||
|
.insert_account(SecretVaultRef::Root, random_secret(), &"".into())
|
||||||
|
.is_ok());
|
||||||
|
assert_eq!(store.accounts().unwrap().len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secret_store_sign() {
|
||||||
|
let dir = TransientDir::create().unwrap();
|
||||||
|
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||||
|
assert!(store
|
||||||
|
.insert_account(SecretVaultRef::Root, random_secret(), &"".into())
|
||||||
|
.is_ok());
|
||||||
|
let accounts = store.accounts().unwrap();
|
||||||
|
assert_eq!(accounts.len(), 1);
|
||||||
|
assert!(store
|
||||||
|
.sign(&accounts[0], &"".into(), &Default::default())
|
||||||
|
.is_ok());
|
||||||
|
assert!(store
|
||||||
|
.sign(&accounts[0], &"1".into(), &Default::default())
|
||||||
|
.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secret_store_change_password() {
|
||||||
|
let dir = TransientDir::create().unwrap();
|
||||||
|
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||||
|
assert!(store
|
||||||
|
.insert_account(SecretVaultRef::Root, random_secret(), &"".into())
|
||||||
|
.is_ok());
|
||||||
|
let accounts = store.accounts().unwrap();
|
||||||
|
assert_eq!(accounts.len(), 1);
|
||||||
|
assert!(store
|
||||||
|
.sign(&accounts[0], &"".into(), &Default::default())
|
||||||
|
.is_ok());
|
||||||
|
assert!(store
|
||||||
|
.change_password(&accounts[0], &"".into(), &"1".into())
|
||||||
|
.is_ok());
|
||||||
|
assert!(store
|
||||||
|
.sign(&accounts[0], &"".into(), &Default::default())
|
||||||
|
.is_err());
|
||||||
|
assert!(store
|
||||||
|
.sign(&accounts[0], &"1".into(), &Default::default())
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secret_store_remove_account() {
|
||||||
|
let dir = TransientDir::create().unwrap();
|
||||||
|
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||||
|
assert!(store
|
||||||
|
.insert_account(SecretVaultRef::Root, random_secret(), &"".into())
|
||||||
|
.is_ok());
|
||||||
|
let accounts = store.accounts().unwrap();
|
||||||
|
assert_eq!(accounts.len(), 1);
|
||||||
|
assert!(store.remove_account(&accounts[0], &"".into()).is_ok());
|
||||||
|
assert_eq!(store.accounts().unwrap().len(), 0);
|
||||||
|
assert!(store.remove_account(&accounts[0], &"".into()).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_path() -> &'static str {
|
||||||
|
match ::std::fs::metadata("ethstore") {
|
||||||
|
Ok(_) => "ethstore/tests/res/geth_keystore",
|
||||||
|
Err(_) => "tests/res/geth_keystore",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pat_path() -> &'static str {
|
||||||
|
match ::std::fs::metadata("ethstore") {
|
||||||
|
Ok(_) => "ethstore/tests/res/pat",
|
||||||
|
Err(_) => "tests/res/pat",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ciphertext_path() -> &'static str {
|
||||||
|
match ::std::fs::metadata("ethstore") {
|
||||||
|
Ok(_) => "ethstore/tests/res/ciphertext",
|
||||||
|
Err(_) => "tests/res/ciphertext",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secret_store_laod_geth_files() {
|
||||||
|
let dir = RootDiskDirectory::at(test_path());
|
||||||
|
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
store.accounts().unwrap(),
|
||||||
|
vec![
|
||||||
|
StoreAccountRef::root("3f49624084b67849c7b4e805c5988c21a430f9d9".into()),
|
||||||
|
StoreAccountRef::root("5ba4dcf897e97c2bdf8315b9ef26c13c085988cf".into()),
|
||||||
|
StoreAccountRef::root("63121b431a52f8043c16fcf0d1df9cb7b5f66649".into()),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secret_store_load_pat_files() {
|
||||||
|
let dir = RootDiskDirectory::at(pat_path());
|
||||||
|
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
store.accounts().unwrap(),
|
||||||
|
vec![
|
||||||
|
StoreAccountRef::root("3f49624084b67849c7b4e805c5988c21a430f9d9".into()),
|
||||||
|
StoreAccountRef::root("5ba4dcf897e97c2bdf8315b9ef26c13c085988cf".into()),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decrypting_files_with_short_ciphertext() {
|
||||||
|
// 31e9d1e6d844bd3a536800ef8d8be6a9975db509, 30
|
||||||
|
let kp1 = KeyPair::from_secret(
|
||||||
|
"000081c29e8142bb6a81bef5a92bda7a8328a5c85bb2f9542e76f9b0f94fc018"
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
// d1e64e5480bfaf733ba7d48712decb8227797a4e , 31
|
||||||
|
let kp2 = KeyPair::from_secret(
|
||||||
|
"00fa7b3db73dc7dfdf8c5fbdb796d741e4488628c41fc4febd9160a866ba0f35"
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let dir = RootDiskDirectory::at(ciphertext_path());
|
||||||
|
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||||
|
let accounts = store.accounts().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
accounts,
|
||||||
|
vec![
|
||||||
|
StoreAccountRef::root("31e9d1e6d844bd3a536800ef8d8be6a9975db509".into()),
|
||||||
|
StoreAccountRef::root("d1e64e5480bfaf733ba7d48712decb8227797a4e".into()),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
let message = Default::default();
|
||||||
|
|
||||||
|
let s1 = store.sign(&accounts[0], &"foo".into(), &message).unwrap();
|
||||||
|
let s2 = store.sign(&accounts[1], &"foo".into(), &message).unwrap();
|
||||||
|
assert!(verify_address(&accounts[0].address, &s1, &message).unwrap());
|
||||||
|
assert!(verify_address(&kp1.address(), &s1, &message).unwrap());
|
||||||
|
assert!(verify_address(&kp2.address(), &s2, &message).unwrap());
|
||||||
|
}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License as published by
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
|
|
||||||
// Parity is distributed in the hope that it will be useful,
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
mod transient_dir;
|
mod transient_dir;
|
||||||
|
|
||||||
82
accounts/ethstore/tests/util/transient_dir.rs
Normal file
82
accounts/ethstore/tests/util/transient_dir.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use ethstore::{
|
||||||
|
accounts_dir::{KeyDirectory, RootDiskDirectory},
|
||||||
|
Error, SafeAccount,
|
||||||
|
};
|
||||||
|
use rand::{OsRng, Rng};
|
||||||
|
use std::{env, fs, path::PathBuf};
|
||||||
|
|
||||||
|
pub fn random_dir() -> PathBuf {
|
||||||
|
let mut rng = OsRng::new().unwrap();
|
||||||
|
let mut dir = env::temp_dir();
|
||||||
|
dir.push(format!("{:x}-{:x}", rng.next_u64(), rng.next_u64()));
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TransientDir {
|
||||||
|
dir: RootDiskDirectory,
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransientDir {
|
||||||
|
pub fn create() -> Result<Self, Error> {
|
||||||
|
let path = random_dir();
|
||||||
|
let result = TransientDir {
|
||||||
|
dir: RootDiskDirectory::create(&path)?,
|
||||||
|
path: path,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open() -> Self {
|
||||||
|
let path = random_dir();
|
||||||
|
TransientDir {
|
||||||
|
dir: RootDiskDirectory::at(&path),
|
||||||
|
path: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TransientDir {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
fs::remove_dir_all(&self.path).expect("Expected to remove temp dir");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyDirectory for TransientDir {
|
||||||
|
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
||||||
|
self.dir.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
|
self.dir.update(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
|
self.dir.insert(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
|
self.dir.remove(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unique_repr(&self) -> Result<u64, Error> {
|
||||||
|
self.dir.unique_repr()
|
||||||
|
}
|
||||||
|
}
|
||||||
71
accounts/src/account_data.rs
Normal file
71
accounts/src/account_data.rs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Account Metadata
|
||||||
|
|
||||||
|
use std::{collections::HashMap, time::Instant};
|
||||||
|
|
||||||
|
use ethkey::{Address, Password};
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
|
/// Type of unlock.
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub enum Unlock {
|
||||||
|
/// If account is unlocked temporarily, it should be locked after first usage.
|
||||||
|
OneTime,
|
||||||
|
/// Account unlocked permanently can always sign message.
|
||||||
|
/// Use with caution.
|
||||||
|
Perm,
|
||||||
|
/// Account unlocked with a timeout
|
||||||
|
Timed(Instant),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Data associated with account.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AccountData {
|
||||||
|
pub unlock: Unlock,
|
||||||
|
pub password: Password,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collected account metadata
|
||||||
|
#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct AccountMeta {
|
||||||
|
/// The name of the account.
|
||||||
|
pub name: String,
|
||||||
|
/// The rest of the metadata of the account.
|
||||||
|
pub meta: String,
|
||||||
|
/// The 128-bit Uuid of the account, if it has one (brain-wallets don't).
|
||||||
|
pub uuid: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountMeta {
|
||||||
|
/// Read a hash map of Address -> AccountMeta
|
||||||
|
pub fn read<R>(reader: R) -> Result<HashMap<Address, Self>, serde_json::Error>
|
||||||
|
where
|
||||||
|
R: ::std::io::Read,
|
||||||
|
{
|
||||||
|
serde_json::from_reader(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a hash map of Address -> AccountMeta
|
||||||
|
pub fn write<W>(m: &HashMap<Address, Self>, writer: &mut W) -> Result<(), serde_json::Error>
|
||||||
|
where
|
||||||
|
W: ::std::io::Write,
|
||||||
|
{
|
||||||
|
serde_json::to_writer(writer, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
46
accounts/src/error.rs
Normal file
46
accounts/src/error.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use ethstore::Error as SSError;
|
||||||
|
|
||||||
|
/// Signing error
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum SignError {
|
||||||
|
/// Account is not unlocked
|
||||||
|
NotUnlocked,
|
||||||
|
/// Account does not exist.
|
||||||
|
NotFound,
|
||||||
|
/// Low-level error from store
|
||||||
|
SStore(SSError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for SignError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
match *self {
|
||||||
|
SignError::NotUnlocked => write!(f, "Account is locked"),
|
||||||
|
SignError::NotFound => write!(f, "Account does not exist"),
|
||||||
|
SignError::SStore(ref e) => write!(f, "{}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SSError> for SignError {
|
||||||
|
fn from(e: SSError) -> Self {
|
||||||
|
SignError::SStore(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
841
accounts/src/lib.rs
Normal file
841
accounts/src/lib.rs
Normal file
@@ -0,0 +1,841 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
//! Account management.
|
||||||
|
|
||||||
|
mod account_data;
|
||||||
|
mod error;
|
||||||
|
mod stores;
|
||||||
|
|
||||||
|
use self::{
|
||||||
|
account_data::{AccountData, Unlock},
|
||||||
|
stores::AddressBook,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use ethkey::{Address, Generator, Message, Password, Public, Random, Secret};
|
||||||
|
use ethstore::{
|
||||||
|
accounts_dir::MemoryDirectory, random_string, EthMultiStore, EthStore, OpaqueSecret,
|
||||||
|
SecretStore, SecretVaultRef, SimpleSecretStore, StoreAccountRef,
|
||||||
|
};
|
||||||
|
use log::*;
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
|
||||||
|
pub use ethkey::Signature;
|
||||||
|
pub use ethstore::{Derivation, Error, IndexDerivation, KeyFile};
|
||||||
|
|
||||||
|
pub use self::{account_data::AccountMeta, error::SignError};
|
||||||
|
|
||||||
|
type AccountToken = Password;
|
||||||
|
|
||||||
|
/// Account management settings.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct AccountProviderSettings {
|
||||||
|
/// Store raw account secret when unlocking the account permanently.
|
||||||
|
pub unlock_keep_secret: bool,
|
||||||
|
/// Disallowed accounts.
|
||||||
|
pub blacklisted_accounts: Vec<Address>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Account management.
|
||||||
|
/// Responsible for unlocking accounts.
|
||||||
|
pub struct AccountProvider {
|
||||||
|
/// For performance reasons some methods can re-use unlocked secrets.
|
||||||
|
unlocked_secrets: RwLock<HashMap<StoreAccountRef, OpaqueSecret>>,
|
||||||
|
/// Unlocked account data.
|
||||||
|
unlocked: RwLock<HashMap<StoreAccountRef, AccountData>>,
|
||||||
|
/// Address book.
|
||||||
|
address_book: RwLock<AddressBook>,
|
||||||
|
/// Accounts on disk
|
||||||
|
sstore: Box<dyn SecretStore>,
|
||||||
|
/// Accounts unlocked with rolling tokens
|
||||||
|
transient_sstore: EthMultiStore,
|
||||||
|
/// When unlocking account permanently we additionally keep a raw secret in memory
|
||||||
|
/// to increase the performance of transaction signing.
|
||||||
|
unlock_keep_secret: bool,
|
||||||
|
/// Disallowed accounts.
|
||||||
|
blacklisted_accounts: Vec<Address>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transient_sstore() -> EthMultiStore {
|
||||||
|
EthMultiStore::open(Box::new(MemoryDirectory::default()))
|
||||||
|
.expect("MemoryDirectory load always succeeds; qed")
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountProvider {
|
||||||
|
/// Creates new account provider.
|
||||||
|
pub fn new(sstore: Box<dyn SecretStore>, settings: AccountProviderSettings) -> Self {
|
||||||
|
if let Ok(accounts) = sstore.accounts() {
|
||||||
|
for account in accounts
|
||||||
|
.into_iter()
|
||||||
|
.filter(|a| settings.blacklisted_accounts.contains(&a.address))
|
||||||
|
{
|
||||||
|
warn!("Local Account {} has a blacklisted (known to be weak) address and will be ignored",
|
||||||
|
account.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove blacklisted accounts from address book.
|
||||||
|
let mut address_book = AddressBook::new(&sstore.local_path());
|
||||||
|
for addr in &settings.blacklisted_accounts {
|
||||||
|
address_book.remove(*addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountProvider {
|
||||||
|
unlocked_secrets: RwLock::new(HashMap::new()),
|
||||||
|
unlocked: RwLock::new(HashMap::new()),
|
||||||
|
address_book: RwLock::new(address_book),
|
||||||
|
sstore: sstore,
|
||||||
|
transient_sstore: transient_sstore(),
|
||||||
|
unlock_keep_secret: settings.unlock_keep_secret,
|
||||||
|
blacklisted_accounts: settings.blacklisted_accounts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates not disk backed provider.
|
||||||
|
pub fn transient_provider() -> Self {
|
||||||
|
AccountProvider {
|
||||||
|
unlocked_secrets: RwLock::new(HashMap::new()),
|
||||||
|
unlocked: RwLock::new(HashMap::new()),
|
||||||
|
address_book: RwLock::new(AddressBook::transient()),
|
||||||
|
sstore: Box::new(
|
||||||
|
EthStore::open(Box::new(MemoryDirectory::default()))
|
||||||
|
.expect("MemoryDirectory load always succeeds; qed"),
|
||||||
|
),
|
||||||
|
transient_sstore: transient_sstore(),
|
||||||
|
unlock_keep_secret: false,
|
||||||
|
blacklisted_accounts: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates new random account.
|
||||||
|
pub fn new_account(&self, password: &Password) -> Result<Address, Error> {
|
||||||
|
self.new_account_and_public(password).map(|d| d.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates new random account and returns address and public key
|
||||||
|
pub fn new_account_and_public(&self, password: &Password) -> Result<(Address, Public), Error> {
|
||||||
|
let acc = Random
|
||||||
|
.generate()
|
||||||
|
.expect("secp context has generation capabilities; qed");
|
||||||
|
let public = acc.public().clone();
|
||||||
|
let secret = acc.secret().clone();
|
||||||
|
let account = self
|
||||||
|
.sstore
|
||||||
|
.insert_account(SecretVaultRef::Root, secret, password)?;
|
||||||
|
Ok((account.address, public))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts new account into underlying store.
|
||||||
|
/// Does not unlock account!
|
||||||
|
pub fn insert_account(&self, secret: Secret, password: &Password) -> Result<Address, Error> {
|
||||||
|
let account = self
|
||||||
|
.sstore
|
||||||
|
.insert_account(SecretVaultRef::Root, secret, password)?;
|
||||||
|
if self.blacklisted_accounts.contains(&account.address) {
|
||||||
|
self.sstore.remove_account(&account, password)?;
|
||||||
|
return Err(Error::InvalidAccount.into());
|
||||||
|
}
|
||||||
|
Ok(account.address)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates new derived account based on the existing one
|
||||||
|
/// If password is not provided, account must be unlocked
|
||||||
|
/// New account will be created with the same password (if save: true)
|
||||||
|
pub fn derive_account(
|
||||||
|
&self,
|
||||||
|
address: &Address,
|
||||||
|
password: Option<Password>,
|
||||||
|
derivation: Derivation,
|
||||||
|
save: bool,
|
||||||
|
) -> Result<Address, SignError> {
|
||||||
|
let account = self.sstore.account_ref(&address)?;
|
||||||
|
let password = password
|
||||||
|
.map(Ok)
|
||||||
|
.unwrap_or_else(|| self.password(&account))?;
|
||||||
|
Ok(if save {
|
||||||
|
self.sstore
|
||||||
|
.insert_derived(SecretVaultRef::Root, &account, &password, derivation)?
|
||||||
|
.address
|
||||||
|
} else {
|
||||||
|
self.sstore
|
||||||
|
.generate_derived(&account, &password, derivation)?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Import a new presale wallet.
|
||||||
|
pub fn import_presale(
|
||||||
|
&self,
|
||||||
|
presale_json: &[u8],
|
||||||
|
password: &Password,
|
||||||
|
) -> Result<Address, Error> {
|
||||||
|
let account = self
|
||||||
|
.sstore
|
||||||
|
.import_presale(SecretVaultRef::Root, presale_json, password)?;
|
||||||
|
Ok(Address::from(account.address).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Import a new wallet.
|
||||||
|
pub fn import_wallet(
|
||||||
|
&self,
|
||||||
|
json: &[u8],
|
||||||
|
password: &Password,
|
||||||
|
gen_id: bool,
|
||||||
|
) -> Result<Address, Error> {
|
||||||
|
let account = self
|
||||||
|
.sstore
|
||||||
|
.import_wallet(SecretVaultRef::Root, json, password, gen_id)?;
|
||||||
|
if self.blacklisted_accounts.contains(&account.address) {
|
||||||
|
self.sstore.remove_account(&account, password)?;
|
||||||
|
return Err(Error::InvalidAccount.into());
|
||||||
|
}
|
||||||
|
Ok(Address::from(account.address).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether an account with a given address is present.
|
||||||
|
pub fn has_account(&self, address: Address) -> bool {
|
||||||
|
self.sstore.account_ref(&address).is_ok() && !self.blacklisted_accounts.contains(&address)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns addresses of all accounts.
|
||||||
|
pub fn accounts(&self) -> Result<Vec<Address>, Error> {
|
||||||
|
let accounts = self.sstore.accounts()?;
|
||||||
|
Ok(accounts
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| a.address)
|
||||||
|
.filter(|address| !self.blacklisted_accounts.contains(address))
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the address of default account.
|
||||||
|
pub fn default_account(&self) -> Result<Address, Error> {
|
||||||
|
Ok(self.accounts()?.first().cloned().unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns each address along with metadata.
|
||||||
|
pub fn addresses_info(&self) -> HashMap<Address, AccountMeta> {
|
||||||
|
self.address_book.read().get()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns each address along with metadata.
|
||||||
|
pub fn set_address_name(&self, account: Address, name: String) {
|
||||||
|
self.address_book.write().set_name(account, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns each address along with metadata.
|
||||||
|
pub fn set_address_meta(&self, account: Address, meta: String) {
|
||||||
|
self.address_book.write().set_meta(account, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes and address from the address book
|
||||||
|
pub fn remove_address(&self, addr: Address) {
|
||||||
|
self.address_book.write().remove(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns each account along with name and meta.
|
||||||
|
pub fn accounts_info(&self) -> Result<HashMap<Address, AccountMeta>, Error> {
|
||||||
|
let r = self
|
||||||
|
.sstore
|
||||||
|
.accounts()?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|a| !self.blacklisted_accounts.contains(&a.address))
|
||||||
|
.map(|a| {
|
||||||
|
(
|
||||||
|
a.address.clone(),
|
||||||
|
self.account_meta(a.address).ok().unwrap_or_default(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns each account along with name and meta.
|
||||||
|
pub fn account_meta(&self, address: Address) -> Result<AccountMeta, Error> {
|
||||||
|
let account = self.sstore.account_ref(&address)?;
|
||||||
|
Ok(AccountMeta {
|
||||||
|
name: self.sstore.name(&account)?,
|
||||||
|
meta: self.sstore.meta(&account)?,
|
||||||
|
uuid: self.sstore.uuid(&account).ok().map(Into::into), // allowed to not have a Uuid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns account public key.
|
||||||
|
pub fn account_public(&self, address: Address, password: &Password) -> Result<Public, Error> {
|
||||||
|
self.sstore
|
||||||
|
.public(&self.sstore.account_ref(&address)?, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns each account along with name and meta.
|
||||||
|
pub fn set_account_name(&self, address: Address, name: String) -> Result<(), Error> {
|
||||||
|
self.sstore
|
||||||
|
.set_name(&self.sstore.account_ref(&address)?, name)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns each account along with name and meta.
|
||||||
|
pub fn set_account_meta(&self, address: Address, meta: String) -> Result<(), Error> {
|
||||||
|
self.sstore
|
||||||
|
.set_meta(&self.sstore.account_ref(&address)?, meta)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the password for `account` is `password`. `false` if not.
|
||||||
|
pub fn test_password(&self, address: &Address, password: &Password) -> Result<bool, Error> {
|
||||||
|
self.sstore
|
||||||
|
.test_password(&self.sstore.account_ref(&address)?, password)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Permanently removes an account.
|
||||||
|
pub fn kill_account(&self, address: &Address, password: &Password) -> Result<(), Error> {
|
||||||
|
self.sstore
|
||||||
|
.remove_account(&self.sstore.account_ref(&address)?, &password)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes the password of `account` from `password` to `new_password`. Fails if incorrect `password` given.
|
||||||
|
pub fn change_password(
|
||||||
|
&self,
|
||||||
|
address: &Address,
|
||||||
|
password: Password,
|
||||||
|
new_password: Password,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.sstore
|
||||||
|
.change_password(&self.sstore.account_ref(address)?, &password, &new_password)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exports an account for given address.
|
||||||
|
pub fn export_account(&self, address: &Address, password: Password) -> Result<KeyFile, Error> {
|
||||||
|
self.sstore
|
||||||
|
.export_account(&self.sstore.account_ref(address)?, &password)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper method used for unlocking accounts.
|
||||||
|
fn unlock_account(
|
||||||
|
&self,
|
||||||
|
address: Address,
|
||||||
|
password: Password,
|
||||||
|
unlock: Unlock,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let account = self.sstore.account_ref(&address)?;
|
||||||
|
|
||||||
|
// check if account is already unlocked permanently, if it is, do nothing
|
||||||
|
let mut unlocked = self.unlocked.write();
|
||||||
|
if let Some(data) = unlocked.get(&account) {
|
||||||
|
if let Unlock::Perm = data.unlock {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.unlock_keep_secret && unlock == Unlock::Perm {
|
||||||
|
// verify password and get the secret
|
||||||
|
let secret = self.sstore.raw_secret(&account, &password)?;
|
||||||
|
self.unlocked_secrets
|
||||||
|
.write()
|
||||||
|
.insert(account.clone(), secret);
|
||||||
|
} else {
|
||||||
|
// verify password by signing dump message
|
||||||
|
// result may be discarded
|
||||||
|
let _ = self.sstore.sign(&account, &password, &Default::default())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = AccountData {
|
||||||
|
unlock: unlock,
|
||||||
|
password: password,
|
||||||
|
};
|
||||||
|
|
||||||
|
unlocked.insert(account, data);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn password(&self, account: &StoreAccountRef) -> Result<Password, SignError> {
|
||||||
|
let mut unlocked = self.unlocked.write();
|
||||||
|
let data = unlocked.get(account).ok_or(SignError::NotUnlocked)?.clone();
|
||||||
|
if let Unlock::OneTime = data.unlock {
|
||||||
|
unlocked
|
||||||
|
.remove(account)
|
||||||
|
.expect("data exists: so key must exist: qed");
|
||||||
|
}
|
||||||
|
if let Unlock::Timed(ref end) = data.unlock {
|
||||||
|
if Instant::now() > *end {
|
||||||
|
unlocked
|
||||||
|
.remove(account)
|
||||||
|
.expect("data exists: so key must exist: qed");
|
||||||
|
return Err(SignError::NotUnlocked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(data.password)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unlocks account permanently.
|
||||||
|
pub fn unlock_account_permanently(
|
||||||
|
&self,
|
||||||
|
account: Address,
|
||||||
|
password: Password,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.unlock_account(account, password, Unlock::Perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unlocks account temporarily (for one signing).
|
||||||
|
pub fn unlock_account_temporarily(
|
||||||
|
&self,
|
||||||
|
account: Address,
|
||||||
|
password: Password,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.unlock_account(account, password, Unlock::OneTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unlocks account temporarily with a timeout.
|
||||||
|
pub fn unlock_account_timed(
|
||||||
|
&self,
|
||||||
|
account: Address,
|
||||||
|
password: Password,
|
||||||
|
duration: Duration,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.unlock_account(account, password, Unlock::Timed(Instant::now() + duration))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if given account is unlocked
|
||||||
|
pub fn is_unlocked(&self, address: &Address) -> bool {
|
||||||
|
let unlocked = self.unlocked.read();
|
||||||
|
let unlocked_secrets = self.unlocked_secrets.read();
|
||||||
|
self.sstore
|
||||||
|
.account_ref(address)
|
||||||
|
.map(|r| unlocked.get(&r).is_some() || unlocked_secrets.get(&r).is_some())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if given account is unlocked permanently
|
||||||
|
pub fn is_unlocked_permanently(&self, address: &Address) -> bool {
|
||||||
|
let unlocked = self.unlocked.read();
|
||||||
|
self.sstore
|
||||||
|
.account_ref(address)
|
||||||
|
.map(|r| {
|
||||||
|
unlocked
|
||||||
|
.get(&r)
|
||||||
|
.map_or(false, |account| account.unlock == Unlock::Perm)
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signs the message. If password is not provided the account must be unlocked.
|
||||||
|
pub fn sign(
|
||||||
|
&self,
|
||||||
|
address: Address,
|
||||||
|
password: Option<Password>,
|
||||||
|
message: Message,
|
||||||
|
) -> Result<Signature, SignError> {
|
||||||
|
let account = self.sstore.account_ref(&address)?;
|
||||||
|
match self.unlocked_secrets.read().get(&account) {
|
||||||
|
Some(secret) => Ok(self.sstore.sign_with_secret(&secret, &message)?),
|
||||||
|
None => {
|
||||||
|
let password = password
|
||||||
|
.map(Ok)
|
||||||
|
.unwrap_or_else(|| self.password(&account))?;
|
||||||
|
Ok(self.sstore.sign(&account, &password, &message)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signs message using the derived secret. If password is not provided the account must be unlocked.
|
||||||
|
pub fn sign_derived(
|
||||||
|
&self,
|
||||||
|
address: &Address,
|
||||||
|
password: Option<Password>,
|
||||||
|
derivation: Derivation,
|
||||||
|
message: Message,
|
||||||
|
) -> Result<Signature, SignError> {
|
||||||
|
let account = self.sstore.account_ref(address)?;
|
||||||
|
let password = password
|
||||||
|
.map(Ok)
|
||||||
|
.unwrap_or_else(|| self.password(&account))?;
|
||||||
|
Ok(self
|
||||||
|
.sstore
|
||||||
|
.sign_derived(&account, &password, derivation, &message)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signs given message with supplied token. Returns a token to use in next signing within this session.
|
||||||
|
pub fn sign_with_token(
|
||||||
|
&self,
|
||||||
|
address: Address,
|
||||||
|
token: AccountToken,
|
||||||
|
message: Message,
|
||||||
|
) -> Result<(Signature, AccountToken), SignError> {
|
||||||
|
let account = self.sstore.account_ref(&address)?;
|
||||||
|
let is_std_password = self.sstore.test_password(&account, &token)?;
|
||||||
|
|
||||||
|
let new_token = Password::from(random_string(16));
|
||||||
|
let signature = if is_std_password {
|
||||||
|
// Insert to transient store
|
||||||
|
self.sstore.copy_account(
|
||||||
|
&self.transient_sstore,
|
||||||
|
SecretVaultRef::Root,
|
||||||
|
&account,
|
||||||
|
&token,
|
||||||
|
&new_token,
|
||||||
|
)?;
|
||||||
|
// sign
|
||||||
|
self.sstore.sign(&account, &token, &message)?
|
||||||
|
} else {
|
||||||
|
// check transient store
|
||||||
|
self.transient_sstore
|
||||||
|
.change_password(&account, &token, &new_token)?;
|
||||||
|
// and sign
|
||||||
|
self.transient_sstore.sign(&account, &new_token, &message)?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((signature, new_token))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypts a message with given token. Returns a token to use in next operation for this account.
|
||||||
|
pub fn decrypt_with_token(
|
||||||
|
&self,
|
||||||
|
address: Address,
|
||||||
|
token: AccountToken,
|
||||||
|
shared_mac: &[u8],
|
||||||
|
message: &[u8],
|
||||||
|
) -> Result<(Vec<u8>, AccountToken), SignError> {
|
||||||
|
let account = self.sstore.account_ref(&address)?;
|
||||||
|
let is_std_password = self.sstore.test_password(&account, &token)?;
|
||||||
|
|
||||||
|
let new_token = Password::from(random_string(16));
|
||||||
|
let message = if is_std_password {
|
||||||
|
// Insert to transient store
|
||||||
|
self.sstore.copy_account(
|
||||||
|
&self.transient_sstore,
|
||||||
|
SecretVaultRef::Root,
|
||||||
|
&account,
|
||||||
|
&token,
|
||||||
|
&new_token,
|
||||||
|
)?;
|
||||||
|
// decrypt
|
||||||
|
self.sstore.decrypt(&account, &token, shared_mac, message)?
|
||||||
|
} else {
|
||||||
|
// check transient store
|
||||||
|
self.transient_sstore
|
||||||
|
.change_password(&account, &token, &new_token)?;
|
||||||
|
// and decrypt
|
||||||
|
self.transient_sstore
|
||||||
|
.decrypt(&account, &token, shared_mac, message)?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((message, new_token))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypts a message. If password is not provided the account must be unlocked.
|
||||||
|
pub fn decrypt(
|
||||||
|
&self,
|
||||||
|
address: Address,
|
||||||
|
password: Option<Password>,
|
||||||
|
shared_mac: &[u8],
|
||||||
|
message: &[u8],
|
||||||
|
) -> Result<Vec<u8>, SignError> {
|
||||||
|
let account = self.sstore.account_ref(&address)?;
|
||||||
|
let password = password
|
||||||
|
.map(Ok)
|
||||||
|
.unwrap_or_else(|| self.password(&account))?;
|
||||||
|
Ok(self
|
||||||
|
.sstore
|
||||||
|
.decrypt(&account, &password, shared_mac, message)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Agree on shared key.
|
||||||
|
pub fn agree(
|
||||||
|
&self,
|
||||||
|
address: Address,
|
||||||
|
password: Option<Password>,
|
||||||
|
other_public: &Public,
|
||||||
|
) -> Result<Secret, SignError> {
|
||||||
|
let account = self.sstore.account_ref(&address)?;
|
||||||
|
let password = password
|
||||||
|
.map(Ok)
|
||||||
|
.unwrap_or_else(|| self.password(&account))?;
|
||||||
|
Ok(self.sstore.agree(&account, &password, other_public)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create new vault.
|
||||||
|
pub fn create_vault(&self, name: &str, password: &Password) -> Result<(), Error> {
|
||||||
|
self.sstore.create_vault(name, password).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open existing vault.
|
||||||
|
pub fn open_vault(&self, name: &str, password: &Password) -> Result<(), Error> {
|
||||||
|
self.sstore.open_vault(name, password).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Close previously opened vault.
|
||||||
|
pub fn close_vault(&self, name: &str) -> Result<(), Error> {
|
||||||
|
self.sstore.close_vault(name).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List all vaults
|
||||||
|
pub fn list_vaults(&self) -> Result<Vec<String>, Error> {
|
||||||
|
self.sstore.list_vaults().map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List all currently opened vaults
|
||||||
|
pub fn list_opened_vaults(&self) -> Result<Vec<String>, Error> {
|
||||||
|
self.sstore.list_opened_vaults().map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change vault password.
|
||||||
|
pub fn change_vault_password(&self, name: &str, new_password: &Password) -> Result<(), Error> {
|
||||||
|
self.sstore
|
||||||
|
.change_vault_password(name, new_password)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change vault of the given address.
|
||||||
|
pub fn change_vault(&self, address: Address, new_vault: &str) -> Result<(), Error> {
|
||||||
|
let new_vault_ref = if new_vault.is_empty() {
|
||||||
|
SecretVaultRef::Root
|
||||||
|
} else {
|
||||||
|
SecretVaultRef::Vault(new_vault.to_owned())
|
||||||
|
};
|
||||||
|
let old_account_ref = self.sstore.account_ref(&address)?;
|
||||||
|
self.sstore
|
||||||
|
.change_account_vault(new_vault_ref, old_account_ref)
|
||||||
|
.map_err(Into::into)
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get vault metadata string.
|
||||||
|
pub fn get_vault_meta(&self, name: &str) -> Result<String, Error> {
|
||||||
|
self.sstore.get_vault_meta(name).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set vault metadata string.
|
||||||
|
pub fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error> {
|
||||||
|
self.sstore.set_vault_meta(name, meta).map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{AccountProvider, Unlock};
|
||||||
|
use ethereum_types::H256;
|
||||||
|
use ethkey::{Address, Generator, Random};
|
||||||
|
use ethstore::{Derivation, StoreAccountRef};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unlock_account_temp() {
|
||||||
|
let kp = Random.generate().unwrap();
|
||||||
|
let ap = AccountProvider::transient_provider();
|
||||||
|
assert!(ap
|
||||||
|
.insert_account(kp.secret().clone(), &"test".into())
|
||||||
|
.is_ok());
|
||||||
|
assert!(ap
|
||||||
|
.unlock_account_temporarily(kp.address(), "test1".into())
|
||||||
|
.is_err());
|
||||||
|
assert!(ap
|
||||||
|
.unlock_account_temporarily(kp.address(), "test".into())
|
||||||
|
.is_ok());
|
||||||
|
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||||
|
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn derived_account_nosave() {
|
||||||
|
let kp = Random.generate().unwrap();
|
||||||
|
let ap = AccountProvider::transient_provider();
|
||||||
|
assert!(ap
|
||||||
|
.insert_account(kp.secret().clone(), &"base".into())
|
||||||
|
.is_ok());
|
||||||
|
assert!(ap
|
||||||
|
.unlock_account_permanently(kp.address(), "base".into())
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
let derived_addr = ap
|
||||||
|
.derive_account(
|
||||||
|
&kp.address(),
|
||||||
|
None,
|
||||||
|
Derivation::SoftHash(H256::from(999)),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.expect("Derivation should not fail");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
ap.unlock_account_permanently(derived_addr, "base".into())
|
||||||
|
.is_err(),
|
||||||
|
"There should be an error because account is not supposed to be saved"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn derived_account_save() {
|
||||||
|
let kp = Random.generate().unwrap();
|
||||||
|
let ap = AccountProvider::transient_provider();
|
||||||
|
assert!(ap
|
||||||
|
.insert_account(kp.secret().clone(), &"base".into())
|
||||||
|
.is_ok());
|
||||||
|
assert!(ap
|
||||||
|
.unlock_account_permanently(kp.address(), "base".into())
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
let derived_addr = ap
|
||||||
|
.derive_account(
|
||||||
|
&kp.address(),
|
||||||
|
None,
|
||||||
|
Derivation::SoftHash(H256::from(999)),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.expect("Derivation should not fail");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
ap.unlock_account_permanently(derived_addr, "base_wrong".into())
|
||||||
|
.is_err(),
|
||||||
|
"There should be an error because password is invalid"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
ap.unlock_account_permanently(derived_addr, "base".into())
|
||||||
|
.is_ok(),
|
||||||
|
"Should be ok because account is saved and password is valid"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn derived_account_sign() {
|
||||||
|
let kp = Random.generate().unwrap();
|
||||||
|
let ap = AccountProvider::transient_provider();
|
||||||
|
assert!(ap
|
||||||
|
.insert_account(kp.secret().clone(), &"base".into())
|
||||||
|
.is_ok());
|
||||||
|
assert!(ap
|
||||||
|
.unlock_account_permanently(kp.address(), "base".into())
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
let derived_addr = ap
|
||||||
|
.derive_account(
|
||||||
|
&kp.address(),
|
||||||
|
None,
|
||||||
|
Derivation::SoftHash(H256::from(1999)),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.expect("Derivation should not fail");
|
||||||
|
ap.unlock_account_permanently(derived_addr, "base".into())
|
||||||
|
.expect("Should be ok because account is saved and password is valid");
|
||||||
|
|
||||||
|
let msg = Default::default();
|
||||||
|
let signed_msg1 = ap
|
||||||
|
.sign(derived_addr, None, msg)
|
||||||
|
.expect("Signing with existing unlocked account should not fail");
|
||||||
|
let signed_msg2 = ap
|
||||||
|
.sign_derived(
|
||||||
|
&kp.address(),
|
||||||
|
None,
|
||||||
|
Derivation::SoftHash(H256::from(1999)),
|
||||||
|
msg,
|
||||||
|
)
|
||||||
|
.expect("Derived signing with existing unlocked account should not fail");
|
||||||
|
|
||||||
|
assert_eq!(signed_msg1, signed_msg2, "Signed messages should match");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unlock_account_perm() {
|
||||||
|
let kp = Random.generate().unwrap();
|
||||||
|
let ap = AccountProvider::transient_provider();
|
||||||
|
assert!(ap
|
||||||
|
.insert_account(kp.secret().clone(), &"test".into())
|
||||||
|
.is_ok());
|
||||||
|
assert!(ap
|
||||||
|
.unlock_account_permanently(kp.address(), "test1".into())
|
||||||
|
.is_err());
|
||||||
|
assert!(ap
|
||||||
|
.unlock_account_permanently(kp.address(), "test".into())
|
||||||
|
.is_ok());
|
||||||
|
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||||
|
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||||
|
assert!(ap
|
||||||
|
.unlock_account_temporarily(kp.address(), "test".into())
|
||||||
|
.is_ok());
|
||||||
|
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||||
|
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unlock_account_timer() {
|
||||||
|
let kp = Random.generate().unwrap();
|
||||||
|
let ap = AccountProvider::transient_provider();
|
||||||
|
assert!(ap
|
||||||
|
.insert_account(kp.secret().clone(), &"test".into())
|
||||||
|
.is_ok());
|
||||||
|
assert!(ap
|
||||||
|
.unlock_account_timed(kp.address(), "test1".into(), Duration::from_secs(60))
|
||||||
|
.is_err());
|
||||||
|
assert!(ap
|
||||||
|
.unlock_account_timed(kp.address(), "test".into(), Duration::from_secs(60))
|
||||||
|
.is_ok());
|
||||||
|
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||||
|
ap.unlocked
|
||||||
|
.write()
|
||||||
|
.get_mut(&StoreAccountRef::root(kp.address()))
|
||||||
|
.unwrap()
|
||||||
|
.unlock = Unlock::Timed(Instant::now());
|
||||||
|
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_sign_and_return_token() {
|
||||||
|
// given
|
||||||
|
let kp = Random.generate().unwrap();
|
||||||
|
let ap = AccountProvider::transient_provider();
|
||||||
|
assert!(ap
|
||||||
|
.insert_account(kp.secret().clone(), &"test".into())
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
// when
|
||||||
|
let (_signature, token) = ap
|
||||||
|
.sign_with_token(kp.address(), "test".into(), Default::default())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// then
|
||||||
|
ap.sign_with_token(kp.address(), token.clone(), Default::default())
|
||||||
|
.expect("First usage of token should be correct.");
|
||||||
|
assert!(
|
||||||
|
ap.sign_with_token(kp.address(), token, Default::default())
|
||||||
|
.is_err(),
|
||||||
|
"Second usage of the same token should fail."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_not_return_blacklisted_account() {
|
||||||
|
// given
|
||||||
|
let mut ap = AccountProvider::transient_provider();
|
||||||
|
let acc = ap.new_account(&"test".into()).unwrap();
|
||||||
|
ap.blacklisted_accounts = vec![acc];
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(
|
||||||
|
ap.accounts_info()
|
||||||
|
.unwrap()
|
||||||
|
.keys()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<Address>>(),
|
||||||
|
vec![]
|
||||||
|
);
|
||||||
|
assert_eq!(ap.accounts().unwrap(), vec![]);
|
||||||
|
}
|
||||||
|
}
|
||||||
237
accounts/src/stores.rs
Normal file
237
accounts/src/stores.rs
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Address Book Store
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fmt, fs, hash, ops,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use ethkey::Address;
|
||||||
|
use log::{trace, warn};
|
||||||
|
|
||||||
|
use crate::AccountMeta;
|
||||||
|
|
||||||
|
/// Disk-backed map from Address to String. Uses JSON.
|
||||||
|
pub struct AddressBook {
|
||||||
|
cache: DiskMap<Address, AccountMeta>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddressBook {
|
||||||
|
/// Creates new address book at given directory.
|
||||||
|
pub fn new(path: &Path) -> Self {
|
||||||
|
let mut r = AddressBook {
|
||||||
|
cache: DiskMap::new(path, "address_book.json"),
|
||||||
|
};
|
||||||
|
r.cache.revert(AccountMeta::read);
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates transient address book (no changes are saved to disk).
|
||||||
|
pub fn transient() -> Self {
|
||||||
|
AddressBook {
|
||||||
|
cache: DiskMap::transient(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the address book.
|
||||||
|
pub fn get(&self) -> HashMap<Address, AccountMeta> {
|
||||||
|
self.cache.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save(&self) {
|
||||||
|
self.cache.save(AccountMeta::write)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets new name for given address.
|
||||||
|
pub fn set_name(&mut self, a: Address, name: String) {
|
||||||
|
{
|
||||||
|
let x = self.cache.entry(a).or_insert_with(|| AccountMeta {
|
||||||
|
name: Default::default(),
|
||||||
|
meta: "{}".to_owned(),
|
||||||
|
uuid: None,
|
||||||
|
});
|
||||||
|
x.name = name;
|
||||||
|
}
|
||||||
|
self.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets new meta for given address.
|
||||||
|
pub fn set_meta(&mut self, a: Address, meta: String) {
|
||||||
|
{
|
||||||
|
let x = self.cache.entry(a).or_insert_with(|| AccountMeta {
|
||||||
|
name: "Anonymous".to_owned(),
|
||||||
|
meta: Default::default(),
|
||||||
|
uuid: None,
|
||||||
|
});
|
||||||
|
x.meta = meta;
|
||||||
|
}
|
||||||
|
self.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes an entry
|
||||||
|
pub fn remove(&mut self, a: Address) {
|
||||||
|
self.cache.remove(&a);
|
||||||
|
self.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disk-serializable HashMap
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DiskMap<K: hash::Hash + Eq, V> {
|
||||||
|
path: PathBuf,
|
||||||
|
cache: HashMap<K, V>,
|
||||||
|
transient: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: hash::Hash + Eq, V> ops::Deref for DiskMap<K, V> {
|
||||||
|
type Target = HashMap<K, V>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.cache
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: hash::Hash + Eq, V> ops::DerefMut for DiskMap<K, V> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.cache
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: hash::Hash + Eq, V> DiskMap<K, V> {
|
||||||
|
pub fn new(path: &Path, file_name: &str) -> Self {
|
||||||
|
let mut path = path.to_owned();
|
||||||
|
path.push(file_name);
|
||||||
|
trace!(target: "diskmap", "path={:?}", path);
|
||||||
|
DiskMap {
|
||||||
|
path: path,
|
||||||
|
cache: HashMap::new(),
|
||||||
|
transient: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transient() -> Self {
|
||||||
|
let mut map = DiskMap::new(&PathBuf::new(), "diskmap.json".into());
|
||||||
|
map.transient = true;
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
fn revert<F, E>(&mut self, read: F)
|
||||||
|
where
|
||||||
|
F: Fn(fs::File) -> Result<HashMap<K, V>, E>,
|
||||||
|
E: fmt::Display,
|
||||||
|
{
|
||||||
|
if self.transient {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
trace!(target: "diskmap", "revert {:?}", self.path);
|
||||||
|
let _ = fs::File::open(self.path.clone())
|
||||||
|
.map_err(|e| trace!(target: "diskmap", "Couldn't open disk map: {}", e))
|
||||||
|
.and_then(|f| {
|
||||||
|
read(f).map_err(|e| warn!(target: "diskmap", "Couldn't read disk map: {}", e))
|
||||||
|
})
|
||||||
|
.and_then(|m| {
|
||||||
|
self.cache = m;
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save<F, E>(&self, write: F)
|
||||||
|
where
|
||||||
|
F: Fn(&HashMap<K, V>, &mut fs::File) -> Result<(), E>,
|
||||||
|
E: fmt::Display,
|
||||||
|
{
|
||||||
|
if self.transient {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
trace!(target: "diskmap", "save {:?}", self.path);
|
||||||
|
let _ = fs::File::create(self.path.clone())
|
||||||
|
.map_err(|e| warn!(target: "diskmap", "Couldn't open disk map for writing: {}", e))
|
||||||
|
.and_then(|mut f| {
|
||||||
|
write(&self.cache, &mut f)
|
||||||
|
.map_err(|e| warn!(target: "diskmap", "Couldn't write to disk map: {}", e))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::AddressBook;
|
||||||
|
use crate::account_data::AccountMeta;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use tempdir::TempDir;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_save_and_reload_address_book() {
|
||||||
|
let tempdir = TempDir::new("").unwrap();
|
||||||
|
let mut b = AddressBook::new(tempdir.path());
|
||||||
|
b.set_name(1.into(), "One".to_owned());
|
||||||
|
b.set_meta(1.into(), "{1:1}".to_owned());
|
||||||
|
let b = AddressBook::new(tempdir.path());
|
||||||
|
assert_eq!(
|
||||||
|
b.get(),
|
||||||
|
vec![(
|
||||||
|
1,
|
||||||
|
AccountMeta {
|
||||||
|
name: "One".to_owned(),
|
||||||
|
meta: "{1:1}".to_owned(),
|
||||||
|
uuid: None
|
||||||
|
}
|
||||||
|
)]
|
||||||
|
.into_iter()
|
||||||
|
.map(|(a, b)| (a.into(), b))
|
||||||
|
.collect::<HashMap<_, _>>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_remove_address() {
|
||||||
|
let tempdir = TempDir::new("").unwrap();
|
||||||
|
let mut b = AddressBook::new(tempdir.path());
|
||||||
|
|
||||||
|
b.set_name(1.into(), "One".to_owned());
|
||||||
|
b.set_name(2.into(), "Two".to_owned());
|
||||||
|
b.set_name(3.into(), "Three".to_owned());
|
||||||
|
b.remove(2.into());
|
||||||
|
|
||||||
|
let b = AddressBook::new(tempdir.path());
|
||||||
|
assert_eq!(
|
||||||
|
b.get(),
|
||||||
|
vec![
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
AccountMeta {
|
||||||
|
name: "One".to_owned(),
|
||||||
|
meta: "{}".to_owned(),
|
||||||
|
uuid: None
|
||||||
|
}
|
||||||
|
),
|
||||||
|
(
|
||||||
|
3,
|
||||||
|
AccountMeta {
|
||||||
|
name: "Three".to_owned(),
|
||||||
|
meta: "{}".to_owned(),
|
||||||
|
uuid: None
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(|(a, b)| (a.into(), b))
|
||||||
|
.collect::<HashMap<_, _>>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
[package]
|
[package]
|
||||||
|
description = "Parity Ethereum Chain Specification"
|
||||||
name = "chainspec"
|
name = "chainspec"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["debris <marek.kotewicz@gmail.com>"]
|
authors = ["Marek Kotewicz <marek@parity.io>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ethjson = { path = "../json" }
|
ethjson = { path = "../json" }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_ignored = "0.0.4"
|
|
||||||
|
|||||||
@@ -1,48 +1,51 @@
|
|||||||
extern crate serde_json;
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
extern crate serde_ignored;
|
// This file is part of OpenEthereum.
|
||||||
extern crate ethjson;
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
extern crate ethjson;
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
use std::collections::BTreeSet;
|
|
||||||
use std::{fs, env, process};
|
|
||||||
use ethjson::spec::Spec;
|
use ethjson::spec::Spec;
|
||||||
|
use std::{env, fs, process};
|
||||||
|
|
||||||
fn quit(s: &str) -> ! {
|
fn quit(s: &str) -> ! {
|
||||||
println!("{}", s);
|
println!("{}", s);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut args = env::args();
|
let mut args = env::args();
|
||||||
if args.len() != 2 {
|
if args.len() != 2 {
|
||||||
quit("You need to specify chainspec.json\n\
|
quit(
|
||||||
|
"You need to specify chainspec.json\n\
|
||||||
\n\
|
\n\
|
||||||
./chainspec <chainspec.json>");
|
./chainspec <chainspec.json>",
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let path = args.nth(1).expect("args.len() == 2; qed");
|
let path = args.nth(1).expect("args.len() == 2; qed");
|
||||||
let file = match fs::File::open(&path) {
|
let file = match fs::File::open(&path) {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
Err(_) => quit(&format!("{} could not be opened", path)),
|
Err(_) => quit(&format!("{} could not be opened", path)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut unused = BTreeSet::new();
|
let spec: Result<Spec, _> = serde_json::from_reader(file);
|
||||||
let mut deserializer = serde_json::Deserializer::from_reader(file);
|
|
||||||
|
|
||||||
let spec: Result<Spec, _> = serde_ignored::deserialize(&mut deserializer, |field| {
|
if let Err(err) = spec {
|
||||||
unused.insert(field.to_string());
|
quit(&format!("{} {}", path, err.to_string()));
|
||||||
});
|
}
|
||||||
|
|
||||||
if let Err(err) = spec {
|
println!("{} is valid", path);
|
||||||
quit(&format!("{} {}", path, err.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !unused.is_empty() {
|
|
||||||
let err = unused.into_iter()
|
|
||||||
.map(|field| format!("{} unexpected field `{}`", path, field))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n");
|
|
||||||
quit(&err);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("{} is valid", path);
|
|
||||||
}
|
}
|
||||||
|
|||||||
14
cli-signer/Cargo.toml
Normal file
14
cli-signer/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
description = "OpenEthereum CLI Signer Tool"
|
||||||
|
homepage = "https://github.com/openethereum/openethereum"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
name = "cli-signer"
|
||||||
|
version = "1.4.0"
|
||||||
|
authors = ["Parity <admin@parity.io>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ethereum-types = "0.4"
|
||||||
|
futures = "0.1"
|
||||||
|
rpassword = "1.0"
|
||||||
|
parity-rpc = { path = "../rpc" }
|
||||||
|
parity-rpc-client = { path = "rpc-client" }
|
||||||
21
cli-signer/rpc-client/Cargo.toml
Normal file
21
cli-signer/rpc-client/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
description = "OpenEthereum RPC Client"
|
||||||
|
homepage = "https://github.com/openethereum/openethereum"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
name = "parity-rpc-client"
|
||||||
|
version = "1.4.0"
|
||||||
|
authors = ["Parity <admin@parity.io>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ethereum-types = "0.4"
|
||||||
|
futures = "0.1"
|
||||||
|
log = "0.4"
|
||||||
|
serde = "1.0"
|
||||||
|
serde_json = "1.0"
|
||||||
|
url = "2"
|
||||||
|
matches = "0.1"
|
||||||
|
parking_lot = "0.9"
|
||||||
|
jsonrpc-core = "15.0.0"
|
||||||
|
jsonrpc-ws-server = "15.0.0"
|
||||||
|
parity-rpc = { path = "../../rpc" }
|
||||||
|
keccak-hash = "0.1"
|
||||||
327
cli-signer/rpc-client/src/client.rs
Normal file
327
cli-signer/rpc-client/src/client.rs
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
fmt::{Debug, Error as FmtError, Formatter},
|
||||||
|
io::{BufRead, BufReader},
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicUsize, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
thread, time,
|
||||||
|
};
|
||||||
|
|
||||||
|
use hash::keccak;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::{fs::File, path::PathBuf};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use ws::ws::{
|
||||||
|
self, Error as WsError, ErrorKind as WsErrorKind, Handler, Handshake, Message, Request,
|
||||||
|
Result as WsResult, Sender,
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde_json::{self as json, Error as JsonError, Value as JsonValue};
|
||||||
|
|
||||||
|
use futures::{done, oneshot, Canceled, Complete, Future};
|
||||||
|
|
||||||
|
use jsonrpc_core::{
|
||||||
|
request::MethodCall,
|
||||||
|
response::{Failure, Output, Success},
|
||||||
|
Error as JsonRpcError, Id, Params, Version,
|
||||||
|
};
|
||||||
|
|
||||||
|
use BoxFuture;
|
||||||
|
|
||||||
|
/// The actual websocket connection handler, passed into the
|
||||||
|
/// event loop of ws-rs
|
||||||
|
struct RpcHandler {
|
||||||
|
pending: Pending,
|
||||||
|
// Option is used here as temporary storage until connection
|
||||||
|
// is setup and the values are moved into the new `Rpc`
|
||||||
|
complete: Option<Complete<Result<Rpc, RpcError>>>,
|
||||||
|
auth_code: String,
|
||||||
|
out: Option<Sender>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RpcHandler {
|
||||||
|
fn new(out: Sender, auth_code: String, complete: Complete<Result<Rpc, RpcError>>) -> Self {
|
||||||
|
RpcHandler {
|
||||||
|
out: Some(out),
|
||||||
|
auth_code: auth_code,
|
||||||
|
pending: Pending::new(),
|
||||||
|
complete: Some(complete),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler for RpcHandler {
|
||||||
|
fn build_request(&mut self, url: &Url) -> WsResult<Request> {
|
||||||
|
match Request::from_url(url) {
|
||||||
|
Ok(mut r) => {
|
||||||
|
let timestamp = time::UNIX_EPOCH
|
||||||
|
.elapsed()
|
||||||
|
.map_err(|err| WsError::new(WsErrorKind::Internal, format!("{}", err)))?;
|
||||||
|
let secs = timestamp.as_secs();
|
||||||
|
let hashed = keccak(format!("{}:{}", self.auth_code, secs));
|
||||||
|
let proto = format!("{:x}_{}", hashed, secs);
|
||||||
|
r.add_protocol(&proto);
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
Err(e) => Err(WsError::new(WsErrorKind::Internal, format!("{}", e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn on_error(&mut self, err: WsError) {
|
||||||
|
match self.complete.take() {
|
||||||
|
Some(c) => match c.send(Err(RpcError::WsError(err))) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(_) => warn!(target: "rpc-client", "Unable to notify about error."),
|
||||||
|
},
|
||||||
|
None => warn!(target: "rpc-client", "unexpected error: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn on_open(&mut self, _: Handshake) -> WsResult<()> {
|
||||||
|
match (self.complete.take(), self.out.take()) {
|
||||||
|
(Some(c), Some(out)) => {
|
||||||
|
let res = c.send(Ok(Rpc {
|
||||||
|
out: out,
|
||||||
|
counter: AtomicUsize::new(0),
|
||||||
|
pending: self.pending.clone(),
|
||||||
|
}));
|
||||||
|
if let Err(_) = res {
|
||||||
|
warn!(target: "rpc-client", "Unable to open a connection.")
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let msg = format!("on_open called twice");
|
||||||
|
Err(WsError::new(WsErrorKind::Internal, msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn on_message(&mut self, msg: Message) -> WsResult<()> {
|
||||||
|
let ret: Result<JsonValue, JsonRpcError>;
|
||||||
|
let response_id;
|
||||||
|
let string = &msg.to_string();
|
||||||
|
match json::from_str::<Output>(&string) {
|
||||||
|
Ok(Output::Success(Success {
|
||||||
|
result,
|
||||||
|
id: Id::Num(id),
|
||||||
|
..
|
||||||
|
})) => {
|
||||||
|
ret = Ok(result);
|
||||||
|
response_id = id as usize;
|
||||||
|
}
|
||||||
|
Ok(Output::Failure(Failure {
|
||||||
|
error,
|
||||||
|
id: Id::Num(id),
|
||||||
|
..
|
||||||
|
})) => {
|
||||||
|
ret = Err(error);
|
||||||
|
response_id = id as usize;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!(
|
||||||
|
target: "rpc-client",
|
||||||
|
"recieved invalid message: {}\n {:?}",
|
||||||
|
string,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
warn!(
|
||||||
|
target: "rpc-client",
|
||||||
|
"recieved invalid message: {}",
|
||||||
|
string
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.pending.remove(response_id) {
|
||||||
|
Some(c) => {
|
||||||
|
if let Err(_) = c.send(ret.map_err(|err| RpcError::JsonRpc(err))) {
|
||||||
|
warn!(target: "rpc-client", "Unable to send response.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => warn!(
|
||||||
|
target: "rpc-client",
|
||||||
|
"warning: unexpected id: {}",
|
||||||
|
response_id
|
||||||
|
),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Keeping track of issued requests to be matched up with responses
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Pending(Arc<Mutex<BTreeMap<usize, Complete<Result<JsonValue, RpcError>>>>>);
|
||||||
|
|
||||||
|
impl Pending {
|
||||||
|
fn new() -> Self {
|
||||||
|
Pending(Arc::new(Mutex::new(BTreeMap::new())))
|
||||||
|
}
|
||||||
|
fn insert(&mut self, k: usize, v: Complete<Result<JsonValue, RpcError>>) {
|
||||||
|
self.0.lock().insert(k, v);
|
||||||
|
}
|
||||||
|
fn remove(&mut self, k: usize) -> Option<Complete<Result<JsonValue, RpcError>>> {
|
||||||
|
self.0.lock().remove(&k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_authcode(path: &PathBuf) -> Result<String, RpcError> {
|
||||||
|
if let Ok(fd) = File::open(path) {
|
||||||
|
if let Some(Ok(line)) = BufReader::new(fd).lines().next() {
|
||||||
|
let mut parts = line.split(';');
|
||||||
|
let token = parts.next();
|
||||||
|
|
||||||
|
if let Some(code) = token {
|
||||||
|
return Ok(code.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(RpcError::NoAuthCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The handle to the connection
|
||||||
|
pub struct Rpc {
|
||||||
|
out: Sender,
|
||||||
|
counter: AtomicUsize,
|
||||||
|
pending: Pending,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rpc {
|
||||||
|
/// Blocking, returns a new initialized connection or RpcError
|
||||||
|
pub fn new(url: &str, authpath: &PathBuf) -> Result<Self, RpcError> {
|
||||||
|
let rpc = Self::connect(url, authpath).map(|rpc| rpc).wait()?;
|
||||||
|
rpc
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Non-blocking, returns a future
|
||||||
|
pub fn connect(url: &str, authpath: &PathBuf) -> BoxFuture<Result<Self, RpcError>, Canceled> {
|
||||||
|
let (c, p) = oneshot::<Result<Self, RpcError>>();
|
||||||
|
match get_authcode(authpath) {
|
||||||
|
Err(e) => return Box::new(done(Ok(Err(e)))),
|
||||||
|
Ok(code) => {
|
||||||
|
let url = String::from(url);
|
||||||
|
// The ws::connect takes a FnMut closure, which means c cannot
|
||||||
|
// be moved into it, since it's consumed on complete.
|
||||||
|
// Therefore we wrap it in an option and pick it out once.
|
||||||
|
let mut once = Some(c);
|
||||||
|
thread::spawn(move || {
|
||||||
|
let conn = ws::connect(url, |out| {
|
||||||
|
// this will panic if the closure is called twice,
|
||||||
|
// which it should never be.
|
||||||
|
let c = once.take().expect("connection closure called only once");
|
||||||
|
RpcHandler::new(out, code.clone(), c)
|
||||||
|
});
|
||||||
|
match conn {
|
||||||
|
Err(err) => {
|
||||||
|
// since ws::connect is only called once, it cannot
|
||||||
|
// both fail and succeed.
|
||||||
|
let c = once.take().expect("connection closure called only once");
|
||||||
|
let _ = c.send(Err(RpcError::WsError(err)));
|
||||||
|
}
|
||||||
|
// c will complete on the `on_open` event in the Handler
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Box::new(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Non-blocking, returns a future of the request response
|
||||||
|
pub fn request<T>(
|
||||||
|
&mut self,
|
||||||
|
method: &'static str,
|
||||||
|
params: Vec<JsonValue>,
|
||||||
|
) -> BoxFuture<Result<T, RpcError>, Canceled>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned + Send + Sized,
|
||||||
|
{
|
||||||
|
let (c, p) = oneshot::<Result<JsonValue, RpcError>>();
|
||||||
|
|
||||||
|
let id = self.counter.fetch_add(1, Ordering::Relaxed);
|
||||||
|
self.pending.insert(id, c);
|
||||||
|
|
||||||
|
let request = MethodCall {
|
||||||
|
jsonrpc: Some(Version::V2),
|
||||||
|
method: method.to_owned(),
|
||||||
|
params: Params::Array(params),
|
||||||
|
id: Id::Num(id as u64),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = json::to_string(&request).expect("request is serializable");
|
||||||
|
let _ = self.out.send(serialized);
|
||||||
|
|
||||||
|
Box::new(p.map(|result| match result {
|
||||||
|
Ok(json) => {
|
||||||
|
let t: T = json::from_value(json)?;
|
||||||
|
Ok(t)
|
||||||
|
}
|
||||||
|
Err(err) => Err(err),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RpcError {
|
||||||
|
WrongVersion(String),
|
||||||
|
ParseError(JsonError),
|
||||||
|
MalformedResponse(String),
|
||||||
|
JsonRpc(JsonRpcError),
|
||||||
|
WsError(WsError),
|
||||||
|
Canceled(Canceled),
|
||||||
|
UnexpectedId,
|
||||||
|
NoAuthCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for RpcError {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
|
||||||
|
match *self {
|
||||||
|
RpcError::WrongVersion(ref s) => write!(f, "Expected version 2.0, got {}", s),
|
||||||
|
RpcError::ParseError(ref err) => write!(f, "ParseError: {}", err),
|
||||||
|
RpcError::MalformedResponse(ref s) => write!(f, "Malformed response: {}", s),
|
||||||
|
RpcError::JsonRpc(ref json) => write!(f, "JsonRpc error: {:?}", json),
|
||||||
|
RpcError::WsError(ref s) => write!(f, "Websocket error: {}", s),
|
||||||
|
RpcError::Canceled(ref s) => write!(f, "Futures error: {:?}", s),
|
||||||
|
RpcError::UnexpectedId => write!(f, "Unexpected response id"),
|
||||||
|
RpcError::NoAuthCode => write!(f, "No authcodes available"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JsonError> for RpcError {
|
||||||
|
fn from(err: JsonError) -> RpcError {
|
||||||
|
RpcError::ParseError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<WsError> for RpcError {
|
||||||
|
fn from(err: WsError) -> RpcError {
|
||||||
|
RpcError::WsError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Canceled> for RpcError {
|
||||||
|
fn from(err: Canceled) -> RpcError {
|
||||||
|
RpcError::Canceled(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
90
cli-signer/rpc-client/src/lib.rs
Normal file
90
cli-signer/rpc-client/src/lib.rs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// OpenEthereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
pub mod client;
|
||||||
|
pub mod signer_client;
|
||||||
|
|
||||||
|
extern crate ethereum_types;
|
||||||
|
extern crate futures;
|
||||||
|
extern crate jsonrpc_core;
|
||||||
|
extern crate jsonrpc_ws_server as ws;
|
||||||
|
extern crate keccak_hash as hash;
|
||||||
|
extern crate parity_rpc as rpc;
|
||||||
|
extern crate parking_lot;
|
||||||
|
extern crate serde;
|
||||||
|
extern crate serde_json;
|
||||||
|
extern crate url;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate matches;
|
||||||
|
|
||||||
|
/// Boxed future response.
|
||||||
|
pub type BoxFuture<T, E> = Box<dyn futures::Future<Item = T, Error = E> + Send>;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use client::{Rpc, RpcError};
|
||||||
|
use futures::Future;
|
||||||
|
use rpc;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_connection_refused() {
|
||||||
|
let (_srv, port, mut authcodes) = rpc::tests::ws::serve();
|
||||||
|
|
||||||
|
let _ = authcodes.generate_new();
|
||||||
|
authcodes.to_file(&authcodes.path).unwrap();
|
||||||
|
|
||||||
|
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port - 1), &authcodes.path);
|
||||||
|
|
||||||
|
let _ = connect
|
||||||
|
.map(|conn| {
|
||||||
|
assert!(matches!(&conn, &Err(RpcError::WsError(_))));
|
||||||
|
})
|
||||||
|
.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_authcode_fail() {
|
||||||
|
let (_srv, port, _) = rpc::tests::ws::serve();
|
||||||
|
let path = PathBuf::from("nonexist");
|
||||||
|
|
||||||
|
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port), &path);
|
||||||
|
|
||||||
|
let _ = connect
|
||||||
|
.map(|conn| {
|
||||||
|
assert!(matches!(&conn, &Err(RpcError::NoAuthCode)));
|
||||||
|
})
|
||||||
|
.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_authcode_correct() {
|
||||||
|
let (_srv, port, mut authcodes) = rpc::tests::ws::serve();
|
||||||
|
|
||||||
|
let _ = authcodes.generate_new();
|
||||||
|
authcodes.to_file(&authcodes.path).unwrap();
|
||||||
|
|
||||||
|
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port), &authcodes.path);
|
||||||
|
|
||||||
|
let _ = connect.map(|conn| assert!(conn.is_ok())).wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user