├── .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 Lido Python SDK 2 | 3 | [![codecov](https://codecov.io/gh/lidofinance/lido-python-sdk/branch/master/graph/badge.svg)](https://codecov.io/gh/lidofinance/lido-python-sdk) 4 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 | --------------------------------------------------------------------------------