Compare commits
178 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 |
@@ -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-ethereum/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
|
||||||
|
|
||||||
|
|||||||
47
.github/CONTRIBUTING.md
vendored
47
.github/CONTRIBUTING.md
vendored
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Do you have a question?
|
## Do you have a question?
|
||||||
|
|
||||||
Check out our [Basic Usage](https://wiki.parity.io/Basic-Usage), [Configuration](https://wiki.parity.io/Configuring-Parity-Ethereum), and [FAQ](https://wiki.parity.io/FAQ) articles on our [wiki](https://wiki.parity.io/)!
|
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-ethereum/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 Ethereum 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 Ethereum?
|
- 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,9 +22,44 @@ Also, try to include **steps to reproduce** the issue and expand on the **actual
|
|||||||
|
|
||||||
## Contribute!
|
## Contribute!
|
||||||
|
|
||||||
If you would like to contribute to Parity Ethereum, please **fork it**, fix bugs or implement features, and [propose a pull request](https://github.com/paritytech/parity-ethereum/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://wiki.parity.io/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.
|
||||||
|
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE.md
vendored
4
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,6 +1,8 @@
|
|||||||
|
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**._
|
||||||
|
|
||||||
- **Parity Ethereum version**: 0.0.0
|
- **OpenEthereum version (>=3.1.0)**: 0.0.0
|
||||||
- **Operating system**: Windows / MacOS / Linux
|
- **Operating system**: Windows / MacOS / Linux
|
||||||
- **Installation**: homebrew / one-line installer / built from source
|
- **Installation**: homebrew / one-line installer / built from source
|
||||||
- **Fully synchronized**: no / yes
|
- **Fully synchronized**: no / yes
|
||||||
|
|||||||
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
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -38,8 +38,10 @@ node_modules
|
|||||||
|
|
||||||
# Build artifacts
|
# Build artifacts
|
||||||
out/
|
out/
|
||||||
parity-clib-examples/cpp/build/
|
|
||||||
|
|
||||||
.vscode
|
.vscode
|
||||||
rls/
|
rls/
|
||||||
/parity.*
|
/parity.*
|
||||||
|
|
||||||
|
# cargo remote artifacts
|
||||||
|
remote-target
|
||||||
|
|||||||
260
.gitlab-ci.yml
260
.gitlab-ci.yml
@@ -1,260 +0,0 @@
|
|||||||
stages:
|
|
||||||
- test
|
|
||||||
- build
|
|
||||||
- publish
|
|
||||||
- optional
|
|
||||||
|
|
||||||
image: parity/rust-parity-ethereum-build:xenial
|
|
||||||
variables:
|
|
||||||
GIT_STRATEGY: fetch
|
|
||||||
GIT_SUBMODULE_STRATEGY: recursive
|
|
||||||
CI_SERVER_NAME: "GitLab CI"
|
|
||||||
CARGO_TARGET: x86_64-unknown-linux-gnu
|
|
||||||
|
|
||||||
.no_git: &no_git # disable git strategy
|
|
||||||
variables:
|
|
||||||
GIT_STRATEGY: none
|
|
||||||
GIT_SUBMODULE_STRATEGY: none
|
|
||||||
|
|
||||||
.releaseable_branches: # list of git refs for building GitLab artifacts (think "pre-release binaries")
|
|
||||||
only: &releaseable_branches
|
|
||||||
- stable
|
|
||||||
- beta
|
|
||||||
- tags
|
|
||||||
- schedules
|
|
||||||
|
|
||||||
.collect_artifacts: &collect_artifacts
|
|
||||||
artifacts:
|
|
||||||
name: "${CI_JOB_NAME}_${SCHEDULE_TAG:-${CI_COMMIT_REF_NAME}}"
|
|
||||||
when: on_success
|
|
||||||
expire_in: 1 mos
|
|
||||||
paths:
|
|
||||||
- artifacts/
|
|
||||||
|
|
||||||
.docker-cache-status: &docker-cache-status
|
|
||||||
variables:
|
|
||||||
CARGO_HOME: "/cargo/${CI_JOB_NAME}"
|
|
||||||
before_script:
|
|
||||||
- SCCACHE_ERROR_LOG=/builds/parity/parity-ethereum/sccache_error.log RUST_LOG=sccache::server=debug sccache --start-server
|
|
||||||
- sccache -s
|
|
||||||
after_script:
|
|
||||||
- echo "All crate-types:"
|
|
||||||
- grep 'parse_arguments.*--crate-type' sccache_error.log | sed -re 's/.*"--crate-type", "([^"]+)".*/\1/' | sort | uniq -c
|
|
||||||
- echo "Non-cacheable reasons:"
|
|
||||||
- grep CannotCache sccache_error.log | sed -re 's/.*CannotCache\((.+)\).*/\1/' | sort | uniq -c
|
|
||||||
tags:
|
|
||||||
- linux-docker
|
|
||||||
|
|
||||||
|
|
||||||
cargo-check 0 3:
|
|
||||||
stage: test
|
|
||||||
<<: *docker-cache-status
|
|
||||||
script:
|
|
||||||
- time cargo check --target $CARGO_TARGET --locked --no-default-features
|
|
||||||
- sccache -s
|
|
||||||
|
|
||||||
cargo-check 1 3:
|
|
||||||
stage: test
|
|
||||||
<<: *docker-cache-status
|
|
||||||
script:
|
|
||||||
- time cargo check --target $CARGO_TARGET --locked --manifest-path util/io/Cargo.toml --no-default-features
|
|
||||||
- sccache -s
|
|
||||||
|
|
||||||
cargo-check 2 3:
|
|
||||||
stage: test
|
|
||||||
<<: *docker-cache-status
|
|
||||||
script:
|
|
||||||
- time cargo check --target $CARGO_TARGET --locked --manifest-path util/io/Cargo.toml --features "mio"
|
|
||||||
- sccache -s
|
|
||||||
|
|
||||||
cargo-audit:
|
|
||||||
stage: test
|
|
||||||
<<: *docker-cache-status
|
|
||||||
script:
|
|
||||||
- cargo audit
|
|
||||||
- sccache -s
|
|
||||||
|
|
||||||
validate-chainspecs:
|
|
||||||
stage: test
|
|
||||||
<<: *docker-cache-status
|
|
||||||
script:
|
|
||||||
- ./scripts/gitlab/validate-chainspecs.sh
|
|
||||||
- sccache -s
|
|
||||||
|
|
||||||
test-cpp:
|
|
||||||
stage: build
|
|
||||||
<<: *docker-cache-status
|
|
||||||
script:
|
|
||||||
- ./scripts/gitlab/test-cpp.sh
|
|
||||||
- sccache -s
|
|
||||||
|
|
||||||
test-linux:
|
|
||||||
stage: build
|
|
||||||
<<: *docker-cache-status
|
|
||||||
script:
|
|
||||||
- ./scripts/gitlab/test-linux.sh
|
|
||||||
- sccache -s
|
|
||||||
|
|
||||||
build-android:
|
|
||||||
stage: build
|
|
||||||
image: parity/rust-android:gitlab-ci
|
|
||||||
variables:
|
|
||||||
CARGO_TARGET: armv7-linux-androideabi
|
|
||||||
script:
|
|
||||||
- scripts/gitlab/build-linux.sh
|
|
||||||
tags:
|
|
||||||
- linux-docker
|
|
||||||
<<: *collect_artifacts
|
|
||||||
|
|
||||||
build-linux: &build-linux
|
|
||||||
stage: build
|
|
||||||
only: *releaseable_branches
|
|
||||||
<<: *docker-cache-status
|
|
||||||
script:
|
|
||||||
- scripts/gitlab/build-linux.sh
|
|
||||||
- sccache -s
|
|
||||||
<<: *collect_artifacts
|
|
||||||
|
|
||||||
build-linux-i386:
|
|
||||||
<<: *build-linux
|
|
||||||
image: parity/rust-parity-ethereum-build:i386
|
|
||||||
variables:
|
|
||||||
CARGO_TARGET: i686-unknown-linux-gnu
|
|
||||||
|
|
||||||
build-linux-arm64:
|
|
||||||
<<: *build-linux
|
|
||||||
image: parity/rust-parity-ethereum-build:arm64
|
|
||||||
variables:
|
|
||||||
CARGO_TARGET: aarch64-unknown-linux-gnu
|
|
||||||
|
|
||||||
build-linux-armhf:
|
|
||||||
<<: *build-linux
|
|
||||||
image: parity/rust-parity-ethereum-build:armhf
|
|
||||||
variables:
|
|
||||||
CARGO_TARGET: armv7-unknown-linux-gnueabihf
|
|
||||||
|
|
||||||
build-darwin:
|
|
||||||
stage: build
|
|
||||||
only: *releaseable_branches
|
|
||||||
variables:
|
|
||||||
CARGO_TARGET: x86_64-apple-darwin
|
|
||||||
CARGO_HOME: "${CI_PROJECT_DIR}/.cargo"
|
|
||||||
CC: gcc
|
|
||||||
CXX: g++
|
|
||||||
script:
|
|
||||||
- scripts/gitlab/build-linux.sh
|
|
||||||
tags:
|
|
||||||
- rust-osx
|
|
||||||
<<: *collect_artifacts
|
|
||||||
|
|
||||||
build-windows:
|
|
||||||
stage: build
|
|
||||||
only: *releaseable_branches
|
|
||||||
variables:
|
|
||||||
CARGO_TARGET: x86_64-pc-windows-msvc
|
|
||||||
CARGO_HOME: "${CI_PROJECT_DIR}/.cargo"
|
|
||||||
script:
|
|
||||||
- sh scripts/gitlab/build-windows.sh
|
|
||||||
tags:
|
|
||||||
- rust-windows
|
|
||||||
<<: *collect_artifacts
|
|
||||||
|
|
||||||
publish-docker:
|
|
||||||
stage: publish
|
|
||||||
only: *releaseable_branches
|
|
||||||
cache: {}
|
|
||||||
dependencies:
|
|
||||||
- build-linux
|
|
||||||
tags:
|
|
||||||
- shell
|
|
||||||
script:
|
|
||||||
- scripts/gitlab/publish-docker.sh parity
|
|
||||||
|
|
||||||
publish-snap: &publish-snap
|
|
||||||
stage: publish
|
|
||||||
only: *releaseable_branches
|
|
||||||
image: snapcore/snapcraft
|
|
||||||
variables:
|
|
||||||
BUILD_ARCH: amd64
|
|
||||||
cache: {}
|
|
||||||
dependencies:
|
|
||||||
- build-linux
|
|
||||||
tags:
|
|
||||||
- linux-docker
|
|
||||||
script:
|
|
||||||
- scripts/gitlab/publish-snap.sh
|
|
||||||
<<: *collect_artifacts
|
|
||||||
|
|
||||||
publish-snap-i386:
|
|
||||||
<<: *publish-snap
|
|
||||||
variables:
|
|
||||||
BUILD_ARCH: i386
|
|
||||||
dependencies:
|
|
||||||
- build-linux-i386
|
|
||||||
|
|
||||||
publish-snap-arm64:
|
|
||||||
<<: *publish-snap
|
|
||||||
variables:
|
|
||||||
BUILD_ARCH: arm64
|
|
||||||
dependencies:
|
|
||||||
- build-linux-arm64
|
|
||||||
|
|
||||||
publish-snap-armhf:
|
|
||||||
<<: *publish-snap
|
|
||||||
variables:
|
|
||||||
BUILD_ARCH: armhf
|
|
||||||
dependencies:
|
|
||||||
- build-linux-armhf
|
|
||||||
|
|
||||||
publish-onchain:
|
|
||||||
stage: publish
|
|
||||||
only: *releaseable_branches
|
|
||||||
cache: {}
|
|
||||||
dependencies:
|
|
||||||
- build-linux
|
|
||||||
- build-darwin
|
|
||||||
- build-windows
|
|
||||||
script:
|
|
||||||
- scripts/gitlab/publish-onchain.sh
|
|
||||||
tags:
|
|
||||||
- linux-docker
|
|
||||||
|
|
||||||
publish-awss3-release:
|
|
||||||
image: parity/awscli:latest
|
|
||||||
stage: publish
|
|
||||||
only: *releaseable_branches
|
|
||||||
<<: *no_git
|
|
||||||
cache: {}
|
|
||||||
dependencies:
|
|
||||||
- build-linux
|
|
||||||
- build-darwin
|
|
||||||
- build-windows
|
|
||||||
script:
|
|
||||||
- echo "__________Push binaries to AWS S3____________"
|
|
||||||
- case "${SCHEDULE_TAG:-${CI_COMMIT_REF_NAME}}" in
|
|
||||||
(beta|stable|nightly)
|
|
||||||
export BUCKET=releases.parity.io/ethereum;
|
|
||||||
;;
|
|
||||||
(*)
|
|
||||||
export BUCKET=builds-parity;
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
- aws s3 sync ./artifacts s3://${BUCKET}/${SCHEDULE_TAG:-${CI_COMMIT_REF_NAME}}/
|
|
||||||
- echo "__________Read from S3____________"
|
|
||||||
- aws s3 ls s3://${BUCKET}/${SCHEDULE_TAG:-${CI_COMMIT_REF_NAME}} --recursive --human-readable --summarize
|
|
||||||
tags:
|
|
||||||
- linux-docker
|
|
||||||
|
|
||||||
publish-docs:
|
|
||||||
stage: publish
|
|
||||||
# <<: *no_git
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
except:
|
|
||||||
- nightly
|
|
||||||
cache: {}
|
|
||||||
script:
|
|
||||||
- scripts/gitlab/publish-docs.sh
|
|
||||||
tags:
|
|
||||||
- linux-docker
|
|
||||||
|
|
||||||
212
CHANGELOG.md
212
CHANGELOG.md
@@ -1,174 +1,56 @@
|
|||||||
## Parity-Ethereum [v2.3.0](https://github.com/paritytech/parity-ethereum/releases/tag/v2.3.0) (2019-01-16)
|
## OpenEthereum v3.1.1
|
||||||
|
|
||||||
Parity-Ethereum 2.3.0-beta is a consensus-relevant security release that reverts Constantinople on the Ethereum network. Upgrading is mandatory for Ethereum, and strongly recommended for other networks.
|
Bug fixes:
|
||||||
|
* Ancient target set. InvalidStateRoot bug (#69) (#149)
|
||||||
|
* Update linked-hash-map to 0.5.3
|
||||||
|
|
||||||
- **Consensus** - Ethereum Network: Pull Constantinople protocol upgrade on Ethereum (#10189)
|
Enhancements:
|
||||||
- Read more: [Security Alert: Ethereum Constantinople Postponement](https://blog.ethereum.org/2019/01/15/security-alert-ethereum-constantinople-postponement/)
|
* Added additional Sg-1,Ca-2,Ca-3 OE bootnodes
|
||||||
- **Networking** - All networks: Ping nodes from discovery (#10167)
|
* Add --ws-max-paxload (#155)
|
||||||
- **Wasm** - Kovan Network: Update pwasm-utils to 0.6.1 (#10134)
|
* 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)
|
||||||
|
|
||||||
Other notable changes:
|
DevOps:
|
||||||
|
* Add custom windows runner (#162)
|
||||||
|
* Remove sscache (#138)
|
||||||
|
* Fix deprecated set-env declaration (#106)
|
||||||
|
|
||||||
- Existing blocks in the database are now kept when restoring a Snapshot. (#8643)
|
|
||||||
- Block and transaction propagation is improved significantly. (#9954)
|
|
||||||
- The ERC-191 Signed Data Standard is now supported by `personal_sign191`. (#9701)
|
|
||||||
- Add support for ERC-191/712 `eth_signTypedData` as a standard for machine-verifiable and human-readable typed data signing with Ethereum keys. (#9631)
|
|
||||||
- Add support for ERC-1186 `eth_getProof` (#9001)
|
|
||||||
- Add experimental RPCs flag to enable ERC-191, ERC-712, and ERC-1186 APIs via `--jsonrpc-experimental` (#9928)
|
|
||||||
- Make `CALLCODE` to trace value to be the code address. (#9881)
|
|
||||||
|
|
||||||
Configuration changes:
|
## OpenEthereum v3.1.0
|
||||||
|
|
||||||
- The EIP-98 transition is now disabled by default. If you previously had no `eip98transition` specified in your chain specification, you would enable this now manually on block `0x0`. (#9955)
|
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.
|
||||||
- Also, unknown fields in chain specs are now rejected. (#9972)
|
|
||||||
- The Tendermint engine was removed from Parity Ethereum and is no longer available and maintained. (#9980)
|
|
||||||
- Ropsten testnet data and keys moved from `test/` to `ropsten/` subdir. To reuse your old keys and data either copy or symlink them to the new location. (#10123)
|
|
||||||
- Strict empty steps validation (#10041)
|
|
||||||
- If you have a chain with`empty_steps` already running, some blocks most likely contain non-strict entries (unordered or duplicated empty steps). In this release `strict_empty_steps_transition` is enabled by default at block `0x0` for any chain with `empty_steps`.
|
|
||||||
- If your network uses `empty_steps` you **must** (A) plan a hard fork and change `strict_empty_steps_transition` to the desired fork block and (B) update the clients of the whole network to 2.2.7-stable / 2.3.0-beta. If for some reason you don't want to do this please set`strict_empty_steps_transition` to `0xfffffffff` to disable it.
|
|
||||||
|
|
||||||
_Note:_ This release marks Parity 2.3 as _beta_. All versions of Parity 2.2 are now considered _stable_.
|
Database migration utility currently in beta: https://github.com/openethereum/3.1-db-upgrade-tool
|
||||||
|
|
||||||
The full list of included changes:
|
The full list of included changes from v2.5.13 to v3.1.0:
|
||||||
|
|
||||||
- Backports for 2.3.0 beta ([#10164](https://github.com/paritytech/parity-ethereum/pull/10164))
|
* Use ubuntu-16.04 for glibc compatibility (#11888) (#73)
|
||||||
- Snap: fix path in script ([#10157](https://github.com/paritytech/parity-ethereum/pull/10157))
|
* Remove classic, kotti, mordor, expanse (#52)
|
||||||
- Make sure parent block is not in importing queue when importing ancient blocks ([#10138](https://github.com/paritytech/parity-ethereum/pull/10138))
|
* Added bad block header hash for ropsten (#49)
|
||||||
- Ci: re-enable snap publishing ([#10142](https://github.com/paritytech/parity-ethereum/pull/10142))
|
* Remove accounts bloom (#33)
|
||||||
- Hf in POA Core (2019-01-18) - Constantinople ([#10155](https://github.com/paritytech/parity-ethereum/pull/10155))
|
* Bump jsonrpc-- to v15
|
||||||
- Update EWF's tobalaba chainspec ([#10152](https://github.com/paritytech/parity-ethereum/pull/10152))
|
* Implement eth/64, remove eth/62 (#46)
|
||||||
- Replace ethcore-logger with env-logger. ([#10102](https://github.com/paritytech/parity-ethereum/pull/10102))
|
* No snapshotting by default (#11814)
|
||||||
- Finality: dont require chain head to be in the chain ([#10054](https://github.com/paritytech/parity-ethereum/pull/10054))
|
* Update Ellaism chainspec
|
||||||
- Remove caching for node connections ([#10143](https://github.com/paritytech/parity-ethereum/pull/10143))
|
* Prometheus, heavy memory calls removed (#27)
|
||||||
- Blooms file iterator empty on out of range position. ([#10145](https://github.com/paritytech/parity-ethereum/pull/10145))
|
* Update ethereum/tests
|
||||||
- Autogen docs for the "Configuring Parity Ethereum" wiki page. ([#10067](https://github.com/paritytech/parity-ethereum/pull/10067))
|
* Implement JSON test suite (#11801)
|
||||||
- Misc: bump license header to 2019 ([#10135](https://github.com/paritytech/parity-ethereum/pull/10135))
|
* Fix issues during block sync (#11265)
|
||||||
- Hide most of the logs from cpp example. ([#10139](https://github.com/paritytech/parity-ethereum/pull/10139))
|
* Fix race same block (#11400)
|
||||||
- Don't try to send oversized packets ([#10042](https://github.com/paritytech/parity-ethereum/pull/10042))
|
* EIP-2537: Precompile for BLS12-381 curve operations (#11707)
|
||||||
- Private tx enabled flag added into STATUS packet ([#9999](https://github.com/paritytech/parity-ethereum/pull/9999))
|
* Remove private transactions
|
||||||
- Update pwasm-utils to 0.6.1 ([#10134](https://github.com/paritytech/parity-ethereum/pull/10134))
|
* Remove GetNodeData
|
||||||
- Extract blockchain from ethcore ([#10114](https://github.com/paritytech/parity-ethereum/pull/10114))
|
* Remove IPFS integration (#11532)
|
||||||
- Ethcore: update hardcoded headers ([#10123](https://github.com/paritytech/parity-ethereum/pull/10123))
|
* Remove updater
|
||||||
- Identity fix ([#10128](https://github.com/paritytech/parity-ethereum/pull/10128))
|
* Remove light client
|
||||||
- Use LenCachingMutex to optimize verification. ([#10117](https://github.com/paritytech/parity-ethereum/pull/10117))
|
* Remove C and Java bindings (#11346)
|
||||||
- Pyethereum keystore support ([#9710](https://github.com/paritytech/parity-ethereum/pull/9710))
|
* Remove whisper (#10855)
|
||||||
- Bump rocksdb-sys to 0.5.5 ([#10124](https://github.com/paritytech/parity-ethereum/pull/10124))
|
* EIP-2315: Simple Subroutines for the EVM (#11629)
|
||||||
- Parity-clib: `async C bindings to RPC requests` + `subscribe/unsubscribe to websocket events` ([#9920](https://github.com/paritytech/parity-ethereum/pull/9920))
|
* Remove deprecated flags (removal of --geth flag)
|
||||||
- Refactor (hardware wallet) : reduce the number of threads ([#9644](https://github.com/paritytech/parity-ethereum/pull/9644))
|
* Remove support for hardware wallets (#10678)
|
||||||
- Hf in POA Sokol (2019-01-04) ([#10077](https://github.com/paritytech/parity-ethereum/pull/10077))
|
* Update bootnodes
|
||||||
- Fix broken links ([#10119](https://github.com/paritytech/parity-ethereum/pull/10119))
|
|
||||||
- Follow-up to [#10105](https://github.com/paritytech/parity-ethereum/issues/10105) ([#10107](https://github.com/paritytech/parity-ethereum/pull/10107))
|
|
||||||
- Move EIP-712 crate back to parity-ethereum ([#10106](https://github.com/paritytech/parity-ethereum/pull/10106))
|
|
||||||
- Move a bunch of stuff around ([#10101](https://github.com/paritytech/parity-ethereum/pull/10101))
|
|
||||||
- Revert "Add --frozen when running cargo ([#10081](https://github.com/paritytech/parity-ethereum/pull/10081))" ([#10105](https://github.com/paritytech/parity-ethereum/pull/10105))
|
|
||||||
- Fix left over small grumbles on whitespaces ([#10084](https://github.com/paritytech/parity-ethereum/pull/10084))
|
|
||||||
- Add --frozen when running cargo ([#10081](https://github.com/paritytech/parity-ethereum/pull/10081))
|
|
||||||
- Fix pubsub new_blocks notifications to include all blocks ([#9987](https://github.com/paritytech/parity-ethereum/pull/9987))
|
|
||||||
- Update some dependencies for compilation with pc-windows-gnu ([#10082](https://github.com/paritytech/parity-ethereum/pull/10082))
|
|
||||||
- Fill transaction hash on ethGetLog of light client. ([#9938](https://github.com/paritytech/parity-ethereum/pull/9938))
|
|
||||||
- Update changelog update for 2.2.5-beta and 2.1.10-stable ([#10064](https://github.com/paritytech/parity-ethereum/pull/10064))
|
|
||||||
- Implement len caching for parking_lot RwLock ([#10032](https://github.com/paritytech/parity-ethereum/pull/10032))
|
|
||||||
- Update parking_lot to 0.7 ([#10050](https://github.com/paritytech/parity-ethereum/pull/10050))
|
|
||||||
- Bump crossbeam. ([#10048](https://github.com/paritytech/parity-ethereum/pull/10048))
|
|
||||||
- Ethcore: enable constantinople on ethereum ([#10031](https://github.com/paritytech/parity-ethereum/pull/10031))
|
|
||||||
- Strict empty steps validation ([#10041](https://github.com/paritytech/parity-ethereum/pull/10041))
|
|
||||||
- Center the Subtitle, use some CAPS ([#10034](https://github.com/paritytech/parity-ethereum/pull/10034))
|
|
||||||
- Change test miner max memory to malloc reports. ([#10024](https://github.com/paritytech/parity-ethereum/pull/10024))
|
|
||||||
- Sort the storage for private state ([#10018](https://github.com/paritytech/parity-ethereum/pull/10018))
|
|
||||||
- Fix: test corpus_inaccessible panic ([#10019](https://github.com/paritytech/parity-ethereum/pull/10019))
|
|
||||||
- Ci: move future releases to ethereum subdir on s3 ([#10017](https://github.com/paritytech/parity-ethereum/pull/10017))
|
|
||||||
- Light(on_demand): decrease default time window to 10 secs ([#10016](https://github.com/paritytech/parity-ethereum/pull/10016))
|
|
||||||
- Light client : failsafe crate (circuit breaker) ([#9790](https://github.com/paritytech/parity-ethereum/pull/9790))
|
|
||||||
- Lencachingmutex ([#9988](https://github.com/paritytech/parity-ethereum/pull/9988))
|
|
||||||
- Version and notification for private contract wrapper added ([#9761](https://github.com/paritytech/parity-ethereum/pull/9761))
|
|
||||||
- Handle failing case for update account cache in require ([#9989](https://github.com/paritytech/parity-ethereum/pull/9989))
|
|
||||||
- Add tokio runtime to ethcore io worker ([#9979](https://github.com/paritytech/parity-ethereum/pull/9979))
|
|
||||||
- Move daemonize before creating account provider ([#10003](https://github.com/paritytech/parity-ethereum/pull/10003))
|
|
||||||
- Docs: update changelogs ([#9990](https://github.com/paritytech/parity-ethereum/pull/9990))
|
|
||||||
- Fix daemonize ([#10000](https://github.com/paritytech/parity-ethereum/pull/10000))
|
|
||||||
- Fix Bloom migration ([#9992](https://github.com/paritytech/parity-ethereum/pull/9992))
|
|
||||||
- Remove tendermint engine support ([#9980](https://github.com/paritytech/parity-ethereum/pull/9980))
|
|
||||||
- Calculate gas for deployment transaction ([#9840](https://github.com/paritytech/parity-ethereum/pull/9840))
|
|
||||||
- Fix unstable peers and slowness in sync ([#9967](https://github.com/paritytech/parity-ethereum/pull/9967))
|
|
||||||
- Adds parity_verifySignature RPC method ([#9507](https://github.com/paritytech/parity-ethereum/pull/9507))
|
|
||||||
- Improve block and transaction propagation ([#9954](https://github.com/paritytech/parity-ethereum/pull/9954))
|
|
||||||
- Deny unknown fields for chainspec ([#9972](https://github.com/paritytech/parity-ethereum/pull/9972))
|
|
||||||
- Fix docker build ([#9971](https://github.com/paritytech/parity-ethereum/pull/9971))
|
|
||||||
- Ci: rearrange pipeline by logic ([#9970](https://github.com/paritytech/parity-ethereum/pull/9970))
|
|
||||||
- Add changelogs for 2.0.9, 2.1.4, 2.1.6, and 2.2.1 ([#9963](https://github.com/paritytech/parity-ethereum/pull/9963))
|
|
||||||
- Add Error message when sync is still in progress. ([#9475](https://github.com/paritytech/parity-ethereum/pull/9475))
|
|
||||||
- Make CALLCODE to trace value to be the code address ([#9881](https://github.com/paritytech/parity-ethereum/pull/9881))
|
|
||||||
- Fix light client informant while syncing ([#9932](https://github.com/paritytech/parity-ethereum/pull/9932))
|
|
||||||
- Add a optional json dump state to evm-bin ([#9706](https://github.com/paritytech/parity-ethereum/pull/9706))
|
|
||||||
- Disable EIP-98 transition by default ([#9955](https://github.com/paritytech/parity-ethereum/pull/9955))
|
|
||||||
- Remove secret_store runtimes. ([#9888](https://github.com/paritytech/parity-ethereum/pull/9888))
|
|
||||||
- Fix a deadlock ([#9952](https://github.com/paritytech/parity-ethereum/pull/9952))
|
|
||||||
- Chore(eip712): remove unused `failure-derive` ([#9958](https://github.com/paritytech/parity-ethereum/pull/9958))
|
|
||||||
- Do not use the home directory as the working dir in docker ([#9834](https://github.com/paritytech/parity-ethereum/pull/9834))
|
|
||||||
- Prevent silent errors in daemon mode, closes [#9367](https://github.com/paritytech/parity-ethereum/issues/9367) ([#9946](https://github.com/paritytech/parity-ethereum/pull/9946))
|
|
||||||
- Fix empty steps ([#9939](https://github.com/paritytech/parity-ethereum/pull/9939))
|
|
||||||
- Adjust requests costs for light client ([#9925](https://github.com/paritytech/parity-ethereum/pull/9925))
|
|
||||||
- Eip-1186: add `eth_getProof` RPC-Method ([#9001](https://github.com/paritytech/parity-ethereum/pull/9001))
|
|
||||||
- Missing blocks in filter_changes RPC ([#9947](https://github.com/paritytech/parity-ethereum/pull/9947))
|
|
||||||
- Allow rust-nightly builds fail in nightly builds ([#9944](https://github.com/paritytech/parity-ethereum/pull/9944))
|
|
||||||
- Update eth-secp256k1 to include fix for BSDs ([#9935](https://github.com/paritytech/parity-ethereum/pull/9935))
|
|
||||||
- Unbreak build on rust -stable ([#9934](https://github.com/paritytech/parity-ethereum/pull/9934))
|
|
||||||
- Keep existing blocks when restoring a Snapshot ([#8643](https://github.com/paritytech/parity-ethereum/pull/8643))
|
|
||||||
- Add experimental RPCs flag ([#9928](https://github.com/paritytech/parity-ethereum/pull/9928))
|
|
||||||
- Clarify poll lifetime ([#9922](https://github.com/paritytech/parity-ethereum/pull/9922))
|
|
||||||
- Docs(require rust 1.30) ([#9923](https://github.com/paritytech/parity-ethereum/pull/9923))
|
|
||||||
- Use block header for building finality ([#9914](https://github.com/paritytech/parity-ethereum/pull/9914))
|
|
||||||
- Simplify cargo audit ([#9918](https://github.com/paritytech/parity-ethereum/pull/9918))
|
|
||||||
- Light-fetch: Differentiate between out-of-gas/manual throw and use required gas from response on failure ([#9824](https://github.com/paritytech/parity-ethereum/pull/9824))
|
|
||||||
- Eip 191 ([#9701](https://github.com/paritytech/parity-ethereum/pull/9701))
|
|
||||||
- Fix(logger): `reqwest` no longer a dependency ([#9908](https://github.com/paritytech/parity-ethereum/pull/9908))
|
|
||||||
- Remove rust-toolchain file ([#9906](https://github.com/paritytech/parity-ethereum/pull/9906))
|
|
||||||
- Foundation: 6692865, ropsten: 4417537, kovan: 9363457 ([#9907](https://github.com/paritytech/parity-ethereum/pull/9907))
|
|
||||||
- Ethcore: use Machine::verify_transaction on parent block ([#9900](https://github.com/paritytech/parity-ethereum/pull/9900))
|
|
||||||
- Chore(rpc-tests): remove unused rand ([#9896](https://github.com/paritytech/parity-ethereum/pull/9896))
|
|
||||||
- Fix: Intermittent failing CI due to addr in use ([#9885](https://github.com/paritytech/parity-ethereum/pull/9885))
|
|
||||||
- Chore(bump docopt): 0.8 -> 1.0 ([#9889](https://github.com/paritytech/parity-ethereum/pull/9889))
|
|
||||||
- Use expect ([#9883](https://github.com/paritytech/parity-ethereum/pull/9883))
|
|
||||||
- Use Weak reference in PubSubClient ([#9886](https://github.com/paritytech/parity-ethereum/pull/9886))
|
|
||||||
- Ci: nuke the gitlab caches ([#9855](https://github.com/paritytech/parity-ethereum/pull/9855))
|
|
||||||
- Remove unused code ([#9884](https://github.com/paritytech/parity-ethereum/pull/9884))
|
|
||||||
- Fix json tracer overflow ([#9873](https://github.com/paritytech/parity-ethereum/pull/9873))
|
|
||||||
- Allow to seal work on latest block ([#9876](https://github.com/paritytech/parity-ethereum/pull/9876))
|
|
||||||
- Fix docker script ([#9854](https://github.com/paritytech/parity-ethereum/pull/9854))
|
|
||||||
- Health endpoint ([#9847](https://github.com/paritytech/parity-ethereum/pull/9847))
|
|
||||||
- Gitlab-ci: make android release build succeed ([#9743](https://github.com/paritytech/parity-ethereum/pull/9743))
|
|
||||||
- Clean up existing benchmarks ([#9839](https://github.com/paritytech/parity-ethereum/pull/9839))
|
|
||||||
- Update Callisto block reward code to support HF1 ([#9811](https://github.com/paritytech/parity-ethereum/pull/9811))
|
|
||||||
- Option to disable keep alive for JSON-RPC http transport ([#9848](https://github.com/paritytech/parity-ethereum/pull/9848))
|
|
||||||
- Classic.json Bootnode Update ([#9828](https://github.com/paritytech/parity-ethereum/pull/9828))
|
|
||||||
- Support MIX. ([#9767](https://github.com/paritytech/parity-ethereum/pull/9767))
|
|
||||||
- Ci: remove failing tests for android, windows, and macos ([#9788](https://github.com/paritytech/parity-ethereum/pull/9788))
|
|
||||||
- Implement NoProof for json tests and update tests reference (replaces [#9744](https://github.com/paritytech/parity-ethereum/issues/9744)) ([#9814](https://github.com/paritytech/parity-ethereum/pull/9814))
|
|
||||||
- Chore(bump regex) ([#9842](https://github.com/paritytech/parity-ethereum/pull/9842))
|
|
||||||
- Ignore global cache for patched accounts ([#9752](https://github.com/paritytech/parity-ethereum/pull/9752))
|
|
||||||
- Move state root verification before gas used ([#9841](https://github.com/paritytech/parity-ethereum/pull/9841))
|
|
||||||
- Fix(docker-aarch64) : cross-compile config ([#9798](https://github.com/paritytech/parity-ethereum/pull/9798))
|
|
||||||
- Version: bump nightly to 2.3.0 ([#9819](https://github.com/paritytech/parity-ethereum/pull/9819))
|
|
||||||
- Tests modification for windows CI ([#9671](https://github.com/paritytech/parity-ethereum/pull/9671))
|
|
||||||
- Eip-712 implementation ([#9631](https://github.com/paritytech/parity-ethereum/pull/9631))
|
|
||||||
- Fix typo ([#9826](https://github.com/paritytech/parity-ethereum/pull/9826))
|
|
||||||
- Clean up serde rename and use rename_all = camelCase when possible ([#9823](https://github.com/paritytech/parity-ethereum/pull/9823))
|
|
||||||
|
|
||||||
## Previous releases
|
|
||||||
|
|
||||||
- [CHANGELOG-2.2](docs/CHANGELOG-2.2.md) (_stable_)
|
|
||||||
- [CHANGELOG-2.1](docs/CHANGELOG-2.1.md) (EOL: 2019-01-16)
|
|
||||||
- [CHANGELOG-2.0](docs/CHANGELOG-2.0.md) (EOL: 2018-11-15)
|
|
||||||
- [CHANGELOG-1.11](docs/CHANGELOG-1.11.md) (EOL: 2018-09-19)
|
|
||||||
- [CHANGELOG-1.10](docs/CHANGELOG-1.10.md) (EOL: 2018-07-18)
|
|
||||||
- [CHANGELOG-1.9](docs/CHANGELOG-1.9.md) (EOL: 2018-05-09)
|
|
||||||
- [CHANGELOG-1.8](docs/CHANGELOG-1.8.md) (EOL: 2018-03-22)
|
|
||||||
- [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)
|
|
||||||
|
|||||||
5782
Cargo.lock
generated
5782
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
36
Cargo.toml
36
Cargo.toml
@@ -1,10 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
description = "Parity Ethereum client"
|
description = "OpenEthereum"
|
||||||
name = "parity-ethereum"
|
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 = "2.4.3"
|
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]
|
||||||
blooms-db = { path = "util/blooms-db" }
|
blooms-db = { path = "util/blooms-db" }
|
||||||
@@ -27,9 +30,10 @@ serde = "1.0"
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
|
hyper = { version = "0.12" }
|
||||||
fdlimit = "0.1"
|
fdlimit = "0.1"
|
||||||
ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }
|
ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }
|
||||||
jsonrpc-core = "10.0.1"
|
jsonrpc-core = "15.0.0"
|
||||||
parity-bytes = "0.1"
|
parity-bytes = "0.1"
|
||||||
common-types = { path = "ethcore/types" }
|
common-types = { path = "ethcore/types" }
|
||||||
ethcore = { path = "ethcore", features = ["parity"] }
|
ethcore = { path = "ethcore", features = ["parity"] }
|
||||||
@@ -38,28 +42,23 @@ ethcore-blockchain = { path = "ethcore/blockchain" }
|
|||||||
ethcore-call-contract = { path = "ethcore/call-contract"}
|
ethcore-call-contract = { path = "ethcore/call-contract"}
|
||||||
ethcore-db = { path = "ethcore/db" }
|
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 = "parity/logger" }
|
||||||
ethcore-miner = { path = "miner" }
|
ethcore-miner = { path = "miner" }
|
||||||
ethcore-network = { path = "util/network" }
|
ethcore-network = { path = "util/network" }
|
||||||
ethcore-private-tx = { path = "ethcore/private-tx" }
|
|
||||||
ethcore-service = { path = "ethcore/service" }
|
ethcore-service = { path = "ethcore/service" }
|
||||||
ethcore-sync = { path = "ethcore/sync" }
|
ethcore-sync = { path = "ethcore/sync" }
|
||||||
ethereum-types = "0.4"
|
ethereum-types = "0.4"
|
||||||
ethkey = { path = "accounts/ethkey" }
|
ethkey = { path = "accounts/ethkey" }
|
||||||
ethstore = { path = "accounts/ethstore" }
|
ethstore = { path = "accounts/ethstore" }
|
||||||
|
fetch = { path = "util/fetch" }
|
||||||
node-filter = { path = "ethcore/node-filter" }
|
node-filter = { path = "ethcore/node-filter" }
|
||||||
rlp = { version = "0.3.0", features = ["ethereum"] }
|
rlp = { version = "0.3.0", features = ["ethereum"] }
|
||||||
cli-signer= { path = "cli-signer" }
|
cli-signer= { path = "cli-signer" }
|
||||||
parity-daemonize = "0.3"
|
parity-daemonize = "0.3"
|
||||||
parity-hash-fetch = { path = "updater/hash-fetch" }
|
|
||||||
parity-ipfs-api = { path = "ipfs" }
|
|
||||||
parity-local-store = { path = "miner/local-store" }
|
parity-local-store = { path = "miner/local-store" }
|
||||||
parity-runtime = { path = "util/runtime" }
|
parity-runtime = { path = "util/runtime" }
|
||||||
parity-rpc = { path = "rpc" }
|
parity-rpc = { path = "rpc" }
|
||||||
parity-updater = { path = "updater" }
|
|
||||||
parity-version = { path = "util/version" }
|
parity-version = { path = "util/version" }
|
||||||
parity-whisper = { path = "whisper" }
|
|
||||||
parity-path = "0.1"
|
parity-path = "0.1"
|
||||||
dir = { path = "util/dir" }
|
dir = { path = "util/dir" }
|
||||||
panic_hook = { path = "util/panic-hook" }
|
panic_hook = { path = "util/panic-hook" }
|
||||||
@@ -68,6 +67,8 @@ migration-rocksdb = { path = "util/migration-rocksdb" }
|
|||||||
kvdb = "0.1"
|
kvdb = "0.1"
|
||||||
kvdb-rocksdb = "0.1.3"
|
kvdb-rocksdb = "0.1.3"
|
||||||
journaldb = { path = "util/journaldb" }
|
journaldb = { path = "util/journaldb" }
|
||||||
|
stats = { path = "util/stats" }
|
||||||
|
prometheus = "0.9.0"
|
||||||
|
|
||||||
ethcore-secretstore = { path = "secret-store", optional = true }
|
ethcore-secretstore = { path = "secret-store", optional = true }
|
||||||
|
|
||||||
@@ -106,22 +107,21 @@ deadlock_detection = ["parking_lot/deadlock_detection"]
|
|||||||
# `valgrind --tool=massif /path/to/parity <parity params>`
|
# `valgrind --tool=massif /path/to/parity <parity params>`
|
||||||
# and `massif-visualizer` for visualization
|
# and `massif-visualizer` for visualization
|
||||||
memory_profiling = []
|
memory_profiling = []
|
||||||
# hardcode version number 1.3.7 of parity to force an update
|
|
||||||
# in order to manually test that parity fall-over to the local version
|
|
||||||
# in case of invalid or deprecated command line arguments are entered
|
|
||||||
test-updater = ["parity-updater/test-updater"]
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
path = "parity/lib.rs"
|
path = "parity/lib.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
path = "parity/main.rs"
|
path = "parity/main.rs"
|
||||||
name = "parity"
|
name = "openethereum"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.test]
|
||||||
|
lto = false
|
||||||
|
opt-level = 3 # makes tests slower to compile, but faster to run
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = false
|
debug = false
|
||||||
|
lto = true
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
# This should only list projects that are not
|
# This should only list projects that are not
|
||||||
@@ -133,8 +133,6 @@ members = [
|
|||||||
"chainspec",
|
"chainspec",
|
||||||
"ethcore/wasm/run",
|
"ethcore/wasm/run",
|
||||||
"evmbin",
|
"evmbin",
|
||||||
"parity-clib",
|
|
||||||
"whisper/cli",
|
|
||||||
"util/triehash-ethereum",
|
"util/triehash-ethereum",
|
||||||
"util/keccak-hasher",
|
"util/keccak-hasher",
|
||||||
"util/patricia-trie-ethereum",
|
"util/patricia-trie-ethereum",
|
||||||
|
|||||||
300
README.md
300
README.md
@@ -1,13 +1,38 @@
|
|||||||

|
# OpenEthereum
|
||||||
|
|
||||||
<h2 align="center">The Fastest and most Advanced Ethereum Client.</h2>
|
Fast and feature-rich multi-network Ethereum client.
|
||||||
|
|
||||||
<p align="center"><strong><a href="https://github.com/paritytech/parity-ethereum/releases/latest">» Download the latest release «</a></strong></p>
|
[» Download the latest release «](https://github.com/openethereum/openethereum/releases/latest)
|
||||||
|
|
||||||
<p align="center"><a href="https://gitlab.parity.io/parity/parity-ethereum/commits/master" target="_blank"><img src="https://gitlab.parity.io/parity/parity-ethereum/badges/master/build.svg" /></a>
|
[![GPL licensed][license-badge]][license-url]
|
||||||
<a href="https://www.gnu.org/licenses/gpl-3.0.en.html" target="_blank"><img src="https://img.shields.io/badge/license-GPL%20v3-green.svg" /></a></p>
|
[![Build Status][ci-badge]][ci-url]
|
||||||
|
[![Discord chat][chat-badge]][chat-url]
|
||||||
|
|
||||||
**Built for mission-critical use**: Miners, service providers, and exchanges need fast synchronisation and maximum uptime. Parity Ethereum provides the core infrastructure essential for speedy and reliable services.
|
[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)
|
||||||
|
|
||||||
|
|
||||||
|
## 1. Description <a id="chapter-001"></a>
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
- Clean, modular codebase for easy customisation
|
- Clean, modular codebase for easy customisation
|
||||||
- Advanced CLI-based client
|
- Advanced CLI-based client
|
||||||
@@ -15,19 +40,21 @@
|
|||||||
- Synchronise in hours, not days with Warp Sync
|
- Synchronise in hours, not days with Warp Sync
|
||||||
- Modular for light integration into your service or product
|
- Modular for light integration into your service or product
|
||||||
|
|
||||||
## Technical Overview
|
## 2. Technical Overview <a id="chapter-002"></a>
|
||||||
|
|
||||||
Parity Ethereum's goal is to be the fastest, lightest, and most secure Ethereum client. We are developing Parity Ethereum using the sophisticated and cutting-edge **Rust programming language**. Parity Ethereum is licensed under the GPLv3 and can be used for all your Ethereum needs.
|
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, Parity Ethereum 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.
|
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.
|
||||||
|
|
||||||
If you run into problems while using Parity Ethereum, check out the [wiki for documentation](https://wiki.parity.io/), feel free to [file an issue in this repository](https://github.com/paritytech/parity-ethereum/issues/new), 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! **For security-critical issues**, please refer to the security policy outlined in [SECURITY.md](SECURITY.md).
|
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 Ethereum's current beta-release is 2.1. You can download it at [the releases page](https://github.com/paritytech/parity-ethereum/releases) or follow the instructions below to build from source. Please, mind the [CHANGELOG.md](CHANGELOG.md) for a list of all changes between different versions.
|
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.
|
||||||
|
|
||||||
## Build Dependencies
|
## 3. Building <a id="chapter-003"></a>
|
||||||
|
|
||||||
Parity Ethereum requires **latest stable Rust version** to build.
|
### 3.1 Build Dependencies <a id="chapter-0031"></a>
|
||||||
|
|
||||||
|
OpenEthereum requires **latest stable Rust version** 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:
|
We recommend installing Rust through [rustup](https://www.rustup.rs/). If you don't already have `rustup`, you can install it like this:
|
||||||
|
|
||||||
@@ -36,7 +63,7 @@ We recommend installing Rust through [rustup](https://www.rustup.rs/). If you do
|
|||||||
$ curl https://sh.rustup.rs -sSf | sh
|
$ curl https://sh.rustup.rs -sSf | sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Parity Ethereum also requires `gcc`, `g++`, `libudev-dev`, `pkg-config`, `file`, `make`, and `cmake` packages to be installed.
|
OpenEthereum also requires `clang` (>= 9.0), `clang++`, `pkg-config`, `file`, `make`, and `cmake` packages to be installed.
|
||||||
|
|
||||||
- OSX:
|
- OSX:
|
||||||
```bash
|
```bash
|
||||||
@@ -45,7 +72,7 @@ We recommend installing Rust through [rustup](https://www.rustup.rs/). If you do
|
|||||||
|
|
||||||
`clang` is required. It comes with Xcode command line tools or can be installed with homebrew.
|
`clang` is required. It comes with Xcode command line tools or can be installed with homebrew.
|
||||||
|
|
||||||
- Windows
|
- Windows:
|
||||||
Make sure you have Visual Studio 2015 with C++ support installed. Next, download and run the `rustup` installer from
|
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:
|
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
|
||||||
@@ -56,14 +83,14 @@ Once you have `rustup` installed, then you need to install:
|
|||||||
* [Perl](https://www.perl.org)
|
* [Perl](https://www.perl.org)
|
||||||
* [Yasm](https://yasm.tortall.net)
|
* [Yasm](https://yasm.tortall.net)
|
||||||
|
|
||||||
Make sure that these binaries are in your `PATH`. After that, you should be able to build Parity Ethereum from source.
|
Make sure that these binaries are in your `PATH`. After that, you should be able to build OpenEthereum from source.
|
||||||
|
|
||||||
## Build from Source Code
|
### 3.2 Build from Source Code <a id="chapter-0032"></a>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# download Parity Ethereum code
|
# download OpenEthereum code
|
||||||
$ git clone https://github.com/paritytech/parity-ethereum
|
$ git clone https://github.com/openethereum/openethereum
|
||||||
$ cd parity-ethereum
|
$ cd openethereum
|
||||||
|
|
||||||
# build in release mode
|
# build in release mode
|
||||||
$ cargo build --release --features final
|
$ cargo build --release --features final
|
||||||
@@ -83,74 +110,209 @@ Note, when compiling a crate and you receive errors, it's in most cases your out
|
|||||||
$ cargo clean
|
$ cargo clean
|
||||||
```
|
```
|
||||||
|
|
||||||
This always compiles 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
|
||||||
```
|
```
|
||||||
|
|
||||||
## Simple One-Line Installer for Mac and Linux
|
so OpenEthereum begins syncing the Ethereum blockchain.
|
||||||
|
|
||||||
```bash
|
#### Using `systemd` service file
|
||||||
bash <(curl https://get.parity.io -L)
|
|
||||||
```
|
|
||||||
|
|
||||||
The one-line installer always defaults to the latest beta release. To install a stable release, run:
|
To start OpenEthereum as a regular user using `systemd` init:
|
||||||
|
|
||||||
```bash
|
1. Copy `./scripts/openethereum.service` to your
|
||||||
bash <(curl https://get.parity.io -L) -r stable
|
|
||||||
```
|
|
||||||
|
|
||||||
## Start Parity Ethereum
|
|
||||||
|
|
||||||
### Manually
|
|
||||||
|
|
||||||
To start Parity Ethereum manually, just run
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ ./target/release/parity
|
|
||||||
```
|
|
||||||
|
|
||||||
so Parity Ethereum begins syncing the Ethereum blockchain.
|
|
||||||
|
|
||||||
### Using `systemd` service file
|
|
||||||
|
|
||||||
To start Parity Ethereum as a regular user using `systemd` init:
|
|
||||||
|
|
||||||
1. Copy `./scripts/parity.service` to your
|
|
||||||
`systemd` user directory (usually `~/.config/systemd/user`).
|
`systemd` user directory (usually `~/.config/systemd/user`).
|
||||||
2. Copy release to bin folder, write `sudo install ./target/release/parity /usr/bin/parity`
|
2. Copy release to bin folder, write `sudo install ./target/release/openethereum /usr/bin/openethereum`
|
||||||
3. To configure Parity Ethereum, write a `/etc/parity/config.toml` config file, see [Configuring Parity Ethereum](https://paritytech.github.io/wiki/Configuring-Parity) for details.
|
3. To configure OpenEthereum, see [our wiki](https://openethereum.github.io/Configuring-OpenEthereum) for details.
|
||||||
|
|
||||||
## Parity Ethereum toolchain
|
## 4. Testing <a id="chapter-004"></a>
|
||||||
|
|
||||||
In addition to the Parity Ethereum client, there are additional tools in this repository available:
|
Download the required test files: `git submodule update --init --recursive`. You can run tests with the following commands:
|
||||||
|
|
||||||
- [evmbin](https://github.com/paritytech/parity-ethereum/blob/master/evmbin/) - EVM implementation for Parity Ethereum.
|
* **All** packages
|
||||||
- [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.
|
cargo test --all
|
||||||
- [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator.
|
```
|
||||||
- [whisper](https://github.com/paritytech/parity-ethereum/blob/master/whisper/) - Implementation of Whisper-v2 PoC.
|
|
||||||
|
|
||||||
## Join the chat!
|
* Specific package
|
||||||
|
```
|
||||||
|
cargo test --package <spec>
|
||||||
|
```
|
||||||
|
|
||||||
Questions? Get in touch with us on Gitter:
|
Replace `<spec>` with one of the packages from the [package list](#package-list) (e.g. `cargo test --package evmbin`).
|
||||||
[](https://gitter.im/paritytech/parity)
|
|
||||||
[](https://gitter.im/paritytech/parity.js)
|
|
||||||
[](https://gitter.im/paritytech/parity/miners)
|
|
||||||
[](https://gitter.im/paritytech/parity-poa)
|
|
||||||
|
|
||||||
Alternatively, join our community on Matrix:
|
You can show your logs in the test output by passing `--nocapture` (i.e. `cargo test --package evmbin -- --nocapture`)
|
||||||
[](https://riot.im/app/#/group/+parity:matrix.parity.io)
|
|
||||||
|
|
||||||
## Documentation
|
## 5. Documentation <a id="chapter-005"></a>
|
||||||
|
|
||||||
Official website: https://parity.io
|
Be sure to [check out our wiki](https://openethereum.github.io/) for more information.
|
||||||
|
|
||||||
Be sure to [check out our wiki](https://wiki.parity.io) for more information.
|
### Viewing documentation for OpenEthereum packages
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
* **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-----
|
|
||||||
```
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
description = "Account management for Parity Ethereum"
|
description = "OpenEthereum Account Management"
|
||||||
homepage = "http://parity.io"
|
homepage = "https://github.com/openethereum/openethereum"
|
||||||
license = "GPL-3.0"
|
license = "GPL-3.0"
|
||||||
name = "ethcore-accounts"
|
name = "ethcore-accounts"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -17,12 +17,6 @@ serde = "1.0"
|
|||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))'.dependencies]
|
|
||||||
hardware-wallet = { path = "hw" }
|
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))'.dependencies]
|
|
||||||
fake-hardware-wallet = { path = "fake-hardware-wallet" }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ethereum-types = "0.4"
|
ethereum-types = "0.4"
|
||||||
tempdir = "0.3"
|
tempdir = "0.3"
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
[package]
|
[package]
|
||||||
|
description = "Parity Ethereum Keys Generator"
|
||||||
name = "ethkey"
|
name = "ethkey"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "1.0"
|
|
||||||
edit-distance = "2.0"
|
edit-distance = "2.0"
|
||||||
parity-crypto = "0.3.0"
|
parity-crypto = "0.3.0"
|
||||||
eth-secp256k1 = { git = "https://github.com/paritytech/rust-secp256k1" }
|
eth-secp256k1 = { git = "https://github.com/paritytech/rust-secp256k1", rev = "ccc06e7480148b723eb44ac56cf4d20eec380b6f" }
|
||||||
ethereum-types = "0.4"
|
ethereum-types = "0.4"
|
||||||
lazy_static = "1.0"
|
lazy_static = "1.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
memzero = { path = "../../util/memzero" }
|
memzero = { path = "../../util/memzero" }
|
||||||
parity-wordlist = "1.2"
|
parity-wordlist = "1.3"
|
||||||
quick-error = "1.2.2"
|
quick-error = "1.2.2"
|
||||||
rand = "0.4"
|
rand = "0.4"
|
||||||
rustc-hex = "1.0"
|
rustc-hex = "1.0"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Parity Ethereum keys generator.
|
|||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
Parity Ethereum keys generator.
|
Parity Ethereum Keys Generator.
|
||||||
Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@@ -218,4 +218,3 @@ _This project is a part of the Parity Ethereum toolchain._
|
|||||||
- [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum function calls encoding.
|
- [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.
|
- [ethstore](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethstore) - Parity Ethereum key management.
|
||||||
- [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator.
|
- [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator.
|
||||||
- [whisper](https://github.com/paritytech/parity-ethereum/blob/master/whisper/) - Implementation of Whisper-v2 PoC.
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
[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>"]
|
||||||
@@ -8,7 +9,7 @@ docopt = "1.0"
|
|||||||
env_logger = "0.5"
|
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"
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
extern crate docopt;
|
extern crate docopt;
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
@@ -26,11 +26,13 @@ extern crate threadpool;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
use std::num::ParseIntError;
|
use std::{env, fmt, io, num::ParseIntError, process, sync};
|
||||||
use std::{env, fmt, process, io, sync};
|
|
||||||
|
|
||||||
use docopt::Docopt;
|
use docopt::Docopt;
|
||||||
use ethkey::{KeyPair, Random, Brain, BrainPrefix, Prefix, Error as EthkeyError, Generator, sign, verify_public, verify_address, brain_recover};
|
use ethkey::{
|
||||||
|
brain_recover, sign, verify_address, verify_public, Brain, BrainPrefix, Error as EthkeyError,
|
||||||
|
Generator, KeyPair, Prefix, Random,
|
||||||
|
};
|
||||||
use rustc_hex::{FromHex, FromHexError};
|
use rustc_hex::{FromHex, FromHexError};
|
||||||
|
|
||||||
const USAGE: &'static str = r#"
|
const USAGE: &'static str = r#"
|
||||||
@@ -65,387 +67,428 @@ Commands:
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct Args {
|
struct Args {
|
||||||
cmd_info: bool,
|
cmd_info: bool,
|
||||||
cmd_generate: bool,
|
cmd_generate: bool,
|
||||||
cmd_random: bool,
|
cmd_random: bool,
|
||||||
cmd_prefix: bool,
|
cmd_prefix: bool,
|
||||||
cmd_sign: bool,
|
cmd_sign: bool,
|
||||||
cmd_verify: bool,
|
cmd_verify: bool,
|
||||||
cmd_public: bool,
|
cmd_public: bool,
|
||||||
cmd_address: bool,
|
cmd_address: bool,
|
||||||
cmd_recover: bool,
|
cmd_recover: bool,
|
||||||
arg_prefix: String,
|
arg_prefix: String,
|
||||||
arg_secret: String,
|
arg_secret: String,
|
||||||
arg_secret_or_phrase: String,
|
arg_secret_or_phrase: String,
|
||||||
arg_known_phrase: String,
|
arg_known_phrase: String,
|
||||||
arg_message: String,
|
arg_message: String,
|
||||||
arg_public: String,
|
arg_public: String,
|
||||||
arg_address: String,
|
arg_address: String,
|
||||||
arg_signature: String,
|
arg_signature: String,
|
||||||
flag_secret: bool,
|
flag_secret: bool,
|
||||||
flag_public: bool,
|
flag_public: bool,
|
||||||
flag_address: bool,
|
flag_address: bool,
|
||||||
flag_brain: bool,
|
flag_brain: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Error {
|
enum Error {
|
||||||
Ethkey(EthkeyError),
|
Ethkey(EthkeyError),
|
||||||
FromHex(FromHexError),
|
FromHex(FromHexError),
|
||||||
ParseInt(ParseIntError),
|
ParseInt(ParseIntError),
|
||||||
Docopt(docopt::Error),
|
Docopt(docopt::Error),
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<EthkeyError> for Error {
|
impl From<EthkeyError> for Error {
|
||||||
fn from(err: EthkeyError) -> Self {
|
fn from(err: EthkeyError) -> Self {
|
||||||
Error::Ethkey(err)
|
Error::Ethkey(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<FromHexError> for Error {
|
impl From<FromHexError> for Error {
|
||||||
fn from(err: FromHexError) -> Self {
|
fn from(err: FromHexError) -> Self {
|
||||||
Error::FromHex(err)
|
Error::FromHex(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ParseIntError> for Error {
|
impl From<ParseIntError> for Error {
|
||||||
fn from(err: ParseIntError) -> Self {
|
fn from(err: ParseIntError) -> Self {
|
||||||
Error::ParseInt(err)
|
Error::ParseInt(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<docopt::Error> for Error {
|
impl From<docopt::Error> for Error {
|
||||||
fn from(err: docopt::Error) -> Self {
|
fn from(err: docopt::Error) -> Self {
|
||||||
Error::Docopt(err)
|
Error::Docopt(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<io::Error> for Error {
|
impl From<io::Error> for Error {
|
||||||
fn from(err: io::Error) -> Self {
|
fn from(err: io::Error) -> Self {
|
||||||
Error::Io(err)
|
Error::Io(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
match *self {
|
match *self {
|
||||||
Error::Ethkey(ref e) => write!(f, "{}", e),
|
Error::Ethkey(ref e) => write!(f, "{}", e),
|
||||||
Error::FromHex(ref e) => write!(f, "{}", e),
|
Error::FromHex(ref e) => write!(f, "{}", e),
|
||||||
Error::ParseInt(ref e) => write!(f, "{}", e),
|
Error::ParseInt(ref e) => write!(f, "{}", e),
|
||||||
Error::Docopt(ref e) => write!(f, "{}", e),
|
Error::Docopt(ref e) => write!(f, "{}", e),
|
||||||
Error::Io(ref e) => write!(f, "{}", e),
|
Error::Io(ref e) => write!(f, "{}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DisplayMode {
|
enum DisplayMode {
|
||||||
KeyPair,
|
KeyPair,
|
||||||
Secret,
|
Secret,
|
||||||
Public,
|
Public,
|
||||||
Address,
|
Address,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplayMode {
|
impl DisplayMode {
|
||||||
fn new(args: &Args) -> Self {
|
fn new(args: &Args) -> Self {
|
||||||
if args.flag_secret {
|
if args.flag_secret {
|
||||||
DisplayMode::Secret
|
DisplayMode::Secret
|
||||||
} else if args.flag_public {
|
} else if args.flag_public {
|
||||||
DisplayMode::Public
|
DisplayMode::Public
|
||||||
} else if args.flag_address {
|
} else if args.flag_address {
|
||||||
DisplayMode::Address
|
DisplayMode::Address
|
||||||
} else {
|
} else {
|
||||||
DisplayMode::KeyPair
|
DisplayMode::KeyPair
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
panic_hook::set_abort();
|
panic_hook::set_abort();
|
||||||
env_logger::try_init().expect("Logger initialized only once.");
|
env_logger::try_init().expect("Logger initialized only once.");
|
||||||
|
|
||||||
match execute(env::args()) {
|
match execute(env::args()) {
|
||||||
Ok(ok) => println!("{}", ok),
|
Ok(ok) => println!("{}", ok),
|
||||||
Err(Error::Docopt(ref e)) => e.exit(),
|
Err(Error::Docopt(ref e)) => e.exit(),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("{}", err);
|
eprintln!("{}", err);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display(result: (KeyPair, Option<String>), mode: DisplayMode) -> String {
|
fn display(result: (KeyPair, Option<String>), mode: DisplayMode) -> String {
|
||||||
let keypair = result.0;
|
let keypair = result.0;
|
||||||
match mode {
|
match mode {
|
||||||
DisplayMode::KeyPair => match result.1 {
|
DisplayMode::KeyPair => match result.1 {
|
||||||
Some(extra_data) => format!("{}\n{}", extra_data, keypair),
|
Some(extra_data) => format!("{}\n{}", extra_data, keypair),
|
||||||
None => format!("{}", keypair)
|
None => format!("{}", keypair),
|
||||||
},
|
},
|
||||||
DisplayMode::Secret => format!("{:x}", keypair.secret()),
|
DisplayMode::Secret => format!("{:x}", keypair.secret()),
|
||||||
DisplayMode::Public => format!("{:x}", keypair.public()),
|
DisplayMode::Public => format!("{:x}", keypair.public()),
|
||||||
DisplayMode::Address => format!("{:x}", keypair.address()),
|
DisplayMode::Address => format!("{:x}", keypair.address()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item=S>, S: AsRef<str> {
|
fn execute<S, I>(command: I) -> Result<String, Error>
|
||||||
let args: Args = Docopt::new(USAGE)
|
where
|
||||||
.and_then(|d| d.argv(command).deserialize())?;
|
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 {
|
return if args.cmd_info {
|
||||||
let display_mode = DisplayMode::new(&args);
|
let display_mode = DisplayMode::new(&args);
|
||||||
|
|
||||||
let result = if args.flag_brain {
|
let result = if args.flag_brain {
|
||||||
let phrase = args.arg_secret_or_phrase;
|
let phrase = args.arg_secret_or_phrase;
|
||||||
let phrase_info = validate_phrase(&phrase);
|
let phrase_info = validate_phrase(&phrase);
|
||||||
let keypair = Brain::new(phrase).generate().expect("Brain wallet generator is infallible; qed");
|
let keypair = Brain::new(phrase)
|
||||||
(keypair, Some(phrase_info))
|
.generate()
|
||||||
} else {
|
.expect("Brain wallet generator is infallible; qed");
|
||||||
let secret = args.arg_secret_or_phrase.parse().map_err(|_| EthkeyError::InvalidSecret)?;
|
(keypair, Some(phrase_info))
|
||||||
(KeyPair::from_secret(secret)?, None)
|
} else {
|
||||||
};
|
let secret = args
|
||||||
Ok(display(result, display_mode))
|
.arg_secret_or_phrase
|
||||||
} else if args.cmd_generate {
|
.parse()
|
||||||
let display_mode = DisplayMode::new(&args);
|
.map_err(|_| EthkeyError::InvalidSecret)?;
|
||||||
let result = if args.cmd_random {
|
(KeyPair::from_secret(secret)?, None)
|
||||||
if args.flag_brain {
|
};
|
||||||
let mut brain = BrainPrefix::new(vec![0], usize::max_value(), BRAIN_WORDS);
|
Ok(display(result, display_mode))
|
||||||
let keypair = brain.generate()?;
|
} else if args.cmd_generate {
|
||||||
let phrase = format!("recovery phrase: {}", brain.phrase());
|
let display_mode = DisplayMode::new(&args);
|
||||||
(keypair, Some(phrase))
|
let result = if args.cmd_random {
|
||||||
} else {
|
if args.flag_brain {
|
||||||
(Random.generate()?, None)
|
let mut brain = BrainPrefix::new(vec![0], usize::max_value(), BRAIN_WORDS);
|
||||||
}
|
let keypair = brain.generate()?;
|
||||||
} else if args.cmd_prefix {
|
let phrase = format!("recovery phrase: {}", brain.phrase());
|
||||||
let prefix = args.arg_prefix.from_hex()?;
|
(keypair, Some(phrase))
|
||||||
let brain = args.flag_brain;
|
} else {
|
||||||
in_threads(move || {
|
(Random.generate()?, None)
|
||||||
let iterations = 1024;
|
}
|
||||||
let prefix = prefix.clone();
|
} else if args.cmd_prefix {
|
||||||
move || {
|
let prefix = args.arg_prefix.from_hex()?;
|
||||||
let prefix = prefix.clone();
|
let brain = args.flag_brain;
|
||||||
let res = if brain {
|
in_threads(move || {
|
||||||
let mut brain = BrainPrefix::new(prefix, iterations, BRAIN_WORDS);
|
let iterations = 1024;
|
||||||
let result = brain.generate();
|
let prefix = prefix.clone();
|
||||||
let phrase = format!("recovery phrase: {}", brain.phrase());
|
move || {
|
||||||
result.map(|keypair| (keypair, Some(phrase)))
|
let prefix = prefix.clone();
|
||||||
} else {
|
let res = if brain {
|
||||||
let result = Prefix::new(prefix, iterations).generate();
|
let mut brain = BrainPrefix::new(prefix, iterations, BRAIN_WORDS);
|
||||||
result.map(|res| (res, None))
|
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))
|
Ok(res.map(Some).unwrap_or(None))
|
||||||
}
|
}
|
||||||
})?
|
})?
|
||||||
} else {
|
} else {
|
||||||
return Ok(format!("{}", USAGE))
|
return Ok(format!("{}", USAGE));
|
||||||
};
|
};
|
||||||
Ok(display(result, display_mode))
|
Ok(display(result, display_mode))
|
||||||
} else if args.cmd_sign {
|
} else if args.cmd_sign {
|
||||||
let secret = args.arg_secret.parse().map_err(|_| EthkeyError::InvalidSecret)?;
|
let secret = args
|
||||||
let message = args.arg_message.parse().map_err(|_| EthkeyError::InvalidMessage)?;
|
.arg_secret
|
||||||
let signature = sign(&secret, &message)?;
|
.parse()
|
||||||
Ok(format!("{}", signature))
|
.map_err(|_| EthkeyError::InvalidSecret)?;
|
||||||
} else if args.cmd_verify {
|
let message = args
|
||||||
let signature = args.arg_signature.parse().map_err(|_| EthkeyError::InvalidSignature)?;
|
.arg_message
|
||||||
let message = args.arg_message.parse().map_err(|_| EthkeyError::InvalidMessage)?;
|
.parse()
|
||||||
let ok = if args.cmd_public {
|
.map_err(|_| EthkeyError::InvalidMessage)?;
|
||||||
let public = args.arg_public.parse().map_err(|_| EthkeyError::InvalidPublic)?;
|
let signature = sign(&secret, &message)?;
|
||||||
verify_public(&public, &signature, &message)?
|
Ok(format!("{}", signature))
|
||||||
} else if args.cmd_address {
|
} else if args.cmd_verify {
|
||||||
let address = args.arg_address.parse().map_err(|_| EthkeyError::InvalidAddress)?;
|
let signature = args
|
||||||
verify_address(&address, &signature, &message)?
|
.arg_signature
|
||||||
} else {
|
.parse()
|
||||||
return Ok(format!("{}", USAGE))
|
.map_err(|_| EthkeyError::InvalidSignature)?;
|
||||||
};
|
let message = args
|
||||||
Ok(format!("{}", ok))
|
.arg_message
|
||||||
} else if args.cmd_recover {
|
.parse()
|
||||||
let display_mode = DisplayMode::new(&args);
|
.map_err(|_| EthkeyError::InvalidMessage)?;
|
||||||
let known_phrase = args.arg_known_phrase;
|
let ok = if args.cmd_public {
|
||||||
let address = args.arg_address.parse().map_err(|_| EthkeyError::InvalidAddress)?;
|
let public = args
|
||||||
let (phrase, keypair) = in_threads(move || {
|
.arg_public
|
||||||
let mut it = brain_recover::PhrasesIterator::from_known_phrase(&known_phrase, BRAIN_WORDS);
|
.parse()
|
||||||
move || {
|
.map_err(|_| EthkeyError::InvalidPublic)?;
|
||||||
let mut i = 0;
|
verify_public(&public, &signature, &message)?
|
||||||
while let Some(phrase) = it.next() {
|
} else if args.cmd_address {
|
||||||
i += 1;
|
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();
|
let keypair = Brain::new(phrase.clone()).generate().unwrap();
|
||||||
if keypair.address() == address {
|
if keypair.address() == address {
|
||||||
return Ok(Some((phrase, keypair)))
|
return Ok(Some((phrase, keypair)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if i >= 1024 {
|
if i >= 1024 {
|
||||||
return Ok(None)
|
return Ok(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(EthkeyError::Custom("Couldn't find any results.".into()))
|
Err(EthkeyError::Custom("Couldn't find any results.".into()))
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
Ok(display((keypair, Some(phrase)), display_mode))
|
Ok(display((keypair, Some(phrase)), display_mode))
|
||||||
} else {
|
} else {
|
||||||
Ok(format!("{}", USAGE))
|
Ok(format!("{}", USAGE))
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const BRAIN_WORDS: usize = 12;
|
const BRAIN_WORDS: usize = 12;
|
||||||
|
|
||||||
fn validate_phrase(phrase: &str) -> String {
|
fn validate_phrase(phrase: &str) -> String {
|
||||||
match Brain::validate_phrase(phrase, BRAIN_WORDS) {
|
match Brain::validate_phrase(phrase, BRAIN_WORDS) {
|
||||||
Ok(()) => format!("The recovery phrase looks correct.\n"),
|
Ok(()) => format!("The recovery phrase looks correct.\n"),
|
||||||
Err(err) => format!("The recover phrase was not generated by Parity: {}", err)
|
Err(err) => format!("The recover phrase was not generated by Parity: {}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn in_threads<F, X, O>(prepare: F) -> Result<O, EthkeyError> where
|
fn in_threads<F, X, O>(prepare: F) -> Result<O, EthkeyError>
|
||||||
O: Send + 'static,
|
where
|
||||||
X: Send + 'static,
|
O: Send + 'static,
|
||||||
F: Fn() -> X,
|
X: Send + 'static,
|
||||||
X: FnMut() -> Result<Option<O>, EthkeyError>,
|
F: Fn() -> X,
|
||||||
|
X: FnMut() -> Result<Option<O>, EthkeyError>,
|
||||||
{
|
{
|
||||||
let pool = threadpool::Builder::new().build();
|
let pool = threadpool::Builder::new().build();
|
||||||
|
|
||||||
let (tx, rx) = sync::mpsc::sync_channel(1);
|
let (tx, rx) = sync::mpsc::sync_channel(1);
|
||||||
let is_done = sync::Arc::new(sync::atomic::AtomicBool::default());
|
let is_done = sync::Arc::new(sync::atomic::AtomicBool::default());
|
||||||
|
|
||||||
for _ in 0..pool.max_count() {
|
for _ in 0..pool.max_count() {
|
||||||
let is_done = is_done.clone();
|
let is_done = is_done.clone();
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
let mut task = prepare();
|
let mut task = prepare();
|
||||||
pool.execute(move || {
|
pool.execute(move || {
|
||||||
loop {
|
loop {
|
||||||
if is_done.load(sync::atomic::Ordering::SeqCst) {
|
if is_done.load(sync::atomic::Ordering::SeqCst) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = match task() {
|
let res = match task() {
|
||||||
Ok(None) => continue,
|
Ok(None) => continue,
|
||||||
Ok(Some(v)) => Ok(v),
|
Ok(Some(v)) => Ok(v),
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
};
|
};
|
||||||
|
|
||||||
// We are interested only in the first response.
|
// We are interested only in the first response.
|
||||||
let _ = tx.send(res);
|
let _ = tx.send(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(solution) = rx.recv() {
|
if let Ok(solution) = rx.recv() {
|
||||||
is_done.store(true, sync::atomic::Ordering::SeqCst);
|
is_done.store(true, sync::atomic::Ordering::SeqCst);
|
||||||
return solution;
|
return solution;
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(EthkeyError::Custom("No results found.".into()))
|
Err(EthkeyError::Custom("No results found.".into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::execute;
|
use super::execute;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn info() {
|
fn info() {
|
||||||
let command = vec!["ethkey", "info", "17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55"]
|
let command = vec![
|
||||||
.into_iter()
|
"ethkey",
|
||||||
.map(Into::into)
|
"info",
|
||||||
.collect::<Vec<String>>();
|
"17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55",
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
let expected =
|
let expected =
|
||||||
"secret: 17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55
|
"secret: 17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55
|
||||||
public: 689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124
|
public: 689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124
|
||||||
address: 26d1ec50b4e62c1d1a40d16e7cacc6a6580757d5".to_owned();
|
address: 26d1ec50b4e62c1d1a40d16e7cacc6a6580757d5".to_owned();
|
||||||
assert_eq!(execute(command).unwrap(), expected);
|
assert_eq!(execute(command).unwrap(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn brain() {
|
fn brain() {
|
||||||
let command = vec!["ethkey", "info", "--brain", "this is sparta"]
|
let command = vec!["ethkey", "info", "--brain", "this is sparta"]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
let expected =
|
let expected =
|
||||||
"The recover phrase was not generated by Parity: The word 'this' does not come from the dictionary.
|
"The recover phrase was not generated by Parity: The word 'this' does not come from the dictionary.
|
||||||
|
|
||||||
secret: aa22b54c0cb43ee30a014afe5ef3664b1cde299feabca46cd3167a85a57c39f2
|
secret: aa22b54c0cb43ee30a014afe5ef3664b1cde299feabca46cd3167a85a57c39f2
|
||||||
public: c4c5398da6843632c123f543d714d2d2277716c11ff612b2a2f23c6bda4d6f0327c31cd58c55a9572c3cc141dade0c32747a13b7ef34c241b26c84adbb28fcf4
|
public: c4c5398da6843632c123f543d714d2d2277716c11ff612b2a2f23c6bda4d6f0327c31cd58c55a9572c3cc141dade0c32747a13b7ef34c241b26c84adbb28fcf4
|
||||||
address: 006e27b6a72e1f34c626762f3c4761547aff1421".to_owned();
|
address: 006e27b6a72e1f34c626762f3c4761547aff1421".to_owned();
|
||||||
assert_eq!(execute(command).unwrap(), expected);
|
assert_eq!(execute(command).unwrap(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn secret() {
|
fn secret() {
|
||||||
let command = vec!["ethkey", "info", "--brain", "this is sparta", "--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()
|
.into_iter()
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
let expected = "aa22b54c0cb43ee30a014afe5ef3664b1cde299feabca46cd3167a85a57c39f2".to_owned();
|
let expected = "true".to_owned();
|
||||||
assert_eq!(execute(command).unwrap(), expected);
|
assert_eq!(execute(command).unwrap(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn public() {
|
fn verify_valid_address() {
|
||||||
let command = vec!["ethkey", "info", "--brain", "this is sparta", "--public"]
|
let command = vec!["ethkey", "verify", "address", "26d1ec50b4e62c1d1a40d16e7cacc6a6580757d5", "c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200", "bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec987"]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
let expected = "c4c5398da6843632c123f543d714d2d2277716c11ff612b2a2f23c6bda4d6f0327c31cd58c55a9572c3cc141dade0c32747a13b7ef34c241b26c84adbb28fcf4".to_owned();
|
let expected = "true".to_owned();
|
||||||
assert_eq!(execute(command).unwrap(), expected);
|
assert_eq!(execute(command).unwrap(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn address() {
|
fn verify_invalid() {
|
||||||
let command = vec!["ethkey", "info", "-b", "this is sparta", "--address"]
|
let command = vec!["ethkey", "verify", "public", "689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124", "c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200", "bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec986"]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
let expected = "006e27b6a72e1f34c626762f3c4761547aff1421".to_owned();
|
let expected = "false".to_owned();
|
||||||
assert_eq!(execute(command).unwrap(), expected);
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,73 +1,74 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::{Generator, KeyPair, Secret};
|
||||||
use keccak::Keccak256;
|
use keccak::Keccak256;
|
||||||
use super::{KeyPair, Generator, Secret};
|
|
||||||
use parity_wordlist;
|
use parity_wordlist;
|
||||||
|
|
||||||
/// Simple brainwallet.
|
/// Simple brainwallet.
|
||||||
pub struct Brain(String);
|
pub struct Brain(String);
|
||||||
|
|
||||||
impl Brain {
|
impl Brain {
|
||||||
pub fn new(s: String) -> Self {
|
pub fn new(s: String) -> Self {
|
||||||
Brain(s)
|
Brain(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_phrase(phrase: &str, expected_words: usize) -> Result<(), ::WordlistError> {
|
pub fn validate_phrase(phrase: &str, expected_words: usize) -> Result<(), ::WordlistError> {
|
||||||
parity_wordlist::validate_phrase(phrase, expected_words)
|
parity_wordlist::validate_phrase(phrase, expected_words)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Generator for Brain {
|
impl Generator for Brain {
|
||||||
type Error = ::Void;
|
type Error = ::Void;
|
||||||
|
|
||||||
fn generate(&mut self) -> Result<KeyPair, Self::Error> {
|
fn generate(&mut self) -> Result<KeyPair, Self::Error> {
|
||||||
let seed = self.0.clone();
|
let seed = self.0.clone();
|
||||||
let mut secret = seed.into_bytes().keccak256();
|
let mut secret = seed.into_bytes().keccak256();
|
||||||
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
loop {
|
loop {
|
||||||
secret = secret.keccak256();
|
secret = secret.keccak256();
|
||||||
|
|
||||||
match i > 16384 {
|
match i > 16384 {
|
||||||
false => i += 1,
|
false => i += 1,
|
||||||
true => {
|
true => {
|
||||||
if let Ok(pair) = Secret::from_unsafe_slice(&secret)
|
if let Ok(pair) =
|
||||||
.and_then(KeyPair::from_secret)
|
Secret::from_unsafe_slice(&secret).and_then(KeyPair::from_secret)
|
||||||
{
|
{
|
||||||
if pair.address()[0] == 0 {
|
if pair.address()[0] == 0 {
|
||||||
trace!("Testing: {}, got: {:?}", self.0, pair.address());
|
trace!("Testing: {}, got: {:?}", self.0, pair.address());
|
||||||
return Ok(pair)
|
return Ok(pair);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use {Brain, Generator};
|
use Brain;
|
||||||
|
use Generator;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_brain() {
|
fn test_brain() {
|
||||||
let words = "this is sparta!".to_owned();
|
let words = "this is sparta!".to_owned();
|
||||||
let first_keypair = Brain::new(words.clone()).generate().unwrap();
|
let first_keypair = Brain::new(words.clone()).generate().unwrap();
|
||||||
let second_keypair = Brain::new(words.clone()).generate().unwrap();
|
let second_keypair = Brain::new(words.clone()).generate().unwrap();
|
||||||
assert_eq!(first_keypair.secret(), second_keypair.secret());
|
assert_eq!(first_keypair.secret(), second_keypair.secret());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,70 +1,73 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use super::{Generator, KeyPair, Error, Brain};
|
use super::{Brain, Error, Generator, KeyPair};
|
||||||
use parity_wordlist as wordlist;
|
use parity_wordlist as wordlist;
|
||||||
|
|
||||||
/// Tries to find brain-seed keypair with address starting with given prefix.
|
/// Tries to find brain-seed keypair with address starting with given prefix.
|
||||||
pub struct BrainPrefix {
|
pub struct BrainPrefix {
|
||||||
prefix: Vec<u8>,
|
prefix: Vec<u8>,
|
||||||
iterations: usize,
|
iterations: usize,
|
||||||
no_of_words: usize,
|
no_of_words: usize,
|
||||||
last_phrase: String,
|
last_phrase: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BrainPrefix {
|
impl BrainPrefix {
|
||||||
pub fn new(prefix: Vec<u8>, iterations: usize, no_of_words: usize) -> Self {
|
pub fn new(prefix: Vec<u8>, iterations: usize, no_of_words: usize) -> Self {
|
||||||
BrainPrefix {
|
BrainPrefix {
|
||||||
prefix,
|
prefix,
|
||||||
iterations,
|
iterations,
|
||||||
no_of_words,
|
no_of_words,
|
||||||
last_phrase: String::new(),
|
last_phrase: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn phrase(&self) -> &str {
|
pub fn phrase(&self) -> &str {
|
||||||
&self.last_phrase
|
&self.last_phrase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Generator for BrainPrefix {
|
impl Generator for BrainPrefix {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn generate(&mut self) -> Result<KeyPair, Error> {
|
fn generate(&mut self) -> Result<KeyPair, Error> {
|
||||||
for _ in 0..self.iterations {
|
for _ in 0..self.iterations {
|
||||||
let phrase = wordlist::random_phrase(self.no_of_words);
|
let phrase = wordlist::random_phrase(self.no_of_words);
|
||||||
let keypair = Brain::new(phrase.clone()).generate().unwrap();
|
let keypair = Brain::new(phrase.clone()).generate().unwrap();
|
||||||
if keypair.address().starts_with(&self.prefix) {
|
if keypair.address().starts_with(&self.prefix) {
|
||||||
self.last_phrase = phrase;
|
self.last_phrase = phrase;
|
||||||
return Ok(keypair)
|
return Ok(keypair);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(Error::Custom("Could not find keypair".into()))
|
Err(Error::Custom("Could not find keypair".into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use {Generator, BrainPrefix};
|
use BrainPrefix;
|
||||||
|
use Generator;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn prefix_generator() {
|
fn prefix_generator() {
|
||||||
let prefix = vec![0x00u8];
|
let prefix = vec![0x00u8];
|
||||||
let keypair = BrainPrefix::new(prefix.clone(), usize::max_value(), 12).generate().unwrap();
|
let keypair = BrainPrefix::new(prefix.clone(), usize::max_value(), 12)
|
||||||
assert!(keypair.address().starts_with(&prefix));
|
.generate()
|
||||||
}
|
.unwrap();
|
||||||
|
assert!(keypair.address().starts_with(&prefix));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
@@ -26,148 +26,153 @@ use super::{Address, Brain, Generator};
|
|||||||
///
|
///
|
||||||
/// Returns `None` if phrase couldn't be found.
|
/// Returns `None` if phrase couldn't be found.
|
||||||
pub fn brain_recover(
|
pub fn brain_recover(
|
||||||
address: &Address,
|
address: &Address,
|
||||||
known_phrase: &str,
|
known_phrase: &str,
|
||||||
expected_words: usize,
|
expected_words: usize,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
let it = PhrasesIterator::from_known_phrase(known_phrase, expected_words);
|
let it = PhrasesIterator::from_known_phrase(known_phrase, expected_words);
|
||||||
for phrase in it {
|
for phrase in it {
|
||||||
let keypair = Brain::new(phrase.clone()).generate().expect("Brain wallets are infallible; qed");
|
let keypair = Brain::new(phrase.clone())
|
||||||
trace!("Testing: {}, got: {:?}", phrase, keypair.address());
|
.generate()
|
||||||
if &keypair.address() == address {
|
.expect("Brain wallets are infallible; qed");
|
||||||
return Some(phrase);
|
trace!("Testing: {}, got: {:?}", phrase, keypair.address());
|
||||||
}
|
if &keypair.address() == address {
|
||||||
}
|
return Some(phrase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_substitutions(word: &str) -> Vec<&'static str> {
|
fn generate_substitutions(word: &str) -> Vec<&'static str> {
|
||||||
let mut words = parity_wordlist::WORDS.iter().cloned()
|
let mut words = parity_wordlist::WORDS
|
||||||
.map(|w| (edit_distance(w, word), w))
|
.iter()
|
||||||
.collect::<Vec<_>>();
|
.cloned()
|
||||||
words.sort_by(|a, b| a.0.cmp(&b.0));
|
.map(|w| (edit_distance(w, word), w))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
words.sort_by(|a, b| a.0.cmp(&b.0));
|
||||||
|
|
||||||
words.into_iter()
|
words.into_iter().map(|pair| pair.1).collect()
|
||||||
.map(|pair| pair.1)
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterator over possible
|
/// Iterator over possible
|
||||||
pub struct PhrasesIterator {
|
pub struct PhrasesIterator {
|
||||||
words: Vec<Vec<&'static str>>,
|
words: Vec<Vec<&'static str>>,
|
||||||
combinations: u64,
|
combinations: u64,
|
||||||
indexes: Vec<usize>,
|
indexes: Vec<usize>,
|
||||||
has_next: bool,
|
has_next: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhrasesIterator {
|
impl PhrasesIterator {
|
||||||
pub fn from_known_phrase(known_phrase: &str, expected_words: usize) -> Self {
|
pub fn from_known_phrase(known_phrase: &str, expected_words: usize) -> Self {
|
||||||
let known_words = parity_wordlist::WORDS.iter().cloned().collect::<HashSet<_>>();
|
let known_words = parity_wordlist::WORDS
|
||||||
let mut words = known_phrase.split(' ')
|
.iter()
|
||||||
.map(|word| match known_words.get(word) {
|
.cloned()
|
||||||
None => {
|
.collect::<HashSet<_>>();
|
||||||
info!("Invalid word '{}', looking for potential substitutions.", word);
|
let mut words = known_phrase
|
||||||
let substitutions = generate_substitutions(word);
|
.split(' ')
|
||||||
info!("Closest words: {:?}", &substitutions[..10]);
|
.map(|word| match known_words.get(word) {
|
||||||
substitutions
|
None => {
|
||||||
},
|
info!(
|
||||||
Some(word) => vec![*word],
|
"Invalid word '{}', looking for potential substitutions.",
|
||||||
})
|
word
|
||||||
.collect::<Vec<_>>();
|
);
|
||||||
|
let substitutions = generate_substitutions(word);
|
||||||
|
info!("Closest words: {:?}", &substitutions[..10]);
|
||||||
|
substitutions
|
||||||
|
}
|
||||||
|
Some(word) => vec![*word],
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// add missing words
|
// add missing words
|
||||||
if words.len() < expected_words {
|
if words.len() < expected_words {
|
||||||
let to_add = expected_words - words.len();
|
let to_add = expected_words - words.len();
|
||||||
info!("Number of words is insuficcient adding {} more.", to_add);
|
info!("Number of words is insuficcient adding {} more.", to_add);
|
||||||
for _ in 0..to_add {
|
for _ in 0..to_add {
|
||||||
words.push(parity_wordlist::WORDS.iter().cloned().collect());
|
words.push(parity_wordlist::WORDS.iter().cloned().collect());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// start searching
|
// start searching
|
||||||
PhrasesIterator::new(words)
|
PhrasesIterator::new(words)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(words: Vec<Vec<&'static str>>) -> Self {
|
pub fn new(words: Vec<Vec<&'static str>>) -> Self {
|
||||||
let combinations = words.iter().fold(1u64, |acc, x| acc * x.len() as u64);
|
let combinations = words.iter().fold(1u64, |acc, x| acc * x.len() as u64);
|
||||||
let indexes = words.iter().map(|_| 0).collect();
|
let indexes = words.iter().map(|_| 0).collect();
|
||||||
info!("Starting to test {} possible combinations.", combinations);
|
info!("Starting to test {} possible combinations.", combinations);
|
||||||
|
|
||||||
PhrasesIterator {
|
PhrasesIterator {
|
||||||
words,
|
words,
|
||||||
combinations,
|
combinations,
|
||||||
indexes,
|
indexes,
|
||||||
has_next: combinations > 0,
|
has_next: combinations > 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn combinations(&self) -> u64 {
|
pub fn combinations(&self) -> u64 {
|
||||||
self.combinations
|
self.combinations
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current(&self) -> String {
|
fn current(&self) -> String {
|
||||||
let mut s = self.words[0][self.indexes[0]].to_owned();
|
let mut s = self.words[0][self.indexes[0]].to_owned();
|
||||||
for i in 1..self.indexes.len() {
|
for i in 1..self.indexes.len() {
|
||||||
s.push(' ');
|
s.push(' ');
|
||||||
s.push_str(self.words[i][self.indexes[i]]);
|
s.push_str(self.words[i][self.indexes[i]]);
|
||||||
}
|
}
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_index(&mut self) -> bool {
|
fn next_index(&mut self) -> bool {
|
||||||
let mut pos = self.indexes.len();
|
let mut pos = self.indexes.len();
|
||||||
while pos > 0 {
|
while pos > 0 {
|
||||||
pos -= 1;
|
pos -= 1;
|
||||||
self.indexes[pos] += 1;
|
self.indexes[pos] += 1;
|
||||||
if self.indexes[pos] >= self.words[pos].len() {
|
if self.indexes[pos] >= self.words[pos].len() {
|
||||||
self.indexes[pos] = 0;
|
self.indexes[pos] = 0;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for PhrasesIterator {
|
impl Iterator for PhrasesIterator {
|
||||||
type Item = String;
|
type Item = String;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<String> {
|
fn next(&mut self) -> Option<String> {
|
||||||
if !self.has_next {
|
if !self.has_next {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let phrase = self.current();
|
let phrase = self.current();
|
||||||
self.has_next = self.next_index();
|
self.has_next = self.next_index();
|
||||||
Some(phrase)
|
Some(phrase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::PhrasesIterator;
|
use super::PhrasesIterator;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_generate_possible_combinations() {
|
fn should_generate_possible_combinations() {
|
||||||
let mut it = PhrasesIterator::new(vec![
|
let mut it =
|
||||||
vec!["1", "2", "3"],
|
PhrasesIterator::new(vec![vec!["1", "2", "3"], vec!["test"], vec!["a", "b", "c"]]);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,189 +1,202 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#![allow(deprecated)]
|
||||||
|
|
||||||
|
use parity_crypto::error::SymmError;
|
||||||
use secp256k1;
|
use secp256k1;
|
||||||
use std::io;
|
use std::io;
|
||||||
use parity_crypto::error::SymmError;
|
|
||||||
|
|
||||||
quick_error! {
|
quick_error! {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Secp(e: secp256k1::Error) {
|
Secp(e: secp256k1::Error) {
|
||||||
display("secp256k1 error: {}", e)
|
display("secp256k1 error: {}", e)
|
||||||
cause(e)
|
cause(e)
|
||||||
from()
|
from()
|
||||||
}
|
}
|
||||||
Io(e: io::Error) {
|
Io(e: io::Error) {
|
||||||
display("i/o error: {}", e)
|
display("i/o error: {}", e)
|
||||||
cause(e)
|
cause(e)
|
||||||
from()
|
from()
|
||||||
}
|
}
|
||||||
InvalidMessage {
|
InvalidMessage {
|
||||||
display("invalid message")
|
display("invalid message")
|
||||||
}
|
}
|
||||||
Symm(e: SymmError) {
|
Symm(e: SymmError) {
|
||||||
cause(e)
|
cause(e)
|
||||||
from()
|
from()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ECDH functions
|
/// ECDH functions
|
||||||
pub mod ecdh {
|
pub mod ecdh {
|
||||||
use secp256k1::{self, ecdh, key};
|
use super::Error;
|
||||||
use super::Error;
|
use secp256k1::{self, ecdh, key};
|
||||||
use {Secret, Public, SECP256K1};
|
use Public;
|
||||||
|
use Secret;
|
||||||
|
use SECP256K1;
|
||||||
|
|
||||||
/// Agree on a shared secret
|
/// Agree on a shared secret
|
||||||
pub fn agree(secret: &Secret, public: &Public) -> Result<Secret, Error> {
|
pub fn agree(secret: &Secret, public: &Public) -> Result<Secret, Error> {
|
||||||
let context = &SECP256K1;
|
let context = &SECP256K1;
|
||||||
let pdata = {
|
let pdata = {
|
||||||
let mut temp = [4u8; 65];
|
let mut temp = [4u8; 65];
|
||||||
(&mut temp[1..65]).copy_from_slice(&public[0..64]);
|
(&mut temp[1..65]).copy_from_slice(&public[0..64]);
|
||||||
temp
|
temp
|
||||||
};
|
};
|
||||||
|
|
||||||
let publ = key::PublicKey::from_slice(context, &pdata)?;
|
let publ = key::PublicKey::from_slice(context, &pdata)?;
|
||||||
let sec = key::SecretKey::from_slice(context, &secret)?;
|
let sec = key::SecretKey::from_slice(context, &secret)?;
|
||||||
let shared = ecdh::SharedSecret::new_raw(context, &publ, &sec);
|
let shared = ecdh::SharedSecret::new_raw(context, &publ, &sec);
|
||||||
|
|
||||||
Secret::from_unsafe_slice(&shared[0..32])
|
Secret::from_unsafe_slice(&shared[0..32])
|
||||||
.map_err(|_| Error::Secp(secp256k1::Error::InvalidSecretKey))
|
.map_err(|_| Error::Secp(secp256k1::Error::InvalidSecretKey))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ECIES function
|
/// ECIES function
|
||||||
pub mod ecies {
|
pub mod ecies {
|
||||||
use parity_crypto::{aes, digest, hmac, is_equal};
|
use super::{ecdh, Error};
|
||||||
use ethereum_types::H128;
|
use ethereum_types::H128;
|
||||||
use super::{ecdh, Error};
|
use parity_crypto::{aes, digest, hmac, is_equal};
|
||||||
use {Random, Generator, Public, Secret};
|
use Generator;
|
||||||
|
use Public;
|
||||||
|
use Random;
|
||||||
|
use Secret;
|
||||||
|
|
||||||
/// Encrypt a message with a public key, writing an HMAC covering both
|
/// Encrypt a message with a public key, writing an HMAC covering both
|
||||||
/// the plaintext and authenticated data.
|
/// the plaintext and authenticated data.
|
||||||
///
|
///
|
||||||
/// Authenticated data may be empty.
|
/// Authenticated data may be empty.
|
||||||
pub fn encrypt(public: &Public, auth_data: &[u8], plain: &[u8]) -> Result<Vec<u8>, Error> {
|
pub fn encrypt(public: &Public, auth_data: &[u8], plain: &[u8]) -> Result<Vec<u8>, Error> {
|
||||||
let r = Random.generate()?;
|
let r = Random.generate()?;
|
||||||
let z = ecdh::agree(r.secret(), public)?;
|
let z = ecdh::agree(r.secret(), public)?;
|
||||||
let mut key = [0u8; 32];
|
let mut key = [0u8; 32];
|
||||||
kdf(&z, &[0u8; 0], &mut key);
|
kdf(&z, &[0u8; 0], &mut key);
|
||||||
|
|
||||||
let ekey = &key[0..16];
|
let ekey = &key[0..16];
|
||||||
let mkey = hmac::SigKey::sha256(&digest::sha256(&key[16..32]));
|
let mkey = hmac::SigKey::sha256(&digest::sha256(&key[16..32]));
|
||||||
|
|
||||||
let mut msg = vec![0u8; 1 + 64 + 16 + plain.len() + 32];
|
let mut msg = vec![0u8; 1 + 64 + 16 + plain.len() + 32];
|
||||||
msg[0] = 0x04u8;
|
msg[0] = 0x04u8;
|
||||||
{
|
{
|
||||||
let msgd = &mut msg[1..];
|
let msgd = &mut msg[1..];
|
||||||
msgd[0..64].copy_from_slice(r.public());
|
msgd[0..64].copy_from_slice(r.public());
|
||||||
let iv = H128::random();
|
let iv = H128::random();
|
||||||
msgd[64..80].copy_from_slice(&iv);
|
msgd[64..80].copy_from_slice(&iv);
|
||||||
{
|
{
|
||||||
let cipher = &mut msgd[(64 + 16)..(64 + 16 + plain.len())];
|
let cipher = &mut msgd[(64 + 16)..(64 + 16 + plain.len())];
|
||||||
aes::encrypt_128_ctr(ekey, &iv, plain, cipher)?;
|
aes::encrypt_128_ctr(ekey, &iv, plain, cipher)?;
|
||||||
}
|
}
|
||||||
let mut hmac = hmac::Signer::with(&mkey);
|
let mut hmac = hmac::Signer::with(&mkey);
|
||||||
{
|
{
|
||||||
let cipher_iv = &msgd[64..(64 + 16 + plain.len())];
|
let cipher_iv = &msgd[64..(64 + 16 + plain.len())];
|
||||||
hmac.update(cipher_iv);
|
hmac.update(cipher_iv);
|
||||||
}
|
}
|
||||||
hmac.update(auth_data);
|
hmac.update(auth_data);
|
||||||
let sig = hmac.sign();
|
let sig = hmac.sign();
|
||||||
msgd[(64 + 16 + plain.len())..].copy_from_slice(&sig);
|
msgd[(64 + 16 + plain.len())..].copy_from_slice(&sig);
|
||||||
}
|
}
|
||||||
Ok(msg)
|
Ok(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decrypt a message with a secret key, checking HMAC for ciphertext
|
/// Decrypt a message with a secret key, checking HMAC for ciphertext
|
||||||
/// and authenticated data validity.
|
/// and authenticated data validity.
|
||||||
pub fn decrypt(secret: &Secret, auth_data: &[u8], encrypted: &[u8]) -> Result<Vec<u8>, Error> {
|
pub fn decrypt(secret: &Secret, auth_data: &[u8], encrypted: &[u8]) -> Result<Vec<u8>, Error> {
|
||||||
let meta_len = 1 + 64 + 16 + 32;
|
let meta_len = 1 + 64 + 16 + 32;
|
||||||
if encrypted.len() < meta_len || encrypted[0] < 2 || encrypted[0] > 4 {
|
if encrypted.len() < meta_len || encrypted[0] < 2 || encrypted[0] > 4 {
|
||||||
return Err(Error::InvalidMessage); //invalid message: publickey
|
return Err(Error::InvalidMessage); //invalid message: publickey
|
||||||
}
|
}
|
||||||
|
|
||||||
let e = &encrypted[1..];
|
let e = &encrypted[1..];
|
||||||
let p = Public::from_slice(&e[0..64]);
|
let p = Public::from_slice(&e[0..64]);
|
||||||
let z = ecdh::agree(secret, &p)?;
|
let z = ecdh::agree(secret, &p)?;
|
||||||
let mut key = [0u8; 32];
|
let mut key = [0u8; 32];
|
||||||
kdf(&z, &[0u8; 0], &mut key);
|
kdf(&z, &[0u8; 0], &mut key);
|
||||||
|
|
||||||
let ekey = &key[0..16];
|
let ekey = &key[0..16];
|
||||||
let mkey = hmac::SigKey::sha256(&digest::sha256(&key[16..32]));
|
let mkey = hmac::SigKey::sha256(&digest::sha256(&key[16..32]));
|
||||||
|
|
||||||
let clen = encrypted.len() - meta_len;
|
let clen = encrypted.len() - meta_len;
|
||||||
let cipher_with_iv = &e[64..(64+16+clen)];
|
let cipher_with_iv = &e[64..(64 + 16 + clen)];
|
||||||
let cipher_iv = &cipher_with_iv[0..16];
|
let cipher_iv = &cipher_with_iv[0..16];
|
||||||
let cipher_no_iv = &cipher_with_iv[16..];
|
let cipher_no_iv = &cipher_with_iv[16..];
|
||||||
let msg_mac = &e[(64+16+clen)..];
|
let msg_mac = &e[(64 + 16 + clen)..];
|
||||||
|
|
||||||
// Verify tag
|
// Verify tag
|
||||||
let mut hmac = hmac::Signer::with(&mkey);
|
let mut hmac = hmac::Signer::with(&mkey);
|
||||||
hmac.update(cipher_with_iv);
|
hmac.update(cipher_with_iv);
|
||||||
hmac.update(auth_data);
|
hmac.update(auth_data);
|
||||||
let mac = hmac.sign();
|
let mac = hmac.sign();
|
||||||
|
|
||||||
if !is_equal(&mac.as_ref()[..], msg_mac) {
|
if !is_equal(&mac.as_ref()[..], msg_mac) {
|
||||||
return Err(Error::InvalidMessage);
|
return Err(Error::InvalidMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut msg = vec![0u8; clen];
|
let mut msg = vec![0u8; clen];
|
||||||
aes::decrypt_128_ctr(ekey, cipher_iv, cipher_no_iv, &mut msg[..])?;
|
aes::decrypt_128_ctr(ekey, cipher_iv, cipher_no_iv, &mut msg[..])?;
|
||||||
Ok(msg)
|
Ok(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kdf(secret: &Secret, s1: &[u8], dest: &mut [u8]) {
|
fn kdf(secret: &Secret, s1: &[u8], dest: &mut [u8]) {
|
||||||
// SEC/ISO/Shoup specify counter size SHOULD be equivalent
|
// SEC/ISO/Shoup specify counter size SHOULD be equivalent
|
||||||
// to size of hash output, however, it also notes that
|
// to size of hash output, however, it also notes that
|
||||||
// the 4 bytes is okay. NIST specifies 4 bytes.
|
// the 4 bytes is okay. NIST specifies 4 bytes.
|
||||||
let mut ctr = 1u32;
|
let mut ctr = 1u32;
|
||||||
let mut written = 0usize;
|
let mut written = 0usize;
|
||||||
while written < dest.len() {
|
while written < dest.len() {
|
||||||
let mut hasher = digest::Hasher::sha256();
|
let mut hasher = digest::Hasher::sha256();
|
||||||
let ctrs = [(ctr >> 24) as u8, (ctr >> 16) as u8, (ctr >> 8) as u8, ctr as u8];
|
let ctrs = [
|
||||||
hasher.update(&ctrs);
|
(ctr >> 24) as u8,
|
||||||
hasher.update(secret);
|
(ctr >> 16) as u8,
|
||||||
hasher.update(s1);
|
(ctr >> 8) as u8,
|
||||||
let d = hasher.finish();
|
ctr as u8,
|
||||||
&mut dest[written..(written + 32)].copy_from_slice(&d);
|
];
|
||||||
written += 32;
|
hasher.update(&ctrs);
|
||||||
ctr += 1;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::ecies;
|
use super::ecies;
|
||||||
use {Random, Generator};
|
use Generator;
|
||||||
|
use Random;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ecies_shared() {
|
fn ecies_shared() {
|
||||||
let kp = Random.generate().unwrap();
|
let kp = Random.generate().unwrap();
|
||||||
let message = b"So many books, so little time";
|
let message = b"So many books, so little time";
|
||||||
|
|
||||||
let shared = b"shared";
|
let shared = b"shared";
|
||||||
let wrong_shared = b"incorrect";
|
let wrong_shared = b"incorrect";
|
||||||
let encrypted = ecies::encrypt(kp.public(), shared, message).unwrap();
|
let encrypted = ecies::encrypt(kp.public(), shared, message).unwrap();
|
||||||
assert!(encrypted[..] != message[..]);
|
assert!(encrypted[..] != message[..]);
|
||||||
assert_eq!(encrypted[0], 0x04);
|
assert_eq!(encrypted[0], 0x04);
|
||||||
|
|
||||||
assert!(ecies::decrypt(kp.secret(), wrong_shared, &encrypted).is_err());
|
assert!(ecies::decrypt(kp.secret(), wrong_shared, &encrypted).is_err());
|
||||||
let decrypted = ecies::decrypt(kp.secret(), shared, &encrypted).unwrap();
|
let decrypted = ecies::decrypt(kp.secret(), shared, &encrypted).unwrap();
|
||||||
assert_eq!(decrypted[..message.len()], message[..]);
|
assert_eq!(decrypted[..message.len()], message[..]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,81 +1,81 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::{fmt, error};
|
use std::{error, fmt};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
/// Crypto error
|
/// Crypto error
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Invalid secret key
|
/// Invalid secret key
|
||||||
InvalidSecret,
|
InvalidSecret,
|
||||||
/// Invalid public key
|
/// Invalid public key
|
||||||
InvalidPublic,
|
InvalidPublic,
|
||||||
/// Invalid address
|
/// Invalid address
|
||||||
InvalidAddress,
|
InvalidAddress,
|
||||||
/// Invalid EC signature
|
/// Invalid EC signature
|
||||||
InvalidSignature,
|
InvalidSignature,
|
||||||
/// Invalid AES message
|
/// Invalid AES message
|
||||||
InvalidMessage,
|
InvalidMessage,
|
||||||
/// IO Error
|
/// IO Error
|
||||||
Io(::std::io::Error),
|
Io(::std::io::Error),
|
||||||
/// Custom
|
/// Custom
|
||||||
Custom(String),
|
Custom(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let msg = match *self {
|
let msg = match *self {
|
||||||
Error::InvalidSecret => "Invalid secret".into(),
|
Error::InvalidSecret => "Invalid secret".into(),
|
||||||
Error::InvalidPublic => "Invalid public".into(),
|
Error::InvalidPublic => "Invalid public".into(),
|
||||||
Error::InvalidAddress => "Invalid address".into(),
|
Error::InvalidAddress => "Invalid address".into(),
|
||||||
Error::InvalidSignature => "Invalid EC signature".into(),
|
Error::InvalidSignature => "Invalid EC signature".into(),
|
||||||
Error::InvalidMessage => "Invalid AES message".into(),
|
Error::InvalidMessage => "Invalid AES message".into(),
|
||||||
Error::Io(ref err) => format!("I/O error: {}", err),
|
Error::Io(ref err) => format!("I/O error: {}", err),
|
||||||
Error::Custom(ref s) => s.clone(),
|
Error::Custom(ref s) => s.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
f.write_fmt(format_args!("Crypto error ({})", msg))
|
f.write_fmt(format_args!("Crypto error ({})", msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for Error {
|
impl error::Error for Error {
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Crypto error"
|
"Crypto error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<String> for Error {
|
impl Into<String> for Error {
|
||||||
fn into(self) -> String {
|
fn into(self) -> String {
|
||||||
format!("{}", self)
|
format!("{}", self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<::secp256k1::Error> for Error {
|
impl From<::secp256k1::Error> for Error {
|
||||||
fn from(e: ::secp256k1::Error) -> Error {
|
fn from(e: ::secp256k1::Error) -> Error {
|
||||||
match e {
|
match e {
|
||||||
::secp256k1::Error::InvalidMessage => Error::InvalidMessage,
|
::secp256k1::Error::InvalidMessage => Error::InvalidMessage,
|
||||||
::secp256k1::Error::InvalidPublicKey => Error::InvalidPublic,
|
::secp256k1::Error::InvalidPublicKey => Error::InvalidPublic,
|
||||||
::secp256k1::Error::InvalidSecretKey => Error::InvalidSecret,
|
::secp256k1::Error::InvalidSecretKey => Error::InvalidSecret,
|
||||||
_ => Error::InvalidSignature,
|
_ => Error::InvalidSignature,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<::std::io::Error> for Error {
|
impl From<::std::io::Error> for Error {
|
||||||
fn from(err: ::std::io::Error) -> Error {
|
fn from(err: ::std::io::Error) -> Error {
|
||||||
Error::Io(err)
|
Error::Io(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,501 +1,589 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//! Extended keys
|
//! Extended keys
|
||||||
|
|
||||||
|
pub use self::derivation::Error as DerivationError;
|
||||||
|
use ethereum_types::H256;
|
||||||
use secret::Secret;
|
use secret::Secret;
|
||||||
use Public;
|
use Public;
|
||||||
use ethereum_types::H256;
|
|
||||||
pub use self::derivation::Error as DerivationError;
|
|
||||||
|
|
||||||
/// Represents label that can be stored as a part of key derivation
|
/// Represents label that can be stored as a part of key derivation
|
||||||
pub trait Label {
|
pub trait Label {
|
||||||
/// Length of the data that label occupies
|
/// Length of the data that label occupies
|
||||||
fn len() -> usize;
|
fn len() -> usize;
|
||||||
|
|
||||||
/// Store label data to the key derivation sequence
|
/// Store label data to the key derivation sequence
|
||||||
/// Must not use more than `len()` bytes from slice
|
/// Must not use more than `len()` bytes from slice
|
||||||
fn store(&self, target: &mut [u8]);
|
fn store(&self, target: &mut [u8]);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Label for u32 {
|
impl Label for u32 {
|
||||||
fn len() -> usize { 4 }
|
fn len() -> usize {
|
||||||
|
4
|
||||||
|
}
|
||||||
|
|
||||||
fn store(&self, target: &mut [u8]) {
|
fn store(&self, target: &mut [u8]) {
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
let bytes = self.to_be_bytes();
|
||||||
|
target[0..4].copy_from_slice(&bytes);
|
||||||
BigEndian::write_u32(&mut target[0..4], *self);
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Key derivation over generic label `T`
|
/// Key derivation over generic label `T`
|
||||||
pub enum Derivation<T: Label> {
|
pub enum Derivation<T: Label> {
|
||||||
/// Soft key derivation (allow proof of parent)
|
/// Soft key derivation (allow proof of parent)
|
||||||
Soft(T),
|
Soft(T),
|
||||||
/// Hard key derivation (does not allow proof of parent)
|
/// Hard key derivation (does not allow proof of parent)
|
||||||
Hard(T),
|
Hard(T),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u32> for Derivation<u32> {
|
impl From<u32> for Derivation<u32> {
|
||||||
fn from(index: u32) -> Self {
|
fn from(index: u32) -> Self {
|
||||||
if index < (2 << 30) {
|
if index < (2 << 30) {
|
||||||
Derivation::Soft(index)
|
Derivation::Soft(index)
|
||||||
}
|
} else {
|
||||||
else {
|
Derivation::Hard(index)
|
||||||
Derivation::Hard(index)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Label for H256 {
|
impl Label for H256 {
|
||||||
fn len() -> usize { 32 }
|
fn len() -> usize {
|
||||||
|
32
|
||||||
|
}
|
||||||
|
|
||||||
fn store(&self, target: &mut [u8]) {
|
fn store(&self, target: &mut [u8]) {
|
||||||
self.copy_to(&mut target[0..32]);
|
self.copy_to(&mut target[0..32]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extended secret key, allows deterministic derivation of subsequent keys.
|
/// Extended secret key, allows deterministic derivation of subsequent keys.
|
||||||
pub struct ExtendedSecret {
|
pub struct ExtendedSecret {
|
||||||
secret: Secret,
|
secret: Secret,
|
||||||
chain_code: H256,
|
chain_code: H256,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtendedSecret {
|
impl ExtendedSecret {
|
||||||
/// New extended key from given secret and chain code.
|
/// New extended key from given secret and chain code.
|
||||||
pub fn with_code(secret: Secret, chain_code: H256) -> ExtendedSecret {
|
pub fn with_code(secret: Secret, chain_code: H256) -> ExtendedSecret {
|
||||||
ExtendedSecret {
|
ExtendedSecret {
|
||||||
secret: secret,
|
secret: secret,
|
||||||
chain_code: chain_code,
|
chain_code: chain_code,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// New extended key from given secret with the random chain code.
|
/// New extended key from given secret with the random chain code.
|
||||||
pub fn new_random(secret: Secret) -> ExtendedSecret {
|
pub fn new_random(secret: Secret) -> ExtendedSecret {
|
||||||
ExtendedSecret::with_code(secret, H256::random())
|
ExtendedSecret::with_code(secret, H256::random())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// New extended key from given secret.
|
/// New extended key from given secret.
|
||||||
/// Chain code will be derived from the secret itself (in a deterministic way).
|
/// Chain code will be derived from the secret itself (in a deterministic way).
|
||||||
pub fn new(secret: Secret) -> ExtendedSecret {
|
pub fn new(secret: Secret) -> ExtendedSecret {
|
||||||
let chain_code = derivation::chain_code(*secret);
|
let chain_code = derivation::chain_code(*secret);
|
||||||
ExtendedSecret::with_code(secret, chain_code)
|
ExtendedSecret::with_code(secret, chain_code)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Derive new private key
|
/// Derive new private key
|
||||||
pub fn derive<T>(&self, index: Derivation<T>) -> ExtendedSecret where T: Label {
|
pub fn derive<T>(&self, index: Derivation<T>) -> ExtendedSecret
|
||||||
let (derived_key, next_chain_code) = derivation::private(*self.secret, self.chain_code, index);
|
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);
|
let derived_secret = Secret::from(derived_key.0);
|
||||||
|
|
||||||
ExtendedSecret::with_code(derived_secret, next_chain_code)
|
ExtendedSecret::with_code(derived_secret, next_chain_code)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Private key component of the extended key.
|
/// Private key component of the extended key.
|
||||||
pub fn as_raw(&self) -> &Secret {
|
pub fn as_raw(&self) -> &Secret {
|
||||||
&self.secret
|
&self.secret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extended public key, allows deterministic derivation of subsequent keys.
|
/// Extended public key, allows deterministic derivation of subsequent keys.
|
||||||
pub struct ExtendedPublic {
|
pub struct ExtendedPublic {
|
||||||
public: Public,
|
public: Public,
|
||||||
chain_code: H256,
|
chain_code: H256,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtendedPublic {
|
impl ExtendedPublic {
|
||||||
/// New extended public key from known parent and chain code
|
/// New extended public key from known parent and chain code
|
||||||
pub fn new(public: Public, chain_code: H256) -> Self {
|
pub fn new(public: Public, chain_code: H256) -> Self {
|
||||||
ExtendedPublic { public: public, chain_code: chain_code }
|
ExtendedPublic {
|
||||||
}
|
public: public,
|
||||||
|
chain_code: chain_code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create new extended public key from known secret
|
/// Create new extended public key from known secret
|
||||||
pub fn from_secret(secret: &ExtendedSecret) -> Result<Self, DerivationError> {
|
pub fn from_secret(secret: &ExtendedSecret) -> Result<Self, DerivationError> {
|
||||||
Ok(
|
Ok(ExtendedPublic::new(
|
||||||
ExtendedPublic::new(
|
derivation::point(**secret.as_raw())?,
|
||||||
derivation::point(**secret.as_raw())?,
|
secret.chain_code.clone(),
|
||||||
secret.chain_code.clone(),
|
))
|
||||||
)
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Derive new public key
|
/// Derive new public key
|
||||||
/// Operation is defined only for index belongs [0..2^31)
|
/// Operation is defined only for index belongs [0..2^31)
|
||||||
pub fn derive<T>(&self, index: Derivation<T>) -> Result<Self, DerivationError> where T: Label {
|
pub fn derive<T>(&self, index: Derivation<T>) -> Result<Self, DerivationError>
|
||||||
let (derived_key, next_chain_code) = derivation::public(self.public, self.chain_code, index)?;
|
where
|
||||||
Ok(ExtendedPublic::new(derived_key, next_chain_code))
|
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 {
|
pub fn public(&self) -> &Public {
|
||||||
&self.public
|
&self.public
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ExtendedKeyPair {
|
pub struct ExtendedKeyPair {
|
||||||
secret: ExtendedSecret,
|
secret: ExtendedSecret,
|
||||||
public: ExtendedPublic,
|
public: ExtendedPublic,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtendedKeyPair {
|
impl ExtendedKeyPair {
|
||||||
pub fn new(secret: Secret) -> Self {
|
pub fn new(secret: Secret) -> Self {
|
||||||
let extended_secret = ExtendedSecret::new(secret);
|
let extended_secret = ExtendedSecret::new(secret);
|
||||||
let extended_public = ExtendedPublic::from_secret(&extended_secret)
|
let extended_public = ExtendedPublic::from_secret(&extended_secret)
|
||||||
.expect("Valid `Secret` always produces valid public; qed");
|
.expect("Valid `Secret` always produces valid public; qed");
|
||||||
ExtendedKeyPair {
|
ExtendedKeyPair {
|
||||||
secret: extended_secret,
|
secret: extended_secret,
|
||||||
public: extended_public,
|
public: extended_public,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_code(secret: Secret, public: Public, chain_code: H256) -> Self {
|
pub fn with_code(secret: Secret, public: Public, chain_code: H256) -> Self {
|
||||||
ExtendedKeyPair {
|
ExtendedKeyPair {
|
||||||
secret: ExtendedSecret::with_code(secret, chain_code.clone()),
|
secret: ExtendedSecret::with_code(secret, chain_code.clone()),
|
||||||
public: ExtendedPublic::new(public, chain_code),
|
public: ExtendedPublic::new(public, chain_code),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_secret(secret: Secret, chain_code: H256) -> Self {
|
pub fn with_secret(secret: Secret, chain_code: H256) -> Self {
|
||||||
let extended_secret = ExtendedSecret::with_code(secret, chain_code);
|
let extended_secret = ExtendedSecret::with_code(secret, chain_code);
|
||||||
let extended_public = ExtendedPublic::from_secret(&extended_secret)
|
let extended_public = ExtendedPublic::from_secret(&extended_secret)
|
||||||
.expect("Valid `Secret` always produces valid public; qed");
|
.expect("Valid `Secret` always produces valid public; qed");
|
||||||
ExtendedKeyPair {
|
ExtendedKeyPair {
|
||||||
secret: extended_secret,
|
secret: extended_secret,
|
||||||
public: extended_public,
|
public: extended_public,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_seed(seed: &[u8]) -> Result<ExtendedKeyPair, DerivationError> {
|
pub fn with_seed(seed: &[u8]) -> Result<ExtendedKeyPair, DerivationError> {
|
||||||
let (master_key, chain_code) = derivation::seed_pair(seed);
|
let (master_key, chain_code) = derivation::seed_pair(seed);
|
||||||
Ok(ExtendedKeyPair::with_secret(
|
Ok(ExtendedKeyPair::with_secret(
|
||||||
Secret::from_unsafe_slice(&*master_key).map_err(|_| DerivationError::InvalidSeed)?,
|
Secret::from_unsafe_slice(&*master_key).map_err(|_| DerivationError::InvalidSeed)?,
|
||||||
chain_code,
|
chain_code,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn secret(&self) -> &ExtendedSecret {
|
pub fn secret(&self) -> &ExtendedSecret {
|
||||||
&self.secret
|
&self.secret
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn public(&self) -> &ExtendedPublic {
|
pub fn public(&self) -> &ExtendedPublic {
|
||||||
&self.public
|
&self.public
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn derive<T>(&self, index: Derivation<T>) -> Result<Self, DerivationError> where T: Label {
|
pub fn derive<T>(&self, index: Derivation<T>) -> Result<Self, DerivationError>
|
||||||
let derived = self.secret.derive(index);
|
where
|
||||||
|
T: Label,
|
||||||
|
{
|
||||||
|
let derived = self.secret.derive(index);
|
||||||
|
|
||||||
Ok(ExtendedKeyPair {
|
Ok(ExtendedKeyPair {
|
||||||
public: ExtendedPublic::from_secret(&derived)?,
|
public: ExtendedPublic::from_secret(&derived)?,
|
||||||
secret: derived,
|
secret: derived,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Derivation functions for private and public keys
|
// Derivation functions for private and public keys
|
||||||
// Work is based on BIP0032
|
// Work is based on BIP0032
|
||||||
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
|
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
|
||||||
mod derivation {
|
mod derivation {
|
||||||
use parity_crypto::hmac;
|
use super::{Derivation, Label};
|
||||||
use ethereum_types::{U256, U512, H512, H256};
|
use ethereum_types::{H256, H512, U256, U512};
|
||||||
use secp256k1::key::{SecretKey, PublicKey};
|
use keccak;
|
||||||
use SECP256K1;
|
use math::curve_order;
|
||||||
use keccak;
|
use parity_crypto::hmac;
|
||||||
use math::curve_order;
|
use secp256k1::key::{PublicKey, SecretKey};
|
||||||
use super::{Label, Derivation};
|
use SECP256K1;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
InvalidHardenedUse,
|
InvalidHardenedUse,
|
||||||
InvalidPoint,
|
InvalidPoint,
|
||||||
MissingIndex,
|
MissingIndex,
|
||||||
InvalidSeed,
|
InvalidSeed,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deterministic derivation of the key using secp256k1 elliptic curve.
|
// Deterministic derivation of the key using secp256k1 elliptic curve.
|
||||||
// Derivation can be either hardened or not.
|
// Derivation can be either hardened or not.
|
||||||
// For hardened derivation, pass u32 index at least 2^31 or custom Derivation::Hard(T) enum
|
// 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
|
// Can panic if passed `private_key` is not a valid secp256k1 private key
|
||||||
// (outside of (0..curve_order()]) field
|
// (outside of (0..curve_order()]) field
|
||||||
pub fn private<T>(private_key: H256, chain_code: H256, index: Derivation<T>) -> (H256, H256) where T: Label {
|
pub fn private<T>(private_key: H256, chain_code: H256, index: Derivation<T>) -> (H256, H256)
|
||||||
match index {
|
where
|
||||||
Derivation::Soft(index) => private_soft(private_key, chain_code, index),
|
T: Label,
|
||||||
Derivation::Hard(index) => private_hard(private_key, chain_code, index),
|
{
|
||||||
}
|
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) {
|
fn hmac_pair(data: &[u8], private_key: H256, chain_code: H256) -> (H256, H256) {
|
||||||
let private: U256 = private_key.into();
|
let private: U256 = private_key.into();
|
||||||
|
|
||||||
// produces 512-bit derived hmac (I)
|
// produces 512-bit derived hmac (I)
|
||||||
let skey = hmac::SigKey::sha512(&*chain_code);
|
let skey = hmac::SigKey::sha512(&*chain_code);
|
||||||
let i_512 = hmac::sign(&skey, &data[..]);
|
let i_512 = hmac::sign(&skey, &data[..]);
|
||||||
|
|
||||||
// left most 256 bits are later added to original private key
|
// left most 256 bits are later added to original private key
|
||||||
let hmac_key: U256 = H256::from_slice(&i_512[0..32]).into();
|
let hmac_key: U256 = H256::from_slice(&i_512[0..32]).into();
|
||||||
// right most 256 bits are new chain code for later derivations
|
// right most 256 bits are new chain code for later derivations
|
||||||
let next_chain_code = H256::from(&i_512[32..64]);
|
let next_chain_code = H256::from(&i_512[32..64]);
|
||||||
|
|
||||||
let child_key = private_add(hmac_key, private).into();
|
let child_key = private_add(hmac_key, private).into();
|
||||||
(child_key, next_chain_code)
|
(child_key, next_chain_code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can panic if passed `private_key` is not a valid secp256k1 private key
|
// Can panic if passed `private_key` is not a valid secp256k1 private key
|
||||||
// (outside of (0..curve_order()]) field
|
// (outside of (0..curve_order()]) field
|
||||||
fn private_soft<T>(private_key: H256, chain_code: H256, index: T) -> (H256, H256) where T: Label {
|
fn private_soft<T>(private_key: H256, chain_code: H256, index: T) -> (H256, H256)
|
||||||
let mut data = vec![0u8; 33 + T::len()];
|
where
|
||||||
|
T: Label,
|
||||||
|
{
|
||||||
|
let mut data = vec![0u8; 33 + T::len()];
|
||||||
|
|
||||||
let sec_private = SecretKey::from_slice(&SECP256K1, &*private_key)
|
let sec_private = SecretKey::from_slice(&SECP256K1, &*private_key)
|
||||||
.expect("Caller should provide valid private key");
|
.expect("Caller should provide valid private key");
|
||||||
let sec_public = PublicKey::from_secret_key(&SECP256K1, &sec_private)
|
let sec_public = PublicKey::from_secret_key(&SECP256K1, &sec_private)
|
||||||
.expect("Caller should provide valid private key");
|
.expect("Caller should provide valid private key");
|
||||||
let public_serialized = sec_public.serialize_vec(&SECP256K1, true);
|
let public_serialized = sec_public.serialize_vec(&SECP256K1, true);
|
||||||
|
|
||||||
// curve point (compressed public key) -- index
|
// curve point (compressed public key) -- index
|
||||||
// 0.33 -- 33..end
|
// 0.33 -- 33..end
|
||||||
data[0..33].copy_from_slice(&public_serialized);
|
data[0..33].copy_from_slice(&public_serialized);
|
||||||
index.store(&mut data[33..]);
|
index.store(&mut data[33..]);
|
||||||
|
|
||||||
hmac_pair(&data, private_key, chain_code)
|
hmac_pair(&data, private_key, chain_code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deterministic derivation of the key using secp256k1 elliptic curve
|
// Deterministic derivation of the key using secp256k1 elliptic curve
|
||||||
// This is hardened derivation and does not allow to associate
|
// This is hardened derivation and does not allow to associate
|
||||||
// corresponding public keys of the original and derived private keys
|
// 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 {
|
fn private_hard<T>(private_key: H256, chain_code: H256, index: T) -> (H256, H256)
|
||||||
let mut data: Vec<u8> = vec![0u8; 33 + T::len()];
|
where
|
||||||
let private: U256 = private_key.into();
|
T: Label,
|
||||||
|
{
|
||||||
|
let mut data: Vec<u8> = vec![0u8; 33 + T::len()];
|
||||||
|
let private: U256 = private_key.into();
|
||||||
|
|
||||||
// 0x00 (padding) -- private_key -- index
|
// 0x00 (padding) -- private_key -- index
|
||||||
// 0 -- 1..33 -- 33..end
|
// 0 -- 1..33 -- 33..end
|
||||||
private.to_big_endian(&mut data[1..33]);
|
private.to_big_endian(&mut data[1..33]);
|
||||||
index.store(&mut data[33..(33 + T::len())]);
|
index.store(&mut data[33..(33 + T::len())]);
|
||||||
|
|
||||||
hmac_pair(&data, private_key, chain_code)
|
hmac_pair(&data, private_key, chain_code)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn private_add(k1: U256, k2: U256) -> U256 {
|
fn private_add(k1: U256, k2: U256) -> U256 {
|
||||||
let sum = U512::from(k1) + U512::from(k2);
|
let sum = U512::from(k1) + U512::from(k2);
|
||||||
modulo(sum, curve_order())
|
modulo(sum, curve_order())
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: surely can be optimized
|
// todo: surely can be optimized
|
||||||
fn modulo(u1: U512, u2: U256) -> U256 {
|
fn modulo(u1: U512, u2: U256) -> U256 {
|
||||||
let dv = u1 / U512::from(u2);
|
let dv = u1 / U512::from(u2);
|
||||||
let md = u1 - (dv * U512::from(u2));
|
let md = u1 - (dv * U512::from(u2));
|
||||||
md.into()
|
md.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn public<T>(public_key: H512, chain_code: H256, derivation: Derivation<T>) -> Result<(H512, H256), Error> where T: Label {
|
pub fn public<T>(
|
||||||
let index = match derivation {
|
public_key: H512,
|
||||||
Derivation::Soft(index) => index,
|
chain_code: H256,
|
||||||
Derivation::Hard(_) => { return Err(Error::InvalidHardenedUse); }
|
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];
|
let mut public_sec_raw = [0u8; 65];
|
||||||
public_sec_raw[0] = 4;
|
public_sec_raw[0] = 4;
|
||||||
public_sec_raw[1..65].copy_from_slice(&*public_key);
|
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_sec =
|
||||||
let public_serialized = public_sec.serialize_vec(&SECP256K1, true);
|
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()];
|
let mut data = vec![0u8; 33 + T::len()];
|
||||||
// curve point (compressed public key) -- index
|
// curve point (compressed public key) -- index
|
||||||
// 0.33 -- 33..end
|
// 0.33 -- 33..end
|
||||||
data[0..33].copy_from_slice(&public_serialized);
|
data[0..33].copy_from_slice(&public_serialized);
|
||||||
index.store(&mut data[33..(33 + T::len())]);
|
index.store(&mut data[33..(33 + T::len())]);
|
||||||
|
|
||||||
// HMAC512SHA produces [derived private(256); new chain code(256)]
|
// HMAC512SHA produces [derived private(256); new chain code(256)]
|
||||||
let skey = hmac::SigKey::sha512(&*chain_code);
|
let skey = hmac::SigKey::sha512(&*chain_code);
|
||||||
let i_512 = hmac::sign(&skey, &data[..]);
|
let i_512 = hmac::sign(&skey, &data[..]);
|
||||||
|
|
||||||
let new_private = H256::from(&i_512[0..32]);
|
let new_private = H256::from(&i_512[0..32]);
|
||||||
let new_chain_code = H256::from(&i_512[32..64]);
|
let new_chain_code = H256::from(&i_512[32..64]);
|
||||||
|
|
||||||
// Generated private key can (extremely rarely) be out of secp256k1 key field
|
// Generated private key can (extremely rarely) be out of secp256k1 key field
|
||||||
if curve_order() <= new_private.clone().into() { return Err(Error::MissingIndex); }
|
if curve_order() <= new_private.clone().into() {
|
||||||
let new_private_sec = SecretKey::from_slice(&SECP256K1, &*new_private)
|
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");
|
.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)
|
let mut new_public = PublicKey::from_secret_key(&SECP256K1, &new_private_sec)
|
||||||
.expect("Valid private key produces valid public key");
|
.expect("Valid private key produces valid public key");
|
||||||
|
|
||||||
// Adding two points on the elliptic curves (combining two public keys)
|
// Adding two points on the elliptic curves (combining two public keys)
|
||||||
new_public.add_assign(&SECP256K1, &public_sec)
|
new_public
|
||||||
.expect("Addition of two valid points produce valid point");
|
.add_assign(&SECP256K1, &public_sec)
|
||||||
|
.expect("Addition of two valid points produce valid point");
|
||||||
|
|
||||||
let serialized = new_public.serialize_vec(&SECP256K1, false);
|
let serialized = new_public.serialize_vec(&SECP256K1, false);
|
||||||
|
|
||||||
Ok((
|
Ok((H512::from(&serialized[1..65]), new_chain_code))
|
||||||
H512::from(&serialized[1..65]),
|
}
|
||||||
new_chain_code,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sha3(slc: &[u8]) -> H256 {
|
fn sha3(slc: &[u8]) -> H256 {
|
||||||
keccak::Keccak256::keccak256(slc).into()
|
keccak::Keccak256::keccak256(slc).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chain_code(secret: H256) -> H256 {
|
pub fn chain_code(secret: H256) -> H256 {
|
||||||
// 10,000 rounds of sha3
|
// 10,000 rounds of sha3
|
||||||
let mut running_sha3 = sha3(&*secret);
|
let mut running_sha3 = sha3(&*secret);
|
||||||
for _ in 0..99999 { running_sha3 = sha3(&*running_sha3); }
|
for _ in 0..99999 {
|
||||||
running_sha3
|
running_sha3 = sha3(&*running_sha3);
|
||||||
}
|
}
|
||||||
|
running_sha3
|
||||||
|
}
|
||||||
|
|
||||||
pub fn point(secret: H256) -> Result<H512, Error> {
|
pub fn point(secret: H256) -> Result<H512, Error> {
|
||||||
let sec = SecretKey::from_slice(&SECP256K1, &*secret)
|
let sec = SecretKey::from_slice(&SECP256K1, &*secret).map_err(|_| Error::InvalidPoint)?;
|
||||||
.map_err(|_| Error::InvalidPoint)?;
|
let public_sec =
|
||||||
let public_sec = PublicKey::from_secret_key(&SECP256K1, &sec)
|
PublicKey::from_secret_key(&SECP256K1, &sec).map_err(|_| Error::InvalidPoint)?;
|
||||||
.map_err(|_| Error::InvalidPoint)?;
|
let serialized = public_sec.serialize_vec(&SECP256K1, false);
|
||||||
let serialized = public_sec.serialize_vec(&SECP256K1, false);
|
Ok(H512::from(&serialized[1..65]))
|
||||||
Ok(H512::from(&serialized[1..65]))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn seed_pair(seed: &[u8]) -> (H256, H256) {
|
pub fn seed_pair(seed: &[u8]) -> (H256, H256) {
|
||||||
let skey = hmac::SigKey::sha512(b"Bitcoin seed");
|
let skey = hmac::SigKey::sha512(b"Bitcoin seed");
|
||||||
let i_512 = hmac::sign(&skey, seed);
|
let i_512 = hmac::sign(&skey, seed);
|
||||||
|
|
||||||
let master_key = H256::from_slice(&i_512[0..32]);
|
let master_key = H256::from_slice(&i_512[0..32]);
|
||||||
let chain_code = H256::from_slice(&i_512[32..64]);
|
let chain_code = H256::from_slice(&i_512[32..64]);
|
||||||
|
|
||||||
(master_key, chain_code)
|
(master_key, chain_code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{ExtendedSecret, ExtendedPublic, ExtendedKeyPair};
|
use super::{derivation, Derivation, ExtendedKeyPair, ExtendedPublic, ExtendedSecret};
|
||||||
use secret::Secret;
|
use ethereum_types::{H128, H256};
|
||||||
use std::str::FromStr;
|
use secret::Secret;
|
||||||
use ethereum_types::{H128, H256};
|
use std::str::FromStr;
|
||||||
use super::{derivation, Derivation};
|
|
||||||
|
|
||||||
fn master_chain_basic() -> (H256, H256) {
|
fn master_chain_basic() -> (H256, H256) {
|
||||||
let seed = H128::from_str("000102030405060708090a0b0c0d0e0f")
|
let seed = H128::from_str("000102030405060708090a0b0c0d0e0f")
|
||||||
.expect("Seed should be valid H128")
|
.expect("Seed should be valid H128")
|
||||||
.to_vec();
|
.to_vec();
|
||||||
|
|
||||||
derivation::seed_pair(&*seed)
|
derivation::seed_pair(&*seed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_extended<F>(f: F, test_private: H256) where F: Fn(ExtendedSecret) -> ExtendedSecret {
|
fn test_extended<F>(f: F, test_private: H256)
|
||||||
let (private_seed, chain_code) = master_chain_basic();
|
where
|
||||||
let extended_secret = ExtendedSecret::with_code(Secret::from(private_seed.0), chain_code);
|
F: Fn(ExtendedSecret) -> ExtendedSecret,
|
||||||
let derived = f(extended_secret);
|
{
|
||||||
assert_eq!(**derived.as_raw(), test_private);
|
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]
|
#[test]
|
||||||
fn smoky() {
|
fn smoky() {
|
||||||
let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap();
|
let secret =
|
||||||
let extended_secret = ExtendedSecret::with_code(secret.clone(), 0u64.into());
|
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||||
|
.unwrap();
|
||||||
|
let extended_secret = ExtendedSecret::with_code(secret.clone(), 0u64.into());
|
||||||
|
|
||||||
// hardened
|
// hardened
|
||||||
assert_eq!(&**extended_secret.as_raw(), &*secret);
|
assert_eq!(&**extended_secret.as_raw(), &*secret);
|
||||||
assert_eq!(&**extended_secret.derive(2147483648.into()).as_raw(), &"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into());
|
assert_eq!(
|
||||||
assert_eq!(&**extended_secret.derive(2147483649.into()).as_raw(), &"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into());
|
&**extended_secret.derive(2147483648.into()).as_raw(),
|
||||||
|
&"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&**extended_secret.derive(2147483649.into()).as_raw(),
|
||||||
|
&"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into()
|
||||||
|
);
|
||||||
|
|
||||||
// normal
|
// normal
|
||||||
assert_eq!(&**extended_secret.derive(0.into()).as_raw(), &"bf6a74e3f7b36fc4c96a1e12f31abc817f9f5904f5a8fc27713163d1f0b713f6".into());
|
assert_eq!(
|
||||||
assert_eq!(&**extended_secret.derive(1.into()).as_raw(), &"bd4fca9eb1f9c201e9448c1eecd66e302d68d4d313ce895b8c134f512205c1bc".into());
|
&**extended_secret.derive(0.into()).as_raw(),
|
||||||
assert_eq!(&**extended_secret.derive(2.into()).as_raw(), &"86932b542d6cab4d9c65490c7ef502d89ecc0e2a5f4852157649e3251e2a3268".into());
|
&"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 extended_public = ExtendedPublic::from_secret(&extended_secret)
|
||||||
let derived_public = extended_public.derive(0.into()).expect("First derivation of public should succeed");
|
.expect("Extended public should be created");
|
||||||
assert_eq!(&*derived_public.public(), &"f7b3244c96688f92372bfd4def26dc4151529747bab9f188a4ad34e141d47bd66522ff048bc6f19a0a4429b04318b1a8796c000265b4fa200dae5f6dda92dd94".into());
|
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(
|
let keypair = ExtendedKeyPair::with_secret(
|
||||||
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap(),
|
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||||
064.into(),
|
.unwrap(),
|
||||||
);
|
064.into(),
|
||||||
assert_eq!(&**keypair.derive(2147483648u32.into()).expect("Derivation of keypair should succeed").secret().as_raw(), &"edef54414c03196557cf73774bc97a645c9a1df2164ed34f0c2a78d1375a930c".into());
|
);
|
||||||
}
|
assert_eq!(
|
||||||
|
&**keypair
|
||||||
|
.derive(2147483648u32.into())
|
||||||
|
.expect("Derivation of keypair should succeed")
|
||||||
|
.secret()
|
||||||
|
.as_raw(),
|
||||||
|
&"edef54414c03196557cf73774bc97a645c9a1df2164ed34f0c2a78d1375a930c".into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn h256_soft_match() {
|
fn h256_soft_match() {
|
||||||
let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap();
|
let secret =
|
||||||
let derivation_secret = H256::from_str("51eaf04f9dbbc1417dc97e789edd0c37ecda88bac490434e367ea81b71b7b015").unwrap();
|
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_secret = ExtendedSecret::with_code(secret.clone(), 0u64.into());
|
||||||
let extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created");
|
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_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 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");
|
let public_from_secret0 = ExtendedPublic::from_secret(&derived_secret0)
|
||||||
|
.expect("Extended public should be created");
|
||||||
|
|
||||||
assert_eq!(public_from_secret0.public(), derived_public0.public());
|
assert_eq!(public_from_secret0.public(), derived_public0.public());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn h256_hard() {
|
fn h256_hard() {
|
||||||
let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap();
|
let secret =
|
||||||
let derivation_secret = H256::from_str("51eaf04f9dbbc1417dc97e789edd0c37ecda88bac490434e367ea81b71b7b015").unwrap();
|
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||||
let extended_secret = ExtendedSecret::with_code(secret.clone(), 1u64.into());
|
.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());
|
assert_eq!(
|
||||||
}
|
&**extended_secret
|
||||||
|
.derive(Derivation::Hard(derivation_secret))
|
||||||
|
.as_raw(),
|
||||||
|
&"2bc2d696fb744d77ff813b4a1ef0ad64e1e5188b622c54ba917acc5ebc7c5486".into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn match_() {
|
fn match_() {
|
||||||
let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap();
|
let secret =
|
||||||
let extended_secret = ExtendedSecret::with_code(secret.clone(), 1.into());
|
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||||
let extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created");
|
.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_secret0 = extended_secret.derive(0.into());
|
||||||
let derived_public0 = extended_public.derive(0.into()).expect("First derivation of public should succeed");
|
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");
|
let public_from_secret0 = ExtendedPublic::from_secret(&derived_secret0)
|
||||||
|
.expect("Extended public should be created");
|
||||||
|
|
||||||
assert_eq!(public_from_secret0.public(), derived_public0.public());
|
assert_eq!(public_from_secret0.public(), derived_public0.public());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_seeds() {
|
fn test_seeds() {
|
||||||
let seed = H128::from_str("000102030405060708090a0b0c0d0e0f")
|
let seed = H128::from_str("000102030405060708090a0b0c0d0e0f")
|
||||||
.expect("Seed should be valid H128")
|
.expect("Seed should be valid H128")
|
||||||
.to_vec();
|
.to_vec();
|
||||||
|
|
||||||
// private key from bitcoin test vector
|
// private key from bitcoin test vector
|
||||||
// xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs
|
// xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs
|
||||||
let test_private = H256::from_str("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
|
let test_private =
|
||||||
.expect("Private should be decoded ok");
|
H256::from_str("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
|
||||||
|
.expect("Private should be decoded ok");
|
||||||
|
|
||||||
let (private_seed, _) = derivation::seed_pair(&*seed);
|
let (private_seed, _) = derivation::seed_pair(&*seed);
|
||||||
|
|
||||||
assert_eq!(private_seed, test_private);
|
assert_eq!(private_seed, test_private);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vector_1() {
|
fn test_vector_1() {
|
||||||
// xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7
|
// xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7
|
||||||
// H(0)
|
// H(0)
|
||||||
test_extended(
|
test_extended(
|
||||||
|secret| secret.derive(2147483648.into()),
|
|secret| secret.derive(2147483648.into()),
|
||||||
H256::from_str("edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea")
|
H256::from_str("edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea")
|
||||||
.expect("Private should be decoded ok")
|
.expect("Private should be decoded ok"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vector_2() {
|
fn test_vector_2() {
|
||||||
// xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs
|
// xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs
|
||||||
// H(0)/1
|
// H(0)/1
|
||||||
test_extended(
|
test_extended(
|
||||||
|secret| secret.derive(2147483648.into()).derive(1.into()),
|
|secret| secret.derive(2147483648.into()).derive(1.into()),
|
||||||
H256::from_str("3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368")
|
H256::from_str("3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368")
|
||||||
.expect("Private should be decoded ok")
|
.expect("Private should be decoded ok"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,33 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use tiny_keccak::Keccak;
|
use tiny_keccak::Keccak;
|
||||||
|
|
||||||
pub trait Keccak256<T> {
|
pub trait Keccak256<T> {
|
||||||
fn keccak256(&self) -> T where T: Sized;
|
fn keccak256(&self) -> T
|
||||||
|
where
|
||||||
|
T: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keccak256<[u8; 32]> for [u8] {
|
impl Keccak256<[u8; 32]> for [u8] {
|
||||||
fn keccak256(&self) -> [u8; 32] {
|
fn keccak256(&self) -> [u8; 32] {
|
||||||
let mut keccak = Keccak::new_keccak256();
|
let mut keccak = Keccak::new_keccak256();
|
||||||
let mut result = [0u8; 32];
|
let mut result = [0u8; 32];
|
||||||
keccak.update(self);
|
keccak.update(self);
|
||||||
keccak.finalize(&mut result);
|
keccak.finalize(&mut result);
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,115 +1,120 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::fmt;
|
use super::{Address, Error, Public, Secret, SECP256K1};
|
||||||
use secp256k1::key;
|
|
||||||
use rustc_hex::ToHex;
|
|
||||||
use keccak::Keccak256;
|
use keccak::Keccak256;
|
||||||
use super::{Secret, Public, Address, SECP256K1, Error};
|
use rustc_hex::ToHex;
|
||||||
|
use secp256k1::key;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
pub fn public_to_address(public: &Public) -> Address {
|
pub fn public_to_address(public: &Public) -> Address {
|
||||||
let hash = public.keccak256();
|
let hash = public.keccak256();
|
||||||
let mut result = Address::default();
|
let mut result = Address::default();
|
||||||
result.copy_from_slice(&hash[12..]);
|
result.copy_from_slice(&hash[12..]);
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
/// secp256k1 key pair
|
/// secp256k1 key pair
|
||||||
pub struct KeyPair {
|
pub struct KeyPair {
|
||||||
secret: Secret,
|
secret: Secret,
|
||||||
public: Public,
|
public: Public,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for KeyPair {
|
impl fmt::Display for KeyPair {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
writeln!(f, "secret: {}", self.secret.to_hex())?;
|
writeln!(f, "secret: {}", self.secret.to_hex())?;
|
||||||
writeln!(f, "public: {}", self.public.to_hex())?;
|
writeln!(f, "public: {}", self.public.to_hex())?;
|
||||||
write!(f, "address: {}", self.address().to_hex())
|
write!(f, "address: {}", self.address().to_hex())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyPair {
|
impl KeyPair {
|
||||||
/// Create a pair from secret key
|
/// Create a pair from secret key
|
||||||
pub fn from_secret(secret: Secret) -> Result<KeyPair, Error> {
|
pub fn from_secret(secret: Secret) -> Result<KeyPair, Error> {
|
||||||
let context = &SECP256K1;
|
let context = &SECP256K1;
|
||||||
let s: key::SecretKey = key::SecretKey::from_slice(context, &secret[..])?;
|
let s: key::SecretKey = key::SecretKey::from_slice(context, &secret[..])?;
|
||||||
let pub_key = key::PublicKey::from_secret_key(context, &s)?;
|
let pub_key = key::PublicKey::from_secret_key(context, &s)?;
|
||||||
let serialized = pub_key.serialize_vec(context, false);
|
let serialized = pub_key.serialize_vec(context, false);
|
||||||
|
|
||||||
let mut public = Public::default();
|
let mut public = Public::default();
|
||||||
public.copy_from_slice(&serialized[1..65]);
|
public.copy_from_slice(&serialized[1..65]);
|
||||||
|
|
||||||
let keypair = KeyPair {
|
let keypair = KeyPair {
|
||||||
secret: secret,
|
secret: secret,
|
||||||
public: public,
|
public: public,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(keypair)
|
Ok(keypair)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_secret_slice(slice: &[u8]) -> Result<KeyPair, Error> {
|
pub fn from_secret_slice(slice: &[u8]) -> Result<KeyPair, Error> {
|
||||||
Self::from_secret(Secret::from_unsafe_slice(slice)?)
|
Self::from_secret(Secret::from_unsafe_slice(slice)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_keypair(sec: key::SecretKey, publ: key::PublicKey) -> Self {
|
pub fn from_keypair(sec: key::SecretKey, publ: key::PublicKey) -> Self {
|
||||||
let context = &SECP256K1;
|
let context = &SECP256K1;
|
||||||
let serialized = publ.serialize_vec(context, false);
|
let serialized = publ.serialize_vec(context, false);
|
||||||
let secret = Secret::from(sec);
|
let secret = Secret::from(sec);
|
||||||
let mut public = Public::default();
|
let mut public = Public::default();
|
||||||
public.copy_from_slice(&serialized[1..65]);
|
public.copy_from_slice(&serialized[1..65]);
|
||||||
|
|
||||||
KeyPair {
|
KeyPair {
|
||||||
secret: secret,
|
secret: secret,
|
||||||
public: public,
|
public: public,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn secret(&self) -> &Secret {
|
pub fn secret(&self) -> &Secret {
|
||||||
&self.secret
|
&self.secret
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn public(&self) -> &Public {
|
pub fn public(&self) -> &Public {
|
||||||
&self.public
|
&self.public
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn address(&self) -> Address {
|
pub fn address(&self) -> Address {
|
||||||
public_to_address(&self.public)
|
public_to_address(&self.public)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use {KeyPair, Secret};
|
use KeyPair;
|
||||||
|
use Secret;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn from_secret() {
|
fn from_secret() {
|
||||||
let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap();
|
let secret =
|
||||||
let _ = KeyPair::from_secret(secret).unwrap();
|
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||||
}
|
.unwrap();
|
||||||
|
let _ = KeyPair::from_secret(secret).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn keypair_display() {
|
fn keypair_display() {
|
||||||
let expected =
|
let expected =
|
||||||
"secret: a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65
|
"secret: a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65
|
||||||
public: 8ce0db0b0359ffc5866ba61903cc2518c3675ef2cf380a7e54bde7ea20e6fa1ab45b7617346cd11b7610001ee6ae5b0155c41cad9527cbcdff44ec67848943a4
|
public: 8ce0db0b0359ffc5866ba61903cc2518c3675ef2cf380a7e54bde7ea20e6fa1ab45b7617346cd11b7610001ee6ae5b0155c41cad9527cbcdff44ec67848943a4
|
||||||
address: 5b073e9233944b5e729e46d618f0d8edf3d9c34a".to_owned();
|
address: 5b073e9233944b5e729e46d618f0d8edf3d9c34a".to_owned();
|
||||||
let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap();
|
let secret =
|
||||||
let kp = KeyPair::from_secret(secret).unwrap();
|
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||||
assert_eq!(format!("{}", kp), expected);
|
.unwrap();
|
||||||
}
|
let kp = KeyPair::from_secret(secret).unwrap();
|
||||||
|
assert_eq!(format!("{}", kp), expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,25 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// #![warn(missing_docs)]
|
// #![warn(missing_docs)]
|
||||||
|
|
||||||
extern crate byteorder;
|
|
||||||
extern crate edit_distance;
|
extern crate edit_distance;
|
||||||
extern crate parity_crypto;
|
|
||||||
extern crate ethereum_types;
|
extern crate ethereum_types;
|
||||||
extern crate memzero;
|
extern crate memzero;
|
||||||
|
extern crate parity_crypto;
|
||||||
extern crate parity_wordlist;
|
extern crate parity_wordlist;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate quick_error;
|
extern crate quick_error;
|
||||||
@@ -40,31 +39,33 @@ extern crate serde_derive;
|
|||||||
mod brain;
|
mod brain;
|
||||||
mod brain_prefix;
|
mod brain_prefix;
|
||||||
mod error;
|
mod error;
|
||||||
mod keypair;
|
mod extended;
|
||||||
mod keccak;
|
mod keccak;
|
||||||
|
mod keypair;
|
||||||
mod password;
|
mod password;
|
||||||
mod prefix;
|
mod prefix;
|
||||||
mod random;
|
mod random;
|
||||||
mod signature;
|
|
||||||
mod secret;
|
mod secret;
|
||||||
mod extended;
|
mod signature;
|
||||||
|
|
||||||
pub mod brain_recover;
|
pub mod brain_recover;
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
pub mod math;
|
pub mod math;
|
||||||
|
|
||||||
pub use self::parity_wordlist::Error as WordlistError;
|
pub use self::{
|
||||||
pub use self::brain::Brain;
|
brain::Brain,
|
||||||
pub use self::brain_prefix::BrainPrefix;
|
brain_prefix::BrainPrefix,
|
||||||
pub use self::error::Error;
|
error::Error,
|
||||||
pub use self::keypair::{KeyPair, public_to_address};
|
extended::{Derivation, DerivationError, ExtendedKeyPair, ExtendedPublic, ExtendedSecret},
|
||||||
pub use self::math::public_is_valid;
|
keypair::{public_to_address, KeyPair},
|
||||||
pub use self::password::Password;
|
math::public_is_valid,
|
||||||
pub use self::prefix::Prefix;
|
parity_wordlist::Error as WordlistError,
|
||||||
pub use self::random::Random;
|
password::Password,
|
||||||
pub use self::signature::{sign, verify_public, verify_address, recover, Signature};
|
prefix::Prefix,
|
||||||
pub use self::secret::Secret;
|
random::Random,
|
||||||
pub use self::extended::{ExtendedPublic, ExtendedSecret, ExtendedKeyPair, DerivationError, Derivation};
|
secret::Secret,
|
||||||
|
signature::{recover, sign, verify_address, verify_public, Signature},
|
||||||
|
};
|
||||||
|
|
||||||
use ethereum_types::H256;
|
use ethereum_types::H256;
|
||||||
|
|
||||||
@@ -72,7 +73,7 @@ pub use ethereum_types::{Address, Public};
|
|||||||
pub type Message = H256;
|
pub type Message = H256;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref SECP256K1: secp256k1::Secp256k1 = secp256k1::Secp256k1::new();
|
pub static ref SECP256K1: secp256k1::Secp256k1 = secp256k1::Secp256k1::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Uninstantiatable error type for infallible generators.
|
/// Uninstantiatable error type for infallible generators.
|
||||||
@@ -81,8 +82,8 @@ pub enum Void {}
|
|||||||
|
|
||||||
/// Generates new keypair.
|
/// Generates new keypair.
|
||||||
pub trait Generator {
|
pub trait Generator {
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
/// Should be called to generate new keypair.
|
/// Should be called to generate new keypair.
|
||||||
fn generate(&mut self) -> Result<KeyPair, Self::Error>;
|
fn generate(&mut self) -> Result<KeyPair, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,129 +1,134 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use super::{SECP256K1, Public, Secret, Error};
|
use super::{Error, Public, Secret, SECP256K1};
|
||||||
use secp256k1::key;
|
use ethereum_types::{H256, U256};
|
||||||
use secp256k1::constants::{GENERATOR_X, GENERATOR_Y, CURVE_ORDER};
|
use secp256k1::{
|
||||||
use ethereum_types::{U256, H256};
|
constants::{CURVE_ORDER, GENERATOR_X, GENERATOR_Y},
|
||||||
|
key,
|
||||||
|
};
|
||||||
|
|
||||||
/// Whether the public key is valid.
|
/// Whether the public key is valid.
|
||||||
pub fn public_is_valid(public: &Public) -> bool {
|
pub fn public_is_valid(public: &Public) -> bool {
|
||||||
to_secp256k1_public(public).ok()
|
to_secp256k1_public(public)
|
||||||
.map_or(false, |p| p.is_valid())
|
.ok()
|
||||||
|
.map_or(false, |p| p.is_valid())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inplace multiply public key by secret key (EC point * scalar)
|
/// Inplace multiply public key by secret key (EC point * scalar)
|
||||||
pub fn public_mul_secret(public: &mut Public, secret: &Secret) -> Result<(), Error> {
|
pub fn public_mul_secret(public: &mut Public, secret: &Secret) -> Result<(), Error> {
|
||||||
let key_secret = secret.to_secp256k1_secret()?;
|
let key_secret = secret.to_secp256k1_secret()?;
|
||||||
let mut key_public = to_secp256k1_public(public)?;
|
let mut key_public = to_secp256k1_public(public)?;
|
||||||
key_public.mul_assign(&SECP256K1, &key_secret)?;
|
key_public.mul_assign(&SECP256K1, &key_secret)?;
|
||||||
set_public(public, &key_public);
|
set_public(public, &key_public);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inplace add one public key to another (EC point + EC point)
|
/// Inplace add one public key to another (EC point + EC point)
|
||||||
pub fn public_add(public: &mut Public, other: &Public) -> Result<(), Error> {
|
pub fn public_add(public: &mut Public, other: &Public) -> Result<(), Error> {
|
||||||
let mut key_public = to_secp256k1_public(public)?;
|
let mut key_public = to_secp256k1_public(public)?;
|
||||||
let other_public = to_secp256k1_public(other)?;
|
let other_public = to_secp256k1_public(other)?;
|
||||||
key_public.add_assign(&SECP256K1, &other_public)?;
|
key_public.add_assign(&SECP256K1, &other_public)?;
|
||||||
set_public(public, &key_public);
|
set_public(public, &key_public);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inplace sub one public key from another (EC point - EC point)
|
/// Inplace sub one public key from another (EC point - EC point)
|
||||||
pub fn public_sub(public: &mut Public, other: &Public) -> Result<(), Error> {
|
pub fn public_sub(public: &mut Public, other: &Public) -> Result<(), Error> {
|
||||||
let mut key_neg_other = to_secp256k1_public(other)?;
|
let mut key_neg_other = to_secp256k1_public(other)?;
|
||||||
key_neg_other.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
|
key_neg_other.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
|
||||||
|
|
||||||
let mut key_public = to_secp256k1_public(public)?;
|
let mut key_public = to_secp256k1_public(public)?;
|
||||||
key_public.add_assign(&SECP256K1, &key_neg_other)?;
|
key_public.add_assign(&SECP256K1, &key_neg_other)?;
|
||||||
set_public(public, &key_public);
|
set_public(public, &key_public);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace public key with its negation (EC point = - EC point)
|
/// Replace public key with its negation (EC point = - EC point)
|
||||||
pub fn public_negate(public: &mut Public) -> Result<(), Error> {
|
pub fn public_negate(public: &mut Public) -> Result<(), Error> {
|
||||||
let mut key_public = to_secp256k1_public(public)?;
|
let mut key_public = to_secp256k1_public(public)?;
|
||||||
key_public.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
|
key_public.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
|
||||||
set_public(public, &key_public);
|
set_public(public, &key_public);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return base point of secp256k1
|
/// Return base point of secp256k1
|
||||||
pub fn generation_point() -> Public {
|
pub fn generation_point() -> Public {
|
||||||
let mut public_sec_raw = [0u8; 65];
|
let mut public_sec_raw = [0u8; 65];
|
||||||
public_sec_raw[0] = 4;
|
public_sec_raw[0] = 4;
|
||||||
public_sec_raw[1..33].copy_from_slice(&GENERATOR_X);
|
public_sec_raw[1..33].copy_from_slice(&GENERATOR_X);
|
||||||
public_sec_raw[33..65].copy_from_slice(&GENERATOR_Y);
|
public_sec_raw[33..65].copy_from_slice(&GENERATOR_Y);
|
||||||
|
|
||||||
let public_key = key::PublicKey::from_slice(&SECP256K1, &public_sec_raw)
|
let public_key = key::PublicKey::from_slice(&SECP256K1, &public_sec_raw)
|
||||||
.expect("constructing using predefined constants; qed");
|
.expect("constructing using predefined constants; qed");
|
||||||
let mut public = Public::default();
|
let mut public = Public::default();
|
||||||
set_public(&mut public, &public_key);
|
set_public(&mut public, &public_key);
|
||||||
public
|
public
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return secp256k1 elliptic curve order
|
/// Return secp256k1 elliptic curve order
|
||||||
pub fn curve_order() -> U256 {
|
pub fn curve_order() -> U256 {
|
||||||
H256::from_slice(&CURVE_ORDER).into()
|
H256::from_slice(&CURVE_ORDER).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_secp256k1_public(public: &Public) -> Result<key::PublicKey, Error> {
|
fn to_secp256k1_public(public: &Public) -> Result<key::PublicKey, Error> {
|
||||||
let public_data = {
|
let public_data = {
|
||||||
let mut temp = [4u8; 65];
|
let mut temp = [4u8; 65];
|
||||||
(&mut temp[1..65]).copy_from_slice(&public[0..64]);
|
(&mut temp[1..65]).copy_from_slice(&public[0..64]);
|
||||||
temp
|
temp
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(key::PublicKey::from_slice(&SECP256K1, &public_data)?)
|
Ok(key::PublicKey::from_slice(&SECP256K1, &public_data)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_public(public: &mut Public, key_public: &key::PublicKey) {
|
fn set_public(public: &mut Public, key_public: &key::PublicKey) {
|
||||||
let key_public_serialized = key_public.serialize_vec(&SECP256K1, false);
|
let key_public_serialized = key_public.serialize_vec(&SECP256K1, false);
|
||||||
public.copy_from_slice(&key_public_serialized[1..65]);
|
public.copy_from_slice(&key_public_serialized[1..65]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::super::{Random, Generator};
|
use super::{
|
||||||
use super::{public_add, public_sub};
|
super::{Generator, Random},
|
||||||
|
public_add, public_sub,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn public_addition_is_commutative() {
|
fn public_addition_is_commutative() {
|
||||||
let public1 = Random.generate().unwrap().public().clone();
|
let public1 = Random.generate().unwrap().public().clone();
|
||||||
let public2 = Random.generate().unwrap().public().clone();
|
let public2 = Random.generate().unwrap().public().clone();
|
||||||
|
|
||||||
let mut left = public1.clone();
|
let mut left = public1.clone();
|
||||||
public_add(&mut left, &public2).unwrap();
|
public_add(&mut left, &public2).unwrap();
|
||||||
|
|
||||||
let mut right = public2.clone();
|
let mut right = public2.clone();
|
||||||
public_add(&mut right, &public1).unwrap();
|
public_add(&mut right, &public1).unwrap();
|
||||||
|
|
||||||
assert_eq!(left, right);
|
assert_eq!(left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn public_addition_is_reversible_with_subtraction() {
|
fn public_addition_is_reversible_with_subtraction() {
|
||||||
let public1 = Random.generate().unwrap().public().clone();
|
let public1 = Random.generate().unwrap().public().clone();
|
||||||
let public2 = Random.generate().unwrap().public().clone();
|
let public2 = Random.generate().unwrap().public().clone();
|
||||||
|
|
||||||
let mut sum = public1.clone();
|
let mut sum = public1.clone();
|
||||||
public_add(&mut sum, &public2).unwrap();
|
public_add(&mut sum, &public2).unwrap();
|
||||||
public_sub(&mut sum, &public2).unwrap();
|
public_sub(&mut sum, &public2).unwrap();
|
||||||
|
|
||||||
assert_eq!(sum, public1);
|
assert_eq!(sum, public1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::{fmt, ptr};
|
use std::{fmt, ptr};
|
||||||
|
|
||||||
@@ -20,40 +20,40 @@ use std::{fmt, ptr};
|
|||||||
pub struct Password(String);
|
pub struct Password(String);
|
||||||
|
|
||||||
impl fmt::Debug for Password {
|
impl fmt::Debug for Password {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "Password(******)")
|
write!(f, "Password(******)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Password {
|
impl Password {
|
||||||
pub fn as_bytes(&self) -> &[u8] {
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
self.0.as_bytes()
|
self.0.as_bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
self.0.as_str()
|
self.0.as_str()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom drop impl to zero out memory.
|
// Custom drop impl to zero out memory.
|
||||||
impl Drop for Password {
|
impl Drop for Password {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
for byte_ref in self.0.as_mut_vec() {
|
for byte_ref in self.0.as_mut_vec() {
|
||||||
ptr::write_volatile(byte_ref, 0)
|
ptr::write_volatile(byte_ref, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for Password {
|
impl From<String> for Password {
|
||||||
fn from(s: String) -> Password {
|
fn from(s: String) -> Password {
|
||||||
Password(s)
|
Password(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a str> for Password {
|
impl<'a> From<&'a str> for Password {
|
||||||
fn from(s: &'a str) -> Password {
|
fn from(s: &'a str) -> Password {
|
||||||
Password::from(String::from(s))
|
Password::from(String::from(s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +1,62 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use super::{Random, Generator, KeyPair, Error};
|
use super::{Error, Generator, KeyPair, Random};
|
||||||
|
|
||||||
/// Tries to find keypair with address starting with given prefix.
|
/// Tries to find keypair with address starting with given prefix.
|
||||||
pub struct Prefix {
|
pub struct Prefix {
|
||||||
prefix: Vec<u8>,
|
prefix: Vec<u8>,
|
||||||
iterations: usize,
|
iterations: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Prefix {
|
impl Prefix {
|
||||||
pub fn new(prefix: Vec<u8>, iterations: usize) -> Self {
|
pub fn new(prefix: Vec<u8>, iterations: usize) -> Self {
|
||||||
Prefix {
|
Prefix {
|
||||||
prefix: prefix,
|
prefix: prefix,
|
||||||
iterations: iterations,
|
iterations: iterations,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Generator for Prefix {
|
impl Generator for Prefix {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn generate(&mut self) -> Result<KeyPair, Error> {
|
fn generate(&mut self) -> Result<KeyPair, Error> {
|
||||||
for _ in 0..self.iterations {
|
for _ in 0..self.iterations {
|
||||||
let keypair = Random.generate()?;
|
let keypair = Random.generate()?;
|
||||||
if keypair.address().starts_with(&self.prefix) {
|
if keypair.address().starts_with(&self.prefix) {
|
||||||
return Ok(keypair)
|
return Ok(keypair);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(Error::Custom("Could not find keypair".into()))
|
Err(Error::Custom("Could not find keypair".into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use {Generator, Prefix};
|
use Generator;
|
||||||
|
use Prefix;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn prefix_generator() {
|
fn prefix_generator() {
|
||||||
let prefix = vec![0xffu8];
|
let prefix = vec![0xffu8];
|
||||||
let keypair = Prefix::new(prefix.clone(), usize::max_value()).generate().unwrap();
|
let keypair = Prefix::new(prefix.clone(), usize::max_value())
|
||||||
assert!(keypair.address().starts_with(&prefix));
|
.generate()
|
||||||
}
|
.unwrap();
|
||||||
|
assert!(keypair.address().starts_with(&prefix));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,45 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use rand::os::OsRng;
|
|
||||||
use super::{Generator, KeyPair, SECP256K1};
|
use super::{Generator, KeyPair, SECP256K1};
|
||||||
|
use rand::os::OsRng;
|
||||||
|
|
||||||
/// Randomly generates new keypair, instantiating the RNG each time.
|
/// Randomly generates new keypair, instantiating the RNG each time.
|
||||||
pub struct Random;
|
pub struct Random;
|
||||||
|
|
||||||
impl Generator for Random {
|
impl Generator for Random {
|
||||||
type Error = ::std::io::Error;
|
type Error = ::std::io::Error;
|
||||||
|
|
||||||
fn generate(&mut self) -> Result<KeyPair, Self::Error> {
|
fn generate(&mut self) -> Result<KeyPair, Self::Error> {
|
||||||
let mut rng = OsRng::new()?;
|
let mut rng = OsRng::new()?;
|
||||||
match rng.generate() {
|
match rng.generate() {
|
||||||
Ok(pair) => Ok(pair),
|
Ok(pair) => Ok(pair),
|
||||||
Err(void) => match void {}, // LLVM unreachable
|
Err(void) => match void {}, // LLVM unreachable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Generator for OsRng {
|
impl Generator for OsRng {
|
||||||
type Error = ::Void;
|
type Error = ::Void;
|
||||||
|
|
||||||
fn generate(&mut self) -> Result<KeyPair, Self::Error> {
|
fn generate(&mut self) -> Result<KeyPair, Self::Error> {
|
||||||
let (sec, publ) = SECP256K1.generate_keypair(self)
|
let (sec, publ) = SECP256K1
|
||||||
.expect("context always created with full capabilities; qed");
|
.generate_keypair(self)
|
||||||
|
.expect("context always created with full capabilities; qed");
|
||||||
|
|
||||||
Ok(KeyPair::from_keypair(sec, publ))
|
Ok(KeyPair::from_keypair(sec, publ))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,298 +1,322 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use rustc_hex::ToHex;
|
|
||||||
use secp256k1::constants::{SECRET_KEY_SIZE as SECP256K1_SECRET_KEY_SIZE};
|
|
||||||
use secp256k1::key;
|
|
||||||
use ethereum_types::H256;
|
use ethereum_types::H256;
|
||||||
use memzero::Memzero;
|
use memzero::Memzero;
|
||||||
use {Error, SECP256K1};
|
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)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
pub struct Secret {
|
pub struct Secret {
|
||||||
inner: Memzero<H256>,
|
inner: Memzero<H256>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToHex for Secret {
|
impl ToHex for Secret {
|
||||||
fn to_hex(&self) -> String {
|
fn to_hex(&self) -> String {
|
||||||
format!("{:x}", *self.inner)
|
format!("{:x}", *self.inner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::LowerHex for Secret {
|
impl fmt::LowerHex for Secret {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
self.inner.fmt(fmt)
|
self.inner.fmt(fmt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Secret {
|
impl fmt::Debug for Secret {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
self.inner.fmt(fmt)
|
self.inner.fmt(fmt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Secret {
|
impl fmt::Display for Secret {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
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])
|
write!(
|
||||||
}
|
fmt,
|
||||||
|
"Secret: 0x{:x}{:x}..{:x}{:x}",
|
||||||
|
self.inner[0], self.inner[1], self.inner[30], self.inner[31]
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Secret {
|
impl Secret {
|
||||||
/// Creates a `Secret` from the given slice, returning `None` if the slice length != 32.
|
/// Creates a `Secret` from the given slice, returning `None` if the slice length != 32.
|
||||||
pub fn from_slice(key: &[u8]) -> Option<Self> {
|
pub fn from_slice(key: &[u8]) -> Option<Self> {
|
||||||
if key.len() != 32 {
|
if key.len() != 32 {
|
||||||
return None
|
return None;
|
||||||
}
|
}
|
||||||
let mut h = H256::default();
|
let mut h = H256::default();
|
||||||
h.copy_from_slice(&key[0..32]);
|
h.copy_from_slice(&key[0..32]);
|
||||||
Some(Secret { inner: Memzero::from(h) })
|
Some(Secret {
|
||||||
}
|
inner: Memzero::from(h),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates zero key, which is invalid for crypto operations, but valid for math operation.
|
/// Creates zero key, which is invalid for crypto operations, but valid for math operation.
|
||||||
pub fn zero() -> Self {
|
pub fn zero() -> Self {
|
||||||
Secret { inner: Memzero::from(H256::default()) }
|
Secret {
|
||||||
}
|
inner: Memzero::from(H256::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Imports and validates the key.
|
/// Imports and validates the key.
|
||||||
pub fn from_unsafe_slice(key: &[u8]) -> Result<Self, Error> {
|
pub fn from_unsafe_slice(key: &[u8]) -> Result<Self, Error> {
|
||||||
let secret = key::SecretKey::from_slice(&super::SECP256K1, key)?;
|
let secret = key::SecretKey::from_slice(&super::SECP256K1, key)?;
|
||||||
Ok(secret.into())
|
Ok(secret.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks validity of this key.
|
/// Checks validity of this key.
|
||||||
pub fn check_validity(&self) -> Result<(), Error> {
|
pub fn check_validity(&self) -> Result<(), Error> {
|
||||||
self.to_secp256k1_secret().map(|_| ())
|
self.to_secp256k1_secret().map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inplace add one secret key to another (scalar + scalar)
|
/// Inplace add one secret key to another (scalar + scalar)
|
||||||
pub fn add(&mut self, other: &Secret) -> Result<(), Error> {
|
pub fn add(&mut self, other: &Secret) -> Result<(), Error> {
|
||||||
match (self.is_zero(), other.is_zero()) {
|
match (self.is_zero(), other.is_zero()) {
|
||||||
(true, true) | (false, true) => Ok(()),
|
(true, true) | (false, true) => Ok(()),
|
||||||
(true, false) => {
|
(true, false) => {
|
||||||
*self = other.clone();
|
*self = other.clone();
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
(false, false) => {
|
(false, false) => {
|
||||||
let mut key_secret = self.to_secp256k1_secret()?;
|
let mut key_secret = self.to_secp256k1_secret()?;
|
||||||
let other_secret = other.to_secp256k1_secret()?;
|
let other_secret = other.to_secp256k1_secret()?;
|
||||||
key_secret.add_assign(&SECP256K1, &other_secret)?;
|
key_secret.add_assign(&SECP256K1, &other_secret)?;
|
||||||
|
|
||||||
*self = key_secret.into();
|
*self = key_secret.into();
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inplace subtract one secret key from another (scalar - scalar)
|
/// Inplace subtract one secret key from another (scalar - scalar)
|
||||||
pub fn sub(&mut self, other: &Secret) -> Result<(), Error> {
|
pub fn sub(&mut self, other: &Secret) -> Result<(), Error> {
|
||||||
match (self.is_zero(), other.is_zero()) {
|
match (self.is_zero(), other.is_zero()) {
|
||||||
(true, true) | (false, true) => Ok(()),
|
(true, true) | (false, true) => Ok(()),
|
||||||
(true, false) => {
|
(true, false) => {
|
||||||
*self = other.clone();
|
*self = other.clone();
|
||||||
self.neg()
|
self.neg()
|
||||||
},
|
}
|
||||||
(false, false) => {
|
(false, false) => {
|
||||||
let mut key_secret = self.to_secp256k1_secret()?;
|
let mut key_secret = self.to_secp256k1_secret()?;
|
||||||
let mut other_secret = other.to_secp256k1_secret()?;
|
let mut other_secret = other.to_secp256k1_secret()?;
|
||||||
other_secret.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
|
other_secret.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
|
||||||
key_secret.add_assign(&SECP256K1, &other_secret)?;
|
key_secret.add_assign(&SECP256K1, &other_secret)?;
|
||||||
|
|
||||||
*self = key_secret.into();
|
*self = key_secret.into();
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inplace decrease secret key (scalar - 1)
|
/// Inplace decrease secret key (scalar - 1)
|
||||||
pub fn dec(&mut self) -> Result<(), Error> {
|
pub fn dec(&mut self) -> Result<(), Error> {
|
||||||
match self.is_zero() {
|
match self.is_zero() {
|
||||||
true => {
|
true => {
|
||||||
*self = key::MINUS_ONE_KEY.into();
|
*self = key::MINUS_ONE_KEY.into();
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
false => {
|
false => {
|
||||||
let mut key_secret = self.to_secp256k1_secret()?;
|
let mut key_secret = self.to_secp256k1_secret()?;
|
||||||
key_secret.add_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
|
key_secret.add_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
|
||||||
|
|
||||||
*self = key_secret.into();
|
*self = key_secret.into();
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inplace multiply one secret key to another (scalar * scalar)
|
/// Inplace multiply one secret key to another (scalar * scalar)
|
||||||
pub fn mul(&mut self, other: &Secret) -> Result<(), Error> {
|
pub fn mul(&mut self, other: &Secret) -> Result<(), Error> {
|
||||||
match (self.is_zero(), other.is_zero()) {
|
match (self.is_zero(), other.is_zero()) {
|
||||||
(true, true) | (true, false) => Ok(()),
|
(true, true) | (true, false) => Ok(()),
|
||||||
(false, true) => {
|
(false, true) => {
|
||||||
*self = Self::zero();
|
*self = Self::zero();
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
(false, false) => {
|
(false, false) => {
|
||||||
let mut key_secret = self.to_secp256k1_secret()?;
|
let mut key_secret = self.to_secp256k1_secret()?;
|
||||||
let other_secret = other.to_secp256k1_secret()?;
|
let other_secret = other.to_secp256k1_secret()?;
|
||||||
key_secret.mul_assign(&SECP256K1, &other_secret)?;
|
key_secret.mul_assign(&SECP256K1, &other_secret)?;
|
||||||
|
|
||||||
*self = key_secret.into();
|
*self = key_secret.into();
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inplace negate secret key (-scalar)
|
/// Inplace negate secret key (-scalar)
|
||||||
pub fn neg(&mut self) -> Result<(), Error> {
|
pub fn neg(&mut self) -> Result<(), Error> {
|
||||||
match self.is_zero() {
|
match self.is_zero() {
|
||||||
true => Ok(()),
|
true => Ok(()),
|
||||||
false => {
|
false => {
|
||||||
let mut key_secret = self.to_secp256k1_secret()?;
|
let mut key_secret = self.to_secp256k1_secret()?;
|
||||||
key_secret.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
|
key_secret.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
|
||||||
|
|
||||||
*self = key_secret.into();
|
*self = key_secret.into();
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inplace inverse secret key (1 / scalar)
|
/// Inplace inverse secret key (1 / scalar)
|
||||||
pub fn inv(&mut self) -> Result<(), Error> {
|
pub fn inv(&mut self) -> Result<(), Error> {
|
||||||
let mut key_secret = self.to_secp256k1_secret()?;
|
let mut key_secret = self.to_secp256k1_secret()?;
|
||||||
key_secret.inv_assign(&SECP256K1)?;
|
key_secret.inv_assign(&SECP256K1)?;
|
||||||
|
|
||||||
*self = key_secret.into();
|
*self = key_secret.into();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute power of secret key inplace (secret ^ pow).
|
/// Compute power of secret key inplace (secret ^ pow).
|
||||||
/// This function is not intended to be used with large powers.
|
/// This function is not intended to be used with large powers.
|
||||||
pub fn pow(&mut self, pow: usize) -> Result<(), Error> {
|
pub fn pow(&mut self, pow: usize) -> Result<(), Error> {
|
||||||
if self.is_zero() {
|
if self.is_zero() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
match pow {
|
match pow {
|
||||||
0 => *self = key::ONE_KEY.into(),
|
0 => *self = key::ONE_KEY.into(),
|
||||||
1 => (),
|
1 => (),
|
||||||
_ => {
|
_ => {
|
||||||
let c = self.clone();
|
let c = self.clone();
|
||||||
for _ in 1..pow {
|
for _ in 1..pow {
|
||||||
self.mul(&c)?;
|
self.mul(&c)?;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create `secp256k1::key::SecretKey` based on this secret
|
/// Create `secp256k1::key::SecretKey` based on this secret
|
||||||
pub fn to_secp256k1_secret(&self) -> Result<key::SecretKey, Error> {
|
pub fn to_secp256k1_secret(&self) -> Result<key::SecretKey, Error> {
|
||||||
Ok(key::SecretKey::from_slice(&SECP256K1, &self[..])?)
|
Ok(key::SecretKey::from_slice(&SECP256K1, &self[..])?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Secret {
|
impl FromStr for Secret {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
Ok(H256::from_str(s).map_err(|e| Error::Custom(format!("{:?}", e)))?.into())
|
Ok(H256::from_str(s)
|
||||||
}
|
.map_err(|e| Error::Custom(format!("{:?}", e)))?
|
||||||
|
.into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<[u8; 32]> for Secret {
|
impl From<[u8; 32]> for Secret {
|
||||||
fn from(k: [u8; 32]) -> Self {
|
fn from(k: [u8; 32]) -> Self {
|
||||||
Secret { inner: Memzero::from(H256(k)) }
|
Secret {
|
||||||
}
|
inner: Memzero::from(H256(k)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<H256> for Secret {
|
impl From<H256> for Secret {
|
||||||
fn from(s: H256) -> Self {
|
fn from(s: H256) -> Self {
|
||||||
s.0.into()
|
s.0.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&'static str> for Secret {
|
impl From<&'static str> for Secret {
|
||||||
fn from(s: &'static str) -> Self {
|
fn from(s: &'static str) -> Self {
|
||||||
s.parse().expect(&format!("invalid string literal for {}: '{}'", stringify!(Self), s))
|
s.parse().expect(&format!(
|
||||||
}
|
"invalid string literal for {}: '{}'",
|
||||||
|
stringify!(Self),
|
||||||
|
s
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<key::SecretKey> for Secret {
|
impl From<key::SecretKey> for Secret {
|
||||||
fn from(key: key::SecretKey) -> Self {
|
fn from(key: key::SecretKey) -> Self {
|
||||||
let mut a = [0; SECP256K1_SECRET_KEY_SIZE];
|
let mut a = [0; SECP256K1_SECRET_KEY_SIZE];
|
||||||
a.copy_from_slice(&key[0 .. SECP256K1_SECRET_KEY_SIZE]);
|
a.copy_from_slice(&key[0..SECP256K1_SECRET_KEY_SIZE]);
|
||||||
a.into()
|
a.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Secret {
|
impl Deref for Secret {
|
||||||
type Target = H256;
|
type Target = H256;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.inner
|
&self.inner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::str::FromStr;
|
use super::{
|
||||||
use super::super::{Random, Generator};
|
super::{Generator, Random},
|
||||||
use super::Secret;
|
Secret,
|
||||||
|
};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multiplicating_secret_inversion_with_secret_gives_one() {
|
fn multiplicating_secret_inversion_with_secret_gives_one() {
|
||||||
let secret = Random.generate().unwrap().secret().clone();
|
let secret = Random.generate().unwrap().secret().clone();
|
||||||
let mut inversion = secret.clone();
|
let mut inversion = secret.clone();
|
||||||
inversion.inv().unwrap();
|
inversion.inv().unwrap();
|
||||||
inversion.mul(&secret).unwrap();
|
inversion.mul(&secret).unwrap();
|
||||||
assert_eq!(inversion, Secret::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap());
|
assert_eq!(
|
||||||
}
|
inversion,
|
||||||
|
Secret::from_str("0000000000000000000000000000000000000000000000000000000000000001")
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn secret_inversion_is_reversible_with_inversion() {
|
fn secret_inversion_is_reversible_with_inversion() {
|
||||||
let secret = Random.generate().unwrap().secret().clone();
|
let secret = Random.generate().unwrap().secret().clone();
|
||||||
let mut inversion = secret.clone();
|
let mut inversion = secret.clone();
|
||||||
inversion.inv().unwrap();
|
inversion.inv().unwrap();
|
||||||
inversion.inv().unwrap();
|
inversion.inv().unwrap();
|
||||||
assert_eq!(inversion, secret);
|
assert_eq!(inversion, secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn secret_pow() {
|
fn secret_pow() {
|
||||||
let secret = Random.generate().unwrap().secret().clone();
|
let secret = Random.generate().unwrap().secret().clone();
|
||||||
|
|
||||||
let mut pow0 = secret.clone();
|
let mut pow0 = secret.clone();
|
||||||
pow0.pow(0).unwrap();
|
pow0.pow(0).unwrap();
|
||||||
assert_eq!(pow0, Secret::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap());
|
assert_eq!(
|
||||||
|
pow0,
|
||||||
|
Secret::from_str("0000000000000000000000000000000000000000000000000000000000000001")
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
let mut pow1 = secret.clone();
|
let mut pow1 = secret.clone();
|
||||||
pow1.pow(1).unwrap();
|
pow1.pow(1).unwrap();
|
||||||
assert_eq!(pow1, secret);
|
assert_eq!(pow1, secret);
|
||||||
|
|
||||||
let mut pow2 = secret.clone();
|
let mut pow2 = secret.clone();
|
||||||
pow2.pow(2).unwrap();
|
pow2.pow(2).unwrap();
|
||||||
let mut pow2_expected = secret.clone();
|
let mut pow2_expected = secret.clone();
|
||||||
pow2_expected.mul(&secret).unwrap();
|
pow2_expected.mul(&secret).unwrap();
|
||||||
assert_eq!(pow2, pow2_expected);
|
assert_eq!(pow2, pow2_expected);
|
||||||
|
|
||||||
let mut pow3 = secret.clone();
|
let mut pow3 = secret.clone();
|
||||||
pow3.pow(3).unwrap();
|
pow3.pow(3).unwrap();
|
||||||
let mut pow3_expected = secret.clone();
|
let mut pow3_expected = secret.clone();
|
||||||
pow3_expected.mul(&secret).unwrap();
|
pow3_expected.mul(&secret).unwrap();
|
||||||
pow3_expected.mul(&secret).unwrap();
|
pow3_expected.mul(&secret).unwrap();
|
||||||
assert_eq!(pow3, pow3_expected);
|
assert_eq!(pow3, pow3_expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,294 +1,325 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::ops::{Deref, DerefMut};
|
use ethereum_types::{H256, H520};
|
||||||
use std::cmp::PartialEq;
|
use public_to_address;
|
||||||
use std::fmt;
|
use rustc_hex::{FromHex, ToHex};
|
||||||
use std::str::FromStr;
|
use secp256k1::{
|
||||||
use std::hash::{Hash, Hasher};
|
key::{PublicKey, SecretKey},
|
||||||
use secp256k1::{Message as SecpMessage, RecoverableSignature, RecoveryId, Error as SecpError};
|
Error as SecpError, Message as SecpMessage, RecoverableSignature, RecoveryId,
|
||||||
use secp256k1::key::{SecretKey, PublicKey};
|
};
|
||||||
use rustc_hex::{ToHex, FromHex};
|
use std::{
|
||||||
use ethereum_types::{H520, H256};
|
cmp::PartialEq,
|
||||||
use {Secret, Public, SECP256K1, Error, Message, public_to_address, Address};
|
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
|
/// Signature encoded as RSV components
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Signature([u8; 65]);
|
pub struct Signature([u8; 65]);
|
||||||
|
|
||||||
impl Signature {
|
impl Signature {
|
||||||
/// Get a slice into the 'r' portion of the data.
|
/// Get a slice into the 'r' portion of the data.
|
||||||
pub fn r(&self) -> &[u8] {
|
pub fn r(&self) -> &[u8] {
|
||||||
&self.0[0..32]
|
&self.0[0..32]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a slice into the 's' portion of the data.
|
/// Get a slice into the 's' portion of the data.
|
||||||
pub fn s(&self) -> &[u8] {
|
pub fn s(&self) -> &[u8] {
|
||||||
&self.0[32..64]
|
&self.0[32..64]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the recovery byte.
|
/// Get the recovery byte.
|
||||||
pub fn v(&self) -> u8 {
|
pub fn v(&self) -> u8 {
|
||||||
self.0[64]
|
self.0[64]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encode the signature into RSV array (V altered to be in "Electrum" notation).
|
/// Encode the signature into RSV array (V altered to be in "Electrum" notation).
|
||||||
pub fn into_electrum(mut self) -> [u8; 65] {
|
pub fn into_electrum(mut self) -> [u8; 65] {
|
||||||
self.0[64] += 27;
|
self.0[64] += 27;
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse bytes as a signature encoded as RSV (V in "Electrum" notation).
|
/// Parse bytes as a signature encoded as RSV (V in "Electrum" notation).
|
||||||
/// May return empty (invalid) signature if given data has invalid length.
|
/// May return empty (invalid) signature if given data has invalid length.
|
||||||
pub fn from_electrum(data: &[u8]) -> Self {
|
pub fn from_electrum(data: &[u8]) -> Self {
|
||||||
if data.len() != 65 || data[64] < 27 {
|
if data.len() != 65 || data[64] < 27 {
|
||||||
// fallback to empty (invalid) signature
|
// fallback to empty (invalid) signature
|
||||||
return Signature::default();
|
return Signature::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut sig = [0u8; 65];
|
let mut sig = [0u8; 65];
|
||||||
sig.copy_from_slice(data);
|
sig.copy_from_slice(data);
|
||||||
sig[64] -= 27;
|
sig[64] -= 27;
|
||||||
Signature(sig)
|
Signature(sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a signature object from the sig.
|
/// Create a signature object from the sig.
|
||||||
pub fn from_rsv(r: &H256, s: &H256, v: u8) -> Self {
|
pub fn from_rsv(r: &H256, s: &H256, v: u8) -> Self {
|
||||||
let mut sig = [0u8; 65];
|
let mut sig = [0u8; 65];
|
||||||
sig[0..32].copy_from_slice(&r);
|
sig[0..32].copy_from_slice(&r);
|
||||||
sig[32..64].copy_from_slice(&s);
|
sig[32..64].copy_from_slice(&s);
|
||||||
sig[64] = v;
|
sig[64] = v;
|
||||||
Signature(sig)
|
Signature(sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if this is a "low" signature.
|
/// Check if this is a "low" signature.
|
||||||
pub fn is_low_s(&self) -> bool {
|
pub fn is_low_s(&self) -> bool {
|
||||||
H256::from_slice(self.s()) <= "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0".into()
|
H256::from_slice(self.s())
|
||||||
}
|
<= "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0".into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if each component of the signature is in range.
|
/// Check if each component of the signature is in range.
|
||||||
pub fn is_valid(&self) -> bool {
|
pub fn is_valid(&self) -> bool {
|
||||||
self.v() <= 1 &&
|
self.v() <= 1
|
||||||
H256::from_slice(self.r()) < "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141".into() &&
|
&& H256::from_slice(self.r())
|
||||||
H256::from_slice(self.r()) >= 1.into() &&
|
< "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141".into()
|
||||||
H256::from_slice(self.s()) < "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141".into() &&
|
&& H256::from_slice(self.r()) >= 1.into()
|
||||||
H256::from_slice(self.s()) >= 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.
|
// manual implementation large arrays don't have trait impls by default.
|
||||||
// remove when integer generics exist
|
// remove when integer generics exist
|
||||||
impl PartialEq for Signature {
|
impl PartialEq for Signature {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
&self.0[..] == &other.0[..]
|
&self.0[..] == &other.0[..]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// manual implementation required in Rust 1.13+, see `std::cmp::AssertParamIsEq`.
|
// manual implementation required in Rust 1.13+, see `std::cmp::AssertParamIsEq`.
|
||||||
impl Eq for Signature { }
|
impl Eq for Signature {}
|
||||||
|
|
||||||
// also manual for the same reason, but the pretty printing might be useful.
|
// also manual for the same reason, but the pretty printing might be useful.
|
||||||
impl fmt::Debug for Signature {
|
impl fmt::Debug for Signature {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
f.debug_struct("Signature")
|
f.debug_struct("Signature")
|
||||||
.field("r", &self.0[0..32].to_hex())
|
.field("r", &self.0[0..32].to_hex())
|
||||||
.field("s", &self.0[32..64].to_hex())
|
.field("s", &self.0[32..64].to_hex())
|
||||||
.field("v", &self.0[64..65].to_hex())
|
.field("v", &self.0[64..65].to_hex())
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Signature {
|
impl fmt::Display for Signature {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
write!(f, "{}", self.to_hex())
|
write!(f, "{}", self.to_hex())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Signature {
|
impl FromStr for Signature {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s.from_hex() {
|
match s.from_hex() {
|
||||||
Ok(ref hex) if hex.len() == 65 => {
|
Ok(ref hex) if hex.len() == 65 => {
|
||||||
let mut data = [0; 65];
|
let mut data = [0; 65];
|
||||||
data.copy_from_slice(&hex[0..65]);
|
data.copy_from_slice(&hex[0..65]);
|
||||||
Ok(Signature(data))
|
Ok(Signature(data))
|
||||||
},
|
}
|
||||||
_ => Err(Error::InvalidSignature)
|
_ => Err(Error::InvalidSignature),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Signature {
|
impl Default for Signature {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Signature([0; 65])
|
Signature([0; 65])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for Signature {
|
impl Hash for Signature {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
H520::from(self.0).hash(state);
|
H520::from(self.0).hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Signature {
|
impl Clone for Signature {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Signature(self.0)
|
Signature(self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<[u8; 65]> for Signature {
|
impl From<[u8; 65]> for Signature {
|
||||||
fn from(s: [u8; 65]) -> Self {
|
fn from(s: [u8; 65]) -> Self {
|
||||||
Signature(s)
|
Signature(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<[u8; 65]> for Signature {
|
impl Into<[u8; 65]> for Signature {
|
||||||
fn into(self) -> [u8; 65] {
|
fn into(self) -> [u8; 65] {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Signature> for H520 {
|
impl From<Signature> for H520 {
|
||||||
fn from(s: Signature) -> Self {
|
fn from(s: Signature) -> Self {
|
||||||
H520::from(s.0)
|
H520::from(s.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<H520> for Signature {
|
impl From<H520> for Signature {
|
||||||
fn from(bytes: H520) -> Self {
|
fn from(bytes: H520) -> Self {
|
||||||
Signature(bytes.into())
|
Signature(bytes.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Signature {
|
impl Deref for Signature {
|
||||||
type Target = [u8; 65];
|
type Target = [u8; 65];
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for Signature {
|
impl DerefMut for Signature {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sign(secret: &Secret, message: &Message) -> Result<Signature, Error> {
|
pub fn sign(secret: &Secret, message: &Message) -> Result<Signature, Error> {
|
||||||
let context = &SECP256K1;
|
let context = &SECP256K1;
|
||||||
let sec = SecretKey::from_slice(context, &secret)?;
|
let sec = SecretKey::from_slice(context, &secret)?;
|
||||||
let s = context.sign_recoverable(&SecpMessage::from_slice(&message[..])?, &sec)?;
|
let s = context.sign_recoverable(&SecpMessage::from_slice(&message[..])?, &sec)?;
|
||||||
let (rec_id, data) = s.serialize_compact(context);
|
let (rec_id, data) = s.serialize_compact(context);
|
||||||
let mut data_arr = [0; 65];
|
let mut data_arr = [0; 65];
|
||||||
|
|
||||||
// no need to check if s is low, it always is
|
// no need to check if s is low, it always is
|
||||||
data_arr[0..64].copy_from_slice(&data[0..64]);
|
data_arr[0..64].copy_from_slice(&data[0..64]);
|
||||||
data_arr[64] = rec_id.to_i32() as u8;
|
data_arr[64] = rec_id.to_i32() as u8;
|
||||||
Ok(Signature(data_arr))
|
Ok(Signature(data_arr))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_public(public: &Public, signature: &Signature, message: &Message) -> Result<bool, Error> {
|
pub fn verify_public(
|
||||||
let context = &SECP256K1;
|
public: &Public,
|
||||||
let rsig = RecoverableSignature::from_compact(context, &signature[0..64], RecoveryId::from_i32(signature[64] as i32)?)?;
|
signature: &Signature,
|
||||||
let sig = rsig.to_standard(context);
|
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 pdata: [u8; 65] = {
|
||||||
let mut temp = [4u8; 65];
|
let mut temp = [4u8; 65];
|
||||||
temp[1..65].copy_from_slice(&**public);
|
temp[1..65].copy_from_slice(&**public);
|
||||||
temp
|
temp
|
||||||
};
|
};
|
||||||
|
|
||||||
let publ = PublicKey::from_slice(context, &pdata)?;
|
let publ = PublicKey::from_slice(context, &pdata)?;
|
||||||
match context.verify(&SecpMessage::from_slice(&message[..])?, &sig, &publ) {
|
match context.verify(&SecpMessage::from_slice(&message[..])?, &sig, &publ) {
|
||||||
Ok(_) => Ok(true),
|
Ok(_) => Ok(true),
|
||||||
Err(SecpError::IncorrectSignature) => Ok(false),
|
Err(SecpError::IncorrectSignature) => Ok(false),
|
||||||
Err(x) => Err(Error::from(x))
|
Err(x) => Err(Error::from(x)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_address(address: &Address, signature: &Signature, message: &Message) -> Result<bool, Error> {
|
pub fn verify_address(
|
||||||
let public = recover(signature, message)?;
|
address: &Address,
|
||||||
let recovered_address = public_to_address(&public);
|
signature: &Signature,
|
||||||
Ok(address == &recovered_address)
|
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> {
|
pub fn recover(signature: &Signature, message: &Message) -> Result<Public, Error> {
|
||||||
let context = &SECP256K1;
|
let context = &SECP256K1;
|
||||||
let rsig = RecoverableSignature::from_compact(context, &signature[0..64], RecoveryId::from_i32(signature[64] as i32)?)?;
|
let rsig = RecoverableSignature::from_compact(
|
||||||
let pubkey = context.recover(&SecpMessage::from_slice(&message[..])?, &rsig)?;
|
context,
|
||||||
let serialized = pubkey.serialize_vec(context, false);
|
&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();
|
let mut public = Public::default();
|
||||||
public.copy_from_slice(&serialized[1..65]);
|
public.copy_from_slice(&serialized[1..65]);
|
||||||
Ok(public)
|
Ok(public)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::str::FromStr;
|
use super::{recover, sign, verify_address, verify_public, Signature};
|
||||||
use {Generator, Random, Message};
|
use std::str::FromStr;
|
||||||
use super::{sign, verify_public, verify_address, recover, Signature};
|
use Generator;
|
||||||
|
use Message;
|
||||||
|
use Random;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn vrs_conversion() {
|
fn vrs_conversion() {
|
||||||
// given
|
// given
|
||||||
let keypair = Random.generate().unwrap();
|
let keypair = Random.generate().unwrap();
|
||||||
let message = Message::default();
|
let message = Message::default();
|
||||||
let signature = sign(keypair.secret(), &message).unwrap();
|
let signature = sign(keypair.secret(), &message).unwrap();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let vrs = signature.clone().into_electrum();
|
let vrs = signature.clone().into_electrum();
|
||||||
let from_vrs = Signature::from_electrum(&vrs);
|
let from_vrs = Signature::from_electrum(&vrs);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert_eq!(signature, from_vrs);
|
assert_eq!(signature, from_vrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn signature_to_and_from_str() {
|
fn signature_to_and_from_str() {
|
||||||
let keypair = Random.generate().unwrap();
|
let keypair = Random.generate().unwrap();
|
||||||
let message = Message::default();
|
let message = Message::default();
|
||||||
let signature = sign(keypair.secret(), &message).unwrap();
|
let signature = sign(keypair.secret(), &message).unwrap();
|
||||||
let string = format!("{}", signature);
|
let string = format!("{}", signature);
|
||||||
let deserialized = Signature::from_str(&string).unwrap();
|
let deserialized = Signature::from_str(&string).unwrap();
|
||||||
assert_eq!(signature, deserialized);
|
assert_eq!(signature, deserialized);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sign_and_recover_public() {
|
fn sign_and_recover_public() {
|
||||||
let keypair = Random.generate().unwrap();
|
let keypair = Random.generate().unwrap();
|
||||||
let message = Message::default();
|
let message = Message::default();
|
||||||
let signature = sign(keypair.secret(), &message).unwrap();
|
let signature = sign(keypair.secret(), &message).unwrap();
|
||||||
assert_eq!(keypair.public(), &recover(&signature, &message).unwrap());
|
assert_eq!(keypair.public(), &recover(&signature, &message).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sign_and_verify_public() {
|
fn sign_and_verify_public() {
|
||||||
let keypair = Random.generate().unwrap();
|
let keypair = Random.generate().unwrap();
|
||||||
let message = Message::default();
|
let message = Message::default();
|
||||||
let signature = sign(keypair.secret(), &message).unwrap();
|
let signature = sign(keypair.secret(), &message).unwrap();
|
||||||
assert!(verify_public(keypair.public(), &signature, &message).unwrap());
|
assert!(verify_public(keypair.public(), &signature, &message).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sign_and_verify_address() {
|
fn sign_and_verify_address() {
|
||||||
let keypair = Random.generate().unwrap();
|
let keypair = Random.generate().unwrap();
|
||||||
let message = Message::default();
|
let message = Message::default();
|
||||||
let signature = sign(keypair.secret(), &message).unwrap();
|
let signature = sign(keypair.secret(), &message).unwrap();
|
||||||
assert!(verify_address(&keypair.address(), &signature, &message).unwrap());
|
assert!(verify_address(&keypair.address(), &signature, &message).unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
|
description = "Parity Ethereum Key Management"
|
||||||
name = "ethstore"
|
name = "ethstore"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
@@ -12,15 +13,13 @@ 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"
|
||||||
tiny-keccak = "1.4"
|
|
||||||
time = "0.1.34"
|
time = "0.1.34"
|
||||||
itertools = "0.5"
|
itertools = "0.5"
|
||||||
parking_lot = "0.7"
|
parking_lot = "0.7"
|
||||||
parity-crypto = "0.3.0"
|
parity-crypto = "0.3.0"
|
||||||
ethereum-types = "0.4"
|
ethereum-types = "0.4"
|
||||||
dir = { path = "../../util/dir" }
|
|
||||||
smallvec = "0.6"
|
smallvec = "0.6"
|
||||||
parity-wordlist = "1.0"
|
parity-wordlist = "1.3"
|
||||||
tempdir = "0.3"
|
tempdir = "0.3"
|
||||||
lazy_static = "1.2.0"
|
lazy_static = "1.2.0"
|
||||||
|
|
||||||
|
|||||||
@@ -337,4 +337,3 @@ _This project is a part of the Parity Ethereum toolchain._
|
|||||||
- [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum function calls encoding.
|
- [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.
|
- [ethstore](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethstore) - Parity Ethereum key management.
|
||||||
- [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator.
|
- [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator.
|
||||||
- [whisper](https://github.com/paritytech/parity-ethereum/blob/master/whisper/) - Implementation of Whisper-v2 PoC.
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
|
description = "Parity Ethereum Key Management CLI"
|
||||||
name = "ethstore-cli"
|
name = "ethstore-cli"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
|||||||
@@ -1,66 +1,66 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::{cmp, thread};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use std::{cmp, collections::VecDeque, sync::Arc, thread};
|
||||||
|
|
||||||
use ethstore::{ethkey::Password, PresaleWallet, Error};
|
use ethstore::{ethkey::Password, Error, PresaleWallet};
|
||||||
use num_cpus;
|
use num_cpus;
|
||||||
|
|
||||||
pub fn run(passwords: VecDeque<Password>, wallet_path: &str) -> Result<(), Error> {
|
pub fn run(passwords: VecDeque<Password>, wallet_path: &str) -> Result<(), Error> {
|
||||||
let passwords = Arc::new(Mutex::new(passwords));
|
let passwords = Arc::new(Mutex::new(passwords));
|
||||||
|
|
||||||
let mut handles = Vec::new();
|
let mut handles = Vec::new();
|
||||||
|
|
||||||
for _ in 0..num_cpus::get() {
|
for _ in 0..num_cpus::get() {
|
||||||
let passwords = passwords.clone();
|
let passwords = passwords.clone();
|
||||||
let wallet = PresaleWallet::open(&wallet_path)?;
|
let wallet = PresaleWallet::open(&wallet_path)?;
|
||||||
handles.push(thread::spawn(move || {
|
handles.push(thread::spawn(move || {
|
||||||
look_for_password(passwords, wallet);
|
look_for_password(passwords, wallet);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
for handle in handles {
|
for handle in handles {
|
||||||
handle.join().map_err(|err| Error::Custom(format!("Error finishing thread: {:?}", err)))?;
|
handle
|
||||||
}
|
.join()
|
||||||
|
.map_err(|err| Error::Custom(format!("Error finishing thread: {:?}", err)))?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn look_for_password(passwords: Arc<Mutex<VecDeque<Password>>>, wallet: PresaleWallet) {
|
fn look_for_password(passwords: Arc<Mutex<VecDeque<Password>>>, wallet: PresaleWallet) {
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
while !passwords.lock().is_empty() {
|
while !passwords.lock().is_empty() {
|
||||||
let package = {
|
let package = {
|
||||||
let mut passwords = passwords.lock();
|
let mut passwords = passwords.lock();
|
||||||
let len = passwords.len();
|
let len = passwords.len();
|
||||||
passwords.split_off(cmp::min(len, 32))
|
passwords.split_off(cmp::min(len, 32))
|
||||||
};
|
};
|
||||||
for pass in package {
|
for pass in package {
|
||||||
counter += 1;
|
counter += 1;
|
||||||
match wallet.decrypt(&pass) {
|
match wallet.decrypt(&pass) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
println!("Found password: {}", pass.as_str());
|
println!("Found password: {}", pass.as_str());
|
||||||
passwords.lock().clear();
|
passwords.lock().clear();
|
||||||
return;
|
return;
|
||||||
},
|
}
|
||||||
_ if counter % 100 == 0 => print!("."),
|
_ if counter % 100 == 0 => print!("."),
|
||||||
_ => {},
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
extern crate dir;
|
extern crate dir;
|
||||||
extern crate docopt;
|
extern crate docopt;
|
||||||
@@ -28,14 +28,15 @@ extern crate env_logger;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
use std::{collections::VecDeque, env, fmt, fs, io::Read, process};
|
||||||
use std::io::Read;
|
|
||||||
use std::{env, process, fs, fmt};
|
|
||||||
|
|
||||||
use docopt::Docopt;
|
use docopt::Docopt;
|
||||||
use ethstore::accounts_dir::{KeyDirectory, RootDiskDirectory};
|
use ethstore::{
|
||||||
use ethstore::ethkey::{Address, Password};
|
accounts_dir::{KeyDirectory, RootDiskDirectory},
|
||||||
use ethstore::{EthStore, SimpleSecretStore, SecretStore, import_accounts, PresaleWallet, SecretVaultRef, StoreAccountRef};
|
ethkey::{Address, Password},
|
||||||
|
import_accounts, EthStore, PresaleWallet, SecretStore, SecretVaultRef, SimpleSecretStore,
|
||||||
|
StoreAccountRef,
|
||||||
|
};
|
||||||
|
|
||||||
mod crack;
|
mod crack;
|
||||||
|
|
||||||
@@ -92,226 +93,271 @@ Commands:
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct Args {
|
struct Args {
|
||||||
cmd_insert: bool,
|
cmd_insert: bool,
|
||||||
cmd_change_pwd: bool,
|
cmd_change_pwd: bool,
|
||||||
cmd_list: bool,
|
cmd_list: bool,
|
||||||
cmd_import: bool,
|
cmd_import: bool,
|
||||||
cmd_import_wallet: bool,
|
cmd_import_wallet: bool,
|
||||||
cmd_find_wallet_pass: bool,
|
cmd_find_wallet_pass: bool,
|
||||||
cmd_remove: bool,
|
cmd_remove: bool,
|
||||||
cmd_sign: bool,
|
cmd_sign: bool,
|
||||||
cmd_public: bool,
|
cmd_public: bool,
|
||||||
cmd_list_vaults: bool,
|
cmd_list_vaults: bool,
|
||||||
cmd_create_vault: bool,
|
cmd_create_vault: bool,
|
||||||
cmd_change_vault_pwd: bool,
|
cmd_change_vault_pwd: bool,
|
||||||
cmd_move_to_vault: bool,
|
cmd_move_to_vault: bool,
|
||||||
cmd_move_from_vault: bool,
|
cmd_move_from_vault: bool,
|
||||||
arg_secret: String,
|
arg_secret: String,
|
||||||
arg_password: String,
|
arg_password: String,
|
||||||
arg_old_pwd: String,
|
arg_old_pwd: String,
|
||||||
arg_new_pwd: String,
|
arg_new_pwd: String,
|
||||||
arg_address: String,
|
arg_address: String,
|
||||||
arg_message: String,
|
arg_message: String,
|
||||||
arg_path: String,
|
arg_path: String,
|
||||||
arg_vault: String,
|
arg_vault: String,
|
||||||
flag_src: String,
|
flag_src: String,
|
||||||
flag_dir: String,
|
flag_dir: String,
|
||||||
flag_vault: String,
|
flag_vault: String,
|
||||||
flag_vault_pwd: String,
|
flag_vault_pwd: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Error {
|
enum Error {
|
||||||
Ethstore(ethstore::Error),
|
Ethstore(ethstore::Error),
|
||||||
Docopt(docopt::Error),
|
Docopt(docopt::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ethstore::Error> for Error {
|
impl From<ethstore::Error> for Error {
|
||||||
fn from(err: ethstore::Error) -> Self {
|
fn from(err: ethstore::Error) -> Self {
|
||||||
Error::Ethstore(err)
|
Error::Ethstore(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<docopt::Error> for Error {
|
impl From<docopt::Error> for Error {
|
||||||
fn from(err: docopt::Error) -> Self {
|
fn from(err: docopt::Error) -> Self {
|
||||||
Error::Docopt(err)
|
Error::Docopt(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
Error::Ethstore(ref err) => fmt::Display::fmt(err, f),
|
Error::Ethstore(ref err) => fmt::Display::fmt(err, f),
|
||||||
Error::Docopt(ref err) => fmt::Display::fmt(err, f),
|
Error::Docopt(ref err) => fmt::Display::fmt(err, f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
panic_hook::set_abort();
|
panic_hook::set_abort();
|
||||||
if env::var("RUST_LOG").is_err() {
|
if env::var("RUST_LOG").is_err() {
|
||||||
env::set_var("RUST_LOG", "warn")
|
env::set_var("RUST_LOG", "warn")
|
||||||
}
|
}
|
||||||
env_logger::try_init().expect("Logger initialized only once.");
|
env_logger::try_init().expect("Logger initialized only once.");
|
||||||
|
|
||||||
match execute(env::args()) {
|
match execute(env::args()) {
|
||||||
Ok(result) => println!("{}", result),
|
Ok(result) => println!("{}", result),
|
||||||
Err(Error::Docopt(ref e)) => e.exit(),
|
Err(Error::Docopt(ref e)) => e.exit(),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("{}", err);
|
eprintln!("{}", err);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_dir(location: &str, password: Option<Password>) -> Result<Box<KeyDirectory>, Error> {
|
fn key_dir(location: &str, password: Option<Password>) -> Result<Box<dyn KeyDirectory>, Error> {
|
||||||
let dir: RootDiskDirectory = match location {
|
let dir: RootDiskDirectory = match location {
|
||||||
"geth" => RootDiskDirectory::create(dir::geth(false))?,
|
path if path.starts_with("parity") => {
|
||||||
"geth-test" => RootDiskDirectory::create(dir::geth(true))?,
|
let chain = path.split('-').nth(1).unwrap_or("ethereum");
|
||||||
path if path.starts_with("parity") => {
|
let mut path = dir::default_data_pathbuf();
|
||||||
let chain = path.split('-').nth(1).unwrap_or("ethereum");
|
path.push("keys");
|
||||||
let path = dir::parity(chain);
|
path.push(chain);
|
||||||
RootDiskDirectory::create(path)?
|
RootDiskDirectory::create(path)?
|
||||||
},
|
}
|
||||||
path => RootDiskDirectory::create(path)?,
|
path => RootDiskDirectory::create(path)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Box::new(dir.with_password(password)))
|
Ok(Box::new(dir.with_password(password)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_args_vault(store: &EthStore, args: &Args) -> Result<SecretVaultRef, Error> {
|
fn open_args_vault(store: &EthStore, args: &Args) -> Result<SecretVaultRef, Error> {
|
||||||
if args.flag_vault.is_empty() {
|
if args.flag_vault.is_empty() {
|
||||||
return Ok(SecretVaultRef::Root);
|
return Ok(SecretVaultRef::Root);
|
||||||
}
|
}
|
||||||
|
|
||||||
let vault_pwd = load_password(&args.flag_vault_pwd)?;
|
let vault_pwd = load_password(&args.flag_vault_pwd)?;
|
||||||
store.open_vault(&args.flag_vault, &vault_pwd)?;
|
store.open_vault(&args.flag_vault, &vault_pwd)?;
|
||||||
Ok(SecretVaultRef::Vault(args.flag_vault.clone()))
|
Ok(SecretVaultRef::Vault(args.flag_vault.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_args_vault_account(store: &EthStore, address: Address, args: &Args) -> Result<StoreAccountRef, Error> {
|
fn open_args_vault_account(
|
||||||
match open_args_vault(store, args)? {
|
store: &EthStore,
|
||||||
SecretVaultRef::Root => Ok(StoreAccountRef::root(address)),
|
address: Address,
|
||||||
SecretVaultRef::Vault(name) => Ok(StoreAccountRef::vault(&name, 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 {
|
fn format_accounts(accounts: &[Address]) -> String {
|
||||||
accounts.iter()
|
accounts
|
||||||
.enumerate()
|
.iter()
|
||||||
.map(|(i, a)| format!("{:2}: 0x{:x}", i, a))
|
.enumerate()
|
||||||
.collect::<Vec<String>>()
|
.map(|(i, a)| format!("{:2}: 0x{:x}", i, a))
|
||||||
.join("\n")
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_vaults(vaults: &[String]) -> String {
|
fn format_vaults(vaults: &[String]) -> String {
|
||||||
vaults.join("\n")
|
vaults.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_password(path: &str) -> Result<Password, Error> {
|
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 file = fs::File::open(path).map_err(|e| {
|
||||||
let mut password = String::new();
|
ethstore::Error::Custom(format!("Error opening password file '{}': {}", path, e))
|
||||||
file.read_to_string(&mut password).map_err(|e| ethstore::Error::Custom(format!("Error reading password file '{}': {}", path, e)))?;
|
})?;
|
||||||
// drop EOF
|
let mut password = String::new();
|
||||||
let _ = password.pop();
|
file.read_to_string(&mut password).map_err(|e| {
|
||||||
Ok(password.into())
|
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> {
|
fn execute<S, I>(command: I) -> Result<String, Error>
|
||||||
let args: Args = Docopt::new(USAGE)
|
where
|
||||||
.and_then(|d| d.argv(command).deserialize())?;
|
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)?)?;
|
let store = EthStore::open(key_dir(&args.flag_dir, None)?)?;
|
||||||
|
|
||||||
return if args.cmd_insert {
|
return if args.cmd_insert {
|
||||||
let secret = args.arg_secret.parse().map_err(|_| ethstore::Error::InvalidSecret)?;
|
let secret = args
|
||||||
let password = load_password(&args.arg_password)?;
|
.arg_secret
|
||||||
let vault_ref = open_args_vault(&store, &args)?;
|
.parse()
|
||||||
let account_ref = store.insert_account(vault_ref, secret, &password)?;
|
.map_err(|_| ethstore::Error::InvalidSecret)?;
|
||||||
Ok(format!("0x{:x}", account_ref.address))
|
let password = load_password(&args.arg_password)?;
|
||||||
} else if args.cmd_change_pwd {
|
let vault_ref = open_args_vault(&store, &args)?;
|
||||||
let address = args.arg_address.parse().map_err(|_| ethstore::Error::InvalidAccount)?;
|
let account_ref = store.insert_account(vault_ref, secret, &password)?;
|
||||||
let old_pwd = load_password(&args.arg_old_pwd)?;
|
Ok(format!("0x{:x}", account_ref.address))
|
||||||
let new_pwd = load_password(&args.arg_new_pwd)?;
|
} else if args.cmd_change_pwd {
|
||||||
let account_ref = open_args_vault_account(&store, address, &args)?;
|
let address = args
|
||||||
let ok = store.change_password(&account_ref, &old_pwd, &new_pwd).is_ok();
|
.arg_address
|
||||||
Ok(format!("{}", ok))
|
.parse()
|
||||||
} else if args.cmd_list {
|
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||||
let vault_ref = open_args_vault(&store, &args)?;
|
let old_pwd = load_password(&args.arg_old_pwd)?;
|
||||||
let accounts = store.accounts()?;
|
let new_pwd = load_password(&args.arg_new_pwd)?;
|
||||||
let accounts: Vec<_> = accounts
|
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||||
.into_iter()
|
let ok = store
|
||||||
.filter(|a| &a.vault == &vault_ref)
|
.change_password(&account_ref, &old_pwd, &new_pwd)
|
||||||
.map(|a| a.address)
|
.is_ok();
|
||||||
.collect();
|
Ok(format!("{}", ok))
|
||||||
Ok(format_accounts(&accounts))
|
} else if args.cmd_list {
|
||||||
} else if args.cmd_import {
|
let vault_ref = open_args_vault(&store, &args)?;
|
||||||
let password = match args.arg_password.as_ref() {
|
let accounts = store.accounts()?;
|
||||||
"" => None,
|
let accounts: Vec<_> = accounts
|
||||||
_ => Some(load_password(&args.arg_password)?)
|
.into_iter()
|
||||||
};
|
.filter(|a| &a.vault == &vault_ref)
|
||||||
let src = key_dir(&args.flag_src, password)?;
|
.map(|a| a.address)
|
||||||
let dst = key_dir(&args.flag_dir, None)?;
|
.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)?;
|
let accounts = import_accounts(&*src, &*dst)?;
|
||||||
Ok(format_accounts(&accounts))
|
Ok(format_accounts(&accounts))
|
||||||
} else if args.cmd_import_wallet {
|
} else if args.cmd_import_wallet {
|
||||||
let wallet = PresaleWallet::open(&args.arg_path)?;
|
let wallet = PresaleWallet::open(&args.arg_path)?;
|
||||||
let password = load_password(&args.arg_password)?;
|
let password = load_password(&args.arg_password)?;
|
||||||
let kp = wallet.decrypt(&password)?;
|
let kp = wallet.decrypt(&password)?;
|
||||||
let vault_ref = open_args_vault(&store, &args)?;
|
let vault_ref = open_args_vault(&store, &args)?;
|
||||||
let account_ref = store.insert_account(vault_ref, kp.secret().clone(), &password)?;
|
let account_ref = store.insert_account(vault_ref, kp.secret().clone(), &password)?;
|
||||||
Ok(format!("0x{:x}", account_ref.address))
|
Ok(format!("0x{:x}", account_ref.address))
|
||||||
} else if args.cmd_find_wallet_pass {
|
} else if args.cmd_find_wallet_pass {
|
||||||
let passwords = load_password(&args.arg_password)?;
|
let passwords = load_password(&args.arg_password)?;
|
||||||
let passwords = passwords.as_str().lines().map(|line| str::to_owned(line).into()).collect::<VecDeque<_>>();
|
let passwords = passwords
|
||||||
crack::run(passwords, &args.arg_path)?;
|
.as_str()
|
||||||
Ok(format!("Password not found."))
|
.lines()
|
||||||
} else if args.cmd_remove {
|
.map(|line| str::to_owned(line).into())
|
||||||
let address = args.arg_address.parse().map_err(|_| ethstore::Error::InvalidAccount)?;
|
.collect::<VecDeque<_>>();
|
||||||
let password = load_password(&args.arg_password)?;
|
crack::run(passwords, &args.arg_path)?;
|
||||||
let account_ref = open_args_vault_account(&store, address, &args)?;
|
Ok(format!("Password not found."))
|
||||||
let ok = store.remove_account(&account_ref, &password).is_ok();
|
} else if args.cmd_remove {
|
||||||
Ok(format!("{}", ok))
|
let address = args
|
||||||
} else if args.cmd_sign {
|
.arg_address
|
||||||
let address = args.arg_address.parse().map_err(|_| ethstore::Error::InvalidAccount)?;
|
.parse()
|
||||||
let message = args.arg_message.parse().map_err(|_| ethstore::Error::InvalidMessage)?;
|
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||||
let password = load_password(&args.arg_password)?;
|
let password = load_password(&args.arg_password)?;
|
||||||
let account_ref = open_args_vault_account(&store, address, &args)?;
|
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||||
let signature = store.sign(&account_ref, &password, &message)?;
|
let ok = store.remove_account(&account_ref, &password).is_ok();
|
||||||
Ok(format!("0x{}", signature))
|
Ok(format!("{}", ok))
|
||||||
} else if args.cmd_public {
|
} else if args.cmd_sign {
|
||||||
let address = args.arg_address.parse().map_err(|_| ethstore::Error::InvalidAccount)?;
|
let address = args
|
||||||
let password = load_password(&args.arg_password)?;
|
.arg_address
|
||||||
let account_ref = open_args_vault_account(&store, address, &args)?;
|
.parse()
|
||||||
let public = store.public(&account_ref, &password)?;
|
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||||
Ok(format!("0x{:x}", public))
|
let message = args
|
||||||
} else if args.cmd_list_vaults {
|
.arg_message
|
||||||
let vaults = store.list_vaults()?;
|
.parse()
|
||||||
Ok(format_vaults(&vaults))
|
.map_err(|_| ethstore::Error::InvalidMessage)?;
|
||||||
} else if args.cmd_create_vault {
|
let password = load_password(&args.arg_password)?;
|
||||||
let password = load_password(&args.arg_password)?;
|
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||||
store.create_vault(&args.arg_vault, &password)?;
|
let signature = store.sign(&account_ref, &password, &message)?;
|
||||||
Ok("OK".to_owned())
|
Ok(format!("0x{}", signature))
|
||||||
} else if args.cmd_change_vault_pwd {
|
} else if args.cmd_public {
|
||||||
let old_pwd = load_password(&args.arg_old_pwd)?;
|
let address = args
|
||||||
let new_pwd = load_password(&args.arg_new_pwd)?;
|
.arg_address
|
||||||
store.open_vault(&args.arg_vault, &old_pwd)?;
|
.parse()
|
||||||
store.change_vault_password(&args.arg_vault, &new_pwd)?;
|
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||||
Ok("OK".to_owned())
|
let password = load_password(&args.arg_password)?;
|
||||||
} else if args.cmd_move_to_vault {
|
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||||
let address = args.arg_address.parse().map_err(|_| ethstore::Error::InvalidAccount)?;
|
let public = store.public(&account_ref, &password)?;
|
||||||
let password = load_password(&args.arg_password)?;
|
Ok(format!("0x{:x}", public))
|
||||||
let account_ref = open_args_vault_account(&store, address, &args)?;
|
} else if args.cmd_list_vaults {
|
||||||
store.open_vault(&args.arg_vault, &password)?;
|
let vaults = store.list_vaults()?;
|
||||||
store.change_account_vault(SecretVaultRef::Vault(args.arg_vault), account_ref)?;
|
Ok(format_vaults(&vaults))
|
||||||
Ok("OK".to_owned())
|
} else if args.cmd_create_vault {
|
||||||
} else if args.cmd_move_from_vault {
|
let password = load_password(&args.arg_password)?;
|
||||||
let address = args.arg_address.parse().map_err(|_| ethstore::Error::InvalidAccount)?;
|
store.create_vault(&args.arg_vault, &password)?;
|
||||||
let password = load_password(&args.arg_password)?;
|
Ok("OK".to_owned())
|
||||||
store.open_vault(&args.arg_vault, &password)?;
|
} else if args.cmd_change_vault_pwd {
|
||||||
store.change_account_vault(SecretVaultRef::Root, StoreAccountRef::vault(&args.arg_vault, address))?;
|
let old_pwd = load_password(&args.arg_old_pwd)?;
|
||||||
Ok("OK".to_owned())
|
let new_pwd = load_password(&args.arg_new_pwd)?;
|
||||||
} else {
|
store.open_vault(&args.arg_vault, &old_pwd)?;
|
||||||
Ok(format!("{}", USAGE))
|
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))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity Ethereum.
|
|
||||||
|
|
||||||
// Parity Ethereum 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.
|
|
||||||
|
|
||||||
// Parity Ethereum 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 Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
extern crate tempdir;
|
|
||||||
use std::process::Command;
|
|
||||||
use tempdir::TempDir;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
fn run(args: &[&str]) -> String {
|
|
||||||
let output = Command::new("cargo")
|
|
||||||
.args(&["run", "--"])
|
|
||||||
.args(args)
|
|
||||||
.output()
|
|
||||||
.unwrap();
|
|
||||||
assert!(output.status.success());
|
|
||||||
String::from_utf8(output.stdout).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn cli_cmd() {
|
|
||||||
Command::new("cargo")
|
|
||||||
.arg("build")
|
|
||||||
.output()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let dir = TempDir::new("test-vault").unwrap();
|
|
||||||
|
|
||||||
let mut passwd = File::create(dir.path().join("test-password")).unwrap();
|
|
||||||
writeln!(passwd, "password").unwrap();
|
|
||||||
|
|
||||||
let mut passwd2 = File::create(dir.path().join("test-vault-addr")).unwrap();
|
|
||||||
writeln!(passwd2, "password2").unwrap();
|
|
||||||
|
|
||||||
let test_password_buf = dir.path().join("test-password");
|
|
||||||
let test_password: &str = test_password_buf.to_str().unwrap();
|
|
||||||
let dir_str: &str = dir.path().to_str().unwrap();
|
|
||||||
let test_vault_addr_buf = dir.path().join("test-vault-addr");
|
|
||||||
let test_vault_addr = test_vault_addr_buf.to_str().unwrap();
|
|
||||||
|
|
||||||
run(&["create-vault", "test-vault", test_password, "--dir", dir_str]);
|
|
||||||
|
|
||||||
let output = run(&["insert", "7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5",
|
|
||||||
test_vault_addr,
|
|
||||||
"--dir", dir_str,
|
|
||||||
"--vault", "test-vault",
|
|
||||||
"--vault-pwd", test_password]);
|
|
||||||
let address = output.trim();
|
|
||||||
|
|
||||||
let output = run(&["list",
|
|
||||||
"--dir", dir_str,
|
|
||||||
"--vault", "test-vault",
|
|
||||||
"--vault-pwd", test_password]);
|
|
||||||
assert_eq!(output, " 0: 0xa8fa5dd30a87bb9e3288d604eb74949c515ab66e\n");
|
|
||||||
|
|
||||||
let output = run(&["sign", &address[2..],
|
|
||||||
test_vault_addr,
|
|
||||||
"7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5",
|
|
||||||
"--dir", dir_str,
|
|
||||||
"--vault", "test-vault",
|
|
||||||
"--vault-pwd", test_password]);
|
|
||||||
assert_eq!(output, "0x54ab6e5cf0c5cb40043fdca5d15d611a3a94285414a076dafecc8dc9c04183f413296a3defff61092c0bb478dc9887ec01070e1275234211208fb8f4be4a9b0101\n");
|
|
||||||
|
|
||||||
let output = run(&["public", &address[2..], test_vault_addr,
|
|
||||||
"--dir", dir_str,
|
|
||||||
"--vault", "test-vault",
|
|
||||||
"--vault-pwd", test_password]);
|
|
||||||
assert_eq!(output, "0x35f222d88b80151857a2877826d940104887376a94c1cbd2c8c7c192eb701df88a18a4ecb8b05b1466c5b3706042027b5e079fe3a3683e66d822b0e047aa3418\n");
|
|
||||||
}
|
|
||||||
@@ -1,59 +1,57 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. 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 struct Aes128Ctr {
|
pub struct Aes128Ctr {
|
||||||
pub iv: [u8; 16],
|
pub iv: [u8; 16],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum Cipher {
|
pub enum Cipher {
|
||||||
Aes128Ctr(Aes128Ctr),
|
Aes128Ctr(Aes128Ctr),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<json::Aes128Ctr> for Aes128Ctr {
|
impl From<json::Aes128Ctr> for Aes128Ctr {
|
||||||
fn from(json: json::Aes128Ctr) -> Self {
|
fn from(json: json::Aes128Ctr) -> Self {
|
||||||
Aes128Ctr {
|
Aes128Ctr { iv: json.iv.into() }
|
||||||
iv: json.iv.into()
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<json::Aes128Ctr> for Aes128Ctr {
|
impl Into<json::Aes128Ctr> for Aes128Ctr {
|
||||||
fn into(self) -> json::Aes128Ctr {
|
fn into(self) -> json::Aes128Ctr {
|
||||||
json::Aes128Ctr {
|
json::Aes128Ctr {
|
||||||
iv: From::from(self.iv)
|
iv: From::from(self.iv),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<json::Cipher> for Cipher {
|
impl From<json::Cipher> for Cipher {
|
||||||
fn from(json: json::Cipher) -> Self {
|
fn from(json: json::Cipher) -> Self {
|
||||||
match json {
|
match json {
|
||||||
json::Cipher::Aes128Ctr(params) => Cipher::Aes128Ctr(From::from(params)),
|
json::Cipher::Aes128Ctr(params) => Cipher::Aes128Ctr(From::from(params)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<json::Cipher> for Cipher {
|
impl Into<json::Cipher> for Cipher {
|
||||||
fn into(self) -> json::Cipher {
|
fn into(self) -> json::Cipher {
|
||||||
match self {
|
match self {
|
||||||
Cipher::Aes128Ctr(params) => json::Cipher::Aes128Ctr(params.into()),
|
Cipher::Aes128Ctr(params) => json::Cipher::Aes128Ctr(params.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,211 +1,234 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::str;
|
use account::{Aes128Ctr, Cipher, Kdf, Pbkdf2, Prf};
|
||||||
use std::num::NonZeroU32;
|
use crypto::{self, Keccak256};
|
||||||
use ethkey::{Password, Secret};
|
use ethkey::{Password, Secret};
|
||||||
use {json, Error, crypto};
|
use json;
|
||||||
use crypto::Keccak256;
|
|
||||||
use random::Random;
|
use random::Random;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use account::{Cipher, Kdf, Aes128Ctr, Pbkdf2, Prf};
|
use std::{num::NonZeroU32, str};
|
||||||
|
use Error;
|
||||||
|
|
||||||
/// Encrypted data
|
/// Encrypted data
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct Crypto {
|
pub struct Crypto {
|
||||||
/// Encryption parameters
|
/// Encryption parameters
|
||||||
pub cipher: Cipher,
|
pub cipher: Cipher,
|
||||||
/// Encrypted data buffer
|
/// Encrypted data buffer
|
||||||
pub ciphertext: Vec<u8>,
|
pub ciphertext: Vec<u8>,
|
||||||
/// Key derivation function parameters
|
/// Key derivation function parameters
|
||||||
pub kdf: Kdf,
|
pub kdf: Kdf,
|
||||||
/// Message authentication code
|
/// Message authentication code
|
||||||
pub mac: [u8; 32],
|
pub mac: [u8; 32],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<json::Crypto> for Crypto {
|
impl From<json::Crypto> for Crypto {
|
||||||
fn from(json: json::Crypto) -> Self {
|
fn from(json: json::Crypto) -> Self {
|
||||||
Crypto {
|
Crypto {
|
||||||
cipher: json.cipher.into(),
|
cipher: json.cipher.into(),
|
||||||
ciphertext: json.ciphertext.into(),
|
ciphertext: json.ciphertext.into(),
|
||||||
kdf: json.kdf.into(),
|
kdf: json.kdf.into(),
|
||||||
mac: json.mac.into(),
|
mac: json.mac.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Crypto> for json::Crypto {
|
impl From<Crypto> for json::Crypto {
|
||||||
fn from(c: Crypto) -> Self {
|
fn from(c: Crypto) -> Self {
|
||||||
json::Crypto {
|
json::Crypto {
|
||||||
cipher: c.cipher.into(),
|
cipher: c.cipher.into(),
|
||||||
ciphertext: c.ciphertext.into(),
|
ciphertext: c.ciphertext.into(),
|
||||||
kdf: c.kdf.into(),
|
kdf: c.kdf.into(),
|
||||||
mac: c.mac.into(),
|
mac: c.mac.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl str::FromStr for Crypto {
|
impl str::FromStr for Crypto {
|
||||||
type Err = <json::Crypto as str::FromStr>::Err;
|
type Err = <json::Crypto as str::FromStr>::Err;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
s.parse::<json::Crypto>().map(Into::into)
|
s.parse::<json::Crypto>().map(Into::into)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Crypto> for String {
|
impl From<Crypto> for String {
|
||||||
fn from(c: Crypto) -> Self {
|
fn from(c: Crypto) -> Self {
|
||||||
json::Crypto::from(c).into()
|
json::Crypto::from(c).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Crypto {
|
impl Crypto {
|
||||||
/// Encrypt account secret
|
/// Encrypt account secret
|
||||||
pub fn with_secret(secret: &Secret, password: &Password, iterations: NonZeroU32) -> Result<Self, crypto::Error> {
|
pub fn with_secret(
|
||||||
Crypto::with_plain(&*secret, password, iterations)
|
secret: &Secret,
|
||||||
}
|
password: &Password,
|
||||||
|
iterations: NonZeroU32,
|
||||||
|
) -> Result<Self, crypto::Error> {
|
||||||
|
Crypto::with_plain(&*secret, password, iterations)
|
||||||
|
}
|
||||||
|
|
||||||
/// Encrypt custom plain data
|
/// Encrypt custom plain data
|
||||||
pub fn with_plain(plain: &[u8], password: &Password, iterations: NonZeroU32) -> Result<Self, crypto::Error> {
|
pub fn with_plain(
|
||||||
let salt: [u8; 32] = Random::random();
|
plain: &[u8],
|
||||||
let iv: [u8; 16] = Random::random();
|
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
|
// two parts of derived key
|
||||||
// DK = [ DK[0..15] DK[16..31] ] = [derived_left_bits, derived_right_bits]
|
// DK = [ DK[0..15] DK[16..31] ] = [derived_left_bits, derived_right_bits]
|
||||||
let (derived_left_bits, derived_right_bits) =
|
let (derived_left_bits, derived_right_bits) =
|
||||||
crypto::derive_key_iterations(password.as_bytes(), &salt, iterations);
|
crypto::derive_key_iterations(password.as_bytes(), &salt, iterations);
|
||||||
|
|
||||||
// preallocated (on-stack in case of `Secret`) buffer to hold cipher
|
// preallocated (on-stack in case of `Secret`) buffer to hold cipher
|
||||||
// length = length(plain) as we are using CTR-approach
|
// length = length(plain) as we are using CTR-approach
|
||||||
let plain_len = plain.len();
|
let plain_len = plain.len();
|
||||||
let mut ciphertext: SmallVec<[u8; 32]> = SmallVec::from_vec(vec![0; plain_len]);
|
let mut ciphertext: SmallVec<[u8; 32]> = SmallVec::from_vec(vec![0; plain_len]);
|
||||||
|
|
||||||
// aes-128-ctr with initial vector of iv
|
// aes-128-ctr with initial vector of iv
|
||||||
crypto::aes::encrypt_128_ctr(&derived_left_bits, &iv, plain, &mut *ciphertext)?;
|
crypto::aes::encrypt_128_ctr(&derived_left_bits, &iv, plain, &mut *ciphertext)?;
|
||||||
|
|
||||||
// KECCAK(DK[16..31] ++ <ciphertext>), where DK[16..31] - derived_right_bits
|
// KECCAK(DK[16..31] ++ <ciphertext>), where DK[16..31] - derived_right_bits
|
||||||
let mac = crypto::derive_mac(&derived_right_bits, &*ciphertext).keccak256();
|
let mac = crypto::derive_mac(&derived_right_bits, &*ciphertext).keccak256();
|
||||||
|
|
||||||
Ok(Crypto {
|
Ok(Crypto {
|
||||||
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
cipher: Cipher::Aes128Ctr(Aes128Ctr { iv: iv }),
|
||||||
iv: iv,
|
ciphertext: ciphertext.into_vec(),
|
||||||
}),
|
kdf: Kdf::Pbkdf2(Pbkdf2 {
|
||||||
ciphertext: ciphertext.into_vec(),
|
dklen: crypto::KEY_LENGTH as u32,
|
||||||
kdf: Kdf::Pbkdf2(Pbkdf2 {
|
salt: salt.to_vec(),
|
||||||
dklen: crypto::KEY_LENGTH as u32,
|
c: iterations,
|
||||||
salt: salt.to_vec(),
|
prf: Prf::HmacSha256,
|
||||||
c: iterations,
|
}),
|
||||||
prf: Prf::HmacSha256,
|
mac: mac,
|
||||||
}),
|
})
|
||||||
mac: mac,
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to decrypt and convert result to account secret
|
/// Try to decrypt and convert result to account secret
|
||||||
pub fn secret(&self, password: &Password) -> Result<Secret, Error> {
|
pub fn secret(&self, password: &Password) -> Result<Secret, Error> {
|
||||||
if self.ciphertext.len() > 32 {
|
if self.ciphertext.len() > 32 {
|
||||||
return Err(Error::InvalidSecret);
|
return Err(Error::InvalidSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
let secret = self.do_decrypt(password, 32)?;
|
let secret = self.do_decrypt(password, 32)?;
|
||||||
Ok(Secret::from_unsafe_slice(&secret)?)
|
Ok(Secret::from_unsafe_slice(&secret)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to decrypt and return result as is
|
/// Try to decrypt and return result as is
|
||||||
pub fn decrypt(&self, password: &Password) -> Result<Vec<u8>, Error> {
|
pub fn decrypt(&self, password: &Password) -> Result<Vec<u8>, Error> {
|
||||||
let expected_len = self.ciphertext.len();
|
let expected_len = self.ciphertext.len();
|
||||||
self.do_decrypt(password, expected_len)
|
self.do_decrypt(password, expected_len)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_decrypt(&self, password: &Password, expected_len: usize) -> Result<Vec<u8>, Error> {
|
fn do_decrypt(&self, password: &Password, expected_len: usize) -> Result<Vec<u8>, Error> {
|
||||||
let (derived_left_bits, derived_right_bits) = match self.kdf {
|
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::Pbkdf2(ref params) => {
|
||||||
Kdf::Scrypt(ref params) => crypto::scrypt::derive_key(password.as_bytes(), ¶ms.salt, params.n, params.p, params.r)?,
|
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();
|
let mac = crypto::derive_mac(&derived_right_bits, &self.ciphertext).keccak256();
|
||||||
|
|
||||||
if !crypto::is_equal(&mac, &self.mac) {
|
if !crypto::is_equal(&mac, &self.mac) {
|
||||||
return Err(Error::InvalidPassword)
|
return Err(Error::InvalidPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut plain: SmallVec<[u8; 32]> = SmallVec::from_vec(vec![0; expected_len]);
|
let mut plain: SmallVec<[u8; 32]> = SmallVec::from_vec(vec![0; expected_len]);
|
||||||
|
|
||||||
match self.cipher {
|
match self.cipher {
|
||||||
Cipher::Aes128Ctr(ref params) => {
|
Cipher::Aes128Ctr(ref params) => {
|
||||||
// checker by callers
|
// checker by callers
|
||||||
debug_assert!(expected_len >= self.ciphertext.len());
|
debug_assert!(expected_len >= self.ciphertext.len());
|
||||||
|
|
||||||
let from = 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..])?;
|
crypto::aes::decrypt_128_ctr(
|
||||||
Ok(plain.into_iter().collect())
|
&derived_left_bits,
|
||||||
},
|
¶ms.iv,
|
||||||
}
|
&self.ciphertext,
|
||||||
}
|
&mut plain[from..],
|
||||||
|
)?;
|
||||||
|
Ok(plain.into_iter().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use ethkey::{Generator, Random};
|
use super::{Crypto, Error, NonZeroU32};
|
||||||
use super::{Crypto, Error, NonZeroU32};
|
use ethkey::{Generator, Random};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(10240).expect("10240 > 0; qed");
|
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(10240).expect("10240 > 0; qed");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn crypto_with_secret_create() {
|
fn crypto_with_secret_create() {
|
||||||
let keypair = Random.generate().unwrap();
|
let keypair = Random.generate().unwrap();
|
||||||
let passwd = "this is sparta".into();
|
let passwd = "this is sparta".into();
|
||||||
let crypto = Crypto::with_secret(keypair.secret(), &passwd, *ITERATIONS).unwrap();
|
let crypto = Crypto::with_secret(keypair.secret(), &passwd, *ITERATIONS).unwrap();
|
||||||
let secret = crypto.secret(&passwd).unwrap();
|
let secret = crypto.secret(&passwd).unwrap();
|
||||||
assert_eq!(keypair.secret(), &secret);
|
assert_eq!(keypair.secret(), &secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn crypto_with_secret_invalid_password() {
|
fn crypto_with_secret_invalid_password() {
|
||||||
let keypair = Random.generate().unwrap();
|
let keypair = Random.generate().unwrap();
|
||||||
let crypto = Crypto::with_secret(keypair.secret(), &"this is sparta".into(), *ITERATIONS).unwrap();
|
let crypto =
|
||||||
assert_matches!(crypto.secret(&"this is sparta!".into()), Err(Error::InvalidPassword))
|
Crypto::with_secret(keypair.secret(), &"this is sparta".into(), *ITERATIONS).unwrap();
|
||||||
}
|
assert_matches!(
|
||||||
|
crypto.secret(&"this is sparta!".into()),
|
||||||
|
Err(Error::InvalidPassword)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn crypto_with_null_plain_data() {
|
fn crypto_with_null_plain_data() {
|
||||||
let original_data = b"";
|
let original_data = b"";
|
||||||
let passwd = "this is sparta".into();
|
let passwd = "this is sparta".into();
|
||||||
let crypto = Crypto::with_plain(&original_data[..], &passwd, *ITERATIONS).unwrap();
|
let crypto = Crypto::with_plain(&original_data[..], &passwd, *ITERATIONS).unwrap();
|
||||||
let decrypted_data = crypto.decrypt(&passwd).unwrap();
|
let decrypted_data = crypto.decrypt(&passwd).unwrap();
|
||||||
assert_eq!(original_data[..], *decrypted_data);
|
assert_eq!(original_data[..], *decrypted_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn crypto_with_tiny_plain_data() {
|
fn crypto_with_tiny_plain_data() {
|
||||||
let original_data = b"{}";
|
let original_data = b"{}";
|
||||||
let passwd = "this is sparta".into();
|
let passwd = "this is sparta".into();
|
||||||
let crypto = Crypto::with_plain(&original_data[..], &passwd, *ITERATIONS).unwrap();
|
let crypto = Crypto::with_plain(&original_data[..], &passwd, *ITERATIONS).unwrap();
|
||||||
let decrypted_data = crypto.decrypt(&passwd).unwrap();
|
let decrypted_data = crypto.decrypt(&passwd).unwrap();
|
||||||
assert_eq!(original_data[..], *decrypted_data);
|
assert_eq!(original_data[..], *decrypted_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn crypto_with_huge_plain_data() {
|
fn crypto_with_huge_plain_data() {
|
||||||
let original_data: Vec<_> = (1..65536).map(|i| (i % 256) as u8).collect();
|
let original_data: Vec<_> = (1..65536).map(|i| (i % 256) as u8).collect();
|
||||||
let passwd = "this is sparta".into();
|
let passwd = "this is sparta".into();
|
||||||
let crypto = Crypto::with_plain(&original_data, &passwd, *ITERATIONS).unwrap();
|
let crypto = Crypto::with_plain(&original_data, &passwd, *ITERATIONS).unwrap();
|
||||||
let decrypted_data = crypto.decrypt(&passwd).unwrap();
|
let decrypted_data = crypto.decrypt(&passwd).unwrap();
|
||||||
assert_eq!(&original_data, &decrypted_data);
|
assert_eq!(&original_data, &decrypted_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,126 +1,126 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use json;
|
use json;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum Prf {
|
pub enum Prf {
|
||||||
HmacSha256,
|
HmacSha256,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct Pbkdf2 {
|
pub struct Pbkdf2 {
|
||||||
pub c: NonZeroU32,
|
pub c: NonZeroU32,
|
||||||
pub dklen: u32,
|
pub dklen: u32,
|
||||||
pub prf: Prf,
|
pub prf: Prf,
|
||||||
pub salt: Vec<u8>,
|
pub salt: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct Scrypt {
|
pub struct Scrypt {
|
||||||
pub dklen: u32,
|
pub dklen: u32,
|
||||||
pub p: u32,
|
pub p: u32,
|
||||||
pub n: u32,
|
pub n: u32,
|
||||||
pub r: u32,
|
pub r: u32,
|
||||||
pub salt: Vec<u8>,
|
pub salt: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum Kdf {
|
pub enum Kdf {
|
||||||
Pbkdf2(Pbkdf2),
|
Pbkdf2(Pbkdf2),
|
||||||
Scrypt(Scrypt),
|
Scrypt(Scrypt),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<json::Prf> for Prf {
|
impl From<json::Prf> for Prf {
|
||||||
fn from(json: json::Prf) -> Self {
|
fn from(json: json::Prf) -> Self {
|
||||||
match json {
|
match json {
|
||||||
json::Prf::HmacSha256 => Prf::HmacSha256,
|
json::Prf::HmacSha256 => Prf::HmacSha256,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<json::Prf> for Prf {
|
impl Into<json::Prf> for Prf {
|
||||||
fn into(self) -> json::Prf {
|
fn into(self) -> json::Prf {
|
||||||
match self {
|
match self {
|
||||||
Prf::HmacSha256 => json::Prf::HmacSha256,
|
Prf::HmacSha256 => json::Prf::HmacSha256,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<json::Pbkdf2> for Pbkdf2 {
|
impl From<json::Pbkdf2> for Pbkdf2 {
|
||||||
fn from(json: json::Pbkdf2) -> Self {
|
fn from(json: json::Pbkdf2) -> Self {
|
||||||
Pbkdf2 {
|
Pbkdf2 {
|
||||||
c: json.c,
|
c: json.c,
|
||||||
dklen: json.dklen,
|
dklen: json.dklen,
|
||||||
prf: From::from(json.prf),
|
prf: From::from(json.prf),
|
||||||
salt: json.salt.into(),
|
salt: json.salt.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<json::Pbkdf2> for Pbkdf2 {
|
impl Into<json::Pbkdf2> for Pbkdf2 {
|
||||||
fn into(self) -> json::Pbkdf2 {
|
fn into(self) -> json::Pbkdf2 {
|
||||||
json::Pbkdf2 {
|
json::Pbkdf2 {
|
||||||
c: self.c,
|
c: self.c,
|
||||||
dklen: self.dklen,
|
dklen: self.dklen,
|
||||||
prf: self.prf.into(),
|
prf: self.prf.into(),
|
||||||
salt: From::from(self.salt),
|
salt: From::from(self.salt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<json::Scrypt> for Scrypt {
|
impl From<json::Scrypt> for Scrypt {
|
||||||
fn from(json: json::Scrypt) -> Self {
|
fn from(json: json::Scrypt) -> Self {
|
||||||
Scrypt {
|
Scrypt {
|
||||||
dklen: json.dklen,
|
dklen: json.dklen,
|
||||||
p: json.p,
|
p: json.p,
|
||||||
n: json.n,
|
n: json.n,
|
||||||
r: json.r,
|
r: json.r,
|
||||||
salt: json.salt.into(),
|
salt: json.salt.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<json::Scrypt> for Scrypt {
|
impl Into<json::Scrypt> for Scrypt {
|
||||||
fn into(self) -> json::Scrypt {
|
fn into(self) -> json::Scrypt {
|
||||||
json::Scrypt {
|
json::Scrypt {
|
||||||
dklen: self.dklen,
|
dklen: self.dklen,
|
||||||
p: self.p,
|
p: self.p,
|
||||||
n: self.n,
|
n: self.n,
|
||||||
r: self.r,
|
r: self.r,
|
||||||
salt: From::from(self.salt),
|
salt: From::from(self.salt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<json::Kdf> for Kdf {
|
impl From<json::Kdf> for Kdf {
|
||||||
fn from(json: json::Kdf) -> Self {
|
fn from(json: json::Kdf) -> Self {
|
||||||
match json {
|
match json {
|
||||||
json::Kdf::Pbkdf2(params) => Kdf::Pbkdf2(From::from(params)),
|
json::Kdf::Pbkdf2(params) => Kdf::Pbkdf2(From::from(params)),
|
||||||
json::Kdf::Scrypt(params) => Kdf::Scrypt(From::from(params)),
|
json::Kdf::Scrypt(params) => Kdf::Scrypt(From::from(params)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<json::Kdf> for Kdf {
|
impl Into<json::Kdf> for Kdf {
|
||||||
fn into(self) -> json::Kdf {
|
fn into(self) -> json::Kdf {
|
||||||
match self {
|
match self {
|
||||||
Kdf::Pbkdf2(params) => json::Kdf::Pbkdf2(params.into()),
|
Kdf::Pbkdf2(params) => json::Kdf::Pbkdf2(params.into()),
|
||||||
Kdf::Scrypt(params) => json::Kdf::Scrypt(params.into()),
|
Kdf::Scrypt(params) => json::Kdf::Scrypt(params.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. 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,8 +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,
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,234 +1,286 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use ethkey::{self, KeyPair, sign, Address, Password, Signature, Message, Public, Secret};
|
use super::crypto::Crypto;
|
||||||
use ethkey::crypto::ecdh::agree;
|
|
||||||
use {json, Error};
|
|
||||||
use account::Version;
|
use account::Version;
|
||||||
use crypto;
|
use crypto;
|
||||||
use super::crypto::Crypto;
|
use ethkey::{
|
||||||
|
self, crypto::ecdh::agree, sign, Address, KeyPair, Message, Password, Public, Secret, Signature,
|
||||||
|
};
|
||||||
|
use json;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
use Error;
|
||||||
|
|
||||||
/// Account representation.
|
/// Account representation.
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct SafeAccount {
|
pub struct SafeAccount {
|
||||||
/// Account ID
|
/// Account ID
|
||||||
pub id: [u8; 16],
|
pub id: [u8; 16],
|
||||||
/// Account version
|
/// Account version
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
/// Account address
|
/// Account address
|
||||||
pub address: Address,
|
pub address: Address,
|
||||||
/// Account private key derivation definition.
|
/// Account private key derivation definition.
|
||||||
pub crypto: Crypto,
|
pub crypto: Crypto,
|
||||||
/// Account filename
|
/// Account filename
|
||||||
pub filename: Option<String>,
|
pub filename: Option<String>,
|
||||||
/// Account name
|
/// Account name
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// Account metadata
|
/// Account metadata
|
||||||
pub meta: String,
|
pub meta: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<json::KeyFile> for SafeAccount {
|
impl Into<json::KeyFile> for SafeAccount {
|
||||||
fn into(self) -> json::KeyFile {
|
fn into(self) -> json::KeyFile {
|
||||||
json::KeyFile {
|
json::KeyFile {
|
||||||
id: From::from(self.id),
|
id: From::from(self.id),
|
||||||
version: self.version.into(),
|
version: self.version.into(),
|
||||||
address: Some(self.address.into()),
|
address: Some(self.address.into()),
|
||||||
crypto: self.crypto.into(),
|
crypto: self.crypto.into(),
|
||||||
name: Some(self.name.into()),
|
name: Some(self.name.into()),
|
||||||
meta: Some(self.meta.into()),
|
meta: Some(self.meta.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SafeAccount {
|
impl SafeAccount {
|
||||||
/// Create a new account
|
/// Create a new account
|
||||||
pub fn create(
|
pub fn create(
|
||||||
keypair: &KeyPair,
|
keypair: &KeyPair,
|
||||||
id: [u8; 16],
|
id: [u8; 16],
|
||||||
password: &Password,
|
password: &Password,
|
||||||
iterations: NonZeroU32,
|
iterations: NonZeroU32,
|
||||||
name: String,
|
name: String,
|
||||||
meta: String
|
meta: String,
|
||||||
) -> Result<Self, crypto::Error> {
|
) -> Result<Self, crypto::Error> {
|
||||||
Ok(SafeAccount {
|
Ok(SafeAccount {
|
||||||
id: id,
|
id: id,
|
||||||
version: Version::V3,
|
version: Version::V3,
|
||||||
crypto: Crypto::with_secret(keypair.secret(), password, iterations)?,
|
crypto: Crypto::with_secret(keypair.secret(), password, iterations)?,
|
||||||
address: keypair.address(),
|
address: keypair.address(),
|
||||||
filename: None,
|
filename: None,
|
||||||
name: name,
|
name: name,
|
||||||
meta: meta,
|
meta: meta,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new `SafeAccount` from the given `json`; if it was read from a
|
/// 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
|
/// file, the `filename` should be `Some` name. If it is as yet anonymous, then it
|
||||||
/// can be left `None`.
|
/// can be left `None`.
|
||||||
/// In case `password` is provided, we will attempt to read the secret from the keyfile
|
/// 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.
|
/// and derive the address from it instead of reading it directly.
|
||||||
/// Providing password is required for `json::KeyFile`s with no address.
|
/// 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> {
|
pub fn from_file(
|
||||||
let crypto = Crypto::from(json.crypto);
|
json: json::KeyFile,
|
||||||
let address = match (password, &json.address) {
|
filename: Option<String>,
|
||||||
(None, Some(json_address)) => json_address.into(),
|
password: &Option<Password>,
|
||||||
(None, None) => Err(Error::Custom(
|
) -> Result<Self, Error> {
|
||||||
"This keystore does not contain address. You need to provide password to import it".into()))?,
|
let crypto = Crypto::from(json.crypto);
|
||||||
(Some(password), json_address) => {
|
let address = match (password, &json.address) {
|
||||||
let derived_address = KeyPair::from_secret(
|
(None, Some(json_address)) => json_address.into(),
|
||||||
crypto.secret(&password).map_err(|_| Error::InvalidPassword)?
|
(None, None) => Err(Error::Custom(
|
||||||
)?.address();
|
"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 {
|
match json_address {
|
||||||
Some(json_address) => {
|
Some(json_address) => {
|
||||||
let json_address = json_address.into();
|
let json_address = json_address.into();
|
||||||
if derived_address != json_address {
|
if derived_address != json_address {
|
||||||
warn!("Detected address mismatch when opening an account. Derived: {:?}, in json got: {:?}",
|
warn!("Detected address mismatch when opening an account. Derived: {:?}, in json got: {:?}",
|
||||||
derived_address, json_address);
|
derived_address, json_address);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => {},
|
_ => {}
|
||||||
}
|
}
|
||||||
derived_address
|
derived_address
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(SafeAccount {
|
Ok(SafeAccount {
|
||||||
id: json.id.into(),
|
id: json.id.into(),
|
||||||
version: json.version.into(),
|
version: json.version.into(),
|
||||||
address,
|
address,
|
||||||
crypto,
|
crypto,
|
||||||
filename,
|
filename,
|
||||||
name: json.name.unwrap_or(String::new()),
|
name: json.name.unwrap_or(String::new()),
|
||||||
meta: json.meta.unwrap_or("{}".to_owned()),
|
meta: json.meta.unwrap_or("{}".to_owned()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new `SafeAccount` from the given vault `json`; if it was read from a
|
/// 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
|
/// file, the `filename` should be `Some` name. If it is as yet anonymous, then it
|
||||||
/// can be left `None`.
|
/// can be left `None`.
|
||||||
pub fn from_vault_file(password: &Password, json: json::VaultKeyFile, filename: Option<String>) -> Result<Self, Error> {
|
pub fn from_vault_file(
|
||||||
let meta_crypto: Crypto = json.metacrypto.into();
|
password: &Password,
|
||||||
let meta_plain = meta_crypto.decrypt(password)?;
|
json: json::VaultKeyFile,
|
||||||
let meta_plain = json::VaultKeyMeta::load(&meta_plain).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
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 {
|
SafeAccount::from_file(
|
||||||
id: json.id,
|
json::KeyFile {
|
||||||
version: json.version,
|
id: json.id,
|
||||||
crypto: json.crypto,
|
version: json.version,
|
||||||
address: Some(meta_plain.address),
|
crypto: json.crypto,
|
||||||
name: meta_plain.name,
|
address: Some(meta_plain.address),
|
||||||
meta: meta_plain.meta,
|
name: meta_plain.name,
|
||||||
}, filename, &None)
|
meta: meta_plain.meta,
|
||||||
}
|
},
|
||||||
|
filename,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new `VaultKeyFile` from the given `self`
|
/// Create a new `VaultKeyFile` from the given `self`
|
||||||
pub fn into_vault_file(self, iterations: NonZeroU32, password: &Password) -> Result<json::VaultKeyFile, Error> {
|
pub fn into_vault_file(
|
||||||
let meta_plain = json::VaultKeyMeta {
|
self,
|
||||||
address: self.address.into(),
|
iterations: NonZeroU32,
|
||||||
name: Some(self.name),
|
password: &Password,
|
||||||
meta: Some(self.meta),
|
) -> Result<json::VaultKeyFile, Error> {
|
||||||
};
|
let meta_plain = json::VaultKeyMeta {
|
||||||
let meta_plain = meta_plain.write().map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
address: self.address.into(),
|
||||||
let meta_crypto = Crypto::with_plain(&meta_plain, password, iterations)?;
|
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 {
|
Ok(json::VaultKeyFile {
|
||||||
id: self.id.into(),
|
id: self.id.into(),
|
||||||
version: self.version.into(),
|
version: self.version.into(),
|
||||||
crypto: self.crypto.into(),
|
crypto: self.crypto.into(),
|
||||||
metacrypto: meta_crypto.into(),
|
metacrypto: meta_crypto.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign a message.
|
/// Sign a message.
|
||||||
pub fn sign(&self, password: &Password, message: &Message) -> Result<Signature, Error> {
|
pub fn sign(&self, password: &Password, message: &Message) -> Result<Signature, Error> {
|
||||||
let secret = self.crypto.secret(password)?;
|
let secret = self.crypto.secret(password)?;
|
||||||
sign(&secret, message).map_err(From::from)
|
sign(&secret, message).map_err(From::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decrypt a message.
|
/// Decrypt a message.
|
||||||
pub fn decrypt(&self, password: &Password, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
|
pub fn decrypt(
|
||||||
let secret = self.crypto.secret(password)?;
|
&self,
|
||||||
ethkey::crypto::ecies::decrypt(&secret, shared_mac, message).map_err(From::from)
|
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.
|
/// Agree on shared key.
|
||||||
pub fn agree(&self, password: &Password, other: &Public) -> Result<Secret, Error> {
|
pub fn agree(&self, password: &Password, other: &Public) -> Result<Secret, Error> {
|
||||||
let secret = self.crypto.secret(password)?;
|
let secret = self.crypto.secret(password)?;
|
||||||
agree(&secret, other).map_err(From::from)
|
agree(&secret, other).map_err(From::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Derive public key.
|
/// Derive public key.
|
||||||
pub fn public(&self, password: &Password) -> Result<Public, Error> {
|
pub fn public(&self, password: &Password) -> Result<Public, Error> {
|
||||||
let secret = self.crypto.secret(password)?;
|
let secret = self.crypto.secret(password)?;
|
||||||
Ok(KeyPair::from_secret(secret)?.public().clone())
|
Ok(KeyPair::from_secret(secret)?.public().clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change account's password.
|
/// Change account's password.
|
||||||
pub fn change_password(&self, old_password: &Password, new_password: &Password, iterations: NonZeroU32) -> Result<Self, Error> {
|
pub fn change_password(
|
||||||
let secret = self.crypto.secret(old_password)?;
|
&self,
|
||||||
let result = SafeAccount {
|
old_password: &Password,
|
||||||
id: self.id.clone(),
|
new_password: &Password,
|
||||||
version: self.version.clone(),
|
iterations: NonZeroU32,
|
||||||
crypto: Crypto::with_secret(&secret, new_password, iterations)?,
|
) -> Result<Self, Error> {
|
||||||
address: self.address.clone(),
|
let secret = self.crypto.secret(old_password)?;
|
||||||
filename: self.filename.clone(),
|
let result = SafeAccount {
|
||||||
name: self.name.clone(),
|
id: self.id.clone(),
|
||||||
meta: self.meta.clone(),
|
version: self.version.clone(),
|
||||||
};
|
crypto: Crypto::with_secret(&secret, new_password, iterations)?,
|
||||||
Ok(result)
|
address: self.address.clone(),
|
||||||
}
|
filename: self.filename.clone(),
|
||||||
|
name: self.name.clone(),
|
||||||
|
meta: self.meta.clone(),
|
||||||
|
};
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if password matches the account.
|
/// Check if password matches the account.
|
||||||
pub fn check_password(&self, password: &Password) -> bool {
|
pub fn check_password(&self, password: &Password) -> bool {
|
||||||
self.crypto.secret(password).is_ok()
|
self.crypto.secret(password).is_ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use ethkey::{Generator, Random, verify_public, Message};
|
use super::{NonZeroU32, SafeAccount};
|
||||||
use super::{SafeAccount, NonZeroU32};
|
use ethkey::{verify_public, Generator, Message, Random};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(10240).expect("10240 > 0; qed");
|
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]
|
#[test]
|
||||||
fn sign_and_verify_public() {
|
fn change_password() {
|
||||||
let keypair = Random.generate().unwrap();
|
let keypair = Random.generate().unwrap();
|
||||||
let password = "hello world".into();
|
let first_password = "hello world".into();
|
||||||
let message = Message::default();
|
let sec_password = "this is sparta".into();
|
||||||
let account = SafeAccount::create(&keypair, [0u8; 16], &password, *ITERATIONS, "Test".to_owned(), "{}".to_owned());
|
let message = Message::default();
|
||||||
let signature = account.unwrap().sign(&password, &message).unwrap();
|
let account = SafeAccount::create(
|
||||||
assert!(verify_public(keypair.public(), &signature, &message).unwrap());
|
&keypair,
|
||||||
}
|
[0u8; 16],
|
||||||
|
&first_password,
|
||||||
#[test]
|
*ITERATIONS,
|
||||||
fn change_password() {
|
"Test".to_owned(),
|
||||||
let keypair = Random.generate().unwrap();
|
"{}".to_owned(),
|
||||||
let first_password = "hello world".into();
|
)
|
||||||
let sec_password = "this is sparta".into();
|
.unwrap();
|
||||||
let message = Message::default();
|
let new_account = account
|
||||||
let account = SafeAccount::create(&keypair, [0u8; 16], &first_password, *ITERATIONS, "Test".to_owned(), "{}".to_owned()).unwrap();
|
.change_password(&first_password, &sec_password, *ITERATIONS)
|
||||||
let new_account = account.change_password(&first_password, &sec_password, *ITERATIONS).unwrap();
|
.unwrap();
|
||||||
assert!(account.sign(&first_password, &message).is_ok());
|
assert!(account.sign(&first_password, &message).is_ok());
|
||||||
assert!(account.sign(&sec_password, &message).is_err());
|
assert!(account.sign(&sec_password, &message).is_err());
|
||||||
assert!(new_account.sign(&first_password, &message).is_err());
|
assert!(new_account.sign(&first_password, &message).is_err());
|
||||||
assert!(new_account.sign(&sec_password, &message).is_ok());
|
assert!(new_account.sign(&sec_password, &message).is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,38 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,103 +1,112 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::{fs, io};
|
use super::{
|
||||||
use std::io::Write;
|
vault::{VaultDiskDirectory, VAULT_FILE_NAME},
|
||||||
use std::path::{PathBuf, Path};
|
KeyDirectory, VaultKey, VaultKeyDirectory, VaultKeyDirectoryProvider,
|
||||||
use std::collections::HashMap;
|
};
|
||||||
use time;
|
|
||||||
use {json, SafeAccount, Error};
|
|
||||||
use json::Uuid;
|
|
||||||
use super::{KeyDirectory, VaultKeyDirectory, VaultKeyDirectoryProvider, VaultKey};
|
|
||||||
use super::vault::{VAULT_FILE_NAME, VaultDiskDirectory};
|
|
||||||
use ethkey::Password;
|
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] = &[
|
const IGNORED_FILES: &'static [&'static str] = &[
|
||||||
"thumbs.db",
|
"thumbs.db",
|
||||||
"address_book.json",
|
"address_book.json",
|
||||||
"dapps_policy.json",
|
"dapps_policy.json",
|
||||||
"dapps_accounts.json",
|
"dapps_accounts.json",
|
||||||
"dapps_history.json",
|
"dapps_history.json",
|
||||||
"vault.json",
|
"vault.json",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Find a unique filename that does not exist using four-letter random suffix.
|
/// 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> {
|
pub fn find_unique_filename_using_random_suffix(
|
||||||
let mut path = parent_path.join(original_filename);
|
parent_path: &Path,
|
||||||
let mut deduped_filename = original_filename.to_string();
|
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() {
|
if path.exists() {
|
||||||
const MAX_RETRIES: usize = 500;
|
const MAX_RETRIES: usize = 500;
|
||||||
let mut retries = 0;
|
let mut retries = 0;
|
||||||
|
|
||||||
while path.exists() {
|
while path.exists() {
|
||||||
if retries >= MAX_RETRIES {
|
if retries >= MAX_RETRIES {
|
||||||
return Err(io::Error::new(io::ErrorKind::Other, "Exceeded maximum retries when deduplicating filename."));
|
return Err(io::Error::new(
|
||||||
}
|
io::ErrorKind::Other,
|
||||||
|
"Exceeded maximum retries when deduplicating filename.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let suffix = ::random::random_string(4);
|
let suffix = ::random::random_string(4);
|
||||||
deduped_filename = format!("{}-{}", original_filename, suffix);
|
deduped_filename = format!("{}-{}", original_filename, suffix);
|
||||||
path.set_file_name(&deduped_filename);
|
path.set_file_name(&deduped_filename);
|
||||||
retries += 1;
|
retries += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(deduped_filename)
|
Ok(deduped_filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new file and restrict permissions to owner only. It errors if the file already exists.
|
/// Create a new file and restrict permissions to owner only. It errors if the file already exists.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn create_new_file_with_permissions_to_owner(file_path: &Path) -> io::Result<fs::File> {
|
pub fn create_new_file_with_permissions_to_owner(file_path: &Path) -> io::Result<fs::File> {
|
||||||
use libc;
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
use std::os::unix::fs::OpenOptionsExt;
|
|
||||||
|
|
||||||
fs::OpenOptions::new()
|
fs::OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
.create_new(true)
|
.create_new(true)
|
||||||
.mode((libc::S_IWUSR | libc::S_IRUSR) as u32)
|
.mode((libc::S_IWUSR | libc::S_IRUSR) as u32)
|
||||||
.open(file_path)
|
.open(file_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new file and restrict permissions to owner only. It errors if the file already exists.
|
/// Create a new file and restrict permissions to owner only. It errors if the file already exists.
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
pub fn create_new_file_with_permissions_to_owner(file_path: &Path) -> io::Result<fs::File> {
|
pub fn create_new_file_with_permissions_to_owner(file_path: &Path) -> io::Result<fs::File> {
|
||||||
fs::OpenOptions::new()
|
fs::OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
.create_new(true)
|
.create_new(true)
|
||||||
.open(file_path)
|
.open(file_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new file and restrict permissions to owner only. It replaces the existing file if it already exists.
|
/// Create a new file and restrict permissions to owner only. It replaces the existing file if it already exists.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn replace_file_with_permissions_to_owner(file_path: &Path) -> io::Result<fs::File> {
|
pub fn replace_file_with_permissions_to_owner(file_path: &Path) -> io::Result<fs::File> {
|
||||||
use libc;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::os::unix::fs::PermissionsExt;
|
|
||||||
|
|
||||||
let file = fs::File::create(file_path)?;
|
let file = fs::File::create(file_path)?;
|
||||||
let mut permissions = file.metadata()?.permissions();
|
let mut permissions = file.metadata()?.permissions();
|
||||||
permissions.set_mode((libc::S_IWUSR | libc::S_IRUSR) as u32);
|
permissions.set_mode((libc::S_IWUSR | libc::S_IRUSR) as u32);
|
||||||
file.set_permissions(permissions)?;
|
file.set_permissions(permissions)?;
|
||||||
|
|
||||||
Ok(file)
|
Ok(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new file and restrict permissions to owner only. It replaces the existing file if it already exists.
|
/// Create a new file and restrict permissions to owner only. It replaces the existing file if it already exists.
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
pub fn replace_file_with_permissions_to_owner(file_path: &Path) -> io::Result<fs::File> {
|
pub fn replace_file_with_permissions_to_owner(file_path: &Path) -> io::Result<fs::File> {
|
||||||
fs::File::create(file_path)
|
fs::File::create(file_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Root keys directory implementation
|
/// Root keys directory implementation
|
||||||
@@ -105,388 +114,495 @@ pub type RootDiskDirectory = DiskDirectory<DiskKeyFileManager>;
|
|||||||
|
|
||||||
/// Disk directory key file manager
|
/// Disk directory key file manager
|
||||||
pub trait KeyFileManager: Send + Sync {
|
pub trait KeyFileManager: Send + Sync {
|
||||||
/// Read `SafeAccount` from given key file stream
|
/// Read `SafeAccount` from given key file stream
|
||||||
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error> where T: io::Read;
|
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error>
|
||||||
|
where
|
||||||
|
T: io::Read;
|
||||||
|
|
||||||
/// Write `SafeAccount` to given key file stream
|
/// Write `SafeAccount` to given key file stream
|
||||||
fn write<T>(&self, account: SafeAccount, writer: &mut T) -> Result<(), Error> where T: io::Write;
|
fn write<T>(&self, account: SafeAccount, writer: &mut T) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: io::Write;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disk-based keys directory implementation
|
/// Disk-based keys directory implementation
|
||||||
pub struct DiskDirectory<T> where T: KeyFileManager {
|
pub struct DiskDirectory<T>
|
||||||
path: PathBuf,
|
where
|
||||||
key_manager: T,
|
T: KeyFileManager,
|
||||||
|
{
|
||||||
|
path: PathBuf,
|
||||||
|
key_manager: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keys file manager for root keys directory
|
/// Keys file manager for root keys directory
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct DiskKeyFileManager {
|
pub struct DiskKeyFileManager {
|
||||||
password: Option<Password>,
|
password: Option<Password>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RootDiskDirectory {
|
impl RootDiskDirectory {
|
||||||
pub fn create<P>(path: P) -> Result<Self, Error> where P: AsRef<Path> {
|
pub fn create<P>(path: P) -> Result<Self, Error>
|
||||||
fs::create_dir_all(&path)?;
|
where
|
||||||
Ok(Self::at(path))
|
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)
|
/// allows to read keyfiles with given password (needed for keyfiles w/o address)
|
||||||
pub fn with_password(&self, password: Option<Password>) -> Self {
|
pub fn with_password(&self, password: Option<Password>) -> Self {
|
||||||
DiskDirectory::new(&self.path, DiskKeyFileManager { password })
|
DiskDirectory::new(&self.path, DiskKeyFileManager { password })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn at<P>(path: P) -> Self where P: AsRef<Path> {
|
pub fn at<P>(path: P) -> Self
|
||||||
DiskDirectory::new(path, DiskKeyFileManager::default())
|
where
|
||||||
}
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
DiskDirectory::new(path, DiskKeyFileManager::default())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> DiskDirectory<T> where T: KeyFileManager {
|
impl<T> DiskDirectory<T>
|
||||||
/// Create new disk directory instance
|
where
|
||||||
pub fn new<P>(path: P, key_manager: T) -> Self where P: AsRef<Path> {
|
T: KeyFileManager,
|
||||||
DiskDirectory {
|
{
|
||||||
path: path.as_ref().to_path_buf(),
|
/// Create new disk directory instance
|
||||||
key_manager: key_manager,
|
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> {
|
fn files(&self) -> Result<Vec<PathBuf>, Error> {
|
||||||
Ok(fs::read_dir(&self.path)?
|
Ok(fs::read_dir(&self.path)?
|
||||||
.flat_map(Result::ok)
|
.flat_map(Result::ok)
|
||||||
.filter(|entry| {
|
.filter(|entry| {
|
||||||
let metadata = entry.metadata().ok();
|
let metadata = entry.metadata().ok();
|
||||||
let file_name = entry.file_name();
|
let file_name = entry.file_name();
|
||||||
let name = file_name.to_string_lossy();
|
let name = file_name.to_string_lossy();
|
||||||
// filter directories
|
// filter directories
|
||||||
metadata.map_or(false, |m| !m.is_dir()) &&
|
metadata.map_or(false, |m| !m.is_dir()) &&
|
||||||
// hidden files
|
// hidden files
|
||||||
!name.starts_with(".") &&
|
!name.starts_with(".") &&
|
||||||
// other ignored files
|
// other ignored files
|
||||||
!IGNORED_FILES.contains(&&*name)
|
!IGNORED_FILES.contains(&&*name)
|
||||||
})
|
})
|
||||||
.map(|entry| entry.path())
|
.map(|entry| entry.path())
|
||||||
.collect::<Vec<PathBuf>>()
|
.collect::<Vec<PathBuf>>())
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn files_hash(&self) -> Result<u64, Error> {
|
pub fn files_hash(&self) -> Result<u64, Error> {
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::{collections::hash_map::DefaultHasher, hash::Hasher};
|
||||||
use std::hash::Hasher;
|
|
||||||
|
|
||||||
let mut hasher = DefaultHasher::new();
|
let mut hasher = DefaultHasher::new();
|
||||||
let files = self.files()?;
|
let files = self.files()?;
|
||||||
for file in files {
|
for file in files {
|
||||||
hasher.write(file.to_str().unwrap_or("").as_bytes())
|
hasher.write(file.to_str().unwrap_or("").as_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(hasher.finish())
|
Ok(hasher.finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn last_modification_date(&self) -> Result<u64, Error> {
|
fn last_modification_date(&self) -> Result<u64, Error> {
|
||||||
use std::time::{Duration, UNIX_EPOCH};
|
use std::time::{Duration, UNIX_EPOCH};
|
||||||
let duration = fs::metadata(&self.path)?.modified()?.duration_since(UNIX_EPOCH).unwrap_or(Duration::default());
|
let duration = fs::metadata(&self.path)?
|
||||||
let timestamp = duration.as_secs() ^ (duration.subsec_nanos() as u64);
|
.modified()?
|
||||||
Ok(timestamp)
|
.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
|
/// all accounts found in keys directory
|
||||||
fn files_content(&self) -> Result<HashMap<PathBuf, SafeAccount>, Error> {
|
fn files_content(&self) -> Result<HashMap<PathBuf, SafeAccount>, Error> {
|
||||||
// it's not done using one iterator cause
|
// it's not done using one iterator cause
|
||||||
// there is an issue with rustc and it takes tooo much time to compile
|
// there is an issue with rustc and it takes tooo much time to compile
|
||||||
let paths = self.files()?;
|
let paths = self.files()?;
|
||||||
Ok(paths
|
Ok(paths
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|path| {
|
.filter_map(|path| {
|
||||||
let filename = Some(path.file_name().and_then(|n| n.to_str()).expect("Keys have valid UTF8 names only.").to_owned());
|
let filename = Some(
|
||||||
fs::File::open(path.clone())
|
path.file_name()
|
||||||
.map_err(Into::into)
|
.and_then(|n| n.to_str())
|
||||||
.and_then(|file| self.key_manager.read(filename, file))
|
.expect("Keys have valid UTF8 names only.")
|
||||||
.map_err(|err| {
|
.to_owned(),
|
||||||
warn!("Invalid key file: {:?} ({})", path, err);
|
);
|
||||||
err
|
fs::File::open(path.clone())
|
||||||
})
|
.map_err(Into::into)
|
||||||
.map(|account| (path, account))
|
.and_then(|file| self.key_manager.read(filename, file))
|
||||||
.ok()
|
.map_err(|err| {
|
||||||
})
|
warn!("Invalid key file: {:?} ({})", path, err);
|
||||||
.collect()
|
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
|
/// 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.
|
/// 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> {
|
pub fn insert_with_filename(
|
||||||
if dedup {
|
&self,
|
||||||
filename = find_unique_filename_using_random_suffix(&self.path, &filename)?;
|
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
|
// path to keyfile
|
||||||
let keyfile_path = self.path.join(filename.as_str());
|
let keyfile_path = self.path.join(filename.as_str());
|
||||||
|
|
||||||
// update account filename
|
// update account filename
|
||||||
let original_account = account.clone();
|
let original_account = account.clone();
|
||||||
let mut account = account;
|
let mut account = account;
|
||||||
account.filename = Some(filename);
|
account.filename = Some(filename);
|
||||||
|
|
||||||
{
|
{
|
||||||
// save the file
|
// save the file
|
||||||
let mut file = if dedup {
|
let mut file = if dedup {
|
||||||
create_new_file_with_permissions_to_owner(&keyfile_path)?
|
create_new_file_with_permissions_to_owner(&keyfile_path)?
|
||||||
} else {
|
} else {
|
||||||
replace_file_with_permissions_to_owner(&keyfile_path)?
|
replace_file_with_permissions_to_owner(&keyfile_path)?
|
||||||
};
|
};
|
||||||
|
|
||||||
// write key content
|
// write key content
|
||||||
self.key_manager.write(original_account, &mut file).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
self.key_manager
|
||||||
|
.write(original_account, &mut file)
|
||||||
|
.map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||||
|
|
||||||
file.flush()?;
|
file.flush()?;
|
||||||
file.sync_all()?;
|
file.sync_all()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(account)
|
Ok(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get key file manager referece
|
/// Get key file manager referece
|
||||||
pub fn key_manager(&self) -> &T {
|
pub fn key_manager(&self) -> &T {
|
||||||
&self.key_manager
|
&self.key_manager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> KeyDirectory for DiskDirectory<T> where T: KeyFileManager {
|
impl<T> KeyDirectory for DiskDirectory<T>
|
||||||
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
where
|
||||||
let accounts = self.files_content()?
|
T: KeyFileManager,
|
||||||
.into_iter()
|
{
|
||||||
.map(|(_, account)| account)
|
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
||||||
.collect();
|
let accounts = self
|
||||||
Ok(accounts)
|
.files_content()?
|
||||||
}
|
.into_iter()
|
||||||
|
.map(|(_, account)| account)
|
||||||
|
.collect();
|
||||||
|
Ok(accounts)
|
||||||
|
}
|
||||||
|
|
||||||
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
// Disk store handles updates correctly iff filename is the same
|
// Disk store handles updates correctly iff filename is the same
|
||||||
let filename = account_filename(&account);
|
let filename = account_filename(&account);
|
||||||
self.insert_with_filename(account, filename, false)
|
self.insert_with_filename(account, filename, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
let filename = account_filename(&account);
|
let filename = account_filename(&account);
|
||||||
self.insert_with_filename(account, filename, true)
|
self.insert_with_filename(account, filename, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
// enumerate all entries in keystore
|
// enumerate all entries in keystore
|
||||||
// and find entry with given address
|
// and find entry with given address
|
||||||
let to_remove = self.files_content()?
|
let to_remove = self
|
||||||
.into_iter()
|
.files_content()?
|
||||||
.find(|&(_, ref acc)| acc.id == account.id && acc.address == account.address);
|
.into_iter()
|
||||||
|
.find(|&(_, ref acc)| acc.id == account.id && acc.address == account.address);
|
||||||
|
|
||||||
// remove it
|
// remove it
|
||||||
match to_remove {
|
match to_remove {
|
||||||
None => Err(Error::InvalidAccount),
|
None => Err(Error::InvalidAccount),
|
||||||
Some((path, _)) => fs::remove_file(path).map_err(From::from)
|
Some((path, _)) => fs::remove_file(path).map_err(From::from),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path(&self) -> Option<&PathBuf> { Some(&self.path) }
|
fn path(&self) -> Option<&PathBuf> {
|
||||||
|
Some(&self.path)
|
||||||
|
}
|
||||||
|
|
||||||
fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> {
|
fn as_vault_provider(&self) -> Option<&dyn VaultKeyDirectoryProvider> {
|
||||||
Some(self)
|
Some(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unique_repr(&self) -> Result<u64, Error> {
|
fn unique_repr(&self) -> Result<u64, Error> {
|
||||||
self.last_modification_date()
|
self.last_modification_date()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> VaultKeyDirectoryProvider for DiskDirectory<T> where T: KeyFileManager {
|
impl<T> VaultKeyDirectoryProvider for DiskDirectory<T>
|
||||||
fn create(&self, name: &str, key: VaultKey) -> Result<Box<VaultKeyDirectory>, Error> {
|
where
|
||||||
let vault_dir = VaultDiskDirectory::create(&self.path, name, key)?;
|
T: KeyFileManager,
|
||||||
Ok(Box::new(vault_dir))
|
{
|
||||||
}
|
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<VaultKeyDirectory>, Error> {
|
fn open(&self, name: &str, key: VaultKey) -> Result<Box<dyn VaultKeyDirectory>, Error> {
|
||||||
let vault_dir = VaultDiskDirectory::at(&self.path, name, key)?;
|
let vault_dir = VaultDiskDirectory::at(&self.path, name, key)?;
|
||||||
Ok(Box::new(vault_dir))
|
Ok(Box::new(vault_dir))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_vaults(&self) -> Result<Vec<String>, Error> {
|
fn list_vaults(&self) -> Result<Vec<String>, Error> {
|
||||||
Ok(fs::read_dir(&self.path)?
|
Ok(fs::read_dir(&self.path)?
|
||||||
.filter_map(|e| e.ok().map(|e| e.path()))
|
.filter_map(|e| e.ok().map(|e| e.path()))
|
||||||
.filter_map(|path| {
|
.filter_map(|path| {
|
||||||
let mut vault_file_path = path.clone();
|
let mut vault_file_path = path.clone();
|
||||||
vault_file_path.push(VAULT_FILE_NAME);
|
vault_file_path.push(VAULT_FILE_NAME);
|
||||||
if vault_file_path.is_file() {
|
if vault_file_path.is_file() {
|
||||||
path.file_name().and_then(|f| f.to_str()).map(|f| f.to_owned())
|
path.file_name()
|
||||||
} else {
|
.and_then(|f| f.to_str())
|
||||||
None
|
.map(|f| f.to_owned())
|
||||||
}
|
} else {
|
||||||
})
|
None
|
||||||
.collect())
|
}
|
||||||
}
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
fn vault_meta(&self, name: &str) -> Result<String, Error> {
|
fn vault_meta(&self, name: &str) -> Result<String, Error> {
|
||||||
VaultDiskDirectory::meta_at(&self.path, name)
|
VaultDiskDirectory::meta_at(&self.path, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyFileManager for DiskKeyFileManager {
|
impl KeyFileManager for DiskKeyFileManager {
|
||||||
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error> where T: io::Read {
|
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error>
|
||||||
let key_file = json::KeyFile::load(reader).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
where
|
||||||
SafeAccount::from_file(key_file, filename, &self.password)
|
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 {
|
fn write<T>(&self, mut account: SafeAccount, writer: &mut T) -> Result<(), Error>
|
||||||
// when account is moved back to root directory from vault
|
where
|
||||||
// => remove vault field from meta
|
T: io::Write,
|
||||||
account.meta = json::remove_vault_name_from_json_meta(&account.meta)
|
{
|
||||||
.map_err(|err| Error::Custom(format!("{:?}", err)))?;
|
// 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();
|
let key_file: json::KeyFile = account.into();
|
||||||
key_file.write(writer).map_err(|e| Error::Custom(format!("{:?}", e)))
|
key_file
|
||||||
}
|
.write(writer)
|
||||||
|
.map_err(|e| Error::Custom(format!("{:?}", e)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn account_filename(account: &SafeAccount) -> String {
|
fn account_filename(account: &SafeAccount) -> String {
|
||||||
// build file path
|
// build file path
|
||||||
account.filename.clone().unwrap_or_else(|| {
|
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.");
|
let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc())
|
||||||
format!("UTC--{}Z--{}", timestamp, Uuid::from(account.id))
|
.expect("Time-format string is valid.");
|
||||||
})
|
format!("UTC--{}Z--{}", timestamp, Uuid::from(account.id))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
extern crate tempdir;
|
extern crate tempdir;
|
||||||
|
|
||||||
use std::{env, fs};
|
use self::tempdir::TempDir;
|
||||||
use std::num::NonZeroU32;
|
use super::{KeyDirectory, RootDiskDirectory, VaultKey};
|
||||||
use super::{KeyDirectory, RootDiskDirectory, VaultKey};
|
use account::SafeAccount;
|
||||||
use account::SafeAccount;
|
use ethkey::{Generator, Random};
|
||||||
use ethkey::{Random, Generator};
|
use std::{env, fs, num::NonZeroU32};
|
||||||
use self::tempdir::TempDir;
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(1024).expect("1024 > 0; qed");
|
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(1024).expect("1024 > 0; qed");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_create_new_account() {
|
fn should_create_new_account() {
|
||||||
// given
|
// given
|
||||||
let mut dir = env::temp_dir();
|
let mut dir = env::temp_dir();
|
||||||
dir.push("ethstore_should_create_new_account");
|
dir.push("ethstore_should_create_new_account");
|
||||||
let keypair = Random.generate().unwrap();
|
let keypair = Random.generate().unwrap();
|
||||||
let password = "hello world".into();
|
let password = "hello world".into();
|
||||||
let directory = RootDiskDirectory::create(dir.clone()).unwrap();
|
let directory = RootDiskDirectory::create(dir.clone()).unwrap();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let account = SafeAccount::create(&keypair, [0u8; 16], &password, *ITERATIONS, "Test".to_owned(), "{}".to_owned());
|
let account = SafeAccount::create(
|
||||||
let res = directory.insert(account.unwrap());
|
&keypair,
|
||||||
|
[0u8; 16],
|
||||||
|
&password,
|
||||||
|
*ITERATIONS,
|
||||||
|
"Test".to_owned(),
|
||||||
|
"{}".to_owned(),
|
||||||
|
);
|
||||||
|
let res = directory.insert(account.unwrap());
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert!(res.is_ok(), "Should save account succesfuly.");
|
assert!(res.is_ok(), "Should save account succesfuly.");
|
||||||
assert!(res.unwrap().filename.is_some(), "Filename has been assigned.");
|
assert!(
|
||||||
|
res.unwrap().filename.is_some(),
|
||||||
|
"Filename has been assigned."
|
||||||
|
);
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
let _ = fs::remove_dir_all(dir);
|
let _ = fs::remove_dir_all(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_handle_duplicate_filenames() {
|
fn should_handle_duplicate_filenames() {
|
||||||
// given
|
// given
|
||||||
let mut dir = env::temp_dir();
|
let mut dir = env::temp_dir();
|
||||||
dir.push("ethstore_should_handle_duplicate_filenames");
|
dir.push("ethstore_should_handle_duplicate_filenames");
|
||||||
let keypair = Random.generate().unwrap();
|
let keypair = Random.generate().unwrap();
|
||||||
let password = "hello world".into();
|
let password = "hello world".into();
|
||||||
let directory = RootDiskDirectory::create(dir.clone()).unwrap();
|
let directory = RootDiskDirectory::create(dir.clone()).unwrap();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let account = SafeAccount::create(&keypair, [0u8; 16], &password, *ITERATIONS, "Test".to_owned(), "{}".to_owned()).unwrap();
|
let account = SafeAccount::create(
|
||||||
let filename = "test".to_string();
|
&keypair,
|
||||||
let dedup = true;
|
[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();
|
directory
|
||||||
let file1 = directory.insert_with_filename(account.clone(), filename.clone(), dedup).unwrap().filename.unwrap();
|
.insert_with_filename(account.clone(), "foo".to_string(), dedup)
|
||||||
let file2 = directory.insert_with_filename(account.clone(), filename.clone(), dedup).unwrap().filename.unwrap();
|
.unwrap();
|
||||||
let file3 = directory.insert_with_filename(account.clone(), filename.clone(), dedup).unwrap().filename.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
|
// then
|
||||||
// the first file should have the original names
|
// the first file should have the original names
|
||||||
assert_eq!(file1, filename);
|
assert_eq!(file1, filename);
|
||||||
|
|
||||||
// the following duplicate files should have a suffix appended
|
// the following duplicate files should have a suffix appended
|
||||||
assert!(file2 != file3);
|
assert!(file2 != file3);
|
||||||
assert_eq!(file2.len(), filename.len() + 5);
|
assert_eq!(file2.len(), filename.len() + 5);
|
||||||
assert_eq!(file3.len(), filename.len() + 5);
|
assert_eq!(file3.len(), filename.len() + 5);
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
let _ = fs::remove_dir_all(dir);
|
let _ = fs::remove_dir_all(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_manage_vaults() {
|
fn should_manage_vaults() {
|
||||||
// given
|
// given
|
||||||
let mut dir = env::temp_dir();
|
let mut dir = env::temp_dir();
|
||||||
dir.push("should_create_new_vault");
|
dir.push("should_create_new_vault");
|
||||||
let directory = RootDiskDirectory::create(dir.clone()).unwrap();
|
let directory = RootDiskDirectory::create(dir.clone()).unwrap();
|
||||||
let vault_name = "vault";
|
let vault_name = "vault";
|
||||||
let password = "password".into();
|
let password = "password".into();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert!(directory.as_vault_provider().is_some());
|
assert!(directory.as_vault_provider().is_some());
|
||||||
|
|
||||||
// and when
|
// and when
|
||||||
let before_root_items_count = fs::read_dir(&dir).unwrap().count();
|
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));
|
let vault = directory
|
||||||
|
.as_vault_provider()
|
||||||
|
.unwrap()
|
||||||
|
.create(vault_name, VaultKey::new(&password, *ITERATIONS));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert!(vault.is_ok());
|
assert!(vault.is_ok());
|
||||||
let after_root_items_count = fs::read_dir(&dir).unwrap().count();
|
let after_root_items_count = fs::read_dir(&dir).unwrap().count();
|
||||||
assert!(after_root_items_count > before_root_items_count);
|
assert!(after_root_items_count > before_root_items_count);
|
||||||
|
|
||||||
// and when
|
// and when
|
||||||
let vault = directory.as_vault_provider().unwrap().open(vault_name, VaultKey::new(&password, *ITERATIONS));
|
let vault = directory
|
||||||
|
.as_vault_provider()
|
||||||
|
.unwrap()
|
||||||
|
.open(vault_name, VaultKey::new(&password, *ITERATIONS));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert!(vault.is_ok());
|
assert!(vault.is_ok());
|
||||||
let after_root_items_count2 = fs::read_dir(&dir).unwrap().count();
|
let after_root_items_count2 = fs::read_dir(&dir).unwrap().count();
|
||||||
assert!(after_root_items_count == after_root_items_count2);
|
assert!(after_root_items_count == after_root_items_count2);
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
let _ = fs::remove_dir_all(dir);
|
let _ = fs::remove_dir_all(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_list_vaults() {
|
fn should_list_vaults() {
|
||||||
// given
|
// given
|
||||||
let temp_path = TempDir::new("").unwrap();
|
let temp_path = TempDir::new("").unwrap();
|
||||||
let directory = RootDiskDirectory::create(&temp_path).unwrap();
|
let directory = RootDiskDirectory::create(&temp_path).unwrap();
|
||||||
let vault_provider = directory.as_vault_provider().unwrap();
|
let vault_provider = directory.as_vault_provider().unwrap();
|
||||||
let iter = NonZeroU32::new(1).expect("1 > 0; qed");
|
let iter = NonZeroU32::new(1).expect("1 > 0; qed");
|
||||||
vault_provider.create("vault1", VaultKey::new(&"password1".into(), iter)).unwrap();
|
vault_provider
|
||||||
vault_provider.create("vault2", VaultKey::new(&"password2".into(), iter)).unwrap();
|
.create("vault1", VaultKey::new(&"password1".into(), iter))
|
||||||
|
.unwrap();
|
||||||
|
vault_provider
|
||||||
|
.create("vault2", VaultKey::new(&"password2".into(), iter))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
let vaults = vault_provider.list_vaults().unwrap();
|
let vaults = vault_provider.list_vaults().unwrap();
|
||||||
assert_eq!(vaults.len(), 2);
|
assert_eq!(vaults.len(), 2);
|
||||||
assert!(vaults.iter().any(|v| &*v == "vault1"));
|
assert!(vaults.iter().any(|v| &*v == "vault1"));
|
||||||
assert!(vaults.iter().any(|v| &*v == "vault2"));
|
assert!(vaults.iter().any(|v| &*v == "vault2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hash_of_files() {
|
fn hash_of_files() {
|
||||||
let temp_path = TempDir::new("").unwrap();
|
let temp_path = TempDir::new("").unwrap();
|
||||||
let directory = RootDiskDirectory::create(&temp_path).unwrap();
|
let directory = RootDiskDirectory::create(&temp_path).unwrap();
|
||||||
|
|
||||||
let hash = directory.files_hash().expect("Files hash should be calculated ok");
|
let hash = directory
|
||||||
assert_eq!(
|
.files_hash()
|
||||||
hash,
|
.expect("Files hash should be calculated ok");
|
||||||
15130871412783076140
|
assert_eq!(hash, 15130871412783076140);
|
||||||
);
|
|
||||||
|
|
||||||
let keypair = Random.generate().unwrap();
|
let keypair = Random.generate().unwrap();
|
||||||
let password = "test pass".into();
|
let password = "test pass".into();
|
||||||
let account = SafeAccount::create(&keypair, [0u8; 16], &password, *ITERATIONS, "Test".to_owned(), "{}".to_owned());
|
let account = SafeAccount::create(
|
||||||
directory.insert(account.unwrap()).expect("Account should be inserted ok");
|
&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");
|
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");
|
assert!(
|
||||||
}
|
new_hash != hash,
|
||||||
|
"hash of the file list should change once directory content changed"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,74 +1,77 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use parking_lot::RwLock;
|
|
||||||
use itertools;
|
|
||||||
use ethkey::Address;
|
use ethkey::Address;
|
||||||
|
use itertools;
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use {SafeAccount, Error};
|
|
||||||
use super::KeyDirectory;
|
use super::KeyDirectory;
|
||||||
|
use Error;
|
||||||
|
use SafeAccount;
|
||||||
|
|
||||||
/// Accounts in-memory storage.
|
/// Accounts in-memory storage.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct MemoryDirectory {
|
pub struct MemoryDirectory {
|
||||||
accounts: RwLock<HashMap<Address, Vec<SafeAccount>>>,
|
accounts: RwLock<HashMap<Address, Vec<SafeAccount>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyDirectory for MemoryDirectory {
|
impl KeyDirectory for MemoryDirectory {
|
||||||
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
||||||
Ok(itertools::Itertools::flatten(self.accounts.read().values().cloned()).collect())
|
Ok(itertools::Itertools::flatten(self.accounts.read().values().cloned()).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
let mut lock = self.accounts.write();
|
let mut lock = self.accounts.write();
|
||||||
let accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new);
|
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
|
// If the filename is the same we just need to replace the entry
|
||||||
accounts.retain(|acc| acc.filename != account.filename);
|
accounts.retain(|acc| acc.filename != account.filename);
|
||||||
accounts.push(account.clone());
|
accounts.push(account.clone());
|
||||||
Ok(account)
|
Ok(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
let mut lock = self.accounts.write();
|
let mut lock = self.accounts.write();
|
||||||
let accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new);
|
let accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new);
|
||||||
accounts.push(account.clone());
|
accounts.push(account.clone());
|
||||||
Ok(account)
|
Ok(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
let mut accounts = self.accounts.write();
|
let mut accounts = self.accounts.write();
|
||||||
let is_empty = if let Some(accounts) = accounts.get_mut(&account.address) {
|
let is_empty = if let Some(accounts) = accounts.get_mut(&account.address) {
|
||||||
if let Some(position) = accounts.iter().position(|acc| acc == account) {
|
if let Some(position) = accounts.iter().position(|acc| acc == account) {
|
||||||
accounts.remove(position);
|
accounts.remove(position);
|
||||||
}
|
}
|
||||||
accounts.is_empty()
|
accounts.is_empty()
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
if is_empty {
|
if is_empty {
|
||||||
accounts.remove(&account.address);
|
accounts.remove(&account.address);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unique_repr(&self) -> Result<u64, Error> {
|
fn unique_repr(&self) -> Result<u64, Error> {
|
||||||
let mut val = 0u64;
|
let mut val = 0u64;
|
||||||
let accounts = self.accounts.read();
|
let accounts = self.accounts.read();
|
||||||
for acc in accounts.keys() { val = val ^ acc.low_u64() }
|
for acc in accounts.keys() {
|
||||||
Ok(val)
|
val = val ^ acc.low_u64()
|
||||||
}
|
}
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//! Accounts Directory
|
//! Accounts Directory
|
||||||
|
|
||||||
use ethkey::Password;
|
use ethkey::Password;
|
||||||
use std::num::NonZeroU32;
|
use std::{num::NonZeroU32, path::PathBuf};
|
||||||
use std::path::{PathBuf};
|
use Error;
|
||||||
use {SafeAccount, Error};
|
use SafeAccount;
|
||||||
|
|
||||||
mod disk;
|
mod disk;
|
||||||
mod memory;
|
mod memory;
|
||||||
@@ -28,79 +28,85 @@ mod vault;
|
|||||||
/// `VaultKeyDirectory::set_key` error
|
/// `VaultKeyDirectory::set_key` error
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SetKeyError {
|
pub enum SetKeyError {
|
||||||
/// Error is fatal and directory is probably in inconsistent state
|
/// Error is fatal and directory is probably in inconsistent state
|
||||||
Fatal(Error),
|
Fatal(Error),
|
||||||
/// Error is non fatal, directory is reverted to pre-operation state
|
/// Error is non fatal, directory is reverted to pre-operation state
|
||||||
NonFatalOld(Error),
|
NonFatalOld(Error),
|
||||||
/// Error is non fatal, directory is consistent with new key
|
/// Error is non fatal, directory is consistent with new key
|
||||||
NonFatalNew(Error),
|
NonFatalNew(Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Vault key
|
/// Vault key
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
pub struct VaultKey {
|
pub struct VaultKey {
|
||||||
/// Vault password
|
/// Vault password
|
||||||
pub password: Password,
|
pub password: Password,
|
||||||
/// Number of iterations to produce a derived key from password
|
/// Number of iterations to produce a derived key from password
|
||||||
pub iterations: NonZeroU32,
|
pub iterations: NonZeroU32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keys directory
|
/// Keys directory
|
||||||
pub trait KeyDirectory: Send + Sync {
|
pub trait KeyDirectory: Send + Sync {
|
||||||
/// Read keys from directory
|
/// Read keys from directory
|
||||||
fn load(&self) -> Result<Vec<SafeAccount>, Error>;
|
fn load(&self) -> Result<Vec<SafeAccount>, Error>;
|
||||||
/// Insert new key to directory
|
/// Insert new key to directory
|
||||||
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error>;
|
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error>;
|
||||||
/// Update key in the directory
|
/// Update key in the directory
|
||||||
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error>;
|
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error>;
|
||||||
/// Remove key from directory
|
/// Remove key from directory
|
||||||
fn remove(&self, account: &SafeAccount) -> Result<(), Error>;
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error>;
|
||||||
/// Get directory filesystem path, if available
|
/// Get directory filesystem path, if available
|
||||||
fn path(&self) -> Option<&PathBuf> { None }
|
fn path(&self) -> Option<&PathBuf> {
|
||||||
/// Return vault provider, if available
|
None
|
||||||
fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> { None }
|
}
|
||||||
/// Unique representation of directory account collection
|
/// Return vault provider, if available
|
||||||
fn unique_repr(&self) -> Result<u64, Error>;
|
fn as_vault_provider(&self) -> Option<&dyn VaultKeyDirectoryProvider> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
/// Unique representation of directory account collection
|
||||||
|
fn unique_repr(&self) -> Result<u64, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Vaults provider
|
/// Vaults provider
|
||||||
pub trait VaultKeyDirectoryProvider {
|
pub trait VaultKeyDirectoryProvider {
|
||||||
/// Create new vault with given key
|
/// Create new vault with given key
|
||||||
fn create(&self, name: &str, key: VaultKey) -> Result<Box<VaultKeyDirectory>, Error>;
|
fn create(&self, name: &str, key: VaultKey) -> Result<Box<dyn VaultKeyDirectory>, Error>;
|
||||||
/// Open existing vault with given key
|
/// Open existing vault with given key
|
||||||
fn open(&self, name: &str, key: VaultKey) -> Result<Box<VaultKeyDirectory>, Error>;
|
fn open(&self, name: &str, key: VaultKey) -> Result<Box<dyn VaultKeyDirectory>, Error>;
|
||||||
/// List all vaults
|
/// List all vaults
|
||||||
fn list_vaults(&self) -> Result<Vec<String>, Error>;
|
fn list_vaults(&self) -> Result<Vec<String>, Error>;
|
||||||
/// Get vault meta
|
/// Get vault meta
|
||||||
fn vault_meta(&self, name: &str) -> Result<String, Error>;
|
fn vault_meta(&self, name: &str) -> Result<String, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Vault directory
|
/// Vault directory
|
||||||
pub trait VaultKeyDirectory: KeyDirectory {
|
pub trait VaultKeyDirectory: KeyDirectory {
|
||||||
/// Cast to `KeyDirectory`
|
/// Cast to `KeyDirectory`
|
||||||
fn as_key_directory(&self) -> &KeyDirectory;
|
fn as_key_directory(&self) -> &dyn KeyDirectory;
|
||||||
/// Vault name
|
/// Vault name
|
||||||
fn name(&self) -> &str;
|
fn name(&self) -> &str;
|
||||||
/// Get vault key
|
/// Get vault key
|
||||||
fn key(&self) -> VaultKey;
|
fn key(&self) -> VaultKey;
|
||||||
/// Set new key for vault
|
/// Set new key for vault
|
||||||
fn set_key(&self, key: VaultKey) -> Result<(), SetKeyError>;
|
fn set_key(&self, key: VaultKey) -> Result<(), SetKeyError>;
|
||||||
/// Get vault meta
|
/// Get vault meta
|
||||||
fn meta(&self) -> String;
|
fn meta(&self) -> String;
|
||||||
/// Set vault meta
|
/// Set vault meta
|
||||||
fn set_meta(&self, meta: &str) -> Result<(), Error>;
|
fn set_meta(&self, meta: &str) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use self::disk::{RootDiskDirectory, DiskKeyFileManager, KeyFileManager};
|
pub use self::{
|
||||||
pub use self::memory::MemoryDirectory;
|
disk::{DiskKeyFileManager, KeyFileManager, RootDiskDirectory},
|
||||||
pub use self::vault::VaultDiskDirectory;
|
memory::MemoryDirectory,
|
||||||
|
vault::VaultDiskDirectory,
|
||||||
|
};
|
||||||
|
|
||||||
impl VaultKey {
|
impl VaultKey {
|
||||||
/// Create new vault key
|
/// Create new vault key
|
||||||
pub fn new(password: &Password, iterations: NonZeroU32) -> Self {
|
pub fn new(password: &Password, iterations: NonZeroU32) -> Self {
|
||||||
VaultKey {
|
VaultKey {
|
||||||
password: password.clone(),
|
password: password.clone(),
|
||||||
iterations: iterations,
|
iterations: iterations,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,33 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::{fs, io};
|
use super::{
|
||||||
use std::path::{PathBuf, Path};
|
super::account::Crypto,
|
||||||
use parking_lot::Mutex;
|
disk::{self, DiskDirectory, KeyFileManager},
|
||||||
use {json, SafeAccount, Error};
|
KeyDirectory, SetKeyError, VaultKey, VaultKeyDirectory,
|
||||||
|
};
|
||||||
use crypto::Keccak256;
|
use crypto::Keccak256;
|
||||||
use super::super::account::Crypto;
|
use json;
|
||||||
use super::{KeyDirectory, VaultKeyDirectory, VaultKey, SetKeyError};
|
use parking_lot::Mutex;
|
||||||
use super::disk::{self, DiskDirectory, KeyFileManager};
|
use std::{
|
||||||
|
fs, io,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
use Error;
|
||||||
|
use SafeAccount;
|
||||||
|
|
||||||
/// Name of vault metadata file
|
/// Name of vault metadata file
|
||||||
pub const VAULT_FILE_NAME: &'static str = "vault.json";
|
pub const VAULT_FILE_NAME: &'static str = "vault.json";
|
||||||
@@ -33,417 +39,489 @@ pub type VaultDiskDirectory = DiskDirectory<VaultKeyFileManager>;
|
|||||||
|
|
||||||
/// Vault key file manager
|
/// Vault key file manager
|
||||||
pub struct VaultKeyFileManager {
|
pub struct VaultKeyFileManager {
|
||||||
name: String,
|
name: String,
|
||||||
key: VaultKey,
|
key: VaultKey,
|
||||||
meta: Mutex<String>,
|
meta: Mutex<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VaultDiskDirectory {
|
impl VaultDiskDirectory {
|
||||||
/// Create new vault directory with given key
|
/// Create new vault directory with given key
|
||||||
pub fn create<P>(root: P, name: &str, key: VaultKey) -> Result<Self, Error> where P: AsRef<Path> {
|
pub fn create<P>(root: P, name: &str, key: VaultKey) -> Result<Self, Error>
|
||||||
// check that vault directory does not exists
|
where
|
||||||
let vault_dir_path = make_vault_dir_path(root, name, true)?;
|
P: AsRef<Path>,
|
||||||
if vault_dir_path.exists() {
|
{
|
||||||
return Err(Error::CreationFailed);
|
// 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
|
// create vault && vault file
|
||||||
let vault_meta = "{}";
|
let vault_meta = "{}";
|
||||||
fs::create_dir_all(&vault_dir_path)?;
|
fs::create_dir_all(&vault_dir_path)?;
|
||||||
if let Err(err) = create_vault_file(&vault_dir_path, &key, vault_meta) {
|
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
|
let _ = fs::remove_dir_all(&vault_dir_path); // can't do anything with this
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(name, key, vault_meta)))
|
Ok(DiskDirectory::new(
|
||||||
}
|
vault_dir_path,
|
||||||
|
VaultKeyFileManager::new(name, key, vault_meta),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// Open existing vault directory with given key
|
/// Open existing vault directory with given key
|
||||||
pub fn at<P>(root: P, name: &str, key: VaultKey) -> Result<Self, Error> where P: AsRef<Path> {
|
pub fn at<P>(root: P, name: &str, key: VaultKey) -> Result<Self, Error>
|
||||||
// check that vault directory exists
|
where
|
||||||
let vault_dir_path = make_vault_dir_path(root, name, true)?;
|
P: AsRef<Path>,
|
||||||
if !vault_dir_path.is_dir() {
|
{
|
||||||
return Err(Error::CreationFailed);
|
// 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
|
// check that passed key matches vault file
|
||||||
let meta = read_vault_file(&vault_dir_path, Some(&key))?;
|
let meta = read_vault_file(&vault_dir_path, Some(&key))?;
|
||||||
|
|
||||||
Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(name, key, &meta)))
|
Ok(DiskDirectory::new(
|
||||||
}
|
vault_dir_path,
|
||||||
|
VaultKeyFileManager::new(name, key, &meta),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// Read vault meta without actually opening the vault
|
/// Read vault meta without actually opening the vault
|
||||||
pub fn meta_at<P>(root: P, name: &str) -> Result<String, Error> where P: AsRef<Path> {
|
pub fn meta_at<P>(root: P, name: &str) -> Result<String, Error>
|
||||||
// check that vault directory exists
|
where
|
||||||
let vault_dir_path = make_vault_dir_path(root, name, true)?;
|
P: AsRef<Path>,
|
||||||
if !vault_dir_path.is_dir() {
|
{
|
||||||
return Err(Error::VaultNotFound);
|
// 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
|
// check that passed key matches vault file
|
||||||
read_vault_file(&vault_dir_path, None)
|
read_vault_file(&vault_dir_path, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_temp_vault(&self, key: VaultKey) -> Result<VaultDiskDirectory, Error> {
|
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 original_path = self
|
||||||
let mut path: PathBuf = original_path.clone();
|
.path()
|
||||||
let name = self.name();
|
.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
|
path.push(name); // to jump to the next level
|
||||||
|
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
loop {
|
loop {
|
||||||
let name = format!("{}_temp_{}", name, index);
|
let name = format!("{}_temp_{}", name, index);
|
||||||
path.set_file_name(&name);
|
path.set_file_name(&name);
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return VaultDiskDirectory::create(original_path, &name, key);
|
return VaultDiskDirectory::create(original_path, &name, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_to_vault(&self, vault: &VaultDiskDirectory) -> Result<(), Error> {
|
fn copy_to_vault(&self, vault: &VaultDiskDirectory) -> Result<(), Error> {
|
||||||
for account in self.load()? {
|
for account in self.load()? {
|
||||||
let filename = account.filename.clone().expect("self is instance of DiskDirectory; DiskDirectory fills filename in load; qed");
|
let filename = account.filename.clone().expect(
|
||||||
vault.insert_with_filename(account, filename, true)?;
|
"self is instance of DiskDirectory; DiskDirectory fills filename in load; qed",
|
||||||
}
|
);
|
||||||
|
vault.insert_with_filename(account, filename, true)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete(&self) -> Result<(), Error> {
|
fn delete(&self) -> Result<(), Error> {
|
||||||
let path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed");
|
let path = self
|
||||||
fs::remove_dir_all(path).map_err(Into::into)
|
.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 {
|
impl VaultKeyDirectory for VaultDiskDirectory {
|
||||||
fn as_key_directory(&self) -> &KeyDirectory {
|
fn as_key_directory(&self) -> &dyn KeyDirectory {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
&self.key_manager().name
|
&self.key_manager().name
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key(&self) -> VaultKey {
|
fn key(&self) -> VaultKey {
|
||||||
self.key_manager().key.clone()
|
self.key_manager().key.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_key(&self, new_key: VaultKey) -> Result<(), SetKeyError> {
|
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 temp_vault = VaultDiskDirectory::create_temp_vault(self, new_key.clone())
|
||||||
let mut source_path = temp_vault.path().expect("temp_vault is instance of DiskDirectory; DiskDirectory always returns path; qed").clone();
|
.map_err(|err| SetKeyError::NonFatalOld(err))?;
|
||||||
let mut target_path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed").clone();
|
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
|
// preserve meta
|
||||||
temp_vault.set_meta(&self.meta()).map_err(SetKeyError::NonFatalOld)?;
|
temp_vault
|
||||||
|
.set_meta(&self.meta())
|
||||||
|
.map_err(SetKeyError::NonFatalOld)?;
|
||||||
|
|
||||||
// jump to next fs level
|
// jump to next fs level
|
||||||
source_path.push("next");
|
source_path.push("next");
|
||||||
target_path.push("next");
|
target_path.push("next");
|
||||||
|
|
||||||
let temp_accounts = self.copy_to_vault(&temp_vault)
|
let temp_accounts = self
|
||||||
.and_then(|_| temp_vault.load())
|
.copy_to_vault(&temp_vault)
|
||||||
.map_err(|err| {
|
.and_then(|_| temp_vault.load())
|
||||||
// ignore error, as we already processing error
|
.map_err(|err| {
|
||||||
let _ = temp_vault.delete();
|
// ignore error, as we already processing error
|
||||||
SetKeyError::NonFatalOld(err)
|
let _ = temp_vault.delete();
|
||||||
})?;
|
SetKeyError::NonFatalOld(err)
|
||||||
|
})?;
|
||||||
|
|
||||||
// we can't just delete temp vault until all files moved, because
|
// we can't just delete temp vault until all files moved, because
|
||||||
// original vault content has already been partially replaced
|
// original vault content has already been partially replaced
|
||||||
// => when error or crash happens here, we can't do anything
|
// => when error or crash happens here, we can't do anything
|
||||||
for temp_account in temp_accounts {
|
for temp_account in temp_accounts {
|
||||||
let filename = temp_account.filename.expect("self is instance of DiskDirectory; DiskDirectory fills filename in load; qed");
|
let filename = temp_account.filename.expect(
|
||||||
source_path.set_file_name(&filename);
|
"self is instance of DiskDirectory; DiskDirectory fills filename in load; qed",
|
||||||
target_path.set_file_name(&filename);
|
);
|
||||||
fs::rename(&source_path, &target_path).map_err(|err| SetKeyError::Fatal(err.into()))?;
|
source_path.set_file_name(&filename);
|
||||||
}
|
target_path.set_file_name(&filename);
|
||||||
source_path.set_file_name(VAULT_FILE_NAME);
|
fs::rename(&source_path, &target_path).map_err(|err| SetKeyError::Fatal(err.into()))?;
|
||||||
target_path.set_file_name(VAULT_FILE_NAME);
|
}
|
||||||
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))
|
temp_vault
|
||||||
}
|
.delete()
|
||||||
|
.map_err(|err| SetKeyError::NonFatalNew(err))
|
||||||
|
}
|
||||||
|
|
||||||
fn meta(&self) -> String {
|
fn meta(&self) -> String {
|
||||||
self.key_manager().meta.lock().clone()
|
self.key_manager().meta.lock().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_meta(&self, meta: &str) -> Result<(), Error> {
|
fn set_meta(&self, meta: &str) -> Result<(), Error> {
|
||||||
let key_manager = self.key_manager();
|
let key_manager = self.key_manager();
|
||||||
let vault_path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed");
|
let vault_path = self
|
||||||
create_vault_file(vault_path, &key_manager.key, meta)?;
|
.path()
|
||||||
*key_manager.meta.lock() = meta.to_owned();
|
.expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed");
|
||||||
Ok(())
|
create_vault_file(vault_path, &key_manager.key, meta)?;
|
||||||
}
|
*key_manager.meta.lock() = meta.to_owned();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VaultKeyFileManager {
|
impl VaultKeyFileManager {
|
||||||
pub fn new(name: &str, key: VaultKey, meta: &str) -> Self {
|
pub fn new(name: &str, key: VaultKey, meta: &str) -> Self {
|
||||||
VaultKeyFileManager {
|
VaultKeyFileManager {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
key: key,
|
key: key,
|
||||||
meta: Mutex::new(meta.to_owned()),
|
meta: Mutex::new(meta.to_owned()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyFileManager for VaultKeyFileManager {
|
impl KeyFileManager for VaultKeyFileManager {
|
||||||
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error> where T: io::Read {
|
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error>
|
||||||
let vault_file = json::VaultKeyFile::load(reader).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
where
|
||||||
let mut safe_account = SafeAccount::from_vault_file(&self.key.password, vault_file, filename.clone())?;
|
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)
|
safe_account.meta = json::insert_vault_name_to_json_meta(&safe_account.meta, &self.name)
|
||||||
.map_err(|err| Error::Custom(format!("{:?}", err)))?;
|
.map_err(|err| Error::Custom(format!("{:?}", err)))?;
|
||||||
Ok(safe_account)
|
Ok(safe_account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write<T>(&self, mut account: SafeAccount, writer: &mut T) -> Result<(), Error> where T: io::Write {
|
fn write<T>(&self, mut account: SafeAccount, writer: &mut T) -> Result<(), Error>
|
||||||
account.meta = json::remove_vault_name_from_json_meta(&account.meta)
|
where
|
||||||
.map_err(|err| Error::Custom(format!("{:?}", err)))?;
|
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)?;
|
let vault_file: json::VaultKeyFile =
|
||||||
vault_file.write(writer).map_err(|e| Error::Custom(format!("{:?}", e)))
|
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
|
/// 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> {
|
fn make_vault_dir_path<P>(root: P, name: &str, check_name: bool) -> Result<PathBuf, Error>
|
||||||
// check vault name
|
where
|
||||||
if check_name && !check_vault_name(name) {
|
P: AsRef<Path>,
|
||||||
return Err(Error::InvalidVaultName);
|
{
|
||||||
}
|
// check vault name
|
||||||
|
if check_name && !check_vault_name(name) {
|
||||||
|
return Err(Error::InvalidVaultName);
|
||||||
|
}
|
||||||
|
|
||||||
let mut vault_dir_path: PathBuf = root.as_ref().into();
|
let mut vault_dir_path: PathBuf = root.as_ref().into();
|
||||||
vault_dir_path.push(name);
|
vault_dir_path.push(name);
|
||||||
Ok(vault_dir_path)
|
Ok(vault_dir_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Every vault must have unique name => we rely on filesystem to check this
|
/// 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
|
/// => vault name must not contain any fs-special characters to avoid directory traversal
|
||||||
/// => we only allow alphanumeric + separator characters in vault name.
|
/// => we only allow alphanumeric + separator characters in vault name.
|
||||||
fn check_vault_name(name: &str) -> bool {
|
fn check_vault_name(name: &str) -> bool {
|
||||||
!name.is_empty()
|
!name.is_empty()
|
||||||
&& name.chars()
|
&& name
|
||||||
.all(|c| c.is_alphanumeric()
|
.chars()
|
||||||
|| c.is_whitespace()
|
.all(|c| c.is_alphanumeric() || c.is_whitespace() || c == '-' || c == '_')
|
||||||
|| c == '-' || c == '_')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Vault can be empty, but still must be pluggable => we store vault password in separate file
|
/// 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> {
|
fn create_vault_file<P>(vault_dir_path: P, key: &VaultKey, meta: &str) -> Result<(), Error>
|
||||||
let password_hash = key.password.as_bytes().keccak256();
|
where
|
||||||
let crypto = Crypto::with_plain(&password_hash, &key.password, key.iterations)?;
|
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 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_name = disk::find_unique_filename_using_random_suffix(
|
||||||
let temp_vault_file_path = vault_dir_path.as_ref().join(&temp_vault_file_name);
|
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
|
// this method is used to rewrite existing vault file
|
||||||
// => write to temporary file first, then rename temporary file to 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 mut vault_file = disk::create_new_file_with_permissions_to_owner(&temp_vault_file_path)?;
|
||||||
let vault_file_contents = json::VaultFile {
|
let vault_file_contents = json::VaultFile {
|
||||||
crypto: crypto.into(),
|
crypto: crypto.into(),
|
||||||
meta: Some(meta.to_owned()),
|
meta: Some(meta.to_owned()),
|
||||||
};
|
};
|
||||||
vault_file_contents.write(&mut vault_file).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
vault_file_contents
|
||||||
drop(vault_file);
|
.write(&mut vault_file)
|
||||||
fs::rename(&temp_vault_file_path, &vault_file_path)?;
|
.map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||||
|
drop(vault_file);
|
||||||
|
fs::rename(&temp_vault_file_path, &vault_file_path)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When vault is opened => we must check that password matches && read metadata
|
/// 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> {
|
fn read_vault_file<P>(vault_dir_path: P, key: Option<&VaultKey>) -> Result<String, Error>
|
||||||
let mut vault_file_path: PathBuf = vault_dir_path.as_ref().into();
|
where
|
||||||
vault_file_path.push(VAULT_FILE_NAME);
|
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 = 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_contents =
|
||||||
let vault_file_meta = vault_file_contents.meta.unwrap_or("{}".to_owned());
|
json::VaultFile::load(vault_file).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||||
let vault_file_crypto: Crypto = vault_file_contents.crypto.into();
|
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 {
|
if let Some(key) = key {
|
||||||
let password_bytes = vault_file_crypto.decrypt(&key.password)?;
|
let password_bytes = vault_file_crypto.decrypt(&key.password)?;
|
||||||
let password_hash = key.password.as_bytes().keccak256();
|
let password_hash = key.password.as_bytes().keccak256();
|
||||||
if password_hash != password_bytes.as_slice() {
|
if password_hash != password_bytes.as_slice() {
|
||||||
return Err(Error::InvalidPassword);
|
return Err(Error::InvalidPassword);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(vault_file_meta)
|
Ok(vault_file_meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
extern crate tempdir;
|
extern crate tempdir;
|
||||||
|
|
||||||
use std::fs;
|
use self::tempdir::TempDir;
|
||||||
use std::io::Write;
|
use super::{
|
||||||
use std::num::NonZeroU32;
|
check_vault_name, create_vault_file, make_vault_dir_path, read_vault_file,
|
||||||
use std::path::PathBuf;
|
VaultDiskDirectory, VaultKey, VAULT_FILE_NAME,
|
||||||
use super::VaultKey;
|
};
|
||||||
use super::{VAULT_FILE_NAME, check_vault_name, make_vault_dir_path, create_vault_file, read_vault_file, VaultDiskDirectory};
|
use std::{fs, io::Write, num::NonZeroU32, path::PathBuf};
|
||||||
use self::tempdir::TempDir;
|
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
lazy_static! {
|
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(1024).expect("1024 > 0; qed");
|
||||||
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(1024).expect("1024 > 0; qed");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_vault_name_succeeds() {
|
fn check_vault_name_succeeds() {
|
||||||
assert!(check_vault_name("vault"));
|
assert!(check_vault_name("vault"));
|
||||||
assert!(check_vault_name("vault with spaces"));
|
assert!(check_vault_name("vault with spaces"));
|
||||||
assert!(check_vault_name("vault with tabs"));
|
assert!(check_vault_name("vault with tabs"));
|
||||||
assert!(check_vault_name("vault_with_underscores"));
|
assert!(check_vault_name("vault_with_underscores"));
|
||||||
assert!(check_vault_name("vault-with-dashes"));
|
assert!(check_vault_name("vault-with-dashes"));
|
||||||
assert!(check_vault_name("vault-with-digits-123"));
|
assert!(check_vault_name("vault-with-digits-123"));
|
||||||
assert!(check_vault_name("vault中文名字"));
|
assert!(check_vault_name("vault中文名字"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_vault_name_fails() {
|
fn check_vault_name_fails() {
|
||||||
assert!(!check_vault_name(""));
|
assert!(!check_vault_name(""));
|
||||||
assert!(!check_vault_name("."));
|
assert!(!check_vault_name("."));
|
||||||
assert!(!check_vault_name("*"));
|
assert!(!check_vault_name("*"));
|
||||||
assert!(!check_vault_name("../.bash_history"));
|
assert!(!check_vault_name("../.bash_history"));
|
||||||
assert!(!check_vault_name("/etc/passwd"));
|
assert!(!check_vault_name("/etc/passwd"));
|
||||||
assert!(!check_vault_name("c:\\windows"));
|
assert!(!check_vault_name("c:\\windows"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_vault_dir_path_succeeds() {
|
fn make_vault_dir_path_succeeds() {
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
assert_eq!(&make_vault_dir_path("/home/user/parity", "vault", true).unwrap(), &Path::new("/home/user/parity/vault"));
|
assert_eq!(
|
||||||
assert_eq!(&make_vault_dir_path("/home/user/parity", "*bad-name*", false).unwrap(), &Path::new("/home/user/parity/*bad-name*"));
|
&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]
|
#[test]
|
||||||
fn make_vault_dir_path_fails() {
|
fn make_vault_dir_path_fails() {
|
||||||
assert!(make_vault_dir_path("/home/user/parity", "*bad-name*", true).is_err());
|
assert!(make_vault_dir_path("/home/user/parity", "*bad-name*", true).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_vault_file_succeeds() {
|
fn create_vault_file_succeeds() {
|
||||||
// given
|
// given
|
||||||
let temp_path = TempDir::new("").unwrap();
|
let temp_path = TempDir::new("").unwrap();
|
||||||
let key = VaultKey::new(&"password".into(), *ITERATIONS);
|
let key = VaultKey::new(&"password".into(), *ITERATIONS);
|
||||||
let mut vault_dir: PathBuf = temp_path.path().into();
|
let mut vault_dir: PathBuf = temp_path.path().into();
|
||||||
vault_dir.push("vault");
|
vault_dir.push("vault");
|
||||||
fs::create_dir_all(&vault_dir).unwrap();
|
fs::create_dir_all(&vault_dir).unwrap();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let result = create_vault_file(&vault_dir, &key, "{}");
|
let result = create_vault_file(&vault_dir, &key, "{}");
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
let mut vault_file_path = vault_dir.clone();
|
let mut vault_file_path = vault_dir.clone();
|
||||||
vault_file_path.push(VAULT_FILE_NAME);
|
vault_file_path.push(VAULT_FILE_NAME);
|
||||||
assert!(vault_file_path.exists() && vault_file_path.is_file());
|
assert!(vault_file_path.exists() && vault_file_path.is_file());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read_vault_file_succeeds() {
|
fn read_vault_file_succeeds() {
|
||||||
// given
|
// given
|
||||||
let temp_path = TempDir::new("").unwrap();
|
let temp_path = TempDir::new("").unwrap();
|
||||||
let key = VaultKey::new(&"password".into(), *ITERATIONS);
|
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 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 dir: PathBuf = temp_path.path().into();
|
||||||
let mut vault_file_path: PathBuf = dir.clone();
|
let mut vault_file_path: PathBuf = dir.clone();
|
||||||
vault_file_path.push(VAULT_FILE_NAME);
|
vault_file_path.push(VAULT_FILE_NAME);
|
||||||
{
|
{
|
||||||
let mut vault_file = fs::File::create(vault_file_path).unwrap();
|
let mut vault_file = fs::File::create(vault_file_path).unwrap();
|
||||||
vault_file.write_all(vault_file_contents.as_bytes()).unwrap();
|
vault_file
|
||||||
}
|
.write_all(vault_file_contents.as_bytes())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let result = read_vault_file(&dir, Some(&key));
|
let result = read_vault_file(&dir, Some(&key));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read_vault_file_fails() {
|
fn read_vault_file_fails() {
|
||||||
// given
|
// given
|
||||||
let temp_path = TempDir::new("").unwrap();
|
let temp_path = TempDir::new("").unwrap();
|
||||||
let key = VaultKey::new(&"password1".into(), *ITERATIONS);
|
let key = VaultKey::new(&"password1".into(), *ITERATIONS);
|
||||||
let dir: PathBuf = temp_path.path().into();
|
let dir: PathBuf = temp_path.path().into();
|
||||||
let mut vault_file_path: PathBuf = dir.clone();
|
let mut vault_file_path: PathBuf = dir.clone();
|
||||||
vault_file_path.push(VAULT_FILE_NAME);
|
vault_file_path.push(VAULT_FILE_NAME);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let result = read_vault_file(&dir, Some(&key));
|
let result = read_vault_file(&dir, Some(&key));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
|
|
||||||
// and when given
|
// 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 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();
|
let mut vault_file = fs::File::create(vault_file_path).unwrap();
|
||||||
vault_file.write_all(vault_file_contents.as_bytes()).unwrap();
|
vault_file
|
||||||
}
|
.write_all(vault_file_contents.as_bytes())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let result = read_vault_file(&dir, Some(&key));
|
let result = read_vault_file(&dir, Some(&key));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn vault_directory_can_be_created() {
|
fn vault_directory_can_be_created() {
|
||||||
// given
|
// given
|
||||||
let temp_path = TempDir::new("").unwrap();
|
let temp_path = TempDir::new("").unwrap();
|
||||||
let key = VaultKey::new(&"password".into(), *ITERATIONS);
|
let key = VaultKey::new(&"password".into(), *ITERATIONS);
|
||||||
let dir: PathBuf = temp_path.path().into();
|
let dir: PathBuf = temp_path.path().into();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let vault = VaultDiskDirectory::create(&dir, "vault", key.clone());
|
let vault = VaultDiskDirectory::create(&dir, "vault", key.clone());
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert!(vault.is_ok());
|
assert!(vault.is_ok());
|
||||||
|
|
||||||
// and when
|
// and when
|
||||||
let vault = VaultDiskDirectory::at(&dir, "vault", key);
|
let vault = VaultDiskDirectory::at(&dir, "vault", key);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert!(vault.is_ok());
|
assert!(vault.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn vault_directory_cannot_be_created_if_already_exists() {
|
fn vault_directory_cannot_be_created_if_already_exists() {
|
||||||
// given
|
// given
|
||||||
let temp_path = TempDir::new("").unwrap();
|
let temp_path = TempDir::new("").unwrap();
|
||||||
let key = VaultKey::new(&"password".into(), *ITERATIONS);
|
let key = VaultKey::new(&"password".into(), *ITERATIONS);
|
||||||
let dir: PathBuf = temp_path.path().into();
|
let dir: PathBuf = temp_path.path().into();
|
||||||
let mut vault_dir = dir.clone();
|
let mut vault_dir = dir.clone();
|
||||||
vault_dir.push("vault");
|
vault_dir.push("vault");
|
||||||
fs::create_dir_all(&vault_dir).unwrap();
|
fs::create_dir_all(&vault_dir).unwrap();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let vault = VaultDiskDirectory::create(&dir, "vault", key);
|
let vault = VaultDiskDirectory::create(&dir, "vault", key);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert!(vault.is_err());
|
assert!(vault.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn vault_directory_cannot_be_opened_if_not_exists() {
|
fn vault_directory_cannot_be_opened_if_not_exists() {
|
||||||
// given
|
// given
|
||||||
let temp_path = TempDir::new("").unwrap();
|
let temp_path = TempDir::new("").unwrap();
|
||||||
let key = VaultKey::new(&"password".into(), *ITERATIONS);
|
let key = VaultKey::new(&"password".into(), *ITERATIONS);
|
||||||
let dir: PathBuf = temp_path.path().into();
|
let dir: PathBuf = temp_path.path().into();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let vault = VaultDiskDirectory::at(&dir, "vault", key);
|
let vault = VaultDiskDirectory::at(&dir, "vault", key);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert!(vault.is_err());
|
assert!(vault.is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,128 +1,126 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
use std::io::Error as IoError;
|
|
||||||
use ethkey::{self, Error as EthKeyError};
|
|
||||||
use crypto::{self, Error as EthCryptoError};
|
use crypto::{self, Error as EthCryptoError};
|
||||||
use ethkey::DerivationError;
|
use ethkey::{self, DerivationError, Error as EthKeyError};
|
||||||
|
use std::{fmt, io::Error as IoError};
|
||||||
|
|
||||||
/// Account-related errors.
|
/// Account-related errors.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// IO error
|
/// IO error
|
||||||
Io(IoError),
|
Io(IoError),
|
||||||
/// Invalid Password
|
/// Invalid Password
|
||||||
InvalidPassword,
|
InvalidPassword,
|
||||||
/// Account's secret is invalid.
|
/// Account's secret is invalid.
|
||||||
InvalidSecret,
|
InvalidSecret,
|
||||||
/// Invalid Vault Crypto meta.
|
/// Invalid Vault Crypto meta.
|
||||||
InvalidCryptoMeta,
|
InvalidCryptoMeta,
|
||||||
/// Invalid Account.
|
/// Invalid Account.
|
||||||
InvalidAccount,
|
InvalidAccount,
|
||||||
/// Invalid Message.
|
/// Invalid Message.
|
||||||
InvalidMessage,
|
InvalidMessage,
|
||||||
/// Invalid Key File
|
/// Invalid Key File
|
||||||
InvalidKeyFile(String),
|
InvalidKeyFile(String),
|
||||||
/// Vaults are not supported.
|
/// Vaults are not supported.
|
||||||
VaultsAreNotSupported,
|
VaultsAreNotSupported,
|
||||||
/// Unsupported vault
|
/// Unsupported vault
|
||||||
UnsupportedVault,
|
UnsupportedVault,
|
||||||
/// Invalid vault name
|
/// Invalid vault name
|
||||||
InvalidVaultName,
|
InvalidVaultName,
|
||||||
/// Vault not found
|
/// Vault not found
|
||||||
VaultNotFound,
|
VaultNotFound,
|
||||||
/// Account creation failed.
|
/// Account creation failed.
|
||||||
CreationFailed,
|
CreationFailed,
|
||||||
/// `EthKey` error
|
/// `EthKey` error
|
||||||
EthKey(EthKeyError),
|
EthKey(EthKeyError),
|
||||||
/// `ethkey::crypto::Error`
|
/// `ethkey::crypto::Error`
|
||||||
EthKeyCrypto(ethkey::crypto::Error),
|
EthKeyCrypto(ethkey::crypto::Error),
|
||||||
/// `EthCrypto` error
|
/// `EthCrypto` error
|
||||||
EthCrypto(EthCryptoError),
|
EthCrypto(EthCryptoError),
|
||||||
/// Derivation error
|
/// Derivation error
|
||||||
Derivation(DerivationError),
|
Derivation(DerivationError),
|
||||||
/// Custom error
|
/// Custom error
|
||||||
Custom(String),
|
Custom(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
let s = match *self {
|
let s = match *self {
|
||||||
Error::Io(ref err) => err.to_string(),
|
Error::Io(ref err) => err.to_string(),
|
||||||
Error::InvalidPassword => "Invalid password".into(),
|
Error::InvalidPassword => "Invalid password".into(),
|
||||||
Error::InvalidSecret => "Invalid secret".into(),
|
Error::InvalidSecret => "Invalid secret".into(),
|
||||||
Error::InvalidCryptoMeta => "Invalid crypted metadata".into(),
|
Error::InvalidCryptoMeta => "Invalid crypted metadata".into(),
|
||||||
Error::InvalidAccount => "Invalid account".into(),
|
Error::InvalidAccount => "Invalid account".into(),
|
||||||
Error::InvalidMessage => "Invalid message".into(),
|
Error::InvalidMessage => "Invalid message".into(),
|
||||||
Error::InvalidKeyFile(ref reason) => format!("Invalid key file: {}", reason),
|
Error::InvalidKeyFile(ref reason) => format!("Invalid key file: {}", reason),
|
||||||
Error::VaultsAreNotSupported => "Vaults are not supported".into(),
|
Error::VaultsAreNotSupported => "Vaults are not supported".into(),
|
||||||
Error::UnsupportedVault => "Vault is not supported for this operation".into(),
|
Error::UnsupportedVault => "Vault is not supported for this operation".into(),
|
||||||
Error::InvalidVaultName => "Invalid vault name".into(),
|
Error::InvalidVaultName => "Invalid vault name".into(),
|
||||||
Error::VaultNotFound => "Vault not found".into(),
|
Error::VaultNotFound => "Vault not found".into(),
|
||||||
Error::CreationFailed => "Account creation failed".into(),
|
Error::CreationFailed => "Account creation failed".into(),
|
||||||
Error::EthKey(ref err) => err.to_string(),
|
Error::EthKey(ref err) => err.to_string(),
|
||||||
Error::EthKeyCrypto(ref err) => err.to_string(),
|
Error::EthKeyCrypto(ref err) => err.to_string(),
|
||||||
Error::EthCrypto(ref err) => err.to_string(),
|
Error::EthCrypto(ref err) => err.to_string(),
|
||||||
Error::Derivation(ref err) => format!("Derivation error: {:?}", err),
|
Error::Derivation(ref err) => format!("Derivation error: {:?}", err),
|
||||||
Error::Custom(ref s) => s.clone(),
|
Error::Custom(ref s) => s.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
write!(f, "{}", s)
|
write!(f, "{}", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<IoError> for Error {
|
impl From<IoError> for Error {
|
||||||
fn from(err: IoError) -> Self {
|
fn from(err: IoError) -> Self {
|
||||||
Error::Io(err)
|
Error::Io(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<EthKeyError> for Error {
|
impl From<EthKeyError> for Error {
|
||||||
fn from(err: EthKeyError) -> Self {
|
fn from(err: EthKeyError) -> Self {
|
||||||
Error::EthKey(err)
|
Error::EthKey(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ethkey::crypto::Error> for Error {
|
impl From<ethkey::crypto::Error> for Error {
|
||||||
fn from(err: ethkey::crypto::Error) -> Self {
|
fn from(err: ethkey::crypto::Error) -> Self {
|
||||||
Error::EthKeyCrypto(err)
|
Error::EthKeyCrypto(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<EthCryptoError> for Error {
|
impl From<EthCryptoError> for Error {
|
||||||
fn from(err: EthCryptoError) -> Self {
|
fn from(err: EthCryptoError) -> Self {
|
||||||
Error::EthCrypto(err)
|
Error::EthCrypto(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<crypto::error::ScryptError> for Error {
|
impl From<crypto::error::ScryptError> for Error {
|
||||||
fn from(err: crypto::error::ScryptError) -> Self {
|
fn from(err: crypto::error::ScryptError) -> Self {
|
||||||
Error::EthCrypto(err.into())
|
Error::EthCrypto(err.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<crypto::error::SymmError> for Error {
|
impl From<crypto::error::SymmError> for Error {
|
||||||
fn from(err: crypto::error::SymmError) -> Self {
|
fn from(err: crypto::error::SymmError) -> Self {
|
||||||
Error::EthCrypto(err.into())
|
Error::EthCrypto(err.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DerivationError> for Error {
|
impl From<DerivationError> for Error {
|
||||||
fn from(err: DerivationError) -> Self {
|
fn from(err: DerivationError) -> Self {
|
||||||
Error::Derivation(err)
|
Error::Derivation(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,41 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//! ethkey reexport to make documentation look pretty.
|
//! ethkey reexport to make documentation look pretty.
|
||||||
pub use _ethkey::*;
|
pub use _ethkey::*;
|
||||||
use json;
|
use json;
|
||||||
|
|
||||||
impl Into<json::H160> for Address {
|
impl Into<json::H160> for Address {
|
||||||
fn into(self) -> json::H160 {
|
fn into(self) -> json::H160 {
|
||||||
let a: [u8; 20] = self.into();
|
let a: [u8; 20] = self.into();
|
||||||
From::from(a)
|
From::from(a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<json::H160> for Address {
|
impl From<json::H160> for Address {
|
||||||
fn from(json: json::H160) -> Self {
|
fn from(json: json::H160) -> Self {
|
||||||
let a: [u8; 20] = json.into();
|
let a: [u8; 20] = json.into();
|
||||||
From::from(a)
|
From::from(a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a json::H160> for Address {
|
impl<'a> From<&'a json::H160> for Address {
|
||||||
fn from(json: &'a json::H160) -> Self {
|
fn from(json: &'a json::H160) -> Self {
|
||||||
let mut a = [0u8; 20];
|
let mut a = [0u8; 20];
|
||||||
a.copy_from_slice(json);
|
a.copy_from_slice(json);
|
||||||
From::from(a)
|
From::from(a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,80 +1,67 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::{collections::HashSet, fs, path::Path};
|
||||||
use std::path::Path;
|
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
|
use accounts_dir::{DiskKeyFileManager, KeyDirectory, KeyFileManager};
|
||||||
use ethkey::Address;
|
use ethkey::Address;
|
||||||
use accounts_dir::{KeyDirectory, RootDiskDirectory, DiskKeyFileManager, KeyFileManager};
|
|
||||||
use dir;
|
|
||||||
use Error;
|
use Error;
|
||||||
|
|
||||||
/// Import an account from a file.
|
/// Import an account from a file.
|
||||||
pub fn import_account(path: &Path, dst: &KeyDirectory) -> Result<Address, Error> {
|
pub fn import_account(path: &Path, dst: &dyn KeyDirectory) -> Result<Address, Error> {
|
||||||
let key_manager = DiskKeyFileManager::default();
|
let key_manager = DiskKeyFileManager::default();
|
||||||
let existing_accounts = dst.load()?.into_iter().map(|a| a.address).collect::<HashSet<_>>();
|
let existing_accounts = dst
|
||||||
let filename = path.file_name().and_then(|n| n.to_str()).map(|f| f.to_owned());
|
.load()?
|
||||||
let account = fs::File::open(&path)
|
.into_iter()
|
||||||
.map_err(Into::into)
|
.map(|a| a.address)
|
||||||
.and_then(|file| key_manager.read(filename, file))?;
|
.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();
|
let address = account.address.clone();
|
||||||
if !existing_accounts.contains(&address) {
|
if !existing_accounts.contains(&address) {
|
||||||
dst.insert(account)?;
|
dst.insert(account)?;
|
||||||
}
|
}
|
||||||
Ok(address)
|
Ok(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Import all accounts from one directory to the other.
|
/// Import all accounts from one directory to the other.
|
||||||
pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result<Vec<Address>, Error> {
|
pub fn import_accounts(
|
||||||
let accounts = src.load()?;
|
src: &dyn KeyDirectory,
|
||||||
let existing_accounts = dst.load()?.into_iter()
|
dst: &dyn KeyDirectory,
|
||||||
.map(|a| a.address)
|
) -> Result<Vec<Address>, Error> {
|
||||||
.collect::<HashSet<_>>();
|
let accounts = src.load()?;
|
||||||
|
let existing_accounts = dst
|
||||||
|
.load()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| a.address)
|
||||||
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
accounts.into_iter()
|
accounts
|
||||||
.filter(|a| !existing_accounts.contains(&a.address))
|
.into_iter()
|
||||||
.map(|a| {
|
.filter(|a| !existing_accounts.contains(&a.address))
|
||||||
let address = a.address.clone();
|
.map(|a| {
|
||||||
dst.insert(a)?;
|
let address = a.address.clone();
|
||||||
Ok(address)
|
dst.insert(a)?;
|
||||||
}).collect()
|
Ok(address)
|
||||||
}
|
})
|
||||||
|
.collect()
|
||||||
/// Provide a `HashSet` of all accounts available for import from the Geth keystore.
|
|
||||||
pub fn read_geth_accounts(testnet: bool) -> Vec<Address> {
|
|
||||||
RootDiskDirectory::at(dir::geth(testnet))
|
|
||||||
.load()
|
|
||||||
.map(|d| d.into_iter().map(|a| a.address).collect())
|
|
||||||
.unwrap_or_else(|_| Vec::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Import specific `desired` accounts from the Geth keystore into `dst`.
|
|
||||||
pub fn import_geth_accounts(dst: &KeyDirectory, desired: HashSet<Address>, testnet: bool) -> Result<Vec<Address>, Error> {
|
|
||||||
let src = RootDiskDirectory::at(dir::geth(testnet));
|
|
||||||
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))
|
|
||||||
.filter(|a| desired.contains(&a.address))
|
|
||||||
.map(|a| {
|
|
||||||
let address = a.address.clone();
|
|
||||||
dst.insert(a)?;
|
|
||||||
Ok(address)
|
|
||||||
}).collect()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,74 +1,82 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// 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};
|
use std::{ops, str};
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|
||||||
use serde::de::Error;
|
|
||||||
use rustc_hex::{ToHex, FromHex, FromHexError};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Bytes(Vec<u8>);
|
pub struct Bytes(Vec<u8>);
|
||||||
|
|
||||||
impl ops::Deref for Bytes {
|
impl ops::Deref for Bytes {
|
||||||
type Target = [u8];
|
type Target = [u8];
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deserialize<'a> for Bytes {
|
impl<'a> Deserialize<'a> for Bytes {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where D: Deserializer<'a>
|
where
|
||||||
{
|
D: Deserializer<'a>,
|
||||||
let s = String::deserialize(deserializer)?;
|
{
|
||||||
let data = s.from_hex().map_err(|e| Error::custom(format!("Invalid hex value {}", e)))?;
|
let s = String::deserialize(deserializer)?;
|
||||||
Ok(Bytes(data))
|
let data = s
|
||||||
}
|
.from_hex()
|
||||||
|
.map_err(|e| Error::custom(format!("Invalid hex value {}", e)))?;
|
||||||
|
Ok(Bytes(data))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for Bytes {
|
impl Serialize for Bytes {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where S: Serializer {
|
where
|
||||||
serializer.serialize_str(&self.0.to_hex())
|
S: Serializer,
|
||||||
}
|
{
|
||||||
|
serializer.serialize_str(&self.0.to_hex())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl str::FromStr for Bytes {
|
impl str::FromStr for Bytes {
|
||||||
type Err = FromHexError;
|
type Err = FromHexError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
s.from_hex().map(Bytes)
|
s.from_hex().map(Bytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&'static str> for Bytes {
|
impl From<&'static str> for Bytes {
|
||||||
fn from(s: &'static str) -> Self {
|
fn from(s: &'static str) -> Self {
|
||||||
s.parse().expect(&format!("invalid string literal for {}: '{}'", stringify!(Self), s))
|
s.parse().expect(&format!(
|
||||||
}
|
"invalid string literal for {}: '{}'",
|
||||||
|
stringify!(Self),
|
||||||
|
s
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Vec<u8>> for Bytes {
|
impl From<Vec<u8>> for Bytes {
|
||||||
fn from(v: Vec<u8>) -> Self {
|
fn from(v: Vec<u8>) -> Self {
|
||||||
Bytes(v)
|
Bytes(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Bytes> for Vec<u8> {
|
impl From<Bytes> for Vec<u8> {
|
||||||
fn from(b: Bytes) -> Self {
|
fn from(b: Bytes) -> Self {
|
||||||
b.0
|
b.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,96 +1,112 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
use serde::{Serialize, Serializer, Deserialize, Deserializer};
|
|
||||||
use serde::de::{Visitor, Error as SerdeError};
|
|
||||||
use super::{Error, H128};
|
use super::{Error, H128};
|
||||||
|
use serde::{
|
||||||
|
de::{Error as SerdeError, Visitor},
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum CipherSer {
|
pub enum CipherSer {
|
||||||
Aes128Ctr,
|
Aes128Ctr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for CipherSer {
|
impl Serialize for CipherSer {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where S: Serializer {
|
where
|
||||||
match *self {
|
S: Serializer,
|
||||||
CipherSer::Aes128Ctr => serializer.serialize_str("aes-128-ctr"),
|
{
|
||||||
}
|
match *self {
|
||||||
}
|
CipherSer::Aes128Ctr => serializer.serialize_str("aes-128-ctr"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deserialize<'a> for CipherSer {
|
impl<'a> Deserialize<'a> for CipherSer {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where D: Deserializer<'a> {
|
where
|
||||||
deserializer.deserialize_any(CipherSerVisitor)
|
D: Deserializer<'a>,
|
||||||
}
|
{
|
||||||
|
deserializer.deserialize_any(CipherSerVisitor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CipherSerVisitor;
|
struct CipherSerVisitor;
|
||||||
|
|
||||||
impl<'a> Visitor<'a> for CipherSerVisitor {
|
impl<'a> Visitor<'a> for CipherSerVisitor {
|
||||||
type Value = CipherSer;
|
type Value = CipherSer;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(formatter, "a valid cipher identifier")
|
write!(formatter, "a valid cipher identifier")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
match value {
|
where
|
||||||
"aes-128-ctr" => Ok(CipherSer::Aes128Ctr),
|
E: SerdeError,
|
||||||
_ => Err(SerdeError::custom(Error::UnsupportedCipher))
|
{
|
||||||
}
|
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 {
|
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||||
self.visit_str(value.as_ref())
|
where
|
||||||
}
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
self.visit_str(value.as_ref())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Aes128Ctr {
|
pub struct Aes128Ctr {
|
||||||
pub iv: H128,
|
pub iv: H128,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum CipherSerParams {
|
pub enum CipherSerParams {
|
||||||
Aes128Ctr(Aes128Ctr),
|
Aes128Ctr(Aes128Ctr),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for CipherSerParams {
|
impl Serialize for CipherSerParams {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where S: Serializer {
|
where
|
||||||
match *self {
|
S: Serializer,
|
||||||
CipherSerParams::Aes128Ctr(ref params) => params.serialize(serializer),
|
{
|
||||||
}
|
match *self {
|
||||||
}
|
CipherSerParams::Aes128Ctr(ref params) => params.serialize(serializer),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deserialize<'a> for CipherSerParams {
|
impl<'a> Deserialize<'a> for CipherSerParams {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where D: Deserializer<'a> {
|
where
|
||||||
Aes128Ctr::deserialize(deserializer)
|
D: Deserializer<'a>,
|
||||||
.map(CipherSerParams::Aes128Ctr)
|
{
|
||||||
.map_err(|_| Error::InvalidCipherParams)
|
Aes128Ctr::deserialize(deserializer)
|
||||||
.map_err(SerdeError::custom)
|
.map(CipherSerParams::Aes128Ctr)
|
||||||
}
|
.map_err(|_| Error::InvalidCipherParams)
|
||||||
|
.map_err(SerdeError::custom)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Cipher {
|
pub enum Cipher {
|
||||||
Aes128Ctr(Aes128Ctr),
|
Aes128Ctr(Aes128Ctr),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,194 +1,218 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::{fmt, str};
|
use super::{Bytes, Cipher, CipherSer, CipherSerParams, Kdf, KdfSer, KdfSerParams, H256};
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{
|
||||||
use serde::ser::SerializeStruct;
|
de::{Error, MapAccess, Visitor},
|
||||||
use serde::de::{Visitor, MapAccess, Error};
|
ser::SerializeStruct,
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use super::{Cipher, CipherSer, CipherSerParams, Kdf, KdfSer, KdfSerParams, H256, Bytes};
|
use std::{fmt, str};
|
||||||
|
|
||||||
pub type CipherText = Bytes;
|
pub type CipherText = Bytes;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Crypto {
|
pub struct Crypto {
|
||||||
pub cipher: Cipher,
|
pub cipher: Cipher,
|
||||||
pub ciphertext: CipherText,
|
pub ciphertext: CipherText,
|
||||||
pub kdf: Kdf,
|
pub kdf: Kdf,
|
||||||
pub mac: H256,
|
pub mac: H256,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl str::FromStr for Crypto {
|
impl str::FromStr for Crypto {
|
||||||
type Err = serde_json::error::Error;
|
type Err = serde_json::error::Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
serde_json::from_str(s)
|
serde_json::from_str(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Crypto> for String {
|
impl From<Crypto> for String {
|
||||||
fn from(c: Crypto) -> Self {
|
fn from(c: Crypto) -> Self {
|
||||||
serde_json::to_string(&c).expect("serialization cannot fail, cause all crypto keys are strings")
|
serde_json::to_string(&c)
|
||||||
}
|
.expect("serialization cannot fail, cause all crypto keys are strings")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CryptoField {
|
enum CryptoField {
|
||||||
Cipher,
|
Cipher,
|
||||||
CipherParams,
|
CipherParams,
|
||||||
CipherText,
|
CipherText,
|
||||||
Kdf,
|
Kdf,
|
||||||
KdfParams,
|
KdfParams,
|
||||||
Mac,
|
Mac,
|
||||||
Version,
|
Version,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deserialize<'a> for CryptoField {
|
impl<'a> Deserialize<'a> for CryptoField {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<CryptoField, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<CryptoField, D::Error>
|
||||||
where D: Deserializer<'a>
|
where
|
||||||
{
|
D: Deserializer<'a>,
|
||||||
deserializer.deserialize_any(CryptoFieldVisitor)
|
{
|
||||||
}
|
deserializer.deserialize_any(CryptoFieldVisitor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CryptoFieldVisitor;
|
struct CryptoFieldVisitor;
|
||||||
|
|
||||||
impl<'a> Visitor<'a> for CryptoFieldVisitor {
|
impl<'a> Visitor<'a> for CryptoFieldVisitor {
|
||||||
type Value = CryptoField;
|
type Value = CryptoField;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(formatter, "a valid crypto struct description")
|
write!(formatter, "a valid crypto struct description")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
where E: Error
|
where
|
||||||
{
|
E: Error,
|
||||||
match value {
|
{
|
||||||
"cipher" => Ok(CryptoField::Cipher),
|
match value {
|
||||||
"cipherparams" => Ok(CryptoField::CipherParams),
|
"cipher" => Ok(CryptoField::Cipher),
|
||||||
"ciphertext" => Ok(CryptoField::CipherText),
|
"cipherparams" => Ok(CryptoField::CipherParams),
|
||||||
"kdf" => Ok(CryptoField::Kdf),
|
"ciphertext" => Ok(CryptoField::CipherText),
|
||||||
"kdfparams" => Ok(CryptoField::KdfParams),
|
"kdf" => Ok(CryptoField::Kdf),
|
||||||
"mac" => Ok(CryptoField::Mac),
|
"kdfparams" => Ok(CryptoField::KdfParams),
|
||||||
"version" => Ok(CryptoField::Version),
|
"mac" => Ok(CryptoField::Mac),
|
||||||
_ => Err(Error::custom(format!("Unknown field: '{}'", value))),
|
"version" => Ok(CryptoField::Version),
|
||||||
}
|
_ => Err(Error::custom(format!("Unknown field: '{}'", value))),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deserialize<'a> for Crypto {
|
impl<'a> Deserialize<'a> for Crypto {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Crypto, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Crypto, D::Error>
|
||||||
where D: Deserializer<'a>
|
where
|
||||||
{
|
D: Deserializer<'a>,
|
||||||
static FIELDS: &'static [&'static str] = &["id", "version", "crypto", "Crypto", "address"];
|
{
|
||||||
deserializer.deserialize_struct("Crypto", FIELDS, CryptoVisitor)
|
static FIELDS: &'static [&'static str] = &["id", "version", "crypto", "Crypto", "address"];
|
||||||
}
|
deserializer.deserialize_struct("Crypto", FIELDS, CryptoVisitor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CryptoVisitor;
|
struct CryptoVisitor;
|
||||||
|
|
||||||
impl<'a> Visitor<'a> for CryptoVisitor {
|
impl<'a> Visitor<'a> for CryptoVisitor {
|
||||||
type Value = Crypto;
|
type Value = Crypto;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(formatter, "a valid vault crypto object")
|
write!(formatter, "a valid vault crypto object")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_map<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
|
fn visit_map<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
|
||||||
where V: MapAccess<'a>
|
where
|
||||||
{
|
V: MapAccess<'a>,
|
||||||
let mut cipher = None;
|
{
|
||||||
let mut cipherparams = None;
|
let mut cipher = None;
|
||||||
let mut ciphertext = None;
|
let mut cipherparams = None;
|
||||||
let mut kdf = None;
|
let mut ciphertext = None;
|
||||||
let mut kdfparams = None;
|
let mut kdf = None;
|
||||||
let mut mac = None;
|
let mut kdfparams = None;
|
||||||
|
let mut mac = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match visitor.next_key()? {
|
match visitor.next_key()? {
|
||||||
Some(CryptoField::Cipher) => { cipher = Some(visitor.next_value()?); }
|
Some(CryptoField::Cipher) => {
|
||||||
Some(CryptoField::CipherParams) => { cipherparams = Some(visitor.next_value()?); }
|
cipher = Some(visitor.next_value()?);
|
||||||
Some(CryptoField::CipherText) => { ciphertext = Some(visitor.next_value()?); }
|
}
|
||||||
Some(CryptoField::Kdf) => { kdf = Some(visitor.next_value()?); }
|
Some(CryptoField::CipherParams) => {
|
||||||
Some(CryptoField::KdfParams) => { kdfparams = Some(visitor.next_value()?); }
|
cipherparams = 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::CipherText) => {
|
||||||
Some(CryptoField::Version) => { visitor.next_value().unwrap_or(()) }
|
ciphertext = Some(visitor.next_value()?);
|
||||||
None => { break; }
|
}
|
||||||
}
|
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) {
|
let cipher = match (cipher, cipherparams) {
|
||||||
(Some(CipherSer::Aes128Ctr), Some(CipherSerParams::Aes128Ctr(params))) => Cipher::Aes128Ctr(params),
|
(Some(CipherSer::Aes128Ctr), Some(CipherSerParams::Aes128Ctr(params))) => {
|
||||||
(None, _) => return Err(V::Error::missing_field("cipher")),
|
Cipher::Aes128Ctr(params)
|
||||||
(Some(_), None) => return Err(V::Error::missing_field("cipherparams")),
|
}
|
||||||
};
|
(None, _) => return Err(V::Error::missing_field("cipher")),
|
||||||
|
(Some(_), None) => return Err(V::Error::missing_field("cipherparams")),
|
||||||
|
};
|
||||||
|
|
||||||
let ciphertext = match ciphertext {
|
let ciphertext = match ciphertext {
|
||||||
Some(ciphertext) => ciphertext,
|
Some(ciphertext) => ciphertext,
|
||||||
None => return Err(V::Error::missing_field("ciphertext")),
|
None => return Err(V::Error::missing_field("ciphertext")),
|
||||||
};
|
};
|
||||||
|
|
||||||
let kdf = match (kdf, kdfparams) {
|
let kdf = match (kdf, kdfparams) {
|
||||||
(Some(KdfSer::Pbkdf2), Some(KdfSerParams::Pbkdf2(params))) => Kdf::Pbkdf2(params),
|
(Some(KdfSer::Pbkdf2), Some(KdfSerParams::Pbkdf2(params))) => Kdf::Pbkdf2(params),
|
||||||
(Some(KdfSer::Scrypt), Some(KdfSerParams::Scrypt(params))) => Kdf::Scrypt(params),
|
(Some(KdfSer::Scrypt), Some(KdfSerParams::Scrypt(params))) => Kdf::Scrypt(params),
|
||||||
(Some(_), Some(_)) => return Err(V::Error::custom("Invalid cipherparams")),
|
(Some(_), Some(_)) => return Err(V::Error::custom("Invalid cipherparams")),
|
||||||
(None, _) => return Err(V::Error::missing_field("kdf")),
|
(None, _) => return Err(V::Error::missing_field("kdf")),
|
||||||
(Some(_), None) => return Err(V::Error::missing_field("kdfparams")),
|
(Some(_), None) => return Err(V::Error::missing_field("kdfparams")),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mac = match mac {
|
let mac = match mac {
|
||||||
Some(mac) => mac,
|
Some(mac) => mac,
|
||||||
None => return Err(V::Error::missing_field("mac")),
|
None => return Err(V::Error::missing_field("mac")),
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = Crypto {
|
let result = Crypto {
|
||||||
cipher: cipher,
|
cipher: cipher,
|
||||||
ciphertext: ciphertext,
|
ciphertext: ciphertext,
|
||||||
kdf: kdf,
|
kdf: kdf,
|
||||||
mac: mac,
|
mac: mac,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for Crypto {
|
impl Serialize for Crypto {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where S: Serializer
|
where
|
||||||
{
|
S: Serializer,
|
||||||
let mut crypto = serializer.serialize_struct("Crypto", 6)?;
|
{
|
||||||
match self.cipher {
|
let mut crypto = serializer.serialize_struct("Crypto", 6)?;
|
||||||
Cipher::Aes128Ctr(ref params) => {
|
match self.cipher {
|
||||||
crypto.serialize_field("cipher", &CipherSer::Aes128Ctr)?;
|
Cipher::Aes128Ctr(ref params) => {
|
||||||
crypto.serialize_field("cipherparams", params)?;
|
crypto.serialize_field("cipher", &CipherSer::Aes128Ctr)?;
|
||||||
},
|
crypto.serialize_field("cipherparams", params)?;
|
||||||
}
|
}
|
||||||
crypto.serialize_field("ciphertext", &self.ciphertext)?;
|
}
|
||||||
match self.kdf {
|
crypto.serialize_field("ciphertext", &self.ciphertext)?;
|
||||||
Kdf::Pbkdf2(ref params) => {
|
match self.kdf {
|
||||||
crypto.serialize_field("kdf", &KdfSer::Pbkdf2)?;
|
Kdf::Pbkdf2(ref params) => {
|
||||||
crypto.serialize_field("kdfparams", params)?;
|
crypto.serialize_field("kdf", &KdfSer::Pbkdf2)?;
|
||||||
},
|
crypto.serialize_field("kdfparams", params)?;
|
||||||
Kdf::Scrypt(ref params) => {
|
}
|
||||||
crypto.serialize_field("kdf", &KdfSer::Scrypt)?;
|
Kdf::Scrypt(ref params) => {
|
||||||
crypto.serialize_field("kdfparams", params)?;
|
crypto.serialize_field("kdf", &KdfSer::Scrypt)?;
|
||||||
},
|
crypto.serialize_field("kdfparams", params)?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
crypto.serialize_field("mac", &self.mac)?;
|
crypto.serialize_field("mac", &self.mac)?;
|
||||||
crypto.end()
|
crypto.end()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,50 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
UnsupportedCipher,
|
UnsupportedCipher,
|
||||||
InvalidCipherParams,
|
InvalidCipherParams,
|
||||||
UnsupportedKdf,
|
UnsupportedKdf,
|
||||||
InvalidUuid,
|
InvalidUuid,
|
||||||
UnsupportedVersion,
|
UnsupportedVersion,
|
||||||
InvalidCiphertext,
|
InvalidCiphertext,
|
||||||
InvalidH256,
|
InvalidH256,
|
||||||
InvalidPrf,
|
InvalidPrf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
match *self {
|
match *self {
|
||||||
Error::InvalidUuid => write!(f, "Invalid Uuid"),
|
Error::InvalidUuid => write!(f, "Invalid Uuid"),
|
||||||
Error::UnsupportedVersion => write!(f, "Unsupported version"),
|
Error::UnsupportedVersion => write!(f, "Unsupported version"),
|
||||||
Error::UnsupportedKdf => write!(f, "Unsupported kdf"),
|
Error::UnsupportedKdf => write!(f, "Unsupported kdf"),
|
||||||
Error::InvalidCiphertext => write!(f, "Invalid ciphertext"),
|
Error::InvalidCiphertext => write!(f, "Invalid ciphertext"),
|
||||||
Error::UnsupportedCipher => write!(f, "Unsupported cipher"),
|
Error::UnsupportedCipher => write!(f, "Unsupported cipher"),
|
||||||
Error::InvalidCipherParams => write!(f, "Invalid cipher params"),
|
Error::InvalidCipherParams => write!(f, "Invalid cipher params"),
|
||||||
Error::InvalidH256 => write!(f, "Invalid hash"),
|
Error::InvalidH256 => write!(f, "Invalid hash"),
|
||||||
Error::InvalidPrf => write!(f, "Invalid prf"),
|
Error::InvalidPrf => write!(f, "Invalid prf"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<String> for Error {
|
impl Into<String> for Error {
|
||||||
fn into(self) -> String {
|
fn into(self) -> String {
|
||||||
format!("{}", self)
|
format!("{}", self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,117 +1,133 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::{ops, fmt, str};
|
|
||||||
use rustc_hex::{FromHex, ToHex};
|
|
||||||
use serde::{Serialize, Serializer, Deserialize, Deserializer};
|
|
||||||
use serde::de::{Visitor, Error as SerdeError};
|
|
||||||
use super::Error;
|
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 {
|
macro_rules! impl_hash {
|
||||||
($name: ident, $size: expr) => {
|
($name: ident, $size: expr) => {
|
||||||
pub struct $name([u8; $size]);
|
pub struct $name([u8; $size]);
|
||||||
|
|
||||||
impl fmt::Debug for $name {
|
impl fmt::Debug for $name {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
let self_ref: &[u8] = &self.0;
|
let self_ref: &[u8] = &self.0;
|
||||||
write!(f, "{:?}", self_ref)
|
write!(f, "{:?}", self_ref)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for $name {
|
impl PartialEq for $name {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
let self_ref: &[u8] = &self.0;
|
let self_ref: &[u8] = &self.0;
|
||||||
let other_ref: &[u8] = &other.0;
|
let other_ref: &[u8] = &other.0;
|
||||||
self_ref == other_ref
|
self_ref == other_ref
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ops::Deref for $name {
|
impl ops::Deref for $name {
|
||||||
type Target = [u8];
|
type Target = [u8];
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for $name {
|
impl Serialize for $name {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where S: Serializer {
|
where
|
||||||
serializer.serialize_str(&self.0.to_hex())
|
S: Serializer,
|
||||||
}
|
{
|
||||||
}
|
serializer.serialize_str(&self.0.to_hex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Deserialize<'a> for $name {
|
impl<'a> Deserialize<'a> for $name {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where D: Deserializer<'a> {
|
where
|
||||||
struct HashVisitor;
|
D: Deserializer<'a>,
|
||||||
|
{
|
||||||
|
struct HashVisitor;
|
||||||
|
|
||||||
impl<'b> Visitor<'b> for HashVisitor {
|
impl<'b> Visitor<'b> for HashVisitor {
|
||||||
type Value = $name;
|
type Value = $name;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(formatter, "a hex-encoded {}", stringify!($name))
|
write!(formatter, "a hex-encoded {}", stringify!($name))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
value.parse().map_err(SerdeError::custom)
|
where
|
||||||
}
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
value.parse().map_err(SerdeError::custom)
|
||||||
|
}
|
||||||
|
|
||||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E> where E: SerdeError {
|
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||||
self.visit_str(value.as_ref())
|
where
|
||||||
}
|
E: SerdeError,
|
||||||
}
|
{
|
||||||
|
self.visit_str(value.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
deserializer.deserialize_any(HashVisitor)
|
deserializer.deserialize_any(HashVisitor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl str::FromStr for $name {
|
impl str::FromStr for $name {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||||
match value.from_hex() {
|
match value.from_hex() {
|
||||||
Ok(ref hex) if hex.len() == $size => {
|
Ok(ref hex) if hex.len() == $size => {
|
||||||
let mut hash = [0u8; $size];
|
let mut hash = [0u8; $size];
|
||||||
hash.clone_from_slice(hex);
|
hash.clone_from_slice(hex);
|
||||||
Ok($name(hash))
|
Ok($name(hash))
|
||||||
}
|
}
|
||||||
_ => Err(Error::InvalidH256),
|
_ => Err(Error::InvalidH256),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&'static str> for $name {
|
impl From<&'static str> for $name {
|
||||||
fn from(s: &'static str) -> Self {
|
fn from(s: &'static str) -> Self {
|
||||||
s.parse().expect(&format!("invalid string literal for {}: '{}'", stringify!($name), s))
|
s.parse().expect(&format!(
|
||||||
}
|
"invalid string literal for {}: '{}'",
|
||||||
}
|
stringify!($name),
|
||||||
|
s
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<[u8; $size]> for $name {
|
impl From<[u8; $size]> for $name {
|
||||||
fn from(bytes: [u8; $size]) -> Self {
|
fn from(bytes: [u8; $size]) -> Self {
|
||||||
$name(bytes)
|
$name(bytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<[u8; $size]> for $name {
|
impl Into<[u8; $size]> for $name {
|
||||||
fn into(self) -> [u8; $size] {
|
fn into(self) -> [u8; $size] {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_hash!(H128, 16);
|
impl_hash!(H128, 16);
|
||||||
|
|||||||
@@ -1,153 +1,179 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//! Universaly unique identifier.
|
//! Universaly unique identifier.
|
||||||
use std::{fmt, str};
|
|
||||||
use rustc_hex::{ToHex, FromHex};
|
|
||||||
use serde::{Deserialize, Serialize, Deserializer, Serializer};
|
|
||||||
use serde::de::{Visitor, Error as SerdeError};
|
|
||||||
use super::Error;
|
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.
|
/// Universaly unique identifier.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Uuid([u8; 16]);
|
pub struct Uuid([u8; 16]);
|
||||||
|
|
||||||
impl From<[u8; 16]> for Uuid {
|
impl From<[u8; 16]> for Uuid {
|
||||||
fn from(uuid: [u8; 16]) -> Self {
|
fn from(uuid: [u8; 16]) -> Self {
|
||||||
Uuid(uuid)
|
Uuid(uuid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Into<String> for &'a Uuid {
|
impl<'a> Into<String> for &'a Uuid {
|
||||||
fn into(self) -> String {
|
fn into(self) -> String {
|
||||||
let d1 = &self.0[0..4];
|
let d1 = &self.0[0..4];
|
||||||
let d2 = &self.0[4..6];
|
let d2 = &self.0[4..6];
|
||||||
let d3 = &self.0[6..8];
|
let d3 = &self.0[6..8];
|
||||||
let d4 = &self.0[8..10];
|
let d4 = &self.0[8..10];
|
||||||
let d5 = &self.0[10..16];
|
let d5 = &self.0[10..16];
|
||||||
[d1, d2, d3, d4, d5].into_iter().map(|d| d.to_hex()).collect::<Vec<String>>().join("-")
|
[d1, d2, d3, d4, d5]
|
||||||
}
|
.iter()
|
||||||
|
.map(|d| d.to_hex())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("-")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<String> for Uuid {
|
impl Into<String> for Uuid {
|
||||||
fn into(self) -> String {
|
fn into(self) -> String {
|
||||||
Into::into(&self)
|
Into::into(&self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<[u8; 16]> for Uuid {
|
impl Into<[u8; 16]> for Uuid {
|
||||||
fn into(self) -> [u8; 16] {
|
fn into(self) -> [u8; 16] {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Uuid {
|
impl fmt::Display for Uuid {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
let s: String = (self as &Uuid).into();
|
let s: String = (self as &Uuid).into();
|
||||||
write!(f, "{}", s)
|
write!(f, "{}", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_into(from: &str, into: &mut [u8]) -> Result<(), Error> {
|
fn copy_into(from: &str, into: &mut [u8]) -> Result<(), Error> {
|
||||||
let from = from.from_hex().map_err(|_| Error::InvalidUuid)?;
|
let from = from.from_hex().map_err(|_| Error::InvalidUuid)?;
|
||||||
|
|
||||||
if from.len() != into.len() {
|
if from.len() != into.len() {
|
||||||
return Err(Error::InvalidUuid);
|
return Err(Error::InvalidUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
into.copy_from_slice(&from);
|
into.copy_from_slice(&from);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl str::FromStr for Uuid {
|
impl str::FromStr for Uuid {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let parts: Vec<&str> = s.split("-").collect();
|
let parts: Vec<&str> = s.split("-").collect();
|
||||||
|
|
||||||
if parts.len() != 5 {
|
if parts.len() != 5 {
|
||||||
return Err(Error::InvalidUuid);
|
return Err(Error::InvalidUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut uuid = [0u8; 16];
|
let mut uuid = [0u8; 16];
|
||||||
|
|
||||||
copy_into(parts[0], &mut uuid[0..4])?;
|
copy_into(parts[0], &mut uuid[0..4])?;
|
||||||
copy_into(parts[1], &mut uuid[4..6])?;
|
copy_into(parts[1], &mut uuid[4..6])?;
|
||||||
copy_into(parts[2], &mut uuid[6..8])?;
|
copy_into(parts[2], &mut uuid[6..8])?;
|
||||||
copy_into(parts[3], &mut uuid[8..10])?;
|
copy_into(parts[3], &mut uuid[8..10])?;
|
||||||
copy_into(parts[4], &mut uuid[10..16])?;
|
copy_into(parts[4], &mut uuid[10..16])?;
|
||||||
|
|
||||||
Ok(Uuid(uuid))
|
Ok(Uuid(uuid))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&'static str> for Uuid {
|
impl From<&'static str> for Uuid {
|
||||||
fn from(s: &'static str) -> Self {
|
fn from(s: &'static str) -> Self {
|
||||||
s.parse().expect(&format!("invalid string literal for {}: '{}'", stringify!(Self), s))
|
s.parse().expect(&format!(
|
||||||
}
|
"invalid string literal for {}: '{}'",
|
||||||
|
stringify!(Self),
|
||||||
|
s
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for Uuid {
|
impl Serialize for Uuid {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where S: Serializer {
|
where
|
||||||
let s: String = self.into();
|
S: Serializer,
|
||||||
serializer.serialize_str(&s)
|
{
|
||||||
}
|
let s: String = self.into();
|
||||||
|
serializer.serialize_str(&s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deserialize<'a> for Uuid {
|
impl<'a> Deserialize<'a> for Uuid {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where D: Deserializer<'a> {
|
where
|
||||||
deserializer.deserialize_any(UuidVisitor)
|
D: Deserializer<'a>,
|
||||||
}
|
{
|
||||||
|
deserializer.deserialize_any(UuidVisitor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UuidVisitor;
|
struct UuidVisitor;
|
||||||
|
|
||||||
impl<'a> Visitor<'a> for UuidVisitor {
|
impl<'a> Visitor<'a> for UuidVisitor {
|
||||||
type Value = Uuid;
|
type Value = Uuid;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(formatter, "a valid hex-encoded UUID")
|
write!(formatter, "a valid hex-encoded UUID")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
value.parse().map_err(SerdeError::custom)
|
where
|
||||||
}
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
value.parse().map_err(SerdeError::custom)
|
||||||
|
}
|
||||||
|
|
||||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E> where E: SerdeError {
|
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||||
self.visit_str(value.as_ref())
|
where
|
||||||
}
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
self.visit_str(value.as_ref())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Uuid;
|
use super::Uuid;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn uuid_from_str() {
|
fn uuid_from_str() {
|
||||||
let uuid: Uuid = "3198bc9c-6672-5ab3-d995-4942343ae5b6".into();
|
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]));
|
assert_eq!(
|
||||||
}
|
uuid,
|
||||||
|
Uuid::from([
|
||||||
|
0x31, 0x98, 0xbc, 0x9c, 0x66, 0x72, 0x5a, 0xb3, 0xd9, 0x95, 0x49, 0x42, 0x34, 0x3a,
|
||||||
|
0xe5, 0xb6
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn uuid_from_and_to_str() {
|
fn uuid_from_and_to_str() {
|
||||||
let from = "3198bc9c-6672-5ab3-d995-4942343ae5b6";
|
let from = "3198bc9c-6672-5ab3-d995-4942343ae5b6";
|
||||||
let uuid: Uuid = from.into();
|
let uuid: Uuid = from.into();
|
||||||
let to: String = uuid.into();
|
let to: String = uuid.into();
|
||||||
assert_eq!(from, &to);
|
assert_eq!(from, &to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,160 +1,186 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::fmt;
|
use super::{Bytes, Error};
|
||||||
use std::num::NonZeroU32;
|
use serde::{
|
||||||
use serde::{Serialize, Serializer, Deserialize, Deserializer};
|
de::{Error as SerdeError, Visitor},
|
||||||
use serde::de::{Visitor, Error as SerdeError};
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
use super::{Error, Bytes};
|
};
|
||||||
|
use std::{fmt, num::NonZeroU32};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum KdfSer {
|
pub enum KdfSer {
|
||||||
Pbkdf2,
|
Pbkdf2,
|
||||||
Scrypt,
|
Scrypt,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for KdfSer {
|
impl Serialize for KdfSer {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where S: Serializer {
|
where
|
||||||
match *self {
|
S: Serializer,
|
||||||
KdfSer::Pbkdf2 => serializer.serialize_str("pbkdf2"),
|
{
|
||||||
KdfSer::Scrypt => serializer.serialize_str("scrypt"),
|
match *self {
|
||||||
}
|
KdfSer::Pbkdf2 => serializer.serialize_str("pbkdf2"),
|
||||||
}
|
KdfSer::Scrypt => serializer.serialize_str("scrypt"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deserialize<'a> for KdfSer {
|
impl<'a> Deserialize<'a> for KdfSer {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where D: Deserializer<'a> {
|
where
|
||||||
deserializer.deserialize_any(KdfSerVisitor)
|
D: Deserializer<'a>,
|
||||||
}
|
{
|
||||||
|
deserializer.deserialize_any(KdfSerVisitor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct KdfSerVisitor;
|
struct KdfSerVisitor;
|
||||||
|
|
||||||
impl<'a> Visitor<'a> for KdfSerVisitor {
|
impl<'a> Visitor<'a> for KdfSerVisitor {
|
||||||
type Value = KdfSer;
|
type Value = KdfSer;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(formatter, "a kdf algorithm identifier")
|
write!(formatter, "a kdf algorithm identifier")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
match value {
|
where
|
||||||
"pbkdf2" => Ok(KdfSer::Pbkdf2),
|
E: SerdeError,
|
||||||
"scrypt" => Ok(KdfSer::Scrypt),
|
{
|
||||||
_ => Err(SerdeError::custom(Error::UnsupportedKdf))
|
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 {
|
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||||
self.visit_str(value.as_ref())
|
where
|
||||||
}
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
self.visit_str(value.as_ref())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Prf {
|
pub enum Prf {
|
||||||
HmacSha256,
|
HmacSha256,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for Prf {
|
impl Serialize for Prf {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where S: Serializer {
|
where
|
||||||
match *self {
|
S: Serializer,
|
||||||
Prf::HmacSha256 => serializer.serialize_str("hmac-sha256"),
|
{
|
||||||
}
|
match *self {
|
||||||
}
|
Prf::HmacSha256 => serializer.serialize_str("hmac-sha256"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deserialize<'a> for Prf {
|
impl<'a> Deserialize<'a> for Prf {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where D: Deserializer<'a> {
|
where
|
||||||
deserializer.deserialize_any(PrfVisitor)
|
D: Deserializer<'a>,
|
||||||
}
|
{
|
||||||
|
deserializer.deserialize_any(PrfVisitor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PrfVisitor;
|
struct PrfVisitor;
|
||||||
|
|
||||||
impl<'a> Visitor<'a> for PrfVisitor {
|
impl<'a> Visitor<'a> for PrfVisitor {
|
||||||
type Value = Prf;
|
type Value = Prf;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(formatter, "a prf algorithm identifier")
|
write!(formatter, "a prf algorithm identifier")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
match value {
|
where
|
||||||
"hmac-sha256" => Ok(Prf::HmacSha256),
|
E: SerdeError,
|
||||||
_ => Err(SerdeError::custom(Error::InvalidPrf)),
|
{
|
||||||
}
|
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 {
|
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||||
self.visit_str(value.as_ref())
|
where
|
||||||
}
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
self.visit_str(value.as_ref())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Pbkdf2 {
|
pub struct Pbkdf2 {
|
||||||
pub c: NonZeroU32,
|
pub c: NonZeroU32,
|
||||||
pub dklen: u32,
|
pub dklen: u32,
|
||||||
pub prf: Prf,
|
pub prf: Prf,
|
||||||
pub salt: Bytes,
|
pub salt: Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Scrypt {
|
pub struct Scrypt {
|
||||||
pub dklen: u32,
|
pub dklen: u32,
|
||||||
pub p: u32,
|
pub p: u32,
|
||||||
pub n: u32,
|
pub n: u32,
|
||||||
pub r: u32,
|
pub r: u32,
|
||||||
pub salt: Bytes,
|
pub salt: Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum KdfSerParams {
|
pub enum KdfSerParams {
|
||||||
Pbkdf2(Pbkdf2),
|
Pbkdf2(Pbkdf2),
|
||||||
Scrypt(Scrypt),
|
Scrypt(Scrypt),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for KdfSerParams {
|
impl Serialize for KdfSerParams {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where S: Serializer {
|
where
|
||||||
match *self {
|
S: Serializer,
|
||||||
KdfSerParams::Pbkdf2(ref params) => params.serialize(serializer),
|
{
|
||||||
KdfSerParams::Scrypt(ref params) => params.serialize(serializer),
|
match *self {
|
||||||
}
|
KdfSerParams::Pbkdf2(ref params) => params.serialize(serializer),
|
||||||
}
|
KdfSerParams::Scrypt(ref params) => params.serialize(serializer),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deserialize<'a> for KdfSerParams {
|
impl<'a> Deserialize<'a> for KdfSerParams {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where D: Deserializer<'a> {
|
where
|
||||||
use serde_json::{Value, from_value};
|
D: Deserializer<'a>,
|
||||||
|
{
|
||||||
|
use serde_json::{from_value, Value};
|
||||||
|
|
||||||
let v: Value = Deserialize::deserialize(deserializer)?;
|
let v: Value = Deserialize::deserialize(deserializer)?;
|
||||||
|
|
||||||
from_value(v.clone()).map(KdfSerParams::Pbkdf2)
|
from_value(v.clone())
|
||||||
.or_else(|_| from_value(v).map(KdfSerParams::Scrypt))
|
.map(KdfSerParams::Pbkdf2)
|
||||||
.map_err(|_| D::Error::custom("Invalid KDF algorithm"))
|
.or_else(|_| from_value(v).map(KdfSerParams::Scrypt))
|
||||||
}
|
.map_err(|_| D::Error::custom("Invalid KDF algorithm"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Kdf {
|
pub enum Kdf {
|
||||||
Pbkdf2(Pbkdf2),
|
Pbkdf2(Pbkdf2),
|
||||||
Scrypt(Scrypt),
|
Scrypt(Scrypt),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,195 +1,227 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::fmt;
|
use super::{Crypto, Uuid, Version, H160};
|
||||||
use std::io::{Read, Write};
|
use serde::{
|
||||||
use serde::{Serialize, Serializer, Deserialize, Deserializer};
|
de::{DeserializeOwned, Error, MapAccess, Visitor},
|
||||||
use serde::de::{Error, Visitor, MapAccess, DeserializeOwned};
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use super::{Uuid, Version, Crypto, H160};
|
use std::{
|
||||||
|
fmt,
|
||||||
|
io::{Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
/// Public opaque type representing serializable `KeyFile`.
|
/// Public opaque type representing serializable `KeyFile`.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct OpaqueKeyFile {
|
pub struct OpaqueKeyFile {
|
||||||
key_file: KeyFile
|
key_file: KeyFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for OpaqueKeyFile {
|
impl Serialize for OpaqueKeyFile {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
S: Serializer,
|
where
|
||||||
{
|
S: Serializer,
|
||||||
self.key_file.serialize(serializer)
|
{
|
||||||
}
|
self.key_file.serialize(serializer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<T> for OpaqueKeyFile where T: Into<KeyFile> {
|
impl<T> From<T> for OpaqueKeyFile
|
||||||
fn from(val: T) -> Self {
|
where
|
||||||
OpaqueKeyFile { key_file: val.into() }
|
T: Into<KeyFile>,
|
||||||
}
|
{
|
||||||
|
fn from(val: T) -> Self {
|
||||||
|
OpaqueKeyFile {
|
||||||
|
key_file: val.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Serialize)]
|
#[derive(Debug, PartialEq, Serialize)]
|
||||||
pub struct KeyFile {
|
pub struct KeyFile {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
pub crypto: Crypto,
|
pub crypto: Crypto,
|
||||||
pub address: Option<H160>,
|
pub address: Option<H160>,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub meta: Option<String>,
|
pub meta: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum KeyFileField {
|
enum KeyFileField {
|
||||||
Id,
|
Id,
|
||||||
Version,
|
Version,
|
||||||
Crypto,
|
Crypto,
|
||||||
Address,
|
Address,
|
||||||
Name,
|
Name,
|
||||||
Meta,
|
Meta,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deserialize<'a> for KeyFileField {
|
impl<'a> Deserialize<'a> for KeyFileField {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<KeyFileField, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<KeyFileField, D::Error>
|
||||||
where D: Deserializer<'a>
|
where
|
||||||
{
|
D: Deserializer<'a>,
|
||||||
deserializer.deserialize_any(KeyFileFieldVisitor)
|
{
|
||||||
}
|
deserializer.deserialize_any(KeyFileFieldVisitor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct KeyFileFieldVisitor;
|
struct KeyFileFieldVisitor;
|
||||||
|
|
||||||
impl<'a> Visitor<'a> for KeyFileFieldVisitor {
|
impl<'a> Visitor<'a> for KeyFileFieldVisitor {
|
||||||
type Value = KeyFileField;
|
type Value = KeyFileField;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(formatter, "a valid key file field")
|
write!(formatter, "a valid key file field")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
where E: Error
|
where
|
||||||
{
|
E: Error,
|
||||||
match value {
|
{
|
||||||
"id" => Ok(KeyFileField::Id),
|
match value {
|
||||||
"version" => Ok(KeyFileField::Version),
|
"id" => Ok(KeyFileField::Id),
|
||||||
"crypto" => Ok(KeyFileField::Crypto),
|
"version" => Ok(KeyFileField::Version),
|
||||||
"Crypto" => Ok(KeyFileField::Crypto),
|
"crypto" => Ok(KeyFileField::Crypto),
|
||||||
"address" => Ok(KeyFileField::Address),
|
"Crypto" => Ok(KeyFileField::Crypto),
|
||||||
"name" => Ok(KeyFileField::Name),
|
"address" => Ok(KeyFileField::Address),
|
||||||
"meta" => Ok(KeyFileField::Meta),
|
"name" => Ok(KeyFileField::Name),
|
||||||
_ => Err(Error::custom(format!("Unknown field: '{}'", value))),
|
"meta" => Ok(KeyFileField::Meta),
|
||||||
}
|
_ => Err(Error::custom(format!("Unknown field: '{}'", value))),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deserialize<'a> for KeyFile {
|
impl<'a> Deserialize<'a> for KeyFile {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<KeyFile, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<KeyFile, D::Error>
|
||||||
where D: Deserializer<'a>
|
where
|
||||||
{
|
D: Deserializer<'a>,
|
||||||
static FIELDS: &'static [&'static str] = &["id", "version", "crypto", "Crypto", "address"];
|
{
|
||||||
deserializer.deserialize_struct("KeyFile", FIELDS, KeyFileVisitor)
|
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
|
fn none_if_empty<'a, T>(v: Option<serde_json::Value>) -> Option<T>
|
||||||
T: DeserializeOwned
|
where
|
||||||
|
T: DeserializeOwned,
|
||||||
{
|
{
|
||||||
v.and_then(|v| if v.is_null() {
|
v.and_then(|v| {
|
||||||
None
|
if v.is_null() {
|
||||||
} else {
|
None
|
||||||
serde_json::from_value(v).ok()
|
} else {
|
||||||
})
|
serde_json::from_value(v).ok()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
struct KeyFileVisitor;
|
struct KeyFileVisitor;
|
||||||
impl<'a> Visitor<'a> for KeyFileVisitor {
|
impl<'a> Visitor<'a> for KeyFileVisitor {
|
||||||
type Value = KeyFile;
|
type Value = KeyFile;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(formatter, "a valid key object")
|
write!(formatter, "a valid key object")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_map<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
|
fn visit_map<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
|
||||||
where V: MapAccess<'a>
|
where
|
||||||
{
|
V: MapAccess<'a>,
|
||||||
let mut id = None;
|
{
|
||||||
let mut version = None;
|
let mut id = None;
|
||||||
let mut crypto = None;
|
let mut version = None;
|
||||||
let mut address = None;
|
let mut crypto = None;
|
||||||
let mut name = None;
|
let mut address = None;
|
||||||
let mut meta = None;
|
let mut name = None;
|
||||||
|
let mut meta = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match visitor.next_key()? {
|
match visitor.next_key()? {
|
||||||
Some(KeyFileField::Id) => { id = Some(visitor.next_value()?); }
|
Some(KeyFileField::Id) => {
|
||||||
Some(KeyFileField::Version) => { version = Some(visitor.next_value()?); }
|
id = Some(visitor.next_value()?);
|
||||||
Some(KeyFileField::Crypto) => { crypto = Some(visitor.next_value()?); }
|
}
|
||||||
Some(KeyFileField::Address) => { address = Some(visitor.next_value()?); }
|
Some(KeyFileField::Version) => {
|
||||||
Some(KeyFileField::Name) => { name = none_if_empty(visitor.next_value().ok()) }
|
version = Some(visitor.next_value()?);
|
||||||
Some(KeyFileField::Meta) => { meta = none_if_empty(visitor.next_value().ok()) }
|
}
|
||||||
None => { break; }
|
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 {
|
let id = match id {
|
||||||
Some(id) => id,
|
Some(id) => id,
|
||||||
None => return Err(V::Error::missing_field("id")),
|
None => return Err(V::Error::missing_field("id")),
|
||||||
};
|
};
|
||||||
|
|
||||||
let version = match version {
|
let version = match version {
|
||||||
Some(version) => version,
|
Some(version) => version,
|
||||||
None => return Err(V::Error::missing_field("version")),
|
None => return Err(V::Error::missing_field("version")),
|
||||||
};
|
};
|
||||||
|
|
||||||
let crypto = match crypto {
|
let crypto = match crypto {
|
||||||
Some(crypto) => crypto,
|
Some(crypto) => crypto,
|
||||||
None => return Err(V::Error::missing_field("crypto")),
|
None => return Err(V::Error::missing_field("crypto")),
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = KeyFile {
|
let result = KeyFile {
|
||||||
id: id,
|
id: id,
|
||||||
version: version,
|
version: version,
|
||||||
crypto: crypto,
|
crypto: crypto,
|
||||||
address: address,
|
address: address,
|
||||||
name: name,
|
name: name,
|
||||||
meta: meta,
|
meta: meta,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyFile {
|
impl KeyFile {
|
||||||
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)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write<W>(&self, writer: &mut W) -> Result<(), serde_json::Error> where W: Write {
|
pub fn write<W>(&self, writer: &mut W) -> Result<(), serde_json::Error>
|
||||||
serde_json::to_writer(writer, self)
|
where
|
||||||
}
|
W: Write,
|
||||||
|
{
|
||||||
|
serde_json::to_writer(writer, self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::str::FromStr;
|
use json::{Aes128Ctr, Cipher, Crypto, Kdf, KeyFile, Scrypt, Uuid, Version};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use json::{KeyFile, Uuid, Version, Crypto, Cipher, Aes128Ctr, Kdf, Scrypt};
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basic_keyfile() {
|
fn basic_keyfile() {
|
||||||
let json = r#"
|
let json = r#"
|
||||||
{
|
{
|
||||||
"address": "6edddfc6349aff20bc6467ccf276c5b52487f7a8",
|
"address": "6edddfc6349aff20bc6467ccf276c5b52487f7a8",
|
||||||
"crypto": {
|
"crypto": {
|
||||||
@@ -214,35 +246,36 @@ mod tests {
|
|||||||
"meta": "{}"
|
"meta": "{}"
|
||||||
}"#;
|
}"#;
|
||||||
|
|
||||||
let expected = KeyFile {
|
let expected = KeyFile {
|
||||||
id: Uuid::from_str("8777d9f6-7860-4b9b-88b7-0b57ee6b3a73").unwrap(),
|
id: Uuid::from_str("8777d9f6-7860-4b9b-88b7-0b57ee6b3a73").unwrap(),
|
||||||
version: Version::V3,
|
version: Version::V3,
|
||||||
address: Some("6edddfc6349aff20bc6467ccf276c5b52487f7a8".into()),
|
address: Some("6edddfc6349aff20bc6467ccf276c5b52487f7a8".into()),
|
||||||
crypto: Crypto {
|
crypto: Crypto {
|
||||||
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||||
iv: "b5a7ec855ec9e2c405371356855fec83".into(),
|
iv: "b5a7ec855ec9e2c405371356855fec83".into(),
|
||||||
}),
|
}),
|
||||||
ciphertext: "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc".into(),
|
ciphertext: "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc"
|
||||||
kdf: Kdf::Scrypt(Scrypt {
|
.into(),
|
||||||
n: 262144,
|
kdf: Kdf::Scrypt(Scrypt {
|
||||||
dklen: 32,
|
n: 262144,
|
||||||
p: 1,
|
dklen: 32,
|
||||||
r: 8,
|
p: 1,
|
||||||
salt: "1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209".into(),
|
r: 8,
|
||||||
}),
|
salt: "1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209".into(),
|
||||||
mac: "46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f".into(),
|
}),
|
||||||
},
|
mac: "46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f".into(),
|
||||||
name: Some("Test".to_owned()),
|
},
|
||||||
meta: Some("{}".to_owned()),
|
name: Some("Test".to_owned()),
|
||||||
};
|
meta: Some("{}".to_owned()),
|
||||||
|
};
|
||||||
|
|
||||||
let keyfile: KeyFile = serde_json::from_str(json).unwrap();
|
let keyfile: KeyFile = serde_json::from_str(json).unwrap();
|
||||||
assert_eq!(keyfile, expected);
|
assert_eq!(keyfile, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn capital_crypto_keyfile() {
|
fn capital_crypto_keyfile() {
|
||||||
let json = r#"
|
let json = r#"
|
||||||
{
|
{
|
||||||
"address": "6edddfc6349aff20bc6467ccf276c5b52487f7a8",
|
"address": "6edddfc6349aff20bc6467ccf276c5b52487f7a8",
|
||||||
"Crypto": {
|
"Crypto": {
|
||||||
@@ -265,60 +298,62 @@ mod tests {
|
|||||||
"version": 3
|
"version": 3
|
||||||
}"#;
|
}"#;
|
||||||
|
|
||||||
let expected = KeyFile {
|
let expected = KeyFile {
|
||||||
id: "8777d9f6-7860-4b9b-88b7-0b57ee6b3a73".into(),
|
id: "8777d9f6-7860-4b9b-88b7-0b57ee6b3a73".into(),
|
||||||
version: Version::V3,
|
version: Version::V3,
|
||||||
address: Some("6edddfc6349aff20bc6467ccf276c5b52487f7a8".into()),
|
address: Some("6edddfc6349aff20bc6467ccf276c5b52487f7a8".into()),
|
||||||
crypto: Crypto {
|
crypto: Crypto {
|
||||||
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||||
iv: "b5a7ec855ec9e2c405371356855fec83".into(),
|
iv: "b5a7ec855ec9e2c405371356855fec83".into(),
|
||||||
}),
|
}),
|
||||||
ciphertext: "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc".into(),
|
ciphertext: "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc"
|
||||||
kdf: Kdf::Scrypt(Scrypt {
|
.into(),
|
||||||
n: 262144,
|
kdf: Kdf::Scrypt(Scrypt {
|
||||||
dklen: 32,
|
n: 262144,
|
||||||
p: 1,
|
dklen: 32,
|
||||||
r: 8,
|
p: 1,
|
||||||
salt: "1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209".into(),
|
r: 8,
|
||||||
}),
|
salt: "1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209".into(),
|
||||||
mac: "46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f".into(),
|
}),
|
||||||
},
|
mac: "46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f".into(),
|
||||||
name: None,
|
},
|
||||||
meta: None,
|
name: None,
|
||||||
};
|
meta: None,
|
||||||
|
};
|
||||||
|
|
||||||
let keyfile: KeyFile = serde_json::from_str(json).unwrap();
|
let keyfile: KeyFile = serde_json::from_str(json).unwrap();
|
||||||
assert_eq!(keyfile, expected);
|
assert_eq!(keyfile, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_and_from_json() {
|
fn to_and_from_json() {
|
||||||
let file = KeyFile {
|
let file = KeyFile {
|
||||||
id: "8777d9f6-7860-4b9b-88b7-0b57ee6b3a73".into(),
|
id: "8777d9f6-7860-4b9b-88b7-0b57ee6b3a73".into(),
|
||||||
version: Version::V3,
|
version: Version::V3,
|
||||||
address: Some("6edddfc6349aff20bc6467ccf276c5b52487f7a8".into()),
|
address: Some("6edddfc6349aff20bc6467ccf276c5b52487f7a8".into()),
|
||||||
crypto: Crypto {
|
crypto: Crypto {
|
||||||
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||||
iv: "b5a7ec855ec9e2c405371356855fec83".into(),
|
iv: "b5a7ec855ec9e2c405371356855fec83".into(),
|
||||||
}),
|
}),
|
||||||
ciphertext: "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc".into(),
|
ciphertext: "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc"
|
||||||
kdf: Kdf::Scrypt(Scrypt {
|
.into(),
|
||||||
n: 262144,
|
kdf: Kdf::Scrypt(Scrypt {
|
||||||
dklen: 32,
|
n: 262144,
|
||||||
p: 1,
|
dklen: 32,
|
||||||
r: 8,
|
p: 1,
|
||||||
salt: "1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209".into(),
|
r: 8,
|
||||||
}),
|
salt: "1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209".into(),
|
||||||
mac: "46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f".into(),
|
}),
|
||||||
},
|
mac: "46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f".into(),
|
||||||
name: Some("Test".to_owned()),
|
},
|
||||||
meta: None,
|
name: Some("Test".to_owned()),
|
||||||
};
|
meta: None,
|
||||||
|
};
|
||||||
|
|
||||||
let serialized = serde_json::to_string(&file).unwrap();
|
let serialized = serde_json::to_string(&file).unwrap();
|
||||||
println!("{}", serialized);
|
println!("{}", serialized);
|
||||||
let deserialized = serde_json::from_str(&serialized).unwrap();
|
let deserialized = serde_json::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
assert_eq!(file, deserialized);
|
assert_eq!(file, deserialized);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//! Contract interface specification.
|
//! Contract interface specification.
|
||||||
|
|
||||||
@@ -29,15 +29,20 @@ mod vault_file;
|
|||||||
mod vault_key_file;
|
mod vault_key_file;
|
||||||
mod version;
|
mod version;
|
||||||
|
|
||||||
pub use self::bytes::Bytes;
|
pub use self::{
|
||||||
pub use self::cipher::{Cipher, CipherSer, CipherSerParams, Aes128Ctr};
|
bytes::Bytes,
|
||||||
pub use self::crypto::{Crypto, CipherText};
|
cipher::{Aes128Ctr, Cipher, CipherSer, CipherSerParams},
|
||||||
pub use self::error::Error;
|
crypto::{CipherText, Crypto},
|
||||||
pub use self::hash::{H128, H160, H256};
|
error::Error,
|
||||||
pub use self::id::Uuid;
|
hash::{H128, H160, H256},
|
||||||
pub use self::kdf::{Kdf, KdfSer, Prf, Pbkdf2, Scrypt, KdfSerParams};
|
id::Uuid,
|
||||||
pub use self::key_file::{KeyFile, OpaqueKeyFile};
|
kdf::{Kdf, KdfSer, KdfSerParams, Pbkdf2, Prf, Scrypt},
|
||||||
pub use self::presale::{PresaleWallet, Encseed};
|
key_file::{KeyFile, OpaqueKeyFile},
|
||||||
pub use self::vault_file::VaultFile;
|
presale::{Encseed, PresaleWallet},
|
||||||
pub use self::vault_key_file::{VaultKeyFile, VaultKeyMeta, insert_vault_name_to_json_meta, remove_vault_name_from_json_meta};
|
vault_file::VaultFile,
|
||||||
pub use self::version::Version;
|
vault_key_file::{
|
||||||
|
insert_vault_name_to_json_meta, remove_vault_name_from_json_meta, VaultKeyFile,
|
||||||
|
VaultKeyMeta,
|
||||||
|
},
|
||||||
|
version::Version,
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,47 +1,50 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::io::Read;
|
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",
|
||||||
@@ -49,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",
|
||||||
@@ -69,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,99 +1,105 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use serde_json;
|
|
||||||
use super::Crypto;
|
use super::Crypto;
|
||||||
|
use serde_json;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
/// Vault meta file
|
/// Vault meta file
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct VaultFile {
|
pub struct VaultFile {
|
||||||
/// Vault password, encrypted with vault password
|
/// Vault password, encrypted with vault password
|
||||||
pub crypto: Crypto,
|
pub crypto: Crypto,
|
||||||
/// Vault metadata string
|
/// Vault metadata string
|
||||||
pub meta: Option<String>,
|
pub meta: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VaultFile {
|
impl VaultFile {
|
||||||
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)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write<W>(&self, writer: &mut W) -> Result<(), serde_json::Error> where W: Write {
|
pub fn write<W>(&self, writer: &mut W) -> Result<(), serde_json::Error>
|
||||||
serde_json::to_writer(writer, self)
|
where
|
||||||
}
|
W: Write,
|
||||||
|
{
|
||||||
|
serde_json::to_writer(writer, self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use serde_json;
|
use json::{Aes128Ctr, Cipher, Crypto, Kdf, Pbkdf2, Prf, VaultFile};
|
||||||
use json::{VaultFile, Crypto, Cipher, Aes128Ctr, Kdf, Pbkdf2, Prf};
|
use serde_json;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(1024).expect("1024 > 0; qed");
|
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(1024).expect("1024 > 0; qed");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_and_from_json() {
|
fn to_and_from_json() {
|
||||||
let file = VaultFile {
|
let file = VaultFile {
|
||||||
crypto: Crypto {
|
crypto: Crypto {
|
||||||
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||||
iv: "0155e3690be19fbfbecabcd440aa284b".into(),
|
iv: "0155e3690be19fbfbecabcd440aa284b".into(),
|
||||||
}),
|
}),
|
||||||
ciphertext: "4d6938a1f49b7782".into(),
|
ciphertext: "4d6938a1f49b7782".into(),
|
||||||
kdf: Kdf::Pbkdf2(Pbkdf2 {
|
kdf: Kdf::Pbkdf2(Pbkdf2 {
|
||||||
c: *ITERATIONS,
|
c: *ITERATIONS,
|
||||||
dklen: 32,
|
dklen: 32,
|
||||||
prf: Prf::HmacSha256,
|
prf: Prf::HmacSha256,
|
||||||
salt: "b6a9338a7ccd39288a86dba73bfecd9101b4f3db9c9830e7c76afdbd4f6872e5".into(),
|
salt: "b6a9338a7ccd39288a86dba73bfecd9101b4f3db9c9830e7c76afdbd4f6872e5".into(),
|
||||||
}),
|
}),
|
||||||
mac: "16381463ea11c6eb2239a9f339c2e780516d29d234ce30ac5f166f9080b5a262".into(),
|
mac: "16381463ea11c6eb2239a9f339c2e780516d29d234ce30ac5f166f9080b5a262".into(),
|
||||||
},
|
},
|
||||||
meta: Some("{}".into()),
|
meta: Some("{}".into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let serialized = serde_json::to_string(&file).unwrap();
|
let serialized = serde_json::to_string(&file).unwrap();
|
||||||
let deserialized = serde_json::from_str(&serialized).unwrap();
|
let deserialized = serde_json::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
assert_eq!(file, deserialized);
|
assert_eq!(file, deserialized);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_and_from_json_no_meta() {
|
fn to_and_from_json_no_meta() {
|
||||||
let file = VaultFile {
|
let file = VaultFile {
|
||||||
crypto: Crypto {
|
crypto: Crypto {
|
||||||
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||||
iv: "0155e3690be19fbfbecabcd440aa284b".into(),
|
iv: "0155e3690be19fbfbecabcd440aa284b".into(),
|
||||||
}),
|
}),
|
||||||
ciphertext: "4d6938a1f49b7782".into(),
|
ciphertext: "4d6938a1f49b7782".into(),
|
||||||
kdf: Kdf::Pbkdf2(Pbkdf2 {
|
kdf: Kdf::Pbkdf2(Pbkdf2 {
|
||||||
c: *ITERATIONS,
|
c: *ITERATIONS,
|
||||||
dklen: 32,
|
dklen: 32,
|
||||||
prf: Prf::HmacSha256,
|
prf: Prf::HmacSha256,
|
||||||
salt: "b6a9338a7ccd39288a86dba73bfecd9101b4f3db9c9830e7c76afdbd4f6872e5".into(),
|
salt: "b6a9338a7ccd39288a86dba73bfecd9101b4f3db9c9830e7c76afdbd4f6872e5".into(),
|
||||||
}),
|
}),
|
||||||
mac: "16381463ea11c6eb2239a9f339c2e780516d29d234ce30ac5f166f9080b5a262".into(),
|
mac: "16381463ea11c6eb2239a9f339c2e780516d29d234ce30ac5f166f9080b5a262".into(),
|
||||||
},
|
},
|
||||||
meta: None,
|
meta: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let serialized = serde_json::to_string(&file).unwrap();
|
let serialized = serde_json::to_string(&file).unwrap();
|
||||||
let deserialized = serde_json::from_str(&serialized).unwrap();
|
let deserialized = serde_json::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
assert_eq!(file, deserialized);
|
assert_eq!(file, deserialized);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,23 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::io::{Read, Write};
|
use super::{Crypto, Uuid, Version, H160};
|
||||||
use serde::de::Error;
|
use serde::de::Error;
|
||||||
use serde_json;
|
use serde_json::{self, error, value::Value};
|
||||||
use serde_json::value::Value;
|
use std::io::{Read, Write};
|
||||||
use serde_json::error;
|
|
||||||
use super::{Uuid, Version, Crypto, H160};
|
|
||||||
|
|
||||||
/// Meta key name for vault field
|
/// Meta key name for vault field
|
||||||
const VAULT_NAME_META_KEY: &'static str = "vault";
|
const VAULT_NAME_META_KEY: &'static str = "vault";
|
||||||
@@ -27,94 +25,112 @@ const VAULT_NAME_META_KEY: &'static str = "vault";
|
|||||||
/// Key file as stored in vaults
|
/// Key file as stored in vaults
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct VaultKeyFile {
|
pub struct VaultKeyFile {
|
||||||
/// Key id
|
/// Key id
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
/// Key version
|
/// Key version
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
/// Secret, encrypted with account password
|
/// Secret, encrypted with account password
|
||||||
pub crypto: Crypto,
|
pub crypto: Crypto,
|
||||||
/// Serialized `VaultKeyMeta`, encrypted with vault password
|
/// Serialized `VaultKeyMeta`, encrypted with vault password
|
||||||
pub metacrypto: Crypto,
|
pub metacrypto: Crypto,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data, stored in `VaultKeyFile::metacrypto`
|
/// Data, stored in `VaultKeyFile::metacrypto`
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct VaultKeyMeta {
|
pub struct VaultKeyMeta {
|
||||||
/// Key address
|
/// Key address
|
||||||
pub address: H160,
|
pub address: H160,
|
||||||
/// Key name
|
/// Key name
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
/// Key metadata
|
/// Key metadata
|
||||||
pub meta: Option<String>,
|
pub meta: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert vault name to the JSON meta field
|
/// 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> {
|
pub fn insert_vault_name_to_json_meta(
|
||||||
let mut meta = if meta.is_empty() {
|
meta: &str,
|
||||||
Value::Object(serde_json::Map::new())
|
vault_name: &str,
|
||||||
} else {
|
) -> Result<String, error::Error> {
|
||||||
serde_json::from_str(meta)?
|
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() {
|
if let Some(meta_obj) = meta.as_object_mut() {
|
||||||
meta_obj.insert(VAULT_NAME_META_KEY.to_owned(), Value::String(vault_name.to_owned()));
|
meta_obj.insert(
|
||||||
serde_json::to_string(meta_obj)
|
VAULT_NAME_META_KEY.to_owned(),
|
||||||
} else {
|
Value::String(vault_name.to_owned()),
|
||||||
Err(error::Error::custom("Meta is expected to be a serialized JSON object"))
|
);
|
||||||
}
|
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
|
/// Remove vault name from the JSON meta field
|
||||||
pub fn remove_vault_name_from_json_meta(meta: &str) -> Result<String, error::Error> {
|
pub fn remove_vault_name_from_json_meta(meta: &str) -> Result<String, error::Error> {
|
||||||
let mut meta = if meta.is_empty() {
|
let mut meta = if meta.is_empty() {
|
||||||
Value::Object(serde_json::Map::new())
|
Value::Object(serde_json::Map::new())
|
||||||
} else {
|
} else {
|
||||||
serde_json::from_str(meta)?
|
serde_json::from_str(meta)?
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(meta_obj) = meta.as_object_mut() {
|
if let Some(meta_obj) = meta.as_object_mut() {
|
||||||
meta_obj.remove(VAULT_NAME_META_KEY);
|
meta_obj.remove(VAULT_NAME_META_KEY);
|
||||||
serde_json::to_string(meta_obj)
|
serde_json::to_string(meta_obj)
|
||||||
} else {
|
} else {
|
||||||
Err(error::Error::custom("Meta is expected to be a serialized JSON object"))
|
Err(error::Error::custom(
|
||||||
}
|
"Meta is expected to be a serialized JSON object",
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VaultKeyFile {
|
impl VaultKeyFile {
|
||||||
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)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write<W>(&self, writer: &mut W) -> Result<(), serde_json::Error> where W: Write {
|
pub fn write<W>(&self, writer: &mut W) -> Result<(), serde_json::Error>
|
||||||
serde_json::to_writer(writer, self)
|
where
|
||||||
}
|
W: Write,
|
||||||
|
{
|
||||||
|
serde_json::to_writer(writer, self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VaultKeyMeta {
|
impl VaultKeyMeta {
|
||||||
pub fn load(bytes: &[u8]) -> Result<Self, serde_json::Error> {
|
pub fn load(bytes: &[u8]) -> Result<Self, serde_json::Error> {
|
||||||
serde_json::from_slice(&bytes)
|
serde_json::from_slice(&bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(&self) -> Result<Vec<u8>, serde_json::Error> {
|
pub fn write(&self) -> Result<Vec<u8>, serde_json::Error> {
|
||||||
let s = serde_json::to_string(self)?;
|
let s = serde_json::to_string(self)?;
|
||||||
Ok(s.as_bytes().into())
|
Ok(s.as_bytes().into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use serde_json;
|
use json::{
|
||||||
use json::{VaultKeyFile, Version, Crypto, Cipher, Aes128Ctr, Kdf, Pbkdf2, Prf,
|
insert_vault_name_to_json_meta, remove_vault_name_from_json_meta, Aes128Ctr, Cipher,
|
||||||
insert_vault_name_to_json_meta, remove_vault_name_from_json_meta};
|
Crypto, Kdf, Pbkdf2, Prf, VaultKeyFile, Version,
|
||||||
use std::num::NonZeroU32;
|
};
|
||||||
|
use serde_json;
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(10240).expect("10240 > 0; qed");
|
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(10240).expect("10240 > 0; qed");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_and_from_json() {
|
fn to_and_from_json() {
|
||||||
let file = VaultKeyFile {
|
let file = VaultKeyFile {
|
||||||
id: "08d82c39-88e3-7a71-6abb-89c8f36c3ceb".into(),
|
id: "08d82c39-88e3-7a71-6abb-89c8f36c3ceb".into(),
|
||||||
version: Version::V3,
|
version: Version::V3,
|
||||||
crypto: Crypto {
|
crypto: Crypto {
|
||||||
@@ -145,33 +161,45 @@ mod test {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let serialized = serde_json::to_string(&file).unwrap();
|
let serialized = serde_json::to_string(&file).unwrap();
|
||||||
let deserialized = serde_json::from_str(&serialized).unwrap();
|
let deserialized = serde_json::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
assert_eq!(file, deserialized);
|
assert_eq!(file, deserialized);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn vault_name_inserted_to_json_meta() {
|
fn vault_name_inserted_to_json_meta() {
|
||||||
assert_eq!(insert_vault_name_to_json_meta(r#""#, "MyVault").unwrap(), r#"{"vault":"MyVault"}"#);
|
assert_eq!(
|
||||||
assert_eq!(insert_vault_name_to_json_meta(r#"{"tags":["kalabala"]}"#, "MyVault").unwrap(), r#"{"tags":["kalabala"],"vault":"MyVault"}"#);
|
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]
|
#[test]
|
||||||
fn vault_name_not_inserted_to_json_meta() {
|
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#"///3533"#, "MyVault").is_err());
|
||||||
assert!(insert_vault_name_to_json_meta(r#""string""#, "MyVault").is_err());
|
assert!(insert_vault_name_to_json_meta(r#""string""#, "MyVault").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn vault_name_removed_from_json_meta() {
|
fn vault_name_removed_from_json_meta() {
|
||||||
assert_eq!(remove_vault_name_from_json_meta(r#"{"vault":"MyVault"}"#).unwrap(), r#"{}"#);
|
assert_eq!(
|
||||||
assert_eq!(remove_vault_name_from_json_meta(r#"{"tags":["kalabala"],"vault":"MyVault"}"#).unwrap(), r#"{"tags":["kalabala"]}"#);
|
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]
|
#[test]
|
||||||
fn vault_name_not_removed_from_json_meta() {
|
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#"///3533"#).is_err());
|
||||||
assert!(remove_vault_name_from_json_meta(r#""string""#).is_err());
|
assert!(remove_vault_name_from_json_meta(r#""string""#).is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +1,67 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
use serde::{Serialize, Serializer, Deserialize, Deserializer};
|
|
||||||
use serde::de::{Error as SerdeError, Visitor};
|
|
||||||
use super::Error;
|
use super::Error;
|
||||||
|
use serde::{
|
||||||
|
de::{Error as SerdeError, Visitor},
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Version {
|
pub enum Version {
|
||||||
V3,
|
V3,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for Version {
|
impl Serialize for Version {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where S: Serializer {
|
where
|
||||||
match *self {
|
S: Serializer,
|
||||||
Version::V3 => serializer.serialize_u64(3)
|
{
|
||||||
}
|
match *self {
|
||||||
}
|
Version::V3 => serializer.serialize_u64(3),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deserialize<'a> for Version {
|
impl<'a> Deserialize<'a> for Version {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Version, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Version, D::Error>
|
||||||
where D: Deserializer<'a> {
|
where
|
||||||
deserializer.deserialize_any(VersionVisitor)
|
D: Deserializer<'a>,
|
||||||
}
|
{
|
||||||
|
deserializer.deserialize_any(VersionVisitor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VersionVisitor;
|
struct VersionVisitor;
|
||||||
|
|
||||||
impl<'a> Visitor<'a> for VersionVisitor {
|
impl<'a> Visitor<'a> for VersionVisitor {
|
||||||
type Value = Version;
|
type Value = Version;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(formatter, "a valid key version identifier")
|
write!(formatter, "a valid key version identifier")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> where E: SerdeError {
|
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
|
||||||
match value {
|
where
|
||||||
3 => Ok(Version::V3),
|
E: SerdeError,
|
||||||
_ => Err(SerdeError::custom(Error::UnsupportedVersion))
|
{
|
||||||
}
|
match value {
|
||||||
}
|
3 => Ok(Version::V3),
|
||||||
|
_ => Err(SerdeError::custom(Error::UnsupportedVersion)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. 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 dir;
|
|
||||||
extern crate itertools;
|
extern crate itertools;
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
extern crate parking_lot;
|
extern crate parking_lot;
|
||||||
@@ -27,13 +26,12 @@ 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 parity_crypto 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]
|
#[macro_use]
|
||||||
@@ -60,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);
|
||||||
|
|||||||
@@ -1,91 +1,93 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::fs;
|
use crypto::{self, pbkdf2, Keccak256};
|
||||||
use std::num::NonZeroU32;
|
use ethkey::{Address, KeyPair, Password, Secret};
|
||||||
use std::path::Path;
|
|
||||||
use json;
|
use json;
|
||||||
use ethkey::{Address, Secret, KeyPair, Password};
|
use std::{fs, num::NonZeroU32, path::Path};
|
||||||
use crypto::{Keccak256, pbkdf2};
|
use Error;
|
||||||
use {crypto, Error};
|
|
||||||
|
|
||||||
/// Pre-sale wallet.
|
/// Pre-sale wallet.
|
||||||
pub struct PresaleWallet {
|
pub struct PresaleWallet {
|
||||||
iv: [u8; 16],
|
iv: [u8; 16],
|
||||||
ciphertext: Vec<u8>,
|
ciphertext: Vec<u8>,
|
||||||
address: Address,
|
address: Address,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<json::PresaleWallet> for PresaleWallet {
|
impl From<json::PresaleWallet> for PresaleWallet {
|
||||||
fn from(wallet: json::PresaleWallet) -> Self {
|
fn from(wallet: json::PresaleWallet) -> Self {
|
||||||
let mut iv = [0u8; 16];
|
let mut iv = [0u8; 16];
|
||||||
iv.copy_from_slice(&wallet.encseed[..16]);
|
iv.copy_from_slice(&wallet.encseed[..16]);
|
||||||
|
|
||||||
let mut ciphertext = vec![];
|
let mut ciphertext = vec![];
|
||||||
ciphertext.extend_from_slice(&wallet.encseed[16..]);
|
ciphertext.extend_from_slice(&wallet.encseed[16..]);
|
||||||
|
|
||||||
PresaleWallet {
|
PresaleWallet {
|
||||||
iv: iv,
|
iv: iv,
|
||||||
ciphertext: ciphertext,
|
ciphertext: ciphertext,
|
||||||
address: Address::from(wallet.address),
|
address: Address::from(wallet.address),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PresaleWallet {
|
impl PresaleWallet {
|
||||||
/// Open a pre-sale wallet.
|
/// Open a pre-sale wallet.
|
||||||
pub fn open<P>(path: P) -> Result<Self, Error> where P: AsRef<Path> {
|
pub fn open<P>(path: P) -> Result<Self, Error>
|
||||||
let file = fs::File::open(path)?;
|
where
|
||||||
let presale = json::PresaleWallet::load(file)
|
P: AsRef<Path>,
|
||||||
.map_err(|e| Error::InvalidKeyFile(format!("{}", e)))?;
|
{
|
||||||
Ok(PresaleWallet::from(presale))
|
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.
|
/// Decrypt the wallet.
|
||||||
pub fn decrypt(&self, password: &Password) -> Result<KeyPair, Error> {
|
pub fn decrypt(&self, password: &Password) -> Result<KeyPair, Error> {
|
||||||
let mut derived_key = [0u8; 32];
|
let mut derived_key = [0u8; 32];
|
||||||
let salt = pbkdf2::Salt(password.as_bytes());
|
let salt = pbkdf2::Salt(password.as_bytes());
|
||||||
let sec = pbkdf2::Secret(password.as_bytes());
|
let sec = pbkdf2::Secret(password.as_bytes());
|
||||||
let iter = NonZeroU32::new(2000).expect("2000 > 0; qed");
|
let iter = NonZeroU32::new(2000).expect("2000 > 0; qed");
|
||||||
pbkdf2::sha256(iter, salt, sec, &mut derived_key);
|
pbkdf2::sha256(iter, salt, sec, &mut derived_key);
|
||||||
|
|
||||||
let mut key = vec![0; self.ciphertext.len()];
|
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)
|
let len =
|
||||||
.map_err(|_| Error::InvalidPassword)?;
|
crypto::aes::decrypt_128_cbc(&derived_key[0..16], &self.iv, &self.ciphertext, &mut key)
|
||||||
let unpadded = &key[..len];
|
.map_err(|_| Error::InvalidPassword)?;
|
||||||
|
let unpadded = &key[..len];
|
||||||
|
|
||||||
let secret = Secret::from_unsafe_slice(&unpadded.keccak256())?;
|
let secret = Secret::from_unsafe_slice(&unpadded.keccak256())?;
|
||||||
if let Ok(kp) = KeyPair::from_secret(secret) {
|
if let Ok(kp) = KeyPair::from_secret(secret) {
|
||||||
if kp.address() == self.address {
|
if kp.address() == self.address {
|
||||||
return Ok(kp)
|
return Ok(kp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(Error::InvalidPassword)
|
Err(Error::InvalidPassword)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::PresaleWallet;
|
use super::PresaleWallet;
|
||||||
use json;
|
use json;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
let json = r#"
|
let json = r#"
|
||||||
{
|
{
|
||||||
"encseed": "137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066",
|
"encseed": "137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066",
|
||||||
"ethaddr": "ede84640d1a1d3e06902048e67aa7db8d52c2ce1",
|
"ethaddr": "ede84640d1a1d3e06902048e67aa7db8d52c2ce1",
|
||||||
@@ -93,9 +95,9 @@ mod tests {
|
|||||||
"btcaddr": "1JvqEc6WLhg6GnyrLBe2ztPAU28KRfuseH"
|
"btcaddr": "1JvqEc6WLhg6GnyrLBe2ztPAU28KRfuseH"
|
||||||
} "#;
|
} "#;
|
||||||
|
|
||||||
let wallet = json::PresaleWallet::load(json.as_bytes()).unwrap();
|
let wallet = json::PresaleWallet::load(json.as_bytes()).unwrap();
|
||||||
let wallet = PresaleWallet::from(wallet);
|
let wallet = PresaleWallet::from(wallet);
|
||||||
assert!(wallet.decrypt(&"123".into()).is_ok());
|
assert!(wallet.decrypt(&"123".into()).is_ok());
|
||||||
assert!(wallet.decrypt(&"124".into()).is_err());
|
assert!(wallet.decrypt(&"124".into()).is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,47 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use rand::{Rng, OsRng};
|
use rand::{OsRng, Rng};
|
||||||
|
|
||||||
pub trait Random {
|
pub trait Random {
|
||||||
fn random() -> Self where Self: Sized;
|
fn random() -> Self
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Random for [u8; 16] {
|
impl Random for [u8; 16] {
|
||||||
fn random() -> Self {
|
fn random() -> Self {
|
||||||
let mut result = [0u8; 16];
|
let mut result = [0u8; 16];
|
||||||
let mut rng = OsRng::new().unwrap();
|
let mut rng = OsRng::new().unwrap();
|
||||||
rng.fill_bytes(&mut result);
|
rng.fill_bytes(&mut result);
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Random for [u8; 32] {
|
impl Random for [u8; 32] {
|
||||||
fn random() -> Self {
|
fn random() -> Self {
|
||||||
let mut result = [0u8; 32];
|
let mut result = [0u8; 32];
|
||||||
let mut rng = OsRng::new().unwrap();
|
let mut rng = OsRng::new().unwrap();
|
||||||
rng.fill_bytes(&mut result);
|
rng.fill_bytes(&mut result);
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a random string of given length.
|
/// Generate a random string of given length.
|
||||||
pub fn random_string(length: usize) -> String {
|
pub fn random_string(length: usize) -> String {
|
||||||
let mut rng = OsRng::new().expect("Not able to operate without random source.");
|
let mut rng = OsRng::new().expect("Not able to operate without random source.");
|
||||||
rng.gen_ascii_chars().take(length).collect()
|
rng.gen_ascii_chars().take(length).collect()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,190 +1,268 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
use ethkey::{Address, Message, Signature, Secret, Password, Public};
|
|
||||||
use Error;
|
|
||||||
use json::{Uuid, OpaqueKeyFile};
|
|
||||||
use ethereum_types::H256;
|
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;
|
use OpaqueSecret;
|
||||||
|
|
||||||
/// Key directory reference
|
/// Key directory reference
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum SecretVaultRef {
|
pub enum SecretVaultRef {
|
||||||
/// Reference to key in root directory
|
/// Reference to key in root directory
|
||||||
Root,
|
Root,
|
||||||
/// Referenc to key in specific vault
|
/// Referenc to key in specific vault
|
||||||
Vault(String),
|
Vault(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stored account reference
|
/// Stored account reference
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, Ord)]
|
||||||
pub struct StoreAccountRef {
|
pub struct StoreAccountRef {
|
||||||
/// Account address
|
/// Account address
|
||||||
pub address: Address,
|
pub address: Address,
|
||||||
/// Vault reference
|
/// Vault reference
|
||||||
pub vault: SecretVaultRef,
|
pub vault: SecretVaultRef,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for StoreAccountRef {
|
impl PartialOrd for StoreAccountRef {
|
||||||
fn partial_cmp(&self, other: &StoreAccountRef) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &StoreAccountRef) -> Option<Ordering> {
|
||||||
Some(self.address.cmp(&other.address).then_with(|| self.vault.cmp(&other.vault)))
|
Some(
|
||||||
}
|
self.address
|
||||||
|
.cmp(&other.address)
|
||||||
|
.then_with(|| self.vault.cmp(&other.vault)),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ::std::borrow::Borrow<Address> for StoreAccountRef {
|
impl ::std::borrow::Borrow<Address> for StoreAccountRef {
|
||||||
fn borrow(&self) -> &Address {
|
fn borrow(&self) -> &Address {
|
||||||
&self.address
|
&self.address
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Simple Secret Store API
|
/// Simple Secret Store API
|
||||||
pub trait SimpleSecretStore: Send + Sync {
|
pub trait SimpleSecretStore: Send + Sync {
|
||||||
/// Inserts new accounts to the store (or vault) with given password.
|
/// Inserts new accounts to the store (or vault) with given password.
|
||||||
fn insert_account(&self, vault: SecretVaultRef, secret: Secret, password: &Password) -> Result<StoreAccountRef, Error>;
|
fn insert_account(
|
||||||
/// Inserts new derived account to the store (or vault) with given password.
|
&self,
|
||||||
fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &Password, derivation: Derivation) -> Result<StoreAccountRef, Error>;
|
vault: SecretVaultRef,
|
||||||
/// Changes accounts password.
|
secret: Secret,
|
||||||
fn change_password(&self, account: &StoreAccountRef, old_password: &Password, new_password: &Password) -> Result<(), Error>;
|
password: &Password,
|
||||||
/// Exports key details for account.
|
) -> Result<StoreAccountRef, Error>;
|
||||||
fn export_account(&self, account: &StoreAccountRef, password: &Password) -> Result<OpaqueKeyFile, Error>;
|
/// Inserts new derived account to the store (or vault) with given password.
|
||||||
/// Entirely removes account from the store and underlying storage.
|
fn insert_derived(
|
||||||
fn remove_account(&self, account: &StoreAccountRef, password: &Password) -> Result<(), Error>;
|
&self,
|
||||||
/// Generates new derived account.
|
vault: SecretVaultRef,
|
||||||
fn generate_derived(&self, account_ref: &StoreAccountRef, password: &Password, derivation: Derivation) -> Result<Address, Error>;
|
account_ref: &StoreAccountRef,
|
||||||
/// Sign a message with given account.
|
password: &Password,
|
||||||
fn sign(&self, account: &StoreAccountRef, password: &Password, message: &Message) -> Result<Signature, Error>;
|
derivation: Derivation,
|
||||||
/// Sign a message with derived account.
|
) -> Result<StoreAccountRef, Error>;
|
||||||
fn sign_derived(&self, account_ref: &StoreAccountRef, password: &Password, derivation: Derivation, message: &Message) -> Result<Signature, Error>;
|
/// Changes accounts password.
|
||||||
/// Decrypt a messages with given account.
|
fn change_password(
|
||||||
fn decrypt(&self, account: &StoreAccountRef, password: &Password, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error>;
|
&self,
|
||||||
/// Agree on shared key.
|
account: &StoreAccountRef,
|
||||||
fn agree(&self, account: &StoreAccountRef, password: &Password, other: &Public) -> Result<Secret, Error>;
|
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.
|
/// Returns all accounts in this secret store.
|
||||||
fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error>;
|
fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error>;
|
||||||
/// Get reference to some account with given address.
|
/// 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.
|
/// 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>;
|
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error>;
|
||||||
|
|
||||||
/// Create new vault with given password
|
/// Create new vault with given password
|
||||||
fn create_vault(&self, name: &str, password: &Password) -> Result<(), Error>;
|
fn create_vault(&self, name: &str, password: &Password) -> Result<(), Error>;
|
||||||
/// Open vault with given password
|
/// Open vault with given password
|
||||||
fn open_vault(&self, name: &str, password: &Password) -> Result<(), Error>;
|
fn open_vault(&self, name: &str, password: &Password) -> Result<(), Error>;
|
||||||
/// Close vault
|
/// Close vault
|
||||||
fn close_vault(&self, name: &str) -> Result<(), Error>;
|
fn close_vault(&self, name: &str) -> Result<(), Error>;
|
||||||
/// List all vaults
|
/// List all vaults
|
||||||
fn list_vaults(&self) -> Result<Vec<String>, Error>;
|
fn list_vaults(&self) -> Result<Vec<String>, Error>;
|
||||||
/// List all currently opened vaults
|
/// List all currently opened vaults
|
||||||
fn list_opened_vaults(&self) -> Result<Vec<String>, Error>;
|
fn list_opened_vaults(&self) -> Result<Vec<String>, Error>;
|
||||||
/// Change vault password
|
/// Change vault password
|
||||||
fn change_vault_password(&self, name: &str, new_password: &Password) -> Result<(), Error>;
|
fn change_vault_password(&self, name: &str, new_password: &Password) -> Result<(), Error>;
|
||||||
/// Cnage account' vault
|
/// Cnage account' vault
|
||||||
fn change_account_vault(&self, vault: SecretVaultRef, account: StoreAccountRef) -> Result<StoreAccountRef, Error>;
|
fn change_account_vault(
|
||||||
/// Get vault metadata string.
|
&self,
|
||||||
fn get_vault_meta(&self, name: &str) -> Result<String, Error>;
|
vault: SecretVaultRef,
|
||||||
/// Set vault metadata string.
|
account: StoreAccountRef,
|
||||||
fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error>;
|
) -> 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
|
/// Secret Store API
|
||||||
pub trait SecretStore: SimpleSecretStore {
|
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>;
|
||||||
|
|
||||||
/// Returns a raw opaque Secret that can be later used to sign a message.
|
/// Signs a message with raw secret.
|
||||||
fn raw_secret(&self, account: &StoreAccountRef, password: &Password) -> Result<OpaqueSecret, Error>;
|
fn sign_with_secret(
|
||||||
|
&self,
|
||||||
|
secret: &OpaqueSecret,
|
||||||
|
message: &Message,
|
||||||
|
) -> Result<Signature, Error> {
|
||||||
|
Ok(::ethkey::sign(&secret.0, message)?)
|
||||||
|
}
|
||||||
|
|
||||||
/// Signs a message with raw secret.
|
/// Imports presale wallet
|
||||||
fn sign_with_secret(&self, secret: &OpaqueSecret, message: &Message) -> Result<Signature, Error> {
|
fn import_presale(
|
||||||
Ok(::ethkey::sign(&secret.0, message)?)
|
&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>;
|
||||||
|
|
||||||
/// Imports presale wallet
|
/// Returns a public key for given account.
|
||||||
fn import_presale(&self, vault: SecretVaultRef, json: &[u8], password: &Password) -> Result<StoreAccountRef, Error>;
|
fn public(&self, account: &StoreAccountRef, password: &Password) -> Result<Public, 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: &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.
|
/// Returns uuid of an account.
|
||||||
fn public(&self, account: &StoreAccountRef, password: &Password) -> Result<Public, Error>;
|
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>;
|
||||||
|
|
||||||
/// Returns uuid of an account.
|
/// Modifies account metadata.
|
||||||
fn uuid(&self, account: &StoreAccountRef) -> Result<Uuid, Error>;
|
fn set_name(&self, account: &StoreAccountRef, name: String) -> Result<(), Error>;
|
||||||
/// Returns account's name.
|
/// Modifies account name.
|
||||||
fn name(&self, account: &StoreAccountRef) -> Result<String, Error>;
|
fn set_meta(&self, account: &StoreAccountRef, meta: String) -> Result<(), Error>;
|
||||||
/// Returns account's metadata.
|
|
||||||
fn meta(&self, account: &StoreAccountRef) -> Result<String, Error>;
|
|
||||||
|
|
||||||
/// Modifies account metadata.
|
/// Returns local path of the store.
|
||||||
fn set_name(&self, account: &StoreAccountRef, name: String) -> Result<(), Error>;
|
fn local_path(&self) -> PathBuf;
|
||||||
/// Modifies account name.
|
|
||||||
fn set_meta(&self, account: &StoreAccountRef, meta: String) -> Result<(), Error>;
|
|
||||||
|
|
||||||
/// Returns local path of the store.
|
|
||||||
fn local_path(&self) -> PathBuf;
|
|
||||||
/// Lists all found geth accounts.
|
|
||||||
fn list_geth_accounts(&self, testnet: bool) -> Vec<Address>;
|
|
||||||
/// Imports geth accounts to the store/vault.
|
|
||||||
fn import_geth_accounts(&self, vault: SecretVaultRef, desired: Vec<Address>, testnet: bool) -> Result<Vec<StoreAccountRef>, Error>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StoreAccountRef {
|
impl StoreAccountRef {
|
||||||
/// Create reference to root account with given address
|
/// Create reference to root account with given address
|
||||||
pub fn root(address: Address) -> Self {
|
pub fn root(address: Address) -> Self {
|
||||||
StoreAccountRef::new(SecretVaultRef::Root, address)
|
StoreAccountRef::new(SecretVaultRef::Root, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create reference to vault account with given address
|
/// Create reference to vault account with given address
|
||||||
pub fn vault(vault_name: &str, address: Address) -> Self {
|
pub fn vault(vault_name: &str, address: Address) -> Self {
|
||||||
StoreAccountRef::new(SecretVaultRef::Vault(vault_name.to_owned()), address)
|
StoreAccountRef::new(SecretVaultRef::Vault(vault_name.to_owned()), address)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create new account reference
|
/// Create new account reference
|
||||||
pub fn new(vault_ref: SecretVaultRef, address: Address) -> Self {
|
pub fn new(vault_ref: SecretVaultRef, address: Address) -> Self {
|
||||||
StoreAccountRef {
|
StoreAccountRef {
|
||||||
vault: vault_ref,
|
vault: vault_ref,
|
||||||
address: address,
|
address: address,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for StoreAccountRef {
|
impl Hash for StoreAccountRef {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
self.address.hash(state);
|
self.address.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Node in hierarchical derivation.
|
/// Node in hierarchical derivation.
|
||||||
pub struct IndexDerivation {
|
pub struct IndexDerivation {
|
||||||
/// Node is soft (allows proof of parent from parent node).
|
/// Node is soft (allows proof of parent from parent node).
|
||||||
pub soft: bool,
|
pub soft: bool,
|
||||||
/// Index sequence of the node.
|
/// Index sequence of the node.
|
||||||
pub index: u32,
|
pub index: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Derivation scheme for keys
|
/// Derivation scheme for keys
|
||||||
pub enum Derivation {
|
pub enum Derivation {
|
||||||
/// Hierarchical derivation
|
/// Hierarchical derivation
|
||||||
Hierarchical(Vec<IndexDerivation>),
|
Hierarchical(Vec<IndexDerivation>),
|
||||||
/// Hash derivation, soft.
|
/// Hash derivation, soft.
|
||||||
SoftHash(H256),
|
SoftHash(H256),
|
||||||
/// Hash derivation, hard.
|
/// Hash derivation, hard.
|
||||||
HardHash(H256),
|
HardHash(H256),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,154 +1,197 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
extern crate rand;
|
|
||||||
extern crate ethstore;
|
extern crate ethstore;
|
||||||
|
extern crate rand;
|
||||||
|
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
use ethstore::{EthStore, SimpleSecretStore, SecretVaultRef, StoreAccountRef};
|
use ethstore::{
|
||||||
use ethstore::ethkey::{Random, Generator, Secret, KeyPair, verify_address};
|
accounts_dir::RootDiskDirectory,
|
||||||
use ethstore::accounts_dir::RootDiskDirectory;
|
ethkey::{verify_address, Generator, KeyPair, Random, Secret},
|
||||||
|
EthStore, SecretVaultRef, SimpleSecretStore, StoreAccountRef,
|
||||||
|
};
|
||||||
use util::TransientDir;
|
use util::TransientDir;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn secret_store_create() {
|
fn secret_store_create() {
|
||||||
let dir = TransientDir::create().unwrap();
|
let dir = TransientDir::create().unwrap();
|
||||||
let _ = EthStore::open(Box::new(dir)).unwrap();
|
let _ = EthStore::open(Box::new(dir)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn secret_store_open_not_existing() {
|
fn secret_store_open_not_existing() {
|
||||||
let dir = TransientDir::open();
|
let dir = TransientDir::open();
|
||||||
let _ = EthStore::open(Box::new(dir)).unwrap();
|
let _ = EthStore::open(Box::new(dir)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn random_secret() -> Secret {
|
fn random_secret() -> Secret {
|
||||||
Random.generate().unwrap().secret().clone()
|
Random.generate().unwrap().secret().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn secret_store_create_account() {
|
fn secret_store_create_account() {
|
||||||
let dir = TransientDir::create().unwrap();
|
let dir = TransientDir::create().unwrap();
|
||||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||||
assert_eq!(store.accounts().unwrap().len(), 0);
|
assert_eq!(store.accounts().unwrap().len(), 0);
|
||||||
assert!(store.insert_account(SecretVaultRef::Root, random_secret(), &"".into()).is_ok());
|
assert!(store
|
||||||
assert_eq!(store.accounts().unwrap().len(), 1);
|
.insert_account(SecretVaultRef::Root, random_secret(), &"".into())
|
||||||
assert!(store.insert_account(SecretVaultRef::Root, random_secret(), &"".into()).is_ok());
|
.is_ok());
|
||||||
assert_eq!(store.accounts().unwrap().len(), 2);
|
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]
|
#[test]
|
||||||
fn secret_store_sign() {
|
fn secret_store_sign() {
|
||||||
let dir = TransientDir::create().unwrap();
|
let dir = TransientDir::create().unwrap();
|
||||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||||
assert!(store.insert_account(SecretVaultRef::Root, random_secret(), &"".into()).is_ok());
|
assert!(store
|
||||||
let accounts = store.accounts().unwrap();
|
.insert_account(SecretVaultRef::Root, random_secret(), &"".into())
|
||||||
assert_eq!(accounts.len(), 1);
|
.is_ok());
|
||||||
assert!(store.sign(&accounts[0], &"".into(), &Default::default()).is_ok());
|
let accounts = store.accounts().unwrap();
|
||||||
assert!(store.sign(&accounts[0], &"1".into(), &Default::default()).is_err());
|
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]
|
#[test]
|
||||||
fn secret_store_change_password() {
|
fn secret_store_change_password() {
|
||||||
let dir = TransientDir::create().unwrap();
|
let dir = TransientDir::create().unwrap();
|
||||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||||
assert!(store.insert_account(SecretVaultRef::Root, random_secret(), &"".into()).is_ok());
|
assert!(store
|
||||||
let accounts = store.accounts().unwrap();
|
.insert_account(SecretVaultRef::Root, random_secret(), &"".into())
|
||||||
assert_eq!(accounts.len(), 1);
|
.is_ok());
|
||||||
assert!(store.sign(&accounts[0], &"".into(), &Default::default()).is_ok());
|
let accounts = store.accounts().unwrap();
|
||||||
assert!(store.change_password(&accounts[0], &"".into(), &"1".into()).is_ok());
|
assert_eq!(accounts.len(), 1);
|
||||||
assert!(store.sign(&accounts[0], &"".into(), &Default::default()).is_err());
|
assert!(store
|
||||||
assert!(store.sign(&accounts[0], &"1".into(), &Default::default()).is_ok());
|
.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]
|
#[test]
|
||||||
fn secret_store_remove_account() {
|
fn secret_store_remove_account() {
|
||||||
let dir = TransientDir::create().unwrap();
|
let dir = TransientDir::create().unwrap();
|
||||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||||
assert!(store.insert_account(SecretVaultRef::Root, random_secret(), &"".into()).is_ok());
|
assert!(store
|
||||||
let accounts = store.accounts().unwrap();
|
.insert_account(SecretVaultRef::Root, random_secret(), &"".into())
|
||||||
assert_eq!(accounts.len(), 1);
|
.is_ok());
|
||||||
assert!(store.remove_account(&accounts[0], &"".into()).is_ok());
|
let accounts = store.accounts().unwrap();
|
||||||
assert_eq!(store.accounts().unwrap().len(), 0);
|
assert_eq!(accounts.len(), 1);
|
||||||
assert!(store.remove_account(&accounts[0], &"".into()).is_err());
|
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 {
|
fn test_path() -> &'static str {
|
||||||
match ::std::fs::metadata("ethstore") {
|
match ::std::fs::metadata("ethstore") {
|
||||||
Ok(_) => "ethstore/tests/res/geth_keystore",
|
Ok(_) => "ethstore/tests/res/geth_keystore",
|
||||||
Err(_) => "tests/res/geth_keystore",
|
Err(_) => "tests/res/geth_keystore",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pat_path() -> &'static str {
|
fn pat_path() -> &'static str {
|
||||||
match ::std::fs::metadata("ethstore") {
|
match ::std::fs::metadata("ethstore") {
|
||||||
Ok(_) => "ethstore/tests/res/pat",
|
Ok(_) => "ethstore/tests/res/pat",
|
||||||
Err(_) => "tests/res/pat",
|
Err(_) => "tests/res/pat",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ciphertext_path() -> &'static str {
|
fn ciphertext_path() -> &'static str {
|
||||||
match ::std::fs::metadata("ethstore") {
|
match ::std::fs::metadata("ethstore") {
|
||||||
Ok(_) => "ethstore/tests/res/ciphertext",
|
Ok(_) => "ethstore/tests/res/ciphertext",
|
||||||
Err(_) => "tests/res/ciphertext",
|
Err(_) => "tests/res/ciphertext",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn secret_store_laod_geth_files() {
|
fn secret_store_laod_geth_files() {
|
||||||
let dir = RootDiskDirectory::at(test_path());
|
let dir = RootDiskDirectory::at(test_path());
|
||||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||||
assert_eq!(store.accounts().unwrap(), vec![
|
assert_eq!(
|
||||||
StoreAccountRef::root("3f49624084b67849c7b4e805c5988c21a430f9d9".into()),
|
store.accounts().unwrap(),
|
||||||
StoreAccountRef::root("5ba4dcf897e97c2bdf8315b9ef26c13c085988cf".into()),
|
vec![
|
||||||
StoreAccountRef::root("63121b431a52f8043c16fcf0d1df9cb7b5f66649".into()),
|
StoreAccountRef::root("3f49624084b67849c7b4e805c5988c21a430f9d9".into()),
|
||||||
]);
|
StoreAccountRef::root("5ba4dcf897e97c2bdf8315b9ef26c13c085988cf".into()),
|
||||||
|
StoreAccountRef::root("63121b431a52f8043c16fcf0d1df9cb7b5f66649".into()),
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn secret_store_load_pat_files() {
|
fn secret_store_load_pat_files() {
|
||||||
let dir = RootDiskDirectory::at(pat_path());
|
let dir = RootDiskDirectory::at(pat_path());
|
||||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||||
assert_eq!(store.accounts().unwrap(), vec![
|
assert_eq!(
|
||||||
StoreAccountRef::root("3f49624084b67849c7b4e805c5988c21a430f9d9".into()),
|
store.accounts().unwrap(),
|
||||||
StoreAccountRef::root("5ba4dcf897e97c2bdf8315b9ef26c13c085988cf".into()),
|
vec![
|
||||||
]);
|
StoreAccountRef::root("3f49624084b67849c7b4e805c5988c21a430f9d9".into()),
|
||||||
|
StoreAccountRef::root("5ba4dcf897e97c2bdf8315b9ef26c13c085988cf".into()),
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_decrypting_files_with_short_ciphertext() {
|
fn test_decrypting_files_with_short_ciphertext() {
|
||||||
// 31e9d1e6d844bd3a536800ef8d8be6a9975db509, 30
|
// 31e9d1e6d844bd3a536800ef8d8be6a9975db509, 30
|
||||||
let kp1 = KeyPair::from_secret("000081c29e8142bb6a81bef5a92bda7a8328a5c85bb2f9542e76f9b0f94fc018".parse().unwrap()).unwrap();
|
let kp1 = KeyPair::from_secret(
|
||||||
// d1e64e5480bfaf733ba7d48712decb8227797a4e , 31
|
"000081c29e8142bb6a81bef5a92bda7a8328a5c85bb2f9542e76f9b0f94fc018"
|
||||||
let kp2 = KeyPair::from_secret("00fa7b3db73dc7dfdf8c5fbdb796d741e4488628c41fc4febd9160a866ba0f35".parse().unwrap()).unwrap();
|
.parse()
|
||||||
let dir = RootDiskDirectory::at(ciphertext_path());
|
.unwrap(),
|
||||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
)
|
||||||
let accounts = store.accounts().unwrap();
|
.unwrap();
|
||||||
assert_eq!(accounts, vec![
|
// d1e64e5480bfaf733ba7d48712decb8227797a4e , 31
|
||||||
StoreAccountRef::root("31e9d1e6d844bd3a536800ef8d8be6a9975db509".into()),
|
let kp2 = KeyPair::from_secret(
|
||||||
StoreAccountRef::root("d1e64e5480bfaf733ba7d48712decb8227797a4e".into()),
|
"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 message = Default::default();
|
||||||
|
|
||||||
let s1 = store.sign(&accounts[0], &"foo".into(), &message).unwrap();
|
let s1 = store.sign(&accounts[0], &"foo".into(), &message).unwrap();
|
||||||
let s2 = store.sign(&accounts[1], &"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(&accounts[0].address, &s1, &message).unwrap());
|
||||||
assert!(verify_address(&kp1.address(), &s1, &message).unwrap());
|
assert!(verify_address(&kp1.address(), &s1, &message).unwrap());
|
||||||
assert!(verify_address(&kp2.address(), &s2, &message).unwrap());
|
assert!(verify_address(&kp2.address(), &s2, &message).unwrap());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. 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;
|
||||||
|
|
||||||
|
|||||||
@@ -1,81 +1,82 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use ethstore::{
|
||||||
use std::{env, fs};
|
accounts_dir::{KeyDirectory, RootDiskDirectory},
|
||||||
use rand::{Rng, OsRng};
|
Error, SafeAccount,
|
||||||
use ethstore::accounts_dir::{KeyDirectory, RootDiskDirectory};
|
};
|
||||||
use ethstore::{Error, SafeAccount};
|
use rand::{OsRng, Rng};
|
||||||
|
use std::{env, fs, path::PathBuf};
|
||||||
|
|
||||||
pub fn random_dir() -> PathBuf {
|
pub fn random_dir() -> PathBuf {
|
||||||
let mut rng = OsRng::new().unwrap();
|
let mut rng = OsRng::new().unwrap();
|
||||||
let mut dir = env::temp_dir();
|
let mut dir = env::temp_dir();
|
||||||
dir.push(format!("{:x}-{:x}", rng.next_u64(), rng.next_u64()));
|
dir.push(format!("{:x}-{:x}", rng.next_u64(), rng.next_u64()));
|
||||||
dir
|
dir
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TransientDir {
|
pub struct TransientDir {
|
||||||
dir: RootDiskDirectory,
|
dir: RootDiskDirectory,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransientDir {
|
impl TransientDir {
|
||||||
pub fn create() -> Result<Self, Error> {
|
pub fn create() -> Result<Self, Error> {
|
||||||
let path = random_dir();
|
let path = random_dir();
|
||||||
let result = TransientDir {
|
let result = TransientDir {
|
||||||
dir: RootDiskDirectory::create(&path)?,
|
dir: RootDiskDirectory::create(&path)?,
|
||||||
path: path,
|
path: path,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open() -> Self {
|
pub fn open() -> Self {
|
||||||
let path = random_dir();
|
let path = random_dir();
|
||||||
TransientDir {
|
TransientDir {
|
||||||
dir: RootDiskDirectory::at(&path),
|
dir: RootDiskDirectory::at(&path),
|
||||||
path: path,
|
path: path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for TransientDir {
|
impl Drop for TransientDir {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
fs::remove_dir_all(&self.path).expect("Expected to remove temp dir");
|
fs::remove_dir_all(&self.path).expect("Expected to remove temp dir");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyDirectory for TransientDir {
|
impl KeyDirectory for TransientDir {
|
||||||
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
||||||
self.dir.load()
|
self.dir.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
self.dir.update(account)
|
self.dir.update(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
self.dir.insert(account)
|
self.dir.insert(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
self.dir.remove(account)
|
self.dir.remove(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unique_repr(&self) -> Result<u64, Error> {
|
fn unique_repr(&self) -> Result<u64, Error> {
|
||||||
self.dir.unique_repr()
|
self.dir.unique_repr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
[package]
|
|
||||||
description = "Fake hardware-wallet, for OS' that don't support libusb"
|
|
||||||
name = "fake-hardware-wallet"
|
|
||||||
version = "0.0.1"
|
|
||||||
license = "GPL-3.0"
|
|
||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
ethereum-types = "0.4"
|
|
||||||
ethkey = { path = "../../accounts/ethkey" }
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity Ethereum.
|
|
||||||
|
|
||||||
// Parity Ethereum 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.
|
|
||||||
|
|
||||||
// Parity Ethereum 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 Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//! Dummy module for platforms that does not provide support for hardware wallets (libusb)
|
|
||||||
|
|
||||||
extern crate ethereum_types;
|
|
||||||
extern crate ethkey;
|
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
use ethereum_types::U256;
|
|
||||||
use ethkey::{Address, Signature};
|
|
||||||
|
|
||||||
pub struct WalletInfo {
|
|
||||||
pub address: Address,
|
|
||||||
pub name: String,
|
|
||||||
pub manufacturer: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
/// `ErrorType` for devices with no `hardware wallet`
|
|
||||||
pub enum Error {
|
|
||||||
NoWallet,
|
|
||||||
KeyNotFound,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TransactionInfo {
|
|
||||||
/// Nonce
|
|
||||||
pub nonce: U256,
|
|
||||||
/// Gas price
|
|
||||||
pub gas_price: U256,
|
|
||||||
/// Gas limit
|
|
||||||
pub gas_limit: U256,
|
|
||||||
/// Receiver
|
|
||||||
pub to: Option<Address>,
|
|
||||||
/// Value
|
|
||||||
pub value: U256,
|
|
||||||
/// Data
|
|
||||||
pub data: Vec<u8>,
|
|
||||||
/// Chain ID
|
|
||||||
pub chain_id: Option<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum KeyPath {
|
|
||||||
/// Ethereum.
|
|
||||||
Ethereum,
|
|
||||||
/// Ethereum classic.
|
|
||||||
EthereumClassic,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `HardwareWalletManager` for devices with no `hardware wallet`
|
|
||||||
pub struct HardwareWalletManager;
|
|
||||||
|
|
||||||
impl HardwareWalletManager {
|
|
||||||
pub fn new() -> Result<Self, Error> {
|
|
||||||
Err(Error::NoWallet)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_key_path(&self, _key_path: KeyPath) {}
|
|
||||||
|
|
||||||
pub fn wallet_info(&self, _: &Address) -> Option<WalletInfo> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_wallets(&self) -> Vec<WalletInfo> {
|
|
||||||
Vec::with_capacity(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_locked_wallets(&self) -> Result<Vec<String>, Error> {
|
|
||||||
Err(Error::NoWallet)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pin_matrix_ack(&self, _: &str, _: &str) -> Result<bool, Error> {
|
|
||||||
Err(Error::NoWallet)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sign_transaction(&self, _address: &Address, _transaction: &TransactionInfo, _rlp_transaction: &[u8]) -> Result<Signature, Error> {
|
|
||||||
Err(Error::NoWallet) }
|
|
||||||
|
|
||||||
pub fn sign_message(&self, _address: &Address, _msg: &[u8]) -> Result<Signature, Error> {
|
|
||||||
Err(Error::NoWallet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "No hardware wallet!!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
[package]
|
|
||||||
description = "Hardware wallet support."
|
|
||||||
homepage = "http://parity.io"
|
|
||||||
license = "GPL-3.0"
|
|
||||||
name = "hardware-wallet"
|
|
||||||
version = "1.12.0"
|
|
||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
log = "0.4"
|
|
||||||
parking_lot = "0.7"
|
|
||||||
protobuf = "1.4"
|
|
||||||
hidapi = { git = "https://github.com/paritytech/hidapi-rs" }
|
|
||||||
libusb = { git = "https://github.com/paritytech/libusb-rs" }
|
|
||||||
trezor-sys = { git = "https://github.com/paritytech/trezor-sys" }
|
|
||||||
ethkey = { path = "../ethkey" }
|
|
||||||
ethereum-types = "0.4"
|
|
||||||
semver = "0.9"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rustc-hex = "1.0"
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,402 +0,0 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity Ethereum.
|
|
||||||
|
|
||||||
// Parity Ethereum 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.
|
|
||||||
|
|
||||||
// Parity Ethereum 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 Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//! Hardware wallet management.
|
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
|
||||||
#![warn(warnings)]
|
|
||||||
|
|
||||||
extern crate ethereum_types;
|
|
||||||
extern crate ethkey;
|
|
||||||
extern crate hidapi;
|
|
||||||
extern crate libusb;
|
|
||||||
extern crate parking_lot;
|
|
||||||
extern crate protobuf;
|
|
||||||
extern crate semver;
|
|
||||||
extern crate trezor_sys;
|
|
||||||
|
|
||||||
#[macro_use] extern crate log;
|
|
||||||
#[cfg(test)] extern crate rustc_hex;
|
|
||||||
|
|
||||||
mod ledger;
|
|
||||||
mod trezor;
|
|
||||||
|
|
||||||
use std::sync::{Arc, atomic, atomic::AtomicBool, Weak};
|
|
||||||
use std::{fmt, time::Duration};
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
use ethereum_types::U256;
|
|
||||||
use ethkey::{Address, Signature};
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
|
|
||||||
const HID_GLOBAL_USAGE_PAGE: u16 = 0xFF00;
|
|
||||||
const HID_USB_DEVICE_CLASS: u8 = 0;
|
|
||||||
const MAX_POLLING_DURATION: Duration = Duration::from_millis(500);
|
|
||||||
const USB_EVENT_POLLING_INTERVAL: Duration = Duration::from_millis(500);
|
|
||||||
|
|
||||||
/// `HardwareWallet` device
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Device {
|
|
||||||
path: String,
|
|
||||||
info: WalletInfo,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Wallet` trait
|
|
||||||
pub trait Wallet<'a> {
|
|
||||||
/// Error
|
|
||||||
type Error;
|
|
||||||
/// Transaction data format
|
|
||||||
type Transaction;
|
|
||||||
|
|
||||||
/// Sign transaction data with wallet managing `address`.
|
|
||||||
fn sign_transaction(&self, address: &Address, transaction: Self::Transaction) -> Result<Signature, Self::Error>;
|
|
||||||
|
|
||||||
/// Set key derivation path for a chain.
|
|
||||||
fn set_key_path(&self, key_path: KeyPath);
|
|
||||||
|
|
||||||
/// Re-populate device list
|
|
||||||
/// Note, this assumes all devices are iterated over and updated
|
|
||||||
fn update_devices(&self, device_direction: DeviceDirection) -> Result<usize, Self::Error>;
|
|
||||||
|
|
||||||
/// Read device info
|
|
||||||
fn read_device(&self, usb: &hidapi::HidApi, dev_info: &hidapi::HidDeviceInfo) -> Result<Device, Self::Error>;
|
|
||||||
|
|
||||||
/// List connected and acknowledged wallets
|
|
||||||
fn list_devices(&self) -> Vec<WalletInfo>;
|
|
||||||
|
|
||||||
/// List locked wallets
|
|
||||||
/// This may be moved if it is the wrong assumption, for example this is not supported by Ledger
|
|
||||||
/// Then this method return a empty vector
|
|
||||||
fn list_locked_devices(&self) -> Vec<String>;
|
|
||||||
|
|
||||||
/// Get wallet info.
|
|
||||||
fn get_wallet(&self, address: &Address) -> Option<WalletInfo>;
|
|
||||||
|
|
||||||
/// Generate ethereum address for a Wallet
|
|
||||||
fn get_address(&self, device: &hidapi::HidDevice) -> Result<Option<Address>, Self::Error>;
|
|
||||||
|
|
||||||
/// Open a device using `device path`
|
|
||||||
/// Note, f - is a closure that borrows HidResult<HidDevice>
|
|
||||||
/// HidDevice is in turn a type alias for a `c_void function pointer`
|
|
||||||
/// For further information see:
|
|
||||||
/// * <https://github.com/paritytech/hidapi-rs>
|
|
||||||
/// * <https://github.com/rust-lang/libc>
|
|
||||||
fn open_path<R, F>(&self, f: F) -> Result<R, Self::Error>
|
|
||||||
where F: Fn() -> Result<R, &'static str>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hardware wallet error.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Ledger device error.
|
|
||||||
LedgerDevice(ledger::Error),
|
|
||||||
/// Trezor device error
|
|
||||||
TrezorDevice(trezor::Error),
|
|
||||||
/// USB error.
|
|
||||||
Usb(libusb::Error),
|
|
||||||
/// HID error
|
|
||||||
Hid(String),
|
|
||||||
/// Hardware wallet not found for specified key.
|
|
||||||
KeyNotFound,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This is the transaction info we need to supply to Trezor message. It's more
|
|
||||||
/// or less a duplicate of `ethcore::transaction::Transaction`, but we can't
|
|
||||||
/// import ethcore here as that would be a circular dependency.
|
|
||||||
pub struct TransactionInfo {
|
|
||||||
/// Nonce
|
|
||||||
pub nonce: U256,
|
|
||||||
/// Gas price
|
|
||||||
pub gas_price: U256,
|
|
||||||
/// Gas limit
|
|
||||||
pub gas_limit: U256,
|
|
||||||
/// Receiver
|
|
||||||
pub to: Option<Address>,
|
|
||||||
/// Value
|
|
||||||
pub value: U256,
|
|
||||||
/// Data
|
|
||||||
pub data: Vec<u8>,
|
|
||||||
/// Chain ID
|
|
||||||
pub chain_id: Option<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hardware wallet information.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct WalletInfo {
|
|
||||||
/// Wallet device name.
|
|
||||||
pub name: String,
|
|
||||||
/// Wallet device manufacturer.
|
|
||||||
pub manufacturer: String,
|
|
||||||
/// Wallet device serial number.
|
|
||||||
pub serial: String,
|
|
||||||
/// Ethereum address.
|
|
||||||
pub address: Address,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Key derivation paths used on hardware wallets.
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum KeyPath {
|
|
||||||
/// Ethereum.
|
|
||||||
Ethereum,
|
|
||||||
/// Ethereum classic.
|
|
||||||
EthereumClassic,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
|
||||||
match *self {
|
|
||||||
Error::KeyNotFound => write!(f, "Key not found for given address."),
|
|
||||||
Error::LedgerDevice(ref e) => write!(f, "{}", e),
|
|
||||||
Error::TrezorDevice(ref e) => write!(f, "{}", e),
|
|
||||||
Error::Usb(ref e) => write!(f, "{}", e),
|
|
||||||
Error::Hid(ref e) => write!(f, "{}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ledger::Error> for Error {
|
|
||||||
fn from(err: ledger::Error) -> Self {
|
|
||||||
match err {
|
|
||||||
ledger::Error::KeyNotFound => Error::KeyNotFound,
|
|
||||||
_ => Error::LedgerDevice(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<trezor::Error> for Error {
|
|
||||||
fn from(err: trezor::Error) -> Self {
|
|
||||||
match err {
|
|
||||||
trezor::Error::KeyNotFound => Error::KeyNotFound,
|
|
||||||
_ => Error::TrezorDevice(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<libusb::Error> for Error {
|
|
||||||
fn from(err: libusb::Error) -> Self {
|
|
||||||
Error::Usb(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Specifies the direction of the `HardwareWallet` i.e, whether it arrived or left
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
pub enum DeviceDirection {
|
|
||||||
/// Device arrived
|
|
||||||
Arrived,
|
|
||||||
/// Device left
|
|
||||||
Left,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for DeviceDirection {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
DeviceDirection::Arrived => write!(f, "arrived"),
|
|
||||||
DeviceDirection::Left => write!(f, "left"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hardware wallet management interface.
|
|
||||||
pub struct HardwareWalletManager {
|
|
||||||
exiting: Arc<AtomicBool>,
|
|
||||||
ledger: Arc<ledger::Manager>,
|
|
||||||
trezor: Arc<trezor::Manager>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HardwareWalletManager {
|
|
||||||
/// Hardware wallet constructor
|
|
||||||
pub fn new() -> Result<Self, Error> {
|
|
||||||
let exiting = Arc::new(AtomicBool::new(false));
|
|
||||||
let hidapi = Arc::new(Mutex::new(hidapi::HidApi::new().map_err(|e| Error::Hid(e.to_string().clone()))?));
|
|
||||||
let ledger = ledger::Manager::new(hidapi.clone());
|
|
||||||
let trezor = trezor::Manager::new(hidapi.clone());
|
|
||||||
let usb_context = Arc::new(libusb::Context::new()?);
|
|
||||||
|
|
||||||
let l = ledger.clone();
|
|
||||||
let t = trezor.clone();
|
|
||||||
let exit = exiting.clone();
|
|
||||||
|
|
||||||
// Subscribe to all vendor IDs (VIDs) and product IDs (PIDs)
|
|
||||||
// This means that the `HardwareWalletManager` is responsible to validate the detected device
|
|
||||||
usb_context.register_callback(
|
|
||||||
None, None, Some(HID_USB_DEVICE_CLASS),
|
|
||||||
Box::new(EventHandler::new(
|
|
||||||
Arc::downgrade(&ledger),
|
|
||||||
Arc::downgrade(&trezor)
|
|
||||||
))
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Hardware event subscriber thread
|
|
||||||
thread::Builder::new()
|
|
||||||
.name("hw_wallet_manager".to_string())
|
|
||||||
.spawn(move || {
|
|
||||||
if let Err(e) = l.update_devices(DeviceDirection::Arrived) {
|
|
||||||
debug!(target: "hw", "Ledger couldn't connect at startup, error: {}", e);
|
|
||||||
}
|
|
||||||
if let Err(e) = t.update_devices(DeviceDirection::Arrived) {
|
|
||||||
debug!(target: "hw", "Trezor couldn't connect at startup, error: {}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
while !exit.load(atomic::Ordering::Acquire) {
|
|
||||||
if let Err(e) = usb_context.handle_events(Some(USB_EVENT_POLLING_INTERVAL)) {
|
|
||||||
debug!(target: "hw", "HardwareWalletManager event handler error: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
exiting,
|
|
||||||
trezor,
|
|
||||||
ledger,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Select key derivation path for a chain.
|
|
||||||
/// Currently, only one hard-coded keypath is supported
|
|
||||||
/// It is managed by `ethcore/account_provider`
|
|
||||||
pub fn set_key_path(&self, key_path: KeyPath) {
|
|
||||||
self.ledger.set_key_path(key_path);
|
|
||||||
self.trezor.set_key_path(key_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List connected wallets. This only returns wallets that are ready to be used.
|
|
||||||
pub fn list_wallets(&self) -> Vec<WalletInfo> {
|
|
||||||
let mut wallets = Vec::new();
|
|
||||||
wallets.extend(self.ledger.list_devices());
|
|
||||||
wallets.extend(self.trezor.list_devices());
|
|
||||||
wallets
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a list of paths to locked hardware wallets
|
|
||||||
/// This is only applicable to Trezor because Ledger only appears as
|
|
||||||
/// a device when it is unlocked
|
|
||||||
pub fn list_locked_wallets(&self) -> Result<Vec<String>, Error> {
|
|
||||||
Ok(self.trezor.list_locked_devices())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get connected wallet info.
|
|
||||||
pub fn wallet_info(&self, address: &Address) -> Option<WalletInfo> {
|
|
||||||
if let Some(info) = self.ledger.get_wallet(address) {
|
|
||||||
Some(info)
|
|
||||||
} else {
|
|
||||||
self.trezor.get_wallet(address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sign a message with the wallet (only supported by Ledger)
|
|
||||||
pub fn sign_message(&self, address: &Address, msg: &[u8]) -> Result<Signature, Error> {
|
|
||||||
if self.ledger.get_wallet(address).is_some() {
|
|
||||||
Ok(self.ledger.sign_message(address, msg)?)
|
|
||||||
} else if self.trezor.get_wallet(address).is_some() {
|
|
||||||
Err(Error::TrezorDevice(trezor::Error::NoSigningMessage))
|
|
||||||
} else {
|
|
||||||
Err(Error::KeyNotFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sign transaction data with wallet managing `address`.
|
|
||||||
pub fn sign_transaction(&self, address: &Address, t_info: &TransactionInfo, encoded_transaction: &[u8]) -> Result<Signature, Error> {
|
|
||||||
if self.ledger.get_wallet(address).is_some() {
|
|
||||||
Ok(self.ledger.sign_transaction(address, encoded_transaction)?)
|
|
||||||
} else if self.trezor.get_wallet(address).is_some() {
|
|
||||||
Ok(self.trezor.sign_transaction(address, t_info)?)
|
|
||||||
} else {
|
|
||||||
Err(Error::KeyNotFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send a pin to a device at a certain path to unlock it
|
|
||||||
/// This is only applicable to Trezor because Ledger only appears as
|
|
||||||
/// a device when it is unlocked
|
|
||||||
pub fn pin_matrix_ack(&self, path: &str, pin: &str) -> Result<bool, Error> {
|
|
||||||
self.trezor.pin_matrix_ack(path, pin).map_err(Error::TrezorDevice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for HardwareWalletManager {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
// Indicate to the USB Hotplug handler that it
|
|
||||||
// shall terminate but don't wait for it to terminate.
|
|
||||||
// If it doesn't terminate for some reason USB Hotplug events will be handled
|
|
||||||
// even if the HardwareWalletManger has been dropped
|
|
||||||
self.exiting.store(true, atomic::Ordering::Release);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hardware wallet event handler
|
|
||||||
///
|
|
||||||
/// Note, that this runs to completion and race-conditions can't occur but it can
|
|
||||||
/// stop other events for being processed with an infinite loop or similar
|
|
||||||
struct EventHandler {
|
|
||||||
ledger: Weak<ledger::Manager>,
|
|
||||||
trezor: Weak<trezor::Manager>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventHandler {
|
|
||||||
/// Trezor event handler constructor
|
|
||||||
pub fn new(ledger: Weak<ledger::Manager>, trezor: Weak<trezor::Manager>) -> Self {
|
|
||||||
Self { ledger, trezor }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_device_info(device: &libusb::Device) -> Result<(u16, u16), Error> {
|
|
||||||
let desc = device.device_descriptor()?;
|
|
||||||
Ok((desc.vendor_id(), desc.product_id()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl libusb::Hotplug for EventHandler {
|
|
||||||
fn device_arrived(&mut self, device: libusb::Device) {
|
|
||||||
// Upgrade reference to an Arc
|
|
||||||
if let (Some(ledger), Some(trezor)) = (self.ledger.upgrade(), self.trezor.upgrade()) {
|
|
||||||
// Version ID and Product ID are available
|
|
||||||
if let Ok((vid, pid)) = Self::extract_device_info(&device) {
|
|
||||||
if trezor::is_valid_trezor(vid, pid) {
|
|
||||||
if !trezor::try_connect_polling(&trezor, &MAX_POLLING_DURATION, DeviceDirection::Arrived) {
|
|
||||||
trace!(target: "hw", "Trezor device was detected but connection failed");
|
|
||||||
}
|
|
||||||
} else if ledger::is_valid_ledger(vid, pid) {
|
|
||||||
if !ledger::try_connect_polling(&ledger, &MAX_POLLING_DURATION, DeviceDirection::Arrived) {
|
|
||||||
trace!(target: "hw", "Ledger device was detected but connection failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn device_left(&mut self, device: libusb::Device) {
|
|
||||||
// Upgrade reference to an Arc
|
|
||||||
if let (Some(ledger), Some(trezor)) = (self.ledger.upgrade(), self.trezor.upgrade()) {
|
|
||||||
// Version ID and Product ID are available
|
|
||||||
if let Ok((vid, pid)) = Self::extract_device_info(&device) {
|
|
||||||
if trezor::is_valid_trezor(vid, pid) {
|
|
||||||
if !trezor::try_connect_polling(&trezor, &MAX_POLLING_DURATION, DeviceDirection::Left) {
|
|
||||||
trace!(target: "hw", "Trezor device was detected but disconnection failed");
|
|
||||||
}
|
|
||||||
} else if ledger::is_valid_ledger(vid, pid) {
|
|
||||||
if !ledger::try_connect_polling(&ledger, &MAX_POLLING_DURATION, DeviceDirection::Left) {
|
|
||||||
trace!(target: "hw", "Ledger device was detected but disconnection failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper to determine if a device is a valid HID
|
|
||||||
pub fn is_valid_hid_device(usage_page: u16, interface_number: i32) -> bool {
|
|
||||||
usage_page == HID_GLOBAL_USAGE_PAGE || interface_number == HID_USB_DEVICE_CLASS as i32
|
|
||||||
}
|
|
||||||
@@ -1,463 +0,0 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity Ethereum.
|
|
||||||
|
|
||||||
// Parity Ethereum 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.
|
|
||||||
|
|
||||||
// Parity Ethereum 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 Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//! Trezor hardware wallet module. Supports Trezor v1.
|
|
||||||
//! See <http://doc.satoshilabs.com/trezor-tech/api-protobuf.html>
|
|
||||||
//! and <https://github.com/trezor/trezor-common/blob/master/protob/protocol.md>
|
|
||||||
//! for protocol details.
|
|
||||||
|
|
||||||
use std::cmp::{min, max};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use ethereum_types::{U256, H256, Address};
|
|
||||||
use ethkey::Signature;
|
|
||||||
use hidapi;
|
|
||||||
use libusb;
|
|
||||||
use parking_lot::{Mutex, RwLock};
|
|
||||||
use protobuf::{self, Message, ProtobufEnum};
|
|
||||||
use super::{DeviceDirection, WalletInfo, TransactionInfo, KeyPath, Wallet, Device, is_valid_hid_device};
|
|
||||||
use trezor_sys::messages::{EthereumAddress, PinMatrixAck, MessageType, EthereumTxRequest, EthereumSignTx, EthereumGetAddress, EthereumTxAck, ButtonAck};
|
|
||||||
|
|
||||||
/// Trezor v1 vendor ID
|
|
||||||
const TREZOR_VID: u16 = 0x534c;
|
|
||||||
/// Trezor product IDs
|
|
||||||
const TREZOR_PIDS: [u16; 1] = [0x0001];
|
|
||||||
|
|
||||||
const ETH_DERIVATION_PATH: [u32; 5] = [0x8000_002C, 0x8000_003C, 0x8000_0000, 0, 0]; // m/44'/60'/0'/0/0
|
|
||||||
const ETC_DERIVATION_PATH: [u32; 5] = [0x8000_002C, 0x8000_003D, 0x8000_0000, 0, 0]; // m/44'/61'/0'/0/0
|
|
||||||
|
|
||||||
/// Hardware wallet error.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Ethereum wallet protocol error.
|
|
||||||
Protocol(&'static str),
|
|
||||||
/// Hidapi error.
|
|
||||||
Usb(hidapi::HidError),
|
|
||||||
/// Libusb error
|
|
||||||
LibUsb(libusb::Error),
|
|
||||||
/// Device with request key is not available.
|
|
||||||
KeyNotFound,
|
|
||||||
/// Signing has been cancelled by user.
|
|
||||||
UserCancel,
|
|
||||||
/// The Message Type given in the trezor RPC call is not something we recognize
|
|
||||||
BadMessageType,
|
|
||||||
/// Trying to read from a closed device at the given path
|
|
||||||
LockedDevice(String),
|
|
||||||
/// Signing messages are not supported by Trezor
|
|
||||||
NoSigningMessage,
|
|
||||||
/// No device arrived
|
|
||||||
NoDeviceArrived,
|
|
||||||
/// No device left
|
|
||||||
NoDeviceLeft,
|
|
||||||
/// Invalid PID or VID
|
|
||||||
InvalidDevice,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
|
||||||
match *self {
|
|
||||||
Error::Protocol(ref s) => write!(f, "Trezor protocol error: {}", s),
|
|
||||||
Error::Usb(ref e) => write!(f, "USB communication error: {}", e),
|
|
||||||
Error::LibUsb(ref e) => write!(f, "LibUSB communication error: {}", e),
|
|
||||||
Error::KeyNotFound => write!(f, "Key not found"),
|
|
||||||
Error::UserCancel => write!(f, "Operation has been cancelled"),
|
|
||||||
Error::BadMessageType => write!(f, "Bad Message Type in RPC call"),
|
|
||||||
Error::LockedDevice(ref s) => write!(f, "Device is locked, needs PIN to perform operations: {}", s),
|
|
||||||
Error::NoSigningMessage=> write!(f, "Signing messages are not supported by Trezor"),
|
|
||||||
Error::NoDeviceArrived => write!(f, "No device arrived"),
|
|
||||||
Error::NoDeviceLeft => write!(f, "No device left"),
|
|
||||||
Error::InvalidDevice => write!(f, "Device with non-supported product ID or vendor ID was detected"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<hidapi::HidError> for Error {
|
|
||||||
fn from(err: hidapi::HidError) -> Self {
|
|
||||||
Error::Usb(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<libusb::Error> for Error {
|
|
||||||
fn from(err: libusb::Error) -> Self {
|
|
||||||
Error::LibUsb(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<protobuf::ProtobufError> for Error {
|
|
||||||
fn from(_: protobuf::ProtobufError) -> Self {
|
|
||||||
Error::Protocol(&"Could not read response from Trezor Device")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trezor device manager
|
|
||||||
pub struct Manager {
|
|
||||||
usb: Arc<Mutex<hidapi::HidApi>>,
|
|
||||||
devices: RwLock<Vec<Device>>,
|
|
||||||
locked_devices: RwLock<Vec<String>>,
|
|
||||||
key_path: RwLock<KeyPath>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// HID Version used for the Trezor device
|
|
||||||
enum HidVersion {
|
|
||||||
V1,
|
|
||||||
V2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Manager {
|
|
||||||
/// Create a new instance.
|
|
||||||
pub fn new(usb: Arc<Mutex<hidapi::HidApi>>) -> Arc<Self> {
|
|
||||||
Arc::new(Self {
|
|
||||||
usb,
|
|
||||||
devices: RwLock::new(Vec::new()),
|
|
||||||
locked_devices: RwLock::new(Vec::new()),
|
|
||||||
key_path: RwLock::new(KeyPath::Ethereum),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pin_matrix_ack(&self, device_path: &str, pin: &str) -> Result<bool, Error> {
|
|
||||||
let unlocked = {
|
|
||||||
let usb = self.usb.lock();
|
|
||||||
let device = self.open_path(|| usb.open_path(&device_path))?;
|
|
||||||
let t = MessageType::MessageType_PinMatrixAck;
|
|
||||||
let mut m = PinMatrixAck::new();
|
|
||||||
m.set_pin(pin.to_string());
|
|
||||||
self.send_device_message(&device, t, &m)?;
|
|
||||||
let (resp_type, _) = self.read_device_response(&device)?;
|
|
||||||
match resp_type {
|
|
||||||
// Getting an Address back means it's unlocked, this is undocumented behavior
|
|
||||||
MessageType::MessageType_EthereumAddress => Ok(true),
|
|
||||||
// Getting anything else means we didn't unlock it
|
|
||||||
_ => Ok(false),
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.update_devices(DeviceDirection::Arrived)?;
|
|
||||||
unlocked
|
|
||||||
}
|
|
||||||
|
|
||||||
fn u256_to_be_vec(&self, val: &U256) -> Vec<u8> {
|
|
||||||
let mut buf = [0_u8; 32];
|
|
||||||
val.to_big_endian(&mut buf);
|
|
||||||
buf.iter().skip_while(|x| **x == 0).cloned().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signing_loop(&self, handle: &hidapi::HidDevice, chain_id: &Option<u64>, data: &[u8]) -> Result<Signature, Error> {
|
|
||||||
let (resp_type, bytes) = self.read_device_response(&handle)?;
|
|
||||||
match resp_type {
|
|
||||||
MessageType::MessageType_Cancel => Err(Error::UserCancel),
|
|
||||||
MessageType::MessageType_ButtonRequest => {
|
|
||||||
self.send_device_message(handle, MessageType::MessageType_ButtonAck, &ButtonAck::new())?;
|
|
||||||
// Signing loop goes back to the top and reading blocks
|
|
||||||
// for up to 5 minutes waiting for response from the device
|
|
||||||
// if the user doesn't click any button within 5 minutes you
|
|
||||||
// get a signing error and the device sort of locks up on the signing screen
|
|
||||||
self.signing_loop(handle, chain_id, data)
|
|
||||||
}
|
|
||||||
MessageType::MessageType_EthereumTxRequest => {
|
|
||||||
let resp: EthereumTxRequest = protobuf::core::parse_from_bytes(&bytes)?;
|
|
||||||
if resp.has_data_length() {
|
|
||||||
let mut msg = EthereumTxAck::new();
|
|
||||||
let len = resp.get_data_length() as usize;
|
|
||||||
msg.set_data_chunk(data[..len].to_vec());
|
|
||||||
self.send_device_message(handle, MessageType::MessageType_EthereumTxAck, &msg)?;
|
|
||||||
self.signing_loop(handle, chain_id, &data[len..])
|
|
||||||
} else {
|
|
||||||
let v = resp.get_signature_v();
|
|
||||||
let r = H256::from_slice(resp.get_signature_r());
|
|
||||||
let s = H256::from_slice(resp.get_signature_s());
|
|
||||||
if let Some(c_id) = *chain_id {
|
|
||||||
// If there is a chain_id supplied, Trezor will return a v
|
|
||||||
// part of the signature that is already adjusted for EIP-155,
|
|
||||||
// so v' = v + 2 * chain_id + 35, but code further down the
|
|
||||||
// pipeline will already do this transformation, so remove it here
|
|
||||||
let adjustment = 35 + 2 * c_id as u32;
|
|
||||||
Ok(Signature::from_rsv(&r, &s, (max(v, adjustment) - adjustment) as u8))
|
|
||||||
} else {
|
|
||||||
// If there isn't a chain_id, v will be returned as v + 27
|
|
||||||
let adjusted_v = if v < 27 { v } else { v - 27 };
|
|
||||||
Ok(Signature::from_rsv(&r, &s, adjusted_v as u8))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MessageType::MessageType_Failure => Err(Error::Protocol("Last message sent to Trezor failed")),
|
|
||||||
_ => Err(Error::Protocol("Unexpected response from Trezor device.")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_device_message(&self, device: &hidapi::HidDevice, msg_type: MessageType, msg: &Message) -> Result<usize, Error> {
|
|
||||||
let msg_id = msg_type as u16;
|
|
||||||
let mut message = msg.write_to_bytes()?;
|
|
||||||
let msg_size = message.len();
|
|
||||||
let mut data = Vec::new();
|
|
||||||
let hid_version = self.probe_hid_version(device)?;
|
|
||||||
// Magic constants
|
|
||||||
data.push(b'#');
|
|
||||||
data.push(b'#');
|
|
||||||
// Convert msg_id to BE and split into bytes
|
|
||||||
data.push(((msg_id >> 8) & 0xFF) as u8);
|
|
||||||
data.push((msg_id & 0xFF) as u8);
|
|
||||||
// Convert msg_size to BE and split into bytes
|
|
||||||
data.push(((msg_size >> 24) & 0xFF) as u8);
|
|
||||||
data.push(((msg_size >> 16) & 0xFF) as u8);
|
|
||||||
data.push(((msg_size >> 8) & 0xFF) as u8);
|
|
||||||
data.push((msg_size & 0xFF) as u8);
|
|
||||||
data.append(&mut message);
|
|
||||||
while data.len() % 63 > 0 {
|
|
||||||
data.push(0);
|
|
||||||
}
|
|
||||||
let mut total_written = 0;
|
|
||||||
for chunk in data.chunks(63) {
|
|
||||||
let mut padded_chunk = match hid_version {
|
|
||||||
HidVersion::V1 => vec![b'?'],
|
|
||||||
HidVersion::V2 => vec![0, b'?'],
|
|
||||||
};
|
|
||||||
padded_chunk.extend_from_slice(&chunk);
|
|
||||||
total_written += device.write(&padded_chunk)?;
|
|
||||||
}
|
|
||||||
Ok(total_written)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn probe_hid_version(&self, device: &hidapi::HidDevice) -> Result<HidVersion, Error> {
|
|
||||||
let mut buf2 = [0xFF_u8; 65];
|
|
||||||
buf2[0] = 0;
|
|
||||||
buf2[1] = 63;
|
|
||||||
let mut buf1 = [0xFF_u8; 64];
|
|
||||||
buf1[0] = 63;
|
|
||||||
if device.write(&buf2)? == 65 {
|
|
||||||
Ok(HidVersion::V2)
|
|
||||||
} else if device.write(&buf1)? == 64 {
|
|
||||||
Ok(HidVersion::V1)
|
|
||||||
} else {
|
|
||||||
Err(Error::Usb("Unable to determine HID Version"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_device_response(&self, device: &hidapi::HidDevice) -> Result<(MessageType, Vec<u8>), Error> {
|
|
||||||
let protocol_err = Error::Protocol(&"Unexpected wire response from Trezor Device");
|
|
||||||
let mut buf = vec![0; 64];
|
|
||||||
|
|
||||||
let first_chunk = device.read_timeout(&mut buf, 300_000)?;
|
|
||||||
if first_chunk < 9 || buf[0] != b'?' || buf[1] != b'#' || buf[2] != b'#' {
|
|
||||||
return Err(protocol_err);
|
|
||||||
}
|
|
||||||
let msg_type = MessageType::from_i32(((buf[3] as i32 & 0xFF) << 8) + (buf[4] as i32 & 0xFF)).ok_or(protocol_err)?;
|
|
||||||
let msg_size = ((buf[5] as u32 & 0xFF) << 24) + ((buf[6] as u32 & 0xFF) << 16) + ((buf[7] as u32 & 0xFF) << 8) + (buf[8] as u32 & 0xFF);
|
|
||||||
let mut data = Vec::new();
|
|
||||||
data.extend_from_slice(&buf[9..]);
|
|
||||||
while data.len() < (msg_size as usize) {
|
|
||||||
device.read_timeout(&mut buf, 10_000)?;
|
|
||||||
data.extend_from_slice(&buf[1..]);
|
|
||||||
}
|
|
||||||
Ok((msg_type, data[..msg_size as usize].to_vec()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Wallet<'a> for Manager {
|
|
||||||
type Error = Error;
|
|
||||||
type Transaction = &'a TransactionInfo;
|
|
||||||
|
|
||||||
fn sign_transaction(&self, address: &Address, t_info: Self::Transaction) ->
|
|
||||||
Result<Signature, Error> {
|
|
||||||
let usb = self.usb.lock();
|
|
||||||
let devices = self.devices.read();
|
|
||||||
let device = devices.iter().find(|d| &d.info.address == address).ok_or(Error::KeyNotFound)?;
|
|
||||||
let handle = self.open_path(|| usb.open_path(&device.path))?;
|
|
||||||
let msg_type = MessageType::MessageType_EthereumSignTx;
|
|
||||||
let mut message = EthereumSignTx::new();
|
|
||||||
match *self.key_path.read() {
|
|
||||||
KeyPath::Ethereum => message.set_address_n(ETH_DERIVATION_PATH.to_vec()),
|
|
||||||
KeyPath::EthereumClassic => message.set_address_n(ETC_DERIVATION_PATH.to_vec()),
|
|
||||||
}
|
|
||||||
message.set_nonce(self.u256_to_be_vec(&t_info.nonce));
|
|
||||||
message.set_gas_limit(self.u256_to_be_vec(&t_info.gas_limit));
|
|
||||||
message.set_gas_price(self.u256_to_be_vec(&t_info.gas_price));
|
|
||||||
message.set_value(self.u256_to_be_vec(&t_info.value));
|
|
||||||
|
|
||||||
if let Some(addr) = t_info.to {
|
|
||||||
message.set_to(addr.to_vec())
|
|
||||||
}
|
|
||||||
let first_chunk_length = min(t_info.data.len(), 1024);
|
|
||||||
let chunk = &t_info.data[0..first_chunk_length];
|
|
||||||
message.set_data_initial_chunk(chunk.to_vec());
|
|
||||||
message.set_data_length(t_info.data.len() as u32);
|
|
||||||
if let Some(c_id) = t_info.chain_id {
|
|
||||||
message.set_chain_id(c_id as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.send_device_message(&handle, msg_type, &message)?;
|
|
||||||
|
|
||||||
self.signing_loop(&handle, &t_info.chain_id, &t_info.data[first_chunk_length..])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_key_path(&self, key_path: KeyPath) {
|
|
||||||
*self.key_path.write() = key_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_devices(&self, device_direction: DeviceDirection) -> Result<usize, Error> {
|
|
||||||
let mut usb = self.usb.lock();
|
|
||||||
usb.refresh_devices();
|
|
||||||
let devices = usb.devices();
|
|
||||||
let num_prev_devices = self.devices.read().len();
|
|
||||||
|
|
||||||
let detected_devices = devices.iter()
|
|
||||||
.filter(|&d| is_valid_trezor(d.vendor_id, d.product_id) &&
|
|
||||||
is_valid_hid_device(d.usage_page, d.interface_number)
|
|
||||||
)
|
|
||||||
.fold(Vec::new(), |mut v, d| {
|
|
||||||
match self.read_device(&usb, &d) {
|
|
||||||
Ok(info) => {
|
|
||||||
trace!(target: "hw", "Found device: {:?}", info);
|
|
||||||
v.push(info);
|
|
||||||
}
|
|
||||||
Err(e) => trace!(target: "hw", "Error reading device info: {}", e),
|
|
||||||
};
|
|
||||||
v
|
|
||||||
});
|
|
||||||
|
|
||||||
let num_curr_devices = detected_devices.len();
|
|
||||||
*self.devices.write() = detected_devices;
|
|
||||||
|
|
||||||
match device_direction {
|
|
||||||
DeviceDirection::Arrived => {
|
|
||||||
if num_curr_devices > num_prev_devices {
|
|
||||||
Ok(num_curr_devices - num_prev_devices)
|
|
||||||
} else {
|
|
||||||
Err(Error::NoDeviceArrived)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DeviceDirection::Left => {
|
|
||||||
if num_prev_devices > num_curr_devices {
|
|
||||||
Ok(num_prev_devices - num_curr_devices)
|
|
||||||
} else {
|
|
||||||
Err(Error::NoDeviceLeft)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_device(&self, usb: &hidapi::HidApi, dev_info: &hidapi::HidDeviceInfo) -> Result<Device, Error> {
|
|
||||||
let handle = self.open_path(|| usb.open_path(&dev_info.path))?;
|
|
||||||
let manufacturer = dev_info.manufacturer_string.clone().unwrap_or_else(|| "Unknown".to_owned());
|
|
||||||
let name = dev_info.product_string.clone().unwrap_or_else(|| "Unknown".to_owned());
|
|
||||||
let serial = dev_info.serial_number.clone().unwrap_or_else(|| "Unknown".to_owned());
|
|
||||||
match self.get_address(&handle) {
|
|
||||||
Ok(Some(addr)) => {
|
|
||||||
Ok(Device {
|
|
||||||
path: dev_info.path.clone(),
|
|
||||||
info: WalletInfo {
|
|
||||||
name,
|
|
||||||
manufacturer,
|
|
||||||
serial,
|
|
||||||
address: addr,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Ok(None) => Err(Error::LockedDevice(dev_info.path.clone())),
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_devices(&self) -> Vec<WalletInfo> {
|
|
||||||
self.devices.read().iter().map(|d| d.info.clone()).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_locked_devices(&self) -> Vec<String> {
|
|
||||||
(*self.locked_devices.read()).clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_wallet(&self, address: &Address) -> Option<WalletInfo> {
|
|
||||||
self.devices.read().iter().find(|d| &d.info.address == address).map(|d| d.info.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_address(&self, device: &hidapi::HidDevice) -> Result<Option<Address>, Error> {
|
|
||||||
let typ = MessageType::MessageType_EthereumGetAddress;
|
|
||||||
let mut message = EthereumGetAddress::new();
|
|
||||||
match *self.key_path.read() {
|
|
||||||
KeyPath::Ethereum => message.set_address_n(ETH_DERIVATION_PATH.to_vec()),
|
|
||||||
KeyPath::EthereumClassic => message.set_address_n(ETC_DERIVATION_PATH.to_vec()),
|
|
||||||
}
|
|
||||||
message.set_show_display(false);
|
|
||||||
self.send_device_message(&device, typ, &message)?;
|
|
||||||
|
|
||||||
let (resp_type, bytes) = self.read_device_response(&device)?;
|
|
||||||
match resp_type {
|
|
||||||
MessageType::MessageType_EthereumAddress => {
|
|
||||||
let response: EthereumAddress = protobuf::core::parse_from_bytes(&bytes)?;
|
|
||||||
Ok(Some(From::from(response.get_address())))
|
|
||||||
}
|
|
||||||
_ => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_path<R, F>(&self, f: F) -> Result<R, Error>
|
|
||||||
where F: Fn() -> Result<R, &'static str>
|
|
||||||
{
|
|
||||||
f().map_err(Into::into)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Poll the device in maximum `max_polling_duration` if it doesn't succeed
|
|
||||||
pub fn try_connect_polling(trezor: &Manager, duration: &Duration, dir: DeviceDirection) -> bool {
|
|
||||||
let start_time = Instant::now();
|
|
||||||
while start_time.elapsed() <= *duration {
|
|
||||||
if let Ok(num_devices) = trezor.update_devices(dir) {
|
|
||||||
trace!(target: "hw", "{} Trezor devices {}", num_devices, dir);
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the detected device is a Trezor device by checking both the product ID and the vendor ID
|
|
||||||
pub fn is_valid_trezor(vid: u16, pid: u16) -> bool {
|
|
||||||
vid == TREZOR_VID && TREZOR_PIDS.contains(&pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore]
|
|
||||||
/// This test can't be run without an actual trezor device connected
|
|
||||||
/// (and unlocked) attached to the machine that's running the test
|
|
||||||
fn test_signature() {
|
|
||||||
use ethereum_types::Address;
|
|
||||||
use MAX_POLLING_DURATION;
|
|
||||||
use super::HardwareWalletManager;
|
|
||||||
|
|
||||||
let manager = HardwareWalletManager::new().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(try_connect_polling(&manager.trezor, &MAX_POLLING_DURATION, DeviceDirection::Arrived), true);
|
|
||||||
|
|
||||||
let addr: Address = manager.list_wallets()
|
|
||||||
.iter()
|
|
||||||
.filter(|d| d.name == "TREZOR".to_string() && d.manufacturer == "SatoshiLabs".to_string())
|
|
||||||
.nth(0)
|
|
||||||
.map(|d| d.address)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let t_info = TransactionInfo {
|
|
||||||
nonce: U256::from(1),
|
|
||||||
gas_price: U256::from(100),
|
|
||||||
gas_limit: U256::from(21_000),
|
|
||||||
to: Some(Address::from(1337)),
|
|
||||||
chain_id: Some(1),
|
|
||||||
value: U256::from(1_000_000),
|
|
||||||
data: (&[1u8; 3000]).to_vec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let signature = manager.trezor.sign_transaction(&addr, &t_info);
|
|
||||||
assert!(signature.is_ok());
|
|
||||||
}
|
|
||||||
@@ -1,73 +1,71 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//! Account Metadata
|
//! Account Metadata
|
||||||
|
|
||||||
use std::{
|
use std::{collections::HashMap, time::Instant};
|
||||||
collections::HashMap,
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ethkey::{Address, Password};
|
use ethkey::{Address, Password};
|
||||||
use serde_derive::{Serialize, Deserialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
|
||||||
/// Type of unlock.
|
/// Type of unlock.
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum Unlock {
|
pub enum Unlock {
|
||||||
/// If account is unlocked temporarily, it should be locked after first usage.
|
/// If account is unlocked temporarily, it should be locked after first usage.
|
||||||
OneTime,
|
OneTime,
|
||||||
/// Account unlocked permanently can always sign message.
|
/// Account unlocked permanently can always sign message.
|
||||||
/// Use with caution.
|
/// Use with caution.
|
||||||
Perm,
|
Perm,
|
||||||
/// Account unlocked with a timeout
|
/// Account unlocked with a timeout
|
||||||
Timed(Instant),
|
Timed(Instant),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data associated with account.
|
/// Data associated with account.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AccountData {
|
pub struct AccountData {
|
||||||
pub unlock: Unlock,
|
pub unlock: Unlock,
|
||||||
pub password: Password,
|
pub password: Password,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collected account metadata
|
/// Collected account metadata
|
||||||
#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct AccountMeta {
|
pub struct AccountMeta {
|
||||||
/// The name of the account.
|
/// The name of the account.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// The rest of the metadata of the account.
|
/// The rest of the metadata of the account.
|
||||||
pub meta: String,
|
pub meta: String,
|
||||||
/// The 128-bit Uuid of the account, if it has one (brain-wallets don't).
|
/// The 128-bit Uuid of the account, if it has one (brain-wallets don't).
|
||||||
pub uuid: Option<String>,
|
pub uuid: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountMeta {
|
impl AccountMeta {
|
||||||
/// Read a hash map of Address -> AccountMeta
|
/// Read a hash map of Address -> AccountMeta
|
||||||
pub fn read<R>(reader: R) -> Result<HashMap<Address, Self>, serde_json::Error> where
|
pub fn read<R>(reader: R) -> Result<HashMap<Address, Self>, serde_json::Error>
|
||||||
R: ::std::io::Read,
|
where
|
||||||
{
|
R: ::std::io::Read,
|
||||||
serde_json::from_reader(reader)
|
{
|
||||||
}
|
serde_json::from_reader(reader)
|
||||||
|
}
|
||||||
|
|
||||||
/// Write a hash map of Address -> AccountMeta
|
/// Write a hash map of Address -> AccountMeta
|
||||||
pub fn write<W>(m: &HashMap<Address, Self>, writer: &mut W) -> Result<(), serde_json::Error> where
|
pub fn write<W>(m: &HashMap<Address, Self>, writer: &mut W) -> Result<(), serde_json::Error>
|
||||||
W: ::std::io::Write,
|
where
|
||||||
{
|
W: ::std::io::Write,
|
||||||
serde_json::to_writer(writer, m)
|
{
|
||||||
}
|
serde_json::to_writer(writer, m)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +1,46 @@
|
|||||||
// Copyright 2015-2018 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 std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use ethstore::{Error as SSError};
|
use ethstore::Error as SSError;
|
||||||
use hardware_wallet::{Error as HardwareError};
|
|
||||||
|
|
||||||
/// Signing error
|
/// Signing error
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SignError {
|
pub enum SignError {
|
||||||
/// Account is not unlocked
|
/// Account is not unlocked
|
||||||
NotUnlocked,
|
NotUnlocked,
|
||||||
/// Account does not exist.
|
/// Account does not exist.
|
||||||
NotFound,
|
NotFound,
|
||||||
/// Low-level hardware device error.
|
/// Low-level error from store
|
||||||
Hardware(HardwareError),
|
SStore(SSError),
|
||||||
/// Low-level error from store
|
|
||||||
SStore(SSError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for SignError {
|
impl fmt::Display for SignError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
match *self {
|
match *self {
|
||||||
SignError::NotUnlocked => write!(f, "Account is locked"),
|
SignError::NotUnlocked => write!(f, "Account is locked"),
|
||||||
SignError::NotFound => write!(f, "Account does not exist"),
|
SignError::NotFound => write!(f, "Account does not exist"),
|
||||||
SignError::Hardware(ref e) => write!(f, "{}", e),
|
SignError::SStore(ref e) => write!(f, "{}", e),
|
||||||
SignError::SStore(ref e) => write!(f, "{}", e),
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<HardwareError> for SignError {
|
|
||||||
fn from(e: HardwareError) -> Self {
|
|
||||||
SignError::Hardware(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SSError> for SignError {
|
impl From<SSError> for SignError {
|
||||||
fn from(e: SSError) -> Self {
|
fn from(e: SSError) -> Self {
|
||||||
SignError::SStore(e)
|
SignError::SStore(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1350
accounts/src/lib.rs
1350
accounts/src/lib.rs
File diff suppressed because it is too large
Load Diff
@@ -1,24 +1,26 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//! Address Book Store
|
//! Address Book Store
|
||||||
|
|
||||||
use std::{fs, fmt, hash, ops};
|
use std::{
|
||||||
use std::collections::HashMap;
|
collections::HashMap,
|
||||||
use std::path::{Path, PathBuf};
|
fmt, fs, hash, ops,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
use ethkey::Address;
|
use ethkey::Address;
|
||||||
use log::{trace, warn};
|
use log::{trace, warn};
|
||||||
@@ -27,163 +29,209 @@ use crate::AccountMeta;
|
|||||||
|
|
||||||
/// Disk-backed map from Address to String. Uses JSON.
|
/// Disk-backed map from Address to String. Uses JSON.
|
||||||
pub struct AddressBook {
|
pub struct AddressBook {
|
||||||
cache: DiskMap<Address, AccountMeta>,
|
cache: DiskMap<Address, AccountMeta>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddressBook {
|
impl AddressBook {
|
||||||
/// Creates new address book at given directory.
|
/// Creates new address book at given directory.
|
||||||
pub fn new(path: &Path) -> Self {
|
pub fn new(path: &Path) -> Self {
|
||||||
let mut r = AddressBook {
|
let mut r = AddressBook {
|
||||||
cache: DiskMap::new(path, "address_book.json")
|
cache: DiskMap::new(path, "address_book.json"),
|
||||||
};
|
};
|
||||||
r.cache.revert(AccountMeta::read);
|
r.cache.revert(AccountMeta::read);
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates transient address book (no changes are saved to disk).
|
/// Creates transient address book (no changes are saved to disk).
|
||||||
pub fn transient() -> Self {
|
pub fn transient() -> Self {
|
||||||
AddressBook {
|
AddressBook {
|
||||||
cache: DiskMap::transient()
|
cache: DiskMap::transient(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the address book.
|
/// Get the address book.
|
||||||
pub fn get(&self) -> HashMap<Address, AccountMeta> {
|
pub fn get(&self) -> HashMap<Address, AccountMeta> {
|
||||||
self.cache.clone()
|
self.cache.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&self) {
|
fn save(&self) {
|
||||||
self.cache.save(AccountMeta::write)
|
self.cache.save(AccountMeta::write)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets new name for given address.
|
/// Sets new name for given address.
|
||||||
pub fn set_name(&mut self, a: Address, name: String) {
|
pub fn set_name(&mut self, a: Address, name: String) {
|
||||||
{
|
{
|
||||||
let x = self.cache.entry(a)
|
let x = self.cache.entry(a).or_insert_with(|| AccountMeta {
|
||||||
.or_insert_with(|| AccountMeta {name: Default::default(), meta: "{}".to_owned(), uuid: None});
|
name: Default::default(),
|
||||||
x.name = name;
|
meta: "{}".to_owned(),
|
||||||
}
|
uuid: None,
|
||||||
self.save();
|
});
|
||||||
}
|
x.name = name;
|
||||||
|
}
|
||||||
|
self.save();
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets new meta for given address.
|
/// Sets new meta for given address.
|
||||||
pub fn set_meta(&mut self, a: Address, meta: String) {
|
pub fn set_meta(&mut self, a: Address, meta: String) {
|
||||||
{
|
{
|
||||||
let x = self.cache.entry(a)
|
let x = self.cache.entry(a).or_insert_with(|| AccountMeta {
|
||||||
.or_insert_with(|| AccountMeta {name: "Anonymous".to_owned(), meta: Default::default(), uuid: None});
|
name: "Anonymous".to_owned(),
|
||||||
x.meta = meta;
|
meta: Default::default(),
|
||||||
}
|
uuid: None,
|
||||||
self.save();
|
});
|
||||||
}
|
x.meta = meta;
|
||||||
|
}
|
||||||
|
self.save();
|
||||||
|
}
|
||||||
|
|
||||||
/// Removes an entry
|
/// Removes an entry
|
||||||
pub fn remove(&mut self, a: Address) {
|
pub fn remove(&mut self, a: Address) {
|
||||||
self.cache.remove(&a);
|
self.cache.remove(&a);
|
||||||
self.save();
|
self.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disk-serializable HashMap
|
/// Disk-serializable HashMap
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct DiskMap<K: hash::Hash + Eq, V> {
|
struct DiskMap<K: hash::Hash + Eq, V> {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
cache: HashMap<K, V>,
|
cache: HashMap<K, V>,
|
||||||
transient: bool,
|
transient: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: hash::Hash + Eq, V> ops::Deref for DiskMap<K, V> {
|
impl<K: hash::Hash + Eq, V> ops::Deref for DiskMap<K, V> {
|
||||||
type Target = HashMap<K, V>;
|
type Target = HashMap<K, V>;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.cache
|
&self.cache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: hash::Hash + Eq, V> ops::DerefMut for DiskMap<K, V> {
|
impl<K: hash::Hash + Eq, V> ops::DerefMut for DiskMap<K, V> {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.cache
|
&mut self.cache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: hash::Hash + Eq, V> DiskMap<K, V> {
|
impl<K: hash::Hash + Eq, V> DiskMap<K, V> {
|
||||||
pub fn new(path: &Path, file_name: &str) -> Self {
|
pub fn new(path: &Path, file_name: &str) -> Self {
|
||||||
let mut path = path.to_owned();
|
let mut path = path.to_owned();
|
||||||
path.push(file_name);
|
path.push(file_name);
|
||||||
trace!(target: "diskmap", "path={:?}", path);
|
trace!(target: "diskmap", "path={:?}", path);
|
||||||
DiskMap {
|
DiskMap {
|
||||||
path: path,
|
path: path,
|
||||||
cache: HashMap::new(),
|
cache: HashMap::new(),
|
||||||
transient: false,
|
transient: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transient() -> Self {
|
pub fn transient() -> Self {
|
||||||
let mut map = DiskMap::new(&PathBuf::new(), "diskmap.json".into());
|
let mut map = DiskMap::new(&PathBuf::new(), "diskmap.json".into());
|
||||||
map.transient = true;
|
map.transient = true;
|
||||||
map
|
map
|
||||||
}
|
}
|
||||||
|
|
||||||
fn revert<F, E>(&mut self, read: F) where
|
fn revert<F, E>(&mut self, read: F)
|
||||||
F: Fn(fs::File) -> Result<HashMap<K, V>, E>,
|
where
|
||||||
E: fmt::Display,
|
F: Fn(fs::File) -> Result<HashMap<K, V>, E>,
|
||||||
{
|
E: fmt::Display,
|
||||||
if self.transient { return; }
|
{
|
||||||
trace!(target: "diskmap", "revert {:?}", self.path);
|
if self.transient {
|
||||||
let _ = fs::File::open(self.path.clone())
|
return;
|
||||||
.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)))
|
trace!(target: "diskmap", "revert {:?}", self.path);
|
||||||
.and_then(|m| {
|
let _ = fs::File::open(self.path.clone())
|
||||||
self.cache = m;
|
.map_err(|e| trace!(target: "diskmap", "Couldn't open disk map: {}", e))
|
||||||
Ok(())
|
.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
|
fn save<F, E>(&self, write: F)
|
||||||
F: Fn(&HashMap<K, V>, &mut fs::File) -> Result<(), E>,
|
where
|
||||||
E: fmt::Display,
|
F: Fn(&HashMap<K, V>, &mut fs::File) -> Result<(), E>,
|
||||||
{
|
E: fmt::Display,
|
||||||
if self.transient { return; }
|
{
|
||||||
trace!(target: "diskmap", "save {:?}", self.path);
|
if self.transient {
|
||||||
let _ = fs::File::create(self.path.clone())
|
return;
|
||||||
.map_err(|e| warn!(target: "diskmap", "Couldn't open disk map for writing: {}", e))
|
}
|
||||||
.and_then(|mut f| {
|
trace!(target: "diskmap", "save {:?}", self.path);
|
||||||
write(&self.cache, &mut f).map_err(|e| warn!(target: "diskmap", "Couldn't write to disk map: {}", e))
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::AddressBook;
|
use super::AddressBook;
|
||||||
use std::collections::HashMap;
|
use crate::account_data::AccountMeta;
|
||||||
use tempdir::TempDir;
|
use std::collections::HashMap;
|
||||||
use crate::account_data::AccountMeta;
|
use tempdir::TempDir;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_save_and_reload_address_book() {
|
fn should_save_and_reload_address_book() {
|
||||||
let tempdir = TempDir::new("").unwrap();
|
let tempdir = TempDir::new("").unwrap();
|
||||||
let mut b = AddressBook::new(tempdir.path());
|
let mut b = AddressBook::new(tempdir.path());
|
||||||
b.set_name(1.into(), "One".to_owned());
|
b.set_name(1.into(), "One".to_owned());
|
||||||
b.set_meta(1.into(), "{1:1}".to_owned());
|
b.set_meta(1.into(), "{1:1}".to_owned());
|
||||||
let b = AddressBook::new(tempdir.path());
|
let b = AddressBook::new(tempdir.path());
|
||||||
assert_eq!(b.get(), vec![
|
assert_eq!(
|
||||||
(1, AccountMeta {name: "One".to_owned(), meta: "{1:1}".to_owned(), uuid: None})
|
b.get(),
|
||||||
].into_iter().map(|(a, b)| (a.into(), b)).collect::<HashMap<_, _>>());
|
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]
|
#[test]
|
||||||
fn should_remove_address() {
|
fn should_remove_address() {
|
||||||
let tempdir = TempDir::new("").unwrap();
|
let tempdir = TempDir::new("").unwrap();
|
||||||
let mut b = AddressBook::new(tempdir.path());
|
let mut b = AddressBook::new(tempdir.path());
|
||||||
|
|
||||||
b.set_name(1.into(), "One".to_owned());
|
b.set_name(1.into(), "One".to_owned());
|
||||||
b.set_name(2.into(), "Two".to_owned());
|
b.set_name(2.into(), "Two".to_owned());
|
||||||
b.set_name(3.into(), "Three".to_owned());
|
b.set_name(3.into(), "Three".to_owned());
|
||||||
b.remove(2.into());
|
b.remove(2.into());
|
||||||
|
|
||||||
let b = AddressBook::new(tempdir.path());
|
let b = AddressBook::new(tempdir.path());
|
||||||
assert_eq!(b.get(), vec![
|
assert_eq!(
|
||||||
(1, AccountMeta{name: "One".to_owned(), meta: "{}".to_owned(), uuid: None}),
|
b.get(),
|
||||||
(3, AccountMeta{name: "Three".to_owned(), meta: "{}".to_owned(), uuid: None}),
|
vec![
|
||||||
].into_iter().map(|(a, b)| (a.into(), b)).collect::<HashMap<_, _>>());
|
(
|
||||||
}
|
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,4 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
|
description = "Parity Ethereum Chain Specification"
|
||||||
name = "chainspec"
|
name = "chainspec"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Marek Kotewicz <marek@parity.io>"]
|
authors = ["Marek Kotewicz <marek@parity.io>"]
|
||||||
|
|||||||
@@ -1,49 +1,51 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
extern crate serde_json;
|
|
||||||
extern crate ethjson;
|
extern crate ethjson;
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
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 spec: Result<Spec, _> = serde_json::from_reader(file);
|
let spec: Result<Spec, _> = serde_json::from_reader(file);
|
||||||
|
|
||||||
if let Err(err) = spec {
|
if let Err(err) = spec {
|
||||||
quit(&format!("{} {}", path, err.to_string()));
|
quit(&format!("{} {}", path, err.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("{} is valid", path);
|
println!("{} is valid", path);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["Parity <admin@parity.io>"]
|
description = "OpenEthereum CLI Signer Tool"
|
||||||
description = "Parity Cli Tool"
|
homepage = "https://github.com/openethereum/openethereum"
|
||||||
homepage = "http://parity.io"
|
|
||||||
license = "GPL-3.0"
|
license = "GPL-3.0"
|
||||||
name = "cli-signer"
|
name = "cli-signer"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
authors = ["Parity <admin@parity.io>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
ethereum-types = "0.4"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
rpassword = "1.0"
|
rpassword = "1.0"
|
||||||
parity-rpc = { path = "../rpc" }
|
parity-rpc = { path = "../rpc" }
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["Parity <admin@parity.io>"]
|
description = "OpenEthereum RPC Client"
|
||||||
description = "Parity Rpc Client"
|
homepage = "https://github.com/openethereum/openethereum"
|
||||||
homepage = "http://parity.io"
|
|
||||||
license = "GPL-3.0"
|
license = "GPL-3.0"
|
||||||
name = "parity-rpc-client"
|
name = "parity-rpc-client"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
authors = ["Parity <admin@parity.io>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
ethereum-types = "0.4"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
url = "1.2.0"
|
url = "2"
|
||||||
matches = "0.1"
|
matches = "0.1"
|
||||||
parking_lot = "0.7"
|
parking_lot = "0.9"
|
||||||
jsonrpc-core = "10.0.1"
|
jsonrpc-core = "15.0.0"
|
||||||
jsonrpc-ws-server = "10.0.1"
|
jsonrpc-ws-server = "15.0.0"
|
||||||
parity-rpc = { path = "../../rpc" }
|
parity-rpc = { path = "../../rpc" }
|
||||||
keccak-hash = "0.1"
|
keccak-hash = "0.1"
|
||||||
|
|||||||
@@ -1,347 +1,327 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::fmt::{Debug, Formatter, Error as FmtError};
|
use std::{
|
||||||
use std::io::{BufReader, BufRead};
|
collections::BTreeMap,
|
||||||
use std::sync::Arc;
|
fmt::{Debug, Error as FmtError, Formatter},
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
io::{BufRead, BufReader},
|
||||||
use std::collections::BTreeMap;
|
sync::{
|
||||||
use std::thread;
|
atomic::{AtomicUsize, Ordering},
|
||||||
use std::time;
|
Arc,
|
||||||
|
},
|
||||||
|
thread, time,
|
||||||
|
};
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use hash::keccak;
|
use hash::keccak;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use std::{fs::File, path::PathBuf};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use std::fs::File;
|
|
||||||
|
|
||||||
use ws::ws::{
|
use ws::ws::{
|
||||||
self,
|
self, Error as WsError, ErrorKind as WsErrorKind, Handler, Handshake, Message, Request,
|
||||||
Request,
|
Result as WsResult, Sender,
|
||||||
Handler,
|
|
||||||
Sender,
|
|
||||||
Handshake,
|
|
||||||
Error as WsError,
|
|
||||||
ErrorKind as WsErrorKind,
|
|
||||||
Message,
|
|
||||||
Result as WsResult,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde_json::{
|
use serde_json::{self as json, Error as JsonError, Value as JsonValue};
|
||||||
self as json,
|
|
||||||
Value as JsonValue,
|
use futures::{done, oneshot, Canceled, Complete, Future};
|
||||||
Error as JsonError,
|
|
||||||
|
use jsonrpc_core::{
|
||||||
|
request::MethodCall,
|
||||||
|
response::{Failure, Output, Success},
|
||||||
|
Error as JsonRpcError, Id, Params, Version,
|
||||||
};
|
};
|
||||||
|
|
||||||
use futures::{Canceled, Complete, Future, oneshot, done};
|
|
||||||
|
|
||||||
use jsonrpc_core::{Id, Version, Params, Error as JsonRpcError};
|
|
||||||
use jsonrpc_core::request::MethodCall;
|
|
||||||
use jsonrpc_core::response::{Output, Success, Failure};
|
|
||||||
|
|
||||||
use BoxFuture;
|
use BoxFuture;
|
||||||
|
|
||||||
/// The actual websocket connection handler, passed into the
|
/// The actual websocket connection handler, passed into the
|
||||||
/// event loop of ws-rs
|
/// event loop of ws-rs
|
||||||
struct RpcHandler {
|
struct RpcHandler {
|
||||||
pending: Pending,
|
pending: Pending,
|
||||||
// Option is used here as temporary storage until connection
|
// Option is used here as temporary storage until connection
|
||||||
// is setup and the values are moved into the new `Rpc`
|
// is setup and the values are moved into the new `Rpc`
|
||||||
complete: Option<Complete<Result<Rpc, RpcError>>>,
|
complete: Option<Complete<Result<Rpc, RpcError>>>,
|
||||||
auth_code: String,
|
auth_code: String,
|
||||||
out: Option<Sender>,
|
out: Option<Sender>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RpcHandler {
|
impl RpcHandler {
|
||||||
fn new(
|
fn new(out: Sender, auth_code: String, complete: Complete<Result<Rpc, RpcError>>) -> Self {
|
||||||
out: Sender,
|
RpcHandler {
|
||||||
auth_code: String,
|
out: Some(out),
|
||||||
complete: Complete<Result<Rpc, RpcError>>
|
auth_code: auth_code,
|
||||||
) -> Self {
|
pending: Pending::new(),
|
||||||
RpcHandler {
|
complete: Some(complete),
|
||||||
out: Some(out),
|
}
|
||||||
auth_code: auth_code,
|
}
|
||||||
pending: Pending::new(),
|
|
||||||
complete: Some(complete),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler for RpcHandler {
|
impl Handler for RpcHandler {
|
||||||
fn build_request(&mut self, url: &Url) -> WsResult<Request> {
|
fn build_request(&mut self, url: &Url) -> WsResult<Request> {
|
||||||
match Request::from_url(url) {
|
match Request::from_url(url) {
|
||||||
Ok(mut r) => {
|
Ok(mut r) => {
|
||||||
let timestamp = time::UNIX_EPOCH.elapsed().map_err(|err| {
|
let timestamp = time::UNIX_EPOCH
|
||||||
WsError::new(WsErrorKind::Internal, format!("{}", err))
|
.elapsed()
|
||||||
})?;
|
.map_err(|err| WsError::new(WsErrorKind::Internal, format!("{}", err)))?;
|
||||||
let secs = timestamp.as_secs();
|
let secs = timestamp.as_secs();
|
||||||
let hashed = keccak(format!("{}:{}", self.auth_code, secs));
|
let hashed = keccak(format!("{}:{}", self.auth_code, secs));
|
||||||
let proto = format!("{:x}_{}", hashed, secs);
|
let proto = format!("{:x}_{}", hashed, secs);
|
||||||
r.add_protocol(&proto);
|
r.add_protocol(&proto);
|
||||||
Ok(r)
|
Ok(r)
|
||||||
},
|
}
|
||||||
Err(e) =>
|
Err(e) => Err(WsError::new(WsErrorKind::Internal, format!("{}", e))),
|
||||||
Err(WsError::new(WsErrorKind::Internal, format!("{}", e))),
|
}
|
||||||
}
|
}
|
||||||
}
|
fn on_error(&mut self, err: WsError) {
|
||||||
fn on_error(&mut self, err: WsError) {
|
match self.complete.take() {
|
||||||
match self.complete.take() {
|
Some(c) => match c.send(Err(RpcError::WsError(err))) {
|
||||||
Some(c) => match c.send(Err(RpcError::WsError(err))) {
|
Ok(_) => {}
|
||||||
Ok(_) => {},
|
Err(_) => warn!(target: "rpc-client", "Unable to notify about error."),
|
||||||
Err(_) => warn!(target: "rpc-client", "Unable to notify about error."),
|
},
|
||||||
},
|
None => warn!(target: "rpc-client", "unexpected error: {}", err),
|
||||||
None => warn!(target: "rpc-client", "unexpected error: {}", err),
|
}
|
||||||
}
|
}
|
||||||
}
|
fn on_open(&mut self, _: Handshake) -> WsResult<()> {
|
||||||
fn on_open(&mut self, _: Handshake) -> WsResult<()> {
|
match (self.complete.take(), self.out.take()) {
|
||||||
match (self.complete.take(), self.out.take()) {
|
(Some(c), Some(out)) => {
|
||||||
(Some(c), Some(out)) => {
|
let res = c.send(Ok(Rpc {
|
||||||
let res = c.send(Ok(Rpc {
|
out: out,
|
||||||
out: out,
|
counter: AtomicUsize::new(0),
|
||||||
counter: AtomicUsize::new(0),
|
pending: self.pending.clone(),
|
||||||
pending: self.pending.clone(),
|
}));
|
||||||
}));
|
if let Err(_) = res {
|
||||||
if let Err(_) = res {
|
warn!(target: "rpc-client", "Unable to open a connection.")
|
||||||
warn!(target: "rpc-client", "Unable to open a connection.")
|
}
|
||||||
}
|
Ok(())
|
||||||
Ok(())
|
}
|
||||||
},
|
_ => {
|
||||||
_ => {
|
let msg = format!("on_open called twice");
|
||||||
let msg = format!("on_open called twice");
|
Err(WsError::new(WsErrorKind::Internal, msg))
|
||||||
Err(WsError::new(WsErrorKind::Internal, msg))
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
fn on_message(&mut self, msg: Message) -> WsResult<()> {
|
||||||
fn on_message(&mut self, msg: Message) -> WsResult<()> {
|
let ret: Result<JsonValue, JsonRpcError>;
|
||||||
let ret: Result<JsonValue, JsonRpcError>;
|
let response_id;
|
||||||
let response_id;
|
let string = &msg.to_string();
|
||||||
let string = &msg.to_string();
|
match json::from_str::<Output>(&string) {
|
||||||
match json::from_str::<Output>(&string) {
|
Ok(Output::Success(Success {
|
||||||
Ok(Output::Success(Success { result, id: Id::Num(id), .. })) =>
|
result,
|
||||||
{
|
id: Id::Num(id),
|
||||||
ret = Ok(result);
|
..
|
||||||
response_id = id as usize;
|
})) => {
|
||||||
}
|
ret = Ok(result);
|
||||||
Ok(Output::Failure(Failure { error, id: Id::Num(id), .. })) => {
|
response_id = id as usize;
|
||||||
ret = Err(error);
|
}
|
||||||
response_id = id as usize;
|
Ok(Output::Failure(Failure {
|
||||||
}
|
error,
|
||||||
Err(e) => {
|
id: Id::Num(id),
|
||||||
warn!(
|
..
|
||||||
target: "rpc-client",
|
})) => {
|
||||||
"recieved invalid message: {}\n {:?}",
|
ret = Err(error);
|
||||||
string,
|
response_id = id as usize;
|
||||||
e
|
}
|
||||||
);
|
Err(e) => {
|
||||||
return Ok(())
|
warn!(
|
||||||
},
|
target: "rpc-client",
|
||||||
_ => {
|
"recieved invalid message: {}\n {:?}",
|
||||||
warn!(
|
string,
|
||||||
target: "rpc-client",
|
e
|
||||||
"recieved invalid message: {}",
|
);
|
||||||
string
|
return Ok(());
|
||||||
);
|
}
|
||||||
return Ok(())
|
_ => {
|
||||||
}
|
warn!(
|
||||||
}
|
target: "rpc-client",
|
||||||
|
"recieved invalid message: {}",
|
||||||
|
string
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match self.pending.remove(response_id) {
|
match self.pending.remove(response_id) {
|
||||||
Some(c) => if let Err(_) = c.send(ret.map_err(|err| RpcError::JsonRpc(err))) {
|
Some(c) => {
|
||||||
warn!(target: "rpc-client", "Unable to send response.")
|
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: {}",
|
None => warn!(
|
||||||
response_id
|
target: "rpc-client",
|
||||||
),
|
"warning: unexpected id: {}",
|
||||||
}
|
response_id
|
||||||
Ok(())
|
),
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keeping track of issued requests to be matched up with responses
|
/// Keeping track of issued requests to be matched up with responses
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Pending(
|
struct Pending(Arc<Mutex<BTreeMap<usize, Complete<Result<JsonValue, RpcError>>>>>);
|
||||||
Arc<Mutex<BTreeMap<usize, Complete<Result<JsonValue, RpcError>>>>>
|
|
||||||
);
|
|
||||||
|
|
||||||
impl Pending {
|
impl Pending {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Pending(Arc::new(Mutex::new(BTreeMap::new())))
|
Pending(Arc::new(Mutex::new(BTreeMap::new())))
|
||||||
}
|
}
|
||||||
fn insert(&mut self, k: usize, v: Complete<Result<JsonValue, RpcError>>) {
|
fn insert(&mut self, k: usize, v: Complete<Result<JsonValue, RpcError>>) {
|
||||||
self.0.lock().insert(k, v);
|
self.0.lock().insert(k, v);
|
||||||
}
|
}
|
||||||
fn remove(
|
fn remove(&mut self, k: usize) -> Option<Complete<Result<JsonValue, RpcError>>> {
|
||||||
&mut self,
|
self.0.lock().remove(&k)
|
||||||
k: usize
|
}
|
||||||
) -> Option<Complete<Result<JsonValue, RpcError>>> {
|
|
||||||
self.0.lock().remove(&k)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_authcode(path: &PathBuf) -> Result<String, RpcError> {
|
fn get_authcode(path: &PathBuf) -> Result<String, RpcError> {
|
||||||
if let Ok(fd) = File::open(path) {
|
if let Ok(fd) = File::open(path) {
|
||||||
if let Some(Ok(line)) = BufReader::new(fd).lines().next() {
|
if let Some(Ok(line)) = BufReader::new(fd).lines().next() {
|
||||||
let mut parts = line.split(';');
|
let mut parts = line.split(';');
|
||||||
let token = parts.next();
|
let token = parts.next();
|
||||||
|
|
||||||
if let Some(code) = token {
|
if let Some(code) = token {
|
||||||
return Ok(code.into());
|
return Ok(code.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(RpcError::NoAuthCode)
|
Err(RpcError::NoAuthCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The handle to the connection
|
/// The handle to the connection
|
||||||
pub struct Rpc {
|
pub struct Rpc {
|
||||||
out: Sender,
|
out: Sender,
|
||||||
counter: AtomicUsize,
|
counter: AtomicUsize,
|
||||||
pending: Pending,
|
pending: Pending,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rpc {
|
impl Rpc {
|
||||||
/// Blocking, returns a new initialized connection or RpcError
|
/// Blocking, returns a new initialized connection or RpcError
|
||||||
pub fn new(url: &str, authpath: &PathBuf) -> Result<Self, RpcError> {
|
pub fn new(url: &str, authpath: &PathBuf) -> Result<Self, RpcError> {
|
||||||
let rpc = Self::connect(url, authpath).map(|rpc| rpc).wait()?;
|
let rpc = Self::connect(url, authpath).map(|rpc| rpc).wait()?;
|
||||||
rpc
|
rpc
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Non-blocking, returns a future
|
/// Non-blocking, returns a future
|
||||||
pub fn connect(
|
pub fn connect(url: &str, authpath: &PathBuf) -> BoxFuture<Result<Self, RpcError>, Canceled> {
|
||||||
url: &str, authpath: &PathBuf
|
let (c, p) = oneshot::<Result<Self, RpcError>>();
|
||||||
) -> BoxFuture<Result<Self, RpcError>, Canceled> {
|
match get_authcode(authpath) {
|
||||||
let (c, p) = oneshot::<Result<Self, RpcError>>();
|
Err(e) => return Box::new(done(Ok(Err(e)))),
|
||||||
match get_authcode(authpath) {
|
Ok(code) => {
|
||||||
Err(e) => return Box::new(done(Ok(Err(e)))),
|
let url = String::from(url);
|
||||||
Ok(code) => {
|
// The ws::connect takes a FnMut closure, which means c cannot
|
||||||
let url = String::from(url);
|
// be moved into it, since it's consumed on complete.
|
||||||
// The ws::connect takes a FnMut closure, which means c cannot
|
// Therefore we wrap it in an option and pick it out once.
|
||||||
// be moved into it, since it's consumed on complete.
|
let mut once = Some(c);
|
||||||
// Therefore we wrap it in an option and pick it out once.
|
thread::spawn(move || {
|
||||||
let mut once = Some(c);
|
let conn = ws::connect(url, |out| {
|
||||||
thread::spawn(move || {
|
// this will panic if the closure is called twice,
|
||||||
let conn = ws::connect(url, |out| {
|
// which it should never be.
|
||||||
// this will panic if the closure is called twice,
|
let c = once.take().expect("connection closure called only once");
|
||||||
// which it should never be.
|
RpcHandler::new(out, code.clone(), c)
|
||||||
let c = once.take()
|
});
|
||||||
.expect("connection closure called only once");
|
match conn {
|
||||||
RpcHandler::new(out, code.clone(), c)
|
Err(err) => {
|
||||||
});
|
// since ws::connect is only called once, it cannot
|
||||||
match conn {
|
// both fail and succeed.
|
||||||
Err(err) => {
|
let c = once.take().expect("connection closure called only once");
|
||||||
// since ws::connect is only called once, it cannot
|
let _ = c.send(Err(RpcError::WsError(err)));
|
||||||
// both fail and succeed.
|
}
|
||||||
let c = once.take()
|
// c will complete on the `on_open` event in the Handler
|
||||||
.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)
|
||||||
_ => ()
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
Box::new(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Non-blocking, returns a future of the request response
|
/// Non-blocking, returns a future of the request response
|
||||||
pub fn request<T>(
|
pub fn request<T>(
|
||||||
&mut self, method: &'static str, params: Vec<JsonValue>
|
&mut self,
|
||||||
) -> BoxFuture<Result<T, RpcError>, Canceled>
|
method: &'static str,
|
||||||
where T: DeserializeOwned + Send + Sized {
|
params: Vec<JsonValue>,
|
||||||
|
) -> BoxFuture<Result<T, RpcError>, Canceled>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned + Send + Sized,
|
||||||
|
{
|
||||||
|
let (c, p) = oneshot::<Result<JsonValue, RpcError>>();
|
||||||
|
|
||||||
let (c, p) = oneshot::<Result<JsonValue, RpcError>>();
|
let id = self.counter.fetch_add(1, Ordering::Relaxed);
|
||||||
|
self.pending.insert(id, c);
|
||||||
|
|
||||||
let id = self.counter.fetch_add(1, Ordering::Relaxed);
|
let request = MethodCall {
|
||||||
self.pending.insert(id, c);
|
jsonrpc: Some(Version::V2),
|
||||||
|
method: method.to_owned(),
|
||||||
|
params: Params::Array(params),
|
||||||
|
id: Id::Num(id as u64),
|
||||||
|
};
|
||||||
|
|
||||||
let request = MethodCall {
|
let serialized = json::to_string(&request).expect("request is serializable");
|
||||||
jsonrpc: Some(Version::V2),
|
let _ = self.out.send(serialized);
|
||||||
method: method.to_owned(),
|
|
||||||
params: Params::Array(params),
|
|
||||||
id: Id::Num(id as u64),
|
|
||||||
};
|
|
||||||
|
|
||||||
let serialized = json::to_string(&request)
|
Box::new(p.map(|result| match result {
|
||||||
.expect("request is serializable");
|
Ok(json) => {
|
||||||
let _ = self.out.send(serialized);
|
let t: T = json::from_value(json)?;
|
||||||
|
Ok(t)
|
||||||
Box::new(p.map(|result| {
|
}
|
||||||
match result {
|
Err(err) => Err(err),
|
||||||
Ok(json) => {
|
}))
|
||||||
let t: T = json::from_value(json)?;
|
}
|
||||||
Ok(t)
|
|
||||||
},
|
|
||||||
Err(err) => Err(err)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum RpcError {
|
pub enum RpcError {
|
||||||
WrongVersion(String),
|
WrongVersion(String),
|
||||||
ParseError(JsonError),
|
ParseError(JsonError),
|
||||||
MalformedResponse(String),
|
MalformedResponse(String),
|
||||||
JsonRpc(JsonRpcError),
|
JsonRpc(JsonRpcError),
|
||||||
WsError(WsError),
|
WsError(WsError),
|
||||||
Canceled(Canceled),
|
Canceled(Canceled),
|
||||||
UnexpectedId,
|
UnexpectedId,
|
||||||
NoAuthCode,
|
NoAuthCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for RpcError {
|
impl Debug for RpcError {
|
||||||
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
|
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
|
||||||
match *self {
|
match *self {
|
||||||
RpcError::WrongVersion(ref s)
|
RpcError::WrongVersion(ref s) => write!(f, "Expected version 2.0, got {}", s),
|
||||||
=> write!(f, "Expected version 2.0, got {}", s),
|
RpcError::ParseError(ref err) => write!(f, "ParseError: {}", err),
|
||||||
RpcError::ParseError(ref err)
|
RpcError::MalformedResponse(ref s) => write!(f, "Malformed response: {}", s),
|
||||||
=> write!(f, "ParseError: {}", err),
|
RpcError::JsonRpc(ref json) => write!(f, "JsonRpc error: {:?}", json),
|
||||||
RpcError::MalformedResponse(ref s)
|
RpcError::WsError(ref s) => write!(f, "Websocket error: {}", s),
|
||||||
=> write!(f, "Malformed response: {}", s),
|
RpcError::Canceled(ref s) => write!(f, "Futures error: {:?}", s),
|
||||||
RpcError::JsonRpc(ref json)
|
RpcError::UnexpectedId => write!(f, "Unexpected response id"),
|
||||||
=> write!(f, "JsonRpc error: {:?}", json),
|
RpcError::NoAuthCode => write!(f, "No authcodes available"),
|
||||||
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 {
|
impl From<JsonError> for RpcError {
|
||||||
fn from(err: JsonError) -> RpcError {
|
fn from(err: JsonError) -> RpcError {
|
||||||
RpcError::ParseError(err)
|
RpcError::ParseError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<WsError> for RpcError {
|
impl From<WsError> for RpcError {
|
||||||
fn from(err: WsError) -> RpcError {
|
fn from(err: WsError) -> RpcError {
|
||||||
RpcError::WsError(err)
|
RpcError::WsError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Canceled> for RpcError {
|
impl From<Canceled> for RpcError {
|
||||||
fn from(err: Canceled) -> RpcError {
|
fn from(err: Canceled) -> RpcError {
|
||||||
RpcError::Canceled(err)
|
RpcError::Canceled(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,32 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod signer_client;
|
pub mod signer_client;
|
||||||
|
|
||||||
|
extern crate ethereum_types;
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
extern crate jsonrpc_core;
|
extern crate jsonrpc_core;
|
||||||
extern crate jsonrpc_ws_server as ws;
|
extern crate jsonrpc_ws_server as ws;
|
||||||
|
extern crate keccak_hash as hash;
|
||||||
extern crate parity_rpc as rpc;
|
extern crate parity_rpc as rpc;
|
||||||
extern crate parking_lot;
|
extern crate parking_lot;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
extern crate url;
|
extern crate url;
|
||||||
extern crate keccak_hash as hash;
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
@@ -35,56 +36,55 @@ extern crate log;
|
|||||||
extern crate matches;
|
extern crate matches;
|
||||||
|
|
||||||
/// Boxed future response.
|
/// Boxed future response.
|
||||||
pub type BoxFuture<T, E> = Box<futures::Future<Item=T, Error=E> + Send>;
|
pub type BoxFuture<T, E> = Box<dyn futures::Future<Item = T, Error = E> + Send>;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use futures::Future;
|
use client::{Rpc, RpcError};
|
||||||
use std::path::PathBuf;
|
use futures::Future;
|
||||||
use client::{Rpc, RpcError};
|
use rpc;
|
||||||
use rpc;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_connection_refused() {
|
fn test_connection_refused() {
|
||||||
let (_srv, port, mut authcodes) = rpc::tests::ws::serve();
|
let (_srv, port, mut authcodes) = rpc::tests::ws::serve();
|
||||||
|
|
||||||
let _ = authcodes.generate_new();
|
let _ = authcodes.generate_new();
|
||||||
authcodes.to_file(&authcodes.path).unwrap();
|
authcodes.to_file(&authcodes.path).unwrap();
|
||||||
|
|
||||||
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port - 1),
|
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port - 1), &authcodes.path);
|
||||||
&authcodes.path);
|
|
||||||
|
|
||||||
let _ = connect.map(|conn| {
|
let _ = connect
|
||||||
assert!(matches!(&conn, &Err(RpcError::WsError(_))));
|
.map(|conn| {
|
||||||
}).wait();
|
assert!(matches!(&conn, &Err(RpcError::WsError(_))));
|
||||||
}
|
})
|
||||||
|
.wait();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_authcode_fail() {
|
fn test_authcode_fail() {
|
||||||
let (_srv, port, _) = rpc::tests::ws::serve();
|
let (_srv, port, _) = rpc::tests::ws::serve();
|
||||||
let path = PathBuf::from("nonexist");
|
let path = PathBuf::from("nonexist");
|
||||||
|
|
||||||
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port), &path);
|
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port), &path);
|
||||||
|
|
||||||
let _ = connect.map(|conn| {
|
let _ = connect
|
||||||
assert!(matches!(&conn, &Err(RpcError::NoAuthCode)));
|
.map(|conn| {
|
||||||
}).wait();
|
assert!(matches!(&conn, &Err(RpcError::NoAuthCode)));
|
||||||
}
|
})
|
||||||
|
.wait();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_authcode_correct() {
|
fn test_authcode_correct() {
|
||||||
let (_srv, port, mut authcodes) = rpc::tests::ws::serve();
|
let (_srv, port, mut authcodes) = rpc::tests::ws::serve();
|
||||||
|
|
||||||
let _ = authcodes.generate_new();
|
let _ = authcodes.generate_new();
|
||||||
authcodes.to_file(&authcodes.path).unwrap();
|
authcodes.to_file(&authcodes.path).unwrap();
|
||||||
|
|
||||||
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port),
|
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port), &authcodes.path);
|
||||||
&authcodes.path);
|
|
||||||
|
|
||||||
let _ = connect.map(|conn| {
|
|
||||||
assert!(conn.is_ok())
|
|
||||||
}).wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let _ = connect.map(|conn| assert!(conn.is_ok())).wait();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +1,76 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use client::{Rpc, RpcError};
|
use client::{Rpc, RpcError};
|
||||||
use rpc::signer::{ConfirmationRequest, TransactionModification, U256, TransactionCondition};
|
use ethereum_types::U256;
|
||||||
|
use futures::Canceled;
|
||||||
|
use rpc::signer::{ConfirmationRequest, TransactionCondition, TransactionModification};
|
||||||
use serde;
|
use serde;
|
||||||
use serde_json::{Value as JsonValue, to_value};
|
use serde_json::{to_value, Value as JsonValue};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use futures::{Canceled};
|
use BoxFuture;
|
||||||
use {BoxFuture};
|
|
||||||
|
|
||||||
pub struct SignerRpc {
|
pub struct SignerRpc {
|
||||||
rpc: Rpc,
|
rpc: Rpc,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignerRpc {
|
impl SignerRpc {
|
||||||
pub fn new(url: &str, authfile: &PathBuf) -> Result<Self, RpcError> {
|
pub fn new(url: &str, authfile: &PathBuf) -> Result<Self, RpcError> {
|
||||||
Ok(SignerRpc { rpc: Rpc::new(&url, authfile)? })
|
Ok(SignerRpc {
|
||||||
}
|
rpc: Rpc::new(&url, authfile)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn requests_to_confirm(&mut self) -> BoxFuture<Result<Vec<ConfirmationRequest>, RpcError>, Canceled> {
|
pub fn requests_to_confirm(
|
||||||
self.rpc.request("signer_requestsToConfirm", vec![])
|
&mut self,
|
||||||
}
|
) -> BoxFuture<Result<Vec<ConfirmationRequest>, RpcError>, Canceled> {
|
||||||
|
self.rpc.request("signer_requestsToConfirm", vec![])
|
||||||
|
}
|
||||||
|
|
||||||
pub fn confirm_request(
|
pub fn confirm_request(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: U256,
|
id: U256,
|
||||||
new_gas: Option<U256>,
|
new_gas: Option<U256>,
|
||||||
new_gas_price: Option<U256>,
|
new_gas_price: Option<U256>,
|
||||||
new_condition: Option<Option<TransactionCondition>>,
|
new_condition: Option<Option<TransactionCondition>>,
|
||||||
pwd: &str
|
pwd: &str,
|
||||||
) -> BoxFuture<Result<U256, RpcError>, Canceled> {
|
) -> BoxFuture<Result<U256, RpcError>, Canceled> {
|
||||||
self.rpc.request("signer_confirmRequest", vec![
|
self.rpc.request(
|
||||||
Self::to_value(&format!("{:#x}", id)),
|
"signer_confirmRequest",
|
||||||
Self::to_value(&TransactionModification { sender: None, gas_price: new_gas_price, gas: new_gas, condition: new_condition }),
|
vec![
|
||||||
Self::to_value(&pwd),
|
Self::to_value(&format!("{:#x}", id)),
|
||||||
])
|
Self::to_value(&TransactionModification {
|
||||||
}
|
sender: None,
|
||||||
|
gas_price: new_gas_price,
|
||||||
|
gas: new_gas,
|
||||||
|
condition: new_condition,
|
||||||
|
}),
|
||||||
|
Self::to_value(&pwd),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reject_request(&mut self, id: U256) -> BoxFuture<Result<bool, RpcError>, Canceled> {
|
pub fn reject_request(&mut self, id: U256) -> BoxFuture<Result<bool, RpcError>, Canceled> {
|
||||||
self.rpc.request("signer_rejectRequest", vec![
|
self.rpc.request(
|
||||||
JsonValue::String(format!("{:#x}", id))
|
"signer_rejectRequest",
|
||||||
])
|
vec![JsonValue::String(format!("{:#x}", id))],
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn to_value<T: serde::Serialize>(v: &T) -> JsonValue {
|
fn to_value<T: serde::Serialize>(v: &T) -> JsonValue {
|
||||||
to_value(v).expect("Our types are always serializable; qed")
|
to_value(v).expect("Our types are always serializable; qed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,195 +1,162 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity Ethereum.
|
// This file is part of OpenEthereum.
|
||||||
|
|
||||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
extern crate ethereum_types;
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
extern crate rpassword;
|
extern crate rpassword;
|
||||||
|
|
||||||
extern crate parity_rpc as rpc;
|
extern crate parity_rpc as rpc;
|
||||||
extern crate parity_rpc_client as client;
|
extern crate parity_rpc_client as client;
|
||||||
|
|
||||||
use rpc::signer::{U256, ConfirmationRequest};
|
|
||||||
use client::signer_client::SignerRpc;
|
use client::signer_client::SignerRpc;
|
||||||
use std::io::{Write, BufRead, BufReader, stdout, stdin};
|
use ethereum_types::U256;
|
||||||
use std::path::PathBuf;
|
use rpc::signer::ConfirmationRequest;
|
||||||
use std::fs::File;
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{stdin, stdout, BufRead, BufReader, Write},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
|
|
||||||
fn sign_interactive(
|
fn sign_interactive(signer: &mut SignerRpc, password: &str, request: ConfirmationRequest) {
|
||||||
signer: &mut SignerRpc,
|
print!(
|
||||||
password: &str,
|
"\n{}\nSign this transaction? (y)es/(N)o/(r)eject: ",
|
||||||
request: ConfirmationRequest
|
request
|
||||||
) {
|
);
|
||||||
print!("\n{}\nSign this transaction? (y)es/(N)o/(r)eject: ", request);
|
let _ = stdout().flush();
|
||||||
let _ = stdout().flush();
|
match BufReader::new(stdin()).lines().next() {
|
||||||
match BufReader::new(stdin()).lines().next() {
|
Some(Ok(line)) => match line.to_lowercase().chars().nth(0) {
|
||||||
Some(Ok(line)) => {
|
Some('y') => match sign_transaction(signer, request.id, password) {
|
||||||
match line.to_lowercase().chars().nth(0) {
|
Ok(s) | Err(s) => println!("{}", s),
|
||||||
Some('y') => {
|
},
|
||||||
match sign_transaction(signer, request.id, password) {
|
Some('r') => match reject_transaction(signer, request.id) {
|
||||||
Ok(s) | Err(s) => println!("{}", s),
|
Ok(s) | Err(s) => println!("{}", s),
|
||||||
}
|
},
|
||||||
}
|
_ => (),
|
||||||
Some('r') => {
|
},
|
||||||
match reject_transaction(signer, request.id) {
|
_ => println!("Could not read from stdin"),
|
||||||
Ok(s) | Err(s) => println!("{}", s),
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => println!("Could not read from stdin")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign_transactions(
|
fn sign_transactions(signer: &mut SignerRpc, password: String) -> Result<String, String> {
|
||||||
signer: &mut SignerRpc,
|
signer
|
||||||
password: String
|
.requests_to_confirm()
|
||||||
) -> Result<String, String> {
|
.map(|reqs| match reqs {
|
||||||
signer.requests_to_confirm().map(|reqs| {
|
Ok(ref reqs) if reqs.is_empty() => Ok("No transactions in signing queue".to_owned()),
|
||||||
match reqs {
|
Ok(reqs) => {
|
||||||
Ok(ref reqs) if reqs.is_empty() => {
|
for r in reqs {
|
||||||
Ok("No transactions in signing queue".to_owned())
|
sign_interactive(signer, &password, r)
|
||||||
}
|
}
|
||||||
Ok(reqs) => {
|
Ok("".to_owned())
|
||||||
for r in reqs {
|
}
|
||||||
sign_interactive(signer, &password, r)
|
Err(err) => Err(format!("error: {:?}", err)),
|
||||||
}
|
})
|
||||||
Ok("".to_owned())
|
.map_err(|err| format!("{:?}", err))
|
||||||
}
|
.wait()?
|
||||||
Err(err) => {
|
|
||||||
Err(format!("error: {:?}", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).map_err(|err| {
|
|
||||||
format!("{:?}", err)
|
|
||||||
}).wait()?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_transactions(signer: &mut SignerRpc) -> Result<String, String> {
|
fn list_transactions(signer: &mut SignerRpc) -> Result<String, String> {
|
||||||
signer.requests_to_confirm().map(|reqs| {
|
signer
|
||||||
match reqs {
|
.requests_to_confirm()
|
||||||
Ok(ref reqs) if reqs.is_empty() => {
|
.map(|reqs| match reqs {
|
||||||
Ok("No transactions in signing queue".to_owned())
|
Ok(ref reqs) if reqs.is_empty() => Ok("No transactions in signing queue".to_owned()),
|
||||||
}
|
Ok(ref reqs) => Ok(format!(
|
||||||
Ok(ref reqs) => {
|
"Transaction queue:\n{}",
|
||||||
Ok(format!("Transaction queue:\n{}", reqs
|
reqs.iter()
|
||||||
.iter()
|
.map(|r| format!("{}", r))
|
||||||
.map(|r| format!("{}", r))
|
.collect::<Vec<String>>()
|
||||||
.collect::<Vec<String>>()
|
.join("\n")
|
||||||
.join("\n")))
|
)),
|
||||||
}
|
Err(err) => Err(format!("error: {:?}", err)),
|
||||||
Err(err) => {
|
})
|
||||||
Err(format!("error: {:?}", err))
|
.map_err(|err| format!("{:?}", err))
|
||||||
}
|
.wait()?
|
||||||
}
|
|
||||||
}).map_err(|err| {
|
|
||||||
format!("{:?}", err)
|
|
||||||
}).wait()?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign_transaction(
|
fn sign_transaction(signer: &mut SignerRpc, id: U256, password: &str) -> Result<String, String> {
|
||||||
signer: &mut SignerRpc, id: U256, password: &str
|
signer
|
||||||
) -> Result<String, String> {
|
.confirm_request(id, None, None, None, password)
|
||||||
signer.confirm_request(id, None, None, None, password).map(|res| {
|
.map(|res| match res {
|
||||||
match res {
|
Ok(u) => Ok(format!("Signed transaction id: {:#x}", u)),
|
||||||
Ok(u) => Ok(format!("Signed transaction id: {:#x}", u)),
|
Err(e) => Err(format!("{:?}", e)),
|
||||||
Err(e) => Err(format!("{:?}", e)),
|
})
|
||||||
}
|
.map_err(|err| format!("{:?}", err))
|
||||||
}).map_err(|err| {
|
.wait()?
|
||||||
format!("{:?}", err)
|
|
||||||
}).wait()?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reject_transaction(
|
fn reject_transaction(signer: &mut SignerRpc, id: U256) -> Result<String, String> {
|
||||||
signer: &mut SignerRpc, id: U256) -> Result<String, String>
|
signer
|
||||||
{
|
.reject_request(id)
|
||||||
signer.reject_request(id).map(|res| {
|
.map(|res| match res {
|
||||||
match res {
|
Ok(true) => Ok(format!("Rejected transaction id {:#x}", id)),
|
||||||
Ok(true) => Ok(format!("Rejected transaction id {:#x}", id)),
|
Ok(false) => Err(format!("No such request")),
|
||||||
Ok(false) => Err(format!("No such request")),
|
Err(e) => Err(format!("{:?}", e)),
|
||||||
Err(e) => Err(format!("{:?}", e)),
|
})
|
||||||
}
|
.map_err(|err| format!("{:?}", err))
|
||||||
}).map_err(|err| {
|
.wait()?
|
||||||
format!("{:?}", err)
|
|
||||||
}).wait()?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// cmds
|
// cmds
|
||||||
|
|
||||||
pub fn signer_list(
|
pub fn signer_list(signerport: u16, authfile: PathBuf) -> Result<String, String> {
|
||||||
signerport: u16, authfile: PathBuf
|
let addr = &format!("ws://127.0.0.1:{}", signerport);
|
||||||
) -> Result<String, String> {
|
let mut signer = SignerRpc::new(addr, &authfile).map_err(|err| format!("{:?}", err))?;
|
||||||
let addr = &format!("ws://127.0.0.1:{}", signerport);
|
list_transactions(&mut signer)
|
||||||
let mut signer = SignerRpc::new(addr, &authfile).map_err(|err| {
|
|
||||||
format!("{:?}", err)
|
|
||||||
})?;
|
|
||||||
list_transactions(&mut signer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn signer_reject(
|
pub fn signer_reject(
|
||||||
id: Option<usize>, signerport: u16, authfile: PathBuf
|
id: Option<usize>,
|
||||||
|
signerport: u16,
|
||||||
|
authfile: PathBuf,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let id = id.ok_or(format!("id required for signer reject"))?;
|
let id = id.ok_or(format!("id required for signer reject"))?;
|
||||||
let addr = &format!("ws://127.0.0.1:{}", signerport);
|
let addr = &format!("ws://127.0.0.1:{}", signerport);
|
||||||
let mut signer = SignerRpc::new(addr, &authfile).map_err(|err| {
|
let mut signer = SignerRpc::new(addr, &authfile).map_err(|err| format!("{:?}", err))?;
|
||||||
format!("{:?}", err)
|
reject_transaction(&mut signer, U256::from(id))
|
||||||
})?;
|
|
||||||
reject_transaction(&mut signer, U256::from(id))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn signer_sign(
|
pub fn signer_sign(
|
||||||
id: Option<usize>,
|
id: Option<usize>,
|
||||||
pwfile: Option<PathBuf>,
|
pwfile: Option<PathBuf>,
|
||||||
signerport: u16,
|
signerport: u16,
|
||||||
authfile: PathBuf
|
authfile: PathBuf,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let password;
|
let password;
|
||||||
match pwfile {
|
match pwfile {
|
||||||
Some(pwfile) => {
|
Some(pwfile) => match File::open(pwfile) {
|
||||||
match File::open(pwfile) {
|
Ok(fd) => match BufReader::new(fd).lines().next() {
|
||||||
Ok(fd) => {
|
Some(Ok(line)) => password = line,
|
||||||
match BufReader::new(fd).lines().next() {
|
_ => return Err(format!("No password in file")),
|
||||||
Some(Ok(line)) => password = line,
|
},
|
||||||
_ => return Err(format!("No password in file"))
|
Err(e) => return Err(format!("Could not open password file: {}", e)),
|
||||||
}
|
},
|
||||||
},
|
None => {
|
||||||
Err(e) =>
|
password = match rpassword::prompt_password_stdout("Password: ") {
|
||||||
return Err(format!("Could not open password file: {}", e))
|
Ok(p) => p,
|
||||||
}
|
Err(e) => return Err(format!("{}", e)),
|
||||||
}
|
}
|
||||||
None => {
|
}
|
||||||
password = match rpassword::prompt_password_stdout("Password: ") {
|
}
|
||||||
Ok(p) => p,
|
|
||||||
Err(e) => return Err(format!("{}", e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let addr = &format!("ws://127.0.0.1:{}", signerport);
|
let addr = &format!("ws://127.0.0.1:{}", signerport);
|
||||||
let mut signer = SignerRpc::new(addr, &authfile).map_err(|err| {
|
let mut signer = SignerRpc::new(addr, &authfile).map_err(|err| format!("{:?}", err))?;
|
||||||
format!("{:?}", err)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
match id {
|
match id {
|
||||||
Some(id) => {
|
Some(id) => sign_transaction(&mut signer, U256::from(id), &password),
|
||||||
sign_transaction(&mut signer, U256::from(id), &password)
|
None => sign_transactions(&mut signer, password),
|
||||||
},
|
}
|
||||||
None => {
|
|
||||||
sign_transactions(&mut signer, password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ Parity 1.4.10 is a first stable release of 1.4.x series. It includes a few minor
|
|||||||
|
|
||||||
- Gas_limit for blocks, mined by Parity will be divisible by 37 (#4154) [#4179](https://github.com/paritytech/parity/pull/4179)
|
- Gas_limit for blocks, mined by Parity will be divisible by 37 (#4154) [#4179](https://github.com/paritytech/parity/pull/4179)
|
||||||
- gas_limit for new blocks will divide evenly by 13
|
- gas_limit for new blocks will divide evenly by 13
|
||||||
- increased PARITY_GAS_LIMIT_DETERMINANT to 37
|
- increased GAS_LIMIT_DETERMINANT to 37
|
||||||
- separate method for marking mined block
|
- separate method for marking mined block
|
||||||
- debug_asserts(gas_limit within protocol range)
|
- debug_asserts(gas_limit within protocol range)
|
||||||
- round_block_gas_limit method is now static
|
- round_block_gas_limit method is now static
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ Full changes:
|
|||||||
- Prevent duplicate incoming connections ([#4180](https://github.com/paritytech/parity/pull/4180))
|
- Prevent duplicate incoming connections ([#4180](https://github.com/paritytech/parity/pull/4180))
|
||||||
- Gas_limit for blocks, mined by Parity will be divisible by 37 ([#4154](https://github.com/paritytech/parity/pull/4154)) [#4176](https://github.com/paritytech/parity/pull/4176)
|
- Gas_limit for blocks, mined by Parity will be divisible by 37 ([#4154](https://github.com/paritytech/parity/pull/4154)) [#4176](https://github.com/paritytech/parity/pull/4176)
|
||||||
- gas_limit for new blocks will divide evenly by 13
|
- gas_limit for new blocks will divide evenly by 13
|
||||||
- increased PARITY_GAS_LIMIT_DETERMINANT to 37
|
- increased GAS_LIMIT_DETERMINANT to 37
|
||||||
- separate method for marking mined block
|
- separate method for marking mined block
|
||||||
- debug_asserts(gas_limit within protocol range)
|
- debug_asserts(gas_limit within protocol range)
|
||||||
- round_block_gas_limit method is now static
|
- round_block_gas_limit method is now static
|
||||||
|
|||||||
@@ -1,3 +1,78 @@
|
|||||||
|
Note: Parity Ethereum 2.2 reached End-of-Life on 2019-02-25 (EOL).
|
||||||
|
|
||||||
|
## Parity-Ethereum [v2.2.11](https://github.com/paritytech/parity-ethereum/releases/tag/v2.2.11) (2019-02-21)
|
||||||
|
|
||||||
|
Parity-Ethereum 2.2.11-stable is a maintenance release that fixes snap and docker installations.
|
||||||
|
|
||||||
|
The full list of included changes:
|
||||||
|
|
||||||
|
- Stable: snap: release untagged versions from branches to the candidate ([#10357](https://github.com/paritytech/parity-ethereum/pull/10357)) ([#10372](https://github.com/paritytech/parity-ethereum/pull/10372))
|
||||||
|
- Snap: release untagged versions from branches to the candidate snap channel ([#10357](https://github.com/paritytech/parity-ethereum/pull/10357))
|
||||||
|
- Snap: add the removable-media plug ([#10377](https://github.com/paritytech/parity-ethereum/pull/10377))
|
||||||
|
- Exchanged old(azure) bootnodes with new(ovh) ones ([#10309](https://github.com/paritytech/parity-ethereum/pull/10309))
|
||||||
|
- Stable Backports ([#10353](https://github.com/paritytech/parity-ethereum/pull/10353))
|
||||||
|
- Version: bump stable to 2.2.11
|
||||||
|
- Snap: prefix version and populate candidate channel ([#10343](https://github.com/paritytech/parity-ethereum/pull/10343))
|
||||||
|
- Snap: populate candidate releases with beta snaps to avoid stale channel
|
||||||
|
- Snap: prefix version with v*
|
||||||
|
- No volumes are needed, just run -v volume:/path/in/the/container ([#10345](https://github.com/paritytech/parity-ethereum/pull/10345))
|
||||||
|
|
||||||
|
## Parity-Ethereum [v2.2.10](https://github.com/paritytech/parity-ethereum/releases/tag/v2.2.10) (2019-02-13)
|
||||||
|
|
||||||
|
Parity-Ethereum 2.2.10-stable is a security-relevant release. A bug in the JSONRPC-deserialization module can cause crashes of all versions of Parity Ethereum nodes if an attacker is able to submit a specially-crafted RPC to certain publicly available endpoints.
|
||||||
|
|
||||||
|
- https://www.parity.io/new-parity-ethereum-update-fixes-several-rpc-vulnerabilities/
|
||||||
|
|
||||||
|
The full list of included changes:
|
||||||
|
|
||||||
|
- Additional error for invalid gas ([#10327](https://github.com/paritytech/parity-ethereum/pull/10327)) ([#10329](https://github.com/paritytech/parity-ethereum/pull/10329))
|
||||||
|
- Backports for Stable 2.2.10 ([#10332](https://github.com/paritytech/parity-ethereum/pull/10332))
|
||||||
|
- fix(docker-aarch64) : cross-compile config ([#9798](https://github.com/paritytech/parity-ethereum/pull/9798))
|
||||||
|
- import rpc transactions sequentially ([#10051](https://github.com/paritytech/parity-ethereum/pull/10051))
|
||||||
|
- fix(docker): fix not receives SIGINT ([#10059](https://github.com/paritytech/parity-ethereum/pull/10059))
|
||||||
|
- snap: official image / test ([#10168](https://github.com/paritytech/parity-ethereum/pull/10168))
|
||||||
|
- perform stripping during build ([#10208](https://github.com/paritytech/parity-ethereum/pull/10208))
|
||||||
|
- Additional tests for uint/hash/bytes deserialization. ([#10279](https://github.com/paritytech/parity-ethereum/pull/10279))
|
||||||
|
- Don't run the CPP example on CI ([#10285](https://github.com/paritytech/parity-ethereum/pull/10285))
|
||||||
|
- CI optimizations ([#10297](https://github.com/paritytech/parity-ethereum/pull/10297))
|
||||||
|
- fix publish job ([#10317](https://github.com/paritytech/parity-ethereum/pull/10317))
|
||||||
|
- Add Statetest support for Constantinople Fix ([#10323](https://github.com/paritytech/parity-ethereum/pull/10323))
|
||||||
|
- Add helper for Timestamp overflows ([#10330](https://github.com/paritytech/parity-ethereum/pull/10330))
|
||||||
|
- Don't add discovery initiators to the node table ([#10305](https://github.com/paritytech/parity-ethereum/pull/10305))
|
||||||
|
- change docker image based on debian instead of ubuntu due to the chan ([#10336](https://github.com/paritytech/parity-ethereum/pull/10336))
|
||||||
|
- role back docker build image and docker deploy image to ubuntu:xenial based ([#10338](https://github.com/paritytech/parity-ethereum/pull/10338))
|
||||||
|
|
||||||
|
## Parity-Ethereum [v2.2.9](https://github.com/paritytech/parity-ethereum/releases/tag/v2.2.9) (2019-02-03)
|
||||||
|
|
||||||
|
Parity-Ethereum 2.2.9-stable is a security-relevant release. A bug in the JSONRPC-deserialization module can cause crashes of all versions of Parity Ethereum nodes if an attacker is able to submit a specially-crafted RPC to certain publicly available endpoints.
|
||||||
|
|
||||||
|
- https://www.parity.io/security-alert-parity-ethereum-03-02/
|
||||||
|
|
||||||
|
The full list of included changes:
|
||||||
|
|
||||||
|
- Additional tests for uint deserialization. ([#10279](https://github.com/paritytech/parity-ethereum/pull/10279)) ([#10281](https://github.com/paritytech/parity-ethereum/pull/10281))
|
||||||
|
- Version: bump stable to 2.2.9 ([#10282](https://github.com/paritytech/parity-ethereum/pull/10282))
|
||||||
|
|
||||||
|
## Parity-Ethereum [v2.2.8](https://github.com/paritytech/parity-ethereum/releases/tag/v2.2.8) (2019-02-01)
|
||||||
|
|
||||||
|
Parity-Ethereum 2.2.8-stable is a consensus-relevant release that enables _St. Petersfork_ on:
|
||||||
|
|
||||||
|
- Ethereum Block `7280000` (along with Constantinople)
|
||||||
|
- Kovan Block `10255201`
|
||||||
|
- Ropsten Block `4939394`
|
||||||
|
- POA Sokol Block `7026400`
|
||||||
|
|
||||||
|
In addition to this, Constantinople is cancelled for the POA Core network. Upgrading is mandatory for clients on any of these chains.
|
||||||
|
|
||||||
|
The full list of included changes:
|
||||||
|
|
||||||
|
- Backports for stable 2.2.8 ([#10224](https://github.com/paritytech/parity-ethereum/pull/10224))
|
||||||
|
- Update for Android cross-compilation. ([#10180](https://github.com/paritytech/parity-ethereum/pull/10180))
|
||||||
|
- Cancel Constantinople HF on POA Core ([#10198](https://github.com/paritytech/parity-ethereum/pull/10198))
|
||||||
|
- Add EIP-1283 disable transition ([#10214](https://github.com/paritytech/parity-ethereum/pull/10214))
|
||||||
|
- Enable St-Peters-Fork ("Constantinople Fix") ([#10223](https://github.com/paritytech/parity-ethereum/pull/10223))
|
||||||
|
- Stable: Macos heapsize force jemalloc ([#10234](https://github.com/paritytech/parity-ethereum/pull/10234)) ([#10258](https://github.com/paritytech/parity-ethereum/pull/10258))
|
||||||
|
|
||||||
## Parity-Ethereum [v2.2.7](https://github.com/paritytech/parity-ethereum/releases/tag/v2.2.7) (2019-01-15)
|
## Parity-Ethereum [v2.2.7](https://github.com/paritytech/parity-ethereum/releases/tag/v2.2.7) (2019-01-15)
|
||||||
|
|
||||||
Parity-Ethereum 2.2.7-stable is a consensus-relevant security release that reverts Constantinople on the Ethereum network. Upgrading is mandatory for Ethereum, and strongly recommended for other networks.
|
Parity-Ethereum 2.2.7-stable is a consensus-relevant security release that reverts Constantinople on the Ethereum network. Upgrading is mandatory for Ethereum, and strongly recommended for other networks.
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user