Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7712d15e79 | |||
| eb886c0860 |
Generated
+38
-416
@@ -1457,16 +1457,6 @@ version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "core-models"
|
||||
version = "0.0.2"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"hax-lib",
|
||||
"pastey",
|
||||
"rand 0.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmos-sdk-proto"
|
||||
version = "0.26.1"
|
||||
@@ -3151,40 +3141,6 @@ dependencies = [
|
||||
"hashbrown 0.15.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hax-lib"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/cryspen/hax/#27ba29ff727fb4c4fffecad5e736181087ce2537"
|
||||
dependencies = [
|
||||
"hax-lib-macros",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hax-lib-macros"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/cryspen/hax/#27ba29ff727fb4c4fffecad5e736181087ce2537"
|
||||
dependencies = [
|
||||
"hax-lib-macros-types",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hax-lib-macros-types"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/cryspen/hax/#27ba29ff727fb4c4fffecad5e736181087ce2537"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hdrhistogram"
|
||||
version = "7.5.4"
|
||||
@@ -3294,7 +3250,7 @@ dependencies = [
|
||||
"idna",
|
||||
"ipnet",
|
||||
"once_cell",
|
||||
"rand 0.9.1",
|
||||
"rand 0.9.0",
|
||||
"ring",
|
||||
"rustls 0.23.25",
|
||||
"thiserror 2.0.12",
|
||||
@@ -3319,7 +3275,7 @@ dependencies = [
|
||||
"moka",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"rand 0.9.1",
|
||||
"rand 0.9.0",
|
||||
"resolv-conf",
|
||||
"rustls 0.23.25",
|
||||
"smallvec",
|
||||
@@ -4194,194 +4150,6 @@ version = "0.2.171"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-chacha20poly1305"
|
||||
version = "0.0.2"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"libcrux-hacl-rs",
|
||||
"libcrux-macros",
|
||||
"libcrux-poly1305",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-curve25519"
|
||||
version = "0.0.2"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"libcrux-hacl-rs",
|
||||
"libcrux-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-ecdh"
|
||||
version = "0.0.3-alpha.1"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"libcrux-curve25519",
|
||||
"libcrux-p256",
|
||||
"rand 0.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-ed25519"
|
||||
version = "0.0.3-alpha.1"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"libcrux-hacl-rs",
|
||||
"libcrux-macros",
|
||||
"libcrux-sha2",
|
||||
"rand_core 0.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-hacl-rs"
|
||||
version = "0.0.2"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"libcrux-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-hkdf"
|
||||
version = "0.0.3-alpha.1"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"libcrux-hacl-rs",
|
||||
"libcrux-hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-hmac"
|
||||
version = "0.0.3-alpha.1"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"libcrux-hacl-rs",
|
||||
"libcrux-macros",
|
||||
"libcrux-sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-intrinsics"
|
||||
version = "0.0.2"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"core-models",
|
||||
"hax-lib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-kem"
|
||||
version = "0.0.3-alpha.1"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"libcrux-ecdh",
|
||||
"libcrux-ml-kem",
|
||||
"libcrux-sha3",
|
||||
"libcrux-traits",
|
||||
"rand 0.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-macros"
|
||||
version = "0.0.2"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-ml-kem"
|
||||
version = "0.0.3-alpha.1"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"hax-lib",
|
||||
"libcrux-intrinsics",
|
||||
"libcrux-platform",
|
||||
"libcrux-secrets",
|
||||
"libcrux-sha3",
|
||||
"rand 0.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-p256"
|
||||
version = "0.0.3-alpha.1"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"libcrux-hacl-rs",
|
||||
"libcrux-macros",
|
||||
"libcrux-sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-platform"
|
||||
version = "0.0.2"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-poly1305"
|
||||
version = "0.0.2"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"libcrux-hacl-rs",
|
||||
"libcrux-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-psq"
|
||||
version = "0.0.3-alpha.1"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"libcrux-chacha20poly1305",
|
||||
"libcrux-ecdh",
|
||||
"libcrux-ed25519",
|
||||
"libcrux-hkdf",
|
||||
"libcrux-hmac",
|
||||
"libcrux-kem",
|
||||
"libcrux-traits",
|
||||
"rand 0.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-secrets"
|
||||
version = "0.0.2"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"hax-lib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-sha2"
|
||||
version = "0.0.3-alpha.1"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"libcrux-hacl-rs",
|
||||
"libcrux-macros",
|
||||
"libcrux-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-sha3"
|
||||
version = "0.0.2"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"hax-lib",
|
||||
"libcrux-intrinsics",
|
||||
"libcrux-platform",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-traits"
|
||||
version = "0.0.3-alpha.1"
|
||||
source = "git+https://github.com/cryspen/libcrux#96abd9f2460ff92811a1c181662590d96c37949c"
|
||||
dependencies = [
|
||||
"rand 0.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.11"
|
||||
@@ -5124,18 +4892,18 @@ dependencies = [
|
||||
"getset",
|
||||
"hex",
|
||||
"humantime-serde",
|
||||
"nym-compact-ecash",
|
||||
"nym-config",
|
||||
"nym-contracts-common",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-ecash-time",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-network-defaults",
|
||||
"nym-node-requests",
|
||||
"nym-compact-ecash 0.1.0",
|
||||
"nym-config 0.1.0",
|
||||
"nym-contracts-common 0.5.0",
|
||||
"nym-credentials-interface 0.1.0",
|
||||
"nym-crypto 0.4.0",
|
||||
"nym-ecash-time 0.1.0",
|
||||
"nym-mixnet-contract-common 0.6.0",
|
||||
"nym-network-defaults 0.1.0",
|
||||
"nym-node-requests 0.1.0",
|
||||
"nym-noise-keys",
|
||||
"nym-serde-helpers",
|
||||
"nym-ticketbooks-merkle",
|
||||
"nym-serde-helpers 0.1.0",
|
||||
"nym-ticketbooks-merkle 0.1.0",
|
||||
"rand_chacha 0.3.1",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -5538,7 +5306,6 @@ dependencies = [
|
||||
"nym-sphinx 0.1.0",
|
||||
"nym-task 0.1.0",
|
||||
"sqlx",
|
||||
"sqlx-pool-guard",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tokio",
|
||||
@@ -5806,7 +5573,6 @@ dependencies = [
|
||||
"nym-ecash-time 0.1.0",
|
||||
"serde",
|
||||
"sqlx",
|
||||
"sqlx-pool-guard",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"zeroize",
|
||||
@@ -6493,10 +6259,10 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"dashmap",
|
||||
"futures",
|
||||
"nym-crypto",
|
||||
"nym-crypto 0.4.0",
|
||||
"nym-noise",
|
||||
"nym-sphinx",
|
||||
"nym-task",
|
||||
"nym-sphinx 0.1.0",
|
||||
"nym-task 0.1.0",
|
||||
"rand 0.8.5",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@@ -6762,7 +6528,7 @@ dependencies = [
|
||||
"nym-mixnet-client",
|
||||
"nym-network-requester",
|
||||
"nym-node-metrics",
|
||||
"nym-node-requests",
|
||||
"nym-node-requests 0.1.0",
|
||||
"nym-noise",
|
||||
"nym-noise-keys",
|
||||
"nym-nonexhaustive-delayqueue",
|
||||
@@ -6825,12 +6591,12 @@ dependencies = [
|
||||
"celes",
|
||||
"humantime",
|
||||
"humantime-serde",
|
||||
"nym-bin-common",
|
||||
"nym-crypto",
|
||||
"nym-exit-policy",
|
||||
"nym-http-api-client",
|
||||
"nym-bin-common 0.6.0",
|
||||
"nym-crypto 0.4.0",
|
||||
"nym-exit-policy 0.1.0",
|
||||
"nym-http-api-client 0.1.0",
|
||||
"nym-noise-keys",
|
||||
"nym-wireguard-types",
|
||||
"nym-wireguard-types 0.1.0",
|
||||
"rand_chacha 0.3.1",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -6883,7 +6649,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-status-api"
|
||||
version = "3.1.0"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
@@ -7009,12 +6775,14 @@ dependencies = [
|
||||
name = "nym-noise"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"bytes",
|
||||
"futures",
|
||||
"nym-crypto",
|
||||
"nym-crypto 0.4.0",
|
||||
"nym-noise-keys",
|
||||
"pin-project",
|
||||
"rand_chacha 0.3.1",
|
||||
"sha2 0.10.9",
|
||||
"snow",
|
||||
"strum 0.26.3",
|
||||
@@ -7028,7 +6796,7 @@ dependencies = [
|
||||
name = "nym-noise-keys"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-crypto 0.4.0",
|
||||
"schemars",
|
||||
"serde",
|
||||
"utoipa",
|
||||
@@ -7136,29 +6904,6 @@ dependencies = [
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-psq-kkt"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"bytes",
|
||||
"criterion",
|
||||
"futures",
|
||||
"libcrux-kem",
|
||||
"libcrux-psq",
|
||||
"libcrux-traits",
|
||||
"nym-crypto",
|
||||
"pin-project",
|
||||
"rand 0.9.1",
|
||||
"sha2 0.10.9",
|
||||
"snow",
|
||||
"strum 0.26.3",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-sdk"
|
||||
version = "0.1.0"
|
||||
@@ -8539,12 +8284,6 @@ version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pastey"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3a8cb46bdc156b1c90460339ae6bfd45ba0394e5effbaa640badb4987fdc261"
|
||||
|
||||
[[package]]
|
||||
name = "peg"
|
||||
version = "0.8.4"
|
||||
@@ -8865,7 +8604,7 @@ dependencies = [
|
||||
"hmac",
|
||||
"md-5",
|
||||
"memchr",
|
||||
"rand 0.9.1",
|
||||
"rand 0.9.0",
|
||||
"sha2 0.10.9",
|
||||
"stringprep",
|
||||
]
|
||||
@@ -8952,15 +8691,6 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc_pidinfo"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af53dad2390f8df98dda1e4188322bdf2f91c86cf6001f51d10d64451edf463a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prometheus"
|
||||
version = "0.14.0"
|
||||
@@ -9116,7 +8846,7 @@ checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"getrandom 0.3.1",
|
||||
"rand 0.9.1",
|
||||
"rand 0.9.0",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls 0.23.25",
|
||||
@@ -9170,12 +8900,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.1"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
|
||||
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.1",
|
||||
"zerocopy 0.8.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10547,19 +10278,6 @@ dependencies = [
|
||||
"whoami",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-pool-guard"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc_pidinfo",
|
||||
"sqlx",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"windows 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-postgres"
|
||||
version = "0.8.6"
|
||||
@@ -11240,7 +10958,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"postgres-protocol",
|
||||
"postgres-types",
|
||||
"rand 0.9.1",
|
||||
"rand 0.9.0",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -12574,28 +12292,6 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.61.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core 0.61.2",
|
||||
"windows-future",
|
||||
"windows-link",
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-collections"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
@@ -12630,30 +12326,6 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement 0.60.0",
|
||||
"windows-interface 0.59.1",
|
||||
"windows-link",
|
||||
"windows-result 0.3.4",
|
||||
"windows-strings 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
"windows-link",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.57.0"
|
||||
@@ -12676,17 +12348,6 @@ dependencies = [
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.57.0"
|
||||
@@ -12709,32 +12370,11 @@ dependencies = [
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.1"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
||||
|
||||
[[package]]
|
||||
name = "windows-numerics"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
"windows-link",
|
||||
]
|
||||
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
@@ -12742,7 +12382,7 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
|
||||
dependencies = [
|
||||
"windows-result 0.3.4",
|
||||
"windows-result 0.3.1",
|
||||
"windows-strings 0.3.1",
|
||||
"windows-targets 0.53.0",
|
||||
]
|
||||
@@ -12767,9 +12407,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
@@ -12793,15 +12433,6 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
@@ -12900,15 +12531,6 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.53.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
|
||||
@@ -64,7 +64,6 @@ members = [
|
||||
"common/node-tester-utils",
|
||||
"common/nonexhaustive-delayqueue",
|
||||
"common/nym-id",
|
||||
"common/nym-psq-kkt",
|
||||
"common/nym-metrics",
|
||||
"common/nym_offline_compact_ecash",
|
||||
"common/nymnoise",
|
||||
@@ -127,7 +126,6 @@ members = [
|
||||
"service-providers/common",
|
||||
"service-providers/ip-packet-router",
|
||||
"service-providers/network-requester",
|
||||
"sqlx-pool-guard",
|
||||
"tools/echo-server",
|
||||
"tools/internal/contract-state-importer/importer-cli",
|
||||
"tools/internal/contract-state-importer/importer-contract",
|
||||
@@ -289,7 +287,6 @@ petgraph = "0.6.5"
|
||||
pin-project = "1.1"
|
||||
pin-project-lite = "0.2.16"
|
||||
publicsuffix = "2.3.0"
|
||||
proc_pidinfo = "0.1.3"
|
||||
quote = "1"
|
||||
rand = "0.8.5"
|
||||
rand_chacha = "0.3"
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::BadGateway;
|
||||
use std::{io, path::PathBuf};
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -18,6 +19,7 @@ pub enum StorageError {
|
||||
|
||||
#[error("failed to perform sqlx migration: {source}")]
|
||||
MigrationError {
|
||||
#[source]
|
||||
#[from]
|
||||
source: sqlx::migrate::MigrateError,
|
||||
},
|
||||
@@ -30,6 +32,7 @@ pub enum StorageError {
|
||||
|
||||
#[error("failed to run the SQL query: {source}")]
|
||||
QueryError {
|
||||
#[source]
|
||||
#[from]
|
||||
source: sqlx::error::Error,
|
||||
},
|
||||
|
||||
@@ -63,6 +63,7 @@ use std::os::raw::c_int as RawFd;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tokio::sync::Mutex;
|
||||
use url::Url;
|
||||
|
||||
#[cfg(all(
|
||||
@@ -195,6 +196,10 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
|
||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
|
||||
derivation_material: Option<DerivationMaterial>,
|
||||
// Shared derivation material wrapped in Arc<Mutex<>> for thread-safe access
|
||||
// across multiple clients. This allows multiple clients to share the same
|
||||
// derivation source while maintaining safe concurrent access.
|
||||
shared_derivation_material: Option<Arc<Mutex<DerivationMaterial>>>,
|
||||
}
|
||||
|
||||
impl<C, S> BaseClientBuilder<C, S>
|
||||
@@ -220,6 +225,7 @@ where
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
derivation_material: None,
|
||||
shared_derivation_material: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +238,18 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Set shared derivation material for thread-safe sharing across multiple clients.
|
||||
/// This is useful when multiple clients need to derive keys from the same source
|
||||
/// while ensuring thread-safe access through Arc<Mutex<>>.
|
||||
#[must_use]
|
||||
pub fn with_shared_derivation_material(
|
||||
mut self,
|
||||
derivation_material: Option<Arc<Mutex<DerivationMaterial>>>,
|
||||
) -> Self {
|
||||
self.shared_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;
|
||||
@@ -704,6 +722,7 @@ where
|
||||
key_store: &S::KeyStore,
|
||||
details_store: &S::GatewaysDetailsStore,
|
||||
derivation_material: Option<DerivationMaterial>,
|
||||
shared_derivation_material: Option<Arc<Mutex<DerivationMaterial>>>,
|
||||
) -> Result<InitialisationResult, ClientCoreError>
|
||||
where
|
||||
<S::KeyStore as KeyStore>::StorageError: Sync + Send,
|
||||
@@ -713,12 +732,24 @@ 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)
|
||||
|
||||
// Key generation priority: individual derivation material > shared derivation material > random generation
|
||||
let keys = match (derivation_material, shared_derivation_material) {
|
||||
// Individual derivation material takes precedence if provided
|
||||
(Some(derivation_material), _) => {
|
||||
ClientKeys::from_master_key(&mut rng, &derivation_material)
|
||||
.map_err(|_| ClientCoreError::HkdfDerivationError {})?
|
||||
}
|
||||
// Use shared derivation material if no individual material is provided
|
||||
(None, Some(shared_derivation_material)) => {
|
||||
let shared_derivation_material = shared_derivation_material.lock().await;
|
||||
ClientKeys::from_master_key(&mut rng, &shared_derivation_material)
|
||||
.map_err(|_| ClientCoreError::HkdfDerivationError {})?
|
||||
}
|
||||
// Fall back to random key generation if no derivation material is available
|
||||
(None, None) => ClientKeys::generate_new(&mut rng),
|
||||
};
|
||||
|
||||
store_client_keys(keys, key_store).await?;
|
||||
}
|
||||
|
||||
@@ -741,6 +772,7 @@ where
|
||||
self.client_store.key_store(),
|
||||
self.client_store.gateway_details_store(),
|
||||
self.derivation_material,
|
||||
self.shared_derivation_material,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{
|
||||
client::replies::reply_storage::{fs_backend, CombinedReplyStorage, ReplyStorageBackend},
|
||||
config,
|
||||
config::Config,
|
||||
error::ClientCoreError,
|
||||
use crate::client::replies::reply_storage::{
|
||||
fs_backend, CombinedReplyStorage, ReplyStorageBackend,
|
||||
};
|
||||
use crate::config;
|
||||
use crate::config::Config;
|
||||
use crate::error::ClientCoreError;
|
||||
use log::{error, info, trace};
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_client_core_gateways_storage::OnDiskGatewaysDetails;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_validator_client::{nyxd, QueryHttpRpcNyxdClient};
|
||||
use std::{io, path::Path};
|
||||
use nym_validator_client::nyxd;
|
||||
use nym_validator_client::QueryHttpRpcNyxdClient;
|
||||
use std::path::Path;
|
||||
use std::{fs, io};
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
|
||||
@@ -20,11 +22,11 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
db_path: P,
|
||||
surb_config: &config::ReplySurbs,
|
||||
) -> Result<fs_backend::Backend, ClientCoreError> {
|
||||
info!("Creating fresh surb database");
|
||||
info!("creating fresh surb database");
|
||||
let mut storage_backend = match fs_backend::Backend::init(db_path).await {
|
||||
Ok(backend) => backend,
|
||||
Err(err) => {
|
||||
error!("setup_fresh_backend: Failed to setup persistent storage backend for our reply needs: {err}");
|
||||
error!("failed to setup persistent storage backend for our reply needs: {err}");
|
||||
return Err(ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
});
|
||||
@@ -38,15 +40,14 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
surb_config.minimum_reply_surb_storage_threshold,
|
||||
surb_config.maximum_reply_surb_storage_threshold,
|
||||
);
|
||||
match storage_backend.init_fresh(&mem_store).await {
|
||||
Ok(()) => Ok(storage_backend),
|
||||
Err(err) => {
|
||||
storage_backend.shutdown().await;
|
||||
Err(ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
})
|
||||
}
|
||||
}
|
||||
storage_backend
|
||||
.init_fresh(&mem_store)
|
||||
.await
|
||||
.map_err(|err| ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
})?;
|
||||
|
||||
Ok(storage_backend)
|
||||
}
|
||||
|
||||
// fn setup_inactive_backend(surb_config: &config::ReplySurbs) -> fs_backend::Backend {
|
||||
@@ -57,11 +58,12 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
// )
|
||||
// }
|
||||
|
||||
async fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
|
||||
fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
|
||||
let db_path = db_path.as_ref();
|
||||
debug_assert!(db_path.exists());
|
||||
|
||||
let now = OffsetDateTime::now_utc().unix_timestamp();
|
||||
|
||||
let suffix = format!("_{now}.corrupted");
|
||||
|
||||
let new_extension =
|
||||
@@ -70,15 +72,11 @@ async fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()
|
||||
} else {
|
||||
suffix
|
||||
};
|
||||
let renamed = db_path.with_extension(new_extension);
|
||||
|
||||
tokio::fs::rename(db_path, &renamed).await.inspect_err(|_| {
|
||||
error!(
|
||||
"Failed to rename corrupt database file: {} to {}",
|
||||
db_path.display(),
|
||||
renamed.display()
|
||||
);
|
||||
})
|
||||
let mut renamed = db_path.to_owned();
|
||||
renamed.set_extension(new_extension);
|
||||
|
||||
fs::rename(db_path, renamed)
|
||||
}
|
||||
|
||||
pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
|
||||
@@ -89,12 +87,13 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
|
||||
// the existing one
|
||||
let db_path = db_path.as_ref();
|
||||
if db_path.exists() {
|
||||
info!("Loading existing surb database");
|
||||
info!("loading existing surb database");
|
||||
match fs_backend::Backend::try_load(db_path, surb_config.fresh_sender_tags).await {
|
||||
Ok(backend) => Ok(backend),
|
||||
Err(err) => {
|
||||
error!("setup_fs_reply_surb_backend: Failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
|
||||
archive_corrupted_database(db_path).await?;
|
||||
error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
|
||||
|
||||
archive_corrupted_database(db_path)?;
|
||||
setup_fresh_backend(db_path, surb_config).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,26 +17,15 @@ nym-crypto = { path = "../../crypto", optional = true, default-features = false
|
||||
nym-sphinx = { path = "../../nymsphinx" }
|
||||
nym-task = { path = "../../task" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||
workspace = true
|
||||
features = ["fs"]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||
workspace = true
|
||||
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
|
||||
optional = true
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx-pool-guard]
|
||||
path = "../../../sqlx-pool-guard"
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
"migrate",
|
||||
] }
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
|
||||
[features]
|
||||
fs-surb-storage = ["sqlx", "nym-crypto", "nym-crypto/hashing"]
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::{io, path::PathBuf};
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -29,6 +30,7 @@ pub enum StorageError {
|
||||
|
||||
#[error("failed to perform sqlx migration: {source}")]
|
||||
MigrationError {
|
||||
#[source]
|
||||
#[from]
|
||||
source: sqlx::migrate::MigrateError,
|
||||
},
|
||||
@@ -41,6 +43,7 @@ pub enum StorageError {
|
||||
|
||||
#[error("failed to run the SQL query: {source}")]
|
||||
QueryError {
|
||||
#[source]
|
||||
#[from]
|
||||
source: sqlx::error::Error,
|
||||
},
|
||||
|
||||
@@ -15,11 +15,9 @@ use sqlx::{
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
use sqlx_pool_guard::SqlitePoolGuard;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StorageManager {
|
||||
connection_pool: SqlitePoolGuard,
|
||||
pub connection_pool: sqlx::SqlitePool,
|
||||
}
|
||||
|
||||
// all SQL goes here
|
||||
@@ -39,7 +37,7 @@ impl StorageManager {
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(&database_path)
|
||||
.filename(database_path)
|
||||
.create_if_missing(fresh)
|
||||
.disable_statement_logging();
|
||||
|
||||
@@ -51,15 +49,11 @@ impl StorageManager {
|
||||
}
|
||||
};
|
||||
|
||||
let connection_pool =
|
||||
SqlitePoolGuard::new(database_path.as_ref().to_path_buf(), connection_pool);
|
||||
|
||||
if let Err(err) = sqlx::migrate!("./fs_surbs_migrations")
|
||||
.run(&*connection_pool)
|
||||
.run(&connection_pool)
|
||||
.await
|
||||
{
|
||||
error!("Failed to initialize SQLx database: {err}");
|
||||
connection_pool.close().await;
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
@@ -67,43 +61,38 @@ impl StorageManager {
|
||||
Ok(StorageManager { connection_pool })
|
||||
}
|
||||
|
||||
/// Close connection pool waiting for all connections to be closed.
|
||||
pub async fn close_pool(&self) {
|
||||
self.connection_pool.close().await;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn status_table_exists(&self) -> Result<bool, sqlx::Error> {
|
||||
sqlx::query!("SELECT name FROM sqlite_master WHERE type='table' AND name='status'")
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
.map(|r| r.is_some())
|
||||
}
|
||||
|
||||
pub async fn create_status_table(&self) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("INSERT INTO status(flush_in_progress, previous_flush_timestamp, client_in_use) VALUES (0, 0, 1)")
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_flush_status(&self) -> Result<bool, sqlx::Error> {
|
||||
sqlx::query!("SELECT flush_in_progress FROM status;")
|
||||
.fetch_one(&*self.connection_pool)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
.map(|r| r.flush_in_progress > 0)
|
||||
}
|
||||
|
||||
pub async fn set_previous_flush_timestamp(&self, timestamp: i64) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("UPDATE status SET previous_flush_timestamp = ?", timestamp)
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_previous_flush_timestamp(&self) -> Result<i64, sqlx::Error> {
|
||||
sqlx::query!("SELECT previous_flush_timestamp FROM status;")
|
||||
.fetch_one(&*self.connection_pool)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
.map(|r| r.previous_flush_timestamp)
|
||||
}
|
||||
@@ -111,14 +100,14 @@ impl StorageManager {
|
||||
pub async fn set_flush_status(&self, in_progress: bool) -> Result<(), sqlx::Error> {
|
||||
let in_progress_int = i64::from(in_progress);
|
||||
sqlx::query!("UPDATE status SET flush_in_progress = ?", in_progress_int)
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_client_in_use_status(&self) -> Result<bool, sqlx::Error> {
|
||||
sqlx::query!("SELECT client_in_use FROM status;")
|
||||
.fetch_one(&*self.connection_pool)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
.map(|r| r.client_in_use > 0)
|
||||
}
|
||||
@@ -126,21 +115,21 @@ impl StorageManager {
|
||||
pub async fn set_client_in_use_status(&self, in_use: bool) -> Result<(), sqlx::Error> {
|
||||
let in_use_int = i64::from(in_use);
|
||||
sqlx::query!("UPDATE status SET client_in_use = ?", in_use_int)
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_all_tags(&self) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM sender_tag;")
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_tags(&self) -> Result<Vec<StoredSenderTag>, sqlx::Error> {
|
||||
sqlx::query_as!(StoredSenderTag, "SELECT * FROM sender_tag;",)
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -152,21 +141,21 @@ impl StorageManager {
|
||||
stored_tag.recipient,
|
||||
stored_tag.tag
|
||||
)
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_all_reply_keys(&self) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM reply_key;")
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_reply_keys(&self) -> Result<Vec<StoredReplyKey>, sqlx::Error> {
|
||||
sqlx::query_as!(StoredReplyKey, "SELECT * FROM reply_key;",)
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -182,14 +171,14 @@ impl StorageManager {
|
||||
stored_reply_key.reply_key,
|
||||
stored_reply_key.sent_at_timestamp
|
||||
)
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_surb_senders(&self) -> Result<Vec<StoredSurbSender>, sqlx::Error> {
|
||||
sqlx::query_as!(StoredSurbSender, "SELECT * FROM reply_surb_sender;",)
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -204,7 +193,7 @@ impl StorageManager {
|
||||
stored_surb_sender.tag,
|
||||
stored_surb_sender.last_sent_timestamp
|
||||
)
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?
|
||||
.last_insert_rowid();
|
||||
Ok(id)
|
||||
@@ -222,17 +211,17 @@ impl StorageManager {
|
||||
"#,
|
||||
sender_id
|
||||
)
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_reply_surb_data(&self) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM reply_surb;")
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query!("DELETE FROM reply_surb_sender;")
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
@@ -250,7 +239,7 @@ impl StorageManager {
|
||||
stored_reply_surb.reply_surb,
|
||||
stored_reply_surb.encoded_key_rotation
|
||||
)
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -264,7 +253,7 @@ impl StorageManager {
|
||||
SELECT min_reply_surb_threshold as "min_reply_surb_threshold: u32", max_reply_surb_threshold as "max_reply_surb_threshold: u32" FROM reply_surb_storage_metadata;
|
||||
"#,
|
||||
)
|
||||
.fetch_one(&*self.connection_pool)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -278,7 +267,7 @@ impl StorageManager {
|
||||
"#,
|
||||
metadata.min_reply_surb_threshold,
|
||||
metadata.max_reply_surb_threshold,
|
||||
).execute(&*self.connection_pool).await?;
|
||||
).execute(&self.connection_pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::backend::fs_backend::manager::StorageManager;
|
||||
use crate::backend::fs_backend::models::{
|
||||
ReplySurbStorageMetadata, StoredReplyKey, StoredReplySurb, StoredSenderTag, StoredSurbSender,
|
||||
};
|
||||
use crate::surb_storage::ReceivedReplySurbs;
|
||||
use crate::{
|
||||
backend::fs_backend::{
|
||||
manager::StorageManager,
|
||||
models::{
|
||||
ReplySurbStorageMetadata, StoredReplyKey, StoredReplySurb, StoredSenderTag,
|
||||
StoredSurbSender,
|
||||
},
|
||||
},
|
||||
surb_storage::ReceivedReplySurbs,
|
||||
CombinedReplyStorage, ReceivedReplySurbsMap, ReplyStorageBackend, SentReplyKeys,
|
||||
UsedSenderTags,
|
||||
CombinedReplyStorage, ReceivedReplySurbsMap, ReplyStorageBackend, SentReplyKeys, UsedSenderTags,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use log::{debug, error, info, warn};
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
@@ -44,17 +41,15 @@ impl Backend {
|
||||
}
|
||||
|
||||
let manager = StorageManager::init(database_path, true).await?;
|
||||
match manager.create_status_table().await {
|
||||
Ok(()) => Ok(Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
manager,
|
||||
}),
|
||||
Err(err) => {
|
||||
manager.close_pool().await;
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
manager.create_status_table().await?;
|
||||
|
||||
let backend = Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
manager,
|
||||
};
|
||||
|
||||
Ok(backend)
|
||||
}
|
||||
|
||||
pub async fn try_load<P: AsRef<Path>>(
|
||||
@@ -69,28 +64,7 @@ impl Backend {
|
||||
}
|
||||
|
||||
let manager = StorageManager::init(database_path, false).await?;
|
||||
match Self::try_load_inner(&manager, fresh_sender_tags).await {
|
||||
Ok(()) => Ok(Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
manager,
|
||||
}),
|
||||
Err(e) => {
|
||||
manager.close_pool().await;
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gracefully close sqlite connection pool and drop backend.
|
||||
pub async fn shutdown(self) {
|
||||
self.manager.close_pool().await
|
||||
}
|
||||
|
||||
async fn try_load_inner(
|
||||
manager: &StorageManager,
|
||||
fresh_sender_tags: bool,
|
||||
) -> Result<(), StorageError> {
|
||||
// the database flush wasn't fully finished and thus the data is in inconsistent state
|
||||
// (we don't really know what's properly saved or what's not)
|
||||
if manager.get_flush_status().await? {
|
||||
@@ -152,11 +126,20 @@ impl Backend {
|
||||
manager.delete_all_tags().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
// manager: StorageManagerState::Storage(manager),
|
||||
manager,
|
||||
})
|
||||
}
|
||||
|
||||
async fn close_pool(&mut self) {
|
||||
self.manager.connection_pool.close().await;
|
||||
}
|
||||
|
||||
async fn rotate(&mut self) -> Result<(), StorageError> {
|
||||
self.manager.close_pool().await;
|
||||
self.close_pool().await;
|
||||
|
||||
let new_extension = if let Some(existing_extension) =
|
||||
self.database_path.extension().and_then(|ext| ext.to_str())
|
||||
@@ -169,8 +152,7 @@ impl Backend {
|
||||
let mut temp_old = self.database_path.clone();
|
||||
temp_old.set_extension(new_extension);
|
||||
|
||||
tokio::fs::rename(&self.database_path, &temp_old)
|
||||
.await
|
||||
fs::rename(&self.database_path, &temp_old)
|
||||
.map_err(|err| StorageError::DatabaseRenameError { source: err })?;
|
||||
self.manager = StorageManager::init(&self.database_path, true).await?;
|
||||
self.manager.create_status_table().await?;
|
||||
@@ -179,10 +161,9 @@ impl Backend {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_old(&mut self) -> Result<(), StorageError> {
|
||||
fn remove_old(&mut self) -> Result<(), StorageError> {
|
||||
if let Some(old_path) = self.temporary_old_path.take() {
|
||||
tokio::fs::remove_file(old_path)
|
||||
.await
|
||||
fs::remove_file(old_path)
|
||||
.map_err(|err| StorageError::DatabaseOldFileRemoveError { source: err })
|
||||
} else {
|
||||
warn!("the old database file doesn't seem to exist!");
|
||||
@@ -354,7 +335,7 @@ impl ReplyStorageBackend for Backend {
|
||||
self.dump_reply_surb_storage_metadata(surbs_ref).await?;
|
||||
self.dump_reply_surbs(surbs_ref).await?;
|
||||
|
||||
self.remove_old().await?;
|
||||
self.remove_old()?;
|
||||
self.end_storage_flush().await
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ where
|
||||
self.backend.load_surb_storage().await
|
||||
}
|
||||
|
||||
// this will have to get enabled after merging develop
|
||||
pub async fn flush_on_shutdown(
|
||||
mut self,
|
||||
mem_state: CombinedReplyStorage,
|
||||
@@ -49,6 +50,7 @@ where
|
||||
shutdown.recv().await;
|
||||
|
||||
info!("PersistentReplyStorage is flushing all reply-related data to underlying storage");
|
||||
info!("you MUST NOT forcefully shutdown now or you risk data corruption!");
|
||||
if let Err(err) = self.backend.flush_surb_storage(&mem_state).await {
|
||||
error!("failed to flush our reply-related data to the persistent storage: {err}")
|
||||
} else {
|
||||
|
||||
@@ -5,7 +5,7 @@ use dashmap::DashMap;
|
||||
use futures::StreamExt;
|
||||
use nym_noise::config::NoiseConfig;
|
||||
use nym_noise::upgrade_noise_initiator;
|
||||
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::framing::codec::NymCodec;
|
||||
use nym_sphinx::framing::packet::FramedNymPacket;
|
||||
use std::io;
|
||||
@@ -250,7 +250,7 @@ impl Client {
|
||||
|
||||
connections_count.fetch_add(1, Ordering::SeqCst);
|
||||
ManagedConnection::new(
|
||||
address.into(),
|
||||
address,
|
||||
noise_config,
|
||||
receiver,
|
||||
initial_connection_timeout,
|
||||
|
||||
@@ -25,7 +25,9 @@ use nym_api_requests::models::{
|
||||
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
||||
};
|
||||
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
|
||||
use nym_api_requests::nym_nodes::{NodesByAddressesResponse, SemiSkimmedNode, SkimmedNode};
|
||||
use nym_api_requests::nym_nodes::{
|
||||
NodesByAddressesResponse, SemiSkimmedNodesWithMetadata, SkimmedNode, SkimmedNodesWithMetadata,
|
||||
};
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_http_api_client::UserAgent;
|
||||
use nym_mixnet_contract_common::EpochRewardedSet;
|
||||
@@ -565,31 +567,6 @@ impl NymApiClient {
|
||||
Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
|
||||
/// retrieve expanded information for all bonded nodes on the network
|
||||
pub async fn get_all_expanded_nodes(
|
||||
&self,
|
||||
) -> Result<Vec<SemiSkimmedNode>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut nodes = Vec::new();
|
||||
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_expanded_nodes(false, Some(page), None)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
if nodes.len() < res.nodes.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
pub async fn health(&self) -> Result<ApiHealthResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.health().await?)
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ use nym_api_requests::models::{
|
||||
NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::{
|
||||
NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponse,
|
||||
SemiSkimmedNode,
|
||||
NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponseV1,
|
||||
PaginatedCachedNodesResponseV2,
|
||||
};
|
||||
use nym_api_requests::pagination::PaginatedResponse;
|
||||
pub use nym_api_requests::{
|
||||
@@ -676,39 +676,6 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_expanded_nodes(
|
||||
&self,
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
) -> Result<PaginatedCachedNodesResponse<SemiSkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
params.push(("no_legacy", "true".to_string()))
|
||||
}
|
||||
|
||||
if let Some(page) = page {
|
||||
params.push(("page", page.to_string()))
|
||||
}
|
||||
|
||||
if let Some(per_page) = per_page {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"unstable",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"semi-skimmed",
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
|
||||
@@ -20,8 +20,6 @@ nym-credentials = { path = "../credentials" }
|
||||
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
|
||||
nym-ecash-time = { path = "../ecash-time" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx-pool-guard]
|
||||
path = "../../sqlx-pool-guard"
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||
workspace = true
|
||||
@@ -33,13 +31,8 @@ features = ["rt-multi-thread", "net", "signal", "fs"]
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
"migrate",
|
||||
] }
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
[features]
|
||||
persistent-storage = ["bincode", "serde"]
|
||||
persistent-storage = ["bincode", "serde"]
|
||||
@@ -7,11 +7,10 @@ use crate::models::{
|
||||
};
|
||||
use nym_ecash_time::Date;
|
||||
use sqlx::{Executor, Sqlite, Transaction};
|
||||
use sqlx_pool_guard::SqlitePoolGuard;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SqliteEcashTicketbookManager {
|
||||
connection_pool: SqlitePoolGuard,
|
||||
connection_pool: sqlx::SqlitePool,
|
||||
}
|
||||
|
||||
impl SqliteEcashTicketbookManager {
|
||||
@@ -20,7 +19,7 @@ impl SqliteEcashTicketbookManager {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `connection_pool`: database connection pool to use.
|
||||
pub fn new(connection_pool: SqlitePoolGuard) -> Self {
|
||||
pub fn new(connection_pool: sqlx::SqlitePool) -> Self {
|
||||
SqliteEcashTicketbookManager { connection_pool }
|
||||
}
|
||||
|
||||
@@ -34,7 +33,7 @@ impl SqliteEcashTicketbookManager {
|
||||
"DELETE FROM ecash_ticketbook WHERE expiration_date <= ?",
|
||||
deadline
|
||||
)
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -61,7 +60,7 @@ impl SqliteEcashTicketbookManager {
|
||||
data,
|
||||
expiration_date,
|
||||
)
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
@@ -91,7 +90,7 @@ impl SqliteEcashTicketbookManager {
|
||||
epoch_id,
|
||||
total_tickets,
|
||||
used_tickets,
|
||||
).execute(&*self.connection_pool).await?;
|
||||
).execute(&self.connection_pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -106,7 +105,7 @@ impl SqliteEcashTicketbookManager {
|
||||
"#,
|
||||
)
|
||||
.bind(data)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await?
|
||||
.is_some();
|
||||
|
||||
@@ -122,7 +121,7 @@ impl SqliteEcashTicketbookManager {
|
||||
FROM ecash_ticketbook
|
||||
"#,
|
||||
)
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -144,7 +143,7 @@ impl SqliteEcashTicketbookManager {
|
||||
ticketbook_id,
|
||||
expected_current_total_spent
|
||||
)
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?
|
||||
.rows_affected();
|
||||
Ok(affected > 0)
|
||||
@@ -154,7 +153,7 @@ impl SqliteEcashTicketbookManager {
|
||||
&self,
|
||||
) -> Result<Vec<StoredPendingTicketbook>, sqlx::Error> {
|
||||
sqlx::query_as("SELECT * FROM pending_issuance")
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -166,7 +165,7 @@ impl SqliteEcashTicketbookManager {
|
||||
"DELETE FROM pending_issuance WHERE deposit_id = ?",
|
||||
pending_id
|
||||
)
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -183,7 +182,7 @@ impl SqliteEcashTicketbookManager {
|
||||
"#,
|
||||
epoch_id
|
||||
)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -209,7 +208,7 @@ impl SqliteEcashTicketbookManager {
|
||||
serialisation_revision,
|
||||
epoch_id
|
||||
)
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -226,7 +225,7 @@ impl SqliteEcashTicketbookManager {
|
||||
"#,
|
||||
epoch_id
|
||||
)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -252,7 +251,7 @@ impl SqliteEcashTicketbookManager {
|
||||
serialisation_revision,
|
||||
epoch_id,
|
||||
)
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -270,7 +269,7 @@ impl SqliteEcashTicketbookManager {
|
||||
"#,
|
||||
expiration_date
|
||||
)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -299,7 +298,7 @@ impl SqliteEcashTicketbookManager {
|
||||
serialisation_revision,
|
||||
expiration_date
|
||||
)
|
||||
.execute(&*self.connection_pool)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
ConnectOptions,
|
||||
};
|
||||
use sqlx_pool_guard::SqlitePoolGuard;
|
||||
use std::path::Path;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
@@ -55,15 +54,15 @@ impl PersistentStorage {
|
||||
/// * `database_path`: path to the database.
|
||||
pub async fn init<P: AsRef<Path>>(database_path: P) -> Result<Self, StorageError> {
|
||||
debug!(
|
||||
"Attempting to connect to database {}",
|
||||
database_path.as_ref().display()
|
||||
"Attempting to connect to database {:?}",
|
||||
database_path.as_ref().as_os_str()
|
||||
);
|
||||
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(&database_path)
|
||||
.filename(database_path)
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
|
||||
@@ -75,17 +74,13 @@ impl PersistentStorage {
|
||||
}
|
||||
};
|
||||
|
||||
let connection_pool =
|
||||
SqlitePoolGuard::new(database_path.as_ref().to_path_buf(), connection_pool);
|
||||
|
||||
if let Err(err) = sqlx::migrate!("./migrations").run(&*connection_pool).await {
|
||||
if let Err(err) = sqlx::migrate!("./migrations").run(&connection_pool).await {
|
||||
error!("Failed to perform migration on the SQLx database: {err}");
|
||||
connection_pool.close().await;
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
Ok(PersistentStorage {
|
||||
storage_manager: SqliteEcashTicketbookManager::new(connection_pool),
|
||||
storage_manager: SqliteEcashTicketbookManager::new(connection_pool.clone()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use hkdf::{
|
||||
},
|
||||
Hkdf,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Sha256, Sha512};
|
||||
|
||||
pub use hkdf::InvalidLength;
|
||||
@@ -60,7 +61,7 @@ where
|
||||
/// // Prepare for the next derivation
|
||||
/// let next_material = material.next();
|
||||
/// ```
|
||||
#[derive(ZeroizeOnDrop)]
|
||||
#[derive(ZeroizeOnDrop, Serialize, Deserialize)]
|
||||
pub struct DerivationMaterial {
|
||||
master_key: [u8; 32],
|
||||
index: u32,
|
||||
|
||||
@@ -33,8 +33,8 @@ impl PersistentStatsStorage {
|
||||
/// * `database_path`: path to the database.
|
||||
pub async fn init<P: AsRef<Path> + Send>(database_path: P) -> Result<Self, StatsStorageError> {
|
||||
debug!(
|
||||
"Attempting to connect to database {}",
|
||||
database_path.as_ref().display()
|
||||
"Attempting to connect to database {:?}",
|
||||
database_path.as_ref().as_os_str()
|
||||
);
|
||||
|
||||
// TODO: we can inject here more stuff based on our gateway global config
|
||||
|
||||
@@ -82,8 +82,8 @@ impl GatewayStorage {
|
||||
message_retrieval_limit: i64,
|
||||
) -> Result<Self, GatewayStorageError> {
|
||||
debug!(
|
||||
"Attempting to connect to database {}",
|
||||
database_path.as_ref().display()
|
||||
"Attempting to connect to database {:?}",
|
||||
database_path.as_ref().as_os_str()
|
||||
);
|
||||
|
||||
// TODO: we can inject here more stuff based on our gateway global config
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(feature = "network")]
|
||||
use crate::{ApiUrlConst, DenomDetails, ValidatorDetails};
|
||||
use crate::{DenomDetails, ValidatorDetails};
|
||||
|
||||
pub const NETWORK_NAME: &str = "mainnet";
|
||||
|
||||
@@ -29,36 +29,10 @@ pub const COCONUT_DKG_CONTRACT_ADDRESS: &str =
|
||||
pub const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy";
|
||||
|
||||
pub const NYXD_URL: &str = "https://rpc.nymtech.net";
|
||||
pub const NYM_API: &str = "https://validator.nymtech.net/api/";
|
||||
pub const NYXD_WS: &str = "wss://rpc.nymtech.net/websocket";
|
||||
pub const EXPLORER_API: &str = "https://explorer.nymtech.net/api/";
|
||||
pub const NYM_API: &str = "https://validator.nymtech.net/api/";
|
||||
#[cfg(feature = "network")]
|
||||
pub const NYM_APIS: &[ApiUrlConst] = &[
|
||||
ApiUrlConst {
|
||||
url: NYM_API,
|
||||
front_hosts: None,
|
||||
},
|
||||
ApiUrlConst {
|
||||
url: "https://nym-fronntdoor.vercel.app/api/",
|
||||
front_hosts: Some(&["vercel.app", "vercel.com"]),
|
||||
},
|
||||
ApiUrlConst {
|
||||
url: "https://nym-frontdoor.global.ssl.fastly.net/api/",
|
||||
front_hosts: Some(&["yelp.global.ssl.fastly.net"]),
|
||||
},
|
||||
];
|
||||
pub const NYM_VPN_API: &str = "https://nymvpn.com/api/";
|
||||
#[cfg(feature = "network")]
|
||||
pub const NYM_VPN_APIS: &[ApiUrlConst] = &[
|
||||
ApiUrlConst {
|
||||
url: NYM_VPN_API,
|
||||
front_hosts: Some(&["vercel.app", "vercel.com"]),
|
||||
},
|
||||
ApiUrlConst {
|
||||
url: "https://nymvpn-frontdoor.global.ssl.fastly.net/api/",
|
||||
front_hosts: Some(&["yelp.global.ssl.fastly.net"]),
|
||||
},
|
||||
];
|
||||
|
||||
// I'm making clippy mad on purpose, because that url HAS TO be updated and deployed before merging
|
||||
pub const EXIT_POLICY_URL: &str =
|
||||
|
||||
@@ -37,37 +37,6 @@ pub struct NymNetworkDetails {
|
||||
pub contracts: NymContracts,
|
||||
pub explorer_api: Option<String>,
|
||||
pub nym_vpn_api_url: Option<String>,
|
||||
pub nym_api_urls: Option<Vec<ApiUrl>>,
|
||||
pub nym_vpn_api_urls: Option<Vec<ApiUrl>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)]
|
||||
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||
pub struct ApiUrl {
|
||||
/// Expects a string formatted Url
|
||||
///
|
||||
/// see https://docs.rs/url/latest/url/struct.Url.html
|
||||
pub url: String,
|
||||
/// Optional alternative equivalent hostnames. Each entry must parse as valid Host
|
||||
///
|
||||
/// see https://docs.rs/url/latest/url/enum.Host.html
|
||||
pub front_hosts: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
pub struct ApiUrlConst<'a> {
|
||||
pub url: &'a str,
|
||||
pub front_hosts: Option<&'a [&'a str]>,
|
||||
}
|
||||
|
||||
impl From<ApiUrlConst<'_>> for ApiUrl {
|
||||
fn from(value: ApiUrlConst) -> Self {
|
||||
ApiUrl {
|
||||
url: value.url.to_string(),
|
||||
front_hosts: value
|
||||
.front_hosts
|
||||
.map(|slice| slice.iter().map(|s| s.to_string()).collect()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// by default we assume the same defaults as mainnet, i.e. same prefixes and denoms
|
||||
@@ -98,8 +67,6 @@ impl NymNetworkDetails {
|
||||
contracts: Default::default(),
|
||||
explorer_api: Default::default(),
|
||||
nym_vpn_api_url: Default::default(),
|
||||
nym_api_urls: Default::default(),
|
||||
nym_vpn_api_urls: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,8 +154,6 @@ impl NymNetworkDetails {
|
||||
},
|
||||
explorer_api: parse_optional_str(mainnet::EXPLORER_API),
|
||||
nym_vpn_api_url: parse_optional_str(mainnet::NYM_VPN_API),
|
||||
nym_api_urls: None,
|
||||
nym_vpn_api_urls: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
[package]
|
||||
name = "nym-psq-kkt"
|
||||
version = "0.1.0"
|
||||
authors = ["Georgio Nicolas <georgio@nymtech.net>"]
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
arc-swap = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
pin-project = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
snow = { workspace = true }
|
||||
strum = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
|
||||
# internal
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric", "serde"]}
|
||||
|
||||
libcrux-traits = { git = "https://github.com/cryspen/libcrux" }
|
||||
libcrux-kem = { git = "https://github.com/cryspen/libcrux" }
|
||||
libcrux-psq = { git = "https://github.com/cryspen/libcrux", features = ["test-utils"] }
|
||||
rand = "0.9.1"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = {workspace = true}
|
||||
|
||||
[[bench]]
|
||||
name = "benches"
|
||||
harness = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,135 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use libcrux_psq::impls::MlKem768;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_psq_kkt::kkt::{
|
||||
KKTInitiator, KKTResponder, KKT_REQ_LEN, KKT_RES_LEN_MLKEM768, KKT_TAG_LEN,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
|
||||
pub fn gen_ed25519_keypair(c: &mut Criterion) {
|
||||
c.bench_function("Generate Ed25519 Keypair", |b| {
|
||||
b.iter(|| {
|
||||
let mut s: [u8; 32] = [0u8; 32];
|
||||
rand::rng().fill_bytes(&mut s);
|
||||
ed25519::KeyPair::from_secret(s, 0)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn gen_mlkem768_keypair(c: &mut Criterion) {
|
||||
c.bench_function("Generate MlKem768 Keypair", |b| {
|
||||
b.iter(|| {
|
||||
libcrux_kem::key_gen(libcrux_kem::Algorithm::MlKem768, &mut rand::rng()).unwrap()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn kkt_benchmark(c: &mut Criterion) {
|
||||
// generate ed25519 keys
|
||||
let mut secret_initiator: [u8; 32] = [0u8; 32];
|
||||
rand::rng().fill_bytes(&mut secret_initiator);
|
||||
let initiator_ed25519_keypair = ed25519::KeyPair::from_secret(secret_initiator, 0);
|
||||
|
||||
let mut secret_responder: [u8; 32] = [0u8; 32];
|
||||
rand::rng().fill_bytes(&mut secret_responder);
|
||||
let responder_ed25519_keypair = ed25519::KeyPair::from_secret(secret_responder, 1);
|
||||
|
||||
// generate kem keypair
|
||||
let (_, responder_kem_public_key) =
|
||||
libcrux_kem::key_gen(libcrux_kem::Algorithm::MlKem768, &mut rand::rng()).unwrap();
|
||||
|
||||
// initialize parties
|
||||
let initiator: KKTInitiator<MlKem768> =
|
||||
KKTInitiator::init(initiator_ed25519_keypair.private_key());
|
||||
|
||||
c.bench_function("Initialize Initiator", |b| {
|
||||
b.iter(|| KKTInitiator::<MlKem768>::init(initiator_ed25519_keypair.private_key()));
|
||||
});
|
||||
|
||||
let responder: KKTResponder<MlKem768> = KKTResponder::init(
|
||||
responder_ed25519_keypair.private_key(),
|
||||
&responder_kem_public_key,
|
||||
);
|
||||
|
||||
c.bench_function("Initialize Responder", |b| {
|
||||
b.iter(|| {
|
||||
KKTResponder::init(
|
||||
responder_ed25519_keypair.private_key(),
|
||||
&responder_kem_public_key,
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
// create buffers
|
||||
let mut request_buffer: [u8; KKT_REQ_LEN] = [0u8; KKT_REQ_LEN];
|
||||
let mut response_buffer: [u8; KKT_RES_LEN_MLKEM768] = [0u8; KKT_RES_LEN_MLKEM768];
|
||||
let mut tag_buffer: [u8; KKT_TAG_LEN] = [0u8; KKT_TAG_LEN];
|
||||
|
||||
c.bench_function("Initiator: Generate Request", |b| {
|
||||
b.iter(|| {
|
||||
initiator.request_kem_pk(&mut request_buffer, &mut tag_buffer);
|
||||
});
|
||||
});
|
||||
|
||||
// generate request
|
||||
initiator.request_kem_pk(&mut request_buffer, &mut tag_buffer);
|
||||
|
||||
c.bench_function("Responder: Ingest Request and Generate Response", |b| {
|
||||
b.iter(|| {
|
||||
responder
|
||||
.respond_kem_pk(
|
||||
&mut response_buffer,
|
||||
initiator_ed25519_keypair.public_key(),
|
||||
&request_buffer,
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
});
|
||||
|
||||
// ingest request, generate response
|
||||
responder
|
||||
.respond_kem_pk(
|
||||
&mut response_buffer,
|
||||
initiator_ed25519_keypair.public_key(),
|
||||
&request_buffer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
c.bench_function("Initiator: Ingest Response and Store Key", |b| {
|
||||
b.iter(|| {
|
||||
let _ = initiator
|
||||
.ingest_response_kem_pk::<MlKem768>(
|
||||
&response_buffer,
|
||||
&tag_buffer,
|
||||
responder_ed25519_keypair.public_key(),
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
});
|
||||
|
||||
// ingest response
|
||||
let received_responder_key = initiator
|
||||
.ingest_response_kem_pk::<MlKem768>(
|
||||
&response_buffer,
|
||||
&tag_buffer,
|
||||
responder_ed25519_keypair.public_key(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// check if the public key received is the same one that we generated at the start
|
||||
assert_eq!(
|
||||
responder_kem_public_key.encode(),
|
||||
received_responder_key.encode()
|
||||
);
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
gen_ed25519_keypair,
|
||||
gen_mlkem768_keypair,
|
||||
kkt_benchmark
|
||||
);
|
||||
criterion_main!(benches);
|
||||
@@ -1,66 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PSQError {
|
||||
#[error("encountered a decryption error")]
|
||||
DecryptionError,
|
||||
|
||||
#[error("encountered a KEM error")]
|
||||
KEMError,
|
||||
|
||||
#[error("encountered a PSQ error")]
|
||||
PSQError,
|
||||
|
||||
#[error("encountered an IO error: {0}")]
|
||||
IoError(#[from] io::Error),
|
||||
|
||||
#[error("Incorrect state")]
|
||||
IncorrectStateError,
|
||||
|
||||
#[error("Handshake did not complete")]
|
||||
HandshakeError,
|
||||
|
||||
#[error("Unknown noise version")]
|
||||
UnknownVersion,
|
||||
|
||||
#[error("Handshake timeout")]
|
||||
HandshakeTimeout(#[from] tokio::time::error::Elapsed),
|
||||
}
|
||||
|
||||
impl From<libcrux_kem::Error> for PSQError {
|
||||
fn from(err: libcrux_kem::Error) -> Self {
|
||||
match err {
|
||||
// Error::Decrypt => PSQError::DecryptionError,
|
||||
err => PSQError::KEMError,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<libcrux_psq::Error> for PSQError {
|
||||
fn from(err: libcrux_psq::Error) -> Self {
|
||||
match err {
|
||||
// Error::Decrypt => PSQError::DecryptionError,
|
||||
err => PSQError::PSQError,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum KKTError {
|
||||
#[error("Signature constructor error")]
|
||||
SigConstructorError,
|
||||
|
||||
#[error("Signature verification error")]
|
||||
SigVerifError,
|
||||
// #[error("Protocol did not complete")]
|
||||
// ProtocolError,
|
||||
|
||||
// #[error("encountered an IO error: {0}")]
|
||||
// IoError(#[from] io::Error),
|
||||
|
||||
// #[error("Handshake timeout")]
|
||||
// HandshakeTimeout(#[from] tokio::time::error::Elapsed),
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use rand::prelude::*;
|
||||
|
||||
use libcrux_kem::MlKem768PublicKey;
|
||||
use libcrux_psq::{impls::MlKem768, traits::PSQ};
|
||||
use libcrux_traits::kem::KEM;
|
||||
|
||||
use crate::error::KKTError;
|
||||
|
||||
const REQ_STR: &[u8] = "KEM_REQ".as_bytes();
|
||||
const RES_STR: &[u8] = "KEM_RES".as_bytes();
|
||||
|
||||
const REQ_LEN: usize = REQ_STR.len() + KKT_TAG_LEN;
|
||||
const RES_LEN_MLKEM768: usize = REQ_LEN + MlKem768PublicKey::len();
|
||||
|
||||
pub const KKT_TAG_LEN: usize = 16;
|
||||
pub const KKT_REQ_LEN: usize = REQ_LEN + ed25519::SIGNATURE_LENGTH;
|
||||
pub const KKT_RES_LEN_MLKEM768: usize = RES_LEN_MLKEM768 + ed25519::SIGNATURE_LENGTH;
|
||||
|
||||
pub struct KKTInitiator<'a, T: PSQ> {
|
||||
signing_key: &'a ed25519::PrivateKey,
|
||||
_t: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: PSQ> KKTInitiator<'a, T> {
|
||||
pub fn init(signing_key: &'a ed25519::PrivateKey) -> Self {
|
||||
Self {
|
||||
signing_key,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
pub fn request_kem_pk(
|
||||
&self,
|
||||
request_buffer: &mut [u8; KKT_REQ_LEN],
|
||||
tag_buffer: &mut [u8; KKT_TAG_LEN],
|
||||
) {
|
||||
// request_buffer[0..7] <- request string
|
||||
request_buffer[0..REQ_STR.len()].copy_from_slice(REQ_STR);
|
||||
|
||||
// tag_buffer <- generate tag
|
||||
rand::rng().fill_bytes(tag_buffer);
|
||||
|
||||
// request_buffer[7..23] <- tag
|
||||
request_buffer[REQ_STR.len()..REQ_LEN].copy_from_slice(tag_buffer);
|
||||
|
||||
// sig <- Sign request_buffer[0..23]
|
||||
let sig = self.signing_key.sign(&request_buffer[0..REQ_LEN]);
|
||||
|
||||
// request_buffer[23..87] <- sig
|
||||
request_buffer[REQ_LEN..].copy_from_slice(&sig.to_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> KKTInitiator<'a, MlKem768> {
|
||||
pub fn ingest_response_kem_pk<T: PSQ>(
|
||||
&self,
|
||||
response: &[u8],
|
||||
tag: &[u8; KKT_TAG_LEN],
|
||||
responder_verification_key: &ed25519::PublicKey,
|
||||
) -> Result<libcrux_kem::PublicKey, KKTError> {
|
||||
// TODO: Refactor asserts into errors
|
||||
// Check size of message
|
||||
assert_eq!(response.len(), KKT_RES_LEN_MLKEM768);
|
||||
// Check if the response string is there at the start of the message
|
||||
assert_eq!(&response[0..RES_STR.len()], RES_STR);
|
||||
// Check if the tag is the one we expect
|
||||
assert_eq!(
|
||||
&response[RES_STR.len()..RES_STR.len() + KKT_TAG_LEN],
|
||||
&tag[..]
|
||||
);
|
||||
|
||||
// Attempt to reconstruct a signature from the received bytes
|
||||
match ed25519::Signature::from_bytes(&response[RES_LEN_MLKEM768..]) {
|
||||
Ok(sig) => {
|
||||
// Attempt to verify signature
|
||||
match responder_verification_key.verify(&response[0..RES_LEN_MLKEM768], &sig) {
|
||||
Ok(()) => {
|
||||
// Extract key from bytes
|
||||
// (has to be an owned sized array, unless I'm missing some function that works with slices)
|
||||
let mut key_bytes: [u8; MlKem768PublicKey::len()] =
|
||||
[0u8; MlKem768PublicKey::len()];
|
||||
key_bytes.copy_from_slice(
|
||||
&response[RES_STR.len() + KKT_TAG_LEN..RES_LEN_MLKEM768],
|
||||
);
|
||||
|
||||
// Create key from bytes and return it
|
||||
Ok(libcrux_kem::PublicKey::MlKem768(MlKem768PublicKey::from(
|
||||
key_bytes,
|
||||
)))
|
||||
}
|
||||
Err(_) => Err(KKTError::SigVerifError),
|
||||
}
|
||||
}
|
||||
Err(_) => Err(KKTError::SigConstructorError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KKTResponder<'a, T: PSQ> {
|
||||
signing_key: &'a ed25519::PrivateKey,
|
||||
kem_public_key: &'a <T::InnerKEM as KEM>::EncapsulationKey,
|
||||
}
|
||||
|
||||
impl<'a> KKTResponder<'a, MlKem768> {
|
||||
pub fn init(
|
||||
signing_key: &'a ed25519::PrivateKey,
|
||||
kem_public_key: &'a libcrux_kem::PublicKey,
|
||||
) -> Self {
|
||||
Self {
|
||||
signing_key,
|
||||
kem_public_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn respond_kem_pk(
|
||||
&self,
|
||||
response_buffer: &mut [u8; KKT_RES_LEN_MLKEM768],
|
||||
initiator_verification_key: &ed25519::PublicKey,
|
||||
request: &[u8],
|
||||
) -> Result<(), KKTError> {
|
||||
// TODO: Refactor asserts into errors
|
||||
// Check request size
|
||||
assert_eq!(request.len(), KKT_REQ_LEN);
|
||||
// Check request string
|
||||
assert_eq!(&request[0..REQ_STR.len()], REQ_STR);
|
||||
|
||||
match ed25519::Signature::from_bytes(&request[REQ_LEN..]) {
|
||||
Ok(sig) => match initiator_verification_key.verify(&request[0..REQ_LEN], &sig) {
|
||||
Ok(()) => {
|
||||
// response_buffer[0..7] <- RES_STR (7 bytes)
|
||||
response_buffer[0..RES_STR.len()].copy_from_slice(RES_STR);
|
||||
|
||||
// response_buffer[7..23] <- tag (16 bytes, sent by initiator)
|
||||
response_buffer[RES_STR.len()..REQ_LEN]
|
||||
.copy_from_slice(&request[REQ_STR.len()..REQ_LEN]);
|
||||
|
||||
// response_buffer[23..1207] <- MlKem768 Public Key (1184 bytes)
|
||||
response_buffer[REQ_LEN..RES_LEN_MLKEM768]
|
||||
.copy_from_slice(self.kem_public_key.encode().as_slice());
|
||||
|
||||
// sign response_buffer[0..1207]
|
||||
let sig = self.signing_key.sign(&response_buffer[0..RES_LEN_MLKEM768]);
|
||||
|
||||
// response_buffer[1207..1271] <- signature (64 bytes)
|
||||
response_buffer[RES_LEN_MLKEM768..].copy_from_slice(&sig.to_bytes());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(_) => Err(KKTError::SigConstructorError),
|
||||
},
|
||||
Err(_) => Err(KKTError::SigVerifError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use crate::kkt::{KKT_REQ_LEN, KKT_RES_LEN_MLKEM768, KKT_TAG_LEN};
|
||||
|
||||
use super::{KKTInitiator, KKTResponder};
|
||||
use libcrux_psq::impls::MlKem768;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use rand::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_kkt_e2e() {
|
||||
// generate ed25519 keys
|
||||
let mut secret_initiator: [u8; 32] = [0u8; 32];
|
||||
rand::rng().fill_bytes(&mut secret_initiator);
|
||||
let initiator_ed25519_keypair = ed25519::KeyPair::from_secret(secret_initiator, 0);
|
||||
|
||||
let mut secret_responder: [u8; 32] = [0u8; 32];
|
||||
rand::rng().fill_bytes(&mut secret_responder);
|
||||
let responder_ed25519_keypair = ed25519::KeyPair::from_secret(secret_responder, 1);
|
||||
|
||||
// generate kem keypair
|
||||
let (_, responder_kem_public_key) =
|
||||
libcrux_kem::key_gen(libcrux_kem::Algorithm::MlKem768, &mut rand::rng()).unwrap();
|
||||
|
||||
// initialize parties
|
||||
let initiator: KKTInitiator<MlKem768> =
|
||||
KKTInitiator::init(initiator_ed25519_keypair.private_key());
|
||||
|
||||
let responder: KKTResponder<MlKem768> = KKTResponder::init(
|
||||
responder_ed25519_keypair.private_key(),
|
||||
&responder_kem_public_key,
|
||||
);
|
||||
|
||||
// create buffers
|
||||
let mut request_buffer: [u8; KKT_REQ_LEN] = [0u8; KKT_REQ_LEN];
|
||||
let mut response_buffer: [u8; KKT_RES_LEN_MLKEM768] = [0u8; KKT_RES_LEN_MLKEM768];
|
||||
let mut tag_buffer: [u8; KKT_TAG_LEN] = [0u8; KKT_TAG_LEN];
|
||||
|
||||
// generate request
|
||||
initiator.request_kem_pk(&mut request_buffer, &mut tag_buffer);
|
||||
|
||||
// ingest request, generate response
|
||||
responder
|
||||
.respond_kem_pk(
|
||||
&mut response_buffer,
|
||||
initiator_ed25519_keypair.public_key(),
|
||||
&request_buffer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// ingest response
|
||||
let received_responder_key = initiator
|
||||
.ingest_response_kem_pk::<MlKem768>(
|
||||
&response_buffer,
|
||||
&tag_buffer,
|
||||
responder_ed25519_keypair.public_key(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// check if the public key received is the same one that we generated at the start
|
||||
assert_eq!(
|
||||
responder_kem_public_key.encode(),
|
||||
received_responder_key.encode()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod error;
|
||||
pub mod kkt;
|
||||
pub mod psq;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use libcrux_psq::impls::MlKem768;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use rand::prelude::*;
|
||||
|
||||
use crate::{
|
||||
kkt::{KKTInitiator, KKTResponder, KKT_REQ_LEN, KKT_RES_LEN_MLKEM768, KKT_TAG_LEN},
|
||||
psq::{PSQInitiator, PSQResponder},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_kkt_psq_e2e() {
|
||||
let mut rng = rand::rng();
|
||||
|
||||
// generate ed25519 keys
|
||||
let mut secret_initiator: [u8; 32] = [0u8; 32];
|
||||
rng.fill_bytes(&mut secret_initiator);
|
||||
let initiator_ed25519_keypair = ed25519::KeyPair::from_secret(secret_initiator, 0);
|
||||
|
||||
let mut secret_responder: [u8; 32] = [0u8; 32];
|
||||
rng.fill_bytes(&mut secret_responder);
|
||||
let responder_ed25519_keypair = ed25519::KeyPair::from_secret(secret_responder, 1);
|
||||
|
||||
// generate kem keypair
|
||||
let (responder_kem_private_key, responder_kem_public_key) =
|
||||
libcrux_kem::key_gen(libcrux_kem::Algorithm::MlKem768, &mut rng).unwrap();
|
||||
|
||||
// initialize parties
|
||||
let kkt_initiator: KKTInitiator<MlKem768> =
|
||||
KKTInitiator::init(initiator_ed25519_keypair.private_key());
|
||||
|
||||
let kkt_responder: KKTResponder<MlKem768> = KKTResponder::init(
|
||||
responder_ed25519_keypair.private_key(),
|
||||
&responder_kem_public_key,
|
||||
);
|
||||
|
||||
// create buffers
|
||||
let mut request_buffer: [u8; KKT_REQ_LEN] = [0u8; KKT_REQ_LEN];
|
||||
let mut response_buffer: [u8; KKT_RES_LEN_MLKEM768] = [0u8; KKT_RES_LEN_MLKEM768];
|
||||
let mut tag_buffer: [u8; KKT_TAG_LEN] = [0u8; KKT_TAG_LEN];
|
||||
|
||||
// generate request
|
||||
kkt_initiator.request_kem_pk(&mut request_buffer, &mut tag_buffer);
|
||||
|
||||
// ingest request, generate response
|
||||
kkt_responder
|
||||
.respond_kem_pk(
|
||||
&mut response_buffer,
|
||||
initiator_ed25519_keypair.public_key(),
|
||||
&request_buffer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// ingest response
|
||||
let received_responder_key = kkt_initiator
|
||||
.ingest_response_kem_pk::<MlKem768>(
|
||||
&response_buffer,
|
||||
&tag_buffer,
|
||||
responder_ed25519_keypair.public_key(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// check if the public key received is the same one that we generated at the start
|
||||
assert_eq!(
|
||||
responder_kem_public_key.encode(),
|
||||
received_responder_key.encode()
|
||||
);
|
||||
|
||||
let mut psq_initiator: PSQInitiator<MlKem768> =
|
||||
PSQInitiator::init(&initiator_ed25519_keypair);
|
||||
let psq_responder: PSQResponder<MlKem768> =
|
||||
PSQResponder::init(&responder_kem_private_key, &responder_kem_public_key);
|
||||
|
||||
let initiator_psq_msg = psq_initiator
|
||||
.initiator_message(&mut rng, &received_responder_key)
|
||||
.unwrap();
|
||||
|
||||
let (responder_psk, responder_psq_msg) = psq_responder
|
||||
.responder_msg(initiator_ed25519_keypair.public_key(), &initiator_psq_msg)
|
||||
.unwrap();
|
||||
|
||||
let initiator_psk = psq_initiator.finalize(&responder_psq_msg).unwrap();
|
||||
|
||||
assert_eq!(initiator_psk, responder_psk);
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::{marker::PhantomData, time::Duration};
|
||||
|
||||
use libcrux_psq::{
|
||||
cred::Ed25519,
|
||||
psk_registration::{Initiator, InitiatorMsg, Responder, ResponderMsg},
|
||||
traits::PSQ,
|
||||
};
|
||||
|
||||
use libcrux_traits::kem::KEM;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use rand::CryptoRng;
|
||||
|
||||
use crate::error::PSQError;
|
||||
|
||||
pub const PSK_LENGTH: usize = 32;
|
||||
|
||||
pub struct PSQInitiator<'a, T: PSQ> {
|
||||
signing_keypair: &'a ed25519::KeyPair,
|
||||
state: Option<Initiator>,
|
||||
_t: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: PSQ> PSQInitiator<'a, T> {
|
||||
pub fn init(signing_keypair: &'a ed25519::KeyPair) -> Self {
|
||||
Self {
|
||||
signing_keypair,
|
||||
state: None,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initiator_message(
|
||||
&mut self,
|
||||
rng: &mut impl CryptoRng,
|
||||
responder_kem_public_key: &'a <T::InnerKEM as KEM>::EncapsulationKey,
|
||||
) -> Result<InitiatorMsg<T::InnerKEM>, PSQError> {
|
||||
let (state, message) = Initiator::send_initial_message::<Ed25519, T>(
|
||||
&[0u8],
|
||||
Duration::from_secs(3600),
|
||||
&responder_kem_public_key,
|
||||
&self.signing_keypair.private_key().to_bytes(),
|
||||
&self.signing_keypair.public_key().to_bytes(),
|
||||
rng,
|
||||
)?;
|
||||
self.state = Some(state);
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
pub fn finalize(&self, responder_message: &ResponderMsg) -> Result<[u8; PSK_LENGTH], PSQError> {
|
||||
match &self.state {
|
||||
Some(state) => match state.complete_handshake(responder_message) {
|
||||
Ok(psk) => Ok(psk.psk),
|
||||
Err(err) => Err(PSQError::KEMError),
|
||||
},
|
||||
None => Err(PSQError::IncorrectStateError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PSQResponder<'a, T: PSQ> {
|
||||
kem_private_key: &'a <T::InnerKEM as KEM>::DecapsulationKey,
|
||||
kem_public_key: &'a <T::InnerKEM as KEM>::EncapsulationKey,
|
||||
_t: PhantomData<T>,
|
||||
}
|
||||
impl<'a, T: PSQ> PSQResponder<'a, T> {
|
||||
pub fn init(
|
||||
kem_private_key: &'a <T::InnerKEM as KEM>::DecapsulationKey,
|
||||
kem_public_key: &'a <T::InnerKEM as KEM>::EncapsulationKey,
|
||||
) -> Self {
|
||||
Self {
|
||||
kem_private_key,
|
||||
kem_public_key,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn responder_msg(
|
||||
&self,
|
||||
initiator_verification_key: &'a ed25519::PublicKey,
|
||||
initiator_message: &InitiatorMsg<T::InnerKEM>,
|
||||
) -> Result<([u8; PSK_LENGTH], ResponderMsg), PSQError> {
|
||||
let (registered_psk, responder_msg) = Responder::send::<Ed25519, T>(
|
||||
&[0u8],
|
||||
Duration::from_secs(3600),
|
||||
&[0u8],
|
||||
self.kem_public_key,
|
||||
self.kem_private_key,
|
||||
&initiator_verification_key.to_bytes(),
|
||||
initiator_message,
|
||||
)?;
|
||||
|
||||
Ok((registered_psk.psk, responder_msg))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use libcrux_psq::impls::{MlKem768, XWingKemDraft06, X25519};
|
||||
use libcrux_psq::traits::PSQ;
|
||||
use libcrux_traits::kem::KEM;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use rand::prelude::*;
|
||||
|
||||
use super::{PSQInitiator, PSQResponder};
|
||||
|
||||
fn test_helper<T: PSQ>(
|
||||
rng: &mut impl CryptoRng,
|
||||
responder_kem_private_key: <T::InnerKEM as KEM>::DecapsulationKey,
|
||||
responder_kem_public_key: <T::InnerKEM as KEM>::EncapsulationKey,
|
||||
) {
|
||||
// generate ed25519 keys
|
||||
|
||||
let mut secret_initiator: [u8; 32] = [0u8; 32];
|
||||
rng.fill_bytes(&mut secret_initiator);
|
||||
let initiator_ed25519_keypair = ed25519::KeyPair::from_secret(secret_initiator, 0);
|
||||
|
||||
let mut initiator: PSQInitiator<T> = PSQInitiator::init(&initiator_ed25519_keypair);
|
||||
let responder: PSQResponder<T> =
|
||||
PSQResponder::init(&responder_kem_private_key, &responder_kem_public_key);
|
||||
|
||||
let initiator_msg = initiator
|
||||
.initiator_message(rng, &responder_kem_public_key)
|
||||
.unwrap();
|
||||
|
||||
let (responder_psk, responder_msg) = responder
|
||||
.responder_msg(initiator_ed25519_keypair.public_key(), &initiator_msg)
|
||||
.unwrap();
|
||||
|
||||
let initiator_psk = initiator.finalize(&responder_msg).unwrap();
|
||||
|
||||
assert_eq!(initiator_psk, responder_psk);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_psq_e2e_mlkem() {
|
||||
let mut rng = rand::rng();
|
||||
|
||||
// generate mlkem keypair
|
||||
let (responder_kem_private_key, responder_kem_public_key) =
|
||||
libcrux_kem::key_gen(libcrux_kem::Algorithm::MlKem768, &mut rng).unwrap();
|
||||
|
||||
test_helper::<MlKem768>(
|
||||
&mut rng,
|
||||
responder_kem_private_key,
|
||||
responder_kem_public_key,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_psq_e2e_xwing() {
|
||||
let mut rng = rand::rng();
|
||||
|
||||
// generate mlkem keypair
|
||||
let (responder_kem_private_key, responder_kem_public_key) =
|
||||
libcrux_kem::key_gen(libcrux_kem::Algorithm::XWingKemDraft06, &mut rand::rng())
|
||||
.unwrap();
|
||||
|
||||
test_helper::<XWingKemDraft06>(
|
||||
&mut rng,
|
||||
responder_kem_private_key,
|
||||
responder_kem_public_key,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_psq_e2e_dhkem() {
|
||||
let mut rng = rand::rng();
|
||||
|
||||
// generate mlkem keypair
|
||||
let (responder_kem_private_key, responder_kem_public_key) =
|
||||
libcrux_kem::key_gen(libcrux_kem::Algorithm::X25519, &mut rand::rng()).unwrap();
|
||||
|
||||
test_helper::<X25519>(
|
||||
&mut rng,
|
||||
responder_kem_private_key,
|
||||
responder_kem_public_key,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -22,5 +22,12 @@ tokio-util = { workspace = true, features = ["codec"] }
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-noise-keys = { path = "keys" }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
rand_chacha = { workspace = true }
|
||||
nym-crypto = { path = "../crypto", features = ["rand"] }
|
||||
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -8,22 +8,25 @@ use serde::{Deserialize, Serialize};
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(from = "u8", into = "u8")]
|
||||
pub enum NoiseVersion {
|
||||
V1 = 1,
|
||||
Unknown, //Implies a newer version we don't know
|
||||
V1,
|
||||
Unknown(u8), //Implies a newer version we don't know
|
||||
}
|
||||
|
||||
impl From<u8> for NoiseVersion {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
1 => NoiseVersion::V1,
|
||||
_ => NoiseVersion::Unknown,
|
||||
other => NoiseVersion::Unknown(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NoiseVersion> for u8 {
|
||||
fn from(version: NoiseVersion) -> Self {
|
||||
version as u8
|
||||
match version {
|
||||
NoiseVersion::V1 => 1,
|
||||
NoiseVersion::Unknown(other) => other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +34,7 @@ impl From<NoiseVersion> for u8 {
|
||||
pub struct VersionedNoiseKey {
|
||||
#[schemars(with = "u8")]
|
||||
#[schema(value_type = u8)]
|
||||
pub version: NoiseVersion,
|
||||
pub supported_version: NoiseVersion,
|
||||
|
||||
#[schemars(with = "String")]
|
||||
#[serde(with = "bs58_x25519_pubkey")]
|
||||
|
||||
@@ -13,17 +13,19 @@ use nym_crypto::asymmetric::x25519;
|
||||
use nym_noise_keys::{NoiseVersion, VersionedNoiseKey};
|
||||
use snow::params::NoiseParams;
|
||||
|
||||
use strum::EnumIter;
|
||||
use strum::{EnumIter, FromRepr};
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, EnumIter)]
|
||||
#[derive(Default, Debug, Clone, Copy, EnumIter, FromRepr, Eq, PartialEq)]
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
pub enum NoisePattern {
|
||||
#[default]
|
||||
XKpsk3,
|
||||
IKpsk2,
|
||||
XKpsk3 = 1,
|
||||
IKpsk2 = 2,
|
||||
}
|
||||
|
||||
impl NoisePattern {
|
||||
pub(crate) fn as_str(&self) -> &'static str {
|
||||
pub(crate) const fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::XKpsk3 => "Noise_XKpsk3_25519_AESGCM_SHA256",
|
||||
Self::IKpsk2 => "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s", //Wireguard handshake (not exactly though)
|
||||
@@ -79,7 +81,7 @@ impl NoiseNetworkView {
|
||||
pub fn swap_view(&self, new: HashMap<SocketAddr, VersionedNoiseKey>) {
|
||||
let noise_support = new
|
||||
.iter()
|
||||
.map(|(s_addr, key)| (s_addr.ip(), key.version))
|
||||
.map(|(s_addr, key)| (s_addr.ip(), key.supported_version))
|
||||
.collect::<HashMap<_, _>>();
|
||||
self.keys.inner.store(Arc::new(new));
|
||||
self.support.inner.store(Arc::new(noise_support));
|
||||
|
||||
@@ -4,30 +4,21 @@
|
||||
use std::io;
|
||||
|
||||
use pin_project::pin_project;
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite},
|
||||
net::TcpStream,
|
||||
};
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use crate::stream::NoiseStream;
|
||||
|
||||
//SW once plain TCP support is dropped, this whole enum can be dropped, and we can only propagate NoiseStream
|
||||
#[pin_project(project = ConnectionProj)]
|
||||
pub enum Connection {
|
||||
Tcp(#[pin] TcpStream),
|
||||
Noise(#[pin] NoiseStream),
|
||||
pub enum Connection<C> {
|
||||
Raw(#[pin] C),
|
||||
Noise(#[pin] Box<NoiseStream<C>>),
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub fn peer_addr(&self) -> Result<std::net::SocketAddr, io::Error> {
|
||||
match self {
|
||||
Self::Noise(stream) => stream.peer_addr(),
|
||||
Self::Tcp(stream) => stream.peer_addr(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for Connection {
|
||||
impl<C> AsyncRead for Connection<C>
|
||||
where
|
||||
C: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
fn poll_read(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
@@ -35,12 +26,15 @@ impl AsyncRead for Connection {
|
||||
) -> std::task::Poll<io::Result<()>> {
|
||||
match self.project() {
|
||||
ConnectionProj::Noise(stream) => stream.poll_read(cx, buf),
|
||||
ConnectionProj::Tcp(stream) => stream.poll_read(cx, buf),
|
||||
ConnectionProj::Raw(stream) => stream.poll_read(cx, buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for Connection {
|
||||
impl<C> AsyncWrite for Connection<C>
|
||||
where
|
||||
C: AsyncWrite + AsyncRead + Unpin,
|
||||
{
|
||||
fn poll_write(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
@@ -48,7 +42,7 @@ impl AsyncWrite for Connection {
|
||||
) -> std::task::Poll<Result<usize, io::Error>> {
|
||||
match self.project() {
|
||||
ConnectionProj::Noise(stream) => stream.poll_write(cx, buf),
|
||||
ConnectionProj::Tcp(stream) => stream.poll_write(cx, buf),
|
||||
ConnectionProj::Raw(stream) => stream.poll_write(cx, buf),
|
||||
}
|
||||
}
|
||||
fn poll_flush(
|
||||
@@ -57,7 +51,7 @@ impl AsyncWrite for Connection {
|
||||
) -> std::task::Poll<Result<(), io::Error>> {
|
||||
match self.project() {
|
||||
ConnectionProj::Noise(stream) => stream.poll_flush(cx),
|
||||
ConnectionProj::Tcp(stream) => stream.poll_flush(cx),
|
||||
ConnectionProj::Raw(stream) => stream.poll_flush(cx),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +61,7 @@ impl AsyncWrite for Connection {
|
||||
) -> std::task::Poll<Result<(), io::Error>> {
|
||||
match self.project() {
|
||||
ConnectionProj::Noise(stream) => stream.poll_shutdown(cx),
|
||||
ConnectionProj::Tcp(stream) => stream.poll_shutdown(cx),
|
||||
ConnectionProj::Raw(stream) => stream.poll_shutdown(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_noise_keys::NoiseVersion;
|
||||
use snow::Error;
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
@@ -22,13 +23,64 @@ pub enum NoiseError {
|
||||
#[error("Handshake did not complete")]
|
||||
HandshakeError,
|
||||
|
||||
#[error("Unknown noise version")]
|
||||
UnknownVersion,
|
||||
#[error("unknown noise version (encoded value: {encoded})")]
|
||||
UnknownVersion { encoded: u8 },
|
||||
|
||||
#[error("unknown noise pattern (encoded value: {encoded})")]
|
||||
UnknownPattern { encoded: u8 },
|
||||
|
||||
#[error("unknown noise message type (encoded value: {encoded})")]
|
||||
UnknownMessageType { encoded: u8 },
|
||||
|
||||
#[error("failed to generate psk for requested version {noise_version}")]
|
||||
PskGenerationFailure { noise_version: u8 },
|
||||
|
||||
#[error("noise initiator attempted to use version v{noise_version} of the protocol - we don't know how to handle it")]
|
||||
UnknownVersionHandshake { noise_version: u8 },
|
||||
|
||||
#[error("noise initiator attempted to use an unexpected noise pattern. we're configured for {configured} while it requested {received}")]
|
||||
UnexpectedNoisePattern {
|
||||
configured: &'static str,
|
||||
received: &'static str,
|
||||
},
|
||||
|
||||
#[error("handshake version has unexpectedly changed. initial was {initial:?} and received {received:?}")]
|
||||
UnexpectedHandshakeVersion {
|
||||
initial: NoiseVersion,
|
||||
received: NoiseVersion,
|
||||
},
|
||||
|
||||
#[error("data packet version has unexpectedly changed. initial was {initial:?} and received {received:?}")]
|
||||
UnexpectedDataVersion {
|
||||
initial: NoiseVersion,
|
||||
received: NoiseVersion,
|
||||
},
|
||||
|
||||
#[error("received a non-handshake message during noise handshake")]
|
||||
NonHandshakeMessageReceived,
|
||||
|
||||
#[error("received a non-data message post noise handshake")]
|
||||
NonDataMessageReceived,
|
||||
|
||||
#[error("handshake message exceeded maximum size (got {size} bytes)")]
|
||||
HandshakeTooBig { size: usize },
|
||||
|
||||
#[error("noise message exceeded maximum size (got {size} bytes)")]
|
||||
DataTooBig { size: usize },
|
||||
|
||||
#[error("Handshake timeout")]
|
||||
HandshakeTimeout(#[from] tokio::time::error::Elapsed),
|
||||
}
|
||||
|
||||
impl NoiseError {
|
||||
pub(crate) fn naive_to_io_error(self) -> std::io::Error {
|
||||
match self {
|
||||
NoiseError::IoError(err) => err,
|
||||
other => std::io::Error::other(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for NoiseError {
|
||||
fn from(err: Error) -> Self {
|
||||
match err {
|
||||
|
||||
+67
-79
@@ -1,55 +1,63 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::NoiseConfig;
|
||||
use crate::connection::Connection;
|
||||
use crate::error::NoiseError;
|
||||
use crate::stream::NoiseStream;
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_noise_keys::NoiseVersion;
|
||||
use sha2::{Digest, Sha256};
|
||||
use snow::{error::Prerequisite, Error};
|
||||
use snow::error::Prerequisite;
|
||||
use snow::Error;
|
||||
use tokio::net::TcpStream;
|
||||
use tracing::*;
|
||||
use tracing::{error, warn};
|
||||
|
||||
pub mod config;
|
||||
pub mod connection;
|
||||
pub mod error;
|
||||
pub mod stream;
|
||||
|
||||
use crate::config::NoiseConfig;
|
||||
use crate::connection::Connection;
|
||||
use crate::error::NoiseError;
|
||||
use crate::stream::NoiseStreamBuilder;
|
||||
|
||||
const NOISE_PSK_PREFIX: &[u8] = b"NYMTECH_NOISE_dQw4w9WgXcQ";
|
||||
|
||||
pub const LATEST_NOISE_VERSION: NoiseVersion = NoiseVersion::V1;
|
||||
|
||||
fn generate_psk_v1(responder_pub_key: &x25519::PublicKey) -> [u8; 32] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(NOISE_PSK_PREFIX);
|
||||
hasher.update(responder_pub_key.to_bytes());
|
||||
hasher.finalize().into()
|
||||
}
|
||||
// TODO: this should be behind some trait because presumably, depending on the version,
|
||||
// other arguments would be needed
|
||||
mod psk_gen {
|
||||
use crate::error::NoiseError;
|
||||
use crate::stream::Psk;
|
||||
use crate::NOISE_PSK_PREFIX;
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_noise_keys::NoiseVersion;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
async fn upgrade_noise_initiator_v1(
|
||||
conn: TcpStream,
|
||||
config: &NoiseConfig,
|
||||
remote_pub_key: &x25519::PublicKey,
|
||||
) -> Result<Connection, NoiseError> {
|
||||
trace!("Perform Noise Handshake, initiator side");
|
||||
pub(crate) fn generate_psk(
|
||||
responder_pub_key: x25519::PublicKey,
|
||||
version: NoiseVersion,
|
||||
) -> Result<Psk, NoiseError> {
|
||||
match version {
|
||||
NoiseVersion::V1 => Ok(generate_psk_v1(responder_pub_key)),
|
||||
NoiseVersion::Unknown(noise_version) => {
|
||||
Err(NoiseError::PskGenerationFailure { noise_version })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let secret_hash = generate_psk_v1(remote_pub_key);
|
||||
let noise_stream = NoiseStream::new_initiator(conn, config, remote_pub_key, &secret_hash)?;
|
||||
|
||||
Ok(Connection::Noise(
|
||||
tokio::time::timeout(config.timeout, noise_stream.perform_handshake()).await??,
|
||||
))
|
||||
fn generate_psk_v1(responder_pub_key: x25519::PublicKey) -> [u8; 32] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(NOISE_PSK_PREFIX);
|
||||
hasher.update(responder_pub_key.to_bytes());
|
||||
hasher.finalize().into()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn upgrade_noise_initiator(
|
||||
conn: TcpStream,
|
||||
config: &NoiseConfig,
|
||||
) -> Result<Connection, NoiseError> {
|
||||
) -> Result<Connection<TcpStream>, NoiseError> {
|
||||
if config.unsafe_disabled {
|
||||
warn!("Noise is disabled in the config. Not attempting any handshake");
|
||||
return Ok(Connection::Tcp(conn));
|
||||
return Ok(Connection::Raw(conn));
|
||||
}
|
||||
|
||||
//Get init material
|
||||
@@ -58,46 +66,34 @@ pub async fn upgrade_noise_initiator(
|
||||
Error::Prereq(Prerequisite::RemotePublicKey)
|
||||
})?;
|
||||
|
||||
match config.get_noise_key(&responder_addr) {
|
||||
Some(key) => match key.version {
|
||||
NoiseVersion::V1 => upgrade_noise_initiator_v1(conn, config, &key.x25519_pubkey).await,
|
||||
// We're talking to a more recent node, but we can't adapt. Let's try to do our best and if it fails, it fails.
|
||||
// If that node sees we're older, it will try to adapt too.
|
||||
NoiseVersion::Unknown => {
|
||||
warn!("{responder_addr} is announcing an unknown version of Noise, we will still attempt our latest known version");
|
||||
upgrade_noise_initiator_v1(conn, config, &key.x25519_pubkey)
|
||||
.await
|
||||
.or(Err(NoiseError::UnknownVersion))
|
||||
}
|
||||
},
|
||||
None => {
|
||||
warn!("{responder_addr} can't speak Noise yet, falling back to TCP");
|
||||
Ok(Connection::Tcp(conn))
|
||||
let Some(key) = config.get_noise_key(&responder_addr) else {
|
||||
warn!("{responder_addr} can't speak Noise yet, falling back to TCP");
|
||||
return Ok(Connection::Raw(conn));
|
||||
};
|
||||
|
||||
let handshake_version = match key.supported_version {
|
||||
NoiseVersion::V1 => NoiseVersion::V1,
|
||||
|
||||
// We're talking to a more recent node, but we can't adapt. Let's try to do our best and if it fails, it fails.
|
||||
// If that node sees we're older, it will try to adapt too.
|
||||
NoiseVersion::Unknown(version) => {
|
||||
warn!("{responder_addr} is announcing an v{version} version of Noise that we don't know how to parse, we will attempt to downgrade to our current highest supported version");
|
||||
LATEST_NOISE_VERSION
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NoiseStreamBuilder::new(conn)
|
||||
.perform_initiator_handshake(config, handshake_version, key.x25519_pubkey)
|
||||
.await
|
||||
.map(|stream| Connection::Noise(Box::new(stream)))
|
||||
}
|
||||
|
||||
async fn upgrade_noise_responder_v1(
|
||||
conn: TcpStream,
|
||||
config: &NoiseConfig,
|
||||
) -> Result<Connection, NoiseError> {
|
||||
trace!("Perform Noise Handshake, responder side");
|
||||
|
||||
let secret_hash = generate_psk_v1(config.local_key.public_key());
|
||||
let noise_stream = NoiseStream::new_responder(conn, config, &secret_hash)?;
|
||||
|
||||
Ok(Connection::Noise(
|
||||
tokio::time::timeout(config.timeout, noise_stream.perform_handshake()).await??,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn upgrade_noise_responder(
|
||||
conn: TcpStream,
|
||||
config: &NoiseConfig,
|
||||
) -> Result<Connection, NoiseError> {
|
||||
) -> Result<Connection<TcpStream>, NoiseError> {
|
||||
if config.unsafe_disabled {
|
||||
warn!("Noise is disabled in the config. Not attempting any handshake");
|
||||
return Ok(Connection::Tcp(conn));
|
||||
return Ok(Connection::Raw(conn));
|
||||
}
|
||||
|
||||
//Get init material
|
||||
@@ -109,22 +105,14 @@ pub async fn upgrade_noise_responder(
|
||||
}
|
||||
};
|
||||
|
||||
// Port is random and we just need the support info
|
||||
match config.get_noise_support(initiator_addr.ip()) {
|
||||
None => {
|
||||
warn!("{initiator_addr} can't speak Noise yet, falling back to TCP",);
|
||||
Ok(Connection::Tcp(conn))
|
||||
}
|
||||
// responder's info on version is shaky, so ideally, initiator has to adapt.
|
||||
// if we are newer, it won't ba able to, so let's try to meet him on his ground.
|
||||
Some(LATEST_NOISE_VERSION) | Some(NoiseVersion::Unknown) => {
|
||||
// Node is announcing the same version as us, great or
|
||||
// Node is announcing a newer version than us, it should adapt to us though
|
||||
upgrade_noise_responder_v1(conn, config).await
|
||||
} //SW sample of code to allow backwards compatibility when we introduce new versions
|
||||
// Some(IntermediateNoiseVersion) => {
|
||||
// Node is announcing an older version, let's try to adapt
|
||||
// upgrade_noise_responder_Vwhatever
|
||||
// }
|
||||
}
|
||||
// if responder doesn't announce noise support, we fallback to tcp
|
||||
if config.get_noise_support(initiator_addr.ip()).is_none() {
|
||||
warn!("{initiator_addr} can't speak Noise yet, falling back to TCP",);
|
||||
return Ok(Connection::Raw(conn));
|
||||
};
|
||||
|
||||
NoiseStreamBuilder::new(conn)
|
||||
.perform_responder_handshake(config)
|
||||
.await
|
||||
.map(|stream| Connection::Noise(Box::new(stream)))
|
||||
}
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{config::NoiseConfig, error::NoiseError};
|
||||
use bytes::BytesMut;
|
||||
use futures::{Sink, SinkExt, Stream, StreamExt};
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use pin_project::pin_project;
|
||||
use snow::{Builder, HandshakeState, TransportState};
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::Poll;
|
||||
use std::{cmp::min, task::ready};
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite, ReadBuf},
|
||||
net::TcpStream,
|
||||
};
|
||||
use tokio_util::codec::{Framed, LengthDelimitedCodec};
|
||||
|
||||
const TAGLEN: usize = 16;
|
||||
const HANDSHAKE_MAX_LEN: usize = 1024; // using this constant to limit the handshake's buffer size
|
||||
|
||||
pub(crate) type Psk = [u8; 32];
|
||||
|
||||
/// Wrapper around a TcpStream
|
||||
#[pin_project]
|
||||
pub struct NoiseStream {
|
||||
#[pin]
|
||||
inner_stream: Framed<TcpStream, LengthDelimitedCodec>,
|
||||
handshake: Option<HandshakeState>,
|
||||
noise: Option<TransportState>,
|
||||
dec_buffer: BytesMut,
|
||||
}
|
||||
|
||||
impl NoiseStream {
|
||||
pub(crate) fn new_initiator(
|
||||
inner_stream: TcpStream,
|
||||
config: &NoiseConfig,
|
||||
remote_pub_key: &x25519::PublicKey,
|
||||
psk: &Psk,
|
||||
) -> Result<NoiseStream, NoiseError> {
|
||||
let handshake = Builder::new(config.pattern.as_noise_params())
|
||||
.local_private_key(config.local_key.private_key().as_bytes())
|
||||
.remote_public_key(&remote_pub_key.to_bytes())
|
||||
.psk(config.pattern.psk_position(), psk)
|
||||
.build_initiator()?;
|
||||
Ok(NoiseStream::new_inner(inner_stream, handshake))
|
||||
}
|
||||
|
||||
pub(crate) fn new_responder(
|
||||
inner_stream: TcpStream,
|
||||
config: &NoiseConfig,
|
||||
psk: &Psk,
|
||||
) -> Result<NoiseStream, NoiseError> {
|
||||
let handshake = Builder::new(config.pattern.as_noise_params())
|
||||
.local_private_key(config.local_key.private_key().as_bytes())
|
||||
.psk(config.pattern.psk_position(), psk)
|
||||
.build_responder()?;
|
||||
Ok(NoiseStream::new_inner(inner_stream, handshake))
|
||||
}
|
||||
|
||||
fn new_inner(inner_stream: TcpStream, handshake: HandshakeState) -> NoiseStream {
|
||||
NoiseStream {
|
||||
inner_stream: LengthDelimitedCodec::builder()
|
||||
.length_field_type::<u16>()
|
||||
.new_framed(inner_stream),
|
||||
handshake: Some(handshake),
|
||||
noise: None,
|
||||
dec_buffer: BytesMut::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn perform_handshake(mut self) -> Result<Self, NoiseError> {
|
||||
//Check if we are in the correct state
|
||||
let Some(mut handshake) = self.handshake.take() else {
|
||||
return Err(NoiseError::IncorrectStateError);
|
||||
};
|
||||
|
||||
while !handshake.is_handshake_finished() {
|
||||
if handshake.is_my_turn() {
|
||||
self.send_handshake_msg(&mut handshake).await?;
|
||||
} else {
|
||||
self.recv_handshake_msg(&mut handshake).await?;
|
||||
}
|
||||
}
|
||||
|
||||
self.noise = Some(handshake.into_transport_mode()?);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
async fn send_handshake_msg(
|
||||
&mut self,
|
||||
handshake: &mut HandshakeState,
|
||||
) -> Result<(), NoiseError> {
|
||||
let mut buf = BytesMut::zeroed(HANDSHAKE_MAX_LEN); // we're in the handshake, we can afford a smaller buffer
|
||||
let len = handshake.write_message(&[], &mut buf)?;
|
||||
buf.truncate(len);
|
||||
self.inner_stream.send(buf.into()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recv_handshake_msg(
|
||||
&mut self,
|
||||
handshake: &mut HandshakeState,
|
||||
) -> Result<(), NoiseError> {
|
||||
match self.inner_stream.next().await {
|
||||
Some(Ok(msg)) => {
|
||||
let mut buf = BytesMut::zeroed(HANDSHAKE_MAX_LEN); // we're in the handshake, we can afford a smaller buffer
|
||||
handshake.read_message(&msg, &mut buf)?;
|
||||
Ok(())
|
||||
}
|
||||
Some(Err(err)) => Err(NoiseError::IoError(err)),
|
||||
None => Err(NoiseError::HandshakeError),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peer_addr(&self) -> Result<std::net::SocketAddr, io::Error> {
|
||||
self.inner_stream.get_ref().peer_addr()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for NoiseStream {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
let projected_self = self.project();
|
||||
|
||||
let pending = match projected_self.inner_stream.poll_next(cx) {
|
||||
Poll::Pending => {
|
||||
//no new data, a return value of Poll::Pending means the waking is already scheduled
|
||||
//Nothing new to decrypt, only check if we can return something from dec_storage, happens after
|
||||
true
|
||||
}
|
||||
|
||||
Poll::Ready(Some(Ok(noise_msg))) => {
|
||||
// We have a new noise msg
|
||||
let mut dec_msg = BytesMut::zeroed(noise_msg.len() - TAGLEN);
|
||||
let len = match projected_self.noise {
|
||||
Some(transport_state) => {
|
||||
match transport_state.read_message(&noise_msg, &mut dec_msg) {
|
||||
Ok(len) => len,
|
||||
Err(_) => return Poll::Ready(Err(io::ErrorKind::InvalidInput.into())),
|
||||
}
|
||||
}
|
||||
None => return Poll::Ready(Err(io::ErrorKind::Other.into())),
|
||||
};
|
||||
projected_self.dec_buffer.extend(&dec_msg[..len]);
|
||||
false
|
||||
}
|
||||
|
||||
Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err)),
|
||||
|
||||
Poll::Ready(None) => {
|
||||
//Stream is done, we might still have data in the buffer though, happens afterwards
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
// Checking if there is something to return from the buffer
|
||||
let read_len = min(buf.remaining(), projected_self.dec_buffer.len());
|
||||
if read_len > 0 {
|
||||
buf.put_slice(&projected_self.dec_buffer.split_to(read_len));
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
|
||||
// buf.remaining == 0 or nothing in the buffer, we must return the value we had from the inner_stream
|
||||
if pending {
|
||||
//If we end up here, it means the previous poll_next was pending as well, hence waking is already scheduled
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for NoiseStream {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
let mut projected_self = self.project();
|
||||
|
||||
// returns on Poll::Pending and Poll:Ready(Err)
|
||||
ready!(projected_self.inner_stream.as_mut().poll_ready(cx))?;
|
||||
|
||||
// Ready to send, encrypting message
|
||||
let mut noise_buf = BytesMut::zeroed(buf.len() + TAGLEN);
|
||||
|
||||
let Ok(len) = (match projected_self.noise {
|
||||
Some(transport_state) => transport_state.write_message(buf, &mut noise_buf),
|
||||
None => return Poll::Ready(Err(io::ErrorKind::Other.into())),
|
||||
}) else {
|
||||
return Poll::Ready(Err(io::ErrorKind::InvalidInput.into()));
|
||||
};
|
||||
noise_buf.truncate(len);
|
||||
|
||||
// Tokio uses the same `start_send ` in their SinkWriter implementation. https://docs.rs/tokio-util/latest/src/tokio_util/io/sink_writer.rs.html#104
|
||||
match projected_self
|
||||
.inner_stream
|
||||
.as_mut()
|
||||
.start_send(noise_buf.into())
|
||||
{
|
||||
Ok(()) => Poll::Ready(Ok(buf.len())),
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
self.project().inner_stream.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
self.project().inner_stream.poll_close(cx)
|
||||
}
|
||||
}
|
||||
@@ -895,15 +895,39 @@ pub struct HostKeys {
|
||||
|
||||
pub current_x25519_sphinx_key: SphinxKey,
|
||||
|
||||
#[serde(default)]
|
||||
pub pre_announced_x25519_sphinx_key: Option<SphinxKey>,
|
||||
|
||||
#[serde(default)]
|
||||
pub x25519_versioned_noise: Option<VersionedNoiseKey>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
|
||||
pub struct SphinxKey {
|
||||
pub rotation_id: u32,
|
||||
|
||||
#[serde(with = "bs58_x25519_pubkey")]
|
||||
#[schemars(with = "String")]
|
||||
#[schema(value_type = String)]
|
||||
pub public_key: x25519::PublicKey,
|
||||
}
|
||||
|
||||
impl From<nym_node_requests::api::v1::node::models::SphinxKey> for SphinxKey {
|
||||
fn from(value: nym_node_requests::api::v1::node::models::SphinxKey) -> Self {
|
||||
SphinxKey {
|
||||
rotation_id: value.rotation_id,
|
||||
public_key: value.public_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nym_node_requests::api::v1::node::models::HostKeys> for HostKeys {
|
||||
fn from(value: nym_node_requests::api::v1::node::models::HostKeys) -> Self {
|
||||
HostKeys {
|
||||
ed25519: value.ed25519_identity,
|
||||
x25519: value.x25519_sphinx,
|
||||
current_x25519_sphinx_key: value.primary_x25519_sphinx_key.into(),
|
||||
pre_announced_x25519_sphinx_key: value.pre_announced_x25519_sphinx_key.map(Into::into),
|
||||
x25519_versioned_noise: value.x25519_versioned_noise,
|
||||
}
|
||||
}
|
||||
@@ -1084,10 +1108,11 @@ impl NymNodeDescription {
|
||||
|
||||
pub fn to_semi_skimmed_node(
|
||||
&self,
|
||||
current_rotation_id: u32,
|
||||
role: NodeRole,
|
||||
performance: Performance,
|
||||
) -> SemiSkimmedNode {
|
||||
let skimmed_node = self.to_skimmed_node(role, performance);
|
||||
let skimmed_node = self.to_skimmed_node(current_rotation_id, role, performance);
|
||||
|
||||
SemiSkimmedNode {
|
||||
basic: skimmed_node,
|
||||
|
||||
@@ -8,7 +8,7 @@ use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey;
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_mixnet_contract_common::nym_node::Role;
|
||||
use nym_mixnet_contract_common::reward_params::Performance;
|
||||
use nym_mixnet_contract_common::{Interval, NodeId};
|
||||
use nym_mixnet_contract_common::{EpochId, Interval, NodeId};
|
||||
use nym_noise_keys::VersionedNoiseKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -1,97 +1,13 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node_describe_cache::DescribedNodes;
|
||||
use crate::node_status_api::models::AxumResult;
|
||||
use crate::nym_nodes::handlers::unstable::helpers::{refreshed_at, LegacyAnnotation};
|
||||
use crate::nym_nodes::handlers::unstable::NodesParamsWithRole;
|
||||
use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
|
||||
use crate::support::http::state::AppState;
|
||||
use crate::unstable_routes::v1::nym_nodes::helpers::NodesParamsWithRole;
|
||||
use axum::extract::{Query, State};
|
||||
use nym_api_requests::models::{
|
||||
NodeAnnotation, NymNodeDescription, OffsetDateTimeJsonSchemaWrapper,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::{NodeRole, PaginatedCachedNodesResponse, SemiSkimmedNode};
|
||||
use nym_api_requests::pagination::PaginatedResponse;
|
||||
use nym_api_requests::nym_nodes::{CachedNodesResponse, SemiSkimmedNode};
|
||||
use nym_http_api_common::FormattedResponse;
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_topology::CachedEpochRewardedSet;
|
||||
use std::collections::HashMap;
|
||||
use tracing::trace;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub type PaginatedSemiSkimmedNodes =
|
||||
AxumResult<FormattedResponse<PaginatedCachedNodesResponse<SemiSkimmedNode>>>;
|
||||
|
||||
//SW TODO : this is copied from skimmed nodes, surely we can do better than that
|
||||
fn build_nym_nodes_response<'a, NI>(
|
||||
rewarded_set: &CachedEpochRewardedSet,
|
||||
nym_nodes_subset: NI,
|
||||
annotations: &HashMap<NodeId, NodeAnnotation>,
|
||||
active_only: bool,
|
||||
) -> Vec<SemiSkimmedNode>
|
||||
where
|
||||
NI: Iterator<Item = &'a NymNodeDescription> + 'a,
|
||||
{
|
||||
let mut nodes = Vec::new();
|
||||
for nym_node in nym_nodes_subset {
|
||||
let node_id = nym_node.node_id;
|
||||
|
||||
let role: NodeRole = rewarded_set.role(node_id).into();
|
||||
|
||||
// if the role is inactive, see if our filter allows it
|
||||
if active_only && role.is_inactive() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// honestly, not sure under what exact circumstances this value could be missing,
|
||||
// but in that case just use 0 performance
|
||||
let annotation = annotations.get(&node_id).copied().unwrap_or_default();
|
||||
|
||||
nodes.push(nym_node.to_semi_skimmed_node(role, annotation.last_24h_performance));
|
||||
}
|
||||
nodes
|
||||
}
|
||||
|
||||
//SW TODO : this is copied from skimmed nodes, surely we can do better than that
|
||||
/// Given all relevant caches, add appropriate legacy nodes to the part of the response
|
||||
fn add_legacy<LN>(
|
||||
nodes: &mut Vec<SemiSkimmedNode>,
|
||||
rewarded_set: &CachedEpochRewardedSet,
|
||||
describe_cache: &DescribedNodes,
|
||||
annotated_legacy_nodes: &HashMap<NodeId, LN>,
|
||||
) where
|
||||
LN: LegacyAnnotation,
|
||||
{
|
||||
for (node_id, legacy) in annotated_legacy_nodes.iter() {
|
||||
let role: NodeRole = rewarded_set.role(*node_id).into();
|
||||
|
||||
// if we have self-described info, prefer it over contract data
|
||||
if let Some(described) = describe_cache.get_node(node_id) {
|
||||
nodes.push(described.to_semi_skimmed_node(role, legacy.performance()))
|
||||
} else {
|
||||
match legacy.try_to_semi_skimmed_node(role) {
|
||||
Ok(node) => nodes.push(node),
|
||||
Err(err) => {
|
||||
let id = legacy.identity();
|
||||
trace!("node {id} is malformed: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // not dead, used in OpenAPI docs
|
||||
#[derive(ToSchema)]
|
||||
#[schema(title = "PaginatedCachedNodesExpandedResponseSchema")]
|
||||
pub struct PaginatedCachedNodesExpandedResponseSchema {
|
||||
pub refreshed_at: OffsetDateTimeJsonSchemaWrapper,
|
||||
#[schema(value_type = SemiSkimmedNode)]
|
||||
pub nodes: PaginatedResponse<SemiSkimmedNode>,
|
||||
}
|
||||
|
||||
/// Return all Nym Nodes and optionally legacy mixnodes/gateways (if `no-legacy` flag is not used)
|
||||
/// that are currently bonded.
|
||||
#[utoipa::path(
|
||||
tag = "Unstable Nym Nodes",
|
||||
get,
|
||||
@@ -99,43 +15,13 @@ pub struct PaginatedCachedNodesExpandedResponseSchema {
|
||||
path = "",
|
||||
context_path = "/v1/unstable/nym-nodes/semi-skimmed",
|
||||
responses(
|
||||
(status = 200, content(
|
||||
(PaginatedCachedNodesExpandedResponseSchema = "application/json"),
|
||||
(PaginatedCachedNodesExpandedResponseSchema = "application/yaml"),
|
||||
(PaginatedCachedNodesExpandedResponseSchema = "application/bincode")
|
||||
))
|
||||
// (status = 200, body = CachedNodesResponse<SemiSkimmedNode>)
|
||||
(status = 501)
|
||||
)
|
||||
)]
|
||||
pub(super) async fn nodes_expanded(
|
||||
state: State<AppState>,
|
||||
query_params: Query<NodesParamsWithRole>,
|
||||
) -> PaginatedSemiSkimmedNodes {
|
||||
// 1. grab all relevant described nym-nodes
|
||||
let rewarded_set = state.rewarded_set().await?;
|
||||
|
||||
let describe_cache = state.describe_nodes_cache_data().await?;
|
||||
let all_nym_nodes = describe_cache.all_nym_nodes();
|
||||
let annotations = state.node_annotations().await?;
|
||||
let legacy_mixnodes = state.legacy_mixnode_annotations().await?;
|
||||
let legacy_gateways = state.legacy_gateways_annotations().await?;
|
||||
|
||||
let mut nodes = build_nym_nodes_response(&rewarded_set, all_nym_nodes, &annotations, false);
|
||||
|
||||
// add legacy gateways to the response
|
||||
add_legacy(&mut nodes, &rewarded_set, &describe_cache, &legacy_gateways);
|
||||
|
||||
// add legacy mixnodes to the response
|
||||
add_legacy(&mut nodes, &rewarded_set, &describe_cache, &legacy_mixnodes);
|
||||
|
||||
// min of all caches
|
||||
let refreshed_at = refreshed_at([
|
||||
rewarded_set.timestamp(),
|
||||
annotations.timestamp(),
|
||||
describe_cache.timestamp(),
|
||||
legacy_mixnodes.timestamp(),
|
||||
legacy_gateways.timestamp(),
|
||||
]);
|
||||
|
||||
let output = query_params.output.unwrap_or_default();
|
||||
Ok(output.to_response(PaginatedCachedNodesResponse::new_full(refreshed_at, nodes)))
|
||||
pub(crate) async fn nodes_expanded(
|
||||
_state: State<AppState>,
|
||||
_query_params: Query<NodesParamsWithRole>,
|
||||
) -> AxumResult<FormattedResponse<CachedNodesResponse<SemiSkimmedNode>>> {
|
||||
Err(AxumErrorResponse::not_implemented())
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use log::{info, warn};
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_client_core::config::ForgetMe;
|
||||
use nym_crypto::asymmetric::ed25519::PrivateKey;
|
||||
use nym_crypto::hkdf::DerivationMaterial;
|
||||
use nym_network_defaults::setup_env;
|
||||
use nym_network_defaults::var_names::NYM_API;
|
||||
use nym_sdk::mixnet::{self, MixnetClient};
|
||||
@@ -13,6 +14,7 @@ use nym_sphinx::chunking::monitoring;
|
||||
use nym_topology::{HardcodedTopologyProvider, NymTopology, NymTopologyMetadata};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::LazyLock;
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
@@ -21,6 +23,7 @@ use std::{
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::OnceCell;
|
||||
use tokio::{signal::ctrl_c, sync::RwLock};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
@@ -45,6 +48,7 @@ async fn make_clients(
|
||||
n_clients: usize,
|
||||
lifetime: u64,
|
||||
topology: NymTopology,
|
||||
derivation_material: Option<Arc<Mutex<DerivationMaterial>>>,
|
||||
) {
|
||||
loop {
|
||||
let spawned_clients = clients.read().await.len();
|
||||
@@ -71,7 +75,12 @@ async fn make_clients(
|
||||
}
|
||||
}
|
||||
info!("Spawning new client");
|
||||
let client = match make_client(topology.clone()).await {
|
||||
let client = match make_client(
|
||||
topology.clone(),
|
||||
derivation_material.as_ref().map(Arc::clone),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(client) => client,
|
||||
Err(err) => {
|
||||
warn!("{}, moving on", err);
|
||||
@@ -85,16 +94,29 @@ async fn make_clients(
|
||||
}
|
||||
}
|
||||
|
||||
async fn make_client(topology: NymTopology) -> Result<MixnetClient> {
|
||||
/// Creates a new mixnet client, optionally using shared derivation material
|
||||
/// for deterministic key generation. This allows multiple monitor clients
|
||||
/// to derive keys from the same source while maintaining thread safety.
|
||||
async fn make_client(
|
||||
topology: NymTopology,
|
||||
derivation_material: Option<Arc<Mutex<DerivationMaterial>>>,
|
||||
) -> Result<MixnetClient> {
|
||||
let net = mixnet::NymNetworkDetails::new_from_env();
|
||||
let topology_provider = Box::new(HardcodedTopologyProvider::new(topology));
|
||||
let mixnet_client = mixnet::MixnetClientBuilder::new_ephemeral()
|
||||
let mut mixnet_client = mixnet::MixnetClientBuilder::new_ephemeral()
|
||||
.network_details(net)
|
||||
.custom_topology_provider(topology_provider)
|
||||
.debug_config(mixnet_debug_config(0))
|
||||
.with_forget_me(ForgetMe::new_all())
|
||||
// .enable_credentials_mode()
|
||||
.build()?;
|
||||
.with_forget_me(ForgetMe::new_all());
|
||||
|
||||
// Configure the client with shared derivation material if available
|
||||
// This ensures all monitor clients use the same key derivation source
|
||||
if let Some(derivation_material) = derivation_material {
|
||||
mixnet_client =
|
||||
mixnet_client.with_shared_derivation_material(Arc::clone(&derivation_material));
|
||||
}
|
||||
|
||||
let mixnet_client = mixnet_client.build()?;
|
||||
|
||||
let client = mixnet_client.connect_to_mixnet().await?;
|
||||
Ok(client)
|
||||
@@ -138,6 +160,12 @@ struct Args {
|
||||
|
||||
#[arg(long, env = "DATABASE_URL")]
|
||||
database_url: Option<String>,
|
||||
|
||||
/// Path to a JSON file containing serialized DerivationMaterial for deterministic
|
||||
/// client key generation. When provided, all monitor clients will derive keys
|
||||
/// from this shared material instead of generating random keys.
|
||||
#[arg(long, env = "DERIVATION_MATERIAL_PATH")]
|
||||
derivation_material_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn generate_key_pair() -> Result<()> {
|
||||
@@ -214,12 +242,24 @@ async fn main() -> Result<()> {
|
||||
|
||||
MIXNET_TIMEOUT.set(args.mixnet_timeout).ok();
|
||||
|
||||
// Load shared derivation material from file if provided
|
||||
// This enables deterministic key generation across all monitor clients
|
||||
let derivation_material = args
|
||||
.derivation_material_path
|
||||
.map(|derivation_material_path| {
|
||||
let file = File::open(derivation_material_path)?;
|
||||
let derivation_material: DerivationMaterial = serde_json::from_reader(file)?;
|
||||
Ok::<_, anyhow::Error>(Arc::new(Mutex::new(derivation_material)))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let spawn_clients = Arc::clone(&clients);
|
||||
tokio::spawn(make_clients(
|
||||
spawn_clients,
|
||||
args.n_clients,
|
||||
args.client_lifetime,
|
||||
TOPOLOGY.get().expect("Topology not set yet!").clone(),
|
||||
derivation_material,
|
||||
));
|
||||
|
||||
let clients_server = clients.clone();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node-status-api"
|
||||
version = "3.1.0"
|
||||
version = "3.0.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
@@ -28,7 +28,7 @@ moka = { workspace = true, features = ["future"] }
|
||||
# Nym API: revert after Cheddar is out
|
||||
nym-contracts-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
nym-mixnet-contract-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
nym-bin-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = ["openapi"]}
|
||||
nym-bin-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
nym-node-status-client = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
nym-crypto = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
nym-http-api-client = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
|
||||
|
||||
@@ -14,7 +14,6 @@ pub(crate) mod metrics;
|
||||
pub(crate) mod mixnodes;
|
||||
pub(crate) mod nym_nodes;
|
||||
pub(crate) mod services;
|
||||
pub(crate) mod status;
|
||||
pub(crate) mod summary;
|
||||
pub(crate) mod testruns;
|
||||
|
||||
@@ -40,8 +39,7 @@ impl RouterBuilder {
|
||||
.nest("/mixnodes", mixnodes::routes())
|
||||
.nest("/services", services::routes())
|
||||
.nest("/summary", summary::routes())
|
||||
.nest("/metrics", metrics::routes())
|
||||
.nest("/status", status::routes()),
|
||||
.nest("/metrics", metrics::routes()),
|
||||
)
|
||||
.nest(
|
||||
"/explorer/v3",
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
use axum::{extract::State, Json, Router};
|
||||
use nym_validator_client::models::BinaryBuildInformationOwned;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::http::{
|
||||
error::HttpResult,
|
||||
state::{AppState, HealthInfo},
|
||||
};
|
||||
|
||||
pub(crate) fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/build_information", axum::routing::get(build_information))
|
||||
.route("/health", axum::routing::get(health))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Status",
|
||||
get,
|
||||
path = "/build_information",
|
||||
context_path = "/v2/status",
|
||||
responses(
|
||||
(status = 200, body = BinaryBuildInformationOwned)
|
||||
)
|
||||
)]
|
||||
#[instrument(level = tracing::Level::INFO, skip_all)]
|
||||
async fn build_information(
|
||||
State(state): State<AppState>,
|
||||
) -> HttpResult<Json<BinaryBuildInformationOwned>> {
|
||||
let build_info = state.build_information().to_owned();
|
||||
|
||||
Ok(Json(build_info))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Status",
|
||||
get,
|
||||
path = "/health",
|
||||
context_path = "/v2/status",
|
||||
responses(
|
||||
(status = 200, body = HealthInfo)
|
||||
)
|
||||
)]
|
||||
#[instrument(level = tracing::Level::INFO, skip_all)]
|
||||
async fn health(State(state): State<AppState>) -> HttpResult<Json<HealthInfo>> {
|
||||
Ok(Json(state.health()))
|
||||
}
|
||||
@@ -1,20 +1,15 @@
|
||||
use cosmwasm_std::Decimal;
|
||||
use itertools::Itertools;
|
||||
use moka::{future::Cache, Entry};
|
||||
use nym_bin_common::bin_info_owned;
|
||||
use nym_contracts_common::NaiveFloat;
|
||||
use nym_crypto::asymmetric::ed25519::PublicKey;
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_validator_client::nym_api::SkimmedNode;
|
||||
use semver::Version;
|
||||
use serde::Serialize;
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use time::UtcDateTime;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{error, instrument, warn};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use super::models::SessionStats;
|
||||
use crate::{
|
||||
db::{queries, DbPool},
|
||||
http::models::{
|
||||
@@ -23,7 +18,7 @@ use crate::{
|
||||
monitor::{DelegationsCache, NodeGeoCache},
|
||||
};
|
||||
|
||||
pub(crate) use nym_validator_client::models::BinaryBuildInformationOwned;
|
||||
use super::models::SessionStats;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct AppState {
|
||||
@@ -33,7 +28,6 @@ pub(crate) struct AppState {
|
||||
agent_max_count: i64,
|
||||
node_geocache: NodeGeoCache,
|
||||
node_delegations: Arc<RwLock<DelegationsCache>>,
|
||||
bin_info: BinaryInfo,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@@ -52,7 +46,6 @@ impl AppState {
|
||||
agent_max_count,
|
||||
node_geocache,
|
||||
node_delegations,
|
||||
bin_info: BinaryInfo::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,15 +78,6 @@ impl AppState {
|
||||
.await
|
||||
.delegations_owned(node_id)
|
||||
}
|
||||
|
||||
pub(crate) fn health(&self) -> HealthInfo {
|
||||
let uptime = (UtcDateTime::now() - self.bin_info.startup_time).whole_seconds();
|
||||
HealthInfo { uptime }
|
||||
}
|
||||
|
||||
pub(crate) fn build_information(&self) -> &BinaryBuildInformationOwned {
|
||||
&self.bin_info.build_info
|
||||
}
|
||||
}
|
||||
|
||||
static GATEWAYS_LIST_KEY: &str = "gateways";
|
||||
@@ -647,23 +631,3 @@ async fn aggregate_node_info_from_db(
|
||||
|
||||
Ok(parsed_nym_nodes)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct BinaryInfo {
|
||||
startup_time: UtcDateTime,
|
||||
build_info: BinaryBuildInformationOwned,
|
||||
}
|
||||
|
||||
impl BinaryInfo {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
startup_time: UtcDateTime::now(),
|
||||
build_info: bin_info_owned!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub(crate) struct HealthInfo {
|
||||
uptime: i64,
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::Deref;
|
||||
use v1::node::models::LegacyHostInformationV3;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
pub mod client;
|
||||
@@ -131,7 +130,7 @@ impl Display for ErrorResponse {
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::api::v1::node::models::HostInformation;
|
||||
use crate::api::v1::node::models::{HostKeys, SphinxKey};
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_noise_keys::{NoiseVersion, VersionedNoiseKey};
|
||||
use rand_chacha::rand_core::SeedableRng;
|
||||
@@ -141,8 +140,9 @@ mod tests {
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]);
|
||||
let ed22519 = ed25519::KeyPair::new(&mut rng);
|
||||
let x25519_sphinx = x25519::KeyPair::new(&mut rng);
|
||||
let x25519_sphinx2 = x25519::KeyPair::new(&mut rng);
|
||||
let x25519_versioned_noise = VersionedNoiseKey {
|
||||
version: NoiseVersion::V1,
|
||||
supported_version: NoiseVersion::V1,
|
||||
x25519_pubkey: *x25519::KeyPair::new(&mut rng).public_key(),
|
||||
};
|
||||
|
||||
@@ -155,6 +155,11 @@ mod tests {
|
||||
keys: crate::api::v1::node::models::HostKeys {
|
||||
ed25519_identity: *ed22519.public_key(),
|
||||
x25519_sphinx: *x25519_sphinx.public_key(),
|
||||
primary_x25519_sphinx_key: SphinxKey {
|
||||
rotation_id: current_rotation_id,
|
||||
public_key: *x25519_sphinx.public_key(),
|
||||
},
|
||||
pre_announced_x25519_sphinx_key: None,
|
||||
x25519_versioned_noise: None,
|
||||
},
|
||||
};
|
||||
@@ -169,6 +174,11 @@ mod tests {
|
||||
keys: crate::api::v1::node::models::HostKeys {
|
||||
ed25519_identity: *ed22519.public_key(),
|
||||
x25519_sphinx: *x25519_sphinx.public_key(),
|
||||
primary_x25519_sphinx_key: SphinxKey {
|
||||
rotation_id: current_rotation_id,
|
||||
public_key: *x25519_sphinx.public_key(),
|
||||
},
|
||||
pre_announced_x25519_sphinx_key: None,
|
||||
x25519_versioned_noise: Some(x25519_versioned_noise),
|
||||
},
|
||||
};
|
||||
@@ -319,84 +329,6 @@ mod tests {
|
||||
assert!(current_struct_noise.verify_host_information())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dummy_legacy_v3_signed_host_verification() {
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]);
|
||||
let ed22519 = ed25519::KeyPair::new(&mut rng);
|
||||
let x25519_sphinx = x25519::KeyPair::new(&mut rng);
|
||||
let x25519_noise = x25519::KeyPair::new(&mut rng);
|
||||
|
||||
let legacy_info_no_noise = crate::api::v1::node::models::LegacyHostInformationV3 {
|
||||
ip_address: vec!["1.1.1.1".parse().unwrap()],
|
||||
hostname: Some("foomp.com".to_string()),
|
||||
keys: crate::api::v1::node::models::LegacyHostKeysV3 {
|
||||
ed25519_identity: *ed22519.public_key(),
|
||||
x25519_sphinx: *x25519_sphinx.public_key(),
|
||||
x25519_noise: None,
|
||||
},
|
||||
};
|
||||
|
||||
//technically this variant should never happen
|
||||
let legacy_info_noise = crate::api::v1::node::models::LegacyHostInformationV3 {
|
||||
ip_address: vec!["1.1.1.1".parse().unwrap()],
|
||||
hostname: Some("foomp.com".to_string()),
|
||||
keys: crate::api::v1::node::models::LegacyHostKeysV3 {
|
||||
ed25519_identity: *ed22519.public_key(),
|
||||
x25519_sphinx: *x25519_sphinx.public_key(),
|
||||
x25519_noise: Some(*x25519_noise.public_key()),
|
||||
},
|
||||
};
|
||||
|
||||
let host_info_no_noise = crate::api::v1::node::models::HostInformation {
|
||||
ip_address: legacy_info_no_noise.ip_address.clone(),
|
||||
hostname: legacy_info_no_noise.hostname.clone(),
|
||||
keys: crate::api::v1::node::models::HostKeys {
|
||||
ed25519_identity: legacy_info_no_noise.keys.ed25519_identity,
|
||||
x25519_sphinx: legacy_info_no_noise.keys.x25519_sphinx,
|
||||
x25519_versioned_noise: None,
|
||||
},
|
||||
};
|
||||
|
||||
let host_info_noise = crate::api::v1::node::models::HostInformation {
|
||||
ip_address: legacy_info_noise.ip_address.clone(),
|
||||
hostname: legacy_info_noise.hostname.clone(),
|
||||
keys: crate::api::v1::node::models::HostKeys {
|
||||
ed25519_identity: legacy_info_noise.keys.ed25519_identity,
|
||||
x25519_sphinx: legacy_info_noise.keys.x25519_sphinx,
|
||||
x25519_versioned_noise: Some(VersionedNoiseKey {
|
||||
version: NoiseVersion::V1,
|
||||
x25519_pubkey: legacy_info_noise.keys.x25519_noise.unwrap(),
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
// signature on legacy data
|
||||
let signature_no_noise = SignedData::new(legacy_info_no_noise, ed22519.private_key())
|
||||
.unwrap()
|
||||
.signature;
|
||||
|
||||
let signature_noise = SignedData::new(legacy_info_noise, ed22519.private_key())
|
||||
.unwrap()
|
||||
.signature;
|
||||
|
||||
// signed blob with the 'current' structure
|
||||
let current_struct_no_noise = SignedData {
|
||||
data: host_info_no_noise,
|
||||
signature: signature_no_noise,
|
||||
};
|
||||
|
||||
let current_struct_noise = SignedData {
|
||||
data: host_info_noise,
|
||||
signature: signature_noise,
|
||||
};
|
||||
|
||||
assert!(!current_struct_no_noise.verify(ed22519.public_key()));
|
||||
assert!(current_struct_no_noise.verify_host_information());
|
||||
|
||||
assert!(!current_struct_noise.verify(ed22519.public_key()));
|
||||
assert!(current_struct_noise.verify_host_information())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dummy_legacy_v2_signed_host_verification() {
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]);
|
||||
@@ -430,7 +362,12 @@ mod tests {
|
||||
hostname: legacy_info_no_noise.hostname.clone(),
|
||||
keys: crate::api::v1::node::models::HostKeys {
|
||||
ed25519_identity: legacy_info_no_noise.keys.ed25519_identity.parse().unwrap(),
|
||||
x25519_sphinx: legacy_info_no_noise.keys.x25519_sphinx.parse().unwrap(),
|
||||
x25519_sphinx: *x25519_sphinx.public_key(),
|
||||
primary_x25519_sphinx_key: SphinxKey {
|
||||
rotation_id: u32::MAX,
|
||||
public_key: *x25519_sphinx.public_key(),
|
||||
},
|
||||
pre_announced_x25519_sphinx_key: None,
|
||||
x25519_versioned_noise: None,
|
||||
},
|
||||
};
|
||||
@@ -441,9 +378,14 @@ mod tests {
|
||||
hostname: legacy_info_noise.hostname.clone(),
|
||||
keys: crate::api::v1::node::models::HostKeys {
|
||||
ed25519_identity: legacy_info_noise.keys.ed25519_identity.parse().unwrap(),
|
||||
x25519_sphinx: legacy_info_noise.keys.x25519_sphinx.parse().unwrap(),
|
||||
x25519_sphinx: *x25519_sphinx.public_key(),
|
||||
primary_x25519_sphinx_key: SphinxKey {
|
||||
rotation_id: u32::MAX,
|
||||
public_key: *x25519_sphinx.public_key(),
|
||||
},
|
||||
pre_announced_x25519_sphinx_key: None,
|
||||
x25519_versioned_noise: Some(VersionedNoiseKey {
|
||||
version: NoiseVersion::V1,
|
||||
supported_version: NoiseVersion::V1,
|
||||
x25519_pubkey: legacy_info_noise.keys.x25519_noise.parse().unwrap(),
|
||||
}),
|
||||
},
|
||||
@@ -476,49 +418,6 @@ mod tests {
|
||||
assert!(current_struct_noise.verify_host_information())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dummy_current_signed_host_verification() {
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]);
|
||||
let ed22519 = ed25519::KeyPair::new(&mut rng);
|
||||
let x25519_sphinx = x25519::KeyPair::new(&mut rng);
|
||||
let x25519_noise = x25519::KeyPair::new(&mut rng);
|
||||
|
||||
let host_info_no_noise = HostInformation {
|
||||
ip_address: vec!["1.1.1.1".parse().unwrap()],
|
||||
hostname: Some("foomp.com".to_string()),
|
||||
keys: crate::api::v1::node::models::HostKeys {
|
||||
ed25519_identity: *ed22519.public_key(),
|
||||
x25519_sphinx: *x25519_sphinx.public_key(),
|
||||
x25519_versioned_noise: None,
|
||||
},
|
||||
};
|
||||
|
||||
let host_info_noise = crate::api::v1::node::models::HostInformation {
|
||||
ip_address: vec!["1.1.1.1".parse().unwrap()],
|
||||
hostname: Some("foomp.com".to_string()),
|
||||
keys: crate::api::v1::node::models::HostKeys {
|
||||
ed25519_identity: *ed22519.public_key(),
|
||||
x25519_sphinx: *x25519_sphinx.public_key(),
|
||||
x25519_versioned_noise: Some(VersionedNoiseKey {
|
||||
version: NoiseVersion::V1,
|
||||
x25519_pubkey: *x25519_noise.public_key(),
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
// signature on legacy data
|
||||
let current_struct_no_noise =
|
||||
SignedData::new(host_info_no_noise, ed22519.private_key()).unwrap();
|
||||
let current_struct_noise = SignedData::new(host_info_noise, ed22519.private_key()).unwrap();
|
||||
|
||||
assert!(current_struct_no_noise.verify(ed22519.public_key()));
|
||||
assert!(current_struct_no_noise.verify_host_information());
|
||||
|
||||
// if noise key is present, the signature is actually valid
|
||||
assert!(current_struct_noise.verify(ed22519.public_key()));
|
||||
assert!(current_struct_noise.verify_host_information())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dummy_legacy_v1_signed_host_verification() {
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]);
|
||||
@@ -540,7 +439,12 @@ mod tests {
|
||||
hostname: legacy_info.hostname.clone(),
|
||||
keys: crate::api::v1::node::models::HostKeys {
|
||||
ed25519_identity: legacy_info.keys.ed25519.parse().unwrap(),
|
||||
x25519_sphinx: legacy_info.keys.x25519.parse().unwrap(),
|
||||
x25519_sphinx: *x25519_sphinx.public_key(),
|
||||
primary_x25519_sphinx_key: SphinxKey {
|
||||
rotation_id: u32::MAX,
|
||||
public_key: *x25519_sphinx.public_key(),
|
||||
},
|
||||
pre_announced_x25519_sphinx_key: None,
|
||||
x25519_versioned_noise: None,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -149,6 +149,77 @@ pub struct HostKeys {
|
||||
pub x25519_versioned_noise: Option<VersionedNoiseKey>,
|
||||
}
|
||||
|
||||
// we need the intermediate struct to help us with the new explicit sphinx key fields
|
||||
#[allow(deprecated)]
|
||||
impl From<HostKeysDeHelper> for HostKeys {
|
||||
fn from(value: HostKeysDeHelper) -> Self {
|
||||
let primary_x25519_sphinx_key = match value.primary_x25519_sphinx_key {
|
||||
None => {
|
||||
// legacy
|
||||
SphinxKey::new_legacy(value.x25519_sphinx)
|
||||
}
|
||||
Some(primary_x25519_sphinx_key) => primary_x25519_sphinx_key,
|
||||
};
|
||||
|
||||
HostKeys {
|
||||
ed25519_identity: value.ed25519_identity,
|
||||
x25519_sphinx: value.x25519_sphinx,
|
||||
primary_x25519_sphinx_key,
|
||||
pre_announced_x25519_sphinx_key: value.pre_announced_x25519_sphinx_key,
|
||||
x25519_versioned_noise: value.x25519_versioned_noise,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct HostKeysDeHelper {
|
||||
/// Base58-encoded ed25519 public key of this node. Currently, it corresponds to either mixnode's or gateway's identity.
|
||||
#[serde(alias = "ed25519")]
|
||||
#[serde(with = "bs58_ed25519_pubkey")]
|
||||
pub ed25519_identity: ed25519::PublicKey,
|
||||
|
||||
#[deprecated(note = "use explicit primary_x25519_sphinx_key instead")]
|
||||
#[serde(alias = "x25519")]
|
||||
#[serde(with = "bs58_x25519_pubkey")]
|
||||
pub x25519_sphinx: x25519::PublicKey,
|
||||
|
||||
/// Current, active, x25519 sphinx key clients are expected to use when constructing packets
|
||||
/// with this node in the route.
|
||||
pub primary_x25519_sphinx_key: Option<SphinxKey>,
|
||||
|
||||
/// Pre-announced x25519 sphinx key clients will use during the following key rotation
|
||||
#[serde(default)]
|
||||
pub pre_announced_x25519_sphinx_key: Option<SphinxKey>,
|
||||
|
||||
/// Base58-encoded x25519 public key of this node used for the noise protocol.
|
||||
#[serde(default)]
|
||||
pub x25519_versioned_noise: Option<VersionedNoiseKey>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct SphinxKey {
|
||||
pub rotation_id: u32,
|
||||
|
||||
#[serde(with = "bs58_x25519_pubkey")]
|
||||
#[schemars(with = "String")]
|
||||
#[cfg_attr(feature = "openapi", schema(value_type = String))]
|
||||
pub public_key: x25519::PublicKey,
|
||||
}
|
||||
|
||||
impl SphinxKey {
|
||||
pub fn new_legacy(public_key: x25519::PublicKey) -> SphinxKey {
|
||||
SphinxKey {
|
||||
rotation_id: u32::MAX,
|
||||
public_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_legacy(&self) -> bool {
|
||||
self.rotation_id == u32::MAX
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct LegacyHostKeysV3 {
|
||||
#[serde(alias = "ed25519")]
|
||||
@@ -181,7 +252,7 @@ impl From<HostKeys> for LegacyHostKeysV3 {
|
||||
fn from(value: HostKeys) -> Self {
|
||||
LegacyHostKeysV3 {
|
||||
ed25519_identity: value.ed25519_identity,
|
||||
x25519_sphinx: value.x25519_sphinx,
|
||||
x25519_sphinx: value.primary_x25519_sphinx_key.public_key,
|
||||
x25519_noise: value.x25519_versioned_noise.map(|k| k.x25519_pubkey),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,4 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::NymNodeError;
|
||||
use crate::node::http::api::api_requests;
|
||||
use crate::node::http::error::NymNodeHttpError;
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_node_requests::api::SignedHostInformation;
|
||||
use nym_noise_keys::VersionedNoiseKey;
|
||||
|
||||
pub mod system_info;
|
||||
|
||||
pub(crate) fn sign_host_details(
|
||||
config: &Config,
|
||||
x22519_sphinx: &x25519::PublicKey,
|
||||
x25519_versioned_noise: &VersionedNoiseKey,
|
||||
ed22519_identity: &ed25519::KeyPair,
|
||||
) -> Result<SignedHostInformation, NymNodeError> {
|
||||
let x25519_versioned_noise = if config.mixnet.debug.unsafe_disable_noise {
|
||||
None
|
||||
} else {
|
||||
Some(*x25519_versioned_noise)
|
||||
};
|
||||
|
||||
let host_info = api_requests::v1::node::models::HostInformation {
|
||||
ip_address: config.host.public_ips.clone(),
|
||||
hostname: config.host.hostname.clone(),
|
||||
keys: api_requests::v1::node::models::HostKeys {
|
||||
ed25519_identity: *ed22519_identity.public_key(),
|
||||
x25519_sphinx: *x22519_sphinx,
|
||||
x25519_versioned_noise,
|
||||
},
|
||||
};
|
||||
|
||||
let signed_info = SignedHostInformation::new(host_info, ed22519_identity.private_key())
|
||||
.map_err(NymNodeHttpError::from)?;
|
||||
Ok(signed_info)
|
||||
}
|
||||
|
||||
@@ -484,7 +484,7 @@ impl ConnectionHandler {
|
||||
|
||||
pub(crate) async fn handle_stream(
|
||||
&mut self,
|
||||
mut mixnet_connection: Framed<Connection, NymCodec>,
|
||||
mut mixnet_connection: Framed<Connection<TcpStream>, NymCodec>,
|
||||
) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
|
||||
@@ -702,16 +702,6 @@ impl NymNode {
|
||||
}
|
||||
|
||||
pub(crate) async fn build_http_server(&self) -> Result<NymNodeHttpServer, NymNodeError> {
|
||||
let host_details = sign_host_details(
|
||||
&self.config,
|
||||
self.x25519_sphinx_keys.public_key(),
|
||||
&VersionedNoiseKey {
|
||||
version: nym_noise::LATEST_NOISE_VERSION,
|
||||
x25519_pubkey: *self.x25519_noise_keys.public_key(),
|
||||
},
|
||||
&self.ed25519_identity_keys,
|
||||
)?;
|
||||
|
||||
let auxiliary_details = api_requests::v1::node::models::AuxiliaryDetails {
|
||||
location: self.config.host.location,
|
||||
announce_ports: AnnouncePorts {
|
||||
@@ -1161,6 +1151,8 @@ impl NymNode {
|
||||
let network_refresher = self.build_network_refresher().await?;
|
||||
let active_clients_store = ActiveClientsStore::new();
|
||||
|
||||
let bloomfilters_manager = self.setup_replay_detection().await?;
|
||||
|
||||
let noise_config = nym_noise::config::NoiseConfig::new(
|
||||
self.x25519_noise_keys.clone(),
|
||||
network_refresher.noise_view(),
|
||||
|
||||
@@ -16,10 +16,13 @@ use nym_topology::{
|
||||
TopologyProvider,
|
||||
};
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::nym_nodes::{NodesByAddressesResponse, SemiSkimmedNode};
|
||||
use nym_validator_client::nym_nodes::{
|
||||
NodesByAddressesResponse, SemiSkimmedNode, SemiSkimmedNodesWithMetadata,
|
||||
};
|
||||
use nym_validator_client::{NymApiClient, ValidatorClientError};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::RwLock;
|
||||
@@ -61,7 +64,9 @@ impl NodesQuerier {
|
||||
res
|
||||
}
|
||||
|
||||
async fn current_nymnodes(&mut self) -> Result<Vec<SemiSkimmedNode>, ValidatorClientError> {
|
||||
async fn current_nymnodes(
|
||||
&mut self,
|
||||
) -> Result<SemiSkimmedNodesWithMetadata, ValidatorClientError> {
|
||||
let res = self
|
||||
.client
|
||||
.get_all_expanded_nodes()
|
||||
@@ -143,20 +148,24 @@ impl TopologyProvider for CachedTopologyProvider {
|
||||
let network_guard = self.cached_network.inner.read().await;
|
||||
let self_node = self.gateway_node.identity_key;
|
||||
|
||||
let mut topology = NymTopology::new_empty(network_guard.rewarded_set.clone())
|
||||
.with_additional_nodes(
|
||||
network_guard
|
||||
.network_nodes
|
||||
.iter()
|
||||
.map(|node| &node.basic)
|
||||
.filter(|node| {
|
||||
if node.supported_roles.mixnode {
|
||||
node.performance.round_to_integer() >= self.min_mix_performance
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}),
|
||||
);
|
||||
let mut topology = NymTopology::new(
|
||||
network_guard.topology_metadata,
|
||||
network_guard.rewarded_set.clone(),
|
||||
Vec::new(),
|
||||
)
|
||||
.with_additional_nodes(
|
||||
network_guard
|
||||
.network_nodes
|
||||
.iter()
|
||||
.map(|node| &node.basic)
|
||||
.filter(|node| {
|
||||
if node.supported_roles.mixnode {
|
||||
node.performance.round_to_integer() >= self.min_mix_performance
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
if !topology.has_node(self.gateway_node.identity_key) {
|
||||
debug!("{self_node} didn't exist in topology. inserting it.",);
|
||||
@@ -187,6 +196,7 @@ impl CachedNetwork {
|
||||
|
||||
struct CachedNetworkInner {
|
||||
rewarded_set: EpochRewardedSet,
|
||||
topology_metadata: NymTopologyMetadata,
|
||||
network_nodes: Vec<SemiSkimmedNode>,
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,5 @@ pub(crate) fn network_details() -> nym_network_defaults::NymNetworkDetails {
|
||||
},
|
||||
explorer_api: parse_optional_str(EXPLORER_API),
|
||||
nym_vpn_api_url: None,
|
||||
nym_vpn_api_urls: None,
|
||||
nym_api_urls: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,7 +459,7 @@ impl fmt::Display for OptionalValidators {
|
||||
.as_ref()
|
||||
.map(|validators| format!(",\nsandbox: [\n{}\n]", validators.iter().format("\n")))
|
||||
.unwrap_or_default();
|
||||
write!(f, "{s1}{s2}")
|
||||
write!(f, "{s1}{s2}{s3}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
#[cfg(unix)]
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use url::Url;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
@@ -67,6 +68,9 @@ pub struct MixnetClientBuilder<S: MixnetClientStorage = Ephemeral> {
|
||||
forget_me: ForgetMe,
|
||||
remember_me: RememberMe,
|
||||
derivation_material: Option<DerivationMaterial>,
|
||||
// Shared derivation material for thread-safe access across multiple clients
|
||||
// Wrapped in Arc<Mutex<>> to allow concurrent access while maintaining safety
|
||||
shared_derivation_material: Option<Arc<Mutex<DerivationMaterial>>>,
|
||||
}
|
||||
|
||||
impl MixnetClientBuilder<Ephemeral> {
|
||||
@@ -106,6 +110,7 @@ impl MixnetClientBuilder<OnDiskPersistent> {
|
||||
forget_me: Default::default(),
|
||||
remember_me: Default::default(),
|
||||
derivation_material: None,
|
||||
shared_derivation_material: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -140,6 +145,7 @@ where
|
||||
forget_me: Default::default(),
|
||||
remember_me: Default::default(),
|
||||
derivation_material: None,
|
||||
shared_derivation_material: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +169,7 @@ where
|
||||
forget_me: self.forget_me,
|
||||
remember_me: self.remember_me,
|
||||
derivation_material: self.derivation_material,
|
||||
shared_derivation_material: self.shared_derivation_material,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +179,18 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Set shared derivation material for deterministic key generation across multiple clients.
|
||||
/// This allows multiple client instances to derive keys from the same source material
|
||||
/// while ensuring thread-safe access through Arc<Mutex<>>.
|
||||
#[must_use]
|
||||
pub fn with_shared_derivation_material(
|
||||
mut self,
|
||||
derivation_material: Arc<Mutex<DerivationMaterial>>,
|
||||
) -> Self {
|
||||
self.shared_derivation_material = Some(derivation_material);
|
||||
self
|
||||
}
|
||||
|
||||
/// Change the underlying storage of this builder to use default implementation of on-disk disk_persistence.
|
||||
#[must_use]
|
||||
pub fn set_default_storage(
|
||||
@@ -335,6 +354,7 @@ where
|
||||
client.forget_me = self.forget_me;
|
||||
client.remember_me = self.remember_me;
|
||||
client.derivation_material = self.derivation_material;
|
||||
client.shared_derivation_material = self.shared_derivation_material;
|
||||
Ok(client)
|
||||
}
|
||||
}
|
||||
@@ -394,6 +414,11 @@ where
|
||||
|
||||
/// The derivation material to use for the client keys, its up to the caller to save this for rederivation later
|
||||
derivation_material: Option<DerivationMaterial>,
|
||||
|
||||
/// Shared derivation material that can be safely accessed across multiple threads/clients.
|
||||
/// This is useful when multiple clients need to derive keys from the same source while
|
||||
/// maintaining thread safety through Arc<Mutex<>> wrapping.
|
||||
shared_derivation_material: Option<Arc<Mutex<DerivationMaterial>>>,
|
||||
}
|
||||
|
||||
impl<S> DisconnectedMixnetClient<S>
|
||||
@@ -451,6 +476,7 @@ where
|
||||
forget_me,
|
||||
remember_me,
|
||||
derivation_material: None,
|
||||
shared_derivation_material: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -678,7 +704,8 @@ where
|
||||
.with_wait_for_gateway(self.wait_for_gateway)
|
||||
.with_forget_me(&self.forget_me)
|
||||
.with_remember_me(&self.remember_me)
|
||||
.with_derivation_material(self.derivation_material);
|
||||
.with_derivation_material(self.derivation_material)
|
||||
.with_shared_derivation_material(self.shared_derivation_material);
|
||||
|
||||
if let Some(user_agent) = self.user_agent {
|
||||
base_builder = base_builder.with_user_agent(user_agent);
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
[package]
|
||||
name = "sqlx-pool-guard"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
tracing.workspace = true
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||
workspace = true
|
||||
features = ["runtime-tokio-rustls", "sqlite"]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||
workspace = true
|
||||
features = ["rt-multi-thread", "macros", "time", "fs"]
|
||||
|
||||
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
|
||||
proc_pidinfo.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows = { version = "0.61", features = [
|
||||
"Win32",
|
||||
"Win32_System",
|
||||
"Win32_System_Memory",
|
||||
"Win32_System_Threading",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Wdk_System_SystemInformation",
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
@@ -1,43 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::{io, path::Path};
|
||||
|
||||
use proc_pidinfo::{
|
||||
ProcFDInfo, ProcFDType, VnodeFdInfoWithPath, proc_pidfdinfo_self, proc_pidinfo_list_self,
|
||||
};
|
||||
|
||||
/// Check if there are no open file descriptors for the given files.
|
||||
///
|
||||
/// Uses `proc_pidinfo` (`sys/proc_info.h`)
|
||||
/// See: http://blog.palominolabs.com/2012/06/19/getting-the-files-being-used-by-a-process-on-mac-os-x/
|
||||
pub async fn check_files_closed(file_paths: &[&Path]) -> io::Result<bool> {
|
||||
let fd_list = proc_pidinfo_list_self::<ProcFDInfo>()?;
|
||||
|
||||
for fd in fd_list
|
||||
.iter()
|
||||
.filter(|s| s.fd_type() == Ok(ProcFDType::VNODE))
|
||||
{
|
||||
let Some(vnode) = proc_pidfdinfo_self::<VnodeFdInfoWithPath>(fd.proc_fd)
|
||||
.inspect_err(|e| {
|
||||
tracing::warn!("proc_pidfdinfo_self::<VnodeFdInfoWithPath>() failure: {e}");
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Ok(true) = vnode
|
||||
.path()
|
||||
.map(|vnode_path| file_paths.contains(&vnode_path))
|
||||
.inspect_err(|e| {
|
||||
tracing::warn!("vnode.path() failure: {e:?}");
|
||||
})
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::{
|
||||
io,
|
||||
ops::{Deref, DerefMut},
|
||||
path::{Path, PathBuf},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
#[path = "windows.rs"]
|
||||
mod imp;
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
#[path = "apple.rs"]
|
||||
mod imp;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
#[path = "linux.rs"]
|
||||
mod imp;
|
||||
|
||||
/// Max number of retry attempts
|
||||
const CHECK_FILES_CLOSED_MAX_ATTEMPTS: u8 = 20;
|
||||
|
||||
/// Delay between file checks
|
||||
const CHECK_FILES_CLOSED_RETRY_DELAY: Duration = Duration::from_millis(100);
|
||||
|
||||
/// `sqlx::SqlitePool` wrapper providing a workaround for the [known bug](https://github.com/launchbadge/sqlx/issues/3217).
|
||||
/// In principle after requesting to close the sqlite pool, the wrapper monitors open file descriptor and polls periodically until all database files are closed.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SqlitePoolGuard {
|
||||
/// Path to sqlite database file.
|
||||
database_path: PathBuf,
|
||||
|
||||
/// Inner connection pool.
|
||||
connection_pool: sqlx::SqlitePool,
|
||||
}
|
||||
|
||||
impl SqlitePoolGuard {
|
||||
/// Create new instance providing path to database and connection pool
|
||||
pub fn new(database_path: PathBuf, connection_pool: sqlx::SqlitePool) -> Self {
|
||||
Self {
|
||||
database_path,
|
||||
connection_pool,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns database path
|
||||
pub fn database_path(&self) -> &Path {
|
||||
&self.database_path
|
||||
}
|
||||
|
||||
/// Close udnerlying sqlite pool and wait for files to be closed before returning.
|
||||
pub async fn close(&self) {
|
||||
// Avoid waiting for db files once the pool is marked closed to ensure that we don't wait on some other sqlite pool to close the database.
|
||||
if !self.connection_pool.is_closed() {
|
||||
tracing::info!("Closing sqlite pool: {}", self.database_path.display());
|
||||
self.close_pool_inner().await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
async fn close_pool_inner(&self) -> std::io::Result<()> {
|
||||
self.connection_pool.close().await;
|
||||
|
||||
self.wait_for_db_files_close().await.inspect_err(|e| {
|
||||
tracing::error!("Failed to wait for file to close: {e}");
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns all database files, including shm and wal files.
|
||||
fn all_database_files(&self) -> Vec<PathBuf> {
|
||||
let mut database_files = vec![];
|
||||
let canonical_path = self
|
||||
.database_path
|
||||
.canonicalize()
|
||||
.inspect_err(|e| {
|
||||
tracing::error!(
|
||||
"Failed to canonicalize path: {}. Cause: {e}",
|
||||
self.database_path.display()
|
||||
);
|
||||
})
|
||||
.unwrap_or(self.database_path.clone());
|
||||
|
||||
if let Some(ext) = canonical_path.extension() {
|
||||
for added_ext in ["-shm", "-wal"] {
|
||||
let mut new_ext = ext.to_owned();
|
||||
new_ext.push(added_ext);
|
||||
database_files.push(canonical_path.with_extension(new_ext));
|
||||
}
|
||||
}
|
||||
database_files.push(canonical_path);
|
||||
database_files
|
||||
}
|
||||
|
||||
/// Wait for database files to be closed before returning.
|
||||
async fn wait_for_db_files_close(&self) -> std::io::Result<()> {
|
||||
let database_files = self.all_database_files();
|
||||
let paths: Vec<&Path> = database_files.iter().map(PathBuf::as_path).collect();
|
||||
|
||||
for _ in 0..CHECK_FILES_CLOSED_MAX_ATTEMPTS {
|
||||
match imp::check_files_closed(&paths)
|
||||
.await
|
||||
.inspect_err(|e| tracing::error!("imp::check_files_closed() failure: {e}"))
|
||||
{
|
||||
Ok(false) | Err(_) => tokio::time::sleep(CHECK_FILES_CLOSED_RETRY_DELAY).await,
|
||||
Ok(true) => return Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::TimedOut,
|
||||
"timed out waiting for sqlite files to be closed",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SqlitePoolGuard {
|
||||
type Target = sqlx::SqlitePool;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.connection_pool
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for SqlitePoolGuard {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.connection_pool
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use sqlx::{
|
||||
ConnectOptions, Executor,
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_wait_close() {
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(tracing::Level::TRACE)
|
||||
.init();
|
||||
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let database_path = temp_dir.path().join("storage.db");
|
||||
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path.clone())
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
let connection_pool = sqlx::SqlitePool::connect_with(opts).await.unwrap();
|
||||
|
||||
connection_pool
|
||||
.execute("create table test (col int)")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let guard = SqlitePoolGuard::new(database_path.clone(), connection_pool);
|
||||
assert!(
|
||||
guard
|
||||
.wait_for_db_files_close()
|
||||
.await
|
||||
.err()
|
||||
.is_some_and(|e| e.kind() == io::ErrorKind::TimedOut)
|
||||
);
|
||||
|
||||
assert!(guard.close_pool_inner().await.is_ok());
|
||||
tokio::fs::remove_file(database_path).await.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::{io, path::Path};
|
||||
|
||||
static PROC_SELF_FD_DIR: &str = "/proc/self/fd/";
|
||||
|
||||
/// Check if there are no open file descriptors for the given files.
|
||||
///
|
||||
/// Linux, Android: uses `/proc/self/fd/` to list open file descriptors
|
||||
/// See: https://stackoverflow.com/a/59797198/351305
|
||||
pub async fn check_files_closed(file_paths: &[&Path]) -> io::Result<bool> {
|
||||
let mut dir = tokio::fs::read_dir(PROC_SELF_FD_DIR).await?;
|
||||
|
||||
while let Ok(Some(entry)) = dir.next_entry().await {
|
||||
if entry
|
||||
.file_type()
|
||||
.await
|
||||
.inspect_err(|e| tracing::warn!("entry.file_type() failure: {e}"))
|
||||
.is_ok_and(|entry_type| entry_type.is_symlink())
|
||||
{
|
||||
match tokio::fs::read_link(entry.path()).await {
|
||||
Ok(resolved_path) => {
|
||||
if file_paths.contains(&resolved_path.as_ref()) {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to read symlink: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::{
|
||||
ffi::{OsString, c_uchar, c_ulong, c_ushort, c_void},
|
||||
io,
|
||||
os::windows::ffi::OsStringExt,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use windows::{
|
||||
Wdk::System::SystemInformation::{NtQuerySystemInformation, SYSTEM_INFORMATION_CLASS},
|
||||
Win32::{
|
||||
Foundation::{HANDLE, MAX_PATH, NTSTATUS, STATUS_INFO_LENGTH_MISMATCH},
|
||||
Storage::FileSystem::{
|
||||
FILE_NAME_NORMALIZED, FILE_TYPE_DISK, GetFileType, GetFinalPathNameByHandleW,
|
||||
},
|
||||
System::{
|
||||
Memory::{
|
||||
GetProcessHeap, HEAP_FLAGS, HEAP_ZERO_MEMORY, HeapAlloc, HeapFree, HeapReAlloc,
|
||||
},
|
||||
Threading::GetCurrentProcessId,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/// Private information class used to retrieve open file handles
|
||||
const SYSTEM_HANDLE_INFORMATION_CLASS: SYSTEM_INFORMATION_CLASS = SYSTEM_INFORMATION_CLASS(0x10);
|
||||
|
||||
/// Initial buffer size holding the handle info
|
||||
/// The number is based on what I observe on a pretty standard Windows 11
|
||||
const SYSTEM_HANDLE_INFORMATION_INITIAL_SIZE: usize = 2_500_000;
|
||||
|
||||
/// Check if there are no open handles to the given files.
|
||||
///
|
||||
/// Uses undocumented NT API to obtain open handles on the system.
|
||||
/// See: https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/get-all-open-handles-and-kernel-object-address-from-userland
|
||||
pub async fn check_files_closed(file_paths: &[&Path]) -> io::Result<bool> {
|
||||
let current_pid = unsafe { GetCurrentProcessId() };
|
||||
|
||||
// Allocate info struct on heap with some initial value
|
||||
let mut reserved_memory = SYSTEM_HANDLE_INFORMATION_INITIAL_SIZE;
|
||||
let mut handle_table_info = HeapGuard::<SystemHandleInformation>::new(reserved_memory)?;
|
||||
|
||||
// Request system handle information
|
||||
let mut status: NTSTATUS = NTSTATUS::default();
|
||||
let mut return_len = reserved_memory as u32;
|
||||
for _ in 0..2 {
|
||||
status = unsafe {
|
||||
NtQuerySystemInformation(
|
||||
SYSTEM_HANDLE_INFORMATION_CLASS,
|
||||
handle_table_info.as_mut_ptr() as _,
|
||||
return_len,
|
||||
&mut return_len,
|
||||
)
|
||||
};
|
||||
|
||||
// Buffer is too small, resize memory and retry again.
|
||||
if status == STATUS_INFO_LENGTH_MISMATCH {
|
||||
tracing::trace!("Buffer is too small ({reserved_memory}), resizing to {return_len}");
|
||||
reserved_memory = return_len as usize;
|
||||
handle_table_info.reallocate(reserved_memory)?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
status.ok()?;
|
||||
|
||||
// Convert returned data into slice
|
||||
let num_handles = unsafe { (*handle_table_info.inner).number_of_handles };
|
||||
let proc_entries = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
(*handle_table_info.as_mut_ptr()).handles.as_ptr(),
|
||||
num_handles as usize,
|
||||
)
|
||||
};
|
||||
|
||||
// Iterate over open file handle entries
|
||||
for entry in proc_entries {
|
||||
if entry.unique_process_id == current_pid {
|
||||
let file_handle = HANDLE(entry.handle_value as _);
|
||||
|
||||
// Filter everything except disk files
|
||||
if unsafe { GetFileType(file_handle) } == FILE_TYPE_DISK {
|
||||
// Obtain canonical path for file handle
|
||||
let mut file_handle_path = vec![0u16; MAX_PATH as usize];
|
||||
let num_chars_without_nul = unsafe {
|
||||
GetFinalPathNameByHandleW(
|
||||
file_handle,
|
||||
&mut file_handle_path,
|
||||
FILE_NAME_NORMALIZED,
|
||||
) as usize
|
||||
};
|
||||
|
||||
if num_chars_without_nul > 0 {
|
||||
let path_str = OsString::from_wide(&file_handle_path[0..num_chars_without_nul]);
|
||||
let file_handle_pathbuf = PathBuf::from(path_str);
|
||||
if file_paths.contains(&file_handle_pathbuf.as_path()) {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct SystemHandleInformation {
|
||||
pub number_of_handles: c_ulong,
|
||||
pub handles: [SystemHandleTableEntryInfo; 1],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct SystemHandleTableEntryInfo {
|
||||
pub unique_process_id: c_ulong,
|
||||
pub object_type_index: c_uchar,
|
||||
pub handle_attributes: c_uchar,
|
||||
pub handle_value: c_ushort,
|
||||
pub object: *mut c_void,
|
||||
pub granted_access: c_ulong,
|
||||
}
|
||||
|
||||
/// Managed heap memory
|
||||
struct HeapGuard<T> {
|
||||
inner: *mut T,
|
||||
process_heap: HANDLE,
|
||||
}
|
||||
|
||||
impl<T> HeapGuard<T> {
|
||||
/// Allocate new memory using `HealAlloc`
|
||||
fn new(length: usize) -> io::Result<Self> {
|
||||
let process_heap = unsafe { GetProcessHeap()? };
|
||||
let inner: *mut T = unsafe { HeapAlloc(process_heap, HEAP_ZERO_MEMORY, length) as _ };
|
||||
|
||||
if inner.is_null() {
|
||||
Err(io::Error::other("Failed to allocate memory"))
|
||||
} else {
|
||||
Ok(Self {
|
||||
inner,
|
||||
process_heap,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Reallocate existing chunk of memory
|
||||
///
|
||||
/// On success: the internal memory pointer is replaced.
|
||||
/// On failure: the internal memory pointer remains the same and still valid.
|
||||
fn reallocate(&mut self, new_length: usize) -> io::Result<()> {
|
||||
let new_ptr: *mut T = unsafe {
|
||||
HeapReAlloc(
|
||||
self.process_heap,
|
||||
HEAP_ZERO_MEMORY,
|
||||
Some(self.inner as _),
|
||||
new_length,
|
||||
) as _
|
||||
};
|
||||
|
||||
if new_ptr.is_null() {
|
||||
Err(io::Error::other("Failed to reallocate memory"))
|
||||
} else {
|
||||
self.inner = new_ptr;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn as_mut_ptr(&self) -> *mut T {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for HeapGuard<T> {
|
||||
fn drop(&mut self) {
|
||||
#[allow(clippy::expect_used)]
|
||||
unsafe {
|
||||
HeapFree(
|
||||
self.process_heap,
|
||||
HEAP_FLAGS(0),
|
||||
Some(self.inner as *mut c_void),
|
||||
)
|
||||
}
|
||||
.expect("HeapFree failure");
|
||||
}
|
||||
}
|
||||
@@ -83,8 +83,6 @@ impl<'a> From<&'a LoadedNetwork> for nym_config::defaults::NymNetworkDetails {
|
||||
contracts,
|
||||
explorer_api: None,
|
||||
nym_vpn_api_url: None,
|
||||
nym_vpn_api_urls: None,
|
||||
nym_api_urls: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,6 @@ impl InitCtx {
|
||||
contracts: Default::default(),
|
||||
explorer_api: None,
|
||||
nym_vpn_api_url: None,
|
||||
nym_vpn_api_urls: None,
|
||||
nym_api_urls: None,
|
||||
};
|
||||
Ok(Config::try_from_nym_network_details(&network_details)?)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user