Migration to ethstaker-deposit-cli

This commit is contained in:
Rémy Roy
2024-11-19 11:44:31 -05:00
parent 7c2908057a
commit 876fb02875
258 changed files with 14856 additions and 4037 deletions

View File

@@ -17,7 +17,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-20.04, macos-13, macos-latest, windows-latest]
python-version: ["3.11"]
python-version: ["3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.vscode/
.venv/
node_modules/
dist/
.pnp.*

View File

@@ -1,7 +1,7 @@
# Wagyu Key Gen
[![gitpoap badge](https://public-api.gitpoap.io/v1/repo/stake-house/wagyu-key-gen/badge)](https://www.gitpoap.io/gh/stake-house/wagyu-key-gen)
Wagyu Key Gen is a GUI application providing functionality to the [staking-deposit-cli](https://github.com/ethereum/staking-deposit-cli). It is a React app running in Electron. See `src/electron/` for the simple electron app and `src/react/` for where the magic happens.
Wagyu Key Gen is a GUI application providing functionality to the [ethstaker-deposit-cli](https://github.com/eth-educators/ethstaker-deposit-cli). It is a React app running in Electron. See `src/electron/` for the simple electron app and `src/react/` for where the magic happens.
### Download wagyu at [https://wagyu.gg](https://wagyu.gg)

View File

@@ -33,7 +33,7 @@ import { doesFileExist } from './BashUtils';
*/
const execFileProm = promisify(execFile);
const ETH2_DEPOSIT_DIR_NAME = "staking-deposit-cli-2.7.0";
const ETH2_DEPOSIT_DIR_NAME = "ethstaker-deposit-cli-0.5.0";
/**
* Paths needed to call the stakingdeposit_proxy application using the Python 3 version installed on
@@ -42,7 +42,7 @@ const ETH2_DEPOSIT_DIR_NAME = "staking-deposit-cli-2.7.0";
const ETH2_DEPOSIT_CLI_PATH = path.join("src", "vendors", ETH2_DEPOSIT_DIR_NAME);
const SCRIPTS_PATH = path.join("src", "scripts");
const REQUIREMENTS_PATH = path.join(ETH2_DEPOSIT_CLI_PATH, "requirements.txt");
const WORD_LIST_PATH = path.join(ETH2_DEPOSIT_CLI_PATH, "staking_deposit", "key_handling",
const WORD_LIST_PATH = path.join(ETH2_DEPOSIT_CLI_PATH, "ethstaker_deposit", "key_handling",
"key_derivation", "word_lists");
const REQUIREMENT_PACKAGES_PATH = path.join("dist", "packages");
const STAKINGDEPOSIT_PROXY_PATH = path.join(SCRIPTS_PATH, "stakingdeposit_proxy.py");
@@ -222,7 +222,7 @@ const generateKeys = async (
}
/**
* Validate a mnemonic using the staking-deposit-cli logic by calling the validate_mnemonic function
* Validate a mnemonic using the ethstaker-deposit-cli logic by calling the validate_mnemonic function
* from the stakingdeposit_proxy application.
*
* @param mnemonic The mnemonic to be validated.

View File

@@ -10,7 +10,7 @@ fi
SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
EDCDIR=staking-deposit-cli-2.7.0
EDCDIR=ethstaker-deposit-cli-0.5.0
TARGETPACKAGESPATH=$SCRIPTPATH/../../dist/packages
ETH2DEPOSITCLIPATH=$SCRIPTPATH/../vendors/$EDCDIR
@@ -19,8 +19,8 @@ ETH2REQUIREMENTSPATH=$ETH2DEPOSITCLIPATH/requirements.txt
PYTHONPATH=$TARGETPACKAGESPATH:$ETH2DEPOSITCLIPATH:$(python3 -c "import sys;print(':'.join(sys.path))")
DISTBINPATH=$SCRIPTPATH/../../build/bin
DISTWORDSPATH=$SCRIPTPATH/../../build/word_lists
SRCWORDSPATH=$SCRIPTPATH/../vendors/$EDCDIR/staking_deposit/key_handling/key_derivation/word_lists
SRCINTLPATH=$SCRIPTPATH/../vendors/$EDCDIR/staking_deposit/intl
SRCWORDSPATH=$SCRIPTPATH/../vendors/$EDCDIR/ethstaker_deposit/key_handling/key_derivation/word_lists
SRCINTLPATH=$SCRIPTPATH/../vendors/$EDCDIR/ethstaker_deposit/intl
mkdir -p $DISTBINPATH
mkdir -p $DISTWORDSPATH
@@ -29,11 +29,17 @@ mkdir -p $TARGETPACKAGESPATH
# Getting all the requirements
python3 -m pip install -r $ETH2REQUIREMENTSPATH --target $TARGETPACKAGESPATH
# Getting packages metadata
PYECCDATA=$(python3 -c "from PyInstaller.utils.hooks import copy_metadata;print(':'.join(copy_metadata('py_ecc')[0]))")
SSZDATA=$(python3 -c "from PyInstaller.utils.hooks import copy_metadata;print(':'.join(copy_metadata('ssz')[0]))")
# Bundling Python stakingdeposit_proxy
PYTHONPATH=$PYTHONPATH pyinstaller \
--onefile \
--distpath $DISTBINPATH \
--add-data "$SRCINTLPATH:staking_deposit/intl" \
--add-data "$SRCINTLPATH:ethstaker_deposit/intl" \
--add-data "$PYECCDATA" \
--add-data "$SSZDATA" \
-p $PYTHONPATH \
$SCRIPTPATH/stakingdeposit_proxy.py

View File

@@ -5,7 +5,7 @@ rem Windows.
SET BATDIR=%~dp0
SET EDCDIR=staking-deposit-cli-2.7.0
SET EDCDIR=ethstaker-deposit-cli-0.5.0
SET TARGETPACKAGESPATH=%BATDIR%..\..\dist\packages
@@ -17,8 +17,8 @@ FOR /F "tokens=* USEBACKQ delims=;" %%F IN (`python -c "import sys;print(';'.joi
SET DISTBINPATH=%BATDIR%..\..\build\bin
SET DISTWORDSPATH=%BATDIR%..\..\build\word_lists
SET SRCWORDSPATH=%BATDIR%..\vendors\%EDCDIR%\staking_deposit\key_handling\key_derivation\word_lists
SET SRCINTLPATH=%BATDIR%..\vendors\%EDCDIR%\staking_deposit\intl
SET SRCWORDSPATH=%BATDIR%..\vendors\%EDCDIR%\ethstaker_deposit\key_handling\key_derivation\word_lists
SET SRCINTLPATH=%BATDIR%..\vendors\%EDCDIR%\ethstaker_deposit\intl
mkdir %DISTBINPATH% > nul 2> nul
mkdir %DISTWORDSPATH% > nul 2> nul
@@ -28,7 +28,7 @@ rem Getting all the requirements
python -m pip install -r %ETH2REQUIREMENTSPATH% --target %TARGETPACKAGESPATH%
rem Bundling Python stakingdeposit_proxy
pyinstaller --onefile --distpath %DISTBINPATH% --add-data "%SRCINTLPATH%;staking_deposit\intl" -p %PYTHONPATH% %BATDIR%stakingdeposit_proxy.py
pyinstaller --onefile --distpath %DISTBINPATH% --add-data "%SRCINTLPATH%;ethstaker_deposit\intl" -p %PYTHONPATH% %BATDIR%stakingdeposit_proxy.py
rem Adding word list
copy /Y %SRCWORDSPATH%\* %DISTWORDSPATH%

View File

@@ -1,7 +1,7 @@
"""The stakingdeposit_proxy application.
This application is used as a proxy between our electron application and the staking-deposit-cli
internals. It exposes some staking-deposit-cli functions as easy to use commands that can be called
This application is used as a proxy between our electron application and the ethstaker-deposit-cli
internals. It exposes some ethstaker-deposit-cli functions as easy to use commands that can be called
on the CLI.
"""
@@ -16,32 +16,32 @@ from typing import (
from eth_typing import HexAddress
from staking_deposit.key_handling.key_derivation.mnemonic import (
from ethstaker_deposit.key_handling.key_derivation.mnemonic import (
get_mnemonic,
reconstruct_mnemonic
)
from eth_utils import is_hex_address, to_normalized_address
from staking_deposit.credentials import (
from ethstaker_deposit.credentials import (
CredentialList,
Credential
)
from staking_deposit.exceptions import ValidationError
from staking_deposit.utils.validation import (
from ethstaker_deposit.exceptions import ValidationError
from ethstaker_deposit.utils.validation import (
validate_deposit,
validate_bls_to_execution_change
)
from staking_deposit.utils.constants import (
from ethstaker_deposit.utils.constants import (
MAX_DEPOSIT_AMOUNT,
)
from staking_deposit.settings import (
from ethstaker_deposit.settings import (
get_chain_setting,
)
from staking_deposit.utils.crypto import SHA256
from ethstaker_deposit.utils.crypto import SHA256
def generate_bls_to_execution_change(
folder: str,
@@ -116,7 +116,7 @@ def generate_bls_to_execution_change(
with open(filefolder, 'w') as f:
json.dump(bls_to_execution_changes, f)
if os.name == 'posix':
os.chmod(filefolder, int('440', 8)) # Read for owner & group
os.chmod(filefolder, int('400', 8)) # Read for owner & group
btec_file = filefolder
with open(btec_file, 'r') as f:
@@ -173,7 +173,7 @@ def validate_bls_credentials(
def validate_mnemonic(mnemonic: str, word_lists_path: str) -> str:
"""Validate a mnemonic using the staking-deposit-cli logic and returns the mnemonic.
"""Validate a mnemonic using the ethstaker-deposit-cli logic and returns the mnemonic.
Keyword arguments:
mnemonic -- the mnemonic to validate
@@ -253,7 +253,7 @@ def generate_keys(args):
with open(filefolder, 'w') as f:
json.dump(deposit_data, f, default=lambda x: x.hex())
if os.name == 'posix':
os.chmod(filefolder, int('440', 8)) # Read for owner & group
os.chmod(filefolder, int('400', 8)) # Read for owner & group
deposits_file = filefolder
items = zip(credentials.credentials, keystore_filefolders)

View File

@@ -0,0 +1,4 @@
[run]
relative_files = True
omit =
tests/**

View File

@@ -0,0 +1 @@
* @valefar-on-discord @remyroy

View File

@@ -0,0 +1,23 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -0,0 +1,6 @@
**What I did**
**Related issue**
<!-- If this is a bug fix, make sure your description includes "fixes #xxxx", or "closes #xxxx" -->
**(not mandatory) A picture of a cute animal, if possible in relation to what you did**

View File

@@ -0,0 +1,43 @@
[comment]: <> (This is a comment, it will not be included in the final release notes.)
[comment]: <> (This template will be used to automatically generate release notes with the ci-build workflow.)
[comment]: <> (The following values will be automatically replaced with generated content from the workflow.)
[comment]: <> (`[GENERATED-RELEASE-NOTES]`: Replaced with the GitHub generated release notes.)
[comment]: <> (`[WORKFLOW-URL]`: Replaced with the link to the workflow that generated the build.)
[comment]: <> (`[BINARIES-TABLE]`: Replaced with the a markdown formatted table with a link to each binary download.)
[comment]: <> (`[DOCKER-TABLE]`: Replaced with the a markdown formatted table with a link to the docker image.)
# Summary
`[Add a small summary here]`
# Known Issues
`[Remove this section if there is no known issue]`
# All changes
`[GENERATED-RELEASE-NOTES]`
# Building process
Release assets were built using Github Actions and [this workflow run](`[WORKFLOW-RUN-URL]`). You can establish the provenance of this build using [our artifact attestations](https://github.com/eth-educators/ethstaker-deposit-cli/attestations).
With [the GitHub CLI](https://cli.github.com/) installed, a simple way to verify these assets is to run this command while replacing `[filename]` with the path to the downloaded asset:
```console
gh attestation verify [filename] --repo eth-educators/ethstaker-deposit-cli
```
This step requires you to be online. If you want to perform this offline, follow [these instructions from GitHub](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/verifying-attestations-offline).
# Binaries
`[BINARIES-TABLE]`
# Docker image
`[DOCKER-TABLE]`
## License
By downloading and using this software, you agree to the [license](LICENSE).

View File

@@ -0,0 +1,372 @@
name: ci-build
run-name: ${{ github.actor }} is building binaries, building the docker image and drafting a release
on:
workflow_dispatch:
push:
tags:
- v*
# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-binaries:
runs-on: ${{ matrix.os }}
permissions:
id-token: write
contents: read
attestations: write
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, ubuntu-22.04-arm64, macos-13, macos-latest, windows-latest]
python-version: ["3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Setup variables (Linux & macOS)
if: ${{ startsWith(matrix.os, 'ubuntu-') || startsWith(matrix.os, 'macos-') }}
env:
MATRIX_OS: '${{ matrix.os }}'
run: |
echo "PYTHONHASHSEED=42" >> "$GITHUB_ENV"
SHORT_SHA=$(echo ${{ github.sha }} | cut -c -7)
echo "SHORT_SHA=${SHORT_SHA}" >> "$GITHUB_ENV"
if [[ $MATRIX_OS == ubuntu-* ]] ;
then
BUILD_SYSTEM=linux
BUILD_CONFIGS_PATH=./build_configs/linux
fi
if [[ $MATRIX_OS == macos-* ]] ;
then
BUILD_SYSTEM=darwin
BUILD_CONFIGS_PATH=./build_configs/macos
brew install coreutils
fi
BUILD_ARCHITECTURE=amd64
if [[ $MATRIX_OS == *arm* ]] || [[ $MATRIX_OS == macos-latest ]] ;
then
BUILD_ARCHITECTURE=arm64
fi
BUILD_FILE_NAME=ethstaker_deposit-cli-${SHORT_SHA}-${BUILD_SYSTEM}-${BUILD_ARCHITECTURE}
mkdir "${BUILD_FILE_NAME}"
echo "BUILD_FILE_NAME=${BUILD_FILE_NAME}" >> "$GITHUB_ENV"
echo "BUILD_CONFIGS_PATH=${BUILD_CONFIGS_PATH}" >> "$GITHUB_ENV"
- name: Setup variables (Windows)
if: ${{ startsWith(matrix.os, 'windows-') }}
env:
MATRIX_OS: '${{ matrix.os }}'
run: |
echo "PYTHONHASHSEED=42" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
$env:SHORT_SHA = "${{ github.sha }}".Substring(0, 7)
echo ("SHORT_SHA=" + $env:SHORT_SHA) | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
if ($env:MATRIX_OS.Contains("arm")) {
$env:BUILD_ARCHITECTURE = "arm64"
}
else {
$env:BUILD_ARCHITECTURE = "amd64"
}
$env:BUILD_FILE_NAME = ("ethstaker_deposit-cli-" + $env:SHORT_SHA + "-windows-" + $env:BUILD_ARCHITECTURE)
echo ("BUILD_FILE_NAME=" + $env:BUILD_FILE_NAME) | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
mkdir $env:BUILD_FILE_NAME
$env:BUILD_FILE_NAME_PATH = (".\" + $env:BUILD_FILE_NAME)
echo ("BUILD_FILE_NAME_PATH=" + $env:BUILD_FILE_NAME_PATH) | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: Build on Linux & macOS
if: ${{ startsWith(matrix.os, 'ubuntu-') || startsWith(matrix.os, 'macos-') }}
run: |
python -m pip install --upgrade pip
pip install -r "${BUILD_CONFIGS_PATH}"/requirements.txt
pyinstaller --distpath ./"${BUILD_FILE_NAME}" "${BUILD_CONFIGS_PATH}"/build.spec
export ARCHIVE_FILE_NAME="${BUILD_FILE_NAME}".tar.gz
echo "ARCHIVE_FILE_NAME=${ARCHIVE_FILE_NAME}" >> "$GITHUB_ENV"
tar -zcvf "${ARCHIVE_FILE_NAME}" ./"${BUILD_FILE_NAME}"
mkdir -p output/artifacts
cp "${ARCHIVE_FILE_NAME}" output/artifacts
sha256sum "${ARCHIVE_FILE_NAME}" | head -c 64 > output/artifacts/"${ARCHIVE_FILE_NAME}".sha256
- name: Build on Windows
if: ${{ startsWith(matrix.os, 'windows-') }}
run: |
python -m pip install --upgrade pip
pip install -r build_configs/windows/requirements.txt
pyinstaller --distpath $env:BUILD_FILE_NAME .\build_configs\windows\build.spec
$env:ZIP_FILE_NAME = ($env:BUILD_FILE_NAME + ".zip")
echo ("ZIP_FILE_NAME=" + $env:ZIP_FILE_NAME) | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Compress-Archive -Path $env:BUILD_FILE_NAME_PATH -DestinationPath $env:ZIP_FILE_NAME
mkdir output\artifacts
copy $env:ZIP_FILE_NAME output\artifacts
$env:CHECKSUM_FILE_NAME_PATH = ("output\artifacts\" + $env:ZIP_FILE_NAME + ".sha256")
certUtil -hashfile $env:ZIP_FILE_NAME SHA256 | findstr /i /v "SHA256" | findstr /i /v "CertUtil" > $env:CHECKSUM_FILE_NAME_PATH
- name: Generate artifacts attestation
uses: actions/attest-build-provenance@v1
with:
subject-path: output/artifacts/*
- name: Archive production artifacts
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.os }}-${{ github.sha }}-${{ github.run_id }}
path: output/artifacts
build-and-push-docker:
runs-on: ubuntu-latest
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
permissions:
contents: read
packages: write
attestations: write
id-token: write
outputs:
metadata: ${{ steps.push.outputs.metadata }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
- name: Log in to the Container registry
# This pinned action came from docker/login-action@v3, releases can be found on https://github.com/docker/login-action/releases
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
- name: Extract metadata (tags, labels) for Docker
id: meta
# This pinned action came from docker/metadata-action@v5, releases can be found on https://github.com/docker/metadata-action/releases
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
- name: Build and push Docker image
id: push
# This pinned action came from docker/build-push-action@v6, releases can be found on https://github.com/docker/build-push-action/releases
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see "[AUTOTITLE](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)."
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
create-release:
needs: [build-binaries, build-and-push-docker]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Download build binaries
uses: actions/download-artifact@v4
with:
path: assets/
pattern: binary-*
- name: Create draft release
uses: actions/github-script@v7
env:
DOCKER_IMAGE_METADATA: '${{ needs.build-and-push-docker.outputs.metadata }}'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
var path = require('path');
var fs = require('fs');
const { DOCKER_IMAGE_METADATA, REGISTRY, IMAGE_NAME } = process.env;
const dockerMetadata = JSON.parse(DOCKER_IMAGE_METADATA);
var tagName = '';
var dockerVersion = '';
if (context.eventName == 'push') {
const tagRegex = /(?:refs\/tags\/)?(v\d+\.\d+\.\d+)$/;
const match = context.ref.match(tagRegex);
if (match) {
tagName = match[1];
dockerVersion = tagName;
} else {
core.setFailed(`Cannot extract the tag version from ref value '${context.ref}'.`);
}
} else if (context.eventName == 'workflow_dispatch') {
tagName = `dev-${context.actor}-${context.sha.substring(0, 7)}-${context.runId}`;
dockerVersion = dockerMetadata['containerimage.digest'].replace(':', '-');
} else {
core.setFailed(`Unhandled triggering event.`);
}
console.log(`Creating draft release for tag ${tagName}...`)
console.log(`tagName: ${tagName}`);
console.log(`context.sha: ${context.sha}`);
const { data: release } = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tagName,
target_commitish: context.sha,
draft: true,
generate_release_notes: true,
});
console.log(`Release ${release.id} created.`);
let binariesMap = new Map();
const emptyBinaryObject = {
system: null,
architecture: null,
binary_archive: null,
binary_archive_download_url: null,
binary_checksum: null,
binary_checksum_download_url: null,
attestation: null,
};
const windowsSystem = 'Windows';
const macOSSystem = 'macOS';
const linuxSystem = 'Linux';
const amd64Architecture = 'x86_64';
const arm64Architecture = 'aarch64';
binariesMap.set('windows-amd64', Object.assign({}, emptyBinaryObject, {
system: windowsSystem,
architecture: amd64Architecture,
}));
binariesMap.set('windows-arm64', Object.assign({}, emptyBinaryObject, {
system: windowsSystem,
architecture: arm64Architecture,
}));
binariesMap.set('darwin-amd64', Object.assign({}, emptyBinaryObject, {
system: macOSSystem,
architecture: amd64Architecture,
}));
binariesMap.set('darwin-arm64', Object.assign({}, emptyBinaryObject, {
system: macOSSystem,
architecture: arm64Architecture,
}));
binariesMap.set('linux-amd64', Object.assign({}, emptyBinaryObject, {
system: linuxSystem,
architecture: amd64Architecture,
}));
binariesMap.set('linux-arm64', Object.assign({}, emptyBinaryObject, {
system: linuxSystem,
architecture: arm64Architecture,
}));
console.log('Uploading release assets...');
const binaryPlatformRegex = /(\w+)-(\w+)(?=\.(?:zip|tar\.gz)(.sha256)?$)/;
const archivesGlobber = await glob.create('assets/*/*')
for await (const file of archivesGlobber.globGenerator()) {
console.log(`Uploading ${path.basename(file)} to the release ${release.id}`);
const fileName = path.basename(file);
const fileContent = fs.readFileSync(file);
const { data: asset } = await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.id,
name: fileName,
data: fileContent,
});
const match = fileName.match(binaryPlatformRegex);
if (match) {
const platform = `${match[1]}-${match[2]}`;
const binaryDetails = binariesMap.get(platform);
if (fileName.endsWith('.sha256')) {
binariesMap.set(platform, Object.assign({}, binaryDetails, {
binary_checksum: fileName,
binary_checksum_download_url: asset.browser_download_url,
}));
} else {
binariesMap.set(platform, Object.assign({}, binaryDetails, {
binary_archive: fileName,
binary_archive_download_url: asset.browser_download_url,
}));
}
}
}
const binariesTable = [
'| System | Architecture | Binary | Checksum |',
'|---------|--------------|--------------------|------------------------|'
];
binariesMap.forEach((details, platform) => {
if (
details.binary_archive !== null &&
details.binary_archive_download_url !== null &&
details.binary_checksum !== null &&
details.binary_checksum_download_url !== null
) {
const system = details.system;
const architecture = details.architecture;
const binaryName = details.binary_archive;
const binaryUrl = details.binary_archive_download_url;
const checksumName = details.binary_checksum;
const checksumUrl = details.binary_checksum_download_url;
const binaryAssetUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/releases/download/${tagName}/${binaryName}`;
const checksumAssetUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/releases/download/${tagName}/${checksumName}`;
binariesTable.push(`| ${system} | ${architecture} | [${binaryName}](${binaryAssetUrl}) | [sha256](${checksumAssetUrl}) |`);
}
});
const binariesTableContent = binariesTable.join('\n');
const dockerTable = [
'| Version | Name | Package |',
'|---------|------|---------|'
];
dockerTable.push(`| ${dockerVersion} | \`${REGISTRY}/${IMAGE_NAME}:${dockerVersion}\` | [Github Package](https://github.com/${context.repo.owner}/${context.repo.repo}/pkgs/container/ethstaker-deposit-cli) |`);
const dockerTableContent = dockerTable.join('\n');
const { data: workflowRun } = await github.rest.actions.getWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.runId,
});
let releaseBodyTemplate = fs.readFileSync('.github/release_template.md', { encoding: 'utf8'});
console.log('Removing comments in release template...');
releaseBodyTemplate = releaseBodyTemplate.replaceAll(/^\[comment\]:\s*<>\s*\((.*?)\)\s*$/gm, '');
releaseBodyTemplate = releaseBodyTemplate.trim();
let releaseBody = releaseBodyTemplate.replaceAll('`[GENERATED-RELEASE-NOTES]`', release.body);
releaseBody = releaseBody.replaceAll('`[BINARIES-TABLE]`', binariesTableContent);
releaseBody = releaseBody.replaceAll('`[WORKFLOW-RUN-URL]`', workflowRun.html_url);
releaseBody = releaseBody.replaceAll('`[DOCKER-TABLE]`', dockerTableContent);
console.log('Updating release body with generated content and template...');
const { data: updatedRelease } = await github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.id,
tag_name: tagName,
target_commitish: context.sha,
body: releaseBody,
});
console.log(`Release ${updatedRelease.id} updated. Explore it on ${updatedRelease.html_url}`);

View File

@@ -0,0 +1,82 @@
# Safely comment on the PR - https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/
name: ci-comment
run-name: ${{ github.actor }} is adding coverage report comment on PR
on:
workflow_run:
workflows: [ "ci-runner" ]
types:
- completed
jobs:
completed-coverage:
runs-on: ubuntu-latest
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
outputs:
coverage: ${{ steps.output.outputs.coverage }}
steps:
- name: Get coverage job success
uses: actions/github-script@v7
env:
WORKFLOW_RUN_ID: '${{ github.event.workflow_run.id }}'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { WORKFLOW_RUN_ID } = process.env;
core.exportVariable('COVERAGE', 'unknown');
const { data: allJobs } = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: WORKFLOW_RUN_ID,
});
allJobs.jobs.forEach((job) => {
if (
job.name === 'coverage-combine' &&
job.status == 'completed' &&
job.conclusion == 'success'
) {
core.exportVariable('COVERAGE', 'completed');
}
});
- name: Output coverage status
id: output
run: echo "coverage=${COVERAGE}" >> "$GITHUB_OUTPUT"
comment:
needs: completed-coverage
# Runs only if we have a completed coverage job from the ci-runner workflow
if: ${{ needs.completed-coverage.outputs.coverage == 'completed' }}
permissions:
issues: write
contents: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: results
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Comment on PR
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
var fs = require('fs');
const issueNumberPath = './issue_number'
if (!fs.existsSync(issueNumberPath)) {
console.log("Coverage artifacts were not downloaded, succeeding early");
return;
}
var issueNumber = Number(fs.readFileSync(issueNumberPath));
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: fs.readFileSync('./coverage-text-report').toString()
});

View File

@@ -0,0 +1,28 @@
name: docs
run-name: ${{ github.actor }} is running docs
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup mdBook
# This pinned action came from peaceiris/actions-mdbook@v2, releases can be found on https://github.com/peaceiris/actions-mdbook/releases
uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08
with:
mdbook-version: '0.4.40'
- run: mdbook build ./docs
- name: Deploy
# This pinned action came from peaceiris/actions-gh-pages@v4, releases can be found on https://github.com/peaceiris/actions-gh-pages/releases
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs/book

View File

@@ -0,0 +1,110 @@
name: ci-runner
run-name: ${{ github.actor }} is testing and measuring code quality
on:
push:
branches: [main]
pull_request:
types: [opened, synchronize, labeled, unlabeled]
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.12
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt -r requirements_test.txt
- name: Run type checker
run: python -m mypy --config-file mypy.ini -p ethstaker_deposit
- name: Run linter
run: flake8 --config=flake8.ini ./ethstaker_deposit ./tests
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 'latest'
- name: Install jsonlint
run: npm install -g @prantlf/jsonlint@16.0.0
- name: Validate JSON files
run: |
find ./ethstaker_deposit/intl -name "*.json" -exec jsonlint --no-duplicate-keys --quiet --compact {} \;
ci-runner:
needs: lint
if: |
contains(github.event.pull_request.labels.*.name, 'run-tests') ||
github.event_name == 'push'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm64, macos-13, macos-latest, windows-latest]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt -r requirements_test.txt
- name: Run tests
run: |
coverage run --data-file=.coverage.${{ matrix.os }}.${{ matrix.python-version }} -m pytest tests
- name: Upload coverage data
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.os }}-${{ matrix.python-version }}
path: .coverage.${{ matrix.os }}.${{ matrix.python-version }}
retention-days: 1
if-no-files-found: error
include-hidden-files: true
coverage-combine:
needs: ci-runner
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.12
cache: 'pip'
- name: Download coverage data
uses: actions/download-artifact@v4
- name: Merge coverage data
id: merge
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt -r requirements_test.txt
coverage combine coverage*
coverage html
- name: Upload final coverage html report
uses: actions/upload-artifact@v4
id: upload-html
with:
name: coverage-html-report
path: htmlcov
- name: Generate text report
run: |
mkdir results
{
echo "Test Coverage: [Download HTML Report](${{ steps.upload-html.outputs.artifact-url }})" ;
echo ;
echo \`\`\` ;
coverage report ;
echo \`\`\` ;
} >> results/coverage-text-report
echo ${{ github.event.number }} > results/issue_number
- name: Upload final coverage text report
uses: actions/upload-artifact@v4
with:
name: results
path: results/

View File

@@ -1,10 +1,15 @@
validator_keys
bls_to_execution_changes
bls_to_execution_changes_keystore
exit_transactions
partial_deposits
validator_keys
# Python testing & linting:
build/
docs/book
dist/
venv/
.venv/
*.pytest_cache
*.hypothesis
*.mypy_cache
@@ -12,3 +17,4 @@ venv/
*.egg
__pycache__
.DS_Store
.coverage

View File

@@ -0,0 +1,25 @@
repos:
- repo: https://github.com/pycqa/flake8
rev: '7.1.1'
hooks:
- id: flake8
files: ^(ethstaker_deposit|tests)/
args: [--config, flake8.ini]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v1.11.2'
hooks:
- id: mypy
additional_dependencies:
- click==8.1.7
- eth-typing==5.0.0
- eth-utils==5.0.0
- pycryptodome==3.20.0
- py-ecc==7.0.1
- ssz==0.5.0
files: ^ethstaker_deposit/
args: [--config-file, mypy.ini]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: 'v4.6.0'
hooks:
- id: check-json
files: ^ethstaker_deposit/intl

View File

@@ -0,0 +1,12 @@
# This image is from python:3.12.7-slim-bookworm (https://hub.docker.com/_/python)
FROM python@sha256:032c52613401895aa3d418a4c563d2d05f993bc3ecc065c8f4e2280978acd249
WORKDIR /app
COPY requirements.txt ./
COPY ethstaker_deposit ./ethstaker_deposit
RUN pip3 install -r requirements.txt
ENTRYPOINT [ "python3", "-m", "ethstaker_deposit" ]

View File

@@ -1,7 +1,7 @@
VENV_NAME?=venv
VENV_ACTIVATE=. $(VENV_NAME)/bin/activate
PYTHON=${VENV_NAME}/bin/python3.8
DOCKER_IMAGE="ethereum/staking-deposit-cli:latest"
PYTHON=${VENV_NAME}/bin/python3.12
DOCKER_IMAGE="eth-educators/ethstaker-deposit-cli:latest"
help:
@echo "clean - remove build and Python file artifacts"
@@ -24,24 +24,22 @@ clean:
$(VENV_NAME)/bin/activate: requirements.txt
@test -d $(VENV_NAME) || python3 -m venv --clear $(VENV_NAME)
${VENV_NAME}/bin/python setup.py install
${VENV_NAME}/bin/python -m pip install -r requirements.txt
${VENV_NAME}/bin/python -m pip install -r requirements_test.txt
${VENV_NAME}/bin/python -m pip install -r requirements.txt -r requirements_test.txt
@touch $(VENV_NAME)/bin/activate
venv_build: $(VENV_NAME)/bin/activate
venv_build_test: venv_build
${VENV_NAME}/bin/python -m pip install -r requirements_test.txt
${VENV_NAME}/bin/python -m pip install -r requirements.txt -r requirements_test.txt
venv_test: venv_build_test
$(VENV_ACTIVATE) && python -m pytest ./tests
venv_lint: venv_build_test
$(VENV_ACTIVATE) && flake8 --config=flake8.ini ./staking_deposit ./tests && mypy --config-file mypy.ini -p staking_deposit
$(VENV_ACTIVATE) && flake8 --config=flake8.ini ./ethstaker_deposit ./tests && mypy --config-file mypy.ini -p ethstaker_deposit
venv_deposit: venv_build
$(VENV_ACTIVATE) && python ./staking_deposit/deposit.py $(filter-out $@,$(MAKECMDGOALS))
$(VENV_ACTIVATE) && python -m ethstaker_deposit $(filter-out $@,$(MAKECMDGOALS))
build_macos: venv_build
${VENV_NAME}/bin/python -m pip install -r ./build_configs/macos/requirements.txt

View File

@@ -0,0 +1,34 @@
# ethstaker-deposit-cli docs
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Documentation
- [Introduction](https://deposit-cli.ethstaker.cc/landing.html)
- [Quick Setup](https://deposit-cli.ethstaker.cc/quick_setup.html)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Introduction
`ethstaker-deposit-cli` is a tool for creating [EIP-2335 format](https://eips.ethereum.org/EIPS/eip-2335) BLS12-381 keystores and a corresponding `deposit_data*.json` file for [Ethereum Staking Launchpad](https://github.com/ethereum/staking-launchpad). One can also provide a keystore file to generate a `signed_exit_transaction*.json` file to be broadcast at a later date to exit a validator.
- **Warning: Please generate your keystores on your own safe, completely offline device.**
- **Warning: Please backup your mnemonic, keystores, and password securely.**
Please read [Launchpad Validator FAQs](https://launchpad.ethereum.org/faq#keys) before generating the keys.
You can find the audit report by Trail of Bits of the original staking-deposit-cli [here](https://github.com/trailofbits/publications/blob/master/reviews/ETH2DepositCLI.pdf). The audit of the updated ethstaker-deposit-cli is forthcoming.
## Canonical Deposit Contract and Launchpad
Ethstaker confirms the canonical Ethereum staking deposit contract addresses and launchpad URLs.
Please be sure that your ETH is deposited only to this deposit contract address, depending on chain.
Depositing to the wrong address **will** lose you your ETH.
- Ethereum mainnet
- Deposit address: [0x00000000219ab540356cBB839Cbe05303d7705Fa](https://etherscan.io/address/0x00000000219ab540356cBB839Cbe05303d7705Fa)
- [Launchpad](https://launchpad.ethereum.org/)
- Ethereum Holešky (Holešovice) testnet
- Deposit address: [0x4242424242424242424242424242424242424242](https://holesky.etherscan.io/address/0x4242424242424242424242424242424242424242)
- [Launchpad](https://holesky.launchpad.ethereum.org/)

View File

@@ -0,0 +1,13 @@
# Security Policy
## Supported Versions
We are still in active development. We will support the latest release with security updates as we find them.
| Version | Supported |
| ------- | ------------------ |
| 0.x.x | :white_check_mark: |
## Reporting a Vulnerability
Private vulnerability reporting is enabled on this repository. Feel free to report any security vulnerability through [the GitHub *Report a vulnerability* feature](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability).

View File

@@ -0,0 +1,6 @@
# common requirements.in
cffi
importlib-metadata
macholib
pyinstaller
setuptools

View File

@@ -0,0 +1,116 @@
# Install the requirements for the program
-r ../../requirements.txt
#
# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# pip-compile --allow-unsafe --generate-hashes --no-annotate --output-file=build_configs/common/requirements.txt build_configs/common/requirements.in
#
altgraph==0.17.4 \
--hash=sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406 \
--hash=sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff
cffi==1.17.0 \
--hash=sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f \
--hash=sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab \
--hash=sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499 \
--hash=sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058 \
--hash=sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693 \
--hash=sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb \
--hash=sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377 \
--hash=sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885 \
--hash=sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2 \
--hash=sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401 \
--hash=sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4 \
--hash=sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b \
--hash=sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59 \
--hash=sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f \
--hash=sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c \
--hash=sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555 \
--hash=sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa \
--hash=sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424 \
--hash=sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb \
--hash=sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2 \
--hash=sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8 \
--hash=sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e \
--hash=sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9 \
--hash=sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82 \
--hash=sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828 \
--hash=sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759 \
--hash=sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc \
--hash=sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118 \
--hash=sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf \
--hash=sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932 \
--hash=sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a \
--hash=sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29 \
--hash=sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206 \
--hash=sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2 \
--hash=sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c \
--hash=sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c \
--hash=sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0 \
--hash=sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a \
--hash=sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195 \
--hash=sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6 \
--hash=sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9 \
--hash=sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc \
--hash=sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb \
--hash=sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0 \
--hash=sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7 \
--hash=sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb \
--hash=sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a \
--hash=sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492 \
--hash=sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720 \
--hash=sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42 \
--hash=sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7 \
--hash=sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d \
--hash=sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d \
--hash=sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb \
--hash=sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4 \
--hash=sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2 \
--hash=sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b \
--hash=sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8 \
--hash=sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e \
--hash=sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204 \
--hash=sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3 \
--hash=sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150 \
--hash=sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4 \
--hash=sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76 \
--hash=sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e \
--hash=sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb \
--hash=sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91
importlib-metadata==8.4.0 \
--hash=sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1 \
--hash=sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5
macholib==1.16.3 \
--hash=sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30 \
--hash=sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c
packaging==24.1 \
--hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
--hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124
pycparser==2.22 \
--hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \
--hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc
pyinstaller==6.10.0 \
--hash=sha256:143840f8056ff7b910bf8f16f6cd92cc10a6c2680bb76d0a25d558d543d21270 \
--hash=sha256:28eca3817f176fdc19747e1afcf434f13bb9f17a644f611be2c5a61b1f498ed7 \
--hash=sha256:308e0a8670c9c9ac0cebbf1bbb492e71b6675606f2ec78bc4adfc830d209e087 \
--hash=sha256:3398a98fa17d47ccb31f8779ecbdacec025f7adb2f22757a54b706ac8b4fe906 \
--hash=sha256:46d75359668993ddd98630a3669dc5249f3c446e35239b43bc7f4155bc574748 \
--hash=sha256:6cf876d7d93b8b4f28d1ad57fa24645cf43119c79e985dd5e5f7a801245e6f53 \
--hash=sha256:703e041718987e46ba0568a2c71ecf2459fddef57cf9edf3efeed4a53e3dae3f \
--hash=sha256:95b55966e563e8b8f31a43882aea10169e9a11fdf38e626d86a2907b640c0701 \
--hash=sha256:b7c90c91921b3749083115b28f30f40abf2bb481ceff196d2b2ce0eaa2b3d429 \
--hash=sha256:d60fb22859e11483af735aec115fdde09467cdbb29edd9844839f2c920b748c0 \
--hash=sha256:db05e3f2f10f9f78c56f1fb163d9cb453433429fe4281218ebaf1ebfd39ba942 \
--hash=sha256:e9989f354ae4ed8a3bec7bdb37ae0d170751d6520e500f049c7cd0632d31d5c3
pyinstaller-hooks-contrib==2024.8 \
--hash=sha256:0057fe9a5c398d3f580e73e58793a1d4a8315ca91c3df01efea1c14ed557825a \
--hash=sha256:29b68d878ab739e967055b56a93eb9b58e529d5b054fbab7a2f2bacf80cef3e2
zipp==3.20.1 \
--hash=sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064 \
--hash=sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b
# The following packages are considered to be unsafe in a requirements file:
setuptools==74.0.0 \
--hash=sha256:0274581a0037b638b9fc1c6883cc71c0210865aaa76073f7882376b641b84e8f \
--hash=sha256:a85e96b8be2b906f3e3e789adec6a9323abf79758ecfa3065bd740d81158b11e

View File

@@ -1,14 +1,19 @@
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.utils.hooks import copy_metadata
datas = [
('../../ethstaker_deposit/key_handling/key_derivation/word_lists/*.txt', './ethstaker_deposit/key_handling/key_derivation/word_lists/'),
('../../ethstaker_deposit/intl', './ethstaker_deposit/intl'),
]
datas += copy_metadata('py_ecc')
datas += copy_metadata('ssz')
block_cipher = None
a = Analysis(['../../staking_deposit/deposit.py'],
a = Analysis(['../../ethstaker_deposit/deposit.py'],
binaries=[],
datas=[
('../../staking_deposit/key_handling/key_derivation/word_lists/*.txt', './staking_deposit/key_handling/key_derivation/word_lists/'),
('../../staking_deposit/intl', './staking_deposit/intl'),
],
datas=datas,
hiddenimports=[],
hookspath=[],
runtime_hooks=[],

View File

@@ -0,0 +1 @@
-r ../common/requirements.txt

View File

@@ -1,14 +1,19 @@
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.utils.hooks import copy_metadata
datas = [
('../../ethstaker_deposit/key_handling/key_derivation/word_lists/*.txt', './ethstaker_deposit/key_handling/key_derivation/word_lists/'),
('../../ethstaker_deposit/intl', './ethstaker_deposit/intl'),
]
datas += copy_metadata('py_ecc')
datas += copy_metadata('ssz')
block_cipher = None
a = Analysis(['..\\..\\staking_deposit\\deposit.py'],
a = Analysis(['../../ethstaker_deposit/deposit.py'],
binaries=[],
datas=[
('..\\..\\staking_deposit\\key_handling\\key_derivation\\word_lists\\*.txt', '.\\staking_deposit\\key_handling\\key_derivation\\word_lists'),
('..\\..\\staking_deposit\\intl', '.\\staking_deposit\\intl'),
],
datas=datas,
hiddenimports=[],
hookspath=[],
runtime_hooks=[],

View File

@@ -0,0 +1 @@
-r ../common/requirements.txt

View File

@@ -1,14 +1,19 @@
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.utils.hooks import copy_metadata
datas = [
('..\\..\\ethstaker_deposit\\key_handling\\key_derivation\\word_lists\\*.txt', '.\\ethstaker_deposit\\key_handling\\key_derivation\\word_lists'),
('..\\..\\ethstaker_deposit\\intl', '.\\ethstaker_deposit\\intl'),
]
datas += copy_metadata('py_ecc')
datas += copy_metadata('ssz')
block_cipher = None
a = Analysis(['../../staking_deposit/deposit.py'],
binaries=None,
datas=[
('../../staking_deposit/key_handling/key_derivation/word_lists/*.txt', './staking_deposit/key_handling/key_derivation/word_lists/'),
('../../staking_deposit/intl', './staking_deposit/intl'),
],
a = Analysis(['..\\..\\ethstaker_deposit\\deposit.py'],
binaries=[],
datas=datas,
hiddenimports=[],
hookspath=[],
runtime_hooks=[],

View File

@@ -0,0 +1,7 @@
# Windows binary build requirements
# Only use this to get new hashes, don't upgrade requirements.txt directly with pip-compile:
# It can't find pywin32
future
pefile
pywin32-ctypes
#pywin32 is not found by pip-compile

View File

@@ -0,0 +1,27 @@
-r ../common/requirements.txt
# Build tools for binary distribution
future==1.0.0 \
--hash=sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216 \
--hash=sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05
pefile==2024.8.26 \
--hash=sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632 \
--hash=sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f
pywin32==306 \
--hash=sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d \
--hash=sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65 \
--hash=sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e \
--hash=sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b \
--hash=sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4 \
--hash=sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040 \
--hash=sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a \
--hash=sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36 \
--hash=sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8 \
--hash=sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e \
--hash=sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802 \
--hash=sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a \
--hash=sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407 \
--hash=sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0
pywin32-ctypes==0.2.3 \
--hash=sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8 \
--hash=sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755

View File

@@ -5,25 +5,23 @@ if [[ "$OSTYPE" == "linux"* ]] || [[ "$OSTYPE" == "linux-android"* ]] || [[ "$OS
if [[ $1 == "install" ]]; then
echo "Installing dependencies..."
pip3 install -r requirements.txt
python3 setup.py install
exit 1
fi
echo "Running deposit-cli..."
python3 ./staking_deposit/deposit.py "$@"
python3 -m ethstaker_deposit "$@"
elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]]; then
echo $OSTYPE
if [[ $1 == "install" ]]; then
echo "Installing dependencies..."
pip install -r requirements.txt
python setup.py install
exit 1
fi
echo "Running deposit-cli..."
python ./staking_deposit/deposit.py "$@"
python -m ethstaker_deposit "$@"
else
echo "Sorry, to run deposit-cli on" $(uname -s)", please see the trouble-shooting on https://github.com/ethereum/staking-deposit-cli"
echo "Sorry, to run deposit-cli on" $(uname -s)", please see the trouble-shooting on https://github.com/eth-educators/ethstaker-deposit-cli"
exit 1
fi

View File

@@ -0,0 +1,4 @@
[book]
language = "en"
multilingual = false
src = "src"

View File

@@ -0,0 +1 @@
deposit-cli.ethstaker.cc

View File

@@ -0,0 +1,30 @@
# Summary
- [Ethstaker Deposit CLI](landing.md)
- [Quick Setup](quick_setup.md)
- [Other Install Options](other_install_options.md)
# Commands
- [New Mnemonic](new_mnemonic.md)
- [Existing Mnemonic](existing_mnemonic.md)
- [Generate BLS to Execution Change](generate_bls_to_execution_change.md)
- [Generate BLS to Execution Change Keystore](generate_bls_to_execution_change_keystore.md)
- [Exit Transaction Keystore](exit_transaction_keystore.md)
- [Exit Transaction Mnemonic](exit_transaction_mnemonic.md)
- [Partial Deposit](partial_deposit.md)
- [Test Keystore](test_keystore.md)
# File formats
- [Keystore](keystore_file.md)
- [Deposit Data](deposit_data_file.md)
- [BLS to Execution Change](bls_to_execution_change_file.md)
- [BLS to Execution Change Keystore](bls_to_execution_change_keystore_file.md)
- [Signed Exit Transaction](signed_exit_transaction_file.md)
# Development
- [Local Development](local_development.md)
- [Release Process](release_process.md)
- [Reporting a Vulnerability](reporting_vulnerability.md)

View File

@@ -0,0 +1,28 @@
# BLS to Execution Change file
A BLS to execution change file is created when calling the **[generate-bls-to-execution-change](generate_bls_to_execution_change.md)** command.
The BLS to execution change file is a JSON file. It contains a list of messages to change the withdrawal credentials for one or many validators. The format of the BLS to execution change file is loosly based on the input for the POST `/eth/v1/beacon/pool/bls_to_execution_changes` [API endpoint](https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolBLSToExecutionChange) as defined by [the Ethereum Beacon APIs](https://github.com/ethereum/beacon-APIs). Part of this content is based on the [SignedBLSToExecutionChange](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#signedblstoexecutionchange) container as defined in the [Ethereum Consensus Specifications](https://github.com/ethereum/consensus-specs/).
## Broadcasting
If you have access to a beacon node client running on your target network, you can publish these messages simply by calling the POST `/eth/v1/beacon/pool/bls_to_execution_changes` [API endpoint](https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolBLSToExecutionChange) and passing the content of the BLS to execution change file as the payload. You can also use [the Beaconcha.in Broadcast Signed Messages tool](https://beaconcha.in/tools/broadcast) which might be easier for most users.
## Example
```JSON
[
{
"message":{
"validator_index":"1804776",
"from_bls_pubkey":"0x970245df5f9cf7a082db195136a3066412b62e8bf04e21d7c3408d7fb36f34f20c4cb0883e798b82523b466f7a61c838",
"to_execution_address":"0x4d496ccc28058b1d74b7a19541663e21154f9c84"
},
"signature":"0xa1e47e6b1fdf4dd5f1dd3ddb3d47d2dcf446d096d49d90afef06a38dc02fba6b4d16d1dc1184c791e54666dabb8bdedd0660bc9bb3bc5d0e592eaf5f0c978cca4fcafe4037672940d6f1a44d2a33503c30cb98ca695979b1de9e321a8a694bc2",
"metadata":{
"network_name":"holesky",
"genesis_validators_root":"0x9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1",
"deposit_cli_version":"0.1.4-dev"
}
}
]
```

View File

@@ -0,0 +1,20 @@
# BLS to Execution Change Keystore file
A BLS to execution change keystore file is created when calling the **[generate-bls-to-execution-change-keystore](generate_bls_to_execution_change_keystore.md)** command.
The BLS to execution change keystore file is a JSON file. The format is very similar to the [BLS to execution change file](bls_to_execution_change_file.md) but with the `from_bls_pubkey` and `metadata` attributes removed.
## Utilizing
There is currently no integration with this file format with either the execution layer or beacon chain. The `signature` value must be provided as the `keystore_signature` for the [Signature file](https://github.com/eth-educators/update-credentials-without-mnemonic#signature-file-format).
## Example
```JSON
{
"message":{
"validator_index":"1804776",
"to_execution_address":"0x4d496ccc28058b1d74b7a19541663e21154f9c84"
},
"signature":"0xa1e47e6b1fdf4dd5f1dd3ddb3d47d2dcf446d096d49d90afef06a38dc02fba6b4d16d1dc1184c791e54666dabb8bdedd0660bc9bb3bc5d0e592eaf5f0c978cca4fcafe4037672940d6f1a44d2a33503c30cb98ca695979b1de9e321a8a694bc2",
}
```

View File

@@ -0,0 +1,59 @@
# Deposit Data file
A deposit data file is created when calling the **[new-mnemonic](new_mnemonic.md)**, the **[existing-mnemonic](existing_mnemonic.md)** or the **[partial-deposit](partial_deposit.md)** command.
The deposit data file is a JSON file that contains a list of deposits that is mostly used for the [Ethereum Staking Launchpad](https://github.com/ethereum/staking-launchpad). It is loosly based on [the DepositData structure](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#depositdata) and the [deposit function](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/deposit-contract.md#deposit-function) from the [deposit smart contract](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/deposit-contract.md).
## Format
Each deposit from the list will contain this structure:
```JSON
{
"pubkey": "string",
"withdrawal_credentials": "string",
"amount": "number",
"signature": "string",
"deposit_message_root": "string",
"deposit_data_root": "string",
"fork_version": "string",
"network_name": "string",
"deposit_cli_version": "string"
}
```
- **pubkey**: The validator public key value to be passed to the deposit function call.
- **withdrawal_credentials**: The withdrawal credentials value to be passed to the deposit function call.
- **amount**: The deposit amount to be sent with the call to the deposit function call. This should always be 32 Ethers (in GWEI, `32000000000`) when performing a deposit for a validator to be activated. The unit for this value is GWEI. For partial deposits, this value can be an integer value higher or equal than 1 and generally lower than or equal to 2048 in Ethers.
- **signature**: The signature value to be passed to the deposit function call.
- **deposit_message_root**: A deposit message root value used for validation by the Ethereum Staking Launchpad.
- **deposit_data_root**: The deposit data root value to be passed to the deposit function call.
- **fork_version**: The fork version of the network that this deposit file was created for.
- **network_name**: The network name of the network that this deposit file was created for.
- **deposit_cli_version**: The tool version used to create this file. We are currently faking this value to work around [an issue](https://github.com/eth-educators/ethstaker-deposit-cli/issues/216) with the Launchpad.
## Example
```JSON
[
{
"pubkey":"962195958e742b8dc5b2a25adede82fc5cd661827cb1e1237025e3d7847801aa5584d5bfdc6893413264cccfbff54128",
"withdrawal_credentials":"0007a213a9a50ddf7e00e53267af1c131ed82fec947f1c9656b54f9a20f7a87f",
"amount":32000000000,
"signature":"a2d56afe4540d5b506aea614848a0838b66c8707a91aa73cdb0e7b59d819f16be64881b5b621184b1668f4f1d024094a1861c5af783ded675b5763047c069c5eb805649f7c04656c96b31b0bccc34ed93c8fcd8f2e4a9e5c03453f305089d765",
"deposit_message_root":"f9bdb1e800b8f9f0db98c77271745e3c6140f18bf420543bda84fa92c393ddc7",
"deposit_data_root":"7cda08cd57c303f8af720b10a0852408bc31e015cb552f0029fc1024b8a1d615",
"fork_version":"01017000",
"network_name":"holesky",
"deposit_cli_version":"2.7.0"
},
{
"pubkey":"86ee0b826d7d5262324ace2fba4ed5f09a1cbef80552e3d945279f19bc4118a98e9a93257eb4c7731ccc10c19835d24f",
"withdrawal_credentials":"00daf39b8996943de9e93d417a6c5a4b958e4ae7a4d6e27f72aa08d41faa012f",
"amount":32000000000,
"signature":"83b227d72d9f1362d8676a61b91bb3882059e89c7d862d8030b1d37e890143869c105a78efce2a734f202da95076782219010e896ec9f8328a9e553134773626810d6c455bc1250a20e52a01684c051dd3e46151587267214cbb396673a13e89",
"deposit_message_root":"99dbb2392fef277c15e710ca0ead058c024adce15f9e8f369bbaea939c0009ed",
"deposit_data_root":"0fcb16fdb536b00a037a3ccde67187d21abfb726d677e40709ae6910d44377a5",
"fork_version":"01017000",
"network_name":"holesky",
"deposit_cli_version":"2.7.0"
}
]
```

View File

@@ -0,0 +1,47 @@
# existing-mnemonic
{{#include ./snippet/warning_message.md}}
## Description
Uses an existing BIP-39 mnemonic phrase for key generation.
## Optional Arguments
- **`--chain`**: The chain to use for generating the deposit data. Options are: 'mainnet', 'sepolia', 'holesky', 'mekong', or 'ephemery'.
- **`--mnemonic`**: The mnemonic you used to create withdrawal credentials. <span class="warning"></span>
- **`--mnemonic_language`**: The language of your mnemonic. If this is not provided we will attempt to determine it based on the mnemonic.
- **`--mnemonic_password`**: The mnemonic password you used in your key generation. Note: It's not the keystore password. <span class="warning"></span>
- **`--validator_start_index`**: The index of the first validator's keys you wish to generate. If this is your first time generating keys with this mnemonic, use 0. If you have generated keys using this mnemonic before, use the next index from which you want to start generating keys from. As an example if you've generated 4 keys before (keys #0, #1, #2, #3), then enter 4 here.
- **`--num_validators`**: Number of validators to create.
- **`--keystore_password`**: The password that is used to encrypt the provided keystore. Note: It's not your mnemonic password. <span class="warning"></span>
- **`--withdrawal_address`**: The Ethereum address that will be used in withdrawal. It typically starts with '0x' followed by 40 hexadecimal characters. Please make sure you have full control over the address you choose here. Once you set a withdrawal address on chain, it cannot be changed.
- **`--compounding / --regular-withdrawal`**: Generates compounding validators with 0x02 withdrawal credentials for a 2048 ETH maximum effective balance or generate regular validators with 0x01 withdrawal credentials for a 32 ETH maximum effective balance. Use of this option requires a withdrawal address. This feature is only supported on networks that have undergone the Pectra fork. Defaults to regular withdrawal.
- **`--amount`**: The amount to deposit to these validators in ether denomination. Must be at least 1 ether and can not have greater precision than 1 gwei. Use of this option requires compounding validators. Defaults to 32 ether.
- **`--pbkdf2`**: Will use pbkdf2 key derivation instead of scrypt for generated keystore files as defined in [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335#decryption-key). This can be a good alternative if you intend to work with a large number of keys, as it can improve performance however it is less secure. You should only use this option if you understand the associated risks and have familiarity with encryption.
- **`--folder`**: The folder where keystore and deposit data files will be saved.
- **`--devnet_chain_setting`**: The custom chain setting of a devnet or testnet. Note that it will override your `--chain` choice. This should be a JSON string containing an object with the following keys: network_name, genesis_fork_version, exit_fork_version and genesis_validator_root.
## Output files
A successful call to this command will result in one or many [keystore files](keystore_file.md) created, one per validator created, and one [deposit data file](deposit_data_file.md) created. The amount for each deposit in the deposit data file should always be 32 Ethers (`32000000000` in GWEI) with this command.
## Example Usage
```sh
./deposit existing-mnemonic
```
## Note
To make a deposit for a different amount other than 32 ETH, you need to have an existing keystore file or create one by using either the above command or **[new-mnemonic](new_mnemonic.md)**. Then, use the **[partial-deposit](partial_deposit.md)** command to specify the desired amount for each validator.

View File

@@ -0,0 +1,31 @@
# exit-transaction-keystore
{{#include ./snippet/warning_message.md}}
## Description
Creates an exit transaction using a keystore file.
## Optional Arguments
- **`--chain`**: The chain to use for generating the deposit data. Options are: 'mainnet', 'sepolia', 'holesky', 'mekong', or 'ephemery'.
- **`--keystore`**: The keystore file associating with the validator you wish to exit.
- **`--keystore_password`**: The password that is used to encrypt the provided keystore. Note: It's not your mnemonic password. <span class="warning"></span>
- **`--validator_index`**: The validator index corresponding to the provided keystore.
- **`--epoch`**: The epoch of when the exit transaction will be valid. The transaction will always be valid by default.
- **`--output_folder`**: The folder path for the `signed_exit_transaction-*` JSON file.
- **`--devnet_chain_setting`**: The custom chain setting of a devnet or testnet. Note that it will override your `--chain` choice. This should be a JSON string containing an object with the following keys: network_name, genesis_fork_version, exit_fork_version and genesis_validator_root.
## Output files
A successful call to this command will result in one [Signed Exit Transaction file](signed_exit_transaction_file.md) created.
## Example Usage
```sh
./deposit exit-transaction-keystore --keystore /path/to/keystore.json
```

View File

@@ -0,0 +1,35 @@
# exit-transaction-mnemonic
{{#include ./snippet/warning_message.md}}
## Description
Creates an exit transaction using a mnemonic phrase.
## Optional Arguments
- **`--chain`**: The chain to use for generating the deposit data. Options are: 'mainnet', 'sepolia', 'holesky', 'mekong', or 'ephemery'.
- **`--mnemonic`**: The mnemonic you used during key generation. <span class="warning"></span>
- **`--mnemonic_language`**: The language of your mnemonic. If this is not provided we will attempt to determine it based on the mnemonic.
- **`--mnemonic_password`**: The mnemonic password you used in your key generation. Note: It's not the keystore password. <span class="warning"></span>
- **`--validator_start_index`**: The index position for the keys to start generating keystores in [ERC-2334 format](https://eips.ethereum.org/EIPS/eip-2334#eth2-specific-parameters) format.
- **`--validator_indices`**: A list of the chosen validator index number(s) as identified on the beacon chain. Split multiple items with whitespaces or commas.
- **`--epoch`**: The epoch of when the exit transaction will be valid. The transaction will always be valid by default.
- **`--output_folder`**: The folder path for the `signed_exit_transaction-*` JSON file.
- **`--devnet_chain_setting`**: The custom chain setting of a devnet or testnet. Note that it will override your `--chain` choice. This should be a JSON string containing an object with the following keys: network_name, genesis_fork_version, exit_fork_version and genesis_validator_root.
## Output files
A successful call to this command will result in one or more [Signed Exit Transaction files](signed_exit_transaction_file.md) created.
## Example Usage
```sh
./deposit exit-transaction-mnemonic
```

View File

@@ -0,0 +1,37 @@
# generate-bls-to-execution-change
{{#include ./snippet/warning_message.md}}
## Description
Generates a BLS to execution address change message. This is used to add a withdrawal address to a validator that does not currently have one.
## Optional Arguments
- **`--bls_to_execution_changes_folder`**: The path to store the change JSON file.
- **`--chain`**: The chain to use for generating the deposit data. Options are: 'mainnet', 'sepolia', 'holesky', 'mekong', or 'ephemery'.
- **`--mnemonic`**: The mnemonic you used to create withdrawal credentials. <span class="warning"></span>
- **`--mnemonic_language`**: The language of your mnemonic. If this is not provided we will attempt to determine it based on the mnemonic.
- **`--mnemonic_password`**: The mnemonic password you used in your key generation. Note: It's not the keystore password. <span class="warning"></span>
- **`--validator_start_index`**: The index position for the keys to start generating withdrawal credentials for.
- **`--validator_indices`**: A list of the chosen validator index number(s) as identified on the beacon chain. Split multiple items with whitespaces or commas.
- **`--bls_withdrawal_credentials_list`**: A list of the old BLS withdrawal credentials of the given validator(s). It is for confirming you are using the correct keys. Split multiple items with whitespaces or commas.
- **`--withdrawal_address`**: The Ethereum address that will be used in withdrawal. It typically starts with '0x' followed by 40 hexadecimal characters. Please make sure you have full control over the address you choose here. Once you set a withdrawal address on chain, it cannot be changed.
- **`--devnet_chain_setting`**: The custom chain setting of a devnet or testnet. Note that it will override your `--chain` choice. This should be a JSON string containing an object with the following keys: network_name, genesis_fork_version, exit_fork_version and genesis_validator_root.
## Output files
A successful call to this command will result in one [BLS to Execution Change file](bls_to_execution_change_file.md) created.
## Example Usage
```sh
./deposit generate-bls-to-execution-change
```

View File

@@ -0,0 +1,35 @@
# generate-bls-to-execution-change-keystore
<div class="warning">
This command is associated with the a proposed solution to update withdrawal credentials for those who are missing their mnemonic. At this point this has not been approved or implemented and there is no guarantee credentials will be modified in the future.
The project is located [here](https://github.com/eth-educators/update-credentials-without-mnemonic) if you would like to learn more.
</div>
## Description
Signs a withdrawal credential update message using the provided keystore. This signature is one of the required proofs of ownership for validators who have lost or are missing their mnemonic and are unable to perform the BLS change needed to update their withdrawal credentials.
## Optional Arguments
- **`--chain`**: The chain to use for generating the deposit data. Options are: 'mainnet', 'sepolia', 'holesky', 'mekong', or 'ephemery'.
- **`--keystore`**: The keystore file associating with the validator you wish to sign with. This keystore file should match the provided validator index.
- **`--keystore_password`**: The password that is used to encrypt the provided keystore. Note: It's not your mnemonic password. <span class="warning"></span>
- **`--validator_index`**: The validator index corresponding to the provided keystore.
- **`--withdrawal_address`**: The Ethereum address that will be used in withdrawal. It typically starts with '0x' followed by 40 hexadecimal characters. Please make sure you have full control over the address you choose here. Once you set a withdrawal address on chain, it cannot be changed.
- **`--output_folder`**: The folder path for the `bls_to_execution_change_keystore_signature-*` JSON file.
- **`--devnet_chain_setting`**: The custom chain setting of a devnet or testnet. Note that it will override your `--chain` choice. This should be a JSON string containing an object with the following keys: network_name, genesis_fork_version, exit_fork_version and genesis_validator_root.
## Output files
A successful call to this command will result in one [BLS to Execution Change Keystore file](bls_to_execution_change_keystore_file.md) created.
## Example Usage
```sh
./deposit generate-bls-to-execution-change-keystore
```

View File

@@ -0,0 +1,7 @@
# Keystore file
A keystore file is created when calling the **[new-mnemonic](new_mnemonic.md)** or the **[existing-mnemonic](existing_mnemonic.md)** command.
The format of the keystore file is defined in the [ERC-2335: BLS12-381 Keystore](https://eips.ethereum.org/EIPS/eip-2335) document.
In the context of staking on the Ethereum blockchain, the keystore file is used to store the validator signing key and transport it across devices while being protected with a password. Once created by a tool such as ethstaker-deposit-cli, it will generally be copied on a device running a validator client to be imported by that validator client with the associated password. Once the validator signing key is available to the validator client, it generally provides all the secrets needed to perform the associated validator's duties.

View File

@@ -0,0 +1,81 @@
# Welcome to the `ethstaker-deposit-cli`!
The [`ethstaker-deposit-cli`](https://github.com/eth-educators/ethstaker-deposit-cli) is a command-line tool forked from Ethereum's [`staking-deposit-cli`](https://github.com/ethereum/staking-deposit-cli) with more functionality and improved performance.
## Important Concerns
This tool generates and/or utilizes mnemonics and validator key material. Both are highly sensitive materials, and their exposure could lead to the loss or theft of funds, as well as the compromise of privacy. Users must take appropriate measures to secure this information.
Here are some core recommendations:
- **Use this tool offline**: We highly recommend using this tool in an offline environment, preferably air-gapped, using a live USB such as [Tails](https://tails.net/install/download/index.en.html).
- **Avoid using passwords or mnemonics as command line arguments**: When executing any command, you will be prompted for the necessary arguments. This prevents the arguments from being stored in your command history which could create an attack vector.
- **Do not expose or share any material**: This tool generates and uses critically secure material that should not be shared or exposed. There is an abundance of thieves that use malicious links and scam tactics to get you to expose sensitive material. Be sure to triple check any tools or websites you are using and remain vigilant.
- **Create backups of your mnemonic**: The mnemonic generated by this tool is essential for your operation and success as a validator. Losing this mnemonic can result in significant financial loss. Carefully follow the instructions when creating a mnemonic, ensure you make a backup copy, and store it in a secure location. For long-term protection, consider using products like CryptoSteel and ColdTi, which offer resistance to the elements.
## Getting Started
If you want to start creating validator keys, please follow the [Quick Setup Instructions](quick_setup.md) or if you would like to run locally, view the [Local Development Instructions](local_development.md).
The general usage of the CLI is:
```sh
./deposit [OPTIONS] COMMAND [ARGS]
```
## Command Options
Each CLI command has a number of command options that can be provided:
- **`--language`**: The language you wish to use the CLI in. Options: العربية, ελληνικά, English, Français, Bahasa melayu, Italiano, 日本語, 한국어, Português do Brasil, român, 简体中文. Default to English.
- **`--non_interactive`**: Run CLI in non-interactive mode. This will skip all confirmation and internet connectivity checks.
- **`--ignore_connectivity`**: Skip internet connectivity check and warning.
## Commands
**You are not required to specify each argument when executing the command. We will prompt you for each argument for security purposes.**
If there is a specific command you would like to understand more, please choose from the following list:
- **[new-mnemonic](new_mnemonic.md)**: Used to generate a new mnemonic, validator keys, and deposit file. It is not recommended to use this command if you have existing validators.
- **[existing-mnemonic](existing_mnemonic.md)**: Provide a mnemonic to regenerate validator keys or create new ones.
- **[generate-bls-to-execution-change](generate_bls_to_execution_change.md)**: Add a withdrawal address to a validator that does not currently have one. It is **required** to have the corresponding mnemonic.
- **[generate-bls-to-execution-change-keystore](generate_bls_to_execution_change_keystore.md)**: Sign an update withdrawal credentials message using your validator keystore.
- **[exit-transaction-keystore](exit_transaction_keystore.md)**: Generate an exit message using the keystore of your validators.
- **[exit-transaction-mnemonic](exit_transaction_mnemonic.md)**: Generate an exit message using the mnemonic of your validators.
- **[partial-deposit](partial_deposit.md)**: Generate a deposit file with an existing validator key. Can be used to initiate a validator or deposit to an existing validator.
- **[test-keystore](test_keystore.md)**: Verify access to the provided keystore file by attempting to decrypt with the provided keystore password.
## Canonical Deposit Contract and Launchpad
Ethstaker confirms the canonical Ethereum staking deposit contract addresses and launchpad URLs.
Please be sure that your ETH is deposited only to this deposit contract address, depending on chain.
Depositing to the wrong address **will** lose you your ETH.
- Ethereum mainnet
- Deposit address: [0x00000000219ab540356cBB839Cbe05303d7705Fa](https://etherscan.io/address/0x00000000219ab540356cBB839Cbe05303d7705Fa)
- [Launchpad](https://launchpad.ethereum.org/)
- Ethereum Holešky (Holešovice) testnet
- Deposit address: [0x4242424242424242424242424242424242424242](https://holesky.etherscan.io/address/0x4242424242424242424242424242424242424242)
- [Launchpad](https://holesky.launchpad.ethereum.org/)
## Contributing
This project is open-source and welcomes contributions from the community. If you would like to contribute to the `ethstaker-deposit-cli`, please read the [Local Development Instructions](local_development.md), fork the project, and create a pull request with a description of the changes you have made and why.
## Support
If you encounter any issues or have questions while using the `ethstaker-deposit-cli`, please contact us on the [Ethstaker discord](https://dsc.gg/ethstaker).

View File

@@ -0,0 +1,79 @@
# Local Development Instructions
To install the `ethstaker-deposit-cli`, follow these steps:
## Prerequisites
Ensure you have the following software installed on your system:
- **Git**: Version control system to clone the repository. [Download Git](https://git-scm.com/downloads)
- **Python 3.9+**: The programming language required to run the tool. [Download Python](https://www.python.org/downloads/)
- **pip**: Package installer for Python, which is included with Python 3.9+.
On Windows, you'll need:
- **Git for Windows**: Version control system to clone the repository. Configure it to associate `.sh` files with `bash`. [Download GfW](https://git-scm.com/download/win)
- **Windows Terminal**: Optional but recommended command line console. Configure GfW to install a Git Bash profile. [Download Windows Terminal](https://apps.microsoft.com/detail/9n0dx20hk701)
- **Python 3.9+**: The programming language required to run the tool. [Download Python](https://apps.microsoft.com/detail/9ncvdn91xzqp)
- **Visual Studio C++**: The compiler required to build some of the dependencies of the tool. [Download VS C++](https://visualstudio.microsoft.com/vs/features/cplusplus/)
## Local Development Steps
1. **Clone the Repository**
```sh
git clone https://github.com/eth-educators/ethstaker-deposit-cli.git
```
2. **Navigate to the Project Directory**
```sh
cd ethstaker-deposit-cli
```
3. **Setup virtualenv (optional)**
Install `venv` if not already installed, e.g. for Debian/Ubuntu:
```sh
sudo apt update && sudo apt install python3-venv
```
Create a new [virtual environment](https://docs.python.org/3/library/venv.html):
```sh
python3 -m venv .venv
source .venv/bin/activate
```
4. **Install Dependencies**
```sh
pip3 install -r requirements.txt
```
5. **Run the CLI**
You can now run the CLI tool using the following command:
```sh
python3 -m ethstaker_deposit [OPTIONS] COMMAND [ARGS]
```
6. **Use pre-commit for PRs**
Install `pre-commit` if not already installed, e.g. for Debian/Ubuntu:
```sh
sudo apt update && sudo apt install pre-commit
```
Enable it for your `git commit` workflow:
```sh
pre-commit install
```
**To execute tests, you will need to install the test dependencies**:
```sh
python3 -m pip install -r requirements.txt -r requirements_test.txt
python3 -m pytest tests
```

View File

@@ -0,0 +1,43 @@
# new-mnemonic
{{#include ./snippet/warning_message.md}}
## Description
Generates a new BIP-39 mnemonic along with validator keystore and deposit files depending on how many validators you wish to create.
## Optional Arguments
- **`--mnemonic_language`**: The language of the BIP-39 mnemonic. Options are: 'chinese_simplified', 'chinese_traditional', 'czech', 'english', 'french', 'italian', 'japanese', 'korean', 'portuguese', 'spanish'.
- **`--chain`**: The chain to use for generating the deposit data. Options are: 'mainnet', 'sepolia', 'holesky', 'mekong', or 'ephemery'.
- **`--num_validators`**: Number of validators to create.
- **`--keystore_password`**: The password that is used to encrypt the provided keystore. Note: It's not your mnemonic password. <span class="warning"></span>
- **`--withdrawal_address`**: The Ethereum address that will be used in withdrawal. It typically starts with '0x' followed by 40 hexadecimal characters. Please make sure you have full control over the address you choose here. Once you set a withdrawal address on chain, it cannot be changed.
- **`--compounding / --regular-withdrawal`**: Generates compounding validators with 0x02 withdrawal credentials for a 2048 ETH maximum effective balance or generate regular validators with 0x01 withdrawal credentials for a 32 ETH maximum effective balance. Use of this option requires a withdrawal address. This feature is only supported on networks that have undergone the Pectra fork. Defaults to regular withdrawal.
- **`--amount`**: The amount to deposit to these validators in ether denomination. Must be at least 1 ether and can not have greater precision than 1 gwei. Use of this option requires compounding validators. Defaults to 32 ether.
- **`--pbkdf2`**: Will use pbkdf2 key encryption instead of scrypt for generated keystore files as defined in [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335#decryption-key). This can be a good alternative if you intend to work with a large number of keys, as it can improve performance. pbkdf2 encryption is, however, less secure than scrypt. You should only use this option if you understand the associated risks and have familiarity with encryption.
- **`--folder`**: The folder where keystore and deposit data files will be saved.
- **`--devnet_chain_setting`**: The custom chain setting of a devnet or testnet. Note that it will override your `--chain` choice. This should be a JSON string containing an object with the following keys: network_name, genesis_fork_version, exit_fork_version and genesis_validator_root.
## Output files
A successful call to this command will result in one or many [keystore files](keystore_file.md) created, one per validator created, and one [deposit data file](deposit_data_file.md) created. The amount for each deposit in the deposit data file should always be 32 Ethers (`32000000000` in GWEI) with this command.
## Example Usage
```sh
./deposit new-mnemonic
```
## Note
The newly generated mnemonic **must** be written down, on a piece of paper or transferred to steel. The clipboard is cleared when this command finishes. If the mnemonic is lost and the validator does not have a withdrawal address, funds **cannot** be recovered.
To make a deposit for a different amount other than 32 ETH, you need to have an existing keystore file or create one by using either the above command or **[existing-mnemonic](existing_mnemonic.md)**. Then, use the **[partial-deposit](partial_deposit.md)** command to specify the desired amount for each validator.

View File

@@ -0,0 +1,268 @@
# Other Install Options
## Linux or MacOS users
### Option 1. Build `deposit-cli` with native Python
1. **Python version checking**
Ensure you are using Python version >= Python3.9:
```sh
python3 -V
```
2. **Installation**
Install the dependencies:
```sh
pip3 install -r requirements.txt
```
Or use the helper script:
```sh
./deposit.sh install
```
3. **Create keys and `deposit_data-*.json`**
Run one of the following command to enter the interactive CLI:
```sh
./deposit.sh new-mnemonic
```
or
```sh
./deposit.sh existing-mnemonic
```
You can also run the tool with optional arguments:
```sh
./deposit.sh new-mnemonic --num_validators=<NUM_VALIDATORS> --mnemonic_language=english --chain=<CHAIN_NAME> --folder=<YOUR_FOLDER_PATH>
```
```sh
./deposit.sh existing-mnemonic --num_validators=<NUM_VALIDATORS> --validator_start_index=<START_INDEX> --chain=<CHAIN_NAME> --folder=<YOUR_FOLDER_PATH>
```
### Option 2. Build `deposit-cli` with `virtualenv`
1. **Python version checking**
Ensure you are using Python version >= Python3.9:
```sh
python3 -V
```
2. **Installation**
Install `venv` if not already installed, e.g. for Debian/Ubuntu:
```sh
sudo apt update && sudo apt install python3-venv
```
Create a new [virtual environment](https://docs.python.org/3/library/venv.html):
```sh
python3 -m venv .venv
source .venv/bin/activate
```
and install the dependencies:
```sh
pip3 install -r requirements.txt
```
3. **Create keys and `deposit_data-*.json`**
Run one of the following command to enter the interactive CLI:
```sh
python3 -m ethstaker_deposit new-mnemonic
```
or
```sh
python3 -m ethstaker_deposit existing-mnemonic
```
You can also run the tool with optional arguments:
```sh
python3 -m ethstaker_deposit new-mnemonic --num_validators=<NUM_VALIDATORS> --mnemonic_language=english --chain=<CHAIN_NAME> --folder=<YOUR_FOLDER_PATH>
```
```sh
python3 -m ethstaker_deposit existing-mnemonic --num_validators=<NUM_VALIDATORS> --validator_start_index=<START_INDEX> --chain=<CHAIN_NAME> --folder=<YOUR_FOLDER_PATH>
```
### Option 3. Use published docker image
1. **Pull the official docker image**
Run the following command to pull the latest docker image published on the Github repository:
```sh
docker pull ghcr.io/eth-educators/ethstaker-deposit-cli:latest
```
2. **Create keys and `deposit_data-*.json`**
Run the following command to enter the interactive CLI:
```sh
docker run -it --rm -v $(pwd)/validator_keys:/app/validator_keys ghcr.io/eth-educators/ethstaker-deposit-cli:latest
```
You can also run the tool with optional arguments:
```sh
docker run -it --rm -v $(pwd)/validator_keys:/app/validator_keys ghcr.io/eth-educators/ethstaker-deposit-cli:latest new-mnemonic --num_validators=<NUM_VALIDATORS> --mnemonic_language=english
```
Example for 1 validator on the [Holesky testnet](https://holesky.launchpad.ethereum.org/) using english:
```sh
docker run -it --rm -v $(pwd)/validator_keys:/app/validator_keys ghcr.io/eth-educators/ethstaker-deposit-cli:latest new-mnemonic --num_validators=1 --mnemonic_language=english --chain=holesky
```
### Option 4. Use local docker image
1. **Build the docker image**
Run the following command to locally build the docker image:
```sh
make build_docker
```
2. **Create keys and `deposit_data-*.json`**
Run the following command to enter the interactive CLI:
```sh
docker run -it --rm -v $(pwd)/validator_keys:/app/validator_keys eth-educators/ethstaker-deposit-cli
```
You can also run the tool with optional arguments:
```sh
docker run -it --rm -v $(pwd)/validator_keys:/app/validator_keys eth-educators/ethstaker-deposit-cli new-mnemonic --num_validators=<NUM_VALIDATORS> --mnemonic_language=english
```
Example for 1 validator on the [Holesky testnet](https://holesky.launchpad.ethereum.org/) using english:
```sh
docker run -it --rm -v $(pwd)/validator_keys:/app/validator_keys eth-educators/ethstaker-deposit-cli new-mnemonic --num_validators=1 --mnemonic_language=english --chain=holesky
```
----
## For Windows users
### Option 1. Build `deposit-cli` with native Python
1. **Python version checking**
Ensure you are using Python version >= Python12 (Assume that you've installed Python 3 as the main Python):
```sh
python -V
```
2. **Installation**
Install the dependencies:
```sh
pip3 install -r requirements.txt
```
Or use the helper script:
```sh
sh deposit.sh install
```
3. **Create keys and `deposit_data-*.json`**
Run one of the following command to enter the interactive CLI:
```sh
./deposit.sh new-mnemonic
```
or
```sh
./deposit.sh existing-mnemonic
```
You can also run the tool with optional arguments:
```sh
./deposit.sh new-mnemonic --num_validators=<NUM_VALIDATORS> --mnemonic_language=english --chain=<CHAIN_NAME> --folder=<YOUR_FOLDER_PATH>
```
```sh
./deposit.sh existing-mnemonic --num_validators=<NUM_VALIDATORS> --validator_start_index=<START_INDEX> --chain=<CHAIN_NAME> --folder=<YOUR_FOLDER_PATH>
```
### Option 2. Build `deposit-cli` with `virtualenv`
1. **Python version checking**
Ensure you are using Python version >= Python3.9 (Assume that you've installed Python 3 as the main Python):
```cmd
python -V
```
2. **Installation**
Create a new [virtual environment](https://docs.python.org/3/library/venv.html):
```sh
python3 -m venv .venv
.\.venv\Scripts\activate
```
and install the dependencies:
```cmd
pip3 install -r requirements.txt
```
3. **Create keys and `deposit_data-*.json`**
Run one of the following command to enter the interactive CLI:
```cmd
python -m ethstaker_deposit new-mnemonic
```
or
```cmd
python -m ethstaker_deposit existing-mnemonic
```
You can also run the tool with optional arguments:
```cmd
python -m ethstaker_deposit new-mnemonic --num_validators=<NUM_VALIDATORS> --mnemonic_language=english --chain=<CHAIN_NAME> --folder=<YOUR_FOLDER_PATH>
```
```cmd
python -m ethstaker_deposit existing-mnemonic --num_validators=<NUM_VALIDATORS> --validator_start_index=<START_INDEX> --chain=<CHAIN_NAME> --folder=<YOUR_FOLDER_PATH>
```

View File

@@ -0,0 +1,34 @@
# partial-deposit
{{#include ./snippet/warning_message.md}}
## Description
Creates a deposit file with an existing validator key. A validator key can be created using the **[new-mnemonic](new_mnemonic.md)** or the **[existing-mnemonic](existing_mnemonic.md)** commands. Can be used to initiate a validator or deposit to an existing validator.
If you wish to create a validator with 0x00 credentials, you must use the **[new-mnemonic](new_mnemonic.md)** or the **[existing-mnemonic](existing_mnemonic.md)** command.
## Optional Arguments
- **`--chain`**: The chain to use for generating the deposit data. Options are: 'mainnet', 'sepolia', 'holesky', 'mekong', or 'ephemery'.
- **`--keystore`**: The keystore file associating with the validator you wish to deposit to.
- **`--keystore_password`**: The password that is used to encrypt the provided keystore. Note: It's not your mnemonic password. <span class="warning"></span>
- **`--amount`**: The amount you wish to deposit in ether. Must be at least 1 and can not have precision beyond 1 gwei. Defaults to 32 ether.
- **`--withdrawal_address`**: The withdrawal address of the existing validator or the desired withdrawal address.
- **`--compounding / --regular-withdrawal`**: Generates compounding validators with 0x02 withdrawal credentials for a 2048 ETH maximum effective balance or generate regular validators with 0x01 withdrawal credentials for a 32 ETH maximum effective balance. Use of this option requires a withdrawal address. This feature is only supported on networks that have undergone the Pectra fork. Defaults to regular withdrawal.
- **`--output_folder`**: The folder path for the `deposit-*` JSON file.
- **`--devnet_chain_setting`**: The custom chain setting of a devnet or testnet. Note that it will override your `--chain` choice. This should be a JSON string containing an object with the following keys: network_name, genesis_fork_version, exit_fork_version and genesis_validator_root.
## Output file
A successful call to this command will result in one [deposit data file](deposit_data_file.md) created.
## Example Usage
```sh
./deposit partial-deposit --keystore /path/to/keystore.json
```

View File

@@ -0,0 +1,67 @@
# Quick Setup
This guide will walk you through the steps to download and set up the `ethstaker-deposit-cli` for your operating system.
**Build requirements**
- [Python **3.9+**](https://www.python.org/about/gettingstarted/)
- [pip3](https://pip.pypa.io/en/stable/installing/)
## Step 1: Download the Latest Release
### Download binary executable file
1. Navigate to the [Releases page](https://github.com/eth-educators/ethstaker-deposit-cli/releases) of the `ethstaker-deposit-cli` repository.
2. Download the corresponding file for your operating system:
- **Windows**: Look for a file with `windows` in the name.
- **MacOS**: Look for a file with `darwin` in the name.
- **Linux**: Look for a file with `linux` in the name.
3. Extract the contents of the zipped file
4. Open a terminal or command prompt and navigate to the extracted folder
For other installation options, including building with python or virtualenv and docker image instructions, go [here](other_install_options.md)
## Step 2: Verify the Installation
1. Make sure you have [the GitHub CLI installed](https://cli.github.com/).
2. Verify the attestation against the corresponding file but be sure to replace the contents with the exact file name:
```sh
gh attestation verify ethstaker_deposit-cli-*******-***.*** --repo eth-educators/ethstaker-deposit-cli
```
This step requires you to be online. If you want to perform this offline, follow [these instructions from GitHub](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/verifying-attestations-offline).
3. You should see `✓ Verification succeeded!` in the output **otherwise do not continue**.
## Step 3: Usage
**Windows users:** You should replace `./deposit` with `deposit.exe` to run properly.
**MacOS users:** In order to run from the terminal, you must first open the file to bypass MacOS code signing issues. Do so by right clicking the `deposit` file and then selecting `Open`.
**Linux users:** On Unix-based systems, keystores and the deposit_data*.json have 440/-r--r----- file permissions (user & group read only). This improves security by limiting which users and processes that have access to these files. If you are getting permission denied errors when handling your keystores, consider changing which user/group owns the file (with chown) or, if need be, change the file permissions with chmod.
Determine which command best suites what you would like to accomplish:
- **[new-mnemonic](new_mnemonic.md)**: Used to generate a new mnemonic, validator keys, and deposit file. It is not recommended to use this command if you have existing validators.
- **[existing-mnemonic](existing_mnemonic.md)**: Provide a mnemonic to regenerate validator keys or create new ones.
- **[generate-bls-to-execution-change](generate_bls_to_execution_change.md)**: Update your withdrawal credentials of existing validators. It is **required** to have the corresponding mnemonic.
- **[generate-bls-to-execution-change-keystore](generate_bls_to_execution_change_keystore.md)**: Sign an update withdrawal credentials message using your validator keystore.
- **[exit-transaction-keystore](exit_transaction_keystore.md)**: Generate an exit message using the keystore of your validators.
- **[exit-transaction-mnemonic](exit_transaction_mnemonic.md)**: Generate an exit message using the mnemonic of your validators.
- **[partial-deposit](partial_deposit.md)**: Generate a partial deposit using a validator keystore.
---
If you encounter any issues, please check the [issues page](https://github.com/eth-educators/ethstaker-deposit-cli/issues) for help or to report a problem. You may also contact us on the [Ethstaker discord](https://dsc.gg/ethstaker).

View File

@@ -0,0 +1,22 @@
# Release Process Instructions
This document is meant as a guide on how to perform and publish a new release version of [ethstaker-deposit-cli](https://github.com/eth-educators/ethstaker-deposit-cli). It includes step by step instructions to complete the release process.
1. Make sure all the tests from the latest [ci-runner workflow](https://github.com/eth-educators/ethstaker-deposit-cli/actions/workflows/runner.yml) on the latest commit of the main branch are completed. Make sure all tests are passing on all the supported platforms.
2. Determine a new version number. Version numbers should adhere to [Semantic Versioning](https://semver.org/). For any official release, it should include a major, a minor and a patch identifier like `1.0.0`.
3. Update `ethstaker_deposit/__init__.py`'s `__version__` variable with the new version number. Commit this change to the main branch of the main repository.
4. Add a tag to the main repository for this changed version commit above. The name of this tag should be a string starting with `v` concatenated with the version number. With git, the main repository cloned and the commit above being the head, it can look like this:
```console
git tag -a -m 'Version 1.0.0' v1.0.0
git push origin v1.0.0
```
5. Wait for all the build assets and the draft release to be created by [the ci-build workflow](https://github.com/eth-educators/ethstaker-deposit-cli/actions/workflows/build.yml).
6. Open the draft release and fill in the different sections correctly.
7. If this is not a production release, check the *Set as a pre-release* checkbox.
8. Click the *Publish release* button.
9. Determine a new dev version number. You can try to guess the next version number to the best of your ability. This will always be subject to change. Add a `dev` identifier to the version number to clearly indicate this is a dev version number.
10. Update `ethstaker_deposit/__init__.py`'s `__version__` variable with a new dev version number. Commit this change to the main branch.
## Release Notes Template
You can find the latest release notes template on https://github.com/eth-educators/ethstaker-deposit-cli/blob/main/.github/release_template.md .

View File

@@ -0,0 +1,3 @@
# Reporting a Vulnerability
Private vulnerability reporting is enabled on [the ethstaker-deposit-cli repository](https://github.com/eth-educators/ethstaker-deposit-cli). Feel free to report any security vulnerability through [the GitHub *Report a vulnerability* feature](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability).

View File

@@ -0,0 +1,20 @@
# Signed Exit Transaction file
A signed exit transaction file is created when calling the **[exit-transaction-keystore](exit_transaction_keystore.md)** or the **[exit-transaction-mnemonic](exit_transaction_mnemonic.md)** command.
The signed exit transaction file is a JSON file. It contains a single message to exit a validator. The format of the signed exit transaction file is loosly based on the input for the POST `/eth/v1/beacon/pool/voluntary_exits` [API endpoint](https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolVoluntaryExit) as defined by [the Ethereum Beacon APIs](https://github.com/ethereum/beacon-APIs). Part of this content is based on the [SignedVoluntaryExit](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#signedvoluntaryexit) signed envelope as defined in the [Ethereum Consensus Specifications](https://github.com/ethereum/consensus-specs/).
## Broadcasting
If you have access to a beacon node client running on your target network, you can publish this message simply by calling the POST `/eth/v1/beacon/pool/voluntary_exits` [API endpoint](https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolVoluntaryExit) and passing the content of the signed exit transaction file as the payload. You can also use [the Beaconcha.in Broadcast Signed Messages tool](https://beaconcha.in/tools/broadcast) which might be easier for most users.
## Example
```JSON
{
"message":{
"epoch":"0",
"validator_index":"1804776"
},
"signature":"0x97fa465cf1081755002e35fab245bd2872381b07cbfa4df245a13e3834aba83a347f5c2a36a34760e9fcc754dd862de700d103b1cb0d5d9ce293242ebf9ad44a6073e5c4794424428a8f983513d88e6ff6ddccbde7b3e0ea43554a0b856f3199"
}
```

View File

@@ -0,0 +1,3 @@
<div class="warning">
Including sensitive arguments when executing CLI commands poses a security risk, as these values can be accessible through disk and shell history. This exposure can allow other users, including admins and malicious actors, to gain access to sensitive information, putting your funds at risk.
</div>

View File

@@ -0,0 +1,20 @@
# test-keystore
{{#include ./snippet/warning_message.md}}
## Description
Will verify access to the provided keystore file by attempting to decrypt it with the provided keystore password.
## Optional Arguments
- **`--keystore`**: The keystore file you wish to verify.
- **`--keystore_password`**: The password used to attempt decryption of the provided keystore file. Note: It is not your mnemonic password. <span class="warning"></span>
## Example Usage
```sh
./deposit test-keystore --keystore /path/to/keystore.json
```

View File

@@ -0,0 +1 @@
__version__ = '0.5.0'

View File

@@ -0,0 +1,4 @@
from ethstaker_deposit import deposit
if __name__ == "__main__":
deposit.run()

View File

@@ -0,0 +1,72 @@
import json
import os
from typing import Any, Dict
from eth_typing import HexAddress
from eth_utils import to_canonical_address
from py_ecc.bls import G2ProofOfPossession as bls
from ethstaker_deposit.exceptions import ValidationError
from ethstaker_deposit.settings import BaseChainSetting
from ethstaker_deposit.utils.ssz import (
BLSToExecutionChangeKeystore,
SignedBLSToExecutionChangeKeystore,
compute_signing_root,
compute_bls_to_execution_change_keystore_domain,
)
from ethstaker_deposit.utils.file_handling import (
sensitive_opener,
)
def bls_to_execution_change_keystore_generation(
chain_setting: BaseChainSetting,
signing_key: int,
withdrawal_address: HexAddress,
validator_index: int) -> SignedBLSToExecutionChangeKeystore:
if withdrawal_address is None:
raise ValueError("The withdrawal address should NOT be empty.")
if chain_setting.GENESIS_VALIDATORS_ROOT is None:
raise ValidationError("The genesis validators root should NOT be empty "
"for this chain to obtain the BLS to execution change.")
message = BLSToExecutionChangeKeystore( # type: ignore[no-untyped-call]
validator_index=validator_index,
to_execution_address=to_canonical_address(withdrawal_address),
)
domain = compute_bls_to_execution_change_keystore_domain(
fork_version=chain_setting.GENESIS_FORK_VERSION,
genesis_validators_root=chain_setting.GENESIS_VALIDATORS_ROOT,
)
signing_root = compute_signing_root(message, domain)
signature = bls.Sign(signing_key, signing_root)
return SignedBLSToExecutionChangeKeystore( # type: ignore[no-untyped-call]
message=message,
signature=signature,
)
def export_bls_to_execution_change_keystore_json(folder: str,
signed_execution_change: SignedBLSToExecutionChangeKeystore,
timestamp: float) -> str:
signed_bls_to_execution_change_keystore_json: Dict[str, Any] = {}
address = '0x' + signed_execution_change.message.to_execution_address.hex() # type: ignore[attr-defined]
index = signed_execution_change.message.validator_index # type: ignore[attr-defined]
signature = '0x' + signed_execution_change.signature.hex() # type: ignore[attr-defined]
message = {
'to_execution_address': address,
'validator_index': index,
}
signed_bls_to_execution_change_keystore_json.update({'message': message})
signed_bls_to_execution_change_keystore_json.update({'signature': signature})
filefolder = os.path.join(
folder,
'bls_to_execution_change_keystore_signature-%s-%i.json' % (index, timestamp)
)
with open(filefolder, 'w', encoding='utf-8', opener=sensitive_opener) as f:
json.dump(signed_bls_to_execution_change_keystore_json, f)
return filefolder

View File

@@ -0,0 +1,128 @@
import click
import pyperclip
from typing import (
Any,
Callable,
Optional,
)
from ethstaker_deposit.exceptions import MultiLanguageError, ValidationError
from ethstaker_deposit.key_handling.key_derivation.mnemonic import (
reconstruct_mnemonic,
)
from ethstaker_deposit.utils import config
from ethstaker_deposit.utils.constants import (
MNEMONIC_LANG_OPTIONS,
WORD_LISTS_PATH,
)
from ethstaker_deposit.utils.click import (
captive_prompt_callback,
choice_prompt_func,
jit_option,
prompt_if_none,
)
from ethstaker_deposit.utils.intl import fuzzy_reverse_dict_lookup, get_first_options, load_text
from ethstaker_deposit.utils.validation import validate_int_range
from .generate_keys import (
generate_keys,
generate_keys_arguments_decorator,
)
def load_mnemonic_arguments_decorator(function: Callable[..., Any]) -> Callable[..., Any]:
'''
This is a decorator that, when applied to a parent-command, implements the
to obtain the necessary arguments for the generate_keys() subcommand.
'''
decorators = [
jit_option(
callback=lambda c, _, mnemonic:
captive_prompt_callback(
lambda mnemonic: validate_mnemonic(mnemonic=mnemonic, language=c.params.get('mnemonic_language')),
prompt=lambda: load_text(['arg_mnemonic', 'prompt'], func='existing_mnemonic'),
prompt_if=prompt_if_none,
)(c, _, mnemonic),
help=lambda: load_text(['arg_mnemonic', 'help'], func='existing_mnemonic'),
param_decls='--mnemonic',
prompt=False,
type=str,
),
jit_option(
callback=captive_prompt_callback(
lambda x: x,
lambda: load_text(['arg_mnemonic_password', 'prompt'], func='existing_mnemonic'),
lambda: load_text(['arg_mnemonic_password', 'confirm'], func='existing_mnemonic'),
lambda: load_text(['arg_mnemonic_password', 'mismatch'], func='existing_mnemonic'),
hide_input=True,
),
default='',
help=lambda: load_text(['arg_mnemonic_password', 'help'], func='existing_mnemonic'),
hidden=True,
param_decls='--mnemonic_password',
prompt=False,
),
jit_option(
callback=validate_mnemonic_language,
default=None,
help=lambda: load_text(['arg_mnemonic_language', 'help'], func='existing_mnemonic'),
param_decls='--mnemonic_language',
prompt=None,
),
]
for decorator in reversed(decorators):
function = decorator(function)
return function
def validate_mnemonic(mnemonic: str, language: Optional[str] = None) -> str:
try:
reconstructed_mnemonic = reconstruct_mnemonic(mnemonic, WORD_LISTS_PATH, language)
except MultiLanguageError as e:
# Get discovered languages from error and prompt user to select one of them
available_languages = sorted(get_first_options({lang: MNEMONIC_LANG_OPTIONS[lang] for lang in e.languages}))
prompt_message = choice_prompt_func(lambda: load_text(['arg_mnemonic_language'], func='validate_mnemonic'),
available_languages,
False)
language = click.prompt(prompt_message())
mnemonic_language = fuzzy_reverse_dict_lookup(language, MNEMONIC_LANG_OPTIONS)
return validate_mnemonic(mnemonic=mnemonic, language=mnemonic_language)
if reconstructed_mnemonic is not None:
return reconstructed_mnemonic
else:
raise ValidationError(load_text(['err_invalid_mnemonic']))
def validate_mnemonic_language(ctx: click.Context, param: Any, language: str) -> Optional[str]:
return fuzzy_reverse_dict_lookup(language, MNEMONIC_LANG_OPTIONS) if language else None
@click.command(
help=load_text(['arg_existing_mnemonic', 'help'], func='existing_mnemonic'),
)
@load_mnemonic_arguments_decorator
@jit_option(
callback=captive_prompt_callback(
lambda num: validate_int_range(num, 0, 2**32),
lambda: load_text(['arg_validator_start_index', 'prompt'], func='existing_mnemonic'),
lambda: load_text(['arg_validator_start_index', 'confirm'], func='existing_mnemonic'),
prompt_if=prompt_if_none,
),
default=0,
help=lambda: load_text(['arg_validator_start_index', 'help'], func='existing_mnemonic'),
param_decls="--validator_start_index",
prompt=False, # the callback handles the prompt
)
@generate_keys_arguments_decorator
@click.pass_context
def existing_mnemonic(ctx: click.Context, mnemonic: str, mnemonic_password: str, **kwargs: Any) -> None:
ctx.obj = {} if ctx.obj is None else ctx.obj # Create a new ctx.obj if it doesn't exist
ctx.obj.update({'mnemonic': mnemonic, 'mnemonic_password': mnemonic_password})
# Clear clipboard
try: # Failing this on headless Linux is expected
if not config.non_interactive:
click.pause(load_text(['msg_confirm_clipboard_clearing']))
pyperclip.copy(' ')
except Exception:
pass
ctx.forward(generate_keys)

View File

@@ -0,0 +1,150 @@
import click
import os
import sys
import time
from typing import Any, Optional
from ethstaker_deposit.exceptions import ValidationError
from ethstaker_deposit.utils.exit_transaction import exit_transaction_generation, export_exit_transaction_json
from ethstaker_deposit.key_handling.keystore import Keystore
from ethstaker_deposit.settings import (
MAINNET,
ALL_CHAIN_KEYS,
get_chain_setting,
BaseChainSetting,
)
from ethstaker_deposit.utils import config
from ethstaker_deposit.utils.click import (
captive_prompt_callback,
choice_prompt_func,
jit_option,
prompt_if_none,
prompt_if_other_is_none,
)
from ethstaker_deposit.utils.constants import DEFAULT_EXIT_TRANSACTION_FOLDER_NAME
from ethstaker_deposit.utils.intl import (
closest_match,
load_text,
)
from ethstaker_deposit.utils.validation import (
validate_int_range,
validate_keystore_file,
verify_signed_exit_json,
validate_devnet_chain_setting,
)
FUNC_NAME = 'exit_transaction_keystore'
@click.command(
help=load_text(['arg_exit_transaction_keystore', 'help'], func=FUNC_NAME),
)
@jit_option(
callback=captive_prompt_callback(
lambda x: closest_match(x, ALL_CHAIN_KEYS),
choice_prompt_func(
lambda: load_text(['arg_exit_transaction_keystore_chain', 'prompt'], func=FUNC_NAME),
ALL_CHAIN_KEYS
),
prompt_if=prompt_if_other_is_none('devnet_chain_setting'),
default=MAINNET,
),
default=MAINNET,
help=lambda: load_text(['arg_exit_transaction_keystore_chain', 'help'], func=FUNC_NAME),
param_decls='--chain',
prompt=False, # the callback handles the prompt
)
@jit_option(
callback=captive_prompt_callback(
lambda file: validate_keystore_file(file),
lambda: load_text(['arg_exit_transaction_keystore_keystore', 'prompt'], func=FUNC_NAME),
prompt_if=prompt_if_none,
),
help=lambda: load_text(['arg_exit_transaction_keystore_keystore', 'help'], func=FUNC_NAME),
param_decls='--keystore',
prompt=False,
)
@jit_option(
callback=captive_prompt_callback(
lambda x: x,
lambda: load_text(['arg_exit_transaction_keystore_keystore_password', 'prompt'], func=FUNC_NAME),
None,
lambda: load_text(['arg_exit_transaction_keystore_keystore_password', 'invalid'], func=FUNC_NAME),
True,
),
help=lambda: load_text(['arg_exit_transaction_keystore_keystore_password', 'help'], func=FUNC_NAME),
hide_input=True,
param_decls='--keystore_password',
prompt=lambda: load_text(['arg_exit_transaction_keystore_keystore_password', 'prompt'], func=FUNC_NAME),
)
@jit_option(
callback=captive_prompt_callback(
lambda num: validate_int_range(num, 0, 2**32),
lambda: load_text(['arg_validator_index', 'prompt'], func=FUNC_NAME),
),
help=lambda: load_text(['arg_validator_index', 'help'], func=FUNC_NAME),
param_decls='--validator_index',
prompt=lambda: load_text(['arg_validator_index', 'prompt'], func=FUNC_NAME),
)
@jit_option(
default=0,
help=lambda: load_text(['arg_exit_transaction_keystore_epoch', 'help'], func=FUNC_NAME),
param_decls='--epoch',
)
@jit_option(
default=os.getcwd(),
help=lambda: load_text(['arg_exit_transaction_keystore_output_folder', 'help'], func=FUNC_NAME),
param_decls='--output_folder',
type=click.Path(exists=True, file_okay=False, dir_okay=True),
)
@jit_option(
callback=validate_devnet_chain_setting,
default=None,
help=lambda: load_text(['arg_devnet_chain_setting', 'help'], func=FUNC_NAME),
param_decls='--devnet_chain_setting',
is_eager=True,
)
@click.pass_context
def exit_transaction_keystore(
ctx: click.Context,
chain: str,
keystore: Keystore,
keystore_password: str,
validator_index: int,
epoch: int,
output_folder: str,
devnet_chain_setting: Optional[BaseChainSetting],
**kwargs: Any) -> None:
try:
secret_bytes = keystore.decrypt(keystore_password)
except ValueError:
click.echo(load_text(['arg_exit_transaction_keystore_keystore_password', 'mismatch']), err=True)
sys.exit(1)
signing_key = int.from_bytes(secret_bytes, 'big')
# Get chain setting
chain_setting = devnet_chain_setting if devnet_chain_setting is not None else get_chain_setting(chain)
signed_exit = exit_transaction_generation(
chain_setting=chain_setting,
signing_key=signing_key,
validator_index=validator_index,
epoch=epoch,
)
folder = os.path.join(output_folder, DEFAULT_EXIT_TRANSACTION_FOLDER_NAME)
if not os.path.exists(folder):
os.mkdir(folder)
click.echo(load_text(['msg_exit_transaction_creation']))
saved_folder = export_exit_transaction_json(folder=folder, signed_exit=signed_exit, timestamp=time.time())
click.echo(load_text(['msg_verify_exit_transaction']))
if (not verify_signed_exit_json(saved_folder, keystore.pubkey, chain_setting)):
raise ValidationError(load_text(['err_verify_exit_transaction']))
click.echo(load_text(['msg_creation_success']) + saved_folder)
if not config.non_interactive:
click.pause(load_text(['msg_pause']))

View File

@@ -0,0 +1,190 @@
import click
import concurrent.futures
import os
import time
from typing import Any, Sequence, Dict, Optional
from ethstaker_deposit.cli.existing_mnemonic import load_mnemonic_arguments_decorator
from ethstaker_deposit.credentials import Credential
from ethstaker_deposit.exceptions import ValidationError
from ethstaker_deposit.settings import (
MAINNET,
ALL_CHAIN_KEYS,
get_chain_setting,
BaseChainSetting,
)
from ethstaker_deposit.utils import config
from ethstaker_deposit.utils.click import (
captive_prompt_callback,
choice_prompt_func,
jit_option,
prompt_if_other_is_none,
)
from ethstaker_deposit.utils.constants import DEFAULT_EXIT_TRANSACTION_FOLDER_NAME
from ethstaker_deposit.utils.intl import (
closest_match,
load_text,
)
from ethstaker_deposit.utils.validation import (
validate_int_range,
validate_validator_indices,
verify_signed_exit_json,
validate_devnet_chain_setting,
)
def _credential_builder(kwargs: Dict[str, Any]) -> Credential:
return Credential(**kwargs)
def _exit_exporter(kwargs: Dict[str, Any]) -> str:
credential: Credential = kwargs.pop('credential')
return credential.save_exit_transaction(**kwargs)
def _exit_verifier(kwargs: Dict[str, Any]) -> bool:
credential: Credential = kwargs.pop('credential')
kwargs['pubkey'] = credential.signing_pk.hex()
kwargs['chain_setting'] = credential.chain_setting
return verify_signed_exit_json(**kwargs)
FUNC_NAME = 'exit_transaction_mnemonic'
@click.command(
help=load_text(['arg_exit_transaction_mnemonic', 'help'], func=FUNC_NAME),
)
@jit_option(
callback=captive_prompt_callback(
lambda x: closest_match(x, ALL_CHAIN_KEYS),
choice_prompt_func(
lambda: load_text(['arg_exit_transaction_mnemonic_chain', 'prompt'], func=FUNC_NAME),
ALL_CHAIN_KEYS
),
prompt_if=prompt_if_other_is_none('devnet_chain_setting'),
default=MAINNET,
),
default=MAINNET,
help=lambda: load_text(['arg_exit_transaction_mnemonic_chain', 'help'], func=FUNC_NAME),
param_decls='--chain',
prompt=False, # the callback handles the prompt
)
@load_mnemonic_arguments_decorator
@jit_option(
callback=captive_prompt_callback(
lambda num: validate_int_range(num, 0, 2**32),
lambda: load_text(['arg_exit_transaction_mnemonic_start_index', 'prompt'], func=FUNC_NAME),
),
default=0,
help=lambda: load_text(['arg_exit_transaction_mnemonic_start_index', 'help'], func=FUNC_NAME),
param_decls="--validator_start_index",
prompt=lambda: load_text(['arg_exit_transaction_mnemonic_start_index', 'prompt'], func=FUNC_NAME),
)
@jit_option(
callback=captive_prompt_callback(
lambda validator_indices: validate_validator_indices(validator_indices),
lambda: load_text(['arg_exit_transaction_mnemonic_indices', 'prompt'], func=FUNC_NAME),
),
help=lambda: load_text(['arg_exit_transaction_mnemonic_indices', 'help'], func=FUNC_NAME),
param_decls='--validator_indices',
prompt=lambda: load_text(['arg_exit_transaction_mnemonic_indices', 'prompt'], func=FUNC_NAME),
)
@jit_option(
default=0,
help=lambda: load_text(['arg_exit_transaction_mnemonic_epoch', 'help'], func=FUNC_NAME),
param_decls='--epoch',
)
@jit_option(
default=os.getcwd(),
help=lambda: load_text(['arg_exit_transaction_mnemonic_output_folder', 'help'], func=FUNC_NAME),
param_decls='--output_folder',
type=click.Path(exists=True, file_okay=False, dir_okay=True),
)
@jit_option(
callback=validate_devnet_chain_setting,
default=None,
help=lambda: load_text(['arg_devnet_chain_setting', 'help'], func=FUNC_NAME),
param_decls='--devnet_chain_setting',
is_eager=True,
)
@click.pass_context
def exit_transaction_mnemonic(
ctx: click.Context,
chain: str,
mnemonic: str,
mnemonic_password: str,
validator_start_index: int,
validator_indices: Sequence[int],
epoch: int,
output_folder: str,
devnet_chain_setting: Optional[BaseChainSetting],
**kwargs: Any) -> None:
folder = os.path.join(output_folder, DEFAULT_EXIT_TRANSACTION_FOLDER_NAME)
# Get chain setting
chain_setting = devnet_chain_setting if devnet_chain_setting is not None else get_chain_setting(chain)
num_keys = len(validator_indices)
key_indices = range(validator_start_index, validator_start_index + num_keys)
# We are not using CredentialList because from_mnemonic assumes key generation flow
credentials = []
with click.progressbar(length=num_keys, label=load_text(['msg_key_creation']), # type: ignore[var-annotated]
show_percent=False, show_pos=True) as bar:
executor_kwargs = [{
'mnemonic': mnemonic,
'mnemonic_password': mnemonic_password,
'index': index,
'amount': 0,
'chain_setting': chain_setting,
'hex_withdrawal_address': None,
'compounding': False,
} for index in key_indices]
with concurrent.futures.ProcessPoolExecutor() as executor:
for credential in executor.map(_credential_builder, executor_kwargs):
credentials.append(credential)
bar.update(1)
if not os.path.exists(folder):
os.mkdir(folder)
transaction_filefolders = []
with click.progressbar(length=num_keys, # type: ignore[var-annotated]
label=load_text(['msg_exit_transaction_creation']),
show_percent=False, show_pos=True) as bar:
executor_kwargs = [{
'credential': credential,
'validator_index': validator_index,
'epoch': epoch,
'folder': folder,
'timestamp': time.time(),
} for credential, validator_index in zip(credentials, validator_indices)]
with concurrent.futures.ProcessPoolExecutor() as executor:
for filefolder in executor.map(_exit_exporter, executor_kwargs):
transaction_filefolders.append(filefolder)
bar.update(1)
with click.progressbar(length=num_keys, # type: ignore[var-annotated]
label=load_text(['msg_verify_exit_transaction']),
show_percent=False, show_pos=True) as bar:
executor_kwargs = [{
'file_folder': file,
'credential': credential,
} for file, credential in zip(transaction_filefolders, credentials)]
with concurrent.futures.ProcessPoolExecutor() as executor:
for valid_exit in executor.map(_exit_verifier, executor_kwargs):
bar.update(1)
if not valid_exit:
raise ValidationError(load_text(['err_verify_exit_transactions']))
click.echo(load_text(['msg_creation_success']) + folder)
if not config.non_interactive:
click.pause(load_text(['msg_pause']))

View File

@@ -1,52 +1,67 @@
import os
import click
import json
import concurrent.futures
from typing import (
Any,
Sequence,
Dict,
Optional
)
from eth_typing import HexAddress
from staking_deposit.credentials import (
from ethstaker_deposit.credentials import (
CredentialList,
Credential
)
from staking_deposit.utils.validation import (
from ethstaker_deposit.utils.ascii_art import OWL_0
from ethstaker_deposit.utils.validation import (
validate_bls_withdrawal_credentials_list,
validate_bls_withdrawal_credentials_matching,
validate_eth1_withdrawal_address,
validate_withdrawal_address,
validate_int_range,
verify_bls_to_execution_change_json,
validate_validator_indices,
validate_devnet_chain_setting,
)
from staking_deposit.utils.constants import (
from ethstaker_deposit.utils.constants import (
DEFAULT_BLS_TO_EXECUTION_CHANGES_FOLDER_NAME,
MAX_DEPOSIT_AMOUNT,
MIN_ACTIVATION_AMOUNT,
)
from staking_deposit.utils.click import (
from ethstaker_deposit.utils.click import (
captive_prompt_callback,
choice_prompt_func,
jit_option,
prompt_if_none,
prompt_if_other_is_none,
)
from staking_deposit.exceptions import ValidationError
from staking_deposit.utils.intl import (
from ethstaker_deposit.exceptions import ValidationError
from ethstaker_deposit.utils.intl import (
closest_match,
load_text,
)
from staking_deposit.settings import (
ALL_CHAINS,
from ethstaker_deposit.utils import config
from ethstaker_deposit.utils.terminal import clear_terminal
from ethstaker_deposit.settings import (
MAINNET,
PRATER,
ALL_CHAIN_KEYS,
get_chain_setting,
get_devnet_chain_setting,
BaseChainSetting,
)
from .existing_mnemonic import (
load_mnemonic_arguments_decorator,
)
def get_password(text: str) -> str:
return click.prompt(text, hide_input=True, show_default=False, type=str)
def _validate_credentials_match(kwargs: Dict[str, Any]) -> Optional[ValidationError]:
credential: Credential = kwargs.pop('credential')
bls_withdrawal_credentials: bytes = kwargs.pop('bls_withdrawal_credentials')
try:
validate_bls_withdrawal_credentials_matching(bls_withdrawal_credentials, credential)
except ValidationError as e:
return e
return None
FUNC_NAME = 'generate_bls_to_execution_change'
@@ -63,20 +78,18 @@ FUNC_NAME = 'generate_bls_to_execution_change'
)
@jit_option(
callback=captive_prompt_callback(
lambda x: closest_match(x, list(ALL_CHAINS.keys())),
lambda x: closest_match(x, ALL_CHAIN_KEYS),
choice_prompt_func(
lambda: load_text(['arg_chain', 'prompt'], func=FUNC_NAME),
list(ALL_CHAINS.keys())
ALL_CHAIN_KEYS
),
prompt_if=prompt_if_other_is_none('devnet_chain_setting'),
default=MAINNET,
),
default=MAINNET,
help=lambda: load_text(['arg_chain', 'help'], func=FUNC_NAME),
param_decls='--chain',
prompt=choice_prompt_func(
lambda: load_text(['arg_chain', 'prompt'], func=FUNC_NAME),
# Since `prater` is alias of `goerli`, do not show `prater` in the prompt message.
list(key for key in ALL_CHAINS.keys() if key != PRATER)
),
prompt=False, # the callback handles the prompt
)
@load_mnemonic_arguments_decorator
@jit_option(
@@ -103,27 +116,30 @@ FUNC_NAME = 'generate_bls_to_execution_change'
lambda bls_withdrawal_credentials_list:
validate_bls_withdrawal_credentials_list(bls_withdrawal_credentials_list),
lambda: load_text(['arg_bls_withdrawal_credentials_list', 'prompt'], func=FUNC_NAME),
prompt_if=prompt_if_none,
),
help=lambda: load_text(['arg_bls_withdrawal_credentials_list', 'help'], func=FUNC_NAME),
param_decls='--bls_withdrawal_credentials_list',
prompt=lambda: load_text(['arg_bls_withdrawal_credentials_list', 'prompt'], func=FUNC_NAME),
prompt=False, # the callback handles the prompt, to avoid second callback with bytes
)
@jit_option(
callback=captive_prompt_callback(
lambda address: validate_eth1_withdrawal_address(None, None, address),
lambda: load_text(['arg_execution_address', 'prompt'], func=FUNC_NAME),
lambda: load_text(['arg_execution_address', 'confirm'], func=FUNC_NAME),
lambda: load_text(['arg_execution_address', 'mismatch'], func=FUNC_NAME),
lambda address: validate_withdrawal_address(None, None, address),
lambda: load_text(['arg_withdrawal_address', 'prompt'], func=FUNC_NAME),
lambda: load_text(['arg_withdrawal_address', 'confirm'], func=FUNC_NAME),
lambda: load_text(['arg_withdrawal_address', 'mismatch'], func=FUNC_NAME),
prompt_if=prompt_if_none,
),
help=lambda: load_text(['arg_execution_address', 'help'], func=FUNC_NAME),
param_decls=['--execution_address', '--eth1_withdrawal_address'],
prompt=lambda: load_text(['arg_execution_address', 'prompt'], func=FUNC_NAME),
help=lambda: load_text(['arg_withdrawal_address', 'help'], func=FUNC_NAME),
param_decls=['--withdrawal_address'],
prompt=False, # the callback handles the prompt
)
@jit_option(
# Only for devnet tests
callback=validate_devnet_chain_setting,
default=None,
help="[DEVNET ONLY] Set specific GENESIS_FORK_VERSION value",
help=lambda: load_text(['arg_devnet_chain_setting', 'help'], func=FUNC_NAME),
param_decls='--devnet_chain_setting',
is_eager=True,
)
@click.pass_context
def generate_bls_to_execution_change(
@@ -135,8 +151,8 @@ def generate_bls_to_execution_change(
validator_start_index: int,
validator_indices: Sequence[int],
bls_withdrawal_credentials_list: Sequence[bytes],
execution_address: HexAddress,
devnet_chain_setting: str,
withdrawal_address: HexAddress,
devnet_chain_setting: Optional[BaseChainSetting],
**kwargs: Any) -> None:
# Generate folder
bls_to_execution_changes_folder = os.path.join(
@@ -147,16 +163,7 @@ def generate_bls_to_execution_change(
os.mkdir(bls_to_execution_changes_folder)
# Get chain setting
chain_setting = get_chain_setting(chain)
if devnet_chain_setting is not None:
click.echo('\n%s\n' % '**[Warning] Using devnet chain setting to generate the SignedBLSToExecutionChange.**\t')
devnet_chain_setting_dict = json.loads(devnet_chain_setting)
chain_setting = get_devnet_chain_setting(
network_name=devnet_chain_setting_dict['network_name'],
genesis_fork_version=devnet_chain_setting_dict['genesis_fork_version'],
genesis_validator_root=devnet_chain_setting_dict['genesis_validator_root'],
)
chain_setting = devnet_chain_setting if devnet_chain_setting is not None else get_chain_setting(chain)
if len(validator_indices) != len(bls_withdrawal_credentials_list):
raise ValueError(
@@ -165,7 +172,7 @@ def generate_bls_to_execution_change(
)
num_validators = len(validator_indices)
amounts = [MAX_DEPOSIT_AMOUNT] * num_validators
amounts = [MIN_ACTIVATION_AMOUNT] * num_validators
credentials = CredentialList.from_mnemonic(
mnemonic=mnemonic,
@@ -174,16 +181,25 @@ def generate_bls_to_execution_change(
amounts=amounts,
chain_setting=chain_setting,
start_index=validator_start_index,
hex_eth1_withdrawal_address=execution_address,
hex_withdrawal_address=withdrawal_address,
compounding=False,
)
# Check if the given old bls_withdrawal_credentials is as same as the mnemonic generated
for i, credential in enumerate(credentials.credentials):
try:
validate_bls_withdrawal_credentials_matching(bls_withdrawal_credentials_list[i], credential)
except ValidationError as e:
click.echo('\n[Error] ' + str(e))
return
with click.progressbar(length=len(credentials.credentials), # type: ignore[var-annotated]
label=load_text(['msg_credentials_verification']),
show_percent=False, show_pos=True) as bar:
executor_kwargs = [{
'credential': credential,
'bls_withdrawal_credentials': bls_withdrawal_credentials_list[i],
} for i, credential in enumerate(credentials.credentials)]
with concurrent.futures.ProcessPoolExecutor() as executor:
for e in executor.map(_validate_credentials_match, executor_kwargs):
bar.update(1)
if e is not None:
click.echo('\n\n[Error] ' + str(e))
return
btec_file = credentials.export_bls_to_execution_change_json(bls_to_execution_changes_folder, validator_indices)
@@ -191,12 +207,15 @@ def generate_bls_to_execution_change(
btec_file,
credentials.credentials,
input_validator_indices=validator_indices,
input_execution_address=execution_address,
input_withdrawal_address=withdrawal_address,
chain_setting=chain_setting,
)
if not json_file_validation_result:
raise ValidationError(load_text(['err_verify_btec']))
clear_terminal()
click.echo(OWL_0)
click.echo(load_text(['msg_creation_success']) + str(bls_to_execution_changes_folder))
click.pause(load_text(['msg_pause']))
if not config.non_interactive:
click.pause(load_text(['msg_pause']))

View File

@@ -0,0 +1,167 @@
import click
import os
import sys
import time
from typing import Any, Optional
from eth_typing import HexAddress
from ethstaker_deposit.bls_to_execution_change_keystore import (
bls_to_execution_change_keystore_generation,
export_bls_to_execution_change_keystore_json,
)
from ethstaker_deposit.exceptions import ValidationError
from ethstaker_deposit.key_handling.keystore import Keystore
from ethstaker_deposit.utils import config
from ethstaker_deposit.utils.validation import (
validate_withdrawal_address,
validate_int_range,
validate_keystore_file,
verify_bls_to_execution_change_keystore_json,
validate_devnet_chain_setting,
)
from ethstaker_deposit.utils.constants import (
DEFAULT_BLS_TO_EXECUTION_CHANGES_KEYSTORE_FOLDER_NAME,
)
from ethstaker_deposit.utils.click import (
captive_prompt_callback,
choice_prompt_func,
jit_option,
prompt_if_none,
prompt_if_other_is_none,
)
from ethstaker_deposit.utils.intl import (
closest_match,
load_text,
)
from ethstaker_deposit.settings import (
MAINNET,
ALL_CHAIN_KEYS,
get_chain_setting,
BaseChainSetting,
)
FUNC_NAME = 'generate_bls_to_execution_change_keystore'
@click.command(
help=load_text(['arg_generate_bls_to_execution_change', 'help'], func=FUNC_NAME),
)
@jit_option(
callback=captive_prompt_callback(
lambda x: closest_match(x, ALL_CHAIN_KEYS),
choice_prompt_func(
lambda: load_text(['arg_chain', 'prompt'], func=FUNC_NAME),
ALL_CHAIN_KEYS
),
prompt_if=prompt_if_other_is_none('devnet_chain_setting'),
default=MAINNET,
),
default=MAINNET,
help=lambda: load_text(['arg_chain', 'help'], func=FUNC_NAME),
param_decls='--chain',
prompt=False, # the callback handles the prompt
)
@jit_option(
callback=captive_prompt_callback(
lambda file: validate_keystore_file(file),
lambda: load_text(['arg_bls_to_execution_changes_keystore_keystore', 'prompt'], func=FUNC_NAME),
prompt_if=prompt_if_none,
),
help=lambda: load_text(['arg_bls_to_execution_changes_keystore_keystore', 'help'], func=FUNC_NAME),
param_decls='--keystore',
prompt=False,
)
@jit_option(
callback=captive_prompt_callback(
lambda x: x,
lambda: load_text(['arg_bls_to_execution_changes_keystore_keystore_password', 'prompt'], func=FUNC_NAME),
None,
lambda: load_text(['arg_bls_to_execution_changes_keystore_keystore_password', 'invalid'], func=FUNC_NAME),
True,
),
help=lambda: load_text(['arg_bls_to_execution_changes_keystore_keystore_password', 'help'], func=FUNC_NAME),
hide_input=True,
param_decls='--keystore_password',
prompt=lambda: load_text(['arg_bls_to_execution_changes_keystore_keystore_password', 'prompt'], func=FUNC_NAME),
)
@jit_option(
callback=captive_prompt_callback(
lambda num: validate_int_range(num, 0, 2**32),
lambda: load_text(['arg_validator_index', 'prompt'], func=FUNC_NAME),
),
help=lambda: load_text(['arg_validator_index', 'help'], func=FUNC_NAME),
param_decls='--validator_index',
prompt=lambda: load_text(['arg_validator_index', 'prompt'], func=FUNC_NAME),
)
@jit_option(
callback=captive_prompt_callback(
lambda address: validate_withdrawal_address(None, None, address, True),
lambda: load_text(['arg_withdrawal_address', 'prompt'], func=FUNC_NAME),
lambda: load_text(['arg_withdrawal_address', 'confirm'], func=FUNC_NAME),
lambda: load_text(['arg_withdrawal_address', 'mismatch'], func=FUNC_NAME),
prompt_if=prompt_if_none,
),
help=lambda: load_text(['arg_withdrawal_address', 'help'], func=FUNC_NAME),
param_decls=['--withdrawal_address'],
prompt=False, # the callback handles the prompt
)
@jit_option(
default=os.getcwd(),
help=lambda: load_text(['arg_bls_to_execution_changes_keystore_output_folder', 'help'], func=FUNC_NAME),
param_decls='--output_folder',
type=click.Path(exists=True, file_okay=False, dir_okay=True),
)
@jit_option(
callback=validate_devnet_chain_setting,
default=None,
help=lambda: load_text(['arg_devnet_chain_setting', 'help'], func=FUNC_NAME),
param_decls='--devnet_chain_setting',
is_eager=True,
)
@click.pass_context
def generate_bls_to_execution_change_keystore(
ctx: click.Context,
chain: str,
keystore: Keystore,
keystore_password: str,
validator_index: int,
withdrawal_address: HexAddress,
output_folder: str,
devnet_chain_setting: Optional[BaseChainSetting],
**kwargs: Any) -> None:
try:
secret_bytes = keystore.decrypt(keystore_password)
except ValueError:
click.echo(load_text(['arg_bls_to_execution_changes_keystore_keystore_password', 'mismatch']), err=True)
sys.exit(1)
signing_key = int.from_bytes(secret_bytes, 'big')
# Get chain setting
chain_setting = devnet_chain_setting if devnet_chain_setting is not None else get_chain_setting(chain)
signed_btec = bls_to_execution_change_keystore_generation(
chain_setting=chain_setting,
signing_key=signing_key,
validator_index=validator_index,
withdrawal_address=withdrawal_address,
)
folder = os.path.join(output_folder, DEFAULT_BLS_TO_EXECUTION_CHANGES_KEYSTORE_FOLDER_NAME)
if not os.path.exists(folder):
os.mkdir(folder)
click.echo(load_text(['msg_key_creation']))
saved_folder = export_bls_to_execution_change_keystore_json(folder=folder,
signed_execution_change=signed_btec,
timestamp=time.time())
click.echo(load_text(['msg_verify_btec']))
if (not verify_bls_to_execution_change_keystore_json(saved_folder, keystore.pubkey, chain_setting)):
raise ValidationError(load_text(['err_verify_btec']))
click.echo(load_text(['msg_creation_success']) + saved_folder)
if not config.non_interactive:
click.pause(load_text(['msg_pause']))

View File

@@ -0,0 +1,209 @@
import click
import os
import time
from typing import (
Any,
Callable,
Optional,
)
from eth_typing import HexAddress
from ethstaker_deposit.credentials import (
CredentialList,
)
from ethstaker_deposit.exceptions import ValidationError
from ethstaker_deposit.utils import config
from ethstaker_deposit.utils.validation import (
verify_deposit_data_json,
validate_int_range,
validate_password_strength,
validate_withdrawal_address,
validate_yesno,
validate_deposit_amount,
validate_devnet_chain_setting,
)
from ethstaker_deposit.utils.constants import (
DEFAULT_VALIDATOR_KEYS_FOLDER_NAME,
MIN_ACTIVATION_AMOUNT,
ETH2GWEI,
)
from ethstaker_deposit.utils.ascii_art import RHINO_0
from ethstaker_deposit.utils.click import (
captive_prompt_callback,
choice_prompt_func,
jit_option,
prompt_if_none,
prompt_if_other_is_none,
prompt_if_other_exists,
prompt_if_other_value,
)
from ethstaker_deposit.utils.intl import (
closest_match,
load_text,
)
from ethstaker_deposit.utils.terminal import clear_terminal
from ethstaker_deposit.settings import (
MAINNET,
ALL_CHAIN_KEYS,
get_chain_setting,
BaseChainSetting,
)
min_activation_amount_eth = MIN_ACTIVATION_AMOUNT // ETH2GWEI
def generate_keys_arguments_decorator(function: Callable[..., Any]) -> Callable[..., Any]:
'''
This is a decorator that, when applied to a parent-command, implements the
to obtain the necessary arguments for the generate_keys() subcommand.
'''
decorators = [
jit_option(
callback=captive_prompt_callback(
lambda num: validate_int_range(num, 1, 2**32),
lambda: load_text(['num_validators', 'prompt'], func='generate_keys_arguments_decorator')
),
help=lambda: load_text(['num_validators', 'help'], func='generate_keys_arguments_decorator'),
param_decls="--num_validators",
prompt=lambda: load_text(['num_validators', 'prompt'], func='generate_keys_arguments_decorator'),
),
jit_option(
default=os.getcwd(),
help=lambda: load_text(['folder', 'help'], func='generate_keys_arguments_decorator'),
param_decls='--folder',
type=click.Path(exists=True, file_okay=False, dir_okay=True),
),
jit_option(
callback=captive_prompt_callback(
lambda x: closest_match(x, ALL_CHAIN_KEYS),
choice_prompt_func(
lambda: load_text(['chain', 'prompt'], func='generate_keys_arguments_decorator'),
ALL_CHAIN_KEYS
),
prompt_if=prompt_if_other_is_none('devnet_chain_setting'),
default=MAINNET,
),
default=MAINNET,
help=lambda: load_text(['chain', 'help'], func='generate_keys_arguments_decorator'),
param_decls='--chain',
prompt=False, # the callback handles the prompt
),
jit_option(
callback=captive_prompt_callback(
validate_password_strength,
lambda: load_text(['keystore_password', 'prompt'], func='generate_keys_arguments_decorator'),
lambda: load_text(['keystore_password', 'confirm'], func='generate_keys_arguments_decorator'),
lambda: load_text(['keystore_password', 'mismatch'], func='generate_keys_arguments_decorator'),
True,
prompt_if=prompt_if_none,
),
help=lambda: load_text(['keystore_password', 'help'], func='generate_keys_arguments_decorator'),
hide_input=True,
param_decls='--keystore_password',
prompt=False, # the callback handles the prompt
),
jit_option(
callback=captive_prompt_callback(
lambda address: validate_withdrawal_address(None, None, address),
lambda: load_text(['arg_withdrawal_address', 'prompt'], func='generate_keys_arguments_decorator'),
lambda: load_text(['arg_withdrawal_address', 'confirm'], func='generate_keys_arguments_decorator'),
lambda: load_text(['arg_withdrawal_address', 'mismatch'], func='generate_keys_arguments_decorator'),
default="",
prompt_if=prompt_if_none,
),
default="",
help=lambda: load_text(['arg_withdrawal_address', 'help'], func='generate_keys_arguments_decorator'),
param_decls=['--withdrawal_address', '--execution_address', '--eth1_withdrawal_address'],
prompt=False, # the callback handles the prompt
),
jit_option(
callback=captive_prompt_callback(
lambda value: validate_yesno(None, None, value),
lambda: load_text(['arg_compounding', 'prompt'], func='generate_keys_arguments_decorator'),
default="False",
prompt_if=prompt_if_other_exists('withdrawal_address'),
),
default=False,
help=lambda: load_text(['arg_compounding', 'help'], func='generate_keys_arguments_decorator'),
param_decls='--compounding/--regular-withdrawal',
prompt=False, # the callback handles the prompt
type=bool,
show_default=True,
),
jit_option(
callback=captive_prompt_callback(
lambda amount: validate_deposit_amount(amount),
lambda: load_text(['arg_amount', 'prompt'], func='generate_keys_arguments_decorator'),
default=str(min_activation_amount_eth),
prompt_if=prompt_if_other_value('compounding', True),
),
default=str(min_activation_amount_eth),
help=lambda: load_text(['arg_amount', 'help'], func='generate_keys_arguments_decorator'),
param_decls='--amount',
prompt=False, # the callback handles the prompt
show_default=True,
),
jit_option(
default=False,
is_flag=True,
param_decls='--pbkdf2',
help=lambda: load_text(['arg_pbkdf2', 'help'], func='generate_keys_arguments_decorator'),
),
jit_option(
callback=validate_devnet_chain_setting,
default=None,
help=lambda: load_text(['arg_devnet_chain_setting', 'help'], func='generate_keys_arguments_decorator'),
param_decls='--devnet_chain_setting',
is_eager=True,
),
]
for decorator in reversed(decorators):
function = decorator(function)
return function
@click.command()
@click.pass_context
def generate_keys(ctx: click.Context, validator_start_index: int,
num_validators: int, folder: str, chain: str, keystore_password: str,
withdrawal_address: HexAddress, compounding: bool, amount: int, pbkdf2: bool,
devnet_chain_setting: Optional[BaseChainSetting], **kwargs: Any) -> None:
mnemonic = ctx.obj['mnemonic']
mnemonic_password = ctx.obj['mnemonic_password']
if withdrawal_address is None or not compounding:
amount = MIN_ACTIVATION_AMOUNT
amounts = [amount] * num_validators
folder = os.path.join(folder, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME)
# Get chain setting
chain_setting = devnet_chain_setting if devnet_chain_setting is not None else get_chain_setting(chain)
if not os.path.exists(folder):
os.mkdir(folder)
clear_terminal()
click.echo(RHINO_0)
click.echo(load_text(['msg_key_creation']))
credentials = CredentialList.from_mnemonic(
mnemonic=mnemonic,
mnemonic_password=mnemonic_password,
num_keys=num_validators,
amounts=amounts,
chain_setting=chain_setting,
start_index=validator_start_index,
hex_withdrawal_address=withdrawal_address,
compounding=compounding,
use_pbkdf2=pbkdf2
)
timestamp = time.time()
keystore_filefolders = credentials.export_keystores(password=keystore_password, folder=folder, timestamp=timestamp)
deposits_file = credentials.export_deposit_data_json(folder=folder, timestamp=timestamp)
if not credentials.verify_keystores(keystore_filefolders=keystore_filefolders, password=keystore_password):
raise ValidationError(load_text(['err_verify_keystores']))
if not verify_deposit_data_json(deposits_file, credentials.credentials):
raise ValidationError(load_text(['err_verify_deposit']))
click.echo(load_text(['msg_creation_success']) + folder)
if not config.non_interactive:
click.pause(load_text(['msg_pause']))

View File

@@ -1,26 +1,28 @@
import click
import pyperclip
from typing import (
Any,
)
from staking_deposit.key_handling.key_derivation.mnemonic import (
from ethstaker_deposit.key_handling.key_derivation.mnemonic import (
get_mnemonic,
reconstruct_mnemonic,
)
from staking_deposit.utils.click import (
from ethstaker_deposit.utils.click import (
captive_prompt_callback,
choice_prompt_func,
jit_option,
)
from staking_deposit.utils.constants import (
from ethstaker_deposit.utils.constants import (
MNEMONIC_LANG_OPTIONS,
WORD_LISTS_PATH,
)
from staking_deposit.utils.intl import (
from ethstaker_deposit.utils.intl import (
fuzzy_reverse_dict_lookup,
load_text,
get_first_options,
)
from ethstaker_deposit.utils.terminal import clear_terminal
from .generate_keys import (
generate_keys,
@@ -38,6 +40,7 @@ languages = get_first_options(MNEMONIC_LANG_OPTIONS)
callback=captive_prompt_callback(
lambda mnemonic_language: fuzzy_reverse_dict_lookup(mnemonic_language, MNEMONIC_LANG_OPTIONS),
choice_prompt_func(lambda: load_text(['arg_mnemonic_language', 'prompt'], func='new_mnemonic'), languages),
default=lambda: load_text(['arg_mnemonic_language', 'default'], func='new_mnemonic'),
),
default=lambda: load_text(['arg_mnemonic_language', 'default'], func='new_mnemonic'),
help=lambda: load_text(['arg_mnemonic_language', 'help'], func='new_mnemonic'),
@@ -48,15 +51,31 @@ languages = get_first_options(MNEMONIC_LANG_OPTIONS)
def new_mnemonic(ctx: click.Context, mnemonic_language: str, **kwargs: Any) -> None:
mnemonic = get_mnemonic(language=mnemonic_language, words_path=WORD_LISTS_PATH)
test_mnemonic = ''
while mnemonic != reconstruct_mnemonic(test_mnemonic, WORD_LISTS_PATH):
click.clear()
click.echo(load_text(['msg_mnemonic_presentation']))
while mnemonic != reconstruct_mnemonic(test_mnemonic, WORD_LISTS_PATH, mnemonic_language):
clear_terminal()
click.echo(
load_text(['msg_mnemonic_presentation'])
+ '\n\n********************\n'
+ load_text(['msg_mnemonic_clipboard_warning'])
+ '\n********************'
)
click.echo('\n\n%s\n\n' % mnemonic)
click.pause(load_text(['msg_press_any_key']))
click.clear()
test_mnemonic = click.prompt(load_text(['msg_mnemonic_retype_prompt']) + '\n\n')
click.clear()
clear_terminal()
test_mnemonic = click.prompt(
load_text(['msg_mnemonic_retype_prompt'])
+ '\n\n********************\n'
+ load_text(['msg_mnemonic_clipboard_warning'])
+ '\n********************\n\n'
)
clear_terminal()
# Clear clipboard
try: # Failing this on headless Linux is expected
click.pause(load_text(['msg_confirm_clipboard_clearing']))
pyperclip.copy(' ')
except Exception:
pass
# Do NOT use mnemonic_password.
ctx.obj = {'mnemonic': mnemonic, 'mnemonic_password': ''}
ctx.params['validator_start_index'] = 0

View File

@@ -0,0 +1,221 @@
import json
import click
import os
import sys
import time
from eth_typing import HexAddress
from eth_utils import to_canonical_address
from py_ecc.bls import G2ProofOfPossession as bls
from typing import Any, Optional
from ethstaker_deposit.key_handling.keystore import Keystore
from ethstaker_deposit.settings import (
fake_cli_version,
MAINNET,
ALL_CHAIN_KEYS,
get_chain_setting,
BaseChainSetting,
)
from ethstaker_deposit.utils import config
from ethstaker_deposit.utils.click import (
captive_prompt_callback,
choice_prompt_func,
jit_option,
prompt_if_none,
prompt_if_other_is_none,
prompt_if_other_exists,
)
from ethstaker_deposit.utils.constants import (
DEFAULT_PARTIAL_DEPOSIT_FOLDER_NAME,
EXECUTION_ADDRESS_WITHDRAWAL_PREFIX,
COMPOUNDING_WITHDRAWAL_PREFIX,
)
from ethstaker_deposit.utils.deposit import export_deposit_data_json
from ethstaker_deposit.utils.intl import (
closest_match,
load_text,
)
from ethstaker_deposit.utils.ssz import (
DepositData,
DepositMessage,
compute_deposit_domain,
compute_signing_root,
)
from ethstaker_deposit.utils.validation import (
validate_deposit,
validate_keystore_file,
validate_deposit_amount,
validate_withdrawal_address,
validate_yesno,
validate_devnet_chain_setting,
)
FUNC_NAME = 'partial_deposit'
@click.command(
help=load_text(['arg_partial_deposit', 'help'], func=FUNC_NAME),
)
@jit_option(
callback=captive_prompt_callback(
lambda x: closest_match(x, ALL_CHAIN_KEYS),
choice_prompt_func(
lambda: load_text(['arg_partial_deposit_chain', 'prompt'], func=FUNC_NAME),
ALL_CHAIN_KEYS
),
prompt_if=prompt_if_other_is_none('devnet_chain_setting'),
default=MAINNET,
),
default=MAINNET,
help=lambda: load_text(['arg_partial_deposit_chain', 'help'], func=FUNC_NAME),
param_decls='--chain',
prompt=False, # the callback handles the prompt
)
@jit_option(
callback=captive_prompt_callback(
lambda file: validate_keystore_file(file),
lambda: load_text(['arg_partial_deposit_keystore', 'prompt'], func=FUNC_NAME),
prompt_if=prompt_if_none,
),
help=lambda: load_text(['arg_partial_deposit_keystore', 'help'], func=FUNC_NAME),
param_decls='--keystore',
prompt=False,
)
@jit_option(
callback=captive_prompt_callback(
lambda x: x,
lambda: load_text(['arg_partial_deposit_keystore_password', 'prompt'], func=FUNC_NAME),
None,
lambda: load_text(['arg_partial_deposit_keystore_password', 'invalid'], func=FUNC_NAME),
True,
),
help=lambda: load_text(['arg_partial_deposit_keystore_password', 'help'], func=FUNC_NAME),
hide_input=True,
param_decls='--keystore_password',
prompt=lambda: load_text(['arg_partial_deposit_keystore_password', 'prompt'], func=FUNC_NAME),
)
@jit_option(
callback=captive_prompt_callback(
lambda amount: validate_deposit_amount(amount),
lambda: load_text(['arg_partial_deposit_amount', 'prompt'], func=FUNC_NAME),
default="32",
prompt_if=prompt_if_none,
),
default="32",
help=lambda: load_text(['arg_partial_deposit_amount', 'help'], func=FUNC_NAME),
param_decls='--amount',
prompt=False, # the callback handles the prompt, to avoid second callback with gwei
)
@jit_option(
callback=captive_prompt_callback(
lambda address: validate_withdrawal_address(None, None, address, True),
lambda: load_text(['arg_withdrawal_address', 'prompt'], func=FUNC_NAME),
lambda: load_text(['arg_withdrawal_address', 'confirm'], func=FUNC_NAME),
lambda: load_text(['arg_withdrawal_address', 'mismatch'], func=FUNC_NAME),
prompt_if=prompt_if_none,
),
help=lambda: load_text(['arg_withdrawal_address', 'help'], func=FUNC_NAME),
param_decls=['--withdrawal_address', '--execution_address', '--eth1_withdrawal_credentials'],
prompt=False, # the callback handles the prompt
)
@jit_option(
callback=captive_prompt_callback(
lambda value: validate_yesno(None, None, value),
lambda: load_text(['arg_compounding', 'prompt'], func=FUNC_NAME),
default="False",
prompt_if=prompt_if_other_exists('withdrawal_address'),
),
default=False,
help=lambda: load_text(['arg_compounding', 'help'], func=FUNC_NAME),
param_decls='--compounding/--regular-withdrawal',
prompt=False, # the callback handles the prompt
type=bool,
show_default=True,
)
@jit_option(
default=os.getcwd(),
help=lambda: load_text(['arg_partial_deposit_output_folder', 'help'], func=FUNC_NAME),
param_decls='--output_folder',
type=click.Path(exists=True, file_okay=False, dir_okay=True),
)
@jit_option(
callback=validate_devnet_chain_setting,
default=None,
help=lambda: load_text(['arg_devnet_chain_setting', 'help'], func=FUNC_NAME),
param_decls='--devnet_chain_setting',
is_eager=True,
)
@click.pass_context
def partial_deposit(
ctx: click.Context,
chain: str,
keystore: Keystore,
keystore_password: str,
amount: int,
withdrawal_address: HexAddress,
compounding: bool,
output_folder: str,
devnet_chain_setting: Optional[BaseChainSetting],
**kwargs: Any) -> None:
try:
secret_bytes = keystore.decrypt(keystore_password)
except ValueError:
click.echo(load_text(['arg_partial_deposit_keystore_password', 'mismatch']), err=True)
sys.exit(1)
signing_key = int.from_bytes(secret_bytes, 'big')
# Get chain setting
chain_setting = devnet_chain_setting if devnet_chain_setting is not None else get_chain_setting(chain)
if compounding:
withdrawal_credentials = COMPOUNDING_WITHDRAWAL_PREFIX
else:
withdrawal_credentials = EXECUTION_ADDRESS_WITHDRAWAL_PREFIX
withdrawal_credentials += b'\x00' * 11
withdrawal_credentials += to_canonical_address(withdrawal_address)
deposit_message = DepositMessage( # type: ignore[no-untyped-call]
pubkey=bls.SkToPk(signing_key),
withdrawal_credentials=withdrawal_credentials,
amount=amount
)
domain = compute_deposit_domain(fork_version=chain_setting.GENESIS_FORK_VERSION)
signing_root = compute_signing_root(deposit_message, domain)
signature = bls.Sign(signing_key, signing_root)
signed_deposit = DepositData( # type: ignore[no-untyped-call]
**deposit_message.as_dict(), # type: ignore[no-untyped-call]
signature=signature
)
folder = os.path.join(output_folder, DEFAULT_PARTIAL_DEPOSIT_FOLDER_NAME)
if not os.path.exists(folder):
os.mkdir(folder)
click.echo(load_text(['msg_partial_deposit_creation']))
deposit_data = signed_deposit.as_dict() # type: ignore[no-untyped-call]
deposit_data.update({'deposit_message_root': deposit_message.hash_tree_root})
deposit_data.update({'deposit_data_root': signed_deposit.hash_tree_root})
deposit_data.update({'fork_version': chain_setting.GENESIS_FORK_VERSION})
deposit_data.update({'network_name': chain_setting.NETWORK_NAME})
deposit_data.update({'deposit_cli_version': fake_cli_version})
saved_folder = export_deposit_data_json(folder, time.time(), [deposit_data])
click.echo(load_text(['msg_verify_partial_deposit']))
deposit_json = []
with open(saved_folder, 'r', encoding='utf-8') as f:
deposit_json = json.load(f)
if (not validate_deposit(deposit_json[0])):
click.echo(load_text(['err_verify_partial_deposit']))
return
click.echo(load_text(['msg_creation_success']) + saved_folder)
if not config.non_interactive:
click.pause(load_text(['msg_pause']))

View File

@@ -0,0 +1,63 @@
import click
import sys
from typing import Any
from ethstaker_deposit.key_handling.keystore import Keystore
from ethstaker_deposit.utils import config
from ethstaker_deposit.utils.click import (
captive_prompt_callback,
jit_option,
prompt_if_none,
)
from ethstaker_deposit.utils.intl import (
load_text,
)
from ethstaker_deposit.utils.validation import validate_keystore_file
FUNC_NAME = 'test_keystore'
@click.command(
help=load_text(['arg_test_keystore', 'help'], func=FUNC_NAME),
)
@jit_option(
callback=captive_prompt_callback(
lambda file: validate_keystore_file(file),
lambda: load_text(['arg_test_keystore_keystore', 'prompt'], func=FUNC_NAME),
prompt_if=prompt_if_none,
),
help=lambda: load_text(['arg_test_keystore_keystore', 'help'], func=FUNC_NAME),
param_decls='--keystore',
prompt=False,
)
@jit_option(
callback=captive_prompt_callback(
lambda x: x,
lambda: load_text(['arg_test_keystore_keystore_password', 'prompt'], func=FUNC_NAME),
None,
lambda: load_text(['arg_test_keystore_keystore_password', 'invalid'], func=FUNC_NAME),
True,
),
help=lambda: load_text(['arg_test_keystore_keystore_password', 'help'], func=FUNC_NAME),
hide_input=True,
param_decls='--keystore_password',
prompt=lambda: load_text(['arg_test_keystore_keystore_password', 'prompt'], func=FUNC_NAME),
)
@click.pass_context
def test_keystore(
ctx: click.Context,
keystore: Keystore,
keystore_password: str,
**kwargs: Any) -> None:
click.echo(load_text(['msg_verify_keystore_file']))
try:
keystore.decrypt(keystore_password)
except ValueError:
click.echo(load_text(['arg_test_keystore_keystore_password', 'mismatch']), err=True)
sys.exit(1)
click.echo(load_text(['msg_verification_success']))
if not config.non_interactive:
click.pause(load_text(['msg_pause']))

View File

@@ -0,0 +1,394 @@
import os
import click
from enum import Enum
import time
import json
import concurrent.futures
from typing import Dict, Optional, Any, Sequence
from eth_typing import Address, HexAddress
from eth_utils import to_canonical_address
from py_ecc.bls import G2ProofOfPossession as bls
from ethstaker_deposit.exceptions import ValidationError
from ethstaker_deposit.utils.exit_transaction import exit_transaction_generation, export_exit_transaction_json
from ethstaker_deposit.key_handling.key_derivation.path import mnemonic_and_path_to_key
from ethstaker_deposit.key_handling.keystore import (
Keystore,
Pbkdf2Keystore,
ScryptKeystore,
)
from ethstaker_deposit.settings import (
fake_cli_version,
DEPOSIT_CLI_VERSION,
BaseChainSetting,
)
from ethstaker_deposit.utils.constants import (
BLS_WITHDRAWAL_PREFIX,
EXECUTION_ADDRESS_WITHDRAWAL_PREFIX,
COMPOUNDING_WITHDRAWAL_PREFIX,
ETH2GWEI,
MAX_DEPOSIT_AMOUNT,
MIN_DEPOSIT_AMOUNT,
)
from ethstaker_deposit.utils.crypto import SHA256
from ethstaker_deposit.utils.deposit import export_deposit_data_json as export_deposit_data_json_util
from ethstaker_deposit.utils.intl import load_text
from ethstaker_deposit.utils.ssz import (
compute_deposit_domain,
compute_bls_to_execution_change_domain,
compute_signing_root,
BLSToExecutionChange,
DepositData,
DepositMessage,
SignedBLSToExecutionChange,
)
from ethstaker_deposit.utils.file_handling import (
sensitive_opener,
)
class WithdrawalType(Enum):
BLS_WITHDRAWAL = 0
EXECUTION_ADDRESS_WITHDRAWAL = 1
COMPOUNDING_WITHDRAWAL = 2
class Credential:
"""
A Credential object contains all of the information for a single validator and the corresponding functionality.
Once created, it is the only object that should be required to perform any processing for a validator.
"""
def __init__(self, *, mnemonic: str, mnemonic_password: str,
index: int, amount: int, chain_setting: BaseChainSetting,
hex_withdrawal_address: Optional[HexAddress],
compounding: Optional[bool] = False,
use_pbkdf2: Optional[bool] = False):
# Set path as EIP-2334 format
# https://eips.ethereum.org/EIPS/eip-2334
purpose = '12381'
coin_type = '3600'
account = str(index)
withdrawal_key_path = f'm/{purpose}/{coin_type}/{account}/0'
self.signing_key_path = f'{withdrawal_key_path}/0'
self.withdrawal_sk = mnemonic_and_path_to_key(
mnemonic=mnemonic, path=withdrawal_key_path, password=mnemonic_password)
self.signing_sk = mnemonic_and_path_to_key(
mnemonic=mnemonic, path=self.signing_key_path, password=mnemonic_password)
self.amount = amount
self.chain_setting = chain_setting
self.hex_withdrawal_address = hex_withdrawal_address
self.compounding = compounding
self.use_pbkdf2 = use_pbkdf2
@property
def signing_pk(self) -> bytes:
return bls.SkToPk(self.signing_sk)
@property
def withdrawal_pk(self) -> bytes:
return bls.SkToPk(self.withdrawal_sk)
@property
def withdrawal_address(self) -> Optional[Address]:
if self.hex_withdrawal_address is None:
return None
return to_canonical_address(self.hex_withdrawal_address)
@property
def withdrawal_prefix(self) -> bytes:
if self.withdrawal_address is not None:
if self.compounding:
return COMPOUNDING_WITHDRAWAL_PREFIX
else:
return EXECUTION_ADDRESS_WITHDRAWAL_PREFIX
else:
return BLS_WITHDRAWAL_PREFIX
@property
def withdrawal_type(self) -> WithdrawalType:
if self.withdrawal_prefix == BLS_WITHDRAWAL_PREFIX:
return WithdrawalType.BLS_WITHDRAWAL
elif self.withdrawal_prefix == EXECUTION_ADDRESS_WITHDRAWAL_PREFIX:
return WithdrawalType.EXECUTION_ADDRESS_WITHDRAWAL
elif self.withdrawal_prefix == COMPOUNDING_WITHDRAWAL_PREFIX:
return WithdrawalType.COMPOUNDING_WITHDRAWAL
else:
raise ValueError(f"Invalid withdrawal_prefix {self.withdrawal_prefix.hex()}")
@property
def withdrawal_credentials(self) -> bytes:
if self.withdrawal_type == WithdrawalType.BLS_WITHDRAWAL:
withdrawal_credentials = BLS_WITHDRAWAL_PREFIX
withdrawal_credentials += SHA256(self.withdrawal_pk)[1:]
elif (
self.withdrawal_type == WithdrawalType.EXECUTION_ADDRESS_WITHDRAWAL
and self.withdrawal_address is not None
):
withdrawal_credentials = EXECUTION_ADDRESS_WITHDRAWAL_PREFIX
withdrawal_credentials += b'\x00' * 11
withdrawal_credentials += self.withdrawal_address
elif (
self.withdrawal_type == WithdrawalType.COMPOUNDING_WITHDRAWAL
and self.withdrawal_address is not None
):
withdrawal_credentials = COMPOUNDING_WITHDRAWAL_PREFIX
withdrawal_credentials += b'\x00' * 11
withdrawal_credentials += self.withdrawal_address
else:
raise ValueError(f"Invalid withdrawal_type {self.withdrawal_type}")
return withdrawal_credentials
@property
def deposit_message(self) -> DepositMessage:
if not MIN_DEPOSIT_AMOUNT <= self.amount <= MAX_DEPOSIT_AMOUNT:
raise ValidationError(f"{self.amount / ETH2GWEI} ETH deposits are not within the bounds of this cli.")
return DepositMessage( # type: ignore[no-untyped-call]
pubkey=self.signing_pk,
withdrawal_credentials=self.withdrawal_credentials,
amount=self.amount,
)
@property
def signed_deposit(self) -> DepositData:
domain = compute_deposit_domain(fork_version=self.chain_setting.GENESIS_FORK_VERSION)
signing_root = compute_signing_root(self.deposit_message, domain)
signed_deposit = DepositData( # type: ignore[no-untyped-call]
**self.deposit_message.as_dict(), # type: ignore[no-untyped-call]
signature=bls.Sign(self.signing_sk, signing_root)
)
return signed_deposit
@property
def deposit_datum_dict(self) -> Dict[str, bytes]:
"""
Return a single deposit datum for 1 validator including all
the information needed to verify and process the deposit.
"""
signed_deposit_datum = self.signed_deposit
datum_dict = signed_deposit_datum.as_dict() # type: ignore[no-untyped-call]
datum_dict.update({'deposit_message_root': self.deposit_message.hash_tree_root})
datum_dict.update({'deposit_data_root': signed_deposit_datum.hash_tree_root})
datum_dict.update({'fork_version': self.chain_setting.GENESIS_FORK_VERSION})
datum_dict.update({'network_name': self.chain_setting.NETWORK_NAME})
datum_dict.update({'deposit_cli_version': fake_cli_version})
return datum_dict
def signing_keystore(self, password: str) -> Keystore:
secret = self.signing_sk.to_bytes(32, 'big')
if self.use_pbkdf2:
return Pbkdf2Keystore.encrypt(secret=secret, password=password, path=self.signing_key_path)
else:
return ScryptKeystore.encrypt(secret=secret, password=password, path=self.signing_key_path)
def save_signing_keystore(self, password: str, folder: str, timestamp: float) -> str:
keystore = self.signing_keystore(password)
filefolder = os.path.join(folder, 'keystore-%s-%i.json' % (keystore.path.replace('/', '_'), timestamp))
keystore.save(filefolder)
return filefolder
def verify_keystore(self, keystore_filefolder: str, password: str) -> bool:
saved_keystore = Keystore.from_file(keystore_filefolder)
secret_bytes = saved_keystore.decrypt(password)
return self.signing_sk == int.from_bytes(secret_bytes, 'big')
def get_bls_to_execution_change(self, validator_index: int) -> SignedBLSToExecutionChange:
if self.withdrawal_address is None:
raise ValueError("The withdrawal address should NOT be empty.")
if self.chain_setting.GENESIS_VALIDATORS_ROOT is None:
raise ValidationError("The genesis validators root should NOT be empty "
"for this chain to obtain the BLS to execution change.")
message = BLSToExecutionChange( # type: ignore[no-untyped-call]
validator_index=validator_index,
from_bls_pubkey=self.withdrawal_pk,
to_execution_address=self.withdrawal_address,
)
domain = compute_bls_to_execution_change_domain(
fork_version=self.chain_setting.GENESIS_FORK_VERSION,
genesis_validators_root=self.chain_setting.GENESIS_VALIDATORS_ROOT,
)
signing_root = compute_signing_root(message, domain)
signature = bls.Sign(self.withdrawal_sk, signing_root)
return SignedBLSToExecutionChange( # type: ignore[no-untyped-call]
message=message,
signature=signature,
)
def get_bls_to_execution_change_dict(self, validator_index: int) -> Dict[str, bytes]:
result_dict: Dict[str, Any] = {}
signed_bls_to_execution_change = self.get_bls_to_execution_change(validator_index)
message = {
'validator_index':
str(signed_bls_to_execution_change.message.validator_index), # type: ignore[attr-defined]
'from_bls_pubkey': '0x'
+ signed_bls_to_execution_change.message.from_bls_pubkey.hex(), # type: ignore[attr-defined]
'to_execution_address': '0x'
+ signed_bls_to_execution_change.message.to_execution_address.hex(), # type: ignore[attr-defined]
}
result_dict.update({'message': message})
result_dict.update({'signature': '0x'
+ signed_bls_to_execution_change.signature.hex()}) # type: ignore[attr-defined]
# metadata
metadata: Dict[str, Any] = {
'network_name': self.chain_setting.NETWORK_NAME,
'genesis_validators_root': '0x' + self.chain_setting.GENESIS_VALIDATORS_ROOT.hex(),
'deposit_cli_version': DEPOSIT_CLI_VERSION,
}
result_dict.update({'metadata': metadata})
return result_dict
def save_exit_transaction(self, validator_index: int, epoch: int, folder: str, timestamp: float) -> str:
signing_key = self.signing_sk
signed_voluntary_exit = exit_transaction_generation(
chain_setting=self.chain_setting,
signing_key=signing_key,
validator_index=validator_index,
epoch=epoch
)
return export_exit_transaction_json(folder=folder, signed_exit=signed_voluntary_exit, timestamp=timestamp)
def _credential_builder(kwargs: Dict[str, Any]) -> Credential:
return Credential(**kwargs)
def _keystore_exporter(kwargs: Dict[str, Any]) -> str:
credential: Credential = kwargs.pop('credential')
return credential.save_signing_keystore(**kwargs)
def _deposit_data_builder(credential: Credential) -> Dict[str, bytes]:
return credential.deposit_datum_dict
def _keystore_verifier(kwargs: Dict[str, Any]) -> bool:
credential: Credential = kwargs.pop('credential')
return credential.verify_keystore(**kwargs)
def _bls_to_execution_change_builder(kwargs: Dict[str, Any]) -> Dict[str, bytes]:
credential: Credential = kwargs.pop('credential')
return credential.get_bls_to_execution_change_dict(**kwargs)
class CredentialList:
"""
A collection of multiple Credentials, one for each validator.
"""
def __init__(self, credentials: list[Credential]):
self.credentials = credentials
@classmethod
def from_mnemonic(cls,
*,
mnemonic: str,
mnemonic_password: str,
num_keys: int,
amounts: list[int],
chain_setting: BaseChainSetting,
start_index: int,
hex_withdrawal_address: Optional[HexAddress],
compounding: Optional[bool] = False,
use_pbkdf2: Optional[bool] = False) -> 'CredentialList':
if len(amounts) != num_keys:
raise ValueError(
f"The number of keys ({num_keys}) doesn't equal to the corresponding deposit amounts ({len(amounts)})."
)
key_indices = range(start_index, start_index + num_keys)
credentials: list[Credential] = []
with click.progressbar(length=num_keys, label=load_text(['msg_key_creation']), # type: ignore[var-annotated]
show_percent=False, show_pos=True) as bar:
executor_kwargs = [{
'mnemonic': mnemonic,
'mnemonic_password': mnemonic_password,
'index': index,
'amount': amounts[index - start_index],
'chain_setting': chain_setting,
'hex_withdrawal_address': hex_withdrawal_address,
'compounding': compounding,
'use_pbkdf2': use_pbkdf2,
} for index in key_indices]
with concurrent.futures.ProcessPoolExecutor() as executor:
for credential in executor.map(_credential_builder, executor_kwargs):
credentials.append(credential)
bar.update(1)
return cls(credentials)
def export_keystores(self, password: str, folder: str, timestamp: float) -> list[str]:
filefolders: list[str] = []
with click.progressbar(length=len(self.credentials), # type: ignore[var-annotated]
label=load_text(['msg_keystore_creation']),
show_percent=False, show_pos=True) as bar:
executor_kwargs = [{
'credential': credential,
'password': password,
'folder': folder,
'timestamp': timestamp,
} for credential in self.credentials]
with concurrent.futures.ProcessPoolExecutor() as executor:
for filefolder in executor.map(_keystore_exporter, executor_kwargs):
filefolders.append(filefolder)
bar.update(1)
return filefolders
def export_deposit_data_json(self, folder: str, timestamp: float) -> str:
deposit_data = []
with click.progressbar(length=len(self.credentials), # type: ignore[var-annotated]
label=load_text(['msg_depositdata_creation']),
show_percent=False, show_pos=True) as bar:
with concurrent.futures.ProcessPoolExecutor() as executor:
for datum_dict in executor.map(_deposit_data_builder, self.credentials):
deposit_data.append(datum_dict)
bar.update(1)
return export_deposit_data_json_util(folder, timestamp, deposit_data)
def verify_keystores(self, keystore_filefolders: list[str], password: str) -> bool:
all_valid_keystores = True
with click.progressbar(length=len(self.credentials), # type: ignore[var-annotated]
label=load_text(['msg_keystore_verification']),
show_percent=False, show_pos=True) as bar:
executor_kwargs = [{
'credential': credential,
'keystore_filefolder': fileholder,
'password': password,
} for credential, fileholder in zip(self.credentials, keystore_filefolders)]
with concurrent.futures.ProcessPoolExecutor() as executor:
for valid_keystore in executor.map(_keystore_verifier, executor_kwargs):
all_valid_keystores &= valid_keystore
bar.update(1)
return all_valid_keystores
def export_bls_to_execution_change_json(self, folder: str, validator_indices: Sequence[int]) -> str:
bls_to_execution_changes = []
with click.progressbar(length=len(self.credentials), # type: ignore[var-annotated]
label=load_text(['msg_bls_to_execution_change_creation']),
show_percent=False, show_pos=True) as bar:
executor_kwargs = [{
'credential': credential,
'validator_index': validator_indices[i],
} for i, credential in enumerate(self.credentials)]
with concurrent.futures.ProcessPoolExecutor() as executor:
for bls_to_execution_change in executor.map(_bls_to_execution_change_builder, executor_kwargs):
bls_to_execution_changes.append(bls_to_execution_change)
bar.update(1)
filefolder = os.path.join(folder, 'bls_to_execution_change-%i.json' % time.time())
with open(filefolder, 'w', encoding='utf-8', opener=sensitive_opener) as f:
json.dump(bls_to_execution_changes, f)
return filefolder

View File

@@ -0,0 +1,134 @@
import click
import socket
import sys
from multiprocessing import freeze_support
from ethstaker_deposit.cli.existing_mnemonic import existing_mnemonic
from ethstaker_deposit.cli.exit_transaction_keystore import exit_transaction_keystore
from ethstaker_deposit.cli.exit_transaction_mnemonic import exit_transaction_mnemonic
from ethstaker_deposit.cli.generate_bls_to_execution_change import generate_bls_to_execution_change
from ethstaker_deposit.cli.generate_bls_to_execution_change_keystore import generate_bls_to_execution_change_keystore
from ethstaker_deposit.cli.new_mnemonic import new_mnemonic
from ethstaker_deposit.cli.partial_deposit import partial_deposit
from ethstaker_deposit.cli.test_keystore import test_keystore
from ethstaker_deposit.exceptions import MultiLanguageError, ValidationError
from ethstaker_deposit.utils.click import (
captive_prompt_callback,
choice_prompt_func,
jit_option,
deactivate_prompts_callback
)
from ethstaker_deposit.utils import config
from ethstaker_deposit.utils.constants import INTL_LANG_OPTIONS
from ethstaker_deposit.utils.intl import (
get_first_options,
fuzzy_reverse_dict_lookup,
load_text,
)
from ethstaker_deposit.settings import (
DEPOSIT_CLI_VERSION,
)
def check_python_version() -> None:
'''
Checks that the python version running is sufficient and exits if not.
'''
if sys.version_info < (3, 9):
click.pause(load_text(['err_python_version']))
sys.exit(78)
def check_connectivity() -> None:
'''
Checks if there is an internet connection and warns the user if so.
'''
if '--help' in sys.argv:
return None
try:
socket.setdefaulttimeout(2)
socket.getaddrinfo('icann.org', 80)
click.pause(load_text(['connectivity_warning']))
except OSError:
return None
# Define commands available to the user and their order
commands = [
new_mnemonic,
existing_mnemonic,
generate_bls_to_execution_change,
generate_bls_to_execution_change_keystore,
exit_transaction_keystore,
exit_transaction_mnemonic,
partial_deposit,
test_keystore,
]
class SortedGroup(click.Group):
def list_commands(self, ctx: click.Context) -> list[str]:
return [x.name for x in commands]
@click.group(cls=SortedGroup)
@click.pass_context
@jit_option(
'--language',
callback=captive_prompt_callback(
lambda language: fuzzy_reverse_dict_lookup(language, INTL_LANG_OPTIONS),
choice_prompt_func(lambda: 'Please choose your language', get_first_options(INTL_LANG_OPTIONS)),
default='English',
),
default='English',
help='The language you wish to use the CLI in.',
prompt=choice_prompt_func(lambda: 'Please choose your language', get_first_options(INTL_LANG_OPTIONS))(),
type=str,
)
@click.option(
'--non_interactive',
callback=deactivate_prompts_callback(["language"]),
default=False,
is_flag=True,
help=(
'Disables interactive prompts. Warning: With this flag, there will be no confirmation step(s) to verify the '
'input value(s). This will also ignore the connectivity check. Please use it carefully.'
),
hidden=False,
)
@click.option(
'--ignore_connectivity',
default=False,
is_flag=True,
help=(
'Disables internet connectivity check. Warning: It is strongly recommended not to use this tool with internet '
'access. Ignoring this check can further the risk of theft and compromise of your generated key material.'
),
hidden=False,
)
@click.version_option(version=DEPOSIT_CLI_VERSION)
def cli(ctx: click.Context, language: str, non_interactive: bool, ignore_connectivity: bool) -> None:
if not ignore_connectivity and not non_interactive:
check_connectivity()
config.language = language
config.non_interactive = non_interactive # Remove interactive commands
for command in commands:
cli.add_command(command)
def run() -> None:
freeze_support() # Needed when running under Windows in a frozen bundle
check_python_version()
try:
cli()
except (MultiLanguageError, ValueError, ValidationError) as e:
click.echo(f"\nError: {e}\n", err=True)
sys.exit(1)
if __name__ == '__main__':
run()

View File

@@ -0,0 +1,9 @@
class ValidationError(Exception):
...
class MultiLanguageError(Exception):
def __init__(self, languages: list[str]):
self.languages = languages
message = "Multiple valid languages found: %s" % ", ".join(languages)
super().__init__(message)

View File

@@ -1,5 +1,5 @@
{
"check_python_version": {
"err_python_version": "إصدار بايثون الخاص بك غير كافٍ، يرجى تثبيت الإصدار 3.8 أو الأحدث."
"err_python_version": "إصدار بايثون الخاص بك غير كافٍ، يرجى تثبيت الإصدار 3.9 أو الأحدث."
}
}

View File

@@ -3,6 +3,6 @@
"msg_deposit_verification": "التحقق من الإيداعات الخاصة بك:\t"
},
"validate_password_strength": {
"msg_password_length": "يجب أن يكون طول كلمة المرور 8 على الأقل. الرجاء إعادة الكتابة"
"msg_password_length": "يجب أن يكون طول كلمة المرور 12 على الأقل. الرجاء إعادة الكتابة"
}
}

View File

@@ -1,5 +1,5 @@
{
"check_python_version": {
"err_python_version": "Η έκδοση python σας δεν είναι επαρκής, εγκαταστήστε την έκδοση 3.8 ή μεγαλύτερη."
"err_python_version": "Η έκδοση python σας δεν είναι επαρκής, εγκαταστήστε την έκδοση 3.9 ή μεγαλύτερη."
}
}

View File

@@ -3,6 +3,6 @@
"msg_deposit_verification": "Επαλήθευση των καταθέσεών σας:\t"
},
"validate_password_strength": {
"msg_password_length": "Το μήκος του κωδικού πρόσβασης πρέπει να είναι τουλάχιστον 8. Πληκτρολογήστε ξανά"
"msg_password_length": "Το μήκος του κωδικού πρόσβασης πρέπει να είναι τουλάχιστον 12. Πληκτρολογήστε ξανά"
}
}

View File

@@ -1,6 +1,7 @@
{
"validate_mnemonic": {
"err_invalid_mnemonic": "That is not a valid mnemonic, please check for typos."
"err_invalid_mnemonic": "That is not a valid mnemonic, please check for typos.",
"arg_mnemonic_language": "We couldn't detect the language of your mnemonic phrase. Please select one of the available language options"
},
"existing_mnemonic": {
"arg_existing_mnemonic": {
@@ -10,6 +11,9 @@
"help": "The mnemonic that you used to generate your keys. (It is recommended not to use this argument, and wait for the CLI to ask you for your mnemonic as otherwise it will appear in your shell history.)",
"prompt": "Please enter your mnemonic separated by spaces (\" \"). Note: you only need to enter the first 4 letters of each word if you'd prefer."
},
"arg_mnemonic_language": {
"help": "The language of your mnemonic. If this is not provided we will attempt to determine it based on the mnemonic provided."
},
"arg_mnemonic_password": {
"help": "This is almost certainly not the argument you are looking for: it is for mnemonic passwords, not keystore passwords. Providing a password here when you didn't use one initially, can result in lost keys (and therefore funds)! Also note that if you used this tool to generate your mnemonic initially, then you did not use a mnemonic password. However, if you are certain you used a password to \"increase\" the security of your mnemonic, this is where you enter it.",
"prompt": "Enter your mnemonic password (if you used one). Make sure you won't forget it, it can not be recovered.",
@@ -19,7 +23,8 @@
"arg_validator_start_index": {
"help": "Enter the index (key number) you wish to start generating more keys from. For example, if you've generated 4 keys in the past, you'd enter 4 here.",
"prompt": "Enter the index (key number) you wish to start generating more keys from. For example, if you've generated 4 keys in the past, you'd enter 4 here.",
"confirm": "Please repeat the index to confirm"
}
"confirm": "Please repeat the validator start index to confirm"
},
"msg_confirm_clipboard_clearing": "WARNING: Your clipboard will be CLEARED. Press any key to clear the clipboard."
}
}

View File

@@ -0,0 +1,38 @@
{
"exit_transaction_keystore": {
"arg_exit_transaction_keystore" :{
"help": "Generate an exit transaction that can be used to exit validators on Ethereum Beacon Chain."
},
"arg_exit_transaction_keystore_chain": {
"help": "The name of the Ethereum PoS chain your validator is running on. \"mainnet\" is the default.",
"prompt": "Please choose the (mainnet or testnet) network/chain name"
},
"arg_exit_transaction_keystore_epoch": {
"help": "The epoch of when the exit transaction will be valid. The transaction will always be valid by default."
},
"arg_exit_transaction_keystore_keystore": {
"help": "The keystore file associated with the validator you wish to exit.",
"prompt": "Please enter the location of your keystore file."
},
"arg_exit_transaction_keystore_keystore_password": {
"help": "The password that is used to encrypt the provided keystore. Note: It's not your mnemonic password. (It is recommended not to use this argument, and wait for the CLI to ask you for your password as otherwise it will appear in your shell history.)",
"prompt": "Enter the password that is used to encrypt the provided keystore.",
"mismatch": "Error: The provided keystore password was unable to decrypt this keystore file. Make sure you have the correct password and try again."
},
"arg_exit_transaction_keystore_output_folder": {
"help": "The folder path where the exit transactions will be saved to. Pointing to `./exit_transactions` by default."
},
"arg_validator_index": {
"help": "The validator index corresponding to the provided keystore.",
"prompt": "Please enter the validator index of your validator that corresponds to the provided keystore as identified on the beacon chain."
},
"arg_devnet_chain_setting": {
"help": "[DEVNET ONLY] Set a specific EXIT_FORK_VERSION and GENESIS_VALIDATORS_ROOT value. This should be a JSON string containing an object with the following keys: network_name, genesis_fork_version, exit_fork_version and genesis_validator_root. It should be similar to what you can find in settings.py. This will override any selected chain."
},
"msg_exit_transaction_creation": "\nCreating your exit transaction...",
"msg_verify_exit_transaction": "\nVerifying your exit transaction...",
"err_verify_exit_transaction": "\nThere was a problem verifying your exit transaction.\nPlease try again",
"msg_creation_success": "\nSuccess!\nYour exit transaction file can be found at: ",
"msg_pause": "\n\nPress any key."
}
}

View File

@@ -0,0 +1,34 @@
{
"exit_transaction_mnemonic": {
"arg_exit_transaction_mnemonic" :{
"help": "Generate an exit transaction that can be used to exit validators on Ethereum Beacon Chain."
},
"arg_exit_transaction_mnemonic_chain": {
"help": "The name of the Ethereum PoS chain your validator is running on. \"mainnet\" is the default.",
"prompt": "Please choose the (mainnet or testnet) network/chain name"
},
"arg_exit_transaction_mnemonic_start_index": {
"help": "Enter the index (key number) which you used when you created your keys. The default value is 0.",
"prompt": "Enter the index (key number) which you used when you created your keys. The default value is 0."
},
"arg_exit_transaction_mnemonic_indices": {
"help": "A list of the validator index number(s) of the certain validator(s)",
"prompt": "Please enter a list of the validator index number(s) of your validator(s) as identified on the beacon chain. Split multiple items with whitespaces or commas."
},
"arg_exit_transaction_mnemonic_epoch": {
"help": "The epoch of when the exit transaction will be valid. The transaction will always be valid by default."
},
"arg_exit_transaction_mnemonic_output_folder": {
"help": "The folder path where the exit transactions will be saved to. Pointing to `./exit_transactions` by default."
},
"arg_devnet_chain_setting": {
"help": "[DEVNET ONLY] Set a specific EXIT_FORK_VERSION and GENESIS_VALIDATORS_ROOT value. This should be a JSON string containing an object with the following keys: network_name, genesis_fork_version, exit_fork_version and genesis_validator_root. It should be similar to what you can find in settings.py. This will override any selected chain."
},
"msg_key_creation": "Creating your keys:\t",
"msg_exit_transaction_creation": "Creating your exit transactions:\t",
"msg_verify_exit_transaction": "Verifying your exit transactions:\t",
"err_verify_exit_transactions": "\nThere was a problem verifying your exit transactions.\nPlease try again",
"msg_creation_success": "\nSuccess!\nYour exit transaction files can be found at: ",
"msg_pause": "\n\nPress any key."
}
}

View File

@@ -3,12 +3,15 @@
"arg_generate_bls_to_execution_change" :{
"help": "Generating the SignedBLSToExecutionChange data to enable withdrawals on Ethereum Beacon Chain."
},
"arg_execution_address": {
"help": "The 20-byte (Eth1) execution address that will be used in withdrawal",
"prompt": "Please enter the 20-byte execution address for the new withdrawal credentials. Note that you CANNOT change it once you have set it on chain.",
"confirm": "Repeat your execution address for confirmation.",
"arg_withdrawal_address": {
"help": "The Ethereum address that will be used in withdrawal. It typically starts with '0x' followed by 40 hexadecimal characters. Please make sure you have full control over the address you choose here. Once you set a withdrawal address on chain, it cannot be changed.",
"prompt": "Please enter the withdrawal address. Note that you CANNOT change it once you have set it on chain.",
"confirm": "Repeat your withdrawal address for confirmation.",
"mismatch": "Error: the two entered values do not match. Please type again."
},
"arg_devnet_chain_setting": {
"help": "[DEVNET ONLY] Set a specific GENESIS_FORK_VERSION and GENESIS_VALIDATORS_ROOT value. This should be a JSON string containing an object with the following keys: network_name, genesis_fork_version, exit_fork_version and genesis_validator_root. It should be similar to what you can find in settings.py. This will override any selected chain."
},
"arg_validator_indices": {
"help": "A list of the validator index number(s) of the certain validator(s)",
"prompt": "Please enter a list of the validator index number(s) of your validator(s) as identified on the beacon chain. Split multiple items with whitespaces or commas."
@@ -34,6 +37,7 @@
"help": "The folder path for the keystore(s). Pointing to `./bls_to_execution_changes` by default."
},
"msg_key_creation": "Creating your SignedBLSToExecutionChange.",
"msg_credentials_verification": "Verifying your withdrawal credentials.",
"msg_creation_success": "\nSuccess!\nYour SignedBLSToExecutionChange JSON file can be found at: ",
"msg_pause": "\n\nPress any key.",
"err_verify_btec": "Failed to verify the bls_to_execution_change JSON files."

View File

@@ -0,0 +1,52 @@
{
"generate_bls_to_execution_change_keystore": {
"arg_generate_bls_to_execution_change" :{
"help": "Generating the SignedBLSToExecutionChange data to enable withdrawals on Ethereum Beacon Chain."
},
"arg_withdrawal_address": {
"help": "The Ethereum address that will be used in withdrawal. It typically starts with '0x' followed by 40 hexadecimal characters. Please make sure you have full control over the address you choose here. Once you set a withdrawal address on chain, it cannot be changed.",
"prompt": "Please enter the withdrawal address. Note that you CANNOT change it once you have set it on chain.",
"confirm": "Repeat your withdrawal address for confirmation.",
"mismatch": "Error: the two entered values do not match. Please type again."
},
"arg_devnet_chain_setting": {
"help": "[DEVNET ONLY] Set a specific GENESIS_FORK_VERSION and GENESIS_VALIDATORS_ROOT value. This should be a JSON string containing an object with the following keys: network_name, genesis_fork_version, exit_fork_version and genesis_validator_root. It should be similar to what you can find in settings.py. This will override any selected chain."
},
"arg_validator_index": {
"help": "The validator index",
"prompt": "Please enter the validator index number of your validator as identified on the beacon chain."
},
"arg_bls_withdrawal_credentials_list": {
"help": "A list of 32-byte old BLS withdrawal credentials of the certain validator(s)",
"prompt": "Please enter a list of the old BLS withdrawal credentials of your validator(s). Split multiple items with whitespaces or commas. The withdrawal credentials are in hexadecimal encoded form."
},
"arg_bls_to_execution_changes_keystore_keystore": {
"help": "The keystore file associated with the validator you wish to exit.",
"prompt": "Please enter the location of your keystore file."
},
"arg_bls_to_execution_changes_keystore_keystore_password": {
"help": "The password that is used to encrypt the provided keystore. Note: It's not your mnemonic password. (It is recommended not to use this argument, and wait for the CLI to ask you for your password as otherwise it will appear in your shell history.)",
"prompt": "Enter the password that is used to encrypt the provided keystore.",
"mismatch": "Error: The provided keystore password was unable to decrypt this keystore file. Make sure you have the correct password and try again."
},
"arg_chain": {
"help": "The name of Ethereum PoS chain you are targeting. Use \"mainnet\" if you are depositing ETH",
"prompt": "Please choose the (mainnet or testnet) network/chain name"
},
"arg_fork": {
"help": "The fork name of the fork you want to signing the message with.",
"prompt": "Please choose the fork name of the fork you want to signing the message with."
},
"arg_bls_to_execution_changes_keystore_folder": {
"help": "The folder path for the keystore(s). Pointing to `./bls_to_execution_changes` by default."
},
"arg_bls_to_execution_changes_keystore_output_folder": {
"help": "Folder where you want to save the bls keystore change"
},
"msg_key_creation": "Creating your SignedBLSToExecutionChangeKeystore.",
"msg_verify_btec": "Verifying the bls_to_execution_changes_keystore JSON file.",
"msg_creation_success": "\nSuccess!\nYour SignedBLSToExecutionChangeKeystore JSON file can be found at: ",
"msg_pause": "\n\nPress any key.",
"err_verify_btec": "Failed to verify the bls_to_execution_change_keystore JSON file."
}
}

View File

@@ -0,0 +1,48 @@
{
"generate_keys_arguments_decorator": {
"num_validators": {
"help": "The number of new validator keys you want to generate (you can always generate more later)",
"prompt": "Please choose how many new validators you wish to run"
},
"folder": {
"help": "The folder path for the keystore(s) and deposit(s). Pointing to `./validator_keys` by default."
},
"chain": {
"help": "The name of Ethereum PoS chain you are targeting. Use \"mainnet\" if you are depositing ETH",
"prompt": "Please choose the (mainnet or testnet) network/chain name"
},
"keystore_password": {
"help": "The password that will secure your keystores. You will need to re-enter this to decrypt them when you setup your Ethereum validators. (It is recommended not to use this argument, and wait for the CLI to ask you for your password, as otherwise it will appear in your shell history.)",
"prompt": "Create a password that secures your validator keystore(s). You will need to re-enter this to decrypt them when you setup your Ethereum validators.",
"confirm": "Repeat your keystore password for confirmation",
"mismatch": "Error: the two entered values do not match. Please type again."
},
"arg_withdrawal_address": {
"help": "The Ethereum address that will be used in withdrawal. It typically starts with '0x' followed by 40 hexadecimal characters. Please make sure you have full control over the address you choose here. Once you set a withdrawal address on chain, it cannot be changed.",
"prompt": "Please enter the optional withdrawal address. Note that you CANNOT change it once you have set it on chain.",
"confirm": "Repeat your withdrawal address for confirmation.",
"mismatch": "Error: the two entered values do not match. Please type again."
},
"arg_compounding": {
"help": "Generates compounding validators with 0x02 withdrawal credentials for a 2048 ETH maximum effective balance or generate regular validators with 0x01 withdrawal credentials for a 32 ETH maximum effective balance. Use of this option requires a withdrawal address. This feature is only supported on networks that have undergone the Pectra fork.",
"prompt": "Please enter yes if you want to generate compounding validators with 0x02 withdrawal credentials for a 2048 ETH maximum effective balance. Compounding validators and 0x02 withdrawal credentials are only supported on networks that have undergone the Pectra fork. Please type no or nothing if you want regular validators with 0x01 withdrawal credentials for a 32 ETH maximum effective balance."
},
"arg_amount": {
"help": "The amount to deposit to these validators in ether denomination. Must be at least 1 ether and can not have greater precision than 1 gwei. Use of this option requires compounding validators.",
"prompt": "Please enter the amount you wish to deposit to these validators. Must be at least 1 ether and can not have greater precision than 1 gwei. 32 is required to activate a new validator"
},
"arg_pbkdf2": {
"help": "Uses the pbkdf2 hashing function instead of scrypt for generated keystore files. "
},
"arg_devnet_chain_setting": {
"help": "[DEVNET ONLY] Set specific GENESIS_FORK_VERSION value. This should be a JSON string containing an object with the following keys: network_name, genesis_fork_version, exit_fork_version and genesis_validator_root. It should be similar to what you can find in settings.py. This will override any selected chain."
}
},
"generate_keys": {
"msg_key_creation": "Creating your keys.",
"msg_creation_success": "\nSuccess!\nYour keys can be found at: ",
"msg_pause": "\n\nPress any key.",
"err_verify_keystores": "Failed to verify the keystores.",
"err_verify_deposit": "Failed to verify the deposit data JSON files."
}
}

View File

@@ -10,6 +10,8 @@
},
"msg_mnemonic_presentation": "This is your mnemonic (seed phrase). Write it down and store it safely. It is the ONLY way to retrieve your deposit.",
"msg_press_any_key": "Press any key when you have written down your mnemonic.",
"msg_mnemonic_retype_prompt": "Please type your mnemonic (separated by spaces) to confirm you have written it down. Note: you only need to enter the first 4 letters of each word if you'd prefer."
"msg_mnemonic_retype_prompt": "Please type your mnemonic (separated by spaces) to confirm you have written it down. Note: you only need to enter the first 4 letters of each word if you'd prefer.",
"msg_mnemonic_clipboard_warning": "WARNING: You MUST write the mnemonic down, as the clipboard will be cleared after this step.",
"msg_confirm_clipboard_clearing": "WARNING: Your clipboard will be CLEARED. Press any key to clear the clipboard."
}
}

View File

@@ -0,0 +1,44 @@
{
"partial_deposit": {
"arg_partial_deposit" :{
"help": "Generate a partial deposit with any amount at least 1 ether which will be signed by the provided validator keystore. This will append to the balance of the provided validator or initiate the creation of one."
},
"arg_partial_deposit_chain": {
"help": "The name of the Ethereum PoS chain your validator is running on. \"mainnet\" is the default.",
"prompt": "Please choose the (mainnet or testnet) network/chain name"
},
"arg_partial_deposit_amount": {
"help": "The amount to deposit to this validator in ether denomination. Must be at least 1 ether and can not have greater precision than 1 gwei. Default is 32 ether.",
"prompt": "Please enter the amount you wish to deposit to this validator. Must be at least 1 ether and can not have greater precision than 1 gwei. 32 is required to activate a new validator"
},
"arg_partial_deposit_keystore": {
"help": "The keystore file associated with the validator you wish to sign with and deposit to.",
"prompt": "Please enter the location of your keystore file."
},
"arg_partial_deposit_keystore_password": {
"help": "The password that is used to encrypt the provided keystore. Note: It's not your mnemonic password. (It is recommended not to use this argument, and wait for the CLI to ask you for your password as otherwise it will appear in your shell history.)",
"prompt": "Enter the password that is used to encrypt the provided keystore.",
"mismatch": "Error: The provided keystore password was unable to decrypt this keystore file. Make sure you have the correct password and try again."
},
"arg_partial_deposit_output_folder": {
"help": "The folder path where the partial deposit will be saved to. Pointing to `./partial_deposits` by default."
},
"arg_withdrawal_address": {
"help": "The withdrawal address of the validator. If you wish to create a validator with 0x00 credentials use the new-mnemonic or existing-mnemonic command.",
"confirm": "Repeat the withdrawal address for confirmation.",
"prompt": "Please enter the withdrawal address. If you wish to create a validator with 0x00 credentials use the new-mnemonic or existing-mnemonic command."
},
"arg_compounding": {
"help": "Generates compounding validators with 0x02 withdrawal credentials for a 2048 ETH maximum effective balance or generate regular validators with 0x01 withdrawal credentials for a 32 ETH maximum effective balance. This feature is only supported on networks that have undergone the Pectra fork.",
"prompt": "Please enter yes if you want to generate compounding validators with 0x02 withdrawal credentials for a 2048 ETH maximum effective balance. Compounding validators and 0x02 withdrawal credentials are only supported on networks that have undergone the Pectra fork. Please type no or nothing if you want regular validators with 0x01 withdrawal credentials for a 32 ETH maximum effective balance."
},
"arg_devnet_chain_setting": {
"help": "[DEVNET ONLY] Set specific GENESIS_FORK_VERSION value. This should be a JSON string containing an object with the following keys: network_name, genesis_fork_version, exit_fork_version and genesis_validator_root. It should be similar to what you can find in settings.py. This will override any selected chain."
},
"msg_partial_deposit_creation": "\nCreating your partial deposit...",
"msg_verify_partial_deposit": "\nVerifying your partial deposit...",
"err_verify_partial_deposit": "\nThere was a problem verifying your partial deposit.\nPlease try again",
"msg_creation_success": "\nSuccess!\nYour partial deposit file can be found at: ",
"msg_pause": "\n\nPress any key."
}
}

View File

@@ -0,0 +1,19 @@
{
"test_keystore": {
"arg_test_keystore" :{
"help": "Verify your keystore file with a provided password"
},
"arg_test_keystore_keystore": {
"help": "The keystore file you wish to verify.",
"prompt": "Please enter the location of your keystore file."
},
"arg_test_keystore_keystore_password": {
"help": "The password used to attempt decryption of the provided keystore file. Note: It is not your mnemonic password. (It is recommended not to use this argument, and wait for the CLI to ask you for your password as otherwise it will appear in your shell history.)",
"prompt": "Enter the password that is used to encrypt the provided keystore.",
"mismatch": "Error: The provided keystore password was unable to decrypt this keystore file. Make sure you have the correct password and try again."
},
"msg_verify_keystore_file": "\nVerifying your keystore file...",
"msg_verification_success": "\nSuccess!\nYour keystore file was successfully decrypted with the provided password!",
"msg_pause": "\n\nPress any key."
}
}

View File

@@ -3,15 +3,15 @@
"msg_key_creation": "Creating your keys:\t\t"
},
"export_keystores": {
"msg_keystore_creation": "Creating your keystores:\t"
"msg_keystore_creation": "Creating your keystore-*.json file(s):\t"
},
"export_deposit_data_json": {
"msg_depositdata_creation": "Creating your depositdata:\t"
"msg_depositdata_creation": "Creating your deposit_data-*.json file(s):\t"
},
"export_bls_to_execution_change_json": {
"msg_bls_to_execution_change_creation": "Creating your SignedBLSToExecutionChange:\t"
},
"verify_keystores": {
"msg_keystore_verification": "Verifying your keystores:\t"
"msg_keystore_verification": "Verifying your keystore-*.json file(s):\t"
}
}

View File

@@ -0,0 +1,8 @@
{
"check_python_version": {
"err_python_version": "Your python version is insufficient, please install version 3.9 or greater."
},
"check_connectivity": {
"connectivity_warning": "\n*** Internet connectivity detected ***\n\nTo mitigate the risk of unauthorized access and safeguard generated key material, it is strongly advised to run this tool in an offline, airgapped environment. Operating online increases susceptibility to potential theft or compromise.\n\nBy continuing, you are accepting responsibility for the risk.\n\nPress any key to continue..."
}
}

View File

@@ -0,0 +1,62 @@
{
"verify_deposit_data_json": {
"msg_deposit_verification": "Verifying your deposit_data-*.json file(s):\t"
},
"validate_password_strength": {
"msg_password_length": "The password length should be at least 12. Please retype.",
"msg_password_utf8_win32": "Your terminal is not UTF-8 encoded. To ensure the keystore file can be imported on Linux, the password should contain only English-language characters. Please retype.",
"msg_password_utf8": "Your terminal is not UTF-8 encoded. To ensure the keystore file can be imported on Linux, the password should contain only English-language characters. Please retype.\nAlternatively, you can quit this program and relaunch it from a UTF-8 encoded terminal."
},
"validate_int_range": {
"err_not_positive_integer": "That is not a positive integer. Please retype."
},
"validate_choice": {
"err_invalid_choice": "That is not one of the valid choices. Please retype your choice."
},
"validate_withdrawal_address": {
"err_invalid_ECDSA_hex_addr": "The given withdrawal address is not in hexadecimal encoded form.",
"err_invalid_ECDSA_hex_addr_checksum": "The given withdrawal address is not in checksum form.",
"err_missing_address": "An address must be provided",
"msg_ECDSA_hex_addr_withdrawal": "**[Warning] you are setting a withdrawal address. Please ensure that you have full control over this address.**"
},
"validate_yesno": {
"err_invalid_bool_value": "This is an invalid value for a yes/no question."
},
"validate_deposit_amount": {
"err_invalid_amount": "Invalid amount provided. Please provide a value of at least 1 ether of how much you wish to deposit.",
"err_not_gwei_denomination": "Invalid amount provided. Please provide a value can not have precision beyond 1 gwei.",
"err_min_deposit": "Invalid amount provided. Please provide a value of at least 1 ether.",
"err_max_deposit": "Invalid amount provided. The max deposit amount is 2048 ether. Please provide a lesser or equal amount."
},
"validate_bls_withdrawal_credentials": {
"err_is_already_01_form": "The given withdrawal credentials is already in 0x01 form. Have you already set the withdrawal address?",
"err_not_bls_form": "The given withdrawal credentials is not in BLS_WITHDRAWAL_PREFIX form."
},
"validate_bls_withdrawal_credentials_matching": {
"err_not_matching": "The given withdrawal credentials does not match the old BLS withdrawal credentials that mnemonic generated."
},
"verify_bls_to_execution_change_json": {
"msg_bls_to_execution_change_verification": "Verifying your BLSToExecutionChange file:\t"
},
"normalize_bls_withdrawal_credentials_to_bytes" :{
"err_incorrect_hex_form": "The given input is not in hexadecimal encoded form."
},
"normalize_input_list": {
"err_incorrect_list": "The given input should be a list of the old BLS withdrawal credentials of your validator(s). Split multiple items with whitespaces or commas."
},
"validate_keystore_file": {
"err_file_not_found": "No file was found. Please verify the provided path and try again.",
"err_invalid_keystore_file": "The discovered file is not the correct keystore file format. Please verify the provided path is to a keystore file and try again."
},
"validate_devnet_chain_setting": {
"err_invalid_devnet_chain_setting": "Invalid JSON string for devnet_chain_setting. It should be a valide JSON object that contains at least the following keys: network_name, genesis_fork_version and exit_fork_version.",
"arg_devnet_chain_setting_warning": "**[Warning] Using devnet chain setting with this command.**"
},
"validate_devnet_chain_setting_json": {
"err_devnet_chain_setting_not_object": "Invalid JSON string for devnet_chain_setting. The JSON string does not contain an object at root.",
"err_devnet_chain_setting_missing_keys": "Invalid JSON string for devnet_chain_setting. It is missing one or more of these key in the root object: network_name, genesis_fork_version or exit_fork_version.",
"err_devnet_chain_setting_invalid_json": "Invalid JSON string for devnet_chain_setting. Decoding the JSON string was impossible. Make sure to pass a valid JSON string.",
"err_devnet_chain_setting_key_length": "Invalid JSON string for devnet_chain_setting. The root object should only contain 3 or 4 keys.",
"err_devnet_chain_setting_invalid_fourth_key": "Invalid JSON string for devnet_chain_setting. The root object is missing the genesis_validator_root key."
}
}

Some files were not shown because too many files have changed in this diff Show More