├── .github └── workflows │ ├── git-version.yml │ └── unit-test.yml ├── .gitignore ├── CMakeLists.txt ├── Cargo.toml ├── GitVersion.yml ├── Guardian └── .__occlum_status ├── LICENSE ├── README.md ├── Secure-Signer └── .__occlum_status ├── Validator └── .__occlum_status ├── build_epid_ra.sh ├── conf ├── ephemery_network_config.json ├── guardian-rust-config.yaml ├── ra_config.json ├── secure-signer-rust-config.yaml └── validator-rust-config.yaml ├── container ├── Dockerfile_SS.ubuntu20.04 └── build_image.sh ├── docs ├── .gitignore ├── 404.html ├── Gemfile ├── Gemfile.lock ├── _config.yml ├── _posts │ └── 2023-01-30-welcome-to-jekyll.markdown ├── assets │ ├── .DS_Store │ └── images │ │ └── Horizontal.png ├── client.md ├── dev_ss.md ├── favicon.ico ├── getting_started.md ├── images │ ├── azure_1.png │ ├── azure_10.png │ ├── azure_11.png │ ├── azure_12.png │ ├── azure_13.png │ ├── azure_14.png │ ├── azure_15.png │ ├── azure_16.png │ ├── azure_17.png │ ├── azure_18.png │ ├── azure_2.png │ ├── azure_3.png │ ├── azure_4.png │ ├── azure_5.png │ ├── azure_6.png │ ├── azure_7.png │ ├── azure_8.png │ ├── azure_9.png │ └── upload_deposit_data.png ├── index.md ├── install.md └── run_ss.md ├── enclave.py ├── lib ├── include │ └── tee │ │ ├── common │ │ ├── error.h │ │ ├── log.h │ │ └── type.h │ │ ├── ra_conf_api.h │ │ ├── ra_ias.h │ │ ├── ra_json.h │ │ ├── ra_quote.h │ │ └── ra_quote_api.h └── src │ ├── ra_ias.cpp │ ├── ra_json.cpp │ └── ra_quote.cpp ├── resources ├── keygen │ ├── paths │ │ ├── bls_keygen.yaml │ │ └── secp256k1_keygen.yaml │ └── schemas.yaml ├── keymanager │ ├── paths │ │ └── keystores.yaml │ └── schemas.yaml ├── redoc-static.html ├── secure-signer.yaml └── signing │ ├── paths │ ├── deposit.yaml │ ├── sign.yaml │ └── upcheck.yaml │ └── schemas.yaml ├── run_container.sh ├── src ├── bin │ ├── guardian.rs │ ├── secure-signer.rs │ └── validator.rs ├── cli │ ├── bls_import.rs │ ├── deposit.rs │ ├── mod.rs │ ├── routes.rs │ └── withdraw.rs ├── client │ ├── guardian.rs │ ├── keygen.rs │ ├── mock │ │ ├── guardian.rs │ │ ├── mod.rs │ │ └── validator.rs │ ├── mod.rs │ ├── secure_signer.rs │ ├── tests │ │ ├── guardian.rs │ │ ├── mod.rs │ │ ├── secure_signer.rs │ │ └── validator.rs │ ├── traits │ │ └── mod.rs │ └── validator.rs ├── constants.rs ├── crypto │ ├── bls_keys.rs │ ├── eth_keys.rs │ ├── keystore.rs │ └── mod.rs ├── enclave │ ├── guardian │ │ ├── handlers │ │ │ ├── attest_fresh_eth_key_with_blockhash.rs │ │ │ ├── mod.rs │ │ │ ├── sign_exit.rs │ │ │ └── validate_custody.rs │ │ └── mod.rs │ ├── mod.rs │ ├── secure_signer │ │ ├── handlers │ │ │ ├── bls_keygen.rs │ │ │ ├── eth_keygen.rs │ │ │ ├── mod.rs │ │ │ └── validator_deposit.rs │ │ └── mod.rs │ ├── shared │ │ ├── handlers │ │ │ ├── health.rs │ │ │ ├── list_bls_keys.rs │ │ │ ├── list_bls_keys_for_vc.rs │ │ │ ├── list_eth_keys.rs │ │ │ ├── mod.rs │ │ │ └── secure_sign_bls.rs │ │ └── mod.rs │ ├── test │ │ ├── integration.rs │ │ └── mod.rs │ ├── types.rs │ └── validator │ │ ├── handlers │ │ ├── attest_fresh_bls_key.rs │ │ └── mod.rs │ │ └── mod.rs ├── eth2 │ ├── eth_signing.rs │ ├── eth_types.rs │ ├── mod.rs │ └── slash_protection.rs ├── io │ ├── key_management.rs │ ├── mod.rs │ ├── ra_config.h │ ├── ra_wrapper.cpp │ └── remote_attestation.rs └── lib.rs └── tests ├── Makefile ├── common ├── bls_keygen_helper.rs ├── eth_keygen_helper.rs ├── eth_specs.rs ├── getter_routes_helper.rs ├── mod.rs └── signing_helper.rs ├── mod.rs └── signing_tests ├── aggregate_and_proof.rs ├── aggregation_slot.rs ├── attestation.rs ├── block.rs ├── block_v2.rs ├── contribution_and_proof.rs ├── deposit.rs ├── mod.rs ├── randao_reveal.rs ├── sync_committee_message.rs ├── sync_committee_selection_proof.rs └── validator_registration.rs /.github/workflows/git-version.yml: -------------------------------------------------------------------------------- 1 | name: "git versioning" 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - master 9 | 10 | env: 11 | BUILD-NUMBER: ${{github.run_number}} 12 | 13 | jobs: 14 | check-semantic-version: 15 | runs-on: [ubuntu-latest] 16 | steps: 17 | - uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Install GitVersion 22 | uses: gittools/actions/gitversion/setup@v0 23 | with: 24 | versionSpec: '5.x' 25 | - name: Determine Version 26 | id: gitversion 27 | uses: gittools/actions/gitversion/execute@v0 28 | with: 29 | useConfigFile: true 30 | configFilePath: GitVersion.yml 31 | updateAssemblyInfo: true 32 | - name: Semver 33 | run: echo $GITVERSION_SEMVER 34 | -------------------------------------------------------------------------------- /.github/workflows/unit-test.yml: -------------------------------------------------------------------------------- 1 | name: secure-signer CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths: .github/workflows/unit-test.yml 7 | pull_request: 8 | branches: [ main ] 9 | paths: .github/workflows/unit-test.yml 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-20.04 14 | steps: 15 | - name: 'Checkout latest' 16 | uses: actions/checkout@v3 17 | 18 | - name: 'Install rust' 19 | run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain none -y 20 | shell: bash 21 | 22 | - name: 'Make tests' 23 | run: | 24 | cd tests 25 | make 26 | shell: bash 27 | 28 | - name: 'Clean up archives' 29 | run: | 30 | cd tests 31 | make clean-archives 32 | shell: bash 33 | 34 | - name: 'Run unit-tests' 35 | run: cargo test -- --test-threads 1 36 | shell: bash 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | build/ 17 | deps/ 18 | etc/ 19 | keys/ 20 | keystores/ 21 | ss_out/ 22 | Secure-Signer/* 23 | Guardian/* 24 | Validator/* 25 | image 26 | initfs 27 | 28 | # Can't keep empty folders but need it for tests not to break. 29 | # So this file is kept here as a place holder. 30 | !Secure-Signer/.__occlum_status 31 | !Validator/.__occlum_status 32 | !Guardian/.__occlum_status 33 | 34 | # Avoid adding consensus spec files to repo. 35 | tests/consensus-spec-tests/ 36 | tests/*rc*.gz 37 | 38 | # Preferences 39 | .vscode/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 3.5) 2 | 3 | MESSAGE(STATUS "Build Mode: ${BUILD_MODE}") 4 | ADD_COMPILE_OPTIONS(-std=c++11) 5 | if(BUILD_MODE STREQUAL "Debug") 6 | ADD_COMPILE_OPTIONS(-O0 -g2 -DDEBUG -UNDEBUG -UEDEBUG) 7 | endif() 8 | 9 | PROJECT(SECURE_SIGNER LANGUAGES CXX VERSION 0.1.0) 10 | MESSAGE(STATUS "BINARY dir " ${CMAKE_CURRENT_BINARY_DIR}) 11 | MESSAGE(STATUS "SOURCE dir " ${CMAKE_CURRENT_SOURCE_DIR}) 12 | 13 | SET(SGXSDK_INSTALL_DIR /opt/intel/sgxsdk) 14 | SET(OCCLUM_INSTALL_DIR /usr/local/occlum/x86_64-linux-musl) 15 | 16 | # Gather all epid RA c++ src files to LIB_SRCS 17 | FILE(GLOB LIB_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/lib/src/*.cpp) 18 | 19 | SET(RALIB occlumra) 20 | # create static libocclumara.a 21 | ADD_LIBRARY(${RALIB} ${LIB_SRCS}) 22 | # include the necessary deps to perform epid RA 23 | TARGET_INCLUDE_DIRECTORIES( 24 | ${RALIB} PUBLIC 25 | ${CMAKE_CURRENT_SOURCE_DIR}/lib/include 26 | ${SGXSDK_INSTALL_DIR}/include 27 | ${OCCLUM_INSTALL_DIR}/include 28 | ${CMAKE_CURRENT_SOURCE_DIR}/deps/cppcodec 29 | ${CMAKE_CURRENT_SOURCE_DIR}/deps/rapidjson/include # for parsing ra_config.json 30 | ) 31 | # link curl lib for making the RA requests 32 | TARGET_LINK_LIBRARIES(${RALIB} 33 | -L${OCCLUM_INSTALL_DIR}/lib -lcurl 34 | ) 35 | 36 | SET(CUSTOM_LIB epid_ra) 37 | FILE(GLOB APP_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/src/io/*.cpp) 38 | # create static libepid_ra.a 39 | ADD_LIBRARY(${CUSTOM_LIB} ${APP_SRCS}) 40 | TARGET_INCLUDE_DIRECTORIES( 41 | ${CUSTOM_LIB} PUBLIC 42 | ${CMAKE_CURRENT_SOURCE_DIR} 43 | ${CMAKE_CURRENT_SOURCE_DIR}/lib/include 44 | ${SGXSDK_INSTALL_DIR}/include 45 | ${OCCLUM_INSTALL_DIR}/include 46 | ) 47 | # link this to libocclumra.a 48 | TARGET_LINK_LIBRARIES(${CUSTOM_LIB} 49 | -L${CMAKE_CURRENT_BINARY_DIR} -l${RALIB} 50 | -L${OCCLUM_INSTALL_DIR}/lib -lcurl -lcrypto 51 | -Wl,-rpath=${CMAKE_CURRENT_BINARY_DIR}:${OCCLUM_INSTALL_DIR}/lib 52 | ) 53 | ADD_DEPENDENCIES(${CUSTOM_LIB} ${RALIB}) 54 | 55 | 56 | # GET SHARED OBJECT FILE FOR RA LIB TO CALL FROM PYTHON 57 | SET(CUSTOM_LIB epid) 58 | FILE(GLOB APP_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/src/io/*.cpp) 59 | # create shared lib libepid.so 60 | ADD_LIBRARY(${CUSTOM_LIB} SHARED ${APP_SRCS}) 61 | TARGET_INCLUDE_DIRECTORIES( 62 | ${CUSTOM_LIB} PUBLIC 63 | ${CMAKE_CURRENT_SOURCE_DIR} 64 | ${CMAKE_CURRENT_SOURCE_DIR}/lib/include 65 | ${SGXSDK_INSTALL_DIR}/include 66 | ${OCCLUM_INSTALL_DIR}/include 67 | ) 68 | # link this to libocclumra.a 69 | TARGET_LINK_LIBRARIES(${CUSTOM_LIB} 70 | -L${CMAKE_CURRENT_BINARY_DIR} -l${RALIB} 71 | -L${OCCLUM_INSTALL_DIR}/lib -lcurl -lcrypto 72 | -Wl,-rpath=${CMAKE_CURRENT_BINARY_DIR}:${OCCLUM_INSTALL_DIR}/lib 73 | ) 74 | ADD_DEPENDENCIES(${CUSTOM_LIB} ${RALIB}) -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "puffersecuresigner" 3 | version = "0.1.0" 4 | authors = ["Jason Vranek "] 5 | edition = "2021" 6 | 7 | [build] 8 | target = "x86_64-unknown-linux-musl" 9 | 10 | [build-dependencies] 11 | cc = "1.0" 12 | 13 | [dependencies] 14 | libc = "0.2" 15 | 16 | # io deps 17 | serde = "1.0" 18 | serde_json = "1.0" 19 | serde_derive = "1.0" 20 | serde-hex = "0.1.0" 21 | env_logger = "0.10.0" 22 | log = "0.4.17" 23 | ethereum_serde_utils = "0.5.1" 24 | 25 | # crypto deps 26 | blsttc = { version = "8.0.2", git = "https://github.com/PufferFinance/blsttc" } 27 | libsecp256k1 = "0.7.1" 28 | rand = "0.8.4" 29 | rand_chacha = "0.2" 30 | hex = "0.4.3" 31 | ecies = { version = "0.2.2", default-features = false, features = ["pure"] } 32 | openssl = "0.10.42" 33 | bytes = "1" 34 | sha3 = "0.10.6" 35 | 36 | # eth deps 37 | eth-keystore = { git = "https://github.com/PufferFinance/eth-keystore-rs" } 38 | eth2_ssz = "0.4.0" 39 | eth2_ssz_derive = "0.3.0" 40 | eth2_ssz_types = { git = "https://github.com/PufferFinance/ssz_types" } 41 | tree_hash = { git = "https://github.com/PufferFinance/tree_hash" } 42 | tree_hash_derive = { git = "https://github.com/PufferFinance/tree_hash" } 43 | num-bigint = "0.4" 44 | 45 | # server deps 46 | tokio = { version = "1", features = ["full"] } 47 | axum = { version = "0.6.20", features = ["macros"] } 48 | tracing-subscriber = "0.3.17" 49 | axum-test = "12.3.0" 50 | 51 | # client deps 52 | reqwest = { version = "0.11", features = ["json"] } 53 | 54 | # misc 55 | anyhow = "1.0.65" 56 | clap = { version = "4.1.1", features = ["derive"], optional = true } 57 | ethers = "2.0.8" 58 | async-trait = "0.1.73" 59 | 60 | 61 | [dev-dependencies] 62 | reqwest = { version = "0.11.16", features = ["json"] } 63 | snap = "1.0.1" 64 | serde_yaml = "0.8.13" 65 | 66 | [features] 67 | sgx = [] 68 | 69 | [[bin]] # Bin to run the sgx-signer rpc 70 | name = "secure-signer" 71 | path = "src/bin/secure-signer.rs" 72 | 73 | [[bin]] # Bin to run the sgx-guardian rpc 74 | name = "guardian" 75 | path = "src/bin/guardian.rs" 76 | 77 | [[bin]] # Bin to run the sgx-validator rpc 78 | name = "validator" 79 | path = "src/bin/validator.rs" 80 | 81 | [[bin]] # Bin to run the client 82 | name = "client" 83 | path = "src/client/mod.rs" 84 | required-features = ["clap"] 85 | -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: Mainline 2 | increment: Inherit 3 | update-build-number: true 4 | branches: 5 | main: 6 | regex: ^main$ 7 | is-mainline: true 8 | release: 9 | regex: .+release?[/-] 10 | tag: useBranchName 11 | increment: Major 12 | feature: 13 | regex: .+feature?[/-] 14 | tag: useBranchName 15 | increment: Minor 16 | hotfix: 17 | regex: .+(bugfix|hotfix(es)?)[/-] 18 | tag: useBranchName 19 | increment: Patch 20 | ignore: 21 | sha: [] 22 | merge-message-formats: {} 23 | assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:GITHUB_RUN_NUMBER}' 24 | -------------------------------------------------------------------------------- /Guardian/.__occlum_status: -------------------------------------------------------------------------------- 1 | running 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Secure-Signer 2 | Secure-Signer is a remote signing tool for Ethereum PoS validators, with the following features: 3 | 4 | - Follows the [Web3Signer](https://consensys.github.io/web3signer/web3signer-eth2.html) specification 5 | - Compatible with existing Consensus clients 6 | - Designed to run on Intel SGX via the [Occlum LibOS](https://github.com/occlum/occlum) 7 | - Provides protection from [slashable offenses](https://github.com/ethereum/consensus-specs/blob/master/specs/phase0/validator.md#how-to-avoid-slashing) 8 | 9 | Validator keys are safeguarded in SGX's encrypted memory and the hardware enforces that Secure-Signer can only sign non-slashable messages. This reduces validator risk from slashing either from accidents or if their system is compromised. 10 | 11 | 12 | > **SECURE SIGNER IS UNDER DEVELOPMENT**, see [DockerHub](https://hub.docker.com/repository/docker/pufferfi/validator/general) for the latest enclave image. 13 | 14 | --- 15 | ## API 16 | 17 | - [API Documentation](https://pufferfinance.github.io/secure-signer-api-docs/redoc-static.html) 18 | 19 | ## Users 20 | - [User Documentation](https://pufferfinance.github.io/secure-signer/) 21 | 22 | ## Developers 23 | - [Developer Documentation](https://pufferfinance.github.io/secure-signer/developers/) 24 | 25 | ## Protocols 26 | - [Puffer Node Operator Documentation](https://docs.puffer.fi/nodes/requirements) 27 | 28 | --- 29 | 30 | ## Acknowledgements 31 | Secure-Signer is funded via an [Ethereum Foundation grant](https://blog.ethereum.org/2023/02/22/allocation-update-q4-22). 32 | 33 | The following dependencies were used and some code might have been insipired by their design decisions as well: 34 | 35 | - [Occulum](https://github.com/occlum/occlum) LibOS - [BSD License](https://github.com/occlum/occlum/blob/master/LICENSE) 36 | 37 | 38 | ## License 39 | Secure Signer is released under Apache 2.0 License. See the copyright information [here](https://github.com/PufferFinance/secure-signer/blob/main/LICENSE). 40 | 41 | -------------------------------------------------------------------------------- /Secure-Signer/.__occlum_status: -------------------------------------------------------------------------------- 1 | running 2 | -------------------------------------------------------------------------------- /Validator/.__occlum_status: -------------------------------------------------------------------------------- 1 | running 2 | -------------------------------------------------------------------------------- /build_epid_ra.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | THISDIR="$(dirname $(readlink -f $0))" 4 | INSTALLDIR="/usr/local/occlum/x86_64-linux-musl" 5 | OCCLUMCC="/usr/local/occlum/bin/occlum-gcc" 6 | OCCLUMCXX="/usr/local/occlum/bin/occlum-g++" 7 | 8 | export CC=$OCCLUMCC 9 | export CXX=$OCCLUMCXX 10 | export PATH=$PATH:$INSTALLDIR/bin 11 | export PKG_CONFIG_LIBDIR=$PKG_CONFIG_LIBDIR:$INSTALLDIR/lib 12 | 13 | DEPSDIR="$THISDIR/deps" 14 | 15 | ALL_COMPONENTS="openssl libcurl secure_signer" 16 | OPENSSLDIR=openssl 17 | CURLDIR=curl 18 | CPPCODECDIR=cppcodec 19 | RAPIDJSONDIR=rapidjson 20 | 21 | SHOW_HELP() { 22 | LOG_INFO "Usage: $0 [component-name]\n" 23 | LOG_INFO "Build component in [$ALL_COMPONENTS] or all by default\n" 24 | exit 0 25 | } 26 | 27 | LOG_DEBUG() { 28 | echo -e "\033[36m$@\033[0m" 29 | } 30 | 31 | LOG_INFO() { 32 | echo -e "\033[32m$@\033[0m" 33 | } 34 | 35 | LOG_ERROR() { 36 | echo -e "\033[31m$@\033[0m" 37 | } 38 | 39 | ERROR_EXIT() { 40 | LOG_ERROR "$@" && exit 1 41 | } 42 | 43 | TRYGET() { 44 | local dst=$1 45 | local url=$2 46 | local pkg=${3:-$(basename $url)} 47 | local flag="./occlum_demo_source" 48 | 49 | # Download package tarball 50 | if [ ! -e $pkg ] ; then 51 | LOG_DEBUG "Downloading $pkg ..." 52 | wget $url -O $pkg || ERROR_EXIT "Fail to download $pkg" 53 | else 54 | LOG_INFO "[READY] $pkg" 55 | fi 56 | 57 | # Prepare the source code directory 58 | if [ ! -f $dst/$flag ] ; then 59 | LOG_DEBUG "Preparing source code: $dst ..." 60 | mkdir -p $dst && \ 61 | tar -xvf $pkg -C $dst --strip-components 1 >/dev/null || \ 62 | ERROR_EXIT "Fail to extract archive file $pkg" 63 | touch $dst/$flag && \ 64 | LOG_DEBUG "Prepare $(basename $dst) source code successfully" 65 | else 66 | LOG_INFO "[READY] $dst" 67 | fi 68 | } 69 | 70 | openssl_check() { 71 | [ -f "$INSTALLDIR/lib/libcrypto.so" ] || return 1 72 | } 73 | 74 | openssl_build() { 75 | cd "$DEPSDIR/$OPENSSLDIR" && \ 76 | CC=$OCCLUMCC CXX=$OCCLUMCXX \ 77 | ./config --prefix=$INSTALLDIR \ 78 | --openssldir=/usr/local/occlum/ssl \ 79 | --with-rand-seed=rdcpu \ 80 | no-zlib no-async no-tests && \ 81 | make -j && make install 82 | } 83 | 84 | libcurl_check() { 85 | [ -f "$INSTALLDIR/lib/libcurl.so" ] || return 1 86 | } 87 | 88 | libcurl_build() { 89 | cd "$DEPSDIR/$CURLDIR" 90 | if [ ! -f ./configure ] ; then 91 | LOG_DEBUG "Building configure file ..." 92 | ./buildconf || exit 1 93 | fi 94 | CC=$OCCLUMCC CXX=$OCCLUMCXX \ 95 | ./configure \ 96 | --prefix=$INSTALLDIR \ 97 | --with-ssl=$INSTALLDIR \ 98 | --without-zlib && \ 99 | make -j && make install 100 | } 101 | 102 | secure_signer_check() { 103 | [ -f "$INSTALLDIR/lib/libepid.a" ] || return 1 104 | } 105 | 106 | secure_signer_build() { 107 | cd "$THISDIR" 108 | rm -rf build && mkdir build && cd build && \ 109 | echo "[+] Cleaned libocclum.ra and libepid.ra" 110 | cmake ../ \ 111 | -DCMAKE_NO_SYSTEM_FROM_IMPORTED=TRUE \ 112 | -DCMAKE_CXX_COMPILER=$OCCLUMCXX \ 113 | -DBUILD_MODE=${BUILDMODE} && \ 114 | make -j $BUILDVERBOSE && \ 115 | cp $THISDIR/build/libocclumra.a $INSTALLDIR/lib 116 | cp $THISDIR/build/libepid_ra.a $INSTALLDIR/lib # added by me 117 | cp $THISDIR/build/libepid.so $INSTALLDIR/lib # added by me 118 | 119 | 120 | # combine the static libs into a single one to link to in rust 121 | COMBINED_LIB="libepid.a" 122 | ar -x ${INSTALLDIR}/lib/libocclumra.a 123 | ar -x ${INSTALLDIR}/lib/libepid_ra.a 124 | ar -x ${INSTALLDIR}/lib/libcurl.a 125 | ar -x ${INSTALLDIR}/lib/libcrypto.a 126 | ar -x ${INSTALLDIR}/lib/libssl.a 127 | ar -crs ${COMBINED_LIB} *.o 128 | rm *.o 129 | cp $THISDIR/build/${COMBINED_LIB} $INSTALLDIR/lib 130 | } 131 | 132 | # Show help menu 133 | [ "$1" == "-h" -o "$1" == "--help" ] && SHOW_HELP 134 | 135 | # Check the build mode 136 | BUILDMODE="Release" 137 | BUILDVERBOSE="" 138 | if [ "$1" == "--debug" ] ; then 139 | BUILDMODE="Debug" 140 | BUILDVERBOSE="VERBOSE=1" 141 | shift; 142 | fi 143 | 144 | # Build specified component or all by default 145 | BUILD_COMPONENTS="${1:-$ALL_COMPONENTS}" 146 | 147 | # Download all components once here together 148 | mkdir -p $DEPSDIR && cd $DEPSDIR || exit 1 149 | TRYGET $OPENSSLDIR https://github.com/openssl/openssl/archive/OpenSSL_1_1_1.tar.gz 150 | TRYGET $CURLDIR https://github.com/curl/curl/archive/curl-7_70_0.tar.gz 151 | TRYGET $CPPCODECDIR https://github.com/tplgy/cppcodec/archive/v0.2.tar.gz cppcodec-0.2.tar.gz 152 | TRYGET $RAPIDJSONDIR https://github.com/Tencent/rapidjson/archive/v1.1.0.tar.gz rapidjson-1.1.0.tar.gz 153 | 154 | for i in $BUILD_COMPONENTS ; do 155 | ${i}_check && LOG_INFO "[READY] build check for $i" && continue 156 | LOG_DEBUG "Building $i ..." && ${i}_build && \ 157 | LOG_DEBUG "Build $i successfully" || ERROR_EXIT "Fail to build $i" 158 | done 159 | 160 | -------------------------------------------------------------------------------- /conf/ephemery_network_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "network_name": "ephemery", 3 | "deposit_cli_version": "2.3.0", 4 | "fork_info": { 5 | "fork": { 6 | "previous_version": "0x1000101a", 7 | "current_version": "0x1000101b", 8 | "epoch": "0" 9 | }, 10 | "genesis_validators_root": "0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69" 11 | } 12 | } -------------------------------------------------------------------------------- /conf/guardian-rust-config.yaml: -------------------------------------------------------------------------------- 1 | includes: 2 | - base.yaml 3 | targets: 4 | - target: /bin 5 | copy: 6 | - files: 7 | - ../target/x86_64-unknown-linux-musl/release/guardian -------------------------------------------------------------------------------- /conf/ra_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ias_url": "https://api.trustedservices.intel.com/sgx/dev/attestation/v4", 3 | "enclave_spid": "42616C98D53C9712639447C9B0E7003F", 4 | "ias_access_key": "5c216729e0524cc887d344a1ea5d2b8b" 5 | } 6 | 7 | -------------------------------------------------------------------------------- /conf/secure-signer-rust-config.yaml: -------------------------------------------------------------------------------- 1 | includes: 2 | - base.yaml 3 | targets: 4 | - target: /bin 5 | copy: 6 | - files: 7 | - ../target/x86_64-unknown-linux-musl/release/secure-signer -------------------------------------------------------------------------------- /conf/validator-rust-config.yaml: -------------------------------------------------------------------------------- 1 | includes: 2 | - base.yaml 3 | targets: 4 | - target: /bin 5 | copy: 6 | - files: 7 | - ../target/x86_64-unknown-linux-musl/release/validator -------------------------------------------------------------------------------- /container/Dockerfile_SS.ubuntu20.04: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | LABEL maintainer="Jason Vranek " 3 | 4 | # Install SGX, EPID, DCAP, and Occlum runtime 5 | ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=1 6 | RUN apt update && DEBIAN_FRONTEND="noninteractive" apt install -y --no-install-recommends gnupg wget ca-certificates jq && \ 7 | echo 'deb [arch=amd64] https://download.01.org/intel-sgx/sgx_repo/ubuntu focal main' | tee /etc/apt/sources.list.d/intel-sgx.list && \ 8 | wget -qO - https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key | apt-key add - && \ 9 | echo 'deb [arch=amd64] https://occlum.io/occlum-package-repos/debian focal main' | tee /etc/apt/sources.list.d/occlum.list && \ 10 | wget -qO - https://occlum.io/occlum-package-repos/debian/public.key | apt-key add - && \ 11 | apt update && \ 12 | apt install -y libsgx-epid && \ 13 | apt install -y libsgx-quote-ex && \ 14 | apt install -y libsgx-dcap-ql && \ 15 | apt install -y libsgx-urts && \ 16 | apt install -y libsgx-uae-service && \ 17 | apt install -y libsgx-dcap-ql && \ 18 | apt install -y libsgx-dcap-default-qpl && \ 19 | apt install -y occlum-runtime && \ 20 | apt clean && \ 21 | rm -rf /var/lib/apt/lists/* 22 | ENV PATH="/opt/occlum/build/bin:/usr/local/occlum/bin:$PATH" 23 | 24 | # Users need build their own applications and generate occlum package first. 25 | ARG OCCLUM_PACKAGE 26 | ADD $OCCLUM_PACKAGE / 27 | 28 | ARG NETWORK_CONFIG 29 | COPY $NETWORK_CONFIG /home/conf/network_config.json 30 | 31 | ARG MRENCLAVE 32 | COPY $MRENCLAVE /MRENCLAVE 33 | 34 | WORKDIR / -------------------------------------------------------------------------------- /container/build_image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | script_dir="$(dirname $(readlink -f $0))" 4 | top_dir=$(dirname "${scripts_dir}") 5 | 6 | function usage { 7 | cat << EOM 8 | usage: $(basename "$0") [OPTION]... 9 | -i the occlum instance tar package after doing "occlum package" 10 | -r the prefix string for registry 11 | -n 12 | -g container image tag 13 | -b 14 | -c path network config dir 15 | -e path to the ss client binary 16 | -m file containing MRENCLAVE measurement 17 | -h usage help 18 | EOM 19 | exit 0 20 | } 21 | 22 | function process_args { 23 | while getopts ":i:r:n:g:b:e:c:m:h" option; do 24 | case "${option}" in 25 | i) package=${OPTARG};; 26 | r) registry=${OPTARG};; 27 | n) name=${OPTARG};; 28 | g) tag=${OPTARG};; 29 | b) base_image_name=${OPTARG};; 30 | e) client=${OPTARG};; 31 | c) network_config=${OPTARG};; 32 | m) measurement=${OPTARG};; 33 | h) usage;; 34 | esac 35 | done 36 | 37 | if [[ "${package}" == "" ]]; then 38 | echo "Error: Please specify your occlum instance package via -i ." 39 | exit 1 40 | fi 41 | 42 | if [[ "${name}" == "" ]]; then 43 | echo "Error: Please specify your container image name via -n ." 44 | exit 1 45 | fi 46 | } 47 | 48 | function build_docker_occlum_image { 49 | cd ${top_dir} 50 | 51 | echo "Build docker Occlum image based on ${package} ..." 52 | sudo -E docker build \ 53 | --network host \ 54 | --build-arg http_proxy=$http_proxy \ 55 | --build-arg https_proxy=$https_proxy \ 56 | --build-arg OCCLUM_PACKAGE=${package} \ 57 | --build-arg SS_CLIENT=${client} \ 58 | --build-arg NETWORK_CONFIG=${network_config} \ 59 | --build-arg MRENCLAVE=${measurement} \ 60 | -f ${base_image_name} . \ 61 | -t ${registry}/${name}:${tag} 62 | } 63 | 64 | process_args "$@" 65 | build_docker_occlum_image -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .sass-cache 3 | .jekyll-cache 4 | .jekyll-metadata 5 | vendor 6 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /404.html 3 | layout: default 4 | --- 5 | 6 | 19 | 20 |
21 |

404

22 | 23 |

Page not found :(

24 |

The requested page could not be found.

25 |
26 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | # Hello! This is where you manage which Jekyll version is used to run. 3 | # When you want to use a different version, change it below, save the 4 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: 5 | # 6 | # bundle exec jekyll serve 7 | # 8 | # This will help ensure the proper Jekyll version is running. 9 | # Happy Jekylling! 10 | gem "jekyll", "~> 4.3.2" 11 | gem "just-the-docs" 12 | # This is the default theme for new Jekyll sites. You may change this to anything you like. 13 | gem "minima", "~> 2.5" 14 | # If you want to use GitHub Pages, remove the "gem "jekyll"" above and 15 | # uncomment the line below. To upgrade, run `bundle update github-pages`. 16 | # gem "github-pages", group: :jekyll_plugins 17 | # If you have any plugins, put them here! 18 | group :jekyll_plugins do 19 | gem "jekyll-feed", "~> 0.12" 20 | end 21 | 22 | # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem 23 | # and associated library. 24 | platforms :mingw, :x64_mingw, :mswin, :jruby do 25 | gem "tzinfo", ">= 1", "< 3" 26 | gem "tzinfo-data" 27 | end 28 | 29 | # Performance-booster for watching directories on Windows 30 | gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin] 31 | 32 | # Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem 33 | # do not have a Java counterpart. 34 | gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] 35 | -------------------------------------------------------------------------------- /docs/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.8.1) 5 | public_suffix (>= 2.0.2, < 6.0) 6 | colorator (1.1.0) 7 | concurrent-ruby (1.2.0) 8 | em-websocket (0.5.3) 9 | eventmachine (>= 0.12.9) 10 | http_parser.rb (~> 0) 11 | eventmachine (1.2.7) 12 | ffi (1.15.5) 13 | forwardable-extended (2.6.0) 14 | google-protobuf (3.21.12) 15 | google-protobuf (3.21.12-x86_64-darwin) 16 | http_parser.rb (0.8.0) 17 | i18n (1.12.0) 18 | concurrent-ruby (~> 1.0) 19 | jekyll (4.3.2) 20 | addressable (~> 2.4) 21 | colorator (~> 1.0) 22 | em-websocket (~> 0.5) 23 | i18n (~> 1.0) 24 | jekyll-sass-converter (>= 2.0, < 4.0) 25 | jekyll-watch (~> 2.0) 26 | kramdown (~> 2.3, >= 2.3.1) 27 | kramdown-parser-gfm (~> 1.0) 28 | liquid (~> 4.0) 29 | mercenary (>= 0.3.6, < 0.5) 30 | pathutil (~> 0.9) 31 | rouge (>= 3.0, < 5.0) 32 | safe_yaml (~> 1.0) 33 | terminal-table (>= 1.8, < 4.0) 34 | webrick (~> 1.7) 35 | jekyll-feed (0.17.0) 36 | jekyll (>= 3.7, < 5.0) 37 | jekyll-sass-converter (3.0.0) 38 | sass-embedded (~> 1.54) 39 | jekyll-seo-tag (2.8.0) 40 | jekyll (>= 3.8, < 5.0) 41 | jekyll-watch (2.2.1) 42 | listen (~> 3.0) 43 | just-the-docs (0.3.3) 44 | jekyll (>= 3.8.5) 45 | jekyll-seo-tag (~> 2.0) 46 | rake (>= 12.3.1, < 13.1.0) 47 | kramdown (2.4.0) 48 | rexml 49 | kramdown-parser-gfm (1.1.0) 50 | kramdown (~> 2.0) 51 | liquid (4.0.4) 52 | listen (3.8.0) 53 | rb-fsevent (~> 0.10, >= 0.10.3) 54 | rb-inotify (~> 0.9, >= 0.9.10) 55 | mercenary (0.4.0) 56 | minima (2.5.1) 57 | jekyll (>= 3.5, < 5.0) 58 | jekyll-feed (~> 0.9) 59 | jekyll-seo-tag (~> 2.1) 60 | pathutil (0.16.2) 61 | forwardable-extended (~> 2.6) 62 | public_suffix (5.0.1) 63 | rake (13.0.6) 64 | rb-fsevent (0.11.2) 65 | rb-inotify (0.10.1) 66 | ffi (~> 1.0) 67 | rexml (3.2.5) 68 | rouge (3.30.0) 69 | safe_yaml (1.0.5) 70 | sass-embedded (1.57.1-arm64-darwin) 71 | google-protobuf (~> 3.21) 72 | sass-embedded (1.57.1-x86_64-darwin) 73 | google-protobuf (~> 3.21) 74 | terminal-table (3.0.2) 75 | unicode-display_width (>= 1.1.1, < 3) 76 | unicode-display_width (2.4.2) 77 | webrick (1.8.1) 78 | 79 | PLATFORMS 80 | arm64-darwin-21 81 | x86_64-darwin-22 82 | 83 | DEPENDENCIES 84 | http_parser.rb (~> 0.6.0) 85 | jekyll (~> 4.3.2) 86 | jekyll-feed (~> 0.12) 87 | just-the-docs 88 | minima (~> 2.5) 89 | tzinfo (>= 1, < 3) 90 | tzinfo-data 91 | wdm (~> 0.1.1) 92 | 93 | BUNDLED WITH 94 | 2.4.5 95 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Jekyll! 2 | # 3 | # This config file is meant for settings that affect your whole blog, values 4 | # which you are expected to set up once and rarely edit after that. If you find 5 | # yourself editing this file very often, consider using Jekyll's data files 6 | # feature for the data you need to update frequently. 7 | # 8 | # For technical reasons, this file is *NOT* reloaded automatically when you use 9 | # 'bundle exec jekyll serve'. If you change this file, please restart the server process. 10 | # 11 | # If you need help with YAML syntax, here are some quick references for you: 12 | # https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml 13 | # https://learnxinyminutes.com/docs/yaml/ 14 | # 15 | # Site settings 16 | # These are used to personalize your new site. If you look in the HTML files, 17 | # you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. 18 | # You can create any custom variable you would like, and they will be accessible 19 | # in the templates via {{ site.myvariable }}. 20 | 21 | title: Secure-Signer 22 | description: Remote Signing tool for Ethereum consensus clients 23 | baseurl: "/secure-signer" # the subpath of your site, e.g. /blog 24 | url: "https://github.com/PufferFinance" # the base hostname & protocol for your site, e.g. http://example.com 25 | 26 | permalink: pretty 27 | 28 | # Build settings 29 | # theme: just-the-docs 30 | remote_theme: pmarsceill/just-the-docs 31 | 32 | 33 | 34 | # Exclude from processing. 35 | # The following items will not be processed, by default. 36 | # Any item listed under the `exclude:` key here will be automatically added to 37 | # the internal "default list". 38 | # 39 | # Excluded items can be processed by explicitly listing the directories or 40 | # their entries' file path in the `include:` list. 41 | exclude: 42 | # from https://github.com/jekyll/jekyll/blob/master/lib/site_template/_config.yml: 43 | - .sass-cache/ 44 | - .jekyll-cache/ 45 | - gemfiles/ 46 | - Gemfile 47 | - Gemfile.lock 48 | - node_modules/ 49 | - vendor/bundle/ 50 | - vendor/cache/ 51 | - vendor/gems/ 52 | - vendor/ruby/ 53 | # specific to the theme website: 54 | - bin/ 55 | - lib/ 56 | - "*.gemspec" 57 | - "*.gem" 58 | - LICENSE.txt 59 | - package.json 60 | - package-lock.json 61 | - Rakefile 62 | - README.md 63 | 64 | 65 | # Set a path/url to a logo that will be displayed instead of the title 66 | # logo: "/assets/images/Horizontal.png" 67 | 68 | 69 | # Enable or disable the site search 70 | # Supports true (default) or false 71 | search_enabled: true 72 | search: 73 | # Split pages into sections that can be searched individually 74 | # Supports 1 - 6, default: 2 75 | heading_level: 2 76 | # Maximum amount of previews per search result 77 | # Default: 3 78 | previews: 2 79 | # Maximum amount of words to display before a matched word in the preview 80 | # Default: 5 81 | preview_words_before: 3 82 | # Maximum amount of words to display after a matched word in the preview 83 | # Default: 10 84 | preview_words_after: 3 85 | # Set the search token separator 86 | # Default: /[\s\-/]+/ 87 | # Example: enable support for hyphenated search words 88 | tokenizer_separator: /[\s/]+/ 89 | # Display the relative url in search results 90 | # Supports true (default) or false 91 | rel_url: true 92 | # Enable or disable the search button that appears in the bottom right corner of every page 93 | # Supports true or false (default) 94 | button: false 95 | 96 | # For copy button on code 97 | enable_copy_code_button: true 98 | 99 | # To disable support for mermaid diagrams (https://mermaid.js.org), 100 | # comment out the `mermaid` and `version` keys below 101 | # By default, consuming the theme as a gem leaves mermaid disabled; it is opt-in 102 | 103 | mermaid: 104 | # Version of mermaid library 105 | # Pick an available version from https://cdn.jsdelivr.net/npm/mermaid/ 106 | version: "9.1.6" 107 | # Put any additional configuration, such as setting the theme, in _includes/mermaid_config.js 108 | # See also docs/ui-components/code 109 | 110 | 111 | heading_anchors: true 112 | 113 | # Makes Aux links open in a new tab. Default is false 114 | aux_links_new_tab: false 115 | 116 | # Sort order for navigation links 117 | # nav_sort: case_insensitive # default, equivalent to nil 118 | nav_sort: case_sensitive # Capital letters sorted before lowercase 119 | 120 | # External navigation links 121 | nav_external_links: 122 | - title: Secure-Signer on github 123 | url: https://github.com/PufferFinance/secure-signer 124 | 125 | # Footer content 126 | # appears at the bottom of every page's main content 127 | 128 | # Back to top link 129 | back_to_top: true 130 | back_to_top_text: "Back to top" 131 | 132 | footer_content: "Distributed by an Apache 2.0 License." 133 | 134 | 135 | # Footer "Edit this page on GitHub" link text 136 | gh_edit_link: true # show or hide edit this page link 137 | gh_edit_link_text: "Edit this page on GitHub" 138 | gh_edit_repository: "https://github.com/PufferFinance/secure-signer" # the github URL for your repo 139 | gh_edit_branch: "main" # the branch that your docs is served from 140 | gh_edit_source: docs # the source that your files originate from 141 | gh_edit_view_mode: "tree" # "tree" or "edit" if you want the user to jump into the editor immediately 142 | 143 | # Color scheme currently only supports "dark", "light"/nil (default), or a custom scheme that you define 144 | color_scheme: nil 145 | 146 | callouts_level: quiet # or loud 147 | callouts: 148 | highlight: 149 | color: yellow 150 | important: 151 | title: Important 152 | color: blue 153 | new: 154 | title: New 155 | color: green 156 | note: 157 | title: Note 158 | color: purple 159 | warning: 160 | title: Warning 161 | color: red 162 | 163 | 164 | # Google Analytics Tracking (optional) 165 | # Supports a CSV of tracking ID strings (eg. "UA-1234567-89,G-1AB234CDE5") 166 | # Note: the main Just the Docs site does *not* use Google Analytics. 167 | # ga_tracking: UA-2709176-10,G-5FG1HLH3XQ 168 | # ga_tracking_anonymize_ip: true # Use GDPR compliant Google Analytics settings (true/nil by default) 169 | 170 | plugins: 171 | - jekyll-seo-tag 172 | 173 | kramdown: 174 | syntax_highlighter_opts: 175 | block: 176 | line_numbers: false 177 | 178 | compress_html: 179 | clippings: all 180 | comments: all 181 | endings: all 182 | startings: [] 183 | blanklines: false 184 | profile: false 185 | -------------------------------------------------------------------------------- /docs/_posts/2023-01-30-welcome-to-jekyll.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Welcome to Jekyll!" 4 | date: 2023-01-30 20:49:50 -0800 5 | categories: jekyll update 6 | --- 7 | You’ll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `jekyll serve`, which launches a web server and auto-regenerates your site when a file is updated. 8 | 9 | Jekyll requires blog post files to be named according to the following format: 10 | 11 | `YEAR-MONTH-DAY-title.MARKUP` 12 | 13 | Where `YEAR` is a four-digit number, `MONTH` and `DAY` are both two-digit numbers, and `MARKUP` is the file extension representing the format used in the file. After that, include the necessary front matter. Take a look at the source for this post to get an idea about how it works. 14 | 15 | Jekyll also offers powerful support for code snippets: 16 | 17 | {% highlight ruby %} 18 | def print_hi(name) 19 | puts "Hi, #{name}" 20 | end 21 | print_hi('Tom') 22 | #=> prints 'Hi, Tom' to STDOUT. 23 | {% endhighlight %} 24 | 25 | Check out the [Jekyll docs][jekyll-docs] for more info on how to get the most out of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub repo][jekyll-gh]. If you have questions, you can ask them on [Jekyll Talk][jekyll-talk]. 26 | 27 | [jekyll-docs]: https://jekyllrb.com/docs/home 28 | [jekyll-gh]: https://github.com/jekyll/jekyll 29 | [jekyll-talk]: https://talk.jekyllrb.com/ 30 | -------------------------------------------------------------------------------- /docs/assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/assets/.DS_Store -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/favicon.ico -------------------------------------------------------------------------------- /docs/getting_started.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Getting Started 4 | permalink: /getting-started/ 5 | nav_order: 2 6 | has_children: false 7 | --- 8 | # Setting up an SGX instance on Azure 9 | Secure-Signer has been developed and tested on Microsoft Azure. The goal of this guide is to expedite setting up your own Azure server. Stay tuned for more information on setting up Secure-Signer at home or on other cloud providers. 10 | 11 | ![azure1](../images/azure_1.png) 12 | 13 | Select `Create a resource`. 14 | 15 | --- 16 | 17 | ![azure2](../images/azure_2.png) 18 | 19 | Select `Create` to start setting up a VM. 20 | 21 | --- 22 | 23 | ![azure3](../images/azure_3.png) 24 | 25 | Ensure that the Image is set to `Ubuntu Server 20.04 LTS -x64 Gen2`. 26 | 27 | --- 28 | 29 | ![azure4](../images/azure_4.png) 30 | 31 | Select a VM size from the `DC-Series`. The smallest (and cheapest) VM with 4 GiB of RAM is sufficient to run Secure-Signer; however, it is recommended to use a larger VM if you plan to run the execution client and consensus client on this server. For this demo, we will use Secure-Signer as a truly remote-signing tool and choose the 4 GiB VM (our execution client and consensus client will be run externally on a different server). 32 | 33 | --- 34 | 35 | ![azure5](../images/azure_5.png) 36 | 37 | Set SSH credentials according to your needs. In this case we will generate a new SSH private key called `Secure-Signer-key` that we can use to sign into this server. 38 | 39 | --- 40 | 41 | ![azure6](../images/azure_6.png) 42 | 43 | Add any additional configurations that fit your needs. Then select `Review and Create`. Verify the OS image and VM size are correct. Then `Create` the VM. 44 | 45 | --- 46 | 47 | ![azure7](../images/azure_7.png) 48 | 49 | Securely store the SSH private key, for example in the `~/.ssh/` directory. 50 | 51 | --- 52 | 53 | ![azure8](../images/azure_8.png) 54 | 55 | Once deployment finishes, select `Go to resource`. 56 | 57 | --- 58 | 59 | ### Increasing VM disk space 60 | > This is optional if Secure-Signer is being used as a standalone remote-signing tool. If you plan to run the consensus client and execution client on this same VM, we recommend following these steps to increase the size of the disk. 61 | 62 | ![azure9](../images/azure_9.png) 63 | 64 | To adjust the disk size, first stop the VM. 65 | 66 | 67 | ![azure10](../images/azure_10.png) 68 | 69 | Select `Stop` 70 | 71 | --- 72 | 73 | ![azure11](../images/azure_11.png) 74 | 75 | Confirm `Yes` and wait for confirmation that is has been stopped. 76 | 77 | --- 78 | 79 | ![azure12](../images/azure_12.png) 80 | 81 | Navigate to the `Disks` tabs on under `Settings` on the left-hand side. 82 | 83 | --- 84 | 85 | ![azure13](../images/azure_13.png) 86 | 87 | Select your `Disk name` in this case `Secure-Signer_OsDisk_1_04e04e...` 88 | 89 | --- 90 | 91 | ![azure14](../images/azure_14.png) 92 | 93 | Select `Size + performance` under `Settings` on the left-hand side. 94 | 95 | --- 96 | 97 | ![azure15](../images/azure_15.png) 98 | 99 | Select a disk size and click `Save`. In this case, a 1024 GiB disk is sufficient to store the required state for the execution client. 100 | 101 | --- 102 | 103 | ![azure16](../images/azure_16.png) 104 | 105 | 106 | ![azure17](../images/azure_17.png) 107 | Navigate back to the overview page and `Start` the VM again. 108 | 109 | --- 110 | 111 | In a terminal run the following, substituting your server’s IP and the path to your SSH private key: 112 | > `ssh -i ss@XX.XX.XXX.XXX` 113 | 114 | ![azure18](../images/azure_18.png) 115 | 116 | Congratulations! At this point you are ready to proceed to the [next section](../installation) to install the dependencies for Secure-Signer. 117 | 118 | ### SSH Tunneling with the Secure-Signer server 119 | It’s possible to run Secure-Signer on the remote Azure server while running your consensus client and execution client locally. Assuming Secure-Signer is installed following the [these steps](../installation) and it is running on port 9003, you can SSH tunnel by running: 120 | `ssh -i -N -L 9003:localhost:9003 ss@XX.XX.XXX.XXX` -------------------------------------------------------------------------------- /docs/images/azure_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_1.png -------------------------------------------------------------------------------- /docs/images/azure_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_10.png -------------------------------------------------------------------------------- /docs/images/azure_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_11.png -------------------------------------------------------------------------------- /docs/images/azure_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_12.png -------------------------------------------------------------------------------- /docs/images/azure_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_13.png -------------------------------------------------------------------------------- /docs/images/azure_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_14.png -------------------------------------------------------------------------------- /docs/images/azure_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_15.png -------------------------------------------------------------------------------- /docs/images/azure_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_16.png -------------------------------------------------------------------------------- /docs/images/azure_17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_17.png -------------------------------------------------------------------------------- /docs/images/azure_18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_18.png -------------------------------------------------------------------------------- /docs/images/azure_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_2.png -------------------------------------------------------------------------------- /docs/images/azure_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_3.png -------------------------------------------------------------------------------- /docs/images/azure_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_4.png -------------------------------------------------------------------------------- /docs/images/azure_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_5.png -------------------------------------------------------------------------------- /docs/images/azure_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_6.png -------------------------------------------------------------------------------- /docs/images/azure_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_7.png -------------------------------------------------------------------------------- /docs/images/azure_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_8.png -------------------------------------------------------------------------------- /docs/images/azure_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/azure_9.png -------------------------------------------------------------------------------- /docs/images/upload_deposit_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PufferFinance/secure-signer/e016932eab9ea204c9fb20baec119a06dacbc600/docs/images/upload_deposit_data.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Feel free to add content and custom Front Matter to this file. 3 | # To modify the layout, see https://jekyllrb.com/docs/themes/#overriding-theme-defaults 4 | 5 | layout: default 6 | title: Home 7 | nav_order: 1 8 | description: "Secure-Signer is a remote-signing tool that interfaces with Ethereum consensus clients to prevents slashable offenses." 9 | permalink: / 10 | --- 11 | 12 | # Protect yourself from slashable offenses 13 | 14 | {: .fs-6 } 15 | 16 | Secure-Signer is a remote-signing tool that interfaces with Ethereum consensus clients to prevents slashable offenses. 17 | {: .fs-6 .fw-300 } 18 | 19 | [Get started now](getting-started){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 } 20 | [View it on GitHub](https://github.com/PufferFinance/secure-signer){: .btn .btn-blue .fs-5 .mb-4 .mb-md-0 } 21 | [API Documentation](https://pufferfinance.github.io/secure-signer-api-docs/redoc-static.html){: .btn .btn-green .fs-5 .mb-4 .mb-md-0 } 22 | 23 | --- 24 | 25 | ## Getting started 26 | 27 | Please follow the [guide](getting-started) to get started. 28 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Installation 4 | permalink: /installation/ 5 | nav_order: 3 6 | has_children: false 7 | --- 8 | 9 | ## Prereqs 10 | 11 | This installation guide assumes an SGX-enabled platform running Ubuntu20.04. First verify that your CPU supports SGX1 or SGX2: 12 | 13 |
14 | ```bash 15 | puffer@Puffer-Dev:~$ cpuid | grep SGX2 16 | SGX2 supported = false 17 | SGX2 supported = false 18 | puffer@Puffer-Dev:~$ cpuid | grep SGX1 19 | SGX1 supported = true 20 | SGX1 supported = true 21 | ``` 22 |
23 | 24 | Next verify your CPU support Flexible Launch Control [FLC](https://www.intel.com/content/www/us/en/developer/articles/technical/an-update-on-3rd-party-attestation.html) 25 | 26 |
27 | ```bash 28 | puffer@Puffer-Dev:~$ cpuid | grep SGX_LC 29 | SGX_LC: SGX launch config supported = true 30 | SGX_LC: SGX launch config supported = true 31 | ``` 32 |
33 | 34 | Verify the Linux Kernel version is at least 5.10: 35 | 36 |
37 | ```bash 38 | puffer@Puffer-Dev:~$ uname -r 39 | 5.15.0-1031-azure 40 | ``` 41 |
42 | 43 | If the Linux Kernel version is out of date, it can be updated by running: 44 |
45 | ```bash 46 | sudo apt install --install-recommends linux-generic-hwe-20.04 47 | ``` 48 |
49 | 50 | 51 | 52 | ## Installing Docker 53 | 54 | The Secure-Signer runtime has been containerized for ease of deployment. To pull the latest Secure-Signer container image, first install Docker: 55 | 56 |
57 | ```bash 58 | sudo apt install -y apt-transport-https ca-certificates curl software-properties-common 59 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 60 | sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable" 61 | apt-cache policy docker-ce 62 | sudo apt install -y docker-ce 63 | ``` 64 |
65 | 66 | 67 | Verify Docker was correctly installed: 68 |
69 | ```bash 70 | sudo systemctl status docker 71 | ``` 72 |
73 | 74 | Run the following commands to run Docker without requiring sudo: 75 | 76 |
77 | ```bash 78 | sudo groupadd docker 79 | sudo usermod -aG docker $USER 80 | ``` 81 |
82 | 83 | ## Installing SGX Drivers 84 | Secure-Signer requires Intel SGX drivers. The following will add Intel's packages to APT sources then install them. These packages are used when performing Remote Attestation with the Intel Attestation Service. 85 | 86 |
87 | ```bash 88 | echo 'deb [arch=amd64] https://download.01.org/intel-sgx/sgx_repo/ubuntu focal main' | sudo tee /etc/apt/sources.list.d/intel-sgx.list 89 | wget -qO - https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key | sudo apt-key add 90 | sudo apt update 91 | sudo apt install -y libsgx-epid libsgx-quote-ex libsgx-dcap-ql libsgx-urts libsgx-uae-service libsgx-dcap-default-qpl 92 | ``` 93 |
94 | 95 | Verify the Intel Architectural Enclave Service Manager is running: 96 |
97 | ```bash 98 | puffer@Puffer-Dev:~$ service aesmd status 99 | ● aesmd.service - Intel(R) Architectural Enclave Service Manager 100 | Loaded: loaded (/lib/systemd/system/aesmd.service; enabled; vendor preset: enabled) 101 | Active: active (running) since Mon 2023-01-16 21:08:30 UTC; 2 weeks 0 days ago 102 | Process: 19616 ExecStartPre=/opt/intel/sgx-aesm-service/aesm/linksgx.sh (code=exited, status=0/SUCCESS) 103 | Process: 19625 ExecStartPre=/bin/mkdir -p /var/run/aesmd/ (code=exited, status=0/SUCCESS) 104 | Process: 19626 ExecStartPre=/bin/chown -R aesmd:aesmd /var/run/aesmd/ (code=exited, status=0/SUCCESS) 105 | Process: 19627 ExecStartPre=/bin/chmod 0755 /var/run/aesmd/ (code=exited, status=0/SUCCESS) 106 | Process: 19628 ExecStartPre=/bin/chown -R aesmd:aesmd /var/opt/aesmd/ (code=exited, status=0/SUCCESS) 107 | Process: 19629 ExecStartPre=/bin/chmod 0750 /var/opt/aesmd/ (code=exited, status=0/SUCCESS) 108 | Process: 19630 ExecStart=/opt/intel/sgx-aesm-service/aesm/aesm_service (code=exited, status=0/SUCCESS) 109 | Main PID: 19631 (aesm_service) 110 | Tasks: 4 (limit: 9530) 111 | Memory: 14.6M 112 | CGroup: /system.slice/aesmd.service 113 | └─19631 /opt/intel/sgx-aesm-service/aesm/aesm_service 114 | 115 | Jan 16 21:08:30 Puffer-Dev systemd[1]: Starting Intel(R) Architectural Enclave Service Manager... 116 | Jan 16 21:08:30 Puffer-Dev aesm_service[19630]: aesm_service: warning: Turn to daemon. Use "--no-daemon" option to execute in foregroun> 117 | Jan 16 21:08:30 Puffer-Dev systemd[1]: Started Intel(R) Architectural Enclave Service Manager. 118 | Jan 16 21:08:30 Puffer-Dev aesm_service[19631]: The server sock is 0x5590971d3720 119 | Jan 21 05:03:23 Puffer-Dev aesm_service[19631]: [ADMIN]EPID Provisioning initiated 120 | Jan 21 05:03:24 Puffer-Dev aesm_service[19631]: The Request ID is 138bcd8af688471f885ae583772ce00b 121 | Jan 21 05:03:24 Puffer-Dev aesm_service[19631]: The Request ID is 80565f6a94be471aadd70d5d44d20e78 122 | Jan 21 05:03:25 Puffer-Dev aesm_service[19631]: [ADMIN]EPID Provisioning successful 123 | ``` 124 |
125 | 126 | Congrats, at this point, your CPU has the necessary prerequisites! Continue to the next section to learn how to run Secure-Signer. 127 | -------------------------------------------------------------------------------- /docs/run_ss.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Running Secure-Signer 4 | permalink: /running/ 5 | nav_order: 4 6 | has_children: true 7 | --- 8 | ## Running the Secure-Signer Container 9 | 10 | ### Prepare a volume 11 | By default, any data created within a Docker container is lost if the container is removed. Secure-Signer maintains our keys and slashing protection databases, so we want this data to persist should anything happen to the container. To do so, we will create a Docker volume called `Secure-Signer-Backup`. 12 |
13 | ```bash 14 | docker volume create Secure-Signer-Backup 15 | ``` 16 |
17 | 18 | We can verify the volume exists and inspect it with the following: 19 |
20 | ```bash 21 | puffer@Puffer-Dev:~$ docker volume ls 22 | DRIVER VOLUME NAME 23 | local Secure-Signer-Backup 24 | 25 | puffer@Puffer-Dev:~$ docker volume inspect Secure-Signer-Backup [0/1657] 26 | [ 27 | { 28 | "CreatedAt": "2023-02-01T00:17:30Z", 29 | "Driver": "local", 30 | "Labels": {}, 31 | "Mountpoint": "/var/lib/docker/volumes/Secure-Signer-Backup/_data", 32 | "Name": "Secure-Signer-Backup", 33 | "Options": {}, 34 | "Scope": "local" 35 | } 36 | ] 37 | ``` 38 |
39 | 40 | 41 | ### Start the container 42 | The `pufferfinance/secure_signer:latest` container image can be found [here](https://hub.docker.com/r/pufferfinance/secure_signer). The following command will start running a secure_signer container with the name `secure_signer_container`. Notice we are mounting the volume `Secure-Signer-Backup` to the `/Secure-Signer` enclave directory so any changes to Secure-Signer persist if the container is removed: 43 |
44 | ```bash 45 | docker run -itd --network host --mount type=volume,source=Secure-Signer-Backup,destination=/Secure-Signer -v /var/run/aesmd:/var/run/aesmd --device /dev/sgx/enclave --device /dev/sgx/provision --name secure_signer_container pufferfinance/secure_signer:latest 46 | ``` 47 |
48 | 49 | Verify that the container is running: 50 | 51 |
52 | ```bash 53 | puffer@Puffer-Dev:~$ docker container ls 54 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 55 | 3ce85f5a1d33 pufferfinance/secure_signer:latest "bash" 4 seconds ago Up 3 seconds secure_signer_container 56 | ``` 57 |
58 | 59 | ### Attach to the container 60 | Attach to the container using its name `secure_signer_container`. Notice the username is now `root`, indicating we are now inside the container. 61 |
62 | ```bash 63 | puffer@Puffer-Dev:~$ docker exec -it secure_signer_container bash 64 | root@Puffer-Dev:/Secure-Signer# 65 | ``` 66 |
67 | 68 | ### Run Secure-Signer 69 | The Secure-Signer enclave is built using the [Occlum LibOS](https://github.com/occlum/occlum). To start Secure-Signer we will use the `occlum run` command and point to the `secure-signer` binary stored within the Occlum enclave image and specify port `9001`. 70 |
71 | ```bash 72 | root@Puffer-Dev:/Secure-Signer# occlum run /bin/secure-signer 9001 73 | Starting SGX Secure-Signer: localhost:9001 74 | ``` 75 |
76 | 77 | The Secure-Signer HTTP server is now running! 78 | 79 | ### Run using Docker exec 80 |
81 | Alternatively, you can start Secure-Signer without attaching to the container by running the following: 82 | ```bash 83 | puffer@Puffer-Dev:~$ docker exec secure_signer_container /bin/bash -c "occlum run /bin/secure-signer 9001" 84 | Starting SGX Secure-Signer: localhost:9001 85 | ``` 86 |
87 | 88 | ### Next steps 89 | Most of the time your consensus client will interface with Secure-Signer, so it is not necessary to learn the full [API](https://pufferfinance.github.io/secure-signer-api-docs/redoc-static.html). However, in the [next section](client) we will learn how to interface with Secure-Signer to perform basic operations like importing and generating validator keys. 90 | -------------------------------------------------------------------------------- /lib/include/tee/common/error.h: -------------------------------------------------------------------------------- 1 | #ifndef REMOTE_ATTESTATION_LIB_INCLUDE_TEE_COMMON_ERROR_H_ 2 | #define REMOTE_ATTESTATION_LIB_INCLUDE_TEE_COMMON_ERROR_H_ 3 | 4 | /* clang-format off */ 5 | 6 | /* Define TeeErrorCode to include the error code 7 | * from both Intel SDK and ourself code */ 8 | typedef int TeeErrorCode; 9 | 10 | #define TEE_MK_ERROR(x) (0xFFFF0000&((x) << 16)) 11 | 12 | #define TEE_SUCCESS (0x00000000) 13 | 14 | #define TEE_ERROR_GENERIC TEE_MK_ERROR(0x0001) 15 | #define TEE_ERROR_PARAMETERS TEE_MK_ERROR(0x0002) 16 | #define TEE_ERROR_MALLOC TEE_MK_ERROR(0x0003) 17 | #define TEE_ERROR_ENCLAVE_NOTINITIALIZED TEE_MK_ERROR(0x0004) 18 | #define TEE_ERROR_REPORT_DATA_SIZE TEE_MK_ERROR(0x0005) 19 | #define TEE_ERROR_PARSE_CONFIGURATIONS TEE_MK_ERROR(0x0006) 20 | #define TEE_ERROR_PARSE_COMMANDLINE TEE_MK_ERROR(0x0007) 21 | 22 | #define TEE_ERROR_FILE_OPEN TEE_MK_ERROR(0x0101) 23 | #define TEE_ERROR_FILE_READ TEE_MK_ERROR(0x0102) 24 | #define TEE_ERROR_FILE_WRITE TEE_MK_ERROR(0x0103) 25 | 26 | #define TEE_ERROR_CONF_LOAD TEE_MK_ERROR(0x0201) 27 | #define TEE_ERROR_CONF_NOTEXIST TEE_MK_ERROR(0x0202) 28 | 29 | #define TEE_ERROR_IAS_CLIENT_INIT TEE_MK_ERROR(0x0501) 30 | #define TEE_ERROR_IAS_CLIENT_CONNECT TEE_MK_ERROR(0x0502) 31 | #define TEE_ERROR_IAS_CLIENT_GETSIGRL TEE_MK_ERROR(0x0503) 32 | #define TEE_ERROR_IAS_CLIENT_GETREPORT TEE_MK_ERROR(0x0504) 33 | #define TEE_ERROR_IAS_CLIENT_UNESCAPE TEE_MK_ERROR(0x0505) 34 | #define TEE_ERROR_IAS_LOAD_CACHED_REPORT TEE_MK_ERROR(0x0506) 35 | 36 | #define TEE_ERROR_SDK_UNEXPECTED TEE_MK_ERROR(0x0FFF) 37 | 38 | #define TEE_ERROR_CODE(rc) (rc) 39 | #define TEE_ERROR_MERGE(ecallcode, retcode) ((ecallcode) | (retcode)) 40 | 41 | /* clang-format on */ 42 | 43 | #endif // REMOTE_ATTESTATION_LIB_INCLUDE_TEE_COMMON_ERROR_H_ 44 | -------------------------------------------------------------------------------- /lib/include/tee/common/log.h: -------------------------------------------------------------------------------- 1 | #ifndef REMOTE_ATTESTATION_LIB_INCLUDE_TEE_COMMON_LOG_H_ 2 | #define REMOTE_ATTESTATION_LIB_INCLUDE_TEE_COMMON_LOG_H_ 3 | 4 | #include 5 | 6 | extern "C" int printf(const char* fmt, ...); 7 | 8 | #ifdef DEBUG 9 | #define TEE_LOG_DEBUG(fmt, ...) \ 10 | printf("[DEBUG][%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__) 11 | #define TEE_LOG_BUFFER(name, ptr, size) \ 12 | do { \ 13 | const uint8_t* buffer = reinterpret_cast(ptr); \ 14 | int len = static_cast((size)); \ 15 | printf("Buffer %s[%p], length: %d(0x%x)\n", (name), buffer, len, len); \ 16 | for (int i = 0; i < len; i++) { \ 17 | if (i && (0 == i % 16)) printf("\n"); \ 18 | printf("%02x ", buffer[i]); \ 19 | } \ 20 | printf("\n"); \ 21 | } while (0) 22 | 23 | #else 24 | #define TEE_LOG_DEBUG(fmt, ...) 25 | #define TEE_LOG_BUFFER(name, ptr, size) 26 | #endif 27 | 28 | #define TEE_LOG_INFO(fmt, ...) \ 29 | printf("[INFO][%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__) 30 | #define TEE_LOG_WARN(fmt, ...) \ 31 | printf("[WARN][%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__) 32 | #define TEE_LOG_ERROR(fmt, ...) \ 33 | printf("[ERROR][%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__) 34 | 35 | #define TEE_LOG_ERROR_TRACE() TEE_LOG_ERROR("[Function] %s", __FUNCTION__) 36 | 37 | #define TEE_CHECK_RETURN(r) \ 38 | do { \ 39 | int ret = (r); \ 40 | if (ret != 0) { \ 41 | TEE_LOG_ERROR_TRACE(); \ 42 | return ret; \ 43 | } \ 44 | } while (0) 45 | 46 | #endif // REMOTE_ATTESTATION_LIB_INCLUDE_TEE_COMMON_LOG_H_ 47 | -------------------------------------------------------------------------------- /lib/include/tee/common/type.h: -------------------------------------------------------------------------------- 1 | #ifndef REMOTE_ATTESTATION_LIB_INCLUDE_TEE_COMMON_TYPE_H_ 2 | #define REMOTE_ATTESTATION_LIB_INCLUDE_TEE_COMMON_TYPE_H_ 3 | 4 | #include 5 | 6 | #include "./sgx_quote.h" 7 | #include "./sgx_report.h" 8 | #include "./sgx_tseal.h" 9 | 10 | #define RCAST(t, v) reinterpret_cast((v)) 11 | #define SCAST(t, v) static_cast((v)) 12 | #define CCAST(t, v) const_cast((v)) 13 | #define RCCAST(t, v) reinterpret_cast(const_cast((v))) 14 | 15 | #define TEE_UNREFERENCED_PARAMETER(p) \ 16 | do { \ 17 | static_cast((p)); \ 18 | } while (0) 19 | 20 | /** 21 | * report_data Input report data which will be included in quote data. 22 | * The first 32 bytes should be the SHA256 hash value of 23 | * the public key which is used in the RA work flow. 24 | * nonce Nonce value to avoid replay attack. All zero to ignore it. 25 | * spid The service provider ID, please use you real SPID, 26 | * otherwise, IAS will return bad request when quote report. 27 | * quote_type Maybe SGX_UNLINKABLE_SIGNATURE or SGX_LINKABLE_SIGNATURE 28 | * quote type. 29 | * sigrl_ptr The SigRL data buffer 30 | * sigrl_len The total length of SigRL data 31 | * quote Output quote structure data in binary format. 32 | */ 33 | typedef struct { 34 | sgx_report_data_t report_data; // input 35 | sgx_quote_sign_type_t quote_type; // input 36 | sgx_spid_t spid; // input 37 | sgx_quote_nonce_t nonce; // input 38 | const uint8_t* sigrl_ptr; // input (optional) 39 | uint32_t sigrl_len; // input (optional) 40 | uint32_t quote_buf_len; // input 41 | union { 42 | uint8_t* as_buf; 43 | sgx_quote_t* as_quote; 44 | } quote; // output 45 | } EnclaveQuoteArgs; 46 | 47 | /** 48 | * endpoint https://xxx.xxx.xxx.xxx: for Intel Attestation Service. 49 | * cert Service provider certificate file path. 50 | * key Service provider private key file path. 51 | * accesskey Service provider access key, see also here: 52 | * https://api.portal.trustedservices.intel.com/EPID-attestation 53 | */ 54 | typedef struct { 55 | std::string endpoint; 56 | std::string cert; 57 | std::string key; 58 | std::string accesskey; 59 | } RaIasServerCfg; 60 | 61 | #endif // REMOTE_ATTESTATION_LIB_INCLUDE_TEE_COMMON_TYPE_H_ 62 | -------------------------------------------------------------------------------- /lib/include/tee/ra_conf_api.h: -------------------------------------------------------------------------------- 1 | #ifndef REMOTE_ATTESTATION_LIB_INCLUDE_TEE_RA_CONF_API_H_ 2 | #define REMOTE_ATTESTATION_LIB_INCLUDE_TEE_RA_CONF_API_H_ 3 | 4 | #include 5 | #include 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | /// Get the string type option in configuration file 12 | std::string TeeConfGetStr(const std::string& conf_file, const char* name, 13 | const std::string& default_value = ""); 14 | 15 | /// If the value of option is filename in the configuration file, 16 | /// use the function to read the file content and return it as string. 17 | std::string TeeConfGetFileStr(const std::string& conf_file, const char* name, 18 | const std::string& default_value = ""); 19 | 20 | /// Get the array type option in configuration file 21 | TeeErrorCode TeeConfGetStrArray(const std::string& conf_file, const char* name, 22 | std::vector* values); 23 | 24 | /// Get the integer type option in configuration file 25 | TeeErrorCode TeeConfGetInt(const std::string& conf_file, const char* name, 26 | int* value); 27 | 28 | #ifdef __cplusplus 29 | } 30 | #endif 31 | 32 | #endif // REMOTE_ATTESTATION_LIB_INCLUDE_TEE_RA_CONF_API_H_ 33 | -------------------------------------------------------------------------------- /lib/include/tee/ra_ias.h: -------------------------------------------------------------------------------- 1 | #ifndef REMOTE_ATTESTATION_LIB_INCLUDE_RA_IAS_H_ 2 | #define REMOTE_ATTESTATION_LIB_INCLUDE_RA_IAS_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "./sgx_uae_epid.h" 8 | #include "./sgx_urts.h" 9 | #include "./sgx_utils.h" 10 | 11 | #include "curl/curl.h" 12 | 13 | #include "tee/common/error.h" 14 | #include "tee/common/type.h" 15 | 16 | #define IAS_REPORT_CLASS_MEMBER(x) \ 17 | public: \ 18 | std::string& x() { \ 19 | return x##_; \ 20 | } \ 21 | const std::string& x() const { \ 22 | return x##_; \ 23 | } \ 24 | std::string* mutable_##x() { \ 25 | return &x##_; \ 26 | } \ 27 | void set_##x(const std::string& value) { \ 28 | x##_ = value; \ 29 | } \ 30 | void set_##x(const char* value) { \ 31 | x##_ = value; \ 32 | } \ 33 | void set_##x(const char* value, size_t size) { \ 34 | x##_ = value; \ 35 | } \ 36 | \ 37 | private: \ 38 | std::string x##_ 39 | 40 | namespace ra { 41 | namespace occlum { 42 | 43 | /// Data structure to hold the IAS sigrl API response 44 | typedef struct { 45 | std::string b64_sigrl; 46 | } RaIasSigrl; 47 | 48 | /// Data structure to hold the IAS sigrl API response 49 | /// Use this class to simulate the protobuf class 50 | /// don't need to introduce the protobuf dependency 51 | class RaIasReport { 52 | IAS_REPORT_CLASS_MEMBER(b64_signature); 53 | IAS_REPORT_CLASS_MEMBER(signing_cert); 54 | IAS_REPORT_CLASS_MEMBER(advisory_url); 55 | IAS_REPORT_CLASS_MEMBER(advisory_ids); 56 | IAS_REPORT_CLASS_MEMBER(response_body); 57 | IAS_REPORT_CLASS_MEMBER(epid_pseudonym); 58 | IAS_REPORT_CLASS_MEMBER(quote_status); 59 | IAS_REPORT_CLASS_MEMBER(b16_platform_info_blob); 60 | IAS_REPORT_CLASS_MEMBER(b64_quote_body); 61 | }; 62 | 63 | /// HTTPS client for connecting to IAS 64 | class RaIasClient { 65 | public: 66 | explicit RaIasClient(const RaIasServerCfg& ias_server); 67 | ~RaIasClient(); 68 | 69 | /// api: /sigrl/ 70 | TeeErrorCode GetSigRL(const sgx_epid_group_id_t& gid, std::string* sigrl); 71 | 72 | /// api: /report 73 | TeeErrorCode FetchReport(const std::string& quote, RaIasReport* ias_report); 74 | 75 | private: 76 | void InitIasConnection(const std::string& url); 77 | 78 | CURL* curl_ = NULL; 79 | curl_slist* headers_ = NULL; 80 | std::string server_endpoint_; 81 | 82 | static std::mutex init_mutex_; 83 | }; 84 | 85 | } // namespace occlum 86 | } // namespace ra 87 | 88 | #endif // REMOTE_ATTESTATION_LIB_INCLUDE_RA_IAS_H_ 89 | -------------------------------------------------------------------------------- /lib/include/tee/ra_json.h: -------------------------------------------------------------------------------- 1 | #ifndef REMOTE_ATTESTATION_LIB_INCLUDE_TEE_RA_JSON_H_ 2 | #define REMOTE_ATTESTATION_LIB_INCLUDE_TEE_RA_JSON_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "rapidjson/document.h" 10 | 11 | #include "tee/common/error.h" 12 | #include "tee/common/log.h" 13 | #include "tee/common/type.h" 14 | 15 | namespace ra { 16 | namespace occlum { 17 | 18 | typedef std::shared_ptr TeeJsonDocPtr; 19 | typedef std::map TeeJsonConfMap; 20 | 21 | class JsonConfig { 22 | public: 23 | // Gets the singleton Unit Test object. 24 | static JsonConfig* GetInstance(); 25 | 26 | // To support both rapidjson::Document and rapidjson::Value 27 | template 28 | static bool CheckString(const T& conf, const char* name); 29 | template 30 | static bool CheckArray(const T& conf, const char* name); 31 | template 32 | static bool CheckInt(const T& conf, const char* name); 33 | template 34 | static bool CheckObj(const T& conf, const char* name); 35 | template 36 | static std::string GetStr(const T& conf, const char* name, 37 | const std::string& default_val = ""); 38 | template 39 | static TeeErrorCode GetStrArray(const T& conf, const char* name, 40 | std::vector* values); 41 | template 42 | static TeeErrorCode GetInt(const T& conf, const char* name, int* value); 43 | 44 | // Load configuration files and then parse and get value(s) 45 | std::string ConfGetStr(const std::string& conf_file, const char* name, 46 | const std::string& default_val = ""); 47 | std::string ConfGetFileStr(const std::string& conf_file, const char* name, 48 | const std::string& default_val = ""); 49 | TeeErrorCode ConfGetStrArray(const std::string& conf_file, const char* name, 50 | std::vector* values); 51 | TeeErrorCode ConfGetInt(const std::string& conf_file, const char* name, 52 | int* value); 53 | 54 | private: 55 | // Hide construction functions 56 | JsonConfig() {} 57 | JsonConfig(const JsonConfig&); 58 | void operator=(JsonConfig const&); 59 | 60 | std::string ReadStringFile(const std::string& filename); 61 | bool ConfigFileExists(const std::string& filename); 62 | 63 | std::string GetConfigFilename(const std::string& filename); 64 | TeeErrorCode LoadConfiguration(const std::string& filename); 65 | 66 | TeeJsonConfMap cfgs_; 67 | }; 68 | 69 | } // namespace occlum 70 | } // namespace ra 71 | 72 | #endif // REMOTE_ATTESTATION_LIB_INCLUDE_TEE_RA_JSON_H_ 73 | -------------------------------------------------------------------------------- /lib/include/tee/ra_quote.h: -------------------------------------------------------------------------------- 1 | #ifndef REMOTE_ATTESTATION_LIB_INCLUDE_RA_QUOTE_H_ 2 | #define REMOTE_ATTESTATION_LIB_INCLUDE_RA_QUOTE_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "./sgx_quote.h" 8 | #include "./sgx_report.h" 9 | #include "./sgx_tseal.h" 10 | #include "./sgx_urts.h" 11 | 12 | #include "tee/common/error.h" 13 | #include "tee/common/type.h" 14 | 15 | #include "tee/ra_ias.h" 16 | 17 | namespace ra { 18 | namespace occlum { 19 | 20 | class RaEnclaveQuote { 21 | public: 22 | // The methods that warp the ioctl device interfaces 23 | static TeeErrorCode SgxDeviceInitQuote(sgx_epid_group_id_t* gid); 24 | static TeeErrorCode SgxDeviceGetQuote(EnclaveQuoteArgs* quote_args); 25 | 26 | // The methods which are higher wrapper of quote and IasClient together. 27 | TeeErrorCode GetEnclaveQuoteB64(const RaIasServerCfg& ias_server, 28 | const std::string& spid, 29 | const sgx_report_data_t& report_data, 30 | std::string* quote_b64); 31 | TeeErrorCode GetEnclaveIasReport(const RaIasServerCfg& ias_server, 32 | const std::string& spid, 33 | const sgx_report_data_t& report_data, 34 | RaIasReport* ias_report); 35 | 36 | private: 37 | uint8_t Hex2Dec(const char hex); 38 | TeeErrorCode GetSpidFromHexStr(const std::string& spid_str); 39 | TeeErrorCode GetIasSigRL(const RaIasServerCfg& ias_server); 40 | TeeErrorCode GetEnclaveQuote(const RaIasServerCfg& ias_server, 41 | const std::string& spid, 42 | const sgx_report_data_t& report_data); 43 | 44 | std::vector quote_buf_; 45 | EnclaveQuoteArgs quote_args_; 46 | }; 47 | 48 | } // namespace occlum 49 | } // namespace ra 50 | 51 | #endif // REMOTE_ATTESTATION_LIB_INCLUDE_RA_QUOTE_H_ 52 | -------------------------------------------------------------------------------- /lib/include/tee/ra_quote_api.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief This header file provides the C APIs to use /dev/sgx device directly. 3 | * 4 | * As a C++ language developer, you can also use the C++ classes declared 5 | * in ra_quote.h file. The C++ classes provide more convenient metheds. 6 | */ 7 | 8 | #ifndef REMOTE_ATTESTATION_LIB_INCLUDE_RA_QUOTE_API_H_ 9 | #define REMOTE_ATTESTATION_LIB_INCLUDE_RA_QUOTE_API_H_ 10 | 11 | #include 12 | 13 | #include "tee/common/error.h" 14 | #include "tee/common/type.h" 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | /** 21 | * @brief Initialization for getting enclave quote 22 | * @param gid return GID for getting SigRL from attestation server 23 | * @return Function run successfully or failed 24 | * @retval 0 on success 25 | * @retval Others when failed 26 | */ 27 | TeeErrorCode InitializeQuote(sgx_epid_group_id_t* gid); 28 | 29 | /** 30 | * @brief Get enclave quote for remote attestation 31 | * @param quote_args All the input parameters required by get quote function. 32 | * The output buffer is also in this structure. Please 33 | * refer to the description of it in type.h header file. 34 | * @return Function run successfully or failed 35 | * @retval 0 on success 36 | * @retval Others when failed 37 | */ 38 | TeeErrorCode GetQuote(EnclaveQuoteArgs* quote_args); 39 | 40 | #ifdef __cplusplus 41 | } 42 | #endif 43 | 44 | #endif // REMOTE_ATTESTATION_LIB_INCLUDE_RA_MANAGER_API_H_ 45 | -------------------------------------------------------------------------------- /resources/keygen/paths/bls_keygen.yaml: -------------------------------------------------------------------------------- 1 | post: 2 | operationId: BLS_KEYGEN 3 | summary: Generate BLS Key. 4 | description: | 5 | Generates a fresh BLS keypair within Secure-Signer. Returns the hex-encoded BLS public key for the private BLS key that was generated in Secure-Signer. The 48-Byte compressed BLS public key is committed to in a remote attestation report, and the resulting remote attestation evidence is also returned. 6 | security: 7 | - bearerAuth: [] 8 | tags: 9 | - BLS Keygen 10 | responses: 11 | "200": 12 | description: Success response 13 | content: 14 | application/json: 15 | schema: 16 | title: KeyGenResponse 17 | type: object 18 | required: [pk_hex, evidence] 19 | properties: 20 | pk_hex: 21 | $ref: "../schemas.yaml#/components/schemas/Pubkey" 22 | evidence: 23 | $ref: "../../signing/schemas.yaml#/components/schemas/AttestationEvidence" 24 | "400": 25 | $ref: "../schemas.yaml#/components/responses/BadRequest" 26 | "401": 27 | $ref: "../schemas.yaml#/components/responses/Unauthorized" 28 | "403": 29 | $ref: "../schemas.yaml#/components/responses/Forbidden" 30 | "500": 31 | $ref: "../schemas.yaml#/components/responses/InternalError" -------------------------------------------------------------------------------- /resources/keygen/paths/secp256k1_keygen.yaml: -------------------------------------------------------------------------------- 1 | post: 2 | operationId: ETH_KEYGEN 3 | summary: Generate ETH Key. 4 | description: | 5 | Generates a fresh ETH (SECP256K1) keypair within Secure-Signer. Returns the hex-encoded ETH public key for the private ETH key that was generated in Secure-Signer. The 33-Byte compressed ETH public key is committed to in a remote attestation report, and the resulting remote attestation evidence is also returned. The `evidence` should be verified before trusting the ETH key, which is used to encrypt a BLS keystore password during importing. 6 | security: 7 | - bearerAuth: [] 8 | tags: 9 | - ETH Keygen 10 | responses: 11 | "200": 12 | description: Success response 13 | content: 14 | application/json: 15 | schema: 16 | title: KeyGenResponse 17 | type: object 18 | required: [pk_hex, evidence] 19 | properties: 20 | pk_hex: 21 | $ref: "../schemas.yaml#/components/schemas/EthPubkey" 22 | evidence: 23 | $ref: "../../signing/schemas.yaml#/components/schemas/AttestationEvidence" 24 | "400": 25 | $ref: "../schemas.yaml#/components/responses/BadRequest" 26 | "401": 27 | $ref: "../schemas.yaml#/components/responses/Unauthorized" 28 | "403": 29 | $ref: "../schemas.yaml#/components/responses/Forbidden" 30 | "500": 31 | $ref: "../schemas.yaml#/components/responses/InternalError" 32 | 33 | get: 34 | operationId: ETH_KEYGEN_LIST 35 | summary: List Generated ETH Keys. 36 | description: | 37 | Returns a list of hex-encoded ETH (SECP256K1) public keys for the private ETH keys that were generated in Secure-Signer. 38 | security: 39 | - bearerAuth: [] 40 | tags: 41 | - ETH Keygen 42 | responses: 43 | "200": 44 | description: Success response 45 | content: 46 | application/json: 47 | schema: 48 | title: ListKeysResponse 49 | type: object 50 | required: [data] 51 | properties: 52 | data: 53 | type: array 54 | items: 55 | type: object 56 | required: [pubkey] 57 | properties: 58 | pubkey: 59 | $ref: "../schemas.yaml#/components/schemas/EthPubkey" 60 | "401": 61 | $ref: "../schemas.yaml#/components/responses/Unauthorized" 62 | "403": 63 | $ref: "../schemas.yaml#/components/responses/Forbidden" 64 | "500": 65 | $ref: "../schemas.yaml#/components/responses/InternalError" -------------------------------------------------------------------------------- /resources/keygen/schemas.yaml: -------------------------------------------------------------------------------- 1 | components: 2 | securitySchemes: 3 | bearerAuth: 4 | type: http 5 | scheme: bearer 6 | bearerFormat: JWT 7 | 8 | schemas: 9 | Pubkey: 10 | type: string 11 | pattern: "^0x[a-fA-F0-9]{96}$" 12 | description: | 13 | The validator's BLS public key, uniquely identifying them. _48-bytes, hex encoded with 0x prefix, case insensitive._ 14 | example: "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a" 15 | 16 | EthPubkey: 17 | type: string 18 | pattern: "^0x[a-fA-F0-9]{96}$" 19 | description: | 20 | A SECP256K1 ETH public key. _33-bytes, hex encoded with 0x prefix, case insensitive._ 21 | example: "0x025f163d5de3470d4b3bf9f739d661a88aeccc257fc4f4735d8c1a905baf5e813b" 22 | 23 | Keystore: 24 | type: string 25 | description: | 26 | JSON serialized representation of a single keystore in EIP-2335: BLS12-381 Keystore format. 27 | example: '{"version":4,"uuid":"9f75a3fa-1e5a-49f9-be3d-f5a19779c6fa","path":"m/12381/3600/0/0/0","pubkey":"0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a","crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":262144,"prf":"hmac-sha256","salt":"8ff8f22ef522a40f99c6ce07fdcfc1db489d54dfbc6ec35613edf5d836fa1407"},"message":""},"checksum":{"function":"sha256","params":{},"message":"9678a69833d2576e3461dd5fa80f6ac73935ae30d69d07659a709b3cd3eddbe3"},"cipher":{"function":"aes-128-ctr","params":{"iv":"31b69f0ac97261e44141b26aa0da693f"},"message":"e8228bafec4fcbaca3b827e586daad381d53339155b034e5eaae676b715ab05e"}}}' 28 | 29 | SlashingProtectionData: 30 | type: string 31 | description: | 32 | JSON serialized representation of the slash protection data in format defined in EIP-3076: Slashing Protection Interchange Format. 33 | example: '{"metadata":{"interchange_format_version":"5","genesis_validators_root":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"},"data":[{"pubkey":"0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a","signed_blocks":[],"signed_attestations":[]}]}' 34 | 35 | ErrorResponse: 36 | type: object 37 | required: [message] 38 | properties: 39 | message: 40 | description: "Detailed error message" 41 | type: string 42 | example: "description of the error that occurred" 43 | 44 | responses: 45 | BadRequest: 46 | description: "Bad request. Request was malformed and could not be processed" 47 | content: 48 | application/json: 49 | schema: 50 | $ref: "#/components/schemas/ErrorResponse" 51 | 52 | Unauthorized: 53 | description: "Unauthorized, no token is found" 54 | content: 55 | application/json: 56 | schema: 57 | $ref: "#/components/schemas/ErrorResponse" 58 | 59 | Forbidden: 60 | description: "Forbidden, a token is found but is invalid" 61 | content: 62 | application/json: 63 | schema: 64 | $ref: "#/components/schemas/ErrorResponse" 65 | 66 | InternalError: 67 | description: "Internal server error. The server encountered an unexpected error indicative of 68 | a serious fault in the system, or a bug." 69 | content: 70 | application/json: 71 | schema: 72 | $ref: "#/components/schemas/ErrorResponse" -------------------------------------------------------------------------------- /resources/keymanager/paths/keystores.yaml: -------------------------------------------------------------------------------- 1 | get: 2 | operationId: KEYMANAGER_LIST 3 | summary: List Keys. 4 | description: | 5 | Returns a list of hex-encoded BLS public keys for the private BLS keys that are in Secure-Signer's custody. 6 | security: 7 | - bearerAuth: [] 8 | tags: 9 | - Keymanager 10 | responses: 11 | "200": 12 | description: Success response 13 | content: 14 | application/json: 15 | schema: 16 | title: ListKeysResponse 17 | type: object 18 | required: [data] 19 | properties: 20 | data: 21 | type: array 22 | items: 23 | type: object 24 | required: [pubkey] 25 | properties: 26 | pubkey: 27 | $ref: "../schemas.yaml#/components/schemas/Pubkey" 28 | "401": 29 | $ref: "../schemas.yaml#/components/responses/Unauthorized" 30 | "403": 31 | $ref: "../schemas.yaml#/components/responses/Forbidden" 32 | "500": 33 | $ref: "../schemas.yaml#/components/responses/InternalError" 34 | 35 | post: 36 | operationId: KEYMANAGER_IMPORT 37 | summary: Import Keystore. 38 | description: | 39 | Import a single BLS keystore conforming to version 4 of [EIP-2335: BLS12-381 Keystore](https://eips.ethlibrary.io/eip-2335.html). The keystore's password is encrypted via ECIES with the `encrypting_pk_hex` ETH SECP256K1 public key that is safeguarded within the enclave, producing `ct_password`. It is expected that the user first have Secure-Signer perform remote attestation with `encrypting_pk_hex` to gain trust. 40 | 41 | The `slashing_protection` follows the [EIP-3076: Slashing Protection Interchange Format](https://eips.ethlibrary.io/eip-3076.html), which may store the signing histories of multiple BLS keys. Currently Secure-Signer only supports importing a single key at a time. This limitation means only the 0th indexed key will be imported: `slashing_protection["data"][0]`. If 42 | this `slashing_protection["data"][0]["pubkey"]` does not match the pubkey decrypted from the keystore, the import will fail. If no `slashing_protection` is supplied, an empty one will be initialized starting from `slot=0`, `source_epoch=0`, and `target_epoch=0`. 43 | security: 44 | - bearerAuth: [] 45 | tags: 46 | - Keymanager 47 | requestBody: 48 | content: 49 | application/json: 50 | schema: 51 | type: object 52 | required: [keystore, ct_password, encrypting_pk_hex] 53 | properties: 54 | keystore: 55 | description: JSON-encoded keystore file (currently only V3 supported). 56 | $ref: "../schemas.yaml#/components/schemas/Keystore" 57 | ct_password: 58 | type: string 59 | description: ECIES encrypted password to unlock the keystore. 60 | example: "0x045f5ecda8ad98023b621fa216a11fa541fbb7bf98795d9af06ee1346a6cd7675c1b8a0b2a65db50c974b43609a4401533ce2b494ebb4a4dd26bea9e9172ae2bb1aea121f14577335ae970" 61 | encrypting_pk_hex: 62 | type: string 63 | description: Hex-encoded ETH SECP256K1 public key (33B) used to encrypt the keystore password via ECIES. 64 | example: "0x02199120115ff926bbeeedf58fe46985df3168b263f47bbcc91ddbf18402804f27" 65 | slashing_protection: 66 | type: string 67 | $ref: "../schemas.yaml#/components/schemas/SlashingProtectionData" 68 | responses: 69 | "200": 70 | description: Success response 71 | content: 72 | application/json: 73 | schema: 74 | title: ImportKeystoresResponse 75 | type: object 76 | required: [data] 77 | properties: 78 | data: 79 | type: array 80 | description: Status result of each `request.keystores` with same length and order of `request.keystores` 81 | items: 82 | type: object 83 | required: [status] 84 | properties: 85 | status: 86 | type: string 87 | description: | 88 | - imported: Keystore successfully decrypted and imported to keymanager permanent storage 89 | - duplicate: Keystore's pubkey is already known to the keymanager 90 | - error: Any other status different to the above: decrypting error, I/O errors, etc. 91 | enum: 92 | - imported 93 | - duplicate 94 | - error 95 | example: imported 96 | message: 97 | type: string 98 | description: error message if status == error 99 | "400": 100 | $ref: "../schemas.yaml#/components/responses/BadRequest" 101 | "401": 102 | $ref: "../schemas.yaml#/components/responses/Unauthorized" 103 | "403": 104 | $ref: "../schemas.yaml#/components/responses/Forbidden" 105 | "500": 106 | $ref: "../schemas.yaml#/components/responses/InternalError" -------------------------------------------------------------------------------- /resources/keymanager/schemas.yaml: -------------------------------------------------------------------------------- 1 | components: 2 | securitySchemes: 3 | bearerAuth: 4 | type: http 5 | scheme: bearer 6 | bearerFormat: JWT 7 | 8 | schemas: 9 | Pubkey: 10 | type: string 11 | pattern: "^0x[a-fA-F0-9]{96}$" 12 | description: | 13 | The validator's BLS public key, uniquely identifying them. _48-bytes, hex encoded with 0x prefix, case insensitive._ 14 | example: "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a" 15 | 16 | Keystore: 17 | type: string 18 | description: | 19 | JSON serialized representation of a single keystore in EIP-2335: BLS12-381 Keystore format. 20 | example: '{ 21 | "crypto": { 22 | "kdf": { 23 | "function": "scrypt", 24 | "params": { 25 | "dklen": 32, 26 | "n": 262144, 27 | "p": 1, 28 | "r": 8, 29 | "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" 30 | }, 31 | "message": "" 32 | }, 33 | "checksum": { 34 | "function": "sha256", 35 | "params": {}, 36 | "message": "d2217fe5f3e9a1e34581ef8a78f7c9928e436d36dacc5e846690a5581e8ea484" 37 | }, 38 | "cipher": { 39 | "function": "aes-128-ctr", 40 | "params": { 41 | "iv": "264daa3f303d7259501c93d997d84fe6" 42 | }, 43 | "message": "06ae90d55fe0a6e9c5c3bc5b170827b2e5cce3929ed3f116c2811e6366dfe20f" 44 | } 45 | }, 46 | "description": "This is a test keystore that uses scrypt to secure the secret.", 47 | "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", 48 | "path": "m/12381/60/3141592653/589793238", 49 | "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", 50 | "version": 4 51 | }' 52 | 53 | SlashingProtectionData: 54 | type: string 55 | description: | 56 | JSON serialized representation of the slash protection data in format defined in EIP-3076: Slashing Protection Interchange Format. 57 | example: '{"metadata":{"interchange_format_version":"5","genesis_validators_root":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"},"data":[{"pubkey":"0x9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07","signed_blocks":[],"signed_attestations":[]}]}' 58 | 59 | ErrorResponse: 60 | type: object 61 | required: [message] 62 | properties: 63 | message: 64 | description: "Detailed error message" 65 | type: string 66 | example: "description of the error that occurred" 67 | 68 | responses: 69 | BadRequest: 70 | description: "Bad request. Request was malformed and could not be processed" 71 | content: 72 | application/json: 73 | schema: 74 | $ref: "#/components/schemas/ErrorResponse" 75 | 76 | Unauthorized: 77 | description: "Unauthorized, no token is found" 78 | content: 79 | application/json: 80 | schema: 81 | $ref: "#/components/schemas/ErrorResponse" 82 | 83 | Forbidden: 84 | description: "Forbidden, a token is found but is invalid" 85 | content: 86 | application/json: 87 | schema: 88 | $ref: "#/components/schemas/ErrorResponse" 89 | 90 | InternalError: 91 | description: "Internal server error. The server encountered an unexpected error indicative of 92 | a serious fault in the system, or a bug." 93 | content: 94 | application/json: 95 | schema: 96 | $ref: "#/components/schemas/ErrorResponse" -------------------------------------------------------------------------------- /resources/secure-signer.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: 'Puffer Secure-Signer Api' 4 | description: 'Sign Eth2 Artifacts' 5 | version: '0.1' 6 | license: 7 | name: 'Apache 2.0' 8 | url: 'http://www.apache.org/licenses/LICENSE-2.0.html' 9 | 10 | servers: 11 | - url: / 12 | - url: http://localhost:9000/ 13 | 14 | paths: 15 | /api/v1/eth2/sign/{identifier}: 16 | $ref: './signing/paths/sign.yaml' 17 | /api/v1/eth2/sign/deposit/{identifier}: 18 | $ref: './signing/paths/deposit.yaml' 19 | /upcheck: 20 | $ref: './signing/paths/upcheck.yaml' 21 | /eth/v1/keygen/bls: 22 | $ref: './keygen/paths/bls_keygen.yaml' 23 | /eth/v1/keygen/secp256k1: 24 | $ref: './keygen/paths/secp256k1_keygen.yaml' 25 | /eth/v1/keystores: 26 | $ref: './keymanager/paths/keystores.yaml' 27 | 28 | externalDocs: 29 | description: 'Secure-Signer User Documentation' 30 | #publicsh this on github pages 31 | # url: '' 32 | -------------------------------------------------------------------------------- /resources/signing/paths/deposit.yaml: -------------------------------------------------------------------------------- 1 | post: 2 | tags: 3 | - 'Deposit' 4 | summary: 'Register a validator' 5 | description: 'Given a `DepositRequest`, uses the specified BLS public key to sign a `DepositResponse`.' 6 | operationId: 'ETH2_DEPOSIT' 7 | parameters: 8 | - name: 'identifier' 9 | in: 'path' 10 | required: true 11 | description: 'Key for which data to sign' 12 | schema: 13 | $ref: "../../keygen/schemas.yaml#/components/schemas/Pubkey" 14 | responses: 15 | '200': 16 | description: 'Success response: a JSON encoded DepositResponse' 17 | content: 18 | application/json: 19 | schema: 20 | $ref: '../schemas.yaml#/components/schemas/DepositResponse' 21 | '400': 22 | description: 'Bad request format' 23 | '412': 24 | description: 'This validator key does not exist' 25 | '500': 26 | description: 'Internal Secure-Signer server error' -------------------------------------------------------------------------------- /resources/signing/paths/sign.yaml: -------------------------------------------------------------------------------- 1 | post: 2 | tags: 3 | - 'Signing' 4 | summary: 'Signs data for ETH2 BLS public key' 5 | description: 'Signs data for the ETH2 BLS public key specified as part of the URL and returns the signature' 6 | operationId: 'ETH2_SIGN' 7 | parameters: 8 | - name: 'identifier' 9 | in: 'path' 10 | required: true 11 | description: 'Key for which data to sign' 12 | schema: 13 | $ref: "../../keygen/schemas.yaml#/components/schemas/Pubkey" 14 | requestBody: 15 | required: true 16 | content: 17 | application/json: 18 | schema: 19 | oneOf: 20 | - $ref: '../schemas.yaml#/components/schemas/AggregationSlotSigning' 21 | - $ref: '../schemas.yaml#/components/schemas/AggregateAndProofSigning' 22 | - $ref: '../schemas.yaml#/components/schemas/AttestationSigning' 23 | - $ref: '../schemas.yaml#/components/schemas/BlockSigning' 24 | - $ref: '../schemas.yaml#/components/schemas/BeaconBlockSigning' 25 | - $ref: '../schemas.yaml#/components/schemas/DepositSigning' 26 | - $ref: '../schemas.yaml#/components/schemas/RandaoRevealSigning' 27 | - $ref: '../schemas.yaml#/components/schemas/VoluntaryExitSigning' 28 | - $ref: '../schemas.yaml#/components/schemas/SyncCommitteeMessageSigning' 29 | - $ref: '../schemas.yaml#/components/schemas/SyncCommitteeSelectionProofSigning' 30 | - $ref: '../schemas.yaml#/components/schemas/SyncCommitteeContributionAndProofSigning' 31 | - $ref: '../schemas.yaml#/components/schemas/ValidatorRegistrationSigning' 32 | discriminator: 33 | propertyName: type 34 | mapping: 35 | AGGREGATION_SLOT: '../schemas.yaml#/components/schemas/AggregationSlotSigning' 36 | AGGREGATE_AND_PROOF: '../schemas.yaml#/components/schemas/AggregateAndProofSigning' 37 | ATTESTATION: '../schemas.yaml#/components/schemas/AttestationSigning' 38 | BLOCK: '../schemas.yaml#/components/schemas/BlockSigning' 39 | BLOCK_V2: '../schemas.yaml#/components/schemas/BeaconBlockSigning' 40 | DEPOSIT: '../schemas.yaml#/components/schemas/DepositSigning' 41 | RANDAO_REVEAL: '../schemas.yaml#/components/schemas/RandaoRevealSigning' 42 | VOLUNTARY_EXIT: '../schemas.yaml#/components/schemas/VoluntaryExitSigning' 43 | SYNC_COMMITTEE_MESSAGE: '../schemas.yaml#/components/schemas/SyncCommitteeMessageSigning' 44 | SYNC_COMMITTEE_SELECTION_PROOF: '../schemas.yaml#/components/schemas/SyncCommitteeSelectionProofSigning' 45 | SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF: '../schemas.yaml#/components/schemas/SyncCommitteeContributionAndProofSigning' 46 | VALIDATOR_REGISTRATION: '../schemas.yaml#/components/schemas/ValidatorRegistrationSigning' 47 | responses: 48 | '200': 49 | description: 'hex encoded string of signature' 50 | content: 51 | application/json: 52 | schema: 53 | $ref: '../schemas.yaml#/components/schemas/SigningResponse' 54 | text/plain: 55 | schema: 56 | type: string 57 | example: '0xb3baa751d0a9132cfe93e4e3d5ff9075111100e3789dca219ade5a24d27e19d16b3353149da1833e9b691bb38634e8dc04469be7032132906c927d7e1a49b414730612877bc6b2810c8f202daf793d1ab0d6b5cb21d52f9e52e883859887a5d9' 58 | '412': 59 | description: 'Signing operation failed due to slashing protection rules' 60 | '404': 61 | description: 'Public Key not found' 62 | '400': 63 | description: 'Bad request format' 64 | '500': 65 | description: 'Internal Web3Signer server error' -------------------------------------------------------------------------------- /resources/signing/paths/upcheck.yaml: -------------------------------------------------------------------------------- 1 | get: 2 | tags: 3 | - 'Server Status' 4 | summary: 'Server Status' 5 | description: | 6 | Checks the Secure-Signer server status. Confirms if Secure-Signer is connected and running. 7 | operationId: 'UPCHECK' 8 | responses: 9 | '200': 10 | description: 'OK' 11 | content: 12 | text/plain; charset=utf-8: 13 | schema: 14 | type: string 15 | example: 'OK' 16 | '500': 17 | description: 'Internal Secure-Signer server error' 18 | -------------------------------------------------------------------------------- /run_container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | script_dir=$(dirname $(realpath $0)) 5 | volume_mount_1="$script_dir:/root/" 6 | volume_mount_2="/var/run/aesmd:/var/run/aesmd" 7 | device_1="/dev/sgx_enclave" 8 | device_2="/dev/sgx_provision" 9 | 10 | # Allow us to talk via localhost 11 | network_name="host" 12 | 13 | # Initialize default values 14 | container_prefix="" 15 | ss_port=9001 16 | 17 | # Function to run the container 18 | function run_container() { 19 | echo "Start ${container_prefix}-server on port ${ss_port}..." 20 | docker run -itd --privileged \ 21 | -v $volume_mount_1${container_prefix} \ 22 | -v $volume_mount_2 \ 23 | --device $device_1 \ 24 | --device $device_2 \ 25 | --network $network_name \ 26 | --name ${container_name} \ 27 | ${registry}/${image_name}:${tag} 28 | 29 | # Run the startup command 30 | docker exec -it $container_name bash -c "$startup_command" 31 | } 32 | 33 | # Function to attach to an existing container 34 | function attach_image() { 35 | if [ "$(docker ps -aq -f name=$container_name)" ]; then 36 | echo "Attaching to ${container_name}..." 37 | docker exec -it ${container_name} bash 38 | else 39 | echo "${container_name} not found, launching now..." 40 | run_container 41 | docker exec -it ${container_name} bash 42 | fi 43 | } 44 | 45 | function clean_existing_container() { 46 | if [ "$(docker ps -aq -f name=$container_name)" ]; then 47 | echo "Removing ${container_name}..." 48 | docker stop $container_name && docker rm $container_name 49 | fi 50 | } 51 | 52 | function usage { 53 | cat << EOM 54 | Run container images in background. 55 | usage: $(basename "$0") [OPTION]... 56 | -p default 9001. 57 | -g set guardian enclave. 58 | -v set validator enclave. 59 | -s set secure-signer enclave. 60 | -d run in development mode. 61 | -r run in release mode. 62 | -a attach to the specified image without running. 63 | -f force remove existing container. 64 | -h usage help 65 | EOM 66 | exit 0 67 | } 68 | 69 | function process_args { 70 | while getopts ":pgvsdarfh" option; do 71 | case "${option}" in 72 | p) ss_port=${OPTARG};; 73 | g) container_prefix="guardian";; 74 | v) container_prefix="validator";; 75 | s) container_prefix="secure-signer";; 76 | d) development=true;; 77 | r) release=true;; 78 | a) attach=true;; 79 | f) remove_old=true;; 80 | h) usage;; 81 | esac 82 | done 83 | } 84 | 85 | process_args "$@" 86 | 87 | if [ -z "$container_prefix" ]; then 88 | echo "Error: Must specify one of -g (guardian), -v (validator), or -s (secure-signer)" 89 | exit 1 90 | fi 91 | 92 | if [ $release ]; then 93 | container_name="${container_prefix}_container" 94 | image_name="${container_prefix}" 95 | registry="pufferfinance" 96 | tag="latest" 97 | startup_command="occlum run /bin/${container_prefix} ${ss_port} &" 98 | elif [ $development ]; then 99 | container_name="${container_prefix}_container_dev" 100 | image_name="occlum" 101 | registry="occlum" 102 | tag="0.29.1-ubuntu20.04" 103 | startup_command="rustup update 1.71.0 && rustup default 1.71.0 && rustup target add x86_64-unknown-linux-musl" 104 | else 105 | echo "Error: Must specify either -r (release) or -d (development) flag" 106 | exit 1 107 | fi 108 | 109 | if [ $remove_old ]; then 110 | clean_existing_container 111 | fi 112 | 113 | if [ $attach ]; then 114 | attach_image 115 | else 116 | run_container 117 | fi 118 | -------------------------------------------------------------------------------- /src/bin/guardian.rs: -------------------------------------------------------------------------------- 1 | extern crate puffersecuresigner; 2 | use puffersecuresigner::{eth2::eth_types::Version, strip_0x_prefix}; 3 | 4 | #[tokio::main] 5 | async fn main() { 6 | tracing_subscriber::fmt::init(); 7 | 8 | let port = std::env::args() 9 | .nth(1) 10 | .unwrap_or("3031".into()) 11 | .parse::() 12 | .expect("BAD PORT"); 13 | let genesis_fork_version_str: String = 14 | std::env::args().nth(2).unwrap_or("00000000".to_string()); 15 | let genesis_fork_version_str: String = strip_0x_prefix!(genesis_fork_version_str); 16 | let mut genesis_fork_version = Version::default(); 17 | genesis_fork_version.copy_from_slice( 18 | &hex::decode(&genesis_fork_version_str).expect("Bad genesis_fork_version"), 19 | ); 20 | 21 | println!( 22 | "Starting SGX Guardian: localhost:{}, using genesis_fork_version: {:?}", 23 | port, genesis_fork_version 24 | ); 25 | 26 | let app = axum::Router::new() 27 | // Endpoint to check health 28 | .route( 29 | "/upcheck", 30 | axum::routing::get(puffersecuresigner::enclave::shared::handlers::health::handler), 31 | ) 32 | // Endpoint generated a new ETH key 33 | .route( 34 | "/eth/v1/keygen", 35 | axum::routing::post( 36 | puffersecuresigner::enclave::guardian::handlers::attest_fresh_eth_key_with_blockhash::handler, 37 | ), 38 | ) 39 | // Endpoint to list the pks of all the generated ETH keys 40 | .route( 41 | "/eth/v1/keygen", 42 | axum::routing::get( 43 | puffersecuresigner::enclave::shared::handlers::list_eth_keys::handler, 44 | ), 45 | ) 46 | // Endpoint to validate and receive BLS keyshare custody 47 | .route( 48 | "/guardian/v1/validate-custody", 49 | axum::routing::post( 50 | puffersecuresigner::enclave::guardian::handlers::validate_custody::handler, 51 | ), 52 | ) 53 | // Endpoint to sign a VoluntaryExitMessage 54 | .route( 55 | "/guardian/v1/sign-exit", 56 | axum::routing::post( 57 | puffersecuresigner::enclave::guardian::handlers::sign_exit::handler, 58 | ), 59 | ) 60 | ; 61 | 62 | let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port)); 63 | 64 | _ = axum::Server::bind(&addr) 65 | .serve(app.into_make_service()) 66 | .await; 67 | } 68 | -------------------------------------------------------------------------------- /src/bin/secure-signer.rs: -------------------------------------------------------------------------------- 1 | extern crate puffersecuresigner; 2 | use puffersecuresigner::{eth2::eth_types::Version, strip_0x_prefix}; 3 | 4 | #[tokio::main] 5 | async fn main() { 6 | tracing_subscriber::fmt::init(); 7 | 8 | let port = std::env::args() 9 | .nth(1) 10 | .unwrap_or("3031".into()) 11 | .parse::() 12 | .expect("BAD PORT"); 13 | let genesis_fork_version_str: String = 14 | std::env::args().nth(2).unwrap_or("00000000".to_string()); 15 | let genesis_fork_version_str: String = strip_0x_prefix!(genesis_fork_version_str); 16 | let mut genesis_fork_version = Version::default(); 17 | genesis_fork_version.copy_from_slice( 18 | &hex::decode(&genesis_fork_version_str).expect("Bad genesis_fork_version"), 19 | ); 20 | 21 | println!( 22 | "Starting SGX Secure-Signer: localhost:{}, using genesis_fork_version: {:?}", 23 | port, genesis_fork_version 24 | ); 25 | 26 | let app_state = puffersecuresigner::enclave::shared::handlers::AppState { 27 | genesis_fork_version, 28 | }; 29 | 30 | let eth_v1 = axum::Router::new() 31 | // Endpoint to securely generate and save an ETH sk 32 | .route( 33 | "/keygen/secp256k1", 34 | axum::routing::post( 35 | puffersecuresigner::enclave::secure_signer::handlers::eth_keygen::handler, 36 | ), 37 | ) 38 | // Endpoint to securely generate and save a BLS sk 39 | .route( 40 | "/keygen/bls", 41 | axum::routing::post( 42 | puffersecuresigner::enclave::secure_signer::handlers::bls_keygen::handler, 43 | ), 44 | ) 45 | // Endpoint to list the pks of all the generated ETH keys 46 | .route( 47 | "/keygen/secp256k1", 48 | axum::routing::get( 49 | puffersecuresigner::enclave::shared::handlers::list_eth_keys::handler, 50 | ), 51 | ) 52 | // Endpoint to list all pks of saved bls keys in the enclave 53 | .route( 54 | "/keystores", 55 | axum::routing::get( 56 | puffersecuresigner::enclave::shared::handlers::list_bls_keys::handler, 57 | ), 58 | ); 59 | 60 | let app = axum::Router::new() 61 | // Endpoint to check health 62 | .route( 63 | "/upcheck", 64 | axum::routing::get(puffersecuresigner::enclave::shared::handlers::health::handler), 65 | ) 66 | .nest("/eth/v1", eth_v1) 67 | // Endpoint to sign DepositData message for registering validator on beacon chain 68 | .route( 69 | "/api/v1/eth2/deposit", 70 | axum::routing::post( 71 | puffersecuresigner::enclave::secure_signer::handlers::validator_deposit::handler, 72 | ), 73 | ) 74 | // Endpoint to request a signature using BLS sk 75 | .route( 76 | "/api/v1/eth2/sign/:bls_pk_hex", 77 | axum::routing::post( 78 | puffersecuresigner::enclave::shared::handlers::secure_sign_bls::handler, 79 | ), 80 | ) 81 | .with_state(app_state); 82 | 83 | let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port)); 84 | 85 | _ = axum::Server::bind(&addr) 86 | .serve(app.into_make_service()) 87 | .await; 88 | } 89 | -------------------------------------------------------------------------------- /src/bin/validator.rs: -------------------------------------------------------------------------------- 1 | extern crate puffersecuresigner; 2 | use puffersecuresigner::{eth2::eth_types::Version, strip_0x_prefix}; 3 | 4 | #[tokio::main] 5 | async fn main() { 6 | tracing_subscriber::fmt::init(); 7 | 8 | let port = std::env::args() 9 | .nth(1) 10 | .unwrap_or("3031".into()) 11 | .parse::() 12 | .expect("BAD PORT"); 13 | let genesis_fork_version_str: String = 14 | std::env::args().nth(2).unwrap_or("00000000".to_string()); 15 | let genesis_fork_version_str: String = strip_0x_prefix!(genesis_fork_version_str); 16 | let mut genesis_fork_version = Version::default(); 17 | genesis_fork_version.copy_from_slice( 18 | &hex::decode(&genesis_fork_version_str).expect("Bad genesis_fork_version"), 19 | ); 20 | 21 | log::info!( 22 | "Starting SGX Validator: localhost:{}, using genesis_fork_version: {:?}", 23 | port, 24 | genesis_fork_version 25 | ); 26 | 27 | let app_state = puffersecuresigner::enclave::shared::handlers::AppState { 28 | genesis_fork_version, 29 | }; 30 | 31 | let app = axum::Router::new() 32 | // Endpoint to check health 33 | .route( 34 | "/upcheck", 35 | axum::routing::get(puffersecuresigner::enclave::shared::handlers::health::handler), 36 | ) 37 | // Endpoint to securely generate and save a BLS sk 38 | .route( 39 | "/bls/v1/keygen", 40 | axum::routing::post( 41 | puffersecuresigner::enclave::validator::handlers::attest_fresh_bls_key::handler, 42 | ), 43 | ) 44 | // Endpoint to list all pks of saved bls keys in the enclave 45 | .route( 46 | "/eth/v1/keystores", 47 | axum::routing::get( 48 | puffersecuresigner::enclave::shared::handlers::list_bls_keys::handler, 49 | ), 50 | ) 51 | // Endpoint to list all bls pubkeys - used by the validator client 52 | .route( 53 | "/api/v1/eth2/publicKeys", 54 | axum::routing::get( 55 | puffersecuresigner::enclave::shared::handlers::list_bls_keys_for_vc::handler, 56 | ), 57 | ) 58 | // Endpoint to request a signature using BLS sk 59 | .route( 60 | "/api/v1/eth2/sign/:bls_pk_hex", 61 | axum::routing::post( 62 | puffersecuresigner::enclave::shared::handlers::secure_sign_bls::handler, 63 | ), 64 | ) 65 | .with_state(app_state); 66 | 67 | let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port)); 68 | 69 | _ = axum::Server::bind(&addr) 70 | .serve(app.into_make_service()) 71 | .await; 72 | } 73 | -------------------------------------------------------------------------------- /src/cli/deposit.rs: -------------------------------------------------------------------------------- 1 | use super::routes; 2 | use super::NetworkConfig; 3 | use anyhow::Context; 4 | use anyhow::{bail, Result}; 5 | use puffersecuresigner::{ 6 | constants::BLS_PUB_KEY_BYTES, 7 | eth2::eth_types::{DepositMessage, DepositRequest, DepositResponse, Version}, 8 | strip_0x_prefix, 9 | }; 10 | use serde_json::Value; 11 | 12 | const DEPOSIT_AMOUNT: u64 = 32000000000; 13 | 14 | fn eth_addr_to_credentials(execution_addr: &str) -> Result { 15 | let addr: String = strip_0x_prefix!(execution_addr); 16 | if addr.len() != 40 { 17 | bail!("Invalid length ETH address") 18 | } 19 | let withdrawal_credentials = format!("0x010000000000000000000000{addr}"); 20 | assert_eq!(withdrawal_credentials.len(), 66); 21 | Ok(withdrawal_credentials) 22 | } 23 | 24 | fn build_deposit_request( 25 | validator_pk_hex: &str, 26 | withdrawal_credentials: &str, 27 | fork_version: Version, 28 | ) -> Result { 29 | let validator_pk_hex: String = strip_0x_prefix!(validator_pk_hex); 30 | let pk_bytes = hex::decode(validator_pk_hex)?; 31 | assert_eq!( 32 | pk_bytes.len(), 33 | BLS_PUB_KEY_BYTES, 34 | "Invalid bls public key length" 35 | ); 36 | 37 | let withdrawal_credentials: String = strip_0x_prefix!(withdrawal_credentials); 38 | let withdrawal_bytes = hex::decode(withdrawal_credentials)?; 39 | assert_eq!( 40 | withdrawal_bytes.len(), 41 | 32, 42 | "Invalid withdrawal credentials length" 43 | ); 44 | let mut withdrawal_fixed_bytes: crate::eth2::eth_types::Bytes32 = [0_u8; 32]; 45 | withdrawal_fixed_bytes.clone_from_slice(&withdrawal_bytes); 46 | 47 | let deposit = DepositMessage { 48 | pubkey: pk_bytes.into(), 49 | withdrawal_credentials: withdrawal_fixed_bytes, 50 | amount: DEPOSIT_AMOUNT, 51 | }; 52 | 53 | let msg = DepositRequest { 54 | signingRoot: None, 55 | deposit, 56 | genesis_fork_version: fork_version, 57 | }; 58 | 59 | Ok(msg) 60 | } 61 | 62 | pub async fn get_deposit_signature( 63 | port: u16, 64 | bls_pk_hex: &str, 65 | execution_addr: &str, 66 | fork_version: Version, 67 | ) -> Result { 68 | let withdrawal_creds = eth_addr_to_credentials(execution_addr)?; 69 | let deposit_req = build_deposit_request(bls_pk_hex, &withdrawal_creds, fork_version)?; 70 | let json_req = serde_json::to_string(&deposit_req)?; 71 | let resp = routes::deposit(port, &json_req).await?; 72 | Ok(resp) 73 | } 74 | 75 | pub fn deposit_data_payload(d: DepositResponse, config: NetworkConfig) -> Result { 76 | let pubkey = d.pubkey; 77 | let withdrawal_credentials = d.withdrawal_credentials; 78 | let amount = d.amount; 79 | let signature = d.signature; 80 | let deposit_message_root = d.deposit_message_root; 81 | let deposit_data_root = d.deposit_data_root; 82 | let network_name = config.network_name; 83 | let fork_version = hex::encode(config.fork_info.fork.current_version); 84 | let deposit_cli_version = config.deposit_cli_version; 85 | 86 | // Build deposit JSON that works with https://goerli.launchpad.ethereum.org/en/upload-deposit-data 87 | let json_string = format!( 88 | r#" 89 | [{{ 90 | "pubkey": "{pubkey}", 91 | "withdrawal_credentials": "{withdrawal_credentials}", 92 | "amount": {amount}, 93 | "signature": "{signature}", 94 | "deposit_message_root": "{deposit_message_root}", 95 | "deposit_data_root": "{deposit_data_root}", 96 | "fork_version": "{fork_version}", 97 | "network_name": "{network_name}", 98 | "deposit_cli_version": "{deposit_cli_version}" 99 | }}]"# 100 | ); 101 | serde_json::from_str(&json_string).with_context(|| "Failed to serialize final DepositData json") 102 | } 103 | -------------------------------------------------------------------------------- /src/cli/withdraw.rs: -------------------------------------------------------------------------------- 1 | use crate::routes::bls_sign; 2 | 3 | use super::NetworkConfig; 4 | use anyhow::Result; 5 | use puffersecuresigner::eth2::eth_signing::BLSSignMsg; 6 | use puffersecuresigner::eth2::eth_types::SignedVoluntaryExit; 7 | use puffersecuresigner::eth2::eth_types::{VoluntaryExit, VoluntaryExitRequest}; 8 | 9 | pub async fn exit_validator( 10 | port: u16, 11 | validator_pk_hex: &str, 12 | epoch: u64, 13 | validator_index: u64, 14 | config: NetworkConfig, 15 | ) -> Result { 16 | let vem = VoluntaryExitRequest { 17 | signingRoot: None, 18 | fork_info: config.fork_info, 19 | voluntary_exit: VoluntaryExit { 20 | epoch, 21 | validator_index, 22 | }, 23 | }; 24 | let req = BLSSignMsg::VOLUNTARY_EXIT(vem); 25 | let json = serde_json::to_string(&req)?; 26 | let sig = bls_sign(port, &json, &validator_pk_hex.to_string()).await?; 27 | Ok(SignedVoluntaryExit { 28 | message: VoluntaryExit { 29 | epoch, 30 | validator_index, 31 | }, 32 | signature: sig.to_ssz_bytes()?, 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /src/client/guardian.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use async_trait::async_trait; 4 | 5 | use crate::client::traits::GuardianClientTrait; 6 | 7 | pub struct GuardianClient { 8 | pub url: String, 9 | pub client: Arc, 10 | } 11 | 12 | #[async_trait] 13 | impl GuardianClientTrait for GuardianClient { 14 | async fn health(&self) -> bool { 15 | let Ok(resp) = self 16 | .client 17 | .get(format!("{}/upcheck", self.url)) 18 | .send() 19 | .await 20 | else { 21 | return false; 22 | }; 23 | resp.status() == reqwest::StatusCode::OK 24 | } 25 | 26 | async fn attest_fresh_eth_key( 27 | &self, 28 | blockhash: &str, 29 | ) -> anyhow::Result { 30 | let data = crate::enclave::guardian::KeygenWithBlockhashRequest { 31 | blockhash: blockhash.to_string(), 32 | }; 33 | 34 | let resp = self 35 | .client 36 | .post(format!("{}/eth/v1/keygen", self.url)) 37 | .json(&data) 38 | .send() 39 | .await?; 40 | 41 | let resp_status = resp.status(); 42 | if !resp_status.is_success() { 43 | let body = resp.text().await?; 44 | return Err(anyhow::anyhow!(body)); 45 | } 46 | 47 | let resp_json = resp.json().await?; 48 | Ok(resp_json) 49 | } 50 | 51 | async fn list_eth_keys(&self) -> anyhow::Result { 52 | let resp = self 53 | .client 54 | .get(format!("{}/eth/v1/keygen", self.url)) 55 | .send() 56 | .await?; 57 | 58 | let resp_status = resp.status(); 59 | if !resp_status.is_success() { 60 | let body = resp.text().await?; 61 | return Err(anyhow::anyhow!(body)); 62 | } 63 | 64 | let resp_json = resp.json().await?; 65 | Ok(resp_json) 66 | } 67 | 68 | async fn validate_custody( 69 | &self, 70 | request: crate::enclave::types::ValidateCustodyRequest, 71 | ) -> anyhow::Result { 72 | let resp = self 73 | .client 74 | .post(format!("{}/guardian/v1/validate-custody", self.url)) 75 | .json(&request) 76 | .send() 77 | .await?; 78 | 79 | let resp_status = resp.status(); 80 | if !resp_status.is_success() { 81 | let body = resp.text().await?; 82 | return Err(anyhow::anyhow!(body)); 83 | } 84 | 85 | let resp_json = resp.json().await?; 86 | Ok(resp_json) 87 | } 88 | 89 | async fn sign_exit( 90 | &self, 91 | request: crate::enclave::types::SignExitRequest, 92 | ) -> anyhow::Result { 93 | let resp = self 94 | .client 95 | .post(format!("{}/guardian/v1/sign-exit", self.url)) 96 | .json(&request) 97 | .send() 98 | .await?; 99 | 100 | let resp_status = resp.status(); 101 | if !resp_status.is_success() { 102 | let body = resp.text().await?; 103 | return Err(anyhow::anyhow!(body)); 104 | } 105 | 106 | let resp_json = resp.json().await?; 107 | Ok(resp_json) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/client/keygen.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use ecies::PublicKey as EthPublicKey; 3 | 4 | pub fn generate_bls_keystore_handler( 5 | keygen_payload: crate::enclave::types::AttestFreshBlsKeyPayload, 6 | keystore_password: &String, 7 | ) -> Result { 8 | log::info!("generate_bls_keystore()"); 9 | generate_bls_keystore( 10 | keygen_payload.withdrawal_credentials, 11 | keygen_payload.guardian_pubkeys, 12 | keygen_payload.threshold, 13 | keystore_password, 14 | keygen_payload.fork_version, 15 | ) 16 | } 17 | 18 | fn generate_bls_keystore( 19 | withdrawal_credentials: [u8; 32], 20 | guardian_public_keys: Vec, 21 | threshold: usize, 22 | password: &String, 23 | fork_version: crate::eth2::eth_types::Version, 24 | ) -> Result { 25 | // Generate a SecretKeySet where t + 1 signature shares can be combined into a full signature. attest_fresh_bls_key() function assumes `threshold = t + 1`, so we must pass new_bls_key(t=threshold - 1) 26 | let secret_key_set = crate::crypto::bls_keys::new_bls_key(threshold - 1); 27 | 28 | // Shard the key into `n` keyshares 29 | let n = guardian_public_keys.len(); 30 | let key_shares = crate::crypto::bls_keys::distribute_key_shares(&secret_key_set, n); 31 | 32 | // Encrypt the shares to using guardian pubkeys 33 | let mut recipient_keys: Vec = Vec::new(); 34 | for (g_pk, (sk_share, pk_share)) in guardian_public_keys.into_iter().zip(key_shares.into_iter()) 35 | { 36 | let k = crate::enclave::validator::RecipientKeys { 37 | guardian_public_key: g_pk, 38 | secret_key_share: sk_share, 39 | public_key_share: pk_share, 40 | } 41 | .encrypt_to_recipient()?; 42 | recipient_keys.push(k); 43 | } 44 | 45 | // Get validator aggregate public key 46 | let validator_pubkey = secret_key_set.public_keys().public_key(); 47 | 48 | // Save validator private key to encrypted keystore 49 | crate::crypto::bls_keys::save_bls_keystore(&secret_key_set, &password)?; 50 | 51 | // Sign DepositMessage to deposit 32 ETH to beacon deposit contract 52 | let (signature, deposit_data_root) = crate::eth2::eth_signing::sign_full_deposit( 53 | &secret_key_set, 54 | withdrawal_credentials.clone(), 55 | fork_version, 56 | )?; 57 | 58 | // Return the payload 59 | Ok(crate::enclave::types::BlsKeygenPayload { 60 | bls_pub_key_set: hex::encode(secret_key_set.public_keys().to_bytes()), 61 | bls_pub_key: validator_pubkey.to_hex(), 62 | signature: hex::encode(&signature[..]), 63 | deposit_data_root: hex::encode(deposit_data_root), 64 | bls_enc_priv_key_shares: recipient_keys 65 | .iter() 66 | .map(|encrypted_key| encrypted_key.encrypted_secret_key_share_hex.clone()) 67 | .collect(), 68 | intel_report: "".to_string(), 69 | intel_sig: "".to_string(), 70 | intel_x509: "".to_string(), 71 | guardian_eth_pub_keys: recipient_keys 72 | .iter() 73 | .map(|k| crate::crypto::eth_keys::eth_pk_to_hex_uncompressed(&k.guardian_public_key)) 74 | .collect(), 75 | withdrawal_credentials: hex::encode(withdrawal_credentials), 76 | fork_version: crate::eth2::eth_types::GENESIS_FORK_VERSION, 77 | }) 78 | } 79 | 80 | #[cfg(test)] 81 | mod tests { 82 | use crate::eth2::eth_types::GENESIS_FORK_VERSION; 83 | 84 | use super::*; 85 | 86 | #[test] 87 | fn test_keygen_and_decrypt() { 88 | let withdrawal_credentials = [1; 32]; 89 | let threshold = 1; 90 | let (_g_sk, g_pk) = crate::crypto::eth_keys::new_eth_key().unwrap(); 91 | let password = "password".to_string(); 92 | 93 | // Validator generates fresh keystore, provisions custody to Guardians, and creates deposit msg 94 | let payload = crate::enclave::types::AttestFreshBlsKeyPayload { 95 | guardian_pubkeys: vec![g_pk.clone()], 96 | withdrawal_credentials: withdrawal_credentials.clone(), 97 | threshold, 98 | fork_version: GENESIS_FORK_VERSION, 99 | do_remote_attestation: false, 100 | }; 101 | let payload = generate_bls_keystore_handler(payload, &password).unwrap(); 102 | 103 | // Verify we can decrypt the keystore 104 | let sk = crate::crypto::bls_keys::fetch_bls_sk_keystore(&payload.bls_pub_key, &password) 105 | .unwrap(); 106 | 107 | // Verify the fields match 108 | assert_eq!( 109 | sk.public_keys().public_key().to_hex(), 110 | payload.public_key_set().unwrap().public_key().to_hex() 111 | ); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/client/mock/guardian.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::sync::{Arc, Mutex}; 3 | 4 | use async_trait::async_trait; 5 | 6 | use crate::client::traits::GuardianClientTrait; 7 | 8 | #[derive(Clone, Debug, Default)] 9 | pub struct MockGuardianClient { 10 | pub health_responses: Arc>>, 11 | pub attest_fresh_eth_key_responses: Arc>>, 12 | pub list_eth_keys_responses: Arc>>, 13 | pub validate_custody_responses: 14 | Arc>>, 15 | pub sign_exit_responses: Arc>>, 16 | } 17 | 18 | impl MockGuardianClient { 19 | pub fn new() -> Self { 20 | Self::default() 21 | } 22 | pub fn push_health_response(&mut self, value: bool) { 23 | self.health_responses.lock().unwrap().push_back(value); 24 | } 25 | pub fn push_attest_fresh_eth_key_response( 26 | &mut self, 27 | value: crate::enclave::types::KeyGenResponse, 28 | ) { 29 | self.attest_fresh_eth_key_responses 30 | .lock() 31 | .unwrap() 32 | .push_back(value); 33 | } 34 | pub fn push_list_eth_keys_response(&mut self, value: crate::enclave::types::ListKeysResponse) { 35 | self.list_eth_keys_responses 36 | .lock() 37 | .unwrap() 38 | .push_back(value); 39 | } 40 | pub fn push_validate_custody_response( 41 | &mut self, 42 | value: crate::enclave::types::ValidateCustodyResponse, 43 | ) { 44 | self.validate_custody_responses 45 | .lock() 46 | .unwrap() 47 | .push_back(value); 48 | } 49 | pub fn push_sign_exit_response(&mut self, value: crate::enclave::types::SignExitResponse) { 50 | self.sign_exit_responses.lock().unwrap().push_back(value); 51 | } 52 | } 53 | 54 | #[async_trait] 55 | impl GuardianClientTrait for MockGuardianClient { 56 | async fn health(&self) -> bool { 57 | match self.health_responses.lock().unwrap().pop_front() { 58 | Some(value) => value, 59 | None => false, 60 | } 61 | } 62 | 63 | async fn attest_fresh_eth_key( 64 | &self, 65 | _blockhash: &str, 66 | ) -> anyhow::Result { 67 | match self 68 | .attest_fresh_eth_key_responses 69 | .lock() 70 | .unwrap() 71 | .pop_front() 72 | { 73 | Some(res) => Ok(res), 74 | None => Err(std::io::Error::new( 75 | std::io::ErrorKind::BrokenPipe, 76 | "No attest_fresh_eth_key response set".to_string(), 77 | ) 78 | .into()), 79 | } 80 | } 81 | 82 | async fn list_eth_keys(&self) -> anyhow::Result { 83 | match self.list_eth_keys_responses.lock().unwrap().pop_front() { 84 | Some(res) => Ok(res), 85 | None => Err(std::io::Error::new( 86 | std::io::ErrorKind::BrokenPipe, 87 | "No list_eth_keys response set".to_string(), 88 | ) 89 | .into()), 90 | } 91 | } 92 | 93 | async fn validate_custody( 94 | &self, 95 | _request: crate::enclave::types::ValidateCustodyRequest, 96 | ) -> anyhow::Result { 97 | match self.validate_custody_responses.lock().unwrap().pop_front() { 98 | Some(res) => Ok(res), 99 | None => Err(std::io::Error::new( 100 | std::io::ErrorKind::BrokenPipe, 101 | "No validate_custody response set".to_string(), 102 | ) 103 | .into()), 104 | } 105 | } 106 | 107 | async fn sign_exit( 108 | &self, 109 | _request: crate::enclave::types::SignExitRequest, 110 | ) -> anyhow::Result { 111 | match self.sign_exit_responses.lock().unwrap().pop_front() { 112 | Some(res) => Ok(res), 113 | None => Err(std::io::Error::new( 114 | std::io::ErrorKind::BrokenPipe, 115 | "No sign_exit response set".to_string(), 116 | ) 117 | .into()), 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/client/mock/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod guardian; 2 | pub mod validator; 3 | -------------------------------------------------------------------------------- /src/client/mock/validator.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | 3 | use crate::client::traits::ValidatorClientTrait; 4 | use crate::enclave::types::ListKeysResponse; 5 | 6 | #[derive(Clone, Debug, Default)] 7 | pub struct MockValidatorClient { 8 | pub health: bool, 9 | pub fresh_bls_key: Option, 10 | pub voluntary_exit_message: Option, 11 | } 12 | 13 | impl MockValidatorClient { 14 | pub fn new() -> Self { 15 | Self::default() 16 | } 17 | pub fn set_health_response(&mut self, response: bool) { 18 | self.health = response; 19 | } 20 | } 21 | 22 | #[async_trait] 23 | impl ValidatorClientTrait for MockValidatorClient { 24 | async fn health(&self) -> bool { 25 | self.health 26 | } 27 | 28 | async fn attest_fresh_bls_key( 29 | &self, 30 | _payload: &crate::enclave::types::AttestFreshBlsKeyPayload, 31 | ) -> anyhow::Result { 32 | match self.fresh_bls_key.as_ref() { 33 | Some(resp) => Ok(resp.clone()), 34 | None => { 35 | Err(std::io::Error::new(std::io::ErrorKind::BrokenPipe, "AAA".to_string()).into()) 36 | } 37 | } 38 | } 39 | 40 | async fn list_bls_keys(&self) -> anyhow::Result { 41 | Ok(ListKeysResponse { data: vec![] }) 42 | } 43 | 44 | async fn sign_voluntary_exit_message( 45 | &self, 46 | _bls_pk_hex: String, 47 | _epoch: crate::eth2::eth_types::Epoch, 48 | _validator_index: crate::eth2::eth_types::ValidatorIndex, 49 | _fork_info: crate::eth2::eth_types::ForkInfo, 50 | ) -> anyhow::Result { 51 | match self.voluntary_exit_message.as_ref() { 52 | Some(resp) => Ok(resp.clone()), 53 | None => { 54 | Err(std::io::Error::new(std::io::ErrorKind::BrokenPipe, "AAA".to_string()).into()) 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/client/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use self::{ 4 | guardian::GuardianClient, secure_signer::SecureSignerClient, validator::ValidatorClient, 5 | }; 6 | 7 | pub mod mock; 8 | pub mod traits; 9 | 10 | mod guardian; 11 | mod keygen; 12 | mod secure_signer; 13 | mod validator; 14 | 15 | #[cfg(test)] 16 | mod tests; 17 | 18 | pub use keygen::generate_bls_keystore_handler; 19 | 20 | pub struct Client { 21 | pub validator: ValidatorClient, 22 | pub secure_signer: SecureSignerClient, 23 | pub guardian: GuardianClient, 24 | } 25 | 26 | fn default_client_guardian_url() -> String { 27 | "http://localhost:9002".to_string() 28 | } 29 | fn default_client_validator_url() -> String { 30 | "http://localhost:9003".to_string() 31 | } 32 | fn default_client_secure_signer_url() -> String { 33 | "http://localhost:9001".to_string() 34 | } 35 | 36 | pub struct ClientBuilder { 37 | validator_url: Option, 38 | secure_signer_url: Option, 39 | guardian_url: Option, 40 | } 41 | 42 | impl ClientBuilder { 43 | pub fn new() -> Self { 44 | ClientBuilder { 45 | validator_url: None, 46 | secure_signer_url: None, 47 | guardian_url: None, 48 | } 49 | } 50 | 51 | pub fn build(self) -> Client { 52 | let client = Arc::new(reqwest::Client::new()); 53 | Client { 54 | validator: ValidatorClient { 55 | url: self.validator_url.unwrap_or(default_client_validator_url()), 56 | client: client.clone(), 57 | }, 58 | guardian: GuardianClient { 59 | url: self.guardian_url.unwrap_or(default_client_guardian_url()), 60 | client: client.clone(), 61 | }, 62 | secure_signer: SecureSignerClient { 63 | url: self 64 | .secure_signer_url 65 | .unwrap_or(default_client_secure_signer_url()), 66 | client: client.clone(), 67 | }, 68 | } 69 | } 70 | 71 | pub fn validator_url(mut self, url: String) -> ClientBuilder { 72 | self.validator_url = Some(url); 73 | self 74 | } 75 | pub fn guardian_url(mut self, url: String) -> ClientBuilder { 76 | self.guardian_url = Some(url); 77 | self 78 | } 79 | pub fn secure_signer_url(mut self, url: String) -> ClientBuilder { 80 | self.secure_signer_url = Some(url); 81 | self 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/client/secure_signer.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | pub struct SecureSignerClient { 4 | pub url: String, 5 | pub client: Arc, 6 | } 7 | 8 | impl SecureSignerClient { 9 | pub async fn health(&self) -> bool { 10 | let Ok(resp) = self 11 | .client 12 | .get(format!("{}/upcheck", self.url)) 13 | .send() 14 | .await 15 | else { 16 | return false; 17 | }; 18 | resp.status() == reqwest::StatusCode::OK 19 | } 20 | 21 | pub async fn list_eth_keys(&self) -> anyhow::Result { 22 | Ok(self 23 | .client 24 | .get(format!("{}/eth/v1/keygen/secp256k1", self.url)) 25 | .send() 26 | .await? 27 | .json() 28 | .await?) 29 | } 30 | 31 | pub async fn list_bls_keys(&self) -> anyhow::Result { 32 | Ok(self 33 | .client 34 | .get(format!("{}/eth/v1/keystores", self.url)) 35 | .send() 36 | .await? 37 | .json() 38 | .await?) 39 | } 40 | 41 | pub async fn generate_eth_key(&self) -> anyhow::Result { 42 | Ok(self 43 | .client 44 | .post(format!("{}/eth/v1/keygen/secp256k1", self.url)) 45 | .send() 46 | .await? 47 | .json() 48 | .await?) 49 | } 50 | 51 | pub async fn generate_bls_key(&self) -> anyhow::Result { 52 | Ok(self 53 | .client 54 | .post(format!("{}/eth/v1/keygen/bls", self.url)) 55 | .send() 56 | .await? 57 | .json() 58 | .await?) 59 | } 60 | 61 | pub async fn secure_sign_bls( 62 | &self, 63 | public_key_hex: &str, 64 | signing_data: crate::eth2::eth_signing::BLSSignMsg, 65 | ) -> anyhow::Result { 66 | Ok(self 67 | .client 68 | .post(format!("{}/api/v1/eth2/sign/{public_key_hex}", self.url)) 69 | .json(&signing_data) 70 | .send() 71 | .await? 72 | .json() 73 | .await?) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/client/tests/guardian.rs: -------------------------------------------------------------------------------- 1 | use crate::eth2::eth_types::GENESIS_FORK_VERSION; 2 | 3 | use crate::client::traits::{GuardianClientTrait, ValidatorClientTrait}; 4 | 5 | #[tokio::test] 6 | async fn call_health_with_success() { 7 | let client = super::build_client(); 8 | assert!(client.guardian.health().await); 9 | } 10 | 11 | #[tokio::test] 12 | async fn call_attest_fresh_eth_with_success() { 13 | let client = super::build_client(); 14 | 15 | // This will panic if the call fails in any way 16 | let _resp: crate::enclave::types::KeyGenResponse = client 17 | .guardian 18 | .attest_fresh_eth_key("0x0000000000000000000000000000000000000000000000000000000000000000") 19 | .await 20 | .unwrap(); 21 | 22 | dbg!(_resp); 23 | } 24 | 25 | #[tokio::test] 26 | async fn call_attest_fresh_eth_with_failure_bad_blockhash() { 27 | let client = super::build_client(); 28 | 29 | // This will panic if the call fails in any way 30 | assert!(client 31 | .guardian 32 | .attest_fresh_eth_key("0xdeadbeef") // Not 32B 33 | .await 34 | .is_err()) 35 | } 36 | 37 | #[tokio::test] 38 | async fn call_validate_custody_with_success() { 39 | let n: usize = 8; 40 | 41 | // Setup Guardians 42 | let mut g_pks: Vec = Vec::new(); 43 | let mut g_sks: Vec = Vec::new(); 44 | for _ in 0..n { 45 | let (sk, pk) = crate::crypto::eth_keys::new_eth_key().unwrap(); 46 | g_pks.push(pk); 47 | g_sks.push(sk); 48 | } 49 | 50 | // Setup BlsKeygenPayload 51 | let client = crate::client::ClientBuilder::new().build(); 52 | let payload = crate::enclave::types::AttestFreshBlsKeyPayload { 53 | guardian_pubkeys: g_pks, 54 | withdrawal_credentials: [1; 32], 55 | threshold: 7, 56 | fork_version: GENESIS_FORK_VERSION, 57 | do_remote_attestation: false, 58 | }; 59 | 60 | let resp: crate::enclave::types::BlsKeygenPayload = client 61 | .validator 62 | .attest_fresh_bls_key(&payload) 63 | .await 64 | .unwrap(); 65 | 66 | dbg!(&resp); 67 | 68 | let pk_set: blsttc::PublicKeySet = 69 | blsttc::PublicKeySet::from_bytes(hex::decode(&resp.bls_pub_key_set).unwrap()).unwrap(); 70 | 71 | for i in 0..n { 72 | let g_sk = g_sks[i].clone(); 73 | let enc_sk_bytes = hex::decode(&resp.bls_enc_priv_key_shares[i]).unwrap(); 74 | let sk_bytes = crate::crypto::eth_keys::envelope_decrypt(&g_sk, &enc_sk_bytes).unwrap(); 75 | let sk_share = 76 | blsttc::SecretKeyShare::from_bytes(sk_bytes[..].try_into().unwrap()).unwrap(); 77 | assert_eq!( 78 | hex::encode(pk_set.public_key_share(i).to_bytes()), 79 | hex::encode(sk_share.public_key_share().to_bytes()), 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/client/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod guardian; 2 | mod secure_signer; 3 | mod validator; 4 | 5 | use crate::client::traits::{GuardianClientTrait, ValidatorClientTrait}; 6 | 7 | use crate::eth2::eth_types::GENESIS_FORK_VERSION; 8 | 9 | fn build_client() -> super::Client { 10 | let builder = super::ClientBuilder::new(); 11 | builder 12 | .validator_url("http://localhost:9003".to_string()) 13 | .guardian_url("http://localhost:9002".to_string()) 14 | .build() 15 | } 16 | 17 | #[tokio::test] 18 | async fn registration_flow_succeeds() { 19 | // let verify_remote_attestation = true; 20 | let verify_remote_attestation = false; 21 | let withdrawal_credentials = [1; 32]; 22 | let threshold = 1; 23 | let mrenclave = "758d532c6bd0a4297431623183e4d5dd5bbe274ebd8bdf9cecfcb8bffefaf186".into(); 24 | let mrsigner = "83d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e".into(); 25 | 26 | let client = build_client(); 27 | 28 | // Guardian generates fresh key 29 | let resp1: crate::enclave::types::KeyGenResponse = client 30 | .guardian 31 | .attest_fresh_eth_key("0x0000000000000000000000000000000000000000000000000000000000000000") 32 | .await 33 | .unwrap(); 34 | 35 | dbg!(&resp1); 36 | 37 | // Guardian's keys increase 38 | let r: crate::enclave::types::ListKeysResponse = client.guardian.list_eth_keys().await.unwrap(); 39 | assert!(dbg!(r.data).len() > 0); 40 | 41 | // Assume guardian called rotateGuardianKey() 42 | 43 | // Assume fetched from on-chain by validator: 44 | let guardian_pk = crate::crypto::eth_keys::eth_pk_from_hex_uncompressed(&resp1.pk_hex).unwrap(); 45 | 46 | // Validator generates fresh key and provisions to Guardian 47 | let payload = crate::enclave::types::AttestFreshBlsKeyPayload { 48 | guardian_pubkeys: vec![guardian_pk.clone()], 49 | withdrawal_credentials: withdrawal_credentials.clone(), 50 | threshold: threshold, 51 | fork_version: GENESIS_FORK_VERSION, 52 | do_remote_attestation: verify_remote_attestation, 53 | }; 54 | 55 | let resp2: crate::enclave::types::BlsKeygenPayload = client 56 | .validator 57 | .attest_fresh_bls_key(&payload) 58 | .await 59 | .unwrap(); 60 | 61 | dbg!(&resp2); 62 | 63 | // Assume validator is enqueued on-chain 64 | let req = crate::enclave::types::ValidateCustodyRequest { 65 | keygen_payload: resp2.clone(), 66 | guardian_enclave_public_key: guardian_pk, 67 | mrenclave, 68 | mrsigner, 69 | verify_remote_attestation, 70 | validator_index: 0, 71 | }; 72 | 73 | // Guardian validates they received custody 74 | let resp3: crate::enclave::types::ValidateCustodyResponse = 75 | client.guardian.validate_custody(req).await.unwrap(); 76 | 77 | dbg!(&resp3); 78 | 79 | // Guardian signs VEMs 80 | let req = crate::enclave::types::SignExitRequest { 81 | bls_pub_key_set: resp2.bls_pub_key_set.clone(), 82 | guardian_index: 0, 83 | validator_index: 0, 84 | fork_info: crate::eth2::eth_types::ForkInfo::default(), 85 | }; 86 | let resp4: crate::enclave::types::SignExitResponse = 87 | client.guardian.sign_exit(req).await.unwrap(); 88 | 89 | dbg!(resp4); 90 | } 91 | 92 | #[tokio::test] 93 | async fn test_cli_keygen_verified_by_guardians() { 94 | let client = build_client(); 95 | let verify_remote_attestation = false; 96 | let withdrawal_credentials = [1; 32]; 97 | let threshold = 1; 98 | let password = "password".to_string(); 99 | 100 | // Guardian generates fresh key 101 | let resp1: crate::enclave::types::KeyGenResponse = client 102 | .guardian 103 | .attest_fresh_eth_key("0x0000000000000000000000000000000000000000000000000000000000000000") 104 | .await 105 | .unwrap(); 106 | 107 | dbg!(&resp1); 108 | 109 | // Assume fetched from on-chain by validator: 110 | let guardian_pk = crate::crypto::eth_keys::eth_pk_from_hex_uncompressed(&resp1.pk_hex).unwrap(); 111 | 112 | // Validator generates a local encrypted keystore and provisions to Guardian 113 | let bls_keygen_input = crate::enclave::types::AttestFreshBlsKeyPayload { 114 | guardian_pubkeys: vec![guardian_pk.clone()], 115 | withdrawal_credentials: withdrawal_credentials.clone(), 116 | threshold: threshold, 117 | fork_version: GENESIS_FORK_VERSION, 118 | do_remote_attestation: verify_remote_attestation, 119 | }; 120 | let bls_keygen_payload = dbg!(crate::client::keygen::generate_bls_keystore_handler( 121 | bls_keygen_input, 122 | &password 123 | ) 124 | .unwrap()); 125 | 126 | // Assume validator is enqueued on-chain 127 | let req = crate::enclave::types::ValidateCustodyRequest { 128 | keygen_payload: bls_keygen_payload.clone(), 129 | guardian_enclave_public_key: guardian_pk, 130 | mrenclave: "".to_string(), 131 | mrsigner: "".to_string(), 132 | verify_remote_attestation, 133 | validator_index: 0, 134 | }; 135 | 136 | // Guardian validates they received custody 137 | let resp3: crate::enclave::types::ValidateCustodyResponse = 138 | client.guardian.validate_custody(req).await.unwrap(); 139 | 140 | dbg!(&resp3); 141 | 142 | // Guardian signs VEMs 143 | let req = crate::enclave::types::SignExitRequest { 144 | bls_pub_key_set: bls_keygen_payload.bls_pub_key_set.clone(), 145 | guardian_index: 0, 146 | validator_index: 0, 147 | fork_info: crate::eth2::eth_types::ForkInfo::default(), 148 | }; 149 | let _resp4: crate::enclave::types::SignExitResponse = 150 | client.guardian.sign_exit(req).await.unwrap(); 151 | } 152 | -------------------------------------------------------------------------------- /src/client/tests/secure_signer.rs: -------------------------------------------------------------------------------- 1 | #[tokio::test] 2 | async fn call_health_with_success() { 3 | let client = super::build_client(); 4 | assert!(client.secure_signer.health().await); 5 | } 6 | 7 | #[tokio::test] 8 | async fn call_generate_eth_key_with_success() { 9 | let client = super::build_client(); 10 | client.secure_signer.generate_eth_key().await.unwrap(); 11 | } 12 | 13 | #[tokio::test] 14 | async fn call_generate_bls_key_with_success() { 15 | let client = super::build_client(); 16 | client.secure_signer.generate_bls_key().await.unwrap(); 17 | } 18 | 19 | #[tokio::test] 20 | async fn call_list_eth_keys_with_success() { 21 | let client = super::build_client(); 22 | client.secure_signer.generate_eth_key().await.unwrap(); 23 | assert!( 24 | client 25 | .secure_signer 26 | .list_eth_keys() 27 | .await 28 | .unwrap() 29 | .data 30 | .len() 31 | > 1 32 | ); 33 | } 34 | 35 | #[tokio::test] 36 | async fn call_list_bls_keys_with_success() { 37 | let client = super::build_client(); 38 | client.secure_signer.generate_bls_key().await.unwrap(); 39 | assert!( 40 | client 41 | .secure_signer 42 | .list_bls_keys() 43 | .await 44 | .unwrap() 45 | .data 46 | .len() 47 | > 1 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /src/client/traits/mod.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | 3 | #[async_trait] 4 | pub trait GuardianClientTrait { 5 | async fn health(&self) -> bool; 6 | 7 | async fn attest_fresh_eth_key( 8 | &self, 9 | blockhash: &str, 10 | ) -> anyhow::Result; 11 | 12 | async fn list_eth_keys(&self) -> anyhow::Result; 13 | 14 | async fn validate_custody( 15 | &self, 16 | request: crate::enclave::types::ValidateCustodyRequest, 17 | ) -> anyhow::Result; 18 | 19 | async fn sign_exit( 20 | &self, 21 | request: crate::enclave::types::SignExitRequest, 22 | ) -> anyhow::Result; 23 | } 24 | 25 | #[async_trait] 26 | pub trait ValidatorClientTrait { 27 | async fn health(&self) -> bool; 28 | 29 | async fn attest_fresh_bls_key( 30 | &self, 31 | payload: &crate::enclave::types::AttestFreshBlsKeyPayload, 32 | ) -> anyhow::Result; 33 | 34 | async fn list_bls_keys(&self) -> anyhow::Result; 35 | 36 | async fn sign_voluntary_exit_message( 37 | &self, 38 | bls_pk_hex: String, 39 | epoch: crate::eth2::eth_types::Epoch, 40 | validator_index: crate::eth2::eth_types::ValidatorIndex, 41 | fork_info: crate::eth2::eth_types::ForkInfo, 42 | ) -> anyhow::Result; 43 | } 44 | -------------------------------------------------------------------------------- /src/client/validator.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use async_trait::async_trait; 4 | 5 | use crate::strip_0x_prefix; 6 | 7 | use crate::client::traits::ValidatorClientTrait; 8 | 9 | pub struct ValidatorClient { 10 | pub url: String, 11 | pub client: Arc, 12 | } 13 | 14 | #[async_trait] 15 | impl ValidatorClientTrait for ValidatorClient { 16 | async fn health(&self) -> bool { 17 | let Ok(resp) = self 18 | .client 19 | .get(format!("{}/upcheck", self.url)) 20 | .send() 21 | .await 22 | else { 23 | return false; 24 | }; 25 | resp.status() == reqwest::StatusCode::OK 26 | } 27 | 28 | async fn attest_fresh_bls_key( 29 | &self, 30 | payload: &crate::enclave::types::AttestFreshBlsKeyPayload, 31 | ) -> anyhow::Result { 32 | let resp = self 33 | .client 34 | .post(format!("{}/bls/v1/keygen", self.url)) 35 | .json(payload) 36 | .send() 37 | .await?; 38 | 39 | let resp_status = resp.status(); 40 | if !resp_status.is_success() { 41 | let body = resp.text().await?; 42 | return Err(anyhow::anyhow!(body)); 43 | } 44 | 45 | let resp_json = resp.json().await?; 46 | Ok(resp_json) 47 | } 48 | 49 | async fn list_bls_keys(&self) -> anyhow::Result { 50 | let resp = self 51 | .client 52 | .get(format!("{}/eth/v1/keystores", self.url)) 53 | .send() 54 | .await?; 55 | 56 | let resp_status = resp.status(); 57 | if !resp_status.is_success() { 58 | let body = resp.text().await?; 59 | return Err(anyhow::anyhow!(body)); 60 | } 61 | 62 | let resp_json = resp.json().await?; 63 | Ok(resp_json) 64 | } 65 | 66 | async fn sign_voluntary_exit_message( 67 | &self, 68 | bls_pk_hex: String, 69 | epoch: crate::eth2::eth_types::Epoch, 70 | validator_index: crate::eth2::eth_types::ValidatorIndex, 71 | fork_info: crate::eth2::eth_types::ForkInfo, 72 | ) -> anyhow::Result { 73 | let bls_pk_hex: String = strip_0x_prefix!(bls_pk_hex); 74 | let vem = crate::eth2::eth_types::VoluntaryExitRequest { 75 | signingRoot: None, 76 | fork_info: fork_info, 77 | voluntary_exit: crate::eth2::eth_types::VoluntaryExit { 78 | epoch, 79 | validator_index, 80 | }, 81 | }; 82 | let req = crate::eth2::eth_signing::BLSSignMsg::VOLUNTARY_EXIT(vem); 83 | 84 | let resp = self 85 | .client 86 | .post(format!("{}/api/v1/eth2/sign/{}", self.url, bls_pk_hex)) 87 | .json(&req) 88 | .send() 89 | .await?; 90 | 91 | let resp_status = resp.status(); 92 | if !resp_status.is_success() { 93 | let body = resp.text().await?; 94 | return Err(anyhow::anyhow!(body)); 95 | } 96 | 97 | let resp_json = resp.json().await?; 98 | Ok(resp_json) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const KEYS_DIR: &str = "./etc/keys/"; 2 | pub const BLS_KEYS_DIR: &str = "./etc/keys/bls_keys/"; 3 | pub const ETH_KEYS_DIR: &str = "./etc/keys/eth_keys/"; 4 | pub const SLASHING_PROTECTION_DIR: &str = "./etc/slashing/"; 5 | 6 | pub const BLS_SIG_BYTES: usize = 96; 7 | pub const BLS_PUB_KEY_BYTES: usize = 48; 8 | pub const BLS_PRIV_KEY_BYTES: usize = 32; 9 | pub const ETH_COMPRESSED_PK_BYTES: usize = 33; 10 | pub const ETH_UNCOMPRESSED_PK_BYTES: usize = 65; 11 | pub const ETH_SIGNATURE_BYTES: usize = 64; 12 | 13 | pub const ALLOW_GROWABLE_SLASH_PROTECTION_DB: bool = false; 14 | 15 | pub const FULL_DEPOSIT_AMOUNT: u64 = 32000000000; 16 | pub const WITHDRAWAL_CREDENTIALS_BYTES: usize = 32; 17 | -------------------------------------------------------------------------------- /src/crypto/keystore.rs: -------------------------------------------------------------------------------- 1 | use crate::strip_0x_prefix; 2 | 3 | use super::eth_keys; 4 | use anyhow::{Context, Result}; 5 | use ecies::SecretKey as EthSecretKey; 6 | use eth_keystore::decrypt_keystore; 7 | 8 | pub fn import_keystore( 9 | keystore: &String, 10 | ct_password_hex: &String, 11 | envelope_sk: &EthSecretKey, 12 | ) -> Result> { 13 | // Decrypt the password 14 | let ct_password_hex: String = strip_0x_prefix!(ct_password_hex); 15 | let ct_password_bytes = hex::decode(ct_password_hex)?; 16 | let password_bytes = eth_keys::envelope_decrypt(envelope_sk, &ct_password_bytes)?; 17 | let password = String::from_utf8(password_bytes).with_context(|| "non-utf8 password")?; 18 | decrypt_keystore(keystore, password).with_context(|| "Failed to decrypt keystore") 19 | } 20 | 21 | #[cfg(test)] 22 | pub mod keystore_tests { 23 | use crate::crypto::eth_keys; 24 | 25 | use super::import_keystore; 26 | use hex::FromHex; 27 | 28 | #[test] 29 | /// Test vec from: https://eips.ethereum.org/EIPS/eip-2335 30 | fn test_import_keystore() { 31 | let keystore = r#" 32 | { 33 | "crypto": { 34 | "kdf": { 35 | "function": "scrypt", 36 | "params": { 37 | "dklen": 32, 38 | "n": 262144, 39 | "p": 1, 40 | "r": 8, 41 | "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" 42 | }, 43 | "message": "" 44 | }, 45 | "checksum": { 46 | "function": "sha256", 47 | "params": {}, 48 | "message": "d2217fe5f3e9a1e34581ef8a78f7c9928e436d36dacc5e846690a5581e8ea484" 49 | }, 50 | "cipher": { 51 | "function": "aes-128-ctr", 52 | "params": { 53 | "iv": "264daa3f303d7259501c93d997d84fe6" 54 | }, 55 | "message": "06ae90d55fe0a6e9c5c3bc5b170827b2e5cce3929ed3f116c2811e6366dfe20f" 56 | } 57 | }, 58 | "description": "This is a test keystore that uses scrypt to secure the secret.", 59 | "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", 60 | "path": "m/12381/60/3141592653/589793238", 61 | "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", 62 | "version": 4 63 | }"#.to_string(); 64 | 65 | let (eth_sk, eth_pk) = eth_keys::new_eth_key().unwrap(); 66 | let encoded_pw = hex::decode("7465737470617373776f7264f09f9491").unwrap(); 67 | let ct_pw = eth_keys::envelope_encrypt(ð_pk, &encoded_pw).unwrap(); 68 | 69 | let bls_sk_bytes = import_keystore(&keystore, &hex::encode(ct_pw), ð_sk).unwrap(); 70 | 71 | assert_eq!( 72 | bls_sk_bytes, 73 | Vec::from_hex("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f") 74 | .unwrap() 75 | ); 76 | } 77 | 78 | #[test] 79 | fn test_encrypt_decrypt_keystore() { 80 | std::fs::create_dir_all("./test_keys").unwrap(); 81 | let secret = 82 | Vec::from_hex("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f") 83 | .unwrap(); 84 | let dir = std::path::Path::new("./test_keys"); 85 | let mut rng = rand::thread_rng(); 86 | let name = eth_keystore::encrypt_key(&dir, &mut rng, &secret, "newpassword", None).unwrap(); 87 | 88 | let keypath = dir.join(&name); 89 | assert_eq!( 90 | eth_keystore::decrypt_key(&keypath, "newpassword").unwrap(), 91 | secret 92 | ); 93 | assert!(eth_keystore::decrypt_key(&keypath, "notanewpassword").is_err()); 94 | assert!(std::fs::remove_file(&keypath).is_ok()); 95 | std::fs::remove_dir_all("./test_keys").ok(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/crypto/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bls_keys; 2 | pub mod eth_keys; 3 | pub mod keystore; 4 | -------------------------------------------------------------------------------- /src/enclave/guardian/handlers/attest_fresh_eth_key_with_blockhash.rs: -------------------------------------------------------------------------------- 1 | use axum::{response::IntoResponse, Json}; 2 | use log::{error, info}; 3 | 4 | /// Generates, saves, and performs remote attestation on a new ETH key. Returns a `KeyGenResponse` on success. 5 | pub async fn handler( 6 | Json(request_data): Json, 7 | ) -> axum::response::Response { 8 | info!("eth_key_gen_with_blockhash_service()"); 9 | match crate::enclave::guardian::attest_new_eth_key_with_blockhash(&request_data.blockhash) { 10 | Ok((evidence, eth_pk)) => { 11 | let resp = crate::enclave::types::KeyGenResponse::from_eth_key(eth_pk, evidence); 12 | (axum::http::status::StatusCode::CREATED, Json(resp)).into_response() 13 | } 14 | Err(e) => { 15 | error!("eth_key_gen_with_blockhash_service() failed with: {}", e); 16 | ( 17 | axum::http::status::StatusCode::INTERNAL_SERVER_ERROR, 18 | format!("eth_key_gen_with_blockhash_service failed: {:?}", e), 19 | ) 20 | .into_response() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/enclave/guardian/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod attest_fresh_eth_key_with_blockhash; 2 | pub mod sign_exit; 3 | pub mod validate_custody; 4 | -------------------------------------------------------------------------------- /src/enclave/guardian/handlers/sign_exit.rs: -------------------------------------------------------------------------------- 1 | use axum::response::IntoResponse; 2 | use axum::Json; 3 | use log::{error, info}; 4 | 5 | pub async fn handler( 6 | Json(request): Json, 7 | ) -> axum::response::Response { 8 | info!("sign_exit()"); 9 | match crate::enclave::guardian::sign_voluntary_exit_message(request) { 10 | Ok(resp) => (axum::http::status::StatusCode::OK, Json(resp)).into_response(), 11 | 12 | Err(e) => { 13 | error!("{:?}", e); 14 | ( 15 | axum::http::status::StatusCode::INTERNAL_SERVER_ERROR, 16 | format!("{}", e), 17 | ) 18 | .into_response() 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/enclave/guardian/handlers/validate_custody.rs: -------------------------------------------------------------------------------- 1 | use axum::response::IntoResponse; 2 | use axum::Json; 3 | use log::{error, info}; 4 | 5 | pub async fn handler( 6 | Json(request): Json, 7 | ) -> axum::response::Response { 8 | info!("validate_custody()"); 9 | match crate::enclave::guardian::verify_and_sign_custody_received(request).await { 10 | Ok(resp) => (axum::http::status::StatusCode::OK, Json(resp)).into_response(), 11 | 12 | Err(e) => { 13 | error!("{:?}", e); 14 | ( 15 | axum::http::status::StatusCode::INTERNAL_SERVER_ERROR, 16 | format!("{}", e), 17 | ) 18 | .into_response() 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/enclave/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod guardian; 2 | pub mod secure_signer; 3 | pub mod shared; 4 | mod test; 5 | pub mod types; 6 | pub mod validator; 7 | -------------------------------------------------------------------------------- /src/enclave/secure_signer/handlers/bls_keygen.rs: -------------------------------------------------------------------------------- 1 | use axum::response::IntoResponse; 2 | use axum::Json; 3 | use log::{error, info}; 4 | 5 | /// Generates, saves, and performs remote attestation on a new ETH key. Returns a `KeyGenResponse` on success. 6 | pub async fn handler() -> axum::response::Response { 7 | info!("eth_bls_gen_service()"); 8 | match crate::enclave::secure_signer::attest_new_bls_key() { 9 | Ok((evidence, eth_pk)) => { 10 | let resp = crate::enclave::types::KeyGenResponse::from_bls_key(eth_pk, evidence); 11 | (axum::http::status::StatusCode::CREATED, Json(resp)).into_response() 12 | } 13 | Err(e) => { 14 | error!("bls_key_gen_service() failed with: {}", e); 15 | ( 16 | axum::http::status::StatusCode::INTERNAL_SERVER_ERROR, 17 | format!("bls_key_gen_service failed: {:?}", e), 18 | ) 19 | .into_response() 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/enclave/secure_signer/handlers/eth_keygen.rs: -------------------------------------------------------------------------------- 1 | use axum::response::IntoResponse; 2 | use axum::Json; 3 | use log::{error, info}; 4 | 5 | /// Generates, saves, and performs remote attestation on a new ETH key. Returns a `KeyGenResponse` on success. 6 | pub async fn handler() -> axum::response::Response { 7 | info!("eth_key_gen_service()"); 8 | match crate::enclave::secure_signer::attest_new_eth_key() { 9 | Ok((evidence, eth_pk)) => { 10 | let resp = crate::enclave::types::KeyGenResponse::from_eth_key(eth_pk, evidence); 11 | (axum::http::status::StatusCode::CREATED, Json(resp)).into_response() 12 | } 13 | Err(e) => { 14 | error!("eth_key_gen_service() failed with: {}", e); 15 | ( 16 | axum::http::status::StatusCode::INTERNAL_SERVER_ERROR, 17 | format!("eth_key_gen_service failed: {:?}", e), 18 | ) 19 | .into_response() 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/enclave/secure_signer/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bls_keygen; 2 | pub mod eth_keygen; 3 | pub mod validator_deposit; 4 | -------------------------------------------------------------------------------- /src/enclave/secure_signer/handlers/validator_deposit.rs: -------------------------------------------------------------------------------- 1 | use axum::{response::IntoResponse, Json}; 2 | use log::info; 3 | use ssz::Encode; 4 | 5 | /// Signs the DepositMessage inside the DepositRequest and returns a DepositResponse 6 | pub async fn handler( 7 | Json(req): Json, 8 | ) -> axum::response::Response { 9 | let bls_pk_hex = hex::encode(req.deposit.pubkey.as_ssz_bytes()); 10 | // Sanitize the input bls_pk_hex 11 | let bls_pk_hex = match crate::crypto::bls_keys::sanitize_bls_pk_hex(&bls_pk_hex) { 12 | Ok(pk) => pk, 13 | Err(e) => { 14 | return ( 15 | axum::http::status::StatusCode::BAD_REQUEST, 16 | format!("Bad bls_pk_hex, {:?}", e), 17 | ) 18 | .into_response(); 19 | } 20 | }; 21 | 22 | if !crate::io::key_management::bls_key_exists(&bls_pk_hex) { 23 | return ( 24 | axum::http::status::StatusCode::PRECONDITION_FAILED, 25 | format!("This validator key does not exist"), 26 | ) 27 | .into_response(); 28 | } 29 | 30 | info!("Deposit request for validator pubkey: {bls_pk_hex}"); 31 | info!("Request:\n{:#?}", req); 32 | 33 | match crate::eth2::eth_signing::get_deposit_signature( 34 | bls_pk_hex, 35 | req.deposit, 36 | req.genesis_fork_version, 37 | ) { 38 | Ok(resp) => (axum::http::status::StatusCode::OK, Json(resp)).into_response(), 39 | Err(e) => { 40 | return ( 41 | axum::http::status::StatusCode::INTERNAL_SERVER_ERROR, 42 | format!("Deposit signing operation failed: {:?}", e), 43 | ) 44 | .into_response(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/enclave/secure_signer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod handlers; 2 | use anyhow::{Context, Result}; 3 | 4 | fn attest_new_eth_key() -> Result<( 5 | crate::io::remote_attestation::AttestationEvidence, 6 | ecies::PublicKey, 7 | )> { 8 | // Generate a fresh SECP256K1 ETH keypair (saving ETH private key) 9 | let pk = crate::crypto::eth_keys::eth_key_gen()?; 10 | 11 | // Commit to the payload 12 | let proof = 13 | crate::io::remote_attestation::AttestationEvidence::new(&pk.serialize_compressed())?; 14 | Ok((proof, pk)) 15 | } 16 | 17 | fn attest_new_bls_key() -> Result<( 18 | crate::io::remote_attestation::AttestationEvidence, 19 | blsttc::PublicKey, 20 | )> { 21 | // Generate a fresh BLS keypair (saving BLS private key) 22 | let sk = crate::crypto::bls_keys::new_bls_key(0); 23 | let pk = sk.public_keys().public_key(); 24 | crate::crypto::bls_keys::save_bls_key(&sk).with_context(|| "Failed to save BLS key")?; 25 | 26 | // Create a new slashing protection database 27 | crate::eth2::slash_protection::SlashingProtectionData::from_pk_hex(&pk.to_hex())?.write()?; 28 | 29 | // Commit to the payload 30 | let proof = crate::io::remote_attestation::AttestationEvidence::new(&pk.to_bytes())?; 31 | Ok((proof, pk)) 32 | } 33 | -------------------------------------------------------------------------------- /src/enclave/shared/handlers/health.rs: -------------------------------------------------------------------------------- 1 | use axum::response::IntoResponse; 2 | 3 | pub async fn handler() -> axum::response::Response { 4 | (axum::http::status::StatusCode::OK).into_response() 5 | } 6 | -------------------------------------------------------------------------------- /src/enclave/shared/handlers/list_bls_keys.rs: -------------------------------------------------------------------------------- 1 | use axum::{response::IntoResponse, Json}; 2 | use log::{error, info}; 3 | 4 | use crate::io::key_management; 5 | 6 | pub async fn handler() -> axum::response::Response { 7 | info!("list_bls_keys()"); 8 | match key_management::list_bls_keys() { 9 | Ok(list_res) => { 10 | let resp = crate::enclave::types::ListKeysResponse::new(list_res); 11 | (axum::http::status::StatusCode::OK, Json(resp)).into_response() 12 | } 13 | Err(e) => { 14 | error!("list_bls_keys() failed with: {:?}", e); 15 | axum::http::status::StatusCode::INTERNAL_SERVER_ERROR.into_response() 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/enclave/shared/handlers/list_bls_keys_for_vc.rs: -------------------------------------------------------------------------------- 1 | use axum::{response::IntoResponse, Json}; 2 | use log::{error, info}; 3 | 4 | use crate::{io::key_management, strip_0x_prefix}; 5 | 6 | pub async fn handler() -> axum::response::Response { 7 | info!("list_bls_keys_for_vc()"); 8 | match key_management::list_bls_keys() { 9 | Ok(list_res) => { 10 | // safely prepend the response with "0x" to match the expected format 11 | let list_res = list_res 12 | .iter() 13 | .map(|x| { 14 | let stripped: &str = strip_0x_prefix!(x); 15 | format!("0x{}", stripped) 16 | }) 17 | .collect::>(); 18 | (axum::http::status::StatusCode::OK, Json(list_res)).into_response() 19 | } 20 | Err(e) => { 21 | error!("list_bls_keys_for_vc() failed with: {:?}", e); 22 | axum::http::status::StatusCode::INTERNAL_SERVER_ERROR.into_response() 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/enclave/shared/handlers/list_eth_keys.rs: -------------------------------------------------------------------------------- 1 | use axum::{response::IntoResponse, Json}; 2 | use log::{error, info}; 3 | 4 | use crate::io::key_management; 5 | 6 | pub async fn handler() -> axum::response::Response { 7 | info!("list_eth_keys()"); 8 | match key_management::list_eth_keys() { 9 | Ok(list_res) => { 10 | let resp = crate::enclave::types::ListKeysResponse::new(list_res); 11 | (axum::http::status::StatusCode::OK, Json(resp)).into_response() 12 | } 13 | Err(e) => { 14 | error!("list_eth_keys() failed with: {:?}", e); 15 | axum::http::status::StatusCode::INTERNAL_SERVER_ERROR.into_response() 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/enclave/shared/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod health; 2 | pub mod list_bls_keys; 3 | pub mod list_bls_keys_for_vc; 4 | pub mod list_eth_keys; 5 | pub mod secure_sign_bls; 6 | 7 | #[derive(Clone)] 8 | pub struct AppState { 9 | pub genesis_fork_version: crate::eth2::eth_types::Version, 10 | } 11 | -------------------------------------------------------------------------------- /src/enclave/shared/handlers/secure_sign_bls.rs: -------------------------------------------------------------------------------- 1 | use axum::{ 2 | extract::{Path, State}, 3 | Json, 4 | }; 5 | use log::info; 6 | 7 | /// Signs the specific type of request 8 | /// Maintains compatibility with https://consensys.github.io/web3signer/web3signer-eth2.html#tag/Signing 9 | pub async fn handler( 10 | Path(bls_pk_hex): Path, 11 | State(state): State, 12 | Json(req): Json, 13 | ) -> axum::response::Response { 14 | info!("secure_sign_bls()"); 15 | crate::enclave::shared::sign_validator_message(Path(bls_pk_hex), State(state), Json(req)) 16 | } 17 | -------------------------------------------------------------------------------- /src/enclave/test/integration.rs: -------------------------------------------------------------------------------- 1 | // #[test] 2 | // fn guardian_receives_keyshard_from_validator_with_custody() { 3 | // let eigen_pod_data = crate::enclave::types::EigenPodData { 4 | // eigen_pod_manager_address: ethers::abi::Address::random(), 5 | // eigen_pod_beacon_address: ethers::abi::Address::random(), 6 | // beacon_proxy_bytecode: hex::encode(vec![1, 2, 3, 4, 5]), 7 | // puffer_pool_address: ethers::abi::Address::random(), 8 | // eigen_pod_proxy_init_code: hex::encode(vec![1, 2, 3, 4, 5]), 9 | // pod_account_owners: vec![ethers::abi::Address::random()], 10 | // }; 11 | 12 | // let (_evidence, guardian_enclave_public_key) = 13 | // crate::enclave::guardian::attest_new_eth_key_with_blockhash( 14 | // "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563", 15 | // ) 16 | // .unwrap(); 17 | 18 | // let guardians = vec![guardian_enclave_public_key]; 19 | 20 | // let keygen_payload = crate::enclave::validator::attest_fresh_bls_key( 21 | // hex::encode(vec![0u8; 32]), 22 | // eigen_pod_data.clone(), 23 | // guardians, 24 | // 1, 25 | // crate::eth2::eth_types::GENESIS_FORK_VERSION, 26 | // ) 27 | // .unwrap(); 28 | 29 | // let (signature, message, has_custody) = 30 | // crate::enclave::guardian::handlers::validate_custody::generate_signature( 31 | // keygen_payload, 32 | // 0, 33 | // guardian_enclave_public_key, 34 | // eigen_pod_data, 35 | // ) 36 | // .unwrap(); 37 | 38 | // // Check if signature is valid 39 | // assert_eq!( 40 | // libsecp256k1::verify(&message, &signature, &guardian_enclave_public_key), 41 | // true 42 | // ); 43 | // assert_eq!(has_custody, true); 44 | // } 45 | 46 | // TODO: fix this test 47 | -------------------------------------------------------------------------------- /src/enclave/test/mod.rs: -------------------------------------------------------------------------------- 1 | mod integration; 2 | -------------------------------------------------------------------------------- /src/enclave/validator/handlers/attest_fresh_bls_key.rs: -------------------------------------------------------------------------------- 1 | use axum::{response::IntoResponse, Json}; 2 | use log::{error, info}; 3 | 4 | pub async fn handler( 5 | Json(keygen_payload): Json, 6 | ) -> axum::response::Response { 7 | info!("attest_fresh_bls_key()"); 8 | match crate::enclave::validator::attest_fresh_bls_key( 9 | keygen_payload.withdrawal_credentials, 10 | keygen_payload.guardian_pubkeys, 11 | keygen_payload.threshold, 12 | keygen_payload.fork_version, 13 | keygen_payload.do_remote_attestation, 14 | ) { 15 | Ok(keygen_result) => { 16 | (axum::http::status::StatusCode::CREATED, Json(keygen_result)).into_response() 17 | } 18 | Err(e) => { 19 | error!("attest_fresh_bls_key() failed with: {:?}", e); 20 | axum::http::status::StatusCode::INTERNAL_SERVER_ERROR.into_response() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/enclave/validator/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod attest_fresh_bls_key; 2 | -------------------------------------------------------------------------------- /src/eth2/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod eth_signing; 2 | pub mod eth_types; 3 | pub mod slash_protection; 4 | -------------------------------------------------------------------------------- /src/io/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod key_management; 2 | pub mod remote_attestation; 3 | -------------------------------------------------------------------------------- /src/io/ra_config.h: -------------------------------------------------------------------------------- 1 | #ifndef REMOTE_ATTESTATION_RA_CONFIG_H_ 2 | #define REMOTE_ATTESTATION_RA_CONFIG_H_ 3 | 4 | #include 5 | 6 | #include "tee/common/error.h" 7 | #include "tee/common/log.h" 8 | #include "tee/ra_conf_api.h" 9 | 10 | constexpr char kRaConf[] = "ra_config.json"; 11 | 12 | constexpr char kConfIasServer[] = "ias_url"; 13 | constexpr char kConfIasCert[] = "ias_sp_cert_file"; 14 | constexpr char kConfIasKey[] = "ias_sp_key_file"; 15 | constexpr char kConfIasAccessKey[] = "ias_access_key"; 16 | constexpr char kConfSPID[] = "enclave_spid"; 17 | 18 | #define RA_CONF_STR(name) TeeConfGetStr(kRaConf, name) 19 | #define RA_CONF_FILE(name) TeeConfGetStr(kRaConf, name) 20 | 21 | #endif // REMOTE_ATTESTATION_RA_CONFIG_H_ 22 | -------------------------------------------------------------------------------- /src/io/ra_wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "ra_config.h" 5 | #include "tee/ra_quote.h" 6 | 7 | 8 | extern "C" 9 | void do_epid_ra(uint8_t data[64], char * report, char * signature, char * signing_cert) { 10 | // 64 Byte report data to embed in the intel-signed report 11 | sgx_report_data_t report_data = {0}; 12 | for (int i = 0; i < 64; ++i) { 13 | report_data.d[i] = data[i]; 14 | // std::cout << "data[" << i << "]: " << (int) report_data.d[i] << std::endl; 15 | } 16 | 17 | // Don't need to set IAS key/cert when we used accesskey authentication 18 | RaIasServerCfg ias_server; 19 | ias_server.endpoint = RA_CONF_STR(kConfIasServer); 20 | ias_server.accesskey = RA_CONF_STR(kConfIasAccessKey); 21 | std::string spid = RA_CONF_STR(kConfSPID); 22 | 23 | ra::occlum::RaEnclaveQuote ra; 24 | ra::occlum::RaIasReport ias_report; 25 | int ret = ra.GetEnclaveIasReport(ias_server, spid, report_data, &ias_report); 26 | if (ret) { 27 | printf("Fail to get quote or fetch report, error code is %x!\n", ret); 28 | } else { 29 | // Write the IAS return values to the input buffers 30 | strcpy(report, ias_report.response_body().c_str()); 31 | strcpy(signature, ias_report.b64_signature().c_str()); 32 | strcpy(signing_cert, ias_report.signing_cert().c_str()); 33 | } 34 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // #[macro_use] 2 | extern crate anyhow; 3 | extern crate env_logger; 4 | extern crate libc; 5 | 6 | pub mod constants; 7 | pub mod crypto; 8 | pub mod enclave; 9 | // TODO: Check lighthouse if we can replace 10 | pub mod client; 11 | pub mod eth2; 12 | pub mod io; 13 | 14 | #[macro_export] 15 | macro_rules! strip_0x_prefix { 16 | ($hex:expr) => { 17 | $hex.strip_prefix("0x").unwrap_or(&$hex).into() 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | TESTS_TAG := v1.4.0-alpha.0 2 | TESTS = general minimal mainnet 3 | TARBALLS = $(patsubst %,%-$(TESTS_TAG).tar.gz,$(TESTS)) 4 | 5 | REPO_NAME := consensus-spec-tests 6 | OUTPUT_DIR := ./$(REPO_NAME) 7 | BASE_URL := https://github.com/ethereum/$(REPO_NAME)/releases/download/$(TESTS_TAG) 8 | 9 | BLS_TEST_REPO_NAME := bls12-381-tests 10 | BLS_TEST_TAG := v0.1.2 11 | BLS_TEST = bls_tests_yaml 12 | BLS_TARBALL = $(patsubst %,%-$(BLS_TEST_TAG).tar.gz,$(BLS_TEST)) 13 | BLS_OUTPUT_DIR := $(OUTPUT_DIR)/$(BLS_TEST_REPO_NAME) 14 | BLS_BASE_URL := https://github.com/ethereum/$(BLS_TEST_REPO_NAME)/releases/download/$(BLS_TEST_TAG) 15 | 16 | WGET := $(if $(LIGHTHOUSE_GITHUB_TOKEN),wget --header="Authorization: $(LIGHTHOUSE_GITHUB_TOKEN)",wget) 17 | 18 | all: 19 | make $(OUTPUT_DIR) 20 | # make $(BLS_OUTPUT_DIR) 21 | 22 | $(OUTPUT_DIR): $(TARBALLS) 23 | mkdir $(OUTPUT_DIR) 24 | for test_tarball in $^; do \ 25 | tar -xzf $$test_tarball -C $(OUTPUT_DIR);\ 26 | done 27 | 28 | $(BLS_OUTPUT_DIR): 29 | mkdir $(BLS_OUTPUT_DIR) 30 | $(WGET) $(BLS_BASE_URL)/$(BLS_TEST).tar.gz -O $(BLS_TARBALL) 31 | tar -xzf $(BLS_TARBALL) -C $(BLS_OUTPUT_DIR) 32 | 33 | %-$(TESTS_TAG).tar.gz: 34 | $(WGET) $(BASE_URL)/$*.tar.gz -O $@ 35 | 36 | clean-test-files: 37 | rm -rf $(OUTPUT_DIR) $(BLS_OUTPUT_DIR) 38 | 39 | clean-archives: 40 | rm -f $(TARBALLS) $(BLS_TARBALL) 41 | 42 | clean: clean-test-files clean-archives 43 | 44 | .PHONY: clean clean-archives clean-test-files -------------------------------------------------------------------------------- /tests/common/bls_keygen_helper.rs: -------------------------------------------------------------------------------- 1 | use super::read_secure_signer_port; 2 | 3 | use anyhow::{Context, Result}; 4 | use blsttc::PublicKey; 5 | use puffersecuresigner::{constants::BLS_PUB_KEY_BYTES, strip_0x_prefix}; 6 | use reqwest::{Client, Response, StatusCode}; 7 | use serde_json; 8 | use std::env; 9 | 10 | pub async fn mock_bls_keygen_route() -> Result { 11 | let test_app = axum::Router::new() 12 | .route( 13 | "/eth/v1/keygen/bls", 14 | axum::routing::post( 15 | puffersecuresigner::enclave::secure_signer::handlers::bls_keygen::handler, 16 | ), 17 | ) 18 | .into_make_service(); 19 | 20 | let server = axum_test::TestServer::new(test_app)?; 21 | 22 | Ok(server.post("/eth/v1/keygen/bls").await) 23 | } 24 | 25 | pub async fn request_bls_keygen_route(port: u16) -> Result { 26 | let client = Client::new(); 27 | let url = format!("http://localhost:{}/eth/v1/keygen/bls", port); 28 | let response = client 29 | .post(&url) 30 | .header("Content-Type", "application/json") 31 | .send() 32 | .await; 33 | 34 | response 35 | } 36 | 37 | pub async fn make_bls_keygen_request( 38 | port: Option, 39 | ) -> Result<( 40 | puffersecuresigner::enclave::types::KeyGenResponse, 41 | StatusCode, 42 | )> { 43 | match port { 44 | // Make the actual http req to a running Secure-Aggregator instance 45 | Some(p) => { 46 | let resp = request_bls_keygen_route(p).await?; 47 | dbg!(&resp); 48 | let status = resp.status(); 49 | let sig: puffersecuresigner::enclave::types::KeyGenResponse = resp 50 | .json() 51 | .await 52 | .with_context(|| format!("Failed to parse to KeyGenResponse"))?; 53 | Ok((sig, status)) 54 | } 55 | // Mock an http request 56 | None => { 57 | let resp = mock_bls_keygen_route().await?; 58 | let sig: puffersecuresigner::enclave::types::KeyGenResponse = 59 | serde_json::from_slice(resp.as_bytes()) 60 | .with_context(|| "Failed to parse to KeyGenResponse")?; 61 | Ok((sig, resp.status_code().into())) 62 | } 63 | } 64 | } 65 | 66 | pub async fn register_new_bls_key( 67 | port: Option, 68 | ) -> puffersecuresigner::enclave::types::KeyGenResponse { 69 | let (resp, status) = make_bls_keygen_request(port).await.unwrap(); 70 | assert_eq!(status, 201); 71 | resp 72 | } 73 | 74 | #[tokio::test] 75 | async fn test_register_new_bls_key() { 76 | let port = read_secure_signer_port(); 77 | let _ = register_new_bls_key(port).await; 78 | } 79 | 80 | #[tokio::test] 81 | async fn test_bls_key_in_remote_attestation_evidence() { 82 | if env::var("SECURE_SIGNER_PORT").is_ok() { 83 | // Local dev is not set so use SGX. 84 | 85 | let port = read_secure_signer_port(); 86 | let resp = register_new_bls_key(port).await; 87 | dbg!(&resp.pk_hex); 88 | dbg!(&port); 89 | 90 | // Verify the report is valid 91 | resp.evidence.verify_intel_signing_certificate().unwrap(); 92 | 93 | // Verify the payload 94 | let pk_hex: String = strip_0x_prefix!(&resp.pk_hex); 95 | let pk = PublicKey::from_hex(&pk_hex).unwrap(); 96 | 97 | let got_payload: [u8; 64] = resp.evidence.get_report_data().unwrap(); 98 | assert_eq!(hex::encode(&got_payload[0..BLS_PUB_KEY_BYTES]), pk.to_hex()); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tests/common/eth_keygen_helper.rs: -------------------------------------------------------------------------------- 1 | use super::read_secure_signer_port; 2 | 3 | use puffersecuresigner::{constants::ETH_COMPRESSED_PK_BYTES, crypto::eth_keys}; 4 | 5 | use anyhow::{Context, Result}; 6 | use reqwest::{Client, Response, StatusCode}; 7 | use serde_json; 8 | use std::env; 9 | 10 | pub async fn mock_eth_keygen_route() -> Result { 11 | let test_app = axum::Router::new() 12 | .route( 13 | "/eth/v1/keygen/secp256k1", 14 | axum::routing::post( 15 | puffersecuresigner::enclave::secure_signer::handlers::eth_keygen::handler, 16 | ), 17 | ) 18 | .into_make_service(); 19 | 20 | let server = axum_test::TestServer::new(test_app)?; 21 | 22 | Ok(server.post("/eth/v1/keygen/secp256k1").await) 23 | } 24 | 25 | pub async fn request_eth_keygen_route(port: u16) -> Result { 26 | let client = Client::new(); 27 | let url = format!("http://localhost:{}/eth/v1/keygen/secp256k1", port); 28 | let response = client 29 | .post(&url) 30 | .header("Content-Type", "application/json") 31 | .send() 32 | .await; 33 | 34 | response 35 | } 36 | 37 | pub async fn make_eth_keygen_request( 38 | port: Option, 39 | ) -> Result<( 40 | puffersecuresigner::enclave::types::KeyGenResponse, 41 | StatusCode, 42 | )> { 43 | match port { 44 | // Make the actual http req to a running Secure-Aggregator instance 45 | Some(p) => { 46 | let resp = request_eth_keygen_route(p).await?; 47 | let status = resp.status(); 48 | let sig: puffersecuresigner::enclave::types::KeyGenResponse = resp 49 | .json() 50 | .await 51 | .with_context(|| format!("Failed to parse to KeyGenResponse"))?; 52 | Ok((sig, status)) 53 | } 54 | // Mock an http request 55 | None => { 56 | let resp = mock_eth_keygen_route().await?; 57 | let sig: puffersecuresigner::enclave::types::KeyGenResponse = 58 | serde_json::from_slice(resp.as_bytes()) 59 | .with_context(|| "Failed to parse to KeyGenResponse")?; 60 | Ok((sig, resp.status_code().into())) 61 | } 62 | } 63 | } 64 | 65 | pub async fn register_new_eth_key( 66 | port: Option, 67 | ) -> puffersecuresigner::enclave::types::KeyGenResponse { 68 | let (resp, status) = make_eth_keygen_request(port).await.unwrap(); 69 | assert_eq!(status, 201); 70 | resp 71 | } 72 | 73 | #[tokio::test] 74 | async fn test_register_new_eth_key() { 75 | let port = read_secure_signer_port(); 76 | let resp = register_new_eth_key(port).await; 77 | let _pk = eth_keys::eth_pk_from_hex_uncompressed(&resp.pk_hex).unwrap(); 78 | dbg!(resp.pk_hex); 79 | } 80 | 81 | #[tokio::test] 82 | async fn test_eth_key_in_remote_attestation_evidence() { 83 | if env::var("SECURE_SIGNER_PORT").is_ok() { 84 | // Local dev is not set so use SGX. 85 | let port = read_secure_signer_port(); 86 | let resp = register_new_eth_key(port).await; 87 | dbg!(&resp.pk_hex); 88 | 89 | // Verify the report is valid 90 | resp.evidence.verify_intel_signing_certificate().unwrap(); 91 | 92 | // Verify the payload 93 | let pk = eth_keys::eth_pk_from_hex_uncompressed(&resp.pk_hex).unwrap(); 94 | 95 | let got_payload: [u8; 64] = resp.evidence.get_report_data().unwrap(); 96 | assert_eq!( 97 | hex::encode(&got_payload[0..ETH_COMPRESSED_PK_BYTES]), 98 | hex::encode(pk.serialize_compressed()) 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | use blsttc::SecretKeySet; 2 | use puffersecuresigner::crypto::bls_keys; 3 | use puffersecuresigner::eth2::slash_protection::SlashingProtectionData; 4 | use puffersecuresigner::strip_0x_prefix; 5 | 6 | pub mod bls_keygen_helper; 7 | pub mod eth_keygen_helper; 8 | pub mod eth_specs; 9 | pub mod getter_routes_helper; 10 | pub mod signing_helper; 11 | 12 | /// Reads the `SECURE_SIGNER_PORT` environment variable. 13 | /// If the return value is Some(port), it is expected that Secure-Signer is running on localhost:port 14 | pub fn read_secure_signer_port() -> Option { 15 | let port = match std::env::var("SECURE_SIGNER_PORT") { 16 | Ok(port_str) => match port_str.parse::() { 17 | Ok(port) => Some(port), 18 | Err(_) => None, 19 | }, 20 | Err(_) => None, 21 | }; 22 | 23 | if port.is_some() { 24 | dbg!("Testing against remote Secure-Signer @ port {port}"); 25 | } else { 26 | dbg!("Testing against local and mocked HTTP endpoints"); 27 | } 28 | port 29 | } 30 | 31 | /// hardcoded bls sk from Lighthouse Web3Signer tests 32 | pub fn setup_dummy_keypair() -> String { 33 | // dummy key 34 | let sk_hex = "5528f51154c1ea9b18eab53aabc1d1a478930aaebde47730b51375df02f0076c"; 35 | dbg!(&sk_hex); 36 | let sk_hex: String = strip_0x_prefix!(sk_hex); 37 | let sk_bytes = hex::decode(sk_hex).unwrap(); 38 | let sk_set = SecretKeySet::from_bytes(sk_bytes).unwrap(); 39 | bls_keys::save_bls_key(&sk_set).unwrap(); 40 | let pk_hex = sk_set.public_keys().public_key().to_hex(); 41 | 42 | // init slashing protection db 43 | let db = SlashingProtectionData::from_pk_hex(&pk_hex).unwrap(); 44 | db.write().unwrap(); 45 | 46 | pk_hex 47 | } 48 | -------------------------------------------------------------------------------- /tests/common/signing_helper.rs: -------------------------------------------------------------------------------- 1 | use super::bls_keygen_helper::register_new_bls_key; 2 | 3 | use super::read_secure_signer_port; 4 | 5 | use anyhow::{Context, Result}; 6 | use puffersecuresigner::eth2::{eth_signing::BLSSignMsg, eth_types::GENESIS_FORK_VERSION}; 7 | use reqwest::{Client, Response, StatusCode}; 8 | use serde_json; 9 | 10 | pub async fn mock_secure_sign_route( 11 | bls_pk: &String, 12 | signing_data: BLSSignMsg, 13 | ) -> Result { 14 | let uri = format!("/api/v1/eth2/sign/{}", bls_pk); 15 | let test_app = axum::Router::new() 16 | .route( 17 | "/api/v1/eth2/sign/:bls_pk_hex", 18 | axum::routing::post( 19 | puffersecuresigner::enclave::shared::handlers::secure_sign_bls::handler, 20 | ), 21 | ) 22 | .with_state(puffersecuresigner::enclave::shared::handlers::AppState { 23 | genesis_fork_version: GENESIS_FORK_VERSION, 24 | }) 25 | .into_make_service(); 26 | 27 | let server = axum_test::TestServer::new(test_app)?; 28 | 29 | Ok(server.post(&uri).json(&signing_data).await) 30 | } 31 | 32 | /// Makes a request to Secure-Aggregator aggregate_route on the specified port 33 | pub async fn request_secure_sign_route( 34 | bls_pk: &String, 35 | sign_msg: &BLSSignMsg, 36 | port: u16, 37 | ) -> Result { 38 | let json_req = serde_json::to_string(sign_msg).unwrap(); 39 | // Create a Reqwest client 40 | let client = Client::new(); 41 | 42 | // Build the URL 43 | let url = format!("http://localhost:{}/api/v1/eth2/sign/{}", port, bls_pk); 44 | 45 | // Make the HTTP request 46 | let response = client 47 | .post(&url) 48 | .header("Content-Type", "application/json") 49 | .body(json_req.clone()) 50 | .send() 51 | .await; 52 | 53 | response 54 | } 55 | /// The aggregate_route_requester function is a utility function that allows you to make an HTTP request to the aggregate route of the Secure-Aggregator service, either by mocking the request or by sending a real HTTP request to a running Secure-Aggregator instance on a specified port. 56 | pub async fn make_signing_route_request( 57 | signing_data: BLSSignMsg, 58 | bls_pk_hex: &String, 59 | port: Option, 60 | ) -> Result<( 61 | Option, 62 | StatusCode, 63 | )> { 64 | match port { 65 | // Make the actual http req to a running Secure-Signer instance 66 | Some(p) => { 67 | let resp = request_secure_sign_route(&bls_pk_hex, &signing_data, p).await?; 68 | let status = resp.status(); 69 | let sig = resp 70 | .json::() 71 | .await 72 | .with_context(|| format!("Failed to parse to SignatureResposne")); 73 | 74 | Ok((sig.ok(), status)) 75 | } 76 | // Mock an http request 77 | None => { 78 | let resp = mock_secure_sign_route(&bls_pk_hex, signing_data).await?; 79 | let status = resp.status_code(); 80 | let sig: Option = 81 | serde_json::from_slice(resp.as_bytes()).ok(); 82 | 83 | Ok((sig, status)) 84 | } 85 | } 86 | } 87 | 88 | #[tokio::test] 89 | async fn test_sign_route() { 90 | let port = read_secure_signer_port(); 91 | let bls_pk_hex = register_new_bls_key(port).await.pk_hex; 92 | 93 | let req = format!( 94 | r#" 95 | {{ 96 | "type": "ATTESTATION", 97 | "fork_info":{{ 98 | "fork":{{ 99 | "previous_version":"0x00000001", 100 | "current_version":"0x00000001", 101 | "epoch":"0" 102 | }}, 103 | "genesis_validators_root":"0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69" 104 | }}, 105 | "signingRoot": "0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69", 106 | "attestation": {{ 107 | "slot": "255", 108 | "index": "65535", 109 | "beacon_block_root": "0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69", 110 | "source": {{ 111 | "epoch": "10", 112 | "root": "0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69" 113 | }}, 114 | "target": {{ 115 | "epoch": "11", 116 | "root": "0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69" 117 | }} 118 | }} 119 | }}"# 120 | ); 121 | 122 | let req: BLSSignMsg = serde_json::from_str(&req).unwrap(); 123 | let (resp, status) = make_signing_route_request(req, &bls_pk_hex.to_string(), port) 124 | .await 125 | .unwrap(); 126 | _ = resp.unwrap(); 127 | assert_eq!(status, 200); 128 | } 129 | -------------------------------------------------------------------------------- /tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | mod signing_tests; 3 | -------------------------------------------------------------------------------- /tests/signing_tests/aggregate_and_proof.rs: -------------------------------------------------------------------------------- 1 | use crate::common; 2 | use crate::common::bls_keygen_helper::register_new_bls_key; 3 | use crate::common::{eth_specs, signing_helper::*}; 4 | use puffersecuresigner::eth2::eth_signing::*; 5 | use puffersecuresigner::eth2::eth_types::*; 6 | use puffersecuresigner::strip_0x_prefix; 7 | use std::path::PathBuf; 8 | 9 | fn aggregate_and_proof_request() -> BLSSignMsg { 10 | // Create an AggregateAndProofRequest 11 | let req = mock_aggregate_and_proof_request(); 12 | let signing_data: AggregateAndProofRequest = serde_json::from_str(&req).unwrap(); 13 | BLSSignMsg::AGGREGATE_AND_PROOF(signing_data) 14 | } 15 | 16 | pub fn mock_aggregate_and_proof_request() -> String { 17 | let req = format!( 18 | r#" 19 | {{ 20 | "type":"AGGREGATE_AND_PROOF", 21 | "fork_info":{{ 22 | "fork":{{ 23 | "previous_version":"0x00000001", 24 | "current_version":"0x00000001", 25 | "epoch":"0" 26 | }}, 27 | "genesis_validators_root":"0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69" 28 | }}, 29 | "signingRoot": "0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69", 30 | "aggregate_and_proof":{{ 31 | "aggregator_index": "5", 32 | "aggregate": {{ 33 | "aggregation_bits": "0x1234", 34 | "data": {{ 35 | "slot": "750", 36 | "index": "1", 37 | "beacon_block_root": "0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69", 38 | "source": {{ 39 | "epoch": "10", 40 | "root": "0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69" 41 | }}, 42 | "target": {{ 43 | "epoch": "12", 44 | "root": "0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69" 45 | }} 46 | }}, 47 | "signature": "0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69" 48 | }}, 49 | "selection_proof": "0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69" 50 | }} 51 | }}"# 52 | ); 53 | req 54 | } 55 | 56 | #[tokio::test] 57 | pub async fn test_aggregate_route_fails_from_invalid_pk_hex() { 58 | let port = common::read_secure_signer_port(); 59 | let req = aggregate_and_proof_request(); 60 | let bls_pk_hex = "0xdeadbeef".to_string(); 61 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 62 | .await 63 | .unwrap(); 64 | assert_eq!(status, 400); 65 | } 66 | 67 | #[tokio::test] 68 | pub async fn test_aggregate_aggregate_and_proof_happy_path() { 69 | let port = common::read_secure_signer_port(); 70 | let req = aggregate_and_proof_request(); 71 | let bls_pk_hex = register_new_bls_key(port).await.pk_hex; 72 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 73 | .await 74 | .unwrap(); 75 | assert_eq!(status, 200); 76 | } 77 | 78 | #[tokio::test] 79 | pub async fn test_aggregate_aggregate_and_proof_happy_path_test_vec() { 80 | let port = None; 81 | let exp_sig = Some("81e56af6c3b9f0ce1c7fd3545a3d689fc2edd2c9dd5451ea5f345cc57d74de76ed940e373fdccc76150e643edc57bdb0145ad3770d9207164484f86f746fb26f889833106e3e17cd49572eb7938a9e4502bba99c3234f32695f73ef3ed18bb51".to_string()); 82 | let req = aggregate_and_proof_request(); 83 | let bls_pk_hex = common::setup_dummy_keypair(); 84 | let (resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 85 | .await 86 | .unwrap(); 87 | assert_eq!(status, 200); 88 | let sig = resp.unwrap().signature; 89 | let got_sig: String = strip_0x_prefix!(sig); 90 | assert_eq!(exp_sig.unwrap(), got_sig); 91 | } 92 | 93 | #[tokio::test] 94 | async fn test_aggregate_and_proof_eth2_specs() { 95 | let path: PathBuf = [eth_specs::BASE_DIR, "AggregateAndProof"].iter().collect(); 96 | dbg!(&path); 97 | let port = common::read_secure_signer_port(); 98 | let req = aggregate_and_proof_request(); 99 | let bls_pk_hex = register_new_bls_key(port).await.pk_hex; 100 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 101 | .await 102 | .unwrap(); 103 | assert_eq!(status, 200); 104 | 105 | let msgs = eth_specs::get_all_test_vecs("AggregateAndProof").unwrap(); 106 | for msg in msgs.into_iter() { 107 | let (_resp, status) = make_signing_route_request(msg, &bls_pk_hex, port) 108 | .await 109 | .unwrap(); 110 | assert_eq!(status, 200); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tests/signing_tests/aggregation_slot.rs: -------------------------------------------------------------------------------- 1 | use crate::common; 2 | use crate::common::bls_keygen_helper::register_new_bls_key; 3 | use crate::common::signing_helper::*; 4 | use puffersecuresigner::eth2::eth_signing::*; 5 | use puffersecuresigner::eth2::eth_types::*; 6 | use puffersecuresigner::strip_0x_prefix; 7 | 8 | fn aggregation_slot_request() -> BLSSignMsg { 9 | // Create an AggregationSlotRequest 10 | let req = mock_aggregation_slot_request(); 11 | let signing_data: AggregationSlotRequest = serde_json::from_str(&req).unwrap(); 12 | dbg!(&signing_data); 13 | BLSSignMsg::AGGREGATION_SLOT(signing_data) 14 | } 15 | 16 | pub fn mock_aggregation_slot_request() -> String { 17 | let req = format!( 18 | r#" 19 | {{ 20 | "type": "AGGREGATION_SLOT", 21 | "fork_info":{{ 22 | "fork":{{ 23 | "previous_version":"0x80000070", 24 | "current_version":"0x80000071", 25 | "epoch":"750" 26 | }}, 27 | "genesis_validators_root":"0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a" 28 | }}, 29 | "signingRoot": "0x2ebfc2d70944cc2fbff6d67c6d9cbb043d7fbe0a660d248b6e666ce110af418a", 30 | "aggregation_slot": {{ 31 | "slot": "123123" 32 | }} 33 | }}"# 34 | ); 35 | req 36 | } 37 | 38 | #[tokio::test] 39 | pub async fn test_aggregate_route_fails_from_invalid_pk_hex() { 40 | let port = common::read_secure_signer_port(); 41 | let req = aggregation_slot_request(); 42 | let bls_pk_hex = "0xdeadbeef".to_string(); 43 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 44 | .await 45 | .unwrap(); 46 | assert_eq!(status, 400); 47 | } 48 | 49 | #[tokio::test] 50 | pub async fn test_aggregate_aggregation_slot_happy_path() { 51 | let port = common::read_secure_signer_port(); 52 | let req = aggregation_slot_request(); 53 | let bls_pk_hex = register_new_bls_key(port).await.pk_hex; 54 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 55 | .await 56 | .unwrap(); 57 | assert_eq!(status, 200); 58 | } 59 | 60 | #[tokio::test] 61 | pub async fn test_aggregate_aggregation_slot_happy_path_test_vec() { 62 | let port = None; 63 | let exp_sig = Some("84eaf231b6b98cafebf914888d98a5239ee69b338b2aa6f87d9c7ecf7f602644ffb75f78bc91fe48b85ae6df660a48e916aef96677b809436b0504fe3e85c22b79d686eb46787ffc0a4d37cbdb1ba45f5c8e22d1e43e6429eb151d3099ff1cdb".to_string()); 64 | let req = aggregation_slot_request(); 65 | let bls_pk_hex = common::setup_dummy_keypair(); 66 | let (resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 67 | .await 68 | .unwrap(); 69 | assert_eq!(status, 200); 70 | let sig = resp.unwrap().signature; 71 | let got_sig: String = strip_0x_prefix!(sig); 72 | assert_eq!(exp_sig.unwrap(), got_sig); 73 | } 74 | -------------------------------------------------------------------------------- /tests/signing_tests/contribution_and_proof.rs: -------------------------------------------------------------------------------- 1 | use crate::common; 2 | use crate::common::bls_keygen_helper::register_new_bls_key; 3 | use crate::common::{eth_specs, signing_helper::*}; 4 | use puffersecuresigner::eth2::eth_signing::*; 5 | use puffersecuresigner::eth2::eth_types::*; 6 | use puffersecuresigner::strip_0x_prefix; 7 | use std::path::PathBuf; 8 | 9 | fn sync_committee_contribution_and_proof_request() -> BLSSignMsg { 10 | // Create a SyncCommitteeContributionAndProofRequest 11 | let req = mock_sync_committee_contribution_and_proof_request(); 12 | dbg!(&req); 13 | let signing_data: SyncCommitteeContributionAndProofRequest = serde_json::from_str(&req) 14 | .expect("Failed to serialize mock SyncCommitteeContributionAndProofRequest"); 15 | dbg!(&signing_data); 16 | BLSSignMsg::SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF(signing_data) 17 | } 18 | 19 | /// aggregation bits must be 128b 20 | fn mock_sync_committee_contribution_and_proof_request() -> String { 21 | let req = format!( 22 | r#" 23 | {{ 24 | "type": "SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF", 25 | "fork_info":{{ 26 | "fork":{{ 27 | "previous_version":"0x80000070", 28 | "current_version":"0x80000071", 29 | "epoch":"750" 30 | }}, 31 | "genesis_validators_root":"0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a" 32 | }}, 33 | "signingRoot": "0x2ebfc2d70944cc2fbff6d67c6d9cbb043d7fbe0a660d248b6e666ce110af418a", 34 | "contribution_and_proof": {{ 35 | "aggregator_index": "123123", 36 | "selection_proof": "0x8209b5391cd69f392b1f02dbc03bab61f574bb6bb54bf87b59e2a85bdc0756f7db6a71ce1b41b727a1f46ccc77b213bf0df1426177b5b29926b39956114421eaa36ec4602969f6f6370a44de44a6bce6dae2136e5fb594cce2a476354264d1ea", 37 | "contribution" : {{ 38 | "slot": "123123", 39 | "beacon_block_root": "0x2ebfc2d70944cc2fbff6d67c6d9cbb043d7fbe0a660d248b6e666ce110af418a", 40 | "subcommittee_index": "12345", 41 | "aggregation_bits": "0x00000000000000000000000000000000", 42 | "signature": "0x8209b5391cd69f392b1f02dbc03bab61f574bb6bb54bf87b59e2a85bdc0756f7db6a71ce1b41b727a1f46ccc77b213bf0df1426177b5b29926b39956114421eaa36ec4602969f6f6370a44de44a6bce6dae2136e5fb594cce2a476354264d1ea" 43 | }} 44 | }} 45 | }}"# 46 | ); 47 | req 48 | } 49 | 50 | #[tokio::test] 51 | async fn test_aggregate_route_fails_from_invalid_pk_hex() { 52 | let port = common::read_secure_signer_port(); 53 | let req = sync_committee_contribution_and_proof_request(); 54 | let bls_pk_hex = "0xdeadbeef".to_string(); 55 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 56 | .await 57 | .unwrap(); 58 | assert_eq!(status, 400); 59 | } 60 | 61 | #[tokio::test] 62 | async fn test_aggregate_sync_committee_contribution_and_proof_happy_path() { 63 | let port = common::read_secure_signer_port(); 64 | let req = sync_committee_contribution_and_proof_request(); 65 | let bls_pk_hex = register_new_bls_key(port).await.pk_hex; 66 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 67 | .await 68 | .unwrap(); 69 | assert_eq!(status, 200); 70 | } 71 | 72 | #[tokio::test] 73 | async fn test_aggregate_sync_committee_contribution_and_proof_happy_path_test_vec() { 74 | let port = None; 75 | let exp_sig = Some("ae7248f762bf491101f3621bb0b1c85dd2264cdec4ebfcc4774c41d41229123728046722e16cf676742a1ac32b1d3d7611042c5d0e5b813d8c71477ccd2e1a4264a66eb3eb3d58b68641c592f210650c0e182357acf1dde03be8fda1011377b3".to_string()); 76 | let req = sync_committee_contribution_and_proof_request(); 77 | let bls_pk_hex = common::setup_dummy_keypair(); 78 | let (resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 79 | .await 80 | .unwrap(); 81 | assert_eq!(status, 200); 82 | let sig = resp.unwrap().signature; 83 | let got_sig: String = strip_0x_prefix!(sig); 84 | assert_eq!(exp_sig.unwrap(), got_sig); 85 | } 86 | 87 | #[tokio::test] 88 | async fn test_sync_committee_contribution_eth2_specs() { 89 | let path: PathBuf = [eth_specs::BASE_DIR, "ContributionAndProof"] 90 | .iter() 91 | .collect(); 92 | dbg!(&path); 93 | 94 | let port = common::read_secure_signer_port(); 95 | let req = sync_committee_contribution_and_proof_request(); 96 | let bls_pk_hex = register_new_bls_key(port).await.pk_hex; 97 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 98 | .await 99 | .unwrap(); 100 | assert_eq!(status, 200); 101 | 102 | let msgs = eth_specs::get_all_test_vecs("ContributionAndProof").unwrap(); 103 | for msg in msgs.into_iter() { 104 | let (_resp, status) = make_signing_route_request(msg, &bls_pk_hex, port) 105 | .await 106 | .unwrap(); 107 | assert_eq!(status, 200); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /tests/signing_tests/deposit.rs: -------------------------------------------------------------------------------- 1 | use crate::common; 2 | use crate::common::bls_keygen_helper::register_new_bls_key; 3 | use crate::common::{eth_specs, signing_helper::*}; 4 | use puffersecuresigner::eth2::eth_signing::*; 5 | use puffersecuresigner::eth2::eth_types::*; 6 | use puffersecuresigner::strip_0x_prefix; 7 | use std::path::PathBuf; 8 | 9 | fn deposit_request() -> BLSSignMsg { 10 | // Create a DepositRequest 11 | let req = mock_deposit_request(); 12 | dbg!(&req); 13 | let signing_data: DepositRequest = 14 | serde_json::from_str(&req).expect("Failed to serialize mock DepositRequest"); 15 | dbg!(&signing_data); 16 | BLSSignMsg::DEPOSIT(signing_data) 17 | } 18 | 19 | pub fn mock_deposit_request() -> String { 20 | let req = format!( 21 | r#" 22 | {{ 23 | "type": "DEPOSIT", 24 | "genesis_fork_version":"00001020", 25 | "deposit": {{ 26 | "pubkey": "0x8996c1117cb75927eb53db74b25c3668c0f7b08d34cdb8de1062bef578fb1c1e32032e0555e9f5be47cd5e8f0f2705d5", 27 | "withdrawal_credentials": "0x75362a41a82133d71eee01e602ad564c73590557bb7c994cf9be5620d2023a58", 28 | "amount":"32000000000" 29 | }} 30 | }}"# 31 | ); 32 | req 33 | } 34 | 35 | #[tokio::test] 36 | async fn test_aggregate_route_fails_from_invalid_pk_hex() { 37 | let port = common::read_secure_signer_port(); 38 | let req = deposit_request(); 39 | let bls_pk_hex = "0xdeadbeef".to_string(); 40 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 41 | .await 42 | .unwrap(); 43 | assert_eq!(status, 400); 44 | } 45 | 46 | #[tokio::test] 47 | async fn test_aggregate_deposit_happy_path() { 48 | let port = common::read_secure_signer_port(); 49 | let req = deposit_request(); 50 | let bls_pk_hex = register_new_bls_key(port).await.pk_hex; 51 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 52 | .await 53 | .unwrap(); 54 | assert_eq!(status, 200); 55 | } 56 | 57 | #[tokio::test] 58 | async fn test_aggregate_deposit_happy_path_test_vec() { 59 | let port = None; 60 | let exp_sig = Some("82cc787865c0fb7147fe7350dd5a71f5d92c6a1771eb951826f6b339a319e1904a2310d5d3cbc5e2d0e5f35f2bfe6da5164c33114663222d4238a43d495876dae873dc6af338c4af4f6dbe1ae181331581bdcd353509a2356977b6625c9ab0e5".to_string()); 61 | let req = deposit_request(); 62 | let bls_pk_hex = common::setup_dummy_keypair(); 63 | let (resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 64 | .await 65 | .unwrap(); 66 | assert_eq!(status, 200); 67 | let sig = resp.unwrap().signature; 68 | let got_sig: String = strip_0x_prefix!(sig); 69 | assert_eq!(exp_sig.unwrap(), got_sig); 70 | } 71 | 72 | #[tokio::test] 73 | async fn test_sync_committee_message_eth2_specs() { 74 | let path: PathBuf = [eth_specs::BASE_DIR, "DepositMessage"].iter().collect(); 75 | dbg!(&path); 76 | let port = common::read_secure_signer_port(); 77 | let req = deposit_request(); 78 | let bls_pk_hex = register_new_bls_key(port).await.pk_hex; 79 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 80 | .await 81 | .unwrap(); 82 | assert_eq!(status, 200); 83 | let msgs = eth_specs::get_all_test_vecs("DepositMessage").unwrap(); 84 | for msg in msgs.into_iter() { 85 | let (_resp, status) = make_signing_route_request(msg, &bls_pk_hex, port) 86 | .await 87 | .unwrap(); 88 | assert_eq!(status, 200); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/signing_tests/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod aggregate_and_proof; 2 | pub mod aggregation_slot; 3 | pub mod attestation; 4 | pub mod block; 5 | pub mod block_v2; 6 | pub mod contribution_and_proof; 7 | pub mod deposit; 8 | pub mod randao_reveal; 9 | pub mod sync_committee_message; 10 | pub mod sync_committee_selection_proof; 11 | pub mod validator_registration; 12 | -------------------------------------------------------------------------------- /tests/signing_tests/randao_reveal.rs: -------------------------------------------------------------------------------- 1 | use crate::common; 2 | use crate::common::bls_keygen_helper::register_new_bls_key; 3 | use crate::common::signing_helper::*; 4 | use puffersecuresigner::eth2::eth_signing::*; 5 | use puffersecuresigner::eth2::eth_types::*; 6 | use puffersecuresigner::strip_0x_prefix; 7 | 8 | fn randao_reveal_request() -> BLSSignMsg { 9 | // Create a RandaoRevealRequest 10 | let req = mock_randao_reveal_request(); 11 | let signing_data: RandaoRevealRequest = serde_json::from_str(&req).unwrap(); 12 | BLSSignMsg::RANDAO_REVEAL(signing_data) 13 | } 14 | 15 | pub fn mock_randao_reveal_request() -> String { 16 | let req = format!( 17 | r#" 18 | {{ 19 | "type":"randao_reveal", 20 | "fork_info":{{ 21 | "fork":{{ 22 | "previous_version":"0x00000000", 23 | "current_version":"0x00000000", 24 | "epoch":"2" 25 | }}, 26 | "genesis_validators_root":"0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a" 27 | }}, 28 | "signingRoot": "0xbf70dbbbc83299fb877334eaeaefb32df44242c1bf078cdc1836dcc3282d4fbd", 29 | "randao_reveal":{{ 30 | "epoch": "10" 31 | }} 32 | }}"# 33 | ); 34 | req 35 | } 36 | #[tokio::test] 37 | pub async fn test_aggregate_route_fails_from_invalid_pk_hex() { 38 | let port = common::read_secure_signer_port(); 39 | let req = randao_reveal_request(); 40 | let bls_pk_hex = "0xdeadbeef".to_string(); 41 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 42 | .await 43 | .unwrap(); 44 | assert_eq!(status, 400); 45 | } 46 | 47 | #[tokio::test] 48 | pub async fn test_aggregate_randao_reveal_happy_path() { 49 | let port = common::read_secure_signer_port(); 50 | let req = randao_reveal_request(); 51 | let bls_pk_hex = register_new_bls_key(port).await.pk_hex; 52 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 53 | .await 54 | .unwrap(); 55 | assert_eq!(status, 200); 56 | } 57 | 58 | #[tokio::test] 59 | pub async fn test_aggregate_randao_reveal_happy_path_test_vec() { 60 | let port = None; 61 | let exp_sig = Some("a8c5eb481ef1c3ea345bc9cb9ce9918e18ef052d8287bacd3b1e1bbd34bc4e1e016602b778535d5b582bc35ea6d2ded106ea2cfec06f8b6c5bd049dbf0a544207ac3b21c634b8e78c2c0135a0000e961adae192203ef168de1edb83618d1a76d".to_string()); 62 | let req = randao_reveal_request(); 63 | let bls_pk_hex = common::setup_dummy_keypair(); 64 | let (resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 65 | .await 66 | .unwrap(); 67 | assert_eq!(status, 200); 68 | let sig = resp.unwrap().signature; 69 | let got_sig: String = strip_0x_prefix!(sig); 70 | assert_eq!(exp_sig.unwrap(), got_sig); 71 | } 72 | -------------------------------------------------------------------------------- /tests/signing_tests/sync_committee_message.rs: -------------------------------------------------------------------------------- 1 | use crate::common; 2 | use crate::common::bls_keygen_helper::register_new_bls_key; 3 | use crate::common::{eth_specs, signing_helper::*}; 4 | use puffersecuresigner::eth2::eth_signing::*; 5 | use puffersecuresigner::eth2::eth_types::*; 6 | use puffersecuresigner::strip_0x_prefix; 7 | use std::path::PathBuf; 8 | 9 | fn sync_committee_message_request() -> BLSSignMsg { 10 | // Create a SyncCommitteeMessageRequest 11 | let req = mock_sync_committee_message_request(); 12 | dbg!(&req); 13 | let signing_data: SyncCommitteeMessageRequest = 14 | serde_json::from_str(&req).expect("Failed to serialize mock SyncCommitteeMessageRequest"); 15 | dbg!(&signing_data); 16 | BLSSignMsg::SYNC_COMMITTEE_MESSAGE(signing_data) 17 | } 18 | 19 | pub fn mock_sync_committee_message_request() -> String { 20 | let req = format!( 21 | r#" 22 | {{ 23 | "type": "sync_committee_message", 24 | "fork_info":{{ 25 | "fork":{{ 26 | "previous_version":"0x80000070", 27 | "current_version":"0x80000071", 28 | "epoch":"750" 29 | }}, 30 | "genesis_validators_root":"0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a" 31 | }}, 32 | "signingRoot": "0x2ebfc2d70944cc2fbff6d67c6d9cbb043d7fbe0a660d248b6e666ce110af418a", 33 | "sync_committee_message": {{ 34 | "slot": "123123", 35 | "beacon_block_root": "0x2ebfc2d70944cc2fbff6d67c6d9cbb043d7fbe0a660d248b6e666ce110af418a" 36 | }} 37 | }}"# 38 | ); 39 | req 40 | } 41 | 42 | #[tokio::test] 43 | pub async fn test_aggregate_route_fails_from_invalid_pk_hex() { 44 | let port = common::read_secure_signer_port(); 45 | let req = sync_committee_message_request(); 46 | let bls_pk_hex = "0xdeadbeef".to_string(); 47 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 48 | .await 49 | .unwrap(); 50 | assert_eq!(status, 400); 51 | } 52 | 53 | #[tokio::test] 54 | pub async fn test_aggregate_sync_committee_message_happy_path() { 55 | let port = common::read_secure_signer_port(); 56 | let req = sync_committee_message_request(); 57 | let bls_pk_hex = register_new_bls_key(port).await.pk_hex; 58 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 59 | .await 60 | .unwrap(); 61 | assert_eq!(status, 200); 62 | } 63 | 64 | #[tokio::test] 65 | pub async fn test_aggregate_sync_committee_message_happy_path_test_vec() { 66 | let port = None; 67 | let exp_sig = Some("8b3c0f3cb3427a6009ee7d2f6691480fcf93d21fc7231d333b0bf997e7fe147f0700e61f4790246ce5650a8510374f3d0d14286e41943a80f30dd9cfc197155f0e8cd4f4ced1f1f2b37214fa146640f59f0b7d59cf61980166287083936eea30".to_string()); 68 | let req = sync_committee_message_request(); 69 | let bls_pk_hex = common::setup_dummy_keypair(); 70 | let (resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 71 | .await 72 | .unwrap(); 73 | assert_eq!(status, 200); 74 | let sig = resp.unwrap().signature; 75 | let got_sig: String = strip_0x_prefix!(sig); 76 | assert_eq!(exp_sig.unwrap(), got_sig); 77 | } 78 | 79 | #[tokio::test] 80 | async fn test_sync_committee_eth2_specs() { 81 | let path: PathBuf = [eth_specs::BASE_DIR, "SyncCommitteeMessage"] 82 | .iter() 83 | .collect(); 84 | dbg!(&path); 85 | 86 | let port = common::read_secure_signer_port(); 87 | let req = sync_committee_message_request(); 88 | let bls_pk_hex = register_new_bls_key(port).await.pk_hex; 89 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 90 | .await 91 | .unwrap(); 92 | assert_eq!(status, 200); 93 | 94 | let msgs = eth_specs::get_all_test_vecs("SyncCommitteeMessage").unwrap(); 95 | for msg in msgs.into_iter() { 96 | let (_resp, status) = make_signing_route_request(msg, &bls_pk_hex, port) 97 | .await 98 | .unwrap(); 99 | assert_eq!(status, 200); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tests/signing_tests/sync_committee_selection_proof.rs: -------------------------------------------------------------------------------- 1 | use crate::common; 2 | use crate::common::bls_keygen_helper::register_new_bls_key; 3 | use crate::common::{eth_specs, signing_helper::*}; 4 | use puffersecuresigner::eth2::eth_signing::*; 5 | use puffersecuresigner::eth2::eth_types::*; 6 | use puffersecuresigner::strip_0x_prefix; 7 | use std::path::PathBuf; 8 | 9 | fn sync_committee_selection_proof_request() -> BLSSignMsg { 10 | // Create a SyncCommitteeSelectionAndProof 11 | let req = mock_sync_committee_selection_proof_request(); 12 | dbg!(&req); 13 | let signing_data: SyncCommitteeSelectionProofRequest = serde_json::from_str(&req) 14 | .expect("Failed to serialize mock SyncCommitteeSelectionProofRequest"); 15 | dbg!(&signing_data); 16 | BLSSignMsg::SYNC_COMMITTEE_SELECTION_PROOF(signing_data) 17 | } 18 | 19 | pub fn mock_sync_committee_selection_proof_request() -> String { 20 | let req = format!( 21 | r#" 22 | {{ 23 | "type": "SYNC_COMMITTEE_SELECTION_PROOF", 24 | "fork_info":{{ 25 | "fork":{{ 26 | "previous_version":"0x80000070", 27 | "current_version":"0x80000071", 28 | "epoch":"750" 29 | }}, 30 | "genesis_validators_root":"0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a" 31 | }}, 32 | "signingRoot": "0x2ebfc2d70944cc2fbff6d67c6d9cbb043d7fbe0a660d248b6e666ce110af418a", 33 | "sync_aggregator_selection_data": {{ 34 | "slot": "123123", 35 | "subcommittee_index": "12345" 36 | }} 37 | }}"# 38 | ); 39 | req 40 | } 41 | 42 | #[tokio::test] 43 | pub async fn test_aggregate_route_fails_from_invalid_pk_hex() { 44 | let port = common::read_secure_signer_port(); 45 | let req = sync_committee_selection_proof_request(); 46 | let bls_pk_hex = "0xdeadbeef".to_string(); 47 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 48 | .await 49 | .unwrap(); 50 | assert_eq!(status, 400); 51 | } 52 | 53 | #[tokio::test] 54 | pub async fn test_aggregate_sync_committee_selection_proof_happy_path() { 55 | let port = common::read_secure_signer_port(); 56 | let req = sync_committee_selection_proof_request(); 57 | let bls_pk_hex = register_new_bls_key(port).await.pk_hex; 58 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 59 | .await 60 | .unwrap(); 61 | assert_eq!(status, 200); 62 | } 63 | 64 | #[tokio::test] 65 | pub async fn test_aggregate_sync_committee_selection_proof_happy_path_test_vec() { 66 | let port = None; 67 | let exp_sig = Some("84b81f509f9ffb74439a0c862aaafbcb7c6a406bddcb7d5c30b668153a8d86b7a10425bf9e04254ae22e1c9f3dbd5fbe172014c74ee17984e0a90dad03ed31597aabc8d00a78af41f9696aa017f65306154f2dd51f669f12155b7de0269881c0".to_string()); 68 | let req = sync_committee_selection_proof_request(); 69 | let bls_pk_hex = common::setup_dummy_keypair(); 70 | let (resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 71 | .await 72 | .unwrap(); 73 | assert_eq!(status, 200); 74 | let sig = resp.unwrap().signature; 75 | let got_sig: String = strip_0x_prefix!(sig); 76 | assert_eq!(exp_sig.unwrap(), got_sig); 77 | } 78 | 79 | #[tokio::test] 80 | async fn test_sync_committee_committee_selection_proof_eth2_specs() { 81 | let path: PathBuf = [eth_specs::BASE_DIR, "SyncAggregatorSelectionData"] 82 | .iter() 83 | .collect(); 84 | dbg!(&path); 85 | 86 | let port = common::read_secure_signer_port(); 87 | let req = sync_committee_selection_proof_request(); 88 | let bls_pk_hex = register_new_bls_key(port).await.pk_hex; 89 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 90 | .await 91 | .unwrap(); 92 | assert_eq!(status, 200); 93 | 94 | let msgs = eth_specs::get_all_test_vecs("SyncAggregatorSelectionData").unwrap(); 95 | for msg in msgs.into_iter() { 96 | let (_resp, status) = make_signing_route_request(msg, &bls_pk_hex, port) 97 | .await 98 | .unwrap(); 99 | assert_eq!(status, 200); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tests/signing_tests/validator_registration.rs: -------------------------------------------------------------------------------- 1 | use crate::common; 2 | use crate::common::bls_keygen_helper::register_new_bls_key; 3 | use crate::common::signing_helper::*; 4 | use puffersecuresigner::eth2::eth_signing::*; 5 | use puffersecuresigner::eth2::eth_types::*; 6 | use puffersecuresigner::strip_0x_prefix; 7 | 8 | fn validator_registration_request() -> BLSSignMsg { 9 | // Create a ValidatorRegistrationRequest 10 | let req = mock_validator_registration_request(); 11 | let signing_data: ValidatorRegistrationRequest = 12 | serde_json::from_str(&req).expect("Failed to serialize mock ValidatorRegistrationRequest"); 13 | BLSSignMsg::VALIDATOR_REGISTRATION(signing_data) 14 | } 15 | 16 | pub fn mock_validator_registration_request() -> String { 17 | let req = format!( 18 | r#" 19 | {{ 20 | "type": "VALIDATOR_REGISTRATION", 21 | "signingRoot": "0x139d59dbb1770fdc582ff75193720352ccc76131e37ac69d0c10e7416f3f3050", 22 | "validator_registration": {{ 23 | "fee_recipient": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a", 24 | "gas_limit": "30000000", 25 | "timestamp":"100", 26 | "pubkey": "0x8349434ad0700e79be65c0c7043945df426bd6d7e288c16671df69d822344f1b0ce8de80360a50550ad782b68035cb18" 27 | }} 28 | }}"# 29 | ); 30 | req 31 | } 32 | 33 | #[tokio::test] 34 | async fn test_aggregate_route_fails_from_invalid_pk_hex() { 35 | let port = common::read_secure_signer_port(); 36 | let req = validator_registration_request(); 37 | let bls_pk_hex = "0xdeadbeef".to_string(); 38 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 39 | .await 40 | .unwrap(); 41 | assert_eq!(status, 400); 42 | } 43 | 44 | #[tokio::test] 45 | async fn test_aggregate_validator_registration_happy_path() { 46 | let port = common::read_secure_signer_port(); 47 | let req = validator_registration_request(); 48 | let bls_pk_hex = register_new_bls_key(port).await.pk_hex; 49 | let (_resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 50 | .await 51 | .unwrap(); 52 | assert_eq!(status, 200); 53 | } 54 | 55 | #[tokio::test] 56 | async fn test_aggregate_validator_registration_happy_path_test_vec() { 57 | let port = None; 58 | let exp_sig = Some("8dc27307e86e464e1eb09247a127cf728df3bdf38bc6871a909a955da178ace5ad3b9087013b0bd24d8af57fb4e5f90f103d200a3e06b4cd56fa780bceac878425de9415f3f947cb279ef9f83141a4c7757100cba5314ac1c0f3dc9b1d92efd5".to_string()); 59 | let req = validator_registration_request(); 60 | let bls_pk_hex = common::setup_dummy_keypair(); 61 | let (resp, status) = make_signing_route_request(req, &bls_pk_hex, port) 62 | .await 63 | .unwrap(); 64 | let sig = resp.unwrap().signature; 65 | assert_eq!(status, 200); 66 | let got_sig: String = strip_0x_prefix!(sig); 67 | assert_eq!(exp_sig.unwrap(), got_sig); 68 | } 69 | --------------------------------------------------------------------------------