mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-12 14:09:25 +00:00
Compare commits
67 Commits
ff-0.14
...
6994d9329b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6994d9329b | ||
|
|
d88bd70f7a | ||
|
|
9e4d83bb2c | ||
|
|
1bfd7d9ba6 | ||
|
|
c521bbb012 | ||
|
|
86facaed95 | ||
|
|
d99ed9698e | ||
|
|
4743ea732c | ||
|
|
3cf0b84523 | ||
|
|
c138950c21 | ||
|
|
9f7dbf2132 | ||
|
|
6357bc0ed4 | ||
|
|
2334725ec8 | ||
|
|
0631607b8f | ||
|
|
d847ec5efb | ||
|
|
b2c962cd3e | ||
|
|
788c4fc0a7 | ||
|
|
04df229df1 | ||
|
|
1f5e5fc7ac | ||
|
|
90880cc9c8 | ||
|
|
e94a04d47c | ||
|
|
0f9a5afa07 | ||
|
|
3de89c717d | ||
|
|
08169e29bb | ||
|
|
b56c6fb39e | ||
|
|
daa0f8f7d5 | ||
|
|
64e74c52ec | ||
|
|
06246618ab | ||
|
|
69e077bf7a | ||
|
|
8319d219d7 | ||
|
|
891362a710 | ||
|
|
08d604fcb3 | ||
|
|
abd48e9206 | ||
|
|
70c36ed06c | ||
|
|
b3b0edb82f | ||
|
|
0f477537a0 | ||
|
|
eb0c19bfff | ||
|
|
0b20004ba1 | ||
|
|
11dba9173f | ||
|
|
1e2e3bd5ce | ||
|
|
df095f027f | ||
|
|
6fc8b30df2 | ||
|
|
74aaac46ef | ||
|
|
1db40914eb | ||
|
|
b5b9d4a871 | ||
|
|
6f61861d4b | ||
|
|
08b95abdd8 | ||
|
|
d740bd2924 | ||
|
|
3a1c6c7247 | ||
|
|
3e82ee60b3 | ||
|
|
303e72c844 | ||
|
|
60d5c06ac3 | ||
|
|
77a2496ade | ||
|
|
d9107b53a6 | ||
|
|
f7c13fd1ca | ||
|
|
798ffc9b28 | ||
|
|
865dee80e5 | ||
|
|
9c217913e6 | ||
|
|
784a273747 | ||
|
|
5cdae6eeb8 | ||
|
|
a1d1de0c9c | ||
|
|
d2a27dc1e5 | ||
|
|
c165c36777 | ||
|
|
f1ad768859 | ||
|
|
cd8b0544f4 | ||
|
|
f5d9d03658 | ||
|
|
98b08eaa38 |
2
.github/actions/bitcoin/action.yml
vendored
2
.github/actions/bitcoin/action.yml
vendored
@@ -37,4 +37,4 @@ runs:
|
|||||||
|
|
||||||
- name: Bitcoin Regtest Daemon
|
- name: Bitcoin Regtest Daemon
|
||||||
shell: bash
|
shell: bash
|
||||||
run: PATH=$PATH:/usr/bin ./orchestration/dev/networks/bitcoin/run.sh -txindex -daemon
|
run: PATH=$PATH:/usr/bin ./orchestration/dev/coins/bitcoin/run.sh -daemon
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ runs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cargo install svm-rs
|
cargo install svm-rs
|
||||||
svm install 0.8.26
|
svm install 0.8.25
|
||||||
svm use 0.8.26
|
svm use 0.8.25
|
||||||
|
|
||||||
# - name: Cache Rust
|
# - name: Cache Rust
|
||||||
# uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43
|
# uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43
|
||||||
|
|||||||
2
.github/actions/monero-wallet-rpc/action.yml
vendored
2
.github/actions/monero-wallet-rpc/action.yml
vendored
@@ -5,7 +5,7 @@ inputs:
|
|||||||
version:
|
version:
|
||||||
description: "Version to download and run"
|
description: "Version to download and run"
|
||||||
required: false
|
required: false
|
||||||
default: v0.18.3.4
|
default: v0.18.3.1
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
|
|||||||
4
.github/actions/monero/action.yml
vendored
4
.github/actions/monero/action.yml
vendored
@@ -5,7 +5,7 @@ inputs:
|
|||||||
version:
|
version:
|
||||||
description: "Version to download and run"
|
description: "Version to download and run"
|
||||||
required: false
|
required: false
|
||||||
default: v0.18.3.4
|
default: v0.18.3.1
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
@@ -43,4 +43,4 @@ runs:
|
|||||||
|
|
||||||
- name: Monero Regtest Daemon
|
- name: Monero Regtest Daemon
|
||||||
shell: bash
|
shell: bash
|
||||||
run: PATH=$PATH:/usr/bin ./orchestration/dev/networks/monero/run.sh --detach
|
run: PATH=$PATH:/usr/bin ./orchestration/dev/coins/monero/run.sh --detach
|
||||||
|
|||||||
4
.github/actions/test-dependencies/action.yml
vendored
4
.github/actions/test-dependencies/action.yml
vendored
@@ -5,12 +5,12 @@ inputs:
|
|||||||
monero-version:
|
monero-version:
|
||||||
description: "Monero version to download and run as a regtest node"
|
description: "Monero version to download and run as a regtest node"
|
||||||
required: false
|
required: false
|
||||||
default: v0.18.3.4
|
default: v0.18.3.1
|
||||||
|
|
||||||
bitcoin-version:
|
bitcoin-version:
|
||||||
description: "Bitcoin version to download and run as a regtest node"
|
description: "Bitcoin version to download and run as a regtest node"
|
||||||
required: false
|
required: false
|
||||||
default: "27.1"
|
default: "27.0"
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
|
|||||||
2
.github/nightly-version
vendored
2
.github/nightly-version
vendored
@@ -1 +1 @@
|
|||||||
nightly-2025-02-01
|
nightly-2024-07-01
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: networks/ Tests
|
name: coins/ Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -7,18 +7,18 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "common/**"
|
- "common/**"
|
||||||
- "crypto/**"
|
- "crypto/**"
|
||||||
- "networks/**"
|
- "coins/**"
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- "common/**"
|
- "common/**"
|
||||||
- "crypto/**"
|
- "crypto/**"
|
||||||
- "networks/**"
|
- "coins/**"
|
||||||
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test-networks:
|
test-coins:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
|
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
|
||||||
@@ -30,9 +30,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features \
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features \
|
||||||
-p bitcoin-serai \
|
-p bitcoin-serai \
|
||||||
-p build-solidity-contracts \
|
|
||||||
-p ethereum-schnorr-contract \
|
|
||||||
-p alloy-simple-request-transport \
|
-p alloy-simple-request-transport \
|
||||||
|
-p ethereum-serai \
|
||||||
-p serai-ethereum-relayer \
|
-p serai-ethereum-relayer \
|
||||||
-p monero-io \
|
-p monero-io \
|
||||||
-p monero-generators \
|
-p monero-generators \
|
||||||
@@ -46,4 +45,7 @@ jobs:
|
|||||||
-p monero-simple-request-rpc \
|
-p monero-simple-request-rpc \
|
||||||
-p monero-address \
|
-p monero-address \
|
||||||
-p monero-wallet \
|
-p monero-wallet \
|
||||||
|
-p monero-seed \
|
||||||
|
-p polyseed \
|
||||||
|
-p monero-wallet-util \
|
||||||
-p monero-serai-verify-chain
|
-p monero-serai-verify-chain
|
||||||
2
.github/workflows/common-tests.yml
vendored
2
.github/workflows/common-tests.yml
vendored
@@ -27,8 +27,6 @@ jobs:
|
|||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features \
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features \
|
||||||
-p std-shims \
|
-p std-shims \
|
||||||
-p zalloc \
|
-p zalloc \
|
||||||
-p patchable-async-sleep \
|
|
||||||
-p serai-db \
|
-p serai-db \
|
||||||
-p serai-env \
|
-p serai-env \
|
||||||
-p serai-task \
|
|
||||||
-p simple-request
|
-p simple-request
|
||||||
|
|||||||
4
.github/workflows/coordinator-tests.yml
vendored
4
.github/workflows/coordinator-tests.yml
vendored
@@ -7,7 +7,7 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "common/**"
|
- "common/**"
|
||||||
- "crypto/**"
|
- "crypto/**"
|
||||||
- "networks/**"
|
- "coins/**"
|
||||||
- "message-queue/**"
|
- "message-queue/**"
|
||||||
- "coordinator/**"
|
- "coordinator/**"
|
||||||
- "orchestration/**"
|
- "orchestration/**"
|
||||||
@@ -18,7 +18,7 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "common/**"
|
- "common/**"
|
||||||
- "crypto/**"
|
- "crypto/**"
|
||||||
- "networks/**"
|
- "coins/**"
|
||||||
- "message-queue/**"
|
- "message-queue/**"
|
||||||
- "coordinator/**"
|
- "coordinator/**"
|
||||||
- "orchestration/**"
|
- "orchestration/**"
|
||||||
|
|||||||
4
.github/workflows/crypto-tests.yml
vendored
4
.github/workflows/crypto-tests.yml
vendored
@@ -35,10 +35,6 @@ jobs:
|
|||||||
-p multiexp \
|
-p multiexp \
|
||||||
-p schnorr-signatures \
|
-p schnorr-signatures \
|
||||||
-p dleq \
|
-p dleq \
|
||||||
-p generalized-bulletproofs \
|
|
||||||
-p generalized-bulletproofs-circuit-abstraction \
|
|
||||||
-p ec-divisors \
|
|
||||||
-p generalized-bulletproofs-ec-gadgets \
|
|
||||||
-p dkg \
|
-p dkg \
|
||||||
-p modular-frost \
|
-p modular-frost \
|
||||||
-p frost-schnorrkel
|
-p frost-schnorrkel
|
||||||
|
|||||||
31
.github/workflows/lint.yml
vendored
31
.github/workflows/lint.yml
vendored
@@ -73,15 +73,6 @@ jobs:
|
|||||||
- name: Run rustfmt
|
- name: Run rustfmt
|
||||||
run: cargo +${{ steps.nightly.outputs.version }} fmt -- --check
|
run: cargo +${{ steps.nightly.outputs.version }} fmt -- --check
|
||||||
|
|
||||||
- name: Install foundry
|
|
||||||
uses: foundry-rs/foundry-toolchain@8f1998e9878d786675189ef566a2e4bf24869773
|
|
||||||
with:
|
|
||||||
version: nightly-41d4e5437107f6f42c7711123890147bc736a609
|
|
||||||
cache: false
|
|
||||||
|
|
||||||
- name: Run forge fmt
|
|
||||||
run: FOUNDRY_FMT_SORT_INPUTS=false FOUNDRY_FMT_LINE_LENGTH=100 FOUNDRY_FMT_TAB_WIDTH=2 FOUNDRY_FMT_BRACKET_SPACING=true FOUNDRY_FMT_INT_TYPES=preserve forge fmt --check $(find . -iname "*.sol")
|
|
||||||
|
|
||||||
machete:
|
machete:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -90,25 +81,3 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cargo install cargo-machete
|
cargo install cargo-machete
|
||||||
cargo machete
|
cargo machete
|
||||||
|
|
||||||
slither:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
|
|
||||||
- name: Slither
|
|
||||||
run: |
|
|
||||||
python3 -m pip install solc-select
|
|
||||||
solc-select install 0.8.26
|
|
||||||
solc-select use 0.8.26
|
|
||||||
|
|
||||||
python3 -m pip install slither-analyzer
|
|
||||||
|
|
||||||
slither --include-paths ./networks/ethereum/schnorr/contracts/Schnorr.sol
|
|
||||||
slither --include-paths ./networks/ethereum/schnorr/contracts ./networks/ethereum/schnorr/contracts/tests/Schnorr.sol
|
|
||||||
slither processor/ethereum/deployer/contracts/Deployer.sol
|
|
||||||
slither processor/ethereum/erc20/contracts/IERC20.sol
|
|
||||||
|
|
||||||
cp networks/ethereum/schnorr/contracts/Schnorr.sol processor/ethereum/router/contracts/
|
|
||||||
cp processor/ethereum/erc20/contracts/IERC20.sol processor/ethereum/router/contracts/
|
|
||||||
cd processor/ethereum/router/contracts
|
|
||||||
slither Router.sol
|
|
||||||
|
|||||||
13
.github/workflows/monero-tests.yaml
vendored
13
.github/workflows/monero-tests.yaml
vendored
@@ -5,12 +5,12 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- develop
|
- develop
|
||||||
paths:
|
paths:
|
||||||
- "networks/monero/**"
|
- "coins/monero/**"
|
||||||
- "processor/**"
|
- "processor/**"
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- "networks/monero/**"
|
- "coins/monero/**"
|
||||||
- "processor/**"
|
- "processor/**"
|
||||||
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -39,6 +39,9 @@ jobs:
|
|||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-simple-request-rpc --lib
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-simple-request-rpc --lib
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-address --lib
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-address --lib
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet --lib
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet --lib
|
||||||
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-seed --lib
|
||||||
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package polyseed --lib
|
||||||
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet-util --lib
|
||||||
|
|
||||||
# Doesn't run unit tests with features as the tests workflow will
|
# Doesn't run unit tests with features as the tests workflow will
|
||||||
|
|
||||||
@@ -47,7 +50,7 @@ jobs:
|
|||||||
# Test against all supported protocol versions
|
# Test against all supported protocol versions
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
version: [v0.17.3.2, v0.18.3.4]
|
version: [v0.17.3.2, v0.18.2.0]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
|
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
|
||||||
@@ -62,11 +65,13 @@ jobs:
|
|||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-serai --test '*'
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-serai --test '*'
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-simple-request-rpc --test '*'
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-simple-request-rpc --test '*'
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet --test '*'
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet --test '*'
|
||||||
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet-util --test '*'
|
||||||
|
|
||||||
- name: Run Integration Tests
|
- name: Run Integration Tests
|
||||||
# Don't run if the the tests workflow also will
|
# Don't run if the the tests workflow also will
|
||||||
if: ${{ matrix.version != 'v0.18.3.4' }}
|
if: ${{ matrix.version != 'v0.18.2.0' }}
|
||||||
run: |
|
run: |
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-serai --all-features --test '*'
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-serai --all-features --test '*'
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-simple-request-rpc --test '*'
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-simple-request-rpc --test '*'
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet --all-features --test '*'
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet --all-features --test '*'
|
||||||
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet-util --all-features --test '*'
|
||||||
|
|||||||
259
.github/workflows/msrv.yml
vendored
259
.github/workflows/msrv.yml
vendored
@@ -1,259 +0,0 @@
|
|||||||
name: Weekly MSRV Check
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * 0"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
msrv-common:
|
|
||||||
name: Run cargo msrv on common
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
|
|
||||||
|
|
||||||
- name: Install Build Dependencies
|
|
||||||
uses: ./.github/actions/build-dependencies
|
|
||||||
|
|
||||||
- name: Install cargo msrv
|
|
||||||
run: cargo install --locked cargo-msrv
|
|
||||||
|
|
||||||
- name: Run cargo msrv on common
|
|
||||||
run: |
|
|
||||||
cargo msrv verify --manifest-path common/zalloc/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path common/std-shims/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path common/env/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path common/db/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path common/task/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path common/request/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path common/patchable-async-sleep/Cargo.toml
|
|
||||||
|
|
||||||
msrv-crypto:
|
|
||||||
name: Run cargo msrv on crypto
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
|
|
||||||
|
|
||||||
- name: Install Build Dependencies
|
|
||||||
uses: ./.github/actions/build-dependencies
|
|
||||||
|
|
||||||
- name: Install cargo msrv
|
|
||||||
run: cargo install --locked cargo-msrv
|
|
||||||
|
|
||||||
- name: Run cargo msrv on crypto
|
|
||||||
run: |
|
|
||||||
cargo msrv verify --manifest-path crypto/transcript/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path crypto/ff-group-tests/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path crypto/dalek-ff-group/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path crypto/ed448/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path crypto/multiexp/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path crypto/dleq/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path crypto/ciphersuite/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path crypto/schnorr/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path crypto/evrf/generalized-bulletproofs/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path crypto/evrf/circuit-abstraction/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path crypto/evrf/divisors/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path crypto/evrf/ec-gadgets/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path crypto/evrf/embedwards25519/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path crypto/evrf/secq256k1/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path crypto/dkg/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path crypto/frost/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path crypto/schnorrkel/Cargo.toml
|
|
||||||
|
|
||||||
msrv-networks:
|
|
||||||
name: Run cargo msrv on networks
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
|
|
||||||
|
|
||||||
- name: Install Build Dependencies
|
|
||||||
uses: ./.github/actions/build-dependencies
|
|
||||||
|
|
||||||
- name: Install cargo msrv
|
|
||||||
run: cargo install --locked cargo-msrv
|
|
||||||
|
|
||||||
- name: Run cargo msrv on networks
|
|
||||||
run: |
|
|
||||||
cargo msrv verify --manifest-path networks/bitcoin/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path networks/ethereum/build-contracts/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path networks/ethereum/schnorr/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path networks/ethereum/alloy-simple-request-transport/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path networks/ethereum/relayer/Cargo.toml --features parity-db
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path networks/monero/io/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path networks/monero/generators/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path networks/monero/primitives/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path networks/monero/ringct/mlsag/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path networks/monero/ringct/clsag/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path networks/monero/ringct/borromean/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path networks/monero/ringct/bulletproofs/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path networks/monero/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path networks/monero/rpc/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path networks/monero/rpc/simple-request/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path networks/monero/wallet/address/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path networks/monero/wallet/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path networks/monero/verify-chain/Cargo.toml
|
|
||||||
|
|
||||||
msrv-message-queue:
|
|
||||||
name: Run cargo msrv on message-queue
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
|
|
||||||
|
|
||||||
- name: Install Build Dependencies
|
|
||||||
uses: ./.github/actions/build-dependencies
|
|
||||||
|
|
||||||
- name: Install cargo msrv
|
|
||||||
run: cargo install --locked cargo-msrv
|
|
||||||
|
|
||||||
- name: Run cargo msrv on message-queue
|
|
||||||
run: |
|
|
||||||
cargo msrv verify --manifest-path message-queue/Cargo.toml --features parity-db
|
|
||||||
|
|
||||||
msrv-processor:
|
|
||||||
name: Run cargo msrv on processor
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
|
|
||||||
|
|
||||||
- name: Install Build Dependencies
|
|
||||||
uses: ./.github/actions/build-dependencies
|
|
||||||
|
|
||||||
- name: Install cargo msrv
|
|
||||||
run: cargo install --locked cargo-msrv
|
|
||||||
|
|
||||||
- name: Run cargo msrv on processor
|
|
||||||
run: |
|
|
||||||
cargo msrv verify --manifest-path processor/view-keys/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path processor/primitives/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path processor/messages/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path processor/scanner/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path processor/scheduler/primitives/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path processor/scheduler/smart-contract/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path processor/scheduler/utxo/primitives/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path processor/scheduler/utxo/standard/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path processor/scheduler/utxo/transaction-chaining/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path processor/key-gen/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path processor/frost-attempt-manager/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path processor/signers/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path processor/bin/Cargo.toml --features parity-db
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path processor/bitcoin/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path processor/ethereum/primitives/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path processor/ethereum/test-primitives/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path processor/ethereum/erc20/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path processor/ethereum/deployer/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path processor/ethereum/router/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path processor/ethereum/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path processor/monero/Cargo.toml
|
|
||||||
|
|
||||||
msrv-coordinator:
|
|
||||||
name: Run cargo msrv on coordinator
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
|
|
||||||
|
|
||||||
- name: Install Build Dependencies
|
|
||||||
uses: ./.github/actions/build-dependencies
|
|
||||||
|
|
||||||
- name: Install cargo msrv
|
|
||||||
run: cargo install --locked cargo-msrv
|
|
||||||
|
|
||||||
- name: Run cargo msrv on coordinator
|
|
||||||
run: |
|
|
||||||
cargo msrv verify --manifest-path coordinator/tributary-sdk/tendermint/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path coordinator/tributary-sdk/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path coordinator/cosign/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path coordinator/substrate/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path coordinator/tributary/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path coordinator/p2p/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path coordinator/p2p/libp2p/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path coordinator/Cargo.toml
|
|
||||||
|
|
||||||
msrv-substrate:
|
|
||||||
name: Run cargo msrv on substrate
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
|
|
||||||
|
|
||||||
- name: Install Build Dependencies
|
|
||||||
uses: ./.github/actions/build-dependencies
|
|
||||||
|
|
||||||
- name: Install cargo msrv
|
|
||||||
run: cargo install --locked cargo-msrv
|
|
||||||
|
|
||||||
- name: Run cargo msrv on substrate
|
|
||||||
run: |
|
|
||||||
cargo msrv verify --manifest-path substrate/primitives/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path substrate/coins/primitives/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path substrate/coins/pallet/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path substrate/dex/pallet/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path substrate/economic-security/pallet/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path substrate/genesis-liquidity/primitives/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path substrate/genesis-liquidity/pallet/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path substrate/in-instructions/primitives/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path substrate/in-instructions/pallet/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path substrate/validator-sets/pallet/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path substrate/validator-sets/primitives/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path substrate/emissions/primitives/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path substrate/emissions/pallet/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path substrate/signals/primitives/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path substrate/signals/pallet/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path substrate/abi/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path substrate/client/Cargo.toml
|
|
||||||
|
|
||||||
cargo msrv verify --manifest-path substrate/runtime/Cargo.toml
|
|
||||||
cargo msrv verify --manifest-path substrate/node/Cargo.toml
|
|
||||||
|
|
||||||
msrv-orchestration:
|
|
||||||
name: Run cargo msrv on orchestration
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
|
|
||||||
|
|
||||||
- name: Install Build Dependencies
|
|
||||||
uses: ./.github/actions/build-dependencies
|
|
||||||
|
|
||||||
- name: Install cargo msrv
|
|
||||||
run: cargo install --locked cargo-msrv
|
|
||||||
|
|
||||||
- name: Run cargo msrv on message-queue
|
|
||||||
run: |
|
|
||||||
cargo msrv verify --manifest-path orchestration/Cargo.toml
|
|
||||||
|
|
||||||
msrv-mini:
|
|
||||||
name: Run cargo msrv on mini
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
|
|
||||||
|
|
||||||
- name: Install Build Dependencies
|
|
||||||
uses: ./.github/actions/build-dependencies
|
|
||||||
|
|
||||||
- name: Install cargo msrv
|
|
||||||
run: cargo install --locked cargo-msrv
|
|
||||||
|
|
||||||
- name: Run cargo msrv on mini
|
|
||||||
run: |
|
|
||||||
cargo msrv verify --manifest-path mini/Cargo.toml
|
|
||||||
4
.github/workflows/no-std.yml
vendored
4
.github/workflows/no-std.yml
vendored
@@ -7,14 +7,14 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "common/**"
|
- "common/**"
|
||||||
- "crypto/**"
|
- "crypto/**"
|
||||||
- "networks/**"
|
- "coins/**"
|
||||||
- "tests/no-std/**"
|
- "tests/no-std/**"
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- "common/**"
|
- "common/**"
|
||||||
- "crypto/**"
|
- "crypto/**"
|
||||||
- "networks/**"
|
- "coins/**"
|
||||||
- "tests/no-std/**"
|
- "tests/no-std/**"
|
||||||
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|||||||
37
.github/workflows/pages.yml
vendored
37
.github/workflows/pages.yml
vendored
@@ -1,7 +1,6 @@
|
|||||||
# MIT License
|
# MIT License
|
||||||
#
|
#
|
||||||
# Copyright (c) 2022 just-the-docs
|
# Copyright (c) 2022 just-the-docs
|
||||||
# Copyright (c) 2022-2024 Luke Parker
|
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -21,21 +20,31 @@
|
|||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
name: Deploy Rust docs and Jekyll site to Pages
|
# This workflow uses actions that are not certified by GitHub.
|
||||||
|
# They are provided by a third-party and are governed by
|
||||||
|
# separate terms of service, privacy policy, and support
|
||||||
|
# documentation.
|
||||||
|
|
||||||
|
# Sample workflow for building and deploying a Jekyll site to GitHub Pages
|
||||||
|
name: Deploy Jekyll site to Pages
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "develop"
|
- "develop"
|
||||||
|
paths:
|
||||||
|
- "docs/**"
|
||||||
|
|
||||||
|
# Allows you to run this workflow manually from the Actions tab
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
pages: write
|
pages: write
|
||||||
id-token: write
|
id-token: write
|
||||||
|
|
||||||
# Only allow one concurrent deployment
|
# Allow one concurrent deployment
|
||||||
concurrency:
|
concurrency:
|
||||||
group: "pages"
|
group: "pages"
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
@@ -44,6 +53,9 @@ jobs:
|
|||||||
# Build job
|
# Build job
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: docs
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@@ -57,24 +69,11 @@ jobs:
|
|||||||
id: pages
|
id: pages
|
||||||
uses: actions/configure-pages@v3
|
uses: actions/configure-pages@v3
|
||||||
- name: Build with Jekyll
|
- name: Build with Jekyll
|
||||||
run: cd ${{ github.workspace }}/docs && bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
|
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
|
||||||
env:
|
env:
|
||||||
JEKYLL_ENV: production
|
JEKYLL_ENV: production
|
||||||
|
|
||||||
- name: Get nightly version to use
|
|
||||||
id: nightly
|
|
||||||
shell: bash
|
|
||||||
run: echo "version=$(cat .github/nightly-version)" >> $GITHUB_OUTPUT
|
|
||||||
- name: Build Dependencies
|
|
||||||
uses: ./.github/actions/build-dependencies
|
|
||||||
- name: Buld Rust docs
|
|
||||||
run: |
|
|
||||||
rustup toolchain install ${{ steps.nightly.outputs.version }} --profile minimal -t wasm32-unknown-unknown -c rust-docs
|
|
||||||
RUSTDOCFLAGS="--cfg docsrs" cargo +${{ steps.nightly.outputs.version }} doc --workspace --all-features
|
|
||||||
mv target/doc docs/_site/rust
|
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-pages-artifact@v3
|
uses: actions/upload-pages-artifact@v1
|
||||||
with:
|
with:
|
||||||
path: "docs/_site/"
|
path: "docs/_site/"
|
||||||
|
|
||||||
@@ -88,4 +87,4 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
id: deployment
|
id: deployment
|
||||||
uses: actions/deploy-pages@v4
|
uses: actions/deploy-pages@v2
|
||||||
|
|||||||
4
.github/workflows/processor-tests.yml
vendored
4
.github/workflows/processor-tests.yml
vendored
@@ -7,7 +7,7 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "common/**"
|
- "common/**"
|
||||||
- "crypto/**"
|
- "crypto/**"
|
||||||
- "networks/**"
|
- "coins/**"
|
||||||
- "message-queue/**"
|
- "message-queue/**"
|
||||||
- "processor/**"
|
- "processor/**"
|
||||||
- "orchestration/**"
|
- "orchestration/**"
|
||||||
@@ -18,7 +18,7 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "common/**"
|
- "common/**"
|
||||||
- "crypto/**"
|
- "crypto/**"
|
||||||
- "networks/**"
|
- "coins/**"
|
||||||
- "message-queue/**"
|
- "message-queue/**"
|
||||||
- "processor/**"
|
- "processor/**"
|
||||||
- "orchestration/**"
|
- "orchestration/**"
|
||||||
|
|||||||
37
.github/workflows/tests.yml
vendored
37
.github/workflows/tests.yml
vendored
@@ -7,7 +7,7 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "common/**"
|
- "common/**"
|
||||||
- "crypto/**"
|
- "crypto/**"
|
||||||
- "networks/**"
|
- "coins/**"
|
||||||
- "message-queue/**"
|
- "message-queue/**"
|
||||||
- "processor/**"
|
- "processor/**"
|
||||||
- "coordinator/**"
|
- "coordinator/**"
|
||||||
@@ -17,7 +17,7 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "common/**"
|
- "common/**"
|
||||||
- "crypto/**"
|
- "crypto/**"
|
||||||
- "networks/**"
|
- "coins/**"
|
||||||
- "message-queue/**"
|
- "message-queue/**"
|
||||||
- "processor/**"
|
- "processor/**"
|
||||||
- "coordinator/**"
|
- "coordinator/**"
|
||||||
@@ -39,33 +39,9 @@ jobs:
|
|||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features \
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features \
|
||||||
-p serai-message-queue \
|
-p serai-message-queue \
|
||||||
-p serai-processor-messages \
|
-p serai-processor-messages \
|
||||||
-p serai-processor-key-gen \
|
-p serai-processor \
|
||||||
-p serai-processor-view-keys \
|
|
||||||
-p serai-processor-frost-attempt-manager \
|
|
||||||
-p serai-processor-primitives \
|
|
||||||
-p serai-processor-scanner \
|
|
||||||
-p serai-processor-scheduler-primitives \
|
|
||||||
-p serai-processor-utxo-scheduler-primitives \
|
|
||||||
-p serai-processor-utxo-scheduler \
|
|
||||||
-p serai-processor-transaction-chaining-scheduler \
|
|
||||||
-p serai-processor-smart-contract-scheduler \
|
|
||||||
-p serai-processor-signers \
|
|
||||||
-p serai-processor-bin \
|
|
||||||
-p serai-bitcoin-processor \
|
|
||||||
-p serai-processor-ethereum-primitives \
|
|
||||||
-p serai-processor-ethereum-test-primitives \
|
|
||||||
-p serai-processor-ethereum-deployer \
|
|
||||||
-p serai-processor-ethereum-router \
|
|
||||||
-p serai-processor-ethereum-erc20 \
|
|
||||||
-p serai-ethereum-processor \
|
|
||||||
-p serai-monero-processor \
|
|
||||||
-p tendermint-machine \
|
-p tendermint-machine \
|
||||||
-p tributary-sdk \
|
-p tributary-chain \
|
||||||
-p serai-cosign \
|
|
||||||
-p serai-coordinator-substrate \
|
|
||||||
-p serai-coordinator-tributary \
|
|
||||||
-p serai-coordinator-p2p \
|
|
||||||
-p serai-coordinator-libp2p-p2p \
|
|
||||||
-p serai-coordinator \
|
-p serai-coordinator \
|
||||||
-p serai-orchestrator \
|
-p serai-orchestrator \
|
||||||
-p serai-docker-tests
|
-p serai-docker-tests
|
||||||
@@ -87,11 +63,6 @@ jobs:
|
|||||||
-p serai-dex-pallet \
|
-p serai-dex-pallet \
|
||||||
-p serai-validator-sets-primitives \
|
-p serai-validator-sets-primitives \
|
||||||
-p serai-validator-sets-pallet \
|
-p serai-validator-sets-pallet \
|
||||||
-p serai-genesis-liquidity-primitives \
|
|
||||||
-p serai-genesis-liquidity-pallet \
|
|
||||||
-p serai-emissions-primitives \
|
|
||||||
-p serai-emissions-pallet \
|
|
||||||
-p serai-economic-security-pallet \
|
|
||||||
-p serai-in-instructions-primitives \
|
-p serai-in-instructions-primitives \
|
||||||
-p serai-in-instructions-pallet \
|
-p serai-in-instructions-pallet \
|
||||||
-p serai-signals-primitives \
|
-p serai-signals-primitives \
|
||||||
|
|||||||
5049
Cargo.lock
generated
5049
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
123
Cargo.toml
123
Cargo.toml
@@ -6,6 +6,7 @@ members = [
|
|||||||
"patches/parking_lot",
|
"patches/parking_lot",
|
||||||
"patches/zstd",
|
"patches/zstd",
|
||||||
"patches/rocksdb",
|
"patches/rocksdb",
|
||||||
|
"patches/proc-macro-crate",
|
||||||
|
|
||||||
# std patches
|
# std patches
|
||||||
"patches/matches",
|
"patches/matches",
|
||||||
@@ -17,10 +18,8 @@ members = [
|
|||||||
|
|
||||||
"common/std-shims",
|
"common/std-shims",
|
||||||
"common/zalloc",
|
"common/zalloc",
|
||||||
"common/patchable-async-sleep",
|
|
||||||
"common/db",
|
"common/db",
|
||||||
"common/env",
|
"common/env",
|
||||||
"common/task",
|
|
||||||
"common/request",
|
"common/request",
|
||||||
|
|
||||||
"crypto/transcript",
|
"crypto/transcript",
|
||||||
@@ -31,75 +30,43 @@ members = [
|
|||||||
"crypto/ciphersuite",
|
"crypto/ciphersuite",
|
||||||
|
|
||||||
"crypto/multiexp",
|
"crypto/multiexp",
|
||||||
|
|
||||||
"crypto/schnorr",
|
"crypto/schnorr",
|
||||||
"crypto/dleq",
|
"crypto/dleq",
|
||||||
|
|
||||||
"crypto/evrf/secq256k1",
|
|
||||||
"crypto/evrf/embedwards25519",
|
|
||||||
"crypto/evrf/generalized-bulletproofs",
|
|
||||||
"crypto/evrf/circuit-abstraction",
|
|
||||||
"crypto/evrf/divisors",
|
|
||||||
"crypto/evrf/ec-gadgets",
|
|
||||||
|
|
||||||
"crypto/dkg",
|
"crypto/dkg",
|
||||||
"crypto/frost",
|
"crypto/frost",
|
||||||
"crypto/schnorrkel",
|
"crypto/schnorrkel",
|
||||||
|
|
||||||
"networks/bitcoin",
|
"coins/bitcoin",
|
||||||
|
|
||||||
"networks/ethereum/build-contracts",
|
"coins/ethereum/alloy-simple-request-transport",
|
||||||
"networks/ethereum/schnorr",
|
"coins/ethereum",
|
||||||
"networks/ethereum/alloy-simple-request-transport",
|
"coins/ethereum/relayer",
|
||||||
"networks/ethereum/relayer",
|
|
||||||
|
|
||||||
"networks/monero/io",
|
"coins/monero/io",
|
||||||
"networks/monero/generators",
|
"coins/monero/generators",
|
||||||
"networks/monero/primitives",
|
"coins/monero/primitives",
|
||||||
"networks/monero/ringct/mlsag",
|
"coins/monero/ringct/mlsag",
|
||||||
"networks/monero/ringct/clsag",
|
"coins/monero/ringct/clsag",
|
||||||
"networks/monero/ringct/borromean",
|
"coins/monero/ringct/borromean",
|
||||||
"networks/monero/ringct/bulletproofs",
|
"coins/monero/ringct/bulletproofs",
|
||||||
"networks/monero",
|
"coins/monero",
|
||||||
"networks/monero/rpc",
|
"coins/monero/rpc",
|
||||||
"networks/monero/rpc/simple-request",
|
"coins/monero/rpc/simple-request",
|
||||||
"networks/monero/wallet/address",
|
"coins/monero/wallet/address",
|
||||||
"networks/monero/wallet",
|
"coins/monero/wallet",
|
||||||
"networks/monero/verify-chain",
|
"coins/monero/wallet/seed",
|
||||||
|
"coins/monero/wallet/polyseed",
|
||||||
|
"coins/monero/wallet/util",
|
||||||
|
"coins/monero/verify-chain",
|
||||||
|
|
||||||
"message-queue",
|
"message-queue",
|
||||||
|
|
||||||
"processor/messages",
|
"processor/messages",
|
||||||
|
"processor",
|
||||||
|
|
||||||
"processor/key-gen",
|
"coordinator/tributary/tendermint",
|
||||||
"processor/view-keys",
|
|
||||||
"processor/frost-attempt-manager",
|
|
||||||
|
|
||||||
"processor/primitives",
|
|
||||||
"processor/scanner",
|
|
||||||
"processor/scheduler/primitives",
|
|
||||||
"processor/scheduler/utxo/primitives",
|
|
||||||
"processor/scheduler/utxo/standard",
|
|
||||||
"processor/scheduler/utxo/transaction-chaining",
|
|
||||||
"processor/scheduler/smart-contract",
|
|
||||||
"processor/signers",
|
|
||||||
|
|
||||||
"processor/bin",
|
|
||||||
"processor/bitcoin",
|
|
||||||
"processor/ethereum/primitives",
|
|
||||||
"processor/ethereum/test-primitives",
|
|
||||||
"processor/ethereum/deployer",
|
|
||||||
"processor/ethereum/router",
|
|
||||||
"processor/ethereum/erc20",
|
|
||||||
"processor/ethereum",
|
|
||||||
"processor/monero",
|
|
||||||
|
|
||||||
"coordinator/tributary-sdk/tendermint",
|
|
||||||
"coordinator/tributary-sdk",
|
|
||||||
"coordinator/cosign",
|
|
||||||
"coordinator/substrate",
|
|
||||||
"coordinator/tributary",
|
"coordinator/tributary",
|
||||||
"coordinator/p2p",
|
|
||||||
"coordinator/p2p/libp2p",
|
|
||||||
"coordinator",
|
"coordinator",
|
||||||
|
|
||||||
"substrate/primitives",
|
"substrate/primitives",
|
||||||
@@ -112,14 +79,6 @@ members = [
|
|||||||
"substrate/validator-sets/primitives",
|
"substrate/validator-sets/primitives",
|
||||||
"substrate/validator-sets/pallet",
|
"substrate/validator-sets/pallet",
|
||||||
|
|
||||||
"substrate/genesis-liquidity/primitives",
|
|
||||||
"substrate/genesis-liquidity/pallet",
|
|
||||||
|
|
||||||
"substrate/emissions/primitives",
|
|
||||||
"substrate/emissions/pallet",
|
|
||||||
|
|
||||||
"substrate/economic-security/pallet",
|
|
||||||
|
|
||||||
"substrate/in-instructions/primitives",
|
"substrate/in-instructions/primitives",
|
||||||
"substrate/in-instructions/pallet",
|
"substrate/in-instructions/pallet",
|
||||||
|
|
||||||
@@ -141,9 +100,9 @@ members = [
|
|||||||
|
|
||||||
"tests/docker",
|
"tests/docker",
|
||||||
"tests/message-queue",
|
"tests/message-queue",
|
||||||
# TODO "tests/processor",
|
"tests/processor",
|
||||||
# TODO "tests/coordinator",
|
"tests/coordinator",
|
||||||
# TODO "tests/full-stack",
|
"tests/full-stack",
|
||||||
"tests/reproducible-runtime",
|
"tests/reproducible-runtime",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -151,32 +110,18 @@ members = [
|
|||||||
# to the extensive operations required for Bulletproofs
|
# to the extensive operations required for Bulletproofs
|
||||||
[profile.dev.package]
|
[profile.dev.package]
|
||||||
subtle = { opt-level = 3 }
|
subtle = { opt-level = 3 }
|
||||||
|
curve25519-dalek = { opt-level = 3 }
|
||||||
|
|
||||||
ff = { opt-level = 3 }
|
ff = { opt-level = 3 }
|
||||||
group = { opt-level = 3 }
|
group = { opt-level = 3 }
|
||||||
|
|
||||||
crypto-bigint = { opt-level = 3 }
|
crypto-bigint = { opt-level = 3 }
|
||||||
secp256k1 = { opt-level = 3 }
|
|
||||||
curve25519-dalek = { opt-level = 3 }
|
|
||||||
dalek-ff-group = { opt-level = 3 }
|
dalek-ff-group = { opt-level = 3 }
|
||||||
minimal-ed448 = { opt-level = 3 }
|
minimal-ed448 = { opt-level = 3 }
|
||||||
|
|
||||||
multiexp = { opt-level = 3 }
|
multiexp = { opt-level = 3 }
|
||||||
|
|
||||||
secq256k1 = { opt-level = 3 }
|
monero-serai = { opt-level = 3 }
|
||||||
embedwards25519 = { opt-level = 3 }
|
|
||||||
generalized-bulletproofs = { opt-level = 3 }
|
|
||||||
generalized-bulletproofs-circuit-abstraction = { opt-level = 3 }
|
|
||||||
ec-divisors = { opt-level = 3 }
|
|
||||||
generalized-bulletproofs-ec-gadgets = { opt-level = 3 }
|
|
||||||
|
|
||||||
dkg = { opt-level = 3 }
|
|
||||||
|
|
||||||
monero-generators = { opt-level = 3 }
|
|
||||||
monero-borromean = { opt-level = 3 }
|
|
||||||
monero-bulletproofs = { opt-level = 3 }
|
|
||||||
monero-mlsag = { opt-level = 3 }
|
|
||||||
monero-clsag = { opt-level = 3 }
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
panic = "unwind"
|
panic = "unwind"
|
||||||
@@ -185,12 +130,17 @@ panic = "unwind"
|
|||||||
# https://github.com/rust-lang-nursery/lazy-static.rs/issues/201
|
# https://github.com/rust-lang-nursery/lazy-static.rs/issues/201
|
||||||
lazy_static = { git = "https://github.com/rust-lang-nursery/lazy-static.rs", rev = "5735630d46572f1e5377c8f2ba0f79d18f53b10c" }
|
lazy_static = { git = "https://github.com/rust-lang-nursery/lazy-static.rs", rev = "5735630d46572f1e5377c8f2ba0f79d18f53b10c" }
|
||||||
|
|
||||||
|
# Needed due to dockertest's usage of `Rc`s when we need `Arc`s
|
||||||
|
dockertest = { git = "https://github.com/orcalabs/dockertest-rs", rev = "4dd6ae24738aa6dc5c89444cc822ea4745517493" }
|
||||||
|
|
||||||
parking_lot_core = { path = "patches/parking_lot_core" }
|
parking_lot_core = { path = "patches/parking_lot_core" }
|
||||||
parking_lot = { path = "patches/parking_lot" }
|
parking_lot = { path = "patches/parking_lot" }
|
||||||
# wasmtime pulls in an old version for this
|
# wasmtime pulls in an old version for this
|
||||||
zstd = { path = "patches/zstd" }
|
zstd = { path = "patches/zstd" }
|
||||||
# Needed for WAL compression
|
# Needed for WAL compression
|
||||||
rocksdb = { path = "patches/rocksdb" }
|
rocksdb = { path = "patches/rocksdb" }
|
||||||
|
# proc-macro-crate 2 binds to an old version of toml for msrv so we patch to 3
|
||||||
|
proc-macro-crate = { path = "patches/proc-macro-crate" }
|
||||||
|
|
||||||
# is-terminal now has an std-based solution with an equivalent API
|
# is-terminal now has an std-based solution with an equivalent API
|
||||||
is-terminal = { path = "patches/is-terminal" }
|
is-terminal = { path = "patches/is-terminal" }
|
||||||
@@ -207,8 +157,6 @@ directories-next = { path = "patches/directories-next" }
|
|||||||
|
|
||||||
[workspace.lints.clippy]
|
[workspace.lints.clippy]
|
||||||
unwrap_or_default = "allow"
|
unwrap_or_default = "allow"
|
||||||
map_unwrap_or = "allow"
|
|
||||||
needless_continue = "allow"
|
|
||||||
borrow_as_ptr = "deny"
|
borrow_as_ptr = "deny"
|
||||||
cast_lossless = "deny"
|
cast_lossless = "deny"
|
||||||
cast_possible_truncation = "deny"
|
cast_possible_truncation = "deny"
|
||||||
@@ -236,9 +184,11 @@ manual_instant_elapsed = "deny"
|
|||||||
manual_let_else = "deny"
|
manual_let_else = "deny"
|
||||||
manual_ok_or = "deny"
|
manual_ok_or = "deny"
|
||||||
manual_string_new = "deny"
|
manual_string_new = "deny"
|
||||||
|
map_unwrap_or = "deny"
|
||||||
match_bool = "deny"
|
match_bool = "deny"
|
||||||
match_same_arms = "deny"
|
match_same_arms = "deny"
|
||||||
missing_fields_in_debug = "deny"
|
missing_fields_in_debug = "deny"
|
||||||
|
needless_continue = "deny"
|
||||||
needless_pass_by_value = "deny"
|
needless_pass_by_value = "deny"
|
||||||
ptr_cast_constness = "deny"
|
ptr_cast_constness = "deny"
|
||||||
range_minus_one = "deny"
|
range_minus_one = "deny"
|
||||||
@@ -246,7 +196,6 @@ range_plus_one = "deny"
|
|||||||
redundant_closure_for_method_calls = "deny"
|
redundant_closure_for_method_calls = "deny"
|
||||||
redundant_else = "deny"
|
redundant_else = "deny"
|
||||||
string_add_assign = "deny"
|
string_add_assign = "deny"
|
||||||
string_slice = "deny"
|
|
||||||
unchecked_duration_subtraction = "deny"
|
unchecked_duration_subtraction = "deny"
|
||||||
uninlined_format_args = "deny"
|
uninlined_format_args = "deny"
|
||||||
unnecessary_box_returns = "deny"
|
unnecessary_box_returns = "deny"
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -5,4 +5,4 @@ a full copy of the AGPL-3.0 License is included in the root of this repository
|
|||||||
as a reference text. This copy should be provided with any distribution of a
|
as a reference text. This copy should be provided with any distribution of a
|
||||||
crate licensed under the AGPL-3.0, as per its terms.
|
crate licensed under the AGPL-3.0, as per its terms.
|
||||||
|
|
||||||
The GitHub actions/workflows (`.github`) are licensed under the MIT license.
|
The GitHub actions (`.github/actions`) are licensed under the MIT license.
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ wallet.
|
|||||||
infrastructure, to our IETF-compliant FROST implementation, to a DLEq proof as
|
infrastructure, to our IETF-compliant FROST implementation, to a DLEq proof as
|
||||||
needed for Bitcoin-Monero atomic swaps.
|
needed for Bitcoin-Monero atomic swaps.
|
||||||
|
|
||||||
- `networks`: Various libraries intended for usage in Serai yet also by the
|
- `coins`: Various coin libraries intended for usage in Serai yet also by the
|
||||||
wider community. This means they will always support the functionality Serai
|
wider community. This means they will always support the functionality Serai
|
||||||
needs, yet won't disadvantage other use cases when possible.
|
needs, yet won't disadvantage other use cases when possible.
|
||||||
|
|
||||||
|
|||||||
6
audits/Cypher Stack coins bitcoin August 2023/README.md
Normal file
6
audits/Cypher Stack coins bitcoin August 2023/README.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Cypher Stack /coins/bitcoin Audit, August 2023
|
||||||
|
|
||||||
|
This audit was over the /coins/bitcoin folder. It is encompassing up to commit
|
||||||
|
5121ca75199dff7bd34230880a1fdd793012068c.
|
||||||
|
|
||||||
|
Please see https://github.com/cypherstack/serai-btc-audit for provenance.
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Cypher Stack /networks/bitcoin Audit, August 2023
|
|
||||||
|
|
||||||
This audit was over the `/networks/bitcoin` folder (at the time located at
|
|
||||||
`/coins/bitcoin`). It is encompassing up to commit
|
|
||||||
5121ca75199dff7bd34230880a1fdd793012068c.
|
|
||||||
|
|
||||||
Please see https://github.com/cypherstack/serai-btc-audit for provenance.
|
|
||||||
Binary file not shown.
@@ -1,427 +0,0 @@
|
|||||||
Attribution-ShareAlike 4.0 International
|
|
||||||
|
|
||||||
=======================================================================
|
|
||||||
|
|
||||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
|
||||||
does not provide legal services or legal advice. Distribution of
|
|
||||||
Creative Commons public licenses does not create a lawyer-client or
|
|
||||||
other relationship. Creative Commons makes its licenses and related
|
|
||||||
information available on an "as-is" basis. Creative Commons gives no
|
|
||||||
warranties regarding its licenses, any material licensed under their
|
|
||||||
terms and conditions, or any related information. Creative Commons
|
|
||||||
disclaims all liability for damages resulting from their use to the
|
|
||||||
fullest extent possible.
|
|
||||||
|
|
||||||
Using Creative Commons Public Licenses
|
|
||||||
|
|
||||||
Creative Commons public licenses provide a standard set of terms and
|
|
||||||
conditions that creators and other rights holders may use to share
|
|
||||||
original works of authorship and other material subject to copyright
|
|
||||||
and certain other rights specified in the public license below. The
|
|
||||||
following considerations are for informational purposes only, are not
|
|
||||||
exhaustive, and do not form part of our licenses.
|
|
||||||
|
|
||||||
Considerations for licensors: Our public licenses are
|
|
||||||
intended for use by those authorized to give the public
|
|
||||||
permission to use material in ways otherwise restricted by
|
|
||||||
copyright and certain other rights. Our licenses are
|
|
||||||
irrevocable. Licensors should read and understand the terms
|
|
||||||
and conditions of the license they choose before applying it.
|
|
||||||
Licensors should also secure all rights necessary before
|
|
||||||
applying our licenses so that the public can reuse the
|
|
||||||
material as expected. Licensors should clearly mark any
|
|
||||||
material not subject to the license. This includes other CC-
|
|
||||||
licensed material, or material used under an exception or
|
|
||||||
limitation to copyright. More considerations for licensors:
|
|
||||||
wiki.creativecommons.org/Considerations_for_licensors
|
|
||||||
|
|
||||||
Considerations for the public: By using one of our public
|
|
||||||
licenses, a licensor grants the public permission to use the
|
|
||||||
licensed material under specified terms and conditions. If
|
|
||||||
the licensor's permission is not necessary for any reason--for
|
|
||||||
example, because of any applicable exception or limitation to
|
|
||||||
copyright--then that use is not regulated by the license. Our
|
|
||||||
licenses grant only permissions under copyright and certain
|
|
||||||
other rights that a licensor has authority to grant. Use of
|
|
||||||
the licensed material may still be restricted for other
|
|
||||||
reasons, including because others have copyright or other
|
|
||||||
rights in the material. A licensor may make special requests,
|
|
||||||
such as asking that all changes be marked or described.
|
|
||||||
Although not required by our licenses, you are encouraged to
|
|
||||||
respect those requests where reasonable. More considerations
|
|
||||||
for the public:
|
|
||||||
wiki.creativecommons.org/Considerations_for_licensees
|
|
||||||
|
|
||||||
=======================================================================
|
|
||||||
|
|
||||||
Creative Commons Attribution-ShareAlike 4.0 International Public
|
|
||||||
License
|
|
||||||
|
|
||||||
By exercising the Licensed Rights (defined below), You accept and agree
|
|
||||||
to be bound by the terms and conditions of this Creative Commons
|
|
||||||
Attribution-ShareAlike 4.0 International Public License ("Public
|
|
||||||
License"). To the extent this Public License may be interpreted as a
|
|
||||||
contract, You are granted the Licensed Rights in consideration of Your
|
|
||||||
acceptance of these terms and conditions, and the Licensor grants You
|
|
||||||
such rights in consideration of benefits the Licensor receives from
|
|
||||||
making the Licensed Material available under these terms and
|
|
||||||
conditions.
|
|
||||||
|
|
||||||
|
|
||||||
Section 1 -- Definitions.
|
|
||||||
|
|
||||||
a. Adapted Material means material subject to Copyright and Similar
|
|
||||||
Rights that is derived from or based upon the Licensed Material
|
|
||||||
and in which the Licensed Material is translated, altered,
|
|
||||||
arranged, transformed, or otherwise modified in a manner requiring
|
|
||||||
permission under the Copyright and Similar Rights held by the
|
|
||||||
Licensor. For purposes of this Public License, where the Licensed
|
|
||||||
Material is a musical work, performance, or sound recording,
|
|
||||||
Adapted Material is always produced where the Licensed Material is
|
|
||||||
synched in timed relation with a moving image.
|
|
||||||
|
|
||||||
b. Adapter's License means the license You apply to Your Copyright
|
|
||||||
and Similar Rights in Your contributions to Adapted Material in
|
|
||||||
accordance with the terms and conditions of this Public License.
|
|
||||||
|
|
||||||
c. BY-SA Compatible License means a license listed at
|
|
||||||
creativecommons.org/compatiblelicenses, approved by Creative
|
|
||||||
Commons as essentially the equivalent of this Public License.
|
|
||||||
|
|
||||||
d. Copyright and Similar Rights means copyright and/or similar rights
|
|
||||||
closely related to copyright including, without limitation,
|
|
||||||
performance, broadcast, sound recording, and Sui Generis Database
|
|
||||||
Rights, without regard to how the rights are labeled or
|
|
||||||
categorized. For purposes of this Public License, the rights
|
|
||||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
|
||||||
Rights.
|
|
||||||
|
|
||||||
e. Effective Technological Measures means those measures that, in the
|
|
||||||
absence of proper authority, may not be circumvented under laws
|
|
||||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
|
||||||
Treaty adopted on December 20, 1996, and/or similar international
|
|
||||||
agreements.
|
|
||||||
|
|
||||||
f. Exceptions and Limitations means fair use, fair dealing, and/or
|
|
||||||
any other exception or limitation to Copyright and Similar Rights
|
|
||||||
that applies to Your use of the Licensed Material.
|
|
||||||
|
|
||||||
g. License Elements means the license attributes listed in the name
|
|
||||||
of a Creative Commons Public License. The License Elements of this
|
|
||||||
Public License are Attribution and ShareAlike.
|
|
||||||
|
|
||||||
h. Licensed Material means the artistic or literary work, database,
|
|
||||||
or other material to which the Licensor applied this Public
|
|
||||||
License.
|
|
||||||
|
|
||||||
i. Licensed Rights means the rights granted to You subject to the
|
|
||||||
terms and conditions of this Public License, which are limited to
|
|
||||||
all Copyright and Similar Rights that apply to Your use of the
|
|
||||||
Licensed Material and that the Licensor has authority to license.
|
|
||||||
|
|
||||||
j. Licensor means the individual(s) or entity(ies) granting rights
|
|
||||||
under this Public License.
|
|
||||||
|
|
||||||
k. Share means to provide material to the public by any means or
|
|
||||||
process that requires permission under the Licensed Rights, such
|
|
||||||
as reproduction, public display, public performance, distribution,
|
|
||||||
dissemination, communication, or importation, and to make material
|
|
||||||
available to the public including in ways that members of the
|
|
||||||
public may access the material from a place and at a time
|
|
||||||
individually chosen by them.
|
|
||||||
|
|
||||||
l. Sui Generis Database Rights means rights other than copyright
|
|
||||||
resulting from Directive 96/9/EC of the European Parliament and of
|
|
||||||
the Council of 11 March 1996 on the legal protection of databases,
|
|
||||||
as amended and/or succeeded, as well as other essentially
|
|
||||||
equivalent rights anywhere in the world.
|
|
||||||
|
|
||||||
m. You means the individual or entity exercising the Licensed Rights
|
|
||||||
under this Public License. Your has a corresponding meaning.
|
|
||||||
|
|
||||||
|
|
||||||
Section 2 -- Scope.
|
|
||||||
|
|
||||||
a. License grant.
|
|
||||||
|
|
||||||
1. Subject to the terms and conditions of this Public License,
|
|
||||||
the Licensor hereby grants You a worldwide, royalty-free,
|
|
||||||
non-sublicensable, non-exclusive, irrevocable license to
|
|
||||||
exercise the Licensed Rights in the Licensed Material to:
|
|
||||||
|
|
||||||
a. reproduce and Share the Licensed Material, in whole or
|
|
||||||
in part; and
|
|
||||||
|
|
||||||
b. produce, reproduce, and Share Adapted Material.
|
|
||||||
|
|
||||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
|
||||||
Exceptions and Limitations apply to Your use, this Public
|
|
||||||
License does not apply, and You do not need to comply with
|
|
||||||
its terms and conditions.
|
|
||||||
|
|
||||||
3. Term. The term of this Public License is specified in Section
|
|
||||||
6(a).
|
|
||||||
|
|
||||||
4. Media and formats; technical modifications allowed. The
|
|
||||||
Licensor authorizes You to exercise the Licensed Rights in
|
|
||||||
all media and formats whether now known or hereafter created,
|
|
||||||
and to make technical modifications necessary to do so. The
|
|
||||||
Licensor waives and/or agrees not to assert any right or
|
|
||||||
authority to forbid You from making technical modifications
|
|
||||||
necessary to exercise the Licensed Rights, including
|
|
||||||
technical modifications necessary to circumvent Effective
|
|
||||||
Technological Measures. For purposes of this Public License,
|
|
||||||
simply making modifications authorized by this Section 2(a)
|
|
||||||
(4) never produces Adapted Material.
|
|
||||||
|
|
||||||
5. Downstream recipients.
|
|
||||||
|
|
||||||
a. Offer from the Licensor -- Licensed Material. Every
|
|
||||||
recipient of the Licensed Material automatically
|
|
||||||
receives an offer from the Licensor to exercise the
|
|
||||||
Licensed Rights under the terms and conditions of this
|
|
||||||
Public License.
|
|
||||||
|
|
||||||
b. Additional offer from the Licensor -- Adapted Material.
|
|
||||||
Every recipient of Adapted Material from You
|
|
||||||
automatically receives an offer from the Licensor to
|
|
||||||
exercise the Licensed Rights in the Adapted Material
|
|
||||||
under the conditions of the Adapter's License You apply.
|
|
||||||
|
|
||||||
c. No downstream restrictions. You may not offer or impose
|
|
||||||
any additional or different terms or conditions on, or
|
|
||||||
apply any Effective Technological Measures to, the
|
|
||||||
Licensed Material if doing so restricts exercise of the
|
|
||||||
Licensed Rights by any recipient of the Licensed
|
|
||||||
Material.
|
|
||||||
|
|
||||||
6. No endorsement. Nothing in this Public License constitutes or
|
|
||||||
may be construed as permission to assert or imply that You
|
|
||||||
are, or that Your use of the Licensed Material is, connected
|
|
||||||
with, or sponsored, endorsed, or granted official status by,
|
|
||||||
the Licensor or others designated to receive attribution as
|
|
||||||
provided in Section 3(a)(1)(A)(i).
|
|
||||||
|
|
||||||
b. Other rights.
|
|
||||||
|
|
||||||
1. Moral rights, such as the right of integrity, are not
|
|
||||||
licensed under this Public License, nor are publicity,
|
|
||||||
privacy, and/or other similar personality rights; however, to
|
|
||||||
the extent possible, the Licensor waives and/or agrees not to
|
|
||||||
assert any such rights held by the Licensor to the limited
|
|
||||||
extent necessary to allow You to exercise the Licensed
|
|
||||||
Rights, but not otherwise.
|
|
||||||
|
|
||||||
2. Patent and trademark rights are not licensed under this
|
|
||||||
Public License.
|
|
||||||
|
|
||||||
3. To the extent possible, the Licensor waives any right to
|
|
||||||
collect royalties from You for the exercise of the Licensed
|
|
||||||
Rights, whether directly or through a collecting society
|
|
||||||
under any voluntary or waivable statutory or compulsory
|
|
||||||
licensing scheme. In all other cases the Licensor expressly
|
|
||||||
reserves any right to collect such royalties.
|
|
||||||
|
|
||||||
|
|
||||||
Section 3 -- License Conditions.
|
|
||||||
|
|
||||||
Your exercise of the Licensed Rights is expressly made subject to the
|
|
||||||
following conditions.
|
|
||||||
|
|
||||||
a. Attribution.
|
|
||||||
|
|
||||||
1. If You Share the Licensed Material (including in modified
|
|
||||||
form), You must:
|
|
||||||
|
|
||||||
a. retain the following if it is supplied by the Licensor
|
|
||||||
with the Licensed Material:
|
|
||||||
|
|
||||||
i. identification of the creator(s) of the Licensed
|
|
||||||
Material and any others designated to receive
|
|
||||||
attribution, in any reasonable manner requested by
|
|
||||||
the Licensor (including by pseudonym if
|
|
||||||
designated);
|
|
||||||
|
|
||||||
ii. a copyright notice;
|
|
||||||
|
|
||||||
iii. a notice that refers to this Public License;
|
|
||||||
|
|
||||||
iv. a notice that refers to the disclaimer of
|
|
||||||
warranties;
|
|
||||||
|
|
||||||
v. a URI or hyperlink to the Licensed Material to the
|
|
||||||
extent reasonably practicable;
|
|
||||||
|
|
||||||
b. indicate if You modified the Licensed Material and
|
|
||||||
retain an indication of any previous modifications; and
|
|
||||||
|
|
||||||
c. indicate the Licensed Material is licensed under this
|
|
||||||
Public License, and include the text of, or the URI or
|
|
||||||
hyperlink to, this Public License.
|
|
||||||
|
|
||||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
|
||||||
reasonable manner based on the medium, means, and context in
|
|
||||||
which You Share the Licensed Material. For example, it may be
|
|
||||||
reasonable to satisfy the conditions by providing a URI or
|
|
||||||
hyperlink to a resource that includes the required
|
|
||||||
information.
|
|
||||||
|
|
||||||
3. If requested by the Licensor, You must remove any of the
|
|
||||||
information required by Section 3(a)(1)(A) to the extent
|
|
||||||
reasonably practicable.
|
|
||||||
|
|
||||||
b. ShareAlike.
|
|
||||||
|
|
||||||
In addition to the conditions in Section 3(a), if You Share
|
|
||||||
Adapted Material You produce, the following conditions also apply.
|
|
||||||
|
|
||||||
1. The Adapter's License You apply must be a Creative Commons
|
|
||||||
license with the same License Elements, this version or
|
|
||||||
later, or a BY-SA Compatible License.
|
|
||||||
|
|
||||||
2. You must include the text of, or the URI or hyperlink to, the
|
|
||||||
Adapter's License You apply. You may satisfy this condition
|
|
||||||
in any reasonable manner based on the medium, means, and
|
|
||||||
context in which You Share Adapted Material.
|
|
||||||
|
|
||||||
3. You may not offer or impose any additional or different terms
|
|
||||||
or conditions on, or apply any Effective Technological
|
|
||||||
Measures to, Adapted Material that restrict exercise of the
|
|
||||||
rights granted under the Adapter's License You apply.
|
|
||||||
|
|
||||||
|
|
||||||
Section 4 -- Sui Generis Database Rights.
|
|
||||||
|
|
||||||
Where the Licensed Rights include Sui Generis Database Rights that
|
|
||||||
apply to Your use of the Licensed Material:
|
|
||||||
|
|
||||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
|
||||||
to extract, reuse, reproduce, and Share all or a substantial
|
|
||||||
portion of the contents of the database;
|
|
||||||
|
|
||||||
b. if You include all or a substantial portion of the database
|
|
||||||
contents in a database in which You have Sui Generis Database
|
|
||||||
Rights, then the database in which You have Sui Generis Database
|
|
||||||
Rights (but not its individual contents) is Adapted Material,
|
|
||||||
|
|
||||||
including for purposes of Section 3(b); and
|
|
||||||
c. You must comply with the conditions in Section 3(a) if You Share
|
|
||||||
all or a substantial portion of the contents of the database.
|
|
||||||
|
|
||||||
For the avoidance of doubt, this Section 4 supplements and does not
|
|
||||||
replace Your obligations under this Public License where the Licensed
|
|
||||||
Rights include other Copyright and Similar Rights.
|
|
||||||
|
|
||||||
|
|
||||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
|
||||||
|
|
||||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
|
||||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
|
||||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
|
||||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
|
||||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
|
||||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
|
||||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
|
||||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
|
||||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
|
||||||
|
|
||||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
|
||||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
|
||||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
|
||||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
|
||||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
|
||||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
|
||||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
|
||||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
|
||||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
|
||||||
|
|
||||||
c. The disclaimer of warranties and limitation of liability provided
|
|
||||||
above shall be interpreted in a manner that, to the extent
|
|
||||||
possible, most closely approximates an absolute disclaimer and
|
|
||||||
waiver of all liability.
|
|
||||||
|
|
||||||
|
|
||||||
Section 6 -- Term and Termination.
|
|
||||||
|
|
||||||
a. This Public License applies for the term of the Copyright and
|
|
||||||
Similar Rights licensed here. However, if You fail to comply with
|
|
||||||
this Public License, then Your rights under this Public License
|
|
||||||
terminate automatically.
|
|
||||||
|
|
||||||
b. Where Your right to use the Licensed Material has terminated under
|
|
||||||
Section 6(a), it reinstates:
|
|
||||||
|
|
||||||
1. automatically as of the date the violation is cured, provided
|
|
||||||
it is cured within 30 days of Your discovery of the
|
|
||||||
violation; or
|
|
||||||
|
|
||||||
2. upon express reinstatement by the Licensor.
|
|
||||||
|
|
||||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
|
||||||
right the Licensor may have to seek remedies for Your violations
|
|
||||||
of this Public License.
|
|
||||||
|
|
||||||
c. For the avoidance of doubt, the Licensor may also offer the
|
|
||||||
Licensed Material under separate terms or conditions or stop
|
|
||||||
distributing the Licensed Material at any time; however, doing so
|
|
||||||
will not terminate this Public License.
|
|
||||||
|
|
||||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
|
||||||
License.
|
|
||||||
|
|
||||||
|
|
||||||
Section 7 -- Other Terms and Conditions.
|
|
||||||
|
|
||||||
a. The Licensor shall not be bound by any additional or different
|
|
||||||
terms or conditions communicated by You unless expressly agreed.
|
|
||||||
|
|
||||||
b. Any arrangements, understandings, or agreements regarding the
|
|
||||||
Licensed Material not stated herein are separate from and
|
|
||||||
independent of the terms and conditions of this Public License.
|
|
||||||
|
|
||||||
|
|
||||||
Section 8 -- Interpretation.
|
|
||||||
|
|
||||||
a. For the avoidance of doubt, this Public License does not, and
|
|
||||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
|
||||||
conditions on any use of the Licensed Material that could lawfully
|
|
||||||
be made without permission under this Public License.
|
|
||||||
|
|
||||||
b. To the extent possible, if any provision of this Public License is
|
|
||||||
deemed unenforceable, it shall be automatically reformed to the
|
|
||||||
minimum extent necessary to make it enforceable. If the provision
|
|
||||||
cannot be reformed, it shall be severed from this Public License
|
|
||||||
without affecting the enforceability of the remaining terms and
|
|
||||||
conditions.
|
|
||||||
|
|
||||||
c. No term or condition of this Public License will be waived and no
|
|
||||||
failure to comply consented to unless expressly agreed to by the
|
|
||||||
Licensor.
|
|
||||||
|
|
||||||
d. Nothing in this Public License constitutes or may be interpreted
|
|
||||||
as a limitation upon, or waiver of, any privileges and immunities
|
|
||||||
that apply to the Licensor or You, including from the legal
|
|
||||||
processes of any jurisdiction or authority.
|
|
||||||
|
|
||||||
|
|
||||||
=======================================================================
|
|
||||||
|
|
||||||
Creative Commons is not a party to its public
|
|
||||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
|
||||||
its public licenses to material it publishes and in those instances
|
|
||||||
will be considered the “Licensor.” The text of the Creative Commons
|
|
||||||
public licenses is dedicated to the public domain under the CC0 Public
|
|
||||||
Domain Dedication. Except for the limited purpose of indicating that
|
|
||||||
material is shared under a Creative Commons public license or as
|
|
||||||
otherwise permitted by the Creative Commons policies published at
|
|
||||||
creativecommons.org/policies, Creative Commons does not authorize the
|
|
||||||
use of the trademark "Creative Commons" or any other trademark or logo
|
|
||||||
of Creative Commons without its prior written consent including,
|
|
||||||
without limitation, in connection with any unauthorized modifications
|
|
||||||
to any of its public licenses or any other arrangements,
|
|
||||||
understandings, or agreements concerning use of licensed material. For
|
|
||||||
the avoidance of doubt, this paragraph does not form part of the
|
|
||||||
public licenses.
|
|
||||||
|
|
||||||
Creative Commons may be contacted at creativecommons.org.
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# Trail of Bits Ethereum Contracts Audit, June 2025
|
|
||||||
|
|
||||||
This audit included:
|
|
||||||
- Our Schnorr contract and associated library (/networks/ethereum/schnorr)
|
|
||||||
- Our Ethereum primitives library (/processor/ethereum/primitives)
|
|
||||||
- Our Deployer contract and associated library (/processor/ethereum/deployer)
|
|
||||||
- Our ERC20 library (/processor/ethereum/erc20)
|
|
||||||
- Our Router contract and associated library (/processor/ethereum/router)
|
|
||||||
|
|
||||||
It is encompassing up to commit 4e0c58464fc4673623938335f06e2e9ea96ca8dd.
|
|
||||||
|
|
||||||
Please see
|
|
||||||
https://github.com/trailofbits/publications/blob/30c4fa3ebf39ff8e4d23ba9567344ec9691697b5/reviews/2025-04-serai-dex-security-review.pdf
|
|
||||||
for provenance.
|
|
||||||
@@ -3,10 +3,10 @@ name = "bitcoin-serai"
|
|||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
description = "A Bitcoin library for FROST-signing transactions"
|
description = "A Bitcoin library for FROST-signing transactions"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/networks/bitcoin"
|
repository = "https://github.com/serai-dex/serai/tree/develop/coins/bitcoin"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>", "Vrx <vrx00@proton.me>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>", "Vrx <vrx00@proton.me>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.80"
|
rust-version = "1.79"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
@@ -18,7 +18,7 @@ workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
std-shims = { version = "0.1.1", path = "../../common/std-shims", default-features = false }
|
std-shims = { version = "0.1.1", path = "../../common/std-shims", default-features = false }
|
||||||
|
|
||||||
thiserror = { version = "2", default-features = false }
|
thiserror = { version = "1", default-features = false, optional = true }
|
||||||
|
|
||||||
zeroize = { version = "^1.5", default-features = false }
|
zeroize = { version = "^1.5", default-features = false }
|
||||||
rand_core = { version = "0.6", default-features = false }
|
rand_core = { version = "0.6", default-features = false }
|
||||||
@@ -26,6 +26,8 @@ rand_core = { version = "0.6", default-features = false }
|
|||||||
bitcoin = { version = "0.32", default-features = false }
|
bitcoin = { version = "0.32", default-features = false }
|
||||||
|
|
||||||
k256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits"] }
|
k256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits"] }
|
||||||
|
|
||||||
|
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", version = "0.3", default-features = false, features = ["recommended"], optional = true }
|
||||||
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.8", default-features = false, features = ["secp256k1"], optional = true }
|
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.8", default-features = false, features = ["secp256k1"], optional = true }
|
||||||
|
|
||||||
hex = { version = "0.4", default-features = false, optional = true }
|
hex = { version = "0.4", default-features = false, optional = true }
|
||||||
@@ -44,7 +46,7 @@ tokio = { version = "1", features = ["macros"] }
|
|||||||
std = [
|
std = [
|
||||||
"std-shims/std",
|
"std-shims/std",
|
||||||
|
|
||||||
"thiserror/std",
|
"thiserror",
|
||||||
|
|
||||||
"zeroize/std",
|
"zeroize/std",
|
||||||
"rand_core/std",
|
"rand_core/std",
|
||||||
@@ -53,6 +55,8 @@ std = [
|
|||||||
"bitcoin/serde",
|
"bitcoin/serde",
|
||||||
|
|
||||||
"k256/std",
|
"k256/std",
|
||||||
|
|
||||||
|
"transcript/std",
|
||||||
"frost",
|
"frost",
|
||||||
|
|
||||||
"hex/std",
|
"hex/std",
|
||||||
@@ -40,12 +40,14 @@ mod frost_crypto {
|
|||||||
|
|
||||||
use bitcoin::hashes::{HashEngine, Hash, sha256::Hash as Sha256};
|
use bitcoin::hashes::{HashEngine, Hash, sha256::Hash as Sha256};
|
||||||
|
|
||||||
|
use transcript::Transcript;
|
||||||
|
|
||||||
use k256::{elliptic_curve::ops::Reduce, U256, Scalar};
|
use k256::{elliptic_curve::ops::Reduce, U256, Scalar};
|
||||||
|
|
||||||
use frost::{
|
use frost::{
|
||||||
curve::{Ciphersuite, Secp256k1},
|
curve::{Ciphersuite, Secp256k1},
|
||||||
Participant, ThresholdKeys, ThresholdView, FrostError,
|
Participant, ThresholdKeys, ThresholdView, FrostError,
|
||||||
algorithm::{Hram as HramTrait, Algorithm, IetfSchnorr as FrostSchnorr},
|
algorithm::{Hram as HramTrait, Algorithm, Schnorr as FrostSchnorr},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -80,17 +82,16 @@ mod frost_crypto {
|
|||||||
///
|
///
|
||||||
/// This must be used with a ThresholdKeys whose group key is even. If it is odd, this will panic.
|
/// This must be used with a ThresholdKeys whose group key is even. If it is odd, this will panic.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Schnorr(FrostSchnorr<Secp256k1, Hram>);
|
pub struct Schnorr<T: Sync + Clone + Debug + Transcript>(FrostSchnorr<Secp256k1, T, Hram>);
|
||||||
impl Schnorr {
|
impl<T: Sync + Clone + Debug + Transcript> Schnorr<T> {
|
||||||
/// Construct a Schnorr algorithm continuing the specified transcript.
|
/// Construct a Schnorr algorithm continuing the specified transcript.
|
||||||
#[allow(clippy::new_without_default)]
|
pub fn new(transcript: T) -> Schnorr<T> {
|
||||||
pub fn new() -> Schnorr {
|
Schnorr(FrostSchnorr::new(transcript))
|
||||||
Schnorr(FrostSchnorr::ietf())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Algorithm<Secp256k1> for Schnorr {
|
impl<T: Sync + Clone + Debug + Transcript> Algorithm<Secp256k1> for Schnorr<T> {
|
||||||
type Transcript = <FrostSchnorr<Secp256k1, Hram> as Algorithm<Secp256k1>>::Transcript;
|
type Transcript = T;
|
||||||
type Addendum = ();
|
type Addendum = ();
|
||||||
type Signature = [u8; 64];
|
type Signature = [u8; 64];
|
||||||
|
|
||||||
@@ -3,6 +3,7 @@ use rand_core::OsRng;
|
|||||||
use secp256k1::{Secp256k1 as BContext, Message, schnorr::Signature};
|
use secp256k1::{Secp256k1 as BContext, Message, schnorr::Signature};
|
||||||
|
|
||||||
use k256::Scalar;
|
use k256::Scalar;
|
||||||
|
use transcript::{Transcript, RecommendedTranscript};
|
||||||
use frost::{
|
use frost::{
|
||||||
curve::Secp256k1,
|
curve::Secp256k1,
|
||||||
Participant,
|
Participant,
|
||||||
@@ -24,7 +25,8 @@ fn test_algorithm() {
|
|||||||
*keys = keys.offset(Scalar::from(offset));
|
*keys = keys.offset(Scalar::from(offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
let algo = Schnorr::new();
|
let algo =
|
||||||
|
Schnorr::<RecommendedTranscript>::new(RecommendedTranscript::new(b"bitcoin-serai sign test"));
|
||||||
let sig = sign(
|
let sig = sign(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
&algo,
|
&algo,
|
||||||
@@ -22,7 +22,7 @@ use bitcoin::{
|
|||||||
Block,
|
Block,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use bitcoin::{hashes::Hash, consensus::encode::Decodable, TapTweakHash};
|
use bitcoin::consensus::encode::Decodable;
|
||||||
|
|
||||||
use crate::crypto::x_only;
|
use crate::crypto::x_only;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
@@ -33,40 +33,12 @@ mod send;
|
|||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub use send::*;
|
pub use send::*;
|
||||||
|
|
||||||
/// Tweak keys to ensure they're usable with Bitcoin's Taproot upgrade.
|
/// Tweak keys to ensure they're usable with Bitcoin.
|
||||||
///
|
///
|
||||||
/// This adds an unspendable script path to the key, preventing any outputs received to this key
|
/// Taproot keys, which these keys are used as, must be even. This offsets the keys until they're
|
||||||
/// from being spent via a script. To have keys which have spendable script paths, further offsets
|
/// even.
|
||||||
/// from this position must be used.
|
|
||||||
///
|
|
||||||
/// After adding an unspendable script path, the key is incremented until its even. This means the
|
|
||||||
/// existence of the unspendable script path may not provable, without an understanding of the
|
|
||||||
/// algorithm used here.
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub fn tweak_keys(keys: &ThresholdKeys<Secp256k1>) -> ThresholdKeys<Secp256k1> {
|
pub fn tweak_keys(keys: &ThresholdKeys<Secp256k1>) -> ThresholdKeys<Secp256k1> {
|
||||||
// Adds the unspendable script path per
|
|
||||||
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-23
|
|
||||||
let keys = {
|
|
||||||
use k256::elliptic_curve::{
|
|
||||||
bigint::{Encoding, U256},
|
|
||||||
ops::Reduce,
|
|
||||||
group::GroupEncoding,
|
|
||||||
};
|
|
||||||
let tweak_hash = TapTweakHash::hash(&keys.group_key().to_bytes().as_slice()[1 ..]);
|
|
||||||
/*
|
|
||||||
https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#cite_ref-13-0 states how the
|
|
||||||
bias is negligible. This reduction shouldn't ever occur, yet if it did, the script path
|
|
||||||
would be unusable due to a check the script path hash is less than the order. That doesn't
|
|
||||||
impact us as we don't want the script path to be usable.
|
|
||||||
*/
|
|
||||||
keys.offset(<Secp256k1 as Ciphersuite>::F::reduce(U256::from_be_bytes(
|
|
||||||
*tweak_hash.to_raw_hash().as_ref(),
|
|
||||||
)))
|
|
||||||
};
|
|
||||||
|
|
||||||
// This doesn't risk re-introducing a script path as you'd have to find a preimage for the tweak
|
|
||||||
// hash with whatever increment, or manipulate the key so that the tweak hash and increment
|
|
||||||
// equals the desired offset, yet manipulating the key would change the tweak hash
|
|
||||||
let (_, offset) = make_even(keys.group_key());
|
let (_, offset) = make_even(keys.group_key());
|
||||||
keys.offset(Scalar::from(offset))
|
keys.offset(Scalar::from(offset))
|
||||||
}
|
}
|
||||||
@@ -170,10 +142,6 @@ impl Scanner {
|
|||||||
///
|
///
|
||||||
/// This means offsets are surjective, not bijective, and the order offsets are registered in
|
/// This means offsets are surjective, not bijective, and the order offsets are registered in
|
||||||
/// may determine the validity of future offsets.
|
/// may determine the validity of future offsets.
|
||||||
///
|
|
||||||
/// The offsets registered must be securely generated. Arbitrary offsets may introduce a script
|
|
||||||
/// path into the output, allowing the output to be spent by satisfaction of an arbitrary script
|
|
||||||
/// (not by the signature of the key).
|
|
||||||
pub fn register_offset(&mut self, mut offset: Scalar) -> Option<Scalar> {
|
pub fn register_offset(&mut self, mut offset: Scalar) -> Option<Scalar> {
|
||||||
// This loop will terminate as soon as an even point is found, with any point having a ~50%
|
// This loop will terminate as soon as an even point is found, with any point having a ~50%
|
||||||
// chance of being even
|
// chance of being even
|
||||||
@@ -7,7 +7,9 @@ use thiserror::Error;
|
|||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use k256::Scalar;
|
use transcript::{Transcript, RecommendedTranscript};
|
||||||
|
|
||||||
|
use k256::{elliptic_curve::sec1::ToEncodedPoint, Scalar};
|
||||||
use frost::{curve::Secp256k1, Participant, ThresholdKeys, FrostError, sign::*};
|
use frost::{curve::Secp256k1, Participant, ThresholdKeys, FrostError, sign::*};
|
||||||
|
|
||||||
use bitcoin::{
|
use bitcoin::{
|
||||||
@@ -44,7 +46,7 @@ pub enum TransactionError {
|
|||||||
#[error("fee was too low to pass the default minimum fee rate")]
|
#[error("fee was too low to pass the default minimum fee rate")]
|
||||||
TooLowFee,
|
TooLowFee,
|
||||||
#[error("not enough funds for these payments")]
|
#[error("not enough funds for these payments")]
|
||||||
NotEnoughFunds { inputs: u64, payments: u64, fee: u64 },
|
NotEnoughFunds,
|
||||||
#[error("transaction was too large")]
|
#[error("transaction was too large")]
|
||||||
TooLargeTransaction,
|
TooLargeTransaction,
|
||||||
}
|
}
|
||||||
@@ -59,11 +61,11 @@ pub struct SignableTransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SignableTransaction {
|
impl SignableTransaction {
|
||||||
fn calculate_weight_vbytes(
|
fn calculate_weight(
|
||||||
inputs: usize,
|
inputs: usize,
|
||||||
payments: &[(ScriptBuf, u64)],
|
payments: &[(ScriptBuf, u64)],
|
||||||
change: Option<&ScriptBuf>,
|
change: Option<&ScriptBuf>,
|
||||||
) -> (u64, u64) {
|
) -> u64 {
|
||||||
// Expand this a full transaction in order to use the bitcoin library's weight function
|
// Expand this a full transaction in order to use the bitcoin library's weight function
|
||||||
let mut tx = Transaction {
|
let mut tx = Transaction {
|
||||||
version: Version(2),
|
version: Version(2),
|
||||||
@@ -97,33 +99,7 @@ impl SignableTransaction {
|
|||||||
// the value is fixed size (so any value could be used here)
|
// the value is fixed size (so any value could be used here)
|
||||||
tx.output.push(TxOut { value: Amount::ZERO, script_pubkey: change.clone() });
|
tx.output.push(TxOut { value: Amount::ZERO, script_pubkey: change.clone() });
|
||||||
}
|
}
|
||||||
|
u64::from(tx.weight())
|
||||||
let weight = tx.weight();
|
|
||||||
|
|
||||||
// Now calculate the size in vbytes
|
|
||||||
|
|
||||||
/*
|
|
||||||
"Virtual transaction size" is weight ceildiv 4 per
|
|
||||||
https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
|
|
||||||
|
|
||||||
https://github.com/bitcoin/bitcoin/blob/306ccd4927a2efe325c8d84be1bdb79edeb29b04
|
|
||||||
/src/policy/policy.cpp#L295-L298
|
|
||||||
implements this almost as expected, with an additional consideration to signature operations
|
|
||||||
|
|
||||||
Signature operations (the second argument of the following call) do not count Taproot
|
|
||||||
signatures per https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki#cite_ref-11-0
|
|
||||||
|
|
||||||
We don't risk running afoul of the Taproot signature limit as it allows at least one per
|
|
||||||
input, which is all we use
|
|
||||||
*/
|
|
||||||
(
|
|
||||||
weight.to_wu(),
|
|
||||||
u64::try_from(bitcoin::policy::get_virtual_tx_size(
|
|
||||||
i64::try_from(weight.to_wu()).unwrap(),
|
|
||||||
0i64,
|
|
||||||
))
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the fee necessary for this transaction to achieve the fee rate specified at
|
/// Returns the fee necessary for this transaction to achieve the fee rate specified at
|
||||||
@@ -152,7 +128,7 @@ impl SignableTransaction {
|
|||||||
payments: &[(ScriptBuf, u64)],
|
payments: &[(ScriptBuf, u64)],
|
||||||
change: Option<ScriptBuf>,
|
change: Option<ScriptBuf>,
|
||||||
data: Option<Vec<u8>>,
|
data: Option<Vec<u8>>,
|
||||||
fee_per_vbyte: u64,
|
fee_per_weight: u64,
|
||||||
) -> Result<SignableTransaction, TransactionError> {
|
) -> Result<SignableTransaction, TransactionError> {
|
||||||
if inputs.is_empty() {
|
if inputs.is_empty() {
|
||||||
Err(TransactionError::NoInputs)?;
|
Err(TransactionError::NoInputs)?;
|
||||||
@@ -201,30 +177,45 @@ impl SignableTransaction {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mut weight, vbytes) = Self::calculate_weight_vbytes(tx_ins.len(), payments, None);
|
let mut weight = Self::calculate_weight(tx_ins.len(), payments, None);
|
||||||
|
let mut needed_fee = fee_per_weight * weight;
|
||||||
|
|
||||||
let mut needed_fee = fee_per_vbyte * vbytes;
|
// "Virtual transaction size" is weight ceildiv 4 per
|
||||||
|
// https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
|
||||||
|
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/306ccd4927a2efe325c8d84be1bdb79edeb29b04/
|
||||||
|
// src/policy/policy.cpp#L295-L298
|
||||||
|
// implements this as expected
|
||||||
|
|
||||||
|
// Technically, it takes whatever's greater, the weight or the amount of signature operations
|
||||||
|
// multiplied by DEFAULT_BYTES_PER_SIGOP (20)
|
||||||
|
// We only use 1 signature per input, and our inputs have a weight exceeding 20
|
||||||
|
// Accordingly, our inputs' weight will always be greater than the cost of the signature ops
|
||||||
|
let vsize = weight.div_ceil(4);
|
||||||
|
debug_assert_eq!(
|
||||||
|
u64::try_from(bitcoin::policy::get_virtual_tx_size(
|
||||||
|
weight.try_into().unwrap(),
|
||||||
|
tx_ins.len().try_into().unwrap()
|
||||||
|
))
|
||||||
|
.unwrap(),
|
||||||
|
vsize
|
||||||
|
);
|
||||||
// Technically, if there isn't change, this TX may still pay enough of a fee to pass the
|
// Technically, if there isn't change, this TX may still pay enough of a fee to pass the
|
||||||
// minimum fee. Such edge cases aren't worth programming when they go against intent, as the
|
// minimum fee. Such edge cases aren't worth programming when they go against intent, as the
|
||||||
// specified fee rate is too low to be valid
|
// specified fee rate is too low to be valid
|
||||||
// bitcoin::policy::DEFAULT_MIN_RELAY_TX_FEE is in sats/kilo-vbyte
|
// bitcoin::policy::DEFAULT_MIN_RELAY_TX_FEE is in sats/kilo-vbyte
|
||||||
if needed_fee < ((u64::from(bitcoin::policy::DEFAULT_MIN_RELAY_TX_FEE) * vbytes) / 1000) {
|
if needed_fee < ((u64::from(bitcoin::policy::DEFAULT_MIN_RELAY_TX_FEE) * vsize) / 1000) {
|
||||||
Err(TransactionError::TooLowFee)?;
|
Err(TransactionError::TooLowFee)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if input_sat < (payment_sat + needed_fee) {
|
if input_sat < (payment_sat + needed_fee) {
|
||||||
Err(TransactionError::NotEnoughFunds {
|
Err(TransactionError::NotEnoughFunds)?;
|
||||||
inputs: input_sat,
|
|
||||||
payments: payment_sat,
|
|
||||||
fee: needed_fee,
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's a change address, check if there's change to give it
|
// If there's a change address, check if there's change to give it
|
||||||
if let Some(change) = change {
|
if let Some(change) = change {
|
||||||
let (weight_with_change, vbytes_with_change) =
|
let weight_with_change = Self::calculate_weight(tx_ins.len(), payments, Some(&change));
|
||||||
Self::calculate_weight_vbytes(tx_ins.len(), payments, Some(&change));
|
let fee_with_change = fee_per_weight * weight_with_change;
|
||||||
let fee_with_change = fee_per_vbyte * vbytes_with_change;
|
|
||||||
if let Some(value) = input_sat.checked_sub(payment_sat + fee_with_change) {
|
if let Some(value) = input_sat.checked_sub(payment_sat + fee_with_change) {
|
||||||
if value >= DUST {
|
if value >= DUST {
|
||||||
tx_outs.push(TxOut { value: Amount::from_sat(value), script_pubkey: change });
|
tx_outs.push(TxOut { value: Amount::from_sat(value), script_pubkey: change });
|
||||||
@@ -262,23 +253,49 @@ impl SignableTransaction {
|
|||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the transaction, sans witness, this will create if signed.
|
/// Returns the outputs this transaction will create.
|
||||||
pub fn transaction(&self) -> &Transaction {
|
pub fn outputs(&self) -> &[TxOut] {
|
||||||
&self.tx
|
&self.tx.output
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a multisig machine for this transaction.
|
/// Create a multisig machine for this transaction.
|
||||||
///
|
///
|
||||||
/// Returns None if the wrong keys are used.
|
/// Returns None if the wrong keys are used.
|
||||||
pub fn multisig(self, keys: &ThresholdKeys<Secp256k1>) -> Option<TransactionMachine> {
|
pub fn multisig(
|
||||||
|
self,
|
||||||
|
keys: &ThresholdKeys<Secp256k1>,
|
||||||
|
mut transcript: RecommendedTranscript,
|
||||||
|
) -> Option<TransactionMachine> {
|
||||||
|
transcript.domain_separate(b"bitcoin_transaction");
|
||||||
|
transcript.append_message(b"root_key", keys.group_key().to_encoded_point(true).as_bytes());
|
||||||
|
|
||||||
|
// Transcript the inputs and outputs
|
||||||
|
let tx = &self.tx;
|
||||||
|
for input in &tx.input {
|
||||||
|
transcript.append_message(b"input_hash", input.previous_output.txid);
|
||||||
|
transcript.append_message(b"input_output_index", input.previous_output.vout.to_le_bytes());
|
||||||
|
}
|
||||||
|
for payment in &tx.output {
|
||||||
|
transcript.append_message(b"output_script", payment.script_pubkey.as_bytes());
|
||||||
|
transcript.append_message(b"output_amount", payment.value.to_sat().to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
let mut sigs = vec![];
|
let mut sigs = vec![];
|
||||||
for i in 0 .. self.tx.input.len() {
|
for i in 0 .. tx.input.len() {
|
||||||
|
let mut transcript = transcript.clone();
|
||||||
|
// This unwrap is safe since any transaction with this many inputs violates the maximum
|
||||||
|
// size allowed under standards, which this lib will error on creation of
|
||||||
|
transcript.append_message(b"signing_input", u32::try_from(i).unwrap().to_le_bytes());
|
||||||
|
|
||||||
let offset = keys.clone().offset(self.offsets[i]);
|
let offset = keys.clone().offset(self.offsets[i]);
|
||||||
if p2tr_script_buf(offset.group_key())? != self.prevouts[i].script_pubkey {
|
if p2tr_script_buf(offset.group_key())? != self.prevouts[i].script_pubkey {
|
||||||
None?;
|
None?;
|
||||||
}
|
}
|
||||||
|
|
||||||
sigs.push(AlgorithmMachine::new(Schnorr::new(), keys.clone().offset(self.offsets[i])));
|
sigs.push(AlgorithmMachine::new(
|
||||||
|
Schnorr::new(transcript),
|
||||||
|
keys.clone().offset(self.offsets[i]),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(TransactionMachine { tx: self, sigs })
|
Some(TransactionMachine { tx: self, sigs })
|
||||||
@@ -291,7 +308,7 @@ impl SignableTransaction {
|
|||||||
/// This will panic if either `cache` is called or the message isn't empty.
|
/// This will panic if either `cache` is called or the message isn't empty.
|
||||||
pub struct TransactionMachine {
|
pub struct TransactionMachine {
|
||||||
tx: SignableTransaction,
|
tx: SignableTransaction,
|
||||||
sigs: Vec<AlgorithmMachine<Secp256k1, Schnorr>>,
|
sigs: Vec<AlgorithmMachine<Secp256k1, Schnorr<RecommendedTranscript>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PreprocessMachine for TransactionMachine {
|
impl PreprocessMachine for TransactionMachine {
|
||||||
@@ -320,7 +337,7 @@ impl PreprocessMachine for TransactionMachine {
|
|||||||
|
|
||||||
pub struct TransactionSignMachine {
|
pub struct TransactionSignMachine {
|
||||||
tx: SignableTransaction,
|
tx: SignableTransaction,
|
||||||
sigs: Vec<AlgorithmSignMachine<Secp256k1, Schnorr>>,
|
sigs: Vec<AlgorithmSignMachine<Secp256k1, Schnorr<RecommendedTranscript>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignMachine<Transaction> for TransactionSignMachine {
|
impl SignMachine<Transaction> for TransactionSignMachine {
|
||||||
@@ -400,7 +417,7 @@ impl SignMachine<Transaction> for TransactionSignMachine {
|
|||||||
|
|
||||||
pub struct TransactionSignatureMachine {
|
pub struct TransactionSignatureMachine {
|
||||||
tx: Transaction,
|
tx: Transaction,
|
||||||
sigs: Vec<AlgorithmSignatureMachine<Secp256k1, Schnorr>>,
|
sigs: Vec<AlgorithmSignatureMachine<Secp256k1, Schnorr<RecommendedTranscript>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignatureMachine<Transaction> for TransactionSignatureMachine {
|
impl SignatureMachine<Transaction> for TransactionSignatureMachine {
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
use std::sync::LazyLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use bitcoin_serai::rpc::Rpc;
|
use bitcoin_serai::rpc::Rpc;
|
||||||
|
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
static SEQUENTIAL_CELL: OnceLock<Mutex<()>> = OnceLock::new();
|
||||||
pub(crate) static SEQUENTIAL: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
|
#[allow(non_snake_case)]
|
||||||
|
pub fn SEQUENTIAL() -> &'static Mutex<()> {
|
||||||
|
SEQUENTIAL_CELL.get_or_init(|| Mutex::new(()))
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) async fn rpc() -> Rpc {
|
pub(crate) async fn rpc() -> Rpc {
|
||||||
@@ -31,7 +34,7 @@ macro_rules! async_sequential {
|
|||||||
$(
|
$(
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn $name() {
|
async fn $name() {
|
||||||
let guard = runner::SEQUENTIAL.lock().await;
|
let guard = runner::SEQUENTIAL().lock().await;
|
||||||
let local = tokio::task::LocalSet::new();
|
let local = tokio::task::LocalSet::new();
|
||||||
local.run_until(async move {
|
local.run_until(async move {
|
||||||
if let Err(err) = tokio::task::spawn_local(async move { $body }).await {
|
if let Err(err) = tokio::task::spawn_local(async move { $body }).await {
|
||||||
@@ -2,6 +2,8 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use rand_core::{RngCore, OsRng};
|
use rand_core::{RngCore, OsRng};
|
||||||
|
|
||||||
|
use transcript::{Transcript, RecommendedTranscript};
|
||||||
|
|
||||||
use k256::{
|
use k256::{
|
||||||
elliptic_curve::{
|
elliptic_curve::{
|
||||||
group::{ff::Field, Group},
|
group::{ff::Field, Group},
|
||||||
@@ -92,11 +94,46 @@ fn sign(
|
|||||||
) -> Transaction {
|
) -> Transaction {
|
||||||
let mut machines = HashMap::new();
|
let mut machines = HashMap::new();
|
||||||
for i in (1 ..= THRESHOLD).map(|i| Participant::new(i).unwrap()) {
|
for i in (1 ..= THRESHOLD).map(|i| Participant::new(i).unwrap()) {
|
||||||
machines.insert(i, tx.clone().multisig(&keys[&i].clone()).unwrap());
|
machines.insert(
|
||||||
|
i,
|
||||||
|
tx.clone()
|
||||||
|
.multisig(&keys[&i].clone(), RecommendedTranscript::new(b"bitcoin-serai Test Transaction"))
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
sign_without_caching(&mut OsRng, machines, &[])
|
sign_without_caching(&mut OsRng, machines, &[])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tweak_keys() {
|
||||||
|
let mut even = false;
|
||||||
|
let mut odd = false;
|
||||||
|
|
||||||
|
// Generate keys until we get an even set and an odd set
|
||||||
|
while !(even && odd) {
|
||||||
|
let mut keys = key_gen(&mut OsRng).drain().next().unwrap().1;
|
||||||
|
if is_even(keys.group_key()) {
|
||||||
|
// Tweaking should do nothing
|
||||||
|
assert_eq!(tweak_keys(&keys).group_key(), keys.group_key());
|
||||||
|
|
||||||
|
even = true;
|
||||||
|
} else {
|
||||||
|
let tweaked = tweak_keys(&keys).group_key();
|
||||||
|
assert_ne!(tweaked, keys.group_key());
|
||||||
|
// Tweaking should produce an even key
|
||||||
|
assert!(is_even(tweaked));
|
||||||
|
|
||||||
|
// Verify it uses the smallest possible offset
|
||||||
|
while keys.group_key().to_encoded_point(true).tag() == Tag::CompressedOddY {
|
||||||
|
keys = keys.offset(Scalar::ONE);
|
||||||
|
}
|
||||||
|
assert_eq!(tweaked, keys.group_key());
|
||||||
|
|
||||||
|
odd = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async_sequential! {
|
async_sequential! {
|
||||||
async fn test_scanner() {
|
async fn test_scanner() {
|
||||||
// Test Scanners are creatable for even keys.
|
// Test Scanners are creatable for even keys.
|
||||||
@@ -195,10 +232,10 @@ async_sequential! {
|
|||||||
Err(TransactionError::TooLowFee),
|
Err(TransactionError::TooLowFee),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(matches!(
|
assert_eq!(
|
||||||
SignableTransaction::new(inputs.clone(), &[(addr(), inputs[0].value() * 2)], None, None, FEE),
|
SignableTransaction::new(inputs.clone(), &[(addr(), inputs[0].value() * 2)], None, None, FEE),
|
||||||
Err(TransactionError::NotEnoughFunds { .. }),
|
Err(TransactionError::NotEnoughFunds),
|
||||||
));
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
SignableTransaction::new(inputs, &vec![(addr(), 1000); 10000], None, None, FEE),
|
SignableTransaction::new(inputs, &vec![(addr(), 1000); 10000], None, None, FEE),
|
||||||
@@ -266,7 +303,7 @@ async_sequential! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the change is correct
|
// Make sure the change is correct
|
||||||
assert_eq!(needed_fee, u64::try_from(tx.vsize()).unwrap() * FEE);
|
assert_eq!(needed_fee, u64::from(tx.weight()) * FEE);
|
||||||
let input_value = output.value() + offset_output.value();
|
let input_value = output.value() + offset_output.value();
|
||||||
let output_value = tx.output.iter().map(|output| output.value.to_sat()).sum::<u64>();
|
let output_value = tx.output.iter().map(|output| output.value.to_sat()).sum::<u64>();
|
||||||
assert_eq!(input_value - output_value, needed_fee);
|
assert_eq!(input_value - output_value, needed_fee);
|
||||||
3
coins/ethereum/.gitignore
vendored
Normal file
3
coins/ethereum/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Solidity build outputs
|
||||||
|
cache
|
||||||
|
artifacts
|
||||||
49
coins/ethereum/Cargo.toml
Normal file
49
coins/ethereum/Cargo.toml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
[package]
|
||||||
|
name = "ethereum-serai"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "An Ethereum library supporting Schnorr signing and on-chain verification"
|
||||||
|
license = "AGPL-3.0-only"
|
||||||
|
repository = "https://github.com/serai-dex/serai/tree/develop/coins/ethereum"
|
||||||
|
authors = ["Luke Parker <lukeparker5132@gmail.com>", "Elizabeth Binks <elizabethjbinks@gmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
rust-version = "1.79"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
thiserror = { version = "1", default-features = false }
|
||||||
|
|
||||||
|
rand_core = { version = "0.6", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
|
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", default-features = false, features = ["recommended"] }
|
||||||
|
|
||||||
|
group = { version = "0.13", default-features = false }
|
||||||
|
k256 = { version = "^0.13.1", default-features = false, features = ["std", "ecdsa", "arithmetic"] }
|
||||||
|
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false, features = ["secp256k1"] }
|
||||||
|
|
||||||
|
alloy-core = { version = "0.7", default-features = false }
|
||||||
|
alloy-sol-types = { version = "0.7", default-features = false, features = ["json"] }
|
||||||
|
alloy-consensus = { version = "0.1", default-features = false, features = ["k256"] }
|
||||||
|
alloy-network = { version = "0.1", default-features = false }
|
||||||
|
alloy-rpc-types-eth = { version = "0.1", default-features = false }
|
||||||
|
alloy-rpc-client = { version = "0.1", default-features = false }
|
||||||
|
alloy-simple-request-transport = { path = "./alloy-simple-request-transport", default-features = false }
|
||||||
|
alloy-provider = { version = "0.1", default-features = false }
|
||||||
|
|
||||||
|
alloy-node-bindings = { version = "0.1", default-features = false, optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false, features = ["tests"] }
|
||||||
|
|
||||||
|
tokio = { version = "1", features = ["macros"] }
|
||||||
|
|
||||||
|
alloy-node-bindings = { version = "0.1", default-features = false }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
tests = ["alloy-node-bindings", "frost/tests"]
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
AGPL-3.0-only license
|
AGPL-3.0-only license
|
||||||
|
|
||||||
Copyright (c) 2023-2025 Luke Parker
|
Copyright (c) 2022-2023 Luke Parker
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License Version 3 as
|
it under the terms of the GNU Affero General Public License Version 3 as
|
||||||
15
coins/ethereum/README.md
Normal file
15
coins/ethereum/README.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Ethereum
|
||||||
|
|
||||||
|
This package contains Ethereum-related functionality, specifically deploying and
|
||||||
|
interacting with Serai contracts.
|
||||||
|
|
||||||
|
While `monero-serai` and `bitcoin-serai` are general purpose libraries,
|
||||||
|
`ethereum-serai` is Serai specific. If any of the utilities are generally
|
||||||
|
desired, please fork and maintain your own copy to ensure the desired
|
||||||
|
functionality is preserved, or open an issue to request we make this library
|
||||||
|
general purpose.
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- solc
|
||||||
|
- [Foundry](https://github.com/foundry-rs/foundry)
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "alloy-simple-request-transport"
|
name = "alloy-simple-request-transport"
|
||||||
version = "0.1.1"
|
version = "0.1.0"
|
||||||
description = "A transport for alloy based off simple-request"
|
description = "A transport for alloy based off simple-request"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/networks/ethereum/alloy-simple-request-transport"
|
repository = "https://github.com/serai-dex/serai/tree/develop/coins/ethereum/alloy-simple-request-transport"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.81"
|
rust-version = "1.74"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
@@ -16,13 +16,13 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tower = "0.5"
|
tower = "0.4"
|
||||||
|
|
||||||
serde_json = { version = "1", default-features = false }
|
serde_json = { version = "1", default-features = false }
|
||||||
simple-request = { path = "../../../common/request", version = "0.1", default-features = false }
|
simple-request = { path = "../../../common/request", default-features = false }
|
||||||
|
|
||||||
alloy-json-rpc = { version = "0.14", default-features = false }
|
alloy-json-rpc = { version = "0.1", default-features = false }
|
||||||
alloy-transport = { version = "0.14", default-features = false }
|
alloy-transport = { version = "0.1", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["tls"]
|
default = ["tls"]
|
||||||
41
coins/ethereum/build.rs
Normal file
41
coins/ethereum/build.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("cargo:rerun-if-changed=contracts/*");
|
||||||
|
println!("cargo:rerun-if-changed=artifacts/*");
|
||||||
|
|
||||||
|
for line in String::from_utf8(Command::new("solc").args(["--version"]).output().unwrap().stdout)
|
||||||
|
.unwrap()
|
||||||
|
.lines()
|
||||||
|
{
|
||||||
|
if let Some(version) = line.strip_prefix("Version: ") {
|
||||||
|
let version = version.split('+').next().unwrap();
|
||||||
|
assert_eq!(version, "0.8.25");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let args = [
|
||||||
|
"--base-path", ".",
|
||||||
|
"-o", "./artifacts", "--overwrite",
|
||||||
|
"--bin", "--abi",
|
||||||
|
"--via-ir", "--optimize",
|
||||||
|
|
||||||
|
"./contracts/IERC20.sol",
|
||||||
|
|
||||||
|
"./contracts/Schnorr.sol",
|
||||||
|
"./contracts/Deployer.sol",
|
||||||
|
"./contracts/Sandbox.sol",
|
||||||
|
"./contracts/Router.sol",
|
||||||
|
|
||||||
|
"./src/tests/contracts/Schnorr.sol",
|
||||||
|
"./src/tests/contracts/ERC20.sol",
|
||||||
|
|
||||||
|
"--no-color",
|
||||||
|
];
|
||||||
|
let solc = Command::new("solc").args(args).output().unwrap();
|
||||||
|
assert!(solc.status.success());
|
||||||
|
for line in String::from_utf8(solc.stderr).unwrap().lines() {
|
||||||
|
assert!(!line.starts_with("Error:"));
|
||||||
|
}
|
||||||
|
}
|
||||||
52
coins/ethereum/contracts/Deployer.sol
Normal file
52
coins/ethereum/contracts/Deployer.sol
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// SPDX-License-Identifier: AGPLv3
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The expected deployment process of the Router is as follows:
|
||||||
|
|
||||||
|
1) A transaction deploying Deployer is made. Then, a deterministic signature is
|
||||||
|
created such that an account with an unknown private key is the creator of
|
||||||
|
the contract. Anyone can fund this address, and once anyone does, the
|
||||||
|
transaction deploying Deployer can be published by anyone. No other
|
||||||
|
transaction may be made from that account.
|
||||||
|
|
||||||
|
2) Anyone deploys the Router through the Deployer. This uses a sequential nonce
|
||||||
|
such that meet-in-the-middle attacks, with complexity 2**80, aren't feasible.
|
||||||
|
While such attacks would still be feasible if the Deployer's address was
|
||||||
|
controllable, the usage of a deterministic signature with a NUMS method
|
||||||
|
prevents that.
|
||||||
|
|
||||||
|
This doesn't have any denial-of-service risks and will resolve once anyone steps
|
||||||
|
forward as deployer. This does fail to guarantee an identical address across
|
||||||
|
every chain, though it enables letting anyone efficiently ask the Deployer for
|
||||||
|
the address (with the Deployer having an identical address on every chain).
|
||||||
|
|
||||||
|
Unfortunately, guaranteeing identical addresses aren't feasible. We'd need the
|
||||||
|
Deployer contract to use a consistent salt for the Router, yet the Router must
|
||||||
|
be deployed with a specific public key for Serai. Since Ethereum isn't able to
|
||||||
|
determine a valid public key (one the result of a Serai DKG) from a dishonest
|
||||||
|
public key, we have to allow multiple deployments with Serai being the one to
|
||||||
|
determine which to use.
|
||||||
|
|
||||||
|
The alternative would be to have a council publish the Serai key on-Ethereum,
|
||||||
|
with Serai verifying the published result. This would introduce a DoS risk in
|
||||||
|
the council not publishing the correct key/not publishing any key.
|
||||||
|
*/
|
||||||
|
|
||||||
|
contract Deployer {
|
||||||
|
event Deployment(bytes32 indexed init_code_hash, address created);
|
||||||
|
|
||||||
|
error DeploymentFailed();
|
||||||
|
|
||||||
|
function deploy(bytes memory init_code) external {
|
||||||
|
address created;
|
||||||
|
assembly {
|
||||||
|
created := create(0, add(init_code, 0x20), mload(init_code))
|
||||||
|
}
|
||||||
|
if (created == address(0)) {
|
||||||
|
revert DeploymentFailed();
|
||||||
|
}
|
||||||
|
// These may be emitted out of order upon re-entrancy
|
||||||
|
emit Deployment(keccak256(init_code), created);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// SPDX-License-Identifier: CC0
|
// SPDX-License-Identifier: CC0
|
||||||
pragma solidity ^0.8.26;
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
interface IERC20 {
|
interface IERC20 {
|
||||||
event Transfer(address indexed from, address indexed to, uint256 value);
|
event Transfer(address indexed from, address indexed to, uint256 value);
|
||||||
@@ -18,17 +18,3 @@ interface IERC20 {
|
|||||||
function approve(address spender, uint256 value) external returns (bool);
|
function approve(address spender, uint256 value) external returns (bool);
|
||||||
function allowance(address owner, address spender) external view returns (uint256);
|
function allowance(address owner, address spender) external view returns (uint256);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SeraiIERC20 {
|
|
||||||
function transferWithInInstruction01BB244A8A(
|
|
||||||
address to,
|
|
||||||
uint256 value,
|
|
||||||
bytes calldata inInstruction
|
|
||||||
) external returns (bool);
|
|
||||||
function transferFromWithInInstruction00081948E0(
|
|
||||||
address from,
|
|
||||||
address to,
|
|
||||||
uint256 value,
|
|
||||||
bytes calldata inInstruction
|
|
||||||
) external returns (bool);
|
|
||||||
}
|
|
||||||
222
coins/ethereum/contracts/Router.sol
Normal file
222
coins/ethereum/contracts/Router.sol
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
// SPDX-License-Identifier: AGPLv3
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
import "./IERC20.sol";
|
||||||
|
|
||||||
|
import "./Schnorr.sol";
|
||||||
|
import "./Sandbox.sol";
|
||||||
|
|
||||||
|
contract Router {
|
||||||
|
// Nonce is incremented for each batch of transactions executed/key update
|
||||||
|
uint256 public nonce;
|
||||||
|
|
||||||
|
// Current public key's x-coordinate
|
||||||
|
// This key must always have the parity defined within the Schnorr contract
|
||||||
|
bytes32 public seraiKey;
|
||||||
|
|
||||||
|
struct OutInstruction {
|
||||||
|
address to;
|
||||||
|
Call[] calls;
|
||||||
|
|
||||||
|
uint256 value;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Signature {
|
||||||
|
bytes32 c;
|
||||||
|
bytes32 s;
|
||||||
|
}
|
||||||
|
|
||||||
|
event SeraiKeyUpdated(
|
||||||
|
uint256 indexed nonce,
|
||||||
|
bytes32 indexed key,
|
||||||
|
Signature signature
|
||||||
|
);
|
||||||
|
event InInstruction(
|
||||||
|
address indexed from,
|
||||||
|
address indexed coin,
|
||||||
|
uint256 amount,
|
||||||
|
bytes instruction
|
||||||
|
);
|
||||||
|
// success is a uint256 representing a bitfield of transaction successes
|
||||||
|
event Executed(
|
||||||
|
uint256 indexed nonce,
|
||||||
|
bytes32 indexed batch,
|
||||||
|
uint256 success,
|
||||||
|
Signature signature
|
||||||
|
);
|
||||||
|
|
||||||
|
// error types
|
||||||
|
error InvalidKey();
|
||||||
|
error InvalidSignature();
|
||||||
|
error InvalidAmount();
|
||||||
|
error FailedTransfer();
|
||||||
|
error TooManyTransactions();
|
||||||
|
|
||||||
|
modifier _updateSeraiKeyAtEndOfFn(
|
||||||
|
uint256 _nonce,
|
||||||
|
bytes32 key,
|
||||||
|
Signature memory sig
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
(key == bytes32(0)) ||
|
||||||
|
((bytes32(uint256(key) % Schnorr.Q)) != key)
|
||||||
|
) {
|
||||||
|
revert InvalidKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
_;
|
||||||
|
|
||||||
|
seraiKey = key;
|
||||||
|
emit SeraiKeyUpdated(_nonce, key, sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(bytes32 _seraiKey) _updateSeraiKeyAtEndOfFn(
|
||||||
|
0,
|
||||||
|
_seraiKey,
|
||||||
|
Signature({ c: bytes32(0), s: bytes32(0) })
|
||||||
|
) {
|
||||||
|
nonce = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateSeraiKey validates the given Schnorr signature against the current
|
||||||
|
// public key, and if successful, updates the contract's public key to the
|
||||||
|
// given one.
|
||||||
|
function updateSeraiKey(
|
||||||
|
bytes32 _seraiKey,
|
||||||
|
Signature calldata sig
|
||||||
|
) external _updateSeraiKeyAtEndOfFn(nonce, _seraiKey, sig) {
|
||||||
|
bytes memory message =
|
||||||
|
abi.encodePacked("updateSeraiKey", block.chainid, nonce, _seraiKey);
|
||||||
|
nonce++;
|
||||||
|
|
||||||
|
if (!Schnorr.verify(seraiKey, message, sig.c, sig.s)) {
|
||||||
|
revert InvalidSignature();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function inInstruction(
|
||||||
|
address coin,
|
||||||
|
uint256 amount,
|
||||||
|
bytes memory instruction
|
||||||
|
) external payable {
|
||||||
|
if (coin == address(0)) {
|
||||||
|
if (amount != msg.value) {
|
||||||
|
revert InvalidAmount();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(bool success, bytes memory res) =
|
||||||
|
address(coin).call(
|
||||||
|
abi.encodeWithSelector(
|
||||||
|
IERC20.transferFrom.selector,
|
||||||
|
msg.sender,
|
||||||
|
address(this),
|
||||||
|
amount
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Require there was nothing returned, which is done by some non-standard
|
||||||
|
// tokens, or that the ERC20 contract did in fact return true
|
||||||
|
bool nonStandardResOrTrue =
|
||||||
|
(res.length == 0) || abi.decode(res, (bool));
|
||||||
|
if (!(success && nonStandardResOrTrue)) {
|
||||||
|
revert FailedTransfer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Due to fee-on-transfer tokens, emitting the amount directly is frowned upon.
|
||||||
|
The amount instructed to transfer may not actually be the amount
|
||||||
|
transferred.
|
||||||
|
|
||||||
|
If we add nonReentrant to every single function which can effect the
|
||||||
|
balance, we can check the amount exactly matches. This prevents transfers of
|
||||||
|
less value than expected occurring, at least, not without an additional
|
||||||
|
transfer to top up the difference (which isn't routed through this contract
|
||||||
|
and accordingly isn't trying to artificially create events).
|
||||||
|
|
||||||
|
If we don't add nonReentrant, a transfer can be started, and then a new
|
||||||
|
transfer for the difference can follow it up (again and again until a
|
||||||
|
rounding error is reached). This contract would believe all transfers were
|
||||||
|
done in full, despite each only being done in part (except for the last
|
||||||
|
one).
|
||||||
|
|
||||||
|
Given fee-on-transfer tokens aren't intended to be supported, the only
|
||||||
|
token planned to be supported is Dai and it doesn't have any fee-on-transfer
|
||||||
|
logic, fee-on-transfer tokens aren't even able to be supported at this time,
|
||||||
|
we simply classify this entire class of tokens as non-standard
|
||||||
|
implementations which induce undefined behavior. It is the Serai network's
|
||||||
|
role not to add support for any non-standard implementations.
|
||||||
|
*/
|
||||||
|
emit InInstruction(msg.sender, coin, amount, instruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute accepts a list of transactions to execute as well as a signature.
|
||||||
|
// if signature verification passes, the given transactions are executed.
|
||||||
|
// if signature verification fails, this function will revert.
|
||||||
|
function execute(
|
||||||
|
OutInstruction[] calldata transactions,
|
||||||
|
Signature calldata sig
|
||||||
|
) external {
|
||||||
|
if (transactions.length > 256) {
|
||||||
|
revert TooManyTransactions();
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes memory message =
|
||||||
|
abi.encode("execute", block.chainid, nonce, transactions);
|
||||||
|
uint256 executed_with_nonce = nonce;
|
||||||
|
// This prevents re-entrancy from causing double spends yet does allow
|
||||||
|
// out-of-order execution via re-entrancy
|
||||||
|
nonce++;
|
||||||
|
|
||||||
|
if (!Schnorr.verify(seraiKey, message, sig.c, sig.s)) {
|
||||||
|
revert InvalidSignature();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 successes;
|
||||||
|
for (uint256 i = 0; i < transactions.length; i++) {
|
||||||
|
bool success;
|
||||||
|
|
||||||
|
// If there are no calls, send to `to` the value
|
||||||
|
if (transactions[i].calls.length == 0) {
|
||||||
|
(success, ) = transactions[i].to.call{
|
||||||
|
value: transactions[i].value,
|
||||||
|
gas: 5_000
|
||||||
|
}("");
|
||||||
|
} else {
|
||||||
|
// If there are calls, ignore `to`. Deploy a new Sandbox and proxy the
|
||||||
|
// calls through that
|
||||||
|
//
|
||||||
|
// We could use a single sandbox in order to reduce gas costs, yet that
|
||||||
|
// risks one person creating an approval that's hooked before another
|
||||||
|
// user's intended action executes, in order to drain their coins
|
||||||
|
//
|
||||||
|
// While technically, that would be a flaw in the sandboxed flow, this
|
||||||
|
// is robust and prevents such flaws from being possible
|
||||||
|
//
|
||||||
|
// We also don't want people to set state via the Sandbox and expect it
|
||||||
|
// future available when anyone else could set a distinct value
|
||||||
|
Sandbox sandbox = new Sandbox();
|
||||||
|
(success, ) = address(sandbox).call{
|
||||||
|
value: transactions[i].value,
|
||||||
|
// TODO: Have the Call specify the gas up front
|
||||||
|
gas: 350_000
|
||||||
|
}(
|
||||||
|
abi.encodeWithSelector(
|
||||||
|
Sandbox.sandbox.selector,
|
||||||
|
transactions[i].calls
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
assembly {
|
||||||
|
successes := or(successes, shl(i, success))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit Executed(
|
||||||
|
executed_with_nonce,
|
||||||
|
keccak256(message),
|
||||||
|
successes,
|
||||||
|
sig
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
coins/ethereum/contracts/Sandbox.sol
Normal file
48
coins/ethereum/contracts/Sandbox.sol
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// SPDX-License-Identifier: AGPLv3
|
||||||
|
pragma solidity ^0.8.24;
|
||||||
|
|
||||||
|
struct Call {
|
||||||
|
address to;
|
||||||
|
uint256 value;
|
||||||
|
bytes data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A minimal sandbox focused on gas efficiency.
|
||||||
|
//
|
||||||
|
// The first call is executed if any of the calls fail, making it a fallback.
|
||||||
|
// All other calls are executed sequentially.
|
||||||
|
contract Sandbox {
|
||||||
|
error AlreadyCalled();
|
||||||
|
error CallsFailed();
|
||||||
|
|
||||||
|
function sandbox(Call[] calldata calls) external payable {
|
||||||
|
// Prevent re-entrancy due to this executing arbitrary calls from anyone
|
||||||
|
// and anywhere
|
||||||
|
bool called;
|
||||||
|
assembly { called := tload(0) }
|
||||||
|
if (called) {
|
||||||
|
revert AlreadyCalled();
|
||||||
|
}
|
||||||
|
assembly { tstore(0, 1) }
|
||||||
|
|
||||||
|
// Execute the calls, starting from 1
|
||||||
|
for (uint256 i = 1; i < calls.length; i++) {
|
||||||
|
(bool success, ) =
|
||||||
|
calls[i].to.call{ value: calls[i].value }(calls[i].data);
|
||||||
|
|
||||||
|
// If this call failed, execute the fallback (call 0)
|
||||||
|
if (!success) {
|
||||||
|
(success, ) =
|
||||||
|
calls[0].to.call{ value: address(this).balance }(calls[0].data);
|
||||||
|
// If this call also failed, revert entirely
|
||||||
|
if (!success) {
|
||||||
|
revert CallsFailed();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't clear the re-entrancy guard as this contract should never be
|
||||||
|
// called again, so there's no reason to spend the effort
|
||||||
|
}
|
||||||
|
}
|
||||||
44
coins/ethereum/contracts/Schnorr.sol
Normal file
44
coins/ethereum/contracts/Schnorr.sol
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// SPDX-License-Identifier: AGPLv3
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
// see https://github.com/noot/schnorr-verify for implementation details
|
||||||
|
library Schnorr {
|
||||||
|
// secp256k1 group order
|
||||||
|
uint256 constant public Q =
|
||||||
|
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
|
||||||
|
|
||||||
|
// Fixed parity for the public keys used in this contract
|
||||||
|
// This avoids spending a word passing the parity in a similar style to
|
||||||
|
// Bitcoin's Taproot
|
||||||
|
uint8 constant public KEY_PARITY = 27;
|
||||||
|
|
||||||
|
error InvalidSOrA();
|
||||||
|
error MalformedSignature();
|
||||||
|
|
||||||
|
// px := public key x-coord, where the public key has a parity of KEY_PARITY
|
||||||
|
// message := 32-byte hash of the message
|
||||||
|
// c := schnorr signature challenge
|
||||||
|
// s := schnorr signature
|
||||||
|
function verify(
|
||||||
|
bytes32 px,
|
||||||
|
bytes memory message,
|
||||||
|
bytes32 c,
|
||||||
|
bytes32 s
|
||||||
|
) internal pure returns (bool) {
|
||||||
|
// ecrecover = (m, v, r, s) -> key
|
||||||
|
// We instead pass the following to obtain the nonce (not the key)
|
||||||
|
// Then we hash it and verify it matches the challenge
|
||||||
|
bytes32 sa = bytes32(Q - mulmod(uint256(s), uint256(px), Q));
|
||||||
|
bytes32 ca = bytes32(Q - mulmod(uint256(c), uint256(px), Q));
|
||||||
|
|
||||||
|
// For safety, we want each input to ecrecover to be 0 (sa, px, ca)
|
||||||
|
// The ecreover precomple checks `r` and `s` (`px` and `ca`) are non-zero
|
||||||
|
// That leaves us to check `sa` are non-zero
|
||||||
|
if (sa == 0) revert InvalidSOrA();
|
||||||
|
address R = ecrecover(sa, KEY_PARITY, px, ca);
|
||||||
|
if (R == address(0)) revert MalformedSignature();
|
||||||
|
|
||||||
|
// Check the signature is correct by rebuilding the challenge
|
||||||
|
return c == keccak256(abi.encodePacked(R, px, message));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,12 +3,11 @@ name = "serai-ethereum-relayer"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "A relayer for Serai's Ethereum transactions"
|
description = "A relayer for Serai's Ethereum transactions"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/networks/ethereum/relayer"
|
repository = "https://github.com/serai-dex/serai/tree/develop/coins/ethereum/relayer"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
keywords = []
|
keywords = []
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
rust-version = "1.72"
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
4
coins/ethereum/relayer/README.md
Normal file
4
coins/ethereum/relayer/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Ethereum Transaction Relayer
|
||||||
|
|
||||||
|
This server collects Ethereum router commands to be published, offering an RPC
|
||||||
|
to fetch them.
|
||||||
@@ -40,8 +40,8 @@ async fn main() {
|
|||||||
db
|
db
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start transaction recipience server
|
// Start command recipience server
|
||||||
// This MUST NOT be publicly exposed
|
// This should not be publicly exposed
|
||||||
// TODO: Add auth
|
// TODO: Add auth
|
||||||
tokio::spawn({
|
tokio::spawn({
|
||||||
let db = db.clone();
|
let db = db.clone();
|
||||||
@@ -58,27 +58,25 @@ async fn main() {
|
|||||||
let mut buf = vec![0; usize::try_from(msg_len).unwrap()];
|
let mut buf = vec![0; usize::try_from(msg_len).unwrap()];
|
||||||
let Ok(_) = socket.read_exact(&mut buf).await else { break };
|
let Ok(_) = socket.read_exact(&mut buf).await else { break };
|
||||||
|
|
||||||
if buf.len() < (4 + 1) {
|
if buf.len() < 5 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let nonce = u32::from_le_bytes(buf[.. 4].try_into().unwrap());
|
let nonce = u32::from_le_bytes(buf[.. 4].try_into().unwrap());
|
||||||
let mut txn = db.txn();
|
let mut txn = db.txn();
|
||||||
// Save the transaction
|
|
||||||
txn.put(nonce.to_le_bytes(), &buf[4 ..]);
|
txn.put(nonce.to_le_bytes(), &buf[4 ..]);
|
||||||
txn.commit();
|
txn.commit();
|
||||||
|
|
||||||
let Ok(()) = socket.write_all(&[1]).await else { break };
|
let Ok(()) = socket.write_all(&[1]).await else { break };
|
||||||
|
|
||||||
log::info!("received transaction to publish (nonce {nonce})");
|
log::info!("received signed command #{nonce}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start transaction fetch server
|
// Start command fetch server
|
||||||
// 5132 ^ ((b'E' << 8) | b'R') + 1
|
// 5132 ^ ((b'E' << 8) | b'R') + 1
|
||||||
// TODO: JSON-RPC server which returns this as JSON?
|
|
||||||
let server = TcpListener::bind("0.0.0.0:20831").await.unwrap();
|
let server = TcpListener::bind("0.0.0.0:20831").await.unwrap();
|
||||||
loop {
|
loop {
|
||||||
let (mut socket, _) = server.accept().await.unwrap();
|
let (mut socket, _) = server.accept().await.unwrap();
|
||||||
@@ -86,17 +84,16 @@ async fn main() {
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let db = db.clone();
|
let db = db.clone();
|
||||||
loop {
|
loop {
|
||||||
// Nonce to get the unsigned transaction for
|
// Nonce to get the router comamnd for
|
||||||
let mut buf = vec![0; 4];
|
let mut buf = vec![0; 4];
|
||||||
let Ok(_) = socket.read_exact(&mut buf).await else { break };
|
let Ok(_) = socket.read_exact(&mut buf).await else { break };
|
||||||
|
|
||||||
let transaction = db.get(&buf[.. 4]).unwrap_or(vec![]);
|
let command = db.get(&buf[.. 4]).unwrap_or(vec![]);
|
||||||
let Ok(()) =
|
let Ok(()) = socket.write_all(&u32::try_from(command.len()).unwrap().to_le_bytes()).await
|
||||||
socket.write_all(&u32::try_from(transaction.len()).unwrap().to_le_bytes()).await
|
|
||||||
else {
|
else {
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
let Ok(()) = socket.write_all(&transaction).await else { break };
|
let Ok(()) = socket.write_all(&command).await else { break };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
37
coins/ethereum/src/abi/mod.rs
Normal file
37
coins/ethereum/src/abi/mod.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use alloy_sol_types::sol;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[allow(warnings)]
|
||||||
|
#[allow(needless_pass_by_value)]
|
||||||
|
#[allow(clippy::all)]
|
||||||
|
#[allow(clippy::ignored_unit_patterns)]
|
||||||
|
#[allow(clippy::redundant_closure_for_method_calls)]
|
||||||
|
mod erc20_container {
|
||||||
|
use super::*;
|
||||||
|
sol!("contracts/IERC20.sol");
|
||||||
|
}
|
||||||
|
pub use erc20_container::IERC20 as erc20;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[allow(warnings)]
|
||||||
|
#[allow(needless_pass_by_value)]
|
||||||
|
#[allow(clippy::all)]
|
||||||
|
#[allow(clippy::ignored_unit_patterns)]
|
||||||
|
#[allow(clippy::redundant_closure_for_method_calls)]
|
||||||
|
mod deployer_container {
|
||||||
|
use super::*;
|
||||||
|
sol!("contracts/Deployer.sol");
|
||||||
|
}
|
||||||
|
pub use deployer_container::Deployer as deployer;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[allow(warnings)]
|
||||||
|
#[allow(needless_pass_by_value)]
|
||||||
|
#[allow(clippy::all)]
|
||||||
|
#[allow(clippy::ignored_unit_patterns)]
|
||||||
|
#[allow(clippy::redundant_closure_for_method_calls)]
|
||||||
|
mod router_container {
|
||||||
|
use super::*;
|
||||||
|
sol!(Router, "artifacts/Router.abi");
|
||||||
|
}
|
||||||
|
pub use router_container::Router as router;
|
||||||
188
coins/ethereum/src/crypto.rs
Normal file
188
coins/ethereum/src/crypto.rs
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
use group::ff::PrimeField;
|
||||||
|
use k256::{
|
||||||
|
elliptic_curve::{ops::Reduce, point::AffineCoordinates, sec1::ToEncodedPoint},
|
||||||
|
ProjectivePoint, Scalar, U256 as KU256,
|
||||||
|
};
|
||||||
|
#[cfg(test)]
|
||||||
|
use k256::{elliptic_curve::point::DecompressPoint, AffinePoint};
|
||||||
|
|
||||||
|
use frost::{
|
||||||
|
algorithm::{Hram, SchnorrSignature},
|
||||||
|
curve::{Ciphersuite, Secp256k1},
|
||||||
|
};
|
||||||
|
|
||||||
|
use alloy_core::primitives::{Parity, Signature as AlloySignature};
|
||||||
|
use alloy_consensus::{SignableTransaction, Signed, TxLegacy};
|
||||||
|
|
||||||
|
use crate::abi::router::{Signature as AbiSignature};
|
||||||
|
|
||||||
|
pub(crate) fn keccak256(data: &[u8]) -> [u8; 32] {
|
||||||
|
alloy_core::primitives::keccak256(data).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn hash_to_scalar(data: &[u8]) -> Scalar {
|
||||||
|
<Scalar as Reduce<KU256>>::reduce_bytes(&keccak256(data).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn address(point: &ProjectivePoint) -> [u8; 20] {
|
||||||
|
let encoded_point = point.to_encoded_point(false);
|
||||||
|
// Last 20 bytes of the hash of the concatenated x and y coordinates
|
||||||
|
// We obtain the concatenated x and y coordinates via the uncompressed encoding of the point
|
||||||
|
keccak256(&encoded_point.as_ref()[1 .. 65])[12 ..].try_into().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deterministically sign a transaction.
|
||||||
|
///
|
||||||
|
/// This function panics if passed a transaction with a non-None chain ID.
|
||||||
|
pub fn deterministically_sign(tx: &TxLegacy) -> Signed<TxLegacy> {
|
||||||
|
assert!(
|
||||||
|
tx.chain_id.is_none(),
|
||||||
|
"chain ID was Some when deterministically signing a TX (causing a non-deterministic signer)"
|
||||||
|
);
|
||||||
|
|
||||||
|
let sig_hash = tx.signature_hash().0;
|
||||||
|
let mut r = hash_to_scalar(&[sig_hash.as_slice(), b"r"].concat());
|
||||||
|
let mut s = hash_to_scalar(&[sig_hash.as_slice(), b"s"].concat());
|
||||||
|
loop {
|
||||||
|
let r_bytes: [u8; 32] = r.to_repr().into();
|
||||||
|
let s_bytes: [u8; 32] = s.to_repr().into();
|
||||||
|
let v = Parity::NonEip155(false);
|
||||||
|
let signature =
|
||||||
|
AlloySignature::from_scalars_and_parity(r_bytes.into(), s_bytes.into(), v).unwrap();
|
||||||
|
let tx = tx.clone().into_signed(signature);
|
||||||
|
if tx.recover_signer().is_ok() {
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-hash until valid
|
||||||
|
r = hash_to_scalar(r_bytes.as_ref());
|
||||||
|
s = hash_to_scalar(s_bytes.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The public key for a Schnorr-signing account.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub struct PublicKey {
|
||||||
|
pub(crate) A: ProjectivePoint,
|
||||||
|
pub(crate) px: Scalar,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PublicKey {
|
||||||
|
/// Construct a new `PublicKey`.
|
||||||
|
///
|
||||||
|
/// This will return None if the provided point isn't eligible to be a public key (due to
|
||||||
|
/// bounds such as parity).
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn new(A: ProjectivePoint) -> Option<PublicKey> {
|
||||||
|
let affine = A.to_affine();
|
||||||
|
// Only allow even keys to save a word within Ethereum
|
||||||
|
let is_odd = bool::from(affine.y_is_odd());
|
||||||
|
if is_odd {
|
||||||
|
None?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x_coord = affine.x();
|
||||||
|
let x_coord_scalar = <Scalar as Reduce<KU256>>::reduce_bytes(&x_coord);
|
||||||
|
// Return None if a reduction would occur
|
||||||
|
// Reductions would be incredibly unlikely and shouldn't be an issue, yet it's one less
|
||||||
|
// headache/concern to have
|
||||||
|
// This does ban a trivial amoount of public keys
|
||||||
|
if x_coord_scalar.to_repr() != x_coord {
|
||||||
|
None?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(PublicKey { A, px: x_coord_scalar })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn point(&self) -> ProjectivePoint {
|
||||||
|
self.A
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn eth_repr(&self) -> [u8; 32] {
|
||||||
|
self.px.to_repr().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn from_eth_repr(repr: [u8; 32]) -> Option<Self> {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let A = Option::<AffinePoint>::from(AffinePoint::decompress(&repr.into(), 0.into()))?.into();
|
||||||
|
Option::from(Scalar::from_repr(repr.into())).map(|px| PublicKey { A, px })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The HRAm to use for the Schnorr contract.
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct EthereumHram {}
|
||||||
|
impl Hram<Secp256k1> for EthereumHram {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn hram(R: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar {
|
||||||
|
let x_coord = A.to_affine().x();
|
||||||
|
|
||||||
|
let mut data = address(R).to_vec();
|
||||||
|
data.extend(x_coord.as_slice());
|
||||||
|
data.extend(m);
|
||||||
|
|
||||||
|
<Scalar as Reduce<KU256>>::reduce_bytes(&keccak256(&data).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A signature for the Schnorr contract.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Signature {
|
||||||
|
pub(crate) c: Scalar,
|
||||||
|
pub(crate) s: Scalar,
|
||||||
|
}
|
||||||
|
impl Signature {
|
||||||
|
pub fn verify(&self, public_key: &PublicKey, message: &[u8]) -> bool {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let R = (Secp256k1::generator() * self.s) - (public_key.A * self.c);
|
||||||
|
EthereumHram::hram(&R, &public_key.A, message) == self.c
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a new `Signature`.
|
||||||
|
///
|
||||||
|
/// This will return None if the signature is invalid.
|
||||||
|
pub fn new(
|
||||||
|
public_key: &PublicKey,
|
||||||
|
message: &[u8],
|
||||||
|
signature: SchnorrSignature<Secp256k1>,
|
||||||
|
) -> Option<Signature> {
|
||||||
|
let c = EthereumHram::hram(&signature.R, &public_key.A, message);
|
||||||
|
if !signature.verify(public_key.A, c) {
|
||||||
|
None?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = Signature { c, s: signature.s };
|
||||||
|
assert!(res.verify(public_key, message));
|
||||||
|
Some(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn c(&self) -> Scalar {
|
||||||
|
self.c
|
||||||
|
}
|
||||||
|
pub fn s(&self) -> Scalar {
|
||||||
|
self.s
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> [u8; 64] {
|
||||||
|
let mut res = [0; 64];
|
||||||
|
res[.. 32].copy_from_slice(self.c.to_repr().as_ref());
|
||||||
|
res[32 ..].copy_from_slice(self.s.to_repr().as_ref());
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(bytes: [u8; 64]) -> std::io::Result<Self> {
|
||||||
|
let mut reader = bytes.as_slice();
|
||||||
|
let c = Secp256k1::read_F(&mut reader)?;
|
||||||
|
let s = Secp256k1::read_F(&mut reader)?;
|
||||||
|
Ok(Signature { c, s })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<&Signature> for AbiSignature {
|
||||||
|
fn from(sig: &Signature) -> AbiSignature {
|
||||||
|
let c: [u8; 32] = sig.c.to_repr().into();
|
||||||
|
let s: [u8; 32] = sig.s.to_repr().into();
|
||||||
|
AbiSignature { c: c.into(), s: s.into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
113
coins/ethereum/src/deployer.rs
Normal file
113
coins/ethereum/src/deployer.rs
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use alloy_core::primitives::{hex::FromHex, Address, B256, U256, Bytes, TxKind};
|
||||||
|
use alloy_consensus::{Signed, TxLegacy};
|
||||||
|
|
||||||
|
use alloy_sol_types::{SolCall, SolEvent};
|
||||||
|
|
||||||
|
use alloy_rpc_types_eth::{BlockNumberOrTag, Filter};
|
||||||
|
use alloy_simple_request_transport::SimpleRequest;
|
||||||
|
use alloy_provider::{Provider, RootProvider};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Error,
|
||||||
|
crypto::{self, keccak256, PublicKey},
|
||||||
|
router::Router,
|
||||||
|
};
|
||||||
|
pub use crate::abi::deployer as abi;
|
||||||
|
|
||||||
|
/// The Deployer contract for the Router contract.
|
||||||
|
///
|
||||||
|
/// This Deployer has a deterministic address, letting it be immediately identified on any
|
||||||
|
/// compatible chain. It then supports retrieving the Router contract's address (which isn't
|
||||||
|
/// deterministic) using a single log query.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Deployer;
|
||||||
|
impl Deployer {
|
||||||
|
/// Obtain the transaction to deploy this contract, already signed.
|
||||||
|
///
|
||||||
|
/// The account this transaction is sent from (which is populated in `from`) must be sufficiently
|
||||||
|
/// funded for this transaction to be submitted. This account has no known private key to anyone,
|
||||||
|
/// so ETH sent can be neither misappropriated nor returned.
|
||||||
|
pub fn deployment_tx() -> Signed<TxLegacy> {
|
||||||
|
let bytecode = include_str!("../artifacts/Deployer.bin");
|
||||||
|
let bytecode =
|
||||||
|
Bytes::from_hex(bytecode).expect("compiled-in Deployer bytecode wasn't valid hex");
|
||||||
|
|
||||||
|
let tx = TxLegacy {
|
||||||
|
chain_id: None,
|
||||||
|
nonce: 0,
|
||||||
|
gas_price: 100_000_000_000u128,
|
||||||
|
// TODO: Use a more accurate gas limit
|
||||||
|
gas_limit: 1_000_000u128,
|
||||||
|
to: TxKind::Create,
|
||||||
|
value: U256::ZERO,
|
||||||
|
input: bytecode,
|
||||||
|
};
|
||||||
|
|
||||||
|
crypto::deterministically_sign(&tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain the deterministic address for this contract.
|
||||||
|
pub fn address() -> [u8; 20] {
|
||||||
|
let deployer_deployer =
|
||||||
|
Self::deployment_tx().recover_signer().expect("deployment_tx didn't have a valid signature");
|
||||||
|
**Address::create(&deployer_deployer, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a new view of the `Deployer`.
|
||||||
|
pub async fn new(provider: Arc<RootProvider<SimpleRequest>>) -> Result<Option<Self>, Error> {
|
||||||
|
let address = Self::address();
|
||||||
|
let code = provider.get_code_at(address.into()).await.map_err(|_| Error::ConnectionError)?;
|
||||||
|
// Contract has yet to be deployed
|
||||||
|
if code.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
Ok(Some(Self))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Yield the `ContractCall` necessary to deploy the Router.
|
||||||
|
pub fn deploy_router(&self, key: &PublicKey) -> TxLegacy {
|
||||||
|
TxLegacy {
|
||||||
|
to: TxKind::Call(Self::address().into()),
|
||||||
|
input: abi::deployCall::new((Router::init_code(key).into(),)).abi_encode().into(),
|
||||||
|
gas_limit: 1_000_000,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the first Router deployed with the specified key as its first key.
|
||||||
|
///
|
||||||
|
/// This is the Router Serai will use, and is the only way to construct a `Router`.
|
||||||
|
pub async fn find_router(
|
||||||
|
&self,
|
||||||
|
provider: Arc<RootProvider<SimpleRequest>>,
|
||||||
|
key: &PublicKey,
|
||||||
|
) -> Result<Option<Router>, Error> {
|
||||||
|
let init_code = Router::init_code(key);
|
||||||
|
let init_code_hash = keccak256(&init_code);
|
||||||
|
|
||||||
|
#[cfg(not(test))]
|
||||||
|
let to_block = BlockNumberOrTag::Finalized;
|
||||||
|
#[cfg(test)]
|
||||||
|
let to_block = BlockNumberOrTag::Latest;
|
||||||
|
|
||||||
|
// Find the first log using this init code (where the init code is binding to the key)
|
||||||
|
// TODO: Make an abstraction for event filtering (de-duplicating common code)
|
||||||
|
let filter =
|
||||||
|
Filter::new().from_block(0).to_block(to_block).address(Address::from(Self::address()));
|
||||||
|
let filter = filter.event_signature(abi::Deployment::SIGNATURE_HASH);
|
||||||
|
let filter = filter.topic1(B256::from(init_code_hash));
|
||||||
|
let logs = provider.get_logs(&filter).await.map_err(|_| Error::ConnectionError)?;
|
||||||
|
|
||||||
|
let Some(first_log) = logs.first() else { return Ok(None) };
|
||||||
|
let router = first_log
|
||||||
|
.log_decode::<abi::Deployment>()
|
||||||
|
.map_err(|_| Error::ConnectionError)?
|
||||||
|
.inner
|
||||||
|
.data
|
||||||
|
.created;
|
||||||
|
|
||||||
|
Ok(Some(Router::new(provider, router)))
|
||||||
|
}
|
||||||
|
}
|
||||||
105
coins/ethereum/src/erc20.rs
Normal file
105
coins/ethereum/src/erc20.rs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
use std::{sync::Arc, collections::HashSet};
|
||||||
|
|
||||||
|
use alloy_core::primitives::{Address, B256, U256};
|
||||||
|
|
||||||
|
use alloy_sol_types::{SolInterface, SolEvent};
|
||||||
|
|
||||||
|
use alloy_rpc_types_eth::Filter;
|
||||||
|
use alloy_simple_request_transport::SimpleRequest;
|
||||||
|
use alloy_provider::{Provider, RootProvider};
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
|
pub use crate::abi::erc20 as abi;
|
||||||
|
use abi::{IERC20Calls, Transfer, transferCall, transferFromCall};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct TopLevelErc20Transfer {
|
||||||
|
pub id: [u8; 32],
|
||||||
|
pub from: [u8; 20],
|
||||||
|
pub amount: U256,
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A view for an ERC20 contract.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Erc20(Arc<RootProvider<SimpleRequest>>, Address);
|
||||||
|
impl Erc20 {
|
||||||
|
/// Construct a new view of the specified ERC20 contract.
|
||||||
|
pub fn new(provider: Arc<RootProvider<SimpleRequest>>, address: [u8; 20]) -> Self {
|
||||||
|
Self(provider, Address::from(&address))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn top_level_transfers(
|
||||||
|
&self,
|
||||||
|
block: u64,
|
||||||
|
to: [u8; 20],
|
||||||
|
) -> Result<Vec<TopLevelErc20Transfer>, Error> {
|
||||||
|
let filter = Filter::new().from_block(block).to_block(block).address(self.1);
|
||||||
|
let filter = filter.event_signature(Transfer::SIGNATURE_HASH);
|
||||||
|
let mut to_topic = [0; 32];
|
||||||
|
to_topic[12 ..].copy_from_slice(&to);
|
||||||
|
let filter = filter.topic2(B256::from(to_topic));
|
||||||
|
let logs = self.0.get_logs(&filter).await.map_err(|_| Error::ConnectionError)?;
|
||||||
|
|
||||||
|
let mut handled = HashSet::new();
|
||||||
|
|
||||||
|
let mut top_level_transfers = vec![];
|
||||||
|
for log in logs {
|
||||||
|
// Double check the address which emitted this log
|
||||||
|
if log.address() != self.1 {
|
||||||
|
Err(Error::ConnectionError)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tx_id = log.transaction_hash.ok_or(Error::ConnectionError)?;
|
||||||
|
let tx =
|
||||||
|
self.0.get_transaction_by_hash(tx_id).await.ok().flatten().ok_or(Error::ConnectionError)?;
|
||||||
|
|
||||||
|
// If this is a top-level call...
|
||||||
|
if tx.to == Some(self.1) {
|
||||||
|
// And we recognize the call...
|
||||||
|
// Don't validate the encoding as this can't be re-encoded to an identical bytestring due
|
||||||
|
// to the InInstruction appended
|
||||||
|
if let Ok(call) = IERC20Calls::abi_decode(&tx.input, false) {
|
||||||
|
// Extract the top-level call's from/to/value
|
||||||
|
let (from, call_to, value) = match call {
|
||||||
|
IERC20Calls::transfer(transferCall { to: call_to, value }) => (tx.from, call_to, value),
|
||||||
|
IERC20Calls::transferFrom(transferFromCall { from, to: call_to, value }) => {
|
||||||
|
(from, call_to, value)
|
||||||
|
}
|
||||||
|
// Treat any other function selectors as unrecognized
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let log = log.log_decode::<Transfer>().map_err(|_| Error::ConnectionError)?.inner.data;
|
||||||
|
|
||||||
|
// Ensure the top-level transfer is equivalent, and this presumably isn't a log for an
|
||||||
|
// internal transfer
|
||||||
|
if (log.from != from) || (call_to != to) || (value != log.value) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that the top-level transfer is confirmed to be equivalent to the log, ensure it's
|
||||||
|
// the only log we handle
|
||||||
|
if handled.contains(&tx_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
handled.insert(tx_id);
|
||||||
|
|
||||||
|
// Read the data appended after
|
||||||
|
let encoded = call.abi_encode();
|
||||||
|
let data = tx.input.as_ref()[encoded.len() ..].to_vec();
|
||||||
|
|
||||||
|
// Push the transfer
|
||||||
|
top_level_transfers.push(TopLevelErc20Transfer {
|
||||||
|
// Since we'll only handle one log for this TX, set the ID to the TX ID
|
||||||
|
id: *tx_id,
|
||||||
|
from: *log.from.0,
|
||||||
|
amount: log.value,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(top_level_transfers)
|
||||||
|
}
|
||||||
|
}
|
||||||
35
coins/ethereum/src/lib.rs
Normal file
35
coins/ethereum/src/lib.rs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub mod alloy {
|
||||||
|
pub use alloy_core::primitives;
|
||||||
|
pub use alloy_core as core;
|
||||||
|
pub use alloy_sol_types as sol_types;
|
||||||
|
|
||||||
|
pub use alloy_consensus as consensus;
|
||||||
|
pub use alloy_network as network;
|
||||||
|
pub use alloy_rpc_types_eth as rpc_types;
|
||||||
|
pub use alloy_simple_request_transport as simple_request_transport;
|
||||||
|
pub use alloy_rpc_client as rpc_client;
|
||||||
|
pub use alloy_provider as provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod crypto;
|
||||||
|
|
||||||
|
pub(crate) mod abi;
|
||||||
|
|
||||||
|
pub mod erc20;
|
||||||
|
pub mod deployer;
|
||||||
|
pub mod router;
|
||||||
|
|
||||||
|
pub mod machine;
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "tests"))]
|
||||||
|
pub mod tests;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("failed to verify Schnorr signature")]
|
||||||
|
InvalidSignature,
|
||||||
|
#[error("couldn't make call/send TX")]
|
||||||
|
ConnectionError,
|
||||||
|
}
|
||||||
414
coins/ethereum/src/machine.rs
Normal file
414
coins/ethereum/src/machine.rs
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
use std::{
|
||||||
|
io::{self, Read},
|
||||||
|
collections::HashMap,
|
||||||
|
};
|
||||||
|
|
||||||
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
|
use transcript::{Transcript, RecommendedTranscript};
|
||||||
|
|
||||||
|
use group::GroupEncoding;
|
||||||
|
use frost::{
|
||||||
|
curve::{Ciphersuite, Secp256k1},
|
||||||
|
Participant, ThresholdKeys, FrostError,
|
||||||
|
algorithm::Schnorr,
|
||||||
|
sign::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
use alloy_core::primitives::U256;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
crypto::{PublicKey, EthereumHram, Signature},
|
||||||
|
router::{
|
||||||
|
abi::{Call as AbiCall, OutInstruction as AbiOutInstruction},
|
||||||
|
Router,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Call {
|
||||||
|
pub to: [u8; 20],
|
||||||
|
pub value: U256,
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
impl Call {
|
||||||
|
pub fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
||||||
|
let mut to = [0; 20];
|
||||||
|
reader.read_exact(&mut to)?;
|
||||||
|
|
||||||
|
let value = {
|
||||||
|
let mut value_bytes = [0; 32];
|
||||||
|
reader.read_exact(&mut value_bytes)?;
|
||||||
|
U256::from_le_slice(&value_bytes)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut data_len = {
|
||||||
|
let mut data_len = [0; 4];
|
||||||
|
reader.read_exact(&mut data_len)?;
|
||||||
|
usize::try_from(u32::from_le_bytes(data_len)).expect("u32 couldn't fit within a usize")
|
||||||
|
};
|
||||||
|
|
||||||
|
// A valid DoS would be to claim a 4 GB data is present for only 4 bytes
|
||||||
|
// We read this in 1 KB chunks to only read data actually present (with a max DoS of 1 KB)
|
||||||
|
let mut data = vec![];
|
||||||
|
while data_len > 0 {
|
||||||
|
let chunk_len = data_len.min(1024);
|
||||||
|
let mut chunk = vec![0; chunk_len];
|
||||||
|
reader.read_exact(&mut chunk)?;
|
||||||
|
data.extend(&chunk);
|
||||||
|
data_len -= chunk_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Call { to, value, data })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||||
|
writer.write_all(&self.to)?;
|
||||||
|
writer.write_all(&self.value.as_le_bytes())?;
|
||||||
|
|
||||||
|
let data_len = u32::try_from(self.data.len())
|
||||||
|
.map_err(|_| io::Error::other("call data length exceeded 2**32"))?;
|
||||||
|
writer.write_all(&data_len.to_le_bytes())?;
|
||||||
|
writer.write_all(&self.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Call> for AbiCall {
|
||||||
|
fn from(call: Call) -> AbiCall {
|
||||||
|
AbiCall { to: call.to.into(), value: call.value, data: call.data.into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum OutInstructionTarget {
|
||||||
|
Direct([u8; 20]),
|
||||||
|
Calls(Vec<Call>),
|
||||||
|
}
|
||||||
|
impl OutInstructionTarget {
|
||||||
|
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
||||||
|
let mut kind = [0xff];
|
||||||
|
reader.read_exact(&mut kind)?;
|
||||||
|
|
||||||
|
match kind[0] {
|
||||||
|
0 => {
|
||||||
|
let mut addr = [0; 20];
|
||||||
|
reader.read_exact(&mut addr)?;
|
||||||
|
Ok(OutInstructionTarget::Direct(addr))
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
let mut calls_len = [0; 4];
|
||||||
|
reader.read_exact(&mut calls_len)?;
|
||||||
|
let calls_len = u32::from_le_bytes(calls_len);
|
||||||
|
|
||||||
|
let mut calls = vec![];
|
||||||
|
for _ in 0 .. calls_len {
|
||||||
|
calls.push(Call::read(reader)?);
|
||||||
|
}
|
||||||
|
Ok(OutInstructionTarget::Calls(calls))
|
||||||
|
}
|
||||||
|
_ => Err(io::Error::other("unrecognized OutInstructionTarget"))?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||||
|
match self {
|
||||||
|
OutInstructionTarget::Direct(addr) => {
|
||||||
|
writer.write_all(&[0])?;
|
||||||
|
writer.write_all(addr)?;
|
||||||
|
}
|
||||||
|
OutInstructionTarget::Calls(calls) => {
|
||||||
|
writer.write_all(&[1])?;
|
||||||
|
let call_len = u32::try_from(calls.len())
|
||||||
|
.map_err(|_| io::Error::other("amount of calls exceeded 2**32"))?;
|
||||||
|
writer.write_all(&call_len.to_le_bytes())?;
|
||||||
|
for call in calls {
|
||||||
|
call.write(writer)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct OutInstruction {
|
||||||
|
pub target: OutInstructionTarget,
|
||||||
|
pub value: U256,
|
||||||
|
}
|
||||||
|
impl OutInstruction {
|
||||||
|
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
||||||
|
let target = OutInstructionTarget::read(reader)?;
|
||||||
|
|
||||||
|
let value = {
|
||||||
|
let mut value_bytes = [0; 32];
|
||||||
|
reader.read_exact(&mut value_bytes)?;
|
||||||
|
U256::from_le_slice(&value_bytes)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(OutInstruction { target, value })
|
||||||
|
}
|
||||||
|
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||||
|
self.target.write(writer)?;
|
||||||
|
writer.write_all(&self.value.as_le_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<OutInstruction> for AbiOutInstruction {
|
||||||
|
fn from(instruction: OutInstruction) -> AbiOutInstruction {
|
||||||
|
match instruction.target {
|
||||||
|
OutInstructionTarget::Direct(addr) => {
|
||||||
|
AbiOutInstruction { to: addr.into(), calls: vec![], value: instruction.value }
|
||||||
|
}
|
||||||
|
OutInstructionTarget::Calls(calls) => AbiOutInstruction {
|
||||||
|
to: [0; 20].into(),
|
||||||
|
calls: calls.into_iter().map(Into::into).collect(),
|
||||||
|
value: instruction.value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum RouterCommand {
|
||||||
|
UpdateSeraiKey { chain_id: U256, nonce: U256, key: PublicKey },
|
||||||
|
Execute { chain_id: U256, nonce: U256, outs: Vec<OutInstruction> },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RouterCommand {
|
||||||
|
pub fn msg(&self) -> Vec<u8> {
|
||||||
|
match self {
|
||||||
|
RouterCommand::UpdateSeraiKey { chain_id, nonce, key } => {
|
||||||
|
Router::update_serai_key_message(*chain_id, *nonce, key)
|
||||||
|
}
|
||||||
|
RouterCommand::Execute { chain_id, nonce, outs } => Router::execute_message(
|
||||||
|
*chain_id,
|
||||||
|
*nonce,
|
||||||
|
outs.iter().map(|out| out.clone().into()).collect(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
||||||
|
let mut kind = [0xff];
|
||||||
|
reader.read_exact(&mut kind)?;
|
||||||
|
|
||||||
|
match kind[0] {
|
||||||
|
0 => {
|
||||||
|
let mut chain_id = [0; 32];
|
||||||
|
reader.read_exact(&mut chain_id)?;
|
||||||
|
|
||||||
|
let mut nonce = [0; 32];
|
||||||
|
reader.read_exact(&mut nonce)?;
|
||||||
|
|
||||||
|
let key = PublicKey::new(Secp256k1::read_G(reader)?)
|
||||||
|
.ok_or(io::Error::other("key for RouterCommand doesn't have an eth representation"))?;
|
||||||
|
Ok(RouterCommand::UpdateSeraiKey {
|
||||||
|
chain_id: U256::from_le_slice(&chain_id),
|
||||||
|
nonce: U256::from_le_slice(&nonce),
|
||||||
|
key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
let mut chain_id = [0; 32];
|
||||||
|
reader.read_exact(&mut chain_id)?;
|
||||||
|
let chain_id = U256::from_le_slice(&chain_id);
|
||||||
|
|
||||||
|
let mut nonce = [0; 32];
|
||||||
|
reader.read_exact(&mut nonce)?;
|
||||||
|
let nonce = U256::from_le_slice(&nonce);
|
||||||
|
|
||||||
|
let mut outs_len = [0; 4];
|
||||||
|
reader.read_exact(&mut outs_len)?;
|
||||||
|
let outs_len = u32::from_le_bytes(outs_len);
|
||||||
|
|
||||||
|
let mut outs = vec![];
|
||||||
|
for _ in 0 .. outs_len {
|
||||||
|
outs.push(OutInstruction::read(reader)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RouterCommand::Execute { chain_id, nonce, outs })
|
||||||
|
}
|
||||||
|
_ => Err(io::Error::other("reading unknown type of RouterCommand"))?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||||
|
match self {
|
||||||
|
RouterCommand::UpdateSeraiKey { chain_id, nonce, key } => {
|
||||||
|
writer.write_all(&[0])?;
|
||||||
|
writer.write_all(&chain_id.as_le_bytes())?;
|
||||||
|
writer.write_all(&nonce.as_le_bytes())?;
|
||||||
|
writer.write_all(&key.A.to_bytes())
|
||||||
|
}
|
||||||
|
RouterCommand::Execute { chain_id, nonce, outs } => {
|
||||||
|
writer.write_all(&[1])?;
|
||||||
|
writer.write_all(&chain_id.as_le_bytes())?;
|
||||||
|
writer.write_all(&nonce.as_le_bytes())?;
|
||||||
|
writer.write_all(&u32::try_from(outs.len()).unwrap().to_le_bytes())?;
|
||||||
|
for out in outs {
|
||||||
|
out.write(writer)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
|
let mut res = vec![];
|
||||||
|
self.write(&mut res).unwrap();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct SignedRouterCommand {
|
||||||
|
command: RouterCommand,
|
||||||
|
signature: Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignedRouterCommand {
|
||||||
|
pub fn new(key: &PublicKey, command: RouterCommand, signature: &[u8; 64]) -> Option<Self> {
|
||||||
|
let c = Secp256k1::read_F(&mut &signature[.. 32]).ok()?;
|
||||||
|
let s = Secp256k1::read_F(&mut &signature[32 ..]).ok()?;
|
||||||
|
let signature = Signature { c, s };
|
||||||
|
|
||||||
|
if !signature.verify(key, &command.msg()) {
|
||||||
|
None?
|
||||||
|
}
|
||||||
|
Some(SignedRouterCommand { command, signature })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(&self) -> &RouterCommand {
|
||||||
|
&self.command
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn signature(&self) -> &Signature {
|
||||||
|
&self.signature
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
||||||
|
let command = RouterCommand::read(reader)?;
|
||||||
|
|
||||||
|
let mut sig = [0; 64];
|
||||||
|
reader.read_exact(&mut sig)?;
|
||||||
|
let signature = Signature::from_bytes(sig)?;
|
||||||
|
|
||||||
|
Ok(SignedRouterCommand { command, signature })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||||
|
self.command.write(writer)?;
|
||||||
|
writer.write_all(&self.signature.to_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RouterCommandMachine {
|
||||||
|
key: PublicKey,
|
||||||
|
command: RouterCommand,
|
||||||
|
machine: AlgorithmMachine<Secp256k1, Schnorr<Secp256k1, RecommendedTranscript, EthereumHram>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RouterCommandMachine {
|
||||||
|
pub fn new(keys: ThresholdKeys<Secp256k1>, command: RouterCommand) -> Option<Self> {
|
||||||
|
// The Schnorr algorithm should be fine without this, even when using the IETF variant
|
||||||
|
// If this is better and more comprehensive, we should do it, even if not necessary
|
||||||
|
let mut transcript = RecommendedTranscript::new(b"ethereum-serai RouterCommandMachine v0.1");
|
||||||
|
let key = keys.group_key();
|
||||||
|
transcript.append_message(b"key", key.to_bytes());
|
||||||
|
transcript.append_message(b"command", command.serialize());
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
key: PublicKey::new(key)?,
|
||||||
|
command,
|
||||||
|
machine: AlgorithmMachine::new(Schnorr::new(transcript), keys),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PreprocessMachine for RouterCommandMachine {
|
||||||
|
type Preprocess = Preprocess<Secp256k1, ()>;
|
||||||
|
type Signature = SignedRouterCommand;
|
||||||
|
type SignMachine = RouterCommandSignMachine;
|
||||||
|
|
||||||
|
fn preprocess<R: RngCore + CryptoRng>(
|
||||||
|
self,
|
||||||
|
rng: &mut R,
|
||||||
|
) -> (Self::SignMachine, Self::Preprocess) {
|
||||||
|
let (machine, preprocess) = self.machine.preprocess(rng);
|
||||||
|
|
||||||
|
(RouterCommandSignMachine { key: self.key, command: self.command, machine }, preprocess)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RouterCommandSignMachine {
|
||||||
|
key: PublicKey,
|
||||||
|
command: RouterCommand,
|
||||||
|
machine: AlgorithmSignMachine<Secp256k1, Schnorr<Secp256k1, RecommendedTranscript, EthereumHram>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignMachine<SignedRouterCommand> for RouterCommandSignMachine {
|
||||||
|
type Params = ();
|
||||||
|
type Keys = ThresholdKeys<Secp256k1>;
|
||||||
|
type Preprocess = Preprocess<Secp256k1, ()>;
|
||||||
|
type SignatureShare = SignatureShare<Secp256k1>;
|
||||||
|
type SignatureMachine = RouterCommandSignatureMachine;
|
||||||
|
|
||||||
|
fn cache(self) -> CachedPreprocess {
|
||||||
|
unimplemented!(
|
||||||
|
"RouterCommand machines don't support caching their preprocesses due to {}",
|
||||||
|
"being already bound to a specific command"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_cache(
|
||||||
|
(): (),
|
||||||
|
_: ThresholdKeys<Secp256k1>,
|
||||||
|
_: CachedPreprocess,
|
||||||
|
) -> (Self, Self::Preprocess) {
|
||||||
|
unimplemented!(
|
||||||
|
"RouterCommand machines don't support caching their preprocesses due to {}",
|
||||||
|
"being already bound to a specific command"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_preprocess<R: Read>(&self, reader: &mut R) -> io::Result<Self::Preprocess> {
|
||||||
|
self.machine.read_preprocess(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign(
|
||||||
|
self,
|
||||||
|
commitments: HashMap<Participant, Self::Preprocess>,
|
||||||
|
msg: &[u8],
|
||||||
|
) -> Result<(RouterCommandSignatureMachine, Self::SignatureShare), FrostError> {
|
||||||
|
if !msg.is_empty() {
|
||||||
|
panic!("message was passed to a RouterCommand machine when it generates its own");
|
||||||
|
}
|
||||||
|
|
||||||
|
let (machine, share) = self.machine.sign(commitments, &self.command.msg())?;
|
||||||
|
|
||||||
|
Ok((RouterCommandSignatureMachine { key: self.key, command: self.command, machine }, share))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RouterCommandSignatureMachine {
|
||||||
|
key: PublicKey,
|
||||||
|
command: RouterCommand,
|
||||||
|
machine:
|
||||||
|
AlgorithmSignatureMachine<Secp256k1, Schnorr<Secp256k1, RecommendedTranscript, EthereumHram>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignatureMachine<SignedRouterCommand> for RouterCommandSignatureMachine {
|
||||||
|
type SignatureShare = SignatureShare<Secp256k1>;
|
||||||
|
|
||||||
|
fn read_share<R: Read>(&self, reader: &mut R) -> io::Result<Self::SignatureShare> {
|
||||||
|
self.machine.read_share(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete(
|
||||||
|
self,
|
||||||
|
shares: HashMap<Participant, Self::SignatureShare>,
|
||||||
|
) -> Result<SignedRouterCommand, FrostError> {
|
||||||
|
let sig = self.machine.complete(shares)?;
|
||||||
|
let signature = Signature::new(&self.key, &self.command.msg(), sig)
|
||||||
|
.expect("machine produced an invalid signature");
|
||||||
|
Ok(SignedRouterCommand { command: self.command, signature })
|
||||||
|
}
|
||||||
|
}
|
||||||
443
coins/ethereum/src/router.rs
Normal file
443
coins/ethereum/src/router.rs
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
use std::{sync::Arc, io, collections::HashSet};
|
||||||
|
|
||||||
|
use k256::{
|
||||||
|
elliptic_curve::{group::GroupEncoding, sec1},
|
||||||
|
ProjectivePoint,
|
||||||
|
};
|
||||||
|
|
||||||
|
use alloy_core::primitives::{hex::FromHex, Address, U256, Bytes, TxKind};
|
||||||
|
#[cfg(test)]
|
||||||
|
use alloy_core::primitives::B256;
|
||||||
|
use alloy_consensus::TxLegacy;
|
||||||
|
|
||||||
|
use alloy_sol_types::{SolValue, SolConstructor, SolCall, SolEvent};
|
||||||
|
|
||||||
|
use alloy_rpc_types_eth::Filter;
|
||||||
|
#[cfg(test)]
|
||||||
|
use alloy_rpc_types_eth::{BlockId, TransactionRequest, TransactionInput};
|
||||||
|
use alloy_simple_request_transport::SimpleRequest;
|
||||||
|
use alloy_provider::{Provider, RootProvider};
|
||||||
|
|
||||||
|
pub use crate::{
|
||||||
|
Error,
|
||||||
|
crypto::{PublicKey, Signature},
|
||||||
|
abi::{erc20::Transfer, router as abi},
|
||||||
|
};
|
||||||
|
use abi::{SeraiKeyUpdated, InInstruction as InInstructionEvent, Executed as ExecutedEvent};
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum Coin {
|
||||||
|
Ether,
|
||||||
|
Erc20([u8; 20]),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Coin {
|
||||||
|
pub fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
||||||
|
let mut kind = [0xff];
|
||||||
|
reader.read_exact(&mut kind)?;
|
||||||
|
Ok(match kind[0] {
|
||||||
|
0 => Coin::Ether,
|
||||||
|
1 => {
|
||||||
|
let mut address = [0; 20];
|
||||||
|
reader.read_exact(&mut address)?;
|
||||||
|
Coin::Erc20(address)
|
||||||
|
}
|
||||||
|
_ => Err(io::Error::other("unrecognized Coin type"))?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||||
|
match self {
|
||||||
|
Coin::Ether => writer.write_all(&[0]),
|
||||||
|
Coin::Erc20(token) => {
|
||||||
|
writer.write_all(&[1])?;
|
||||||
|
writer.write_all(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct InInstruction {
|
||||||
|
pub id: ([u8; 32], u64),
|
||||||
|
pub from: [u8; 20],
|
||||||
|
pub coin: Coin,
|
||||||
|
pub amount: U256,
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
pub key_at_end_of_block: ProjectivePoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InInstruction {
|
||||||
|
pub fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
||||||
|
let id = {
|
||||||
|
let mut id_hash = [0; 32];
|
||||||
|
reader.read_exact(&mut id_hash)?;
|
||||||
|
let mut id_pos = [0; 8];
|
||||||
|
reader.read_exact(&mut id_pos)?;
|
||||||
|
let id_pos = u64::from_le_bytes(id_pos);
|
||||||
|
(id_hash, id_pos)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut from = [0; 20];
|
||||||
|
reader.read_exact(&mut from)?;
|
||||||
|
|
||||||
|
let coin = Coin::read(reader)?;
|
||||||
|
let mut amount = [0; 32];
|
||||||
|
reader.read_exact(&mut amount)?;
|
||||||
|
let amount = U256::from_le_slice(&amount);
|
||||||
|
|
||||||
|
let mut data_len = [0; 4];
|
||||||
|
reader.read_exact(&mut data_len)?;
|
||||||
|
let data_len = usize::try_from(u32::from_le_bytes(data_len))
|
||||||
|
.map_err(|_| io::Error::other("InInstruction data exceeded 2**32 in length"))?;
|
||||||
|
let mut data = vec![0; data_len];
|
||||||
|
reader.read_exact(&mut data)?;
|
||||||
|
|
||||||
|
let mut key_at_end_of_block = <ProjectivePoint as GroupEncoding>::Repr::default();
|
||||||
|
reader.read_exact(&mut key_at_end_of_block)?;
|
||||||
|
let key_at_end_of_block = Option::from(ProjectivePoint::from_bytes(&key_at_end_of_block))
|
||||||
|
.ok_or(io::Error::other("InInstruction had key at end of block which wasn't valid"))?;
|
||||||
|
|
||||||
|
Ok(InInstruction { id, from, coin, amount, data, key_at_end_of_block })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||||
|
writer.write_all(&self.id.0)?;
|
||||||
|
writer.write_all(&self.id.1.to_le_bytes())?;
|
||||||
|
|
||||||
|
writer.write_all(&self.from)?;
|
||||||
|
|
||||||
|
self.coin.write(writer)?;
|
||||||
|
writer.write_all(&self.amount.as_le_bytes())?;
|
||||||
|
|
||||||
|
writer.write_all(
|
||||||
|
&u32::try_from(self.data.len())
|
||||||
|
.map_err(|_| {
|
||||||
|
io::Error::other("InInstruction being written had data exceeding 2**32 in length")
|
||||||
|
})?
|
||||||
|
.to_le_bytes(),
|
||||||
|
)?;
|
||||||
|
writer.write_all(&self.data)?;
|
||||||
|
|
||||||
|
writer.write_all(&self.key_at_end_of_block.to_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Executed {
|
||||||
|
pub tx_id: [u8; 32],
|
||||||
|
pub nonce: u64,
|
||||||
|
pub signature: [u8; 64],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The contract Serai uses to manage its state.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Router(Arc<RootProvider<SimpleRequest>>, Address);
|
||||||
|
impl Router {
|
||||||
|
pub(crate) fn code() -> Vec<u8> {
|
||||||
|
let bytecode = include_str!("../artifacts/Router.bin");
|
||||||
|
Bytes::from_hex(bytecode).expect("compiled-in Router bytecode wasn't valid hex").to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn init_code(key: &PublicKey) -> Vec<u8> {
|
||||||
|
let mut bytecode = Self::code();
|
||||||
|
// Append the constructor arguments
|
||||||
|
bytecode.extend((abi::constructorCall { _seraiKey: key.eth_repr().into() }).abi_encode());
|
||||||
|
bytecode
|
||||||
|
}
|
||||||
|
|
||||||
|
// This isn't pub in order to force users to use `Deployer::find_router`.
|
||||||
|
pub(crate) fn new(provider: Arc<RootProvider<SimpleRequest>>, address: Address) -> Self {
|
||||||
|
Self(provider, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn address(&self) -> [u8; 20] {
|
||||||
|
**self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the key for Serai at the specified block.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub async fn serai_key(&self, at: [u8; 32]) -> Result<PublicKey, Error> {
|
||||||
|
let call = TransactionRequest::default()
|
||||||
|
.to(self.1)
|
||||||
|
.input(TransactionInput::new(abi::seraiKeyCall::new(()).abi_encode().into()));
|
||||||
|
let bytes = self
|
||||||
|
.0
|
||||||
|
.call(&call)
|
||||||
|
.block(BlockId::Hash(B256::from(at).into()))
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::ConnectionError)?;
|
||||||
|
let res =
|
||||||
|
abi::seraiKeyCall::abi_decode_returns(&bytes, true).map_err(|_| Error::ConnectionError)?;
|
||||||
|
PublicKey::from_eth_repr(res._0.0).ok_or(Error::ConnectionError)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the message to be signed in order to update the key for Serai.
|
||||||
|
pub(crate) fn update_serai_key_message(chain_id: U256, nonce: U256, key: &PublicKey) -> Vec<u8> {
|
||||||
|
let mut buffer = b"updateSeraiKey".to_vec();
|
||||||
|
buffer.extend(&chain_id.to_be_bytes::<32>());
|
||||||
|
buffer.extend(&nonce.to_be_bytes::<32>());
|
||||||
|
buffer.extend(&key.eth_repr());
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the key representing Serai.
|
||||||
|
pub fn update_serai_key(&self, public_key: &PublicKey, sig: &Signature) -> TxLegacy {
|
||||||
|
// TODO: Set a more accurate gas
|
||||||
|
TxLegacy {
|
||||||
|
to: TxKind::Call(self.1),
|
||||||
|
input: abi::updateSeraiKeyCall::new((public_key.eth_repr().into(), sig.into()))
|
||||||
|
.abi_encode()
|
||||||
|
.into(),
|
||||||
|
gas_limit: 100_000,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current nonce for the published batches.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub async fn nonce(&self, at: [u8; 32]) -> Result<U256, Error> {
|
||||||
|
let call = TransactionRequest::default()
|
||||||
|
.to(self.1)
|
||||||
|
.input(TransactionInput::new(abi::nonceCall::new(()).abi_encode().into()));
|
||||||
|
let bytes = self
|
||||||
|
.0
|
||||||
|
.call(&call)
|
||||||
|
.block(BlockId::Hash(B256::from(at).into()))
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::ConnectionError)?;
|
||||||
|
let res =
|
||||||
|
abi::nonceCall::abi_decode_returns(&bytes, true).map_err(|_| Error::ConnectionError)?;
|
||||||
|
Ok(res._0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the message to be signed in order to update the key for Serai.
|
||||||
|
pub(crate) fn execute_message(
|
||||||
|
chain_id: U256,
|
||||||
|
nonce: U256,
|
||||||
|
outs: Vec<abi::OutInstruction>,
|
||||||
|
) -> Vec<u8> {
|
||||||
|
("execute".to_string(), chain_id, nonce, outs).abi_encode_params()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute a batch of `OutInstruction`s.
|
||||||
|
pub fn execute(&self, outs: &[abi::OutInstruction], sig: &Signature) -> TxLegacy {
|
||||||
|
TxLegacy {
|
||||||
|
to: TxKind::Call(self.1),
|
||||||
|
input: abi::executeCall::new((outs.to_vec(), sig.into())).abi_encode().into(),
|
||||||
|
// TODO
|
||||||
|
gas_limit: 100_000 + ((200_000 + 10_000) * u128::try_from(outs.len()).unwrap()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn key_at_end_of_block(&self, block: u64) -> Result<Option<ProjectivePoint>, Error> {
|
||||||
|
let filter = Filter::new().from_block(0).to_block(block).address(self.1);
|
||||||
|
let filter = filter.event_signature(SeraiKeyUpdated::SIGNATURE_HASH);
|
||||||
|
let all_keys = self.0.get_logs(&filter).await.map_err(|_| Error::ConnectionError)?;
|
||||||
|
if all_keys.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
let last_key_x_coordinate_log = all_keys.last().ok_or(Error::ConnectionError)?;
|
||||||
|
let last_key_x_coordinate = last_key_x_coordinate_log
|
||||||
|
.log_decode::<SeraiKeyUpdated>()
|
||||||
|
.map_err(|_| Error::ConnectionError)?
|
||||||
|
.inner
|
||||||
|
.data
|
||||||
|
.key;
|
||||||
|
|
||||||
|
let mut compressed_point = <ProjectivePoint as GroupEncoding>::Repr::default();
|
||||||
|
compressed_point[0] = u8::from(sec1::Tag::CompressedEvenY);
|
||||||
|
compressed_point[1 ..].copy_from_slice(last_key_x_coordinate.as_slice());
|
||||||
|
|
||||||
|
let key =
|
||||||
|
Option::from(ProjectivePoint::from_bytes(&compressed_point)).ok_or(Error::ConnectionError)?;
|
||||||
|
Ok(Some(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn in_instructions(
|
||||||
|
&self,
|
||||||
|
block: u64,
|
||||||
|
allowed_tokens: &HashSet<[u8; 20]>,
|
||||||
|
) -> Result<Vec<InInstruction>, Error> {
|
||||||
|
let Some(key_at_end_of_block) = self.key_at_end_of_block(block).await? else {
|
||||||
|
return Ok(vec![]);
|
||||||
|
};
|
||||||
|
|
||||||
|
let filter = Filter::new().from_block(block).to_block(block).address(self.1);
|
||||||
|
let filter = filter.event_signature(InInstructionEvent::SIGNATURE_HASH);
|
||||||
|
let logs = self.0.get_logs(&filter).await.map_err(|_| Error::ConnectionError)?;
|
||||||
|
|
||||||
|
let mut transfer_check = HashSet::new();
|
||||||
|
let mut in_instructions = vec![];
|
||||||
|
for log in logs {
|
||||||
|
// Double check the address which emitted this log
|
||||||
|
if log.address() != self.1 {
|
||||||
|
Err(Error::ConnectionError)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = (
|
||||||
|
log.block_hash.ok_or(Error::ConnectionError)?.into(),
|
||||||
|
log.log_index.ok_or(Error::ConnectionError)?,
|
||||||
|
);
|
||||||
|
|
||||||
|
let tx_hash = log.transaction_hash.ok_or(Error::ConnectionError)?;
|
||||||
|
let tx = self
|
||||||
|
.0
|
||||||
|
.get_transaction_by_hash(tx_hash)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.ok_or(Error::ConnectionError)?;
|
||||||
|
|
||||||
|
let log =
|
||||||
|
log.log_decode::<InInstructionEvent>().map_err(|_| Error::ConnectionError)?.inner.data;
|
||||||
|
|
||||||
|
let coin = if log.coin.0 == [0; 20] {
|
||||||
|
Coin::Ether
|
||||||
|
} else {
|
||||||
|
let token = *log.coin.0;
|
||||||
|
|
||||||
|
if !allowed_tokens.contains(&token) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this also counts as a top-level transfer via the token, drop it
|
||||||
|
//
|
||||||
|
// Necessary in order to handle a potential edge case with some theoretical token
|
||||||
|
// implementations
|
||||||
|
//
|
||||||
|
// This will either let it be handled by the top-level transfer hook or will drop it
|
||||||
|
// entirely on the side of caution
|
||||||
|
if tx.to == Some(token.into()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all logs for this TX
|
||||||
|
let receipt = self
|
||||||
|
.0
|
||||||
|
.get_transaction_receipt(tx_hash)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::ConnectionError)?
|
||||||
|
.ok_or(Error::ConnectionError)?;
|
||||||
|
let tx_logs = receipt.inner.logs();
|
||||||
|
|
||||||
|
// Find a matching transfer log
|
||||||
|
let mut found_transfer = false;
|
||||||
|
for tx_log in tx_logs {
|
||||||
|
let log_index = tx_log.log_index.ok_or(Error::ConnectionError)?;
|
||||||
|
// Ensure we didn't already use this transfer to check a distinct InInstruction event
|
||||||
|
if transfer_check.contains(&log_index) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this log is from the token we expected to be transferred
|
||||||
|
if tx_log.address().0 != token {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Check if this is a transfer log
|
||||||
|
// https://github.com/alloy-rs/core/issues/589
|
||||||
|
if tx_log.topics()[0] != Transfer::SIGNATURE_HASH {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Ok(transfer) = Transfer::decode_log(&tx_log.inner.clone(), true) else { continue };
|
||||||
|
// Check if this is a transfer to us for the expected amount
|
||||||
|
if (transfer.to == self.1) && (transfer.value == log.amount) {
|
||||||
|
transfer_check.insert(log_index);
|
||||||
|
found_transfer = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found_transfer {
|
||||||
|
// This shouldn't be a ConnectionError
|
||||||
|
// This is an exploit, a non-conforming ERC20, or an invalid connection
|
||||||
|
// This should halt the process which is sufficient, yet this is sub-optimal
|
||||||
|
// TODO
|
||||||
|
Err(Error::ConnectionError)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Coin::Erc20(token)
|
||||||
|
};
|
||||||
|
|
||||||
|
in_instructions.push(InInstruction {
|
||||||
|
id,
|
||||||
|
from: *log.from.0,
|
||||||
|
coin,
|
||||||
|
amount: log.amount,
|
||||||
|
data: log.instruction.as_ref().to_vec(),
|
||||||
|
key_at_end_of_block,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(in_instructions)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn executed_commands(&self, block: u64) -> Result<Vec<Executed>, Error> {
|
||||||
|
let mut res = vec![];
|
||||||
|
|
||||||
|
{
|
||||||
|
let filter = Filter::new().from_block(block).to_block(block).address(self.1);
|
||||||
|
let filter = filter.event_signature(SeraiKeyUpdated::SIGNATURE_HASH);
|
||||||
|
let logs = self.0.get_logs(&filter).await.map_err(|_| Error::ConnectionError)?;
|
||||||
|
|
||||||
|
for log in logs {
|
||||||
|
// Double check the address which emitted this log
|
||||||
|
if log.address() != self.1 {
|
||||||
|
Err(Error::ConnectionError)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tx_id = log.transaction_hash.ok_or(Error::ConnectionError)?.into();
|
||||||
|
|
||||||
|
let log =
|
||||||
|
log.log_decode::<SeraiKeyUpdated>().map_err(|_| Error::ConnectionError)?.inner.data;
|
||||||
|
|
||||||
|
let mut signature = [0; 64];
|
||||||
|
signature[.. 32].copy_from_slice(log.signature.c.as_ref());
|
||||||
|
signature[32 ..].copy_from_slice(log.signature.s.as_ref());
|
||||||
|
res.push(Executed {
|
||||||
|
tx_id,
|
||||||
|
nonce: log.nonce.try_into().map_err(|_| Error::ConnectionError)?,
|
||||||
|
signature,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let filter = Filter::new().from_block(block).to_block(block).address(self.1);
|
||||||
|
let filter = filter.event_signature(ExecutedEvent::SIGNATURE_HASH);
|
||||||
|
let logs = self.0.get_logs(&filter).await.map_err(|_| Error::ConnectionError)?;
|
||||||
|
|
||||||
|
for log in logs {
|
||||||
|
// Double check the address which emitted this log
|
||||||
|
if log.address() != self.1 {
|
||||||
|
Err(Error::ConnectionError)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tx_id = log.transaction_hash.ok_or(Error::ConnectionError)?.into();
|
||||||
|
|
||||||
|
let log = log.log_decode::<ExecutedEvent>().map_err(|_| Error::ConnectionError)?.inner.data;
|
||||||
|
|
||||||
|
let mut signature = [0; 64];
|
||||||
|
signature[.. 32].copy_from_slice(log.signature.c.as_ref());
|
||||||
|
signature[32 ..].copy_from_slice(log.signature.s.as_ref());
|
||||||
|
res.push(Executed {
|
||||||
|
tx_id,
|
||||||
|
nonce: log.nonce.try_into().map_err(|_| Error::ConnectionError)?,
|
||||||
|
signature,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tests")]
|
||||||
|
pub fn key_updated_filter(&self) -> Filter {
|
||||||
|
Filter::new().address(self.1).event_signature(SeraiKeyUpdated::SIGNATURE_HASH)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "tests")]
|
||||||
|
pub fn executed_filter(&self) -> Filter {
|
||||||
|
Filter::new().address(self.1).event_signature(ExecutedEvent::SIGNATURE_HASH)
|
||||||
|
}
|
||||||
|
}
|
||||||
13
coins/ethereum/src/tests/abi/mod.rs
Normal file
13
coins/ethereum/src/tests/abi/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
use alloy_sol_types::sol;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[allow(warnings)]
|
||||||
|
#[allow(needless_pass_by_value)]
|
||||||
|
#[allow(clippy::all)]
|
||||||
|
#[allow(clippy::ignored_unit_patterns)]
|
||||||
|
#[allow(clippy::redundant_closure_for_method_calls)]
|
||||||
|
mod schnorr_container {
|
||||||
|
use super::*;
|
||||||
|
sol!("src/tests/contracts/Schnorr.sol");
|
||||||
|
}
|
||||||
|
pub(crate) use schnorr_container::TestSchnorr as schnorr;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPLv3
|
||||||
pragma solidity ^0.8.26;
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
contract TestERC20 {
|
contract TestERC20 {
|
||||||
event Transfer(address indexed from, address indexed to, uint256 value);
|
event Transfer(address indexed from, address indexed to, uint256 value);
|
||||||
@@ -8,57 +8,44 @@ contract TestERC20 {
|
|||||||
function name() public pure returns (string memory) {
|
function name() public pure returns (string memory) {
|
||||||
return "Test ERC20";
|
return "Test ERC20";
|
||||||
}
|
}
|
||||||
|
|
||||||
function symbol() public pure returns (string memory) {
|
function symbol() public pure returns (string memory) {
|
||||||
return "TEST";
|
return "TEST";
|
||||||
}
|
}
|
||||||
|
|
||||||
function decimals() public pure returns (uint8) {
|
function decimals() public pure returns (uint8) {
|
||||||
return 18;
|
return 18;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 public totalSupply;
|
function totalSupply() public pure returns (uint256) {
|
||||||
|
return 1_000_000 * 10e18;
|
||||||
|
}
|
||||||
|
|
||||||
mapping(address => uint256) balances;
|
mapping(address => uint256) balances;
|
||||||
mapping(address => mapping(address => uint256)) allowances;
|
mapping(address => mapping(address => uint256)) allowances;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
balances[msg.sender] = totalSupply();
|
||||||
|
}
|
||||||
|
|
||||||
function balanceOf(address owner) public view returns (uint256) {
|
function balanceOf(address owner) public view returns (uint256) {
|
||||||
return balances[owner];
|
return balances[owner];
|
||||||
}
|
}
|
||||||
|
|
||||||
function transfer(address to, uint256 value) public returns (bool) {
|
function transfer(address to, uint256 value) public returns (bool) {
|
||||||
balances[msg.sender] -= value;
|
balances[msg.sender] -= value;
|
||||||
balances[to] += value;
|
balances[to] += value;
|
||||||
emit Transfer(msg.sender, to, value);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function transferFrom(address from, address to, uint256 value) public returns (bool) {
|
function transferFrom(address from, address to, uint256 value) public returns (bool) {
|
||||||
allowances[from][msg.sender] -= value;
|
allowances[from][msg.sender] -= value;
|
||||||
balances[from] -= value;
|
balances[from] -= value;
|
||||||
balances[to] += value;
|
balances[to] += value;
|
||||||
emit Transfer(from, to, value);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function approve(address spender, uint256 value) public returns (bool) {
|
function approve(address spender, uint256 value) public returns (bool) {
|
||||||
allowances[msg.sender][spender] = value;
|
allowances[msg.sender][spender] = value;
|
||||||
emit Approval(msg.sender, spender, value);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function allowance(address owner, address spender) public view returns (uint256) {
|
function allowance(address owner, address spender) public view returns (uint256) {
|
||||||
return allowances[owner][spender];
|
return allowances[owner][spender];
|
||||||
}
|
}
|
||||||
|
|
||||||
function mint(address owner, uint256 value) external {
|
|
||||||
balances[owner] += value;
|
|
||||||
totalSupply += value;
|
|
||||||
emit Transfer(address(0), owner, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function magicApprove(address owner, address spender, uint256 value) external {
|
|
||||||
allowances[owner][spender] = value;
|
|
||||||
emit Approval(owner, spender, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
15
coins/ethereum/src/tests/contracts/Schnorr.sol
Normal file
15
coins/ethereum/src/tests/contracts/Schnorr.sol
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// SPDX-License-Identifier: AGPLv3
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
import "../../../contracts/Schnorr.sol";
|
||||||
|
|
||||||
|
contract TestSchnorr {
|
||||||
|
function verify(
|
||||||
|
bytes32 px,
|
||||||
|
bytes calldata message,
|
||||||
|
bytes32 c,
|
||||||
|
bytes32 s
|
||||||
|
) external pure returns (bool) {
|
||||||
|
return Schnorr.verify(px, message, c, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
105
coins/ethereum/src/tests/crypto.rs
Normal file
105
coins/ethereum/src/tests/crypto.rs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
use rand_core::OsRng;
|
||||||
|
|
||||||
|
use group::ff::{Field, PrimeField};
|
||||||
|
use k256::{
|
||||||
|
ecdsa::{
|
||||||
|
self, hazmat::SignPrimitive, signature::hazmat::PrehashVerifier, SigningKey, VerifyingKey,
|
||||||
|
},
|
||||||
|
Scalar, ProjectivePoint,
|
||||||
|
};
|
||||||
|
|
||||||
|
use frost::{
|
||||||
|
curve::{Ciphersuite, Secp256k1},
|
||||||
|
algorithm::{Hram, IetfSchnorr},
|
||||||
|
tests::{algorithm_machines, sign},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{crypto::*, tests::key_gen};
|
||||||
|
|
||||||
|
// The ecrecover opcode, yet with parity replacing v
|
||||||
|
pub(crate) fn ecrecover(message: Scalar, odd_y: bool, r: Scalar, s: Scalar) -> Option<[u8; 20]> {
|
||||||
|
let sig = ecdsa::Signature::from_scalars(r, s).ok()?;
|
||||||
|
let message: [u8; 32] = message.to_repr().into();
|
||||||
|
alloy_core::primitives::Signature::from_signature_and_parity(
|
||||||
|
sig,
|
||||||
|
alloy_core::primitives::Parity::Parity(odd_y),
|
||||||
|
)
|
||||||
|
.ok()?
|
||||||
|
.recover_address_from_prehash(&alloy_core::primitives::B256::from(message))
|
||||||
|
.ok()
|
||||||
|
.map(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ecrecover() {
|
||||||
|
let private = SigningKey::random(&mut OsRng);
|
||||||
|
let public = VerifyingKey::from(&private);
|
||||||
|
|
||||||
|
// Sign the signature
|
||||||
|
const MESSAGE: &[u8] = b"Hello, World!";
|
||||||
|
let (sig, recovery_id) = private
|
||||||
|
.as_nonzero_scalar()
|
||||||
|
.try_sign_prehashed(
|
||||||
|
<Secp256k1 as Ciphersuite>::F::random(&mut OsRng),
|
||||||
|
&keccak256(MESSAGE).into(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Sanity check the signature verifies
|
||||||
|
#[allow(clippy::unit_cmp)] // Intended to assert this wasn't changed to Result<bool>
|
||||||
|
{
|
||||||
|
assert_eq!(public.verify_prehash(&keccak256(MESSAGE), &sig).unwrap(), ());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the ecrecover
|
||||||
|
assert_eq!(
|
||||||
|
ecrecover(
|
||||||
|
hash_to_scalar(MESSAGE),
|
||||||
|
u8::from(recovery_id.unwrap().is_y_odd()) == 1,
|
||||||
|
*sig.r(),
|
||||||
|
*sig.s()
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
address(&ProjectivePoint::from(public.as_affine()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the sign test with the EthereumHram
|
||||||
|
#[test]
|
||||||
|
fn test_signing() {
|
||||||
|
let (keys, _) = key_gen();
|
||||||
|
|
||||||
|
const MESSAGE: &[u8] = b"Hello, World!";
|
||||||
|
|
||||||
|
let algo = IetfSchnorr::<Secp256k1, EthereumHram>::ietf();
|
||||||
|
let _sig =
|
||||||
|
sign(&mut OsRng, &algo, keys.clone(), algorithm_machines(&mut OsRng, &algo, &keys), MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn preprocess_signature_for_ecrecover(
|
||||||
|
R: ProjectivePoint,
|
||||||
|
public_key: &PublicKey,
|
||||||
|
m: &[u8],
|
||||||
|
s: Scalar,
|
||||||
|
) -> (Scalar, Scalar) {
|
||||||
|
let c = EthereumHram::hram(&R, &public_key.A, m);
|
||||||
|
let sa = -(s * public_key.px);
|
||||||
|
let ca = -(c * public_key.px);
|
||||||
|
(sa, ca)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ecrecover_hack() {
|
||||||
|
let (keys, public_key) = key_gen();
|
||||||
|
|
||||||
|
const MESSAGE: &[u8] = b"Hello, World!";
|
||||||
|
|
||||||
|
let algo = IetfSchnorr::<Secp256k1, EthereumHram>::ietf();
|
||||||
|
let sig =
|
||||||
|
sign(&mut OsRng, &algo, keys.clone(), algorithm_machines(&mut OsRng, &algo, &keys), MESSAGE);
|
||||||
|
|
||||||
|
let (sa, ca) = preprocess_signature_for_ecrecover(sig.R, &public_key, MESSAGE, sig.s);
|
||||||
|
let q = ecrecover(sa, false, public_key.px, ca).unwrap();
|
||||||
|
assert_eq!(q, address(&sig.R));
|
||||||
|
}
|
||||||
131
coins/ethereum/src/tests/mod.rs
Normal file
131
coins/ethereum/src/tests/mod.rs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
use std::{sync::Arc, collections::HashMap};
|
||||||
|
|
||||||
|
use rand_core::OsRng;
|
||||||
|
|
||||||
|
use k256::{Scalar, ProjectivePoint};
|
||||||
|
use frost::{curve::Secp256k1, Participant, ThresholdKeys, tests::key_gen as frost_key_gen};
|
||||||
|
|
||||||
|
use alloy_core::{
|
||||||
|
primitives::{Address, U256, Bytes, TxKind},
|
||||||
|
hex::FromHex,
|
||||||
|
};
|
||||||
|
use alloy_consensus::{SignableTransaction, TxLegacy};
|
||||||
|
|
||||||
|
use alloy_rpc_types_eth::TransactionReceipt;
|
||||||
|
use alloy_simple_request_transport::SimpleRequest;
|
||||||
|
use alloy_provider::{Provider, RootProvider};
|
||||||
|
|
||||||
|
use crate::crypto::{address, deterministically_sign, PublicKey};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod crypto;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod abi;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod schnorr;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod router;
|
||||||
|
|
||||||
|
pub fn key_gen() -> (HashMap<Participant, ThresholdKeys<Secp256k1>>, PublicKey) {
|
||||||
|
let mut keys = frost_key_gen::<_, Secp256k1>(&mut OsRng);
|
||||||
|
let mut group_key = keys[&Participant::new(1).unwrap()].group_key();
|
||||||
|
|
||||||
|
let mut offset = Scalar::ZERO;
|
||||||
|
while PublicKey::new(group_key).is_none() {
|
||||||
|
offset += Scalar::ONE;
|
||||||
|
group_key += ProjectivePoint::GENERATOR;
|
||||||
|
}
|
||||||
|
for keys in keys.values_mut() {
|
||||||
|
*keys = keys.offset(offset);
|
||||||
|
}
|
||||||
|
let public_key = PublicKey::new(group_key).unwrap();
|
||||||
|
|
||||||
|
(keys, public_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Use a proper error here
|
||||||
|
pub async fn send(
|
||||||
|
provider: &RootProvider<SimpleRequest>,
|
||||||
|
wallet: &k256::ecdsa::SigningKey,
|
||||||
|
mut tx: TxLegacy,
|
||||||
|
) -> Option<TransactionReceipt> {
|
||||||
|
let verifying_key = *wallet.verifying_key().as_affine();
|
||||||
|
let address = Address::from(address(&verifying_key.into()));
|
||||||
|
|
||||||
|
// https://github.com/alloy-rs/alloy/issues/539
|
||||||
|
// let chain_id = provider.get_chain_id().await.unwrap();
|
||||||
|
// tx.chain_id = Some(chain_id);
|
||||||
|
tx.chain_id = None;
|
||||||
|
tx.nonce = provider.get_transaction_count(address).await.unwrap();
|
||||||
|
// 100 gwei
|
||||||
|
tx.gas_price = 100_000_000_000u128;
|
||||||
|
|
||||||
|
let sig = wallet.sign_prehash_recoverable(tx.signature_hash().as_ref()).unwrap();
|
||||||
|
assert_eq!(address, tx.clone().into_signed(sig.into()).recover_signer().unwrap());
|
||||||
|
assert!(
|
||||||
|
provider.get_balance(address).await.unwrap() >
|
||||||
|
((U256::from(tx.gas_price) * U256::from(tx.gas_limit)) + tx.value)
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut bytes = vec![];
|
||||||
|
tx.encode_with_signature_fields(&sig.into(), &mut bytes);
|
||||||
|
let pending_tx = provider.send_raw_transaction(&bytes).await.ok()?;
|
||||||
|
pending_tx.get_receipt().await.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fund_account(
|
||||||
|
provider: &RootProvider<SimpleRequest>,
|
||||||
|
wallet: &k256::ecdsa::SigningKey,
|
||||||
|
to_fund: Address,
|
||||||
|
value: U256,
|
||||||
|
) -> Option<()> {
|
||||||
|
let funding_tx =
|
||||||
|
TxLegacy { to: TxKind::Call(to_fund), gas_limit: 21_000, value, ..Default::default() };
|
||||||
|
assert!(send(provider, wallet, funding_tx).await.unwrap().status());
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Use a proper error here
|
||||||
|
pub async fn deploy_contract(
|
||||||
|
client: Arc<RootProvider<SimpleRequest>>,
|
||||||
|
wallet: &k256::ecdsa::SigningKey,
|
||||||
|
name: &str,
|
||||||
|
) -> Option<Address> {
|
||||||
|
let hex_bin_buf = std::fs::read_to_string(format!("./artifacts/{name}.bin")).unwrap();
|
||||||
|
let hex_bin =
|
||||||
|
if let Some(stripped) = hex_bin_buf.strip_prefix("0x") { stripped } else { &hex_bin_buf };
|
||||||
|
let bin = Bytes::from_hex(hex_bin).unwrap();
|
||||||
|
|
||||||
|
let deployment_tx = TxLegacy {
|
||||||
|
chain_id: None,
|
||||||
|
nonce: 0,
|
||||||
|
// 100 gwei
|
||||||
|
gas_price: 100_000_000_000u128,
|
||||||
|
gas_limit: 1_000_000,
|
||||||
|
to: TxKind::Create,
|
||||||
|
value: U256::ZERO,
|
||||||
|
input: bin,
|
||||||
|
};
|
||||||
|
|
||||||
|
let deployment_tx = deterministically_sign(&deployment_tx);
|
||||||
|
|
||||||
|
// Fund the deployer address
|
||||||
|
fund_account(
|
||||||
|
&client,
|
||||||
|
wallet,
|
||||||
|
deployment_tx.recover_signer().unwrap(),
|
||||||
|
U256::from(deployment_tx.tx().gas_limit) * U256::from(deployment_tx.tx().gas_price),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let (deployment_tx, sig, _) = deployment_tx.into_parts();
|
||||||
|
let mut bytes = vec![];
|
||||||
|
deployment_tx.encode_with_signature_fields(&sig, &mut bytes);
|
||||||
|
let pending_tx = client.send_raw_transaction(&bytes).await.ok()?;
|
||||||
|
let receipt = pending_tx.get_receipt().await.ok()?;
|
||||||
|
assert!(receipt.status());
|
||||||
|
|
||||||
|
Some(receipt.contract_address.unwrap())
|
||||||
|
}
|
||||||
184
coins/ethereum/src/tests/router.rs
Normal file
184
coins/ethereum/src/tests/router.rs
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
use std::{convert::TryFrom, sync::Arc, collections::HashMap};
|
||||||
|
|
||||||
|
use rand_core::OsRng;
|
||||||
|
|
||||||
|
use group::Group;
|
||||||
|
use k256::ProjectivePoint;
|
||||||
|
use frost::{
|
||||||
|
curve::Secp256k1,
|
||||||
|
Participant, ThresholdKeys,
|
||||||
|
algorithm::IetfSchnorr,
|
||||||
|
tests::{algorithm_machines, sign},
|
||||||
|
};
|
||||||
|
|
||||||
|
use alloy_core::primitives::{Address, U256};
|
||||||
|
|
||||||
|
use alloy_simple_request_transport::SimpleRequest;
|
||||||
|
use alloy_rpc_types_eth::BlockTransactionsKind;
|
||||||
|
use alloy_rpc_client::ClientBuilder;
|
||||||
|
use alloy_provider::{Provider, RootProvider};
|
||||||
|
|
||||||
|
use alloy_node_bindings::{Anvil, AnvilInstance};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
crypto::*,
|
||||||
|
deployer::Deployer,
|
||||||
|
router::{Router, abi as router},
|
||||||
|
tests::{key_gen, send, fund_account},
|
||||||
|
};
|
||||||
|
|
||||||
|
async fn setup_test() -> (
|
||||||
|
AnvilInstance,
|
||||||
|
Arc<RootProvider<SimpleRequest>>,
|
||||||
|
u64,
|
||||||
|
Router,
|
||||||
|
HashMap<Participant, ThresholdKeys<Secp256k1>>,
|
||||||
|
PublicKey,
|
||||||
|
) {
|
||||||
|
let anvil = Anvil::new().spawn();
|
||||||
|
|
||||||
|
let provider = RootProvider::new(
|
||||||
|
ClientBuilder::default().transport(SimpleRequest::new(anvil.endpoint()), true),
|
||||||
|
);
|
||||||
|
let chain_id = provider.get_chain_id().await.unwrap();
|
||||||
|
let wallet = anvil.keys()[0].clone().into();
|
||||||
|
let client = Arc::new(provider);
|
||||||
|
|
||||||
|
// Make sure the Deployer constructor returns None, as it doesn't exist yet
|
||||||
|
assert!(Deployer::new(client.clone()).await.unwrap().is_none());
|
||||||
|
|
||||||
|
// Deploy the Deployer
|
||||||
|
let tx = Deployer::deployment_tx();
|
||||||
|
fund_account(
|
||||||
|
&client,
|
||||||
|
&wallet,
|
||||||
|
tx.recover_signer().unwrap(),
|
||||||
|
U256::from(tx.tx().gas_limit) * U256::from(tx.tx().gas_price),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (tx, sig, _) = tx.into_parts();
|
||||||
|
let mut bytes = vec![];
|
||||||
|
tx.encode_with_signature_fields(&sig, &mut bytes);
|
||||||
|
|
||||||
|
let pending_tx = client.send_raw_transaction(&bytes).await.unwrap();
|
||||||
|
let receipt = pending_tx.get_receipt().await.unwrap();
|
||||||
|
assert!(receipt.status());
|
||||||
|
let deployer =
|
||||||
|
Deployer::new(client.clone()).await.expect("network error").expect("deployer wasn't deployed");
|
||||||
|
|
||||||
|
let (keys, public_key) = key_gen();
|
||||||
|
|
||||||
|
// Verify the Router constructor returns None, as it doesn't exist yet
|
||||||
|
assert!(deployer.find_router(client.clone(), &public_key).await.unwrap().is_none());
|
||||||
|
|
||||||
|
// Deploy the router
|
||||||
|
let receipt = send(&client, &anvil.keys()[0].clone().into(), deployer.deploy_router(&public_key))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(receipt.status());
|
||||||
|
let contract = deployer.find_router(client.clone(), &public_key).await.unwrap().unwrap();
|
||||||
|
|
||||||
|
(anvil, client, chain_id, contract, keys, public_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn latest_block_hash(client: &RootProvider<SimpleRequest>) -> [u8; 32] {
|
||||||
|
client
|
||||||
|
.get_block(client.get_block_number().await.unwrap().into(), BlockTransactionsKind::Hashes)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.header
|
||||||
|
.hash
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_deploy_contract() {
|
||||||
|
let (_anvil, client, _, router, _, public_key) = setup_test().await;
|
||||||
|
|
||||||
|
let block_hash = latest_block_hash(&client).await;
|
||||||
|
assert_eq!(router.serai_key(block_hash).await.unwrap(), public_key);
|
||||||
|
assert_eq!(router.nonce(block_hash).await.unwrap(), U256::try_from(1u64).unwrap());
|
||||||
|
// TODO: Check it emitted SeraiKeyUpdated(public_key) at its genesis
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_and_sign(
|
||||||
|
keys: &HashMap<Participant, ThresholdKeys<Secp256k1>>,
|
||||||
|
public_key: &PublicKey,
|
||||||
|
message: &[u8],
|
||||||
|
) -> Signature {
|
||||||
|
let algo = IetfSchnorr::<Secp256k1, EthereumHram>::ietf();
|
||||||
|
let sig =
|
||||||
|
sign(&mut OsRng, &algo, keys.clone(), algorithm_machines(&mut OsRng, &algo, keys), message);
|
||||||
|
|
||||||
|
Signature::new(public_key, message, sig).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_router_update_serai_key() {
|
||||||
|
let (anvil, client, chain_id, contract, keys, public_key) = setup_test().await;
|
||||||
|
|
||||||
|
let next_key = loop {
|
||||||
|
let point = ProjectivePoint::random(&mut OsRng);
|
||||||
|
let Some(next_key) = PublicKey::new(point) else { continue };
|
||||||
|
break next_key;
|
||||||
|
};
|
||||||
|
|
||||||
|
let message = Router::update_serai_key_message(
|
||||||
|
U256::try_from(chain_id).unwrap(),
|
||||||
|
U256::try_from(1u64).unwrap(),
|
||||||
|
&next_key,
|
||||||
|
);
|
||||||
|
let sig = hash_and_sign(&keys, &public_key, &message);
|
||||||
|
|
||||||
|
let first_block_hash = latest_block_hash(&client).await;
|
||||||
|
assert_eq!(contract.serai_key(first_block_hash).await.unwrap(), public_key);
|
||||||
|
|
||||||
|
let receipt =
|
||||||
|
send(&client, &anvil.keys()[0].clone().into(), contract.update_serai_key(&next_key, &sig))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(receipt.status());
|
||||||
|
|
||||||
|
let second_block_hash = latest_block_hash(&client).await;
|
||||||
|
assert_eq!(contract.serai_key(second_block_hash).await.unwrap(), next_key);
|
||||||
|
// Check this does still offer the historical state
|
||||||
|
assert_eq!(contract.serai_key(first_block_hash).await.unwrap(), public_key);
|
||||||
|
// TODO: Check logs
|
||||||
|
|
||||||
|
println!("gas used: {:?}", receipt.gas_used);
|
||||||
|
// println!("logs: {:?}", receipt.logs);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_router_execute() {
|
||||||
|
let (anvil, client, chain_id, contract, keys, public_key) = setup_test().await;
|
||||||
|
|
||||||
|
let to = Address::from([0; 20]);
|
||||||
|
let value = U256::ZERO;
|
||||||
|
let tx = router::OutInstruction { to, value, calls: vec![] };
|
||||||
|
let txs = vec![tx];
|
||||||
|
|
||||||
|
let first_block_hash = latest_block_hash(&client).await;
|
||||||
|
let nonce = contract.nonce(first_block_hash).await.unwrap();
|
||||||
|
assert_eq!(nonce, U256::try_from(1u64).unwrap());
|
||||||
|
|
||||||
|
let message = Router::execute_message(U256::try_from(chain_id).unwrap(), nonce, txs.clone());
|
||||||
|
let sig = hash_and_sign(&keys, &public_key, &message);
|
||||||
|
|
||||||
|
let receipt =
|
||||||
|
send(&client, &anvil.keys()[0].clone().into(), contract.execute(&txs, &sig)).await.unwrap();
|
||||||
|
assert!(receipt.status());
|
||||||
|
|
||||||
|
let second_block_hash = latest_block_hash(&client).await;
|
||||||
|
assert_eq!(contract.nonce(second_block_hash).await.unwrap(), U256::try_from(2u64).unwrap());
|
||||||
|
// Check this does still offer the historical state
|
||||||
|
assert_eq!(contract.nonce(first_block_hash).await.unwrap(), U256::try_from(1u64).unwrap());
|
||||||
|
// TODO: Check logs
|
||||||
|
|
||||||
|
println!("gas used: {:?}", receipt.gas_used);
|
||||||
|
// println!("logs: {:?}", receipt.logs);
|
||||||
|
}
|
||||||
93
coins/ethereum/src/tests/schnorr.rs
Normal file
93
coins/ethereum/src/tests/schnorr.rs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use rand_core::OsRng;
|
||||||
|
|
||||||
|
use group::ff::PrimeField;
|
||||||
|
use k256::Scalar;
|
||||||
|
|
||||||
|
use frost::{
|
||||||
|
curve::Secp256k1,
|
||||||
|
algorithm::IetfSchnorr,
|
||||||
|
tests::{algorithm_machines, sign},
|
||||||
|
};
|
||||||
|
|
||||||
|
use alloy_core::primitives::Address;
|
||||||
|
|
||||||
|
use alloy_sol_types::SolCall;
|
||||||
|
|
||||||
|
use alloy_rpc_types_eth::{TransactionInput, TransactionRequest};
|
||||||
|
use alloy_simple_request_transport::SimpleRequest;
|
||||||
|
use alloy_rpc_client::ClientBuilder;
|
||||||
|
use alloy_provider::{Provider, RootProvider};
|
||||||
|
|
||||||
|
use alloy_node_bindings::{Anvil, AnvilInstance};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Error,
|
||||||
|
crypto::*,
|
||||||
|
tests::{key_gen, deploy_contract, abi::schnorr as abi},
|
||||||
|
};
|
||||||
|
|
||||||
|
async fn setup_test() -> (AnvilInstance, Arc<RootProvider<SimpleRequest>>, Address) {
|
||||||
|
let anvil = Anvil::new().spawn();
|
||||||
|
|
||||||
|
let provider = RootProvider::new(
|
||||||
|
ClientBuilder::default().transport(SimpleRequest::new(anvil.endpoint()), true),
|
||||||
|
);
|
||||||
|
let wallet = anvil.keys()[0].clone().into();
|
||||||
|
let client = Arc::new(provider);
|
||||||
|
|
||||||
|
let address = deploy_contract(client.clone(), &wallet, "TestSchnorr").await.unwrap();
|
||||||
|
(anvil, client, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_deploy_contract() {
|
||||||
|
setup_test().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn call_verify(
|
||||||
|
provider: &RootProvider<SimpleRequest>,
|
||||||
|
contract: Address,
|
||||||
|
public_key: &PublicKey,
|
||||||
|
message: &[u8],
|
||||||
|
signature: &Signature,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let px: [u8; 32] = public_key.px.to_repr().into();
|
||||||
|
let c_bytes: [u8; 32] = signature.c.to_repr().into();
|
||||||
|
let s_bytes: [u8; 32] = signature.s.to_repr().into();
|
||||||
|
let call = TransactionRequest::default().to(contract).input(TransactionInput::new(
|
||||||
|
abi::verifyCall::new((px.into(), message.to_vec().into(), c_bytes.into(), s_bytes.into()))
|
||||||
|
.abi_encode()
|
||||||
|
.into(),
|
||||||
|
));
|
||||||
|
let bytes = provider.call(&call).await.map_err(|_| Error::ConnectionError)?;
|
||||||
|
let res =
|
||||||
|
abi::verifyCall::abi_decode_returns(&bytes, true).map_err(|_| Error::ConnectionError)?;
|
||||||
|
|
||||||
|
if res._0 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidSignature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_ecrecover_hack() {
|
||||||
|
let (_anvil, client, contract) = setup_test().await;
|
||||||
|
|
||||||
|
let (keys, public_key) = key_gen();
|
||||||
|
|
||||||
|
const MESSAGE: &[u8] = b"Hello, World!";
|
||||||
|
|
||||||
|
let algo = IetfSchnorr::<Secp256k1, EthereumHram>::ietf();
|
||||||
|
let sig =
|
||||||
|
sign(&mut OsRng, &algo, keys.clone(), algorithm_machines(&mut OsRng, &algo, &keys), MESSAGE);
|
||||||
|
let sig = Signature::new(&public_key, MESSAGE, sig).unwrap();
|
||||||
|
|
||||||
|
call_verify(&client, contract, &public_key, MESSAGE, &sig).await.unwrap();
|
||||||
|
// Test an invalid signature fails
|
||||||
|
let mut sig = sig;
|
||||||
|
sig.s += Scalar::ONE;
|
||||||
|
assert!(call_verify(&client, contract, &public_key, MESSAGE, &sig).await.is_err());
|
||||||
|
}
|
||||||
@@ -3,10 +3,10 @@ name = "monero-serai"
|
|||||||
version = "0.1.4-alpha"
|
version = "0.1.4-alpha"
|
||||||
description = "A modern Monero transaction library"
|
description = "A modern Monero transaction library"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/networks/monero"
|
repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.80"
|
rust-version = "1.79"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
@@ -32,11 +32,6 @@ monero-bulletproofs = { path = "ringct/bulletproofs", version = "0.1", default-f
|
|||||||
|
|
||||||
hex-literal = "0.4"
|
hex-literal = "0.4"
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
hex = { version = "0.4", default-features = false, features = ["std"] }
|
|
||||||
serde = { version = "1", default-features = false, features = ["std", "derive"] }
|
|
||||||
serde_json = { version = "1", default-features = false, features = ["std"] }
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = [
|
std = [
|
||||||
"std-shims/std",
|
"std-shims/std",
|
||||||
@@ -53,4 +48,5 @@ std = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
compile-time-generators = ["curve25519-dalek/precomputed-tables", "monero-bulletproofs/compile-time-generators"]
|
compile-time-generators = ["curve25519-dalek/precomputed-tables", "monero-bulletproofs/compile-time-generators"]
|
||||||
|
multisig = ["monero-clsag/multisig", "std"]
|
||||||
default = ["std", "compile-time-generators"]
|
default = ["std", "compile-time-generators"]
|
||||||
@@ -3,10 +3,9 @@ name = "monero-generators"
|
|||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
description = "Monero's hash to point function and generators"
|
description = "Monero's hash to point function and generators"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/networks/monero/generators"
|
repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/generators"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.80"
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
use std_shims::{sync::LazyLock, vec::Vec};
|
use std_shims::{sync::OnceLock, vec::Vec};
|
||||||
|
|
||||||
use sha3::{Digest, Keccak256};
|
use sha3::{Digest, Keccak256};
|
||||||
|
|
||||||
@@ -21,30 +21,33 @@ fn keccak256(data: &[u8]) -> [u8; 32] {
|
|||||||
Keccak256::digest(data).into()
|
Keccak256::digest(data).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static H_CELL: OnceLock<EdwardsPoint> = OnceLock::new();
|
||||||
/// Monero's `H` generator.
|
/// Monero's `H` generator.
|
||||||
///
|
///
|
||||||
/// Contrary to convention (`G` for values, `H` for randomness), `H` is used by Monero for amounts
|
/// Contrary to convention (`G` for values, `H` for randomness), `H` is used by Monero for amounts
|
||||||
/// within Pedersen commitments.
|
/// within Pedersen commitments.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub static H: LazyLock<EdwardsPoint> = LazyLock::new(|| {
|
pub fn H() -> EdwardsPoint {
|
||||||
decompress_point(keccak256(&ED25519_BASEPOINT_POINT.compress().to_bytes()))
|
*H_CELL.get_or_init(|| {
|
||||||
.unwrap()
|
decompress_point(keccak256(&ED25519_BASEPOINT_POINT.compress().to_bytes()))
|
||||||
.mul_by_cofactor()
|
.unwrap()
|
||||||
});
|
.mul_by_cofactor()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
static H_POW_2_CELL: LazyLock<[EdwardsPoint; 64]> = LazyLock::new(|| {
|
static H_POW_2_CELL: OnceLock<[EdwardsPoint; 64]> = OnceLock::new();
|
||||||
let mut res = [*H; 64];
|
|
||||||
for i in 1 .. 64 {
|
|
||||||
res[i] = res[i - 1] + res[i - 1];
|
|
||||||
}
|
|
||||||
res
|
|
||||||
});
|
|
||||||
/// Monero's `H` generator, multiplied by 2**i for i in 1 ..= 64.
|
/// Monero's `H` generator, multiplied by 2**i for i in 1 ..= 64.
|
||||||
///
|
///
|
||||||
/// This table is useful when working with amounts, which are u64s.
|
/// This table is useful when working with amounts, which are u64s.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn H_pow_2() -> &'static [EdwardsPoint; 64] {
|
pub fn H_pow_2() -> &'static [EdwardsPoint; 64] {
|
||||||
&H_POW_2_CELL
|
H_POW_2_CELL.get_or_init(|| {
|
||||||
|
let mut res = [H(); 64];
|
||||||
|
for i in 1 .. 64 {
|
||||||
|
res[i] = res[i - 1] + res[i - 1];
|
||||||
|
}
|
||||||
|
res
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The maximum amount of commitments provable for within a single range proof.
|
/// The maximum amount of commitments provable for within a single range proof.
|
||||||
@@ -71,7 +74,7 @@ pub fn bulletproofs_generators(dst: &'static [u8]) -> Generators {
|
|||||||
// The maximum amount of bits used within a single range proof.
|
// The maximum amount of bits used within a single range proof.
|
||||||
const MAX_MN: usize = MAX_COMMITMENTS * COMMITMENT_BITS;
|
const MAX_MN: usize = MAX_COMMITMENTS * COMMITMENT_BITS;
|
||||||
|
|
||||||
let mut preimage = H.compress().to_bytes().to_vec();
|
let mut preimage = H().compress().to_bytes().to_vec();
|
||||||
preimage.extend(dst);
|
preimage.extend(dst);
|
||||||
|
|
||||||
let mut res = Generators { G: Vec::with_capacity(MAX_MN), H: Vec::with_capacity(MAX_MN) };
|
let mut res = Generators { G: Vec::with_capacity(MAX_MN), H: Vec::with_capacity(MAX_MN) };
|
||||||
@@ -3,10 +3,9 @@ name = "monero-io"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Serialization functions, as within the Monero protocol"
|
description = "Serialization functions, as within the Monero protocol"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/networks/monero/io"
|
repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/io"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.80"
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
@@ -3,10 +3,10 @@ name = "monero-primitives"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Primitives for the Monero protocol"
|
description = "Primitives for the Monero protocol"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/networks/monero/primitives"
|
repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/primitives"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.80"
|
rust-version = "1.79"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
use std_shims::{io, vec::Vec};
|
use std_shims::{io, vec::Vec};
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std_shims::sync::LazyLock;
|
use std_shims::sync::OnceLock;
|
||||||
|
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
@@ -28,15 +28,15 @@ mod tests;
|
|||||||
|
|
||||||
// On std, we cache some variables in statics.
|
// On std, we cache some variables in statics.
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
static INV_EIGHT_CELL: LazyLock<Scalar> = LazyLock::new(|| Scalar::from(8u8).invert());
|
static INV_EIGHT_CELL: OnceLock<Scalar> = OnceLock::new();
|
||||||
/// The inverse of 8 over l, the prime factor of the order of Ed25519.
|
/// The inverse of 8 over l.
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn INV_EIGHT() -> Scalar {
|
pub fn INV_EIGHT() -> Scalar {
|
||||||
*INV_EIGHT_CELL
|
*INV_EIGHT_CELL.get_or_init(|| Scalar::from(8u8).invert())
|
||||||
}
|
}
|
||||||
// In no-std environments, we prefer the reduced memory use and calculate it ad-hoc.
|
// In no-std environments, we prefer the reduced memory use and calculate it ad-hoc.
|
||||||
/// The inverse of 8 over l, the prime factor of the order of Ed25519.
|
/// The inverse of 8 over l.
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn INV_EIGHT() -> Scalar {
|
pub fn INV_EIGHT() -> Scalar {
|
||||||
@@ -44,13 +44,12 @@ pub fn INV_EIGHT() -> Scalar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
static G_PRECOMP_CELL: LazyLock<VartimeEdwardsPrecomputation> =
|
static G_PRECOMP_CELL: OnceLock<VartimeEdwardsPrecomputation> = OnceLock::new();
|
||||||
LazyLock::new(|| VartimeEdwardsPrecomputation::new([ED25519_BASEPOINT_POINT]));
|
|
||||||
/// A cached (if std) pre-computation of the Ed25519 generator, G.
|
/// A cached (if std) pre-computation of the Ed25519 generator, G.
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn G_PRECOMP() -> &'static VartimeEdwardsPrecomputation {
|
pub fn G_PRECOMP() -> &'static VartimeEdwardsPrecomputation {
|
||||||
&G_PRECOMP_CELL
|
G_PRECOMP_CELL.get_or_init(|| VartimeEdwardsPrecomputation::new([ED25519_BASEPOINT_POINT]))
|
||||||
}
|
}
|
||||||
/// A cached (if std) pre-computation of the Ed25519 generator, G.
|
/// A cached (if std) pre-computation of the Ed25519 generator, G.
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
@@ -106,7 +105,7 @@ impl Commitment {
|
|||||||
|
|
||||||
/// Calculate the Pedersen commitment, as a point, from this transparent structure.
|
/// Calculate the Pedersen commitment, as a point, from this transparent structure.
|
||||||
pub fn calculate(&self) -> EdwardsPoint {
|
pub fn calculate(&self) -> EdwardsPoint {
|
||||||
EdwardsPoint::vartime_double_scalar_mul_basepoint(&Scalar::from(self.amount), &H, &self.mask)
|
EdwardsPoint::vartime_double_scalar_mul_basepoint(&Scalar::from(self.amount), &H(), &self.mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the Commitment.
|
/// Write the Commitment.
|
||||||
@@ -138,23 +137,13 @@ impl Commitment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Decoy data, as used for producing Monero's ring signatures.
|
/// Decoy data, as used for producing Monero's ring signatures.
|
||||||
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct Decoys {
|
pub struct Decoys {
|
||||||
offsets: Vec<u64>,
|
offsets: Vec<u64>,
|
||||||
signer_index: u8,
|
signer_index: u8,
|
||||||
ring: Vec<[EdwardsPoint; 2]>,
|
ring: Vec<[EdwardsPoint; 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Debug for Decoys {
|
|
||||||
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
|
|
||||||
fmt
|
|
||||||
.debug_struct("Decoys")
|
|
||||||
.field("offsets", &self.offsets)
|
|
||||||
.field("ring", &self.ring)
|
|
||||||
.finish_non_exhaustive()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::len_without_is_empty)]
|
#[allow(clippy::len_without_is_empty)]
|
||||||
impl Decoys {
|
impl Decoys {
|
||||||
/// Create a new instance of decoy data.
|
/// Create a new instance of decoy data.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use core::cmp::Ordering;
|
use core::cmp::Ordering;
|
||||||
use std_shims::{
|
use std_shims::{
|
||||||
sync::LazyLock,
|
sync::OnceLock,
|
||||||
io::{self, *},
|
io::{self, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -10,14 +10,18 @@ use curve25519_dalek::scalar::Scalar;
|
|||||||
|
|
||||||
use monero_io::*;
|
use monero_io::*;
|
||||||
|
|
||||||
|
static PRECOMPUTED_SCALARS_CELL: OnceLock<[Scalar; 8]> = OnceLock::new();
|
||||||
// Precomputed scalars used to recover an incorrectly reduced scalar.
|
// Precomputed scalars used to recover an incorrectly reduced scalar.
|
||||||
static PRECOMPUTED_SCALARS: LazyLock<[Scalar; 8]> = LazyLock::new(|| {
|
#[allow(non_snake_case)]
|
||||||
let mut precomputed_scalars = [Scalar::ONE; 8];
|
fn PRECOMPUTED_SCALARS() -> [Scalar; 8] {
|
||||||
for (i, scalar) in precomputed_scalars.iter_mut().enumerate().skip(1) {
|
*PRECOMPUTED_SCALARS_CELL.get_or_init(|| {
|
||||||
*scalar = Scalar::from(u8::try_from((i * 2) + 1).unwrap());
|
let mut precomputed_scalars = [Scalar::ONE; 8];
|
||||||
}
|
for (i, scalar) in precomputed_scalars.iter_mut().enumerate().skip(1) {
|
||||||
precomputed_scalars
|
*scalar = Scalar::from(u8::try_from((i * 2) + 1).unwrap());
|
||||||
});
|
}
|
||||||
|
precomputed_scalars
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// An unreduced scalar.
|
/// An unreduced scalar.
|
||||||
///
|
///
|
||||||
@@ -123,12 +127,14 @@ impl UnreducedScalar {
|
|||||||
return Scalar::from_bytes_mod_order(self.0);
|
return Scalar::from_bytes_mod_order(self.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let precomputed_scalars = PRECOMPUTED_SCALARS();
|
||||||
|
|
||||||
let mut recovered = Scalar::ZERO;
|
let mut recovered = Scalar::ZERO;
|
||||||
for &numb in self.non_adjacent_form().iter().rev() {
|
for &numb in self.non_adjacent_form().iter().rev() {
|
||||||
recovered += recovered;
|
recovered += recovered;
|
||||||
match numb.cmp(&0) {
|
match numb.cmp(&0) {
|
||||||
Ordering::Greater => recovered += PRECOMPUTED_SCALARS[usize::try_from(numb).unwrap() / 2],
|
Ordering::Greater => recovered += precomputed_scalars[usize::try_from(numb).unwrap() / 2],
|
||||||
Ordering::Less => recovered -= PRECOMPUTED_SCALARS[usize::try_from(-numb).unwrap() / 2],
|
Ordering::Less => recovered -= precomputed_scalars[usize::try_from(-numb).unwrap() / 2],
|
||||||
Ordering::Equal => (),
|
Ordering::Equal => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,10 +3,10 @@ name = "monero-borromean"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Borromean ring signatures arranged into a range proof, as done by the Monero protocol"
|
description = "Borromean ring signatures arranged into a range proof, as done by the Monero protocol"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/networks/monero/ringct/borromean"
|
repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/ringct/borromean"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.80"
|
rust-version = "1.79"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
@@ -3,10 +3,10 @@ name = "monero-bulletproofs"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Bulletproofs(+) range proofs, as defined by the Monero protocol"
|
description = "Bulletproofs(+) range proofs, as defined by the Monero protocol"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/networks/monero/ringct/bulletproofs"
|
repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/ringct/bulletproofs"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.80"
|
rust-version = "1.79"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
@@ -18,10 +18,11 @@ workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
std-shims = { path = "../../../../common/std-shims", version = "^0.1.1", default-features = false }
|
std-shims = { path = "../../../../common/std-shims", version = "^0.1.1", default-features = false }
|
||||||
|
|
||||||
thiserror = { version = "2", default-features = false }
|
thiserror = { version = "1", default-features = false, optional = true }
|
||||||
|
|
||||||
rand_core = { version = "0.6", default-features = false }
|
rand_core = { version = "0.6", default-features = false }
|
||||||
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
||||||
|
subtle = { version = "^2.4", default-features = false }
|
||||||
|
|
||||||
# Cryptographic dependencies
|
# Cryptographic dependencies
|
||||||
curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] }
|
curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] }
|
||||||
@@ -42,10 +43,11 @@ hex-literal = "0.4"
|
|||||||
std = [
|
std = [
|
||||||
"std-shims/std",
|
"std-shims/std",
|
||||||
|
|
||||||
"thiserror/std",
|
"thiserror",
|
||||||
|
|
||||||
"rand_core/std",
|
"rand_core/std",
|
||||||
"zeroize/std",
|
"zeroize/std",
|
||||||
|
"subtle/std",
|
||||||
|
|
||||||
"monero-io/std",
|
"monero-io/std",
|
||||||
"monero-generators/std",
|
"monero-generators/std",
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user