Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 095b66ed5e |
@@ -54,20 +54,6 @@ jobs:
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
# To avoid running out of disk space, skip generating debug symbols
|
||||
- name: Set debug to false (unix)
|
||||
if: contains(matrix.os, 'ubuntu') || contains(matrix.os, 'mac')
|
||||
run: |
|
||||
sed -i.bak 's/\[profile.dev\]/\[profile.dev\]\ndebug = false/' Cargo.toml
|
||||
git diff
|
||||
|
||||
- name: Set debug to false (win)
|
||||
if: contains(matrix.os, 'windows')
|
||||
shell: pwsh
|
||||
run: |
|
||||
(Get-Content Cargo.toml) -replace '\[profile.dev\]', "`$&`ndebug = false" | Set-Content Cargo.toml
|
||||
git diff
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
|
||||
@@ -9,8 +9,6 @@ on:
|
||||
paths:
|
||||
- 'contracts/**'
|
||||
- 'common/**'
|
||||
- 'Cargo.lock'
|
||||
- 'Cargo.toml'
|
||||
- '.github/workflows/ci-contracts.yml'
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -30,12 +30,6 @@ jobs:
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Set debug to false
|
||||
working-directory: nym-wallet
|
||||
run: |
|
||||
sed -i.bak '1s/^/\[profile.dev\]\ndebug = false\n\n/' Cargo.toml
|
||||
git diff
|
||||
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
|
||||
@@ -45,11 +45,6 @@ jobs:
|
||||
- name: Install wasm-bindgen-cli
|
||||
run: cargo install wasm-bindgen-cli
|
||||
|
||||
- name: Set debug to false
|
||||
run: |
|
||||
sed -i.bak 's/\[profile.dev\]/\[profile.dev\]\ndebug = false/' Cargo.toml
|
||||
git diff
|
||||
|
||||
- name: "Build"
|
||||
run: make sdk-wasm-build
|
||||
|
||||
|
||||
-148
@@ -4,154 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.4-dorina-patched] (2025-03-06)
|
||||
|
||||
- use legacy crypto for constructing SURB headers ([#5579])
|
||||
- bugfix: make sure to correctly decode response content when putting it into error message ([#5571])
|
||||
- Tweak surb management to be more conservative ([#5570])
|
||||
- Deserialize v5 authenticator requests ([#5568])
|
||||
- chore: additional logs when attempting to load ecash keys ([#5567])
|
||||
- add full response body to error message upon decoding failure ([#5566])
|
||||
- hotfix: ensure we bail on merkle leaves insertion upon missing data ([#5565])
|
||||
- feature: v2 authentication request (#5537) ([#5563])
|
||||
- Create authenticator v5 request/response types ([#5561])
|
||||
|
||||
[#5579]: https://github.com/nymtech/nym/pull/5579
|
||||
[#5571]: https://github.com/nymtech/nym/pull/5571
|
||||
[#5570]: https://github.com/nymtech/nym/pull/5570
|
||||
[#5568]: https://github.com/nymtech/nym/pull/5568
|
||||
[#5567]: https://github.com/nymtech/nym/pull/5567
|
||||
[#5566]: https://github.com/nymtech/nym/pull/5566
|
||||
[#5565]: https://github.com/nymtech/nym/pull/5565
|
||||
[#5563]: https://github.com/nymtech/nym/pull/5563
|
||||
[#5561]: https://github.com/nymtech/nym/pull/5561
|
||||
|
||||
## [2025.4-dorina] (2025-03-04)
|
||||
|
||||
- fixed sphinx version metrics registration ([#5546])
|
||||
- Feature/chain status api ([#5539])
|
||||
- Add SURBs soft threshold ([#5535])
|
||||
- Simplify IPR v8 ([#5532])
|
||||
- Shared instance for DNS AsyncResolver ([#5523])
|
||||
- merge #5512 again after reverting due to incorrect rebase ([#5520])
|
||||
- cherry-pick 17d3ff2d775f61aee381d90a304ed416c08f33fc onto dorina ([#5519])
|
||||
- cherry-pick 6e5d0dac1b75413c5f09122b0d953f8ec6ef48df onto dorina ([#5518])
|
||||
- chore: workspace global panic preventing lints ([#5512])
|
||||
- bugfix: dont query for ecash apis unless necessary when spending ticketbooks ([#5508])
|
||||
- bugfix: bound check when recovering a reply SURB ([#5502])
|
||||
- chore: removed all old coconut code ([#5500])
|
||||
- IPR request types v8 ([#5498])
|
||||
- Support static routes for HTTP requests ([#5487])
|
||||
- build(deps): bump the patch-updates group across 1 directory with 3 updates ([#5482])
|
||||
- added missing import to doctest ([#5480])
|
||||
- adjusted TestSetup::new_complex to ensure bonded node's existence ([#5478])
|
||||
- Trigger contracts CI on main workspace Cargo changes ([#5477])
|
||||
- build(deps): bump http from 1.1.0 to 1.2.0 ([#5472])
|
||||
- build(deps): bump utoipa-swagger-ui from 8.0.3 to 8.1.0 ([#5471])
|
||||
- build(deps): bump colored from 2.1.0 to 2.2.0 ([#5470])
|
||||
- build(deps): bump celes from 2.4.0 to 2.5.0 ([#5469])
|
||||
- build(deps): bump the patch-updates group with 2 updates ([#5467])
|
||||
- build(deps): bump elliptic from 6.5.4 to 6.6.1 in /docker/typescript_client/upload_contract ([#5463])
|
||||
- Run cargo autoinherit ([#5460])
|
||||
- Fix clippy::precedence ([#5457])
|
||||
- Provide Interval context with node descriptor endpoints ([#5456])
|
||||
- fix: update fx average rate calcs to ignore 0 values ([#5454])
|
||||
- Feature/add gbp currency ([#5453])
|
||||
- Add helper to extract a list of sqlite files with journal files wal/shm ([#5452])
|
||||
- Add a middleware layer to the nym api allowing for data compression ([#5451])
|
||||
- Condense core API functionalities and enable gzip decompression for reqwest payloads ([#5450])
|
||||
- build(deps): bump uniffi_build from 0.25.3 to 0.29.0 ([#5448])
|
||||
- Upgrade tower to 0.5.2 ([#5446])
|
||||
- build(deps): bump hickory-proto from 0.24.2 to 0.24.3 ([#5444])
|
||||
- Seedable clients ([#5440])
|
||||
- build(deps): bump the patch-updates group across 1 directory with 10 updates ([#5439])
|
||||
- Remove all recv_with_delay and add shutdown condition to loops in client-core ([#5435])
|
||||
- Disable the test for checking the remaining bandwidth in nym-node-status-api ([#5425])
|
||||
- Dz nym node stats ([#5418])
|
||||
- build(deps): bump hyper from 1.4.1 to 1.6.0 ([#5416])
|
||||
- build(deps): bump publicsuffix from 2.2.3 to 2.3.0 ([#5367])
|
||||
- Nymnode entrypoint docker ([#5300])
|
||||
|
||||
[#5546]: https://github.com/nymtech/nym/pull/5546
|
||||
[#5539]: https://github.com/nymtech/nym/pull/5539
|
||||
[#5535]: https://github.com/nymtech/nym/pull/5535
|
||||
[#5532]: https://github.com/nymtech/nym/pull/5532
|
||||
[#5523]: https://github.com/nymtech/nym/pull/5523
|
||||
[#5520]: https://github.com/nymtech/nym/pull/5520
|
||||
[#5519]: https://github.com/nymtech/nym/pull/5519
|
||||
[#5518]: https://github.com/nymtech/nym/pull/5518
|
||||
[#5512]: https://github.com/nymtech/nym/pull/5512
|
||||
[#5508]: https://github.com/nymtech/nym/pull/5508
|
||||
[#5502]: https://github.com/nymtech/nym/pull/5502
|
||||
[#5500]: https://github.com/nymtech/nym/pull/5500
|
||||
[#5498]: https://github.com/nymtech/nym/pull/5498
|
||||
[#5487]: https://github.com/nymtech/nym/pull/5487
|
||||
[#5482]: https://github.com/nymtech/nym/pull/5482
|
||||
[#5480]: https://github.com/nymtech/nym/pull/5480
|
||||
[#5478]: https://github.com/nymtech/nym/pull/5478
|
||||
[#5477]: https://github.com/nymtech/nym/pull/5477
|
||||
[#5472]: https://github.com/nymtech/nym/pull/5472
|
||||
[#5471]: https://github.com/nymtech/nym/pull/5471
|
||||
[#5470]: https://github.com/nymtech/nym/pull/5470
|
||||
[#5469]: https://github.com/nymtech/nym/pull/5469
|
||||
[#5467]: https://github.com/nymtech/nym/pull/5467
|
||||
[#5463]: https://github.com/nymtech/nym/pull/5463
|
||||
[#5460]: https://github.com/nymtech/nym/pull/5460
|
||||
[#5457]: https://github.com/nymtech/nym/pull/5457
|
||||
[#5456]: https://github.com/nymtech/nym/pull/5456
|
||||
[#5454]: https://github.com/nymtech/nym/pull/5454
|
||||
[#5453]: https://github.com/nymtech/nym/pull/5453
|
||||
[#5452]: https://github.com/nymtech/nym/pull/5452
|
||||
[#5451]: https://github.com/nymtech/nym/pull/5451
|
||||
[#5450]: https://github.com/nymtech/nym/pull/5450
|
||||
[#5448]: https://github.com/nymtech/nym/pull/5448
|
||||
[#5446]: https://github.com/nymtech/nym/pull/5446
|
||||
[#5444]: https://github.com/nymtech/nym/pull/5444
|
||||
[#5440]: https://github.com/nymtech/nym/pull/5440
|
||||
[#5439]: https://github.com/nymtech/nym/pull/5439
|
||||
[#5435]: https://github.com/nymtech/nym/pull/5435
|
||||
[#5425]: https://github.com/nymtech/nym/pull/5425
|
||||
[#5418]: https://github.com/nymtech/nym/pull/5418
|
||||
[#5416]: https://github.com/nymtech/nym/pull/5416
|
||||
[#5367]: https://github.com/nymtech/nym/pull/5367
|
||||
[#5300]: https://github.com/nymtech/nym/pull/5300
|
||||
|
||||
## [2025.3-ruta] (2025-02-10)
|
||||
|
||||
- Push down forget me to client configs ([#5431])
|
||||
- Fix statistics shutdown ([#5426])
|
||||
- Make wait_for_graceful_shutdown to be pub ([#5424])
|
||||
- Upgrade to thiserror 2.0 ([#5414])
|
||||
- build(deps): bump the patch-updates group across 1 directory with 9 updates ([#5406])
|
||||
- Relocate a validator api function ([#5401])
|
||||
- Send shutdown instead of panic when reaching max fail ([#5398])
|
||||
- Change Explorer URL to new smooshed nodes ([#5396])
|
||||
- reduce log severity for checking topology validity ([#5395])
|
||||
- MixnetClient can send ClientRequests ([#5381])
|
||||
- Fix missing path triggers for CI ([#5380])
|
||||
- Uncouple storage reference for bandwidth client ([#5372])
|
||||
- build(deps): bump tokio from 1.40.0 to 1.43.0 ([#5370])
|
||||
- DNS resolver configuration for internal HTTP client lookups ([#5355])
|
||||
- Update README.md ([#5328])
|
||||
- Update README.md ([#5327])
|
||||
|
||||
[#5431]: https://github.com/nymtech/nym/pull/5431
|
||||
[#5426]: https://github.com/nymtech/nym/pull/5426
|
||||
[#5424]: https://github.com/nymtech/nym/pull/5424
|
||||
[#5414]: https://github.com/nymtech/nym/pull/5414
|
||||
[#5406]: https://github.com/nymtech/nym/pull/5406
|
||||
[#5401]: https://github.com/nymtech/nym/pull/5401
|
||||
[#5398]: https://github.com/nymtech/nym/pull/5398
|
||||
[#5396]: https://github.com/nymtech/nym/pull/5396
|
||||
[#5395]: https://github.com/nymtech/nym/pull/5395
|
||||
[#5381]: https://github.com/nymtech/nym/pull/5381
|
||||
[#5380]: https://github.com/nymtech/nym/pull/5380
|
||||
[#5372]: https://github.com/nymtech/nym/pull/5372
|
||||
[#5370]: https://github.com/nymtech/nym/pull/5370
|
||||
[#5355]: https://github.com/nymtech/nym/pull/5355
|
||||
[#5328]: https://github.com/nymtech/nym/pull/5328
|
||||
[#5327]: https://github.com/nymtech/nym/pull/5327
|
||||
|
||||
## [2025.2-hu] (2025-02-04)
|
||||
|
||||
- Feature/remove double spending bloomfilter ([#5417])
|
||||
|
||||
Generated
+237
-305
File diff suppressed because it is too large
Load Diff
+18
-35
@@ -66,6 +66,7 @@ members = [
|
||||
"common/nym-id",
|
||||
"common/nym-metrics",
|
||||
"common/nym_offline_compact_ecash",
|
||||
"common/nymcoconut",
|
||||
"common/nymsphinx",
|
||||
"common/nymsphinx/acknowledgements",
|
||||
"common/nymsphinx/addressing",
|
||||
@@ -186,21 +187,18 @@ readme = "README.md"
|
||||
|
||||
[workspace.dependencies]
|
||||
addr = "0.15.6"
|
||||
aead = "0.5.2"
|
||||
aes = "0.8.1"
|
||||
aes-gcm = "0.10.1"
|
||||
aes-gcm-siv = "0.11.1"
|
||||
ammonia = "4"
|
||||
aead = "0.5.2"
|
||||
anyhow = "1.0.95"
|
||||
arc-swap = "1.7.1"
|
||||
argon2 = "0.5.0"
|
||||
async-trait = "0.1.86"
|
||||
axum = "0.7.5"
|
||||
axum-client-ip = "0.6.1"
|
||||
axum = "0.7.5"
|
||||
axum-extra = "0.9.4"
|
||||
axum-test = "16.2.0"
|
||||
base64 = "0.22.1"
|
||||
base85rs = "0.1.3"
|
||||
bincode = "1.3.3"
|
||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||
bit-vec = "0.7.0" # can we unify those?
|
||||
@@ -211,16 +209,16 @@ bs58 = "0.5.1"
|
||||
bytecodec = "0.4.15"
|
||||
bytes = "1.7.2"
|
||||
cargo_metadata = "0.18.1"
|
||||
celes = "2.5.0"
|
||||
celes = "2.4.0"
|
||||
cfg-if = "1.0.0"
|
||||
chacha20 = "0.9.0"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
chrono = "0.4.39"
|
||||
cipher = "0.4.3"
|
||||
clap = "4.5.30"
|
||||
clap = "4.5.28"
|
||||
clap_complete = "4.5"
|
||||
clap_complete_fig = "4.5"
|
||||
colored = "2.2"
|
||||
colored = "2.0"
|
||||
comfy-table = "7.1.4"
|
||||
console = "0.15.10"
|
||||
console-subscriber = "0.1.1"
|
||||
@@ -241,10 +239,8 @@ doc-comment = "0.3"
|
||||
dotenvy = "0.15.6"
|
||||
ecdsa = "0.16"
|
||||
ed25519-dalek = "2.1"
|
||||
encoding_rs = "0.8.35"
|
||||
env_logger = "0.11.6"
|
||||
envy = "0.4"
|
||||
etherparse = "0.13.0"
|
||||
envy = "0.4"
|
||||
eyre = "0.6.9"
|
||||
fastrand = "2.1.1"
|
||||
flate2 = "1.0.35"
|
||||
@@ -257,16 +253,16 @@ handlebars = "3.5.5"
|
||||
headers = "0.4.0"
|
||||
hex = "0.4.3"
|
||||
hex-literal = "0.3.3"
|
||||
hickory-resolver = "0.24.4"
|
||||
hickory-resolver = "0.24.3"
|
||||
hkdf = "0.12.3"
|
||||
hmac = "0.12.1"
|
||||
http = "1"
|
||||
http-body-util = "0.1"
|
||||
httpcodec = "0.2.3"
|
||||
human-repr = "1.1.0"
|
||||
humantime = "2.1.0"
|
||||
humantime-serde = "1.1.1"
|
||||
hyper = "1.6.0"
|
||||
human-repr = "1.1.0"
|
||||
hyper = "1.4.1"
|
||||
hyper-util = "0.1"
|
||||
indicatif = "0.17.11"
|
||||
inquire = "0.6.2"
|
||||
@@ -280,6 +276,7 @@ ledger-transport = "0.10.0"
|
||||
ledger-transport-hid = "0.10.0"
|
||||
log = "0.4"
|
||||
maxminddb = "0.23.0"
|
||||
rs_merkle = "1.4.2"
|
||||
mime = "0.3.17"
|
||||
moka = { version = "0.12", features = ["future"] }
|
||||
nix = "0.27.1"
|
||||
@@ -294,7 +291,7 @@ petgraph = "0.6.5"
|
||||
pin-project = "1.1"
|
||||
pin-project-lite = "0.2.16"
|
||||
pretty_env_logger = "0.4.0"
|
||||
publicsuffix = "2.3.0"
|
||||
publicsuffix = "2.2.3"
|
||||
quote = "1"
|
||||
rand = "0.8.5"
|
||||
rand_chacha = "0.3"
|
||||
@@ -308,7 +305,6 @@ reqwest = { version = "0.12.4", default-features = false }
|
||||
rocket = "0.5.0"
|
||||
rocket_cors = "0.6.0"
|
||||
rocket_okapi = "0.8.0"
|
||||
rs_merkle = "1.4.2"
|
||||
safer-ffi = "0.1.13"
|
||||
schemars = "0.8.21"
|
||||
semver = "1.0.25"
|
||||
@@ -322,7 +318,7 @@ serde_with = "3.9.0"
|
||||
serde_yaml = "0.9.25"
|
||||
sha2 = "0.10.8"
|
||||
si-scale = "0.2.3"
|
||||
sphinx-packet = "=0.3.2"
|
||||
sphinx-packet = "0.1.1"
|
||||
sqlx = "0.7.4"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
@@ -330,32 +326,29 @@ subtle-encoding = "0.5"
|
||||
syn = "1"
|
||||
sysinfo = "0.33.0"
|
||||
tap = "1.0.1"
|
||||
tar = "0.4.44"
|
||||
tar = "0.4.43"
|
||||
tempfile = "3.15"
|
||||
thiserror = "2.0"
|
||||
time = "0.3.37"
|
||||
tokio = "1.43"
|
||||
tokio-postgres = "0.7"
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-test = "0.4.4"
|
||||
tokio-tun = "0.11.5"
|
||||
tokio-tungstenite = { version = "0.20.1" }
|
||||
tokio-util = "0.7.13"
|
||||
toml = "0.8.20"
|
||||
tower = "0.5.2"
|
||||
tower = "0.4.13"
|
||||
tower-http = "0.5.2"
|
||||
tracing = "0.1.41"
|
||||
tracing-log = "0.2"
|
||||
tracing-opentelemetry = "0.19.0"
|
||||
tracing-subscriber = "0.3.19"
|
||||
tracing-tree = "0.2.2"
|
||||
tracing-log = "0.2"
|
||||
ts-rs = "10.1.0"
|
||||
tungstenite = { version = "0.20.1", default-features = false }
|
||||
uniffi = "0.29.0"
|
||||
uniffi_build = "0.29.0"
|
||||
url = "2.5"
|
||||
utoipa = "5.2"
|
||||
utoipa-swagger-ui = "8.1"
|
||||
utoipa-swagger-ui = "8.0"
|
||||
utoipauto = "0.2"
|
||||
uuid = "*"
|
||||
vergen = { version = "=8.3.1", default-features = false }
|
||||
@@ -370,7 +363,7 @@ prometheus = { version = "0.13.0" }
|
||||
# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork
|
||||
# as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm
|
||||
# plus to make our live easier we need serde support from https://github.com/zkcrypto/bls12_381/pull/125
|
||||
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", default-features = false, branch = "temp/experimental-serdect-updated" }
|
||||
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", default-features = false, branch = "temp/experimental-serdect" }
|
||||
group = { version = "0.13.0", default-features = false }
|
||||
ff = { version = "0.13.0", default-features = false }
|
||||
subtle = "2.5.0"
|
||||
@@ -438,13 +431,3 @@ opt-level = 'z'
|
||||
[profile.release.package.mix-fetch-wasm]
|
||||
# lto = true
|
||||
opt-level = 'z'
|
||||
|
||||
[workspace.lints.clippy]
|
||||
unwrap_used = "deny"
|
||||
expect_used = "deny"
|
||||
todo = "deny"
|
||||
dbg_macro = "deny"
|
||||
exit = "deny"
|
||||
panic = "deny"
|
||||
unimplemented = "deny"
|
||||
unreachable = "deny"
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.50"
|
||||
version = "1.1.47"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -49,7 +49,7 @@ impl SocketClient {
|
||||
client_output: ClientOutput,
|
||||
client_state: ClientState,
|
||||
self_address: &Recipient,
|
||||
task_client: nym_task::TaskClient,
|
||||
shutdown: nym_task::TaskClient,
|
||||
packet_type: PacketType,
|
||||
) {
|
||||
info!("Starting websocket listener...");
|
||||
@@ -77,15 +77,10 @@ impl SocketClient {
|
||||
shared_lane_queue_lengths,
|
||||
reply_controller_sender,
|
||||
Some(packet_type),
|
||||
task_client.fork("websocket_handler"),
|
||||
);
|
||||
|
||||
websocket::Listener::new(
|
||||
config.socket.host,
|
||||
config.socket.listening_port,
|
||||
task_client.with_suffix("websocket_listener"),
|
||||
)
|
||||
.start(websocket_handler);
|
||||
websocket::Listener::new(config.socket.host, config.socket.listening_port)
|
||||
.start(websocket_handler, shutdown);
|
||||
}
|
||||
|
||||
/// blocking version of `start_socket` method. Will run forever (or until SIGINT is sent)
|
||||
|
||||
@@ -19,7 +19,6 @@ use nym_sphinx::receiver::ReconstructedMessage;
|
||||
use nym_task::connections::{
|
||||
ConnectionCommand, ConnectionCommandSender, ConnectionId, LaneQueueLengths, TransmissionLane,
|
||||
};
|
||||
use nym_task::TaskClient;
|
||||
use std::time::Duration;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time::Instant;
|
||||
@@ -44,11 +43,9 @@ pub(crate) struct HandlerBuilder {
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
packet_type: Option<PacketType>,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl HandlerBuilder {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
msg_input: InputMessageSender,
|
||||
client_connection_tx: ConnectionCommandSender,
|
||||
@@ -57,7 +54,6 @@ impl HandlerBuilder {
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
packet_type: Option<PacketType>,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
Self {
|
||||
msg_input,
|
||||
@@ -67,14 +63,11 @@ impl HandlerBuilder {
|
||||
lane_queue_lengths,
|
||||
reply_controller_sender,
|
||||
packet_type,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make sure we only ever have one active handler
|
||||
pub fn create_active_handler(&self) -> Handler {
|
||||
let mut task_client = self.task_client.fork("active_handler");
|
||||
task_client.disarm();
|
||||
Handler {
|
||||
msg_input: self.msg_input.clone(),
|
||||
client_connection_tx: self.client_connection_tx.clone(),
|
||||
@@ -85,7 +78,6 @@ impl HandlerBuilder {
|
||||
lane_queue_lengths: self.lane_queue_lengths.clone(),
|
||||
reply_controller_sender: self.reply_controller_sender.clone(),
|
||||
packet_type: self.packet_type,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,18 +92,16 @@ pub(crate) struct Handler {
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
packet_type: Option<PacketType>,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl Drop for Handler {
|
||||
fn drop(&mut self) {
|
||||
if let Err(err) = self
|
||||
if self
|
||||
.buffer_requester
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverDisconnect)
|
||||
.is_err()
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("failed to disconnect the receiver from the buffer: {err}");
|
||||
}
|
||||
error!("we failed to disconnect the receiver from the buffer! presumably the shutdown procedure has been initiated!")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,23 +125,10 @@ impl Handler {
|
||||
};
|
||||
|
||||
// get the number of pending replies waiting for reply surbs
|
||||
let reply_queue_length = match self
|
||||
let reply_queue_length = self
|
||||
.reply_controller_sender
|
||||
.get_lane_queue_length(connection_id)
|
||||
.await
|
||||
{
|
||||
Ok(length) => length,
|
||||
Err(err) => {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!(
|
||||
"Failed to get reply queue length for connection {connection_id}: {err}"
|
||||
);
|
||||
}
|
||||
// We're just going to assume that the queue is empty, and I think that's okay
|
||||
// during shutdown.
|
||||
0
|
||||
}
|
||||
};
|
||||
.await;
|
||||
|
||||
let queue_length = base_length + reply_queue_length;
|
||||
|
||||
@@ -191,11 +168,10 @@ impl Handler {
|
||||
|
||||
// the ack control is now responsible for chunking, etc.
|
||||
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type);
|
||||
if let Err(err) = self.msg_input.send(input_msg).await {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send message to the input buffer: {err}");
|
||||
}
|
||||
}
|
||||
self.msg_input
|
||||
.send(input_msg)
|
||||
.await
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
|
||||
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
||||
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
||||
@@ -224,11 +200,10 @@ impl Handler {
|
||||
|
||||
let input_msg =
|
||||
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
|
||||
if let Err(err) = self.msg_input.send(input_msg).await {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send anonymous message to the input buffer: {err}");
|
||||
}
|
||||
}
|
||||
self.msg_input
|
||||
.send(input_msg)
|
||||
.await
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
|
||||
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
||||
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
||||
@@ -252,11 +227,10 @@ impl Handler {
|
||||
});
|
||||
|
||||
let input_msg = InputMessage::new_reply(recipient_tag, message, lane, self.packet_type);
|
||||
if let Err(err) = self.msg_input.send(input_msg).await {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send reply message to the input buffer: {err}");
|
||||
}
|
||||
}
|
||||
self.msg_input
|
||||
.send(input_msg)
|
||||
.await
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
|
||||
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
||||
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
||||
@@ -271,14 +245,9 @@ impl Handler {
|
||||
}
|
||||
|
||||
fn handle_closed_connection(&self, connection_id: u64) -> Option<ServerResponse> {
|
||||
if let Err(err) = self
|
||||
.client_connection_tx
|
||||
self.client_connection_tx
|
||||
.unbounded_send(ConnectionCommand::Close(connection_id))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send close connection command: {err}");
|
||||
}
|
||||
}
|
||||
.unwrap();
|
||||
None
|
||||
}
|
||||
|
||||
@@ -393,10 +362,11 @@ impl Handler {
|
||||
}
|
||||
}
|
||||
|
||||
async fn listen_for_requests(&mut self, mut msg_receiver: ReconstructedMessagesReceiver) {
|
||||
let mut task_client = self.task_client.fork("select");
|
||||
task_client.disarm();
|
||||
|
||||
async fn listen_for_requests(
|
||||
&mut self,
|
||||
mut msg_receiver: ReconstructedMessagesReceiver,
|
||||
mut task_client: nym_task::TaskClient,
|
||||
) {
|
||||
while !task_client.is_shutdown() {
|
||||
tokio::select! {
|
||||
// we can either get a client request from the websocket
|
||||
@@ -445,7 +415,15 @@ impl Handler {
|
||||
}
|
||||
|
||||
// consume self to make sure `drop` is called after this is done
|
||||
pub(crate) async fn handle_connection(mut self, socket: TcpStream) {
|
||||
pub(crate) async fn handle_connection(
|
||||
mut self,
|
||||
socket: TcpStream,
|
||||
mut task_client: nym_task::TaskClient,
|
||||
) {
|
||||
// We don't want a crash in the connection handler to trigger a shutdown of the whole
|
||||
// process.
|
||||
task_client.disarm();
|
||||
|
||||
let ws_stream = match accept_async(socket).await {
|
||||
Ok(ws_stream) => ws_stream,
|
||||
Err(err) => {
|
||||
@@ -458,18 +436,14 @@ impl Handler {
|
||||
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
|
||||
|
||||
// tell the buffer to start sending stuff to us
|
||||
if let Err(err) =
|
||||
self.buffer_requester
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
|
||||
reconstructed_sender,
|
||||
))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("failed to announce the receiver to the buffer: {err}");
|
||||
}
|
||||
}
|
||||
self.buffer_requester
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
|
||||
reconstructed_sender,
|
||||
))
|
||||
.expect("the buffer request failed!");
|
||||
|
||||
self.listen_for_requests(reconstructed_receiver).await;
|
||||
self.listen_for_requests(reconstructed_receiver, task_client)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use super::handler::HandlerBuilder;
|
||||
use log::*;
|
||||
use nym_task::TaskClient;
|
||||
use std::net::IpAddr;
|
||||
use std::{net::SocketAddr, process, sync::Arc};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
@@ -23,19 +22,21 @@ impl State {
|
||||
pub(crate) struct Listener {
|
||||
address: SocketAddr,
|
||||
state: State,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl Listener {
|
||||
pub(crate) fn new(host: IpAddr, port: u16, task_client: TaskClient) -> Self {
|
||||
pub(crate) fn new(host: IpAddr, port: u16) -> Self {
|
||||
Listener {
|
||||
address: SocketAddr::new(host, port),
|
||||
state: State::AwaitingConnection,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self, handler: HandlerBuilder) {
|
||||
pub(crate) async fn run(
|
||||
&mut self,
|
||||
handler: HandlerBuilder,
|
||||
mut task_client: nym_task::TaskClient,
|
||||
) {
|
||||
let tcp_listener = match tokio::net::TcpListener::bind(self.address).await {
|
||||
Ok(listener) => listener,
|
||||
Err(err) => {
|
||||
@@ -46,11 +47,11 @@ impl Listener {
|
||||
|
||||
let notify = Arc::new(Notify::new());
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
// When the handler finishes we check if shutdown is signalled
|
||||
_ = notify.notified() => {
|
||||
if self.task_client.is_shutdown() {
|
||||
if task_client.is_shutdown() {
|
||||
log::trace!("Websocket listener: detected shutdown after connection closed");
|
||||
break;
|
||||
}
|
||||
@@ -59,7 +60,7 @@ impl Listener {
|
||||
}
|
||||
// ... but when there is no connected client at the time of shutdown being
|
||||
// signalled, we handle it here.
|
||||
_ = self.task_client.recv() => {
|
||||
_ = task_client.recv() => {
|
||||
if !self.state.is_connected() {
|
||||
log::trace!("Not connected: shutting down");
|
||||
break;
|
||||
@@ -87,8 +88,9 @@ impl Listener {
|
||||
// hanging because the executor doesn't come back here
|
||||
let notify_clone = Arc::clone(¬ify);
|
||||
let fresh_handler = handler.create_active_handler();
|
||||
let task_client_handler = task_client.clone();
|
||||
tokio::spawn(async move {
|
||||
fresh_handler.handle_connection(socket).await;
|
||||
fresh_handler.handle_connection(socket, task_client_handler).await;
|
||||
notify_clone.notify_one();
|
||||
});
|
||||
self.state = State::Connected;
|
||||
@@ -102,9 +104,13 @@ impl Listener {
|
||||
log::debug!("Websocket listener: Exiting");
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self, handler: HandlerBuilder) -> JoinHandle<()> {
|
||||
pub(crate) fn start(
|
||||
mut self,
|
||||
handler: HandlerBuilder,
|
||||
shutdown: nym_task::TaskClient,
|
||||
) -> JoinHandle<()> {
|
||||
info!("Running websocket on {:?}", self.address.to_string());
|
||||
|
||||
tokio::spawn(async move { self.run(handler).await })
|
||||
tokio::spawn(async move { self.run(handler, shutdown).await })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.50"
|
||||
version = "1.1.47"
|
||||
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"
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
allow-unwrap-in-tests = true
|
||||
allow-expect-in-tests = true
|
||||
allow-panic-in-tests = true
|
||||
@@ -6,15 +6,14 @@ pub mod v1;
|
||||
pub mod v2;
|
||||
pub mod v3;
|
||||
pub mod v4;
|
||||
pub mod v5;
|
||||
|
||||
mod error;
|
||||
mod util;
|
||||
|
||||
pub use error::Error;
|
||||
pub use v5 as latest;
|
||||
pub use v4 as latest;
|
||||
|
||||
pub const CURRENT_VERSION: u8 = 5;
|
||||
pub const CURRENT_VERSION: u8 = 4;
|
||||
|
||||
fn make_bincode_serializer() -> impl bincode::Options {
|
||||
use bincode::Options;
|
||||
|
||||
@@ -8,8 +8,8 @@ use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
|
||||
use crate::{
|
||||
v1, v2, v3, v4,
|
||||
v5::{self, registration::IpPair},
|
||||
v1, v2, v3,
|
||||
v4::{self, registration::IpPair},
|
||||
Error,
|
||||
};
|
||||
|
||||
@@ -19,7 +19,6 @@ pub enum AuthenticatorVersion {
|
||||
V2,
|
||||
V3,
|
||||
V4,
|
||||
V5,
|
||||
UNKNOWN,
|
||||
}
|
||||
|
||||
@@ -35,8 +34,6 @@ impl From<Protocol> for AuthenticatorVersion {
|
||||
AuthenticatorVersion::V3
|
||||
} else if value.version == v4::VERSION {
|
||||
AuthenticatorVersion::V4
|
||||
} else if value.version == v5::VERSION {
|
||||
AuthenticatorVersion::V5
|
||||
} else {
|
||||
AuthenticatorVersion::UNKNOWN
|
||||
}
|
||||
@@ -71,12 +68,6 @@ impl InitMessage for v4::registration::InitMessage {
|
||||
}
|
||||
}
|
||||
|
||||
impl InitMessage for v5::registration::InitMessage {
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.pub_key
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FinalMessage {
|
||||
fn pub_key(&self) -> PeerPublicKey;
|
||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error>;
|
||||
@@ -147,24 +138,6 @@ impl FinalMessage for v4::registration::FinalMessage {
|
||||
self.gateway_client.verify(private_key, nonce)
|
||||
}
|
||||
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.gateway_client.private_ips.into()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||
self.credential.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FinalMessage for v5::registration::FinalMessage {
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.gateway_client.pub_key
|
||||
}
|
||||
|
||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
self.gateway_client.verify(private_key, nonce)
|
||||
}
|
||||
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.gateway_client.private_ips
|
||||
}
|
||||
@@ -209,39 +182,29 @@ impl TopUpMessage for v4::topup::TopUpMessage {
|
||||
}
|
||||
}
|
||||
|
||||
impl TopUpMessage for v5::topup::TopUpMessage {
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.pub_key
|
||||
}
|
||||
|
||||
fn credential(&self) -> CredentialSpendingData {
|
||||
self.credential.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AuthenticatorRequest {
|
||||
Initial {
|
||||
msg: Box<dyn InitMessage + Send + Sync + 'static>,
|
||||
protocol: Protocol,
|
||||
reply_to: Option<Recipient>,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
},
|
||||
Final {
|
||||
msg: Box<dyn FinalMessage + Send + Sync + 'static>,
|
||||
protocol: Protocol,
|
||||
reply_to: Option<Recipient>,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
},
|
||||
QueryBandwidth {
|
||||
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
|
||||
protocol: Protocol,
|
||||
reply_to: Option<Recipient>,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
},
|
||||
TopUpBandwidth {
|
||||
msg: Box<dyn TopUpMessage + Send + Sync + 'static>,
|
||||
protocol: Protocol,
|
||||
reply_to: Option<Recipient>,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
},
|
||||
}
|
||||
@@ -255,7 +218,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
version: value.version,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
reply_to: Some(value.reply_to),
|
||||
reply_to: value.reply_to,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v1::request::AuthenticatorRequestData::Final(gateway_client) => Self::Final {
|
||||
@@ -264,7 +227,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
version: value.version,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
reply_to: Some(value.reply_to),
|
||||
reply_to: value.reply_to,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v1::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
@@ -274,7 +237,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
version: value.version,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
reply_to: Some(value.reply_to),
|
||||
reply_to: value.reply_to,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
@@ -288,20 +251,20 @@ impl From<v2::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
v2::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
reply_to: value.reply_to,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v2::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
reply_to: value.reply_to,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v2::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
reply_to: value.reply_to,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
@@ -315,20 +278,20 @@ impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
v3::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
reply_to: value.reply_to,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v3::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
reply_to: value.reply_to,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v3::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
reply_to: value.reply_to,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
@@ -336,7 +299,7 @@ impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
Self::TopUpBandwidth {
|
||||
msg: top_up_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
reply_to: value.reply_to,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
@@ -350,20 +313,20 @@ impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
v4::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
reply_to: value.reply_to,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v4::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
reply_to: value.reply_to,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v4::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
reply_to: value.reply_to,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
@@ -371,42 +334,7 @@ impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
Self::TopUpBandwidth {
|
||||
msg: top_up_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
fn from(value: v5::request::AuthenticatorRequest) -> Self {
|
||||
match value.data {
|
||||
v5::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v5::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v5::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
|
||||
Self::TopUpBandwidth {
|
||||
msg: top_up_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
reply_to: value.reply_to,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ impl From<IpAddr> for IpPair {
|
||||
std::net::IpAddr::V4(ipv4_addr) => (ipv4_addr.octets()[2], ipv4_addr.octets()[3]),
|
||||
std::net::IpAddr::V6(ipv6_addr) => (ipv6_addr.octets()[14], ipv6_addr.octets()[15]),
|
||||
};
|
||||
let last_bytes = ((before_last_byte as u16) << 8) | last_byte as u16;
|
||||
let last_bytes = (before_last_byte as u16) << 8 | last_byte as u16;
|
||||
let ipv4 = Ipv4Addr::new(
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1],
|
||||
|
||||
@@ -1,478 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
|
||||
use crate::{v4, v5};
|
||||
|
||||
impl From<v4::request::AuthenticatorRequest> for v5::request::AuthenticatorRequest {
|
||||
fn from(authenticator_request: v4::request::AuthenticatorRequest) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
version: 5,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
data: authenticator_request.data.into(),
|
||||
request_id: authenticator_request.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::request::AuthenticatorRequestData> for v5::request::AuthenticatorRequestData {
|
||||
fn from(authenticator_request_data: v4::request::AuthenticatorRequestData) -> Self {
|
||||
match authenticator_request_data {
|
||||
v4::request::AuthenticatorRequestData::Initial(init_msg) => {
|
||||
v5::request::AuthenticatorRequestData::Initial(init_msg.into())
|
||||
}
|
||||
v4::request::AuthenticatorRequestData::Final(final_msg) => {
|
||||
v5::request::AuthenticatorRequestData::Final(Box::new((*final_msg).into()))
|
||||
}
|
||||
v4::request::AuthenticatorRequestData::QueryBandwidth(pub_key) => {
|
||||
v5::request::AuthenticatorRequestData::QueryBandwidth(pub_key)
|
||||
}
|
||||
v4::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
|
||||
v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::InitMessage> for v5::registration::InitMessage {
|
||||
fn from(init_msg: v4::registration::InitMessage) -> Self {
|
||||
Self {
|
||||
pub_key: init_msg.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::FinalMessage> for v5::registration::FinalMessage {
|
||||
fn from(final_msg: v4::registration::FinalMessage) -> Self {
|
||||
Self {
|
||||
gateway_client: final_msg.gateway_client.into(),
|
||||
credential: final_msg.credential,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::GatewayClient> for v5::registration::GatewayClient {
|
||||
fn from(gateway_client: v4::registration::GatewayClient) -> Self {
|
||||
Self {
|
||||
pub_key: gateway_client.pub_key,
|
||||
private_ips: gateway_client.private_ips.into(),
|
||||
mac: gateway_client.mac.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::GatewayClient> for v4::registration::GatewayClient {
|
||||
fn from(gateway_client: v5::registration::GatewayClient) -> Self {
|
||||
Self {
|
||||
pub_key: gateway_client.pub_key,
|
||||
private_ips: gateway_client.private_ips.into(),
|
||||
mac: gateway_client.mac.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::ClientMac> for v5::registration::ClientMac {
|
||||
fn from(client_mac: v4::registration::ClientMac) -> Self {
|
||||
Self::new((*client_mac).clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::ClientMac> for v4::registration::ClientMac {
|
||||
fn from(client_mac: v5::registration::ClientMac) -> Self {
|
||||
Self::new((*client_mac).clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<v4::topup::TopUpMessage>> for Box<v5::topup::TopUpMessage> {
|
||||
fn from(top_up_message: Box<v4::topup::TopUpMessage>) -> Self {
|
||||
Box::new(v5::topup::TopUpMessage {
|
||||
pub_key: top_up_message.pub_key,
|
||||
credential: top_up_message.credential,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::response::AuthenticatorResponse> for v5::response::AuthenticatorResponse {
|
||||
fn from(value: v4::response::AuthenticatorResponse) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
version: 5,
|
||||
service_provider_type: value.protocol.service_provider_type,
|
||||
},
|
||||
data: value.data.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::response::AuthenticatorResponseData> for v5::response::AuthenticatorResponseData {
|
||||
fn from(authenticator_response_data: v4::response::AuthenticatorResponseData) -> Self {
|
||||
match authenticator_response_data {
|
||||
v4::response::AuthenticatorResponseData::PendingRegistration(pending_response) => {
|
||||
v5::response::AuthenticatorResponseData::PendingRegistration(
|
||||
pending_response.into(),
|
||||
)
|
||||
}
|
||||
v4::response::AuthenticatorResponseData::Registered(registered_response) => {
|
||||
v5::response::AuthenticatorResponseData::Registered(registered_response.into())
|
||||
}
|
||||
v4::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response,
|
||||
) => v5::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response.into(),
|
||||
),
|
||||
v4::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response) => {
|
||||
v5::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::response::RegisteredResponse> for v5::response::RegisteredResponse {
|
||||
fn from(value: v4::response::RegisteredResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply: value.reply.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::response::PendingRegistrationResponse> for v5::response::PendingRegistrationResponse {
|
||||
fn from(value: v4::response::PendingRegistrationResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply: value.reply.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::RegistrationData> for v5::registration::RegistrationData {
|
||||
fn from(value: v4::registration::RegistrationData) -> Self {
|
||||
Self {
|
||||
nonce: value.nonce,
|
||||
gateway_data: value.gateway_data.into(),
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::RegistrationData> for v4::registration::RegistrationData {
|
||||
fn from(value: v5::registration::RegistrationData) -> Self {
|
||||
Self {
|
||||
nonce: value.nonce,
|
||||
gateway_data: value.gateway_data.into(),
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::response::RemainingBandwidthResponse> for v5::response::RemainingBandwidthResponse {
|
||||
fn from(value: v4::response::RemainingBandwidthResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply: value.reply.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::response::TopUpBandwidthResponse> for v5::response::TopUpBandwidthResponse {
|
||||
fn from(value: v4::response::TopUpBandwidthResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply: value.reply.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::RegistredData> for v5::registration::RegistredData {
|
||||
fn from(value: v4::registration::RegistredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ips: value.private_ips.into(),
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::RemainingBandwidthData> for v5::registration::RemainingBandwidthData {
|
||||
fn from(value: v4::registration::RemainingBandwidthData) -> Self {
|
||||
Self {
|
||||
available_bandwidth: value.available_bandwidth,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::IpPair> for v5::registration::IpPair {
|
||||
fn from(value: v4::registration::IpPair) -> Self {
|
||||
Self {
|
||||
ipv4: value.ipv4,
|
||||
ipv6: value.ipv6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::IpPair> for v4::registration::IpPair {
|
||||
fn from(value: v5::registration::IpPair) -> Self {
|
||||
Self {
|
||||
ipv4: value.ipv4,
|
||||
ipv6: value.ipv6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{
|
||||
net::{Ipv4Addr, Ipv6Addr},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_crypto::asymmetric::encryption::PrivateKey;
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use x25519_dalek::PublicKey;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
util::tests::{CREDENTIAL_BYTES, RECIPIENT},
|
||||
v4,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn upgrade_initial_req() {
|
||||
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
|
||||
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
|
||||
|
||||
let (msg, _) = v4::request::AuthenticatorRequest::new_initial_request(
|
||||
v4::registration::InitMessage::new(pub_key),
|
||||
reply_to,
|
||||
);
|
||||
let upgraded_msg = v5::request::AuthenticatorRequest::from(msg);
|
||||
|
||||
assert_eq!(
|
||||
upgraded_msg.protocol,
|
||||
Protocol {
|
||||
version: 5,
|
||||
service_provider_type: ServiceProviderType::Authenticator
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v5::request::AuthenticatorRequestData::Initial(v5::registration::InitMessage {
|
||||
pub_key
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_final_req() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let local_secret = PrivateKey::new(&mut rng);
|
||||
let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
|
||||
let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap();
|
||||
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
|
||||
let ips = v4::registration::IpPair::new(ipv4, ipv6);
|
||||
let nonce = 42;
|
||||
let gateway_client = v4::registration::GatewayClient::new(
|
||||
&local_secret,
|
||||
(&remote_secret).into(),
|
||||
ips,
|
||||
nonce,
|
||||
);
|
||||
let credential = Some(CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap());
|
||||
let final_message = v4::registration::FinalMessage {
|
||||
gateway_client: gateway_client.clone(),
|
||||
credential: credential.clone(),
|
||||
};
|
||||
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
|
||||
|
||||
let (msg, _) =
|
||||
v4::request::AuthenticatorRequest::new_final_request(final_message, reply_to);
|
||||
let upgraded_msg = v5::request::AuthenticatorRequest::from(msg);
|
||||
|
||||
assert_eq!(
|
||||
upgraded_msg.protocol,
|
||||
Protocol {
|
||||
version: 5,
|
||||
service_provider_type: ServiceProviderType::Authenticator
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v5::request::AuthenticatorRequestData::Final(Box::new(
|
||||
v5::registration::FinalMessage {
|
||||
gateway_client: v5::registration::GatewayClient::new(
|
||||
&local_secret,
|
||||
(&remote_secret).into(),
|
||||
v5::registration::IpPair::new(ipv4, ipv6),
|
||||
nonce
|
||||
),
|
||||
credential
|
||||
}
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_query_req() {
|
||||
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
|
||||
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
|
||||
|
||||
let (msg, _) = v4::request::AuthenticatorRequest::new_query_request(pub_key, reply_to);
|
||||
let upgraded_msg = v5::request::AuthenticatorRequest::from(msg);
|
||||
|
||||
assert_eq!(
|
||||
upgraded_msg.protocol,
|
||||
Protocol {
|
||||
version: 5,
|
||||
service_provider_type: ServiceProviderType::Authenticator
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v5::request::AuthenticatorRequestData::QueryBandwidth(pub_key)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_pending_reg_resp() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let local_secret = PrivateKey::new(&mut rng);
|
||||
let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
|
||||
let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap();
|
||||
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
|
||||
let ips = v4::registration::IpPair::new(ipv4, ipv6);
|
||||
let nonce = 42;
|
||||
let wg_port = 51822;
|
||||
let gateway_data = v4::registration::GatewayClient::new(
|
||||
&local_secret,
|
||||
(&remote_secret).into(),
|
||||
ips,
|
||||
nonce,
|
||||
);
|
||||
let registration_data = v4::registration::RegistrationData {
|
||||
nonce,
|
||||
gateway_data,
|
||||
wg_port,
|
||||
};
|
||||
let request_id = 123;
|
||||
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
|
||||
|
||||
let msg = v4::response::AuthenticatorResponse::new_pending_registration_success(
|
||||
registration_data,
|
||||
request_id,
|
||||
reply_to,
|
||||
);
|
||||
let upgraded_msg = v5::response::AuthenticatorResponse::from(msg);
|
||||
|
||||
assert_eq!(
|
||||
upgraded_msg.protocol,
|
||||
Protocol {
|
||||
version: 5,
|
||||
service_provider_type: ServiceProviderType::Authenticator
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v5::response::AuthenticatorResponseData::PendingRegistration(
|
||||
v5::response::PendingRegistrationResponse {
|
||||
request_id,
|
||||
reply: v5::registration::RegistrationData {
|
||||
nonce,
|
||||
gateway_data: v5::registration::GatewayClient::new(
|
||||
&local_secret,
|
||||
(&remote_secret).into(),
|
||||
v5::registration::IpPair::new(ipv4, ipv6),
|
||||
nonce
|
||||
),
|
||||
wg_port
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_registered_resp() {
|
||||
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
|
||||
let ipv4 = Ipv4Addr::from_str("10.1.10.10").unwrap();
|
||||
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
|
||||
let private_ips = v4::registration::IpPair::new(ipv4, ipv6);
|
||||
let wg_port = 51822;
|
||||
let registred_data = v4::registration::RegistredData {
|
||||
pub_key,
|
||||
private_ips,
|
||||
wg_port,
|
||||
};
|
||||
let request_id = 123;
|
||||
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
|
||||
|
||||
let msg = v4::response::AuthenticatorResponse::new_registered(
|
||||
registred_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
);
|
||||
let upgraded_msg = v5::response::AuthenticatorResponse::from(msg);
|
||||
|
||||
assert_eq!(
|
||||
upgraded_msg.protocol,
|
||||
Protocol {
|
||||
version: 5,
|
||||
service_provider_type: ServiceProviderType::Authenticator
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v5::response::AuthenticatorResponseData::Registered(v5::response::RegisteredResponse {
|
||||
request_id,
|
||||
reply: v5::registration::RegistredData {
|
||||
wg_port,
|
||||
pub_key,
|
||||
private_ips: v5::registration::IpPair::new(ipv4, ipv6)
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_remaining_bandwidth_resp() {
|
||||
let available_bandwidth = 42;
|
||||
let remaining_bandwidth_data = Some(v4::registration::RemainingBandwidthData {
|
||||
available_bandwidth,
|
||||
});
|
||||
let request_id = 123;
|
||||
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
|
||||
|
||||
let msg = v4::response::AuthenticatorResponse::new_remaining_bandwidth(
|
||||
remaining_bandwidth_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
);
|
||||
let upgraded_msg = v5::response::AuthenticatorResponse::from(msg);
|
||||
|
||||
assert_eq!(
|
||||
upgraded_msg.protocol,
|
||||
Protocol {
|
||||
version: 5,
|
||||
service_provider_type: ServiceProviderType::Authenticator
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v5::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
v5::response::RemainingBandwidthResponse {
|
||||
request_id,
|
||||
reply: Some(v5::registration::RemainingBandwidthData {
|
||||
available_bandwidth,
|
||||
})
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod conversion;
|
||||
pub mod registration;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod topup;
|
||||
|
||||
pub const VERSION: u8 = 5;
|
||||
@@ -1,287 +0,0 @@
|
||||
// -2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::Error;
|
||||
use base64::{engine::general_purpose, Engine};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_network_defaults::constants::{WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6};
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::time::SystemTime;
|
||||
use std::{fmt, ops::Deref, str::FromStr};
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
use hmac::{Hmac, Mac};
|
||||
#[cfg(feature = "verify")]
|
||||
use nym_crypto::asymmetric::encryption::PrivateKey;
|
||||
#[cfg(feature = "verify")]
|
||||
use sha2::Sha256;
|
||||
|
||||
pub type PendingRegistrations = HashMap<PeerPublicKey, RegistrationData>;
|
||||
pub type PrivateIPs = HashMap<IpPair, Taken>;
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
pub type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
pub type Nonce = u64;
|
||||
pub type Taken = Option<SystemTime>;
|
||||
|
||||
pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct IpPair {
|
||||
pub ipv4: Ipv4Addr,
|
||||
pub ipv6: Ipv6Addr,
|
||||
}
|
||||
|
||||
impl IpPair {
|
||||
pub fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self {
|
||||
IpPair { ipv4, ipv6 }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Ipv4Addr, Ipv6Addr)> for IpPair {
|
||||
fn from((ipv4, ipv6): (Ipv4Addr, Ipv6Addr)) -> Self {
|
||||
IpPair { ipv4, ipv6 }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IpPair {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "({}, {})", self.ipv4, self.ipv6)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IpAddr> for IpPair {
|
||||
fn from(value: IpAddr) -> Self {
|
||||
let (before_last_byte, last_byte) = match value {
|
||||
std::net::IpAddr::V4(ipv4_addr) => (ipv4_addr.octets()[2], ipv4_addr.octets()[3]),
|
||||
std::net::IpAddr::V6(ipv6_addr) => (ipv6_addr.octets()[14], ipv6_addr.octets()[15]),
|
||||
};
|
||||
let last_bytes = ((before_last_byte as u16) << 8) | last_byte as u16;
|
||||
let ipv4 = Ipv4Addr::new(
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1],
|
||||
before_last_byte,
|
||||
last_byte,
|
||||
);
|
||||
let ipv6 = Ipv6Addr::new(
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[0],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[1],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[2],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[3],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[4],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[5],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[6],
|
||||
last_bytes,
|
||||
);
|
||||
IpPair::new(ipv4, ipv6)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct InitMessage {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
}
|
||||
|
||||
impl InitMessage {
|
||||
pub fn new(pub_key: PeerPublicKey) -> Self {
|
||||
InitMessage { pub_key }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct FinalMessage {
|
||||
/// Gateway client data
|
||||
pub gateway_client: GatewayClient,
|
||||
|
||||
/// Ecash credential
|
||||
pub credential: Option<CredentialSpendingData>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RegistrationData {
|
||||
pub nonce: u64,
|
||||
pub gateway_data: GatewayClient,
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RegistredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ips: IpPair,
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RemainingBandwidthData {
|
||||
pub available_bandwidth: i64,
|
||||
}
|
||||
|
||||
/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret.
|
||||
/// Gateway/Nym node can then verify pub_key payload using the same process
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct GatewayClient {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
|
||||
/// Assigned private IPs (v4 and v6)
|
||||
pub private_ips: IpPair,
|
||||
|
||||
/// Sha256 hmac on the data (alongside the prior nonce)
|
||||
pub mac: ClientMac,
|
||||
}
|
||||
|
||||
impl GatewayClient {
|
||||
#[cfg(feature = "verify")]
|
||||
pub fn new(
|
||||
local_secret: &PrivateKey,
|
||||
remote_public: x25519_dalek::PublicKey,
|
||||
private_ips: IpPair,
|
||||
nonce: u64,
|
||||
) -> Self {
|
||||
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
|
||||
#[allow(clippy::expect_used)]
|
||||
let static_secret = x25519_dalek::StaticSecret::from(local_secret.to_bytes());
|
||||
let local_public: x25519_dalek::PublicKey = (&static_secret).into();
|
||||
|
||||
let dh = static_secret.diffie_hellman(&remote_public);
|
||||
|
||||
// TODO: change that to use our nym_crypto::hmac module instead
|
||||
#[allow(clippy::expect_used)]
|
||||
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())
|
||||
.expect("x25519 shared secret is always 32 bytes long");
|
||||
|
||||
mac.update(local_public.as_bytes());
|
||||
mac.update(private_ips.to_string().as_bytes());
|
||||
mac.update(&nonce.to_le_bytes());
|
||||
|
||||
GatewayClient {
|
||||
pub_key: PeerPublicKey::new(local_public),
|
||||
private_ips,
|
||||
mac: ClientMac(mac.finalize().into_bytes().to_vec()),
|
||||
}
|
||||
}
|
||||
|
||||
// Reusable secret should be gateways Wireguard PK
|
||||
// Client should perform this step when generating its payload, using its own WG PK
|
||||
#[cfg(feature = "verify")]
|
||||
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
|
||||
#[allow(clippy::expect_used)]
|
||||
let static_secret = x25519_dalek::StaticSecret::from(gateway_key.to_bytes());
|
||||
|
||||
let dh = static_secret.diffie_hellman(&self.pub_key);
|
||||
|
||||
// TODO: change that to use our nym_crypto::hmac module instead
|
||||
#[allow(clippy::expect_used)]
|
||||
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())
|
||||
.expect("x25519 shared secret is always 32 bytes long");
|
||||
|
||||
mac.update(self.pub_key.as_bytes());
|
||||
mac.update(self.private_ips.to_string().as_bytes());
|
||||
mac.update(&nonce.to_le_bytes());
|
||||
|
||||
mac.verify_slice(&self.mac)
|
||||
.map_err(|source| Error::FailedClientMacVerification {
|
||||
client: self.pub_key.to_string(),
|
||||
source,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pub_key(&self) -> PeerPublicKey {
|
||||
self.pub_key
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: change the inner type into generic array of size HmacSha256::OutputSize
|
||||
// TODO2: rely on our internal crypto/hmac
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ClientMac(Vec<u8>);
|
||||
|
||||
impl fmt::Display for ClientMac {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", general_purpose::STANDARD.encode(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientMac {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(mac: Vec<u8>) -> Self {
|
||||
ClientMac(mac)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ClientMac {
|
||||
type Target = Vec<u8>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ClientMac {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mac_bytes: Vec<u8> =
|
||||
general_purpose::STANDARD
|
||||
.decode(s)
|
||||
.map_err(|source| Error::MalformedClientMac {
|
||||
mac: s.to_string(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
Ok(ClientMac(mac_bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ClientMac {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let encoded_key = general_purpose::STANDARD.encode(self.0.clone());
|
||||
serializer.serialize_str(&encoded_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ClientMac {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let encoded_key = String::deserialize(deserializer)?;
|
||||
ClientMac::from_str(&encoded_key).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
|
||||
#[test]
|
||||
fn create_ip_pair() {
|
||||
let ipv4: IpAddr = Ipv4Addr::from_str("10.1.10.50").unwrap().into();
|
||||
let ipv6: IpAddr = Ipv6Addr::from_str("fc01::0a32").unwrap().into();
|
||||
|
||||
assert_eq!(IpPair::from(ipv4), IpPair::from(ipv6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "verify")]
|
||||
fn client_request_roundtrip() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let gateway_key_pair = encryption::KeyPair::new(&mut rng);
|
||||
let client_key_pair = encryption::KeyPair::new(&mut rng);
|
||||
|
||||
let nonce = 1234567890;
|
||||
|
||||
let client = GatewayClient::new(
|
||||
client_key_pair.private_key(),
|
||||
x25519_dalek::PublicKey::from(gateway_key_pair.public_key().to_bytes()),
|
||||
IpPair::new("10.0.0.42".parse().unwrap(), "fc00::42".parse().unwrap()),
|
||||
nonce,
|
||||
);
|
||||
assert!(client.verify(gateway_key_pair.private_key(), nonce).is_ok())
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{
|
||||
registration::{FinalMessage, InitMessage},
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
|
||||
use super::VERSION;
|
||||
|
||||
fn generate_random() -> u64 {
|
||||
use rand::RngCore;
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AuthenticatorRequest {
|
||||
pub protocol: Protocol,
|
||||
pub data: AuthenticatorRequestData,
|
||||
pub request_id: u64,
|
||||
}
|
||||
|
||||
impl AuthenticatorRequest {
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
|
||||
pub fn new_initial_request(init_message: InitMessage) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::Initial(init_message),
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_final_request(final_message: FinalMessage) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::Final(Box::new(final_message)),
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_query_request(peer_public_key: PeerPublicKey) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::QueryBandwidth(peer_public_key),
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_topup_request(top_up_message: TopUpMessage) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::TopUpBandwidth(Box::new(top_up_message)),
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum AuthenticatorRequestData {
|
||||
Initial(InitMessage),
|
||||
Final(Box<FinalMessage>),
|
||||
QueryBandwidth(PeerPublicKey),
|
||||
TopUpBandwidth(Box<TopUpMessage>),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn check_first_bytes_protocol() {
|
||||
let version = 5;
|
||||
let data = AuthenticatorRequest {
|
||||
protocol: Protocol {
|
||||
version,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
data: AuthenticatorRequestData::Initial(InitMessage::new(
|
||||
PeerPublicKey::from_str("yvNUDpT5l7W/xDhiu6HkqTHDQwbs/B3J5UrLmORl1EQ=").unwrap(),
|
||||
)),
|
||||
request_id: 1,
|
||||
};
|
||||
let bytes = *data.to_bytes().unwrap().first_chunk::<2>().unwrap();
|
||||
assert_eq!(bytes, [version, ServiceProviderType::Authenticator as u8]);
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
|
||||
use super::VERSION;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AuthenticatorResponse {
|
||||
pub protocol: Protocol,
|
||||
pub data: AuthenticatorResponseData,
|
||||
}
|
||||
|
||||
impl AuthenticatorResponse {
|
||||
pub fn new_pending_registration_success(
|
||||
registration_data: RegistrationData,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorResponseData::PendingRegistration(PendingRegistrationResponse {
|
||||
reply: registration_data,
|
||||
request_id,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_registered(registred_data: RegistredData, request_id: u64) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorResponseData::Registered(RegisteredResponse {
|
||||
reply: registred_data,
|
||||
request_id,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_remaining_bandwidth(
|
||||
remaining_bandwidth_data: Option<RemainingBandwidthData>,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse {
|
||||
reply: remaining_bandwidth_data,
|
||||
request_id,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_topup_bandwidth(
|
||||
remaining_bandwidth_data: RemainingBandwidthData,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorResponseData::TopUpBandwidth(TopUpBandwidthResponse {
|
||||
reply: remaining_bandwidth_data,
|
||||
request_id,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match &self.data {
|
||||
AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::Registered(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::TopUpBandwidth(response) => Some(response.request_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum AuthenticatorResponseData {
|
||||
PendingRegistration(PendingRegistrationResponse),
|
||||
Registered(RegisteredResponse),
|
||||
RemainingBandwidth(RemainingBandwidthResponse),
|
||||
TopUpBandwidth(TopUpBandwidthResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PendingRegistrationResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: RegistrationData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: RegistredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RemainingBandwidthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: Option<RemainingBandwidthData>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TopUpBandwidthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: RemainingBandwidthData,
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct TopUpMessage {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
|
||||
/// Ecash credential
|
||||
pub credential: CredentialSpendingData,
|
||||
}
|
||||
@@ -105,24 +105,26 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
async fn get_aggregate_verification_key(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
ecash_apis: &mut ApiClientsWrapper<'_, C>,
|
||||
apis: &mut ApiClientsWrapper,
|
||||
) -> Result<VerificationKeyAuth, BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let ecash_apis = apis.get_or_init(epoch_id, &self.client).await?;
|
||||
get_aggregate_verification_key(&self.storage, epoch_id, ecash_apis).await
|
||||
}
|
||||
|
||||
async fn get_coin_index_signatures(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
ecash_apis: &mut ApiClientsWrapper<'_, C>,
|
||||
apis: &mut ApiClientsWrapper,
|
||||
) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let ecash_apis = apis.get_or_init(epoch_id, &self.client).await?;
|
||||
get_coin_index_signatures(&self.storage, epoch_id, ecash_apis).await
|
||||
}
|
||||
|
||||
@@ -130,12 +132,13 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
expiration_date: Date,
|
||||
ecash_apis: &mut ApiClientsWrapper<'_, C>,
|
||||
apis: &mut ApiClientsWrapper,
|
||||
) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let ecash_apis = apis.get_or_init(epoch_id, &self.client).await?;
|
||||
get_expiration_date_signatures(&self.storage, epoch_id, expiration_date, ecash_apis).await
|
||||
}
|
||||
|
||||
@@ -151,7 +154,7 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
{
|
||||
let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
|
||||
let expiration_date = retrieved_ticketbook.ticketbook.expiration_date();
|
||||
let mut api_clients = ApiClientsWrapper::new(&self.client, epoch_id);
|
||||
let mut api_clients = Default::default();
|
||||
|
||||
let verification_key = self
|
||||
.get_aggregate_verification_key(epoch_id, &mut api_clients)
|
||||
|
||||
@@ -21,67 +21,30 @@ use rand::thread_rng;
|
||||
use std::fmt::Display;
|
||||
use std::future::Future;
|
||||
|
||||
pub(crate) trait EcashClientsProvider {
|
||||
async fn try_get_ecash_clients(
|
||||
&mut self,
|
||||
) -> Result<Vec<EcashApiClient>, BandwidthControllerError>;
|
||||
}
|
||||
// it really doesn't need the RwLock because it's never moved across tasks,
|
||||
// but we need all the Send/Sync action
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ApiClientsWrapper(Option<Vec<EcashApiClient>>);
|
||||
|
||||
impl EcashClientsProvider for Vec<EcashApiClient> {
|
||||
async fn try_get_ecash_clients(
|
||||
impl ApiClientsWrapper {
|
||||
pub(crate) async fn get_or_init<C>(
|
||||
&mut self,
|
||||
) -> Result<Vec<EcashApiClient>, BandwidthControllerError> {
|
||||
Ok(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> EcashClientsProvider for &mut ApiClientsWrapper<'_, C>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
{
|
||||
async fn try_get_ecash_clients(
|
||||
&mut self,
|
||||
) -> Result<Vec<EcashApiClient>, BandwidthControllerError> {
|
||||
self.clients().await
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum ApiClientsWrapper<'a, C> {
|
||||
Uninitialised {
|
||||
query_client: &'a C,
|
||||
epoch_id: EpochId,
|
||||
},
|
||||
Cached {
|
||||
clients: Vec<EcashApiClient>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a, C> ApiClientsWrapper<'a, C> {
|
||||
pub(crate) fn new(query_client: &'a C, epoch_id: EpochId) -> Self {
|
||||
ApiClientsWrapper::Uninitialised {
|
||||
query_client,
|
||||
epoch_id,
|
||||
}
|
||||
}
|
||||
|
||||
async fn clients(&mut self) -> Result<Vec<EcashApiClient>, BandwidthControllerError>
|
||||
dkg_client: &C,
|
||||
) -> Result<Vec<EcashApiClient>, BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
{
|
||||
match self {
|
||||
ApiClientsWrapper::Uninitialised {
|
||||
query_client,
|
||||
epoch_id,
|
||||
} => {
|
||||
let clients = all_ecash_api_clients(*query_client, *epoch_id).await?;
|
||||
*self = ApiClientsWrapper::Cached {
|
||||
clients: clients.clone(),
|
||||
};
|
||||
|
||||
Ok(clients)
|
||||
}
|
||||
ApiClientsWrapper::Cached { clients } => Ok(clients.clone()),
|
||||
if let Some(cached) = &self.0 {
|
||||
return Ok(cached.clone());
|
||||
}
|
||||
|
||||
let clients = all_ecash_api_clients(dkg_client, epoch_id).await?;
|
||||
|
||||
// technically we don't have to be cloning all the clients here, but it's way simpler than
|
||||
// dealing with locking and whatnot given the performance penalty is negligible
|
||||
self.0 = Some(clients.clone());
|
||||
Ok(clients)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +76,7 @@ where
|
||||
pub(crate) async fn get_aggregate_verification_key<St>(
|
||||
storage: &St,
|
||||
epoch_id: EpochId,
|
||||
mut ecash_apis: impl EcashClientsProvider,
|
||||
ecash_apis: Vec<EcashApiClient>,
|
||||
) -> Result<VerificationKeyAuth, BandwidthControllerError>
|
||||
where
|
||||
St: Storage,
|
||||
@@ -127,8 +90,6 @@ where
|
||||
return Ok(stored);
|
||||
};
|
||||
|
||||
let ecash_apis = ecash_apis.try_get_ecash_clients().await?;
|
||||
|
||||
let master_vk = query_random_apis_until_success(
|
||||
ecash_apis,
|
||||
|api| async move { api.api_client.master_verification_key(Some(epoch_id)).await },
|
||||
@@ -154,7 +115,7 @@ where
|
||||
pub(crate) async fn get_coin_index_signatures<St>(
|
||||
storage: &St,
|
||||
epoch_id: EpochId,
|
||||
mut ecash_apis: impl EcashClientsProvider,
|
||||
ecash_apis: Vec<EcashApiClient>,
|
||||
) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError>
|
||||
where
|
||||
St: Storage,
|
||||
@@ -168,8 +129,6 @@ where
|
||||
return Ok(stored);
|
||||
};
|
||||
|
||||
let ecash_apis = ecash_apis.try_get_ecash_clients().await?;
|
||||
|
||||
let index_sigs = query_random_apis_until_success(
|
||||
ecash_apis,
|
||||
|api| async move {
|
||||
@@ -200,7 +159,7 @@ pub(crate) async fn get_expiration_date_signatures<St>(
|
||||
storage: &St,
|
||||
epoch_id: EpochId,
|
||||
expiration_date: Date,
|
||||
mut ecash_apis: impl EcashClientsProvider,
|
||||
ecash_apis: Vec<EcashApiClient>,
|
||||
) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError>
|
||||
where
|
||||
St: Storage,
|
||||
@@ -214,8 +173,6 @@ where
|
||||
return Ok(stored);
|
||||
};
|
||||
|
||||
let ecash_apis = ecash_apis.try_get_ecash_clients().await?;
|
||||
|
||||
let expiration_sigs = query_random_apis_until_success(
|
||||
ecash_apis,
|
||||
|api| async move {
|
||||
|
||||
@@ -45,12 +45,11 @@ const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70;
|
||||
// clients/client-core/src/client/replies/reply_storage/surb_storage.rs
|
||||
const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
|
||||
const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200;
|
||||
const DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER: usize = 0;
|
||||
|
||||
// define how much to request at once
|
||||
// clients/client-core/src/client/replies/reply_controller.rs
|
||||
const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10;
|
||||
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 50;
|
||||
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100;
|
||||
|
||||
const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500;
|
||||
|
||||
@@ -622,10 +621,6 @@ pub struct ReplySurbs {
|
||||
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
|
||||
pub maximum_reply_surb_storage_threshold: usize,
|
||||
|
||||
/// Defines the soft threshold ontop of the minimum reply surb storage threshold for when the client
|
||||
/// should proactively request additional reply surbs.
|
||||
pub minimum_reply_surb_threshold_buffer: usize,
|
||||
|
||||
/// Defines the minimum number of reply surbs the client would request.
|
||||
pub minimum_reply_surb_request_size: u32,
|
||||
|
||||
@@ -665,7 +660,6 @@ impl Default for ReplySurbs {
|
||||
ReplySurbs {
|
||||
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
||||
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
||||
minimum_reply_surb_threshold_buffer: DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER,
|
||||
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
|
||||
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
|
||||
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
|
||||
|
||||
@@ -181,7 +181,6 @@ impl From<ConfigV5> for Config {
|
||||
maximum_reply_surb_age: value.debug.reply_surbs.maximum_reply_surb_age,
|
||||
maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age,
|
||||
surb_mix_hops: value.debug.reply_surbs.surb_mix_hops,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
@@ -40,7 +40,6 @@ use nym_client_core_config_types::ForgetMe;
|
||||
use nym_client_core_gateways_storage::{GatewayDetails, GatewaysDetailsStore};
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_crypto::hkdf::DerivationMaterial;
|
||||
use nym_gateway_client::client::config::GatewayClientConfig;
|
||||
use nym_gateway_client::{
|
||||
AcknowledgementReceiver, GatewayClient, GatewayConfig, MixnetMessageReceiver, PacketRouter,
|
||||
@@ -193,8 +192,6 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
|
||||
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
|
||||
derivation_material: Option<DerivationMaterial>,
|
||||
}
|
||||
|
||||
impl<C, S> BaseClientBuilder<C, S>
|
||||
@@ -219,19 +216,9 @@ where
|
||||
setup_method: GatewaySetup::MustLoad { gateway_id: None },
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
derivation_material: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_derivation_material(
|
||||
mut self,
|
||||
derivation_material: Option<DerivationMaterial>,
|
||||
) -> Self {
|
||||
self.derivation_material = derivation_material;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self {
|
||||
self.config.debug.forget_me = *forget_me;
|
||||
@@ -310,7 +297,7 @@ where
|
||||
topology_accessor: TopologyAccessor,
|
||||
mix_tx: BatchMixMessageSender,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
shutdown: TaskClient,
|
||||
) {
|
||||
info!("Starting loop cover traffic stream...");
|
||||
|
||||
@@ -323,10 +310,9 @@ where
|
||||
debug_config.traffic,
|
||||
debug_config.cover_traffic,
|
||||
stats_tx,
|
||||
task_client,
|
||||
);
|
||||
|
||||
stream.start();
|
||||
stream.start_with_shutdown(shutdown);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -341,7 +327,7 @@ where
|
||||
reply_controller_receiver: ReplyControllerReceiver,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
task_client: TaskClient,
|
||||
shutdown: TaskClient,
|
||||
packet_type: PacketType,
|
||||
stats_tx: ClientStatsSender,
|
||||
) {
|
||||
@@ -359,9 +345,8 @@ where
|
||||
lane_queue_lengths,
|
||||
client_connection_rx,
|
||||
stats_tx,
|
||||
task_client,
|
||||
)
|
||||
.start(packet_type);
|
||||
.start_with_shutdown(shutdown, packet_type);
|
||||
}
|
||||
|
||||
// buffer controlling all messages fetched from provider
|
||||
@@ -384,9 +369,8 @@ where
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
metrics_reporter,
|
||||
shutdown,
|
||||
);
|
||||
controller.start()
|
||||
controller.start_with_shutdown(shutdown)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -575,22 +559,15 @@ where
|
||||
topology_accessor: TopologyAccessor,
|
||||
local_gateway: NodeIdentity,
|
||||
wait_for_gateway: bool,
|
||||
mut task_client: TaskClient,
|
||||
mut shutdown: TaskClient,
|
||||
) -> Result<(), ClientCoreError> {
|
||||
let topology_refresher_config =
|
||||
TopologyRefresherConfig::new(topology_config.topology_refresh_rate);
|
||||
|
||||
if topology_config.disable_refreshing {
|
||||
// if we're not spawning the refresher, don't cause shutdown immediately
|
||||
info!("The background topology refesher is not going to be started");
|
||||
task_client.disarm();
|
||||
}
|
||||
|
||||
let mut topology_refresher = TopologyRefresher::new(
|
||||
topology_refresher_config,
|
||||
topology_accessor,
|
||||
topology_provider,
|
||||
task_client,
|
||||
);
|
||||
// before returning, block entire runtime to refresh the current network view so that any
|
||||
// components depending on topology would see a non-empty view
|
||||
@@ -631,11 +608,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if !topology_config.disable_refreshing {
|
||||
if topology_config.disable_refreshing {
|
||||
// if we're not spawning the refresher, don't cause shutdown immediately
|
||||
info!("The topology refesher is not going to be started");
|
||||
shutdown.disarm();
|
||||
} else {
|
||||
// don't spawn the refresher if we don't want to be refreshing the topology.
|
||||
// only use the initial values obtained
|
||||
info!("Starting topology refresher...");
|
||||
topology_refresher.start();
|
||||
topology_refresher.start_with_shutdown(shutdown);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -646,17 +627,17 @@ where
|
||||
user_agent: Option<UserAgent>,
|
||||
client_stats_id: String,
|
||||
input_sender: Sender<InputMessage>,
|
||||
task_client: TaskClient,
|
||||
shutdown: TaskClient,
|
||||
) -> ClientStatsSender {
|
||||
info!("Starting statistics control...");
|
||||
StatisticsControl::create_and_start(
|
||||
StatisticsControl::create_and_start_with_shutdown(
|
||||
config.debug.stats_reporting,
|
||||
user_agent
|
||||
.map(|u| u.application)
|
||||
.unwrap_or("unknown".to_string()),
|
||||
client_stats_id,
|
||||
input_sender.clone(),
|
||||
task_client,
|
||||
shutdown.with_suffix("controller"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -666,8 +647,8 @@ where
|
||||
) -> (BatchMixMessageSender, ClientRequestSender) {
|
||||
info!("Starting mix traffic controller...");
|
||||
let (mix_traffic_controller, mix_tx, client_tx) =
|
||||
MixTrafficController::new(gateway_transceiver, shutdown);
|
||||
mix_traffic_controller.start();
|
||||
MixTrafficController::new(gateway_transceiver);
|
||||
mix_traffic_controller.start_with_shutdown(shutdown);
|
||||
(mix_tx, client_tx)
|
||||
}
|
||||
|
||||
@@ -703,7 +684,6 @@ where
|
||||
setup_method: GatewaySetup,
|
||||
key_store: &S::KeyStore,
|
||||
details_store: &S::GatewaysDetailsStore,
|
||||
derivation_material: Option<DerivationMaterial>,
|
||||
) -> Result<InitialisationResult, ClientCoreError>
|
||||
where
|
||||
<S::KeyStore as KeyStore>::StorageError: Sync + Send,
|
||||
@@ -713,12 +693,7 @@ where
|
||||
if key_store.load_keys().await.is_err() {
|
||||
info!("could not find valid client keys - a new set will be generated");
|
||||
let mut rng = OsRng;
|
||||
let keys = if let Some(derivation_material) = derivation_material {
|
||||
ClientKeys::from_master_key(&mut rng, &derivation_material)
|
||||
.map_err(|_| ClientCoreError::HkdfDerivationError {})?
|
||||
} else {
|
||||
ClientKeys::generate_new(&mut rng)
|
||||
};
|
||||
let keys = ClientKeys::generate_new(&mut rng);
|
||||
store_client_keys(keys, key_store).await?;
|
||||
}
|
||||
|
||||
@@ -740,7 +715,6 @@ where
|
||||
self.setup_method,
|
||||
self.client_store.key_store(),
|
||||
self.client_store.gateway_details_store(),
|
||||
self.derivation_material,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ use nym_sphinx::cover::generate_loop_cover_packet;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_sphinx::utils::sample_poisson_duration;
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
use nym_task::TaskClient;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
@@ -65,8 +64,6 @@ where
|
||||
packet_type: PacketType,
|
||||
|
||||
stats_tx: ClientStatsSender,
|
||||
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R> Stream for LoopCoverTrafficStream<R>
|
||||
@@ -113,7 +110,6 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
traffic_config: config::Traffic,
|
||||
cover_config: config::CoverTraffic,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
@@ -132,7 +128,6 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
secondary_packet_size: traffic_config.secondary_packet_size,
|
||||
packet_type: traffic_config.packet_type,
|
||||
stats_tx,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +175,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
}
|
||||
};
|
||||
|
||||
let cover_message = match generate_loop_cover_packet(
|
||||
let cover_message = generate_loop_cover_packet(
|
||||
&mut self.rng,
|
||||
topology_ref,
|
||||
&self.ack_key,
|
||||
@@ -189,15 +184,8 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
self.cover_traffic.loop_cover_traffic_average_delay,
|
||||
cover_traffic_packet_size,
|
||||
self.packet_type,
|
||||
) {
|
||||
Ok(cover_message) => cover_message,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"Somehow failed to generate a loop cover message with a valid topology: {err}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
)
|
||||
.expect("Somehow failed to generate a loop cover message with a valid topology");
|
||||
|
||||
if let Err(err) = self.mix_tx.try_send(vec![cover_message]) {
|
||||
match err {
|
||||
@@ -229,7 +217,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
pub fn start(mut self) {
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
|
||||
if self.cover_traffic.disable_loop_cover_traffic_stream {
|
||||
// we should have never got here in the first place - the task should have never been created to begin with
|
||||
// so panic and review the code that lead to this branch
|
||||
@@ -243,8 +231,6 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
);
|
||||
self.set_next_delay(sampled);
|
||||
|
||||
let mut shutdown = self.task_client.fork("select");
|
||||
|
||||
spawn_future(async move {
|
||||
debug!("Started LoopCoverTrafficStream with graceful shutdown support");
|
||||
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use nym_crypto::{
|
||||
asymmetric::{encryption, identity},
|
||||
hkdf::{DerivationMaterial, InvalidLength},
|
||||
};
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
@@ -13,7 +10,6 @@ use std::sync::Arc;
|
||||
use zeroize::ZeroizeOnDrop;
|
||||
|
||||
pub mod persistence;
|
||||
mod test;
|
||||
|
||||
// Note: to support key rotation in the future, all keys will require adding an extra smart pointer,
|
||||
// most likely an AtomicCell, or if it doesn't work as I think it does, a Mutex. Although I think
|
||||
@@ -47,24 +43,6 @@ impl ClientKeys {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_master_key<R>(
|
||||
rng: &mut R,
|
||||
derivation_material: &DerivationMaterial,
|
||||
) -> Result<Self, InvalidLength>
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
let secret = derivation_material.derive_secret()?;
|
||||
Ok(ClientKeys {
|
||||
identity_keypair: Arc::new(identity::KeyPair::from_secret(
|
||||
secret,
|
||||
derivation_material.index(),
|
||||
)),
|
||||
encryption_keypair: Arc::new(encryption::KeyPair::new(rng)),
|
||||
ack_key: Arc::new(AckKey::new(rng)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_keys(
|
||||
id_keypair: identity::KeyPair,
|
||||
enc_keypair: encryption::KeyPair,
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::client::key_manager::ClientKeys;
|
||||
use nym_crypto::hkdf::DerivationMaterial;
|
||||
use rand::SeedableRng;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
|
||||
#[test]
|
||||
fn test_from_master_key_success() {
|
||||
// Set up a deterministic RNG.
|
||||
let seed = [33u8; 32];
|
||||
let mut rng = ChaCha20Rng::from_seed(seed);
|
||||
|
||||
// Set up the derivation material.
|
||||
let master_key = b"this is a secret master key";
|
||||
let salt = b"unique-salt";
|
||||
let derivation_material = DerivationMaterial::new(master_key, 0, salt);
|
||||
|
||||
// Generate ClientKeys from the master key.
|
||||
let client_keys = ClientKeys::from_master_key(&mut rng, &derivation_material)
|
||||
.expect("Failed to create client keys");
|
||||
|
||||
assert_eq!(
|
||||
client_keys.identity_keypair().public_key().to_string(),
|
||||
String::from("FX4Undr5LPPBA7zThWWpAKXKQTXSbW1C28PnxbCqUkU4")
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
client_keys.identity_keypair().private_key().to_string(),
|
||||
String::from("6S3uMi2rU5SwyUUYCiMrF5qqdcYnEDMYLggBSvavVzEt")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_master_key_deterministic_identity() {
|
||||
// Using identical derivation material should result in the exactly same identity keypair.
|
||||
let seed = [1u8; 32];
|
||||
let mut rng1 = ChaCha20Rng::from_seed(seed);
|
||||
let mut rng2 = ChaCha20Rng::from_seed(seed);
|
||||
|
||||
let master_key = b"another secret master key";
|
||||
let salt = b"deterministic-salt";
|
||||
let index = 7u32;
|
||||
let derivation_material = DerivationMaterial::new(master_key, index, salt);
|
||||
|
||||
let client_keys1 = ClientKeys::from_master_key(&mut rng1, &derivation_material)
|
||||
.expect("Failed to create client keys (first instance)");
|
||||
let client_keys2 = ClientKeys::from_master_key(&mut rng2, &derivation_material)
|
||||
.expect("Failed to create client keys (second instance)");
|
||||
|
||||
assert_eq!(
|
||||
client_keys1.identity_keypair().public_key().to_string(),
|
||||
client_keys2.identity_keypair().public_key().to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
client_keys1.identity_keypair().private_key().to_string(),
|
||||
client_keys2.identity_keypair().private_key().to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_master_key_different_indices() {
|
||||
// Changing the index should yield a different identity key.
|
||||
let seed = [5u8; 32];
|
||||
let mut rng = ChaCha20Rng::from_seed(seed);
|
||||
|
||||
let master_key = b"same secret key";
|
||||
let salt = b"same-salt";
|
||||
|
||||
let derivation_material1 = DerivationMaterial::new(master_key, 1, salt);
|
||||
let derivation_material2 = DerivationMaterial::new(master_key, 2, salt);
|
||||
|
||||
let client_keys1 = ClientKeys::from_master_key(&mut rng, &derivation_material1)
|
||||
.expect("Failed to create client keys for index 1");
|
||||
let client_keys2 = ClientKeys::from_master_key(&mut rng, &derivation_material2)
|
||||
.expect("Failed to create client keys for index 2");
|
||||
|
||||
assert_ne!(
|
||||
client_keys1.identity_keypair().public_key().to_string(),
|
||||
client_keys2.identity_keypair().public_key().to_string()
|
||||
);
|
||||
|
||||
assert_ne!(
|
||||
client_keys1.identity_keypair().private_key().to_string(),
|
||||
client_keys2.identity_keypair().private_key().to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ use crate::spawn_future;
|
||||
use log::*;
|
||||
use nym_gateway_requests::ClientRequest;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_task::TaskClient;
|
||||
use transceiver::ErasedGatewayError;
|
||||
|
||||
pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<Vec<MixPacket>>;
|
||||
@@ -33,14 +32,11 @@ pub struct MixTrafficController {
|
||||
// TODO: this is temporary work-around.
|
||||
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
|
||||
consecutive_gateway_failure_count: usize,
|
||||
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl MixTrafficController {
|
||||
pub fn new<T>(
|
||||
gateway_transceiver: T,
|
||||
task_client: TaskClient,
|
||||
) -> (
|
||||
MixTrafficController,
|
||||
BatchMixMessageSender,
|
||||
@@ -60,7 +56,6 @@ impl MixTrafficController {
|
||||
mix_rx: message_receiver,
|
||||
client_rx: client_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
task_client,
|
||||
},
|
||||
message_sender,
|
||||
client_sender,
|
||||
@@ -69,7 +64,6 @@ impl MixTrafficController {
|
||||
|
||||
pub fn new_dynamic(
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
task_client: TaskClient,
|
||||
) -> (
|
||||
MixTrafficController,
|
||||
BatchMixMessageSender,
|
||||
@@ -84,7 +78,6 @@ impl MixTrafficController {
|
||||
mix_rx: message_receiver,
|
||||
client_rx: client_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
task_client,
|
||||
},
|
||||
message_sender,
|
||||
client_sender,
|
||||
@@ -116,11 +109,11 @@ impl MixTrafficController {
|
||||
result
|
||||
}
|
||||
|
||||
pub fn start(mut self) {
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
|
||||
spawn_future(async move {
|
||||
debug!("Started MixTrafficController with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
mix_packets = self.mix_rx.recv() => match mix_packets {
|
||||
Some(mix_packets) => {
|
||||
@@ -132,7 +125,7 @@ impl MixTrafficController {
|
||||
error!("Failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead");
|
||||
// Do we need to handle the embedded mixnet client case
|
||||
// separately?
|
||||
self.task_client.send_we_stopped(Box::new(ClientCoreError::GatewayFailedToForwardMessages));
|
||||
shutdown.send_we_stopped(Box::new(ClientCoreError::GatewayFailedToForwardMessages));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -153,13 +146,13 @@ impl MixTrafficController {
|
||||
log::trace!("MixTrafficController, client request channel closed");
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("MixTrafficController: Received shutdown");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
shutdown.recv_timeout().await;
|
||||
|
||||
log::debug!("MixTrafficController: Exiting");
|
||||
});
|
||||
|
||||
+6
-15
@@ -11,7 +11,6 @@ use nym_sphinx::{
|
||||
acknowledgements::{identifier::recover_identifier, AckKey},
|
||||
chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID},
|
||||
};
|
||||
use nym_task::TaskClient;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Module responsible for listening for any data resembling acknowledgements from the network
|
||||
@@ -21,7 +20,6 @@ pub(super) struct AcknowledgementListener {
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
action_sender: AckActionSender,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl AcknowledgementListener {
|
||||
@@ -30,14 +28,12 @@ impl AcknowledgementListener {
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
action_sender: AckActionSender,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
AcknowledgementListener {
|
||||
ack_key,
|
||||
ack_receiver,
|
||||
action_sender,
|
||||
stats_tx,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,14 +64,9 @@ impl AcknowledgementListener {
|
||||
trace!("Received {} from the mix network", frag_id);
|
||||
self.stats_tx
|
||||
.report(PacketStatisticsEvent::RealAckReceived(ack_content.len()).into());
|
||||
if let Err(err) = self
|
||||
.action_sender
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_remove(frag_id))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send remove action to action controller: {err}");
|
||||
}
|
||||
}
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn handle_ack_receiver_item(&mut self, item: Vec<Vec<u8>>) {
|
||||
@@ -85,10 +76,10 @@ impl AcknowledgementListener {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
debug!("Started AcknowledgementListener with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
acks = self.ack_receiver.next() => match acks {
|
||||
Some(acks) => self.handle_ack_receiver_item(acks).await,
|
||||
@@ -97,12 +88,12 @@ impl AcknowledgementListener {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("AcknowledgementListener: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("AcknowledgementListener: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
+16
-15
@@ -9,7 +9,6 @@ use log::*;
|
||||
use nym_nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue, QueueKey};
|
||||
use nym_sphinx::chunking::fragment::FragmentIdentifier;
|
||||
use nym_sphinx::Delay as SphinxDelay;
|
||||
use nym_task::TaskClient;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -102,8 +101,6 @@ pub(super) struct ActionController {
|
||||
|
||||
/// Channel for notifying `RetransmissionRequestListener` about expired acknowledgements.
|
||||
retransmission_sender: RetransmissionRequestSender,
|
||||
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl ActionController {
|
||||
@@ -111,7 +108,6 @@ impl ActionController {
|
||||
config: Config,
|
||||
retransmission_sender: RetransmissionRequestSender,
|
||||
incoming_actions: AckActionReceiver,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
ActionController {
|
||||
config,
|
||||
@@ -119,7 +115,6 @@ impl ActionController {
|
||||
pending_acks_timers: NonExhaustiveDelayQueue::new(),
|
||||
incoming_actions,
|
||||
retransmission_sender,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +216,11 @@ impl ActionController {
|
||||
}
|
||||
|
||||
// note: when the entry expires it's automatically removed from pending_acks_timers
|
||||
fn handle_expired_ack_timer(&mut self, expired_ack: Expired<FragmentIdentifier>) {
|
||||
fn handle_expired_ack_timer(
|
||||
&mut self,
|
||||
expired_ack: Expired<FragmentIdentifier>,
|
||||
task_client: &mut nym_task::TaskClient,
|
||||
) {
|
||||
// I'm honestly not sure how to handle it, because getting it means other things in our
|
||||
// system are already misbehaving. If we ever see this panic, then I guess we should worry
|
||||
// about it. Perhaps just reschedule it at later point?
|
||||
@@ -239,13 +238,15 @@ impl ActionController {
|
||||
// downgrading an arc and then upgrading vs cloning is difference of 30ns vs 15ns
|
||||
// so it's literally a NO difference while it might prevent us from unnecessarily
|
||||
// resending data (in maybe 1 in 1 million cases, but it's something)
|
||||
if let Err(err) = self
|
||||
if self
|
||||
.retransmission_sender
|
||||
.unbounded_send(Arc::downgrade(pending_ack_data))
|
||||
.is_err()
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
log::error!("Failed to send pending ack for retransmission: {err}");
|
||||
}
|
||||
assert!(
|
||||
task_client.is_shutdown_poll(),
|
||||
"Failed to send pending ack for retransmission"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// this shouldn't cause any issues but shouldn't have happened to begin with!
|
||||
@@ -264,10 +265,10 @@ impl ActionController {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
debug!("Started ActionController with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
action = self.incoming_actions.next() => match action {
|
||||
Some(action) => self.process_action(action),
|
||||
@@ -279,19 +280,19 @@ impl ActionController {
|
||||
}
|
||||
},
|
||||
expired_ack = self.pending_acks_timers.next() => match expired_ack {
|
||||
Some(expired_ack) => self.handle_expired_ack_timer(expired_ack),
|
||||
Some(expired_ack) => self.handle_expired_ack_timer(expired_ack, &mut shutdown),
|
||||
None => {
|
||||
log::trace!("ActionController: Stopping since ack channel closed");
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("ActionController: Received shutdown");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("ActionController: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
+5
-15
@@ -11,7 +11,6 @@ use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_task::TaskClient;
|
||||
use rand::{CryptoRng, Rng};
|
||||
|
||||
/// Module responsible for dealing with the received messages: splitting them, creating acknowledgements,
|
||||
@@ -24,7 +23,6 @@ where
|
||||
input_receiver: InputMessageReceiver,
|
||||
message_handler: MessageHandler<R>,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R> InputMessageListener<R>
|
||||
@@ -38,13 +36,11 @@ where
|
||||
input_receiver: InputMessageReceiver,
|
||||
message_handler: MessageHandler<R>,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
InputMessageListener {
|
||||
input_receiver,
|
||||
message_handler,
|
||||
reply_controller_sender,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,14 +63,8 @@ where
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
// offload reply handling to the dedicated task
|
||||
if let Err(err) = self
|
||||
.reply_controller_sender
|
||||
self.reply_controller_sender
|
||||
.send_reply(recipient_tag, data, lane)
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("failed to send a reply - {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_plain_message(
|
||||
@@ -174,10 +164,10 @@ where
|
||||
};
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
debug!("Started InputMessageListener with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
input_msg = self.input_receiver.recv() => match input_msg {
|
||||
Some(input_msg) => {
|
||||
@@ -188,12 +178,12 @@ where
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("InputMessageListener: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("InputMessageListener: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
+27
-18
@@ -24,7 +24,6 @@ use nym_sphinx::{
|
||||
Delay as SphinxDelay,
|
||||
};
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use nym_task::TaskClient;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::{
|
||||
sync::{Arc, Weak},
|
||||
@@ -67,7 +66,7 @@ pub(crate) enum PacketDestination {
|
||||
|
||||
/// Structure representing a data `Fragment` that is on-route to the specified `Recipient`
|
||||
#[derive(Debug)]
|
||||
pub struct PendingAcknowledgement {
|
||||
pub(crate) struct PendingAcknowledgement {
|
||||
message_chunk: Fragment,
|
||||
delay: SphinxDelay,
|
||||
destination: PacketDestination,
|
||||
@@ -217,7 +216,6 @@ where
|
||||
message_handler: MessageHandler<R>,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
let (retransmission_tx, retransmission_rx) = mpsc::unbounded();
|
||||
|
||||
@@ -227,7 +225,6 @@ where
|
||||
action_config,
|
||||
retransmission_tx,
|
||||
connectors.ack_action_receiver,
|
||||
task_client.fork("action_controller"),
|
||||
);
|
||||
|
||||
// will listen for any acks coming from the network
|
||||
@@ -236,7 +233,6 @@ where
|
||||
connectors.ack_receiver,
|
||||
connectors.ack_action_sender.clone(),
|
||||
stats_tx,
|
||||
task_client.fork("acknowledgement_listener"),
|
||||
);
|
||||
|
||||
// will listen for any new messages from the client
|
||||
@@ -244,7 +240,6 @@ where
|
||||
connectors.input_receiver,
|
||||
message_handler.clone(),
|
||||
reply_controller_sender.clone(),
|
||||
task_client.fork("input_message_listener"),
|
||||
);
|
||||
|
||||
// will listen for any ack timeouts and trigger retransmission
|
||||
@@ -254,16 +249,12 @@ where
|
||||
message_handler,
|
||||
retransmission_rx,
|
||||
reply_controller_sender,
|
||||
task_client.fork("retransmission_request_listener"),
|
||||
);
|
||||
|
||||
// will listen for events indicating the packet was sent through the network so that
|
||||
// the retransmission timer should be started.
|
||||
let sent_notification_listener = SentNotificationListener::new(
|
||||
connectors.sent_notifier,
|
||||
connectors.ack_action_sender,
|
||||
task_client.with_suffix("sent_notification_listener"),
|
||||
);
|
||||
let sent_notification_listener =
|
||||
SentNotificationListener::new(connectors.sent_notifier, connectors.ack_action_sender);
|
||||
|
||||
AcknowledgementController {
|
||||
acknowledgement_listener,
|
||||
@@ -274,35 +265,53 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn start(self, packet_type: PacketType) {
|
||||
pub(super) fn start_with_shutdown(
|
||||
self,
|
||||
shutdown: nym_task::TaskClient,
|
||||
packet_type: PacketType,
|
||||
) {
|
||||
let mut acknowledgement_listener = self.acknowledgement_listener;
|
||||
let mut input_message_listener = self.input_message_listener;
|
||||
let mut retransmission_request_listener = self.retransmission_request_listener;
|
||||
let mut sent_notification_listener = self.sent_notification_listener;
|
||||
let mut action_controller = self.action_controller;
|
||||
|
||||
let shutdown_handle = shutdown.fork("acknowledgement_listener");
|
||||
spawn_future(async move {
|
||||
acknowledgement_listener.run().await;
|
||||
acknowledgement_listener
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
.await;
|
||||
debug!("The acknowledgement listener has finished execution!");
|
||||
});
|
||||
|
||||
let shutdown_handle = shutdown.fork("input_message_listener");
|
||||
spawn_future(async move {
|
||||
input_message_listener.run().await;
|
||||
input_message_listener
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
.await;
|
||||
debug!("The input listener has finished execution!");
|
||||
});
|
||||
|
||||
let shutdown_handle = shutdown.fork("retransmission_request_listener");
|
||||
spawn_future(async move {
|
||||
retransmission_request_listener.run(packet_type).await;
|
||||
retransmission_request_listener
|
||||
.run_with_shutdown(shutdown_handle, packet_type)
|
||||
.await;
|
||||
debug!("The retransmission request listener has finished execution!");
|
||||
});
|
||||
|
||||
let shutdown_handle = shutdown.fork("sent_notification_listener");
|
||||
spawn_future(async move {
|
||||
sent_notification_listener.run().await;
|
||||
sent_notification_listener
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
.await;
|
||||
debug!("The sent notification listener has finished execution!");
|
||||
});
|
||||
|
||||
spawn_future(async move {
|
||||
action_controller.run().await;
|
||||
action_controller
|
||||
.run_with_shutdown(shutdown.with_suffix("action_controller"))
|
||||
.await;
|
||||
debug!("The controller has finished execution!");
|
||||
});
|
||||
}
|
||||
|
||||
+17
-32
@@ -14,7 +14,7 @@ use log::*;
|
||||
use nym_sphinx::chunking::fragment::Fragment;
|
||||
use nym_sphinx::preparer::PreparedFragment;
|
||||
use nym_sphinx::{addressing::clients::Recipient, params::PacketType};
|
||||
use nym_task::{connections::TransmissionLane, TaskClient};
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
@@ -25,7 +25,6 @@ pub(super) struct RetransmissionRequestListener<R> {
|
||||
message_handler: MessageHandler<R>,
|
||||
request_receiver: RetransmissionRequestReceiver,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R> RetransmissionRequestListener<R>
|
||||
@@ -38,7 +37,6 @@ where
|
||||
message_handler: MessageHandler<R>,
|
||||
request_receiver: RetransmissionRequestReceiver,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
RetransmissionRequestListener {
|
||||
maximum_retransmissions,
|
||||
@@ -46,7 +44,6 @@ where
|
||||
message_handler,
|
||||
request_receiver,
|
||||
reply_controller_sender,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,12 +79,9 @@ where
|
||||
if let Some(limit) = self.maximum_retransmissions {
|
||||
if timed_out_ack.retransmissions >= limit {
|
||||
warn!("reached maximum number of allowed retransmissions for the packet");
|
||||
if let Err(err) = self
|
||||
.action_sender
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_remove(frag_id))
|
||||
{
|
||||
error!("Failed to send remove action to the controller: {err}");
|
||||
}
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -99,16 +93,11 @@ where
|
||||
} => {
|
||||
// if this is retransmission for reply, offload it to the dedicated task
|
||||
// that deals with all the surbs
|
||||
if let Err(err) = self.reply_controller_sender.send_retransmission_data(
|
||||
return self.reply_controller_sender.send_retransmission_data(
|
||||
*recipient_tag,
|
||||
weak_timed_out_ack,
|
||||
*extra_surb_request,
|
||||
) {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send retransmission data to the reply controller: {err}");
|
||||
}
|
||||
}
|
||||
return;
|
||||
);
|
||||
}
|
||||
PacketDestination::KnownRecipient(recipient) => {
|
||||
self.prepare_normal_retransmission_chunk(
|
||||
@@ -125,12 +114,9 @@ where
|
||||
Err(err) => {
|
||||
warn!("Could not retransmit the packet - {err}");
|
||||
// we NEED to start timer here otherwise we will have this guy permanently stuck in memory
|
||||
if let Err(err) = self
|
||||
.action_sender
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_start_timer(frag_id))
|
||||
{
|
||||
error!("Failed to send start timer action to the controller: {err}");
|
||||
}
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -155,14 +141,9 @@ where
|
||||
// is sent to the `OutQueueControl` and has gone through its internal queue
|
||||
// with the additional poisson delay.
|
||||
// And since Actions are executed in order `UpdateTimer` will HAVE TO be executed before `StartTimer`
|
||||
if let Err(err) = self
|
||||
.action_sender
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_update_pending_ack(frag_id, new_delay))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send update pending ack action to the controller: {err}");
|
||||
}
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
// send to `OutQueueControl` to eventually send to the mix network
|
||||
self.message_handler
|
||||
@@ -176,10 +157,14 @@ where
|
||||
.await
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self, packet_type: PacketType) {
|
||||
pub(super) async fn run_with_shutdown(
|
||||
&mut self,
|
||||
mut shutdown: nym_task::TaskClient,
|
||||
packet_type: PacketType,
|
||||
) {
|
||||
debug!("Started RetransmissionRequestListener with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
timed_out_ack = self.request_receiver.next() => match timed_out_ack {
|
||||
Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack, packet_type).await,
|
||||
@@ -188,12 +173,12 @@ where
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("RetransmissionRequestListener: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("RetransmissionRequestListener: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
+6
-15
@@ -6,7 +6,6 @@ use super::SentPacketNotificationReceiver;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nym_sphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
|
||||
use nym_task::TaskClient;
|
||||
|
||||
/// Module responsible for starting up retransmission timers.
|
||||
/// It is required because when we send our packet to the `real traffic stream` controlled
|
||||
@@ -15,19 +14,16 @@ use nym_task::TaskClient;
|
||||
pub(super) struct SentNotificationListener {
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
action_sender: AckActionSender,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl SentNotificationListener {
|
||||
pub(super) fn new(
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
action_sender: AckActionSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
SentNotificationListener {
|
||||
sent_notifier,
|
||||
action_sender,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,20 +32,15 @@ impl SentNotificationListener {
|
||||
trace!("sent off a cover message - no need to start retransmission timer!");
|
||||
return;
|
||||
}
|
||||
if let Err(err) = self
|
||||
.action_sender
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_start_timer(frag_id))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send start timer action to action controller: {err}");
|
||||
}
|
||||
}
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
debug!("Started SentNotificationListener with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
frag_id = self.sent_notifier.next() => match frag_id {
|
||||
Some(frag_id) => {
|
||||
@@ -60,13 +51,13 @@ impl SentNotificationListener {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("SentNotificationListener: Received shutdown");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(self.task_client.is_shutdown_poll());
|
||||
assert!(shutdown.is_shutdown_poll());
|
||||
log::debug!("SentNotificationListener: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_sphinx::preparer::{MessagePreparer, PreparedFragment};
|
||||
use nym_sphinx::Delay;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_task::TaskClient;
|
||||
use nym_topology::{NymRouteProvider, NymTopologyError};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::collections::HashMap;
|
||||
@@ -33,12 +32,10 @@ pub enum PreparationError {
|
||||
#[error(transparent)]
|
||||
NymTopologyError(#[from] NymTopologyError),
|
||||
|
||||
#[error("message too long for a single SURB, splitting into {fragments} fragments.")]
|
||||
#[error("The received message cannot be sent using a single reply surb. It ended up getting split into {fragments} fragments.")]
|
||||
MessageTooLongForSingleSurb { fragments: usize },
|
||||
|
||||
#[error(
|
||||
"not enough reply SURBs to send the message, available: {available} required: {required}."
|
||||
)]
|
||||
#[error("Not enough reply SURBs to send the message. We have {available} available and require at least {required}.")]
|
||||
NotEnoughSurbs { available: usize, required: usize },
|
||||
}
|
||||
|
||||
@@ -152,14 +149,12 @@ pub(crate) struct MessageHandler<R> {
|
||||
topology_access: TopologyAccessor,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
tag_storage: UsedSenderTags,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R> MessageHandler<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
config: Config,
|
||||
rng: R,
|
||||
@@ -168,7 +163,6 @@ where
|
||||
topology_access: TopologyAccessor,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
tag_storage: UsedSenderTags,
|
||||
task_client: TaskClient,
|
||||
) -> Self
|
||||
where
|
||||
R: Copy,
|
||||
@@ -189,7 +183,6 @@ where
|
||||
topology_access,
|
||||
reply_key_storage,
|
||||
tag_storage,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,25 +609,15 @@ where
|
||||
}
|
||||
|
||||
pub(crate) fn update_ack_delay(&self, id: FragmentIdentifier, new_delay: Delay) {
|
||||
if let Err(err) = self
|
||||
.action_sender
|
||||
self.action_sender
|
||||
.unbounded_send(Action::UpdatePendingAck(id, new_delay))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send update action to the controller: {err}");
|
||||
}
|
||||
}
|
||||
.expect("action control task has died")
|
||||
}
|
||||
|
||||
pub(crate) fn insert_pending_acks(&self, pending_acks: Vec<PendingAcknowledgement>) {
|
||||
if let Err(err) = self
|
||||
.action_sender
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_insert(pending_acks))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send insert action to the controller: {err}");
|
||||
}
|
||||
}
|
||||
.expect("action control task has died")
|
||||
}
|
||||
|
||||
// tells real message sender (with the poisson timer) to send this to the mix network
|
||||
@@ -648,9 +631,9 @@ where
|
||||
.send((messages, transmission_lane))
|
||||
.await
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to forward messages to the real message sender: {err}");
|
||||
}
|
||||
error!(
|
||||
"Failed to forward messages to the real message sender (OutQueueControl): {err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
|
||||
use nym_task::TaskClient;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -148,7 +147,6 @@ impl RealMessagesController<OsRng> {
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
@@ -179,7 +177,6 @@ impl RealMessagesController<OsRng> {
|
||||
topology_access.clone(),
|
||||
reply_storage.key_storage(),
|
||||
reply_storage.tags_storage(),
|
||||
task_client.fork("message_handler"),
|
||||
);
|
||||
|
||||
let ack_control = AcknowledgementController::new(
|
||||
@@ -189,7 +186,6 @@ impl RealMessagesController<OsRng> {
|
||||
message_handler.clone(),
|
||||
reply_controller_sender,
|
||||
stats_tx.clone(),
|
||||
task_client.fork("ack_control"),
|
||||
);
|
||||
|
||||
let reply_control = ReplyController::new(
|
||||
@@ -197,7 +193,6 @@ impl RealMessagesController<OsRng> {
|
||||
message_handler,
|
||||
reply_storage,
|
||||
reply_controller_receiver,
|
||||
task_client.fork("reply_controller"),
|
||||
);
|
||||
|
||||
let out_queue_control = OutQueueControl::new(
|
||||
@@ -210,7 +205,6 @@ impl RealMessagesController<OsRng> {
|
||||
lane_queue_lengths,
|
||||
client_connection_rx,
|
||||
stats_tx,
|
||||
task_client.with_suffix("out_queue_control"),
|
||||
);
|
||||
|
||||
RealMessagesController {
|
||||
@@ -220,20 +214,22 @@ impl RealMessagesController<OsRng> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(self, packet_type: PacketType) {
|
||||
pub fn start_with_shutdown(self, shutdown: nym_task::TaskClient, packet_type: PacketType) {
|
||||
let mut out_queue_control = self.out_queue_control;
|
||||
let ack_control = self.ack_control;
|
||||
let mut reply_control = self.reply_control;
|
||||
|
||||
let shutdown_handle = shutdown.fork("out_queue_control");
|
||||
spawn_future(async move {
|
||||
out_queue_control.run().await;
|
||||
out_queue_control.run_with_shutdown(shutdown_handle).await;
|
||||
debug!("The out queue controller has finished execution!");
|
||||
});
|
||||
let shutdown_handle = shutdown.fork("reply_control");
|
||||
spawn_future(async move {
|
||||
reply_control.run().await;
|
||||
reply_control.run_with_shutdown(shutdown_handle).await;
|
||||
debug!("The reply controller has finished execution!");
|
||||
});
|
||||
|
||||
ack_control.start(packet_type);
|
||||
ack_control.start_with_shutdown(shutdown.with_suffix("ack_control"), packet_type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, C
|
||||
use nym_task::connections::{
|
||||
ConnectionCommand, ConnectionCommandReceiver, ConnectionId, LaneQueueLengths, TransmissionLane,
|
||||
};
|
||||
use nym_task::TaskClient;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
@@ -118,8 +117,6 @@ where
|
||||
|
||||
/// Channel used for sending metrics events (specifically `PacketStatistics` events) to the metrics tracker.
|
||||
stats_tx: ClientStatsSender,
|
||||
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -179,7 +176,6 @@ where
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
OutQueueControl {
|
||||
config,
|
||||
@@ -194,7 +190,6 @@ where
|
||||
client_connection_rx,
|
||||
lane_queue_lengths,
|
||||
stats_tx,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,9 +198,7 @@ where
|
||||
// queues and client load rather than the required delay. So realistically we can treat
|
||||
// whatever is about to happen as negligible additional delay.
|
||||
trace!("{} is about to get sent to the mixnet", frag_id);
|
||||
if let Err(err) = self.sent_notifier.unbounded_send(frag_id) {
|
||||
error!("Failed to notify about sent message: {err}");
|
||||
}
|
||||
self.sent_notifier.unbounded_send(frag_id).unwrap();
|
||||
}
|
||||
|
||||
fn loop_cover_message_size(&mut self) -> PacketSize {
|
||||
@@ -278,9 +271,7 @@ where
|
||||
};
|
||||
|
||||
if let Err(err) = self.mix_tx.send(vec![next_message]).await {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
log::error!("Failed to send: {err}");
|
||||
}
|
||||
log::error!("Failed to send: {err}");
|
||||
} else {
|
||||
let event = if fragment_id.is_some() {
|
||||
PacketStatisticsEvent::RealPacketSent(packet_size)
|
||||
@@ -513,29 +504,21 @@ where
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn log_status(&self, shutdown: &mut TaskClient) {
|
||||
fn log_status(&self, shutdown: &mut nym_task::TaskClient) {
|
||||
use crate::error::ClientCoreStatusMessage;
|
||||
|
||||
let packets = self.transmission_buffer.total_size();
|
||||
let lanes = self.transmission_buffer.lanes();
|
||||
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();
|
||||
|
||||
let lane_status = lanes
|
||||
.iter()
|
||||
.map(|lane_name| {
|
||||
let lane_length = self.transmission_buffer.lane_length(lane_name).unwrap_or(0);
|
||||
format!("{lane_name:?}: {lane_length}")
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
|
||||
let status_str = if self.config.traffic.disable_main_poisson_packet_distribution {
|
||||
format!("Packet backlog: {lane_status}, no delay")
|
||||
format!("Packet backlog: {backlog:.2} kiB ({packets}), {lanes} lanes, no delay")
|
||||
} else {
|
||||
format!("Packet backlog: {lane_status}, avg delay: {delay}ms ({mult})")
|
||||
format!(
|
||||
"Packet backlog: {backlog:.2} kiB ({packets}), {lanes} lanes, avg delay: {delay}ms ({mult})"
|
||||
)
|
||||
};
|
||||
|
||||
if packets > 1000 {
|
||||
log::warn!("{status_str}");
|
||||
} else if packets > 0 {
|
||||
@@ -552,16 +535,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
debug!("Started OutQueueControl with graceful shutdown support");
|
||||
|
||||
let mut shutdown = self.task_client.fork("select");
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let mut status_timer = tokio::time::interval(Duration::from_secs(5));
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
|
||||
@@ -20,7 +20,6 @@ use nym_sphinx::message::{NymMessage, PlainMessage};
|
||||
use nym_sphinx::params::ReplySurbKeyDigestAlgorithm;
|
||||
use nym_sphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
use nym_task::TaskClient;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -153,7 +152,6 @@ struct ReceivedMessagesBuffer<R: MessageReceiver> {
|
||||
inner: Arc<Mutex<ReceivedMessagesBufferInner<R>>>,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
@@ -162,7 +160,6 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
ReceivedMessagesBuffer {
|
||||
inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner {
|
||||
@@ -175,7 +172,6 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
})),
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,15 +257,11 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = self.reply_controller_sender.send_additional_surbs(
|
||||
self.reply_controller_sender.send_additional_surbs(
|
||||
msg.sender_tag,
|
||||
reply_surbs,
|
||||
from_surb_request,
|
||||
) {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("{err}");
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
reconstructed
|
||||
}
|
||||
@@ -284,14 +276,8 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
ReplyMessageContent::Data { message } => reconstructed.push(message.into()),
|
||||
ReplyMessageContent::SurbRequest { recipient, amount } => {
|
||||
debug!("received request for {amount} additional reply SURBs from {recipient}");
|
||||
if let Err(err) = self
|
||||
.reply_controller_sender
|
||||
.send_additional_surbs_request(*recipient, amount)
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("{err}");
|
||||
}
|
||||
}
|
||||
self.reply_controller_sender
|
||||
.send_additional_surbs_request(*recipient, amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -413,19 +399,16 @@ pub enum ReceivedBufferMessage {
|
||||
struct RequestReceiver<R: MessageReceiver> {
|
||||
received_buffer: ReceivedMessagesBuffer<R>,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver> RequestReceiver<R> {
|
||||
fn new(
|
||||
received_buffer: ReceivedMessagesBuffer<R>,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
RequestReceiver {
|
||||
received_buffer,
|
||||
query_receiver,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,12 +423,12 @@ impl<R: MessageReceiver> RequestReceiver<R> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
debug!("Started RequestReceiver with graceful shutdown support");
|
||||
while !self.task_client.is_shutdown() {
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("RequestReceiver: Received shutdown");
|
||||
}
|
||||
request = self.query_receiver.next() => {
|
||||
@@ -458,7 +441,7 @@ impl<R: MessageReceiver> RequestReceiver<R> {
|
||||
},
|
||||
}
|
||||
}
|
||||
self.task_client.recv().await;
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("RequestReceiver: Exiting");
|
||||
}
|
||||
}
|
||||
@@ -466,25 +449,25 @@ impl<R: MessageReceiver> RequestReceiver<R> {
|
||||
struct FragmentedMessageReceiver<R: MessageReceiver> {
|
||||
received_buffer: ReceivedMessagesBuffer<R>,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver> FragmentedMessageReceiver<R> {
|
||||
fn new(
|
||||
received_buffer: ReceivedMessagesBuffer<R>,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
FragmentedMessageReceiver {
|
||||
received_buffer,
|
||||
mixnet_packet_receiver,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self) -> Result<(), MessageRecoveryError> {
|
||||
async fn run_with_shutdown(
|
||||
&mut self,
|
||||
mut shutdown: nym_task::TaskClient,
|
||||
) -> Result<(), MessageRecoveryError> {
|
||||
debug!("Started FragmentedMessageReceiver with graceful shutdown support");
|
||||
while !self.task_client.is_shutdown() {
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
new_messages = self.mixnet_packet_receiver.next() => {
|
||||
if let Some(new_messages) = new_messages {
|
||||
@@ -494,12 +477,12 @@ impl<R: MessageReceiver> FragmentedMessageReceiver<R> {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv_with_delay() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("FragmentedMessageReceiver: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("FragmentedMessageReceiver: Exiting");
|
||||
Ok(())
|
||||
}
|
||||
@@ -518,42 +501,41 @@ impl<R: MessageReceiver + Clone + Send + 'static> ReceivedMessagesBufferControll
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
metrics_reporter: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
let received_buffer = ReceivedMessagesBuffer::new(
|
||||
local_encryption_keypair,
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
metrics_reporter,
|
||||
task_client.fork("received_messages_buffer"),
|
||||
);
|
||||
|
||||
ReceivedMessagesBufferController {
|
||||
fragmented_message_receiver: FragmentedMessageReceiver::new(
|
||||
received_buffer.clone(),
|
||||
mixnet_packet_receiver,
|
||||
task_client.fork("fragmented_message_receiver"),
|
||||
),
|
||||
request_receiver: RequestReceiver::new(
|
||||
received_buffer,
|
||||
query_receiver,
|
||||
task_client.with_suffix("request_receiver"),
|
||||
),
|
||||
request_receiver: RequestReceiver::new(received_buffer, query_receiver),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(self) {
|
||||
pub fn start_with_shutdown(self, shutdown: nym_task::TaskClient) {
|
||||
let mut fragmented_message_receiver = self.fragmented_message_receiver;
|
||||
let mut request_receiver = self.request_receiver;
|
||||
|
||||
let shutdown_handle = shutdown.fork("fragmented_message_receiver");
|
||||
spawn_future(async move {
|
||||
match fragmented_message_receiver.run().await {
|
||||
match fragmented_message_receiver
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(e) => error!("{e}"),
|
||||
}
|
||||
});
|
||||
spawn_future(async move {
|
||||
request_receiver.run().await;
|
||||
request_receiver
|
||||
.run_with_shutdown(shutdown.with_suffix("request_receiver"))
|
||||
.await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::anonymous_replies::ReplySurb;
|
||||
use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier};
|
||||
use nym_task::connections::{ConnectionId, TransmissionLane};
|
||||
use nym_task::TaskClient;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::cmp::{max, min};
|
||||
use std::collections::btree_map::Entry;
|
||||
@@ -69,9 +68,6 @@ pub struct ReplyController<R> {
|
||||
|
||||
message_handler: MessageHandler<R>,
|
||||
full_reply_storage: CombinedReplyStorage,
|
||||
|
||||
// Listen for shutdown signals
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R> ReplyController<R>
|
||||
@@ -83,7 +79,6 @@ where
|
||||
message_handler: MessageHandler<R>,
|
||||
full_reply_storage: CombinedReplyStorage,
|
||||
request_receiver: ReplyControllerReceiver,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
ReplyController {
|
||||
config,
|
||||
@@ -92,7 +87,6 @@ where
|
||||
pending_retransmissions: HashMap::new(),
|
||||
message_handler,
|
||||
full_reply_storage,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +96,6 @@ where
|
||||
fragments: I,
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
trace!("buffering pending replies for {recipient}");
|
||||
self.pending_replies
|
||||
.entry(*recipient)
|
||||
.or_insert_with(TransmissionBuffer::new)
|
||||
@@ -114,7 +107,6 @@ where
|
||||
recipient: &AnonymousSenderTag,
|
||||
fragments: Vec<(TransmissionLane, Fragment)>,
|
||||
) {
|
||||
trace!("re-inserting pending replies for {recipient}");
|
||||
// the buffer should ALWAYS exist at this point, if it doesn't, it's a bug...
|
||||
self.pending_replies
|
||||
.entry(*recipient)
|
||||
@@ -127,7 +119,6 @@ where
|
||||
recipient: &AnonymousSenderTag,
|
||||
data: Vec<Arc<PendingAcknowledgement>>,
|
||||
) {
|
||||
trace!("re-inserting pending retransmissions for {recipient}");
|
||||
// the underlying entry MUST exist as we've just got data from there
|
||||
let map_entry = self
|
||||
.pending_retransmissions
|
||||
@@ -145,7 +136,7 @@ where
|
||||
}
|
||||
|
||||
fn should_request_more_surbs(&self, target: &AnonymousSenderTag) -> bool {
|
||||
trace!("checking if we should request more surbs from {target}");
|
||||
trace!("checking if we should request more surbs from {:?}", target);
|
||||
|
||||
let pending_queue_size = self
|
||||
.pending_replies
|
||||
@@ -161,6 +152,11 @@ where
|
||||
|
||||
let total_queue = pending_queue_size + retransmission_queue;
|
||||
|
||||
// simple as that - there's absolutely nothing to retransmit
|
||||
if total_queue == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let available_surbs = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
@@ -177,27 +173,11 @@ where
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.max_surb_threshold();
|
||||
let min_surbs_threshold_buffer =
|
||||
self.config.reply_surbs.minimum_reply_surb_threshold_buffer;
|
||||
|
||||
// After clearing the queue, we want to have at least `min_surbs_threshold` surbs available
|
||||
// and reserved for requesting additional surbs, and in addition to that we also want to
|
||||
// have `min_surbs_threshold_buffer` surbs available proactively.
|
||||
let target_surbs_after_clearing_queue = min_surbs_threshold + min_surbs_threshold_buffer;
|
||||
debug!("total queue size: {total_queue} = pending data {pending_queue_size} + pending retransmission {retransmission_queue}, available surbs: {available_surbs} pending surbs: {pending_surbs} threshold range: {min_surbs_threshold}..{max_surbs_threshold}");
|
||||
|
||||
// Check if we have enough surbs to handle the total queue and maintain minimum thresholds
|
||||
let total_required_surbs = total_queue + target_surbs_after_clearing_queue;
|
||||
let total_available_surbs = pending_surbs + available_surbs;
|
||||
|
||||
debug!("total queue size: {total_queue} = pending data {pending_queue_size} + pending retransmission {retransmission_queue}, available surbs: {available_surbs} pending surbs: {pending_surbs} threshold range: {min_surbs_threshold}..+{min_surbs_threshold_buffer}..{max_surbs_threshold}");
|
||||
|
||||
// We should request more surbs if:
|
||||
// 1. We haven't hit the maximum surb threshold, and
|
||||
// 2. We don't have enough surbs to handle the queue plus minimum thresholds
|
||||
let is_below_max_threshold = total_available_surbs < max_surbs_threshold;
|
||||
let is_below_required_surbs = total_available_surbs < total_required_surbs;
|
||||
|
||||
is_below_max_threshold && is_below_required_surbs
|
||||
(pending_surbs + available_surbs) < max_surbs_threshold
|
||||
&& (pending_surbs + available_surbs) < (total_queue + min_surbs_threshold)
|
||||
}
|
||||
|
||||
async fn handle_send_reply(
|
||||
@@ -258,10 +238,6 @@ where
|
||||
&recipient_tag,
|
||||
);
|
||||
warn!("failed to send reply to {recipient_tag}: {err}");
|
||||
info!(
|
||||
"buffering {no_fragments} fragments for {recipient_tag}",
|
||||
no_fragments = to_send.len()
|
||||
);
|
||||
self.insert_pending_replies(&recipient_tag, to_send, lane);
|
||||
}
|
||||
}
|
||||
@@ -269,13 +245,6 @@ where
|
||||
|
||||
// if there's leftover data we didn't send because we didn't have enough (or any) surbs - buffer it
|
||||
if !fragments.is_empty() {
|
||||
// Ideally we should have enough surbs above the minimum threshold to handle sending
|
||||
// new replies without having to first request more surbs. That's why I'd like to log
|
||||
// these cases as they might indicate a problem with the surb management.
|
||||
debug!(
|
||||
"buffering {no_fragments} fragments for {recipient_tag}",
|
||||
no_fragments = fragments.len()
|
||||
);
|
||||
self.insert_pending_replies(&recipient_tag, fragments, lane);
|
||||
}
|
||||
|
||||
@@ -290,7 +259,6 @@ where
|
||||
target: AnonymousSenderTag,
|
||||
amount: u32,
|
||||
) -> Result<(), PreparationError> {
|
||||
debug!("requesting {amount} additional reply surbs for {target}");
|
||||
let reply_surb = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
@@ -712,7 +680,7 @@ where
|
||||
// it should take into consideration the average latency, sending rate and queue size.
|
||||
// it should request as many surbs as it takes to saturate its sending rate before next batch arrives
|
||||
async fn request_reply_surbs_for_queue_clearing(&mut self, target: AnonymousSenderTag) {
|
||||
trace!("requesting surbs for queue clearing");
|
||||
trace!("requesting surbs for queues clearing");
|
||||
|
||||
let pending_queue_size = self
|
||||
.pending_replies
|
||||
@@ -726,18 +694,17 @@ where
|
||||
.map(|pending_queue| pending_queue.len())
|
||||
.unwrap_or_default();
|
||||
|
||||
let min_surbs_buffer = self.config.reply_surbs.minimum_reply_surb_threshold_buffer as u32;
|
||||
|
||||
let total_queue = (pending_queue_size + retransmission_queue) as u32;
|
||||
|
||||
// To proactively request additional surbs, we aim to have a buffer of extra surbs in our
|
||||
// storage.
|
||||
let total_queue_with_buffer = total_queue + min_surbs_buffer;
|
||||
if total_queue == 0 {
|
||||
trace!("the pending queues for {:?} are already empty", target);
|
||||
return;
|
||||
}
|
||||
|
||||
let request_size = min(
|
||||
self.config.reply_surbs.maximum_reply_surb_request_size,
|
||||
max(
|
||||
total_queue_with_buffer,
|
||||
total_queue,
|
||||
self.config.reply_surbs.minimum_reply_surb_request_size,
|
||||
),
|
||||
);
|
||||
@@ -746,7 +713,7 @@ where
|
||||
.request_additional_reply_surbs(target, request_size)
|
||||
.await
|
||||
{
|
||||
info!("{err}")
|
||||
warn!("failed to request additional surbs... - {err}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -879,11 +846,9 @@ where
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
pub(crate) async fn run(&mut self) {
|
||||
pub(crate) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
debug!("Started ReplyController with graceful shutdown support");
|
||||
|
||||
let mut shutdown = self.task_client.fork("select");
|
||||
|
||||
let polling_rate = Duration::from_secs(5);
|
||||
let mut stale_inspection = new_interval_stream(polling_rate);
|
||||
|
||||
@@ -895,7 +860,7 @@ where
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("ReplyController: Received shutdown");
|
||||
},
|
||||
req = self.request_receiver.next() => match req {
|
||||
|
||||
@@ -15,27 +15,6 @@ pub(crate) fn new_control_channels() -> (ReplyControllerSender, ReplyControllerR
|
||||
(tx.into(), rx)
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ReplyControllerSenderError {
|
||||
#[error("failed to send retransmission data to reply controller")]
|
||||
SendRetransmissionData(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||
|
||||
#[error("failed to send reply to reply controller")]
|
||||
SendReply(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||
|
||||
#[error("failed to send additional surbs to reply controller")]
|
||||
AdditionalSurbs(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||
|
||||
#[error("failed to send additional surbs request to reply controller")]
|
||||
AdditionalSurbsRequest(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||
|
||||
#[error("failed to request lane queue length from reply controller")]
|
||||
LaneQueueLength(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||
|
||||
#[error("response channel was dropped before we could receive the response")]
|
||||
ResponseChannelDropped(#[source] oneshot::Canceled),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ReplyControllerSender(mpsc::UnboundedSender<ReplyControllerMessage>);
|
||||
|
||||
@@ -51,14 +30,14 @@ impl ReplyControllerSender {
|
||||
recipient: AnonymousSenderTag,
|
||||
timed_out_ack: Weak<PendingAcknowledgement>,
|
||||
extra_surb_request: bool,
|
||||
) -> Result<(), ReplyControllerSenderError> {
|
||||
) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::RetransmitReply {
|
||||
recipient,
|
||||
timed_out_ack,
|
||||
extra_surb_request,
|
||||
})
|
||||
.map_err(ReplyControllerSenderError::SendRetransmissionData)
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub(crate) fn send_reply(
|
||||
@@ -66,14 +45,14 @@ impl ReplyControllerSender {
|
||||
recipient: AnonymousSenderTag,
|
||||
message: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
) -> Result<(), ReplyControllerSenderError> {
|
||||
) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::SendReply {
|
||||
recipient,
|
||||
message,
|
||||
lane,
|
||||
})
|
||||
.map_err(ReplyControllerSenderError::SendReply)
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub(crate) fn send_additional_surbs(
|
||||
@@ -81,47 +60,42 @@ impl ReplyControllerSender {
|
||||
sender_tag: AnonymousSenderTag,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
from_surb_request: bool,
|
||||
) -> Result<(), ReplyControllerSenderError> {
|
||||
) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::AdditionalSurbs {
|
||||
sender_tag,
|
||||
reply_surbs,
|
||||
from_surb_request,
|
||||
})
|
||||
.map_err(ReplyControllerSenderError::AdditionalSurbs)
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub(crate) fn send_additional_surbs_request(
|
||||
&self,
|
||||
recipient: Recipient,
|
||||
amount: u32,
|
||||
) -> Result<(), ReplyControllerSenderError> {
|
||||
pub(crate) fn send_additional_surbs_request(&self, recipient: Recipient, amount: u32) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::AdditionalSurbsRequest {
|
||||
recipient: Box::new(recipient),
|
||||
amount,
|
||||
})
|
||||
.map_err(ReplyControllerSenderError::AdditionalSurbsRequest)
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub async fn get_lane_queue_length(
|
||||
&self,
|
||||
connection_id: ConnectionId,
|
||||
) -> Result<usize, ReplyControllerSenderError> {
|
||||
pub async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> usize {
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
if let Err(err) = self
|
||||
.0
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::LaneQueueLength {
|
||||
connection_id,
|
||||
response_channel: response_tx,
|
||||
})
|
||||
{
|
||||
return Err(ReplyControllerSenderError::LaneQueueLength(err));
|
||||
}
|
||||
.expect("ReplyControllerReceiver has died!");
|
||||
|
||||
response_rx
|
||||
.await
|
||||
.map_err(ReplyControllerSenderError::ResponseChannelDropped)
|
||||
match response_rx.await {
|
||||
Ok(length) => length,
|
||||
Err(_) => {
|
||||
error!("The reply controller has dropped our response channel!");
|
||||
// TODO: should we panic here instead? this message implies something weird and unrecoverable has happened
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,10 +110,7 @@ impl ReplyQueueLengths {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_lane_queue_length(
|
||||
&self,
|
||||
connection_id: ConnectionId,
|
||||
) -> Result<usize, ReplyControllerSenderError> {
|
||||
pub async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> usize {
|
||||
self.reply_controller_sender
|
||||
.get_lane_queue_length(connection_id)
|
||||
.await
|
||||
@@ -149,7 +120,7 @@ impl ReplyQueueLengths {
|
||||
pub(crate) type ReplyControllerReceiver = mpsc::UnboundedReceiver<ReplyControllerMessage>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ReplyControllerMessage {
|
||||
pub(crate) enum ReplyControllerMessage {
|
||||
RetransmitReply {
|
||||
recipient: AnonymousSenderTag,
|
||||
timed_out_ack: Weak<PendingAcknowledgement>,
|
||||
|
||||
@@ -22,7 +22,7 @@ use nym_sphinx::addressing::Recipient;
|
||||
use nym_statistics_common::clients::{
|
||||
ClientStatsController, ClientStatsReceiver, ClientStatsSender,
|
||||
};
|
||||
use nym_task::{connections::TransmissionLane, TaskClient};
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{
|
||||
@@ -51,9 +51,6 @@ pub(crate) struct StatisticsControl {
|
||||
|
||||
/// Config for stats reporting (enabled, address, interval)
|
||||
reporting_config: StatsReporting,
|
||||
|
||||
/// Task client for listening for shutdown
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl StatisticsControl {
|
||||
@@ -62,24 +59,19 @@ impl StatisticsControl {
|
||||
client_type: String,
|
||||
client_stats_id: String,
|
||||
report_tx: InputMessageSender,
|
||||
task_client: TaskClient,
|
||||
) -> (Self, ClientStatsSender) {
|
||||
let (stats_tx, stats_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
let stats = ClientStatsController::new(client_stats_id, client_type);
|
||||
|
||||
let mut task_client_stats_sender = task_client.fork("stats_sender");
|
||||
task_client_stats_sender.disarm();
|
||||
|
||||
(
|
||||
StatisticsControl {
|
||||
stats,
|
||||
stats_rx,
|
||||
report_tx,
|
||||
reporting_config,
|
||||
task_client,
|
||||
},
|
||||
ClientStatsSender::new(Some(stats_tx), task_client_stats_sender),
|
||||
ClientStatsSender::new(Some(stats_tx)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -99,7 +91,7 @@ impl StatisticsControl {
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
async fn run_with_shutdown(&mut self, mut task_client: nym_task::TaskClient) {
|
||||
log::debug!("Started StatisticsControl with graceful shutdown support");
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -129,10 +121,10 @@ impl StatisticsControl {
|
||||
let mut snapshot_interval =
|
||||
gloo_timers::future::IntervalStream::new(SNAPSHOT_INTERVAL.as_millis() as u32);
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
_ = task_client.recv() => {
|
||||
log::trace!("StatisticsControl: Received shutdown");
|
||||
break;
|
||||
},
|
||||
@@ -157,34 +149,29 @@ impl StatisticsControl {
|
||||
}
|
||||
|
||||
_ = local_report_interval.next() => {
|
||||
self.stats.local_report(&mut self.task_client);
|
||||
self.stats.local_report(&mut task_client);
|
||||
}
|
||||
}
|
||||
}
|
||||
log::debug!("StatisticsControl: Exiting");
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self) {
|
||||
pub(crate) fn start_with_shutdown(mut self, task_client: nym_task::TaskClient) {
|
||||
spawn_future(async move {
|
||||
self.run().await;
|
||||
self.run_with_shutdown(task_client).await;
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn create_and_start(
|
||||
pub(crate) fn create_and_start_with_shutdown(
|
||||
reporting_config: StatsReporting,
|
||||
client_type: String,
|
||||
client_stats_id: String,
|
||||
report_tx: InputMessageSender,
|
||||
task_client: TaskClient,
|
||||
task_client: nym_task::TaskClient,
|
||||
) -> ClientStatsSender {
|
||||
let (controller, sender) = Self::create(
|
||||
reporting_config,
|
||||
client_type,
|
||||
client_stats_id,
|
||||
report_tx,
|
||||
task_client,
|
||||
);
|
||||
controller.start();
|
||||
let (controller, sender) =
|
||||
Self::create(reporting_config, client_type, client_stats_id, report_tx);
|
||||
controller.start_with_shutdown(task_client);
|
||||
sender
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ pub(crate) use accessor::{TopologyAccessor, TopologyReadPermit};
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nym_sphinx::addressing::nodes::NodeIdentity;
|
||||
use nym_task::TaskClient;
|
||||
use nym_topology::NymTopologyError;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -44,8 +43,6 @@ pub struct TopologyRefresher {
|
||||
|
||||
refresh_rate: Duration,
|
||||
consecutive_failure_count: usize,
|
||||
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl TopologyRefresher {
|
||||
@@ -53,14 +50,12 @@ impl TopologyRefresher {
|
||||
cfg: TopologyRefresherConfig,
|
||||
topology_accessor: TopologyAccessor,
|
||||
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
TopologyRefresher {
|
||||
topology_provider,
|
||||
topology_accessor,
|
||||
refresh_rate: cfg.refresh_rate,
|
||||
consecutive_failure_count: 0,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +142,7 @@ impl TopologyRefresher {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(mut self) {
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
|
||||
spawn_future(async move {
|
||||
debug!("Started TopologyRefresher with graceful shutdown support");
|
||||
|
||||
@@ -160,17 +155,17 @@ impl TopologyRefresher {
|
||||
let mut interval =
|
||||
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
_ = interval.next() => {
|
||||
self.try_refresh().await;
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("TopologyRefresher: Received shutdown");
|
||||
},
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("TopologyRefresher: Exiting");
|
||||
})
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ impl<T> TransmissionBuffer<T> {
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn lanes(&self) -> Vec<TransmissionLane> {
|
||||
self.buffer.keys().cloned().collect()
|
||||
pub(crate) fn num_lanes(&self) -> usize {
|
||||
self.buffer.keys().count()
|
||||
}
|
||||
|
||||
pub(crate) fn lane_length(&self, lane: &TransmissionLane) -> Option<usize> {
|
||||
@@ -83,7 +83,6 @@ impl<T> TransmissionBuffer<T> {
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[allow(unused)]
|
||||
pub(crate) fn total_size_in_bytes(&self) -> usize
|
||||
where
|
||||
T: SizedData,
|
||||
|
||||
@@ -222,9 +222,6 @@ pub enum ClientCoreError {
|
||||
"fresh registration with gateway {gateway_id} somehow requires an additional key upgrade!"
|
||||
)]
|
||||
UnexpectedKeyUpgrade { gateway_id: String },
|
||||
|
||||
#[error("failed to derive keys from master key")]
|
||||
HkdfDerivationError {},
|
||||
}
|
||||
|
||||
/// Set of messages that the client can send to listeners via the task manager
|
||||
|
||||
@@ -120,13 +120,6 @@ impl Backend {
|
||||
manager.delete_all_tags().await?;
|
||||
}
|
||||
|
||||
// We ran into a bug where the client was re-using the same sender tag while connected to
|
||||
// different gateways. This does not work since changing the gateway changes the
|
||||
// nym-addres.
|
||||
// A stop-gap solution is to always purge the tags when the client is started.
|
||||
// When writing this I'm not yet sure what the correct solution is.
|
||||
manager.delete_all_tags().await?;
|
||||
|
||||
Ok(Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
|
||||
@@ -20,8 +20,8 @@ use nym_credentials_interface::TicketType;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_requests::registration::handshake::client_handshake;
|
||||
use nym_gateway_requests::{
|
||||
BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt,
|
||||
SensitiveServerResponse, ServerResponse, SharedGatewayKey, SharedSymmetricKey,
|
||||
BinaryRequest, ClientControlRequest, ClientRequest, SensitiveServerResponse, ServerResponse,
|
||||
SharedGatewayKey, SharedSymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
@@ -563,10 +563,28 @@ impl<C, St> GatewayClient<C, St> {
|
||||
Ok(zeroizing_updated_key)
|
||||
}
|
||||
|
||||
async fn send_authenticate_request_and_handle_response(
|
||||
&mut self,
|
||||
msg: ClientControlRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
async fn authenticate(&mut self) -> Result<(), GatewayClientError> {
|
||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
};
|
||||
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
debug!("authenticating with gateway");
|
||||
|
||||
let self_address = self
|
||||
.local_identity
|
||||
.as_ref()
|
||||
.public_key()
|
||||
.derive_destination_address();
|
||||
|
||||
let msg = ClientControlRequest::new_authenticate(
|
||||
self_address,
|
||||
shared_key,
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
)?;
|
||||
|
||||
match self.send_websocket_message(msg).await? {
|
||||
ServerResponse::Authenticate {
|
||||
protocol_version,
|
||||
@@ -590,51 +608,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn authenticate_v1(&mut self) -> Result<(), GatewayClientError> {
|
||||
debug!("using v1 authentication");
|
||||
|
||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
};
|
||||
|
||||
let self_address = self
|
||||
.local_identity
|
||||
.public_key()
|
||||
.derive_destination_address();
|
||||
|
||||
let msg = ClientControlRequest::new_authenticate(
|
||||
self_address,
|
||||
shared_key,
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
)?;
|
||||
self.send_authenticate_request_and_handle_response(msg)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn authenticate_v2(&mut self) -> Result<(), GatewayClientError> {
|
||||
debug!("using v2 authentication");
|
||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
};
|
||||
|
||||
let msg = ClientControlRequest::new_authenticate_v2(shared_key, &self.local_identity)?;
|
||||
self.send_authenticate_request_and_handle_response(msg)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn authenticate(&mut self, use_v2: bool) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
debug!("authenticating with gateway");
|
||||
|
||||
if use_v2 {
|
||||
self.authenticate_v2().await
|
||||
} else {
|
||||
self.authenticate_v1().await
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper method to either call register or authenticate based on self.shared_key value
|
||||
#[instrument(skip_all,
|
||||
fields(
|
||||
@@ -650,25 +623,19 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// 1. check gateway's protocol version
|
||||
let gw_protocol = match self.get_gateway_protocol().await {
|
||||
Ok(protocol) => Some(protocol),
|
||||
let supports_aes_gcm_siv = match self.get_gateway_protocol().await {
|
||||
Ok(protocol) => protocol >= AES_GCM_SIV_PROTOCOL_VERSION,
|
||||
Err(_) => {
|
||||
// if we failed to send the request, it means the gateway is running the old binary,
|
||||
// so it has reset our connection - we have to reconnect
|
||||
self.establish_connection().await?;
|
||||
None
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let supports_aes_gcm_siv = gw_protocol.supports_aes256_gcm_siv();
|
||||
let supports_auth_v2 = gw_protocol.supports_authenticate_v2();
|
||||
|
||||
if !supports_aes_gcm_siv {
|
||||
warn!("this gateway is on an old version that doesn't support AES256-GCM-SIV");
|
||||
}
|
||||
if !supports_auth_v2 {
|
||||
warn!("this gateway is on an old version that doesn't support authentication v2")
|
||||
}
|
||||
|
||||
if self.authenticated {
|
||||
debug!("Already authenticated");
|
||||
@@ -683,7 +650,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
if self.shared_key.is_some() {
|
||||
self.authenticate(false).await?;
|
||||
self.authenticate().await?;
|
||||
|
||||
if self.authenticated {
|
||||
// if we are authenticated it means we MUST have an associated shared_key
|
||||
@@ -1016,7 +983,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// if we're reconnecting, because we lost connection, we need to re-authenticate the connection
|
||||
self.authenticate(false).await?;
|
||||
self.authenticate().await?;
|
||||
|
||||
// this call is NON-blocking
|
||||
self.start_listening_for_mixnet_messages()?;
|
||||
@@ -1086,7 +1053,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
connection: SocketState::NotConnected,
|
||||
packet_router,
|
||||
bandwidth_controller: None,
|
||||
stats_reporter: ClientStatsSender::new(None, task_client.clone()),
|
||||
stats_reporter: ClientStatsSender::new(None),
|
||||
negotiated_protocol: None,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
|
||||
@@ -28,6 +28,7 @@ use nym_network_defaults::{ChainDetails, NymNetworkDetails};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::time::SystemTime;
|
||||
use tendermint_rpc::endpoint::block::Response as BlockResponse;
|
||||
use tendermint_rpc::endpoint::*;
|
||||
use tendermint_rpc::{Error as TendermintRpcError, Order};
|
||||
use url::Url;
|
||||
@@ -62,7 +63,6 @@ pub use cw3;
|
||||
pub use cw4;
|
||||
pub use cw_controllers;
|
||||
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
|
||||
pub use tendermint_rpc::endpoint::block::Response as BlockResponse;
|
||||
pub use tendermint_rpc::{
|
||||
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
|
||||
query::Query,
|
||||
|
||||
@@ -24,7 +24,6 @@ ed25519-dalek = { workspace = true, features = ["rand_core"], optional = true }
|
||||
rand = { workspace = true, optional = true }
|
||||
serde_bytes = { workspace = true, optional = true }
|
||||
serde = { workspace = true, features = ["derive"], optional = true }
|
||||
sha2 = { workspace = true, optional = true }
|
||||
subtle-encoding = { workspace = true, features = ["bech32-preview"] }
|
||||
thiserror = { workspace = true }
|
||||
zeroize = { workspace = true, optional = true, features = ["zeroize_derive"] }
|
||||
@@ -37,13 +36,11 @@ nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
|
||||
rand_chacha = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["sphinx"]
|
||||
aead = ["dep:aead", "aead/std", "aes-gcm-siv", "generic-array"]
|
||||
serde = ["dep:serde", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"]
|
||||
asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"]
|
||||
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array", "sha2"]
|
||||
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array"]
|
||||
stream_cipher = ["aes", "ctr", "cipher", "generic-array"]
|
||||
sphinx = ["nym-sphinx-types/sphinx"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
outfox = ["nym-sphinx-types/outfox"]
|
||||
|
||||
@@ -202,18 +202,6 @@ impl PemStorableKey for PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<x25519_dalek::PublicKey> for PublicKey {
|
||||
fn from(public_key: x25519_dalek::PublicKey) -> Self {
|
||||
PublicKey(public_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PublicKey> for x25519_dalek::PublicKey {
|
||||
fn from(public_key: PublicKey) -> Self {
|
||||
public_key.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||
pub struct PrivateKey(x25519_dalek::StaticSecret);
|
||||
|
||||
@@ -320,15 +308,109 @@ impl PemStorableKey for PrivateKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<x25519_dalek::StaticSecret> for PrivateKey {
|
||||
fn from(secret: x25519_dalek::StaticSecret) -> Self {
|
||||
PrivateKey(secret)
|
||||
// compatibility with sphinx keys:
|
||||
#[cfg(feature = "sphinx")]
|
||||
impl From<PublicKey> for nym_sphinx_types::PublicKey {
|
||||
fn from(key: PublicKey) -> Self {
|
||||
nym_sphinx_types::PublicKey::from(key.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<x25519_dalek::StaticSecret> for PrivateKey {
|
||||
fn as_ref(&self) -> &x25519_dalek::StaticSecret {
|
||||
&self.0
|
||||
#[cfg(feature = "sphinx")]
|
||||
impl<'a> From<&'a PublicKey> for nym_sphinx_types::PublicKey {
|
||||
fn from(key: &'a PublicKey) -> Self {
|
||||
nym_sphinx_types::PublicKey::from((*key).to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sphinx")]
|
||||
impl From<nym_sphinx_types::PublicKey> for PublicKey {
|
||||
fn from(pub_key: nym_sphinx_types::PublicKey) -> Self {
|
||||
Self(x25519_dalek::PublicKey::from(*pub_key.as_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sphinx")]
|
||||
impl From<PrivateKey> for nym_sphinx_types::PrivateKey {
|
||||
fn from(key: PrivateKey) -> Self {
|
||||
nym_sphinx_types::PrivateKey::from(key.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sphinx")]
|
||||
impl<'a> From<&'a PrivateKey> for nym_sphinx_types::PrivateKey {
|
||||
fn from(key: &'a PrivateKey) -> Self {
|
||||
nym_sphinx_types::PrivateKey::from(key.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sphinx")]
|
||||
impl From<nym_sphinx_types::PrivateKey> for PrivateKey {
|
||||
fn from(private_key: nym_sphinx_types::PrivateKey) -> Self {
|
||||
let private_key_bytes = private_key.to_bytes();
|
||||
assert_eq!(private_key_bytes.len(), PRIVATE_KEY_SIZE);
|
||||
Self::from_bytes(&private_key_bytes).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod sphinx_key_conversion {
|
||||
use super::*;
|
||||
use rand_chacha::rand_core::SeedableRng;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
|
||||
pub(super) fn test_rng() -> ChaCha20Rng {
|
||||
let dummy_seed = [42u8; 32];
|
||||
ChaCha20Rng::from_seed(dummy_seed)
|
||||
}
|
||||
|
||||
const NUM_ITERATIONS: usize = 100;
|
||||
|
||||
#[test]
|
||||
fn works_for_forward_conversion() {
|
||||
let mut rng = test_rng();
|
||||
|
||||
for _ in 0..NUM_ITERATIONS {
|
||||
let keys = KeyPair::new(&mut rng);
|
||||
let private = &keys.private_key;
|
||||
let public = &keys.public_key;
|
||||
|
||||
let dummy_remote = KeyPair::new(&mut rng);
|
||||
let dh1 = private.diffie_hellman(&dummy_remote.public_key);
|
||||
|
||||
let public_bytes = public.to_bytes();
|
||||
|
||||
let sphinx_private: nym_sphinx_types::PrivateKey = private.into();
|
||||
let recovered_private = PrivateKey::from(sphinx_private);
|
||||
|
||||
let dh2 = recovered_private.diffie_hellman(&dummy_remote.public_key);
|
||||
|
||||
let sphinx_public: nym_sphinx_types::PublicKey = public.into();
|
||||
let recovered_public = PublicKey::from(sphinx_public);
|
||||
assert_eq!(public_bytes, recovered_public.to_bytes());
|
||||
|
||||
// even though the byte representation of the private key changed, the resultant DH is the same
|
||||
// which is what matters
|
||||
assert_eq!(dh1, dh2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_for_backward_conversion() {
|
||||
for _ in 0..NUM_ITERATIONS {
|
||||
let (sphinx_private, sphinx_public) = nym_sphinx_types::crypto::keygen();
|
||||
|
||||
let private_bytes = sphinx_private.to_bytes();
|
||||
let public_bytes = sphinx_public.as_bytes();
|
||||
|
||||
let private: PrivateKey = sphinx_private.into();
|
||||
let recovered_sphinx_private: nym_sphinx_types::PrivateKey = private.into();
|
||||
|
||||
let public: PublicKey = sphinx_public.into();
|
||||
let recovered_sphinx_public: nym_sphinx_types::PublicKey = public.into();
|
||||
assert_eq!(private_bytes, recovered_sphinx_private.to_bytes());
|
||||
assert_eq!(public_bytes, recovered_sphinx_public.as_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub use ed25519_dalek::SignatureError;
|
||||
use ed25519_dalek::{SecretKey, Signer, SigningKey};
|
||||
use ed25519_dalek::{Signer, SigningKey};
|
||||
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
@@ -18,7 +18,7 @@ pub mod serde_helpers;
|
||||
use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
|
||||
|
||||
#[cfg(feature = "rand")]
|
||||
use rand::{CryptoRng, Rng, RngCore};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::de::Error as SerdeError;
|
||||
#[cfg(feature = "serde")]
|
||||
@@ -62,33 +62,16 @@ pub struct KeyPair {
|
||||
// nothing secret about public key
|
||||
#[zeroize(skip)]
|
||||
public_key: PublicKey,
|
||||
|
||||
#[zeroize(skip)]
|
||||
index: u32,
|
||||
}
|
||||
|
||||
/// All keys will always have an index field populated this is to prevent anyone from figuring out if
|
||||
/// the keys are derived or random, and alter their behaviour based on that.
|
||||
impl KeyPair {
|
||||
#[cfg(feature = "rand")]
|
||||
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
|
||||
let index = rng.gen();
|
||||
let ed25519_signing_key = ed25519_dalek::SigningKey::generate(rng);
|
||||
|
||||
KeyPair {
|
||||
private_key: PrivateKey(ed25519_signing_key.to_bytes()),
|
||||
public_key: PublicKey(ed25519_signing_key.verifying_key()),
|
||||
index,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_secret(secret: SecretKey, index: u32) -> Self {
|
||||
let ed25519_signing_key = SigningKey::from(secret);
|
||||
|
||||
KeyPair {
|
||||
private_key: PrivateKey(ed25519_signing_key.to_bytes()),
|
||||
public_key: PublicKey(ed25519_signing_key.verifying_key()),
|
||||
index,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,31 +87,15 @@ impl KeyPair {
|
||||
Ok(KeyPair {
|
||||
private_key: PrivateKey::from_bytes(priv_bytes)?,
|
||||
public_key: PublicKey::from_bytes(pub_bytes)?,
|
||||
index: fake_index(pub_bytes),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Reduces a byte slice into a u32 value by XOR-ing all its bytes into a 4-byte accumulator.
|
||||
/// The process iterates over every byte in the input slice, XOR-ing each one into a slot based on its index modulo 4.
|
||||
/// If the input slice contains fewer than 4 bytes, the remaining positions in the accumulator remain zero.
|
||||
/// Finally, the accumulator is interpreted in big-endian order to produce the resulting u32.
|
||||
/// Index is used to verify deterministic identity key, master key and salt are also requried for verification.
|
||||
fn fake_index(input: &[u8]) -> u32 {
|
||||
let mut accumulator = [0u8; 4];
|
||||
for (i, &byte) in input.iter().enumerate() {
|
||||
accumulator[i % 4] ^= byte;
|
||||
}
|
||||
u32::from_be_bytes(accumulator)
|
||||
}
|
||||
|
||||
impl From<PrivateKey> for KeyPair {
|
||||
fn from(private_key: PrivateKey) -> Self {
|
||||
let public_key = (&private_key).into();
|
||||
KeyPair {
|
||||
public_key,
|
||||
public_key: (&private_key).into(),
|
||||
private_key,
|
||||
index: fake_index(public_key.to_bytes().as_ref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,7 +115,6 @@ impl PemStorableKeyPair for KeyPair {
|
||||
KeyPair {
|
||||
private_key,
|
||||
public_key,
|
||||
index: fake_index(public_key.to_bytes().as_ref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,6 @@ use hkdf::{
|
||||
},
|
||||
Hkdf,
|
||||
};
|
||||
use sha2::{Sha256, Sha512};
|
||||
|
||||
pub use hkdf::InvalidLength;
|
||||
use zeroize::ZeroizeOnDrop;
|
||||
|
||||
/// Perform HKDF `extract` then `expand` as a single step.
|
||||
pub fn extract_then_expand<D>(
|
||||
@@ -32,80 +28,3 @@ where
|
||||
|
||||
Ok(okm)
|
||||
}
|
||||
|
||||
/// `DerivationMaterial` encapsulates parameters for deterministic key derivation using
|
||||
/// HKDF (SHA-512).
|
||||
///
|
||||
/// It consists of:
|
||||
/// - A master key (`master_key`): the base secret.
|
||||
/// - An index (`index`): ensures unique derivations.
|
||||
/// - A salt (`salt`): adds additional uniqueness, should be application specific.
|
||||
///
|
||||
/// Use the `derive_secret()` method to generate a 32-byte secret. To prepare for a new derivation,
|
||||
/// call the `next()` method, which increments the index. **It is the caller's responsibility to
|
||||
/// track and persist the derivation index if keys need to be rederived.**
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use nym_crypto::hkdf::DerivationMaterial;
|
||||
///
|
||||
/// let master_key = [0u8; 32]; // your secret master key
|
||||
/// let salt = b"unique-salt-value";
|
||||
/// let material = DerivationMaterial::new(master_key, 0, salt);
|
||||
///
|
||||
/// // Derive a secret
|
||||
/// let secret = material.derive_secret().expect("Failed to derive secret");
|
||||
///
|
||||
/// // Prepare for the next derivation
|
||||
/// let next_material = material.next();
|
||||
/// ```
|
||||
#[derive(ZeroizeOnDrop)]
|
||||
pub struct DerivationMaterial {
|
||||
master_key: [u8; 32],
|
||||
index: u32,
|
||||
salt: Vec<u8>,
|
||||
}
|
||||
|
||||
impl DerivationMaterial {
|
||||
pub fn index(&self) -> u32 {
|
||||
self.index
|
||||
}
|
||||
|
||||
pub fn salt(&self) -> &[u8] {
|
||||
&self.salt
|
||||
}
|
||||
|
||||
/// Derives a 32-byte seed from a master seed and an index using HKDF (with SHA-512).
|
||||
///
|
||||
/// The `salt` and the use of the index (as info) bind this derivation to an application/client.
|
||||
pub fn derive_secret(&self) -> Result<[u8; 32], hkdf::InvalidLength> {
|
||||
let salt = &self.salt;
|
||||
let info = self.index.to_be_bytes(); // Use the index as info
|
||||
let hk = Hkdf::<Sha512>::new(Some(salt), &self.master_key);
|
||||
let mut okm = [0u8; 32];
|
||||
hk.expand(&info, &mut okm)?;
|
||||
Ok(okm)
|
||||
}
|
||||
|
||||
pub fn new<T: AsRef<[u8]>>(master_key: T, index: u32, salt: &[u8]) -> Self {
|
||||
// Coerce master_key to [u8; 32]
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(master_key.as_ref());
|
||||
let master_key = hasher.finalize().into();
|
||||
|
||||
Self {
|
||||
master_key,
|
||||
index,
|
||||
salt: salt.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&self) -> Self {
|
||||
Self {
|
||||
master_key: self.master_key,
|
||||
index: self.index + 1,
|
||||
salt: self.salt.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,8 @@ pub fn compute_keyed_hmac<D>(key: &[u8], data: &[u8]) -> HmacOutput<D>
|
||||
where
|
||||
D: Digest + BlockSizeUser,
|
||||
{
|
||||
// SAFETY: hmac is fine with keys of any size; if they're smaller than the block size of the underlying
|
||||
// digest, they're padded with 0. if they're larger they're hashed and padded
|
||||
// the reason for `Result` return type is due to the trait definition
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let mut hmac = SimpleHmac::<D>::new_from_slice(key).unwrap();
|
||||
let mut hmac = SimpleHmac::<D>::new_from_slice(key)
|
||||
.expect("HMAC was instantiated with a key of an invalid size!");
|
||||
hmac.update(data);
|
||||
hmac.finalize()
|
||||
}
|
||||
@@ -30,11 +27,8 @@ pub fn recompute_keyed_hmac_and_verify_tag<D>(key: &[u8], data: &[u8], tag: &[u8
|
||||
where
|
||||
D: Digest + BlockSizeUser,
|
||||
{
|
||||
// SAFETY: hmac is fine with keys of any size; if they're smaller than the block size of the underlying
|
||||
// digest, they're padded with 0. if they're larger they're hashed and padded
|
||||
// the reason for `Result` return type is due to the trait definition
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let mut hmac = SimpleHmac::<D>::new_from_slice(key).unwrap();
|
||||
let mut hmac = SimpleHmac::<D>::new_from_slice(key)
|
||||
.expect("HMAC was instantiated with a key of an invalid size!");
|
||||
hmac.update(data);
|
||||
|
||||
let tag_arr = Output::<D>::from_slice(tag);
|
||||
|
||||
@@ -27,16 +27,12 @@ where
|
||||
// after performing diffie-hellman we don't care about the private component anymore
|
||||
let dh_result = ephemeral_keypair.private_key().diffie_hellman(remote_key);
|
||||
|
||||
// SAFETY: while this is a relatively weak assumption, it's unlikely that any stream cipher has `C::key_size()`
|
||||
// larger than 255 * chunk_size of the digest (so for example keys larger than 8160 bytes if sh256 is used)
|
||||
#[allow(clippy::expect_used)]
|
||||
// there is no reason for this to fail as our okm is expected to be only C::KeySize bytes
|
||||
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::key_size())
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
// SAFETY: the generated okm has exactly `C::key_size()` elements,
|
||||
// so this call is safe
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let derived_shared_key = Key::<C>::from_exact_iter(okm).unwrap();
|
||||
let derived_shared_key =
|
||||
Key::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!");
|
||||
|
||||
(ephemeral_keypair, derived_shared_key)
|
||||
}
|
||||
@@ -52,14 +48,9 @@ where
|
||||
{
|
||||
let dh_result = local_key.diffie_hellman(remote_key);
|
||||
|
||||
// SAFETY: while this is a relatively weak assumption, it's unlikely that any stream cipher has `C::key_size()`
|
||||
// larger than 255 * chunk_size of the digest (so for example keys larger than 8160 bytes if sh256 is used)
|
||||
#[allow(clippy::expect_used)]
|
||||
// there is no reason for this to fail as our okm is expected to be only C::KeySize bytes
|
||||
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::key_size())
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
// SAFETY: the generated okm has exactly `C::key_size()` elements,
|
||||
// so this call is safe
|
||||
#[allow(clippy::unwrap_used)]
|
||||
Key::<C>::from_exact_iter(okm).unwrap()
|
||||
Key::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!")
|
||||
}
|
||||
|
||||
@@ -60,15 +60,20 @@ where
|
||||
Iv::<C>::default()
|
||||
}
|
||||
|
||||
pub fn try_iv_from_slice<C>(b: &[u8]) -> Option<&IV<C>>
|
||||
pub fn iv_from_slice<C>(b: &[u8]) -> &IV<C>
|
||||
where
|
||||
C: IvSizeUser,
|
||||
{
|
||||
if b.len() != C::iv_size() {
|
||||
None
|
||||
} else {
|
||||
Some(IV::<C>::from_slice(b))
|
||||
// `from_slice` would have caused a panic about this issue anyway.
|
||||
// Now we at least have slightly more information
|
||||
panic!(
|
||||
"Tried to convert {} bytes to IV. Expected {}",
|
||||
b.len(),
|
||||
C::iv_size()
|
||||
)
|
||||
}
|
||||
IV::<C>::from_slice(b)
|
||||
}
|
||||
|
||||
// TODO: there's really no way to use more parts of the keystream if it was required at some point.
|
||||
|
||||
@@ -21,7 +21,7 @@ lazy_static = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
rand_core = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
sha2 = "0.9"
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
+2
-96
@@ -54,12 +54,12 @@ pub(crate) fn hash_to_scalar<M: AsRef<[u8]>>(msg: M, domain: &[u8]) -> Scalar {
|
||||
pub(crate) fn hash_to_scalars<M: AsRef<[u8]>>(msg: M, domain: &[u8], n: usize) -> Vec<Scalar> {
|
||||
let mut output = vec![Scalar::zero(); n];
|
||||
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<Sha256>, _>([msg], domain, &mut output);
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<Sha256>>(msg.as_ref(), domain, &mut output);
|
||||
output
|
||||
}
|
||||
|
||||
pub(crate) fn hash_g2<M: AsRef<[u8]>>(msg: M, domain: &[u8]) -> G2Projective {
|
||||
<G2Projective as HashToCurve<ExpandMsgXmd<Sha256>>>::hash_to_curve([msg], domain)
|
||||
<G2Projective as HashToCurve<ExpandMsgXmd<Sha256>>>::hash_to_curve(msg, domain)
|
||||
}
|
||||
|
||||
pub(crate) fn combine_scalar_chunks(chunks: &[Scalar]) -> Scalar {
|
||||
@@ -112,97 +112,3 @@ pub(crate) fn deserialize_g2(b: &[u8]) -> Option<G2Projective> {
|
||||
G2Projective::from_bytes(&encoding).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bls12_381::G2Affine;
|
||||
|
||||
#[test]
|
||||
fn test_hash_to_scalar() {
|
||||
let msg1 = "foo";
|
||||
let expected1 = Scalar::from_bytes(&[
|
||||
253, 57, 224, 227, 175, 195, 226, 82, 46, 175, 33, 126, 171, 239, 255, 92, 108, 168, 6,
|
||||
79, 90, 11, 235, 236, 221, 10, 85, 133, 42, 81, 95, 30,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let msg2 = "bar";
|
||||
let expected2 = Scalar::from_bytes(&[
|
||||
48, 83, 69, 52, 42, 18, 135, 244, 211, 190, 160, 196, 118, 154, 24, 126, 0, 125, 72,
|
||||
201, 170, 225, 123, 201, 52, 120, 171, 132, 235, 182, 20, 26,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let msg3 = [
|
||||
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
|
||||
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
|
||||
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
|
||||
60, 42, 92, 128, 131, 161, 43,
|
||||
];
|
||||
let expected3 = Scalar::from_bytes(&[
|
||||
128, 189, 8, 43, 186, 55, 52, 61, 171, 196, 159, 177, 162, 100, 27, 143, 85, 83, 218,
|
||||
171, 91, 220, 155, 25, 7, 38, 2, 36, 4, 93, 136, 4,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
hash_to_scalar(msg1, b"NYMECASH-V01-CS02-with-expander-SHA256"),
|
||||
expected1
|
||||
);
|
||||
assert_eq!(
|
||||
hash_to_scalar(msg2, b"NYMECASH-V01-CS02-with-expander-SHA256"),
|
||||
expected2
|
||||
);
|
||||
assert_eq!(
|
||||
hash_to_scalar(msg3, b"NYMECASH-V01-CS02-with-expander-SHA256"),
|
||||
expected3
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_g2() {
|
||||
let msg1 = "foo";
|
||||
let expected1 = G2Affine::from_compressed(&[
|
||||
175, 187, 62, 7, 29, 17, 42, 93, 28, 93, 234, 253, 101, 166, 158, 187, 153, 82, 93, 18,
|
||||
11, 233, 36, 107, 51, 117, 30, 127, 32, 254, 210, 77, 133, 12, 253, 255, 84, 128, 36,
|
||||
214, 234, 103, 50, 21, 26, 78, 112, 49, 20, 69, 19, 109, 7, 78, 33, 227, 196, 180, 168,
|
||||
219, 73, 251, 192, 221, 41, 138, 160, 131, 191, 186, 156, 117, 179, 179, 191, 235, 171,
|
||||
26, 219, 148, 170, 179, 11, 38, 137, 14, 95, 115, 171, 186, 163, 82, 158, 6, 239, 88,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
let msg2 = "bar";
|
||||
let expected2 = G2Affine::from_compressed(&[
|
||||
183, 25, 90, 187, 34, 184, 30, 182, 215, 242, 158, 83, 116, 34, 210, 96, 188, 79, 83,
|
||||
255, 100, 122, 90, 188, 196, 93, 164, 253, 20, 106, 205, 33, 48, 140, 60, 149, 66, 246,
|
||||
121, 244, 146, 66, 170, 60, 113, 95, 102, 237, 25, 231, 8, 42, 121, 124, 180, 140, 34,
|
||||
104, 173, 251, 89, 189, 28, 196, 49, 66, 101, 38, 68, 44, 40, 235, 21, 35, 204, 123,
|
||||
218, 238, 216, 92, 134, 217, 212, 246, 176, 77, 187, 0, 245, 134, 132, 73, 31, 44, 137,
|
||||
197,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
let msg3 = [
|
||||
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
|
||||
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
|
||||
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
|
||||
60, 42, 92, 128, 131, 161, 43,
|
||||
];
|
||||
let expected3 = G2Affine::from_compressed(&[
|
||||
151, 185, 8, 123, 223, 150, 192, 192, 115, 10, 3, 129, 49, 179, 31, 108, 0, 17, 46,
|
||||
231, 184, 164, 247, 228, 22, 142, 87, 70, 120, 111, 154, 15, 245, 110, 32, 84, 53, 117,
|
||||
239, 93, 89, 119, 32, 17, 39, 250, 198, 137, 6, 95, 137, 202, 54, 244, 238, 190, 11,
|
||||
217, 237, 95, 72, 59, 140, 56, 3, 42, 61, 195, 192, 101, 46, 204, 207, 75, 70, 176,
|
||||
207, 48, 24, 195, 248, 234, 178, 168, 54, 109, 19, 189, 51, 52, 120, 69, 248, 226, 102,
|
||||
91,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
assert_eq!(hash_g2(msg1, b"DUMMY_TEST_DOMAIN"), expected1);
|
||||
assert_eq!(hash_g2(msg2, b"DUMMY_TEST_DOMAIN"), expected2);
|
||||
assert_eq!(hash_g2(msg3, b"DUMMY_TEST_DOMAIN"), expected3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,14 +20,11 @@ serde_json = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
time = { workspace = true }
|
||||
subtle = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
nym-crypto = { path = "../crypto", features = ["aead", "hashing"] }
|
||||
nym-pemstore = { path = "../pemstore" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-serde-helpers = { path = "../serde-helpers", features = ["base64"] }
|
||||
nym-task = { path = "../task" }
|
||||
|
||||
nym-credentials = { path = "../credentials" }
|
||||
|
||||
@@ -15,12 +15,6 @@ use thiserror::Error;
|
||||
// this is no longer constant size due to the differences in ciphertext between aes128ctr and aes256gcm-siv (inclusion of tag)
|
||||
pub struct EncryptedAddressBytes(Vec<u8>);
|
||||
|
||||
impl From<Vec<u8>> for EncryptedAddressBytes {
|
||||
fn from(encrypted_address: Vec<u8>) -> Self {
|
||||
EncryptedAddressBytes(encrypted_address)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EncryptedAddressConversionError {
|
||||
#[error("Failed to decode the encrypted address - {0}")]
|
||||
|
||||
@@ -19,7 +19,7 @@ pub use shared_key::{
|
||||
SharedGatewayKey, SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey,
|
||||
};
|
||||
|
||||
pub const CURRENT_PROTOCOL_VERSION: u8 = AUTHENTICATE_V2_PROTOCOL_VERSION;
|
||||
pub const CURRENT_PROTOCOL_VERSION: u8 = AES_GCM_SIV_PROTOCOL_VERSION;
|
||||
|
||||
/// Defines the current version of the communication protocol between gateway and clients.
|
||||
/// It has to be incremented for any breaking change.
|
||||
@@ -27,29 +27,10 @@ pub const CURRENT_PROTOCOL_VERSION: u8 = AUTHENTICATE_V2_PROTOCOL_VERSION;
|
||||
// 1 - initial release
|
||||
// 2 - changes to client credentials structure
|
||||
// 3 - change to AES-GCM-SIV and non-zero IVs
|
||||
// 4 - introduction of v2 authentication protocol to prevent reply attacks
|
||||
pub const INITIAL_PROTOCOL_VERSION: u8 = 1;
|
||||
pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2;
|
||||
pub const AES_GCM_SIV_PROTOCOL_VERSION: u8 = 3;
|
||||
pub const AUTHENTICATE_V2_PROTOCOL_VERSION: u8 = 4;
|
||||
|
||||
// TODO: could using `Mac` trait here for OutputSize backfire?
|
||||
// Should hmac itself be exposed, imported and used instead?
|
||||
pub type LegacyGatewayMacSize = <GatewayIntegrityHmacAlgorithm as OutputSizeUser>::OutputSize;
|
||||
|
||||
pub trait GatewayProtocolVersionExt {
|
||||
fn supports_aes256_gcm_siv(&self) -> bool;
|
||||
fn supports_authenticate_v2(&self) -> bool;
|
||||
}
|
||||
|
||||
impl GatewayProtocolVersionExt for Option<u8> {
|
||||
fn supports_aes256_gcm_siv(&self) -> bool {
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= AES_GCM_SIV_PROTOCOL_VERSION
|
||||
}
|
||||
|
||||
fn supports_authenticate_v2(&self) -> bool {
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= AUTHENTICATE_V2_PROTOCOL_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use crate::SharedKeyUsageError;
|
||||
use nym_credentials_interface::CompactEcashError;
|
||||
use nym_crypto::asymmetric::ed25519::SignatureError;
|
||||
use nym_sphinx::addressing::nodes::NymNodeRoutingAddressError;
|
||||
use nym_sphinx::forwarding::packet::MixPacketFormattingError;
|
||||
use nym_sphinx::params::packet_sizes::PacketSize;
|
||||
@@ -93,34 +92,7 @@ pub enum GatewayRequestsError {
|
||||
#[error("the provided [v1] credential has invalid number of parameters - {0}")]
|
||||
InvalidNumberOfEmbededParameters(u32),
|
||||
|
||||
#[error("failed to authenticate the client: {0}")]
|
||||
Authentication(#[from] AuthenticationFailure),
|
||||
|
||||
// variant to catch legacy errors
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum AuthenticationFailure {
|
||||
#[error(transparent)]
|
||||
KeyUsageFailure(#[from] SharedKeyUsageError),
|
||||
|
||||
#[error("failed to verify provided address ciphertext")]
|
||||
MalformedCiphertext,
|
||||
|
||||
#[error("failed to verify request signature")]
|
||||
InvalidSignature(#[from] SignatureError),
|
||||
|
||||
#[error("provided request timestamp is in the future")]
|
||||
RequestTimestampInFuture,
|
||||
|
||||
#[error("the client is not registered")]
|
||||
NotRegistered,
|
||||
|
||||
#[error("the provided request is too stale to process")]
|
||||
StaleRequest,
|
||||
|
||||
#[error("the provided request timestamp is smaller or equal to a one previously used")]
|
||||
RequestReuse,
|
||||
}
|
||||
|
||||
+1
-22
@@ -2,21 +2,16 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::models::CredentialSpendingRequest;
|
||||
use crate::text_request::authenticate::AuthenticateRequest;
|
||||
use crate::{
|
||||
GatewayRequestsError, SharedGatewayKey, SymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
|
||||
AUTHENTICATE_V2_PROTOCOL_VERSION, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION,
|
||||
INITIAL_PROTOCOL_VERSION,
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
use tungstenite::Message;
|
||||
|
||||
pub mod authenticate;
|
||||
|
||||
// wrapper for all encrypted requests for ease of use
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
@@ -73,9 +68,6 @@ pub enum ClientControlRequest {
|
||||
enc_address: String,
|
||||
iv: String,
|
||||
},
|
||||
|
||||
AuthenticateV2(Box<AuthenticateRequest>),
|
||||
|
||||
#[serde(alias = "handshakePayload")]
|
||||
RegisterHandshakeInitRequest {
|
||||
#[serde(default)]
|
||||
@@ -131,22 +123,9 @@ impl ClientControlRequest {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_authenticate_v2(
|
||||
shared_key: &SharedGatewayKey,
|
||||
identity_keys: &ed25519::KeyPair,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
// if we're using v2 authentication, we must announce at least that protocol version
|
||||
let protocol_version = AUTHENTICATE_V2_PROTOCOL_VERSION;
|
||||
|
||||
Ok(ClientControlRequest::AuthenticateV2(Box::new(
|
||||
AuthenticateRequest::new(protocol_version, shared_key, identity_keys)?,
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
match self {
|
||||
ClientControlRequest::Authenticate { .. } => "Authenticate".to_string(),
|
||||
ClientControlRequest::AuthenticateV2(..) => "AuthenticateV2".to_string(),
|
||||
ClientControlRequest::RegisterHandshakeInitRequest { .. } => {
|
||||
"RegisterHandshakeInitRequest".to_string()
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{AuthenticationFailure, GatewayRequestsError, SharedGatewayKey};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::iter;
|
||||
use std::time::Duration;
|
||||
use subtle::ConstantTimeEq;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthenticateRequest {
|
||||
#[serde(flatten)]
|
||||
pub content: AuthenticateRequestContent,
|
||||
|
||||
pub request_signature: ed25519::Signature,
|
||||
}
|
||||
|
||||
impl AuthenticateRequest {
|
||||
pub fn new(
|
||||
protocol_version: u8,
|
||||
shared_key: &SharedGatewayKey,
|
||||
identity_keys: &ed25519::KeyPair,
|
||||
) -> Result<AuthenticateRequest, GatewayRequestsError> {
|
||||
let content = AuthenticateRequestContent::new(
|
||||
protocol_version,
|
||||
shared_key,
|
||||
*identity_keys.public_key(),
|
||||
)?;
|
||||
let plaintext = content.plaintext();
|
||||
let request_signature = identity_keys.private_key().sign(&plaintext);
|
||||
|
||||
Ok(AuthenticateRequest {
|
||||
content,
|
||||
request_signature,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn verify_timestamp(&self, max_request_age: Duration) -> Result<(), AuthenticationFailure> {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
if self.content.request_timestamp() + max_request_age < now {
|
||||
return Err(AuthenticationFailure::StaleRequest);
|
||||
}
|
||||
if self.content.request_timestamp() > now {
|
||||
return Err(AuthenticationFailure::RequestTimestampInFuture);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn ensure_timestamp_not_reused(
|
||||
&self,
|
||||
previous: OffsetDateTime,
|
||||
) -> Result<(), AuthenticationFailure> {
|
||||
if self.content.request_timestamp() <= previous {
|
||||
return Err(AuthenticationFailure::RequestReuse);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_ciphertext(
|
||||
&self,
|
||||
shared_key: &SharedGatewayKey,
|
||||
) -> Result<(), AuthenticationFailure> {
|
||||
let expected = shared_key.encrypt(
|
||||
self.content
|
||||
.client_identity
|
||||
.derive_destination_address()
|
||||
.as_bytes_ref(),
|
||||
Some(&self.content.nonce),
|
||||
)?;
|
||||
|
||||
if !bool::from(expected.ct_eq(&self.content.address_ciphertext)) {
|
||||
return Err(AuthenticationFailure::MalformedCiphertext);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_signature(&self) -> Result<(), AuthenticationFailure> {
|
||||
let plaintext = self.content.plaintext();
|
||||
self.content
|
||||
.client_identity
|
||||
.verify(plaintext, &self.request_signature)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthenticateRequestContent {
|
||||
pub protocol_version: u8,
|
||||
|
||||
// this is identical to the client's address
|
||||
pub client_identity: ed25519::PublicKey,
|
||||
|
||||
#[serde(with = "nym_serde_helpers::base64")]
|
||||
pub address_ciphertext: Vec<u8>,
|
||||
|
||||
#[serde(with = "nym_serde_helpers::base64")]
|
||||
pub nonce: Vec<u8>,
|
||||
|
||||
pub request_unix_timestamp: u64,
|
||||
}
|
||||
|
||||
impl AuthenticateRequestContent {
|
||||
fn new(
|
||||
protocol_version: u8,
|
||||
shared_key: &SharedGatewayKey,
|
||||
client_identity: ed25519::PublicKey,
|
||||
) -> Result<AuthenticateRequestContent, GatewayRequestsError> {
|
||||
let nonce = shared_key.random_nonce_or_iv();
|
||||
let destination_address = client_identity.derive_destination_address();
|
||||
|
||||
let address_ciphertext =
|
||||
shared_key.encrypt(destination_address.as_bytes_ref(), Some(&nonce))?;
|
||||
let now = OffsetDateTime::now_utc();
|
||||
Ok(AuthenticateRequestContent {
|
||||
protocol_version,
|
||||
client_identity,
|
||||
address_ciphertext,
|
||||
nonce,
|
||||
request_unix_timestamp: now.unix_timestamp() as u64, // SAFETY: we're running this in post 1970...
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthenticateRequestContent {
|
||||
pub fn plaintext(&self) -> Vec<u8> {
|
||||
iter::once(self.protocol_version)
|
||||
.chain(self.client_identity.to_bytes())
|
||||
.chain(self.address_ciphertext.iter().copied())
|
||||
.chain(self.nonce.iter().copied())
|
||||
.chain(self.request_unix_timestamp.to_be_bytes())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn request_timestamp(&self) -> OffsetDateTime {
|
||||
OffsetDateTime::from_unix_timestamp(self.request_unix_timestamp as i64)
|
||||
.unwrap_or(OffsetDateTime::UNIX_EPOCH)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
ALTER TABLE shared_keys
|
||||
ADD COLUMN last_used_authentication TIMESTAMP WITHOUT TIME ZONE;
|
||||
@@ -200,20 +200,6 @@ impl GatewayStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_last_used_authentication_timestamp(
|
||||
&self,
|
||||
client_id: i64,
|
||||
last_used_authentication_timestamp: OffsetDateTime,
|
||||
) -> Result<(), GatewayStorageError> {
|
||||
self.shared_key_manager
|
||||
.update_last_used_authentication_timestamp(
|
||||
client_id,
|
||||
last_used_authentication_timestamp,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_client(&self, client_id: i64) -> Result<Option<Client>, GatewayStorageError> {
|
||||
let client = self.client_manager.get_client(client_id).await?;
|
||||
Ok(client)
|
||||
|
||||
@@ -14,13 +14,13 @@ pub struct Client {
|
||||
|
||||
#[derive(FromRow)]
|
||||
pub struct PersistedSharedKeys {
|
||||
#[allow(dead_code)]
|
||||
pub client_id: i64,
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub client_address_bs58: String,
|
||||
pub derived_aes128_ctr_blake3_hmac_keys_bs58: Option<String>,
|
||||
pub derived_aes256_gcm_siv_key: Option<Vec<u8>>,
|
||||
pub last_used_authentication: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
impl TryFrom<PersistedSharedKeys> for SharedGatewayKey {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::models::PersistedSharedKeys;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct SharedKeysManager {
|
||||
@@ -69,22 +68,6 @@ impl SharedKeysManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn update_last_used_authentication_timestamp(
|
||||
&self,
|
||||
client_id: i64,
|
||||
last_used: OffsetDateTime,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"UPDATE shared_keys SET last_used_authentication = ? WHERE client_id = ?;",
|
||||
last_used,
|
||||
client_id
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tries to retrieve shared keys stored for the particular client.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -94,10 +77,13 @@ impl SharedKeysManager {
|
||||
&self,
|
||||
client_address_bs58: &str,
|
||||
) -> Result<Option<PersistedSharedKeys>, sqlx::Error> {
|
||||
sqlx::query_as("SELECT * FROM shared_keys WHERE client_address_bs58 = ?")
|
||||
.bind(client_address_bs58)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
sqlx::query_as!(
|
||||
PersistedSharedKeys,
|
||||
"SELECT * FROM shared_keys WHERE client_address_bs58 = ?",
|
||||
client_address_bs58
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Removes from the database shared keys derived with the particular client.
|
||||
|
||||
@@ -12,7 +12,7 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json", "gzip"] }
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
http.workspace = true
|
||||
url = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
@@ -21,12 +21,6 @@ serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# used for decoding text responses (they were already implicitly included)
|
||||
bytes = { workspace = true }
|
||||
encoding_rs = { workspace = true }
|
||||
mime = { workspace = true }
|
||||
|
||||
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||
@@ -38,4 +32,4 @@ workspace = true
|
||||
features = ["tokio"]
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["rt", "macros"] }
|
||||
tokio = { workspace = true, features=["rt", "macros"] }
|
||||
|
||||
@@ -13,18 +13,15 @@
|
||||
|
||||
use crate::ClientBuilder;
|
||||
|
||||
use std::{
|
||||
net::SocketAddr,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
|
||||
use hickory_resolver::lookup_ip::LookupIp;
|
||||
use hickory_resolver::{
|
||||
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ResolverOpts},
|
||||
error::ResolveError,
|
||||
lookup_ip::LookupIpIntoIter,
|
||||
TokioAsyncResolver,
|
||||
};
|
||||
use hickory_resolver::{error::ResolveErrorKind, lookup_ip::LookupIp};
|
||||
use once_cell::sync::OnceCell;
|
||||
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
|
||||
use tracing::warn;
|
||||
@@ -41,14 +38,6 @@ struct SocketAddrs {
|
||||
iter: LookupIpIntoIter,
|
||||
}
|
||||
|
||||
// n.b. static items do not call [`Drop`] on program termination, so this won't be deallocated.
|
||||
// this is fine, as the OS can deallocate the terminated program faster than we can free memory
|
||||
// but tools like valgrind might report "memory leaks" as it isn't obvious this is intentional.
|
||||
static SHARED_RESOLVER: LazyLock<HickoryDnsResolver> = LazyLock::new(|| {
|
||||
tracing::debug!("Initializing shared DNS resolver");
|
||||
HickoryDnsResolver::default()
|
||||
});
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("hickory-dns resolver error: {hickory_error}")]
|
||||
/// Error occurring while resolving a hostname into an IP address.
|
||||
@@ -58,62 +47,29 @@ pub struct HickoryDnsError {
|
||||
}
|
||||
|
||||
/// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait.
|
||||
///
|
||||
/// Typical use involves instantiating using the `Default` implementation and then resolving using
|
||||
/// methods or trait implementations.
|
||||
///
|
||||
/// The default initialization uses a shared underlying `AsyncResolver`. If a thread local resolver
|
||||
/// is required use `thread_resolver()` to build a resolver with an independently instantiated
|
||||
/// internal `AsyncResolver`.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct HickoryDnsResolver {
|
||||
// Since we might not have been called in the context of a
|
||||
// Tokio Runtime in initialization, so we must delay the actual
|
||||
// construction of the resolver.
|
||||
/// Since we might not have been called in the context of a
|
||||
/// Tokio Runtime in initialization, so we must delay the actual
|
||||
/// construction of the resolver.
|
||||
state: Arc<OnceCell<TokioAsyncResolver>>,
|
||||
fallback: Arc<OnceCell<TokioAsyncResolver>>,
|
||||
dont_use_shared: bool,
|
||||
}
|
||||
|
||||
impl Resolve for HickoryDnsResolver {
|
||||
fn resolve(&self, name: Name) -> Resolving {
|
||||
let resolver = self.state.clone();
|
||||
let fallback = self.fallback.clone();
|
||||
let independent = self.dont_use_shared;
|
||||
Box::pin(async move {
|
||||
let resolver = resolver.get_or_try_init(|| {
|
||||
// using a closure here is slightly gross, but this makes sure that if the
|
||||
// lazy-init returns an error it can be handled by the client
|
||||
if independent {
|
||||
new_resolver()
|
||||
} else {
|
||||
Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone())
|
||||
}
|
||||
})?;
|
||||
let resolver = resolver.get_or_try_init(new_resolver)?;
|
||||
|
||||
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
|
||||
let lookup = match resolver.lookup_ip(name.as_str()).await {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
// on failure use the fall back system configured DNS resolver
|
||||
match e.kind() {
|
||||
ResolveErrorKind::NoRecordsFound { .. } => {}
|
||||
_ => {
|
||||
warn!("primary DNS failed w/ error {e}: using system fallback");
|
||||
}
|
||||
}
|
||||
let resolver = fallback.get_or_try_init(|| {
|
||||
// using a closure here is slightly gross, but this makes sure that if the
|
||||
// lazy-init returns an error it can be handled by the client
|
||||
if independent {
|
||||
new_resolver_system()
|
||||
} else {
|
||||
Ok(SHARED_RESOLVER
|
||||
.fallback
|
||||
.get_or_try_init(new_resolver_system)?
|
||||
.clone())
|
||||
}
|
||||
})?;
|
||||
warn!("primary DNS failed w/ error {e}: using system fallback");
|
||||
let resolver = fallback.get_or_try_init(new_resolver_system)?;
|
||||
resolver.lookup_ip(name.as_str()).await?
|
||||
}
|
||||
};
|
||||
@@ -137,55 +93,21 @@ impl Iterator for SocketAddrs {
|
||||
impl HickoryDnsResolver {
|
||||
/// Attempt to resolve a domain name to a set of ['IpAddr']s
|
||||
pub async fn resolve_str(&self, name: &str) -> Result<LookupIp, HickoryDnsError> {
|
||||
let resolver = self.state.get_or_try_init(|| self.new_resolver())?;
|
||||
let resolver = self.state.get_or_try_init(new_resolver)?;
|
||||
|
||||
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
|
||||
let lookup = match resolver.lookup_ip(name).await {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
// on failure use the fall back system configured DNS resolver
|
||||
match e.kind() {
|
||||
ResolveErrorKind::NoRecordsFound { .. } => {}
|
||||
_ => {
|
||||
warn!("primary DNS failed w/ error {e}: using system fallback");
|
||||
}
|
||||
}
|
||||
let resolver = self
|
||||
.fallback
|
||||
.get_or_try_init(|| self.new_resolver_system())?;
|
||||
warn!("primary DNS failed w/ error {e}: using system fallback");
|
||||
let resolver = self.fallback.get_or_try_init(new_resolver_system)?;
|
||||
resolver.lookup_ip(name).await?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(lookup)
|
||||
}
|
||||
|
||||
/// Create a (lazy-initialized) resolver that is not shared across threads.
|
||||
pub fn thread_resolver() -> Self {
|
||||
Self {
|
||||
dont_use_shared: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn new_resolver(&self) -> Result<TokioAsyncResolver, HickoryDnsError> {
|
||||
if self.dont_use_shared {
|
||||
new_resolver()
|
||||
} else {
|
||||
Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn new_resolver_system(&self) -> Result<TokioAsyncResolver, HickoryDnsError> {
|
||||
if self.dont_use_shared {
|
||||
new_resolver_system()
|
||||
} else {
|
||||
Ok(SHARED_RESOLVER
|
||||
.fallback
|
||||
.get_or_try_init(new_resolver_system)?
|
||||
.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new resolver with a custom DoT based configuration. The options are overridden to look
|
||||
|
||||
+361
-478
File diff suppressed because it is too large
Load Diff
@@ -7,16 +7,11 @@ use http::HeaderValue;
|
||||
use nym_bin_common::build_information::{BinaryBuildInformation, BinaryBuildInformationOwned};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Characteristic elements sent to the API providing basic context information of the requesting client.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct UserAgent {
|
||||
/// The internal crate / application / subsystem making use of API client
|
||||
pub application: String,
|
||||
/// version of the calling crate / application / subsystem
|
||||
pub version: String,
|
||||
/// client platform
|
||||
pub platform: String,
|
||||
/// source commit version for the calling calling crate / subsystem
|
||||
pub git_commit: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ mime = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
serde_yaml = { workspace = true }
|
||||
subtle.workspace = true
|
||||
tower = { workspace = true }
|
||||
tracing.workspace = true
|
||||
utoipa = { workspace = true, optional = true }
|
||||
|
||||
@@ -7,7 +7,6 @@ use axum::{extract::Request, response::Response};
|
||||
use futures::future::BoxFuture;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use subtle::ConstantTimeEq;
|
||||
use tower::{Layer, Service};
|
||||
use tracing::{debug, instrument, trace};
|
||||
use zeroize::Zeroizing;
|
||||
@@ -77,7 +76,7 @@ impl<S> RequireAuth<S> {
|
||||
return Err("`Authorization` header must contain non-empty `Bearer` token");
|
||||
}
|
||||
|
||||
if bool::from(self.bearer_token.as_bytes().ct_ne(bearer_token.as_bytes())) {
|
||||
if self.bearer_token.as_str() != bearer_token {
|
||||
return Err("`Authorization` header does not contain the correct `Bearer` token");
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ bincode = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-service-provider-requests-common = { path = "../service-provider-requests-common" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
rand = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
@@ -2,18 +2,24 @@ use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
// The current version of the protocol.
|
||||
// The idea here is that we add new request response types at least one version before we start
|
||||
// using them.
|
||||
// Also, depending on the version in the client connect message the IPR could respond with a
|
||||
// matching older version.
|
||||
pub use v6::request;
|
||||
pub use v6::response;
|
||||
|
||||
pub mod codec;
|
||||
pub mod sign;
|
||||
pub mod v6;
|
||||
pub mod v7;
|
||||
pub mod v8;
|
||||
|
||||
// version 3: initial version
|
||||
// version 4: IPv6 support
|
||||
// version 5: Add severity level to info response
|
||||
// version 6: Increase the available IPs
|
||||
// version 7: Add signature support (for the future)
|
||||
// version 8: Anonymous sends
|
||||
pub const CURRENT_VERSION: u8 = 6;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct IpPair {
|
||||
@@ -39,9 +45,3 @@ fn make_bincode_serializer() -> impl bincode::Options {
|
||||
.with_big_endian()
|
||||
.with_varint_encoding()
|
||||
}
|
||||
|
||||
fn generate_random() -> u64 {
|
||||
use rand::RngCore;
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{v6, v7};
|
||||
|
||||
impl From<v7::response::StaticConnectFailureReason> for v6::response::StaticConnectFailureReason {
|
||||
fn from(failure: v7::response::StaticConnectFailureReason) -> Self {
|
||||
match failure {
|
||||
v7::response::StaticConnectFailureReason::RequestedIpAlreadyInUse => {
|
||||
v6::response::StaticConnectFailureReason::RequestedIpAlreadyInUse
|
||||
}
|
||||
v7::response::StaticConnectFailureReason::RequestedNymAddressAlreadyInUse => {
|
||||
v6::response::StaticConnectFailureReason::RequestedNymAddressAlreadyInUse
|
||||
}
|
||||
v7::response::StaticConnectFailureReason::OutOfDateTimestamp => {
|
||||
v6::response::StaticConnectFailureReason::Other("out of date timestamp".to_string())
|
||||
}
|
||||
v7::response::StaticConnectFailureReason::Other(reason) => {
|
||||
v6::response::StaticConnectFailureReason::Other(reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v7::response::DynamicConnectFailureReason> for v6::response::DynamicConnectFailureReason {
|
||||
fn from(failure: v7::response::DynamicConnectFailureReason) -> Self {
|
||||
match failure {
|
||||
v7::response::DynamicConnectFailureReason::RequestedNymAddressAlreadyInUse => {
|
||||
v6::response::DynamicConnectFailureReason::RequestedNymAddressAlreadyInUse
|
||||
}
|
||||
v7::response::DynamicConnectFailureReason::NoAvailableIp => {
|
||||
v6::response::DynamicConnectFailureReason::NoAvailableIp
|
||||
}
|
||||
v7::response::DynamicConnectFailureReason::Other(err) => {
|
||||
v6::response::DynamicConnectFailureReason::Other(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v7::response::InfoResponseReply> for v6::response::InfoResponseReply {
|
||||
fn from(reply: v7::response::InfoResponseReply) -> Self {
|
||||
match reply {
|
||||
v7::response::InfoResponseReply::Generic { msg } => {
|
||||
v6::response::InfoResponseReply::Generic { msg }
|
||||
}
|
||||
v7::response::InfoResponseReply::VersionMismatch {
|
||||
request_version,
|
||||
response_version,
|
||||
} => v6::response::InfoResponseReply::VersionMismatch {
|
||||
request_version,
|
||||
response_version,
|
||||
},
|
||||
v7::response::InfoResponseReply::ExitPolicyFilterCheckFailed { dst } => {
|
||||
v6::response::InfoResponseReply::ExitPolicyFilterCheckFailed { dst }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v7::response::InfoLevel> for v6::response::InfoLevel {
|
||||
fn from(level: v7::response::InfoLevel) -> Self {
|
||||
match level {
|
||||
v7::response::InfoLevel::Info => v6::response::InfoLevel::Info,
|
||||
v7::response::InfoLevel::Warn => v6::response::InfoLevel::Warn,
|
||||
v7::response::InfoLevel::Error => v6::response::InfoLevel::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod conversion;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::{v6, v7};
|
||||
|
||||
impl From<v6::request::IpPacketRequest> for v7::request::IpPacketRequest {
|
||||
fn from(ip_packet_request: v6::request::IpPacketRequest) -> Self {
|
||||
Self {
|
||||
version: 7,
|
||||
data: ip_packet_request.data.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::request::IpPacketRequestData> for v7::request::IpPacketRequestData {
|
||||
fn from(ip_packet_request_data: v6::request::IpPacketRequestData) -> Self {
|
||||
match ip_packet_request_data {
|
||||
v6::request::IpPacketRequestData::StaticConnect(r) => {
|
||||
v7::request::IpPacketRequestData::StaticConnect(
|
||||
v7::request::SignedStaticConnectRequest {
|
||||
request: r.into(),
|
||||
signature: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
v6::request::IpPacketRequestData::DynamicConnect(r) => {
|
||||
v7::request::IpPacketRequestData::DynamicConnect(
|
||||
v7::request::SignedDynamicConnectRequest {
|
||||
request: r.into(),
|
||||
signature: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
v6::request::IpPacketRequestData::Disconnect(r) => {
|
||||
v7::request::IpPacketRequestData::Disconnect(v7::request::SignedDisconnectRequest {
|
||||
request: r.into(),
|
||||
signature: None,
|
||||
})
|
||||
}
|
||||
v6::request::IpPacketRequestData::Data(r) => {
|
||||
v7::request::IpPacketRequestData::Data(r.into())
|
||||
}
|
||||
v6::request::IpPacketRequestData::Ping(r) => {
|
||||
v7::request::IpPacketRequestData::Ping(r.into())
|
||||
}
|
||||
v6::request::IpPacketRequestData::Health(r) => {
|
||||
v7::request::IpPacketRequestData::Health(r.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::request::StaticConnectRequest> for v7::request::StaticConnectRequest {
|
||||
fn from(static_connect_request: v6::request::StaticConnectRequest) -> Self {
|
||||
Self {
|
||||
request_id: static_connect_request.request_id,
|
||||
ips: static_connect_request.ips,
|
||||
reply_to: static_connect_request.reply_to,
|
||||
reply_to_hops: static_connect_request.reply_to_hops,
|
||||
reply_to_avg_mix_delays: static_connect_request.reply_to_avg_mix_delays,
|
||||
buffer_timeout: static_connect_request.buffer_timeout,
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl From<v6::request::DynamicConnectRequest> for v7::request::DynamicConnectRequest {
|
||||
fn from(dynamic_connect_request: v6::request::DynamicConnectRequest) -> Self {
|
||||
Self {
|
||||
request_id: dynamic_connect_request.request_id,
|
||||
reply_to: dynamic_connect_request.reply_to,
|
||||
reply_to_hops: dynamic_connect_request.reply_to_hops,
|
||||
reply_to_avg_mix_delays: dynamic_connect_request.reply_to_avg_mix_delays,
|
||||
buffer_timeout: dynamic_connect_request.buffer_timeout,
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::request::DisconnectRequest> for v7::request::SignedDisconnectRequest {
|
||||
fn from(disconnect_request: v6::request::DisconnectRequest) -> Self {
|
||||
Self {
|
||||
request: disconnect_request.into(),
|
||||
signature: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::request::DisconnectRequest> for v7::request::DisconnectRequest {
|
||||
fn from(disconnect_request: v6::request::DisconnectRequest) -> Self {
|
||||
Self {
|
||||
request_id: disconnect_request.request_id,
|
||||
reply_to: disconnect_request.reply_to,
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::request::DataRequest> for v7::request::DataRequest {
|
||||
fn from(data_request: v6::request::DataRequest) -> Self {
|
||||
Self {
|
||||
ip_packets: data_request.ip_packets,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::request::PingRequest> for v7::request::PingRequest {
|
||||
fn from(ping_request: v6::request::PingRequest) -> Self {
|
||||
Self {
|
||||
request_id: ping_request.request_id,
|
||||
reply_to: ping_request.reply_to,
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::request::HealthRequest> for v7::request::HealthRequest {
|
||||
fn from(health_request: v6::request::HealthRequest) -> Self {
|
||||
Self {
|
||||
request_id: health_request.request_id,
|
||||
reply_to: health_request.reply_to,
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
pub mod conversion;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod signature;
|
||||
|
||||
pub const VERSION: u8 = 7;
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
use std::fmt;
|
||||
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::{
|
||||
sign::{SignatureError, SignedRequest},
|
||||
IpPair,
|
||||
use crate::{make_bincode_serializer, IpPair};
|
||||
|
||||
use super::{
|
||||
signature::{SignatureError, SignedRequest},
|
||||
VERSION,
|
||||
};
|
||||
|
||||
use super::VERSION;
|
||||
fn generate_random() -> u64 {
|
||||
use rand::RngCore;
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct IpPacketRequest {
|
||||
pub version: u8,
|
||||
pub data: IpPacketRequestData,
|
||||
@@ -26,7 +30,7 @@ impl IpPacketRequest {
|
||||
reply_to_avg_mix_delays: Option<f64>,
|
||||
buffer_timeout: Option<u64>,
|
||||
) -> (Self, u64) {
|
||||
let request_id = crate::generate_random();
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
@@ -54,7 +58,7 @@ impl IpPacketRequest {
|
||||
reply_to_avg_mix_delays: Option<f64>,
|
||||
buffer_timeout: Option<u64>,
|
||||
) -> (Self, u64) {
|
||||
let request_id = crate::generate_random();
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
@@ -75,7 +79,7 @@ impl IpPacketRequest {
|
||||
}
|
||||
|
||||
pub fn new_disconnect_request(reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = crate::generate_random();
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
@@ -100,7 +104,7 @@ impl IpPacketRequest {
|
||||
}
|
||||
|
||||
pub fn new_ping(reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = crate::generate_random();
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
@@ -115,7 +119,7 @@ impl IpPacketRequest {
|
||||
}
|
||||
|
||||
pub fn new_health_request(reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = crate::generate_random();
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
@@ -151,27 +155,16 @@ impl IpPacketRequest {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> Result<(), SignatureError> {
|
||||
match &self.data {
|
||||
IpPacketRequestData::StaticConnect(request) => request.verify(),
|
||||
IpPacketRequestData::DynamicConnect(request) => request.verify(),
|
||||
IpPacketRequestData::Disconnect(request) => request.verify(),
|
||||
IpPacketRequestData::Data(_) => Ok(()),
|
||||
IpPacketRequestData::Ping(_) => Ok(()),
|
||||
IpPacketRequestData::Health(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
crate::make_bincode_serializer().serialize(self)
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
crate::make_bincode_serializer().deserialize(&message.message)
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,19 +179,6 @@ pub enum IpPacketRequestData {
|
||||
Health(HealthRequest),
|
||||
}
|
||||
|
||||
impl fmt::Display for IpPacketRequestData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
IpPacketRequestData::StaticConnect(_) => write!(f, "StaticConnect"),
|
||||
IpPacketRequestData::DynamicConnect(_) => write!(f, "DynamicConnect"),
|
||||
IpPacketRequestData::Disconnect(_) => write!(f, "Disconnect"),
|
||||
IpPacketRequestData::Data(_) => write!(f, "Data"),
|
||||
IpPacketRequestData::Ping(_) => write!(f, "Ping"),
|
||||
IpPacketRequestData::Health(_) => write!(f, "Health"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IpPacketRequestData {
|
||||
pub fn add_signature(&mut self, signature: identity::Signature) -> Option<identity::Signature> {
|
||||
match self {
|
||||
@@ -222,9 +202,9 @@ impl IpPacketRequestData {
|
||||
|
||||
pub fn signable_request(&self) -> Option<Result<Vec<u8>, SignatureError>> {
|
||||
match self {
|
||||
IpPacketRequestData::StaticConnect(request) => Some(request.request_as_bytes()),
|
||||
IpPacketRequestData::DynamicConnect(request) => Some(request.request_as_bytes()),
|
||||
IpPacketRequestData::Disconnect(request) => Some(request.request_as_bytes()),
|
||||
IpPacketRequestData::StaticConnect(request) => Some(request.request()),
|
||||
IpPacketRequestData::DynamicConnect(request) => Some(request.request()),
|
||||
IpPacketRequestData::Disconnect(request) => Some(request.request()),
|
||||
IpPacketRequestData::Data(_) => None,
|
||||
IpPacketRequestData::Ping(_) => None,
|
||||
IpPacketRequestData::Health(_) => None,
|
||||
@@ -262,7 +242,7 @@ pub struct StaticConnectRequest {
|
||||
impl StaticConnectRequest {
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
crate::make_bincode_serializer().serialize(self)
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,11 +253,11 @@ pub struct SignedStaticConnectRequest {
|
||||
}
|
||||
|
||||
impl SignedRequest for SignedStaticConnectRequest {
|
||||
fn identity(&self) -> Option<&identity::PublicKey> {
|
||||
Some(self.request.reply_to.identity())
|
||||
fn identity(&self) -> &identity::PublicKey {
|
||||
self.request.reply_to.identity()
|
||||
}
|
||||
|
||||
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
fn request(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
self.request
|
||||
.to_bytes()
|
||||
.map_err(|error| SignatureError::RequestSerializationError {
|
||||
@@ -326,7 +306,7 @@ pub struct DynamicConnectRequest {
|
||||
impl DynamicConnectRequest {
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
crate::make_bincode_serializer().serialize(self)
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,11 +317,11 @@ pub struct SignedDynamicConnectRequest {
|
||||
}
|
||||
|
||||
impl SignedRequest for SignedDynamicConnectRequest {
|
||||
fn identity(&self) -> Option<&identity::PublicKey> {
|
||||
Some(self.request.reply_to.identity())
|
||||
fn identity(&self) -> &identity::PublicKey {
|
||||
self.request.reply_to.identity()
|
||||
}
|
||||
|
||||
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
fn request(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
self.request
|
||||
.to_bytes()
|
||||
.map_err(|error| SignatureError::RequestSerializationError {
|
||||
@@ -375,7 +355,7 @@ pub struct DisconnectRequest {
|
||||
impl DisconnectRequest {
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
crate::make_bincode_serializer().serialize(self)
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,11 +366,11 @@ pub struct SignedDisconnectRequest {
|
||||
}
|
||||
|
||||
impl SignedRequest for SignedDisconnectRequest {
|
||||
fn identity(&self) -> Option<&identity::PublicKey> {
|
||||
Some(self.request.reply_to.identity())
|
||||
fn identity(&self) -> &identity::PublicKey {
|
||||
self.request.reply_to.identity()
|
||||
}
|
||||
|
||||
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
fn request(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
self.request
|
||||
.to_bytes()
|
||||
.map_err(|error| SignatureError::RequestSerializationError {
|
||||
|
||||
+9
-18
@@ -1,7 +1,6 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use time::OffsetDateTime;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
|
||||
// For reply protection, if a request is older than this, it will be rejected
|
||||
const MAX_REQUEST_AGE: Duration = Duration::from_secs(10);
|
||||
@@ -23,37 +22,29 @@ pub enum SignatureError {
|
||||
#[error("signature verification failed")]
|
||||
VerificationFailed {
|
||||
message: String,
|
||||
error: ed25519::SignatureError,
|
||||
error: identity::SignatureError,
|
||||
},
|
||||
}
|
||||
|
||||
pub trait SignedRequest {
|
||||
fn identity(&self) -> Option<&ed25519::PublicKey>;
|
||||
fn identity(&self) -> &identity::PublicKey;
|
||||
|
||||
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError>;
|
||||
fn request(&self) -> Result<Vec<u8>, SignatureError>;
|
||||
|
||||
fn signature(&self) -> Option<&ed25519::Signature>;
|
||||
fn signature(&self) -> Option<&identity::Signature>;
|
||||
|
||||
fn timestamp(&self) -> OffsetDateTime;
|
||||
fn timestamp(&self) -> time::OffsetDateTime;
|
||||
|
||||
fn verify(&self) -> Result<(), SignatureError> {
|
||||
let identity = match self.identity() {
|
||||
Some(identity) => identity,
|
||||
None => {
|
||||
// If we are not revealing our identity, we don't need to verify anything
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(signature) = self.signature() {
|
||||
// First check that the request is recent enough
|
||||
if OffsetDateTime::now_utc() - self.timestamp() > MAX_REQUEST_AGE {
|
||||
if time::OffsetDateTime::now_utc() - self.timestamp() > MAX_REQUEST_AGE {
|
||||
return Err(SignatureError::RequestOutOfDate);
|
||||
}
|
||||
|
||||
let request_as_bytes = self.request_as_bytes()?;
|
||||
let request_as_bytes = self.request()?;
|
||||
|
||||
identity
|
||||
self.identity()
|
||||
.verify(request_as_bytes, signature)
|
||||
.map_err(|error| SignatureError::VerificationFailed {
|
||||
message: "signature verification failed".to_string(),
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
pub const VERSION: u8 = 8;
|
||||
@@ -1,304 +0,0 @@
|
||||
use std::fmt;
|
||||
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use super::VERSION;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct IpPacketRequest {
|
||||
pub protocol: Protocol,
|
||||
pub data: IpPacketRequestData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum IpPacketRequestData {
|
||||
Data(DataRequest),
|
||||
Control(Box<ControlRequest>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum ControlRequest {
|
||||
Connect(ConnectRequest),
|
||||
Disconnect(DisconnectRequest),
|
||||
Ping(PingRequest),
|
||||
Health(HealthRequest),
|
||||
}
|
||||
|
||||
// A data request is when the client wants to send an IP packet to a destination.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DataRequest {
|
||||
pub ip_packets: bytes::Bytes,
|
||||
}
|
||||
|
||||
// A dynamic connect request is when the client does not provide the internal IP address it will use
|
||||
// on the ip packet router, and instead requests one to be assigned to it.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ConnectRequest {
|
||||
pub request_id: u64,
|
||||
|
||||
// The maximum time in milliseconds the IPR should wait when filling up a mix packet
|
||||
// with ip packets.
|
||||
pub buffer_timeout: Option<u64>,
|
||||
|
||||
// Timestamp of when the request was sent by the client.
|
||||
pub timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
// A disconnect request is when the client wants to disconnect from the ip packet router and free
|
||||
// up the allocated IP address.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DisconnectRequest {
|
||||
pub request_id: u64,
|
||||
|
||||
// Timestamp of when the request was sent by the client.
|
||||
pub timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
// A ping request is when the client wants to check if the ip packet router is still alive.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PingRequest {
|
||||
pub request_id: u64,
|
||||
|
||||
// Timestamp of when the request was sent by the client.
|
||||
pub timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct HealthRequest {
|
||||
pub request_id: u64,
|
||||
|
||||
// Timestamp of when the request was sent by the client.
|
||||
pub timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
impl IpPacketRequest {
|
||||
pub fn new_connect_request(buffer_timeout: Option<u64>) -> (Self, u64) {
|
||||
let protocol = Protocol {
|
||||
version: VERSION,
|
||||
service_provider_type: ServiceProviderType::IpPacketRouter,
|
||||
};
|
||||
let request_id = rand::random();
|
||||
let timestamp = OffsetDateTime::now_utc();
|
||||
let connect = ConnectRequest {
|
||||
request_id,
|
||||
buffer_timeout,
|
||||
timestamp,
|
||||
};
|
||||
let request = Self {
|
||||
protocol,
|
||||
data: IpPacketRequestData::Control(Box::new(ControlRequest::Connect(connect))),
|
||||
};
|
||||
(request, request_id)
|
||||
}
|
||||
|
||||
pub fn new_disconnect_request() -> (Self, u64) {
|
||||
let protocol = Protocol {
|
||||
version: VERSION,
|
||||
service_provider_type: ServiceProviderType::IpPacketRouter,
|
||||
};
|
||||
let request_id = rand::random();
|
||||
let timestamp = OffsetDateTime::now_utc();
|
||||
let disconnect = DisconnectRequest {
|
||||
request_id,
|
||||
timestamp,
|
||||
};
|
||||
let request = Self {
|
||||
protocol,
|
||||
data: IpPacketRequestData::Control(Box::new(ControlRequest::Disconnect(disconnect))),
|
||||
};
|
||||
(request, request_id)
|
||||
}
|
||||
|
||||
pub fn new_data_request(ip_packets: bytes::Bytes) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
version: VERSION,
|
||||
service_provider_type: ServiceProviderType::IpPacketRouter,
|
||||
},
|
||||
data: IpPacketRequestData::Data(DataRequest { ip_packets }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_ping() -> (Self, u64) {
|
||||
let protocol = Protocol {
|
||||
version: VERSION,
|
||||
service_provider_type: ServiceProviderType::IpPacketRouter,
|
||||
};
|
||||
let request_id = rand::random();
|
||||
let timestamp = OffsetDateTime::now_utc();
|
||||
let ping_request = PingRequest {
|
||||
request_id,
|
||||
timestamp,
|
||||
};
|
||||
let request = Self {
|
||||
protocol,
|
||||
data: IpPacketRequestData::Control(Box::new(ControlRequest::Ping(ping_request))),
|
||||
};
|
||||
(request, request_id)
|
||||
}
|
||||
|
||||
pub fn new_health_request() -> (Self, u64) {
|
||||
let protocol = Protocol {
|
||||
version: VERSION,
|
||||
service_provider_type: ServiceProviderType::IpPacketRouter,
|
||||
};
|
||||
let request_id = rand::random();
|
||||
let timestamp = OffsetDateTime::now_utc();
|
||||
let health_request = HealthRequest {
|
||||
request_id,
|
||||
timestamp,
|
||||
};
|
||||
let request = Self {
|
||||
protocol,
|
||||
data: IpPacketRequestData::Control(Box::new(ControlRequest::Health(health_request))),
|
||||
};
|
||||
(request, request_id)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match self.data {
|
||||
IpPacketRequestData::Control(ref c) => Some(c.id()),
|
||||
IpPacketRequestData::Data(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
crate::make_bincode_serializer().serialize(self)
|
||||
}
|
||||
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
crate::make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IpPacketRequest {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"IpPacketRequest {{ version: {}, data: {} }}",
|
||||
self.protocol.version, self.data
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IpPacketRequestData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
IpPacketRequestData::Data(_) => write!(f, "Data"),
|
||||
IpPacketRequestData::Control(c) => write!(f, "Control({})", c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ControlRequest {
|
||||
fn id(&self) -> u64 {
|
||||
match self {
|
||||
ControlRequest::Connect(request) => request.request_id,
|
||||
ControlRequest::Disconnect(request) => request.request_id,
|
||||
ControlRequest::Ping(request) => request.request_id,
|
||||
ControlRequest::Health(request) => request.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ControlRequest {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ControlRequest::Connect(_) => write!(f, "Connect"),
|
||||
ControlRequest::Disconnect(_) => write!(f, "Disconnect"),
|
||||
ControlRequest::Ping(_) => write!(f, "Ping"),
|
||||
ControlRequest::Health(_) => write!(f, "Health"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectRequest {
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
crate::make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl DisconnectRequest {
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
crate::make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use time::macros::datetime;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_size_of_request() {
|
||||
let connect = IpPacketRequest {
|
||||
protocol: Protocol {
|
||||
version: 4,
|
||||
service_provider_type: ServiceProviderType::IpPacketRouter,
|
||||
},
|
||||
data: IpPacketRequestData::Control(Box::new(ControlRequest::Connect(ConnectRequest {
|
||||
request_id: 123,
|
||||
buffer_timeout: None,
|
||||
timestamp: datetime!(2024-01-01 12:59:59.5 UTC),
|
||||
}))),
|
||||
};
|
||||
assert_eq!(connect.to_bytes().unwrap().len(), 21);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_size_of_data() {
|
||||
let data = IpPacketRequest {
|
||||
protocol: Protocol {
|
||||
version: 4,
|
||||
service_provider_type: ServiceProviderType::IpPacketRouter,
|
||||
},
|
||||
data: IpPacketRequestData::Data(DataRequest {
|
||||
ip_packets: bytes::Bytes::from(vec![1u8; 32]),
|
||||
}),
|
||||
};
|
||||
assert_eq!(data.to_bytes().unwrap().len(), 36);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_and_deserialize_data_request() {
|
||||
let data = IpPacketRequest {
|
||||
protocol: Protocol {
|
||||
version: 4,
|
||||
service_provider_type: ServiceProviderType::IpPacketRouter,
|
||||
},
|
||||
data: IpPacketRequestData::Data(DataRequest {
|
||||
ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
|
||||
}),
|
||||
};
|
||||
|
||||
let serialized = data.to_bytes().unwrap();
|
||||
let deserialized = IpPacketRequest::from_reconstructed_message(
|
||||
&nym_sphinx::receiver::ReconstructedMessage {
|
||||
message: serialized,
|
||||
sender_tag: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(deserialized.protocol.version, 4);
|
||||
assert_eq!(
|
||||
deserialized.protocol.service_provider_type,
|
||||
ServiceProviderType::IpPacketRouter
|
||||
);
|
||||
assert_eq!(
|
||||
deserialized.data,
|
||||
IpPacketRequestData::Data(DataRequest {
|
||||
ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
use nym_bin_common::build_information::BinaryBuildInformationOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{make_bincode_serializer, IpPair};
|
||||
|
||||
use super::VERSION;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct IpPacketResponse {
|
||||
pub version: u8,
|
||||
pub data: IpPacketResponseData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum IpPacketResponseData {
|
||||
Data(DataResponse),
|
||||
Control(Box<ControlResponse>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DataResponse {
|
||||
pub ip_packet: bytes::Bytes,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ControlResponse {
|
||||
// Response for a connect request
|
||||
Connect(ConnectResponse),
|
||||
|
||||
// Response for a disconnect initiqated by the client
|
||||
Disconnect(DisconnectResponse),
|
||||
|
||||
// Message from the server that the client got disconnected without the client initiating it
|
||||
UnrequestedDisconnect(UnrequestedDisconnect),
|
||||
|
||||
// Response to ping request
|
||||
Pong(PongResponse),
|
||||
|
||||
// Response for a health request
|
||||
Health(Box<HealthResponse>),
|
||||
|
||||
// Info response. This can be anything from informative messages to errors
|
||||
Info(InfoResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ConnectResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: ConnectResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ConnectResponseReply {
|
||||
Success(ConnectSuccess),
|
||||
Failure(ConnectFailureReason),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ConnectSuccess {
|
||||
pub ips: IpPair,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum ConnectFailureReason {
|
||||
#[error("client is already connected")]
|
||||
ClientAlreadyConnected,
|
||||
|
||||
#[error("no available ip address")]
|
||||
NoAvailableIp,
|
||||
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DisconnectResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: DisconnectResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum DisconnectResponseReply {
|
||||
Success,
|
||||
Failure(DisconnectFailureReason),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum DisconnectFailureReason {
|
||||
#[error("client is not connected")]
|
||||
ClientNotConnected,
|
||||
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct UnrequestedDisconnect {
|
||||
pub reason: UnrequestedDisconnectReason,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum UnrequestedDisconnectReason {
|
||||
#[error("client mixnet traffic timeout")]
|
||||
ClientMixnetTrafficTimeout,
|
||||
|
||||
#[error("client tun traffic timeout")]
|
||||
ClientTunTrafficTimeout,
|
||||
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PongResponse {
|
||||
pub request_id: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct HealthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: HealthResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct HealthResponseReply {
|
||||
// Return the binary build information of the IPR
|
||||
pub build_info: BinaryBuildInformationOwned,
|
||||
|
||||
// Return if the IPR has performed a successful routing test.
|
||||
pub routable: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct InfoResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: InfoResponseReply,
|
||||
pub level: InfoLevel,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum InfoResponseReply {
|
||||
#[error("{msg}")]
|
||||
Generic { msg: String },
|
||||
|
||||
#[error(
|
||||
"version mismatch: response is v{request_version} and response is v{response_version}"
|
||||
)]
|
||||
VersionMismatch {
|
||||
request_version: u8,
|
||||
response_version: u8,
|
||||
},
|
||||
|
||||
#[error("destination failed exit policy filter check: {dst}")]
|
||||
ExitPolicyFilterCheckFailed { dst: String },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum InfoLevel {
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl IpPacketResponse {
|
||||
pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self {
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: IpPacketResponseData::Data(DataResponse { ip_packet }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match &self.data {
|
||||
IpPacketResponseData::Data(_) => None,
|
||||
IpPacketResponseData::Control(response) => response.id(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl IpPacketResponseData {
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ControlResponse {
|
||||
fn id(&self) -> Option<u64> {
|
||||
match self {
|
||||
ControlResponse::Connect(response) => Some(response.request_id),
|
||||
ControlResponse::Disconnect(response) => Some(response.request_id),
|
||||
ControlResponse::UnrequestedDisconnect(_) => None,
|
||||
ControlResponse::Pong(response) => Some(response.request_id),
|
||||
ControlResponse::Health(response) => Some(response.request_id),
|
||||
ControlResponse::Info(response) => Some(response.request_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectResponseReply {
|
||||
pub fn is_success(&self) -> bool {
|
||||
match self {
|
||||
ConnectResponseReply::Success(_) => true,
|
||||
ConnectResponseReply::Failure(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,4 +26,4 @@ utoipa = [ "dep:utoipa" ]
|
||||
|
||||
[build-dependencies]
|
||||
regex = { workspace = true }
|
||||
cargo_metadata = { workspace = true }
|
||||
cargo_metadata = { version = "0.18" }
|
||||
|
||||
@@ -15,10 +15,10 @@ bls12_381 = { workspace = true, features = ["alloc", "pairings", "experimental",
|
||||
bincode.workspace = true
|
||||
cfg-if.workspace = true
|
||||
itertools = { workspace = true }
|
||||
digest = { workspace = true }
|
||||
digest = "0.9"
|
||||
rand = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
sha2 = "0.9"
|
||||
bs58 = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
rayon = { workspace = true, optional = true }
|
||||
@@ -31,7 +31,7 @@ nym-pemstore = { path = "../pemstore" }
|
||||
nym-network-defaults = { path = "../network-defaults", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { workspace = true, features = ["html_reports"] }
|
||||
criterion = { version = "0.5.1", features = ["html_reports"] }
|
||||
|
||||
|
||||
[[bench]]
|
||||
|
||||
@@ -113,13 +113,17 @@ const G1_HASH_DOMAIN: &[u8] = b"NYMECASH-V01-CS02-with-BLS12381G1_XMD:SHA-256_SS
|
||||
const SCALAR_HASH_DOMAIN: &[u8] = b"NYMECASH-V01-CS02-with-expander-SHA256";
|
||||
|
||||
pub fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
|
||||
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve([msg], G1_HASH_DOMAIN)
|
||||
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G1_HASH_DOMAIN)
|
||||
}
|
||||
|
||||
pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
|
||||
let mut output = vec![Scalar::zero()];
|
||||
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>, _>([msg], SCALAR_HASH_DOMAIN, &mut output);
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>>(
|
||||
msg.as_ref(),
|
||||
SCALAR_HASH_DOMAIN,
|
||||
&mut output,
|
||||
);
|
||||
output[0]
|
||||
}
|
||||
|
||||
@@ -397,75 +401,4 @@ mod tests {
|
||||
assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2));
|
||||
assert_ne!(hash_to_scalar(msg1), hash_to_scalar(msg2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_to_scalar() {
|
||||
let msg1 = "foo";
|
||||
let expected1 = Scalar::from_bytes(&[
|
||||
253, 57, 224, 227, 175, 195, 226, 82, 46, 175, 33, 126, 171, 239, 255, 92, 108, 168, 6,
|
||||
79, 90, 11, 235, 236, 221, 10, 85, 133, 42, 81, 95, 30,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let msg2 = "bar";
|
||||
let expected2 = Scalar::from_bytes(&[
|
||||
48, 83, 69, 52, 42, 18, 135, 244, 211, 190, 160, 196, 118, 154, 24, 126, 0, 125, 72,
|
||||
201, 170, 225, 123, 201, 52, 120, 171, 132, 235, 182, 20, 26,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let msg3 = [
|
||||
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
|
||||
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
|
||||
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
|
||||
60, 42, 92, 128, 131, 161, 43,
|
||||
];
|
||||
let expected3 = Scalar::from_bytes(&[
|
||||
128, 189, 8, 43, 186, 55, 52, 61, 171, 196, 159, 177, 162, 100, 27, 143, 85, 83, 218,
|
||||
171, 91, 220, 155, 25, 7, 38, 2, 36, 4, 93, 136, 4,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(hash_to_scalar(msg1), expected1);
|
||||
assert_eq!(hash_to_scalar(msg2), expected2);
|
||||
assert_eq!(hash_to_scalar(msg3), expected3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_to_g1() {
|
||||
let msg1 = "foo";
|
||||
let expected1 = G1Affine::from_compressed(&[
|
||||
161, 109, 186, 0, 192, 221, 83, 87, 71, 31, 120, 201, 185, 35, 62, 239, 46, 120, 117,
|
||||
150, 191, 227, 128, 161, 78, 201, 207, 167, 86, 181, 229, 115, 2, 6, 178, 16, 251, 118,
|
||||
219, 115, 184, 96, 2, 10, 31, 63, 150, 70,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
let msg2 = "bar";
|
||||
let expected2 = G1Affine::from_compressed(&[
|
||||
135, 102, 204, 42, 221, 49, 209, 192, 250, 87, 59, 255, 197, 93, 37, 113, 38, 2, 154,
|
||||
233, 68, 234, 206, 182, 121, 212, 166, 210, 74, 155, 190, 33, 203, 237, 176, 60, 249,
|
||||
241, 53, 170, 18, 168, 49, 35, 1, 151, 205, 174,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
let msg3 = [
|
||||
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
|
||||
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
|
||||
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
|
||||
60, 42, 92, 128, 131, 161, 43,
|
||||
];
|
||||
let expected3 = G1Affine::from_compressed(&[
|
||||
184, 200, 211, 115, 47, 45, 39, 185, 105, 9, 222, 247, 132, 241, 121, 130, 238, 224,
|
||||
155, 109, 105, 201, 137, 154, 132, 149, 214, 233, 136, 69, 77, 132, 174, 30, 46, 123,
|
||||
20, 92, 219, 18, 45, 29, 208, 127, 158, 145, 130, 41,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
assert_eq!(hash_g1(msg1), expected1);
|
||||
assert_eq!(hash_g1(msg2), expected2);
|
||||
assert_eq!(hash_g1(msg3), expected3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
[package]
|
||||
name = "nym-coconut"
|
||||
version = "0.5.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>", "Ania Piotrowska <ania@nymtech.net>"]
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bls12_381 = { workspace = true, default-features = false, features = ["pairings", "alloc", "experimental"] }
|
||||
itertools = { workspace = true }
|
||||
digest = "0.9"
|
||||
rand = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
bs58 = { workspace = true }
|
||||
sha2 = "0.9"
|
||||
zeroize = { workspace = true, optional = true }
|
||||
|
||||
nym-dkg = { path = "../dkg" }
|
||||
nym-pemstore = { path = "../pemstore" }
|
||||
|
||||
[dependencies.ff]
|
||||
workspace = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.group]
|
||||
workspace = true
|
||||
default-features = false
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { workspace = true, features = ["html_reports"] }
|
||||
doc-comment = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
key-zeroize = ["zeroize", "bls12_381/zeroize"]
|
||||
default = []
|
||||
|
||||
|
||||
[target.'cfg(target_env = "wasm32-unknown-unknown")'.dependencies]
|
||||
getrandom = { version="0.2", features=["js"] }
|
||||
@@ -0,0 +1 @@
|
||||
This project was partially funded through the NGI0 PET Fund, a fund established by NL.net with financial support from the European Commission's NGI programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 825310.
|
||||
@@ -0,0 +1,360 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bls12_381::{multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, Scalar};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use ff::Field;
|
||||
use group::{Curve, Group};
|
||||
use nym_coconut::{
|
||||
aggregate_signature_shares_and_verify, aggregate_verification_keys, blind_sign,
|
||||
prepare_blind_sign, prove_bandwidth_credential, random_scalars_refs, setup, ttp_keygen,
|
||||
verify_credential, verify_partial_blind_signature, Attribute, BlindedSignature, Parameters,
|
||||
Signature, SignatureShare, VerificationKey,
|
||||
};
|
||||
use rand::seq::SliceRandom;
|
||||
use std::ops::Neg;
|
||||
use std::time::Duration;
|
||||
|
||||
#[allow(unused)]
|
||||
fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
|
||||
let gt1 = bls12_381::pairing(g11, g21);
|
||||
let gt2 = bls12_381::pairing(g12, g22);
|
||||
assert_eq!(gt1, gt2)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn multi_miller_pairing_affine(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
|
||||
let miller_loop_result = multi_miller_loop(&[
|
||||
(g11, &G2Prepared::from(*g21)),
|
||||
(&g12.neg(), &G2Prepared::from(*g22)),
|
||||
]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn bench_pairings(c: &mut Criterion) {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let g1 = G1Affine::generator();
|
||||
let g2 = G2Affine::generator();
|
||||
let r = Scalar::random(&mut rng);
|
||||
let s = Scalar::random(&mut rng);
|
||||
|
||||
let g11 = (g1 * r).to_affine();
|
||||
let g21 = (g2 * s).to_affine();
|
||||
let g21_prep = G2Prepared::from(g21);
|
||||
|
||||
let g12 = (g1 * s).to_affine();
|
||||
let g22 = (g2 * r).to_affine();
|
||||
let g22_prep = G2Prepared::from(g22);
|
||||
|
||||
c.bench_function("double pairing", |b| {
|
||||
b.iter(|| double_pairing(&g11, &g21, &g12, &g22))
|
||||
});
|
||||
|
||||
c.bench_function("multi miller in affine", |b| {
|
||||
b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22))
|
||||
});
|
||||
|
||||
c.bench_function("multi miller with prepared g2", |b| {
|
||||
b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep))
|
||||
});
|
||||
|
||||
c.bench_function("multi miller with semi-prepared g2", |b| {
|
||||
b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep))
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn multi_miller_pairing_with_prepared(
|
||||
g11: &G1Affine,
|
||||
g21: &G2Prepared,
|
||||
g12: &G1Affine,
|
||||
g22: &G2Prepared,
|
||||
) {
|
||||
let miller_loop_result = multi_miller_loop(&[(g11, g21), (&g12.neg(), g22)]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
// the case of being able to prepare G2 generator
|
||||
#[allow(unused)]
|
||||
fn multi_miller_pairing_with_semi_prepared(
|
||||
g11: &G1Affine,
|
||||
g21: &G2Affine,
|
||||
g12: &G1Affine,
|
||||
g22: &G2Prepared,
|
||||
) {
|
||||
let miller_loop_result =
|
||||
multi_miller_loop(&[(g11, &G2Prepared::from(*g21)), (&g12.neg(), g22)]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn unblind_and_aggregate(
|
||||
params: &Parameters,
|
||||
blinded_signatures: &[BlindedSignature],
|
||||
partial_verification_keys: &[VerificationKey],
|
||||
private_attributes: &[&Attribute],
|
||||
public_attributes: &[&Attribute],
|
||||
commitment_hash: &G1Projective,
|
||||
pedersen_commitments_openings: &[Scalar],
|
||||
verification_key: &VerificationKey,
|
||||
) -> Signature {
|
||||
// Unblind all partial signatures
|
||||
let unblinded_signatures: Vec<Signature> = blinded_signatures
|
||||
.iter()
|
||||
.zip(partial_verification_keys.iter())
|
||||
.map(|(signature, partial_verification_key)| {
|
||||
signature
|
||||
.unblind_and_verify(
|
||||
params,
|
||||
partial_verification_key,
|
||||
private_attributes,
|
||||
public_attributes,
|
||||
commitment_hash,
|
||||
pedersen_commitments_openings,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let unblinded_signature_shares: Vec<SignatureShare> = unblinded_signatures
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64))
|
||||
.collect();
|
||||
|
||||
let mut attributes = vec![];
|
||||
attributes.extend_from_slice(private_attributes);
|
||||
attributes.extend_from_slice(public_attributes);
|
||||
aggregate_signature_shares_and_verify(
|
||||
params,
|
||||
verification_key,
|
||||
&attributes,
|
||||
&unblinded_signature_shares,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
struct BenchCase {
|
||||
num_authorities: u64,
|
||||
threshold_p: f32,
|
||||
num_public_attrs: u32,
|
||||
num_private_attrs: u32,
|
||||
}
|
||||
|
||||
impl BenchCase {
|
||||
fn threshold(&self) -> u64 {
|
||||
(self.num_authorities as f32 * self.threshold_p).round() as u64
|
||||
}
|
||||
|
||||
fn num_attrs(&self) -> u32 {
|
||||
self.num_public_attrs + self.num_private_attrs
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_coconut(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("benchmark-coconut");
|
||||
group.measurement_time(Duration::from_secs(1000));
|
||||
let case = BenchCase {
|
||||
num_authorities: 100,
|
||||
threshold_p: 0.7,
|
||||
num_public_attrs: 2,
|
||||
num_private_attrs: 2,
|
||||
};
|
||||
|
||||
let params = setup(case.num_public_attrs + case.num_private_attrs).unwrap();
|
||||
|
||||
random_scalars_refs!(public_attributes, params, case.num_public_attrs as usize);
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
let private_attributes = vec![&serial_number, &binding_number];
|
||||
|
||||
// The prepare blind sign is performed by the user
|
||||
let (pedersen_commitments_openings, blind_sign_request) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap();
|
||||
|
||||
// CLIENT BENCHMARK: Data needed to ask for a credential
|
||||
// Let's benchmark the operations the client has to perform
|
||||
// to ask for a credential
|
||||
group.bench_function(
|
||||
format!(
|
||||
"[Client] prepare_blind_sign_{}_authorities_{}_attributes_{}_threshold",
|
||||
case.num_authorities,
|
||||
case.num_attrs(),
|
||||
case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap())
|
||||
},
|
||||
);
|
||||
|
||||
// keys for the validators
|
||||
let coconut_keypairs = ttp_keygen(¶ms, case.threshold(), case.num_authorities).unwrap();
|
||||
|
||||
// VALIDATOR BENCHMARK: Issue partial credential
|
||||
// we pick only one key pair, as we want to validate how much does it
|
||||
// take for a single validator to issue a partial credential
|
||||
let mut rng = rand::thread_rng();
|
||||
let keypair = coconut_keypairs.choose(&mut rng).unwrap();
|
||||
|
||||
group.bench_function(
|
||||
format!(
|
||||
"[Validator] compute_single_blind_sign_for_credential_with_{}_attributes",
|
||||
case.num_attrs(),
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
blind_sign(
|
||||
¶ms,
|
||||
keypair.secret_key(),
|
||||
&blind_sign_request,
|
||||
&public_attributes,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// computing all partial credentials
|
||||
// NOTE: in reality, each validator computes only single signature
|
||||
let mut blinded_signatures = Vec::new();
|
||||
for keypair in coconut_keypairs.iter() {
|
||||
let blinded_signature = blind_sign(
|
||||
¶ms,
|
||||
keypair.secret_key(),
|
||||
&blind_sign_request,
|
||||
&public_attributes,
|
||||
)
|
||||
.unwrap();
|
||||
blinded_signatures.push(blinded_signature)
|
||||
}
|
||||
|
||||
let verification_keys: Vec<VerificationKey> = coconut_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key().clone())
|
||||
.collect();
|
||||
|
||||
// verify a random partial blind signature
|
||||
let rand_idx = 1;
|
||||
let random_blind_signature = blinded_signatures.get(rand_idx).unwrap();
|
||||
let partial_verification_key = verification_keys.get(rand_idx).unwrap();
|
||||
|
||||
group.bench_function(
|
||||
format!(
|
||||
"verify_partial_blind_signature_{}_private_attributes_{}_public_attributes",
|
||||
case.num_private_attrs, case.num_public_attrs
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
verify_partial_blind_signature(
|
||||
¶ms,
|
||||
blind_sign_request.get_private_attributes_pedersen_commitments(),
|
||||
&public_attributes,
|
||||
random_blind_signature,
|
||||
partial_verification_key,
|
||||
)
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// Lets bench worse case, ie aggregating all
|
||||
let indices: Vec<u64> = (1..=case.num_authorities).collect();
|
||||
// aggregate verification keys
|
||||
let aggr_verification_key =
|
||||
aggregate_verification_keys(&verification_keys, Some(&indices)).unwrap();
|
||||
|
||||
// CLIENT OPERATION: Unblind partial singatures and aggregate into single signature
|
||||
let aggregated_signature = unblind_and_aggregate(
|
||||
¶ms,
|
||||
&blinded_signatures,
|
||||
&verification_keys,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&blind_sign_request.get_commitment_hash(),
|
||||
&pedersen_commitments_openings,
|
||||
&aggr_verification_key,
|
||||
);
|
||||
|
||||
// CLIENT BENCHMARK: aggregate all partial credentials
|
||||
group.bench_function(
|
||||
format!(
|
||||
"[Client] unblind_and_aggregate_partial_credentials_{}_authorities_{}_attributes_{}_threshold",
|
||||
case.num_authorities,
|
||||
case.num_attrs(),
|
||||
case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
unblind_and_aggregate(
|
||||
¶ms,
|
||||
&blinded_signatures,
|
||||
&verification_keys,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&blind_sign_request.get_commitment_hash(),
|
||||
&pedersen_commitments_openings,
|
||||
&aggr_verification_key)
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// CLIENT OPERATION: Randomize credentials and generate any cryptographic material to verify them
|
||||
let theta = prove_bandwidth_credential(
|
||||
¶ms,
|
||||
&aggr_verification_key,
|
||||
&aggregated_signature,
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// CLIENT BENCHMARK
|
||||
group.bench_function(
|
||||
format!(
|
||||
"[Client] randomize_and_prove_credential_{}_authorities_{}_attributes_{}_threshold",
|
||||
case.num_authorities,
|
||||
case.num_attrs(),
|
||||
case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
prove_bandwidth_credential(
|
||||
¶ms,
|
||||
&aggr_verification_key,
|
||||
&aggregated_signature,
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// VERIFIER OPERATION
|
||||
// Verify credentials
|
||||
verify_credential(¶ms, &aggr_verification_key, &theta, &public_attributes);
|
||||
|
||||
// VERIFICATION BENCHMARK
|
||||
group.bench_function(
|
||||
format!(
|
||||
"[Verifier] verify_credentials_{}_authorities_{}_attributes_{}_threshold",
|
||||
case.num_authorities,
|
||||
case.num_attrs(),
|
||||
case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
verify_credential(¶ms, &aggr_verification_key, &theta, &public_attributes)
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
criterion_group!(benches, bench_coconut);
|
||||
criterion_main!(benches);
|
||||
@@ -0,0 +1,354 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use core::ops::{Deref, Mul};
|
||||
|
||||
use bls12_381::{G1Projective, Scalar};
|
||||
use group::Curve;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::traits::{Base58, Bytable};
|
||||
use crate::utils::{try_deserialize_g1_projective, try_deserialize_scalar};
|
||||
use crate::Attribute;
|
||||
|
||||
/// Type alias for the ephemeral key generated during ElGamal encryption
|
||||
pub type EphemeralKey = Scalar;
|
||||
|
||||
/// Two G1 points representing ElGamal ciphertext
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct Ciphertext(pub(crate) G1Projective, pub(crate) G1Projective);
|
||||
|
||||
impl TryFrom<&[u8]> for Ciphertext {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Ciphertext> {
|
||||
if bytes.len() != 96 {
|
||||
return Err(CoconutError::Deserialization(format!(
|
||||
"Ciphertext must be exactly 96 bytes, got {}",
|
||||
bytes.len()
|
||||
)));
|
||||
}
|
||||
|
||||
// safety: we just checked for the length so the unwraps are fine
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let c1_bytes: &[u8; 48] = &bytes[..48].try_into().unwrap();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let c2_bytes: &[u8; 48] = &bytes[48..].try_into().unwrap();
|
||||
|
||||
let c1 = try_deserialize_g1_projective(
|
||||
c1_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize compressed c1".to_string()),
|
||||
)?;
|
||||
let c2 = try_deserialize_g1_projective(
|
||||
c2_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize compressed c2".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(Ciphertext(c1, c2))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ciphertext {
|
||||
pub fn c1(&self) -> &G1Projective {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn c2(&self) -> &G1Projective {
|
||||
&self.1
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> [u8; 96] {
|
||||
let mut bytes = [0u8; 96];
|
||||
bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed());
|
||||
bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed());
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Ciphertext> {
|
||||
Ciphertext::try_from(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// PrivateKey used in the ElGamal encryption scheme to recover the plaintext
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct PrivateKey(pub(crate) Scalar);
|
||||
|
||||
impl PrivateKey {
|
||||
/// Decrypt takes the ElGamal encryption of a message and returns a point on the G1 curve
|
||||
/// that represents original h^m.
|
||||
pub fn decrypt(&self, ciphertext: &Ciphertext) -> G1Projective {
|
||||
let (c1, c2) = &(ciphertext.0, ciphertext.1);
|
||||
|
||||
// (gamma^k * h^m) / (g1^{d * k}) | note: gamma = g1^d
|
||||
c2 - c1 * self.0
|
||||
}
|
||||
|
||||
pub fn public_key(&self, params: &Parameters) -> PublicKey {
|
||||
PublicKey(params.gen1() * self.0)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> [u8; 32] {
|
||||
self.0.to_bytes()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8; 32]) -> Result<PrivateKey> {
|
||||
try_deserialize_scalar(
|
||||
bytes,
|
||||
CoconutError::Deserialization(
|
||||
"Failed to deserialize ElGamal private key - it was not in the canonical form"
|
||||
.to_string(),
|
||||
),
|
||||
)
|
||||
.map(PrivateKey)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bytable for PrivateKey {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
self.to_bytes().to_vec()
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||
let received = slice.len();
|
||||
let Ok(arr) = slice.try_into() else {
|
||||
return Err(CoconutError::UnexpectedArrayLength {
|
||||
typ: "elgamal::PrivateKey".to_string(),
|
||||
received,
|
||||
expected: 32,
|
||||
});
|
||||
};
|
||||
|
||||
PrivateKey::from_bytes(arr)
|
||||
}
|
||||
}
|
||||
|
||||
impl Base58 for PrivateKey {}
|
||||
|
||||
// TODO: perhaps be more explicit and apart from gamma also store generator and group order?
|
||||
/// PublicKey used in the ElGamal encryption scheme to produce the ciphertext
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct PublicKey(G1Projective);
|
||||
|
||||
impl PublicKey {
|
||||
/// Encrypt encrypts the given message in the form of h^m,
|
||||
/// where h is a point on the G1 curve using the given public key.
|
||||
/// The random k is returned alongside the encryption
|
||||
/// as it is required by the Coconut Scheme to create proofs of knowledge.
|
||||
pub fn encrypt(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
h: &G1Projective,
|
||||
msg: &Scalar,
|
||||
) -> (Ciphertext, EphemeralKey) {
|
||||
let k = params.random_scalar();
|
||||
// c1 = g1^k
|
||||
let c1 = params.gen1() * k;
|
||||
// c2 = gamma^k * h^m
|
||||
let c2 = self.0 * k + h * msg;
|
||||
|
||||
(Ciphertext(c1, c2), k)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> [u8; 48] {
|
||||
self.0.to_affine().to_compressed()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8; 48]) -> Result<PublicKey> {
|
||||
try_deserialize_g1_projective(
|
||||
bytes,
|
||||
CoconutError::Deserialization(
|
||||
"Failed to deserialize compressed ElGamal public key".to_string(),
|
||||
),
|
||||
)
|
||||
.map(PublicKey)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bytable for PublicKey {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
self.to_bytes().into()
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||
let received = slice.len();
|
||||
let Ok(arr) = slice.try_into() else {
|
||||
return Err(CoconutError::UnexpectedArrayLength {
|
||||
typ: "elgamal::PublicKey".to_string(),
|
||||
received,
|
||||
expected: 48,
|
||||
});
|
||||
};
|
||||
|
||||
PublicKey::from_bytes(arr)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for PublicKey {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(slice: &[u8]) -> Result<PublicKey> {
|
||||
PublicKey::try_from_byte_slice(slice)
|
||||
}
|
||||
}
|
||||
|
||||
impl Base58 for PublicKey {}
|
||||
|
||||
impl Deref for PublicKey {
|
||||
type Target = G1Projective;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mul<&'a Scalar> for &PublicKey {
|
||||
type Output = G1Projective;
|
||||
|
||||
fn mul(self, rhs: &'a Scalar) -> Self::Output {
|
||||
self.0 * rhs
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
/// A convenient wrapper for both keys of the ElGamal keypair
|
||||
pub struct ElGamalKeyPair {
|
||||
private_key: PrivateKey,
|
||||
public_key: PublicKey,
|
||||
}
|
||||
|
||||
impl ElGamalKeyPair {
|
||||
pub fn public_key(&self) -> &PublicKey {
|
||||
&self.public_key
|
||||
}
|
||||
|
||||
pub fn private_key(&self) -> &PrivateKey {
|
||||
&self.private_key
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a fresh ElGamal keypair using the group generator specified by the provided [Parameters]
|
||||
pub fn elgamal_keygen(params: &Parameters) -> ElGamalKeyPair {
|
||||
let private_key = params.random_scalar();
|
||||
let gamma = params.gen1() * private_key;
|
||||
|
||||
ElGamalKeyPair {
|
||||
private_key: PrivateKey(private_key),
|
||||
public_key: PublicKey(gamma),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_attribute_encryption(
|
||||
params: &Parameters,
|
||||
private_attributes: &[&Attribute],
|
||||
pub_key: &PublicKey,
|
||||
commitment_hash: G1Projective,
|
||||
) -> (Vec<Ciphertext>, Vec<EphemeralKey>) {
|
||||
private_attributes
|
||||
.iter()
|
||||
.map(|m| pub_key.encrypt(params, &commitment_hash, m))
|
||||
.unzip()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn keygen() {
|
||||
let params = Parameters::default();
|
||||
let keypair = super::elgamal_keygen(¶ms);
|
||||
|
||||
let expected = params.gen1() * keypair.private_key.0;
|
||||
let gamma = keypair.public_key.0;
|
||||
assert_eq!(
|
||||
expected, gamma,
|
||||
"Public key, gamma, should be equal to g1^d, where d is the private key"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encryption() {
|
||||
let params = Parameters::default();
|
||||
let keypair = super::elgamal_keygen(¶ms);
|
||||
|
||||
let r = params.random_scalar();
|
||||
let h = params.gen1() * r;
|
||||
let m = params.random_scalar();
|
||||
|
||||
let (ciphertext, ephemeral_key) = keypair.public_key.encrypt(¶ms, &h, &m);
|
||||
|
||||
let expected_c1 = params.gen1() * ephemeral_key;
|
||||
assert_eq!(expected_c1, ciphertext.0, "c1 should be equal to g1^k");
|
||||
|
||||
let expected_c2 = keypair.public_key.0 * ephemeral_key + h * m;
|
||||
assert_eq!(
|
||||
expected_c2, ciphertext.1,
|
||||
"c2 should be equal to gamma^k * h^m"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decryption() {
|
||||
let params = Parameters::default();
|
||||
let keypair = super::elgamal_keygen(¶ms);
|
||||
|
||||
let r = params.random_scalar();
|
||||
let h = params.gen1() * r;
|
||||
let m = params.random_scalar();
|
||||
|
||||
let (ciphertext, _) = keypair.public_key.encrypt(¶ms, &h, &m);
|
||||
let dec = keypair.private_key.decrypt(&ciphertext);
|
||||
|
||||
let expected = h * m;
|
||||
assert_eq!(
|
||||
expected, dec,
|
||||
"after ElGamal decryption, original h^m should be obtained"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_key_bytes_roundtrip() {
|
||||
let params = Parameters::default();
|
||||
let private_key = PrivateKey(params.random_scalar());
|
||||
let bytes = private_key.to_bytes();
|
||||
|
||||
// also make sure it is equivalent to the internal scalar's bytes
|
||||
assert_eq!(private_key.0.to_bytes(), bytes);
|
||||
assert_eq!(private_key, PrivateKey::from_bytes(&bytes).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_key_bytes_roundtrip() {
|
||||
let params = Parameters::default();
|
||||
let r = params.random_scalar();
|
||||
let public_key = PublicKey(params.gen1() * r);
|
||||
let bytes = public_key.to_bytes();
|
||||
|
||||
// also make sure it is equivalent to the internal g1 compressed bytes
|
||||
assert_eq!(public_key.0.to_affine().to_compressed(), bytes);
|
||||
assert_eq!(public_key, PublicKey::from_bytes(&bytes).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ciphertext_bytes_roundtrip() {
|
||||
let params = Parameters::default();
|
||||
let r = params.random_scalar();
|
||||
let s = params.random_scalar();
|
||||
let ciphertext = Ciphertext(params.gen1() * r, params.gen1() * s);
|
||||
let bytes = ciphertext.to_bytes();
|
||||
|
||||
// also make sure it is equivalent to the internal g1 compressed bytes concatenated
|
||||
let expected_bytes = [
|
||||
ciphertext.0.to_affine().to_compressed(),
|
||||
ciphertext.1.to_affine().to_compressed(),
|
||||
]
|
||||
.concat();
|
||||
assert_eq!(expected_bytes, bytes);
|
||||
assert_eq!(ciphertext, Ciphertext::try_from(&bytes[..]).unwrap())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// A `Result` alias where the `Err` case is `coconut_rs::Error`.
|
||||
pub type Result<T> = std::result::Result<T, CoconutError>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CoconutError {
|
||||
#[error("Setup error: {0}")]
|
||||
Setup(String),
|
||||
|
||||
#[error("encountered error during keygen")]
|
||||
Keygen,
|
||||
|
||||
#[error("Issuance related error: {0}")]
|
||||
Issuance(String),
|
||||
|
||||
#[error("Tried to prepare blind sign request for higher than specified number of attributes (max: {}, requested: {})", max, requested)]
|
||||
IssuanceMaxAttributes { max: usize, requested: usize },
|
||||
|
||||
#[error("Interpolation error: {0}")]
|
||||
Interpolation(String),
|
||||
|
||||
#[error("Aggregation error: {0}")]
|
||||
Aggregation(String),
|
||||
|
||||
#[error("Unblind error: {0}")]
|
||||
Unblind(String),
|
||||
|
||||
#[error("Verification error: {0}")]
|
||||
Verification(String),
|
||||
|
||||
#[error("Deserialization error: {0}")]
|
||||
Deserialization(String),
|
||||
|
||||
#[error(
|
||||
"Deserailization error, expected at least {} bytes, got {}",
|
||||
min,
|
||||
actual
|
||||
)]
|
||||
DeserializationMinLength { min: usize, actual: usize },
|
||||
|
||||
#[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {object} or {modulus_target} % {modulus} == 0")]
|
||||
DeserializationInvalidLength {
|
||||
actual: usize,
|
||||
target: usize,
|
||||
modulus_target: usize,
|
||||
modulus: usize,
|
||||
object: String,
|
||||
},
|
||||
|
||||
#[error("received an array of unexpected size for deserialization of {typ}. got {received} but expected {expected}")]
|
||||
UnexpectedArrayLength {
|
||||
typ: String,
|
||||
received: usize,
|
||||
expected: usize,
|
||||
},
|
||||
|
||||
#[error("failed to decode the base58 representation: {0}")]
|
||||
Base58DecodingFailure(#[from] bs58::decode::Error),
|
||||
|
||||
#[error("failed to deserialize scalar from the received bytes - it might not have been canonically encoded")]
|
||||
ScalarDeserializationFailure,
|
||||
|
||||
#[error("failed to deserialize G1Projective point from the received bytes - it might not have been canonically encoded")]
|
||||
G1ProjectiveDeserializationFailure,
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
use crate::{BlindSignRequest, BlindedSignature, Bytable, VerifyCredentialRequest};
|
||||
|
||||
macro_rules! impl_clone {
|
||||
($struct:ident) => {
|
||||
impl Clone for $struct {
|
||||
fn clone(&self) -> Self {
|
||||
Self::try_from_byte_slice(&self.to_byte_vec()).unwrap()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_clone!(BlindSignRequest);
|
||||
impl_clone!(BlindedSignature);
|
||||
impl_clone!(VerifyCredentialRequest);
|
||||
@@ -0,0 +1,2 @@
|
||||
mod clone;
|
||||
mod serde;
|
||||
@@ -0,0 +1,57 @@
|
||||
use crate::elgamal::PrivateKey;
|
||||
use crate::scheme::SecretKey;
|
||||
use crate::{
|
||||
Base58, BlindSignRequest, BlindedSignature, PublicKey, Signature, VerificationKey,
|
||||
VerifyCredentialRequest,
|
||||
};
|
||||
use serde::de::Unexpected;
|
||||
use serde::{de::Error, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt;
|
||||
|
||||
macro_rules! impl_serde {
|
||||
($struct:ident, $visitor:ident) => {
|
||||
pub struct $visitor {}
|
||||
|
||||
impl Serialize for $struct {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_bs58())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Visitor<'de> for $visitor {
|
||||
type Value = $struct;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(formatter, "A base58 encoded struct")
|
||||
}
|
||||
|
||||
fn visit_str<E: Error>(self, s: &str) -> Result<Self::Value, E> {
|
||||
match $struct::try_from_bs58(s) {
|
||||
Ok(x) => Ok(x),
|
||||
Err(_) => Err(Error::invalid_value(Unexpected::Str(s), &self)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for $struct {
|
||||
fn deserialize<D>(deserializer: D) -> Result<$struct, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str($visitor {})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_serde!(SecretKey, V1);
|
||||
impl_serde!(VerificationKey, V2);
|
||||
impl_serde!(PublicKey, V3);
|
||||
impl_serde!(PrivateKey, V4);
|
||||
impl_serde!(BlindSignRequest, V5);
|
||||
impl_serde!(BlindedSignature, V6);
|
||||
impl_serde!(Signature, V7);
|
||||
impl_serde!(VerifyCredentialRequest, V8);
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
pub use bls12_381::Scalar;
|
||||
pub use elgamal::elgamal_keygen;
|
||||
pub use elgamal::ElGamalKeyPair;
|
||||
pub use elgamal::PublicKey;
|
||||
pub use error::CoconutError;
|
||||
pub use scheme::aggregation::aggregate_key_shares;
|
||||
pub use scheme::aggregation::aggregate_signature_shares;
|
||||
pub use scheme::aggregation::aggregate_signature_shares_and_verify;
|
||||
pub use scheme::aggregation::aggregate_verification_keys;
|
||||
pub use scheme::issuance::blind_sign;
|
||||
pub use scheme::issuance::prepare_blind_sign;
|
||||
pub use scheme::issuance::sign;
|
||||
pub use scheme::issuance::verify_partial_blind_signature;
|
||||
pub use scheme::issuance::BlindSignRequest;
|
||||
pub use scheme::keygen::keygen;
|
||||
pub use scheme::keygen::ttp_keygen;
|
||||
pub use scheme::keygen::KeyPair;
|
||||
pub use scheme::keygen::SecretKey;
|
||||
pub use scheme::keygen::VerificationKey;
|
||||
pub use scheme::keygen::VerificationKeyShare;
|
||||
pub use scheme::setup::setup;
|
||||
pub use scheme::setup::Parameters;
|
||||
pub use scheme::verification::check_vk_pairing;
|
||||
pub use scheme::verification::prove_bandwidth_credential;
|
||||
pub use scheme::verification::verify;
|
||||
pub use scheme::verification::verify_credential;
|
||||
pub use scheme::verification::BlindedSerialNumber;
|
||||
pub use scheme::verification::VerifyCredentialRequest;
|
||||
pub use scheme::BlindedSignature;
|
||||
pub use scheme::Signature;
|
||||
pub use scheme::SignatureShare;
|
||||
pub use scheme::SignerIndex;
|
||||
pub use traits::Base58;
|
||||
pub use traits::Bytable;
|
||||
pub use utils::hash_to_scalar;
|
||||
|
||||
pub mod elgamal;
|
||||
mod error;
|
||||
mod impls;
|
||||
mod proofs;
|
||||
mod scheme;
|
||||
pub mod tests;
|
||||
mod traits;
|
||||
pub mod utils;
|
||||
|
||||
pub type Attribute = bls12_381::Scalar;
|
||||
pub type PrivateAttribute = Attribute;
|
||||
pub type PublicAttribute = Attribute;
|
||||
|
||||
pub use bls12_381::G1Projective;
|
||||
@@ -0,0 +1,619 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TODO: look at https://crates.io/crates/merlin to perhaps use it instead?
|
||||
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use bls12_381::{G1Projective, G2Projective, Scalar};
|
||||
use digest::generic_array::typenum::Unsigned;
|
||||
use digest::Digest;
|
||||
use group::GroupEncoding;
|
||||
use itertools::izip;
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::scheme::issuance::compute_hash;
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::scheme::VerificationKey;
|
||||
use crate::utils::{try_deserialize_scalar, try_deserialize_scalar_vec};
|
||||
use crate::Attribute;
|
||||
|
||||
// as per the reference python implementation
|
||||
type ChallengeDigest = Sha256;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct ProofCmCs {
|
||||
challenge: Scalar,
|
||||
response_opening: Scalar,
|
||||
response_openings: Vec<Scalar>,
|
||||
response_attributes: Vec<Scalar>,
|
||||
}
|
||||
|
||||
// note: this is slightly different from the reference python implementation
|
||||
// as we omit the unnecessary string conversion. Instead we concatenate byte
|
||||
// representations together and hash that.
|
||||
// note2: G1 and G2 elements are using their compressed representations
|
||||
// and as per the bls12-381 library all elements are using big-endian form
|
||||
/// Generates a Scalar [or Fp] challenge by hashing a number of elliptic curve points.
|
||||
fn compute_challenge<D, I, B>(iter: I) -> Scalar
|
||||
where
|
||||
D: Digest,
|
||||
I: Iterator<Item = B>,
|
||||
B: AsRef<[u8]>,
|
||||
{
|
||||
let mut h = D::new();
|
||||
for point_representation in iter {
|
||||
h.update(point_representation);
|
||||
}
|
||||
let digest = h.finalize();
|
||||
|
||||
// TODO: I don't like the 0 padding here (though it's what we've been using before,
|
||||
// but we never had a security audit anyway...)
|
||||
// instead we could maybe use the `from_bytes` variant and adding some suffix
|
||||
// when computing the digest until we produce a valid scalar.
|
||||
let mut bytes = [0u8; 64];
|
||||
let pad_size = 64usize
|
||||
.checked_sub(D::OutputSize::to_usize())
|
||||
.unwrap_or_default();
|
||||
|
||||
bytes[pad_size..].copy_from_slice(&digest);
|
||||
|
||||
Scalar::from_bytes_wide(&bytes)
|
||||
}
|
||||
|
||||
fn produce_response(witness: &Scalar, challenge: &Scalar, secret: &Scalar) -> Scalar {
|
||||
witness - challenge * secret
|
||||
}
|
||||
|
||||
// note: it's caller's responsibility to ensure witnesses.len() = secrets.len()
|
||||
fn produce_responses<S>(witnesses: &[Scalar], challenge: &Scalar, secrets: &[S]) -> Vec<Scalar>
|
||||
where
|
||||
S: Borrow<Scalar>,
|
||||
{
|
||||
debug_assert_eq!(witnesses.len(), secrets.len());
|
||||
|
||||
witnesses
|
||||
.iter()
|
||||
.zip(secrets.iter())
|
||||
.map(|(w, x)| produce_response(w, challenge, x.borrow()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl ProofCmCs {
|
||||
/// Construct non-interactive zero-knowledge proof of correctness of the ciphertexts and the commitment
|
||||
/// using the Fiat-Shamir heuristic.
|
||||
pub(crate) fn construct(
|
||||
params: &Parameters,
|
||||
commitment: &G1Projective,
|
||||
commitment_opening: &Scalar,
|
||||
commitments: &[G1Projective],
|
||||
pedersen_commitments_openings: &[Scalar],
|
||||
private_attributes: &[&Attribute],
|
||||
public_attributes: &[&Attribute],
|
||||
) -> Self {
|
||||
// note: this is only called from `prepare_blind_sign` that already checks
|
||||
// whether private attributes are non-empty and whether we don't have too many
|
||||
// attributes in total to sign.
|
||||
// we also know, due to the single call place, that ephemeral_keys.len() == private_attributes.len()
|
||||
|
||||
// witness creation
|
||||
let witness_commitment_opening = params.random_scalar();
|
||||
let witness_pedersen_commitments_openings =
|
||||
params.n_random_scalars(pedersen_commitments_openings.len());
|
||||
let witness_attributes = params.n_random_scalars(private_attributes.len());
|
||||
|
||||
// recompute h
|
||||
let h = compute_hash(*commitment, public_attributes);
|
||||
let hs_bytes = params
|
||||
.gen_hs()
|
||||
.iter()
|
||||
.map(|h| h.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let g1 = params.gen1();
|
||||
|
||||
// compute commitments
|
||||
|
||||
// zkp commitment for the attributes commitment cm
|
||||
// Ccm = (wr * g1) + (wm[0] * hs[0]) + ... + (wm[i] * hs[i])
|
||||
let commitment_attributes = g1 * witness_commitment_opening
|
||||
+ witness_attributes
|
||||
.iter()
|
||||
.zip(params.gen_hs().iter())
|
||||
.map(|(wm_i, hs_i)| hs_i * wm_i)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
// zkp commitments for the individual attributes
|
||||
let commitments_attributes = witness_pedersen_commitments_openings
|
||||
.iter()
|
||||
.zip(witness_attributes.iter())
|
||||
.map(|(o_j, m_j)| g1 * o_j + h * m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let commitments_bytes = commitments
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let commitments_attributes_bytes = commitments_attributes
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// compute challenge
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(params.gen1().to_bytes().as_ref())
|
||||
.chain(hs_bytes.iter().map(|hs| hs.as_ref()))
|
||||
.chain(std::iter::once(h.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(commitment.to_bytes().as_ref()))
|
||||
.chain(commitments_bytes.iter().map(|cm| cm.as_ref()))
|
||||
.chain(std::iter::once(commitment_attributes.to_bytes().as_ref()))
|
||||
.chain(commitments_attributes_bytes.iter().map(|cm| cm.as_ref())),
|
||||
);
|
||||
|
||||
// Responses
|
||||
let response_opening =
|
||||
produce_response(&witness_commitment_opening, &challenge, commitment_opening);
|
||||
let response_openings = produce_responses(
|
||||
&witness_pedersen_commitments_openings,
|
||||
&challenge,
|
||||
&pedersen_commitments_openings.iter().collect::<Vec<_>>(),
|
||||
);
|
||||
let response_attributes =
|
||||
produce_responses(&witness_attributes, &challenge, private_attributes);
|
||||
|
||||
ProofCmCs {
|
||||
challenge,
|
||||
response_opening,
|
||||
response_openings,
|
||||
response_attributes,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn verify(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
commitment: &G1Projective,
|
||||
commitments: &[G1Projective],
|
||||
public_attributes: &[&Attribute],
|
||||
) -> bool {
|
||||
if self.response_attributes.len() != commitments.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// recompute h
|
||||
let h = compute_hash(*commitment, public_attributes);
|
||||
let g1 = params.gen1();
|
||||
|
||||
let hs_bytes = params
|
||||
.gen_hs()
|
||||
.iter()
|
||||
.map(|h| h.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// recompute witnesses commitments
|
||||
// Cw = (cm * c) + (rr * g1) + (rm[0] * hs[0]) + ... + (rm[n] * hs[n])
|
||||
let commitment_attributes = (commitment
|
||||
- public_attributes
|
||||
.iter()
|
||||
.zip(params.gen_hs().iter().skip(self.response_attributes.len()))
|
||||
.map(|(&pub_attr, hs)| hs * pub_attr)
|
||||
.sum::<G1Projective>())
|
||||
* self.challenge
|
||||
+ g1 * self.response_opening
|
||||
+ self
|
||||
.response_attributes
|
||||
.iter()
|
||||
.zip(params.gen_hs().iter())
|
||||
.map(|(res_attr, hs)| hs * res_attr)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
let commitments_attributes = izip!(
|
||||
commitments.iter(),
|
||||
self.response_openings.iter(),
|
||||
self.response_attributes.iter()
|
||||
)
|
||||
.map(|(cm_j, r_o_j, r_m_j)| cm_j * self.challenge + g1 * r_o_j + h * r_m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let commitments_bytes = commitments
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let commitments_attributes_bytes = commitments_attributes
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// re-compute the challenge
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(params.gen1().to_bytes().as_ref())
|
||||
.chain(hs_bytes.iter().map(|hs| hs.as_ref()))
|
||||
.chain(std::iter::once(h.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(commitment.to_bytes().as_ref()))
|
||||
.chain(commitments_bytes.iter().map(|cm| cm.as_ref()))
|
||||
.chain(std::iter::once(commitment_attributes.to_bytes().as_ref()))
|
||||
.chain(commitments_attributes_bytes.iter().map(|cm| cm.as_ref())),
|
||||
);
|
||||
|
||||
challenge == self.challenge
|
||||
}
|
||||
|
||||
// challenge || response opening || openings len || response openings || attributes len ||
|
||||
// response attributes
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let openings_len = self.response_openings.len() as u64;
|
||||
let attributes_len = self.response_attributes.len() as u64;
|
||||
|
||||
let mut bytes = Vec::with_capacity(16 + (2 + openings_len + attributes_len) as usize * 32);
|
||||
|
||||
bytes.extend_from_slice(&self.challenge.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_opening.to_bytes());
|
||||
|
||||
bytes.extend_from_slice(&openings_len.to_le_bytes());
|
||||
for ro in &self.response_openings {
|
||||
bytes.extend_from_slice(&ro.to_bytes());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&attributes_len.to_le_bytes());
|
||||
for rm in &self.response_attributes {
|
||||
bytes.extend_from_slice(&rm.to_bytes());
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
// at the very minimum there must be a single attribute being proven
|
||||
if bytes.len() < 32 * 4 + 16 || (bytes.len() - 16) % 32 != 0 {
|
||||
return Err(CoconutError::Deserialization(
|
||||
"tried to deserialize proof of commitments with bytes of invalid length"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut idx = 0;
|
||||
// safety: bound checked + constant offset
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let challenge_bytes = bytes[idx..idx + 32].try_into().unwrap();
|
||||
idx += 32;
|
||||
// safety: bound checked + constant offset
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let response_opening_bytes = bytes[idx..idx + 32].try_into().unwrap();
|
||||
idx += 32;
|
||||
|
||||
let challenge = try_deserialize_scalar(
|
||||
&challenge_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize challenge".to_string()),
|
||||
)?;
|
||||
|
||||
let response_opening = try_deserialize_scalar(
|
||||
&response_opening_bytes,
|
||||
CoconutError::Deserialization(
|
||||
"Failed to deserialize the response to the random".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
// safety: bound checked + constant offset
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let ro_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
|
||||
idx += 8;
|
||||
if bytes[idx..].len() < ro_len as usize * 32 + 8 {
|
||||
return Err(
|
||||
CoconutError::Deserialization(
|
||||
"tried to deserialize proof of ciphertexts and commitment with insufficient number of bytes provided".to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
let ro_end = idx + ro_len as usize * 32;
|
||||
let response_openings = try_deserialize_scalar_vec(
|
||||
ro_len,
|
||||
&bytes[idx..ro_end],
|
||||
CoconutError::Deserialization("Failed to deserialize openings response".to_string()),
|
||||
)?;
|
||||
|
||||
// safety: bound checked + constant offset
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let rm_len = u64::from_le_bytes(bytes[ro_end..ro_end + 8].try_into().unwrap());
|
||||
let response_attributes = try_deserialize_scalar_vec(
|
||||
rm_len,
|
||||
&bytes[ro_end + 8..],
|
||||
CoconutError::Deserialization("Failed to deserialize attributes response".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(ProofCmCs {
|
||||
challenge,
|
||||
response_opening,
|
||||
response_openings,
|
||||
response_attributes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ProofKappaZeta {
|
||||
// c
|
||||
challenge: Scalar,
|
||||
|
||||
// responses
|
||||
response_serial_number: Scalar,
|
||||
response_binding_number: Scalar,
|
||||
response_blinder: Scalar,
|
||||
}
|
||||
|
||||
impl ProofKappaZeta {
|
||||
pub(crate) fn construct(
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKey,
|
||||
serial_number: &Attribute,
|
||||
binding_number: &Attribute,
|
||||
blinding_factor: &Scalar,
|
||||
blinded_message: &G2Projective,
|
||||
blinded_serial_number: &G2Projective,
|
||||
) -> Self {
|
||||
// create the witnesses
|
||||
let witness_blinder = params.random_scalar();
|
||||
let witness_serial_number = params.random_scalar();
|
||||
let witness_binding_number = params.random_scalar();
|
||||
let witness_attributes = [witness_serial_number, witness_binding_number];
|
||||
|
||||
let beta_bytes = verification_key
|
||||
.beta_g2
|
||||
.iter()
|
||||
.map(|beta_i| beta_i.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// witnesses commitments
|
||||
// Aw = g2 * wt + alpha + beta[0] * wm[0] + ... + beta[i] * wm[i]
|
||||
let commitment_kappa = params.gen2() * witness_blinder
|
||||
+ verification_key.alpha
|
||||
+ witness_attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(wm_i, beta_i)| beta_i * wm_i)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
// zeta is the public value associated with the serial number
|
||||
let commitment_zeta = params.gen2() * witness_serial_number;
|
||||
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(params.gen2().to_bytes().as_ref())
|
||||
.chain(std::iter::once(blinded_message.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(blinded_serial_number.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(verification_key.alpha.to_bytes().as_ref()))
|
||||
.chain(beta_bytes.iter().map(|b| b.as_ref()))
|
||||
.chain(std::iter::once(commitment_kappa.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(commitment_zeta.to_bytes().as_ref())),
|
||||
);
|
||||
|
||||
// responses
|
||||
let response_blinder = produce_response(&witness_blinder, &challenge, blinding_factor);
|
||||
let response_serial_number =
|
||||
produce_response(&witness_serial_number, &challenge, serial_number);
|
||||
let response_binding_number =
|
||||
produce_response(&witness_binding_number, &challenge, binding_number);
|
||||
|
||||
ProofKappaZeta {
|
||||
challenge,
|
||||
response_serial_number,
|
||||
response_binding_number,
|
||||
response_blinder,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn private_attributes_len(&self) -> usize {
|
||||
2
|
||||
}
|
||||
|
||||
pub(crate) fn verify(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKey,
|
||||
kappa: &G2Projective,
|
||||
zeta: &G2Projective,
|
||||
) -> bool {
|
||||
let beta_bytes = verification_key
|
||||
.beta_g2
|
||||
.iter()
|
||||
.map(|beta_i| beta_i.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let response_attributes = [self.response_serial_number, self.response_binding_number];
|
||||
// re-compute witnesses commitments
|
||||
// Aw = (c * kappa) + (rt * g2) + ((1 - c) * alpha) + (rm[0] * beta[0]) + ... + (rm[i] * beta[i])
|
||||
let commitment_kappa = kappa * self.challenge
|
||||
+ params.gen2() * self.response_blinder
|
||||
+ verification_key.alpha * (Scalar::one() - self.challenge)
|
||||
+ response_attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
// zeta is the public value associated with the serial number
|
||||
let commitment_zeta = zeta * self.challenge + params.gen2() * self.response_serial_number;
|
||||
|
||||
// compute the challenge
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(params.gen2().to_bytes().as_ref())
|
||||
.chain(std::iter::once(kappa.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zeta.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(verification_key.alpha.to_bytes().as_ref()))
|
||||
.chain(beta_bytes.iter().map(|b| b.as_ref()))
|
||||
.chain(std::iter::once(commitment_kappa.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(commitment_zeta.to_bytes().as_ref())),
|
||||
);
|
||||
|
||||
challenge == self.challenge
|
||||
}
|
||||
|
||||
// challenge || response serial number || response binding number || repose blinder
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let attributes_len = 2; // because we have serial number and the binding number
|
||||
let mut bytes = Vec::with_capacity((1 + attributes_len + 1) as usize * 32);
|
||||
|
||||
bytes.extend_from_slice(&self.challenge.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_serial_number.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_binding_number.to_bytes());
|
||||
|
||||
bytes.extend_from_slice(&self.response_blinder.to_bytes());
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
// at the very minimum there must be a single attribute being proven
|
||||
if bytes.len() != 128 {
|
||||
return Err(CoconutError::DeserializationInvalidLength {
|
||||
actual: bytes.len(),
|
||||
modulus_target: bytes.len(),
|
||||
modulus: 32,
|
||||
object: "kappa and zeta".to_string(),
|
||||
target: 32 * 4,
|
||||
});
|
||||
}
|
||||
|
||||
// safety: bound checked + constant offset
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let challenge_bytes = bytes[..32].try_into().unwrap();
|
||||
let challenge = try_deserialize_scalar(
|
||||
&challenge_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize challenge".to_string()),
|
||||
)?;
|
||||
|
||||
// safety: bound checked + constant offset
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let serial_number_bytes = &bytes[32..64].try_into().unwrap();
|
||||
let response_serial_number = try_deserialize_scalar(
|
||||
serial_number_bytes,
|
||||
CoconutError::Deserialization("failed to deserialize the serial number".to_string()),
|
||||
)?;
|
||||
|
||||
// safety: bound checked + constant offset
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let binding_number_bytes = &bytes[64..96].try_into().unwrap();
|
||||
let response_binding_number = try_deserialize_scalar(
|
||||
binding_number_bytes,
|
||||
CoconutError::Deserialization("failed to deserialize the binding number".to_string()),
|
||||
)?;
|
||||
|
||||
// safety: bound checked + constant offset
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let blinder_bytes = bytes[96..].try_into().unwrap();
|
||||
let response_blinder = try_deserialize_scalar(
|
||||
&blinder_bytes,
|
||||
CoconutError::Deserialization("failed to deserialize the blinder".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(ProofKappaZeta {
|
||||
challenge,
|
||||
response_serial_number,
|
||||
response_binding_number,
|
||||
response_blinder,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// proof builder:
|
||||
// - commitment
|
||||
// - challenge
|
||||
// - responses
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::scheme::keygen::keygen;
|
||||
use crate::scheme::setup::setup;
|
||||
use crate::scheme::verification::{compute_kappa, compute_zeta};
|
||||
use crate::tests::helpers::random_scalars_refs;
|
||||
use group::Group;
|
||||
use rand::thread_rng;
|
||||
|
||||
#[test]
|
||||
fn proof_cm_cs_bytes_roundtrip() {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let params = setup(1).unwrap();
|
||||
let cm = G1Projective::random(&mut rng);
|
||||
let r = params.random_scalar();
|
||||
let cms: [G1Projective; 1] = [G1Projective::random(&mut rng)];
|
||||
let rs = params.n_random_scalars(1);
|
||||
random_scalars_refs!(private_attributes, params, 1);
|
||||
|
||||
// 0 public 1 private
|
||||
let pi_s = ProofCmCs::construct(¶ms, &cm, &r, &cms, &rs, &private_attributes, &[]);
|
||||
|
||||
let bytes = pi_s.to_bytes();
|
||||
assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s);
|
||||
|
||||
let params = setup(2).unwrap();
|
||||
let cm = G1Projective::random(&mut rng);
|
||||
let r = params.random_scalar();
|
||||
let cms: [G1Projective; 2] = [
|
||||
G1Projective::random(&mut rng),
|
||||
G1Projective::random(&mut rng),
|
||||
];
|
||||
let rs = params.n_random_scalars(2);
|
||||
random_scalars_refs!(private_attributes, params, 2);
|
||||
|
||||
// 0 public 2 privates
|
||||
let pi_s = ProofCmCs::construct(¶ms, &cm, &r, &cms, &rs, &private_attributes, &[]);
|
||||
|
||||
let bytes = pi_s.to_bytes();
|
||||
assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proof_kappa_zeta_bytes_roundtrip() {
|
||||
let params = setup(4).unwrap();
|
||||
|
||||
let keypair = keygen(¶ms);
|
||||
|
||||
// we don't care about 'correctness' of the proof. only whether we can correctly recover it from bytes
|
||||
let serial_number = ¶ms.random_scalar();
|
||||
let binding_number = ¶ms.random_scalar();
|
||||
let private_attributes = vec![serial_number, binding_number];
|
||||
|
||||
let r = params.random_scalar();
|
||||
let kappa = compute_kappa(¶ms, keypair.verification_key(), &private_attributes, r);
|
||||
let zeta = compute_zeta(¶ms, serial_number);
|
||||
|
||||
// 0 public 2 private
|
||||
let pi_v = ProofKappaZeta::construct(
|
||||
¶ms,
|
||||
keypair.verification_key(),
|
||||
serial_number,
|
||||
binding_number,
|
||||
&r,
|
||||
&kappa,
|
||||
&zeta,
|
||||
);
|
||||
|
||||
let proof_bytes = pi_v.to_bytes();
|
||||
|
||||
let proof_from_bytes = ProofKappaZeta::from_bytes(&proof_bytes).unwrap();
|
||||
assert_eq!(proof_from_bytes, pi_v);
|
||||
|
||||
// 2 public 2 private
|
||||
let params = setup(4).unwrap();
|
||||
let keypair = keygen(¶ms);
|
||||
|
||||
let pi_v = ProofKappaZeta::construct(
|
||||
¶ms,
|
||||
keypair.verification_key(),
|
||||
serial_number,
|
||||
binding_number,
|
||||
&r,
|
||||
&kappa,
|
||||
&zeta,
|
||||
);
|
||||
|
||||
let proof_bytes = pi_v.to_bytes();
|
||||
|
||||
let proof_from_bytes = ProofKappaZeta::from_bytes(&proof_bytes).unwrap();
|
||||
assert_eq!(proof_from_bytes, pi_v);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,432 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use core::iter::Sum;
|
||||
use core::ops::Mul;
|
||||
|
||||
use bls12_381::{G2Prepared, G2Projective, Scalar};
|
||||
use group::Curve;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::scheme::verification::check_bilinear_pairing;
|
||||
use crate::scheme::{PartialSignature, Signature, SignatureShare, SignerIndex, VerificationKey};
|
||||
use crate::utils::perform_lagrangian_interpolation_at_origin;
|
||||
use crate::{Attribute, Parameters, VerificationKeyShare};
|
||||
|
||||
pub(crate) trait Aggregatable: Sized {
|
||||
fn aggregate(aggregatable: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self>;
|
||||
|
||||
fn check_unique_indices(indices: &[SignerIndex]) -> bool {
|
||||
// if aggregation is a threshold one, all indices should be unique
|
||||
indices.iter().unique_by(|&index| index).count() == indices.len()
|
||||
}
|
||||
}
|
||||
|
||||
// includes `VerificationKey`
|
||||
impl<T> Aggregatable for T
|
||||
where
|
||||
T: Sum,
|
||||
for<'a> T: Sum<&'a T>,
|
||||
for<'a> &'a T: Mul<Scalar, Output = T>,
|
||||
{
|
||||
fn aggregate(aggregatable: &[T], indices: Option<&[u64]>) -> Result<T> {
|
||||
if aggregatable.is_empty() {
|
||||
return Err(CoconutError::Aggregation("Empty set of values".to_string()));
|
||||
}
|
||||
|
||||
if let Some(indices) = indices {
|
||||
if !Self::check_unique_indices(indices) {
|
||||
return Err(CoconutError::Aggregation("Non-unique indices".to_string()));
|
||||
}
|
||||
perform_lagrangian_interpolation_at_origin(indices, aggregatable)
|
||||
} else {
|
||||
// non-threshold
|
||||
Ok(aggregatable.iter().sum())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Aggregatable for PartialSignature {
|
||||
fn aggregate(sigs: &[PartialSignature], indices: Option<&[u64]>) -> Result<Signature> {
|
||||
let h = sigs
|
||||
.first()
|
||||
.ok_or_else(|| CoconutError::Aggregation("Empty set of signatures".to_string()))?
|
||||
.sig1();
|
||||
|
||||
// TODO: is it possible to avoid this allocation?
|
||||
let sigmas = sigs.iter().map(|sig| *sig.sig2()).collect::<Vec<_>>();
|
||||
let aggr_sigma = Aggregatable::aggregate(&sigmas, indices)?;
|
||||
|
||||
Ok(Signature(*h, aggr_sigma))
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures all provided verification keys were generated to verify the same number of attributes.
|
||||
fn check_same_key_size(keys: &[VerificationKey]) -> bool {
|
||||
keys.iter().map(|vk| vk.beta_g1.len()).all_equal()
|
||||
&& keys.iter().map(|vk| vk.beta_g2.len()).all_equal()
|
||||
}
|
||||
|
||||
pub fn aggregate_verification_keys(
|
||||
keys: &[VerificationKey],
|
||||
indices: Option<&[SignerIndex]>,
|
||||
) -> Result<VerificationKey> {
|
||||
if !check_same_key_size(keys) {
|
||||
return Err(CoconutError::Aggregation(
|
||||
"Verification keys are of different sizes".to_string(),
|
||||
));
|
||||
}
|
||||
Aggregatable::aggregate(keys, indices)
|
||||
}
|
||||
|
||||
pub fn aggregate_key_shares(shares: &[VerificationKeyShare]) -> Result<VerificationKey> {
|
||||
let (keys, indices): (Vec<_>, Vec<_>) = shares
|
||||
.iter()
|
||||
.map(|share| (share.key.clone(), share.index))
|
||||
.unzip();
|
||||
|
||||
aggregate_verification_keys(&keys, Some(&indices))
|
||||
}
|
||||
|
||||
pub fn aggregate_signatures(
|
||||
signatures: &[PartialSignature],
|
||||
indices: Option<&[SignerIndex]>,
|
||||
) -> Result<Signature> {
|
||||
Aggregatable::aggregate(signatures, indices)
|
||||
}
|
||||
|
||||
pub fn aggregate_signatures_and_verify(
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKey,
|
||||
attributes: &[&Attribute],
|
||||
signatures: &[PartialSignature],
|
||||
indices: Option<&[SignerIndex]>,
|
||||
) -> Result<Signature> {
|
||||
// aggregate the signature
|
||||
let signature = aggregate_signatures(signatures, indices)?;
|
||||
|
||||
// Verify the signature
|
||||
let alpha = verification_key.alpha;
|
||||
|
||||
let tmp = attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(&attr, beta_i)| beta_i * attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
if bool::from(signature.0.is_identity()) {
|
||||
return Err(CoconutError::Aggregation(
|
||||
"Verification of the aggregated signature failed - h is an identity point".to_string(),
|
||||
));
|
||||
}
|
||||
if !check_bilinear_pairing(
|
||||
&signature.0.to_affine(),
|
||||
&G2Prepared::from((alpha + tmp).to_affine()),
|
||||
&signature.1.to_affine(),
|
||||
params.prepared_miller_g2(),
|
||||
) {
|
||||
return Err(CoconutError::Aggregation(
|
||||
"Verification of the aggregated signature failed".to_string(),
|
||||
));
|
||||
}
|
||||
Ok(signature)
|
||||
}
|
||||
|
||||
pub fn aggregate_signature_shares(shares: &[SignatureShare]) -> Result<Signature> {
|
||||
let (signatures, indices): (Vec<_>, Vec<_>) = shares
|
||||
.iter()
|
||||
.map(|share| (*share.signature(), share.index()))
|
||||
.unzip();
|
||||
|
||||
aggregate_signatures(&signatures, Some(&indices))
|
||||
}
|
||||
|
||||
pub fn aggregate_signature_shares_and_verify(
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKey,
|
||||
attributes: &[&Attribute],
|
||||
shares: &[SignatureShare],
|
||||
) -> Result<Signature> {
|
||||
let (signatures, indices): (Vec<_>, Vec<_>) = shares
|
||||
.iter()
|
||||
.map(|share| (*share.signature(), share.index()))
|
||||
.unzip();
|
||||
|
||||
aggregate_signatures_and_verify(
|
||||
params,
|
||||
verification_key,
|
||||
attributes,
|
||||
&signatures,
|
||||
Some(&indices),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::scheme::issuance::sign;
|
||||
use crate::scheme::keygen::ttp_keygen;
|
||||
use crate::scheme::verification::verify;
|
||||
use crate::tests::helpers::random_scalars_refs;
|
||||
use bls12_381::G1Projective;
|
||||
use group::Group;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn key_aggregation_works_for_any_subset_of_keys() {
|
||||
let params = Parameters::new(2).unwrap();
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
|
||||
let vks = keypairs
|
||||
.into_iter()
|
||||
.map(|keypair| keypair.verification_key().clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let aggr_vk1 = aggregate_verification_keys(&vks[..3], Some(&[1, 2, 3])).unwrap();
|
||||
let aggr_vk2 = aggregate_verification_keys(&vks[2..], Some(&[3, 4, 5])).unwrap();
|
||||
|
||||
assert_eq!(aggr_vk1, aggr_vk2);
|
||||
|
||||
// TODO: should those two actually work or not?
|
||||
// aggregating threshold+1
|
||||
let aggr_more = aggregate_verification_keys(&vks[1..], Some(&[2, 3, 4, 5])).unwrap();
|
||||
assert_eq!(aggr_vk1, aggr_more);
|
||||
|
||||
// aggregating all
|
||||
let aggr_all = aggregate_verification_keys(&vks, Some(&[1, 2, 3, 4, 5])).unwrap();
|
||||
assert_eq!(aggr_all, aggr_vk1);
|
||||
|
||||
// not taking enough points (threshold was 3)
|
||||
let aggr_not_enough = aggregate_verification_keys(&vks[..2], Some(&[1, 2])).unwrap();
|
||||
assert_ne!(aggr_not_enough, aggr_vk1);
|
||||
|
||||
// taking wrong index
|
||||
let aggr_bad = aggregate_verification_keys(&vks[2..], Some(&[42, 123, 100])).unwrap();
|
||||
assert_ne!(aggr_vk1, aggr_bad);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_aggregation_doesnt_work_for_empty_set_of_keys() {
|
||||
let keys: Vec<VerificationKey> = vec![];
|
||||
assert!(aggregate_verification_keys(&keys, None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_aggregation_doesnt_work_if_indices_have_invalid_length() {
|
||||
let keys = vec![VerificationKey::identity(3)];
|
||||
|
||||
assert!(aggregate_verification_keys(&keys, Some(&[])).is_err());
|
||||
assert!(aggregate_verification_keys(&keys, Some(&[1, 2])).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_aggregation_doesnt_work_for_non_unique_indices() {
|
||||
let keys = vec![VerificationKey::identity(3), VerificationKey::identity(3)];
|
||||
|
||||
assert!(aggregate_verification_keys(&keys, Some(&[1, 1])).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_aggregation_doesnt_work_for_keys_of_different_size() {
|
||||
let keys = vec![VerificationKey::identity(3), VerificationKey::identity(1)];
|
||||
|
||||
assert!(aggregate_verification_keys(&keys, None).is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_aggregation_works_for_any_subset_of_signatures() {
|
||||
let params = Parameters::new(2).unwrap();
|
||||
random_scalars_refs!(attributes, params, 2);
|
||||
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
|
||||
let (sks, vks): (Vec<_>, Vec<_>) = keypairs
|
||||
.into_iter()
|
||||
.map(|keypair| {
|
||||
(
|
||||
keypair.secret_key().clone(),
|
||||
keypair.verification_key().clone(),
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
|
||||
let sigs = sks
|
||||
.iter()
|
||||
.map(|sk| sign(sk, &attributes).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// aggregating (any) threshold works
|
||||
let aggr_vk_1 = aggregate_verification_keys(&vks[..3], Some(&[1, 2, 3])).unwrap();
|
||||
let aggr_sig1 = aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_1,
|
||||
&attributes,
|
||||
&sigs[..3],
|
||||
Some(&[1, 2, 3]),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let aggr_vk_2 = aggregate_verification_keys(&vks[2..], Some(&[3, 4, 5])).unwrap();
|
||||
let aggr_sig2 = aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_1,
|
||||
&attributes,
|
||||
&sigs[2..],
|
||||
Some(&[3, 4, 5]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(aggr_sig1, aggr_sig2);
|
||||
|
||||
// verify credential for good measure
|
||||
assert!(verify(¶ms, &aggr_vk_1, &attributes, &aggr_sig1));
|
||||
assert!(verify(¶ms, &aggr_vk_2, &attributes, &aggr_sig2));
|
||||
|
||||
// aggregating threshold+1 works
|
||||
let aggr_vk_more = aggregate_verification_keys(&vks[1..], Some(&[2, 3, 4, 5])).unwrap();
|
||||
let aggr_more = aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_more,
|
||||
&attributes,
|
||||
&sigs[1..],
|
||||
Some(&[2, 3, 4, 5]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(aggr_sig1, aggr_more);
|
||||
|
||||
// aggregating all
|
||||
let aggr_vk_all = aggregate_verification_keys(&vks, Some(&[1, 2, 3, 4, 5])).unwrap();
|
||||
let aggr_all = aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_all,
|
||||
&attributes,
|
||||
&sigs,
|
||||
Some(&[1, 2, 3, 4, 5]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(aggr_all, aggr_sig1);
|
||||
|
||||
// not taking enough points (threshold was 3) should fail
|
||||
let aggr_vk_not_enough = aggregate_verification_keys(&vks[..2], Some(&[1, 2])).unwrap();
|
||||
let aggr_not_enough = aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_not_enough,
|
||||
&attributes,
|
||||
&sigs[..2],
|
||||
Some(&[1, 2]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_ne!(aggr_not_enough, aggr_sig1);
|
||||
|
||||
// taking wrong index should fail
|
||||
let aggr_vk_bad = aggregate_verification_keys(&vks[2..], Some(&[1, 2, 3])).unwrap();
|
||||
assert!(aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_bad,
|
||||
&attributes,
|
||||
&sigs[2..],
|
||||
Some(&[42, 123, 100]),
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
fn random_signature() -> Signature {
|
||||
let mut rng = rand::thread_rng();
|
||||
Signature(
|
||||
G1Projective::random(&mut rng),
|
||||
G1Projective::random(&mut rng),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_aggregation_doesnt_work_for_empty_set_of_signatures() {
|
||||
let signatures: Vec<Signature> = vec![];
|
||||
let params = Parameters::new(2).unwrap();
|
||||
random_scalars_refs!(attributes, params, 2);
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
|
||||
let (_, vks): (Vec<_>, Vec<_>) = keypairs
|
||||
.into_iter()
|
||||
.map(|keypair| {
|
||||
(
|
||||
keypair.secret_key().clone(),
|
||||
keypair.verification_key().clone(),
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
|
||||
let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap();
|
||||
assert!(aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_all,
|
||||
&attributes,
|
||||
&signatures,
|
||||
None
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_aggregation_doesnt_work_if_indices_have_invalid_length() {
|
||||
let signatures = vec![random_signature()];
|
||||
let params = Parameters::new(2).unwrap();
|
||||
random_scalars_refs!(attributes, params, 2);
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
let (_, vks): (Vec<_>, Vec<_>) = keypairs
|
||||
.into_iter()
|
||||
.map(|keypair| {
|
||||
(
|
||||
keypair.secret_key().clone(),
|
||||
keypair.verification_key().clone(),
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap();
|
||||
|
||||
assert!(aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_all,
|
||||
&attributes,
|
||||
&signatures,
|
||||
Some(&[])
|
||||
)
|
||||
.is_err());
|
||||
assert!(aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_all,
|
||||
&attributes,
|
||||
&signatures,
|
||||
Some(&[1, 2]),
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_aggregation_doesnt_work_for_non_unique_indices() {
|
||||
let signatures = vec![random_signature(), random_signature()];
|
||||
let params = Parameters::new(2).unwrap();
|
||||
random_scalars_refs!(attributes, params, 2);
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
let (_, vks): (Vec<_>, Vec<_>) = keypairs
|
||||
.into_iter()
|
||||
.map(|keypair| {
|
||||
(
|
||||
keypair.secret_key().clone(),
|
||||
keypair.verification_key().clone(),
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap();
|
||||
|
||||
assert!(aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_all,
|
||||
&attributes,
|
||||
&signatures,
|
||||
Some(&[1, 1]),
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
// TODO: test for aggregating non-threshold keys
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user