Compare commits

..

18 Commits

Author SHA1 Message Date
benedettadavico c45e8da43d Merge branch 'develop-with-release-1.1.0-merged-in' into feature/validator-api-tests 2022-11-09 10:15:54 +01:00
benedettadavico 12cc49a734 WIP 2022-11-09 10:05:47 +01:00
benedettadavico 7e56a9e88c WIP 2022-11-08 16:22:39 +01:00
benedettadavico 9790009eac WIP 2022-11-08 12:30:27 +01:00
benedettadavico 379d593daf Updating more tests 2022-11-07 18:37:31 +01:00
benedettadavico ce75b99b6f Merge branch 'release/v1.1.0' into feature/validator-api-tests 2022-11-07 17:36:21 +01:00
benedettadavico bcb7c41fd7 Updating validator api tests for v2 contracts 2022-11-07 17:31:25 +01:00
benedettadavico bb091ce47f Updating validator api tests for v2 contracts 2022-11-07 17:28:13 +01:00
benedettadavico effed4d7d6 Merge branch 'release/v1.1.0' into feature/validator-api-tests 2022-11-07 09:36:16 +01:00
benedettadavico d480ddb133 fixing failing tests 2022-08-15 15:20:23 +02:00
benedettadavico b119820591 Clean up 2022-08-15 09:25:28 +02:00
benedettadavico e128949dc2 Clean up 2022-08-13 20:40:08 +02:00
benedettadavico 9499b987e5 possible approach to validating address length and proxy type 2022-08-13 20:31:50 +02:00
benedettadavico d6ac786295 adding tests 2022-08-12 15:51:23 +02:00
tommy 4d09d9c3db remove 1-2-1 mapping 2022-08-12 13:30:27 +02:00
tommy 8c9044adf3 remove the need to map to type 2022-08-12 13:26:46 +02:00
tommy 472085ca52 Fix up look sharp
- added missing .git files
- fixed paths
- run the linter
2022-08-12 11:18:17 +02:00
benedettadavico 2f089e80ff adding onto the validator-api tests 2022-08-12 10:12:57 +02:00
278 changed files with 6941 additions and 7834 deletions
Vendored
BIN
View File
Binary file not shown.
+1 -4
View File
@@ -16,10 +16,7 @@ jobs:
- name: Install cargo deny
run: cargo install --locked cargo-deny
- name: Run cargo deny
run: |
find . -name Cargo.toml -exec cargo deny --manifest-path {} check \
advisories -A advisory-not-detected --hide-inclusion-graph \; &> \
>(uniq &> .github/workflows/support-files/notifications/deny.message )
run: cargo deny check advisories --hide-inclusion-graph &> .github/workflows/support-files/notifications/deny.message
- uses: actions/upload-artifact@v3
with:
name: report
+7 -9
View File
@@ -29,12 +29,6 @@ jobs:
override: true
components: rustfmt, clippy
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
@@ -54,6 +48,12 @@ jobs:
command: test
args: --workspace --all-features -- --ignored
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- uses: actions-rs/clippy-check@v1
name: Clippy checks
with:
@@ -66,8 +66,6 @@ jobs:
command: clippy
args: --workspace -- -D warnings
# COCONUT stuff
- name: Build all binaries with coconut enabled
uses: actions-rs/cargo@v1
with:
@@ -84,4 +82,4 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-targets --features=coconut -- -D warnings
args: --features=coconut -- -D warnings
+72
View File
@@ -0,0 +1,72 @@
name: Continuous integration on dispatch
on: workflow_dispatch
jobs:
build:
runs-on: [ self-hosted, custom-linux ]
# Enable sccache via environment variable
env:
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
- name: Check out repository code
uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --all-features
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- uses: actions-rs/clippy-check@v1
name: Clippy checks
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
- name: Run clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --workspace -- -D warnings
- name: Build all binaries with coconut enabled
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --features=coconut
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --features=coconut
- name: Run clippy with coconut enabled
uses: actions-rs/cargo@v1
with:
command: clippy
args: --features=coconut -- -D warnings
@@ -1,56 +0,0 @@
name: CI for Network Explorer API
on:
workflow_dispatch:
release:
types: [created]
env:
NETWORK: mainnet
jobs:
publish-nym:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
- name: Check the release tag starts with `nym-explorer-api-`
if: startsWith(github.ref, 'refs/tags/nym-explorer-api-') == false && github.event_name != 'workflow_dispatch'
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-explorer-api-...')
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Build all explorer-api
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path explorer-api/Cargo.toml --workspace --release
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: my-artifact
path: |
target/release/explorer-api
retention-days: 30
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with:
files: |
target/release/explorer-api
@@ -1,49 +1,32 @@
name: Nightly builds on latest release
name: Nightly builds on dispatch
on:
schedule:
- cron: '14 2 * * *'
on: workflow_dispatch
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from nightly_build_release_matrix.json
- uses: actions/checkout@v3
# creates the matrix strategy from nightly_build_matrix_includes.json
- uses: actions/checkout@v2
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/nightly_build_release_matrix.json'
inputFile: '.github/workflows/nightly_build_matrix_on_dispatch.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
get_release:
runs-on: ubuntu-latest
needs: matrix_prep
outputs:
output1: ${{ steps.step2.outputs.latest_release }}
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Fetch all branches
run: git fetch --all
- name: Set output variable to latest release branch
id: step2
run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+' | tail -n 1 | sed 's/ origin\///')" >> $GITHUB_OUTPUT
build:
needs: [get_release,matrix_prep]
needs: matrix_prep
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
if: matrix.os == 'ubuntu-latest'
- name: Check out latest release branch
uses: actions/checkout@v3
with:
ref: ${{needs.get_release.outputs.output1}}
- name: Check out repository code
uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
@@ -59,12 +42,6 @@ jobs:
command: build
args: --workspace
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests
uses: actions-rs/cargo@v1
with:
@@ -122,12 +99,6 @@ jobs:
command: build
args: --workspace --features=coconut
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
@@ -180,7 +151,7 @@ jobs:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
- name: Check out repository code
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Keybase - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
@@ -189,14 +160,14 @@ jobs:
if: env.WORKFLOW_CONCLUSION == 'failure'
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Nym nightly build on latest release"
NYM_PROJECT_NAME: "Nym nightly build"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-nightly-release"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMTECH_TEAM }}"
KEYBASE_NYM_CHANNEL: "${{ secrets.KEYBASE_CHANNEL_DEV_CORE_ID }}"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
@@ -25,9 +25,6 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
- name: Check the release tag starts with `nym-binaries-`
if: startsWith(github.ref, 'refs/tags/nym-binaries-') == false && github.event_name != 'workflow_dispatch'
uses: actions/github-script@v3
+32
View File
@@ -0,0 +1,32 @@
name: Tests for validator API
on:
push:
paths:
- "validator-api/tests/**"
defaults:
run:
working-directory: validator-api/tests
jobs:
test:
name: validator api tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Node v18
uses: actions/setup-node@v3
with:
node-version: 18.1.0
- name: Install yarn
run: yarn install
- name: Run yarn
run: yarn
- name: Launch tests
run: yarn test
working-directory: validator-api/tests
+19 -35
View File
@@ -6,52 +6,35 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
### Added
- binaries: add `-c` shortform for `--config-env-file`
- websocket-requests: add server response signalling current packet queue length in the client
### Changed
- clients: add concept of transmission lanes to better handle multiple data streams ([#1720])
- socks5-client: wait closing inbound connection until data is sent, and throttle incoming data in general ([#1783])
[#1720]: https://github.com/nymtech/nym/pull/1720
[#1783]: https://github.com/nymtech/nym/pull/1783
## [v1.1.0](https://github.com/nymtech/nym/tree/v1.1.0) (2022-11-09)
### Added
- clients: add testing-only support for two more extended packet sizes (8kb and 16kb).
- common/ledger: new library for communicating with a Ledger device ([#1640])
- native-client/socks5-client/wasm-client: `disable_loop_cover_traffic_stream` Debug config option to disable the separate loop cover traffic stream ([#1666])
- native-client/socks5-client/wasm-client: `disable_main_poisson_packet_distribution` Debug config option to make the client ignore poisson distribution in the main packet stream and ONLY send real message (and as fast as they come) ([#1664])
- native-client/socks5-client/wasm-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671])
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611])
- validator-api: add `interval_operating_cost` and `profit_margin_percent` to compute reward estimation endpoint
- nym-cli: added CLI tool for interacting with the Nyx blockchain and Nym mixnet smart contracts ([#1577])
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611](https://github.com/nymtech/nym/pull/1611))
- common/ledger: new library for communicating with a Ledger device ([#1640])
- native-client/socks5-client: `disable_loop_cover_traffic_stream` Debug config option to disable the separate loop cover traffic stream ([#1666])
- native-client/socks5-client: `disable_main_poisson_packet_distribution` Debug config option to make the client ignore poisson distribution in the main packet stream and ONLY send real message (and as fast as they come) ([#1664])
- native-client/socks5-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671])
- wasm-client: uses updated wasm-compatible `client-core` so that it's now capable of packet retransmission, cover traffic and poisson delay (among other things!) ([#1673])
- validator-api: add `interval_operating_cost` and `profit_margin_percent` to cmpute reward estimation endpoint
- native-client/socks5-client/network-requester: improve handling error cases ([#1713])
- vesting-contract: optional locked token pledge cap per account ([#1687]), defaults to 100_000 NYM
- clients: add testing-only support for two more extended packet sizes (8kb and 16kb).
### Fixed
- validator-api, mixnode, gateway should now prefer values in config.toml over mainnet defaults ([#1645])
- validator-api should now correctly update historical uptimes for all mixnodes and gateways every 24h ([#1721])
- socks5-client: fix bug where in some cases packet reordering could trigger a connection being closed too early ([#1702],[#1724])
- validator-api: mixnode, gateway should now prefer values in config.toml over mainnet defaults ([#1645])
- validator-api: should now correctly update historical uptimes for all mixnodes and gateways every 24h ([#1721])
### Changed
- clients: bound the sphinx packet channel and reduce sending rate if gateway can't keep up ([#1703],[#1725])
- gateway-client: will attempt to read now as many as 8 websocket messages at once, assuming they're already available on the socket ([#1669])
- moved `Percent` struct to `contracts-common`, change affects explorer-api
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591])
- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681])
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591])
- wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585])
- validator-api: changes to internal SQL schema due to the mixnet contract revamp ([#1472])
- validator-api: changes to internal data structures due to the mixnet contract revamp ([#1472])
- validator-api: split epoch-operations into multiple separate transactions ([#1472])
- gateway-client: will attempt to read now as many as 8 websocket messages at once, assuming they're already available on the socket ([#1669])
- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681])
- moved `Percent` struct to to `contracts-common`, change affects explorer-api
- clients: bound the sphinx packet channel and reduce sending rate if gateway can't keep up ([#1703],[#1725])
[#1472]: https://github.com/nymtech/nym/pull/1472
[#1541]: https://github.com/nymtech/nym/pull/1541
[#1558]: https://github.com/nymtech/nym/pull/1558
[#1577]: https://github.com/nymtech/nym/pull/1577
@@ -59,15 +42,16 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1591]: https://github.com/nymtech/nym/pull/1591
[#1640]: https://github.com/nymtech/nym/pull/1640
[#1645]: https://github.com/nymtech/nym/pull/1645
[#1611]: https://github.com/nymtech/nym/pull/1611
[#1664]: https://github.com/nymtech/nym/pull/1664
[#1666]: https://github.com/nymtech/nym/pull/1645
[#1669]: https://github.com/nymtech/nym/pull/1669
[#1671]: https://github.com/nymtech/nym/pull/1671
[#1673]: https://github.com/nymtech/nym/pull/1673
[#1681]: https://github.com/nymtech/nym/pull/1681
[#1687]: https://github.com/nymtech/nym/pull/1687
[#1702]: https://github.com/nymtech/nym/pull/1702
[#1703]: https://github.com/nymtech/nym/pull/1703
[#1713]: https://github.com/nymtech/nym/pull/1713
[#1721]: https://github.com/nymtech/nym/pull/1721
[#1724]: https://github.com/nymtech/nym/pull/1724
[#1725]: https://github.com/nymtech/nym/pull/1725
Generated
+17 -51
View File
@@ -576,19 +576,10 @@ dependencies = [
"os_str_bytes",
]
[[package]]
name = "client-connections"
version = "0.1.0"
dependencies = [
"futures",
"log",
]
[[package]]
name = "client-core"
version = "1.1.0"
version = "1.0.1"
dependencies = [
"client-connections",
"config",
"crypto",
"dirs",
@@ -636,16 +627,6 @@ dependencies = [
"serde",
]
[[package]]
name = "coconut-dkg-common"
version = "0.1.0"
dependencies = [
"contracts-common",
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
name = "coconut-interface"
version = "0.1.0"
@@ -755,9 +736,7 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
name = "contracts-common"
version = "0.1.0"
dependencies = [
"bs58",
"cosmwasm-std",
"dkg",
"schemars",
"serde",
"serde_json",
@@ -871,9 +850,9 @@ dependencies = [
[[package]]
name = "cpufeatures"
version = "0.2.5"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
dependencies = [
"libc",
]
@@ -1423,7 +1402,6 @@ dependencies = [
"ff 0.11.0",
"group 0.11.0",
"lazy_static",
"pemstore",
"rand 0.8.5",
"rand_chacha 0.3.1",
"rand_core 0.6.3",
@@ -1475,9 +1453,9 @@ dependencies = [
[[package]]
name = "ed25519"
version = "1.5.2"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369"
checksum = "3d5c4b5e5959dc2c2b89918d8e2cc40fcdd623cef026ed09d2f0ee05199dc8e4"
dependencies = [
"serde",
"signature",
@@ -1604,7 +1582,7 @@ dependencies = [
[[package]]
name = "explorer-api"
version = "1.1.0"
version = "1.0.1"
dependencies = [
"chrono",
"clap 3.2.8",
@@ -3092,7 +3070,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.0"
version = "1.0.0"
dependencies = [
"anyhow",
"base64",
@@ -3144,10 +3122,9 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.0"
version = "1.0.2"
dependencies = [
"clap 3.2.8",
"client-connections",
"client-core",
"coconut-interface",
"completions",
@@ -3169,7 +3146,6 @@ dependencies = [
"serde",
"serde_json",
"sled",
"tap",
"task",
"thiserror",
"tokio",
@@ -3184,7 +3160,7 @@ dependencies = [
[[package]]
name = "nym-gateway"
version = "1.1.0"
version = "1.0.2"
dependencies = [
"anyhow",
"async-trait",
@@ -3231,7 +3207,7 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "1.1.0"
version = "1.0.2"
dependencies = [
"anyhow",
"bs58",
@@ -3273,11 +3249,10 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.0"
version = "1.0.2"
dependencies = [
"async-trait",
"clap 3.2.8",
"client-connections",
"completions",
"dirs",
"futures",
@@ -3321,10 +3296,9 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.0"
version = "1.0.2"
dependencies = [
"clap 3.2.8",
"client-connections",
"client-core",
"coconut-interface",
"completions",
@@ -3349,7 +3323,6 @@ dependencies = [
"serde",
"snafu 0.6.10",
"socks5-requests",
"tap",
"task",
"thiserror",
"tokio",
@@ -3388,15 +3361,13 @@ dependencies = [
[[package]]
name = "nym-validator-api"
version = "1.1.0"
version = "1.0.2"
dependencies = [
"anyhow",
"async-trait",
"bs58",
"cfg-if 1.0.0",
"clap 3.2.8",
"coconut-bandwidth-contract-common",
"coconut-dkg-common",
"coconut-interface",
"config",
"console-subscriber",
@@ -3408,7 +3379,6 @@ dependencies = [
"cw-utils",
"cw3",
"dirs",
"dkg",
"dotenv",
"futures",
"gateway-client",
@@ -3422,7 +3392,6 @@ dependencies = [
"nymcoconut",
"nymsphinx",
"okapi",
"pemstore",
"pin-project",
"pretty_env_logger",
"rand 0.7.3",
@@ -3475,18 +3444,16 @@ name = "nymcoconut"
version = "0.5.0"
dependencies = [
"bincode",
"bls12_381 0.6.0",
"bls12_381 0.5.0",
"bs58",
"criterion",
"digest 0.9.0",
"dkg",
"doc-comment",
"ff 0.11.0",
"ff 0.10.1",
"getrandom 0.2.6",
"group 0.11.0",
"group 0.10.0",
"itertools",
"rand 0.8.5",
"rand_chacha 0.3.1",
"serde",
"serde_derive",
"sha2 0.9.9",
@@ -4156,7 +4123,6 @@ name = "proxy-helpers"
version = "0.1.0"
dependencies = [
"bytes",
"client-connections",
"futures",
"log",
"ordered-buffer",
@@ -6441,7 +6407,6 @@ dependencies = [
"base64",
"bip39",
"coconut-bandwidth-contract-common",
"coconut-dkg-common",
"coconut-interface",
"colored",
"config",
@@ -6551,6 +6516,7 @@ version = "0.1.0"
dependencies = [
"contracts-common",
"cosmwasm-std",
"log",
"mixnet-contract-common",
"schemars",
"serde",
-2
View File
@@ -26,12 +26,10 @@ members = [
"common/client-libs/gateway-client",
"common/client-libs/mixnet-client",
"common/client-libs/validator-client",
"common/client-connections",
"common/coconut-interface",
"common/commands",
"common/config",
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
"common/cosmwasm-smart-contracts/coconut-dkg",
"common/cosmwasm-smart-contracts/contracts-common",
"common/cosmwasm-smart-contracts/mixnet-contract",
"common/cosmwasm-smart-contracts/multisig-contract",
+3 -3
View File
@@ -92,9 +92,6 @@ build-wallet:
build-connect:
cargo build --manifest-path nym-connect/Cargo.toml --workspace
build-explorer-api:
cargo build --manifest-path explorer-api/Cargo.toml --workspace
build-wasm-client:
cargo build --manifest-path clients/webassembly/Cargo.toml --workspace --target wasm32-unknown-unknown
@@ -125,3 +122,6 @@ mixnet-opt: wasm
generate-typescript:
cd tools/ts-rs-cli && cargo run && cd ../..
yarn types:lint:fix
run-validator-tests:
cd validator-api/tests/functional_test && yarn test
+3 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "client-core"
version = "1.1.0"
version = "1.0.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
@@ -14,13 +14,11 @@ log = "0.4"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] }
sled = { version = "0.34", optional = true }
tap = "1.0.1"
thiserror = "1.0.34"
url = { version ="2.2", features = ["serde"] }
# internal
config = { path = "../../common/config" }
client-connections = { path = "../../common/client-connections" }
crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
#gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] }
@@ -30,6 +28,7 @@ nymsphinx = { path = "../../common/nymsphinx" }
pemstore = { path = "../../common/pemstore" }
topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
tap = "1.0.1"
tokio = { version = "1.21.2", features = ["time", "macros"]}
@@ -57,5 +56,4 @@ tempfile = "3.1.0"
default = ["reply-surb"]
wasm = ["gateway-client/wasm"]
coconut = ["gateway-client/coconut", "gateway-requests/coconut"]
reply-surb = ["sled"]
reply-surb = ["sled"]
@@ -178,10 +178,6 @@ impl LoopCoverTrafficStream<OsRng> {
// This isn't a problem, if the channel is full means we're already sending the
// max amount of messages downstream can handle.
log::debug!("Failed to send cover message - channel full");
// However it's still useful to alert the user that the gateway or the link to
// the gateway can't keep up. Either due to insufficient bandwidth on the
// client side, or that the gateway is overloaded.
log::warn!("Failed to send: gateway appears to not keep up");
}
TrySendError::Closed(_) => {
log::warn!("Failed to send cover message - channel closed");
@@ -1,9 +1,9 @@
use client_connections::TransmissionLane;
use futures::channel::mpsc;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::anonymous_replies::ReplySurb;
pub type InputMessageSender = tokio::sync::mpsc::Sender<InputMessage>;
pub type InputMessageReceiver = tokio::sync::mpsc::Receiver<InputMessage>;
pub type InputMessageSender = mpsc::UnboundedSender<InputMessage>;
pub type InputMessageReceiver = mpsc::UnboundedReceiver<InputMessage>;
#[derive(Debug)]
pub enum InputMessage {
@@ -11,7 +11,6 @@ pub enum InputMessage {
recipient: Recipient,
data: Vec<u8>,
with_reply_surb: bool,
lane: TransmissionLane,
},
Reply {
reply_surb: ReplySurb,
@@ -20,17 +19,11 @@ pub enum InputMessage {
}
impl InputMessage {
pub fn new_fresh(
recipient: Recipient,
data: Vec<u8>,
with_reply_surb: bool,
lane: TransmissionLane,
) -> Self {
pub fn new_fresh(recipient: Recipient, data: Vec<u8>, with_reply_surb: bool) -> Self {
InputMessage::Fresh {
recipient,
data,
with_reply_surb,
lane,
}
}
@@ -33,7 +33,7 @@ impl AcknowledgementListener {
}
async fn on_ack(&mut self, ack_content: Vec<u8>) {
trace!("Received an ack");
debug!("Received an ack");
let frag_id = match recover_identifier(&self.ack_key, &ack_content)
.map(FragmentIdentifier::try_from_bytes)
{
@@ -8,7 +8,7 @@ use crate::client::{
real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage},
topology_control::TopologyAccessor,
};
use client_connections::TransmissionLane;
use futures::StreamExt;
use log::*;
use nymsphinx::anonymous_replies::ReplySurb;
use nymsphinx::preparer::MessagePreparer;
@@ -104,7 +104,6 @@ where
content: Vec<u8>,
with_reply_surb: bool,
) -> Option<Vec<RealMessage>> {
log::trace!("handling msg size: {}", content.len());
let topology_permit = self.topology_access.get_read_permit().await;
let topology = match topology_permit
.try_get_valid_topology_ref(&self.ack_recipient, Some(&recipient))
@@ -165,36 +164,27 @@ where
}
async fn on_input_message(&mut self, msg: InputMessage) {
let (real_messages, lane) = match msg {
let real_messages = match msg {
InputMessage::Fresh {
recipient,
data,
with_reply_surb,
lane,
} => (
} => {
self.handle_fresh_message(recipient, data, with_reply_surb)
.await,
lane,
),
InputMessage::Reply { reply_surb, data } => (
self.handle_reply(reply_surb, data)
.await
.map(|message| vec![message]),
TransmissionLane::Reply,
),
}
InputMessage::Reply { reply_surb, data } => self
.handle_reply(reply_surb, data)
.await
.map(|message| vec![message]),
};
// there's no point in trying to send nothing
if let Some(real_messages) = real_messages {
// tells real message sender (with the poisson timer) to send this to the mix network
if self
.real_message_sender
.send((real_messages, lane))
.await
.is_err()
{
panic!();
}
self.real_message_sender
.unbounded_send(real_messages)
.unwrap();
}
}
@@ -204,7 +194,7 @@ where
while !shutdown.is_shutdown() {
tokio::select! {
input_msg = self.input_receiver.recv() => match input_msg {
input_msg = self.input_receiver.next() => match input_msg {
Some(input_msg) => {
self.on_input_message(input_msg).await;
},
@@ -225,7 +215,7 @@ where
#[cfg(target_arch = "wasm32")]
pub(super) async fn run(&mut self) {
debug!("Started InputMessageListener without graceful shutdown support");
while let Some(input_msg) = self.input_receiver.recv().await {
while let Some(input_msg) = self.input_receiver.next().await {
self.on_input_message(input_msg).await;
}
}
@@ -1,21 +1,17 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::{
action_controller::{Action, ActionSender},
PendingAcknowledgement, RetransmissionRequestReceiver,
};
use super::action_controller::{Action, ActionSender};
use super::PendingAcknowledgement;
use super::RetransmissionRequestReceiver;
use crate::client::{
real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage},
topology_control::TopologyAccessor,
};
use client_connections::TransmissionLane;
use futures::StreamExt;
use log::*;
use nymsphinx::{
acknowledgements::AckKey, addressing::clients::Recipient, preparer::MessagePreparer,
};
use nymsphinx::preparer::MessagePreparer;
use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient};
use rand::{CryptoRng, Rng};
use std::sync::{Arc, Weak};
@@ -116,17 +112,12 @@ where
.unwrap();
// send to `OutQueueControl` to eventually send to the mix network
if self
.real_message_sender
.send((
vec![RealMessage::new(prepared_fragment.mix_packet, frag_id)],
TransmissionLane::Retransmission,
))
.await
.is_err()
{
panic!();
}
self.real_message_sender
.unbounded_send(vec![RealMessage::new(
prepared_fragment.mix_packet,
frag_id,
)])
.unwrap();
}
#[cfg(not(target_arch = "wasm32"))]
@@ -8,15 +8,12 @@
use self::{
acknowledgement_control::AcknowledgementController, real_traffic_stream::OutQueueControl,
};
use crate::{
client::{
inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender,
real_messages_control::acknowledgement_control::AcknowledgementControllerConnectors,
topology_control::TopologyAccessor,
},
spawn_future,
use crate::client::real_messages_control::acknowledgement_control::AcknowledgementControllerConnectors;
use crate::client::{
inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender,
topology_control::TopologyAccessor,
};
use client_connections::{ClosedConnectionReceiver, LaneQueueLengths};
use crate::spawn_future;
use futures::channel::mpsc;
use gateway_client::AcknowledgementReceiver;
use log::*;
@@ -106,7 +103,6 @@ where
// obviously when we finally make shared rng that is on 'higher' level, this should become
// generic `R`
impl RealMessagesController<OsRng> {
#[allow(clippy::too_many_arguments)]
pub fn new(
config: Config,
ack_receiver: AcknowledgementReceiver,
@@ -114,12 +110,10 @@ impl RealMessagesController<OsRng> {
mix_sender: BatchMixMessageSender,
topology_access: TopologyAccessor,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
lane_queue_lengths: LaneQueueLengths,
closed_connection_rx: ClosedConnectionReceiver,
) -> Self {
let rng = OsRng;
let (real_message_sender, real_message_receiver) = tokio::sync::mpsc::channel(1);
let (real_message_sender, real_message_receiver) = mpsc::unbounded();
let (sent_notifier_tx, sent_notifier_rx) = mpsc::unbounded();
let ack_controller_connectors = AcknowledgementControllerConnectors::new(
@@ -165,8 +159,6 @@ impl RealMessagesController<OsRng> {
rng,
config.self_recipient,
topology_access,
lane_queue_lengths,
closed_connection_rx,
);
RealMessagesController {
@@ -4,9 +4,7 @@
use crate::client::mix_traffic::BatchMixMessageSender;
use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender;
use crate::client::topology_control::TopologyAccessor;
use client_connections::{
ClosedConnectionReceiver, ConnectionCommand, ConnectionId, LaneQueueLengths, TransmissionLane,
};
use futures::channel::mpsc;
use futures::task::{Context, Poll};
use futures::{Future, Stream, StreamExt};
use log::*;
@@ -18,6 +16,7 @@ use nymsphinx::forwarding::packet::MixPacket;
use nymsphinx::params::PacketSize;
use nymsphinx::utils::sample_poisson_duration;
use rand::{CryptoRng, Rng};
use std::collections::VecDeque;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
@@ -28,22 +27,22 @@ use tokio::time;
#[cfg(target_arch = "wasm32")]
use wasm_timer;
use self::{
sending_delay_controller::SendingDelayController, transmission_buffer::TransmissionBuffer,
};
mod sending_delay_controller;
mod transmission_buffer;
#[cfg(not(target_arch = "wasm32"))]
fn get_time_now() -> time::Instant {
time::Instant::now()
}
#[cfg(target_arch = "wasm32")]
fn get_time_now() -> wasm_timer::Instant {
wasm_timer::Instant::now()
}
// The minimum time between increasing the average delay between packets. If we hit the ceiling in
// the available buffer space we want to take somewhat swift action, but we still need to give a
// short time to give the channel a chance reduce pressure.
const INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 1;
// The minimum time between decreasing the average delay between packets. We don't want to change
// to quickly to keep things somewhat stable. Also there are buffers downstreams meaning we need to
// wait a little to see the effect before we decrease further.
const DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 30;
// If we enough time passes without any sign of backpressure in the channel, we can consider
// lowering the average delay. The goal is to keep somewhat stable, rather than maxing out
// bandwidth at all times.
const ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS: u64 = 30;
// The maximum multiplier we apply to the base average Poisson delay.
const MAX_DELAY_MULTIPLIER: u32 = 6;
// The minium multiplier we apply to the base average Poisson delay.
const MIN_DELAY_MULTIPLIER: u32 = 1;
/// Configurable parameters of the `OutQueueControl`
pub(crate) struct Config {
@@ -86,6 +85,101 @@ impl Config {
}
}
struct SendingDelayController {
/// Multiply the average sending delay.
/// This is normally set to unity, but if we detect backpressure we increase this
/// multiplier. We use discrete steps.
current_multiplier: u32,
/// Maximum delay multiplier
upper_bound: u32,
/// Minimum delay multiplier
lower_bound: u32,
/// To make sure we don't change the multiplier to fast, we limit a change to some duration
#[cfg(not(target_arch = "wasm32"))]
time_when_changed: time::Instant,
#[cfg(target_arch = "wasm32")]
time_when_changed: wasm_timer::Instant,
/// If we have a long enough time without any backpressure detected we try reducing the sending
/// delay multiplier
#[cfg(not(target_arch = "wasm32"))]
time_when_backpressure_detected: time::Instant,
#[cfg(target_arch = "wasm32")]
time_when_backpressure_detected: wasm_timer::Instant,
}
#[cfg(not(target_arch = "wasm32"))]
fn get_time_now() -> time::Instant {
time::Instant::now()
}
#[cfg(target_arch = "wasm32")]
fn get_time_now() -> wasm_timer::Instant {
wasm_timer::Instant::now()
}
impl SendingDelayController {
fn new(lower_bound: u32, upper_bound: u32) -> Self {
assert!(lower_bound <= upper_bound);
let now = get_time_now();
SendingDelayController {
current_multiplier: MIN_DELAY_MULTIPLIER,
upper_bound,
lower_bound,
time_when_changed: now,
time_when_backpressure_detected: now,
}
}
fn current_multiplier(&self) -> u32 {
self.current_multiplier
}
fn increase_delay_multiplier(&mut self) {
self.current_multiplier =
(self.current_multiplier + 1).clamp(self.lower_bound, self.upper_bound);
self.time_when_changed = get_time_now();
log::debug!(
"Increasing sending delay multiplier to: {}",
self.current_multiplier
);
}
fn decrease_delay_multiplier(&mut self) {
self.current_multiplier =
(self.current_multiplier - 1).clamp(self.lower_bound, self.upper_bound);
self.time_when_changed = get_time_now();
log::debug!(
"Decreasing sending delay multiplier to: {}",
self.current_multiplier
);
}
fn record_backpressure_detected(&mut self) {
self.time_when_backpressure_detected = get_time_now();
}
fn not_increased_delay_recently(&self) -> bool {
get_time_now()
> self.time_when_changed + Duration::from_secs(INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS)
}
fn is_sending_reliable(&self) -> bool {
let now = get_time_now();
let delay_change_interval = Duration::from_secs(DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS);
let acceptable_time_without_backpressure =
Duration::from_secs(ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS);
now > self.time_when_backpressure_detected + acceptable_time_without_backpressure
&& now > self.time_when_changed + delay_change_interval
}
}
pub(crate) struct OutQueueControl<R>
where
R: CryptoRng + Rng,
@@ -109,7 +203,7 @@ where
// To make sure we don't overload the mix_tx channel, we limit the rate we are pushing
// messages.
sending_delay_controller: SendingDelayController,
sending_rate_controller: SendingDelayController,
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// out to the network without any further delays.
@@ -128,16 +222,8 @@ where
/// Accessor to the common instance of network topology.
topology_access: TopologyAccessor,
/// Buffer containing all incoming real messages keyed by transmission lane, that we will send
/// out to the mixnet.
transmission_buffer: TransmissionBuffer,
/// Incoming channel for being notified of closed connections, so that we can close lanes
/// corresponding to connections. To avoid sending traffic unnecessary
closed_connection_rx: ClosedConnectionReceiver,
/// Report queue lengths so that upstream can backoff sending data, and keep connections open.
lane_queue_lengths: LaneQueueLengths,
/// Buffer containing all real messages received. It is first exhausted before more are pulled.
received_buffer: VecDeque<RealMessage>,
}
pub(crate) struct RealMessage {
@@ -156,9 +242,8 @@ impl RealMessage {
// messages are already prepared, etc. the real point of it is to forward it to mix_traffic
// after sufficient delay
pub(crate) type BatchRealMessageSender =
tokio::sync::mpsc::Sender<(Vec<RealMessage>, TransmissionLane)>;
type BatchRealMessageReceiver = tokio::sync::mpsc::Receiver<(Vec<RealMessage>, TransmissionLane)>;
pub(crate) type BatchRealMessageSender = mpsc::UnboundedSender<Vec<RealMessage>>;
type BatchRealMessageReceiver = mpsc::UnboundedReceiver<Vec<RealMessage>>;
pub(crate) enum StreamMessage {
Cover,
@@ -181,23 +266,22 @@ where
rng: R,
our_full_destination: Recipient,
topology_access: TopologyAccessor,
lane_queue_lengths: LaneQueueLengths,
closed_connection_rx: ClosedConnectionReceiver,
) -> Self {
OutQueueControl {
config,
ack_key,
sent_notifier,
next_delay: None,
sending_delay_controller: Default::default(),
sending_rate_controller: SendingDelayController::new(
MIN_DELAY_MULTIPLIER,
MAX_DELAY_MULTIPLIER,
),
mix_tx,
real_receiver,
our_full_destination,
rng,
topology_access,
transmission_buffer: Default::default(),
closed_connection_rx,
lane_queue_lengths,
received_buffer: VecDeque::with_capacity(0), // we won't be putting any data into this guy directly
}
}
@@ -262,10 +346,6 @@ where
self.sent_notify(fragment_id);
}
// In addition to closing connections on receiving messages throught closed_connection_rx,
// also close connections when sufficiently stale.
self.transmission_buffer.prune_stale_connections();
// JS: Not entirely sure why or how it fixes stuff, but without the yield call,
// the UnboundedReceiver [of mix_rx] will not get a chance to read anything
// JS2: Basically it was the case that with high enough rate, the stream had already a next value
@@ -277,71 +357,44 @@ where
tokio::task::yield_now().await;
}
fn on_close_connection(&mut self, connection_id: ConnectionId) {
log::debug!("Removing lane for connection: {connection_id}");
self.transmission_buffer
.remove(&TransmissionLane::ConnectionId(connection_id));
}
fn current_average_message_sending_delay(&self) -> Duration {
self.config.average_message_sending_delay
* self.sending_delay_controller.current_multiplier()
* self.sending_rate_controller.current_multiplier()
}
fn adjust_current_average_message_sending_delay(&mut self) {
let used_slots = self.mix_tx.max_capacity() - self.mix_tx.capacity();
log::trace!(
"used_slots: {used_slots}, current_multiplier: {}",
self.sending_delay_controller.current_multiplier()
self.sending_rate_controller.current_multiplier()
);
// Even just a single used slot is enough to signal backpressure
if used_slots > 0 {
log::trace!("Backpressure detected");
self.sending_delay_controller.record_backpressure_detected();
self.sending_rate_controller.record_backpressure_detected();
}
// If the buffer is running out, slow down the sending rate
if self.mix_tx.capacity() == 0
&& self.sending_delay_controller.not_increased_delay_recently()
&& self.sending_rate_controller.not_increased_delay_recently()
{
self.sending_delay_controller.increase_delay_multiplier();
self.sending_rate_controller.increase_delay_multiplier();
}
// Very carefully step up the sending rate in case it seems like we can solidly handle the
// current rate.
if self.sending_delay_controller.is_sending_reliable() {
self.sending_delay_controller.decrease_delay_multiplier();
if self.sending_rate_controller.is_sending_reliable() {
self.sending_rate_controller.decrease_delay_multiplier();
}
}
fn pop_next_message(&mut self) -> Option<RealMessage> {
// Pop the next message from the transmission buffer
let (lane, real_next) = self.transmission_buffer.pop_next_message_at_random()?;
// Update the published queue length
let lane_length = self.transmission_buffer.lane_length(&lane);
self.lane_queue_lengths.set(&lane, lane_length);
Some(real_next)
}
fn poll_poisson(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> {
// The average delay could change depending on if backpressure in the downstream channel
// (mix_tx) was detected.
self.adjust_current_average_message_sending_delay();
let avg_delay = self.current_average_message_sending_delay();
// Start by checking if we have any incoming messages about closed connections
// NOTE: this feels a bit iffy, the `OutQueueControl` is getting ripe for a rewrite to
// something simpler.
if let Poll::Ready(Some(id)) = Pin::new(&mut self.closed_connection_rx).poll_next(cx) {
match id {
ConnectionCommand::Close(id) => self.on_close_connection(id),
ConnectionCommand::ActiveConnections(_) => panic!(),
}
}
if let Some(ref mut next_delay) = &mut self.next_delay {
// it is not yet time to return a message
if next_delay.as_mut().poll(cx).is_pending() {
@@ -366,32 +419,28 @@ where
next_delay.as_mut().reset(next_poisson_delay);
}
// On every iteration we get new messages from upstream. Given that these come bunched
// in `Vec`, this ensures that on average we will fetch messages faster than we can
// send, which is a condition for being able to multiplex sphinx packets from multiple
// data streams.
match Pin::new(&mut self.real_receiver).poll_recv(cx) {
// check if we have anything immediately available
if let Some(real_available) = self.received_buffer.pop_front() {
return Poll::Ready(Some(StreamMessage::Real(Box::new(real_available))));
}
// decide what kind of message to send
match Pin::new(&mut self.real_receiver).poll_next(cx) {
// in the case our real message channel stream was closed, we should also indicate we are closed
// (and whoever is using the stream should panic)
Poll::Ready(None) => Poll::Ready(None),
Poll::Ready(Some((real_messages, conn_id))) => {
log::trace!("handling real_messages: size: {}", real_messages.len());
self.transmission_buffer.store(&conn_id, real_messages);
let real_next = self.pop_next_message().expect("Just stored one");
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
// if there are more messages available, return first one and store the rest
Poll::Ready(Some(real_messages)) => {
self.received_buffer = real_messages.into();
// we MUST HAVE received at least ONE message
Poll::Ready(Some(StreamMessage::Real(Box::new(
self.received_buffer.pop_front().unwrap(),
))))
}
Poll::Pending => {
if let Some(real_next) = self.pop_next_message() {
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
} else {
// otherwise construct a dummy one
Poll::Ready(Some(StreamMessage::Cover))
}
}
// otherwise construct a dummy one
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)),
}
} else {
// we never set an initial delay - let's do it now
@@ -413,36 +462,32 @@ where
}
fn poll_immediate(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> {
// Start by checking if we have any incoming messages about closed connections
if let Poll::Ready(Some(id)) = Pin::new(&mut self.closed_connection_rx).poll_next(cx) {
match id {
ConnectionCommand::Close(id) => self.on_close_connection(id),
ConnectionCommand::ActiveConnections(_) => panic!(),
// check if we have anything immediately available
if let Some(real_available) = self.received_buffer.pop_front() {
// if there are more messages immediately available, notify the runtime
// because we should be polled again
if !self.received_buffer.is_empty() {
cx.waker().wake_by_ref()
}
return Poll::Ready(Some(StreamMessage::Real(Box::new(real_available))));
}
match Pin::new(&mut self.real_receiver).poll_recv(cx) {
match Pin::new(&mut self.real_receiver).poll_next(cx) {
// in the case our real message channel stream was closed, we should also indicate we are closed
// (and whoever is using the stream should panic)
Poll::Ready(None) => Poll::Ready(None),
Poll::Ready(Some((real_messages, conn_id))) => {
log::trace!("handling real_messages: size: {}", real_messages.len());
// First store what we got for the given connection id
self.transmission_buffer.store(&conn_id, real_messages);
let real_next = self.pop_next_message().expect("we just added one");
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
// if there are more messages available, return first one and store the rest
Poll::Ready(Some(real_messages)) => {
self.received_buffer = real_messages.into();
// we MUST HAVE received at least ONE message
Poll::Ready(Some(StreamMessage::Real(Box::new(
self.received_buffer.pop_front().unwrap(),
))))
}
Poll::Pending => {
if let Some(real_next) = self.pop_next_message() {
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
} else {
Poll::Pending
}
}
// if there's nothing, then there's nothing
Poll::Pending => Poll::Pending,
}
}
@@ -457,47 +502,24 @@ where
}
}
#[cfg(not(target_arch = "wasm32"))]
fn log_status(&self) {
let packets = self.transmission_buffer.total_size();
let backlog = self.transmission_buffer.total_size_in_bytes() as f64 / 1024.0;
let lanes = self.transmission_buffer.num_lanes();
let mult = self.sending_delay_controller.current_multiplier();
let delay = self.current_average_message_sending_delay().as_millis();
if self.config.disable_poisson_packet_distribution {
log::info!(
"Status: {lanes} lanes, backlog: {:.2} kiB ({packets}), no delay",
backlog
);
} else {
log::info!(
"Status: {lanes} lanes, backlog: {:.2} kiB ({packets}), avg delay: {}ms ({mult})",
backlog,
delay
);
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started OutQueueControl with graceful shutdown support");
let mut status_timer = tokio::time::interval(Duration::from_secs(1));
while !shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
log::trace!("OutQueueControl: Received shutdown");
}
_ = status_timer.tick() => {
self.log_status();
}
next_message = self.next() => if let Some(next_message) = next_message {
self.on_message(next_message).await;
} else {
log::trace!("OutQueueControl: Stopping since channel closed");
break;
next_message = self.next() => match next_message {
Some(next_message) => {
self.on_message(next_message).await;
},
None => {
log::trace!("OutQueueControl: Stopping since channel closed");
break;
}
}
}
}
@@ -1,124 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::get_time_now;
use std::time::Duration;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time;
#[cfg(target_arch = "wasm32")]
use wasm_timer;
// The minimum time between increasing the average delay between packets. If we hit the ceiling in
// the available buffer space we want to take somewhat swift action, but we still need to give a
// short time to give the channel a chance reduce pressure.
const INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 1;
// The minimum time between decreasing the average delay between packets. We don't want to change
// to quickly to keep things somewhat stable. Also there are buffers downstreams meaning we need to
// wait a little to see the effect before we decrease further.
const DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 30;
// If we enough time passes without any sign of backpressure in the channel, we can consider
// lowering the average delay. The goal is to keep somewhat stable, rather than maxing out
// bandwidth at all times.
const ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS: u64 = 30;
// The maximum multiplier we apply to the base average Poisson delay.
const MAX_DELAY_MULTIPLIER: u32 = 6;
// The minium multiplier we apply to the base average Poisson delay.
const MIN_DELAY_MULTIPLIER: u32 = 1;
pub(crate) struct SendingDelayController {
/// Multiply the average sending delay.
/// This is normally set to unity, but if we detect backpressure we increase this
/// multiplier. We use discrete steps.
current_multiplier: u32,
/// Maximum delay multiplier
upper_bound: u32,
/// Minimum delay multiplier
lower_bound: u32,
/// To make sure we don't change the multiplier to fast, we limit a change to some duration
#[cfg(not(target_arch = "wasm32"))]
time_when_changed: time::Instant,
#[cfg(target_arch = "wasm32")]
time_when_changed: wasm_timer::Instant,
/// If we have a long enough time without any backpressure detected we try reducing the sending
/// delay multiplier
#[cfg(not(target_arch = "wasm32"))]
time_when_backpressure_detected: time::Instant,
#[cfg(target_arch = "wasm32")]
time_when_backpressure_detected: wasm_timer::Instant,
}
impl Default for SendingDelayController {
fn default() -> Self {
SendingDelayController::new(MIN_DELAY_MULTIPLIER, MAX_DELAY_MULTIPLIER)
}
}
impl SendingDelayController {
pub(crate) fn new(lower_bound: u32, upper_bound: u32) -> Self {
assert!(lower_bound <= upper_bound);
let now = get_time_now();
SendingDelayController {
current_multiplier: MIN_DELAY_MULTIPLIER,
upper_bound,
lower_bound,
time_when_changed: now,
time_when_backpressure_detected: now,
}
}
pub(crate) fn current_multiplier(&self) -> u32 {
self.current_multiplier
}
pub(crate) fn increase_delay_multiplier(&mut self) {
if self.current_multiplier < self.upper_bound {
self.current_multiplier =
(self.current_multiplier + 1).clamp(self.lower_bound, self.upper_bound);
self.time_when_changed = get_time_now();
log::debug!(
"Increasing sending delay multiplier to: {}",
self.current_multiplier
);
} else {
log::warn!("Trying to increase delay multipler higher than allowed");
}
}
pub(crate) fn decrease_delay_multiplier(&mut self) {
if self.current_multiplier > self.lower_bound {
self.current_multiplier =
(self.current_multiplier - 1).clamp(self.lower_bound, self.upper_bound);
self.time_when_changed = get_time_now();
log::debug!(
"Decreasing sending delay multiplier to: {}",
self.current_multiplier
);
}
}
pub(crate) fn record_backpressure_detected(&mut self) {
self.time_when_backpressure_detected = get_time_now();
}
pub(crate) fn not_increased_delay_recently(&self) -> bool {
get_time_now()
> self.time_when_changed + Duration::from_secs(INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS)
}
pub(crate) fn is_sending_reliable(&self) -> bool {
let now = get_time_now();
let delay_change_interval = Duration::from_secs(DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS);
let acceptable_time_without_backpressure =
Duration::from_secs(ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS);
now > self.time_when_backpressure_detected + acceptable_time_without_backpressure
&& now > self.time_when_changed + delay_change_interval
}
}
@@ -1,211 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_connections::TransmissionLane;
use rand::seq::SliceRandom;
use std::{
collections::{HashMap, HashSet, VecDeque},
time::Duration,
};
#[cfg(not(target_arch = "wasm32"))]
use tokio::time;
#[cfg(target_arch = "wasm32")]
use wasm_timer;
use super::{get_time_now, RealMessage};
// The number of lanes included in the oldest set. Used when we need to prioritize traffic.
const OLDEST_LANE_SET_SIZE: usize = 5;
// As a way of prune connections we also check for timeouts.
const MSG_CONSIDERED_STALE_AFTER_SECS: u64 = 10 * 60;
#[derive(Default)]
pub(crate) struct TransmissionBuffer {
buffer: HashMap<TransmissionLane, LaneBufferEntry>,
}
impl TransmissionBuffer {
#[allow(unused)]
pub(crate) fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
pub(crate) fn remove(&mut self, lane: &TransmissionLane) -> Option<LaneBufferEntry> {
self.buffer.remove(lane)
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn num_lanes(&self) -> usize {
self.buffer.keys().count()
}
pub(crate) fn lane_length(&self, lane: &TransmissionLane) -> Option<usize> {
self.buffer.get(lane).map(LaneBufferEntry::len)
}
#[allow(unused)]
pub(crate) fn connections(&self) -> HashSet<u64> {
self.buffer
.keys()
.filter_map(|lane| match lane {
TransmissionLane::ConnectionId(id) => Some(id),
_ => None,
})
.copied()
.collect()
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn total_size(&self) -> usize {
self.buffer.values().map(LaneBufferEntry::len).sum()
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn total_size_in_bytes(&self) -> usize {
self.buffer
.values()
.map(|lane_buffer_entry| {
lane_buffer_entry
.real_messages
.iter()
.map(|real_message| real_message.mix_packet.sphinx_packet().len())
.sum::<usize>()
})
.sum()
}
fn get_oldest_set(&self) -> Vec<TransmissionLane> {
let mut buffer: Vec<_> = self
.buffer
.iter()
.map(|(k, v)| (k, v.messages_transmitted))
.collect();
buffer.sort_by_key(|v| v.1);
buffer
.iter()
.rev()
.map(|(k, _)| *k)
.take(OLDEST_LANE_SET_SIZE)
.copied()
.collect()
}
pub(crate) fn store(&mut self, lane: &TransmissionLane, real_messages: Vec<RealMessage>) {
if let Some(lane_buffer_entry) = self.buffer.get_mut(lane) {
lane_buffer_entry.append(real_messages);
} else {
self.buffer
.insert(*lane, LaneBufferEntry::new(real_messages));
}
}
fn pick_random_lane(&self) -> Option<&TransmissionLane> {
let lanes: Vec<&TransmissionLane> = self.buffer.keys().collect();
lanes.choose(&mut rand::thread_rng()).copied()
}
fn pick_random_small_lane(&self) -> Option<&TransmissionLane> {
let lanes: Vec<&TransmissionLane> = self
.buffer
.iter()
.filter(|(_, v)| v.is_small())
.map(|(k, _)| k)
.collect();
lanes.choose(&mut rand::thread_rng()).copied()
}
fn pick_random_old_lane(&self) -> Option<TransmissionLane> {
let lanes = self.get_oldest_set();
lanes.choose(&mut rand::thread_rng()).copied()
}
fn pop_front_from_lane(&mut self, lane: &TransmissionLane) -> Option<RealMessage> {
let real_msgs_queued = self.buffer.get_mut(lane)?;
let real_next = real_msgs_queued.pop_front()?;
real_msgs_queued.messages_transmitted += 1;
if real_msgs_queued.is_empty() {
self.buffer.remove(lane);
}
Some(real_next)
}
pub(crate) fn pop_next_message_at_random(&mut self) -> Option<(TransmissionLane, RealMessage)> {
if self.buffer.is_empty() {
return None;
}
// Very basic heuristic where we prioritize according to small lanes first, the older lanes
// to try to finish lanes when possible, then the rest.
let lane = if let Some(small_lane) = self.pick_random_small_lane() {
*small_lane
} else if let Some(old_lane) = self.pick_random_old_lane() {
old_lane
} else {
*self.pick_random_lane()?
};
let msg = self.pop_front_from_lane(&lane)?;
log::trace!("picking to send from lane: {:?}", lane);
Some((lane, msg))
}
pub(crate) fn prune_stale_connections(&mut self) {
let stale_entries: Vec<_> = self
.buffer
.iter()
.filter_map(|(lane, entry)| if entry.is_stale() { Some(lane) } else { None })
.copied()
.collect();
for lane in stale_entries {
self.remove(&lane);
}
}
}
pub(crate) struct LaneBufferEntry {
pub real_messages: VecDeque<RealMessage>,
pub messages_transmitted: usize,
#[cfg(not(target_arch = "wasm32"))]
pub time_for_last_activity: time::Instant,
#[cfg(target_arch = "wasm32")]
pub time_for_last_activity: wasm_timer::Instant,
}
impl LaneBufferEntry {
fn new(real_messages: Vec<RealMessage>) -> Self {
LaneBufferEntry {
real_messages: real_messages.into(),
messages_transmitted: 0,
time_for_last_activity: get_time_now(),
}
}
fn append(&mut self, real_messages: Vec<RealMessage>) {
self.real_messages.append(&mut real_messages.into());
self.time_for_last_activity = get_time_now();
}
fn pop_front(&mut self) -> Option<RealMessage> {
self.real_messages.pop_front()
}
fn is_small(&self) -> bool {
self.real_messages.len() < 100
}
fn is_stale(&self) -> bool {
get_time_now() - self.time_for_last_activity
> Duration::from_secs(MSG_CONSIDERED_STALE_AFTER_SECS)
}
fn len(&self) -> usize {
self.real_messages.len()
}
fn is_empty(&self) -> bool {
self.real_messages.is_empty()
}
}
@@ -208,7 +208,7 @@ impl ReceivedMessagesBuffer {
}
async fn handle_new_received(&mut self, msgs: Vec<Vec<u8>>) {
trace!(
debug!(
"Processing {:?} new message that might get added to the buffer!",
msgs.len()
);
@@ -8,13 +8,10 @@ use nymsphinx::anonymous_replies::{
};
use std::path::Path;
#[derive(Debug, thiserror::Error)]
#[derive(Debug)]
pub enum ReplyKeyStorageError {
#[error("DB Read Error: {0}")]
DbReadError(sled::Error),
#[error("DB Write Error: {0}")]
DbWriteError(sled::Error),
#[error("DB Open Error: {0}")]
DbOpenError(sled::Error),
}
+1 -1
View File
@@ -22,7 +22,7 @@ cfg_if::cfg_if! {
#[clap(author = "Nymtech", version, about)]
struct Cli {
/// Path pointing to an env file that configures the client.
#[clap(short, long)]
#[clap(long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
/// Path where the sqlite credental database will be located.
+1 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.0"
version = "1.0.2"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
@@ -33,7 +33,6 @@ tokio-tungstenite = "0.14" # websocket
## internal
client-core = { path = "../client-core" }
client-connections = { path = "../../common/client-connections" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
@@ -51,7 +50,6 @@ topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
version-checker = { path = "../../common/version-checker" }
websocket-requests = { path = "websocket-requests" }
tap = "1.0.1"
[features]
coconut = ["coconut-interface", "credentials", "credentials/coconut", "gateway-requests/coconut", "gateway-client/coconut", "client-core/coconut"]
@@ -43,7 +43,6 @@ async fn send_file_with_reply() {
recipient,
message: read_data,
with_reply_surb: true,
connection_id: 0,
};
println!("sending content of 'dummy_file' over the mix network...");
@@ -92,7 +91,6 @@ async fn send_file_without_reply() {
recipient,
message: read_data,
with_reply_surb: false,
connection_id: 0,
};
println!("sending content of 'dummy_file' over the mix network...");
+16 -64
View File
@@ -1,9 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_connections::{
ClosedConnectionReceiver, ClosedConnectionSender, LaneQueueLengths, TransmissionLane,
};
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender,
@@ -34,7 +31,6 @@ use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use nymsphinx::anonymous_replies::ReplySurb;
use nymsphinx::receiver::ReconstructedMessage;
use tap::TapFallible;
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
use crate::client::config::{Config, SocketType};
@@ -114,7 +110,6 @@ impl NymClient {
stream.start_with_shutdown(shutdown);
}
#[allow(clippy::too_many_arguments)]
fn start_real_traffic_controller(
&self,
topology_accessor: TopologyAccessor,
@@ -122,8 +117,6 @@ impl NymClient {
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
lane_queue_lengths: LaneQueueLengths,
closed_connection_rx: ClosedConnectionReceiver,
shutdown: ShutdownListener,
) {
let mut controller_config = real_messages_control::Config::new(
@@ -153,8 +146,6 @@ impl NymClient {
mix_sender,
topology_accessor,
reply_key_storage,
lane_queue_lengths,
closed_connection_rx,
)
.start_with_shutdown(shutdown);
}
@@ -288,18 +279,11 @@ impl NymClient {
&self,
buffer_requester: ReceivedBufferRequestSender,
msg_input: InputMessageSender,
shared_lane_queue_lengths: LaneQueueLengths,
closed_connection_tx: ClosedConnectionSender,
) {
info!("Starting websocket listener...");
let websocket_handler = websocket::Handler::new(
msg_input,
closed_connection_tx,
buffer_requester,
&self.as_mix_recipient(),
shared_lane_queue_lengths,
);
let websocket_handler =
websocket::Handler::new(msg_input, buffer_requester, self.as_mix_recipient());
websocket::Listener::new(self.config.get_listening_port()).start(websocket_handler);
}
@@ -307,43 +291,27 @@ impl NymClient {
/// EXPERIMENTAL DIRECT RUST API
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
/// well enough in local tests)
pub async fn send_message(
&mut self,
recipient: Recipient,
message: Vec<u8>,
with_reply_surb: bool,
) {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb, lane);
pub fn send_message(&mut self, recipient: Recipient, message: Vec<u8>, with_reply_surb: bool) {
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb);
if self
.input_tx
self.input_tx
.as_ref()
.expect("start method was not called before!")
.send(input_msg)
.await
.is_err()
{
panic!();
}
.unbounded_send(input_msg)
.unwrap();
}
/// EXPERIMENTAL DIRECT RUST API
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
/// well enough in local tests)
pub async fn send_reply(&mut self, reply_surb: ReplySurb, message: Vec<u8>) {
pub fn send_reply(&mut self, reply_surb: ReplySurb, message: Vec<u8>) {
let input_msg = InputMessage::new_reply(reply_surb, message);
if self
.input_tx
self.input_tx
.as_ref()
.expect("start method was not called before!")
.send(input_msg)
.await
.is_err()
{
panic!();
}
.unbounded_send(input_msg)
.unwrap();
}
/// EXPERIMENTAL DIRECT RUST API
@@ -400,7 +368,7 @@ impl NymClient {
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages
let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
let (input_sender, input_receiver) = mpsc::unbounded::<InputMessage>();
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
@@ -408,10 +376,7 @@ impl NymClient {
let reply_key_storage =
ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path())
.tap_err(|err| {
log::error!("Failed to load reply key storage - is it perhaps already in use?");
log::error!("{}", err);
})?;
.expect("Failed to load reply key storage!");
// Shutdown notifier for signalling tasks to stop
let shutdown = ShutdownNotifier::default();
@@ -438,22 +403,12 @@ impl NymClient {
let sphinx_message_sender =
Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe());
// Channels that the websocket listener can use to signal downstream to the real traffic
// controller that connections are closed.
let (closed_connection_tx, closed_connection_rx) = mpsc::unbounded();
// Shared queue length data. Published by the `OutQueueController` in the client, and used
// primarily to throttle incoming connections (e.g socks5 for attached network-requesters)
let shared_lane_queue_lengths = LaneQueueLengths::new();
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
reply_key_storage,
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
shared_lane_queue_lengths.clone(),
closed_connection_rx,
shutdown.subscribe(),
);
@@ -470,12 +425,9 @@ impl NymClient {
}
match self.config.get_socket_type() {
SocketType::WebSocket => self.start_websocket_listener(
received_buffer_request_sender,
input_sender,
shared_lane_queue_lengths,
closed_connection_tx,
),
SocketType::WebSocket => {
self.start_websocket_listener(received_buffer_request_sender, input_sender)
}
SocketType::None => {
// if we did not start the socket, it means we're running (supposedly) in the native mode
// and hence we should announce 'ourselves' to the buffer
+1 -1
View File
@@ -50,7 +50,7 @@ fn long_version_static() -> &'static str {
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
pub(crate) struct Cli {
/// Path pointing to an env file that configures the client.
#[clap(short, long)]
#[clap(long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
#[clap(subcommand)]
+1 -3
View File
@@ -1,4 +1,4 @@
use client_core::{client::reply_key_storage::ReplyKeyStorageError, error::ClientCoreError};
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity::Ed25519RecoveryError;
use gateway_client::error::GatewayClientError;
use validator_client::ValidatorClientError;
@@ -15,8 +15,6 @@ pub enum ClientError {
ValidatorClientError(#[from] ValidatorClientError),
#[error("client-core error: {0}")]
ClientCoreError(#[from] ClientCoreError),
#[error("Reply key storage error: {0}")]
ReplyKeyStorageError(#[from] ReplyKeyStorageError),
#[error("Failed to load config for: {0}")]
FailedToLoadConfig(String),
+49 -101
View File
@@ -1,9 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_connections::{
ClosedConnectionSender, ConnectionCommand, LaneQueueLengths, TransmissionLane,
};
use client_core::client::{
inbound_messages::{InputMessage, InputMessageSender},
received_buffer::{
@@ -37,12 +34,10 @@ impl Default for ReceivedResponseType {
pub(crate) struct Handler {
msg_input: InputMessageSender,
closed_connection_tx: ClosedConnectionSender,
buffer_requester: ReceivedBufferRequestSender,
self_full_address: Recipient,
socket: Option<WebSocketStream<TcpStream>>,
received_response_type: ReceivedResponseType,
lane_queue_lengths: LaneQueueLengths,
}
// clone is used to use handler on a new connection, which initially is `None`
@@ -50,12 +45,10 @@ impl Clone for Handler {
fn clone(&self) -> Self {
Handler {
msg_input: self.msg_input.clone(),
closed_connection_tx: self.closed_connection_tx.clone(),
buffer_requester: self.buffer_requester.clone(),
self_full_address: self.self_full_address,
socket: None,
received_response_type: Default::default(),
lane_queue_lengths: self.lane_queue_lengths.clone(),
}
}
}
@@ -71,61 +64,38 @@ impl Drop for Handler {
impl Handler {
pub(crate) fn new(
msg_input: InputMessageSender,
closed_connection_tx: ClosedConnectionSender,
buffer_requester: ReceivedBufferRequestSender,
self_full_address: &Recipient,
lane_queue_lengths: LaneQueueLengths,
self_full_address: Recipient,
) -> Self {
Handler {
msg_input,
closed_connection_tx,
buffer_requester,
self_full_address: *self_full_address,
self_full_address,
socket: None,
received_response_type: Default::default(),
lane_queue_lengths,
}
}
async fn handle_send(
fn handle_send(
&mut self,
recipient: &Recipient,
recipient: Recipient,
message: Vec<u8>,
with_reply_surb: bool,
connection_id: u64,
) -> Option<ServerResponse> {
// the ack control is now responsible for chunking, etc.
let lane = TransmissionLane::ConnectionId(connection_id);
let input_msg = InputMessage::new_fresh(*recipient, message, with_reply_surb, lane);
if self.msg_input.send(input_msg).await.is_err() {
panic!();
}
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb);
self.msg_input.unbounded_send(input_msg).unwrap();
// on receiving a send, we reply back the current lane queue length for that connection id.
// Note that this does _NOT_ take into account the packets that have been received but not
// yet reach `OutQueueControl`, so it might be a tad low.
if let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() {
let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0);
return Some(ServerResponse::LaneQueueLength(connection_id, queue_length));
}
log::warn!("Failed to get the lane queue length lock, not responding back with the current queue length");
None
}
async fn handle_reply(
&mut self,
reply_surb: ReplySurb,
message: Vec<u8>,
) -> Option<ServerResponse> {
fn handle_reply(&mut self, reply_surb: ReplySurb, message: Vec<u8>) -> Option<ServerResponse> {
if message.len() > ReplySurb::max_msg_len(Default::default()) {
return Some(ServerResponse::new_error(format!("too long message to put inside a reply SURB. Received: {} bytes and maximum is {} bytes", message.len(), ReplySurb::max_msg_len(Default::default()))));
}
let input_msg = InputMessage::new_reply(reply_surb, message);
if self.msg_input.send(input_msg).await.is_err() {
panic!();
}
self.msg_input.unbounded_send(input_msg).unwrap();
None
}
@@ -134,48 +104,22 @@ impl Handler {
ServerResponse::SelfAddress(self.self_full_address)
}
fn handle_closed_connection(&self, connection_id: u64) -> Option<ServerResponse> {
self.closed_connection_tx
.unbounded_send(ConnectionCommand::Close(connection_id))
.unwrap();
None
}
fn handle_get_lane_queue_length(&self, connection_id: u64) -> Option<ServerResponse> {
let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() else {
log::warn!(
"Failed to get the lane queue length lock, not responding back with the current queue length"
);
return None;
};
let lane = TransmissionLane::ConnectionId(connection_id);
let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0);
Some(ServerResponse::LaneQueueLength(connection_id, queue_length))
}
async fn handle_request(&mut self, request: ClientRequest) -> Option<ServerResponse> {
fn handle_request(&mut self, request: ClientRequest) -> Option<ServerResponse> {
match request {
ClientRequest::Send {
recipient,
message,
with_reply_surb,
connection_id,
} => {
self.handle_send(&recipient, message, with_reply_surb, connection_id)
.await
}
} => self.handle_send(recipient, message, with_reply_surb),
ClientRequest::Reply {
message,
reply_surb,
} => self.handle_reply(reply_surb, message).await,
} => self.handle_reply(reply_surb, message),
ClientRequest::SelfAddress => Some(self.handle_self_address()),
ClientRequest::ClosedConnection(id) => self.handle_closed_connection(id),
ClientRequest::GetLaneQueueLength(id) => self.handle_get_lane_queue_length(id),
}
}
async fn handle_text_message(&mut self, msg: String) -> Option<WsMessage> {
fn handle_text_message(&mut self, msg: String) -> Option<WsMessage> {
debug!("Handling text message request");
trace!("Content: {:?}", msg);
@@ -184,13 +128,13 @@ impl Handler {
let response = match client_request {
Err(err) => Some(ServerResponse::Error(err)),
Ok(req) => self.handle_request(req).await,
Ok(req) => self.handle_request(req),
};
response.map(|resp| WsMessage::text(resp.into_text()))
}
async fn handle_binary_message(&mut self, msg: &[u8]) -> Option<WsMessage> {
fn handle_binary_message(&mut self, msg: Vec<u8>) -> Option<WsMessage> {
debug!("Handling binary message request");
self.received_response_type = ReceivedResponseType::Binary;
@@ -198,23 +142,49 @@ impl Handler {
let response = match client_request {
Err(err) => Some(ServerResponse::Error(err)),
Ok(req) => self.handle_request(req).await,
Ok(req) => self.handle_request(req),
};
response.map(|resp| WsMessage::Binary(resp.into_binary()))
}
async fn handle_ws_request(&mut self, raw_request: WsMessage) -> Option<WsMessage> {
fn handle_ws_request(&mut self, raw_request: WsMessage) -> Option<WsMessage> {
// apparently tungstenite auto-handles ping/pong/close messages so for now let's ignore
// them and let's test that claim. If that's not the case, just copy code from
// old version of this file.
match raw_request {
WsMessage::Text(text_message) => self.handle_text_message(text_message).await,
WsMessage::Binary(binary_message) => self.handle_binary_message(&binary_message).await,
WsMessage::Text(text_message) => self.handle_text_message(text_message),
WsMessage::Binary(binary_message) => self.handle_binary_message(binary_message),
_ => None,
}
}
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
// let's just play along for now
fn prepare_reconstructed_binary(
&self,
reconstructed_messages: Vec<ReconstructedMessage>,
) -> Vec<Result<WsMessage, WsError>> {
reconstructed_messages
.into_iter()
.map(ServerResponse::Received)
.map(|resp| Ok(WsMessage::Binary(resp.into_binary())))
.collect()
}
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
// let's just play along for now
fn prepare_reconstructed_text(
&self,
reconstructed_messages: Vec<ReconstructedMessage>,
) -> Vec<Result<WsMessage, WsError>> {
reconstructed_messages
.into_iter()
.map(ServerResponse::Received)
.map(|resp| Ok(WsMessage::Text(resp.into_text())))
.collect()
}
async fn push_websocket_received_plaintexts(
&mut self,
reconstructed_messages: Vec<ReconstructedMessage>,
@@ -223,8 +193,10 @@ impl Handler {
// if it's text or binary, but for time being we use the naive assumption that if
// client is sending Message::Text it expects text back. Same for Message::Binary
let response_messages = match self.received_response_type {
ReceivedResponseType::Binary => prepare_reconstructed_binary(reconstructed_messages),
ReceivedResponseType::Text => prepare_reconstructed_text(reconstructed_messages),
ReceivedResponseType::Binary => {
self.prepare_reconstructed_binary(reconstructed_messages)
}
ReceivedResponseType::Text => self.prepare_reconstructed_text(reconstructed_messages),
};
let mut send_stream = futures::stream::iter(response_messages);
@@ -272,7 +244,7 @@ impl Handler {
break;
}
if let Some(response) = self.handle_ws_request(socket_msg).await {
if let Some(response) = self.handle_ws_request(socket_msg) {
if let Err(err) = self.send_websocket_response(response).await {
warn!(
"Failed to send message over websocket: {}. Assuming the connection is dead.",
@@ -319,27 +291,3 @@ impl Handler {
self.listen_for_requests(reconstructed_receiver).await;
}
}
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
// let's just play along for now
fn prepare_reconstructed_binary(
reconstructed_messages: Vec<ReconstructedMessage>,
) -> Vec<Result<WsMessage, WsError>> {
reconstructed_messages
.into_iter()
.map(ServerResponse::Received)
.map(|resp| Ok(WsMessage::Binary(resp.into_binary())))
.collect()
}
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
// let's just play along for now
fn prepare_reconstructed_text(
reconstructed_messages: Vec<ReconstructedMessage>,
) -> Vec<Result<WsMessage, WsError>> {
reconstructed_messages
.into_iter()
.map(ServerResponse::Received)
.map(|resp| Ok(WsMessage::Text(resp.into_text())))
.collect()
}
+15 -102
View File
@@ -20,12 +20,6 @@ pub const REPLY_REQUEST_TAG: u8 = 0x01;
/// Value tag representing [`SelfAddress`] variant of the [`ClientRequest`]
pub const SELF_ADDRESS_REQUEST_TAG: u8 = 0x02;
/// Value tag representing [`ClosedConnection`] variant of the [`ClientRequest`]
pub const CLOSED_CONNECTION_REQUEST_TAG: u8 = 0x03;
/// Value tag representing [`GetLaneQueueLength`] variant of the [`ClientRequest`]
pub const GET_LANE_QUEUE_LENGHT_TAG: u8 = 0x04;
#[allow(non_snake_case)]
#[derive(Debug)]
pub enum ClientRequest {
@@ -34,42 +28,32 @@ pub enum ClientRequest {
message: Vec<u8>,
// Perhaps we could change it to a number to indicate how many reply_SURBs we want to include?
with_reply_surb: bool,
connection_id: u64,
},
Reply {
message: Vec<u8>,
reply_surb: ReplySurb,
},
SelfAddress,
ClosedConnection(u64),
GetLaneQueueLength(u64),
}
// we could have been parsing it directly TryFrom<WsMessage>, but we want to retain
// information about whether it came from binary or text to send appropriate response back
impl ClientRequest {
// SEND_REQUEST_TAG || with_surb || recipient || conn_id || data_len || data
fn serialize_send(
recipient: Recipient,
data: Vec<u8>,
with_reply_surb: bool,
connection_id: u64,
) -> Vec<u8> {
// SEND_REQUEST_TAG || with_surb || recipient || data_len || data
fn serialize_send(recipient: Recipient, data: Vec<u8>, with_reply_surb: bool) -> Vec<u8> {
let data_len_bytes = (data.len() as u64).to_be_bytes();
let conn_id_bytes = connection_id.to_be_bytes();
std::iter::once(SEND_REQUEST_TAG)
.chain(std::iter::once(with_reply_surb as u8))
.chain(recipient.to_bytes().iter().cloned()) // will not be length prefixed because the length is constant
.chain(conn_id_bytes.iter().cloned())
.chain(data_len_bytes.iter().cloned())
.chain(data.into_iter())
.collect()
}
// SEND_REQUEST_TAG || with_reply || recipient || conn_id || data_len || data
// SEND_REQUEST_TAG || with_reply || recipient || data_len || data
fn deserialize_send(b: &[u8]) -> Result<Self, error::Error> {
// we need to have at least 1 (tag) + 1 (reply flag) + Recipient::LEN + 2*sizeof<u64> bytes
if b.len() < 2 + Recipient::LEN + 2 * size_of::<u64>() {
// we need to have at least 1 (tag) + 1 (reply flag) + Recipient::LEN + sizeof<u64> bytes
if b.len() < 2 + Recipient::LEN + size_of::<u64>() {
return Err(error::Error::new(
ErrorKind::TooShortRequest,
"not enough data provided to recover 'send'".to_string(),
@@ -102,15 +86,9 @@ impl ClientRequest {
}
};
let mut connection_id_bytes = [0u8; size_of::<u64>()];
connection_id_bytes
.copy_from_slice(&b[2 + Recipient::LEN..2 + Recipient::LEN + size_of::<u64>()]);
let connection_id = u64::from_be_bytes(connection_id_bytes);
let data_len_bytes =
&b[2 + Recipient::LEN + size_of::<u64>()..2 + Recipient::LEN + 2 * size_of::<u64>()];
let data_len_bytes = &b[2 + Recipient::LEN..2 + Recipient::LEN + size_of::<u64>()];
let data_len = u64::from_be_bytes(data_len_bytes.try_into().unwrap());
let data = &b[2 + Recipient::LEN + 2 * size_of::<u64>()..];
let data = &b[2 + Recipient::LEN + size_of::<u64>()..];
if data.len() as u64 != data_len {
return Err(error::Error::new(
ErrorKind::MalformedRequest,
@@ -126,12 +104,11 @@ impl ClientRequest {
with_reply_surb,
recipient,
message: data.to_vec(),
connection_id,
})
}
// REPLY_REQUEST_TAG || surb_len || surb || message_len || message
fn serialize_reply(message: Vec<u8>, reply_surb: &ReplySurb) -> Vec<u8> {
fn serialize_reply(message: Vec<u8>, reply_surb: ReplySurb) -> Vec<u8> {
let reply_surb_bytes = reply_surb.to_bytes();
let surb_len_bytes = (reply_surb_bytes.len() as u64).to_be_bytes();
let message_len_bytes = (message.len() as u64).to_be_bytes();
@@ -225,65 +202,20 @@ impl ClientRequest {
ClientRequest::SelfAddress
}
// CLOSED_CONNECTION_REQUEST_TAG
fn serialize_closed_connection(connection_id: u64) -> Vec<u8> {
let conn_id_bytes = connection_id.to_be_bytes();
std::iter::once(CLOSED_CONNECTION_REQUEST_TAG)
.chain(conn_id_bytes.iter().copied())
.collect()
}
// CLOSED_CONNECTION_REQUEST_TAG
fn deserialize_closed_connection(b: &[u8]) -> Self {
// this MUST match because it was called by 'deserialize'
debug_assert_eq!(b[0], CLOSED_CONNECTION_REQUEST_TAG);
let mut connection_id_bytes = [0u8; size_of::<u64>()];
connection_id_bytes.copy_from_slice(&b[1..=size_of::<u64>()]);
let connection_id = u64::from_be_bytes(connection_id_bytes);
ClientRequest::ClosedConnection(connection_id)
}
// GET_LANE_QUEUE_LENGHT_TAG
fn serialize_get_lane_queue_lengths(connection_id: u64) -> Vec<u8> {
let conn_id_bytes = connection_id.to_be_bytes();
std::iter::once(GET_LANE_QUEUE_LENGHT_TAG)
.chain(conn_id_bytes.iter().copied())
.collect()
}
// GET_LANE_QUEUE_LENGHT_TAG
fn deserialize_get_lane_queue_length(b: &[u8]) -> Self {
// this MUST match because it was called by 'deserialize'
debug_assert_eq!(b[0], GET_LANE_QUEUE_LENGHT_TAG);
let mut connection_id_bytes = [0u8; size_of::<u64>()];
connection_id_bytes.copy_from_slice(&b[1..=size_of::<u64>()]);
let connection_id = u64::from_be_bytes(connection_id_bytes);
ClientRequest::GetLaneQueueLength(connection_id)
}
pub fn serialize(self) -> Vec<u8> {
match self {
ClientRequest::Send {
recipient,
message,
with_reply_surb,
connection_id,
} => Self::serialize_send(recipient, message, with_reply_surb, connection_id),
} => Self::serialize_send(recipient, message, with_reply_surb),
ClientRequest::Reply {
message,
reply_surb,
} => Self::serialize_reply(message, &reply_surb),
} => Self::serialize_reply(message, reply_surb),
ClientRequest::SelfAddress => Self::serialize_self_address(),
ClientRequest::ClosedConnection(id) => Self::serialize_closed_connection(id),
ClientRequest::GetLaneQueueLength(id) => Self::serialize_get_lane_queue_lengths(id),
}
}
@@ -313,17 +245,15 @@ impl ClientRequest {
SEND_REQUEST_TAG => Self::deserialize_send(b),
REPLY_REQUEST_TAG => Self::deserialize_reply(b),
SELF_ADDRESS_REQUEST_TAG => Ok(Self::deserialize_self_address(b)),
CLOSED_CONNECTION_REQUEST_TAG => Ok(Self::deserialize_closed_connection(b)),
GET_LANE_QUEUE_LENGHT_TAG => Ok(Self::deserialize_get_lane_queue_length(b)),
n => Err(error::Error::new(
ErrorKind::UnknownRequest,
format!("type {n}"),
format!("type {}", n),
)),
}
}
pub fn try_from_binary(raw_req: &[u8]) -> Result<Self, error::Error> {
Self::deserialize(raw_req)
pub fn try_from_binary(raw_req: Vec<u8>) -> Result<Self, error::Error> {
Self::deserialize(&raw_req)
}
pub fn try_from_text(raw_req: String) -> Result<Self, error::Error> {
@@ -350,7 +280,6 @@ mod tests {
recipient,
message: b"foomp".to_vec(),
with_reply_surb: false,
connection_id: 42,
};
let bytes = send_request_no_surb.serialize();
@@ -360,12 +289,10 @@ mod tests {
recipient,
message,
with_reply_surb,
connection_id,
} => {
assert_eq!(recipient.to_string(), recipient_string);
assert_eq!(message, b"foomp".to_vec());
assert!(!with_reply_surb);
assert_eq!(connection_id, 42)
assert!(!with_reply_surb)
}
_ => unreachable!(),
}
@@ -374,7 +301,6 @@ mod tests {
recipient,
message: b"foomp".to_vec(),
with_reply_surb: true,
connection_id: 213,
};
let bytes = send_request_surb.serialize();
@@ -384,12 +310,10 @@ mod tests {
recipient,
message,
with_reply_surb,
connection_id,
} => {
assert_eq!(recipient.to_string(), recipient_string);
assert_eq!(message, b"foomp".to_vec());
assert!(with_reply_surb);
assert_eq!(connection_id, 213)
assert!(with_reply_surb)
}
_ => unreachable!(),
}
@@ -428,15 +352,4 @@ mod tests {
_ => unreachable!(),
}
}
#[test]
fn close_connection_request_serialization_works() {
let close_connection_request = ClientRequest::ClosedConnection(42);
let bytes = close_connection_request.serialize();
let recovered = ClientRequest::deserialize(&bytes).unwrap();
match recovered {
ClientRequest::ClosedConnection(id) => assert_eq!(id, 42),
_ => unreachable!(),
}
}
}
@@ -23,14 +23,10 @@ pub const RECEIVED_RESPONSE_TAG: u8 = 0x01;
/// Value tag representing [`SelfAddress`] variant of the [`ServerResponse`]
pub const SELF_ADDRESS_RESPONSE_TAG: u8 = 0x02;
/// Value tag representing [`LaneQueueLength`] variant of the [`ServerResponse`]
pub const LANE_QUEUE_LENGTH_RESPONSE_TAG: u8 = 0x03;
#[derive(Debug)]
pub enum ServerResponse {
Received(ReconstructedMessage),
SelfAddress(Recipient),
LaneQueueLength(u64, usize),
Error(error::Error),
}
@@ -197,31 +193,6 @@ impl ServerResponse {
Ok(ServerResponse::SelfAddress(recipient))
}
// LANE_QUEUE_LENGTH_RESPONSE_TAG || lane || queue_length
fn serialize_lane_queue_length(lane: u64, queue_length: usize) -> Vec<u8> {
std::iter::once(LANE_QUEUE_LENGTH_RESPONSE_TAG)
.chain(lane.to_be_bytes().iter().cloned())
.chain(queue_length.to_be_bytes().iter().cloned())
.collect()
}
// LANE_QUEUE_LENGTH_RESPONSE_TAG || lane || queue_length
fn deserialize_lane_queue_length(b: &[u8]) -> Result<Self, error::Error> {
// this MUST match because it was called by 'deserialize'
debug_assert_eq!(b[0], LANE_QUEUE_LENGTH_RESPONSE_TAG);
let mut lane_bytes = [0u8; size_of::<u64>()];
lane_bytes.copy_from_slice(&b[1..=size_of::<u64>()]);
let lane = u64::from_be_bytes(lane_bytes);
let mut queue_length_bytes = [0u8; size_of::<usize>()];
queue_length_bytes
.copy_from_slice(&b[1 + size_of::<u64>()..1 + size_of::<u64>() + size_of::<usize>()]);
let queue_length = usize::from_be_bytes(queue_length_bytes);
Ok(ServerResponse::LaneQueueLength(lane, queue_length))
}
// ERROR_RESPONSE_TAG || err_code || msg_len || msg
fn serialize_error(error: error::Error) -> Vec<u8> {
let message_len_bytes = (error.message.len() as u64).to_be_bytes();
@@ -301,9 +272,6 @@ impl ServerResponse {
Self::serialize_received(reconstructed_message)
}
ServerResponse::SelfAddress(address) => Self::serialize_self_address(address),
ServerResponse::LaneQueueLength(lane, queue_length) => {
Self::serialize_lane_queue_length(lane, queue_length)
}
ServerResponse::Error(err) => Self::serialize_error(err),
}
}
@@ -334,7 +302,6 @@ impl ServerResponse {
match response_tag {
RECEIVED_RESPONSE_TAG => Self::deserialize_received(b),
SELF_ADDRESS_RESPONSE_TAG => Self::deserialize_self_address(b),
LANE_QUEUE_LENGTH_RESPONSE_TAG => Self::deserialize_lane_queue_length(b),
ERROR_RESPONSE_TAG => Self::deserialize_error(b),
n => Err(error::Error::new(
ErrorKind::UnknownResponse,
@@ -411,20 +378,6 @@ mod tests {
}
}
#[test]
fn lane_queue_length_response_serialization_works() {
let lane_queue_length_response = ServerResponse::LaneQueueLength(13, 42);
let bytes = lane_queue_length_response.serialize();
let recovered = ServerResponse::deserialize(&bytes).unwrap();
match recovered {
ServerResponse::LaneQueueLength(lane, queue_length) => {
assert_eq!(lane, 13);
assert_eq!(queue_length, 42)
}
_ => unreachable!(),
}
}
#[test]
fn error_response_serialization_works() {
let dummy_error = error::Error::new(ErrorKind::UnknownRequest, "foomp message".to_string());
@@ -20,7 +20,6 @@ pub(super) enum ClientRequestText {
message: String,
recipient: String,
with_reply_surb: bool,
connection_id: u64,
},
SelfAddress,
#[serde(rename_all = "camelCase")]
@@ -47,7 +46,6 @@ impl TryInto<ClientRequest> for ClientRequestText {
message,
recipient,
with_reply_surb,
connection_id,
} => {
let message_bytes = message.into_bytes();
let recipient = Recipient::try_from_base58_string(recipient).map_err(|err| {
@@ -58,7 +56,6 @@ impl TryInto<ClientRequest> for ClientRequestText {
message: message_bytes,
recipient,
with_reply_surb,
connection_id,
})
}
ClientRequestText::SelfAddress => Ok(ClientRequest::SelfAddress),
@@ -94,10 +91,6 @@ pub(super) enum ServerResponseText {
SelfAddress {
address: String,
},
LaneQueueLength {
lane: u64,
queue_length: usize,
},
Error {
message: String,
},
@@ -139,9 +132,6 @@ impl From<ServerResponse> for ServerResponseText {
ServerResponse::SelfAddress(recipient) => ServerResponseText::SelfAddress {
address: recipient.to_string(),
},
ServerResponse::LaneQueueLength(lane, queue_length) => {
ServerResponseText::LaneQueueLength { lane, queue_length }
}
ServerResponse::Error(err) => ServerResponseText::Error {
message: err.to_string(),
},
+1 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.0"
version = "1.0.2"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
@@ -26,7 +26,6 @@ url = "2.2"
# internal
client-core = { path = "../client-core" }
client-connections = { path = "../../common/client-connections" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
@@ -46,7 +45,6 @@ task = { path = "../../common/task" }
topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
version-checker = { path = "../../common/version-checker" }
tap = "1.0.1"
[features]
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut", "credentials/coconut", "client-core/coconut"]
+3 -32
View File
@@ -9,7 +9,6 @@ use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer,
};
use client_connections::{ClosedConnectionReceiver, ClosedConnectionSender, LaneQueueLengths};
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender,
@@ -37,7 +36,6 @@ use gateway_client::{
use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use tap::TapFallible;
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
pub mod config;
@@ -112,7 +110,6 @@ impl NymClient {
stream.start_with_shutdown(shutdown);
}
#[allow(clippy::too_many_arguments)]
fn start_real_traffic_controller(
&self,
topology_accessor: TopologyAccessor,
@@ -120,8 +117,6 @@ impl NymClient {
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
closed_connection_rx: ClosedConnectionReceiver,
lane_queue_lengths: LaneQueueLengths,
shutdown: ShutdownListener,
) {
let mut controller_config = client_core::client::real_messages_control::Config::new(
@@ -151,8 +146,6 @@ impl NymClient {
mix_sender,
topology_accessor,
reply_key_storage,
lane_queue_lengths,
closed_connection_rx,
)
.start_with_shutdown(shutdown);
}
@@ -286,8 +279,6 @@ impl NymClient {
&self,
buffer_requester: ReceivedBufferRequestSender,
msg_input: InputMessageSender,
closed_connection_tx: ClosedConnectionSender,
lane_queue_lengths: LaneQueueLengths,
shutdown: ShutdownListener,
) {
info!("Starting socks5 listener...");
@@ -300,14 +291,9 @@ impl NymClient {
authenticator,
self.config.get_provider_mix_address(),
self.as_mix_recipient(),
lane_queue_lengths,
shutdown,
);
tokio::spawn(async move {
sphinx_socks
.serve(msg_input, buffer_requester, closed_connection_tx)
.await
});
tokio::spawn(async move { sphinx_socks.serve(msg_input, buffer_requester).await });
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
@@ -375,7 +361,7 @@ impl NymClient {
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages
let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
let (input_sender, input_receiver) = mpsc::unbounded::<InputMessage>();
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
@@ -383,10 +369,7 @@ impl NymClient {
let reply_key_storage =
ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path())
.tap_err(|err| {
log::error!("Failed to load reply key storage - is it perhaps already in use?");
log::error!("{}", err);
})?;
.expect("Failed to load reply key storage!");
// Shutdown notifier for signalling tasks to stop
let shutdown = ShutdownNotifier::default();
@@ -413,22 +396,12 @@ impl NymClient {
let sphinx_message_sender =
Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe());
// Channel for announcing closed (socks5) connections by the controller.
// This will be forwarded to `OutQueueControl`
let (closed_connection_tx, closed_connection_rx) = mpsc::unbounded();
// Shared queue length data. Published by the `OutQueueController` in the client, and used
// primarily to throttle incoming connections
let shared_lane_queue_lengths = LaneQueueLengths::new();
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
reply_key_storage,
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
closed_connection_rx,
shared_lane_queue_lengths.clone(),
shutdown.subscribe(),
);
@@ -447,8 +420,6 @@ impl NymClient {
self.start_socks5_listener(
received_buffer_request_sender,
input_sender,
closed_connection_tx,
shared_lane_queue_lengths,
shutdown.subscribe(),
);
+1 -3
View File
@@ -51,7 +51,7 @@ fn long_version_static() -> &'static str {
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
pub(crate) struct Cli {
/// Path pointing to an env file that configures the client.
#[clap(short, long)]
#[clap(long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
#[clap(subcommand)]
@@ -62,10 +62,8 @@ pub(crate) struct Cli {
pub(crate) enum Commands {
/// Initialise a Nym client. Do this first!
Init(init::Init),
/// Run the Nym client with provided configuration client optionally overriding set parameters
Run(run::Run),
/// Try to upgrade the client
Upgrade(upgrade::Upgrade),
+1 -3
View File
@@ -1,4 +1,4 @@
use client_core::{client::reply_key_storage::ReplyKeyStorageError, error::ClientCoreError};
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity::Ed25519RecoveryError;
use gateway_client::error::GatewayClientError;
use validator_client::ValidatorClientError;
@@ -15,8 +15,6 @@ pub enum Socks5ClientError {
ValidatorClientError(#[from] ValidatorClientError),
#[error("client-core error: {0}")]
ClientCoreError(#[from] ClientCoreError),
#[error("Reply key storage error: {0}")]
ReplyKeyStorageError(#[from] ReplyKeyStorageError),
#[error("Failed to load config for: {0}")]
FailedToLoadConfig(String),
+5 -18
View File
@@ -4,8 +4,8 @@ use super::authentication::{AuthenticationMethods, Authenticator, User};
use super::request::{SocksCommand, SocksRequest};
use super::types::{ResponseCode, SocksProxyError};
use super::{RESERVED, SOCKS_VERSION};
use client_connections::{LaneQueueLengths, TransmissionLane};
use client_core::client::inbound_messages::{InputMessage, InputMessageSender};
use client_core::client::inbound_messages::InputMessage;
use client_core::client::inbound_messages::InputMessageSender;
use futures::channel::mpsc;
use futures::task::{Context, Poll};
use log::*;
@@ -141,7 +141,6 @@ pub(crate) struct SocksClient {
service_provider: Recipient,
self_address: Recipient,
started_proxy: bool,
lane_queue_lengths: LaneQueueLengths,
shutdown_listener: ShutdownListener,
}
@@ -159,7 +158,6 @@ impl Drop for SocksClient {
impl SocksClient {
/// Create a new SOCKClient
#[allow(clippy::too_many_arguments)]
pub fn new(
stream: TcpStream,
authenticator: Authenticator,
@@ -167,7 +165,6 @@ impl SocksClient {
service_provider: Recipient,
controller_sender: ControllerSender,
self_address: Recipient,
lane_queue_lengths: LaneQueueLengths,
shutdown_listener: ShutdownListener,
) -> Self {
let connection_id = Self::generate_random();
@@ -182,7 +179,6 @@ impl SocksClient {
service_provider,
self_address,
started_proxy: false,
lane_queue_lengths,
shutdown_listener,
}
}
@@ -234,15 +230,8 @@ impl SocksClient {
let req = Request::new_connect(self.connection_id, remote_address, self.self_address);
let msg = Message::Request(req);
let input_message = InputMessage::new_fresh(
self.service_provider,
msg.into_bytes(),
false,
TransmissionLane::ConnectionId(self.connection_id),
);
if self.input_sender.send(input_message).await.is_err() {
panic!();
}
let input_message = InputMessage::new_fresh(self.service_provider, msg.into_bytes(), false);
self.input_sender.unbounded_send(input_message).unwrap();
}
async fn run_proxy(&mut self, conn_receiver: ConnectionReceiver, remote_proxy_target: String) {
@@ -265,14 +254,12 @@ impl SocksClient {
conn_receiver,
input_sender,
connection_id,
Some(self.lane_queue_lengths.clone()),
self.shutdown_listener.clone(),
)
.run(move |conn_id, read_data, socket_closed| {
let provider_request = Request::new_send(conn_id, read_data, socket_closed);
let provider_message = Message::Request(provider_request);
let lane = TransmissionLane::ConnectionId(conn_id);
InputMessage::new_fresh(recipient, provider_message.into_bytes(), false, lane)
InputMessage::new_fresh(recipient, provider_message.into_bytes(), false)
})
.await
.into_inner();
+1 -7
View File
@@ -4,7 +4,6 @@ use super::{
mixnet_responses::MixnetResponseListener,
types::{ResponseCode, SocksProxyError},
};
use client_connections::{ClosedConnectionSender, LaneQueueLengths};
use client_core::client::{
inbound_messages::InputMessageSender, received_buffer::ReceivedBufferRequestSender,
};
@@ -21,7 +20,6 @@ pub struct SphinxSocksServer {
listening_address: SocketAddr,
service_provider: Recipient,
self_address: Recipient,
lane_queue_lengths: LaneQueueLengths,
shutdown: ShutdownListener,
}
@@ -32,7 +30,6 @@ impl SphinxSocksServer {
authenticator: Authenticator,
service_provider: Recipient,
self_address: Recipient,
lane_queue_lengths: LaneQueueLengths,
shutdown: ShutdownListener,
) -> Self {
// hardcode ip as we (presumably) ONLY want to listen locally. If we change it, we can
@@ -44,7 +41,6 @@ impl SphinxSocksServer {
listening_address: format!("{}:{}", ip, port).parse().unwrap(),
service_provider,
self_address,
lane_queue_lengths,
shutdown,
}
}
@@ -55,14 +51,13 @@ impl SphinxSocksServer {
&mut self,
input_sender: InputMessageSender,
buffer_requester: ReceivedBufferRequestSender,
closed_connection_tx: ClosedConnectionSender,
) -> Result<(), SocksProxyError> {
let listener = TcpListener::bind(self.listening_address).await.unwrap();
info!("Serving Connections...");
// controller for managing all active connections
let (mut active_streams_controller, controller_sender) =
Controller::new(closed_connection_tx, false, self.shutdown.clone());
Controller::new(self.shutdown.clone());
tokio::spawn(async move {
active_streams_controller.run().await;
});
@@ -88,7 +83,6 @@ impl SphinxSocksServer {
self.service_provider,
controller_sender.clone(),
self.self_address,
self.lane_queue_lengths.clone(),
self.shutdown.clone(),
);
+5 -7
View File
@@ -1,7 +1,7 @@
[package]
name = "nym-client-wasm"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "1.1.0"
version = "1.0.1"
edition = "2021"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
@@ -19,18 +19,16 @@ coconut = ["coconut-interface", "credentials", "gateway-client/coconut"]
[dependencies]
futures = "0.3"
js-sys = "0.3"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
tokio = { version = "1.21.2", features = ["sync"] }
url = "2.2"
wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
url = "2.2"
# internal
client-core = { path = "../client-core", default-features = false, features = ["wasm"] }
client-connections = { path = "../../common/client-connections" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
@@ -61,4 +59,4 @@ wasm-opt = true
[profile.release]
lto = true
opt-level = 'z'
opt-level = 'z'
-5
View File
@@ -1,5 +0,0 @@
clippy:
cargo clippy --all-features --target wasm32-unknown-unknown -- -D warnings
test:
wasm-pack test --node
@@ -1,217 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
#[wasm_bindgen(typescript_custom_section)]
const TS_DEFS: &'static str = r#"
export interface BinaryMessage {
kind: number,
payload: Uint8Array;
headers: string,
}
export interface StringMessage {
kind: number,
payload: string;
}
"#;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = "BinaryMessage")]
pub type IBinaryMessage;
#[wasm_bindgen(typescript_type = "StringMessage")]
pub type IStringMessage;
}
#[derive(Serialize, Deserialize)]
pub struct BinaryMessage {
pub kind: u8,
pub payload: Vec<u8>,
pub headers: String,
}
#[derive(Serialize, Deserialize)]
pub struct StringMessage {
pub kind: u8,
pub payload: String,
}
/// Create a new binary message with a user-specified `kind`.
#[wasm_bindgen]
pub fn create_binary_message(kind: u8, payload: Vec<u8>) -> Vec<u8> {
create_binary_message_with_headers(kind, payload, "".to_string())
}
/// Create a new message with a UTF-8 encoded string `payload` and a user-specified `kind`.
#[wasm_bindgen]
pub fn create_binary_message_from_string(kind: u8, payload: String) -> Vec<u8> {
create_binary_message_with_headers(kind, payload.as_bytes().to_vec(), "".to_string())
}
/// Create a new binary message with a user-specified `kind`, and `headers` as a string.
#[wasm_bindgen]
pub fn create_binary_message_with_headers(kind: u8, payload: Vec<u8>, headers: String) -> Vec<u8> {
let headers = headers.as_bytes().to_vec();
let size = (headers.len() as u64).to_be_bytes().to_vec();
vec![vec![kind], size, headers, payload].concat()
}
/// Parse the `kind` and byte array `payload` from a byte array
#[wasm_bindgen]
pub async fn parse_binary_message(message: Vec<u8>) -> Result<IBinaryMessage, JsError> {
if message.len() < 2 {
return Err(JsError::new(
"Could not parse message, as less than 2 bytes long",
));
}
let (kind, _headers, payload) = parse_binary_payload(&message);
Ok(serde_wasm_bindgen::to_value(&BinaryMessage {
kind,
payload: payload.to_vec(),
headers: "".to_string(),
})
.unwrap()
.unchecked_into::<IBinaryMessage>())
}
/// Parse the `kind` and byte array `payload` from a byte array with headers
#[wasm_bindgen]
pub async fn parse_binary_message_with_headers(
message: Vec<u8>,
) -> Result<IBinaryMessage, JsError> {
if message.len() < 2 {
return Err(JsError::new(
"Could not parse message, as less than 2 bytes long",
));
}
let (kind, headers, payload) = parse_binary_payload(&message);
Ok(serde_wasm_bindgen::to_value(&BinaryMessage {
kind,
payload: payload.to_vec(),
headers,
})
.unwrap()
.unchecked_into::<IBinaryMessage>())
}
/// Parse the `kind` and UTF-8 string `payload` from a byte array with headers
#[wasm_bindgen]
pub async fn parse_string_message_with_headers(
message: Vec<u8>,
) -> Result<IStringMessage, JsError> {
if message.len() < 2 {
return Err(JsError::new(
"Could not parse message, as less than 2 bytes long",
));
}
let (kind, _headers, payload) = parse_binary_payload(&message);
let payload = String::from_utf8_lossy(payload).into_owned();
Ok(
serde_wasm_bindgen::to_value(&StringMessage { kind, payload })
.unwrap()
.unchecked_into::<IStringMessage>(),
)
}
pub(crate) fn parse_binary_payload(message: &[u8]) -> (u8, String, &[u8]) {
// 1st byte is the kind
let kind = message[0];
// then the size as u64 big endian
let mut size = [0u8; 8];
size.clone_from_slice(&message[1..9]);
let size = u64::from_be_bytes(size) as usize;
// then the headers
let headers = String::from_utf8_lossy(&message[9..9 + size]).into_owned();
// finally the payload
let payload = &message[9 + size..];
(kind, headers, payload)
}
/// Parse the `kind` and UTF-8 string `payload` from a byte array
#[wasm_bindgen]
pub async fn parse_string_message(message: Vec<u8>) -> Result<IStringMessage, JsError> {
if message.len() < 2 {
return Err(JsError::new(
"Could not parse message, as less than 2 bytes long",
));
}
let kind = message[0];
let payload = String::from_utf8_lossy(&message[1..]).into_owned();
Ok(
serde_wasm_bindgen::to_value(&StringMessage { kind, payload })
.unwrap()
.unchecked_into::<IStringMessage>(),
)
}
#[cfg(test)]
mod tests {
use super::{create_binary_message_with_headers, parse_binary_payload};
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
fn test_binary_with_headers() {
let message_as_bytes = create_binary_message_with_headers(
42u8,
vec![0u8, 1u8, 2u8],
"test headers".to_string(),
);
// calculate header size
let headers = "test headers".as_bytes().to_vec();
let size = headers.len();
// the expected size
let expected_size = 12;
assert_eq!(size, expected_size);
assert_eq!(message_as_bytes[0], 42u8);
assert_eq!(message_as_bytes[1..9], 12u64.to_be_bytes());
assert_eq!(
message_as_bytes[9 + expected_size..9 + expected_size + 3],
vec![0u8, 1u8, 2u8]
);
let res = parse_binary_payload(&message_as_bytes);
assert_eq!(res.0, 42u8);
assert_eq!(res.1, "test headers".to_string());
assert_eq!(res.2, vec![0u8, 1u8, 2u8]);
}
#[wasm_bindgen_test]
fn test_binary_with_empty_headers() {
let message_as_bytes =
create_binary_message_with_headers(42u8, vec![0u8, 1u8, 2u8], "".to_string());
let expected_size = 0;
assert_eq!(message_as_bytes[0], 42u8);
assert_eq!(message_as_bytes[1..9], 0u64.to_be_bytes());
assert_eq!(
message_as_bytes[9 + expected_size..9 + expected_size + 3],
vec![0u8, 1u8, 2u8]
);
let res = parse_binary_payload(&message_as_bytes);
assert_eq!(res.0, 42u8);
assert_eq!(res.1, "".to_string());
assert_eq!(res.2, vec![0u8, 1u8, 2u8]);
}
}
+12 -42
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use self::config::Config;
use client_connections::{ClosedConnectionReceiver, LaneQueueLengths, TransmissionLane};
use client_core::client::{
cover_traffic_stream::LoopCoverTrafficStream,
inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender},
@@ -26,7 +25,7 @@ use nymsphinx::addressing::clients::Recipient;
use rand::rngs::OsRng;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use wasm_utils::console_log;
use wasm_utils::{console_log, console_warn};
pub mod config;
@@ -45,7 +44,6 @@ pub struct NymClient {
// callbacks
on_message: Option<js_sys::Function>,
on_binary_message: Option<js_sys::Function>,
on_gateway_connect: Option<js_sys::Function>,
}
@@ -57,7 +55,6 @@ impl NymClient {
config,
key_manager: Self::setup_key_manager(),
on_message: None,
on_binary_message: None,
on_gateway_connect: None,
input_tx: None,
}
@@ -75,11 +72,8 @@ impl NymClient {
self.on_message = Some(on_message);
}
pub fn set_on_binary_message(&mut self, on_binary_message: js_sys::Function) {
self.on_binary_message = Some(on_binary_message);
}
pub fn set_on_gateway_connect(&mut self, on_connect: js_sys::Function) {
console_log!("setting on connect...");
self.on_gateway_connect = Some(on_connect)
}
@@ -128,8 +122,6 @@ impl NymClient {
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
closed_connection_rx: ClosedConnectionReceiver,
lane_queue_lengths: LaneQueueLengths,
) {
let mut controller_config = real_messages_control::Config::new(
self.key_manager.ack_key(),
@@ -154,8 +146,6 @@ impl NymClient {
input_receiver,
mix_sender,
topology_accessor,
lane_queue_lengths,
closed_connection_rx,
)
.start();
}
@@ -276,7 +266,6 @@ impl NymClient {
received_buffer_request_sender: ReceivedBufferRequestSender,
) {
let on_message = self.on_message.take();
let on_binary_message = self.on_binary_message.take();
spawn_local(async move {
let (reconstructed_sender, mut reconstructed_receiver) = mpsc::unbounded();
@@ -291,14 +280,8 @@ impl NymClient {
let this = JsValue::null();
while let Some(reconstructed) = reconstructed_receiver.next().await {
for msg in reconstructed {
if let Some(ref callback_binary) = on_binary_message {
let arg1 = serde_wasm_bindgen::to_value(&msg.message).unwrap();
callback_binary
.call1(&this, &arg1)
.expect("on binary message failed!");
}
if let Some(ref callback) = on_message {
if let Some(ref callback) = on_message {
for msg in reconstructed {
if msg.reply_surb.is_some() {
console_log!("the received message contained a reply-surb that we do not know how to handle (yet)")
}
@@ -306,6 +289,9 @@ impl NymClient {
let arg1 = serde_wasm_bindgen::to_value(&stringified).unwrap();
callback.call1(&this, &arg1).expect("on message failed!");
}
} else {
console_warn!("no on_message callback was specified. the received message content is getting dropped");
console_log!("the raw messages: {:?}", reconstructed)
}
}
});
@@ -326,16 +312,12 @@ impl NymClient {
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages
let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
let (input_sender, input_receiver) = mpsc::unbounded::<InputMessage>();
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
let shared_topology_accessor = TopologyAccessor::new();
// Channel that the real traffix controller can listed to for closing connections.
// Currently unused in the wasm client.
let (_closed_connection_tx, closed_connection_rx) = mpsc::unbounded();
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone())
@@ -355,17 +337,11 @@ impl NymClient {
// The MixTrafficController then sends the actual traffic
let sphinx_message_sender = Self::start_mix_traffic_controller(gateway_client);
// Shared queue length data. Published by the `OutQueueController` in the client, and used
// primarily to throttle incoming connections
let shared_lane_queue_lengths = LaneQueueLengths::new();
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
closed_connection_rx,
shared_lane_queue_lengths,
);
if !self.config.debug.disable_loop_cover_traffic_stream {
@@ -391,20 +367,14 @@ impl NymClient {
console_log!("Sending {} bytes to {}", message.len(), recipient);
let recipient = Recipient::try_from_base58_string(recipient).unwrap();
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_fresh(recipient, message, false, lane);
let input_msg = InputMessage::new_fresh(recipient, message, false);
if self
.input_tx
self.input_tx
.as_ref()
.expect("start method was not called before!")
.send(input_msg)
.await
.is_err()
{
panic!();
}
.unbounded_send(input_msg)
.unwrap();
self
}
-2
View File
@@ -3,8 +3,6 @@
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
pub mod binary_message_helper;
#[cfg(target_arch = "wasm32")]
mod client;
#[cfg(target_arch = "wasm32")]
-10
View File
@@ -1,10 +0,0 @@
[package]
name = "client-connections"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
futures = "0.3"
log = "0.4.17"
-113
View File
@@ -1,113 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::collections::HashMap;
use futures::channel::mpsc;
pub type ConnectionId = u64;
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum TransmissionLane {
General,
Reply,
Retransmission,
Control,
ConnectionId(ConnectionId),
}
/// Announce connections that are closed, for whoever is interested.
/// One usecase is that the network-requester and socks5-client wants to know about this, so that
/// they can forward this to the `OutQueueControl` (via `ClientRequest` for the network-requester)
pub type ClosedConnectionSender = mpsc::UnboundedSender<ConnectionCommand>;
pub type ClosedConnectionReceiver = mpsc::UnboundedReceiver<ConnectionCommand>;
pub enum ConnectionCommand {
// Announce that at a connection was closed. E.g the `OutQueueControl` uses this to discard
// transmission lanes.
Close(ConnectionId),
// In the network requester for example, we usually want to broadcast active connections
// regularly, so we know what connections we need to request lane queue lengths for from the
// client.
// In the socks5-client, this is not needed since have direct access to the lane queue lengths.
ActiveConnections(Vec<ConnectionId>),
}
// The `OutQueueControl` publishes the backlog per lane, primarily so that upstream can slow down
// if needed.
#[derive(Clone, Debug)]
pub struct LaneQueueLengths(std::sync::Arc<std::sync::Mutex<LaneQueueLengthsInner>>);
impl LaneQueueLengths {
pub fn new() -> Self {
LaneQueueLengths(std::sync::Arc::new(std::sync::Mutex::new(
LaneQueueLengthsInner {
map: HashMap::new(),
},
)))
}
pub fn set(&mut self, lane: &TransmissionLane, lane_length: Option<usize>) {
match self.0.lock() {
Ok(mut inner) => {
if let Some(length) = lane_length {
inner
.map
.entry(*lane)
.and_modify(|e| *e = length)
.or_insert(length);
} else {
inner.map.remove(lane);
}
}
Err(err) => log::warn!("Failed to set lane queue length: {err}"),
}
}
pub fn get(&self, lane: &TransmissionLane) -> Option<usize> {
match self.0.lock() {
Ok(inner) => inner.get(lane),
Err(err) => {
log::warn!("Failed to get lane queue length: {err}");
None
}
}
}
}
impl Default for LaneQueueLengths {
fn default() -> Self {
Self::new()
}
}
impl std::ops::Deref for LaneQueueLengths {
type Target = std::sync::Arc<std::sync::Mutex<LaneQueueLengthsInner>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug)]
pub struct LaneQueueLengthsInner {
pub map: HashMap<TransmissionLane, usize>,
}
impl LaneQueueLengthsInner {
pub fn get(&self, lane: &TransmissionLane) -> Option<usize> {
self.map.get(lane).copied()
}
pub fn values(&self) -> impl Iterator<Item = &usize> {
self.map.values()
}
pub fn modify<F>(&mut self, lane: &TransmissionLane, f: F)
where
F: FnOnce(&mut usize),
{
self.map.entry(*lane).and_modify(f);
}
}
@@ -11,11 +11,9 @@ rust-version = "1.56"
base64 = "0.13"
colored = "2.0"
cw3 = "0.13.1"
coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg" }
contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" }
mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" }
vesting-contract-common = { path= "../../cosmwasm-smart-contracts/vesting-contract" }
contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" }
coconut-bandwidth-contract-common = { path= "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
vesting-contract = { path = "../../../contracts/vesting" }
@@ -2,8 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{validator_api, ValidatorClientError};
use coconut_dkg_common::dealer::ContractDealing;
use coconut_dkg_common::types::DealerDetails;
use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::MixId;
use mixnet_contract_common::{GatewayBond, IdentityKeyRef};
@@ -18,7 +16,7 @@ use validator_api_requests::models::{
};
#[cfg(feature = "nymd-client")]
use crate::nymd::traits::{DkgQueryClient, MixnetQueryClient};
use crate::nymd::traits::MixnetQueryClient;
#[cfg(feature = "nymd-client")]
use crate::nymd::{self, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient};
#[cfg(feature = "nymd-client")]
@@ -45,7 +43,6 @@ pub struct Config {
gateway_page_limit: Option<u32>,
mixnode_delegations_page_limit: Option<u32>,
rewarded_set_page_limit: Option<u32>,
dealers_page_limit: Option<u32>,
}
#[cfg(feature = "nymd-client")]
@@ -75,7 +72,6 @@ impl Config {
gateway_page_limit: None,
mixnode_delegations_page_limit: None,
rewarded_set_page_limit: None,
dealers_page_limit: None,
})
}
@@ -123,7 +119,6 @@ pub struct Client<C> {
gateway_page_limit: Option<u32>,
mixnode_delegations_page_limit: Option<u32>,
rewarded_set_page_limit: Option<u32>,
dealers_page_limit: Option<u32>,
// ideally they would have been read-only, but unfortunately rust doesn't have such features
pub validator_api: validator_api::Client,
@@ -150,7 +145,6 @@ impl Client<SigningNymdClient> {
gateway_page_limit: config.gateway_page_limit,
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
rewarded_set_page_limit: config.rewarded_set_page_limit,
dealers_page_limit: config.dealers_page_limit,
validator_api: validator_api_client,
nymd: nymd_client,
})
@@ -184,7 +178,6 @@ impl Client<QueryNymdClient> {
gateway_page_limit: config.gateway_page_limit,
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
rewarded_set_page_limit: config.rewarded_set_page_limit,
dealers_page_limit: config.dealers_page_limit,
validator_api: validator_api_client,
nymd: nymd_client,
})
@@ -526,82 +519,6 @@ impl<C> Client<C> {
Ok(events)
}
pub async fn get_all_nymd_current_dealers(
&self,
) -> Result<Vec<DealerDetails>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut dealers = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_current_dealers_paged(start_after.take(), self.dealers_page_limit)
.await?;
dealers.append(&mut paged_response.dealers);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res.into_string())
} else {
break;
}
}
Ok(dealers)
}
pub async fn get_all_nymd_past_dealers(
&self,
) -> Result<Vec<DealerDetails>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut dealers = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_past_dealers_paged(start_after.take(), self.dealers_page_limit)
.await?;
dealers.append(&mut paged_response.dealers);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res.into_string())
} else {
break;
}
}
Ok(dealers)
}
pub async fn get_all_nymd_epoch_dealings(
&self,
idx: usize,
) -> Result<Vec<ContractDealing>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut dealings = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_dealings_paged(idx, start_after.take(), self.dealers_page_limit)
.await?;
dealings.append(&mut paged_response.dealings);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res.into_string())
} else {
break;
}
}
Ok(dealings)
}
}
// validator-api wrappers
@@ -6,9 +6,6 @@ use cosmrs::tendermint::abci;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
pub use coconut_bandwidth_contract_common::event_attributes::*;
pub use coconut_dkg_common::event_attributes::*;
// it seems that currently validators just emit stringified events (which are also returned as part of deliverTx response)
// as theirs logs
#[derive(Debug, Serialize, Deserialize)]
@@ -65,7 +65,6 @@ pub struct Config {
pub(crate) bandwidth_claim_contract_address: Option<AccountId>,
pub(crate) coconut_bandwidth_contract_address: Option<AccountId>,
pub(crate) multisig_contract_address: Option<AccountId>,
pub(crate) coconut_dkg_contract_address: Option<AccountId>,
// TODO: add this in later commits
// pub(crate) gas_price: GasPrice,
}
@@ -119,10 +118,6 @@ impl Config {
details.contracts.multisig_contract_address.as_ref(),
prefix,
)?,
coconut_dkg_contract_address: Self::parse_optional_account(
details.contracts.coconut_dkg_contract_address.as_ref(),
prefix,
)?,
})
}
}
@@ -280,14 +275,6 @@ impl<C> NymdClient<C> {
self.config.multisig_contract_address.as_ref().unwrap()
}
// TODO: this should get changed into Result<&AccountId, NymdError> (or Option<&AccountId> in future commits
// note: what unwrap is doing here is just moving a failure that would have normally
// occurred in `connect` when attempting to parse an empty address,
// so it's not introducing new source of failure (just moves it)
pub fn coconut_dkg_contract_address(&self) -> &AccountId {
self.config.coconut_dkg_contract_address.as_ref().unwrap()
}
pub fn set_simulated_gas_multiplier(&mut self, multiplier: f32) {
self.simulated_gas_multiplier = multiplier;
}
@@ -1,114 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::error::NymdError;
use crate::nymd::{CosmWasmClient, NymdClient};
use async_trait::async_trait;
use coconut_dkg_common::dealer::{
DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse,
};
use coconut_dkg_common::msg::QueryMsg as DkgQueryMsg;
use coconut_dkg_common::types::{EpochState, MinimumDepositResponse};
use cosmrs::AccountId;
#[async_trait]
pub trait DkgQueryClient {
async fn get_current_epoch_state(&self) -> Result<EpochState, NymdError>;
async fn get_dealer_details(
&self,
address: &AccountId,
) -> Result<DealerDetailsResponse, NymdError>;
async fn get_current_dealers_paged(
&self,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedDealerResponse, NymdError>;
async fn get_past_dealers_paged(
&self,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedDealerResponse, NymdError>;
async fn get_deposit_amount(&self) -> Result<MinimumDepositResponse, NymdError>;
async fn get_dealings_paged(
&self,
idx: usize,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedDealingsResponse, NymdError>;
}
#[async_trait]
impl<C> DkgQueryClient for NymdClient<C>
where
C: CosmWasmClient + Send + Sync,
{
async fn get_current_epoch_state(&self) -> Result<EpochState, NymdError> {
let request = DkgQueryMsg::GetCurrentEpochState {};
self.client
.query_contract_smart(self.coconut_dkg_contract_address(), &request)
.await
}
async fn get_dealer_details(
&self,
address: &AccountId,
) -> Result<DealerDetailsResponse, NymdError> {
let request = DkgQueryMsg::GetDealerDetails {
dealer_address: address.to_string(),
};
self.client
.query_contract_smart(self.coconut_dkg_contract_address(), &request)
.await
}
async fn get_current_dealers_paged(
&self,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedDealerResponse, NymdError> {
let request = DkgQueryMsg::GetCurrentDealers {
start_after,
limit: page_limit,
};
self.client
.query_contract_smart(self.coconut_dkg_contract_address(), &request)
.await
}
async fn get_past_dealers_paged(
&self,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedDealerResponse, NymdError> {
let request = DkgQueryMsg::GetPastDealers {
start_after,
limit: page_limit,
};
self.client
.query_contract_smart(self.coconut_dkg_contract_address(), &request)
.await
}
async fn get_deposit_amount(&self) -> Result<MinimumDepositResponse, NymdError> {
let request = DkgQueryMsg::GetDepositAmount {};
self.client
.query_contract_smart(self.coconut_dkg_contract_address(), &request)
.await
}
async fn get_dealings_paged(
&self,
idx: usize,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedDealingsResponse, NymdError> {
let request = DkgQueryMsg::GetDealing {
idx: idx as u64,
limit: page_limit,
start_after,
};
self.client
.query_contract_smart(self.coconut_dkg_contract_address(), &request)
.await
}
}
@@ -1,73 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::traits::dkg_query_client::DkgQueryClient;
use crate::nymd::{Fee, NymdClient, SigningCosmWasmClient};
use async_trait::async_trait;
use coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg;
use coconut_dkg_common::types::EncodedBTEPublicKeyWithProof;
use contracts_common::dealings::ContractSafeBytes;
#[async_trait]
pub trait DkgSigningClient {
async fn register_dealer(
&self,
bte_key: EncodedBTEPublicKeyWithProof,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn submit_dealing_bytes(
&self,
commitment: ContractSafeBytes,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
}
#[async_trait]
impl<C> DkgSigningClient for NymdClient<C>
where
C: SigningCosmWasmClient + Send + Sync,
{
async fn register_dealer(
&self,
bte_key: EncodedBTEPublicKeyWithProof,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let req = DkgExecuteMsg::RegisterDealer {
bte_key_with_proof: bte_key,
};
let deposit = self.get_deposit_amount().await?;
self.client
.execute(
self.address(),
self.coconut_dkg_contract_address(),
&req,
fee.unwrap_or_default(),
format!("registering {} as a dealer", self.address()),
vec![deposit.amount.into()],
)
.await
}
async fn submit_dealing_bytes(
&self,
dealing_bytes: ContractSafeBytes,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let req = DkgExecuteMsg::CommitDealing { dealing_bytes };
self.client
.execute(
self.address(),
self.coconut_dkg_contract_address(),
&req,
fee.unwrap_or_default(),
"dealing commitment",
vec![],
)
.await
}
}
@@ -3,8 +3,6 @@
mod coconut_bandwidth_query_client;
mod coconut_bandwidth_signing_client;
mod dkg_query_client;
mod dkg_signing_client;
mod mixnet_query_client;
mod mixnet_signing_client;
mod multisig_query_client;
@@ -14,8 +12,6 @@ mod vesting_signing_client;
pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient;
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
pub use dkg_query_client::DkgQueryClient;
pub use dkg_signing_client::DkgSigningClient;
pub use mixnet_query_client::MixnetQueryClient;
pub use mixnet_signing_client::MixnetSigningClient;
pub use multisig_query_client::MultisigQueryClient;
@@ -13,7 +13,7 @@ use validator_client::nymd::wallet::DirectSecp256k1HdWallet;
pub struct SignatureOutputJson {
pub account_id: String,
pub public_key: PublicKey,
pub signature_as_hex: String,
pub signature: String,
}
#[derive(Debug, Parser)]
@@ -46,7 +46,7 @@ pub fn sign(args: Args, prefix: &str, mnemonic: Option<bip39::Mnemonic>) {
let output = SignatureOutputJson {
account_id: account.address().to_string(),
public_key: account.public_key(),
signature_as_hex: signature.to_string(),
signature: signature.to_string(),
};
println!("{}", json!(output));
}
@@ -1,4 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub const PROPOSAL_ID: &str = "proposal_id";
@@ -1,5 +1,4 @@
pub mod deposit;
pub mod event_attributes;
pub mod events;
pub mod msg;
pub mod spend_credential;
@@ -1,13 +0,0 @@
[package]
name = "coconut-dkg-common"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmwasm-std = "1.0.0"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
contracts-common = { path = "../contracts-common" }
@@ -1,102 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof, NodeIndex};
use cosmwasm_std::{Addr, Coin};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct DealerDetails {
pub address: Addr,
pub bte_public_key_with_proof: EncodedBTEPublicKeyWithProof,
pub assigned_index: NodeIndex,
pub deposit: Coin,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum DealerType {
Current,
Past,
Unknown,
}
impl DealerType {
pub fn is_current(&self) -> bool {
matches!(&self, DealerType::Current)
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct DealerDetailsResponse {
pub details: Option<DealerDetails>,
pub dealer_type: DealerType,
}
impl DealerDetailsResponse {
pub fn new(details: Option<DealerDetails>, dealer_type: DealerType) -> Self {
DealerDetailsResponse {
details,
dealer_type,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct PagedDealerResponse {
pub dealers: Vec<DealerDetails>,
pub per_page: usize,
pub start_next_after: Option<Addr>,
}
impl PagedDealerResponse {
pub fn new(
dealers: Vec<DealerDetails>,
per_page: usize,
start_next_after: Option<Addr>,
) -> Self {
PagedDealerResponse {
dealers,
per_page,
start_next_after,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct ContractDealing {
pub dealing: ContractSafeBytes,
pub dealer: Addr,
}
impl ContractDealing {
pub fn new(dealing: ContractSafeBytes, dealer: Addr) -> Self {
ContractDealing { dealing, dealer }
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct PagedDealingsResponse {
pub dealings: Vec<ContractDealing>,
pub per_page: usize,
pub start_next_after: Option<Addr>,
}
impl PagedDealingsResponse {
pub fn new(
dealings: Vec<ContractDealing>,
per_page: usize,
start_next_after: Option<Addr>,
) -> Self {
PagedDealingsResponse {
dealings,
per_page,
start_next_after,
}
}
}
@@ -1,4 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub const NODE_INDEX: &str = "node_index";
@@ -1,4 +0,0 @@
pub mod dealer;
pub mod event_attributes;
pub mod msg;
pub mod types;
@@ -1,60 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct InstantiateMsg {
pub group_addr: String,
pub mix_denom: String,
pub admin: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
RegisterDealer {
bte_key_with_proof: EncodedBTEPublicKeyWithProof,
},
CommitDealing {
dealing_bytes: ContractSafeBytes,
},
AdvanceEpochState {},
// DEBUG ONLY TXs. THEY SHALL BE REMOVED BEFORE FINALISING THE CODE
// only exists for debugging purposes on local network to reset the entire state of the contract
DebugUnsafeResetAll {
init_msg: InstantiateMsg,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GetCurrentEpochState {},
GetDealerDetails {
dealer_address: String,
},
GetCurrentDealers {
limit: Option<u32>,
start_after: Option<String>,
},
GetPastDealers {
limit: Option<u32>,
start_after: Option<String>,
},
GetDepositAmount {},
GetDealing {
idx: u64,
limit: Option<u32>,
start_after: Option<String>,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {}
@@ -1,87 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
pub use crate::dealer::{DealerDetails, PagedDealerResponse};
pub use contracts_common::dealings::ContractSafeBytes;
pub use cosmwasm_std::{Addr, Coin};
pub type EncodedBTEPublicKeyWithProof = String;
pub type EncodedBTEPublicKeyWithProofRef<'a> = &'a str;
pub type NodeIndex = u64;
// 2 public attributes, 2 private attributes, 1 fixed for coconut credential
pub const TOTAL_DEALINGS: usize = 2 + 2 + 1;
// currently (it is still extremely likely to change, we might be able to get rid of verification key-related complaints),
// the epoch can be in the following states (in order):
// 1. PublicKeySubmission -> potential dealers are submitting their BTE and ed25519 public keys to participate in dealing exchange
// 2. DealingExchange -> the actual (off-chain) dealing exchange is happening
// 3. ComplaintSubmission -> receivers submitting evidence of other dealers sending malformed data
// 4. ComplaintVoting -> (if any complaints were submitted) receivers voting on the validity of the evidence provided
// 5. VerificationKeySubmission -> receivers submitting their partial (and master) verification keys
// 6. VerificationKeyMismatchSubmission -> receivers / watchers raising issue that the submitted VK are mismatched with their local derivations
// 7. VerificationKeyMismatchVoting -> (if any complaints were submitted) receivers voting on received mismatches
// 8. InProgress -> all receivers have all their secrets derived and all is good
//
// Note: It's important that the variant ordering is not changed otherwise it would mess up the derived `PartialOrd`
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)]
#[serde(rename_all = "snake_case")]
pub enum EpochState {
PublicKeySubmission,
DealingExchange,
VerificationKeySubmission,
InProgress,
}
impl Default for EpochState {
fn default() -> Self {
Self::PublicKeySubmission
}
}
impl Display for EpochState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
EpochState::PublicKeySubmission => write!(f, "PublicKeySubmission"),
EpochState::DealingExchange => write!(f, "DealingExchange"),
EpochState::VerificationKeySubmission => write!(f, "VerificationKeySubmission"),
EpochState::InProgress => write!(f, "InProgress"),
}
}
}
impl EpochState {
pub fn next(self) -> Option<Self> {
match self {
EpochState::PublicKeySubmission => Some(EpochState::DealingExchange),
EpochState::DealingExchange => Some(EpochState::VerificationKeySubmission),
EpochState::VerificationKeySubmission => Some(EpochState::InProgress),
EpochState::InProgress => None,
}
}
pub fn all_until(&self, end: Self) -> Vec<Self> {
let mut states = vec![*self];
while states.last().unwrap() != &end {
let next_state = states.last().unwrap().next().expect("somehow reached the end of state diff -> this should be impossible under any circumstances!");
states.push(next_state);
}
states
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct MinimumDepositResponse {
pub amount: Coin,
}
impl MinimumDepositResponse {
pub fn new(amount: Coin) -> Self {
MinimumDepositResponse { amount }
}
}
@@ -7,15 +7,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bs58 = "0.4.0"
cosmwasm-std = "1.0.0"
dkg = { path = "../../../common/crypto/dkg", optional = true }
schemars = "0.8"
serde = { version = "1.0", features = ["derive"] }
schemars = "0.8"
thiserror = "1"
[dev-dependencies]
serde_json = "1.0.0"
[features]
coconut = ["dkg"]
@@ -1,87 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(feature = "coconut")]
use dkg::{error::DkgError, Dealing};
use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{Display, Formatter};
use std::ops::Deref;
// some sane upper-bound size on byte sizes
// currently set to 128 bytes
pub const MAX_DISPLAY_SIZE: usize = 128;
// TODO: if we are to use this for different types, it might make sense to introduce something like
// CommitmentTypeId field on the below for distinguishing different ones. it would somehow become part of the trait
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, JsonSchema)]
pub struct ContractSafeBytes(Vec<u8>);
impl Deref for ContractSafeBytes {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Display for ContractSafeBytes {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if !self.0.is_empty() {
write!(f, "0x")?;
}
for byte in self.0.iter().take(MAX_DISPLAY_SIZE) {
write!(f, "{:02X}", byte)?;
}
// just some sanity safeguards
if self.0.len() > MAX_DISPLAY_SIZE {
write!(f, "...")?;
}
Ok(())
}
}
// since cosmwasm stores everything with byte representation of stringified json, it's actually more efficient
// to serialize this as a string as opposed to keeping it as vector of bytes.
// for example vec![255,255] would have string representation of "[255,255]" and will be serialized to
// [91, 50, 53, 53, 44, 50, 53, 53, 93]. the equivalent base58 encoded string `"LUv"` will be serialized to
// [34, 76, 85, 118, 34]
//
// the difference between base58 and base64 is rather minimal and I've gone with base58 for consistency sake
impl Serialize for ContractSafeBytes {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&bs58::encode(&self.0).into_string())
}
}
impl<'de> Deserialize<'de> for ContractSafeBytes {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = <String>::deserialize(deserializer)?;
let bytes = bs58::decode(&s)
.into_vec()
.map_err(serde::de::Error::custom)?;
Ok(ContractSafeBytes(bytes))
}
}
#[cfg(feature = "coconut")]
impl<'a> From<&'a Dealing> for ContractSafeBytes {
fn from(dealing: &'a Dealing) -> Self {
ContractSafeBytes(dealing.to_bytes())
}
}
#[cfg(feature = "coconut")]
impl<'a> TryFrom<&'a ContractSafeBytes> for Dealing {
type Error = DkgError;
fn try_from(value: &'a ContractSafeBytes) -> Result<Self, Self::Error> {
Dealing::try_from_bytes(&value.0)
}
}
@@ -4,7 +4,6 @@
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
pub mod dealings;
pub mod events;
pub mod types;
@@ -413,6 +413,4 @@ pub enum QueryMsg {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {
pub vesting_contract_address: Option<String>,
}
pub struct MigrateMsg {}
@@ -9,6 +9,7 @@ mixnet-contract-common = { path = "../mixnet-contract" }
contracts-common = { path = "../contracts-common" }
serde = { version = "1.0", features = ["derive"] }
schemars = "0.8"
log = "0.4"
ts-rs = {version = "6.1.2", optional = true}
[features]
@@ -6,6 +6,7 @@
use contracts_common::Percent;
use cosmwasm_std::{Addr, Coin, Timestamp, Uint128};
use log::warn;
use mixnet_contract_common::MixId;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -61,18 +62,29 @@ impl FromStr for PledgeCap {
fn from_str(cap: &str) -> Result<Self, Self::Err> {
let cap = cap.replace('_', "").replace(',', ".");
match Percent::from_str(&cap) {
Ok(p) => Ok(PledgeCap::Percent(p)),
Err(_) => match cap.parse::<u128>() {
Ok(i) => Ok(PledgeCap::Absolute(Uint128::from(i))),
Err(_e) => Err(format!("Could not parse {} as Percent or Uint128", cap)),
},
Ok(p) => {
if p.is_zero() {
warn!("Pledge cap set to 0%, are you sure this is right?")
}
Ok(PledgeCap::Percent(p))
}
Err(_) => {
match cap.parse::<u128>() {
Ok(i) => {
if i < 100_000_000_000 {
warn!("PledgeCap set to less then 100_000 NYM, are you sure this is right?");
}
Ok(PledgeCap::Absolute(Uint128::from(i)))
}
Err(_e) => Err(format!("Could not parse {} as Percent or Uint128", cap)),
}
}
}
}
}
impl Default for PledgeCap {
fn default() -> Self {
#[allow(clippy::expect_used)]
PledgeCap::Percent(Percent::from_percentage_value(10).expect("This can never fail!"))
}
}
-2
View File
@@ -25,8 +25,6 @@ serde_derive = "1.0"
thiserror = "1.0"
zeroize = { version = "1.4", features = ["zeroize_derive"] }
pemstore = { path = "../../pemstore" }
[dependencies.group]
version = "0.11"
default-features = false
+52 -15
View File
@@ -9,7 +9,7 @@ use dkg::bte::proof_discrete_log::ProofOfDiscreteLog;
use dkg::bte::proof_sharing::ProofOfSecretSharing;
use dkg::bte::{
decrypt_share, encrypt_shares, keygen, proof_chunking, proof_sharing, setup, DecryptionKey,
PublicKey,
Epoch, PublicKey,
};
use dkg::interpolation::polynomial::Polynomial;
use dkg::{Dealing, NodeIndex, Share};
@@ -54,6 +54,7 @@ pub fn creating_dealing_for_3_parties(c: &mut Criterion) {
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 2;
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 3);
@@ -65,6 +66,7 @@ pub fn creating_dealing_for_3_parties(c: &mut Criterion) {
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
)
@@ -78,6 +80,7 @@ pub fn verifying_dealing_made_for_3_parties_and_recovering_share(c: &mut Criteri
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 2;
let epoch = Epoch::new(2);
let (receivers, mut dks) = prepare_keys(&mut rng, 3);
let (dealing, _) = Dealing::create(
@@ -85,18 +88,22 @@ pub fn verifying_dealing_made_for_3_parties_and_recovering_share(c: &mut Criteri
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
);
let first_key = dks.get_mut(0).unwrap();
first_key.try_update_to(epoch, &params, &mut rng).unwrap();
c.bench_function(
"verifying single dealing made for 3 parties (threshold 2) and recovering share",
|b| {
b.iter(|| {
assert!(dealing.verify(&params, threshold, &receivers, None).is_ok());
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, None).unwrap());
assert!(dealing
.verify(&params, epoch, threshold, &receivers, None)
.is_ok());
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap());
})
},
);
@@ -107,6 +114,7 @@ pub fn creating_dealing_for_20_parties(c: &mut Criterion) {
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 14;
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 20);
@@ -120,6 +128,7 @@ pub fn creating_dealing_for_20_parties(c: &mut Criterion) {
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
)
@@ -134,6 +143,7 @@ pub fn verifying_dealing_made_for_20_parties_and_recovering_share(c: &mut Criter
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 14;
let epoch = Epoch::new(2);
let (receivers, mut dks) = prepare_keys(&mut rng, 20);
let (dealing, _) = Dealing::create(
@@ -141,18 +151,22 @@ pub fn verifying_dealing_made_for_20_parties_and_recovering_share(c: &mut Criter
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
);
let first_key = dks.get_mut(0).unwrap();
first_key.try_update_to(epoch, &params, &mut rng).unwrap();
c.bench_function(
"verifying single dealing made for 20 parties (threshold 14) and recovering share",
|b| {
b.iter(|| {
assert!(dealing.verify(&params, threshold, &receivers, None).is_ok());
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, None).unwrap());
assert!(dealing
.verify(&params, epoch, threshold, &receivers, None)
.is_ok());
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap());
})
},
);
@@ -163,6 +177,7 @@ pub fn creating_dealing_for_100_parties(c: &mut Criterion) {
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 67;
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
@@ -176,6 +191,7 @@ pub fn creating_dealing_for_100_parties(c: &mut Criterion) {
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
)
@@ -190,6 +206,7 @@ pub fn verifying_dealing_made_for_100_parties_and_recovering_share(c: &mut Crite
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 67;
let epoch = Epoch::new(2);
let (receivers, mut dks) = prepare_keys(&mut rng, 100);
let (dealing, _) = Dealing::create(
@@ -197,18 +214,22 @@ pub fn verifying_dealing_made_for_100_parties_and_recovering_share(c: &mut Crite
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
);
let first_key = dks.get_mut(0).unwrap();
first_key.try_update_to(epoch, &params, &mut rng).unwrap();
c.bench_function(
"verifying single dealing made for 100 parties (threshold 67) and recovering share",
|b| {
b.iter(|| {
assert!(dealing.verify(&params, threshold, &receivers, None).is_ok());
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, None).unwrap());
assert!(dealing
.verify(&params, epoch, threshold, &receivers, None)
.is_ok());
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap());
})
},
);
@@ -245,6 +266,7 @@ pub fn creating_proof_of_chunking_for_100_parties(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
@@ -261,7 +283,7 @@ pub fn creating_proof_of_chunking_for_100_parties(c: &mut Criterion) {
.collect::<Vec<_>>();
let ordered_public_keys = receivers.values().copied().collect::<Vec<_>>();
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, &params, &mut rng);
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, &params, &mut rng);
c.bench_function("creating proof of chunking for 100 parties", |b| {
b.iter(|| {
@@ -279,6 +301,7 @@ pub fn verifying_proof_of_chunking_for_100_parties(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
@@ -295,7 +318,7 @@ pub fn verifying_proof_of_chunking_for_100_parties(c: &mut Criterion) {
.collect::<Vec<_>>();
let ordered_public_keys = receivers.values().copied().collect::<Vec<_>>();
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, &params, &mut rng);
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, &params, &mut rng);
let chunking_instance = proof_chunking::Instance::new(&ordered_public_keys, &ciphertexts);
let proof_of_chunking =
@@ -315,6 +338,7 @@ pub fn creating_proof_of_secret_sharing_for_100_parties(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
@@ -330,7 +354,7 @@ pub fn creating_proof_of_secret_sharing_for_100_parties(c: &mut Criterion) {
.map(|(share, key)| (share, key))
.collect::<Vec<_>>();
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, &params, &mut rng);
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, &params, &mut rng);
let combined_ciphertexts = ciphertexts.combine_ciphertexts();
let combined_r = hazmat.combine_rs();
@@ -357,6 +381,7 @@ pub fn verifying_proof_of_secret_sharing_for_100_parties(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
@@ -372,7 +397,7 @@ pub fn verifying_proof_of_secret_sharing_for_100_parties(c: &mut Criterion) {
.map(|(share, key)| (share, key))
.collect::<Vec<_>>();
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, &params, &mut rng);
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, &params, &mut rng);
let combined_ciphertexts = ciphertexts.combine_ciphertexts();
let combined_r = hazmat.combine_rs();
@@ -405,6 +430,7 @@ pub fn single_share_encryption(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (_, pk) = keygen(&params, &mut rng);
let polynomial = Polynomial::new_random(&mut rng, 3);
@@ -414,6 +440,7 @@ pub fn single_share_encryption(c: &mut Criterion) {
b.iter(|| {
black_box(encrypt_shares(
&[(&share, pk.public_key())],
epoch,
&params,
&mut rng,
))
@@ -425,6 +452,7 @@ pub fn share_encryption_100(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
let polynomial = Polynomial::new_random(&mut rng, 3);
@@ -440,7 +468,14 @@ pub fn share_encryption_100(c: &mut Criterion) {
.collect::<Vec<_>>();
c.bench_function("100 shares encryption", |b| {
b.iter(|| black_box(encrypt_shares(&remote_share_key_pairs, &params, &mut rng)))
b.iter(|| {
black_box(encrypt_shares(
&remote_share_key_pairs,
epoch,
&params,
&mut rng,
))
})
});
}
@@ -448,14 +483,16 @@ pub fn share_decryption(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let (dk, pk) = keygen(&params, &mut rng);
let epoch = Epoch::new(2);
let (mut dk, pk) = keygen(&params, &mut rng);
let polynomial = Polynomial::new_random(&mut rng, 3);
let share: Share = polynomial.evaluate_at(&Scalar::from(42)).into();
let (ciphertexts, _) = encrypt_shares(&[(&share, pk.public_key())], &params, &mut rng);
let (ciphertexts, _) = encrypt_shares(&[(&share, pk.public_key())], epoch, &params, &mut rng);
dk.try_update_to(epoch, &params, &mut rng).unwrap();
c.bench_function("single share decryption", |b| {
b.iter(|| black_box(decrypt_share(&dk, 0, &ciphertexts, None)))
b.iter(|| black_box(decrypt_share(&dk, 0, &ciphertexts, epoch, None)))
});
}
+179 -26
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::bte::keys::{DecryptionKey, PublicKey};
use crate::bte::{evaluate_f, Params, CHUNK_SIZE, G2_GENERATOR_PREPARED, NUM_CHUNKS, PAIRING_BASE};
use crate::bte::{Epoch, Params, CHUNK_SIZE, G2_GENERATOR_PREPARED, NUM_CHUNKS, PAIRING_BASE};
use crate::error::DkgError;
use crate::utils::{combine_g1_chunks, combine_scalar_chunks, deserialize_g1, deserialize_g2};
use crate::{Chunk, ChunkedShare, Share};
@@ -24,7 +24,7 @@ pub struct Ciphertexts {
}
impl Ciphertexts {
pub fn verify_integrity(&self, params: &Params) -> bool {
pub fn verify_integrity(&self, params: &Params, epoch: Epoch) -> bool {
// if this checks fails it means the ciphertext is undefined as values
// in `r`, `s` and `z` are meaningless since technically this ciphertext
// has been created for 0 parties
@@ -33,7 +33,9 @@ impl Ciphertexts {
}
let g1_neg = G1Affine::generator().neg();
let f = evaluate_f(params);
let f = epoch
.as_extended_tau(&self.rr, &self.ss, &self.ciphertext_chunks)
.evaluate_f(params);
// we have to use `f` in up to `NUM_CHUNKS` pairings (if everything is valid),
// so perform some precomputation on it
@@ -190,6 +192,7 @@ impl HazmatRandomness {
pub fn encrypt_shares(
shares: &[(&Share, &PublicKey)],
epoch: Epoch,
params: &Params,
mut rng: impl RngCore,
) -> (Ciphertexts, HazmatRandomness) {
@@ -239,7 +242,7 @@ pub fn encrypt_shares(
let rr = rr.try_into().unwrap();
let ss = ss.try_into().unwrap();
let f = evaluate_f(params);
let f = epoch.as_extended_tau(&rr, &ss, &cc).evaluate_f(params);
let mut zz = Vec::with_capacity(NUM_CHUNKS);
for i in 0..NUM_CHUNKS {
@@ -266,22 +269,35 @@ pub fn decrypt_share(
// in the case of multiple receivers, specifies which index of ciphertext chunks should be used
i: usize,
ciphertext: &Ciphertexts,
epoch: Epoch,
lookup_table: Option<&BabyStepGiantStepLookup>,
) -> Result<Share, DkgError> {
let mut plaintext = ChunkedShare::default();
let decryption_node = dk.try_get_compatible_node(epoch)?;
let extended_tau = epoch.as_extended_tau(
&ciphertext.rr,
&ciphertext.ss,
&ciphertext.ciphertext_chunks,
);
if i >= ciphertext.ciphertext_chunks.len() {
return Err(DkgError::UnavailableCiphertext(i));
}
let b_neg = dk
.dh
let height = decryption_node.tau.height();
let b_neg = decryption_node
.ds
.iter()
.fold(dk.b, |acc, d_i| acc + d_i)
.chain(decryption_node.dh.iter())
.zip(extended_tau.0.iter().by_vals().skip(height))
.filter(|(_, i)| *i)
.map(|(d_i, _)| d_i)
.fold(decryption_node.b, |acc, d_i| acc + d_i)
.neg()
.to_affine();
let e_neg = dk.e.neg().to_affine();
let e_neg = decryption_node.e.neg().to_affine();
for j in 0..NUM_CHUNKS {
let rr_j = &ciphertext.rr[j];
@@ -292,7 +308,7 @@ pub fn decrypt_share(
let miller = bls12_381::multi_miller_loop(&[
(&cc_ij.to_affine(), &G2_GENERATOR_PREPARED),
(&rr_j.to_affine(), &G2Prepared::from(b_neg)),
(&dk.a.to_affine(), &G2Prepared::from(zz_j)),
(&decryption_node.a.to_affine(), &G2Prepared::from(zz_j)),
(&ss_j.to_affine(), &G2Prepared::from(e_neg)),
]);
let m = miller.final_exponentiation();
@@ -450,6 +466,7 @@ mod tests {
let (decryption_key1, public_key1) = keygen(&params, &mut rng);
let (decryption_key2, public_key2) = keygen(&params, &mut rng);
let epoch = Epoch::new(0);
let lookup_table = &DEFAULT_BSGS_TABLE;
@@ -458,13 +475,13 @@ mod tests {
let m2 = Share::random(&mut rng);
let shares = &[(&m1, &public_key1.key), (&m2, &public_key2.key)];
let (ciphertext, hazmat) = encrypt_shares(shares, &params, &mut rng);
let (ciphertext, hazmat) = encrypt_shares(shares, epoch, &params, &mut rng);
verify_hazmat_rand(&ciphertext, &hazmat);
let recovered1 =
decrypt_share(&decryption_key1, 0, &ciphertext, Some(lookup_table)).unwrap();
decrypt_share(&decryption_key1, 0, &ciphertext, epoch, Some(lookup_table)).unwrap();
let recovered2 =
decrypt_share(&decryption_key2, 1, &ciphertext, Some(lookup_table)).unwrap();
decrypt_share(&decryption_key2, 1, &ciphertext, epoch, Some(lookup_table)).unwrap();
assert_eq!(m1, recovered1);
assert_eq!(m2, recovered2);
}
@@ -477,8 +494,15 @@ mod tests {
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let (decryption_key1, public_key1) = keygen(&params, &mut rng);
let (decryption_key2, public_key2) = keygen(&params, &mut rng);
let (mut decryption_key1, public_key1) = keygen(&params, &mut rng);
let (mut decryption_key2, public_key2) = keygen(&params, &mut rng);
let epoch = Epoch::new(12345);
decryption_key1
.try_update_to(epoch, &params, &mut rng)
.unwrap();
decryption_key2
.try_update_to(epoch, &params, &mut rng)
.unwrap();
let lookup_table = &DEFAULT_BSGS_TABLE;
@@ -487,18 +511,121 @@ mod tests {
let m2 = Share::random(&mut rng);
let shares = &[(&m1, &public_key1.key), (&m2, &public_key2.key)];
let (ciphertext, hazmat) = encrypt_shares(shares, &params, &mut rng);
let (ciphertext, hazmat) = encrypt_shares(shares, epoch, &params, &mut rng);
verify_hazmat_rand(&ciphertext, &hazmat);
let recovered1 =
decrypt_share(&decryption_key1, 0, &ciphertext, Some(lookup_table)).unwrap();
decrypt_share(&decryption_key1, 0, &ciphertext, epoch, Some(lookup_table)).unwrap();
let recovered2 =
decrypt_share(&decryption_key2, 1, &ciphertext, Some(lookup_table)).unwrap();
decrypt_share(&decryption_key2, 1, &ciphertext, epoch, Some(lookup_table)).unwrap();
assert_eq!(m1, recovered1);
assert_eq!(m2, recovered2);
}
}
#[test]
fn decryption_with_root_key() {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let (root_key, public_key) = keygen(&params, &mut rng);
let share = Share::random(&mut rng);
let epoch0 = Epoch::new(0);
let epoch42 = Epoch::new(42);
let epoch_big = Epoch::new(3292547435);
let (ciphertext1, hazmat1) =
encrypt_shares(&[(&share, &public_key.key)], epoch0, &params, &mut rng);
verify_hazmat_rand(&ciphertext1, &hazmat1);
let (ciphertext2, hazmat2) =
encrypt_shares(&[(&share, &public_key.key)], epoch42, &params, &mut rng);
verify_hazmat_rand(&ciphertext2, &hazmat2);
let (ciphertext3, hazmat3) =
encrypt_shares(&[(&share, &public_key.key)], epoch_big, &params, &mut rng);
verify_hazmat_rand(&ciphertext3, &hazmat3);
let recovered1 = decrypt_share(&root_key, 0, &ciphertext1, epoch0, None).unwrap();
let recovered2 = decrypt_share(&root_key, 0, &ciphertext2, epoch42, None).unwrap();
let recovered3 = decrypt_share(&root_key, 0, &ciphertext3, epoch_big, None).unwrap();
assert_eq!(share, recovered1);
assert_eq!(share, recovered2);
assert_eq!(share, recovered3);
}
#[test]
#[ignore] // expensive test
fn update_and_decrypt_10() {
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let (mut decryption_key, public_key) = keygen(&params, &mut rng);
for epoch_value in 0..10 {
let epoch = Epoch::new(epoch_value);
let share = Share::random(&mut rng);
decryption_key
.try_update_to(epoch, &params, &mut rng)
.unwrap();
let (ciphertext, hazmat) =
encrypt_shares(&[(&share, &public_key.key)], epoch, &params, &mut rng);
verify_hazmat_rand(&ciphertext, &hazmat);
let recovered = decrypt_share(&decryption_key, 0, &ciphertext, epoch, None).unwrap();
assert_eq!(share, recovered);
}
}
#[test]
#[ignore] // expensive test
fn reblinding_node_doesnt_affect_decryption() {
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let (mut decryption_key, public_key) = keygen(&params, &mut rng);
let epoch = Epoch::new(12345);
decryption_key
.try_update_to(epoch, &params, &mut rng)
.unwrap();
for node in decryption_key.nodes.iter_mut() {
node.reblind(&params, &mut rng);
}
let share = Share::random(&mut rng);
let (ciphertext, hazmat) =
encrypt_shares(&[(&share, &public_key.key)], epoch, &params, &mut rng);
verify_hazmat_rand(&ciphertext, &hazmat);
let recovered = decrypt_share(&decryption_key, 0, &ciphertext, epoch, None).unwrap();
assert_eq!(share, recovered);
// attempt to update the key again so we have to derive fresh nodes using previous reblinded results
let epoch2 = Epoch::new(67890);
decryption_key
.try_update_to(epoch2, &params, &mut rng)
.unwrap();
for node in decryption_key.nodes.iter_mut() {
node.reblind(&params, &mut rng);
}
let share2 = Share::random(&mut rng);
let (ciphertext, hazmat) =
encrypt_shares(&[(&share2, &public_key.key)], epoch2, &params, &mut rng);
verify_hazmat_rand(&ciphertext, &hazmat);
let recovered = decrypt_share(&decryption_key, 0, &ciphertext, epoch2, None).unwrap();
assert_eq!(share2, recovered);
}
#[test]
#[ignore] // expensive test
fn ciphertext_integrity_check_passes_for_valid_data() {
@@ -507,11 +634,14 @@ mod tests {
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (_, public_key) = keygen(&params, &mut rng);
let (mut dk, public_key) = keygen(&params, &mut rng);
let epoch = Epoch::new(1);
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let share = Share::random(&mut rng);
let (ciphertext, _) = encrypt_shares(&[(&share, &public_key.key)], &params, &mut rng);
assert!(ciphertext.verify_integrity(&params))
let (ciphertext, _) =
encrypt_shares(&[(&share, &public_key.key)], epoch, &params, &mut rng);
assert!(ciphertext.verify_integrity(&params, epoch))
}
#[test]
@@ -522,22 +652,45 @@ mod tests {
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (_, public_key) = keygen(&params, &mut rng);
let (mut dk, public_key) = keygen(&params, &mut rng);
let epoch = Epoch::new(1);
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let share = Share::random(&mut rng);
let (ciphertext, _) = encrypt_shares(&[(&share, &public_key.key)], &params, &mut rng);
let (ciphertext, _) =
encrypt_shares(&[(&share, &public_key.key)], epoch, &params, &mut rng);
let mut bad_cipher1 = ciphertext.clone();
bad_cipher1.rr[4] = G1Projective::generator();
assert!(!bad_cipher1.verify_integrity(&params));
assert!(!bad_cipher1.verify_integrity(&params, epoch));
let mut bad_cipher2 = ciphertext.clone();
bad_cipher2.ss[4] = G1Projective::generator();
assert!(!bad_cipher2.verify_integrity(&params));
assert!(!bad_cipher2.verify_integrity(&params, epoch));
let mut bad_cipher3 = ciphertext;
bad_cipher3.zz[4] = G2Projective::generator();
assert!(!bad_cipher3.verify_integrity(&params));
assert!(!bad_cipher3.verify_integrity(&params, epoch));
}
#[test]
#[ignore] // expensive test
fn ciphertext_integrity_check_passes_fails_for_wrong_epoch() {
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, public_key) = keygen(&params, &mut rng);
let epoch = Epoch::new(1);
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let share = Share::random(&mut rng);
let (ciphertext, _) =
encrypt_shares(&[(&share, &public_key.key)], epoch, &params, &mut rng);
let another_epoch = Epoch::new(2);
assert!(!ciphertext.verify_integrity(&params, another_epoch))
}
#[test]
@@ -558,7 +711,7 @@ mod tests {
}
let refs = shares.iter().zip(public_keys.iter()).collect::<Vec<_>>();
let (ciphertext, hazmat) = encrypt_shares(&refs, &params, &mut rng);
let (ciphertext, hazmat) = encrypt_shares(&refs, Epoch::new(42), &params, &mut rng);
let combined_r = combine_scalar_chunks(hazmat.r());
let combined_rr = ciphertext.combine_rs();
+712 -146
View File
@@ -2,16 +2,306 @@
// SPDX-License-Identifier: Apache-2.0
use crate::bte::proof_discrete_log::ProofOfDiscreteLog;
use crate::bte::Params;
use crate::bte::{Epoch, Params, Tau};
use crate::error::DkgError;
use crate::utils::{deserialize_g1, deserialize_g2, deserialize_scalar};
use bls12_381::{G1Projective, G2Projective, Scalar};
use ff::Field;
use group::GroupEncoding;
use pemstore::traits::{PemStorableKey, PemStorableKeyPair};
use rand_core::RngCore;
use zeroize::Zeroize;
#[derive(Debug, Zeroize)]
#[zeroize(drop)]
#[cfg_attr(test, derive(Clone, PartialEq))]
pub(crate) struct Node {
pub(crate) tau: Tau,
// g1^rho
pub(crate) a: G1Projective,
// g2^x
pub(crate) b: G2Projective,
// f_i^rho, up to lambda_t elements
pub(crate) ds: Vec<G2Projective>,
// fh_i^rho, always lambda_h elements
pub(crate) dh: Vec<G2Projective>,
// h^rho
pub(crate) e: G2Projective,
}
impl Node {
fn new_root(
a: G1Projective,
b: G2Projective,
ds: Vec<G2Projective>,
dh: Vec<G2Projective>,
e: G2Projective,
) -> Self {
Node {
tau: Tau::new_root(),
a,
b,
ds,
dh,
e,
}
}
fn is_root(&self) -> bool {
self.tau.0.is_empty()
}
pub(crate) fn reblind(&mut self, params: &Params, mut rng: impl RngCore) {
let delta = Scalar::random(&mut rng);
self.a += G1Projective::generator() * delta;
// TODO: or do we have to do full tau evaluation here?
self.b += self.tau.evaluate_partial_f(params) * delta;
self.ds
.iter_mut()
.zip(params.fs.iter().skip(self.tau.height()))
.for_each(|(d_i, f_i)| *d_i += f_i * delta);
self.dh
.iter_mut()
.zip(params.fh.iter())
.for_each(|(d_i, f_i)| *d_i += f_i * delta);
self.e += params.h * delta;
}
// note: it's unsafe to use this method outside `try_update_to` as
// we have guaranteed there that `self` is parent of the target
// and that `self.tau != target_tau`
/// Given `self` with `Tau1` and `target_tau` with `Tau2`, such that `Tau1` prefixes `Tau2`,
/// i.e. `Tau2 == Tau1 || SUFFIX`, and `Tau2` is a leaf node, derive all required crypto material
/// for its construction.
fn derive_target_child_with_partials(
&self,
params: &Params,
target_tau: Tau,
partial_b: &G2Projective,
partial_f: &G2Projective,
mut rng: impl RngCore,
) -> Self {
debug_assert!(self.tau.is_parent_of(&target_tau));
debug_assert_ne!(self.tau, target_tau);
let delta = Scalar::random(&mut rng);
let a = self.a + G1Projective::generator() * delta;
let b = partial_b + partial_f * delta;
let ds = self
.ds
.iter()
.zip(params.fs.iter())
.skip(target_tau.height())
.map(|(d_i, f_i)| d_i + f_i * delta)
.collect();
let dh = self
.dh
.iter()
.zip(params.fh.iter())
.map(|(dh_i, fh_i)| dh_i + fh_i * delta)
.collect();
let e = self.e + params.h * delta;
Node {
tau: target_tau,
a,
b,
ds,
dh,
e,
}
}
// note: it's unsafe to use this method outside `try_update_to` as
// we have guaranteed there that `self` is parent of the target
// and that `self.tau != target_tau`
/// Given `self` with `Tau1` and `most_direct_parent` with `Tau2`, such that `Tau1` prefixes `Tau2`,
/// i.e. `Tau2 == Tau1 || SUFFIX`, derive node with `Tau3 = Tau2 || 1`
fn derive_right_nonfinal_child_of_with_partials(
&self,
params: &Params,
most_direct_parent: Tau,
partial_b: &G2Projective,
partial_f: &G2Projective,
mut rng: impl RngCore,
) -> Self {
let right_branch = most_direct_parent.right_child();
debug_assert!(self.tau.is_parent_of(&most_direct_parent));
debug_assert!(self.tau.is_parent_of(&right_branch));
debug_assert_ne!(self.tau, right_branch);
// n is height difference between self and the child
let n = right_branch.height() - self.tau.height();
// i is the index of the last bit we just added
let i = right_branch.height() - 1;
let delta = Scalar::random(&mut rng);
let a = self.a + G1Projective::generator() * delta;
let d0 = self.ds[n - 1];
let b = partial_b + d0 + (partial_f + params.fs[i]) * delta;
let ds = self
.ds
.iter()
.skip(n)
.zip(params.fs.iter().skip(right_branch.height()))
.map(|(d_i, f_i)| d_i + f_i * delta)
.collect();
let dh = self
.dh
.iter()
.zip(params.fh.iter())
.map(|(dh_i, fh_i)| dh_i + fh_i * delta)
.collect();
let e = self.e + params.h * delta;
Node {
tau: right_branch,
a,
b,
ds,
dh,
e,
}
}
// tau_bytes_len || tau || a || b || len_ds || ds || len_dh || dh || e
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let g1_elements = 1;
let g2_elements = self.ds.len() + self.dh.len() + 2;
let tau_bytes = self.tau.to_bytes();
// the extra 12 comes from the triple u32 we use for encoding lengths of tau, ds and dh
let mut bytes =
Vec::with_capacity(tau_bytes.len() + g1_elements * 48 + g2_elements * 96 + 12);
bytes.extend_from_slice(&((tau_bytes.len() as u32).to_be_bytes()));
bytes.extend_from_slice(&tau_bytes);
bytes.extend_from_slice(self.a.to_bytes().as_ref());
bytes.extend_from_slice(self.b.to_bytes().as_ref());
bytes.extend_from_slice(&((self.ds.len() as u32).to_be_bytes()));
for d_i in &self.ds {
bytes.extend_from_slice(d_i.to_bytes().as_ref());
}
bytes.extend_from_slice(&((self.dh.len() as u32).to_be_bytes()));
for dh_i in &self.dh {
bytes.extend_from_slice(dh_i.to_bytes().as_ref());
}
bytes.extend_from_slice(self.e.to_bytes().as_ref());
bytes
}
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, DkgError> {
// at the very least we require bytes for:
// - tau_len ( 4 )
// - tau ( could be 0 for root node )
// - a ( 48 )
// - b ( 96 )
// - length indication of ds ( 4 )
// - length indication of dh ( 4 )
// - e ( 96 )
if bytes.len() < 4 + 48 + 96 + 4 + 4 + 96 {
return Err(DkgError::new_deserialization_failure(
"Node",
"insufficient number of bytes provided",
));
}
let tau_len = u32::from_be_bytes((&bytes[..4]).try_into().unwrap()) as usize;
let mut i = 4;
let tau = Tau::try_from_bytes(&bytes[i..i + tau_len])?;
i += tau_len;
// perform another length check to account for bytes consumed by tau
if bytes[i..].len() < 48 + 96 + 4 + 4 + 96 {
return Err(DkgError::new_deserialization_failure(
"Node",
"insufficient number of bytes provided",
));
}
let a = deserialize_g1(&bytes[i..i + 48]).ok_or_else(|| {
DkgError::new_deserialization_failure("Node.a", "invalid curve point")
})?;
i += 48;
let b = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
DkgError::new_deserialization_failure("Node.b", "invalid curve point")
})?;
i += 96;
let ds_len = u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
i += 4;
if bytes[i..].len() < ds_len * 96 + 4 {
return Err(DkgError::new_deserialization_failure(
"Node",
"insufficient number of bytes provided (ds)",
));
}
let mut ds = Vec::with_capacity(ds_len);
for j in 0..ds_len {
let d_i = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
DkgError::new_deserialization_failure(
format!("Node.ds_{}", j),
"invalid curve point",
)
})?;
ds.push(d_i);
i += 96;
}
let dh_len = u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
i += 4;
if bytes[i..].len() != (dh_len + 1) * 96 {
return Err(DkgError::new_deserialization_failure(
"Node",
"insufficient number of bytes provided (dh)",
));
}
let mut dh = Vec::with_capacity(dh_len);
for j in 0..dh_len {
let dh_i = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
DkgError::new_deserialization_failure(
format!("Node.dh_{}", j),
"invalid curve point",
)
})?;
dh.push(dh_i);
i += 96;
}
let e = deserialize_g2(&bytes[i..]).ok_or_else(|| {
DkgError::new_deserialization_failure("Node.h", "invalid curve point")
})?;
Ok(Node {
tau,
a,
b,
ds,
dh,
e,
})
}
}
// produces public key and a decryption key for the root of the tree
pub fn keygen(params: &Params, mut rng: impl RngCore) -> (DecryptionKey, PublicKeyWithProof) {
let g1 = G1Projective::generator();
@@ -27,10 +317,11 @@ pub fn keygen(params: &Params, mut rng: impl RngCore) -> (DecryptionKey, PublicK
let a = g1 * rho;
let b = g2 * x + params.f0 * rho;
let ds = params.fs.iter().map(|f_i| f_i * rho).collect();
let dh = params.fh.iter().map(|fh_i| fh_i * rho).collect();
let e = params.h * rho;
let dk = DecryptionKey::new_root(a, b, dh, e);
let dk = DecryptionKey::new_root(Node::new_root(a, b, ds, dh, e));
let public_key = PublicKey(y);
let key_with_proof = PublicKeyWithProof {
@@ -60,20 +351,6 @@ pub struct PublicKeyWithProof {
pub(crate) proof: ProofOfDiscreteLog,
}
impl PemStorableKey for PublicKeyWithProof {
type Error = DkgError;
fn pem_type() -> &'static str {
"DKG PUBLIC KEY WITH PROOF"
}
fn to_bytes(&self) -> Vec<u8> {
self.to_bytes()
}
fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
Self::try_from_bytes(bytes)
}
}
impl PublicKeyWithProof {
pub fn verify(&self) -> bool {
self.key.verify(&self.proof)
@@ -137,157 +414,231 @@ impl PublicKeyWithProof {
#[zeroize(drop)]
#[cfg_attr(test, derive(PartialEq))]
pub struct DecryptionKey {
// g1^rho
pub(crate) a: G1Projective,
// g2^x * f0^rho
pub(crate) b: G2Projective,
// fh_i^rho, always lambda_h elements
pub(crate) dh: Vec<G2Projective>,
// h^rho
pub(crate) e: G2Projective,
}
impl PemStorableKey for DecryptionKey {
type Error = DkgError;
fn pem_type() -> &'static str {
"DKG DECRYPTION KEY"
}
fn to_bytes(&self) -> Vec<u8> {
self.to_bytes()
}
fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
Self::try_from_bytes(bytes)
}
// note that the nodes are ordered from "right" to "left"
pub(crate) nodes: Vec<Node>,
}
impl DecryptionKey {
fn new_root(a: G1Projective, b: G2Projective, dh: Vec<G2Projective>, e: G2Projective) -> Self {
DecryptionKey { a, b, dh, e }
fn new_root(root_node: Node) -> Self {
DecryptionKey {
nodes: vec![root_node],
}
}
fn current(&self) -> Result<&Node, DkgError> {
// we must have at least a single node, otherwise we have a malformed key
self.nodes.last().ok_or(DkgError::MalformedDecryptionKey)
}
pub fn current_epoch(&self, params: &Params) -> Result<Option<Epoch>, DkgError> {
let current_node = self.current()?;
if current_node.is_root() {
Ok(None)
} else {
Epoch::try_from_tau(&current_node.tau, params).map(Option::Some)
}
}
pub(crate) fn try_get_compatible_node(&self, epoch: Epoch) -> Result<&Node, DkgError> {
let tau = epoch.as_tau();
self.nodes
.iter()
.rev()
.find(|node| node.tau.is_parent_of(&tau))
.ok_or(DkgError::ExpiredKey)
}
pub fn try_update_to_next_epoch(
&mut self,
params: &Params,
mut rng: impl RngCore,
) -> Result<(), DkgError> {
if self.nodes.is_empty() {
return Err(DkgError::MalformedDecryptionKey);
}
let mut target_epoch = Epoch::new(0);
if self.nodes.len() == 1 && self.nodes[0].is_root() {
return self.try_update_to(target_epoch, params, &mut rng);
}
// unwrap is fine as we have asserted self.nodes is not empty
self.nodes.pop().unwrap();
if let Some(tail) = self.nodes.last() {
target_epoch = tail.tau.lowest_valid_epoch_child(params)?;
} else {
// essentially our key consisted of only a single node and it wasn't a root,
// so either it was malformed or we somehow reached the final epoch and wanted to update
// beyond that. Either way, update to l + 1 is impossible
return Err(DkgError::MalformedDecryptionKey);
}
self.try_update_to(target_epoch, params, &mut rng)
}
/// Attempts to update `self` to the provided `epoch`. If the update is not possible,
/// because the target was in the past or the key is malformed, an error is returned.
///
/// Note that this method mutates the key in place and if the original key was malformed,
/// there are no guarantees about its internal state post-call.
pub fn try_update_to(
&mut self,
target_epoch: Epoch,
params: &Params,
mut rng: impl RngCore,
) -> Result<(), DkgError> {
if self.nodes.is_empty() {
// somehow we have an empty decryption key
return Err(DkgError::MalformedDecryptionKey);
}
// makes it easier to work with since we will be generating non-leaf nodes
let target_tau = target_epoch.as_tau();
let current_tau = &self.current()?.tau;
if current_tau == &target_tau {
// our key is already updated to the target
return Ok(());
}
if current_tau > &target_tau {
// we cannot derive keys for past epochs
return Err(DkgError::TargetEpochUpdateInThePast);
}
// drop the nodes that are no longer required and get the most direct parent for the target epoch available
let mut parent = loop {
// if pop() fails the key is malformed since we checked that the target_epoch > current_epoch,
// hence the update should have been possible
let tail = self.nodes.pop().ok_or(DkgError::MalformedDecryptionKey)?;
if tail.tau.is_parent_of(&target_tau) {
break tail;
}
};
// essentially the case of updating epoch n to n + 1, where n is even;
// in that case the last two nodes are [..., epoch_{n+1}, epoch_n]
// so we just have to reblind the n+1 node and we're done
if parent.tau == target_tau {
parent.reblind(params, &mut rng);
self.nodes.push(parent);
return Ok(());
}
// accumulators, note that the previous elements have already been included by the parent,
// i.e. for example for parent at height l <= n, b = g2^x * f0^rho * d1^{tau_1} * ... * dl^{tau_l}
// new_b_accumulator = b * d1^{tau_1} * d2^{tau_2} * ... * dn^{tau_n}
// new_f_accumulator = f0 * f1^{tau_1} * f2^{tau_2} * ... * fn^{tau_n} (up to lambda_t)
let mut new_b_accumulator = parent.b;
let mut new_f_accumulator = parent.tau.evaluate_partial_f(params);
let parent_height = parent.tau.height();
// path from the parent to the child
for (n, bit) in target_tau
.0
.iter()
.by_vals()
.skip(parent.tau.height())
.enumerate()
{
// ith bit of the [child] epoch
// note that n represents height difference between parent and the current bit
let i = n + parent_height;
// if the bit is NOT set, push the right '1' subtree (for future keys)
// so for example if given parent with some `PREFIX` tau and target_epoch being `PREFIX || 010`,
// in the first loop iteration we're going to look at bit `0` and
// derive child node `PREFIX || 1` so that in the future we could derive keys for all other epochs starting with `PREFIX || 1`
// in the next loop iteration we're going to look at bit `1` and simply update the accumulators,
// as we don't need to generate any "left" nodes as all of them would have constructed epochs that are already in the past
// finally, in the last iteration, we look at the bit `0` and derive node `PREFIX || 011`,
// i.e. the one that FOLLOWS the target node.
if !bit {
let direct_parent = target_tau.try_get_parent_at_height(i)?;
self.nodes
.push(parent.derive_right_nonfinal_child_of_with_partials(
params,
direct_parent,
&new_b_accumulator,
&new_f_accumulator,
&mut rng,
));
} else {
// only update the accumulators when the bit is set, as d^0 == identity, so there's
// no point in doing anything else;
// note that we don't have to generate any new nodes when going into the right branch
// of the tree as everything on the left would have been in the past, so we don't care about them
new_b_accumulator += parent.ds[n]; // add d0
new_f_accumulator += params.fs[i]; // f_i
}
}
self.nodes.push(parent.derive_target_child_with_partials(
params,
target_epoch.as_tau(),
&new_b_accumulator,
&new_f_accumulator,
&mut rng,
));
Ok(())
}
pub fn to_bytes(&self) -> Vec<u8> {
let g1_elements = 1;
let g2_elements = self.dh.len() + 2;
let num_nodes = self.nodes.len() as u32;
// the extra 8 comes from the triple u32 we use for encoding lengths of ds and dh
let mut bytes = Vec::with_capacity(g1_elements * 48 + g2_elements * 96 + 8);
// unfortunately we're not going to know the expected capacity
let mut bytes = Vec::new();
bytes.extend_from_slice(&num_nodes.to_be_bytes());
bytes.extend_from_slice(self.a.to_bytes().as_ref());
bytes.extend_from_slice(self.b.to_bytes().as_ref());
bytes.extend_from_slice(&((self.dh.len() as u32).to_be_bytes()));
for dh_i in &self.dh {
bytes.extend_from_slice(dh_i.to_bytes().as_ref());
for node in &self.nodes {
let mut node_bytes = node.to_bytes();
bytes.extend_from_slice(&((node_bytes.len() as u32).to_be_bytes()));
bytes.append(&mut node_bytes)
}
bytes.extend_from_slice(self.e.to_bytes().as_ref());
bytes
}
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, DkgError> {
// at the very least we require bytes for:
// - a ( 48 )
// - b ( 96 )
// - length indication of dh ( 4 )
// - e ( 96 )
if bytes.len() < 48 + 96 + 4 + 96 {
pub fn try_from_bytes(b: &[u8]) -> Result<Self, DkgError> {
// we have to be able to read the length of nodes
if b.len() < 4 {
return Err(DkgError::new_deserialization_failure(
"Node",
"DecryptionKey",
"insufficient number of bytes provided",
));
}
let nodes_len = u32::from_be_bytes([b[0], b[1], b[2], b[3]]) as usize;
let mut nodes = Vec::with_capacity(nodes_len);
let mut i = 0;
let a = deserialize_g1(&bytes[i..i + 48]).ok_or_else(|| {
DkgError::new_deserialization_failure("Node.a", "invalid curve point")
})?;
i += 48;
let mut i = 4;
for _ in 0..nodes_len {
// check if we can actually read the length...
if b[i..].len() < 4 {
return Err(DkgError::new_deserialization_failure(
"DecryptionKey.Node",
"insufficient number of bytes provided for BTE Node recovery",
));
}
let b = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
DkgError::new_deserialization_failure("Node.b", "invalid curve point")
})?;
i += 96;
let node_bytes = u32::from_be_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) as usize;
if b[i + 4..].len() < node_bytes {
return Err(DkgError::new_deserialization_failure(
"DecryptionKey.Node",
"insufficient number of bytes provided for BTE Node recovery",
));
}
i += 4;
let dh_len = u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
i += 4;
if bytes[i..].len() != (dh_len + 1) * 96 {
return Err(DkgError::new_deserialization_failure(
"Node",
"insufficient number of bytes provided (dh)",
));
let node = Node::try_from_bytes(&b[i..i + node_bytes])?;
nodes.push(node);
i += node_bytes;
}
let mut dh = Vec::with_capacity(dh_len);
for j in 0..dh_len {
let dh_i = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
DkgError::new_deserialization_failure(
format!("Node.dh_{}", j),
"invalid curve point",
)
})?;
dh.push(dh_i);
i += 96;
}
let e = deserialize_g2(&bytes[i..]).ok_or_else(|| {
DkgError::new_deserialization_failure("Node.h", "invalid curve point")
})?;
Ok(Self { a, b, dh, e })
}
}
pub struct KeyPair {
pub(crate) private_key: DecryptionKey,
pub(crate) public_key: PublicKeyWithProof,
}
impl KeyPair {
pub fn new(params: &Params, rng: impl RngCore) -> Self {
let (dk, pk) = keygen(params, rng);
Self {
private_key: dk,
public_key: pk,
}
}
pub fn private_key(&self) -> &DecryptionKey {
&self.private_key
}
pub fn public_key(&self) -> &PublicKeyWithProof {
&self.public_key
}
pub fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Result<Self, DkgError> {
Ok(KeyPair {
private_key: DecryptionKey::try_from_bytes(priv_bytes)?,
public_key: PublicKeyWithProof::try_from_bytes(pub_bytes)?,
})
}
}
impl PemStorableKeyPair for KeyPair {
type PrivatePemKey = DecryptionKey;
type PublicPemKey = PublicKeyWithProof;
fn private_key(&self) -> &Self::PrivatePemKey {
self.private_key()
}
fn public_key(&self) -> &Self::PublicPemKey {
self.public_key()
}
fn from_keys(private_key: Self::PrivatePemKey, public_key: Self::PublicPemKey) -> Self {
KeyPair {
private_key,
public_key,
}
Ok(DecryptionKey { nodes })
}
}
@@ -295,8 +646,163 @@ impl PemStorableKeyPair for KeyPair {
mod tests {
use super::*;
use crate::bte::setup;
use bitvec::bitvec;
use bitvec::order::Msb0;
use rand_core::SeedableRng;
#[test]
#[ignore] // expensive test
fn basic_coverage_nodes() {
// it's some basic test I've been performing when writing the update function, but figured
// might as well put it into a unit test. note that it doesn't check the entire structure,
// but just the few last nodes of low height
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, _) = keygen(&params, &mut rng);
let root_node_copy = dk.nodes.clone();
// this is a root node
assert_eq!(dk.nodes.len(), 1);
assert!(dk.nodes[0].is_root());
// we have to have a node for right branch on each height (1, 01, 001, ... etc)
// plus an additional one for the two left-most leaves (epochs "0" and "1")
dk.try_update_to(Epoch::new(0), &params, &mut rng).unwrap();
assert_eq!(dk.nodes.len(), 33);
let expected_last = Tau::new(0);
// (and yes, I had to look up those names in a thesaurus)
let expected_penultimate = Tau::new(1);
// note that this value is 31bit long
let expected_antepenultimate = Tau(bitvec![u32, Msb0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1
]);
let mut nodes_iter = dk.nodes.iter().rev();
assert_eq!(expected_last, nodes_iter.next().unwrap().tau);
assert_eq!(expected_penultimate, nodes_iter.next().unwrap().tau);
assert_eq!(expected_antepenultimate, nodes_iter.next().unwrap().tau);
let mut epoch_zero_nodes = dk.nodes.clone();
// nodes for epoch1 should be identical for those for epoch0 minus the 00..00 leaf
dk.try_update_to(Epoch::new(1), &params, &mut rng).unwrap();
assert_eq!(dk.nodes.len(), 32);
epoch_zero_nodes.pop().unwrap();
assert_eq!(
epoch_zero_nodes
.iter()
.map(|node| node.tau.clone())
.collect::<Vec<_>>(),
dk.nodes
.iter()
.map(|node| node.tau.clone())
.collect::<Vec<_>>()
);
dk.try_update_to(Epoch::new(2), &params, &mut rng).unwrap();
dk.try_update_to(Epoch::new(3), &params, &mut rng).unwrap();
dk.try_update_to(Epoch::new(4), &params, &mut rng).unwrap();
let expected_last = Tau::new(4);
let expected_penultimate = Tau::new(5);
let expected_antepenultimate = Tau(bitvec![u32, Msb0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1
]);
let expected_preantepenultimate = Tau(bitvec![u32, Msb0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
]);
assert_eq!(dk.nodes.len(), 32);
let mut nodes_iter = dk.nodes.iter().rev();
assert_eq!(expected_last, nodes_iter.next().unwrap().tau);
assert_eq!(expected_penultimate, nodes_iter.next().unwrap().tau);
assert_eq!(expected_antepenultimate, nodes_iter.next().unwrap().tau);
assert_eq!(expected_preantepenultimate, nodes_iter.next().unwrap().tau);
// the result should be the same of regardless if we update incrementally or go to the target immediately
let mut new_root = DecryptionKey {
nodes: root_node_copy,
};
new_root
.try_update_to(Epoch::new(4), &params, &mut rng)
.unwrap();
assert_eq!(
dk.nodes
.iter()
.map(|node| node.tau.clone())
.collect::<Vec<_>>(),
new_root
.nodes
.iter()
.map(|node| node.tau.clone())
.collect::<Vec<_>>()
);
// getting expected nodes for those epochs is non-trivial for test purposes, but the last node
// should ALWAYS be equal to the target epoch
dk.try_update_to(Epoch::new(42), &params, &mut rng).unwrap();
assert_eq!(dk.nodes.last().unwrap().tau, Tau::new(42));
dk.try_update_to(Epoch::new(123456), &params, &mut rng)
.unwrap();
assert_eq!(dk.nodes.last().unwrap().tau, Tau::new(123456));
dk.try_update_to(Epoch::new(3292547435), &params, &mut rng)
.unwrap();
assert_eq!(dk.nodes.last().unwrap().tau, Tau::new(3292547435));
// trying to go to past epochs fails
assert!(dk
.try_update_to(Epoch::new(531), &params, &mut rng)
.is_err())
}
#[test]
#[ignore] // expensive test
fn updating_to_next_epoch() {
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, _) = keygen(&params, &mut rng);
// for root node current epoch is `None`
assert_eq!(None, dk.current_epoch(&params).unwrap());
// for root node it should result in epoch 0
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(Some(Epoch::new(0)), dk.current_epoch(&params).unwrap());
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(Some(Epoch::new(1)), dk.current_epoch(&params).unwrap());
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(Some(Epoch::new(2)), dk.current_epoch(&params).unwrap());
// if we start from some non-root epoch, it should result in l + 1
dk.try_update_to(Epoch::new(42), &params, &mut rng).unwrap();
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(Some(Epoch::new(43)), dk.current_epoch(&params).unwrap());
dk.try_update_to(Epoch::new(12345), &params, &mut rng)
.unwrap();
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(Some(Epoch::new(12346)), dk.current_epoch(&params).unwrap());
dk.try_update_to(Epoch::new(3292547435), &params, &mut rng)
.unwrap();
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(
Some(Epoch::new(3292547436)),
dk.current_epoch(&params).unwrap()
);
}
#[test]
fn public_key_with_proof_roundtrip() {
let params = setup();
@@ -310,4 +816,64 @@ mod tests {
assert_eq!(pk, recovered)
}
#[test]
#[ignore] // expensive test
fn bte_node_roundtrip() {
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, _) = keygen(&params, &mut rng);
let root_node = dk.nodes[0].clone();
let bytes = root_node.to_bytes();
let recovered = Node::try_from_bytes(&bytes).unwrap();
assert_eq!(root_node, recovered);
dk.try_update_to(Epoch::new(3292547435), &params, &mut rng)
.unwrap();
for node in &dk.nodes {
let bytes = node.to_bytes();
let recovered = Node::try_from_bytes(&bytes).unwrap();
assert_eq!(node, &recovered);
}
}
#[test]
#[ignore] // expensive test
fn decryption_key_node_roundtrip() {
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, _) = keygen(&params, &mut rng);
let bytes = dk.to_bytes();
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
assert_eq!(dk, recovered);
dk.try_update_to(Epoch::new(0), &params, &mut rng).unwrap();
let bytes = dk.to_bytes();
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
assert_eq!(dk, recovered);
dk.try_update_to(Epoch::new(1), &params, &mut rng).unwrap();
let bytes = dk.to_bytes();
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
assert_eq!(dk, recovered);
dk.try_update_to(Epoch::new(42), &params, &mut rng).unwrap();
let bytes = dk.to_bytes();
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
assert_eq!(dk, recovered);
dk.try_update_to(Epoch::new(3292547435), &params, &mut rng)
.unwrap();
let bytes = dk.to_bytes();
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
assert_eq!(dk, recovered);
}
}
+394 -6
View File
@@ -1,11 +1,17 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::utils::hash_g2;
use crate::error::DkgError;
use crate::utils::{hash_g2, RandomOracleBuilder};
use crate::{Chunk, Share};
use bls12_381::{G1Affine, G2Affine, G2Prepared, G2Projective, Gt};
use bitvec::field::BitField;
use bitvec::order::Msb0;
use bitvec::vec::BitVec;
use bitvec::view::BitView;
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt};
use group::Curve;
use lazy_static::lazy_static;
use zeroize::Zeroize;
pub mod encryption;
pub mod keys;
@@ -29,6 +35,10 @@ lazy_static! {
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
const SETUP_DOMAIN: &[u8] = b"NYM_COCONUT_NIDKG_V01_CS01_WITH_BLS12381G2_XMD:SHA-256_SSWU_RO_SETUP";
// this particular domain is not for curve hashing, but might as well also follow the same naming pattern
const TREE_TAU_EXTENSION_DOMAIN: &[u8] = b"NYM_COCONUT_NIDKG_V01_CS01_SHA-256_TREE_EXTENSION";
const MAX_EPOCHS_EXP: usize = 32;
const HASH_SECURITY_PARAM: usize = 256;
// note: CHUNK_BYTES * NUM_CHUNKS must equal to SCALAR_SIZE
@@ -39,17 +49,253 @@ pub const SCALAR_SIZE: usize = 32;
/// In paper B; number of distinct chunks
pub const CHUNK_SIZE: usize = 1 << (CHUNK_BYTES << 3);
// considers all lambda_h bits
pub fn evaluate_f(params: &Params) -> G2Projective {
params.fh.iter().fold(params.f0, |acc, f_i| acc + f_i)
pub(crate) type EpochStore = u32;
#[derive(Clone, Debug, PartialEq, PartialOrd)]
// None empty bitvec implies this is a root node
pub(crate) struct Tau(BitVec<EpochStore, Msb0>);
impl Tau {
pub fn new_root() -> Self {
Tau(BitVec::new())
}
// TODO: perhaps this should be explicitly moved to some test module
#[cfg(test)]
pub(crate) fn new(epoch: EpochStore) -> Self {
Tau(epoch.view_bits().to_bitvec())
}
#[allow(unused)]
pub fn left_child(&self) -> Self {
let mut child = self.0.clone();
child.push(false);
Tau(child)
}
pub fn right_child(&self) -> Self {
let mut child = self.0.clone();
child.push(true);
Tau(child)
}
pub fn is_leaf(&self, params: &Params) -> bool {
self.height() == params.lambda_t
}
pub fn try_get_parent_at_height(&self, height: usize) -> Result<Self, DkgError> {
if height > self.0.len() {
return Err(DkgError::NotAValidParent);
}
Ok(Tau(self.0[..height].to_bitvec()))
}
// essentially is this tau prefixing the other
pub fn is_parent_of(&self, other: &Tau) -> bool {
if self.0.len() > other.0.len() {
return false;
}
for (i, b) in self.0.iter().enumerate() {
if b != other.0[i] {
return false;
}
}
true
}
pub fn lowest_valid_epoch_child(&self, params: &Params) -> Result<Epoch, DkgError> {
if self.0.len() > params.lambda_t {
// this node is already BELOW a valid leaf-epoch node. it can only happen
// if either some invariant was broken or additional data was pushed to `tau`
// in order compute some intermediate results, but in that case this method should have
// never been called anyway. tl;dr: if this is called, the underlying key is malformed
return Err(DkgError::NotAValidParent);
}
let mut child = self.0.clone();
for _ in 0..(params.lambda_t - self.0.len()) {
child.push(false)
}
// the unwrap here is fine as we ensure we have exactly `params.tree_height` bits here
// (we could just propagate the error instead of unwraping and putting it behind an `Ok` anyway
// but I'd prefer to just blow up since this would be a serious error
Ok(Epoch::try_from_tau(&Tau(child), params).unwrap())
}
pub fn height(&self) -> usize {
self.0.len()
}
fn extend(
&self,
rr: &[G1Projective; NUM_CHUNKS],
ss: &[G1Projective; NUM_CHUNKS],
cc: &[[G1Projective; NUM_CHUNKS]],
) -> Self {
let mut random_oracle_builder = RandomOracleBuilder::new(TREE_TAU_EXTENSION_DOMAIN);
random_oracle_builder.update_with_g1_elements(rr.iter());
random_oracle_builder.update_with_g1_elements(ss.iter());
for ciphertext_chunks in cc {
random_oracle_builder.update_with_g1_elements(ciphertext_chunks.iter());
}
let tau_mem = self.0.as_raw_slice();
assert_eq!(tau_mem.len(), 1, "tau length invariant was broken");
random_oracle_builder.update(tau_mem[0].to_be_bytes());
let oracle_output = random_oracle_builder.finalize();
debug_assert_eq!(oracle_output.len() * 8, HASH_SECURITY_PARAM);
let mut extended_tau = self.clone();
for byte in oracle_output {
extended_tau
.0
.extend_from_bitslice(byte.view_bits::<Msb0>())
}
extended_tau
}
// considers all lambda_t + lambda_h bits
fn evaluate_f(&self, params: &Params) -> G2Projective {
self.0
.iter()
.by_vals()
.zip(params.fs.iter().chain(params.fh.iter()))
.filter(|(i, _)| *i)
.map(|(_, f_i)| f_i)
.fold(params.f0, |acc, f_i| acc + f_i)
}
// only considers up to lambda_t bits
fn evaluate_partial_f(&self, params: &Params) -> G2Projective {
self.0
.iter()
.by_vals()
.zip(params.fs.iter())
.filter(|(i, _)| *i)
.map(|(_, f_i)| f_i)
.fold(params.f0, |acc, f_i| acc + f_i)
}
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let len_bytes = (self.0.len() as u32).to_be_bytes();
len_bytes
.into_iter()
.chain(self.0.chunks(8).map(BitField::load_be))
.collect()
}
pub(crate) fn try_from_bytes(b: &[u8]) -> Result<Self, DkgError> {
if b.len() < 4 {
return Err(DkgError::new_deserialization_failure(
"Tau",
"insufficient number of bytes provided",
));
}
let tau_len = u32::from_be_bytes([b[0], b[1], b[2], b[3]]) as usize;
// maximum theoretical length
if tau_len > MAX_EPOCHS_EXP + HASH_SECURITY_PARAM {
return Err(DkgError::new_deserialization_failure(
"Tau",
format!(
"malformed length {} is greater than maximum {}",
tau_len,
MAX_EPOCHS_EXP + HASH_SECURITY_PARAM
),
));
}
if tau_len == 0 {
if b.len() != 4 {
Err(DkgError::new_deserialization_failure(
"Tau",
"malformed bytes",
))
} else {
Ok(Tau::new_root())
}
} else if b.len() == 4 {
Err(DkgError::new_deserialization_failure(
"Tau",
"insufficient number of bytes provided",
))
} else {
let mut inner = BitVec::repeat(false, tau_len);
for (slot, &byte) in inner.chunks_mut(8).zip(b[4..].iter()) {
slot.store_be(byte);
}
Ok(Tau(inner))
}
}
}
impl Zeroize for Tau {
fn zeroize(&mut self) {
for v in self.0.as_raw_mut_slice() {
v.zeroize()
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd)]
pub struct Epoch(EpochStore);
impl Epoch {
pub fn new(value: EpochStore) -> Self {
Epoch(value)
}
pub(crate) fn as_tau(&self) -> Tau {
(*self).into()
}
pub(crate) fn as_extended_tau(
&self,
rr: &[G1Projective; NUM_CHUNKS],
ss: &[G1Projective; NUM_CHUNKS],
cc: &[[G1Projective; NUM_CHUNKS]],
) -> Tau {
self.as_tau().extend(rr, ss, cc)
}
pub(crate) fn try_from_tau(tau: &Tau, params: &Params) -> Result<Self, DkgError> {
if !tau.is_leaf(params) {
Err(DkgError::MalformedEpoch)
} else {
Ok(Epoch(tau.0.load_be()))
}
}
}
impl From<Epoch> for Tau {
fn from(epoch: Epoch) -> Self {
Tau(epoch.0.view_bits().to_bitvec())
}
}
impl From<EpochStore> for Epoch {
fn from(epoch: EpochStore) -> Self {
Epoch(epoch)
}
}
pub struct Params {
/// Maximum size of an epoch, in bits.
pub lambda_t: usize,
/// Security parameter of our $H_{\Lamda_H}$ hash function
pub lambda_h: usize,
// keeping f0 separate from the rest of the curve points makes it easier to work with tau
f0: G2Projective,
fh: Vec<G2Projective>, // f_{lambda_h}, f_{lambda_h+1}, .... f_{lambda_h} in the paper
fs: Vec<G2Projective>, // f_1, f_2, .... f_{lambda_t} in the paper
fh: Vec<G2Projective>, // f_{lambda_t+1}, f_{lambda_t+1}, .... f_{lambda_t+lambda_h} in the paper
h: G2Projective,
/// Precomputed `h` used for the miller loop
@@ -59,6 +305,10 @@ pub struct Params {
pub fn setup() -> Params {
let f0 = hash_g2(b"f0", SETUP_DOMAIN);
let fs = (1..=MAX_EPOCHS_EXP)
.map(|i| hash_g2(format!("f{}", i), SETUP_DOMAIN))
.collect();
let fh = (0..HASH_SECURITY_PARAM)
.map(|i| hash_g2(format!("fh{}", i), SETUP_DOMAIN))
.collect();
@@ -66,10 +316,148 @@ pub fn setup() -> Params {
let h = hash_g2(b"h", SETUP_DOMAIN);
Params {
lambda_t: MAX_EPOCHS_EXP,
lambda_h: HASH_SECURITY_PARAM,
f0,
fs,
fh,
h,
_h_prepared: G2Prepared::from(h.to_affine()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use bitvec::bitvec;
use bitvec::order::Msb0;
#[test]
fn creating_tau_from_epoch() {
assert!(Tau::new_root().0.is_empty());
let zero = Tau::new(0);
assert!(zero.0.iter().by_vals().all(|b| !b));
let one = Tau::new(1);
let mut iter = one.0.iter().by_vals();
// first 31 bits are 0, the last one is 1
for _ in 0..31 {
assert!(!iter.next().unwrap())
}
assert!(iter.next().unwrap());
// 101010 in binary
let forty_two = Tau::new(42);
// first 26 bits are not set
let mut iter = forty_two.0.iter().by_vals();
for _ in 0..26 {
assert!(!iter.next().unwrap())
}
assert!(iter.next().unwrap());
assert!(!iter.next().unwrap());
assert!(iter.next().unwrap());
assert!(!iter.next().unwrap());
assert!(iter.next().unwrap());
assert!(!iter.next().unwrap());
// value that requires an actual u32 (i.e. takes 4 bytes to represent)
// 11000100_01000000_01001001_01101011 in binary
let big_val = Tau::new(3292547435);
let expected = bitvec![u32, Msb0;
1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1,
0, 1, 1
];
assert_eq!(expected, big_val.0)
}
#[test]
fn getting_parent_at_height() {
let tau = Tau(bitvec![u32, Msb0; 1,0,1,1,0,0,1]);
let expected_0 = Tau(BitVec::new());
let expected_1 = Tau(bitvec![u32, Msb0; 1]);
let expected_5 = Tau(bitvec![u32, Msb0; 1,0,1,1,0]);
assert_eq!(expected_0, tau.try_get_parent_at_height(0).unwrap());
assert_eq!(expected_1, tau.try_get_parent_at_height(1).unwrap());
assert_eq!(expected_5, tau.try_get_parent_at_height(5).unwrap());
assert_eq!(tau, tau.try_get_parent_at_height(7).unwrap());
assert!(tau.try_get_parent_at_height(8).is_err())
}
#[test]
fn converting_tau_to_epoch() {
let params = setup();
let tau0: Tau = Epoch::new(0).into();
let tau1: Tau = Epoch::new(1).into();
let tau42: Tau = Epoch::new(42).into();
let tau_big: Tau = Epoch::new(3292547435).into();
assert_eq!(Epoch::new(0), Epoch::try_from_tau(&tau0, &params).unwrap());
assert_eq!(Epoch::new(1), Epoch::try_from_tau(&tau1, &params).unwrap());
assert_eq!(
Epoch::new(42),
Epoch::try_from_tau(&tau42, &params).unwrap()
);
assert_eq!(
Epoch::new(3292547435),
Epoch::try_from_tau(&tau_big, &params).unwrap()
);
assert!(Epoch::try_from_tau(&Tau(BitVec::new()), &params).is_err());
assert!(Epoch::try_from_tau(&Tau(bitvec![u32, Msb0; 1,0,1,1,0]), &params).is_err());
let _31bit_tau = Tau(bitvec![u32, Msb0;
1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1,
0, 1
]);
assert!(Epoch::try_from_tau(&_31bit_tau, &params).is_err());
let _33bit_tau = Tau(bitvec![u32, Msb0;
1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1,
0, 1, 1, 0
]);
assert!(Epoch::try_from_tau(&_33bit_tau, &params).is_err());
}
#[test]
fn tau_roundtrip() {
let good_taus = vec![
Tau::new_root(),
Tau::new(0),
Tau::new(1),
Tau::new(2),
Tau::new(42),
Tau::new(123456),
Tau::new(3292547435),
Tau::new(u32::MAX),
];
for tau in good_taus {
let bytes = tau.to_bytes();
let recovered = Tau::try_from_bytes(&bytes).unwrap();
assert_eq!(tau, recovered);
}
// more valid variants
let mut another_tau = Tau::new(u32::MAX);
another_tau.0.push(true);
another_tau.0.push(false);
another_tau.0.push(true);
let bytes = another_tau.to_bytes();
let recovered = Tau::try_from_bytes(&bytes).unwrap();
assert_eq!(another_tau, recovered);
// ensure there are no panics
let big_length_bytes = [255, 255, 255, 255, 42];
assert!(Tau::try_from_bytes(&big_length_bytes).is_err());
assert!(Tau::try_from_bytes(&[]).is_err());
assert!(Tau::try_from_bytes(&[1, 1, 1, 1]).is_err());
assert!(Tau::try_from_bytes(&[0, 0, 0, 1]).is_err());
assert!(Tau::try_from_bytes(&[1, 0, 0, 0]).is_err());
assert!(Tau::try_from_bytes(&[1, 0, 0]).is_err());
}
}
+39 -6
View File
@@ -3,7 +3,9 @@
use crate::bte::proof_chunking::ProofOfChunking;
use crate::bte::proof_sharing::ProofOfSecretSharing;
use crate::bte::{encrypt_shares, proof_chunking, proof_sharing, Ciphertexts, Params, PublicKey};
use crate::bte::{
encrypt_shares, proof_chunking, proof_sharing, Ciphertexts, Epoch, Params, PublicKey,
};
use crate::error::DkgError;
use crate::interpolation::polynomial::{Polynomial, PublicCoefficients};
use crate::interpolation::{
@@ -32,6 +34,7 @@ impl Dealing {
params: &Params,
dealer_index: NodeIndex,
threshold: Threshold,
epoch: Epoch,
// BTreeMap ensures the keys are sorted by their indices
receivers: &BTreeMap<NodeIndex, PublicKey>,
prior_resharing_secret: Option<Scalar>,
@@ -55,7 +58,8 @@ impl Dealing {
.collect::<Vec<_>>();
let ordered_public_keys = receivers.values().copied().collect::<Vec<_>>();
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, params, &mut rng);
let (ciphertexts, hazmat) =
encrypt_shares(&remote_share_key_pairs, epoch, params, &mut rng);
// create proofs of knowledge
let chunking_instance = proof_chunking::Instance::new(&ordered_public_keys, &ciphertexts);
@@ -104,6 +108,7 @@ impl Dealing {
pub fn verify(
&self,
params: &Params,
epoch: Epoch,
threshold: Threshold,
receivers: &BTreeMap<NodeIndex, PublicKey>,
prior_resharing_public: Option<G2Projective>,
@@ -129,7 +134,7 @@ impl Dealing {
});
}
if !self.ciphertexts.verify_integrity(params) {
if !self.ciphertexts.verify_integrity(params, epoch) {
return Err(DkgError::FailedCiphertextIntegrityCheck);
}
@@ -364,18 +369,32 @@ mod tests {
full_keys.push((dk, pk))
}
// start off in a defined epoch (i.e. not root);
let epoch = Epoch::new(2);
let dealings = node_indices
.iter()
.map(|&dealer_index| {
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0
Dealing::create(
&mut rng,
&params,
dealer_index,
threshold,
epoch,
&receivers,
None,
)
.0
})
.collect::<Vec<_>>();
let mut derived_secrets = Vec::new();
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let shares = dealings
.iter()
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, epoch, None).unwrap())
.collect();
derived_secrets.push(
combine_shares(shares, &receivers.keys().copied().collect::<Vec<_>>()).unwrap(),
@@ -418,10 +437,22 @@ mod tests {
full_keys.push((dk, pk))
}
// start off in a defined epoch (i.e. not root);
let epoch = Epoch::new(2);
let dealings = node_indices
.iter()
.map(|&dealer_index| {
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0
Dealing::create(
&mut rng,
&params,
dealer_index,
threshold,
epoch,
&receivers,
None,
)
.0
})
.collect::<Vec<_>>();
@@ -447,6 +478,7 @@ mod tests {
let parties = 5;
let threshold = ((parties as f32 * 2.) / 3. + 1.) as Threshold;
let node_indices = (1..=parties).collect::<Vec<_>>();
let epoch = Epoch::new(2);
let mut receivers = BTreeMap::new();
for index in &node_indices {
@@ -459,6 +491,7 @@ mod tests {
&params,
node_indices[0],
threshold,
epoch,
&receivers,
None,
);
+50 -11
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use bls12_381::{G2Projective, Scalar};
use dkg::bte::{decrypt_share, keygen, setup};
use dkg::bte::{decrypt_share, keygen, setup, Epoch};
use dkg::interpolation::perform_lagrangian_interpolation_at_origin;
use dkg::{combine_shares, try_recover_verification_keys, Dealing};
use rand_core::SeedableRng;
@@ -32,6 +32,9 @@ fn single_sender() {
full_keys.push((dk, pk))
}
// start off in a defined epoch (i.e. not root);
let epoch = Epoch::new(2);
// TODO: HERE BE SERIALIZATION / DESERIALIZATION THAT'S NOT IMPLEMENTED YET
// verify remote proofs of key possession
for key in full_keys.iter() {
@@ -43,20 +46,23 @@ fn single_sender() {
&params,
node_indices[0],
threshold,
epoch,
&receivers,
None,
);
dealing
.verify(&params, threshold, &receivers, None)
.verify(&params, epoch, threshold, &receivers, None)
.unwrap();
// make sure each share is actually decryptable (even though proofs say they must be, perform this sanity check)
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
let _recovered = decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap();
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let _recovered = decrypt_share(dk, i, &dealing.ciphertexts, epoch, None).unwrap();
}
// and for good measure, check that the dealer's share matches decryption result
let recovered_dealer = decrypt_share(&full_keys[0].0, 0, &dealing.ciphertexts, None).unwrap();
let recovered_dealer =
decrypt_share(&full_keys[0].0, 0, &dealing.ciphertexts, epoch, None).unwrap();
assert_eq!(recovered_dealer, dealer_share.unwrap());
}
@@ -81,6 +87,9 @@ fn full_threshold_secret_sharing() {
full_keys.push((dk, pk))
}
// start off in a defined epoch (i.e. not root);
let epoch = Epoch::new(2);
// TODO: HERE BE SERIALIZATION / DESERIALIZATION THAT'S NOT IMPLEMENTED YET
// verify remote proofs of key possession
for key in full_keys.iter() {
@@ -90,12 +99,21 @@ fn full_threshold_secret_sharing() {
let dealings = node_indices
.iter()
.map(|&dealer_index| {
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0
Dealing::create(
&mut rng,
&params,
dealer_index,
threshold,
epoch,
&receivers,
None,
)
.0
})
.collect::<Vec<_>>();
for dealing in dealings.iter() {
dealing
.verify(&params, threshold, &receivers, None)
.verify(&params, epoch, threshold, &receivers, None)
.unwrap();
}
@@ -107,9 +125,11 @@ fn full_threshold_secret_sharing() {
let mut derived_secrets = Vec::new();
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let shares = dealings
.iter()
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, epoch, None).unwrap())
.collect();
// we know dealer_share matches, but it would be inconvenient to try to put them in here,
@@ -163,10 +183,22 @@ fn full_threshold_secret_resharing() {
full_keys.push((dk, pk))
}
// start off in a defined epoch (i.e. not root);
let epoch = Epoch::new(2);
let first_dealings = node_indices
.iter()
.map(|&dealer_index| {
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0
Dealing::create(
&mut rng,
&params,
dealer_index,
threshold,
epoch,
&receivers,
None,
)
.0
})
.collect::<Vec<_>>();
@@ -176,9 +208,11 @@ fn full_threshold_secret_resharing() {
let mut derived_secrets = Vec::new();
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let shares = first_dealings
.iter()
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, epoch, None).unwrap())
.collect();
let recovered_secret =
@@ -193,6 +227,8 @@ fn full_threshold_secret_resharing() {
])
.unwrap();
let next_epoch = Epoch::new(3);
// attempt to create resharing dealings!
let resharing_dealings = node_indices
.iter()
@@ -203,6 +239,7 @@ fn full_threshold_secret_resharing() {
&params,
dealer_index,
threshold,
next_epoch,
&receivers,
Some(*prior_secret),
)
@@ -212,7 +249,7 @@ fn full_threshold_secret_resharing() {
for (reshared_dealing, prior_vk) in resharing_dealings.iter().zip(recovered_partials.iter()) {
reshared_dealing
.verify(&params, threshold, &receivers, Some(*prior_vk))
.verify(&params, next_epoch, threshold, &receivers, Some(*prior_vk))
.unwrap();
}
@@ -222,9 +259,11 @@ fn full_threshold_secret_resharing() {
let mut reshared_secrets = Vec::new();
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
dk.try_update_to(next_epoch, &params, &mut rng).unwrap();
let shares = resharing_dealings
.iter()
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, next_epoch, None).unwrap())
.collect();
let recovered_secret =
-13
View File
@@ -36,7 +36,6 @@ pub struct NymContracts {
pub bandwidth_claim_contract_address: Option<String>,
pub coconut_bandwidth_contract_address: Option<String>,
pub multisig_contract_address: Option<String>,
pub coconut_dkg_contract_address: Option<String>,
}
// I wanted to use the simpler `NetworkDetails` name, but there's a clash
@@ -97,9 +96,6 @@ impl NymNetworkDetails {
.with_multisig_contract(Some(
var(var_names::MULTISIG_CONTRACT_ADDRESS).expect("multisig contract not set"),
))
.with_coconut_dkg_contract(Some(
var(var_names::COCONUT_DKG_CONTRACT_ADDRESS).expect("coconut dkg contract not set"),
))
}
pub fn new_mainnet() -> Self {
@@ -125,9 +121,6 @@ impl NymNetworkDetails {
mainnet::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
),
multisig_contract_address: parse_optional_str(mainnet::MULTISIG_CONTRACT_ADDRESS),
coconut_dkg_contract_address: parse_optional_str(
mainnet::COCONUT_DKG_CONTRACT_ADDRESS,
),
},
}
}
@@ -197,12 +190,6 @@ impl NymNetworkDetails {
self.contracts.multisig_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_coconut_dkg_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.coconut_dkg_contract_address = contract.map(Into::into);
self
}
}
#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
+1 -2
View File
@@ -10,7 +10,7 @@ pub const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6);
pub const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
"n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr";
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
@@ -18,7 +18,6 @@ pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
-1
View File
@@ -15,7 +15,6 @@ pub const VESTING_CONTRACT_ADDRESS: &str = "VESTING_CONTRACT_ADDRESS";
pub const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = "BANDWIDTH_CLAIM_CONTRACT_ADDRESS";
pub const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "COCONUT_BANDWIDTH_CONTRACT_ADDRESS";
pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS";
pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "COCONUT_DKG_CONTRACT_ADDRESS";
pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS";
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "STATISTICS_SERVICE_DOMAIN_ADDRESS";
pub const NYMD_VALIDATOR: &str = "NYMD_VALIDATOR";
+3 -5
View File
@@ -7,7 +7,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", branch ="gt-serialisation", default-features = false, features = ["pairings", "alloc", "experimental"] }
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
itertools = "0.10"
digest = "0.9"
rand = "0.8"
@@ -18,18 +18,16 @@ bs58 = "0.4.0"
sha2 = "0.9"
[dependencies.ff]
version = "0.11"
version = "0.10"
default-features = false
[dependencies.group]
version = "0.11"
version = "0.10"
default-features = false
[dev-dependencies]
criterion = { version="0.3", features=["html_reports"] }
doc-comment = "0.3"
dkg = { path = "../crypto/dkg" }
rand_chacha = "0.3"
[dev-dependencies.bincode]
version = "1"
-1
View File
@@ -17,7 +17,6 @@ pub use scheme::issuance::prepare_blind_sign;
pub use scheme::issuance::BlindSignRequest;
pub use scheme::keygen::ttp_keygen;
pub use scheme::keygen::KeyPair;
pub use scheme::keygen::SecretKey;
pub use scheme::keygen::VerificationKey;
pub use scheme::setup::setup;
pub use scheme::setup::Parameters;
-14
View File
@@ -71,12 +71,6 @@ impl TryFrom<&[u8]> for SecretKey {
}
impl SecretKey {
/// Following a (distributed) key generation process, scalar values can be obtained
/// outside of the normal key generation process.
pub fn create_from_raw(x: Scalar, ys: Vec<Scalar>) -> Self {
Self { x, ys }
}
/// Derive verification key using this secret key.
pub fn verification_key(&self, params: &Parameters) -> VerificationKey {
let g1 = params.gen1();
@@ -369,14 +363,6 @@ pub struct KeyPair {
impl KeyPair {
const MARKER_BYTES: &'static [u8] = b"coconutkeypair";
pub fn from_keys(secret_key: SecretKey, verification_key: VerificationKey) -> Self {
Self {
secret_key,
verification_key,
index: None,
}
}
pub fn secret_key(&self) -> SecretKey {
self.secret_key.clone()
}
+5 -52
View File
@@ -1,16 +1,11 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::tests::helpers::tests::generate_dkg_keys;
use crate::{
aggregate_verification_keys, setup, tests::helpers::*, ttp_keygen, verify_credential,
CoconutError, VerificationKey,
aggregate_verification_keys, setup, tests::helpers::theta_from_keys_and_attributes, ttp_keygen,
verify_credential, CoconutError, VerificationKey,
};
#[test]
fn keygen() -> Result<(), CoconutError> {
fn main() -> Result<(), CoconutError> {
let params = setup(5)?;
let node_indices = vec![15u64, 248, 33521];
let public_attributes = params.n_random_scalars(2);
@@ -23,52 +18,10 @@ fn keygen() -> Result<(), CoconutError> {
.collect();
// aggregate verification keys
let verification_key = aggregate_verification_keys(&verification_keys, Some(&node_indices))?;
let verification_key = aggregate_verification_keys(&verification_keys, Some(&[1, 2, 3]))?;
// Generate cryptographic material to verify them
let theta = theta_from_keys_and_attributes(
&params,
&coconut_keypairs,
&node_indices,
&public_attributes,
)?;
// Verify credentials
assert!(verify_credential(
&params,
&verification_key,
&theta,
&public_attributes,
));
Ok(())
}
#[test]
fn dkg() -> Result<(), CoconutError> {
let params = setup(5)?;
let node_indices = vec![15u64, 248, 33521];
let public_attributes = params.n_random_scalars(2);
// generate_keys
let coconut_keypairs = generate_dkg_keys(5, &node_indices);
let verification_keys: Vec<VerificationKey> = coconut_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
// aggregate verification keys
let verification_key = aggregate_verification_keys(&verification_keys, Some(&node_indices))?;
// Generate cryptographic material to verify them
let theta = theta_from_keys_and_attributes(
&params,
&coconut_keypairs,
&node_indices,
&public_attributes,
)?;
let theta = theta_from_keys_and_attributes(&params, &coconut_keypairs, &public_attributes)?;
// Verify credentials
assert!(verify_credential(
+22 -101
View File
@@ -7,7 +7,6 @@ use itertools::izip;
pub fn theta_from_keys_and_attributes(
params: &Parameters,
coconut_keypairs: &Vec<KeyPair>,
indices: &[scheme::SignerIndex],
public_attributes: &Vec<PublicAttribute>,
) -> Result<Theta, CoconutError> {
let serial_number = params.random_scalar();
@@ -24,7 +23,12 @@ pub fn theta_from_keys_and_attributes(
.collect();
// aggregate verification keys
let verification_key = aggregate_verification_keys(&verification_keys, Some(indices))?;
let indices: Vec<u64> = coconut_keypairs
.iter()
.enumerate()
.map(|(idx, _)| (idx + 1) as u64)
.collect();
let verification_key = aggregate_verification_keys(&verification_keys, Some(&indices))?;
// generate blinded signatures
let mut blinded_signatures = Vec::new();
@@ -40,31 +44,26 @@ pub fn theta_from_keys_and_attributes(
}
// Unblind
let unblinded_signatures: Vec<(scheme::SignerIndex, Signature)> = izip!(
indices.iter(),
blinded_signatures.iter(),
verification_keys.iter()
)
.map(|(idx, s, vk)| {
(
*idx,
s.unblind(
params,
vk,
&private_attributes,
public_attributes,
&blind_sign_request.get_commitment_hash(),
&commitments_openings,
)
.unwrap(),
)
})
.collect();
let unblinded_signatures: Vec<Signature> =
izip!(blinded_signatures.iter(), verification_keys.iter())
.map(|(s, vk)| {
s.unblind(
params,
vk,
&private_attributes,
public_attributes,
&blind_sign_request.get_commitment_hash(),
&commitments_openings,
)
.unwrap()
})
.collect();
// Aggregate signatures
let signature_shares: Vec<SignatureShare> = unblinded_signatures
.iter()
.map(|(idx, signature)| SignatureShare::new(*signature, *idx))
.enumerate()
.map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64))
.collect();
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
@@ -86,81 +85,3 @@ pub fn theta_from_keys_and_attributes(
Ok(theta)
}
#[cfg(test)]
pub mod tests {
use crate::{KeyPair, Parameters, SecretKey};
use bls12_381::Scalar;
use dkg::{bte::decrypt_share, combine_shares, Dealing, NodeIndex};
use rand_chacha::rand_core::SeedableRng;
use std::fmt::Debug;
pub fn generate_dkg_secrets(node_indices: &[NodeIndex]) -> Vec<Scalar> {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = dkg::bte::setup();
// the simplest possible case
let threshold = 2;
let mut receivers = std::collections::BTreeMap::new();
let mut full_keys = Vec::new();
for index in node_indices {
let (dk, pk) = dkg::bte::keygen(&params, &mut rng);
receivers.insert(*index, *pk.public_key());
full_keys.push((dk, pk))
}
let dealings = node_indices
.iter()
.map(|&dealer_index| {
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0
})
.collect::<Vec<_>>();
let mut derived_secrets = Vec::new();
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
let shares = dealings
.iter()
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
.collect();
let recovered_secret =
combine_shares(shares, &receivers.keys().copied().collect::<Vec<_>>()).unwrap();
derived_secrets.push(recovered_secret)
}
derived_secrets
}
pub fn generate_dkg_keys(num_attributes: u32, node_indices: &[NodeIndex]) -> Vec<KeyPair> {
let params = Parameters::new(num_attributes).unwrap();
let mut all_secrets = vec![];
for _ in 0..num_attributes {
let secrets = generate_dkg_secrets(node_indices);
all_secrets.push(secrets);
}
let signers = transpose_matrix(all_secrets);
signers
.into_iter()
.map(|mut secrets| {
let x = secrets.pop().unwrap();
let sk = SecretKey::create_from_raw(x, secrets);
let vk = sk.verification_key(&params);
KeyPair::from_keys(sk, vk)
})
.collect()
}
pub(crate) fn transpose_matrix<T: Debug>(matrix: Vec<Vec<T>>) -> Vec<Vec<T>> {
let len = matrix[0].len();
let mut iters: Vec<_> = matrix.into_iter().map(|d| d.into_iter()).collect();
(0..len)
.map(|_| {
iters
.iter_mut()
.map(|it| it.next().unwrap())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
.try_into()
.unwrap()
}
}
+1 -1
View File
@@ -32,7 +32,7 @@ impl Polynomial {
Scalar::zero()
// if x is zero then we can ignore most of the expensive computation and
// just return the last term of the polynomial
} else if x.is_zero().into() {
} else if x.is_zero() {
// we checked that coefficients are not empty so unwrap here is fine
*self.coefficients.first().unwrap()
} else {
+1 -4
View File
@@ -14,11 +14,8 @@ tokio-util = { version = "0.7.3", features = [ "io" ] } # reason for getting thi
# In the long run, the dependency should probably get removed in favour of pure-tokio implementation, but for time being it's fine.
futures = "0.3"
log = "0.4"
# internal
client-connections = { path = "../../client-connections" }
ordered-buffer = { path = "../ordered-buffer" }
socks5-requests = { path = "../requests" }
ordered-buffer = { path = "../ordered-buffer" }
task = { path = "../../task" }
[dev-dependencies]
@@ -1,18 +1,13 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_connections::{ClosedConnectionSender, ConnectionCommand};
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use ordered_buffer::{OrderedMessage, OrderedMessageBuffer, ReadContiguousData};
use socks5_requests::ConnectionId;
use std::{
collections::{HashMap, HashSet},
time::Duration,
};
use std::collections::{HashMap, HashSet};
use task::ShutdownListener;
use tokio::time;
/// A generic message produced after reading from a socket/connection. It includes data that was
/// actually read alongside boolean indicating whether the connection got closed so that
@@ -78,11 +73,6 @@ pub struct Controller {
// to avoid memory issues
recently_closed: HashSet<ConnectionId>,
// Broadcast closed connections
closed_connection_tx: ClosedConnectionSender,
broadcast_connections: bool,
// TODO: this can potentially be abused to ddos and kill provider. Not sure at this point
// how to handle it more gracefully
@@ -94,19 +84,13 @@ pub struct Controller {
}
impl Controller {
pub fn new(
closed_connection_tx: ClosedConnectionSender,
broadcast_connections: bool,
shutdown: ShutdownListener,
) -> (Self, ControllerSender) {
pub fn new(shutdown: ShutdownListener) -> (Self, ControllerSender) {
let (sender, receiver) = mpsc::unbounded();
(
Controller {
active_connections: HashMap::new(),
receiver,
recently_closed: HashSet::new(),
closed_connection_tx,
broadcast_connections,
pending_messages: HashMap::new(),
shutdown,
},
@@ -143,20 +127,6 @@ impl Controller {
)
}
self.recently_closed.insert(conn_id);
// Announce closed connections, currently used by the `OutQueueControl`.
self.closed_connection_tx
.unbounded_send(ConnectionCommand::Close(conn_id))
.unwrap();
}
fn broadcast_active_connections(&mut self) {
// What about the recently closed ones? Hopefully we can ignore them ...
let conn_ids = self.active_connections.keys().copied().collect();
self.closed_connection_tx
.unbounded_send(ConnectionCommand::ActiveConnections(conn_ids))
.unwrap();
}
fn send_to_connection(&mut self, conn_id: ConnectionId, payload: Vec<u8>, is_closed: bool) {
@@ -202,21 +172,15 @@ impl Controller {
pending.push((payload, is_closed));
} else if !is_closed {
error!(
"Tried to write to closed connection {} ({} bytes were 'lost)",
conn_id,
"Tried to write to closed connection ({} bytes were 'lost)",
payload.len()
);
} else {
debug!(
"Tried to write to closed connection {}, but remote is already closed",
conn_id
)
debug!("Tried to write to closed connection, but remote is already closed")
}
}
pub async fn run(&mut self) {
let mut interval = time::interval(Duration::from_millis(500));
loop {
tokio::select! {
command = self.receiver.next() => match command {
@@ -231,12 +195,7 @@ impl Controller {
log::trace!("SOCKS5 Controller: Stopping since channel closed");
break;
}
},
_ = interval.tick() => {
if self.broadcast_connections {
self.broadcast_active_connections();
}
},
}
}
}
assert!(self.shutdown.is_shutdown_poll());
@@ -5,20 +5,17 @@ use super::MixProxySender;
use super::SHUTDOWN_TIMEOUT;
use crate::available_reader::AvailableReader;
use bytes::Bytes;
use client_connections::LaneQueueLengths;
use client_connections::TransmissionLane;
use futures::FutureExt;
use futures::StreamExt;
use log::*;
use ordered_buffer::OrderedMessageSender;
use socks5_requests::ConnectionId;
use std::time::Duration;
use std::{io, sync::Arc};
use task::ShutdownListener;
use tokio::select;
use tokio::{net::tcp::OwnedReadHalf, sync::Notify, time::sleep};
async fn send_empty_close<F, S>(
fn send_empty_close<F, S>(
connection_id: ConnectionId,
message_sender: &mut OrderedMessageSender,
mix_sender: &MixProxySender<S>,
@@ -27,17 +24,12 @@ async fn send_empty_close<F, S>(
F: Fn(ConnectionId, Vec<u8>, bool) -> S,
{
let ordered_msg = message_sender.wrap_message(Vec::new()).into_bytes();
if mix_sender
.send(adapter_fn(connection_id, ordered_msg, true))
.await
.is_err()
{
panic!();
}
mix_sender
.unbounded_send(adapter_fn(connection_id, ordered_msg, true))
.unwrap();
}
#[allow(clippy::too_many_arguments)]
async fn deal_with_data<F, S>(
fn deal_with_data<F, S>(
read_data: Option<io::Result<Bytes>>,
local_destination_address: &str,
remote_source_address: &str,
@@ -45,7 +37,6 @@ async fn deal_with_data<F, S>(
message_sender: &mut OrderedMessageSender,
mix_sender: &MixProxySender<S>,
adapter_fn: F,
lane_queue_lengths: Option<LaneQueueLengths>,
) -> bool
where
F: Fn(ConnectionId, Vec<u8>, bool) -> S,
@@ -72,30 +63,9 @@ where
// if we're sending through the mixnet increase the sequence number...
let ordered_msg = message_sender.wrap_message(read_data.to_vec()).into_bytes();
log::trace!(
"pushing data down the input sender: size: {}",
ordered_msg.len()
);
// If we are closing the socket, wait until the data has passed `OutQueueControl` and the lane
// is empty, otherwise just wait until we are reasonably close to finish sending as a way to
// throttle the incoming data.
if let Some(lane_queue_lengths) = lane_queue_lengths {
if is_finished {
wait_until_lane_empty(lane_queue_lengths, connection_id).await;
} else {
// We allow a bit of slack when this is not the last msg
wait_until_lane_almost_empty(lane_queue_lengths, connection_id).await;
}
}
if mix_sender
.send(adapter_fn(connection_id, ordered_msg, is_finished))
.await
.is_err()
{
panic!();
}
mix_sender
.unbounded_send(adapter_fn(connection_id, ordered_msg, is_finished))
.unwrap();
if is_finished {
// technically we already informed it when we sent the message to mixnet above
@@ -105,55 +75,6 @@ where
is_finished
}
async fn wait_until_lane_empty(lane_queue_lengths: LaneQueueLengths, connection_id: u64) {
if tokio::time::timeout(
Duration::from_secs(15),
wait_for_lane(
lane_queue_lengths,
connection_id,
0,
Duration::from_millis(500),
),
)
.await
.is_err()
{
log::warn!("Wait until lane empty timed out");
}
}
async fn wait_until_lane_almost_empty(lane_queue_lengths: LaneQueueLengths, connection_id: u64) {
if tokio::time::timeout(
Duration::from_secs(15),
wait_for_lane(
lane_queue_lengths,
connection_id,
10,
Duration::from_millis(100),
),
)
.await
.is_err()
{
log::warn!("Wait until lane almost empty timed out");
}
}
async fn wait_for_lane(
lane_queue_lengths: LaneQueueLengths,
connection_id: u64,
queue_length_threshold: usize,
sleep_duration: Duration,
) {
while let Some(queue) = lane_queue_lengths.get(&TransmissionLane::ConnectionId(connection_id)) {
if queue > queue_length_threshold {
sleep(sleep_duration).await;
} else {
break;
}
}
}
#[allow(clippy::too_many_arguments)]
pub(super) async fn run_inbound<F, S>(
mut reader: OwnedReadHalf,
@@ -163,7 +84,6 @@ pub(super) async fn run_inbound<F, S>(
mix_sender: MixProxySender<S>,
adapter_fn: F,
shutdown_notify: Arc<Notify>,
lane_queue_lengths: Option<LaneQueueLengths>,
mut shutdown_listener: ShutdownListener,
) -> OwnedReadHalf
where
@@ -178,27 +98,15 @@ where
loop {
select! {
read_data = &mut available_reader.next() => {
if deal_with_data(
read_data,
&local_destination_address,
&remote_source_address,
connection_id,
&mut message_sender,
&mix_sender,
&adapter_fn,
lane_queue_lengths.clone()
).await {
if deal_with_data(read_data, &local_destination_address, &remote_source_address, connection_id, &mut message_sender, &mix_sender, &adapter_fn) {
break
}
}
_ = &mut shutdown_future => {
debug!(
"closing inbound proxy after outbound was closed {:?} ago",
SHUTDOWN_TIMEOUT
);
debug!("closing inbound proxy after outbound was closed {:?} ago", SHUTDOWN_TIMEOUT);
// inform remote just in case it was closed because of lack of heartbeat.
// worst case the remote will just have couple of false negatives
send_empty_close(connection_id, &mut message_sender, &mix_sender, &adapter_fn).await;
send_empty_close(connection_id, &mut message_sender, &mix_sender, &adapter_fn);
break;
}
_ = shutdown_listener.recv() => {
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::connection_controller::ConnectionReceiver;
use client_connections::LaneQueueLengths;
use futures::channel::mpsc;
use socks5_requests::ConnectionId;
use std::{sync::Arc, time::Duration};
use task::ShutdownListener;
@@ -29,8 +29,7 @@ impl From<(Vec<u8>, bool)> for ProxyMessage {
}
}
pub type MixProxySender<S> = tokio::sync::mpsc::Sender<S>;
pub type MixProxyReader<S> = tokio::sync::mpsc::Receiver<S>;
pub type MixProxySender<S> = mpsc::UnboundedSender<S>;
// TODO: when we finally get to implementing graceful shutdown,
// on Drop this guy should tell the remote that it's closed now
@@ -46,7 +45,6 @@ pub struct ProxyRunner<S> {
local_destination_address: String,
remote_source_address: String,
connection_id: ConnectionId,
lane_queue_lengths: Option<LaneQueueLengths>,
// Listens to shutdown commands from higher up
shutdown_listener: ShutdownListener,
@@ -56,7 +54,6 @@ impl<S> ProxyRunner<S>
where
S: Send + 'static,
{
#[allow(clippy::too_many_arguments)]
pub fn new(
socket: TcpStream,
local_destination_address: String, // addresses are provided for better logging
@@ -64,7 +61,6 @@ where
mix_receiver: ConnectionReceiver,
mix_sender: MixProxySender<S>,
connection_id: ConnectionId,
lane_queue_lengths: Option<LaneQueueLengths>,
shutdown_listener: ShutdownListener,
) -> Self {
ProxyRunner {
@@ -74,7 +70,6 @@ where
local_destination_address,
remote_source_address,
connection_id,
lane_queue_lengths,
shutdown_listener,
}
}
@@ -83,7 +78,7 @@ where
// request/response as required by entity running particular side of the proxy.
pub async fn run<F>(mut self, adapter_fn: F) -> Self
where
F: Fn(ConnectionId, Vec<u8>, bool) -> S + Send + Sync + 'static,
F: Fn(ConnectionId, Vec<u8>, bool) -> S + Send + 'static,
{
let (read_half, write_half) = self.socket.take().unwrap().into_split();
let shutdown_notify = Arc::new(Notify::new());
@@ -97,7 +92,6 @@ where
self.mix_sender.clone(),
adapter_fn,
Arc::clone(&shutdown_notify),
self.lane_queue_lengths.clone(),
self.shutdown_listener.clone(),
);
+4 -36
View File
@@ -1,43 +1,8 @@
## Unreleased
## Added
- Added migration code to the mixnet contract to allow updating stored vesting contract address to make it easier to deploy any future environments ([#1759],[#1769])
[#1759]: https://github.com/nymtech/nym/pull/1759
[#1769]: https://github.com/nymtech/nym/pull/1769
## [nym-contracts-v1.1.0](https://github.com/nymtech/nym/tree/nym-contracts-v1.1.0) (2022-11-09)
### Changed
- mixnet-contract: rework of rewarding ([#1472]), which includes, but is not limited to:
- internal reward accounting was modified to be similar to the ideas presented in Cosmos' F1 paper, which results in throughput gains and no storage or gas cost bloat over time,
- introduced internal queues for pending epoch and interval events that only get resolved once relevant epoch/interval rolls over
- the contract no longer stores any historical information regarding past epochs/parameters/stake state for the purposes of rewarding
- a lot of queries got renamed to keep naming more consistent,
- introduced new utility-based queries such as a query for reward estimation for the current epoch,
- mixnodes are now identified by a monotonously increasing `mix_id`
- bonding now results in getting fresh `mix_id` and thus if given node decides to unbond and rebond, it will lose all its delegations,
- mixnode operators are now allowed to set their operating costs as opposed to having fixed value of 40nym/interval
- rewarding parameters are now correctly updated at an **interval** end
- rewarding parameters now include a staking supply scale factor attribute (beta in the tokenomics paper)
- node performance can now be more granular with internal `Decimal` representation as opposed to an `u8`
- node profit margin can now be more granular with internal `Decimal` representation as opposed to an `u8`
- mixnode operators are now allowed to change their configuration options, such as port information, without having to unbond
- mixnode unbonding is no longer instantaneous, instead it happens once an epoch rolls over
- it is now possible to query for operator and node history to see how often (and with what parameters) they rebonded
- other minor bugfixes and changes
- ...
- new exciting bugs to find and squash
- vesting-contract: optional locked token pledge cap per account ([#1687]), defaults to 10%
- vesting-contract: updated internal delegation storage due to mixnet contract revamp ([#1472])
### Added
- vesting-contract: added query for obtaining contract build information ([#1726])
[#1472]: https://github.com/nymtech/nym/pull/1472
[#1687]: https://github.com/nymtech/nym/pull/1687
[#1726]: https://github.com/nymtech/nym/pull/1726
@@ -46,10 +11,12 @@
### Added
- vesting-contract: added queries for delegation timestamps and paged query for all vesting delegations in the contract ([#1569])
- all binaries: added shell completion and [Fig](fig.io) spec generation ([#1638])
### Changed
- mixnet-contract: compounding delegator rewards now happens instantaneously as opposed to having to wait for the current epoch to finish ([#1571])
- network-requester: updated CLI to use `clap` macros ([#1638])
### Fixed
@@ -59,8 +26,9 @@
[#1544]: https://github.com/nymtech/nym/pull/1544
[#1569]: https://github.com/nymtech/nym/pull/1569
[#1571]: https://github.com/nymtech/nym/pull/1571
[#1569]: https://github.com/nymtech/nym/pull/1571
[#1613]: https://github.com/nymtech/nym/pull/1613
[#1638]: https://github.com/nymtech/nym/pull/1638
## [nym-contracts-v1.0.1](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.1) (2022-06-22)
+6 -31
View File
@@ -189,31 +189,6 @@ dependencies = [
"serde",
]
[[package]]
name = "coconut-dkg"
version = "0.1.0"
dependencies = [
"coconut-dkg-common",
"cosmwasm-std",
"cosmwasm-storage",
"cw-controllers",
"cw-storage-plus",
"cw4",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "coconut-dkg-common"
version = "0.1.0"
dependencies = [
"contracts-common",
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
name = "coconut-test"
version = "0.1.0"
@@ -245,7 +220,6 @@ checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3"
name = "contracts-common"
version = "0.1.0"
dependencies = [
"bs58",
"cosmwasm-std",
"schemars",
"serde",
@@ -313,9 +287,9 @@ dependencies = [
[[package]]
name = "cpufeatures"
version = "0.2.5"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
dependencies = [
"libc",
]
@@ -595,9 +569,9 @@ dependencies = [
[[package]]
name = "ed25519"
version = "1.5.2"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369"
checksum = "eed12bbf7b5312f8da1c2722bc06d8c6b12c2d86a7fb35a194c7f3e6fc2bbe39"
dependencies = [
"signature",
]
@@ -956,7 +930,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "mixnet-contract"
version = "1.1.0"
version = "1.0.2"
dependencies = [
"bs58",
"cosmwasm-schema",
@@ -1651,6 +1625,7 @@ version = "0.1.0"
dependencies = [
"contracts-common",
"cosmwasm-std",
"log",
"mixnet-contract-common",
"schemars",
"serde",
+1 -1
View File
@@ -1,5 +1,5 @@
[workspace]
members = ["coconut-bandwidth", "mixnet", "vesting", "multisig/cw3-flex-multisig", "multisig/cw4-group", "coconut-test", "coconut-dkg"]
members = ["coconut-bandwidth", "mixnet", "vesting", "multisig/cw3-flex-multisig", "multisig/cw4-group", "coconut-test"]
[profile.release]
opt-level = 3
-22
View File
@@ -1,22 +0,0 @@
[package]
name = "coconut-dkg"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" }
cosmwasm-std = "1.0.0"
cosmwasm-storage = "1.0.0"
cw-storage-plus = "0.13.4"
cw-controllers = "0.13.4"
cw4 = { version = "0.13.4" }
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = "1.0.23"
-7
View File
@@ -1,7 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Uint128;
// to be determined whether those should be constants or exist as contract state
pub(crate) const MINIMUM_DEPOSIT: Uint128 = Uint128::new(1_000_000_000);
-6
View File
@@ -1,6 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod queries;
pub mod storage;
pub mod transactions;
@@ -1,67 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealers::storage::{self, IndexedDealersMap};
use coconut_dkg_common::dealer::{DealerDetailsResponse, DealerType, PagedDealerResponse};
use cosmwasm_std::{Deps, Order, StdResult};
use cw_storage_plus::Bound;
fn query_dealers(
deps: Deps<'_>,
start_after: Option<String>,
limit: Option<u32>,
underlying_map: IndexedDealersMap<'_>,
) -> StdResult<PagedDealerResponse> {
let limit = limit
.unwrap_or(storage::DEALERS_PAGE_DEFAULT_LIMIT)
.min(storage::DEALERS_PAGE_MAX_LIMIT) as usize;
let addr = start_after
.map(|addr| deps.api.addr_validate(&addr))
.transpose()?;
let start = addr.as_ref().map(Bound::exclusive);
let dealers = underlying_map
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<_>>>()?;
let start_next_after = dealers.last().map(|dealer| dealer.address.clone());
Ok(PagedDealerResponse::new(dealers, limit, start_next_after))
}
pub fn query_dealer_details(
deps: Deps<'_>,
dealer_address: String,
) -> StdResult<DealerDetailsResponse> {
let addr = deps.api.addr_validate(&dealer_address)?;
if let Some(current) = storage::current_dealers().may_load(deps.storage, &addr)? {
return Ok(DealerDetailsResponse::new(
Some(current),
DealerType::Current,
));
}
if let Some(past) = storage::past_dealers().may_load(deps.storage, &addr)? {
return Ok(DealerDetailsResponse::new(Some(past), DealerType::Past));
}
Ok(DealerDetailsResponse::new(None, DealerType::Unknown))
}
pub fn query_current_dealers_paged(
deps: Deps<'_>,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedDealerResponse> {
query_dealers(deps, start_after, limit, storage::current_dealers())
}
pub fn query_past_dealers_paged(
deps: Deps<'_>,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedDealerResponse> {
query_dealers(deps, start_after, limit, storage::past_dealers())
}
@@ -1,49 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_dkg_common::types::{DealerDetails, NodeIndex};
use cosmwasm_std::{Addr, StdResult, Storage};
use cw_storage_plus::{Index, IndexList, IndexedMap, Item, UniqueIndex};
const CURRENT_DEALERS_PK: &str = "crd";
const PAST_DEALERS_PK: &str = "ptd";
const DEALERS_NODE_INDEX_IDX_NAMESPACE: &str = "dni";
pub(crate) const DEALERS_PAGE_MAX_LIMIT: u32 = 75;
pub(crate) const DEALERS_PAGE_DEFAULT_LIMIT: u32 = 50;
pub(crate) const NODE_INDEX_COUNTER: Item<NodeIndex> = Item::new("node_index_counter");
pub(crate) type IndexedDealersMap<'a> = IndexedMap<'a, &'a Addr, DealerDetails, DealersIndex<'a>>;
pub(crate) struct DealersIndex<'a> {
pub(crate) node_index: UniqueIndex<'a, NodeIndex, DealerDetails>,
}
impl<'a> IndexList<DealerDetails> for DealersIndex<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<DealerDetails>> + '_> {
let v: Vec<&dyn Index<DealerDetails>> = vec![&self.node_index];
Box::new(v.into_iter())
}
}
pub(crate) fn current_dealers<'a>() -> IndexedDealersMap<'a> {
let indexes = DealersIndex {
node_index: UniqueIndex::new(|d| d.assigned_index, DEALERS_NODE_INDEX_IDX_NAMESPACE),
};
IndexedMap::new(CURRENT_DEALERS_PK, indexes)
}
pub(crate) fn past_dealers<'a>() -> IndexedDealersMap<'a> {
let indexes = DealersIndex {
node_index: UniqueIndex::new(|d| d.assigned_index, DEALERS_NODE_INDEX_IDX_NAMESPACE),
};
IndexedMap::new(PAST_DEALERS_PK, indexes)
}
pub(crate) fn next_node_index(store: &mut dyn Storage) -> StdResult<NodeIndex> {
// make sure we don't start from 0, otherwise all the crypto breaks (kinda)
let id: NodeIndex = NODE_INDEX_COUNTER.may_load(store)?.unwrap_or_default() + 1;
NODE_INDEX_COUNTER.save(store, &id)?;
Ok(id)
}
@@ -1,103 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::constants::MINIMUM_DEPOSIT;
use crate::dealers::storage as dealers_storage;
use crate::epoch_state::utils::check_epoch_state;
use crate::{ContractError, State, STATE};
use coconut_dkg_common::types::{DealerDetails, EncodedBTEPublicKeyWithProof, EpochState};
use cosmwasm_std::{Addr, Coin, DepsMut, MessageInfo, Response};
// currently we only require that
// a) it's part of the signer group
// b) it isn't already a dealer
fn verify_dealer(deps: DepsMut<'_>, state: &State, dealer: &Addr) -> Result<(), ContractError> {
if dealers_storage::current_dealers()
.may_load(deps.storage, dealer)?
.is_some()
{
return Err(ContractError::AlreadyADealer);
}
state
.group_addr
.is_voting_member(&deps.querier, dealer, None)?
.ok_or(ContractError::Unauthorized {})?;
Ok(())
}
fn validate_dealer_deposit(state: &State, mut deposit: Vec<Coin>) -> Result<Coin, ContractError> {
// check if anything was put as deposit
if deposit.is_empty() {
return Err(ContractError::NoDepositFound {
denom: state.mix_denom.clone(),
});
}
if deposit.len() > 1 {
return Err(ContractError::MultipleDenoms);
}
// check that the denomination is correct
if deposit[0].denom != state.mix_denom {
return Err(ContractError::WrongDenom {
denom: state.mix_denom.clone(),
});
}
// check that we have at least MINIMUM_DEPOSIT coins in our deposit
if deposit[0].amount < MINIMUM_DEPOSIT {
return Err(ContractError::InsufficientDeposit {
received: deposit[0].amount.into(),
minimum: MINIMUM_DEPOSIT.into(),
});
}
// the unwrap would have been safe here under all circumstances, since we checked whether the vector is empty
// but in case something did change, change option into an error
deposit.pop().ok_or(ContractError::NoDepositFound {
denom: state.mix_denom.clone(),
})
}
pub fn try_add_dealer(
mut deps: DepsMut<'_>,
info: MessageInfo,
bte_key_with_proof: EncodedBTEPublicKeyWithProof,
) -> Result<Response, ContractError> {
check_epoch_state(deps.storage, EpochState::PublicKeySubmission)?;
let state = STATE.load(deps.storage)?;
verify_dealer(deps.branch(), &state, &info.sender)?;
// validate and extract sent deposit
let deposit = validate_dealer_deposit(&state, info.funds)?;
// if it was already a dealer in the past, assign the same node index
let node_index = if let Some(prior_details) =
dealers_storage::past_dealers().may_load(deps.storage, &info.sender)?
{
// since this dealer is going to become active now, remove it from the past dealers
dealers_storage::past_dealers().replace(
deps.storage,
&info.sender,
None,
Some(&prior_details),
)?;
prior_details.assigned_index
} else {
dealers_storage::next_node_index(deps.storage)?
};
// save the dealer into the storage
let dealer_details = DealerDetails {
address: info.sender.clone(),
bte_public_key_with_proof: bte_key_with_proof,
assigned_index: node_index,
deposit,
};
dealers_storage::current_dealers().save(deps.storage, &info.sender, &dealer_details)?;
Ok(Response::new().add_attribute("node_index", node_index.to_string()))
}
@@ -1,6 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod queries;
pub mod storage;
pub mod transactions;

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