├── .coveragerc
├── .github
├── CODEOWNERS
├── actions
│ └── update_blst.yml
├── pull_request_template.md
└── workflows
│ ├── compile_blst.yml
│ └── publish.yml
├── .gitignore
├── .gitmodules
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── MANIFEST.in
├── README.md
├── blst-lib
├── aarch64
│ └── libblst.a
├── blst.h
├── blst.hpp
├── blst_aux.h
├── blst_wrap.cpp
├── darwin-arm64
│ └── libblst.a
├── darwin
│ └── libblst.a
├── linux
│ └── libblst.a
└── windows
│ └── blst.lib
├── cpuinfo.sh
├── lido_sdk
├── __init__.py
├── blstverify
│ ├── __init__.py
│ ├── blst.py
│ ├── test.py
│ └── verifier.py
├── config.py
├── contract
│ ├── __init__.py
│ ├── abi
│ │ ├── Lido.json
│ │ └── NodeOperatorsRegistry.json
│ ├── contract.py
│ ├── execute_contract.py
│ └── load_contract.py
├── eth2deposit
│ ├── __init__.py
│ └── ssz.py
├── eth_multicall
│ ├── __init__.py
│ ├── multicall.py
│ └── multicall_address.py
├── lido.py
├── main.py
├── methods
│ ├── __init__.py
│ ├── keys.py
│ ├── operators.py
│ ├── stats.py
│ └── typing.py
└── network
│ ├── __init__.py
│ └── type.py
├── poetry.lock
├── pyproject.toml
├── setup.py
└── tests
├── __init__.py
├── fixtures.py
├── test_blst_verifier.py
├── test_contracts.py
├── test_lido.py
├── test_multicall.py
└── utils.py
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | branch = True
3 | source = blank-python-project
4 |
5 | omit =
6 | tests/*
7 | notebooks/*
8 | lido/blstverify/test.py
9 |
10 | [report]
11 | exclude_lines =
12 | if self.debug:
13 | pragma: no cover
14 | raise NotImplementedError
15 | if __name__ == .__main__.:
16 | ignore_errors = True
17 | omit =
18 | tests/*
19 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # These owners will be the default owners for everything in
2 | # the repo. Unless a later match takes precedence.
3 | * @F4ever @infloop @avsetsin
4 |
--------------------------------------------------------------------------------
/.github/actions/update_blst.yml:
--------------------------------------------------------------------------------
1 | name: Build blst and create PR
2 |
3 | on: workflow_dispatch
4 |
5 | jobs:
6 | update-blst-interface:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | with:
11 | submodules: true
12 |
13 | - name: Setup Python
14 | uses: actions/setup-python@v2
15 | with:
16 | python-version: 3.10
17 | architecture: x64
18 |
19 | - name: Setup SWIG
20 | run: |
21 | pip install swig
22 |
23 | - name: Build blst library bindings for python
24 | run: |
25 | cd blst/bindings/python;
26 | ./run.me;
27 |
28 | - name: Copy files
29 | run: |
30 | cp ./blst/bindings/python/blst_wrap.cpp blst-lib;
31 | cp ./blst/bindings/python/blst.py lido_sdk/blstverify;
32 | cp ./blst/bindings/blst.h ./blst-lib/;
33 | cp ./blst/bindings/blst.hpp ./blst-lib/;
34 | cp ./blst/bindings/blst_aux.h ./blst-lib/;
35 |
36 | - name: Create pull request
37 | uses: lidofinance/create-pull-request@v4
38 | branch: feature/recompile-blst-lib
39 |
40 | build-blst-macos:
41 | runs-on: macos-latest
42 | steps:
43 | - uses: actions/checkout@v2
44 | with:
45 | submodules: true
46 |
47 | - name: Build 'blst' dynamic library (macos)
48 | run: |
49 | ./blst/build.sh;
50 | mkdir -p ./blst-lib/darwin/;
51 | cp ./blst/libblst.a ./blst-lib/darwin/;
52 |
53 | - name: Create Pull Request
54 | uses: lidofinance/create-pull-request@v4
55 | branch: feature/recompile-blst-lib
56 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ### Before merging Pull Request to master make sure:
2 | - [ ] You added tests to new feature
3 | - [ ] Tests are green
4 | - [ ] Version in setup.py incremented
5 | - [ ] Version in pyproject.toml is same as in setup.py
6 | - [ ] You added description with updates to CHANGELOG.md
7 | - [ ] You updated README.md (if applicable)
8 | - [ ] Lido SDK from [Test PyPI](https://test.pypi.org/project/lido-sdk/) is working well (lib updates after each comment)
9 |
--------------------------------------------------------------------------------
/.github/workflows/compile_blst.yml:
--------------------------------------------------------------------------------
1 | name: Build blst and create PR
2 |
3 | on: workflow_dispatch
4 |
5 | jobs:
6 | update-blst-interface:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v3
10 | with:
11 | submodules: true
12 |
13 | - name: Setup Python
14 | uses: actions/setup-python@v4
15 | with:
16 | python-version: "3.10"
17 | architecture: x64
18 |
19 | - name: Setup SWIG
20 | run: |
21 | pip install swig
22 |
23 | - name: Build blst library bindings for python
24 | run: |
25 | cd blst/bindings/python;
26 | ./run.me;
27 |
28 | - name: Copy files
29 | run: |
30 | cp ./blst/bindings/python/blst_wrap.cpp blst-lib;
31 | cp ./blst/bindings/python/blst.py lido_sdk/blstverify;
32 | cp ./blst/bindings/blst.h ./blst-lib/;
33 | cp ./blst/bindings/blst.hpp ./blst-lib/;
34 | cp ./blst/bindings/blst_aux.h ./blst-lib/;
35 |
36 | - name: Create pull request
37 | uses: lidofinance/create-pull-request@v4
38 | with:
39 | branch: feature/recompile-blst-lib
40 | title: 'Update blst lib.'
41 | commit-message: 'Update blst interface.'
42 | delete-branch: true
43 | signoff: true
44 |
45 | build-blst-macos:
46 | needs: update-blst-interface
47 | runs-on: macos-latest
48 |
49 | steps:
50 | - uses: actions/checkout@v3
51 | with:
52 | submodules: true
53 | ref: 'feature/recompile-blst-lib'
54 |
55 | - name: Build 'blst' dynamic library (macos)
56 | run: |
57 | cd blst;
58 | ./build.sh;
59 | mkdir -p ../blst-lib/darwin/;
60 | cp libblst.a ../blst-lib/darwin/;
61 |
62 | - name: Commit macos lib
63 | run: |
64 | git config user.email "info@lido.fi";
65 | git config user.name "Raman";
66 | git add ./blst-lib/darwin/libblst.a;
67 | git commit -m 'Update blst lib for macos.';
68 | git push;
69 |
70 | build-blst-windows:
71 | needs: update-blst-interface
72 | runs-on: windows-latest
73 |
74 | steps:
75 | - uses: actions/checkout@v3
76 | with:
77 | submodules: true
78 | ref: 'feature/recompile-blst-lib'
79 |
80 | - name: Prepare MSVC dev environment (windows)
81 | uses: ilammy/msvc-dev-cmd@v1
82 |
83 | - name: Build 'blst' dynamic library (windows)
84 | run: |
85 | cd blst/;
86 | ./build.bat;
87 | mkdir -p ../blst-lib/windows/;
88 | cp ./blst.lib ../blst-lib/windows/;
89 |
90 | - name: Commit windows lib
91 | run: |
92 | git config user.email "info@lido.fi";
93 | git config user.name "Raman";
94 | git add ./blst-lib/windows/blst.lib;
95 | git commit -m 'Update blst lib for windows.';
96 | git push;
97 |
98 | build-blst-ubuntu:
99 | needs: update-blst-interface
100 | runs-on: ubuntu-latest
101 |
102 | steps:
103 | - uses: actions/checkout@v3
104 | with:
105 | submodules: true
106 | ref: 'feature/recompile-blst-lib'
107 |
108 | - name: Build 'blst' dynamic library (ubuntu)
109 | run: |
110 | cd blst/;
111 | sed -i 's/cflags="-D__ADX__ $cflags"/echo "adx skip"/' build.sh;
112 | ./build.sh;
113 | mkdir -p ../blst-lib/linux/;
114 | cp libblst.a ../blst-lib/linux/;
115 |
116 | - name: Commit linux lib
117 | run: |
118 | git config user.email "info@lido.fi";
119 | git config user.name "Raman";
120 | git add ./blst-lib/linux/libblst.a;
121 | git commit -m 'Update blst lib for linux.';
122 | git push;
123 |
124 | build-blst-arm64:
125 | needs: update-blst-interface
126 | runs-on: ubuntu-latest
127 |
128 | steps:
129 | - uses: actions/checkout@v3
130 | with:
131 | submodules: true
132 | ref: 'feature/recompile-blst-lib'
133 |
134 | # https://github.com/docker/setup-qemu-action
135 | - name: Set up QEMU
136 | uses: docker/setup-qemu-action@v2
137 | # https://github.com/docker/setup-buildx-action
138 | - name: Set up Docker Buildx
139 | uses: docker/setup-buildx-action@v2
140 |
141 | - name: Build test arm binding
142 | run: |
143 | mkdir -p ./blst-lib/aarch64/;
144 | docker buildx build --platform linux/arm64 -t armbuild:latest --load --progress=plain .;
145 | docker create --name armbuild --platform linux/arm64 armbuild:latest;
146 | docker cp armbuild:/blst/libblst.a ./blst-lib/aarch64/;
147 |
148 | - name: Commit linux lib
149 | run: |
150 | git config user.email "info@lido.fi";
151 | git config user.name "Raman";
152 | git add ./blst-lib/aarch64/libblst.a;
153 | git commit -m 'Update blst lib for arm64.';
154 | git push;
155 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: publish
2 |
3 |
4 | on:
5 | push:
6 | branches: [ master ]
7 | tags: [ "*" ]
8 | paths-ignore:
9 | - '.github/**'
10 | pull_request:
11 | branches: [ master ]
12 |
13 | jobs:
14 | build-wheels-and-publish:
15 | runs-on: ${{matrix.os}}
16 | strategy:
17 | fail-fast: false
18 | matrix:
19 | os: [ ubuntu-latest, windows-latest, macos-latest ]
20 | python-version: [ "3.7", "3.8", "3.9", "3.10" ]
21 | architecture: [ x64 ]
22 | defaults:
23 | run:
24 | shell: bash
25 | steps:
26 | - uses: actions/checkout@v3
27 | with:
28 | submodules: true
29 |
30 | - name: Setup Python
31 | uses: actions/setup-python@v4
32 | with:
33 | python-version: ${{ matrix.python-version }}
34 | architecture: ${{ matrix.architecture }}
35 |
36 | - name: Installing pip packages
37 | run: |
38 | pip install wheel
39 | pip install twine
40 |
41 | - uses: Gr1N/setup-poetry@v8
42 | with:
43 | poetry-version: "1.2.2"
44 | - name: Install dependencies
45 | run: |
46 | poetry install
47 |
48 | - name: Testing (native blst tests)
49 | run: |
50 | python setup.py build_ext --inplace
51 | python ./lido_sdk/blstverify/test.py
52 |
53 | - name: Testing (pytest)
54 | env:
55 | INFURA_PROJECT_ID: ${{ secrets.INFURA_PROJECT_ID }}
56 | run: |
57 | poetry run pytest .
58 |
59 | - name: Building binary wheel distribution (windows and macos)
60 | if: matrix.os == 'macos-latest' || matrix.os == 'windows-latest'
61 | run: |
62 | python setup.py bdist_wheel --universal
63 |
64 | - name: Building source distrubution (linux only)
65 | if: matrix.os == 'ubuntu-latest'
66 | run: |
67 | python setup.py sdist
68 |
69 | - name: Upload binary distrubutions as artifacts
70 | uses: actions/upload-artifact@v2
71 | with:
72 | name: wheels
73 | path: ./dist
74 | if-no-files-found: error
75 |
76 | - name: Publish distribution to PyPI
77 | if: startsWith(github.event.ref, 'refs/tags')
78 | env:
79 | TWINE_USERNAME: __token__
80 | TWINE_NON_INTERACTIVE: 1
81 | TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
82 | run: |
83 | twine upload --non-interactive --skip-existing --verbose 'dist/*'
84 |
85 | - name: Publish distribution to Test PyPI
86 | continue-on-error: true
87 | env:
88 | TWINE_REPOSITORY_URL: https://test.pypi.org/legacy/
89 | TWINE_USERNAME: __token__
90 | TWINE_NON_INTERACTIVE: 1
91 | TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
92 | run: |
93 | twine upload --non-interactive --skip-existing --verbose 'dist/*'
94 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
98 | __pypackages__/
99 |
100 | # Celery stuff
101 | celerybeat-schedule
102 | celerybeat.pid
103 |
104 | # SageMath parsed files
105 | *.sage.py
106 |
107 | # Environments
108 | .env
109 | .venv
110 | env/
111 | venv/
112 | ENV/
113 | env.bak/
114 | venv.bak/
115 |
116 | # Spyder project settings
117 | .spyderproject
118 | .spyproject
119 |
120 | # Rope project settings
121 | .ropeproject
122 |
123 | # mkdocs documentation
124 | /site
125 |
126 | # mypy
127 | .mypy_cache/
128 | .dmypy.json
129 | dmypy.json
130 |
131 | # Pyre type checker
132 | .pyre/
133 |
134 | # pytype static type analyzer
135 | .pytype/
136 |
137 | # Cython debug symbols
138 | cython_debug/
139 |
140 | # Project
141 | .idea/
142 |
143 | # BLST
144 | blst
145 | lido/blstverify/*.pyd
146 | lido/blstverify/*.dll
147 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "blst"]
2 | path = blst
3 | url = https://github.com/supranational/blst
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | All notable changes to this project are documented in this file.
4 |
5 | This changelog format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | [comment]: <> (## [Unreleased](https://github.com/lidofinance/lido-python-sdk) - 2021-09-15)
9 |
10 | ## [4.1.0](https://github.com/lidofinance/lido-python-sdk/pull/91) - 2023-10-23
11 | ### Changed
12 | - Removed support for Kovan, Rinkeby, xDai, Ropsten, Kintsugi, Kiln and Zhejiang.
13 | - Added support to Holesky.
14 |
15 | ## [4.0.0](https://github.com/lidofinance/lido-python-sdk/pull/89) - 2023-03-10
16 | ### Fixed
17 | - Support to Zhejiang testnet V2
18 |
19 | ## [3.0.4](https://github.com/lidofinance/lido-python-sdk/pull/86) - 2023-02-12
20 | ### Fixed
21 | - Node Operators has no keys issue.
22 |
23 | ## [3.0.0](https://github.com/lidofinance/lido-python-sdk/pull/84) - 2023-02-12
24 | ### Added
25 | - Support to Zhejiang testnet and new versions of contracts
26 |
27 | ## [2.7.0](https://github.com/lidofinance/lido-python-sdk/pull/79) - 2022-12-14
28 | ### Added
29 | - Support to ARM for MacOS.
30 |
31 | ## [2.6.0](https://github.com/lidofinance/lido-python-sdk) - 2022-10-27
32 | ### Added
33 | - Support to ARM for linux. Move build blst to separate wf.
34 |
35 | ## [2.5.4](https://github.com/lidofinance/lido-python-sdk) - 2022-10-04
36 | ### Added
37 | - Support to ARM, Update blst lib ([#0069](https://github.com/lidofinance/lido-python-sdk/pull/69))
38 | - Fix for [2.5.2] release.
39 |
40 | ## [2.5.3](https://github.com/lidofinance/lido-python-sdk) - 2022-07-22
41 | ### Removed
42 | - Removed PoA check for Goerli nerwork ([#0068](https://github.com/lidofinance/lido-python-sdk/pull/68))
43 |
44 | ## [2.5.2](https://github.com/lidofinance/lido-python-sdk) - 2022-04-01
45 | ### Fix
46 | - Set upper bound to `multicall` version
47 |
48 | ## [2.5.1](https://github.com/lidofinance/lido-python-sdk) - 2022-04-01
49 | ### Changed
50 | - Added Kiln network ([#0067](https://github.com/lidofinance/lido-python-sdk/pull/67))
51 |
52 | ## [2.5.0](https://github.com/lidofinance/lido-python-sdk) - 2021-02-15
53 | ### Changed
54 | - Now python processes validates a bunch of keys (1000) instead of 1. ([#0065](https://github.com/lidofinance/lido-python-sdk/pull/65))
55 |
56 | ### Fix
57 | - Fix typo in multicall decode code inputs.
58 |
59 | ## [2.4.7](https://github.com/lidofinance/lido-python-sdk) - 2021-12-28
60 | ### Changed
61 | - Added Kintsugi network ([#0060](https://github.com/lidofinance/lido-python-sdk/pull/60))
62 |
63 | ## [2.4.6](https://github.com/lidofinance/lido-python-sdk) - 2021-12-28
64 | ### Fixed
65 | - Library works correctly without typing_extensions when python >= 3.8 ([#0060](https://github.com/lidofinance/lido-python-sdk/pull/60))
66 |
67 | ## [2.4.5](https://github.com/lidofinance/lido-python-sdk) - 2021-12-22
68 | ### Changed
69 | - Change library dependency ([#0059](https://github.com/lidofinance/lido-python-sdk/pull/59))
70 |
71 | ## [2.4.3](https://github.com/lidofinance/lido-python-sdk) - 2021-11-18
72 | ### Changed
73 | - Fixes for configurable timeouts ([#0058](https://github.com/lidofinance/lido-python-sdk/pull/58))
74 |
75 | ## [2.4.2](https://github.com/lidofinance/lido-python-sdk) - 2021-11-17
76 | ### Added
77 | - Added configurable timeouts for Pool and Thread Executors ([#0056](https://github.com/lidofinance/lido-python-sdk/pull/56))
78 |
79 | ## [2.4.1](https://github.com/lidofinance/lido-python-sdk) - 2021-11-09
80 | ### Added
81 | - Fixed empty call_args when operators have zero unused keys while updating keys (`lido.update_keys`) ([#0054](https://github.com/lidofinance/lido-python-sdk/pull/54))
82 |
83 | ## [2.4.0](https://github.com/lidofinance/lido-python-sdk) - 2021-10-01
84 | ### Added
85 | - Add ability to update keys in optimal way (`lido.update_keys`) ([#0050](https://github.com/lidofinance/lido-python-sdk/pull/50))
86 |
87 | ## [2.3.1](https://github.com/lidofinance/lido-python-sdk) - 2021-09-22
88 | ### Changed
89 | - Move from req.txt to poetry ([#0048](https://github.com/lidofinance/lido-python-sdk/pull/48))
90 |
91 | ## [2.2.2](https://github.com/lidofinance/lido-python-sdk) - 2021-09-22
92 | ### Added
93 | - Add Rinkeby support ([#0047](https://github.com/lidofinance/lido-python-sdk/pull/47))
94 |
95 | ## [2.2.0](https://github.com/lidofinance/lido-python-sdk) - 2021-09-14
96 | ### Changed
97 | - Remove strict param from validate_keys method ([#0042](https://github.com/lidofinance/lido-python-sdk/pull/42))
98 | - Fixed behavior when input is an empty array in lido methods ([#0043](https://github.com/lidofinance/lido-python-sdk/pull/43))
99 |
100 | ## [2.1.2](https://github.com/lidofinance/lido-python-sdk) - 2021-09-09
101 | ### Changed
102 | - Cache eth_chain_id value ([#0037](https://github.com/lidofinance/lido-python-sdk/pull/37))
103 |
104 | ## [2.1.1](https://github.com/lidofinance/lido-python-sdk) - 2021-09-03
105 | ### Changed
106 | - Update web3 from 5.23.0 to 5.23.1. Now "used" key is optional for verification ([#0035](https://github.com/lidofinance/lido-python-sdk/pull/35))
107 |
108 | ## [2.1.0](https://github.com/lidofinance/lido-python-sdk) - 2021-09-03
109 | ### Added
110 | - Add new params that could be provided to multicall ([#0032](https://github.com/lidofinance/lido-python-sdk/pull/32))
111 |
112 | ## [2.0.1](https://github.com/lidofinance/lido-python-sdk) - 2021-09-01
113 | ### Changed
114 | - Renamed library root package to `lido_sdk`, added more tests, fixed ABIs packaging ([#0029](https://github.com/lidofinance/lido-python-sdk/pull/29))
115 |
116 | ## [1.0.1](https://github.com/lidofinance/lido-python-sdk) - 2021-08-31
117 | ### Added
118 | - Lido public class ([#0020](https://github.com/lidofinance/lido-python-sdk/pull/20))
119 | - Added fast BLS verification ([#0019](https://github.com/lidofinance/lido-python-sdk/pull/19))
120 |
121 | ### Changed
122 | - If strict is false, for validation we will try possible WC only for already used keys ([#0024](https://github.com/lidofinance/lido-python-sdk/pull/24))
123 |
124 | ## [0.3.0](https://github.com/lidofinance/lido-python-sdk) - 2021-08-26
125 | ### Fixed
126 | - Move eth2deposit code to repository
127 |
128 | ## [0.2.0](https://github.com/lidofinance/lido-python-sdk) - 2021-08-26
129 | ### Added
130 | - Github actions ([#0005](https://github.com/lidofinance/lido-python-sdk/pull/5))
131 | - Contract interact and contract's multicall support ([#0006](https://github.com/lidofinance/lido-python-sdk/pull/6))
132 | - Base operator's methods ([#0008](https://github.com/lidofinance/lido-python-sdk/pull/8))
133 | - Key validation method ([#0011](https://github.com/lidofinance/lido-python-sdk/pull/11))
134 | - Stats method ([#0013](https://github.com/lidofinance/lido-python-sdk/pull/11))
135 |
136 | ### Fixed
137 | - Code coverage ([#0014](https://github.com/lidofinance/lido-python-sdk/pull/14))
138 |
139 | ## [0.1.0](https://github.com/lidofinance/lido-python-sdk) - 2021-08-20
140 | ### Added
141 | - Initial release
142 | - Setup configuration ([#0002](https://github.com/lidofinance/lido-python-sdk/pull/2))
143 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # This is used to build bindings for arm64
2 | FROM python:3.10.6-slim-bullseye
3 |
4 | RUN apt update && apt install -y build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libsqlite3-dev libreadline-dev libffi-dev libbz2-dev g++ make
5 |
6 | WORKDIR .
7 | COPY . .
8 |
9 | RUN cd blst && sed -i 's/cflags="-D__ADX__ $cflags"/echo "adx skip"/' build.sh && ./build.sh
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 P2P
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include blst-lib *.cpp *.hpp *.h *.a *.lib
2 | recursive-include blst/bindings/vectors/hash_to_curve *.json
3 | recursive-include lido_sdk/contract/abi *.json
4 | include README.md
5 | include CHANGELOG.md
6 | include LICENSE
7 | include *.txt
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
Lido Python SDK
2 |
3 | [](https://codecov.io/gh/lidofinance/lido-python-sdk)
4 | [](https://github.com/psf/black)
5 | [](https://opensource.org/licenses/MIT)
6 |
7 | ### ❗Python SDK supports only Curated Staking module. Check out [KAPI](https://github.com/lidofinance/lido-keys-api) project if you need **all** Lido keys.❗
8 |
9 | Lido Python SDK - convenient interface to check validator's pub keys in Node Operators registry.
10 | Provides ability to download, verify and check for duplicates keys in curated module (Node Operators registry).
11 |
12 | ## Installation
13 | This library is available on PyPi:
14 |
15 | ```bash
16 | pip install lido-sdk
17 | ```
18 |
19 | ## Fast start
20 |
21 | 1. Create Web3 provider. One of fast options to start is INFURA.
22 | ```python
23 | from web3 import Web3
24 |
25 | w3 = Web3(Web3.HTTPProvider('https://mainnet.infura.io/v3/{INFURA_PROJECT_ID}'))
26 | ```
27 |
28 | 2. Create Lido instance and provide web3 provider
29 | ```python
30 | from lido_sdk import Lido
31 |
32 | lido = Lido(w3)
33 | ```
34 |
35 | 3. Call one
36 | ```python
37 | response = lido.fetch_all_keys_and_validate()
38 |
39 | if response['invalid_keys'] or response['duplicated_keys']:
40 | # This is not cool
41 | print('There is invalid or duplicated keys\n')
42 | print(response)
43 | else:
44 | print('Everything is good!')
45 | ```
46 |
47 | ## Params for Lido
48 | | Param name | Default value | Description |
49 | |---------------------------------|---------------|----------------------------------------------------------------------|
50 | | w3 | required | Web3 provider |
51 | | MULTICALL_MAX_BUNCH | 275 | Count of calls in one multicall (not recommended to increase) |
52 | | MULTICALL_MAX_WORKERS | 6 | Count of requests in parallel (not recommended to have more than 12) |
53 | | MULTICALL_MAX_RETRIES | 5 | Count of retries before exception will be raised |
54 | | MULTICALL_POOL_EXECUTOR_TIMEOUT | 30 | Thread pool timeout for multicall (seconds) |
55 | | VALIDATE_POOL_EXECUTOR_TIMEOUT | 10 | Process pool timeout for keys validation (seconds) |
56 |
57 | Settings example if timeout exception was raised:
58 | ```python
59 | Lido(w3=w3, MULTICALL_MAX_BUNCH=100, MULTICALL_MAX_WORKERS=3)
60 | ```
61 |
62 | ## Base methods
63 | Everything you need is in Lido class.
64 |
65 | - `Lido.get_operators_indexes(self) -> List[int]`
66 | Returns: Node operators indexes in contract.
67 | ```
68 | >>> lido.get_operators_indexes()
69 |
70 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
71 | ```
72 |
73 | - `Lido.get_operators_data(self, operators_indexes: Optional[List[int]] = None) -> List[Operator]`
74 | Receives: List of operators indexes. If nothing provided will take previous return from `get_operators_indexes` method.
75 | Returns: List of operators details.
76 | ```
77 | >>> lido.get_operators_data([1])
78 |
79 | [{'active': True, 'name': 'Certus One', 'rewardAddress': '0x8d689476eb446a1fb0065bffac32398ed7f89165', 'stakingLimit': 1000, 'stoppedValidators': 0, 'totalSigningKeys': 1000, 'usedSigningKeys': 1000, 'index': 1}]```
80 | ```
81 |
82 | - `Lido.get_operators_keys(self, operators: Optional[List[Operator]] = None) -> List[OperatorKey]`
83 | Receives: List of operators details. If nothing provided will take previous return from `get_operators_data` method.
84 | Returns: List of keys in contract.
85 | ```
86 | >>> lido.get_operators_keys(operators_data)
87 |
88 | [{'key': b'...', 'depositSignature': b'...', 'used': False, 'index': 6921, 'operator_index': 8}, ...]
89 | ```
90 |
91 | - `Lido.update_keys(self) -> List[OperatorKey]`
92 | Returns actual keys list. Works only in `get_operators_keys` was called before. Should be used to periodically update keys.
93 | Faster because not all keys are updated from the contract.
94 | ```
95 | >>> lido.update_keys()
96 |
97 | [{'key': b'...', 'depositSignature': b'...', 'used': False, 'index': 6521, 'operator_index': 5}]
98 | ```
99 |
100 | - `Lido.validate_keys(self, keys: Optional[List[OperatorKey]] = None) -> List[OperatorKey]`
101 | Receives: List of keys to validate. If nothing provided will take previous return from `get_operators_keys` method.
102 | Returns: List of invalid keys.
103 | ```
104 | >>> lido.validate_keys()
105 |
106 | [{'key': b'...', 'depositSignature': b'...', 'used': False, 'index': 6521, 'operator_index': 5}]
107 | ```
108 |
109 | - `Lido.find_duplicated_keys(self, keys: Optional[List[OperatorKey]] = None) -> List[Tuple[OperatorKey, OperatorKey]]`
110 | Receives: List of keys to compare. If nothing provided will take previous return from `get_operators_keys` method.
111 | Returns: List of same pairs keys.
112 | ```
113 | >>> lido.find_duplicated_keys()
114 |
115 | [
116 | (
117 | {'key': b'abc...', 'index': 222, 'operator_index': 5, ...},
118 | {'key': b'abc...', 'index': 111, 'operator_index': 5, ...}
119 | )
120 | ]
121 | ```
122 |
123 | - `Lido.get_status(self) -> dict`
124 | Returns dict with Lido current state.
125 | ```
126 | >>> lido.get_status()
127 |
128 | {
129 | 'isStopped': False,
130 | 'totalPooledEther': 1045230979275869331637351,
131 | 'withdrawalCredentials': b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb9\xd7\x93Hx\xb4\xfb\x96\x10\xb3\xfe\x8a^D\x1e\x8f\xad~)?',
132 | 'bufferedEther': 76467538672788331637351,
133 | 'feeBasisPoints': 1000,
134 | 'treasuryFeeBasisPoints': 0,
135 | 'insuranceFeeBasisPoints': 5000,
136 | 'operatorsFeeBasisPoints': 5000,
137 | 'depositedValidators': 29800,
138 | 'beaconValidators': 29800,
139 | 'beaconBalance': 968763440603081000000000,
140 | 'last_block': 13110151,
141 | 'last_blocktime': 1630103538,
142 | }
143 | ```
144 |
145 | - `Lido.fetch_all_keys_and_validate(self) -> Dict[str, list]`
146 | Makes all steps below except `get_status`.
147 | Returns all invalid and duplicated keys.
148 | ```
149 | >>> lido.fetch_all_keys_and_validate()
150 |
151 | {
152 | 'invalid_keys': [...],
153 | 'duplicated_keys': [...],
154 | }
155 | ```
156 |
157 | ## Issues
158 |
159 | There is issues with using blst lib on macos ARM cpu.
160 | But everything works on linux ARM cpu.
161 |
162 | ## Main Features
163 |
164 | ### Multicall Function Calls
165 |
166 | - Instead of making network requests one-by-one, this library combines many requests into one RPC call. It uses [banteg/multicall.py](https://github.com/banteg/multicall.py), a Python wrapper for [makerdao/multicall](https://github.com/makerdao/multicall).
167 | - Fast validation system powered by [blst](https://github.com/supranational/blst)
168 |
169 | ### Automatic Testnet / Mainnet Switching
170 |
171 | Depending on which network is configured in web3 object, a set of contracts will be used.
172 | Available networks:
173 | - Mainnet
174 | - Görli
175 | - Holesky
176 |
177 | ## Development
178 |
179 | Clone project:
180 | ```bash
181 | git clone --recurse-submodules https://github.com/lidofinance/lido-python-sdk.git
182 | cd lido-python-sdk
183 | ```
184 | Create virtual env:
185 | ```bash
186 | virtualenv .env --python=python3
187 | source .env/bin/activate
188 | ```
189 | Install all dependencies:
190 | ```bash
191 | poetry install
192 | ```
193 | Activate virtual env
194 | ```bash
195 | poetry shell
196 | ```
197 | Build blst locally (linux):
198 | ```bash
199 | cd blst/
200 | ./build.sh
201 | cd ..
202 | mkdir -p ./blst-lib/linux/
203 | cp ./blst/libblst.a ./blst-lib/linux/
204 | cp ./blst/bindings/blst.h ./blst-lib/
205 | cp ./blst/bindings/blst.hpp ./blst-lib/
206 | cp ./blst/bindings/blst_aux.h ./blst-lib/
207 | python setup.py build_ext --inplace
208 | ```
209 | Build blst locally (osx):
210 | ```bash
211 | cd blst/
212 | ./build.sh
213 | cd ..
214 | mkdir -p ./blst-lib/darwin/
215 | cp ./blst/libblst.a ./blst-lib/darwin/
216 | cp ./blst/bindings/blst.h ./blst-lib/
217 | cp ./blst/bindings/blst.hpp ./blst-lib/
218 | cp ./blst/bindings/blst_aux.h ./blst-lib/
219 | python setup.py build_ext --inplace
220 | ```
221 | Build blst locally (osx arm):
222 | ```bash
223 | cd blst/
224 | ./build.sh
225 | cd ..
226 | mkdir -p ./blst-lib/darwin-arm64/
227 | cp ./blst/libblst.a ./blst-lib/darwin-arm64/
228 | cp ./blst/bindings/blst.h ./blst-lib/
229 | cp ./blst/bindings/blst.hpp ./blst-lib/
230 | cp ./blst/bindings/blst_aux.h ./blst-lib/
231 | python setup.py build_ext --inplace
232 | ```
233 |
234 | ## How to test
235 | Simply run in project root directory:
236 | ```bash
237 | poetry run pytest .
238 | ```
239 |
240 | ## Release new version
241 | ```bash
242 | git tag v2.x.x master
243 | git push --tags
244 | ```
245 | New version should be published after all pipelines passed.
246 |
247 | ## Rebuild blst
248 | Goto actions "Build blst and create PR".
249 | Note that darwin-arm64 binaries are not rebuilt automatically due to GitHub Actions not supporting macOS runners on arm yet
250 | Review PR and merge.
251 | Do a release.
252 |
--------------------------------------------------------------------------------
/blst-lib/aarch64/libblst.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lidofinance/lido-python-sdk/4820b3106311fc39a62483948e62907b140bf060/blst-lib/aarch64/libblst.a
--------------------------------------------------------------------------------
/blst-lib/blst.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Supranational LLC
3 | * Licensed under the Apache License, Version 2.0, see LICENSE for details.
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 | #ifndef __BLST_H__
7 | #define __BLST_H__
8 |
9 | #ifdef __SIZE_TYPE__
10 | typedef __SIZE_TYPE__ size_t;
11 | #else
12 | #include
13 | #endif
14 |
15 | #if defined(__UINT8_TYPE__) && defined(__UINT32_TYPE__) \
16 | && defined(__UINT64_TYPE__)
17 | typedef __UINT8_TYPE__ uint8_t;
18 | typedef __UINT32_TYPE__ uint32_t;
19 | typedef __UINT64_TYPE__ uint64_t;
20 | #else
21 | #include
22 | #endif
23 |
24 | #ifdef __cplusplus
25 | extern "C" {
26 | #elif defined(__BLST_CGO__)
27 | typedef _Bool bool; /* it's assumed that cgo calls modern enough compiler */
28 | #elif defined(__STDC_VERSION__) && __STDC_VERSION__>=199901
29 | # define bool _Bool
30 | #else
31 | # define bool int
32 | #endif
33 |
34 | #ifdef SWIG
35 | # define DEFNULL =NULL
36 | #elif defined __cplusplus
37 | # define DEFNULL =0
38 | #else
39 | # define DEFNULL
40 | #endif
41 |
42 | typedef enum {
43 | BLST_SUCCESS = 0,
44 | BLST_BAD_ENCODING,
45 | BLST_POINT_NOT_ON_CURVE,
46 | BLST_POINT_NOT_IN_GROUP,
47 | BLST_AGGR_TYPE_MISMATCH,
48 | BLST_VERIFY_FAIL,
49 | BLST_PK_IS_INFINITY,
50 | BLST_BAD_SCALAR,
51 | } BLST_ERROR;
52 |
53 | typedef uint8_t byte;
54 | typedef uint64_t limb_t;
55 |
56 | typedef struct { byte b[256/8]; } blst_scalar;
57 | typedef struct { limb_t l[256/8/sizeof(limb_t)]; } blst_fr;
58 | typedef struct { limb_t l[384/8/sizeof(limb_t)]; } blst_fp;
59 | /* 0 is "real" part, 1 is "imaginary" */
60 | typedef struct { blst_fp fp[2]; } blst_fp2;
61 | typedef struct { blst_fp2 fp2[3]; } blst_fp6;
62 | typedef struct { blst_fp6 fp6[2]; } blst_fp12;
63 |
64 | void blst_scalar_from_uint32(blst_scalar *out, const uint32_t a[8]);
65 | void blst_uint32_from_scalar(uint32_t out[8], const blst_scalar *a);
66 | void blst_scalar_from_uint64(blst_scalar *out, const uint64_t a[4]);
67 | void blst_uint64_from_scalar(uint64_t out[4], const blst_scalar *a);
68 | void blst_scalar_from_bendian(blst_scalar *out, const byte a[32]);
69 | void blst_bendian_from_scalar(byte out[32], const blst_scalar *a);
70 | void blst_scalar_from_lendian(blst_scalar *out, const byte a[32]);
71 | void blst_lendian_from_scalar(byte out[32], const blst_scalar *a);
72 | bool blst_scalar_fr_check(const blst_scalar *a);
73 | bool blst_sk_check(const blst_scalar *a);
74 | bool blst_sk_add_n_check(blst_scalar *out, const blst_scalar *a,
75 | const blst_scalar *b);
76 | bool blst_sk_sub_n_check(blst_scalar *out, const blst_scalar *a,
77 | const blst_scalar *b);
78 | bool blst_sk_mul_n_check(blst_scalar *out, const blst_scalar *a,
79 | const blst_scalar *b);
80 | void blst_sk_inverse(blst_scalar *out, const blst_scalar *a);
81 | bool blst_scalar_from_le_bytes(blst_scalar *out, const byte *in, size_t len);
82 | bool blst_scalar_from_be_bytes(blst_scalar *out, const byte *in, size_t len);
83 |
84 | #ifndef SWIG
85 | /*
86 | * BLS12-381-specifc Fr operations.
87 | */
88 | void blst_fr_add(blst_fr *ret, const blst_fr *a, const blst_fr *b);
89 | void blst_fr_sub(blst_fr *ret, const blst_fr *a, const blst_fr *b);
90 | void blst_fr_mul_by_3(blst_fr *ret, const blst_fr *a);
91 | void blst_fr_lshift(blst_fr *ret, const blst_fr *a, size_t count);
92 | void blst_fr_rshift(blst_fr *ret, const blst_fr *a, size_t count);
93 | void blst_fr_mul(blst_fr *ret, const blst_fr *a, const blst_fr *b);
94 | void blst_fr_sqr(blst_fr *ret, const blst_fr *a);
95 | void blst_fr_cneg(blst_fr *ret, const blst_fr *a, bool flag);
96 | void blst_fr_eucl_inverse(blst_fr *ret, const blst_fr *a);
97 | void blst_fr_inverse(blst_fr *ret, const blst_fr *a);
98 |
99 | void blst_fr_from_uint64(blst_fr *ret, const uint64_t a[4]);
100 | void blst_uint64_from_fr(uint64_t ret[4], const blst_fr *a);
101 | void blst_fr_from_scalar(blst_fr *ret, const blst_scalar *a);
102 | void blst_scalar_from_fr(blst_scalar *ret, const blst_fr *a);
103 |
104 | /*
105 | * BLS12-381-specifc Fp operations.
106 | */
107 | void blst_fp_add(blst_fp *ret, const blst_fp *a, const blst_fp *b);
108 | void blst_fp_sub(blst_fp *ret, const blst_fp *a, const blst_fp *b);
109 | void blst_fp_mul_by_3(blst_fp *ret, const blst_fp *a);
110 | void blst_fp_mul_by_8(blst_fp *ret, const blst_fp *a);
111 | void blst_fp_lshift(blst_fp *ret, const blst_fp *a, size_t count);
112 | void blst_fp_mul(blst_fp *ret, const blst_fp *a, const blst_fp *b);
113 | void blst_fp_sqr(blst_fp *ret, const blst_fp *a);
114 | void blst_fp_cneg(blst_fp *ret, const blst_fp *a, bool flag);
115 | void blst_fp_eucl_inverse(blst_fp *ret, const blst_fp *a);
116 | void blst_fp_inverse(blst_fp *ret, const blst_fp *a);
117 | bool blst_fp_sqrt(blst_fp *ret, const blst_fp *a);
118 |
119 | void blst_fp_from_uint32(blst_fp *ret, const uint32_t a[12]);
120 | void blst_uint32_from_fp(uint32_t ret[12], const blst_fp *a);
121 | void blst_fp_from_uint64(blst_fp *ret, const uint64_t a[6]);
122 | void blst_uint64_from_fp(uint64_t ret[6], const blst_fp *a);
123 | void blst_fp_from_bendian(blst_fp *ret, const byte a[48]);
124 | void blst_bendian_from_fp(byte ret[48], const blst_fp *a);
125 | void blst_fp_from_lendian(blst_fp *ret, const byte a[48]);
126 | void blst_lendian_from_fp(byte ret[48], const blst_fp *a);
127 |
128 | /*
129 | * BLS12-381-specifc Fp2 operations.
130 | */
131 | void blst_fp2_add(blst_fp2 *ret, const blst_fp2 *a, const blst_fp2 *b);
132 | void blst_fp2_sub(blst_fp2 *ret, const blst_fp2 *a, const blst_fp2 *b);
133 | void blst_fp2_mul_by_3(blst_fp2 *ret, const blst_fp2 *a);
134 | void blst_fp2_mul_by_8(blst_fp2 *ret, const blst_fp2 *a);
135 | void blst_fp2_lshift(blst_fp2 *ret, const blst_fp2 *a, size_t count);
136 | void blst_fp2_mul(blst_fp2 *ret, const blst_fp2 *a, const blst_fp2 *b);
137 | void blst_fp2_sqr(blst_fp2 *ret, const blst_fp2 *a);
138 | void blst_fp2_cneg(blst_fp2 *ret, const blst_fp2 *a, bool flag);
139 | void blst_fp2_eucl_inverse(blst_fp2 *ret, const blst_fp2 *a);
140 | void blst_fp2_inverse(blst_fp2 *ret, const blst_fp2 *a);
141 | bool blst_fp2_sqrt(blst_fp2 *ret, const blst_fp2 *a);
142 |
143 | /*
144 | * BLS12-381-specifc Fp12 operations.
145 | */
146 | void blst_fp12_sqr(blst_fp12 *ret, const blst_fp12 *a);
147 | void blst_fp12_cyclotomic_sqr(blst_fp12 *ret, const blst_fp12 *a);
148 | void blst_fp12_mul(blst_fp12 *ret, const blst_fp12 *a, const blst_fp12 *b);
149 | void blst_fp12_mul_by_xy00z0(blst_fp12 *ret, const blst_fp12 *a,
150 | const blst_fp6 *xy00z0);
151 | void blst_fp12_conjugate(blst_fp12 *a);
152 | void blst_fp12_inverse(blst_fp12 *ret, const blst_fp12 *a);
153 | /* caveat lector! |n| has to be non-zero and not more than 3! */
154 | void blst_fp12_frobenius_map(blst_fp12 *ret, const blst_fp12 *a, size_t n);
155 | bool blst_fp12_is_equal(const blst_fp12 *a, const blst_fp12 *b);
156 | bool blst_fp12_is_one(const blst_fp12 *a);
157 | bool blst_fp12_in_group(const blst_fp12 *a);
158 | const blst_fp12 *blst_fp12_one();
159 | #endif // SWIG
160 |
161 | /*
162 | * BLS12-381-specifc point operations.
163 | */
164 | typedef struct { blst_fp x, y, z; } blst_p1;
165 | typedef struct { blst_fp x, y; } blst_p1_affine;
166 |
167 | void blst_p1_add(blst_p1 *out, const blst_p1 *a, const blst_p1 *b);
168 | void blst_p1_add_or_double(blst_p1 *out, const blst_p1 *a, const blst_p1 *b);
169 | void blst_p1_add_affine(blst_p1 *out, const blst_p1 *a,
170 | const blst_p1_affine *b);
171 | void blst_p1_add_or_double_affine(blst_p1 *out, const blst_p1 *a,
172 | const blst_p1_affine *b);
173 | void blst_p1_double(blst_p1 *out, const blst_p1 *a);
174 | void blst_p1_mult(blst_p1 *out, const blst_p1 *p, const byte *scalar,
175 | size_t nbits);
176 | void blst_p1_cneg(blst_p1 *p, bool cbit);
177 | void blst_p1_to_affine(blst_p1_affine *out, const blst_p1 *in);
178 | void blst_p1_from_affine(blst_p1 *out, const blst_p1_affine *in);
179 | bool blst_p1_on_curve(const blst_p1 *p);
180 | bool blst_p1_in_g1(const blst_p1 *p);
181 | bool blst_p1_is_equal(const blst_p1 *a, const blst_p1 *b);
182 | bool blst_p1_is_inf(const blst_p1 *a);
183 | const blst_p1 *blst_p1_generator();
184 |
185 | bool blst_p1_affine_on_curve(const blst_p1_affine *p);
186 | bool blst_p1_affine_in_g1(const blst_p1_affine *p);
187 | bool blst_p1_affine_is_equal(const blst_p1_affine *a, const blst_p1_affine *b);
188 | bool blst_p1_affine_is_inf(const blst_p1_affine *a);
189 | const blst_p1_affine *blst_p1_affine_generator();
190 |
191 | typedef struct { blst_fp2 x, y, z; } blst_p2;
192 | typedef struct { blst_fp2 x, y; } blst_p2_affine;
193 |
194 | void blst_p2_add(blst_p2 *out, const blst_p2 *a, const blst_p2 *b);
195 | void blst_p2_add_or_double(blst_p2 *out, const blst_p2 *a, const blst_p2 *b);
196 | void blst_p2_add_affine(blst_p2 *out, const blst_p2 *a,
197 | const blst_p2_affine *b);
198 | void blst_p2_add_or_double_affine(blst_p2 *out, const blst_p2 *a,
199 | const blst_p2_affine *b);
200 | void blst_p2_double(blst_p2 *out, const blst_p2 *a);
201 | void blst_p2_mult(blst_p2 *out, const blst_p2 *p, const byte *scalar,
202 | size_t nbits);
203 | void blst_p2_cneg(blst_p2 *p, bool cbit);
204 | void blst_p2_to_affine(blst_p2_affine *out, const blst_p2 *in);
205 | void blst_p2_from_affine(blst_p2 *out, const blst_p2_affine *in);
206 | bool blst_p2_on_curve(const blst_p2 *p);
207 | bool blst_p2_in_g2(const blst_p2 *p);
208 | bool blst_p2_is_equal(const blst_p2 *a, const blst_p2 *b);
209 | bool blst_p2_is_inf(const blst_p2 *a);
210 | const blst_p2 *blst_p2_generator();
211 |
212 | bool blst_p2_affine_on_curve(const blst_p2_affine *p);
213 | bool blst_p2_affine_in_g2(const blst_p2_affine *p);
214 | bool blst_p2_affine_is_equal(const blst_p2_affine *a, const blst_p2_affine *b);
215 | bool blst_p2_affine_is_inf(const blst_p2_affine *a);
216 | const blst_p2_affine *blst_p2_affine_generator();
217 |
218 | /*
219 | * Multi-scalar multiplications and other multi-point operations.
220 | */
221 |
222 | void blst_p1s_to_affine(blst_p1_affine dst[], const blst_p1 *const points[],
223 | size_t npoints);
224 | void blst_p1s_add(blst_p1 *ret, const blst_p1_affine *const points[],
225 | size_t npoints);
226 |
227 | size_t blst_p1s_mult_wbits_precompute_sizeof(size_t wbits, size_t npoints);
228 | void blst_p1s_mult_wbits_precompute(blst_p1_affine table[], size_t wbits,
229 | const blst_p1_affine *const points[],
230 | size_t npoints);
231 | size_t blst_p1s_mult_wbits_scratch_sizeof(size_t npoints);
232 | void blst_p1s_mult_wbits(blst_p1 *ret, const blst_p1_affine table[],
233 | size_t wbits, size_t npoints,
234 | const byte *const scalars[], size_t nbits,
235 | limb_t *scratch);
236 |
237 | size_t blst_p1s_mult_pippenger_scratch_sizeof(size_t npoints);
238 | void blst_p1s_mult_pippenger(blst_p1 *ret, const blst_p1_affine *const points[],
239 | size_t npoints, const byte *const scalars[],
240 | size_t nbits, limb_t *scratch);
241 | void blst_p1s_tile_pippenger(blst_p1 *ret, const blst_p1_affine *const points[],
242 | size_t npoints, const byte *const scalars[],
243 | size_t nbits, limb_t *scratch,
244 | size_t bit0, size_t window);
245 |
246 | void blst_p2s_to_affine(blst_p2_affine dst[], const blst_p2 *const points[],
247 | size_t npoints);
248 | void blst_p2s_add(blst_p2 *ret, const blst_p2_affine *const points[],
249 | size_t npoints);
250 |
251 | size_t blst_p2s_mult_wbits_precompute_sizeof(size_t wbits, size_t npoints);
252 | void blst_p2s_mult_wbits_precompute(blst_p2_affine table[], size_t wbits,
253 | const blst_p2_affine *const points[],
254 | size_t npoints);
255 | size_t blst_p2s_mult_wbits_scratch_sizeof(size_t npoints);
256 | void blst_p2s_mult_wbits(blst_p2 *ret, const blst_p2_affine table[],
257 | size_t wbits, size_t npoints,
258 | const byte *const scalars[], size_t nbits,
259 | limb_t *scratch);
260 |
261 | size_t blst_p2s_mult_pippenger_scratch_sizeof(size_t npoints);
262 | void blst_p2s_mult_pippenger(blst_p2 *ret, const blst_p2_affine *const points[],
263 | size_t npoints, const byte *const scalars[],
264 | size_t nbits, limb_t *scratch);
265 | void blst_p2s_tile_pippenger(blst_p2 *ret, const blst_p2_affine *const points[],
266 | size_t npoints, const byte *const scalars[],
267 | size_t nbits, limb_t *scratch,
268 | size_t bit0, size_t window);
269 |
270 | /*
271 | * Hash-to-curve operations.
272 | */
273 | #ifndef SWIG
274 | void blst_map_to_g1(blst_p1 *out, const blst_fp *u, const blst_fp *v DEFNULL);
275 | void blst_map_to_g2(blst_p2 *out, const blst_fp2 *u, const blst_fp2 *v DEFNULL);
276 | #endif
277 |
278 | void blst_encode_to_g1(blst_p1 *out,
279 | const byte *msg, size_t msg_len,
280 | const byte *DST DEFNULL, size_t DST_len DEFNULL,
281 | const byte *aug DEFNULL, size_t aug_len DEFNULL);
282 | void blst_hash_to_g1(blst_p1 *out,
283 | const byte *msg, size_t msg_len,
284 | const byte *DST DEFNULL, size_t DST_len DEFNULL,
285 | const byte *aug DEFNULL, size_t aug_len DEFNULL);
286 |
287 | void blst_encode_to_g2(blst_p2 *out,
288 | const byte *msg, size_t msg_len,
289 | const byte *DST DEFNULL, size_t DST_len DEFNULL,
290 | const byte *aug DEFNULL, size_t aug_len DEFNULL);
291 | void blst_hash_to_g2(blst_p2 *out,
292 | const byte *msg, size_t msg_len,
293 | const byte *DST DEFNULL, size_t DST_len DEFNULL,
294 | const byte *aug DEFNULL, size_t aug_len DEFNULL);
295 |
296 | /*
297 | * Zcash-compatible serialization/deserialization.
298 | */
299 | void blst_p1_serialize(byte out[96], const blst_p1 *in);
300 | void blst_p1_compress(byte out[48], const blst_p1 *in);
301 | void blst_p1_affine_serialize(byte out[96], const blst_p1_affine *in);
302 | void blst_p1_affine_compress(byte out[48], const blst_p1_affine *in);
303 | BLST_ERROR blst_p1_uncompress(blst_p1_affine *out, const byte in[48]);
304 | BLST_ERROR blst_p1_deserialize(blst_p1_affine *out, const byte in[96]);
305 |
306 | void blst_p2_serialize(byte out[192], const blst_p2 *in);
307 | void blst_p2_compress(byte out[96], const blst_p2 *in);
308 | void blst_p2_affine_serialize(byte out[192], const blst_p2_affine *in);
309 | void blst_p2_affine_compress(byte out[96], const blst_p2_affine *in);
310 | BLST_ERROR blst_p2_uncompress(blst_p2_affine *out, const byte in[96]);
311 | BLST_ERROR blst_p2_deserialize(blst_p2_affine *out, const byte in[192]);
312 |
313 | /*
314 | * Specification defines two variants, 'minimal-signature-size' and
315 | * 'minimal-pubkey-size'. To unify appearance we choose to distinguish
316 | * them by suffix referring to the public key type, more specifically
317 | * _pk_in_g1 corresponds to 'minimal-pubkey-size' and _pk_in_g2 - to
318 | * 'minimal-signature-size'. It might appear a bit counterintuitive
319 | * in sign call, but no matter how you twist it, something is bound to
320 | * turn a little odd.
321 | */
322 | /*
323 | * Secret-key operations.
324 | */
325 | void blst_keygen(blst_scalar *out_SK, const byte *IKM, size_t IKM_len,
326 | const byte *info DEFNULL, size_t info_len DEFNULL);
327 | void blst_sk_to_pk_in_g1(blst_p1 *out_pk, const blst_scalar *SK);
328 | void blst_sign_pk_in_g1(blst_p2 *out_sig, const blst_p2 *hash,
329 | const blst_scalar *SK);
330 | void blst_sk_to_pk_in_g2(blst_p2 *out_pk, const blst_scalar *SK);
331 | void blst_sign_pk_in_g2(blst_p1 *out_sig, const blst_p1 *hash,
332 | const blst_scalar *SK);
333 |
334 | /*
335 | * Pairing interface.
336 | */
337 | #ifndef SWIG
338 | void blst_miller_loop(blst_fp12 *ret, const blst_p2_affine *Q,
339 | const blst_p1_affine *P);
340 | void blst_final_exp(blst_fp12 *ret, const blst_fp12 *f);
341 | void blst_precompute_lines(blst_fp6 Qlines[68], const blst_p2_affine *Q);
342 | void blst_miller_loop_lines(blst_fp12 *ret, const blst_fp6 Qlines[68],
343 | const blst_p1_affine *P);
344 | bool blst_fp12_finalverify(const blst_fp12 *gt1, const blst_fp12 *gt2);
345 | #endif
346 |
347 | #ifdef __BLST_CGO__
348 | typedef limb_t blst_pairing;
349 | #elif defined(__BLST_RUST_BINDGEN__)
350 | typedef struct {} blst_pairing;
351 | #else
352 | typedef struct blst_opaque blst_pairing;
353 | #endif
354 |
355 | size_t blst_pairing_sizeof();
356 | void blst_pairing_init(blst_pairing *new_ctx, bool hash_or_encode,
357 | const byte *DST DEFNULL, size_t DST_len DEFNULL);
358 | const byte *blst_pairing_get_dst(const blst_pairing *ctx);
359 | void blst_pairing_commit(blst_pairing *ctx);
360 | BLST_ERROR blst_pairing_aggregate_pk_in_g2(blst_pairing *ctx,
361 | const blst_p2_affine *PK,
362 | const blst_p1_affine *signature,
363 | const byte *msg, size_t msg_len,
364 | const byte *aug DEFNULL,
365 | size_t aug_len DEFNULL);
366 | BLST_ERROR blst_pairing_chk_n_aggr_pk_in_g2(blst_pairing *ctx,
367 | const blst_p2_affine *PK,
368 | bool pk_grpchk,
369 | const blst_p1_affine *signature,
370 | bool sig_grpchk,
371 | const byte *msg, size_t msg_len,
372 | const byte *aug DEFNULL,
373 | size_t aug_len DEFNULL);
374 | BLST_ERROR blst_pairing_mul_n_aggregate_pk_in_g2(blst_pairing *ctx,
375 | const blst_p2_affine *PK,
376 | const blst_p1_affine *sig,
377 | const byte *scalar,
378 | size_t nbits,
379 | const byte *msg,
380 | size_t msg_len,
381 | const byte *aug DEFNULL,
382 | size_t aug_len DEFNULL);
383 | BLST_ERROR blst_pairing_chk_n_mul_n_aggr_pk_in_g2(blst_pairing *ctx,
384 | const blst_p2_affine *PK,
385 | bool pk_grpchk,
386 | const blst_p1_affine *sig,
387 | bool sig_grpchk,
388 | const byte *scalar,
389 | size_t nbits,
390 | const byte *msg,
391 | size_t msg_len,
392 | const byte *aug DEFNULL,
393 | size_t aug_len DEFNULL);
394 | BLST_ERROR blst_pairing_aggregate_pk_in_g1(blst_pairing *ctx,
395 | const blst_p1_affine *PK,
396 | const blst_p2_affine *signature,
397 | const byte *msg, size_t msg_len,
398 | const byte *aug DEFNULL,
399 | size_t aug_len DEFNULL);
400 | BLST_ERROR blst_pairing_chk_n_aggr_pk_in_g1(blst_pairing *ctx,
401 | const blst_p1_affine *PK,
402 | bool pk_grpchk,
403 | const blst_p2_affine *signature,
404 | bool sig_grpchk,
405 | const byte *msg, size_t msg_len,
406 | const byte *aug DEFNULL,
407 | size_t aug_len DEFNULL);
408 | BLST_ERROR blst_pairing_mul_n_aggregate_pk_in_g1(blst_pairing *ctx,
409 | const blst_p1_affine *PK,
410 | const blst_p2_affine *sig,
411 | const byte *scalar,
412 | size_t nbits,
413 | const byte *msg,
414 | size_t msg_len,
415 | const byte *aug DEFNULL,
416 | size_t aug_len DEFNULL);
417 | BLST_ERROR blst_pairing_chk_n_mul_n_aggr_pk_in_g1(blst_pairing *ctx,
418 | const blst_p1_affine *PK,
419 | bool pk_grpchk,
420 | const blst_p2_affine *sig,
421 | bool sig_grpchk,
422 | const byte *scalar,
423 | size_t nbits,
424 | const byte *msg,
425 | size_t msg_len,
426 | const byte *aug DEFNULL,
427 | size_t aug_len DEFNULL);
428 | BLST_ERROR blst_pairing_merge(blst_pairing *ctx, const blst_pairing *ctx1);
429 | bool blst_pairing_finalverify(const blst_pairing *ctx,
430 | const blst_fp12 *gtsig DEFNULL);
431 |
432 |
433 | /*
434 | * Customarily applications aggregate signatures separately.
435 | * In which case application would have to pass NULLs for |signature|
436 | * to blst_pairing_aggregate calls and pass aggregated signature
437 | * collected with these calls to blst_pairing_finalverify. Inputs are
438 | * Zcash-compatible "straight-from-wire" byte vectors, compressed or
439 | * not.
440 | */
441 | BLST_ERROR blst_aggregate_in_g1(blst_p1 *out, const blst_p1 *in,
442 | const byte *zwire);
443 | BLST_ERROR blst_aggregate_in_g2(blst_p2 *out, const blst_p2 *in,
444 | const byte *zwire);
445 |
446 | void blst_aggregated_in_g1(blst_fp12 *out, const blst_p1_affine *signature);
447 | void blst_aggregated_in_g2(blst_fp12 *out, const blst_p2_affine *signature);
448 |
449 | /*
450 | * "One-shot" CoreVerify entry points.
451 | */
452 | BLST_ERROR blst_core_verify_pk_in_g1(const blst_p1_affine *pk,
453 | const blst_p2_affine *signature,
454 | bool hash_or_encode,
455 | const byte *msg, size_t msg_len,
456 | const byte *DST DEFNULL,
457 | size_t DST_len DEFNULL,
458 | const byte *aug DEFNULL,
459 | size_t aug_len DEFNULL);
460 | BLST_ERROR blst_core_verify_pk_in_g2(const blst_p2_affine *pk,
461 | const blst_p1_affine *signature,
462 | bool hash_or_encode,
463 | const byte *msg, size_t msg_len,
464 | const byte *DST DEFNULL,
465 | size_t DST_len DEFNULL,
466 | const byte *aug DEFNULL,
467 | size_t aug_len DEFNULL);
468 |
469 | extern const blst_p1_affine BLS12_381_G1;
470 | extern const blst_p1_affine BLS12_381_NEG_G1;
471 | extern const blst_p2_affine BLS12_381_G2;
472 | extern const blst_p2_affine BLS12_381_NEG_G2;
473 |
474 | #include "blst_aux.h"
475 |
476 | #ifdef __cplusplus
477 | }
478 | #endif
479 | #endif
480 |
--------------------------------------------------------------------------------
/blst-lib/blst_aux.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Supranational LLC
3 | * Licensed under the Apache License, Version 2.0, see LICENSE for details.
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 | #ifndef __BLST_AUX_H__
7 | #define __BLST_AUX_H__
8 | /*
9 | * This file lists interfaces that might be promoted to blst.h or removed,
10 | * depending on their proven/unproven worthiness.
11 | */
12 |
13 | void blst_fr_to(blst_fr *ret, const blst_fr *a);
14 | void blst_fr_from(blst_fr *ret, const blst_fr *a);
15 |
16 | void blst_fp_to(blst_fp *ret, const blst_fp *a);
17 | void blst_fp_from(blst_fp *ret, const blst_fp *a);
18 |
19 | bool blst_fp_is_square(const blst_fp *a);
20 | bool blst_fp2_is_square(const blst_fp2 *a);
21 |
22 | void blst_p1_from_jacobian(blst_p1 *out, const blst_p1 *in);
23 | void blst_p2_from_jacobian(blst_p2 *out, const blst_p2 *in);
24 |
25 | /*
26 | * Below functions produce both point and deserialized outcome of
27 | * SkToPk and Sign. However, deserialized outputs are pre-decorated
28 | * with sign and infinity bits. This means that you have to bring the
29 | * output into compliance prior returning to application. If you want
30 | * compressed point value, then do [equivalent of]
31 | *
32 | * byte temp[96];
33 | * blst_sk_to_pk2_in_g1(temp, out_pk, SK);
34 | * temp[0] |= 0x80;
35 | * memcpy(out, temp, 48);
36 | *
37 | * Otherwise do
38 | *
39 | * blst_sk_to_pk2_in_g1(out, out_pk, SK);
40 | * out[0] &= ~0x20;
41 | *
42 | * Either |out| or |out_| can be NULL.
43 | */
44 | void blst_sk_to_pk2_in_g1(byte out[96], blst_p1_affine *out_pk,
45 | const blst_scalar *SK);
46 | void blst_sign_pk2_in_g1(byte out[192], blst_p2_affine *out_sig,
47 | const blst_p2 *hash, const blst_scalar *SK);
48 | void blst_sk_to_pk2_in_g2(byte out[192], blst_p2_affine *out_pk,
49 | const blst_scalar *SK);
50 | void blst_sign_pk2_in_g2(byte out[96], blst_p1_affine *out_sig,
51 | const blst_p1 *hash, const blst_scalar *SK);
52 |
53 | typedef struct {} blst_uniq;
54 |
55 | size_t blst_uniq_sizeof(size_t n_nodes);
56 | void blst_uniq_init(blst_uniq *tree);
57 | bool blst_uniq_test(blst_uniq *tree, const byte *msg, size_t len);
58 |
59 | #ifdef expand_message_xmd
60 | void expand_message_xmd(unsigned char *bytes, size_t len_in_bytes,
61 | const unsigned char *aug, size_t aug_len,
62 | const unsigned char *msg, size_t msg_len,
63 | const unsigned char *DST, size_t DST_len);
64 | #else
65 | void blst_expand_message_xmd(byte *out, size_t out_len,
66 | const byte *msg, size_t msg_len,
67 | const byte *DST, size_t DST_len);
68 | #endif
69 |
70 | void blst_p1_unchecked_mult(blst_p1 *out, const blst_p1 *p, const byte *scalar,
71 | size_t nbits);
72 | void blst_p2_unchecked_mult(blst_p2 *out, const blst_p2 *p, const byte *scalar,
73 | size_t nbits);
74 |
75 | void blst_pairing_raw_aggregate(blst_pairing *ctx, const blst_p2_affine *q,
76 | const blst_p1_affine *p);
77 | blst_fp12 *blst_pairing_as_fp12(blst_pairing *ctx);
78 | void blst_bendian_from_fp12(byte out[48*12], const blst_fp12 *a);
79 |
80 | void blst_keygen_v3(blst_scalar *out_SK, const byte *IKM, size_t IKM_len,
81 | const byte *info DEFNULL, size_t info_len DEFNULL);
82 | void blst_keygen_v4_5(blst_scalar *out_SK, const byte *IKM, size_t IKM_len,
83 | const byte *salt, size_t salt_len,
84 | const byte *info DEFNULL, size_t info_len DEFNULL);
85 | void blst_keygen_v5(blst_scalar *out_SK, const byte *IKM, size_t IKM_len,
86 | const byte *salt, size_t salt_len,
87 | const byte *info DEFNULL, size_t info_len DEFNULL);
88 | void blst_derive_master_eip2333(blst_scalar *out_SK,
89 | const byte *IKM, size_t IKM_len);
90 | void blst_derive_child_eip2333(blst_scalar *out_SK, const blst_scalar *SK,
91 | uint32_t child_index);
92 |
93 | void blst_scalar_from_hexascii(blst_scalar *out, const byte *hex);
94 | void blst_fr_from_hexascii(blst_fr *ret, const byte *hex);
95 | void blst_fp_from_hexascii(blst_fp *ret, const byte *hex);
96 |
97 | size_t blst_p1_sizeof();
98 | size_t blst_p1_affine_sizeof();
99 | size_t blst_p2_sizeof();
100 | size_t blst_p2_affine_sizeof();
101 | size_t blst_fp12_sizeof();
102 | #endif
103 |
--------------------------------------------------------------------------------
/blst-lib/darwin-arm64/libblst.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lidofinance/lido-python-sdk/4820b3106311fc39a62483948e62907b140bf060/blst-lib/darwin-arm64/libblst.a
--------------------------------------------------------------------------------
/blst-lib/darwin/libblst.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lidofinance/lido-python-sdk/4820b3106311fc39a62483948e62907b140bf060/blst-lib/darwin/libblst.a
--------------------------------------------------------------------------------
/blst-lib/linux/libblst.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lidofinance/lido-python-sdk/4820b3106311fc39a62483948e62907b140bf060/blst-lib/linux/libblst.a
--------------------------------------------------------------------------------
/blst-lib/windows/blst.lib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lidofinance/lido-python-sdk/4820b3106311fc39a62483948e62907b140bf060/blst-lib/windows/blst.lib
--------------------------------------------------------------------------------
/cpuinfo.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | if (grep -q -e '^flags.*\badx\b' /proc/cpuinfo) 2>/dev/null; then
4 | echo "[present] adx"
5 | else
6 | echo "[absent] adx"
7 | fi
8 | if (grep -q -e '^flags.*\bavx\b' /proc/cpuinfo) 2>/dev/null; then
9 | echo "[present] avx"
10 | else
11 | echo "[absent] avx"
12 | fi
13 | if (grep -q -e '^flags.*\bavx2\b' /proc/cpuinfo) 2>/dev/null; then
14 | echo "[present] avx2"
15 | else
16 | echo "[absent] avx2"
17 | fi
18 |
--------------------------------------------------------------------------------
/lido_sdk/__init__.py:
--------------------------------------------------------------------------------
1 | from lido_sdk.lido import Lido
2 |
--------------------------------------------------------------------------------
/lido_sdk/blstverify/__init__.py:
--------------------------------------------------------------------------------
1 | from lido_sdk.blstverify.verifier import verify
2 |
--------------------------------------------------------------------------------
/lido_sdk/blstverify/blst.py:
--------------------------------------------------------------------------------
1 | # This file was automatically generated by SWIG (http://www.swig.org).
2 | # Version 4.0.2
3 | #
4 | # Do not make changes to this file unless you know what you are doing--modify
5 | # the SWIG interface file instead.
6 |
7 | from sys import version_info as _swig_python_version_info
8 | if _swig_python_version_info < (2, 7, 0):
9 | raise RuntimeError("Python 2.7 or later required")
10 |
11 | # Import the low-level C/C++ module
12 | if __package__ or "." in __name__:
13 | from . import _blst
14 | else:
15 | import _blst
16 |
17 | try:
18 | import builtins as __builtin__
19 | except ImportError:
20 | import __builtin__
21 |
22 | _swig_new_instance_method = _blst.SWIG_PyInstanceMethod_New
23 | _swig_new_static_method = _blst.SWIG_PyStaticMethod_New
24 |
25 | def _swig_repr(self):
26 | try:
27 | strthis = "proxy of " + self.this.__repr__()
28 | except __builtin__.Exception:
29 | strthis = ""
30 | return "<%s.%s; %s >" % (self.__class__.__module__, self.__class__.__name__, strthis,)
31 |
32 |
33 | def _swig_setattr_nondynamic_instance_variable(set):
34 | def set_instance_attr(self, name, value):
35 | if name == "thisown":
36 | self.this.own(value)
37 | elif name == "this":
38 | set(self, name, value)
39 | elif hasattr(self, name) and isinstance(getattr(type(self), name), property):
40 | set(self, name, value)
41 | else:
42 | raise AttributeError("You cannot add instance attributes to %s" % self)
43 | return set_instance_attr
44 |
45 |
46 | def _swig_setattr_nondynamic_class_variable(set):
47 | def set_class_attr(cls, name, value):
48 | if hasattr(cls, name) and not isinstance(getattr(cls, name), property):
49 | set(cls, name, value)
50 | else:
51 | raise AttributeError("You cannot add class attributes to %s" % cls)
52 | return set_class_attr
53 |
54 |
55 | def _swig_add_metaclass(metaclass):
56 | """Class decorator for adding a metaclass to a SWIG wrapped class - a slimmed down version of six.add_metaclass"""
57 | def wrapper(cls):
58 | return metaclass(cls.__name__, cls.__bases__, cls.__dict__.copy())
59 | return wrapper
60 |
61 |
62 | class _SwigNonDynamicMeta(type):
63 | """Meta class to enforce nondynamic attributes (no new attributes) for a class"""
64 | __setattr__ = _swig_setattr_nondynamic_class_variable(type.__setattr__)
65 |
66 |
67 | BLST_SUCCESS = _blst.BLST_SUCCESS
68 | BLST_BAD_ENCODING = _blst.BLST_BAD_ENCODING
69 | BLST_POINT_NOT_ON_CURVE = _blst.BLST_POINT_NOT_ON_CURVE
70 | BLST_POINT_NOT_IN_GROUP = _blst.BLST_POINT_NOT_IN_GROUP
71 | BLST_AGGR_TYPE_MISMATCH = _blst.BLST_AGGR_TYPE_MISMATCH
72 | BLST_VERIFY_FAIL = _blst.BLST_VERIFY_FAIL
73 | BLST_PK_IS_INFINITY = _blst.BLST_PK_IS_INFINITY
74 | class SecretKey(object):
75 | thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
76 | __repr__ = _swig_repr
77 | keygen = _swig_new_instance_method(_blst.SecretKey_keygen)
78 | keygen_v3 = _swig_new_instance_method(_blst.SecretKey_keygen_v3)
79 | keygen_v4_5 = _swig_new_instance_method(_blst.SecretKey_keygen_v4_5)
80 | keygen_v5 = _swig_new_instance_method(_blst.SecretKey_keygen_v5)
81 | derive_master_eip2333 = _swig_new_instance_method(_blst.SecretKey_derive_master_eip2333)
82 | derive_child_eip2333 = _swig_new_instance_method(_blst.SecretKey_derive_child_eip2333)
83 | from_bendian = _swig_new_instance_method(_blst.SecretKey_from_bendian)
84 | from_lendian = _swig_new_instance_method(_blst.SecretKey_from_lendian)
85 | to_bendian = _swig_new_instance_method(_blst.SecretKey_to_bendian)
86 | to_lendian = _swig_new_instance_method(_blst.SecretKey_to_lendian)
87 |
88 | def __init__(self):
89 | _blst.SecretKey_swiginit(self, _blst.new_SecretKey())
90 | __swig_destroy__ = _blst.delete_SecretKey
91 |
92 | # Register SecretKey in _blst:
93 | _blst.SecretKey_swigregister(SecretKey)
94 |
95 | class Scalar(object):
96 | thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
97 | __repr__ = _swig_repr
98 |
99 | def __init__(self, *args):
100 | _blst.Scalar_swiginit(self, _blst.new_Scalar(*args))
101 | dup = _swig_new_instance_method(_blst.Scalar_dup)
102 | from_bendian = _swig_new_instance_method(_blst.Scalar_from_bendian)
103 | from_lendian = _swig_new_instance_method(_blst.Scalar_from_lendian)
104 | to_bendian = _swig_new_instance_method(_blst.Scalar_to_bendian)
105 | to_lendian = _swig_new_instance_method(_blst.Scalar_to_lendian)
106 | add = _swig_new_instance_method(_blst.Scalar_add)
107 | sub = _swig_new_instance_method(_blst.Scalar_sub)
108 | mul = _swig_new_instance_method(_blst.Scalar_mul)
109 | inverse = _swig_new_instance_method(_blst.Scalar_inverse)
110 | __swig_destroy__ = _blst.delete_Scalar
111 |
112 | # Register Scalar in _blst:
113 | _blst.Scalar_swigregister(Scalar)
114 |
115 | class P1_Affine(object):
116 | thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
117 | __repr__ = _swig_repr
118 |
119 | def __init__(self, *args):
120 | _blst.P1_Affine_swiginit(self, _blst.new_P1_Affine(*args))
121 | dup = _swig_new_instance_method(_blst.P1_Affine_dup)
122 | to_jacobian = _swig_new_instance_method(_blst.P1_Affine_to_jacobian)
123 | serialize = _swig_new_instance_method(_blst.P1_Affine_serialize)
124 | compress = _swig_new_instance_method(_blst.P1_Affine_compress)
125 | on_curve = _swig_new_instance_method(_blst.P1_Affine_on_curve)
126 | in_group = _swig_new_instance_method(_blst.P1_Affine_in_group)
127 | is_inf = _swig_new_instance_method(_blst.P1_Affine_is_inf)
128 | is_equal = _swig_new_instance_method(_blst.P1_Affine_is_equal)
129 | core_verify = _swig_new_instance_method(_blst.P1_Affine_core_verify)
130 | generator = _swig_new_static_method(_blst.P1_Affine_generator)
131 | __swig_destroy__ = _blst.delete_P1_Affine
132 |
133 | # Register P1_Affine in _blst:
134 | _blst.P1_Affine_swigregister(P1_Affine)
135 | P1_Affine_generator = _blst.P1_Affine_generator
136 |
137 | class P1(object):
138 | thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
139 | __repr__ = _swig_repr
140 |
141 | def __init__(self, *args):
142 | _blst.P1_swiginit(self, _blst.new_P1(*args))
143 | dup = _swig_new_instance_method(_blst.P1_dup)
144 | to_affine = _swig_new_instance_method(_blst.P1_to_affine)
145 | serialize = _swig_new_instance_method(_blst.P1_serialize)
146 | compress = _swig_new_instance_method(_blst.P1_compress)
147 | on_curve = _swig_new_instance_method(_blst.P1_on_curve)
148 | in_group = _swig_new_instance_method(_blst.P1_in_group)
149 | is_inf = _swig_new_instance_method(_blst.P1_is_inf)
150 | is_equal = _swig_new_instance_method(_blst.P1_is_equal)
151 | aggregate = _swig_new_instance_method(_blst.P1_aggregate)
152 | sign_with = _swig_new_instance_method(_blst.P1_sign_with)
153 | hash_to = _swig_new_instance_method(_blst.P1_hash_to)
154 | encode_to = _swig_new_instance_method(_blst.P1_encode_to)
155 | mult = _swig_new_instance_method(_blst.P1_mult)
156 | cneg = _swig_new_instance_method(_blst.P1_cneg)
157 | neg = _swig_new_instance_method(_blst.P1_neg)
158 | add = _swig_new_instance_method(_blst.P1_add)
159 | dbl = _swig_new_instance_method(_blst.P1_dbl)
160 | generator = _swig_new_static_method(_blst.P1_generator)
161 | __swig_destroy__ = _blst.delete_P1
162 |
163 | # Register P1 in _blst:
164 | _blst.P1_swigregister(P1)
165 | P1_generator = _blst.P1_generator
166 |
167 | class P1_Affines(object):
168 | thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
169 |
170 | def __init__(self, *args, **kwargs):
171 | raise AttributeError("No constructor defined")
172 | __repr__ = _swig_repr
173 | mult_pippenger = _swig_new_static_method(_blst.P1_Affines_mult_pippenger)
174 | add = _swig_new_static_method(_blst.P1_Affines_add)
175 | as_memory = _swig_new_static_method(_blst.P1_Affines_as_memory)
176 |
177 | # Register P1_Affines in _blst:
178 | _blst.P1_Affines_swigregister(P1_Affines)
179 | P1_Affines_mult_pippenger = _blst.P1_Affines_mult_pippenger
180 | P1_Affines_add = _blst.P1_Affines_add
181 | P1_Affines_as_memory = _blst.P1_Affines_as_memory
182 |
183 | class P2_Affine(object):
184 | thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
185 | __repr__ = _swig_repr
186 |
187 | def __init__(self, *args):
188 | _blst.P2_Affine_swiginit(self, _blst.new_P2_Affine(*args))
189 | dup = _swig_new_instance_method(_blst.P2_Affine_dup)
190 | to_jacobian = _swig_new_instance_method(_blst.P2_Affine_to_jacobian)
191 | serialize = _swig_new_instance_method(_blst.P2_Affine_serialize)
192 | compress = _swig_new_instance_method(_blst.P2_Affine_compress)
193 | on_curve = _swig_new_instance_method(_blst.P2_Affine_on_curve)
194 | in_group = _swig_new_instance_method(_blst.P2_Affine_in_group)
195 | is_inf = _swig_new_instance_method(_blst.P2_Affine_is_inf)
196 | is_equal = _swig_new_instance_method(_blst.P2_Affine_is_equal)
197 | core_verify = _swig_new_instance_method(_blst.P2_Affine_core_verify)
198 | generator = _swig_new_static_method(_blst.P2_Affine_generator)
199 | __swig_destroy__ = _blst.delete_P2_Affine
200 |
201 | # Register P2_Affine in _blst:
202 | _blst.P2_Affine_swigregister(P2_Affine)
203 | P2_Affine_generator = _blst.P2_Affine_generator
204 |
205 | class P2(object):
206 | thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
207 | __repr__ = _swig_repr
208 |
209 | def __init__(self, *args):
210 | _blst.P2_swiginit(self, _blst.new_P2(*args))
211 | dup = _swig_new_instance_method(_blst.P2_dup)
212 | to_affine = _swig_new_instance_method(_blst.P2_to_affine)
213 | serialize = _swig_new_instance_method(_blst.P2_serialize)
214 | compress = _swig_new_instance_method(_blst.P2_compress)
215 | on_curve = _swig_new_instance_method(_blst.P2_on_curve)
216 | in_group = _swig_new_instance_method(_blst.P2_in_group)
217 | is_inf = _swig_new_instance_method(_blst.P2_is_inf)
218 | is_equal = _swig_new_instance_method(_blst.P2_is_equal)
219 | aggregate = _swig_new_instance_method(_blst.P2_aggregate)
220 | sign_with = _swig_new_instance_method(_blst.P2_sign_with)
221 | hash_to = _swig_new_instance_method(_blst.P2_hash_to)
222 | encode_to = _swig_new_instance_method(_blst.P2_encode_to)
223 | mult = _swig_new_instance_method(_blst.P2_mult)
224 | cneg = _swig_new_instance_method(_blst.P2_cneg)
225 | neg = _swig_new_instance_method(_blst.P2_neg)
226 | add = _swig_new_instance_method(_blst.P2_add)
227 | dbl = _swig_new_instance_method(_blst.P2_dbl)
228 | generator = _swig_new_static_method(_blst.P2_generator)
229 | __swig_destroy__ = _blst.delete_P2
230 |
231 | # Register P2 in _blst:
232 | _blst.P2_swigregister(P2)
233 | P2_generator = _blst.P2_generator
234 |
235 | class P2_Affines(object):
236 | thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
237 |
238 | def __init__(self, *args, **kwargs):
239 | raise AttributeError("No constructor defined")
240 | __repr__ = _swig_repr
241 | mult_pippenger = _swig_new_static_method(_blst.P2_Affines_mult_pippenger)
242 | add = _swig_new_static_method(_blst.P2_Affines_add)
243 | as_memory = _swig_new_static_method(_blst.P2_Affines_as_memory)
244 |
245 | # Register P2_Affines in _blst:
246 | _blst.P2_Affines_swigregister(P2_Affines)
247 | P2_Affines_mult_pippenger = _blst.P2_Affines_mult_pippenger
248 | P2_Affines_add = _blst.P2_Affines_add
249 | P2_Affines_as_memory = _blst.P2_Affines_as_memory
250 |
251 | G1 = _blst.G1
252 | G2 = _blst.G2
253 | class PT(object):
254 | thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
255 | __repr__ = _swig_repr
256 |
257 | def __init__(self, *args):
258 | _blst.PT_swiginit(self, _blst.new_PT(*args))
259 | dup = _swig_new_instance_method(_blst.PT_dup)
260 | is_one = _swig_new_instance_method(_blst.PT_is_one)
261 | is_equal = _swig_new_instance_method(_blst.PT_is_equal)
262 | sqr = _swig_new_instance_method(_blst.PT_sqr)
263 | mul = _swig_new_instance_method(_blst.PT_mul)
264 | final_exp = _swig_new_instance_method(_blst.PT_final_exp)
265 | in_group = _swig_new_instance_method(_blst.PT_in_group)
266 | to_bendian = _swig_new_instance_method(_blst.PT_to_bendian)
267 | finalverify = _swig_new_static_method(_blst.PT_finalverify)
268 | one = _swig_new_static_method(_blst.PT_one)
269 | __swig_destroy__ = _blst.delete_PT
270 |
271 | # Register PT in _blst:
272 | _blst.PT_swigregister(PT)
273 | PT_finalverify = _blst.PT_finalverify
274 | PT_one = _blst.PT_one
275 |
276 | class Pairing(object):
277 | thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
278 | __repr__ = _swig_repr
279 |
280 | def __init__(self, hash_or_encode, DST):
281 | _blst.Pairing_swiginit(self, _blst.new_Pairing(hash_or_encode, DST))
282 | __swig_destroy__ = _blst.delete_Pairing
283 | aggregate = _swig_new_instance_method(_blst.Pairing_aggregate)
284 | mul_n_aggregate = _swig_new_instance_method(_blst.Pairing_mul_n_aggregate)
285 | commit = _swig_new_instance_method(_blst.Pairing_commit)
286 | merge = _swig_new_instance_method(_blst.Pairing_merge)
287 | finalverify = _swig_new_instance_method(_blst.Pairing_finalverify)
288 | raw_aggregate = _swig_new_instance_method(_blst.Pairing_raw_aggregate)
289 | as_fp12 = _swig_new_instance_method(_blst.Pairing_as_fp12)
290 |
291 | # Register Pairing in _blst:
292 | _blst.Pairing_swigregister(Pairing)
293 |
294 | cdata = _blst.cdata
295 | memmove = _blst.memmove
296 |
297 | cvar = _blst.cvar
298 | BLS12_381_G1 = cvar.BLS12_381_G1
299 | BLS12_381_NEG_G1 = cvar.BLS12_381_NEG_G1
300 | BLS12_381_G2 = cvar.BLS12_381_G2
301 | BLS12_381_NEG_G2 = cvar.BLS12_381_NEG_G2
302 |
303 |
--------------------------------------------------------------------------------
/lido_sdk/blstverify/test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 | import sys
5 | import re
6 | import hashlib
7 |
8 |
9 | print("testing...") or sys.stdout.flush()
10 | ########################################################################
11 | import blst
12 |
13 | msg = b"assertion" # this what we're signing
14 | DST = b"MY-DST" # domain separation tag
15 |
16 | SK = blst.SecretKey()
17 | SK.keygen(b"*"*32) # secret key
18 |
19 | ########################################################################
20 | # generate public key and signature
21 |
22 | pk_for_wire = blst.P1(SK).serialize()
23 |
24 | # # optional vvvvvvvvvvv augmentation
25 | sig_for_wire = blst.P2().hash_to(msg, DST, pk_for_wire) \
26 | .sign_with(SK) \
27 | .serialize()
28 |
29 | ########################################################################
30 | # at this point 'pk_for_wire', 'sig_for_wire' and 'msg' are
31 | # "sent over network," so now on "receiver" side
32 |
33 | sig = blst.P2_Affine(sig_for_wire)
34 | pk = blst.P1_Affine(pk_for_wire)
35 | if not pk.in_group(): # vet the public key
36 | raise AssertionError("disaster")
37 | ctx = blst.Pairing(True, DST)
38 | ctx.aggregate(pk, sig, msg, pk_for_wire)
39 | ctx.commit()
40 | if not ctx.finalverify():
41 | raise AssertionError("disaster")
42 |
43 | ########################################################################
44 | # generate public key and signature
45 |
46 | pk_for_wire = blst.P2(SK).serialize()
47 |
48 | # # optional vvvvvvvvvvv augmentation
49 | sig_for_wire = blst.P1().hash_to(msg, DST, pk_for_wire) \
50 | .sign_with(SK) \
51 | .serialize()
52 |
53 | ########################################################################
54 | # at this point 'pk_for_wire', 'sig_for_wire' and 'msg' are
55 | # "sent over network," so now on "receiver" side
56 |
57 | sig = blst.P1_Affine(sig_for_wire)
58 | pk = blst.P2_Affine(pk_for_wire)
59 | if not pk.in_group(): # vet the public key
60 | raise AssertionError("disaster")
61 | ctx = blst.Pairing(True, DST)
62 | ctx.aggregate(pk, sig, msg, pk_for_wire)
63 | ctx.commit()
64 | if not ctx.finalverify():
65 | raise AssertionError("disaster")
66 |
67 | if sys.version_info.major < 3:
68 | print("OK")
69 | sys.exit(0)
70 |
71 | ########################################################################
72 | # from https://github.com/supranational/blst/issues/5
73 |
74 | pk_for_wire = bytes.fromhex("ab10fc693d038b73d67279127501a05f0072cbb7147c68650ef6ac4e0a413e5cabd1f35c8711e1f7d9d885bbc3b8eddc")
75 | sig_for_wire = bytes.fromhex("a44158c08c8c584477770feec2afa24d5a0b0bab2800414cb9efbb37c40339b6318c9349dad8de27ae644376d71232580ff5102c7a8579a6d2627c6e40b0ced737a60c66c7ebd377c04bf5ac957bf05bc8b6b09fbd7bdd2a7fa1090b5a0760bb")
76 | msg = bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000")
77 | DST = bytes.fromhex("424c535f5349475f424c53313233383147325f584d443a5348412d3235365f535357555f524f5f504f505f")
78 |
79 | sig = blst.P2_Affine(sig_for_wire)
80 | pk = blst.P1_Affine(pk_for_wire)
81 | if not pk.in_group(): # vet the public key
82 | raise AssertionError("disaster")
83 | if sig.core_verify(pk, True, msg, DST) != blst.BLST_SUCCESS:
84 | raise AssertionError("disaster")
85 |
86 | ########################################################################
87 | # test vectors from draft-irtf-cfrg-hash-to-curve
88 |
89 | try:
90 | import json
91 | import glob
92 |
93 | coord = re.compile(r'0x([0-9a-f]+)(?:\s*,\s*0x([0-9a-f]+))?', re.IGNORECASE)
94 |
95 | def serialize_json_point(P):
96 | ret = b''
97 | x = coord.search(P['x'])
98 | if x.group(2):
99 | ret += bytes.fromhex(x.group(2))
100 | ret += bytes.fromhex(x.group(1))
101 | y = coord.search(P['y'])
102 | if y.group(2):
103 | ret += bytes.fromhex(y.group(2))
104 | ret += bytes.fromhex(y.group(1))
105 | return ret
106 |
107 | for file in glob.glob("../vectors/hash_to_curve/BLS12381G*.json"):
108 | print(file)
109 | data = json.load(open(file))
110 |
111 | if data['curve'] == "BLS12-381 G1":
112 | point = blst.P1()
113 | else:
114 | point = blst.P2()
115 |
116 | DST = bytes(data['dst'], 'ascii')
117 |
118 | if data['randomOracle']:
119 | func = "point.hash_to(msg, DST).serialize()"
120 | else:
121 | func = "point.encode_to(msg, DST).serialize()"
122 |
123 | for vec in data['vectors']:
124 | msg = bytes(vec['msg'], 'ascii')
125 | if eval(func) != serialize_json_point(vec['P']):
126 | raise AssertionError(msg)
127 | except ModuleNotFoundError:
128 | print("skipping hash-to-curve KATs")
129 |
130 | ########################################################################
131 | # test multi-scalar multiplication for self-consistency
132 |
133 | points = []
134 | scalars = []
135 | total = 0
136 | for _ in range(0, 42):
137 | p = os.urandom(8)
138 | s = int.from_bytes(os.urandom(8), "big")
139 | points.append(blst.G1().mult(p))
140 | scalars.append(s)
141 | total += s * int.from_bytes(p, "little")
142 | a = blst.P1_Affines.mult_pippenger(blst.P1_Affines.as_memory(points), scalars)
143 | if not a.is_equal(blst.G1().mult(total)):
144 | raise AssertionError("disaster")
145 |
146 | points = []
147 | scalars = []
148 | total = 0
149 | for _ in range(0, 42):
150 | p = os.urandom(8)
151 | s = int.from_bytes(os.urandom(8), "big")
152 | points.append(blst.G2().mult(p))
153 | scalars.append(s)
154 | total += s * int.from_bytes(p, "little")
155 | a = blst.P2_Affines.mult_pippenger(blst.P2_Affines.as_memory(points), scalars)
156 | if not a.is_equal(blst.G2().mult(total)):
157 | raise AssertionError("disaster")
158 |
159 | ########################################################################
160 | # rudimentary blind signature PoC
161 |
162 | # Signer's public key, implicitly trusted.
163 | PK = blst.P1(SK).to_affine()
164 |
165 | # User wants to have |msg| signed,
166 | H_msg = blst.P2().hash_to(msg, DST, PK.compress())
167 | # chooses random |r|,
168 | r = blst.Scalar().from_bendian(os.urandom(32)) # should be PRF in real life...
169 | # blinds the H(|msg|) with |r| and sends it to the Signer.
170 | msg_for_wire = H_msg.dup().sign_with(r).serialize()
171 |
172 | # Signer signs and sends the result back to the User.
173 | blind_msg = blst.P2(msg_for_wire)
174 | if not blind_msg.in_group(): # is User messing with Signer?
175 | raise AssertionError("disaster")
176 | sig_for_wire = blind_msg.sign_with(SK).serialize()
177 |
178 | # User ...
179 | signature = blst.P2(sig_for_wire)
180 | if not signature.in_group(): # is Signer messing with User?
181 | raise AssertionError("disaster")
182 | # unblinds the result with 1/|r| to produce the actual |signature|,
183 | signature = signature.sign_with(r.inverse()).to_affine()
184 | # and double-checks if the Signer was honest?
185 | C1 = blst.PT(signature)
186 | C2 = blst.PT(H_msg.to_affine(), PK)
187 | if not blst.PT.finalverify(C1, C2):
188 | raise AssertionError("disaster")
189 |
190 | # Now |signature| can be verified as any other, e.g....
191 | ctx = blst.Pairing(True, DST)
192 | ctx.aggregate(PK, signature, msg, PK.compress())
193 | ctx.commit()
194 | if not ctx.finalverify():
195 | raise AssertionError("disaster")
196 |
197 | ########################################################################
198 | # [a variant of] BBS+ PoC, https://eprint.iacr.org/2008/136.pdf
199 |
200 | # Signer's public key, implicitly trusted.
201 | w = blst.P2(SK)
202 | w_for_wire = w.compress()
203 |
204 | ### The block of messages
205 |
206 | msgs = ["assertion1", "assertion2", "assertion3", "assertionN"]
207 | m = []
208 | for msg in msgs:
209 | h = hashlib.sha512()
210 | h.update(msg.encode('utf-8'))
211 | m.append(blst.Scalar().from_bendian(h.digest()))
212 |
213 | ### Everybody involved:
214 |
215 | count = len(msgs)
216 | g = []
217 | for i in range(count+1):
218 | g.append(blst.P1().hash_to(b"\0" + i.to_bytes(4, "big") +
219 | b"\0" + count.to_bytes(4, "big"),
220 | DST, w_for_wire))
221 |
222 | ### Signing the block of messages.
223 |
224 | e = blst.Scalar().from_bendian(os.urandom(32)) # should be PRF in real life...
225 | s = blst.Scalar().from_bendian(os.urandom(32))
226 |
227 | a = blst.G1().add(g[0].dup().mult(s))
228 | for i in range(count):
229 | a.add(g[i+1].dup().mult(m[i]))
230 |
231 | A = a.sign_with(e.dup().add(SK).inverse()).to_affine()
232 |
233 | signature = [A, e, s] # serialize to application liking
234 |
235 | ### Signature Verification.
236 | # Verifier deserializes |signature|, recalculates |a| and verifies.
237 | A, e, s = signature # fake deserialization
238 | a = blst.G1().add(g[0].dup().mult(s))
239 | for i in range(count):
240 | a.add(g[i+1].dup().mult(m[i]))
241 |
242 | if not A.in_group():
243 | AssertionError("disaster")
244 | C1 = blst.PT(blst.G2().mult(e).add(w).to_affine(), A)
245 | C2 = blst.PT(a.to_affine())
246 | if not blst.PT.finalverify(C1, C2):
247 | raise AssertionError("disaster")
248 |
249 | ### Blind-signing Committed Block of Messages.
250 |
251 | # User creates |commitment| and sends it to Signer.
252 | s_prime = blst.Scalar().from_bendian(os.urandom(32))
253 | commitment = g[0].dup().sign_with(s_prime)
254 | for i in range(count):
255 | commitment.add(g[i+1].dup().mult(m[i]))
256 |
257 | # Signer challenges User with |c|.
258 | c = blst.Scalar().from_bendian(os.urandom(32))
259 | # User provides proof of commitment.
260 | s_tilde = blst.Scalar().from_bendian(os.urandom(32))
261 | ZK = g[0].dup().sign_with(s_tilde)
262 | r = [s_tilde.add(c.dup().mul(s_prime))]
263 | for i in range(count):
264 | r.append(blst.Scalar().from_bendian(os.urandom(32)))
265 | ZK.add(g[i+1].dup().mult(r[-1]))
266 | r[-1].add(c.dup().mul(m[i]))
267 | # Signer verifies the proof, |ZK| and |r|.
268 | ZK_verify = commitment.dup().neg().mult(c)
269 | for i in range(count+1):
270 | ZK_verify.add(g[i].dup().mult(r[i]))
271 | if not ZK_verify.is_equal(ZK):
272 | raise AssertionError("disaster")
273 |
274 | # Signer signs the |commitment| and sends the |blind_signature| back.
275 | if not commitment.in_group(): # is User messing with Signer?
276 | raise AssertionError("disaster")
277 | e = blst.Scalar().from_bendian(os.urandom(32))
278 | s_pprime = blst.Scalar().from_bendian(os.urandom(32))
279 |
280 | A = blst.G1().add(g[0].dup().mult(s_pprime)) \
281 | .add(commitment) \
282 | .sign_with(e.dup().add(SK).inverse()).to_affine()
283 |
284 | blind_signature = [A, e, s_pprime] # serialize to application liking
285 |
286 | # User unblinds the |blind_signature| by adding |s_prime| and |s_pprime|.
287 | signature = blind_signature.copy() # fake deserialization
288 | if not signature[0].in_group(): # is Signer messing with User?
289 | raise AssertionError("disaster")
290 | signature[2].add(s_prime) # serialize to application liking
291 |
292 | ### Signature Verification.
293 | # Verifier deserializes |signature|, recalculates |a| and verifies.
294 | A, e, s = signature # fake deserialization
295 | a = blst.G1().add(g[0].dup().mult(s))
296 | for i in range(count):
297 | a.add(g[i+1].dup().mult(m[i]))
298 |
299 | if not A.in_group():
300 | raise AssertionError("disaster")
301 | C1 = blst.PT(blst.G2().mult(e).add(w).to_affine(), A)
302 | C2 = blst.PT(a.to_affine())
303 | if not blst.PT.finalverify(C1, C2):
304 | raise AssertionError("disaster")
305 |
306 | ########################################################################
307 | # low-order points
308 |
309 | p11 = blst.P1(bytes.fromhex("80803f0d09fec09a95f2ee7495323c15c162270c7cceaffa8566e941c66bcf206e72955d58b3b32e564de3209d672ca5"))
310 | if p11.in_group():
311 | raise AssertionError("disaster")
312 | if not p11.mult(11).is_inf():
313 | raise AssertionError("disaster")
314 |
315 | p10177 = blst.P1(bytes.fromhex("808717b0e03ef9e9d0503cd9ce355585d12274bac029dc22efdd69251267b97699cc426df4df5b39440f0945d45245b2"))
316 | if p10177.in_group():
317 | raise AssertionError("disaster")
318 | if not p10177.mult(10177).is_inf():
319 | raise AssertionError("disaster")
320 |
321 | p859267 = blst.P1(bytes.fromhex("8051cca76de8a5cf79a107c701064b894e500dffc532d573882bf2cd5b6e1b47e8842e83af679d10af9301b815501f89"))
322 | if p859267.in_group():
323 | raise AssertionError("disaster")
324 | if not p859267.mult(859267).is_inf():
325 | raise AssertionError("disaster")
326 |
327 | p13 = blst.P2(bytes.fromhex("808514e6f6c41ddb685cd8e351d7cb2ee891d54d5850d42c440365b15dd8ab06b53da9ebb22bc31cea2234d30499b09f01b290cae398ecc1fda3d4beb248af58bd351c3d99a5b6c770fdb54bf46b123261b55cf0762aeef2341e35e90608fc31"))
328 | if p13.in_group():
329 | raise AssertionError("disaster")
330 | if not p13.mult(13).is_inf():
331 | raise AssertionError("disaster")
332 |
333 | p23 = blst.P2(bytes.fromhex("acaaaa268d201642af7eeb2c46b03ecf8af71b902f652884577e52994047fcde2f8b5d931fdacb2575937b72ef2d3b0c0da315f7c31904614d8110d493d1d7a00da97d5b640be19e4cc5dd302ad8e17aa853035d7a56e5c24347164c186d6d00"))
334 | if p23.in_group():
335 | raise AssertionError("disaster")
336 | if not p23.mult(23).is_inf():
337 | raise AssertionError("disaster")
338 |
339 | p2713 = blst.P2(bytes.fromhex("8086d285d08693d80267eae43cf3c42e7afef039389870a294884f07ad86df364a3f62d426463e08d4d89b28370fbc3e1411846bb9982973c176431685ca1750d52ae2cf6095a42c726e8413ab7ab8c84eb83fd305ea7585f6c4e11aa4f00437"))
340 | if p2713.in_group():
341 | raise AssertionError("disaster")
342 | if not p2713.mult(2713).is_inf():
343 | raise AssertionError("disaster")
344 |
345 | ########################################################################
346 | # EIP-2333 tests
347 |
348 |
349 | def eip2333_test(seed, master_SK, child_index, child_SK):
350 | len = (seed.bit_length() + 7) // 8
351 | seed = seed.to_bytes(32 if len < 32 else len, "big")
352 | SK = blst.SecretKey()
353 | SK.derive_master_eip2333(seed)
354 | if master_SK != int.from_bytes(SK.to_bendian(), "big"):
355 | raise AssertionError("disaster")
356 | SK.derive_child_eip2333(SK, child_index)
357 | if child_SK != int.from_bytes(SK.to_bendian(), "big"):
358 | raise AssertionError("disaster")
359 |
360 |
361 | # test case 0
362 | seed = 0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04
363 | master_SK = 6083874454709270928345386274498605044986640685124978867557563392430687146096
364 | child_index = 0
365 | child_SK = 20397789859736650942317412262472558107875392172444076792671091975210932703118
366 | eip2333_test(seed, master_SK, child_index, child_SK)
367 |
368 | # test case 1
369 | seed = 0x3141592653589793238462643383279502884197169399375105820974944592
370 | master_SK = 29757020647961307431480504535336562678282505419141012933316116377660817309383
371 | child_index = 3141592653
372 | child_SK = 25457201688850691947727629385191704516744796114925897962676248250929345014287
373 | eip2333_test(seed, master_SK, child_index, child_SK)
374 |
375 | # test case 2
376 | seed = 0x0099FF991111002299DD7744EE3355BBDD8844115566CC55663355668888CC00
377 | master_SK = 27580842291869792442942448775674722299803720648445448686099262467207037398656
378 | child_index = 4294967295
379 | child_SK = 29358610794459428860402234341874281240803786294062035874021252734817515685787
380 | eip2333_test(seed, master_SK, child_index, child_SK)
381 |
382 | # test case 3
383 | seed = 0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3
384 | master_SK = 19022158461524446591288038168518313374041767046816487870552872741050760015818
385 | child_index = 42
386 | child_SK = 31372231650479070279774297061823572166496564838472787488249775572789064611981
387 | eip2333_test(seed, master_SK, child_index, child_SK)
388 |
389 | print("OK")
390 |
--------------------------------------------------------------------------------
/lido_sdk/blstverify/verifier.py:
--------------------------------------------------------------------------------
1 | from .blst import PT, Pairing, P1_Affine, P2_Affine, BLST_SUCCESS
2 |
3 | HASH_OR_ENCODE = True
4 | DST = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"
5 | VALID_TYPES = (bytes, bytearray)
6 |
7 |
8 | def verify(pubkey: bytes, message: bytes, signature: bytes) -> bool:
9 | if (
10 | (not isinstance(pubkey, VALID_TYPES))
11 | or (not isinstance(message, VALID_TYPES))
12 | or (not isinstance(signature, VALID_TYPES))
13 | ):
14 | return False
15 |
16 | try:
17 | pk_affine = P1_Affine(pubkey)
18 | sig_affine = P2_Affine(signature)
19 |
20 | ctx = Pairing(HASH_OR_ENCODE, DST)
21 | result = ctx.aggregate(pk_affine, sig_affine, message)
22 | if result != BLST_SUCCESS:
23 | return False
24 |
25 | ctx.commit()
26 | gtsig = PT(sig_affine)
27 |
28 | return ctx.finalverify(gtsig)
29 | except RuntimeError:
30 | return False
31 |
--------------------------------------------------------------------------------
/lido_sdk/config.py:
--------------------------------------------------------------------------------
1 | # Multicall default settings settings
2 | MULTICALL_MAX_BUNCH: int = 275
3 | MULTICALL_MAX_WORKERS: int = 6
4 | MULTICALL_MAX_RETRIES: int = 5
5 | MULTICALL_POOL_EXECUTOR_TIMEOUT: int = 30
6 | VALIDATE_POOL_EXECUTOR_TIMEOUT: int = 100
7 |
--------------------------------------------------------------------------------
/lido_sdk/contract/__init__.py:
--------------------------------------------------------------------------------
1 | from lido_sdk.contract.load_contract import LidoContract, NodeOpsContract
2 |
--------------------------------------------------------------------------------
/lido_sdk/contract/abi/Lido.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "constant": false,
4 | "inputs": [],
5 | "name": "resume",
6 | "outputs": [],
7 | "payable": false,
8 | "stateMutability": "nonpayable",
9 | "type": "function"
10 | },
11 | {
12 | "constant": true,
13 | "inputs": [],
14 | "name": "name",
15 | "outputs": [
16 | {
17 | "name": "",
18 | "type": "string"
19 | }
20 | ],
21 | "payable": false,
22 | "stateMutability": "pure",
23 | "type": "function"
24 | },
25 | {
26 | "constant": false,
27 | "inputs": [],
28 | "name": "stop",
29 | "outputs": [],
30 | "payable": false,
31 | "stateMutability": "nonpayable",
32 | "type": "function"
33 | },
34 | {
35 | "constant": true,
36 | "inputs": [],
37 | "name": "hasInitialized",
38 | "outputs": [
39 | {
40 | "name": "",
41 | "type": "bool"
42 | }
43 | ],
44 | "payable": false,
45 | "stateMutability": "view",
46 | "type": "function"
47 | },
48 | {
49 | "constant": false,
50 | "inputs": [
51 | {
52 | "name": "_spender",
53 | "type": "address"
54 | },
55 | {
56 | "name": "_amount",
57 | "type": "uint256"
58 | }
59 | ],
60 | "name": "approve",
61 | "outputs": [
62 | {
63 | "name": "",
64 | "type": "bool"
65 | }
66 | ],
67 | "payable": false,
68 | "stateMutability": "nonpayable",
69 | "type": "function"
70 | },
71 | {
72 | "constant": true,
73 | "inputs": [],
74 | "name": "STAKING_CONTROL_ROLE",
75 | "outputs": [
76 | {
77 | "name": "",
78 | "type": "bytes32"
79 | }
80 | ],
81 | "payable": false,
82 | "stateMutability": "view",
83 | "type": "function"
84 | },
85 | {
86 | "constant": true,
87 | "inputs": [],
88 | "name": "totalSupply",
89 | "outputs": [
90 | {
91 | "name": "",
92 | "type": "uint256"
93 | }
94 | ],
95 | "payable": false,
96 | "stateMutability": "view",
97 | "type": "function"
98 | },
99 | {
100 | "constant": true,
101 | "inputs": [
102 | {
103 | "name": "_ethAmount",
104 | "type": "uint256"
105 | }
106 | ],
107 | "name": "getSharesByPooledEth",
108 | "outputs": [
109 | {
110 | "name": "",
111 | "type": "uint256"
112 | }
113 | ],
114 | "payable": false,
115 | "stateMutability": "view",
116 | "type": "function"
117 | },
118 | {
119 | "constant": true,
120 | "inputs": [],
121 | "name": "isStakingPaused",
122 | "outputs": [
123 | {
124 | "name": "",
125 | "type": "bool"
126 | }
127 | ],
128 | "payable": false,
129 | "stateMutability": "view",
130 | "type": "function"
131 | },
132 | {
133 | "constant": false,
134 | "inputs": [
135 | {
136 | "name": "_sender",
137 | "type": "address"
138 | },
139 | {
140 | "name": "_recipient",
141 | "type": "address"
142 | },
143 | {
144 | "name": "_amount",
145 | "type": "uint256"
146 | }
147 | ],
148 | "name": "transferFrom",
149 | "outputs": [
150 | {
151 | "name": "",
152 | "type": "bool"
153 | }
154 | ],
155 | "payable": false,
156 | "stateMutability": "nonpayable",
157 | "type": "function"
158 | },
159 | {
160 | "constant": true,
161 | "inputs": [
162 | {
163 | "name": "_script",
164 | "type": "bytes"
165 | }
166 | ],
167 | "name": "getEVMScriptExecutor",
168 | "outputs": [
169 | {
170 | "name": "",
171 | "type": "address"
172 | }
173 | ],
174 | "payable": false,
175 | "stateMutability": "view",
176 | "type": "function"
177 | },
178 | {
179 | "constant": false,
180 | "inputs": [
181 | {
182 | "name": "_maxStakeLimit",
183 | "type": "uint256"
184 | },
185 | {
186 | "name": "_stakeLimitIncreasePerBlock",
187 | "type": "uint256"
188 | }
189 | ],
190 | "name": "setStakingLimit",
191 | "outputs": [],
192 | "payable": false,
193 | "stateMutability": "nonpayable",
194 | "type": "function"
195 | },
196 | {
197 | "constant": true,
198 | "inputs": [],
199 | "name": "RESUME_ROLE",
200 | "outputs": [
201 | {
202 | "name": "",
203 | "type": "bytes32"
204 | }
205 | ],
206 | "payable": false,
207 | "stateMutability": "view",
208 | "type": "function"
209 | },
210 | {
211 | "constant": false,
212 | "inputs": [
213 | {
214 | "name": "_lidoLocator",
215 | "type": "address"
216 | },
217 | {
218 | "name": "_eip712StETH",
219 | "type": "address"
220 | }
221 | ],
222 | "name": "finalizeUpgrade_v2",
223 | "outputs": [],
224 | "payable": false,
225 | "stateMutability": "nonpayable",
226 | "type": "function"
227 | },
228 | {
229 | "constant": true,
230 | "inputs": [],
231 | "name": "decimals",
232 | "outputs": [
233 | {
234 | "name": "",
235 | "type": "uint8"
236 | }
237 | ],
238 | "payable": false,
239 | "stateMutability": "pure",
240 | "type": "function"
241 | },
242 | {
243 | "constant": true,
244 | "inputs": [],
245 | "name": "getRecoveryVault",
246 | "outputs": [
247 | {
248 | "name": "",
249 | "type": "address"
250 | }
251 | ],
252 | "payable": false,
253 | "stateMutability": "view",
254 | "type": "function"
255 | },
256 | {
257 | "constant": true,
258 | "inputs": [],
259 | "name": "DOMAIN_SEPARATOR",
260 | "outputs": [
261 | {
262 | "name": "",
263 | "type": "bytes32"
264 | }
265 | ],
266 | "payable": false,
267 | "stateMutability": "view",
268 | "type": "function"
269 | },
270 | {
271 | "constant": true,
272 | "inputs": [],
273 | "name": "getTotalPooledEther",
274 | "outputs": [
275 | {
276 | "name": "",
277 | "type": "uint256"
278 | }
279 | ],
280 | "payable": false,
281 | "stateMutability": "view",
282 | "type": "function"
283 | },
284 | {
285 | "constant": false,
286 | "inputs": [
287 | {
288 | "name": "_newDepositedValidators",
289 | "type": "uint256"
290 | }
291 | ],
292 | "name": "unsafeChangeDepositedValidators",
293 | "outputs": [],
294 | "payable": false,
295 | "stateMutability": "nonpayable",
296 | "type": "function"
297 | },
298 | {
299 | "constant": true,
300 | "inputs": [],
301 | "name": "PAUSE_ROLE",
302 | "outputs": [
303 | {
304 | "name": "",
305 | "type": "bytes32"
306 | }
307 | ],
308 | "payable": false,
309 | "stateMutability": "view",
310 | "type": "function"
311 | },
312 | {
313 | "constant": false,
314 | "inputs": [
315 | {
316 | "name": "_spender",
317 | "type": "address"
318 | },
319 | {
320 | "name": "_addedValue",
321 | "type": "uint256"
322 | }
323 | ],
324 | "name": "increaseAllowance",
325 | "outputs": [
326 | {
327 | "name": "",
328 | "type": "bool"
329 | }
330 | ],
331 | "payable": false,
332 | "stateMutability": "nonpayable",
333 | "type": "function"
334 | },
335 | {
336 | "constant": true,
337 | "inputs": [],
338 | "name": "getTreasury",
339 | "outputs": [
340 | {
341 | "name": "",
342 | "type": "address"
343 | }
344 | ],
345 | "payable": false,
346 | "stateMutability": "view",
347 | "type": "function"
348 | },
349 | {
350 | "constant": true,
351 | "inputs": [],
352 | "name": "isStopped",
353 | "outputs": [
354 | {
355 | "name": "",
356 | "type": "bool"
357 | }
358 | ],
359 | "payable": false,
360 | "stateMutability": "view",
361 | "type": "function"
362 | },
363 | {
364 | "constant": true,
365 | "inputs": [],
366 | "name": "getBufferedEther",
367 | "outputs": [
368 | {
369 | "name": "",
370 | "type": "uint256"
371 | }
372 | ],
373 | "payable": false,
374 | "stateMutability": "view",
375 | "type": "function"
376 | },
377 | {
378 | "constant": false,
379 | "inputs": [
380 | {
381 | "name": "_lidoLocator",
382 | "type": "address"
383 | },
384 | {
385 | "name": "_eip712StETH",
386 | "type": "address"
387 | }
388 | ],
389 | "name": "initialize",
390 | "outputs": [],
391 | "payable": true,
392 | "stateMutability": "payable",
393 | "type": "function"
394 | },
395 | {
396 | "constant": false,
397 | "inputs": [],
398 | "name": "receiveELRewards",
399 | "outputs": [],
400 | "payable": true,
401 | "stateMutability": "payable",
402 | "type": "function"
403 | },
404 | {
405 | "constant": true,
406 | "inputs": [],
407 | "name": "getWithdrawalCredentials",
408 | "outputs": [
409 | {
410 | "name": "",
411 | "type": "bytes32"
412 | }
413 | ],
414 | "payable": false,
415 | "stateMutability": "view",
416 | "type": "function"
417 | },
418 | {
419 | "constant": true,
420 | "inputs": [],
421 | "name": "getCurrentStakeLimit",
422 | "outputs": [
423 | {
424 | "name": "",
425 | "type": "uint256"
426 | }
427 | ],
428 | "payable": false,
429 | "stateMutability": "view",
430 | "type": "function"
431 | },
432 | {
433 | "constant": true,
434 | "inputs": [],
435 | "name": "getStakeLimitFullInfo",
436 | "outputs": [
437 | {
438 | "name": "isStakingPaused",
439 | "type": "bool"
440 | },
441 | {
442 | "name": "isStakingLimitSet",
443 | "type": "bool"
444 | },
445 | {
446 | "name": "currentStakeLimit",
447 | "type": "uint256"
448 | },
449 | {
450 | "name": "maxStakeLimit",
451 | "type": "uint256"
452 | },
453 | {
454 | "name": "maxStakeLimitGrowthBlocks",
455 | "type": "uint256"
456 | },
457 | {
458 | "name": "prevStakeLimit",
459 | "type": "uint256"
460 | },
461 | {
462 | "name": "prevStakeBlockNumber",
463 | "type": "uint256"
464 | }
465 | ],
466 | "payable": false,
467 | "stateMutability": "view",
468 | "type": "function"
469 | },
470 | {
471 | "constant": false,
472 | "inputs": [
473 | {
474 | "name": "_sender",
475 | "type": "address"
476 | },
477 | {
478 | "name": "_recipient",
479 | "type": "address"
480 | },
481 | {
482 | "name": "_sharesAmount",
483 | "type": "uint256"
484 | }
485 | ],
486 | "name": "transferSharesFrom",
487 | "outputs": [
488 | {
489 | "name": "",
490 | "type": "uint256"
491 | }
492 | ],
493 | "payable": false,
494 | "stateMutability": "nonpayable",
495 | "type": "function"
496 | },
497 | {
498 | "constant": true,
499 | "inputs": [
500 | {
501 | "name": "_account",
502 | "type": "address"
503 | }
504 | ],
505 | "name": "balanceOf",
506 | "outputs": [
507 | {
508 | "name": "",
509 | "type": "uint256"
510 | }
511 | ],
512 | "payable": false,
513 | "stateMutability": "view",
514 | "type": "function"
515 | },
516 | {
517 | "constant": false,
518 | "inputs": [],
519 | "name": "resumeStaking",
520 | "outputs": [],
521 | "payable": false,
522 | "stateMutability": "nonpayable",
523 | "type": "function"
524 | },
525 | {
526 | "constant": true,
527 | "inputs": [],
528 | "name": "getFeeDistribution",
529 | "outputs": [
530 | {
531 | "name": "treasuryFeeBasisPoints",
532 | "type": "uint16"
533 | },
534 | {
535 | "name": "insuranceFeeBasisPoints",
536 | "type": "uint16"
537 | },
538 | {
539 | "name": "operatorsFeeBasisPoints",
540 | "type": "uint16"
541 | }
542 | ],
543 | "payable": false,
544 | "stateMutability": "view",
545 | "type": "function"
546 | },
547 | {
548 | "constant": false,
549 | "inputs": [],
550 | "name": "receiveWithdrawals",
551 | "outputs": [],
552 | "payable": true,
553 | "stateMutability": "payable",
554 | "type": "function"
555 | },
556 | {
557 | "constant": true,
558 | "inputs": [
559 | {
560 | "name": "_sharesAmount",
561 | "type": "uint256"
562 | }
563 | ],
564 | "name": "getPooledEthByShares",
565 | "outputs": [
566 | {
567 | "name": "",
568 | "type": "uint256"
569 | }
570 | ],
571 | "payable": false,
572 | "stateMutability": "view",
573 | "type": "function"
574 | },
575 | {
576 | "constant": true,
577 | "inputs": [
578 | {
579 | "name": "token",
580 | "type": "address"
581 | }
582 | ],
583 | "name": "allowRecoverability",
584 | "outputs": [
585 | {
586 | "name": "",
587 | "type": "bool"
588 | }
589 | ],
590 | "payable": false,
591 | "stateMutability": "view",
592 | "type": "function"
593 | },
594 | {
595 | "constant": true,
596 | "inputs": [
597 | {
598 | "name": "owner",
599 | "type": "address"
600 | }
601 | ],
602 | "name": "nonces",
603 | "outputs": [
604 | {
605 | "name": "",
606 | "type": "uint256"
607 | }
608 | ],
609 | "payable": false,
610 | "stateMutability": "view",
611 | "type": "function"
612 | },
613 | {
614 | "constant": true,
615 | "inputs": [],
616 | "name": "appId",
617 | "outputs": [
618 | {
619 | "name": "",
620 | "type": "bytes32"
621 | }
622 | ],
623 | "payable": false,
624 | "stateMutability": "view",
625 | "type": "function"
626 | },
627 | {
628 | "constant": true,
629 | "inputs": [],
630 | "name": "getOracle",
631 | "outputs": [
632 | {
633 | "name": "",
634 | "type": "address"
635 | }
636 | ],
637 | "payable": false,
638 | "stateMutability": "view",
639 | "type": "function"
640 | },
641 | {
642 | "constant": true,
643 | "inputs": [],
644 | "name": "eip712Domain",
645 | "outputs": [
646 | {
647 | "name": "name",
648 | "type": "string"
649 | },
650 | {
651 | "name": "version",
652 | "type": "string"
653 | },
654 | {
655 | "name": "chainId",
656 | "type": "uint256"
657 | },
658 | {
659 | "name": "verifyingContract",
660 | "type": "address"
661 | }
662 | ],
663 | "payable": false,
664 | "stateMutability": "view",
665 | "type": "function"
666 | },
667 | {
668 | "constant": true,
669 | "inputs": [],
670 | "name": "getContractVersion",
671 | "outputs": [
672 | {
673 | "name": "",
674 | "type": "uint256"
675 | }
676 | ],
677 | "payable": false,
678 | "stateMutability": "view",
679 | "type": "function"
680 | },
681 | {
682 | "constant": true,
683 | "inputs": [],
684 | "name": "getInitializationBlock",
685 | "outputs": [
686 | {
687 | "name": "",
688 | "type": "uint256"
689 | }
690 | ],
691 | "payable": false,
692 | "stateMutability": "view",
693 | "type": "function"
694 | },
695 | {
696 | "constant": false,
697 | "inputs": [
698 | {
699 | "name": "_recipient",
700 | "type": "address"
701 | },
702 | {
703 | "name": "_sharesAmount",
704 | "type": "uint256"
705 | }
706 | ],
707 | "name": "transferShares",
708 | "outputs": [
709 | {
710 | "name": "",
711 | "type": "uint256"
712 | }
713 | ],
714 | "payable": false,
715 | "stateMutability": "nonpayable",
716 | "type": "function"
717 | },
718 | {
719 | "constant": true,
720 | "inputs": [],
721 | "name": "symbol",
722 | "outputs": [
723 | {
724 | "name": "",
725 | "type": "string"
726 | }
727 | ],
728 | "payable": false,
729 | "stateMutability": "pure",
730 | "type": "function"
731 | },
732 | {
733 | "constant": true,
734 | "inputs": [],
735 | "name": "getEIP712StETH",
736 | "outputs": [
737 | {
738 | "name": "",
739 | "type": "address"
740 | }
741 | ],
742 | "payable": false,
743 | "stateMutability": "view",
744 | "type": "function"
745 | },
746 | {
747 | "constant": false,
748 | "inputs": [
749 | {
750 | "name": "",
751 | "type": "address"
752 | }
753 | ],
754 | "name": "transferToVault",
755 | "outputs": [],
756 | "payable": false,
757 | "stateMutability": "nonpayable",
758 | "type": "function"
759 | },
760 | {
761 | "constant": true,
762 | "inputs": [
763 | {
764 | "name": "_sender",
765 | "type": "address"
766 | },
767 | {
768 | "name": "_role",
769 | "type": "bytes32"
770 | },
771 | {
772 | "name": "_params",
773 | "type": "uint256[]"
774 | }
775 | ],
776 | "name": "canPerform",
777 | "outputs": [
778 | {
779 | "name": "",
780 | "type": "bool"
781 | }
782 | ],
783 | "payable": false,
784 | "stateMutability": "view",
785 | "type": "function"
786 | },
787 | {
788 | "constant": false,
789 | "inputs": [
790 | {
791 | "name": "_referral",
792 | "type": "address"
793 | }
794 | ],
795 | "name": "submit",
796 | "outputs": [
797 | {
798 | "name": "",
799 | "type": "uint256"
800 | }
801 | ],
802 | "payable": true,
803 | "stateMutability": "payable",
804 | "type": "function"
805 | },
806 | {
807 | "constant": false,
808 | "inputs": [
809 | {
810 | "name": "_spender",
811 | "type": "address"
812 | },
813 | {
814 | "name": "_subtractedValue",
815 | "type": "uint256"
816 | }
817 | ],
818 | "name": "decreaseAllowance",
819 | "outputs": [
820 | {
821 | "name": "",
822 | "type": "bool"
823 | }
824 | ],
825 | "payable": false,
826 | "stateMutability": "nonpayable",
827 | "type": "function"
828 | },
829 | {
830 | "constant": true,
831 | "inputs": [],
832 | "name": "getEVMScriptRegistry",
833 | "outputs": [
834 | {
835 | "name": "",
836 | "type": "address"
837 | }
838 | ],
839 | "payable": false,
840 | "stateMutability": "view",
841 | "type": "function"
842 | },
843 | {
844 | "constant": false,
845 | "inputs": [
846 | {
847 | "name": "_recipient",
848 | "type": "address"
849 | },
850 | {
851 | "name": "_amount",
852 | "type": "uint256"
853 | }
854 | ],
855 | "name": "transfer",
856 | "outputs": [
857 | {
858 | "name": "",
859 | "type": "bool"
860 | }
861 | ],
862 | "payable": false,
863 | "stateMutability": "nonpayable",
864 | "type": "function"
865 | },
866 | {
867 | "constant": false,
868 | "inputs": [
869 | {
870 | "name": "_maxDepositsCount",
871 | "type": "uint256"
872 | },
873 | {
874 | "name": "_stakingModuleId",
875 | "type": "uint256"
876 | },
877 | {
878 | "name": "_depositCalldata",
879 | "type": "bytes"
880 | }
881 | ],
882 | "name": "deposit",
883 | "outputs": [],
884 | "payable": false,
885 | "stateMutability": "nonpayable",
886 | "type": "function"
887 | },
888 | {
889 | "constant": true,
890 | "inputs": [],
891 | "name": "UNSAFE_CHANGE_DEPOSITED_VALIDATORS_ROLE",
892 | "outputs": [
893 | {
894 | "name": "",
895 | "type": "bytes32"
896 | }
897 | ],
898 | "payable": false,
899 | "stateMutability": "view",
900 | "type": "function"
901 | },
902 | {
903 | "constant": true,
904 | "inputs": [],
905 | "name": "getBeaconStat",
906 | "outputs": [
907 | {
908 | "name": "depositedValidators",
909 | "type": "uint256"
910 | },
911 | {
912 | "name": "beaconValidators",
913 | "type": "uint256"
914 | },
915 | {
916 | "name": "beaconBalance",
917 | "type": "uint256"
918 | }
919 | ],
920 | "payable": false,
921 | "stateMutability": "view",
922 | "type": "function"
923 | },
924 | {
925 | "constant": false,
926 | "inputs": [],
927 | "name": "removeStakingLimit",
928 | "outputs": [],
929 | "payable": false,
930 | "stateMutability": "nonpayable",
931 | "type": "function"
932 | },
933 | {
934 | "constant": false,
935 | "inputs": [
936 | {
937 | "name": "_reportTimestamp",
938 | "type": "uint256"
939 | },
940 | {
941 | "name": "_timeElapsed",
942 | "type": "uint256"
943 | },
944 | {
945 | "name": "_clValidators",
946 | "type": "uint256"
947 | },
948 | {
949 | "name": "_clBalance",
950 | "type": "uint256"
951 | },
952 | {
953 | "name": "_withdrawalVaultBalance",
954 | "type": "uint256"
955 | },
956 | {
957 | "name": "_elRewardsVaultBalance",
958 | "type": "uint256"
959 | },
960 | {
961 | "name": "_sharesRequestedToBurn",
962 | "type": "uint256"
963 | },
964 | {
965 | "name": "_withdrawalFinalizationBatches",
966 | "type": "uint256[]"
967 | },
968 | {
969 | "name": "_simulatedShareRate",
970 | "type": "uint256"
971 | }
972 | ],
973 | "name": "handleOracleReport",
974 | "outputs": [
975 | {
976 | "name": "postRebaseAmounts",
977 | "type": "uint256[4]"
978 | }
979 | ],
980 | "payable": false,
981 | "stateMutability": "nonpayable",
982 | "type": "function"
983 | },
984 | {
985 | "constant": true,
986 | "inputs": [],
987 | "name": "getFee",
988 | "outputs": [
989 | {
990 | "name": "totalFee",
991 | "type": "uint16"
992 | }
993 | ],
994 | "payable": false,
995 | "stateMutability": "view",
996 | "type": "function"
997 | },
998 | {
999 | "constant": true,
1000 | "inputs": [],
1001 | "name": "kernel",
1002 | "outputs": [
1003 | {
1004 | "name": "",
1005 | "type": "address"
1006 | }
1007 | ],
1008 | "payable": false,
1009 | "stateMutability": "view",
1010 | "type": "function"
1011 | },
1012 | {
1013 | "constant": true,
1014 | "inputs": [],
1015 | "name": "getTotalShares",
1016 | "outputs": [
1017 | {
1018 | "name": "",
1019 | "type": "uint256"
1020 | }
1021 | ],
1022 | "payable": false,
1023 | "stateMutability": "view",
1024 | "type": "function"
1025 | },
1026 | {
1027 | "constant": false,
1028 | "inputs": [
1029 | {
1030 | "name": "_owner",
1031 | "type": "address"
1032 | },
1033 | {
1034 | "name": "_spender",
1035 | "type": "address"
1036 | },
1037 | {
1038 | "name": "_value",
1039 | "type": "uint256"
1040 | },
1041 | {
1042 | "name": "_deadline",
1043 | "type": "uint256"
1044 | },
1045 | {
1046 | "name": "_v",
1047 | "type": "uint8"
1048 | },
1049 | {
1050 | "name": "_r",
1051 | "type": "bytes32"
1052 | },
1053 | {
1054 | "name": "_s",
1055 | "type": "bytes32"
1056 | }
1057 | ],
1058 | "name": "permit",
1059 | "outputs": [],
1060 | "payable": false,
1061 | "stateMutability": "nonpayable",
1062 | "type": "function"
1063 | },
1064 | {
1065 | "constant": true,
1066 | "inputs": [
1067 | {
1068 | "name": "_owner",
1069 | "type": "address"
1070 | },
1071 | {
1072 | "name": "_spender",
1073 | "type": "address"
1074 | }
1075 | ],
1076 | "name": "allowance",
1077 | "outputs": [
1078 | {
1079 | "name": "",
1080 | "type": "uint256"
1081 | }
1082 | ],
1083 | "payable": false,
1084 | "stateMutability": "view",
1085 | "type": "function"
1086 | },
1087 | {
1088 | "constant": true,
1089 | "inputs": [],
1090 | "name": "isPetrified",
1091 | "outputs": [
1092 | {
1093 | "name": "",
1094 | "type": "bool"
1095 | }
1096 | ],
1097 | "payable": false,
1098 | "stateMutability": "view",
1099 | "type": "function"
1100 | },
1101 | {
1102 | "constant": true,
1103 | "inputs": [],
1104 | "name": "getLidoLocator",
1105 | "outputs": [
1106 | {
1107 | "name": "",
1108 | "type": "address"
1109 | }
1110 | ],
1111 | "payable": false,
1112 | "stateMutability": "view",
1113 | "type": "function"
1114 | },
1115 | {
1116 | "constant": true,
1117 | "inputs": [],
1118 | "name": "canDeposit",
1119 | "outputs": [
1120 | {
1121 | "name": "",
1122 | "type": "bool"
1123 | }
1124 | ],
1125 | "payable": false,
1126 | "stateMutability": "view",
1127 | "type": "function"
1128 | },
1129 | {
1130 | "constant": true,
1131 | "inputs": [],
1132 | "name": "STAKING_PAUSE_ROLE",
1133 | "outputs": [
1134 | {
1135 | "name": "",
1136 | "type": "bytes32"
1137 | }
1138 | ],
1139 | "payable": false,
1140 | "stateMutability": "view",
1141 | "type": "function"
1142 | },
1143 | {
1144 | "constant": true,
1145 | "inputs": [],
1146 | "name": "getDepositableEther",
1147 | "outputs": [
1148 | {
1149 | "name": "",
1150 | "type": "uint256"
1151 | }
1152 | ],
1153 | "payable": false,
1154 | "stateMutability": "view",
1155 | "type": "function"
1156 | },
1157 | {
1158 | "constant": true,
1159 | "inputs": [
1160 | {
1161 | "name": "_account",
1162 | "type": "address"
1163 | }
1164 | ],
1165 | "name": "sharesOf",
1166 | "outputs": [
1167 | {
1168 | "name": "",
1169 | "type": "uint256"
1170 | }
1171 | ],
1172 | "payable": false,
1173 | "stateMutability": "view",
1174 | "type": "function"
1175 | },
1176 | {
1177 | "constant": false,
1178 | "inputs": [],
1179 | "name": "pauseStaking",
1180 | "outputs": [],
1181 | "payable": false,
1182 | "stateMutability": "nonpayable",
1183 | "type": "function"
1184 | },
1185 | {
1186 | "constant": true,
1187 | "inputs": [],
1188 | "name": "getTotalELRewardsCollected",
1189 | "outputs": [
1190 | {
1191 | "name": "",
1192 | "type": "uint256"
1193 | }
1194 | ],
1195 | "payable": false,
1196 | "stateMutability": "view",
1197 | "type": "function"
1198 | },
1199 | {
1200 | "payable": true,
1201 | "stateMutability": "payable",
1202 | "type": "fallback"
1203 | },
1204 | {
1205 | "anonymous": false,
1206 | "inputs": [],
1207 | "name": "StakingPaused",
1208 | "type": "event"
1209 | },
1210 | {
1211 | "anonymous": false,
1212 | "inputs": [],
1213 | "name": "StakingResumed",
1214 | "type": "event"
1215 | },
1216 | {
1217 | "anonymous": false,
1218 | "inputs": [
1219 | {
1220 | "indexed": false,
1221 | "name": "maxStakeLimit",
1222 | "type": "uint256"
1223 | },
1224 | {
1225 | "indexed": false,
1226 | "name": "stakeLimitIncreasePerBlock",
1227 | "type": "uint256"
1228 | }
1229 | ],
1230 | "name": "StakingLimitSet",
1231 | "type": "event"
1232 | },
1233 | {
1234 | "anonymous": false,
1235 | "inputs": [],
1236 | "name": "StakingLimitRemoved",
1237 | "type": "event"
1238 | },
1239 | {
1240 | "anonymous": false,
1241 | "inputs": [
1242 | {
1243 | "indexed": true,
1244 | "name": "reportTimestamp",
1245 | "type": "uint256"
1246 | },
1247 | {
1248 | "indexed": false,
1249 | "name": "preCLValidators",
1250 | "type": "uint256"
1251 | },
1252 | {
1253 | "indexed": false,
1254 | "name": "postCLValidators",
1255 | "type": "uint256"
1256 | }
1257 | ],
1258 | "name": "CLValidatorsUpdated",
1259 | "type": "event"
1260 | },
1261 | {
1262 | "anonymous": false,
1263 | "inputs": [
1264 | {
1265 | "indexed": false,
1266 | "name": "depositedValidators",
1267 | "type": "uint256"
1268 | }
1269 | ],
1270 | "name": "DepositedValidatorsChanged",
1271 | "type": "event"
1272 | },
1273 | {
1274 | "anonymous": false,
1275 | "inputs": [
1276 | {
1277 | "indexed": true,
1278 | "name": "reportTimestamp",
1279 | "type": "uint256"
1280 | },
1281 | {
1282 | "indexed": false,
1283 | "name": "preCLBalance",
1284 | "type": "uint256"
1285 | },
1286 | {
1287 | "indexed": false,
1288 | "name": "postCLBalance",
1289 | "type": "uint256"
1290 | },
1291 | {
1292 | "indexed": false,
1293 | "name": "withdrawalsWithdrawn",
1294 | "type": "uint256"
1295 | },
1296 | {
1297 | "indexed": false,
1298 | "name": "executionLayerRewardsWithdrawn",
1299 | "type": "uint256"
1300 | },
1301 | {
1302 | "indexed": false,
1303 | "name": "postBufferedEther",
1304 | "type": "uint256"
1305 | }
1306 | ],
1307 | "name": "ETHDistributed",
1308 | "type": "event"
1309 | },
1310 | {
1311 | "anonymous": false,
1312 | "inputs": [
1313 | {
1314 | "indexed": true,
1315 | "name": "reportTimestamp",
1316 | "type": "uint256"
1317 | },
1318 | {
1319 | "indexed": false,
1320 | "name": "timeElapsed",
1321 | "type": "uint256"
1322 | },
1323 | {
1324 | "indexed": false,
1325 | "name": "preTotalShares",
1326 | "type": "uint256"
1327 | },
1328 | {
1329 | "indexed": false,
1330 | "name": "preTotalEther",
1331 | "type": "uint256"
1332 | },
1333 | {
1334 | "indexed": false,
1335 | "name": "postTotalShares",
1336 | "type": "uint256"
1337 | },
1338 | {
1339 | "indexed": false,
1340 | "name": "postTotalEther",
1341 | "type": "uint256"
1342 | },
1343 | {
1344 | "indexed": false,
1345 | "name": "sharesMintedAsFees",
1346 | "type": "uint256"
1347 | }
1348 | ],
1349 | "name": "TokenRebased",
1350 | "type": "event"
1351 | },
1352 | {
1353 | "anonymous": false,
1354 | "inputs": [
1355 | {
1356 | "indexed": false,
1357 | "name": "lidoLocator",
1358 | "type": "address"
1359 | }
1360 | ],
1361 | "name": "LidoLocatorSet",
1362 | "type": "event"
1363 | },
1364 | {
1365 | "anonymous": false,
1366 | "inputs": [
1367 | {
1368 | "indexed": false,
1369 | "name": "amount",
1370 | "type": "uint256"
1371 | }
1372 | ],
1373 | "name": "ELRewardsReceived",
1374 | "type": "event"
1375 | },
1376 | {
1377 | "anonymous": false,
1378 | "inputs": [
1379 | {
1380 | "indexed": false,
1381 | "name": "amount",
1382 | "type": "uint256"
1383 | }
1384 | ],
1385 | "name": "WithdrawalsReceived",
1386 | "type": "event"
1387 | },
1388 | {
1389 | "anonymous": false,
1390 | "inputs": [
1391 | {
1392 | "indexed": true,
1393 | "name": "sender",
1394 | "type": "address"
1395 | },
1396 | {
1397 | "indexed": false,
1398 | "name": "amount",
1399 | "type": "uint256"
1400 | },
1401 | {
1402 | "indexed": false,
1403 | "name": "referral",
1404 | "type": "address"
1405 | }
1406 | ],
1407 | "name": "Submitted",
1408 | "type": "event"
1409 | },
1410 | {
1411 | "anonymous": false,
1412 | "inputs": [
1413 | {
1414 | "indexed": false,
1415 | "name": "amount",
1416 | "type": "uint256"
1417 | }
1418 | ],
1419 | "name": "Unbuffered",
1420 | "type": "event"
1421 | },
1422 | {
1423 | "anonymous": false,
1424 | "inputs": [
1425 | {
1426 | "indexed": true,
1427 | "name": "executor",
1428 | "type": "address"
1429 | },
1430 | {
1431 | "indexed": false,
1432 | "name": "script",
1433 | "type": "bytes"
1434 | },
1435 | {
1436 | "indexed": false,
1437 | "name": "input",
1438 | "type": "bytes"
1439 | },
1440 | {
1441 | "indexed": false,
1442 | "name": "returnData",
1443 | "type": "bytes"
1444 | }
1445 | ],
1446 | "name": "ScriptResult",
1447 | "type": "event"
1448 | },
1449 | {
1450 | "anonymous": false,
1451 | "inputs": [
1452 | {
1453 | "indexed": true,
1454 | "name": "vault",
1455 | "type": "address"
1456 | },
1457 | {
1458 | "indexed": true,
1459 | "name": "token",
1460 | "type": "address"
1461 | },
1462 | {
1463 | "indexed": false,
1464 | "name": "amount",
1465 | "type": "uint256"
1466 | }
1467 | ],
1468 | "name": "RecoverToVault",
1469 | "type": "event"
1470 | },
1471 | {
1472 | "anonymous": false,
1473 | "inputs": [
1474 | {
1475 | "indexed": false,
1476 | "name": "eip712StETH",
1477 | "type": "address"
1478 | }
1479 | ],
1480 | "name": "EIP712StETHInitialized",
1481 | "type": "event"
1482 | },
1483 | {
1484 | "anonymous": false,
1485 | "inputs": [
1486 | {
1487 | "indexed": true,
1488 | "name": "from",
1489 | "type": "address"
1490 | },
1491 | {
1492 | "indexed": true,
1493 | "name": "to",
1494 | "type": "address"
1495 | },
1496 | {
1497 | "indexed": false,
1498 | "name": "sharesValue",
1499 | "type": "uint256"
1500 | }
1501 | ],
1502 | "name": "TransferShares",
1503 | "type": "event"
1504 | },
1505 | {
1506 | "anonymous": false,
1507 | "inputs": [
1508 | {
1509 | "indexed": true,
1510 | "name": "account",
1511 | "type": "address"
1512 | },
1513 | {
1514 | "indexed": false,
1515 | "name": "preRebaseTokenAmount",
1516 | "type": "uint256"
1517 | },
1518 | {
1519 | "indexed": false,
1520 | "name": "postRebaseTokenAmount",
1521 | "type": "uint256"
1522 | },
1523 | {
1524 | "indexed": false,
1525 | "name": "sharesAmount",
1526 | "type": "uint256"
1527 | }
1528 | ],
1529 | "name": "SharesBurnt",
1530 | "type": "event"
1531 | },
1532 | {
1533 | "anonymous": false,
1534 | "inputs": [],
1535 | "name": "Stopped",
1536 | "type": "event"
1537 | },
1538 | {
1539 | "anonymous": false,
1540 | "inputs": [],
1541 | "name": "Resumed",
1542 | "type": "event"
1543 | },
1544 | {
1545 | "anonymous": false,
1546 | "inputs": [
1547 | {
1548 | "indexed": true,
1549 | "name": "from",
1550 | "type": "address"
1551 | },
1552 | {
1553 | "indexed": true,
1554 | "name": "to",
1555 | "type": "address"
1556 | },
1557 | {
1558 | "indexed": false,
1559 | "name": "value",
1560 | "type": "uint256"
1561 | }
1562 | ],
1563 | "name": "Transfer",
1564 | "type": "event"
1565 | },
1566 | {
1567 | "anonymous": false,
1568 | "inputs": [
1569 | {
1570 | "indexed": true,
1571 | "name": "owner",
1572 | "type": "address"
1573 | },
1574 | {
1575 | "indexed": true,
1576 | "name": "spender",
1577 | "type": "address"
1578 | },
1579 | {
1580 | "indexed": false,
1581 | "name": "value",
1582 | "type": "uint256"
1583 | }
1584 | ],
1585 | "name": "Approval",
1586 | "type": "event"
1587 | },
1588 | {
1589 | "anonymous": false,
1590 | "inputs": [
1591 | {
1592 | "indexed": false,
1593 | "name": "version",
1594 | "type": "uint256"
1595 | }
1596 | ],
1597 | "name": "ContractVersionSet",
1598 | "type": "event"
1599 | }
1600 | ]
--------------------------------------------------------------------------------
/lido_sdk/contract/contract.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, List, Optional
2 |
3 | from web3 import Web3
4 |
5 |
6 | class Contract:
7 | """
8 | Contract class that converts abi interface and list of registry addresses to useful object.
9 | This is some kind of simplified interfaces from brownie.
10 |
11 | When you receive the Contract instance you will be able to use all contract methods in simple and multicall way.
12 | Example:
13 | LidoContract.{contractMethodName}(web3, args)
14 | or for multicall
15 | LidoContract.{contractMethodName}_multicall(web3, [args0, args1, ...])
16 | it is similar to
17 | LidoContract.{contractMethodName}(web3, args0)
18 | LidoContract.{contractMethodName}(web3, args1)
19 | ...
20 | """
21 |
22 | def __init__(self, registry_addresses: Dict[int, str], contract_abi: List[Dict]):
23 | """
24 | @param registry_addresses: It is a dictionary where chain_id is a key and str is the address in this Chain
25 | where contract is deployed.
26 | @param contract_abi: Typical contract interface
27 |
28 | Example of usage can be found in lido/contract/load_contract.py
29 | """
30 | self.registry_addresses = registry_addresses
31 | self.contract_abi = contract_abi
32 |
33 | for abi_element in contract_abi:
34 | if abi_element["type"] == "function":
35 | self._create_contract_method(abi_element)
36 |
37 | def _create_contract_method(self, abi_function):
38 | """Create all methods announced in contract's abi"""
39 |
40 | def call(w3: Web3, args: Optional[List] = None):
41 | from lido_sdk.contract.execute_contract import execute_contract_call
42 |
43 | return execute_contract_call(
44 | w3,
45 | self.registry_addresses[w3.eth.chain_id],
46 | abi_function["name"],
47 | abi_function["inputs"],
48 | abi_function["outputs"],
49 | args=args,
50 | )
51 |
52 | def multicall(w3: Web3, args_list: Optional[List[List]] = None):
53 | from lido_sdk.contract.execute_contract import execute_contract_multicall
54 |
55 | args_list = args_list or [[]]
56 |
57 | return execute_contract_multicall(
58 | w3,
59 | self.registry_addresses[w3.eth.chain_id],
60 | abi_function["name"],
61 | abi_function["inputs"],
62 | abi_function["outputs"],
63 | args_list=args_list,
64 | )
65 |
66 | setattr(self, abi_function["name"], call)
67 | setattr(self, f"{abi_function['name']}_multicall", multicall)
68 |
--------------------------------------------------------------------------------
/lido_sdk/contract/execute_contract.py:
--------------------------------------------------------------------------------
1 | from typing import List, Dict
2 |
3 | from multicall import Call
4 | from web3 import Web3
5 |
6 | from lido_sdk.eth_multicall import Multicall
7 |
8 |
9 | def execute_contract_multicall(
10 | w3: Web3,
11 | registry_address: str,
12 | abi_method_name: str,
13 | abi_input: List[Dict],
14 | abi_returns: List[Dict],
15 | args_list: List[List],
16 | ):
17 | """
18 | @param w3: Web3 instance
19 | @param registry_address: Contract address in current network
20 | @param abi_method_name: Contract name of method
21 | @param abi_input: Params that this method receives, exp: [{'type': 'uint256', 'name': 'contractId'}, ...]
22 | @param abi_returns: Params that function returns, exp: [{'type': 'uint256', 'name': 'signedKeys'}, ...]
23 | @param args_list: List of bunches of arg each of those will be used to call contract, exp: [[True, 1], [True, 2], ...]
24 | @return: List of results from each call that we did.
25 | """
26 | return Multicall(
27 | calls=[
28 | _create_contract_call(
29 | w3, registry_address, abi_method_name, abi_input, abi_returns, args
30 | )
31 | for args in args_list
32 | ],
33 | _w3=w3,
34 | )()
35 |
36 |
37 | def execute_contract_call(
38 | w3: Web3,
39 | registry_address: str,
40 | abi_method_name: str,
41 | abi_input: List[Dict],
42 | abi_returns: List[Dict],
43 | args: List = None,
44 | ) -> Dict:
45 | """
46 | @param w3: Web3 instance
47 | @param registry_address: Contract address in current network
48 | @param abi_method_name: Contract name of method
49 | @param abi_input: Params that this method receives, exp: [{'type': 'uint256', 'name': 'contractId'}, ...]
50 | @param abi_returns: Params that function returns, exp: [{'type': 'uint256', 'name': 'signedKeys'}, ...]
51 | @param args: List of arguments that will be used to call this function
52 | @return: List of results from each call that we did.
53 | """
54 | return _create_contract_call(
55 | w3, registry_address, abi_method_name, abi_input, abi_returns, args
56 | )()
57 |
58 |
59 | def _create_contract_call(
60 | w3: Web3,
61 | registry_address: str,
62 | abi_method_name: str,
63 | abi_input: List[Dict],
64 | abi_returns: List[Dict],
65 | args: List = None,
66 | ) -> Call:
67 | input_args = ",".join([x["type"] for x in abi_input])
68 | output_args = ",".join([x["type"] for x in abi_returns])
69 | returns = [(abi_return["name"], lambda y: y) for abi_return in abi_returns]
70 |
71 | function = f"{abi_method_name}({input_args})({output_args})"
72 |
73 | call = Call(
74 | target=registry_address,
75 | function=function,
76 | returns=returns,
77 | _w3=w3,
78 | )
79 |
80 | call.args = args
81 |
82 | return call
83 |
--------------------------------------------------------------------------------
/lido_sdk/contract/load_contract.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 |
4 | from lido_sdk.contract.contract import Contract
5 | from lido_sdk.network import Network
6 |
7 |
8 | LIDO_ADDRESSES = {
9 | Network.Mainnet: "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84",
10 | Network.Görli: "0x1643E812aE58766192Cf7D2Cf9567dF2C37e9B7F",
11 | Network.Holesky: "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034",
12 | }
13 |
14 | NODE_OPS_ADDRESSES = {
15 | Network.Mainnet: "0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5",
16 | Network.Görli: "0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6320",
17 | Network.Holesky: "0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC",
18 | }
19 |
20 |
21 | def _get_contract_abi(contract_name: str):
22 | script_dir = os.path.dirname(__file__)
23 |
24 | with open(os.path.join(script_dir, "abi", contract_name)) as file:
25 | return json.load(file)
26 |
27 |
28 | # Load all supported contracts here
29 | LidoContract = Contract(LIDO_ADDRESSES, _get_contract_abi("Lido.json"))
30 | NodeOpsContract = Contract(
31 | NODE_OPS_ADDRESSES, _get_contract_abi("NodeOperatorsRegistry.json")
32 | )
33 |
--------------------------------------------------------------------------------
/lido_sdk/eth2deposit/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lidofinance/lido-python-sdk/4820b3106311fc39a62483948e62907b140bf060/lido_sdk/eth2deposit/__init__.py
--------------------------------------------------------------------------------
/lido_sdk/eth2deposit/ssz.py:
--------------------------------------------------------------------------------
1 | from ssz import (
2 | Serializable,
3 | uint64,
4 | bytes4,
5 | bytes32,
6 | bytes48,
7 | )
8 |
9 |
10 | ZERO_BYTES32 = b"\x00" * 32
11 |
12 | # Eth2-spec constants taken from https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md
13 | DOMAIN_DEPOSIT = bytes.fromhex("03000000")
14 |
15 |
16 | class SigningData(Serializable):
17 | fields = [("object_root", bytes32), ("domain", bytes32)]
18 |
19 |
20 | class ForkData(Serializable):
21 | fields = [
22 | ("current_version", bytes4),
23 | ("genesis_validators_root", bytes32),
24 | ]
25 |
26 |
27 | class DepositMessage(Serializable):
28 | """
29 | Ref: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#depositmessage
30 | """
31 |
32 | fields = [
33 | ("pubkey", bytes48),
34 | ("withdrawal_credentials", bytes32),
35 | ("amount", uint64),
36 | ]
37 |
38 |
39 | def compute_deposit_domain(fork_version: bytes) -> bytes:
40 | """
41 | Deposit-only `compute_domain`
42 | """
43 | if len(fork_version) != 4:
44 | raise ValueError(f"Fork version should be in 4 bytes. Got {len(fork_version)}.")
45 | domain_type = DOMAIN_DEPOSIT
46 | fork_data_root = compute_deposit_fork_data_root(fork_version)
47 | return domain_type + fork_data_root[:28]
48 |
49 |
50 | def compute_deposit_fork_data_root(current_version: bytes) -> bytes:
51 | """
52 | Return the appropriate ForkData root for a given deposit version.
53 | """
54 | genesis_validators_root = ZERO_BYTES32 # For deposit, it's fixed value
55 | if len(current_version) != 4:
56 | raise ValueError(
57 | f"Fork version should be in 4 bytes. Got {len(current_version)}."
58 | )
59 | return ForkData(
60 | current_version=current_version,
61 | genesis_validators_root=genesis_validators_root,
62 | ).hash_tree_root
63 |
64 |
65 | def compute_signing_root(ssz_object: Serializable, domain: bytes) -> bytes:
66 | """
67 | Return the signing root of an object by calculating the root of the object-domain tree.
68 | The root is the hash tree root of:
69 | https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#signingdata
70 | """
71 | if len(domain) != 32:
72 | raise ValueError(f"Domain should be in 32 bytes. Got {len(domain)}.")
73 | domain_wrapped_object = SigningData(
74 | object_root=ssz_object.hash_tree_root,
75 | domain=domain,
76 | )
77 | return domain_wrapped_object.hash_tree_root
78 |
--------------------------------------------------------------------------------
/lido_sdk/eth_multicall/__init__.py:
--------------------------------------------------------------------------------
1 | from lido_sdk.eth_multicall.multicall import Multicall
2 |
--------------------------------------------------------------------------------
/lido_sdk/eth_multicall/multicall.py:
--------------------------------------------------------------------------------
1 | from concurrent.futures import ThreadPoolExecutor
2 |
3 | from multicall import Call, Multicall as DefaultMulticall
4 |
5 | from lido_sdk import config
6 | from lido_sdk.eth_multicall.multicall_address import MULTICALL_ADDRESSES
7 |
8 |
9 | class Multicall(DefaultMulticall):
10 | """
11 | Upgraded version of Multicall from https://github.com/banteg/multicall.py
12 |
13 | Improves:
14 | - Added MAX_CALLS_PER_MULTICALL param to avoid huge and slow batches in Multicall
15 | - results from multicall is not a dict, but a list now. We are making a lot of requests to one contract's method,
16 | so we don't wanna loose data.
17 | """
18 |
19 | def __init__(self, **kwargs):
20 | super().__init__(**kwargs)
21 |
22 | self.max_call_bunch = config.MULTICALL_MAX_BUNCH
23 | self.max_workers = config.MULTICALL_MAX_WORKERS
24 | self.max_retries = config.MULTICALL_MAX_RETRIES
25 |
26 | def __call__(self):
27 | calls_list = [
28 | self.calls[i : i + self.max_call_bunch]
29 | for i in range(0, len(self.calls), self.max_call_bunch)
30 | ]
31 |
32 | with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
33 | thread_results = executor.map(
34 | self.execute,
35 | calls_list,
36 | timeout=config.MULTICALL_POOL_EXECUTOR_TIMEOUT,
37 | )
38 |
39 | result = []
40 |
41 | for thread_result in thread_results:
42 | result.extend(thread_result)
43 |
44 | return result
45 |
46 | def execute(self, contract_calls):
47 | aggregate = Call(
48 | MULTICALL_ADDRESSES[self.w3.eth.chain_id],
49 | "aggregate((address,bytes)[])(uint256,bytes[])",
50 | returns=None,
51 | _w3=self.w3,
52 | block_id=self.block_id,
53 | )
54 |
55 | args = [[[call.target, call.data] for call in contract_calls]]
56 |
57 | for retry_num in range(self.max_retries):
58 | try:
59 | block, outputs = aggregate(args)
60 | except ValueError as error:
61 | if retry_num == self.max_retries - 1:
62 | raise error
63 | else:
64 | results = []
65 | for call, output in zip(contract_calls, outputs):
66 | results.append(call.decode_output(output))
67 |
68 | return results
69 |
70 | # Not expected exception
71 | raise Exception("Bug in Multicall")
72 |
--------------------------------------------------------------------------------
/lido_sdk/eth_multicall/multicall_address.py:
--------------------------------------------------------------------------------
1 | from lido_sdk.network import Network
2 |
3 |
4 | MULTICALL_ADDRESSES = {
5 | Network.Mainnet: "0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441",
6 | Network.Görli: "0x77dCa2C955b15e9dE4dbBCf1246B4B85b651e50e",
7 | Network.Holesky: "0x34cA8B8879C3578FBcDd50D88ECc29eDdBEDE0fD",
8 | }
9 |
--------------------------------------------------------------------------------
/lido_sdk/lido.py:
--------------------------------------------------------------------------------
1 | import copy
2 | from typing import List, Optional, Tuple, Dict
3 |
4 | from web3 import Web3
5 |
6 | from lido_sdk import config
7 | from lido_sdk.methods import (
8 | find_duplicated_keys,
9 | validate_keys,
10 | get_operators_indexes,
11 | get_operators_data,
12 | get_operators_keys,
13 | get_status,
14 | )
15 | from lido_sdk.methods.operators import get_keys_by_indexes
16 | from lido_sdk.methods.typing import Operator, OperatorKey
17 |
18 |
19 | class LidoException(Exception):
20 | pass
21 |
22 |
23 | class Lido:
24 | operators_indexes = None
25 | operators = None
26 | keys = None
27 |
28 | def __init__(self, w3: Web3, **kwargs):
29 | self._w3 = w3
30 | self._set_configs(kwargs)
31 |
32 | def _set_configs(self, kwargs: Dict):
33 | chain_id = self._w3.eth.chain_id
34 | # Lifehack to cache chain_id
35 | self._w3.eth._chain_id = lambda: chain_id
36 |
37 | for key, value in kwargs.items():
38 | setattr(config, key, value)
39 |
40 | def get_operators_indexes(self) -> List[int]:
41 | """
42 | @return: List of operator's indexes in Lido.
43 | """
44 | self.operators_indexes = get_operators_indexes(self._w3)
45 | return self.operators_indexes
46 |
47 | def get_operators_data(
48 | self, operators_indexes: Optional[List[int]] = None
49 | ) -> List[Operator]:
50 | """
51 | It will fetch details for each operator specified.
52 |
53 | @param operators_indexes: List operators indexes to fetch.
54 | @return: List of operators details.
55 | """
56 | operators_indexes = (
57 | self.operators_indexes if operators_indexes is None else operators_indexes
58 | )
59 | if operators_indexes is None:
60 | raise LidoException(
61 | "`get_operators_indexes` should be called first or provide `operators_indexes` param"
62 | )
63 |
64 | self.operators = get_operators_data(self._w3, operators_indexes)
65 |
66 | return self.operators
67 |
68 | def get_operators_keys(
69 | self,
70 | operators: Optional[List[Operator]] = None,
71 | ) -> List[OperatorKey]:
72 | """
73 | Returns all keys for specified operators.
74 |
75 | @param operators: List of operators details. We need few fields to fetch "index" and "totalSigningKeys".
76 | @return: List of keys. Each key can be identified and refetched by "index" and "operator_index".
77 | """
78 | operators = self.operators if operators is None else operators
79 | if operators is None:
80 | raise LidoException(
81 | "`get_operators_data` should be called first or provide `operators` param"
82 | )
83 |
84 | self.keys = get_operators_keys(self._w3, operators)
85 |
86 | return self.keys
87 |
88 | def update_keys(self) -> List[OperatorKey]:
89 | """
90 | All keys in Lido object will be updated in optimal way.
91 |
92 | @return: Actual keys list
93 | """
94 |
95 | if self.keys is None:
96 | raise LidoException("`get_operators_keys` should be called first")
97 |
98 | self.get_operators_indexes()
99 | old_operators = copy.deepcopy(self.operators)
100 | self.get_operators_data()
101 |
102 | key_args = self._get_key_args_to_call(old_operators, self.operators)
103 |
104 | keys = get_keys_by_indexes(self._w3, key_args)
105 |
106 | self.keys = self._merge_keys(self.keys, keys)
107 |
108 | return self.keys
109 |
110 | @staticmethod
111 | def _get_key_args_to_call(
112 | old_operators: List[Operator], new_operators: List[Operator]
113 | ) -> List[Tuple[int, int]]:
114 | """
115 | Check diff between previous operators and new operator's update and generate args for multicall to fetch new
116 | and old unused keys.
117 | """
118 | key_args = []
119 |
120 | for operator in new_operators:
121 | prev_op_state = next(
122 | op for op in old_operators if op["index"] == operator["index"]
123 | )
124 |
125 | if prev_op_state:
126 | start_index_keys = prev_op_state["usedSigningKeys"]
127 | else:
128 | start_index_keys = 0
129 |
130 | for key_index in range(start_index_keys, operator["totalSigningKeys"]):
131 | key_args.append((operator["index"], key_index))
132 |
133 | return key_args
134 |
135 | @staticmethod
136 | def _merge_keys(
137 | old_keys: List[OperatorKey], new_keys: List[OperatorKey]
138 | ) -> List[OperatorKey]:
139 | """
140 | Merge keys from last request with old one.
141 | We should merge keys by index and operator_index.
142 | If key exists in new list and in old - new key is actual.
143 | If only old key exists - seems it was deleted or used.
144 | if it was used we should leave it in list
145 | if it wasn't used we should remove it, because it was deleted recently.
146 | """
147 | updated_keys = []
148 |
149 | for old_key in old_keys:
150 | new_key = Lido._find_key(
151 | old_key["index"], old_key["operator_index"], new_keys
152 | )
153 |
154 | if new_key:
155 | updated_keys.append(new_key)
156 | elif old_key["used"]:
157 | updated_keys.append(old_key)
158 |
159 | for new_key in new_keys:
160 | key = Lido._find_key(
161 | new_key["index"], new_key["operator_index"], updated_keys
162 | )
163 |
164 | if not key:
165 | updated_keys.append(new_key)
166 |
167 | return updated_keys
168 |
169 | @staticmethod
170 | def _find_key(
171 | index: int, operator_index: int, keys: List[OperatorKey]
172 | ) -> Optional[OperatorKey]:
173 | return next(
174 | (
175 | key
176 | for key in keys
177 | if key["index"] == index and key["operator_index"] == operator_index
178 | ),
179 | None,
180 | )
181 |
182 | def validate_keys(
183 | self, keys: Optional[List[OperatorKey]] = None
184 | ) -> List[OperatorKey]:
185 | """
186 | Validate all provided keys with pub_key, signature, withdrawal_credentials and DepositDomain.
187 |
188 | @param keys: List of operators keys.
189 | @return: All invalid keys that were found.
190 | """
191 | keys = self.keys if keys is None else keys
192 | if keys is None:
193 | raise LidoException(
194 | "`get_operators_keys` should be called first or provide `keys` param"
195 | )
196 |
197 | return validate_keys(self._w3, keys)
198 |
199 | def find_duplicated_keys(
200 | self, keys: Optional[List[OperatorKey]] = None
201 | ) -> List[Tuple[OperatorKey, OperatorKey]]:
202 | """
203 | Find and returns all keys duplicates.
204 |
205 | @param keys: List a keys to check.
206 | @return: List of duplicate pairs keys.
207 | """
208 | keys = self.keys if keys is None else keys
209 | if keys is None:
210 | raise LidoException(
211 | "`get_operators_keys` should be called first or provide `keys` param"
212 | )
213 |
214 | return find_duplicated_keys(keys)
215 |
216 | def get_status(self) -> dict:
217 | """
218 | Return a dict with Lido contract actual state.
219 | """
220 | return get_status(self._w3)
221 |
222 | def fetch_all_keys_and_validate(self) -> Dict[str, list]:
223 | """
224 | Function that makes all flow: fetches operators and keys, than it validate all keys.
225 |
226 | @return: Is a dict with two keys.
227 | - invalid_keys - for details see Lido.validate_keys method.
228 | - duplicated_keys - for details see Lido.find_duplicated_keys method.
229 | """
230 | self.get_operators_indexes()
231 | self.get_operators_data()
232 | self.get_operators_keys()
233 | invalid_keys = self.validate_keys()
234 | duplicated_keys = self.find_duplicated_keys()
235 |
236 | return {
237 | "invalid_keys": invalid_keys,
238 | "duplicated_keys": duplicated_keys,
239 | }
240 |
--------------------------------------------------------------------------------
/lido_sdk/main.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lidofinance/lido-python-sdk/4820b3106311fc39a62483948e62907b140bf060/lido_sdk/main.py
--------------------------------------------------------------------------------
/lido_sdk/methods/__init__.py:
--------------------------------------------------------------------------------
1 | from lido_sdk.methods.keys import find_duplicated_keys, validate_keys, validate_key
2 | from lido_sdk.methods.operators import (
3 | get_operators_indexes,
4 | get_operators_data,
5 | get_operators_keys,
6 | )
7 | from lido_sdk.methods.stats import get_status
8 |
--------------------------------------------------------------------------------
/lido_sdk/methods/keys.py:
--------------------------------------------------------------------------------
1 | from concurrent.futures import ProcessPoolExecutor
2 | from typing import List, Tuple
3 |
4 | from lido_sdk import config
5 | from lido_sdk.eth2deposit.ssz import (
6 | compute_deposit_domain,
7 | DepositMessage,
8 | compute_signing_root,
9 | )
10 | from web3 import Web3
11 |
12 | from lido_sdk.contract import LidoContract
13 | from lido_sdk.methods.typing import OperatorKey
14 | from lido_sdk.network.type import WITHDRAWAL_CREDENTIALS, GENESIS_FORK_VERSION
15 | from lido_sdk.blstverify.verifier import verify
16 |
17 |
18 | def find_duplicated_keys(
19 | keys: List[OperatorKey],
20 | ) -> List[Tuple[OperatorKey, OperatorKey]]:
21 | """
22 | Find all duplicates in list of keys
23 |
24 | @param keys: List of keys
25 | @return: Returns list of pair keys (key_1, key_2) that are duplicating
26 | """
27 | keys_dict = {}
28 | duplicates = []
29 |
30 | for key in keys:
31 | if key["key"] not in keys_dict:
32 | keys_dict[key["key"]] = key
33 | else:
34 | duplicates.append((keys_dict[key["key"]], key))
35 |
36 | return duplicates
37 |
38 |
39 | def _get_withdrawal_credentials(chain_id: int):
40 | """
41 | @param chain_id: Network chain id
42 | @return: Possible withdrawal credentials
43 | """
44 | return [bytes.fromhex(cred[2:]) for cred in WITHDRAWAL_CREDENTIALS[chain_id]]
45 |
46 |
47 | def validate_keys(w3: Web3, keys: List[OperatorKey]) -> List[OperatorKey]:
48 | """
49 | @param w3: Web3
50 | @param keys: List of keys to validate
51 | @return: List of keys that are invalid
52 | """
53 | deposit_domain = compute_deposit_domain(GENESIS_FORK_VERSION[w3.eth.chain_id])
54 |
55 | actual_credential = LidoContract.getWithdrawalCredentials(w3)[""]
56 | possible_credentials = _get_withdrawal_credentials(w3.eth.chain_id)
57 |
58 | invalid_keys = []
59 |
60 | key_params = [
61 | (key, actual_credential, possible_credentials, deposit_domain) for key in keys
62 | ]
63 |
64 | with ProcessPoolExecutor() as executor:
65 | results = executor.map(
66 | _executor_validate_keys_list,
67 | [key_params[i : i + 1000] for i in range(0, len(key_params), 1000)],
68 | timeout=config.VALIDATE_POOL_EXECUTOR_TIMEOUT,
69 | )
70 |
71 | for result in results:
72 | for is_valid, key in result:
73 | if not is_valid:
74 | invalid_keys.append(key)
75 |
76 | return invalid_keys
77 |
78 |
79 | def _executor_validate_keys_list(keys: List[Tuple]) -> List[Tuple[bool, OperatorKey]]:
80 | result = []
81 |
82 | # In each key -> actual_credential, possible_credential, deposit_domain
83 | for key in keys:
84 | result.append(
85 | (
86 | _executor_validate_key(key),
87 | key[0],
88 | )
89 | )
90 |
91 | return result
92 |
93 |
94 | def _executor_validate_key(data: Tuple) -> bool:
95 | key, actual_credential, possible_credential, deposit_domain = data
96 |
97 | is_valid = validate_key(key, actual_credential, deposit_domain)
98 |
99 | if not is_valid and key.get("used", False):
100 | for wc in possible_credential:
101 | is_valid = validate_key(key, wc, deposit_domain)
102 |
103 | if is_valid:
104 | break
105 |
106 | return is_valid
107 |
108 |
109 | def validate_key(
110 | key: OperatorKey, withdrawal_credential: bytes, deposit_domain: bytes
111 | ) -> bool:
112 | """
113 | @param key: Key to check.
114 | @param withdrawal_credential: Possible credential.
115 | @param deposit_domain: Magic bytes.
116 | @return: Bool - Valid or Invalid this key
117 | """
118 | pub_key = key["key"]
119 | signature = key["depositSignature"]
120 |
121 | ETH32 = 32 * 10**9
122 | deposit_message = DepositMessage(
123 | pubkey=key["key"],
124 | withdrawal_credentials=withdrawal_credential,
125 | amount=ETH32,
126 | )
127 |
128 | message = compute_signing_root(deposit_message, deposit_domain)
129 | is_valid = verify(pub_key, message, signature)
130 |
131 | if is_valid:
132 | return is_valid
133 |
134 | return False
135 |
--------------------------------------------------------------------------------
/lido_sdk/methods/operators.py:
--------------------------------------------------------------------------------
1 | from typing import List, Tuple
2 |
3 | from web3 import Web3
4 |
5 | from lido_sdk.contract.load_contract import NodeOpsContract
6 | from lido_sdk.methods.typing import Operator, OperatorKey
7 |
8 |
9 | def get_operators_indexes(w3: Web3) -> List[int]:
10 | """
11 | @param w3: Web3 instance
12 | @return: Node operators count
13 | """
14 | operators_count = NodeOpsContract.getNodeOperatorsCount(w3)[""]
15 | return [x for x in range(operators_count)]
16 |
17 |
18 | def get_operators_data(w3: Web3, operators_index_list: List[int]) -> List[Operator]:
19 | """
20 | @param w3: Web3 instance
21 | @param operators_index_list: Operator's indexes to fetch
22 | @return: List of dictionary with operators details
23 | """
24 | if not operators_index_list:
25 | return []
26 |
27 | operators = NodeOpsContract.getNodeOperator_multicall(
28 | w3,
29 | [(i, True) for i in operators_index_list],
30 | )
31 |
32 | # Add index to each operator
33 | for index, operator in zip(operators_index_list, operators):
34 | operator["index"] = index
35 |
36 | return operators
37 |
38 |
39 | def get_operators_keys(w3: Web3, operators: List[Operator]) -> List[OperatorKey]:
40 | """
41 | @param w3: Web3 instance
42 | @param operators: List of method's details from get_operators_data. But we need only `index` and `totalSigningKeys`.
43 | @return: List of dicts (OperatorKey)
44 | """
45 | args_list = []
46 |
47 | if not operators:
48 | return []
49 |
50 | for args in _index_generator(operators):
51 | args_list.append(args)
52 |
53 | if not args_list:
54 | return []
55 |
56 | keys = NodeOpsContract.getSigningKey_multicall(w3, args_list)
57 |
58 | for key, (operator_index, key_index) in zip(keys, _index_generator(operators)):
59 | key["index"] = key_index
60 | key["operator_index"] = operator_index
61 |
62 | return keys
63 |
64 |
65 | def _index_generator(operators):
66 | for operator in operators:
67 | for key_index in range(operator["totalSigningKeys"]):
68 | yield operator["index"], key_index
69 |
70 |
71 | def get_keys_by_indexes(
72 | w3: Web3, call_args: List[Tuple[int, int]]
73 | ) -> List[OperatorKey]:
74 | """
75 | Via this method you can fetch list of keys by operator_index and key_index
76 | e.g. get_keys_by_index(w3, ((0, 0), (0, 1)) ) -> Will be fetched first two First operator's keys
77 |
78 | @param w3: Web3 instance
79 | @param call_args: List of operator_index and keys_index
80 | @return: List of dicts (OperatorKey)
81 | """
82 | if not call_args:
83 | return []
84 |
85 | keys = NodeOpsContract.getSigningKey_multicall(w3, call_args)
86 |
87 | for key, (operator_index, key_index) in zip(keys, call_args):
88 | key["index"] = key_index
89 | key["operator_index"] = operator_index
90 |
91 | return keys
92 |
--------------------------------------------------------------------------------
/lido_sdk/methods/stats.py:
--------------------------------------------------------------------------------
1 | from web3 import Web3
2 |
3 | from lido_sdk.contract import LidoContract
4 |
5 |
6 | def get_status(w3: Web3):
7 | return {
8 | "isStopped": LidoContract.isStopped(w3)[""],
9 | "totalPooledEther": LidoContract.getTotalPooledEther(w3)[""],
10 | "withdrawalCredentials": LidoContract.getWithdrawalCredentials(w3)[""],
11 | "bufferedEther": LidoContract.getBufferedEther(w3)[""],
12 | **LidoContract.getFee(w3),
13 | **LidoContract.getFeeDistribution(w3),
14 | **LidoContract.getBeaconStat(w3),
15 | "last_block": w3.eth.getBlock("latest")["number"],
16 | "last_blocktime": w3.eth.getBlock("latest")["timestamp"],
17 | }
18 |
--------------------------------------------------------------------------------
/lido_sdk/methods/typing.py:
--------------------------------------------------------------------------------
1 | try:
2 | # For python >= 3.8
3 | from typing import TypedDict
4 | except ImportError:
5 | # For python 3.7
6 | from typing_extensions import TypedDict
7 |
8 |
9 | class Operator(TypedDict):
10 | index: int
11 | active: bool
12 | name: str
13 | rewardAddress: str
14 | stakingLimit: int
15 | stoppedValidators: int
16 | totalSigningKeys: int
17 | usedSigningKeys: int
18 |
19 |
20 | class OperatorKey(TypedDict):
21 | index: int
22 | operator_index: int
23 | key: bytes
24 | depositSignature: bytes
25 | used: bool
26 |
--------------------------------------------------------------------------------
/lido_sdk/network/__init__.py:
--------------------------------------------------------------------------------
1 | from lido_sdk.network.type import Network
2 |
--------------------------------------------------------------------------------
/lido_sdk/network/type.py:
--------------------------------------------------------------------------------
1 | from enum import IntEnum
2 |
3 |
4 | class Network(IntEnum):
5 | Mainnet = 1
6 | Görli = 5
7 | Holesky = 17000
8 |
9 |
10 | # Network
11 | GENESIS_FORK_VERSION = {
12 | Network.Mainnet: bytes.fromhex("00000000"),
13 | Network.Görli: bytes.fromhex("00001020"),
14 | Network.Holesky: bytes.fromhex("01017000"),
15 | }
16 |
17 | # Existing withdrawal credentials on the chain
18 | # Will be filtered for unique values
19 | # Will be used as a fallback for used keys
20 | WITHDRAWAL_CREDENTIALS = {
21 | Network.Mainnet: [
22 | "0x009690e5d4472c7c0dbdf490425d89862535d2a52fb686333f3a0a9ff5d2125e"
23 | ],
24 | Network.Görli: [
25 | "0x00040517ce98f81070cea20e35610a3ae23a45f0883b0b035afc5717cc2e833e"
26 | ],
27 | Network.Holesky: [],
28 | }
29 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "lido-sdk"
3 | version = "4.1.0"
4 | description = "This library consolidates various functions to efficiently load network data for Lido, validate node operator keys and find key duplicates."
5 | authors = ["Lido "]
6 | license = "MIT License"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.7.4"
10 | multicall = "0.1.3"
11 | web3 = "^5.23.1"
12 | ssz = "^0.2.4"
13 |
14 | [tool.poetry.dev-dependencies]
15 | pytest = "^6.2.5"
16 | pytest-mock = "^3.6.1"
17 | pytest-cov = "^2.12.1"
18 | codecov = "^2.1.12"
19 | wheel = "^0.38.4"
20 | twine = "^3.4.2"
21 |
22 | [build-system]
23 | requires = [
24 | "setuptools",
25 | "wheel",
26 | ]
27 | build-backend = "setuptools.build_meta"
28 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | import platform
4 | import io
5 | import os
6 | import sys
7 |
8 | from setuptools import find_packages, setup, Extension
9 |
10 | # Detecting target platform
11 | PLATFORMS = {"windows", "linux", "darwin", "cygwin", "android"}
12 |
13 | target = platform.system().lower()
14 |
15 | if "pydroid3" in sys.executable.lower():
16 | target = "android"
17 |
18 | for known in PLATFORMS:
19 | if target.startswith(known):
20 | target = known
21 |
22 | if target not in PLATFORMS:
23 | target = "linux"
24 |
25 | if platform.processor() == "arm" and target == "darwin":
26 | target = 'darwin-arm64'
27 | elif platform.processor() in ["arm", "aarch64"]:
28 | target = "aarch64"
29 |
30 | # C/C++ Extensions
31 | LIBRARIES = []
32 | STATIC_LIBRARIES = ["blst"]
33 | STATIC_LIB_DIR = f"blst-lib/{target}"
34 | LIBRARY_DIRS = ["blst-lib/"]
35 | INCLUDE_DIRS = ["blst-lib/"]
36 | SOURCES = [
37 | "blst-lib/blst_wrap.cpp",
38 | ]
39 |
40 | DEPENDS = [
41 | "blst-lib/blst.h",
42 | "blst-lib/blst.hpp",
43 | "blst-lib/blst_aux.h",
44 | "blst-lib/libblst.a",
45 | "blst-lib/blstlib.dll",
46 | ]
47 |
48 | LIBRARIES.extend(STATIC_LIBRARIES)
49 | LIBRARY_DIRS.append(STATIC_LIB_DIR)
50 |
51 | if target == "windows":
52 | EXTRA_OBJECTS = [
53 | "{}/{}.lib".format(STATIC_LIB_DIR, lib_name) for lib_name in STATIC_LIBRARIES
54 | ]
55 | else: # POSIX
56 | EXTRA_OBJECTS = [
57 | "{}/lib{}.a".format(STATIC_LIB_DIR, lib_name) for lib_name in STATIC_LIBRARIES
58 | ]
59 |
60 | ext_modules = [
61 | Extension(
62 | "lido_sdk.blstverify._blst",
63 | sources=SOURCES,
64 | depends=DEPENDS,
65 | include_dirs=INCLUDE_DIRS,
66 | libraries=LIBRARIES,
67 | library_dirs=LIBRARY_DIRS,
68 | extra_objects=EXTRA_OBJECTS,
69 | py_limited_api=True,
70 | extra_compile_args=["-std=c++11"],
71 | ),
72 | ]
73 |
74 | # ---------------- setup --------------------
75 |
76 | here = os.path.abspath(os.path.dirname(__file__))
77 | with io.open(os.path.join(here, "README.md"), encoding="utf-8") as f:
78 | long_description = "\n" + f.read()
79 |
80 | # Where the magic happens:
81 | setup(
82 | name="lido-sdk",
83 | version="4.1.0",
84 | description="This library consolidates various functions to efficiently load network data for Lido,"
85 | " validate node operator keys and find key duplicates.",
86 | long_description=long_description,
87 | long_description_content_type="text/markdown",
88 | author="Lido",
89 | author_email="info@lido.fi",
90 | python_requires=">=3.7,<4",
91 | url="https://github.com/lidofinance/lido-python-sdk",
92 | package_dir={"": "."},
93 | packages=find_packages(exclude=("tests",)),
94 | package_data={"lido_sdk.contract": ["abi/*.json"]},
95 | install_requires=[
96 | "multicall==0.1.3",
97 | "web3>=5.23.1,<6",
98 | "ssz>=0.2.4,<1",
99 | ],
100 | tests_require=["pytest==6.2.4"],
101 | include_package_data=True,
102 | license="MIT",
103 | classifiers=[
104 | "License :: OSI Approved :: MIT License",
105 | "Development Status :: 5 - Production/Stable",
106 | # Supported Python versions
107 | "Programming Language :: Python :: 3 :: Only",
108 | "Programming Language :: Python :: 3.7",
109 | "Programming Language :: Python :: 3.8",
110 | "Programming Language :: Python :: 3.9",
111 | ],
112 | ext_modules=ext_modules,
113 | dependency_links=[],
114 | )
115 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lidofinance/lido-python-sdk/4820b3106311fc39a62483948e62907b140bf060/tests/__init__.py
--------------------------------------------------------------------------------
/tests/fixtures.py:
--------------------------------------------------------------------------------
1 | OPERATORS_DATA = [
2 | {
3 | "active": True,
4 | "name": "Staking Facilities",
5 | "rewardAddress": "0xdd4bc51496dc93a0c47008e820e0d80745476f22",
6 | "stakingLimit": 4400,
7 | "stoppedValidators": 0,
8 | "totalSigningKeys": 2,
9 | "usedSigningKeys": 1,
10 | },
11 | {
12 | "active": True,
13 | "name": "Certus One",
14 | "rewardAddress": "0x8d689476eb446a1fb0065bffac32398ed7f89165",
15 | "stakingLimit": 1000,
16 | "stoppedValidators": 0,
17 | "totalSigningKeys": 3,
18 | "usedSigningKeys": 2,
19 | },
20 | ]
21 |
22 |
23 | OPERATORS_KEYS = [
24 | {
25 | "key": b"\x81\xb5\xaea\xa8\x989i\x03\x89\x7f\x94\xbe\xa0\xe0b\xc3\xa6\x92^\xe9=0\xf4\xd4\xae\xe9;S;IU\x1a"
26 | b"\xc37\xdax\xff*\xb0\xcf\xbb\n\xdb8\x0c\xad\x94",
27 | "depositSignature": b"\x96\xa8\x8f\x8e\x88>\x9f\xf6\xf3\x97Q\xa2\xcb\xfc\xa3\x94\x9fO\xa9j\xe9\x84D\xd0"
28 | b"\x05\xb6\xea\x9f\xaa\xc0\xc3KR\xd5\x95\xf9B\x8d\x90\x1d\xdd\x815$\x83}\x86d\x01"
29 | b"\xedL\xed=\x84\xe7\xe88\xa2e\x06\xae.\xf3\xbf\x0b\xf1\xb8\xd3\x8b+\xd7\xbd\xb6\xc1"
30 | b"<_F\xb8H\xd0-\xdc\x11\x08d\x9e\x96\x07\xcfM/\xce\xcd\xd8\x07\xbb",
31 | "used": True,
32 | },
33 | {
34 | "key": b"\x958\x05p\x83g\xb0\xb5\xf6q\rA`\x8c\xcd\xd0\xd5\xa6y8\xe1\x0eh\xdd\x01\x08\x90\xd4\xbf\xef\xdc"
35 | b"\xde\x87CpB;\n\xf0\xd0\xa0S\xb7\xb9\x8a\xe2\xd6\xed",
36 | "depositSignature": b"\x82/\xa3\xbf\xa7\xb9\xe6Xd3\xcf\x8c\x99\xbe\x02'{"
37 | b"\xd2S:\xb7\x9a<\xb9@\r\x01\xb8G@\x90\r\xd8\x9bUU\xc3y\xee\xf5\x1e\xb5\xfaI!\xcb"
38 | b"`~\x10\x88\xbfPz\xd2\xbf\xd8Vh\xf8v-O\x84yh-\xb55\x1de\xdf\r\xc8\x87["
39 | b"'\x02\xd1\xbf~B\x0bPn,}\xa7\xed\x81#\x0f\x9e\xbb\x0fQY",
40 | "used": False,
41 | },
42 | {
43 | "key": b"\x81M\xc0\xf5Z\xc3\xfb\x02C\x16h\xad\xf6\xf8\xfa\x1c7\xfb\x9b\xaa["
44 | b"\x87\xf5\xbeQ\x9a72\x05\x93=\xfet/=\xf5f\xcb\xa3\xa3[["
45 | b"\xe1\x94\x0e\x1d\xff\xd5",
46 | "depositSignature": b"\x81\xb2G\x91\xa8\x9f5\x96\xab\xeb\xb2\x94\xe15\x02\xe3\x98\xd9l\xbd\xd2\xa7\r\x87"
47 | b"\xb7\xfa\x89\xcc\x93\x01\x90\x80\xa2\x90[\xb7D~\x0c@\x8f%i\xa1{\x0c\xe6("
48 | b"\x16>\x8c\xac\xa4\xea[i\xafM\xb4X\xdf\xfc\xca\x10\x0f\x8f\xcb\x96\xed\xee\xf3\x00r"
49 | b'\xcaC"r\x1a\xdcU\x91\xda\x8a\xad\xff\xac\x8bs\rx\xf8L;\x9f?\x92',
50 | "used": True,
51 | },
52 | {
53 | "key": b"\x88\xd1\xac\x7f3x\x0f\xd3("
54 | b"\xbe\xe6\tW\xb22\\\xfaA\xb3q\x96\x14\xb6bamE%\xe5\xb4x\xb3\xa8\x1dI\x06qRi6\xe3\xeaA$(\xc8DQ",
55 | "depositSignature": b"\x99\x85\xf2\xeboD_^+\xae\"c3\xf4ya'\xc0\x1e\xfe\xd7\xf4\xa4\xa3["
56 | b"bq\x9d\xd5G\x08y\xd17~\x005\xc0\x0c\xca\xa1\xcf\x89%B\xbbB{"
57 | b"\x07!\x13\xa8\xe6\x90\xe9c\xd3\xe5\xb5\x90\xff\xd0\x05\x1c\x06\x80\xe3E\xcd\x1e\xe91"
58 | b"\x8f%Yye\x85D\\I\x0b\x8e\x9d\xb4\x17\xe9\x9a\xc5^\x99\xc7\xe2\x81\xac\xed",
59 | "used": True,
60 | },
61 | {
62 | "key": b'\x8c\xc5\xb3\xff\xd4\xa2\xc27\xabY\x88;\x8e\x0e"#S2\x9f5\x84\xa0\xc7\x16\x06#"\x9b\x1f\xcbh\x87\xbb'
63 | b"\xea\xdd\xd5\xd5}x\xb9`Jr\x0e\xc8\xfe:\xd5",
64 | "depositSignature": b"\xa6\x97\xd6n\xbb@\xd7\xbe[\xaa\x98e`aN\xe3\x98\xab\x0ej)r\x1e\xe1\xd7i\x9b\xc1\x85l"
65 | b"/\x9d\xce-\xeak\xe3\xc8@\x1d\xcfN$\x9ftg\xd3\xb8\x10Z\xb5M\x8c\xb2\x99\x1c9\xd1\xde"
66 | b"\xccO\xfc\x94N\xc6\xac9\x01\xa7\xf6\xe9\x96oUJ\x99q\x1a;Y!\xda\xa9\x8a\xb3\xe5\x02qP"
67 | b"|\xfe6\xbf\xfc\xbc/",
68 | "used": False,
69 | },
70 | ]
71 |
72 | VALID_KEY_BYTEARRAY = {
73 | "pubkey": bytearray(
74 | b"\xad\xd9\xa5\x0b\xc6\xde5B\xc8\x81\xe3e\x07\x99JRh5&\xf7]\xdc\xef,"
75 | b"U\xe5\x05t\xc5\xc6^\xbd\x97?\xffHGWb\x9d\xed\x05\xe4~\xf2\xbf\xd56"
76 | ),
77 | "signing_root": bytearray(
78 | b"\x84\xd6\xcd{l\xb0\xfb\x00\x94\x95A\x07}-in,\x8f\xf0.\xc1i\x98\xd5!\x9d\xed^\xd2\x84\xf9l"
79 | ),
80 | "signature": bytearray(
81 | b"\x85\xe6\x14_\xee\x91np\xbb\xa6\x95\x15F1\x03\x1b\x97r\xedS\xe5Y\x0b<2XP7g)\x88\xcbg-\t\xab\xcd\x17\x12\x9e"
82 | b"\x87\x0f\x15SdK\xa2^\x10)\xf5\xf9\xd2\xeb\x11\xc0\xe8c\xb3P\xb4\x1dm1\x8b\x9eC\xb7\xa2\xdb\xec\xf2\xdb["
83 | b"\xb3\x12\xf4\x0c\xab\x99\x0bG\x1f5\xfb\x10\x15w\xc4\xaby#\xca{\x03l"
84 | ),
85 | }
86 |
87 | VALID_KEY_BYTES = {
88 | "pubkey": bytes(
89 | b"\xad\xd9\xa5\x0b\xc6\xde5B\xc8\x81\xe3e\x07\x99JRh5&\xf7]\xdc\xef,"
90 | b"U\xe5\x05t\xc5\xc6^\xbd\x97?\xffHGWb\x9d\xed\x05\xe4~\xf2\xbf\xd56"
91 | ),
92 | "signing_root": bytes(
93 | b"\x84\xd6\xcd{l\xb0\xfb\x00\x94\x95A\x07}-in,\x8f\xf0.\xc1i\x98\xd5!\x9d\xed^\xd2\x84\xf9l"
94 | ),
95 | "signature": bytes(
96 | b"\x85\xe6\x14_\xee\x91np\xbb\xa6\x95\x15F1\x03\x1b\x97r\xedS\xe5Y\x0b<2XP7g)\x88\xcbg-\t\xab\xcd\x17\x12\x9e"
97 | b"\x87\x0f\x15SdK\xa2^\x10)\xf5\xf9\xd2\xeb\x11\xc0\xe8c\xb3P\xb4\x1dm1\x8b\x9eC\xb7\xa2\xdb\xec\xf2\xdb["
98 | b"\xb3\x12\xf4\x0c\xab\x99\x0bG\x1f5\xfb\x10\x15w\xc4\xaby#\xca{\x03l"
99 | ),
100 | }
101 |
102 | INVALID_KEY_BYTEARRAY = {
103 | "pubkey": bytearray(
104 | b"\xad\xd9\xa5\x0b\xc6\xde4B\xc8\x81\xe3e\x07\x99JRh5&\xf7]\xdc\xef,"
105 | b"U\xe5\x05t\xc5\xc6^\xbd\x97?\xffHGWb\x9d\xed\x05\xe4~\xf2\xbf\xd56"
106 | ),
107 | "signing_root": bytearray(
108 | b"\x84\xd6\xcd{l\xb0\xfb\x00\x94\x94A\x07}-in,\x8f\xf0.\xc1i\x98\xd5!\x9d\xed^\xd2\x84\xf9l"
109 | ),
110 | "signature": bytearray(
111 | b"\x85\xe6\x14_\xee\x91np\xbb\xa6\x95\x15F1\x03\x1b\x97r\xedS\xe5Y\x0b<2XP7g)\x88\xcbg-\t\xab\xcd\x17\x12\x9e"
112 | b"\x87\x0f\x15SdK\xa2^\x10)\xf5\xf8\xd2\xeb\x11\xc0\xe8c\xb3P\xb4\x1dm1\x8b\x9eC\xb7\xa2\xdb\xec\xf2\xdb["
113 | b"\xb3\x12\xf4\x0c\xab\x99\x0bG\x1f5\xfb\x10\x15w\xc4\xaby#\xca{\x03l"
114 | ),
115 | }
116 |
117 | INVALID_KEY_BYTES = {
118 | "pubkey": bytearray(
119 | b"\xad\xd9\xa5\x0b\xc6\xde4B\xc8\x81\xe3e\x07\x99JRh5&\xf7]\xdc\xef,"
120 | b"U\xe5\x05t\xc5\xc6^\xbd\x97?\xffHGWb\x9d\xed\x05\xe4~\xf2\xbf\xd56"
121 | ),
122 | "signing_root": bytearray(
123 | b"\x84\xd6\xcd{l\xb0\xfb\x00\x94\x94A\x07}-in,\x8f\xf0.\xc1i\x98\xd5!\x9d\xed^\xd2\x84\xf9l"
124 | ),
125 | "signature": bytearray(
126 | b"\x85\xe6\x14_\xee\x91np\xbb\xa6\x95\x15F1\x03\x1b\x97r\xedS\xe5Y\x0b<2XP7g)\x88\xcbg-\t\xab\xcd\x17\x12\x9e"
127 | b"\x87\x0f\x15SdK\xa2^\x10)\xf5\xf8\xd2\xeb\x11\xc0\xe8c\xb3P\xb4\x1dm1\x8b\x9eC\xb7\xa2\xdb\xec\xf2\xdb["
128 | b"\xb3\x12\xf4\x0c\xab\x99\x0bG\x1f5\xfb\x10\x15w\xc4\xaby#\xca{\x03l"
129 | ),
130 | }
131 |
--------------------------------------------------------------------------------
/tests/test_blst_verifier.py:
--------------------------------------------------------------------------------
1 | from lido_sdk.blstverify import verify
2 | from tests.fixtures import (
3 | VALID_KEY_BYTEARRAY,
4 | VALID_KEY_BYTES,
5 | INVALID_KEY_BYTEARRAY,
6 | INVALID_KEY_BYTES,
7 | )
8 |
9 |
10 | def test_valid_bls_bytearray():
11 | pubkey = VALID_KEY_BYTEARRAY["pubkey"]
12 | signing_root = VALID_KEY_BYTEARRAY["signing_root"]
13 | signature = VALID_KEY_BYTEARRAY["signature"]
14 |
15 | res = verify(pubkey, signing_root, signature)
16 |
17 | assert res is True
18 |
19 |
20 | def test_invalid_bls_bytearray():
21 | pubkey = INVALID_KEY_BYTEARRAY["pubkey"]
22 | signing_root = INVALID_KEY_BYTEARRAY["signing_root"]
23 | signature = INVALID_KEY_BYTEARRAY["signature"]
24 |
25 | res = verify(pubkey, signing_root, signature)
26 |
27 | assert res is False
28 |
29 |
30 | def test_valid_bls_bytes():
31 | pubkey = VALID_KEY_BYTES["pubkey"]
32 | signing_root = VALID_KEY_BYTES["signing_root"]
33 | signature = VALID_KEY_BYTES["signature"]
34 |
35 | res = verify(pubkey, signing_root, signature)
36 |
37 | assert res is True
38 |
39 |
40 | def test_invalid_bls_bytes():
41 | pubkey = INVALID_KEY_BYTES["pubkey"]
42 | signing_root = INVALID_KEY_BYTES["signing_root"]
43 | signature = INVALID_KEY_BYTES["signature"]
44 |
45 | res = verify(pubkey, signing_root, signature)
46 |
47 | assert res is False
48 |
49 |
50 | def test_empty_pubkey():
51 | pubkey = bytearray()
52 | signing_root = VALID_KEY_BYTEARRAY["signing_root"]
53 | signature = VALID_KEY_BYTEARRAY["signature"]
54 |
55 | res = verify(pubkey, signing_root, signature)
56 |
57 | assert res is False
58 |
59 |
60 | def test_invalid_pubkey():
61 | pubkey = INVALID_KEY_BYTEARRAY["pubkey"]
62 | signing_root = VALID_KEY_BYTEARRAY["signing_root"]
63 | signature = VALID_KEY_BYTEARRAY["signature"]
64 |
65 | res = verify(pubkey, signing_root, signature)
66 |
67 | assert res is False
68 |
69 |
70 | def test_invalid_signing_root():
71 | pubkey = VALID_KEY_BYTEARRAY["pubkey"]
72 | signing_root = INVALID_KEY_BYTEARRAY["signing_root"]
73 | signature = VALID_KEY_BYTEARRAY["signature"]
74 |
75 | res = verify(pubkey, signing_root, signature)
76 |
77 | assert res is False
78 |
79 |
80 | def test_empty_signing_root():
81 | pubkey = VALID_KEY_BYTEARRAY["pubkey"]
82 | signing_root = bytearray()
83 | signature = VALID_KEY_BYTEARRAY["signature"]
84 |
85 | res = verify(pubkey, signing_root, signature)
86 |
87 | assert res is False
88 |
89 |
90 | def test_invalid_signature():
91 | pubkey = VALID_KEY_BYTEARRAY["pubkey"]
92 | signing_root = INVALID_KEY_BYTEARRAY["signing_root"]
93 | signature = INVALID_KEY_BYTEARRAY["signature"]
94 |
95 | res = verify(pubkey, signing_root, signature)
96 |
97 | assert res is False
98 |
99 |
100 | def test_empty_signature():
101 | pubkey = VALID_KEY_BYTEARRAY["pubkey"]
102 | signing_root = VALID_KEY_BYTEARRAY["signing_root"]
103 | signature = bytearray()
104 |
105 | res = verify(pubkey, signing_root, signature)
106 |
107 | assert res is False
108 |
109 |
110 | def test_invalid_string_arguments():
111 | pubkey = ""
112 | signing_root = ""
113 | signature = ""
114 |
115 | res = verify(pubkey, signing_root, signature)
116 |
117 | assert res is False
118 |
119 |
120 | def test_invalid_int_arguments():
121 | pubkey = 10
122 | signing_root = 20
123 | signature = 30
124 |
125 | res = verify(pubkey, signing_root, signature)
126 |
127 | assert res is False
128 |
129 |
130 | def test_invalid_bool_arguments():
131 | pubkey = True
132 | signing_root = False
133 | signature = True
134 |
135 | res = verify(pubkey, signing_root, signature)
136 |
137 | assert res is False
138 |
139 |
140 | def test_invalid_bytes_arguments():
141 | pubkey = bytes()
142 | signing_root = bytes()
143 | signature = bytes()
144 |
145 | res = verify(pubkey, signing_root, signature)
146 |
147 | assert res is False
148 |
--------------------------------------------------------------------------------
/tests/test_contracts.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, List
2 | from unittest.mock import PropertyMock
3 |
4 | from web3 import Web3
5 |
6 | from lido_sdk.contract.contract import Contract
7 | from lido_sdk.contract.load_contract import _get_contract_abi
8 | from tests.utils import MockTestCase
9 |
10 |
11 | class ContractTest(MockTestCase):
12 | def test_load_contract(self):
13 | from lido_sdk.contract.load_contract import LidoContract, NodeOpsContract
14 |
15 | lido_abi = _get_contract_abi("Lido.json")
16 |
17 | self._check_all_methods_exists(LidoContract, lido_abi)
18 |
19 | node_abi = _get_contract_abi("NodeOperatorsRegistry.json")
20 | self._check_all_methods_exists(NodeOpsContract, node_abi)
21 |
22 | def _check_all_methods_exists(self, contract: Contract, contract_abi: List[Dict]):
23 | for element in contract_abi:
24 | if element["type"] == "function":
25 | self.assertTrue(getattr(contract, element["name"], None))
26 | self.assertTrue(getattr(contract, element["name"] + "_multicall", None))
27 |
28 | def test_contract_call_function(self):
29 | call = self.mocker.patch(
30 | "lido_sdk.contract.execute_contract.execute_contract_call", return_value=2
31 | )
32 | contract_multicall = self.mocker.patch(
33 | "multicall.Call.__call__",
34 | return_value=(
35 | 2,
36 | [
37 | b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\r",
38 | b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\r",
39 | ],
40 | ),
41 | )
42 | self.mocker.patch(
43 | "web3.eth.Eth.chain_id", return_value=1, new_callable=PropertyMock
44 | )
45 |
46 | from lido_sdk.contract.load_contract import NodeOpsContract
47 |
48 | w3 = Web3()
49 |
50 | NodeOpsContract.getNodeOperatorsCount(w3)
51 | call.assert_called_once()
52 |
53 | NodeOpsContract.getNodeOperatorsCount_multicall(w3, [[], []])
54 |
55 | contract_multicall.assert_called_once()
56 | self.assertEqual(2, len(contract_multicall.call_args[0][0][0]))
57 |
--------------------------------------------------------------------------------
/tests/test_lido.py:
--------------------------------------------------------------------------------
1 | import copy
2 | from unittest.mock import PropertyMock
3 |
4 | from web3 import Web3
5 |
6 | from lido_sdk import Lido
7 | from lido_sdk.methods import (
8 | get_operators_indexes,
9 | get_operators_data,
10 | get_operators_keys,
11 | validate_keys,
12 | find_duplicated_keys,
13 | )
14 | from tests.fixtures import OPERATORS_DATA, OPERATORS_KEYS
15 | from tests.utils import get_mainnet_provider, MockTestCase
16 |
17 |
18 | class LidoE2ETest(MockTestCase):
19 | def test_main_flow_methods(self):
20 | w3 = get_mainnet_provider()
21 | operators_count = get_operators_indexes(w3)[:1]
22 |
23 | operators_data = get_operators_data(w3, operators_count)
24 | operators_data[0]["totalSigningKeys"] = 30
25 | keys = get_operators_keys(w3, operators_data)
26 |
27 | invalid_keys = validate_keys(w3, keys)
28 | duplicates = find_duplicated_keys(keys)
29 |
30 | self.assertListEqual(invalid_keys, [])
31 | self.assertListEqual(duplicates, [])
32 |
33 |
34 | class OperatorTest(MockTestCase):
35 | def setUp(self) -> None:
36 | self.mocker.patch(
37 | "web3.eth.Eth.chain_id", return_value=1, new_callable=PropertyMock
38 | )
39 | self.w3 = Web3()
40 |
41 | self.lido = Lido(self.w3)
42 |
43 | def test_get_operators_indexes(self):
44 | self.mocker.patch(
45 | "lido_sdk.contract.load_contract.NodeOpsContract.getNodeOperatorsCount",
46 | return_value={"": 5},
47 | )
48 |
49 | operator_indexes = self.lido.get_operators_indexes()
50 | self.assertListEqual([x for x in range(5)], operator_indexes)
51 |
52 | def test_get_operators_data(self):
53 | """We are checking that indexes are assigned correctly"""
54 | self.mocker.patch(
55 | "lido_sdk.contract.load_contract.NodeOpsContract.getNodeOperator_multicall",
56 | return_value=OPERATORS_DATA,
57 | )
58 |
59 | self.lido.operators_indexes = [0, 1]
60 | operators_data = self.lido.get_operators_data([0, 1])
61 | self.assertEqual(2, len(operators_data))
62 | self.assertEqual(0, operators_data[0]["index"])
63 | self.assertEqual(1, operators_data[1]["index"])
64 |
65 | """Input is an empty array"""
66 | self.lido.operators_indexes = [0, 1]
67 | operators_data = self.lido.get_operators_data([])
68 | self.assertEqual(0, len(operators_data))
69 |
70 | """Input is None"""
71 | self.lido.operators_indexes = [0, 1]
72 | operators_data = self.lido.get_operators_data()
73 | self.assertEqual(2, len(operators_data))
74 | self.assertEqual(0, operators_data[0]["index"])
75 | self.assertEqual(1, operators_data[1]["index"])
76 |
77 | def test_get_operators_keys(self):
78 | self.mocker.patch(
79 | "lido_sdk.contract.load_contract.NodeOpsContract.getSigningKey_multicall",
80 | return_value=OPERATORS_KEYS,
81 | )
82 |
83 | operators = OPERATORS_DATA[:]
84 |
85 | operators[0]["index"] = 0
86 | operators[1]["index"] = 1
87 |
88 | keys = self.lido.get_operators_keys(OPERATORS_DATA)
89 |
90 | expected_indexes = [
91 | {"index": 0, "operator_index": 0},
92 | {"index": 1, "operator_index": 0},
93 | {"index": 0, "operator_index": 1},
94 | {"index": 1, "operator_index": 1},
95 | {"index": 2, "operator_index": 1},
96 | ]
97 |
98 | for expected_key, key in zip(expected_indexes, keys):
99 | self.assertEqual(expected_key["index"], key["index"])
100 | self.assertEqual(expected_key["operator_index"], key["operator_index"])
101 |
102 | """Input is None"""
103 | self.lido.operators = OPERATORS_DATA
104 | keys = self.lido.get_operators_keys()
105 | self.assertEqual(5, len(keys))
106 |
107 | """Input is an empty array"""
108 | self.lido.operators = OPERATORS_DATA
109 | keys = self.lido.get_operators_keys([])
110 | self.assertEqual(0, len(keys))
111 |
112 | def test_validate_keys(self):
113 | self.mocker.patch(
114 | "lido_sdk.contract.load_contract.LidoContract.getWithdrawalCredentials",
115 | return_value={
116 | "": b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb9\xd7\x93Hx\xb5\xfb\x96\x10\xb3\xfe\x8a^D\x1e\x8f\xad~)?"
117 | },
118 | )
119 |
120 | invalid_keys = self.lido.validate_keys(OPERATORS_KEYS)
121 | self.assertEqual(2, len(invalid_keys))
122 |
123 | """Forcing lido.keys have invalid keys and input is an empty array"""
124 | self.lido.keys = OPERATORS_KEYS
125 | invalid_keys = self.lido.validate_keys([])
126 | self.assertEqual(0, len(invalid_keys))
127 |
128 | """Forcing lido.keys have invalid keys and input is None"""
129 | self.lido.keys = OPERATORS_KEYS
130 | invalid_keys = self.lido.validate_keys()
131 | self.assertEqual(2, len(invalid_keys))
132 |
133 | def test_find_duplicated_keys(self):
134 | duplicates = self.lido.find_duplicated_keys(
135 | [*OPERATORS_KEYS, OPERATORS_KEYS[0]]
136 | )
137 |
138 | self.assertEqual(1, len(duplicates))
139 | self.assertEqual(duplicates[0][0]["key"], duplicates[0][1]["key"])
140 |
141 | """Forcing lido.keys are empty and input is None"""
142 | self.lido.keys = []
143 | duplicates = self.lido.find_duplicated_keys()
144 | self.assertEqual(0, len(duplicates))
145 |
146 | """Forcing lido.keys empty array and input is an empty array"""
147 | self.lido.keys = []
148 | duplicates = self.lido.find_duplicated_keys([])
149 | self.assertEqual(0, len(duplicates))
150 |
151 | """Forcing lido.keys have duplicates and input is an empty array"""
152 | self.lido.keys = [*OPERATORS_KEYS, OPERATORS_KEYS[0]]
153 | duplicates = self.lido.find_duplicated_keys([])
154 | self.assertEqual(0, len(duplicates))
155 |
156 | """Forcing lido.keys have duplicates and input is None"""
157 | self.lido.keys = [*OPERATORS_KEYS, OPERATORS_KEYS[0]]
158 | duplicates = self.lido.find_duplicated_keys()
159 | self.assertEqual(1, len(duplicates))
160 | self.assertEqual(duplicates[0][0]["key"], duplicates[0][1]["key"])
161 |
162 | def test_keys_update(self):
163 | self.mocker.patch(
164 | "lido_sdk.contract.load_contract.NodeOpsContract.getNodeOperatorsCount",
165 | return_value={"": 5},
166 | )
167 | self.mocker.patch(
168 | "lido_sdk.contract.load_contract.NodeOpsContract.getNodeOperator_multicall",
169 | return_value=OPERATORS_DATA,
170 | )
171 | self.mocker.patch(
172 | "lido_sdk.contract.load_contract.NodeOpsContract.getSigningKey_multicall",
173 | return_value=OPERATORS_KEYS,
174 | )
175 |
176 | self.lido.get_operators_indexes()
177 | self.lido.get_operators_data()
178 | self.lido.get_operators_keys()
179 |
180 | operators = copy.deepcopy(OPERATORS_DATA)
181 | operators[0]["totalSigningKeys"] += 2
182 | operators[0]["usedSigningKeys"] += 1
183 | operators[1]["totalSigningKeys"] -= 1
184 |
185 | self.mocker.patch(
186 | "lido_sdk.contract.load_contract.NodeOpsContract.getNodeOperator_multicall",
187 | return_value=operators,
188 | )
189 |
190 | keys = copy.deepcopy(
191 | [
192 | OPERATORS_KEYS[1].copy(),
193 | OPERATORS_KEYS[1].copy(),
194 | OPERATORS_KEYS[1].copy(),
195 | ]
196 | )
197 | keys[0]["used"] = True
198 | keys[1]["index"] += 1
199 | keys[2]["index"] += 2
200 | keys_list_call = self.mocker.patch(
201 | "lido_sdk.contract.load_contract.NodeOpsContract.getSigningKey_multicall",
202 | return_value=keys,
203 | )
204 |
205 | self.lido.update_keys()
206 |
207 | self.assertEqual(len(keys_list_call.call_args[0][1]), 3)
208 |
209 | # Two keys were added (operator 0)
210 | # One key was removed (operator 1)
211 | self.assertEqual(len(self.lido.keys), 6)
212 |
213 | key = next(
214 | (
215 | key
216 | for key in self.lido.keys
217 | if key["index"] == 1 and key["operator_index"] == 0
218 | )
219 | )
220 | self.assertTrue(key["used"])
221 |
222 | def test_keys_update_when_unused_keys_removed(self):
223 | self.mocker.patch(
224 | "lido_sdk.contract.load_contract.NodeOpsContract.getNodeOperatorsCount",
225 | return_value={"": 2},
226 | )
227 | self.mocker.patch(
228 | "lido_sdk.contract.load_contract.NodeOpsContract.getNodeOperator_multicall",
229 | return_value=OPERATORS_DATA,
230 | )
231 | self.mocker.patch(
232 | "lido_sdk.contract.load_contract.NodeOpsContract.getSigningKey_multicall",
233 | return_value=OPERATORS_KEYS,
234 | )
235 |
236 | self.lido.get_operators_indexes()
237 | self.lido.get_operators_data()
238 | self.lido.get_operators_keys()
239 |
240 | operators = copy.deepcopy(OPERATORS_DATA)
241 | # All unused keys were removed (operator 0)
242 | # All unused keys were removed (operator 1)
243 | operators[0]["totalSigningKeys"] = 1
244 | operators[0]["usedSigningKeys"] = 1
245 | operators[1]["totalSigningKeys"] = 2
246 | operators[1]["usedSigningKeys"] = 2
247 |
248 | self.mocker.patch(
249 | "lido_sdk.contract.load_contract.NodeOpsContract.getNodeOperator_multicall",
250 | return_value=operators,
251 | )
252 |
253 | self.lido.update_keys()
254 |
255 | # All unused keys were removed (operator 0)
256 | # All unused keys were removed (operator 1)
257 | self.assertEqual(len(self.lido.keys), 3)
258 |
259 | def test_keys_bulk_verify(self):
260 | self.mocker.patch(
261 | "lido_sdk.contract.load_contract.LidoContract.getWithdrawalCredentials",
262 | return_value={
263 | "": b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb9\xd7\x93Hx\xb5\xfb\x96\x10\xb3\xfe\x8a^D\x1e\x8f\xad~)?"
264 | },
265 | )
266 |
267 | keys_to_check = [
268 | OPERATORS_KEYS[0],
269 | *OPERATORS_KEYS[2:3] * 200 * 2,
270 | OPERATORS_KEYS[1],
271 | ]
272 |
273 | invalid_keys = self.lido.validate_keys(keys_to_check) # 2000 keys
274 | self.assertEqual(2, len(invalid_keys))
275 |
276 | self.assertEqual(invalid_keys[0], OPERATORS_KEYS[0])
277 | self.assertEqual(invalid_keys[1], OPERATORS_KEYS[1])
278 |
--------------------------------------------------------------------------------
/tests/test_multicall.py:
--------------------------------------------------------------------------------
1 | from unittest.mock import PropertyMock
2 |
3 | from web3 import Web3
4 |
5 | from tests.utils import MockTestCase, get_mainnet_provider
6 | from lido_sdk import config
7 |
8 |
9 | def _get_broken_endpoint_generator(retries_to_success):
10 | for i in range(retries_to_success):
11 | if i == retries_to_success - 1:
12 | yield (
13 | 13146382,
14 | (
15 | b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\r",
16 | b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\r",
17 | ),
18 | )
19 | else:
20 | yield ValueError(
21 | {"code": -32000, "message": "execution aborted (timeout = 5s)"}
22 | )
23 |
24 |
25 | class ValidationTest(MockTestCase):
26 | def setUp(self) -> None:
27 | self.mocker.patch(
28 | "web3.eth.Eth.chain_id", return_value=1, new_callable=PropertyMock
29 | )
30 | self.w3 = Web3()
31 |
32 | def test_multicall_params(self):
33 | exception_count = 5
34 |
35 | contract_multicall = self.mocker.patch(
36 | "multicall.Call.__call__",
37 | side_effect=_get_broken_endpoint_generator(exception_count),
38 | )
39 | self.mocker.patch(
40 | "web3.eth.Eth.chain_id", return_value=1, new_callable=PropertyMock
41 | )
42 |
43 | from lido_sdk.contract.load_contract import NodeOpsContract
44 |
45 | w3 = Web3()
46 |
47 | config.MULTICALL_MAX_RETRIES = 3
48 |
49 | with self.assertRaises(ValueError):
50 | NodeOpsContract.getNodeOperatorsCount_multicall(w3, [[], []])
51 |
52 | config.MULTICALL_MAX_RETRIES = 6
53 |
54 | NodeOpsContract.getNodeOperatorsCount_multicall(w3, [[], []])
55 |
56 | self.assertEqual(contract_multicall._mock_call_count, exception_count)
57 | self.assertEqual(2, len(contract_multicall.call_args[0][0][0]))
58 |
--------------------------------------------------------------------------------
/tests/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | from unittest import TestCase
3 |
4 | import pytest
5 |
6 | from web3 import Web3
7 | from web3.middleware import geth_poa_middleware
8 |
9 |
10 | class MockTestCase(TestCase):
11 | @pytest.fixture(autouse=True)
12 | def __inject_fixtures(self, mocker):
13 | self.mocker = mocker
14 |
15 |
16 | def get_mainnet_provider():
17 | return _get_web3_provider("mainnet")
18 |
19 |
20 | def _get_web3_provider(net: str):
21 | w3 = Web3(
22 | Web3.HTTPProvider(
23 | f"https://{net}.infura.io/v3/{os.getenv('INFURA_PROJECT_ID')}"
24 | )
25 | )
26 | w3.middleware_onion.inject(geth_poa_middleware, layer=0)
27 | return w3
28 |
--------------------------------------------------------------------------------