Compare commits
152 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 099de36c60 | |||
| 9e5890a0d7 | |||
| 3bda5f59a3 | |||
| 154dfa089b | |||
| bd0cbbc18a | |||
| ed0e7a7a25 | |||
| 5b35cfcfb2 | |||
| d3ba008b88 | |||
| a04a782dbf | |||
| f5d9fda0b1 | |||
| aebd386382 | |||
| 9a6f96b5e0 | |||
| 5a3ff0f9f7 | |||
| 160db34651 | |||
| ae20d2afb8 | |||
| 41b7a2a20d | |||
| 208ec4574b | |||
| 2bff66e2c7 | |||
| 1aad5fc1bf | |||
| cb3e73fbd7 | |||
| eabb36b975 | |||
| 2eed8e3f6c | |||
| bfac3e0b89 | |||
| 90680ceb16 | |||
| f9c5684d6c | |||
| ffb053fe4a | |||
| e83be64a52 | |||
| 32c897f789 | |||
| 9ff37d2f9f | |||
| a6ebfb521d | |||
| ac23ef924a | |||
| 5a770614dd | |||
| 8f8cd79a65 | |||
| d8f73ef97a | |||
| c7fb89bd5e | |||
| 3c2d47ad18 | |||
| 6f13720530 | |||
| 0efd7a2318 | |||
| 2ca2b9e032 | |||
| d92a8ea028 | |||
| 7483d10701 | |||
| ca75c06f4c | |||
| 73632a0ae7 | |||
| 3d3dd80247 | |||
| 1d481db179 | |||
| cae97663c1 | |||
| 795329b874 | |||
| 87ea3fcfc4 | |||
| 289343d1c8 | |||
| f96f74f2f1 | |||
| 3ec2ea904f | |||
| 04373589b1 | |||
| 1a8814ccdc | |||
| d62a41b9c1 | |||
| d3e30e98f9 | |||
| 88a49dfc7e | |||
| 66a54aeab3 | |||
| d6afa74284 | |||
| 49e2be5b04 | |||
| 1cfddb942b | |||
| 49c43617c9 | |||
| ff01fc79e3 | |||
| 5cf53b7002 | |||
| 387d07fb93 | |||
| dcd6dcc6e3 | |||
| e7d0c1812a | |||
| 7bbac26676 | |||
| 688ac2efb5 | |||
| f348e6972a | |||
| dd97eb13a8 | |||
| 92d9cb7dab | |||
| 5a4dfafe9f | |||
| fa93c4598f | |||
| edbcade5f5 | |||
| 3f0194a9aa | |||
| c2517ac63b | |||
| 3fa74c90ff | |||
| 96f3192694 | |||
| f61b898c4f | |||
| c9ff550311 | |||
| 740cc72ec8 | |||
| 6e7bac1e7e | |||
| 691884e20a | |||
| 400d71bf07 | |||
| ffe55ba072 | |||
| 00f1ce98ba | |||
| b02bbdef19 | |||
| 78e1d84905 | |||
| 2638952f5a | |||
| 9a3bd7a2a9 | |||
| ad9aee0ec0 | |||
| f687ebb0f5 | |||
| ddf2770c8e | |||
| 16c942d72e | |||
| 0ee727bac1 | |||
| 675cf3d7da | |||
| 9a0cbf5072 | |||
| 6f3dd9f778 | |||
| 7a7fbce8ea | |||
| 36242fa257 | |||
| b764fcc756 | |||
| ac676760d4 | |||
| 20819331f3 | |||
| 6b6980c523 | |||
| 8b0953624f | |||
| 24a260fbc9 | |||
| 510ad11c98 | |||
| 627334cfe2 | |||
| d4c98e3ff5 | |||
| 9821dd994b | |||
| a977310225 | |||
| 8e16678f74 | |||
| 52c46f371e | |||
| 3010d5192f | |||
| 721ad9d8bb | |||
| 85803ec11c | |||
| 83da1f228b | |||
| c663ba08f2 | |||
| 92bf31d9f4 | |||
| 646f522142 | |||
| be3dd2c250 | |||
| db826c4fb4 | |||
| b960dc8aaf | |||
| da70ae70a5 | |||
| 914b8a6dc2 | |||
| ad2552ec78 | |||
| 45686f7ca6 | |||
| 27554f52e3 | |||
| 29edc8799a | |||
| 46875cdf2f | |||
| 629081b5ec | |||
| d2c77d7f64 | |||
| eab7eb03c7 | |||
| ecc47cd418 | |||
| 71c975d20c | |||
| f0705cd1f9 | |||
| b6d5f780d2 | |||
| 0b46e5b753 | |||
| 2c65460164 | |||
| e86419540c | |||
| 3771cb9188 | |||
| e8f6d6e55d | |||
| 536b892c91 | |||
| 0b6cb236d8 | |||
| f0361a200b | |||
| f1c5e8bdc0 | |||
| b03d737393 | |||
| 9b44674f43 | |||
| 588839740f | |||
| 4353bab636 | |||
| 05957c366f | |||
| 60e14f866e |
@@ -9,12 +9,17 @@ on:
|
|||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
enable_wireguard:
|
enable_wireguard:
|
||||||
description: 'Add --features wireguard'
|
description: "Add --features wireguard"
|
||||||
required: true
|
required: true
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
|
enable_deb:
|
||||||
|
description: "True to enable cargo-deb installation and .deb package building"
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '14 0 * * *'
|
- cron: "14 0 * * *"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- "clients/**"
|
- "clients/**"
|
||||||
@@ -30,6 +35,7 @@ on:
|
|||||||
- "sdk/rust/nym-sdk/**"
|
- "sdk/rust/nym-sdk/**"
|
||||||
- "service-providers/**"
|
- "service-providers/**"
|
||||||
- "tools/**"
|
- "tools/**"
|
||||||
|
- "nymvisor/**"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-nym:
|
publish-nym:
|
||||||
@@ -84,12 +90,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
command: install
|
command: install
|
||||||
args: cargo-deb
|
args: cargo-deb
|
||||||
|
if: github.event_name == 'workflow_dispatch' && inputs.enable_deb == true
|
||||||
|
|
||||||
- name: Build deb packages
|
- name: Build deb packages
|
||||||
shell: bash
|
shell: bash
|
||||||
run: make deb
|
run: make deb
|
||||||
|
if: github.event_name == 'workflow_dispatch' && inputs.enable_deb == true
|
||||||
# If this was a manual workflow_dispatch, publish binaries.
|
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
@@ -105,6 +111,7 @@ jobs:
|
|||||||
target/release/nym-network-requester
|
target/release/nym-network-requester
|
||||||
target/release/nym-network-statistics
|
target/release/nym-network-statistics
|
||||||
target/release/nym-cli
|
target/release/nym-cli
|
||||||
|
target/release/nymvisor
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
|
||||||
# If this was a pull_request or nightly, upload to build server
|
# If this was a pull_request or nightly, upload to build server
|
||||||
@@ -122,12 +129,14 @@ jobs:
|
|||||||
cp target/release/nym-api $OUTPUT_DIR
|
cp target/release/nym-api $OUTPUT_DIR
|
||||||
cp target/release/nym-network-requester $OUTPUT_DIR
|
cp target/release/nym-network-requester $OUTPUT_DIR
|
||||||
cp target/release/nym-network-statistics $OUTPUT_DIR
|
cp target/release/nym-network-statistics $OUTPUT_DIR
|
||||||
|
cp target/release/nymvisor $OUTPUT_DIR
|
||||||
cp target/release/nym-cli $OUTPUT_DIR
|
cp target/release/nym-cli $OUTPUT_DIR
|
||||||
cp target/release/explorer-api $OUTPUT_DIR
|
cp target/release/explorer-api $OUTPUT_DIR
|
||||||
cp target/debian/*.deb $OUTPUT_DIR
|
if [ ${{ github.event_name == 'workflow_dispatch' && inputs.enable_deb == true }} = true ]; then
|
||||||
|
cp target/debian/*.deb $OUTPUT_DIR
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Deploy branch to CI www
|
- name: Deploy branch to CI www
|
||||||
# if: github.event_name == 'schedule' || github.event_name == 'pull_request'
|
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: easingthemes/ssh-deploy@main
|
uses: easingthemes/ssh-deploy@main
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ jobs:
|
|||||||
client_hash: ${{ steps.binary-hashes.outputs.client_hash }}
|
client_hash: ${{ steps.binary-hashes.outputs.client_hash }}
|
||||||
mixnode_hash: ${{ steps.binary-hashes.outputs.mixnode_hash }}
|
mixnode_hash: ${{ steps.binary-hashes.outputs.mixnode_hash }}
|
||||||
gateway_hash: ${{ steps.binary-hashes.outputs.gateway_hash }}
|
gateway_hash: ${{ steps.binary-hashes.outputs.gateway_hash }}
|
||||||
|
nymvisor_hash: ${{ steps.binary-hashes.outputs.nymvisor_hash }}
|
||||||
socks5_hash: ${{ steps.binary-hashes.outputs.socks5_hash }}
|
socks5_hash: ${{ steps.binary-hashes.outputs.socks5_hash }}
|
||||||
netreq_hash: ${{ steps.binary-hashes.outputs.netreq_hash }}
|
netreq_hash: ${{ steps.binary-hashes.outputs.netreq_hash }}
|
||||||
cli_hash: ${{ steps.binary-hashes.outputs.cli_hash }}
|
cli_hash: ${{ steps.binary-hashes.outputs.cli_hash }}
|
||||||
@@ -36,6 +37,7 @@ jobs:
|
|||||||
client_version: ${{ steps.binary-versions.outputs.client_version }}
|
client_version: ${{ steps.binary-versions.outputs.client_version }}
|
||||||
mixnode_version: ${{ steps.binary-versions.outputs.mixnode_version }}
|
mixnode_version: ${{ steps.binary-versions.outputs.mixnode_version }}
|
||||||
gateway_version: ${{ steps.binary-versions.outputs.gateway_version }}
|
gateway_version: ${{ steps.binary-versions.outputs.gateway_version }}
|
||||||
|
nymvisor_version: ${{ steps.binary-versions.outputs.nymvisor_version }}
|
||||||
socks5_version: ${{ steps.binary-versions.outputs.socks5_version }}
|
socks5_version: ${{ steps.binary-versions.outputs.socks5_version }}
|
||||||
netreq_version: ${{ steps.binary-versions.outputs.netreq_version }}
|
netreq_version: ${{ steps.binary-versions.outputs.netreq_version }}
|
||||||
cli_version: ${{ steps.binary-versions.outputs.cli_version }}
|
cli_version: ${{ steps.binary-versions.outputs.cli_version }}
|
||||||
@@ -78,6 +80,7 @@ jobs:
|
|||||||
target/release/nym-network-requester
|
target/release/nym-network-requester
|
||||||
target/release/nym-network-statistics
|
target/release/nym-network-statistics
|
||||||
target/release/nym-cli
|
target/release/nym-cli
|
||||||
|
target/release/nymvisor
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
|
||||||
- id: create-release
|
- id: create-release
|
||||||
@@ -95,6 +98,7 @@ jobs:
|
|||||||
target/release/nym-network-requester
|
target/release/nym-network-requester
|
||||||
target/release/nym-network-statistics
|
target/release/nym-network-statistics
|
||||||
target/release/nym-cli
|
target/release/nym-cli
|
||||||
|
target/release/nymvisor
|
||||||
|
|
||||||
push-release-data-client:
|
push-release-data-client:
|
||||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||||
|
|||||||
@@ -4,6 +4,23 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [2024.1-marabou] (2024-02-15)
|
||||||
|
|
||||||
|
**New Features:**
|
||||||
|
- Introduced nymvisor support for nym-api, gateway, and mixnode binaries ([#4158])
|
||||||
|
- Revamped nym-api execution with the addition of init and run commands ([#4225])
|
||||||
|
|
||||||
|
**Enhancements:**
|
||||||
|
- Implemented internal improvements for gateways to optimize internal packet routing
|
||||||
|
- Improved routing score calculation
|
||||||
|
|
||||||
|
**Bug Fixes:**
|
||||||
|
- Resolved various bugs to enhance overall stability
|
||||||
|
|
||||||
|
[#4158]: https://github.com/nymtech/nym/pull/4158
|
||||||
|
[#4225]: https://github.com/nymtech/nym/pull/4225
|
||||||
|
|
||||||
|
|
||||||
## [2023.5-rolo] (2023-11-28)
|
## [2023.5-rolo] (2023-11-28)
|
||||||
|
|
||||||
- Gateway won't open websocket listener until embedded Network Requester becomes available ([#4166])
|
- Gateway won't open websocket listener until embedded Network Requester becomes available ([#4166])
|
||||||
|
|||||||
Generated
+121
-68
@@ -825,6 +825,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896"
|
checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sha2 0.10.8",
|
"sha2 0.10.8",
|
||||||
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2318,6 +2319,7 @@ dependencies = [
|
|||||||
"digest 0.10.7",
|
"digest 0.10.7",
|
||||||
"elliptic-curve 0.13.6",
|
"elliptic-curve 0.13.6",
|
||||||
"rfc6979 0.4.0",
|
"rfc6979 0.4.0",
|
||||||
|
"serdect",
|
||||||
"signature 2.1.0",
|
"signature 2.1.0",
|
||||||
"spki 0.7.2",
|
"spki 0.7.2",
|
||||||
]
|
]
|
||||||
@@ -2428,6 +2430,7 @@ dependencies = [
|
|||||||
"pkcs8 0.10.2",
|
"pkcs8 0.10.2",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
"sec1 0.7.3",
|
"sec1 0.7.3",
|
||||||
|
"serdect",
|
||||||
"subtle 2.4.1",
|
"subtle 2.4.1",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -2509,7 +2512,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "explorer-api"
|
name = "explorer-api"
|
||||||
version = "1.1.32"
|
version = "1.1.33"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap 4.4.7",
|
"clap 4.4.7",
|
||||||
@@ -2517,7 +2520,6 @@ dependencies = [
|
|||||||
"humantime-serde",
|
"humantime-serde",
|
||||||
"isocountry",
|
"isocountry",
|
||||||
"itertools 0.10.5",
|
"itertools 0.10.5",
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"maxminddb",
|
"maxminddb",
|
||||||
"nym-bin-common",
|
"nym-bin-common",
|
||||||
@@ -3407,7 +3409,7 @@ dependencies = [
|
|||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"hyper",
|
"hyper",
|
||||||
"rustls 0.21.7",
|
"rustls 0.21.10",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.1",
|
"tokio-rustls 0.24.1",
|
||||||
]
|
]
|
||||||
@@ -4918,12 +4920,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nym-api"
|
name = "nym-api"
|
||||||
version = "1.1.34"
|
version = "1.1.35"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bip39",
|
"bip39",
|
||||||
"bs58 0.4.0",
|
"bs58 0.5.0",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"clap 4.4.7",
|
"clap 4.4.7",
|
||||||
"console-subscriber",
|
"console-subscriber",
|
||||||
@@ -4937,7 +4939,7 @@ dependencies = [
|
|||||||
"getset",
|
"getset",
|
||||||
"humantime-serde",
|
"humantime-serde",
|
||||||
"itertools 0.12.0",
|
"itertools 0.12.0",
|
||||||
"lazy_static",
|
"k256",
|
||||||
"log",
|
"log",
|
||||||
"nym-api-requests",
|
"nym-api-requests",
|
||||||
"nym-bandwidth-controller",
|
"nym-bandwidth-controller",
|
||||||
@@ -4945,7 +4947,6 @@ dependencies = [
|
|||||||
"nym-coconut",
|
"nym-coconut",
|
||||||
"nym-coconut-bandwidth-contract-common",
|
"nym-coconut-bandwidth-contract-common",
|
||||||
"nym-coconut-dkg-common",
|
"nym-coconut-dkg-common",
|
||||||
"nym-coconut-interface",
|
|
||||||
"nym-config",
|
"nym-config",
|
||||||
"nym-contracts-common",
|
"nym-contracts-common",
|
||||||
"nym-credential-storage",
|
"nym-credential-storage",
|
||||||
@@ -4996,11 +4997,12 @@ dependencies = [
|
|||||||
name = "nym-api-requests"
|
name = "nym-api-requests"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bs58 0.4.0",
|
"bs58 0.5.0",
|
||||||
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
|
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
|
||||||
"cosmwasm-std",
|
"cosmwasm-std",
|
||||||
|
"ecdsa 0.16.8",
|
||||||
"getset",
|
"getset",
|
||||||
"nym-coconut-interface",
|
"nym-credentials-interface",
|
||||||
"nym-crypto",
|
"nym-crypto",
|
||||||
"nym-mixnet-contract-common",
|
"nym-mixnet-contract-common",
|
||||||
"nym-node-requests",
|
"nym-node-requests",
|
||||||
@@ -5015,9 +5017,11 @@ name = "nym-bandwidth-controller"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bip39",
|
"bip39",
|
||||||
"nym-coconut-interface",
|
"log",
|
||||||
|
"nym-coconut",
|
||||||
"nym-credential-storage",
|
"nym-credential-storage",
|
||||||
"nym-credentials",
|
"nym-credentials",
|
||||||
|
"nym-credentials-interface",
|
||||||
"nym-crypto",
|
"nym-crypto",
|
||||||
"nym-network-defaults",
|
"nym-network-defaults",
|
||||||
"nym-validator-client",
|
"nym-validator-client",
|
||||||
@@ -5068,12 +5072,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nym-cli"
|
name = "nym-cli"
|
||||||
version = "1.1.33"
|
version = "1.1.34"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
"bip39",
|
"bip39",
|
||||||
"bs58 0.4.0",
|
"bs58 0.5.0",
|
||||||
"clap 4.4.7",
|
"clap 4.4.7",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
"clap_complete_fig",
|
"clap_complete_fig",
|
||||||
@@ -5098,7 +5102,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
"bip39",
|
"bip39",
|
||||||
"bs58 0.4.0",
|
"bs58 0.5.0",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"clap 4.4.7",
|
"clap 4.4.7",
|
||||||
"comfy-table",
|
"comfy-table",
|
||||||
@@ -5106,6 +5110,7 @@ dependencies = [
|
|||||||
"cosmwasm-std",
|
"cosmwasm-std",
|
||||||
"csv",
|
"csv",
|
||||||
"cw-utils",
|
"cw-utils",
|
||||||
|
"futures",
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"humantime-serde",
|
"humantime-serde",
|
||||||
"inquire",
|
"inquire",
|
||||||
@@ -5121,7 +5126,9 @@ dependencies = [
|
|||||||
"nym-credential-storage",
|
"nym-credential-storage",
|
||||||
"nym-credential-utils",
|
"nym-credential-utils",
|
||||||
"nym-credentials",
|
"nym-credentials",
|
||||||
|
"nym-credentials-interface",
|
||||||
"nym-crypto",
|
"nym-crypto",
|
||||||
|
"nym-id",
|
||||||
"nym-mixnet-contract-common",
|
"nym-mixnet-contract-common",
|
||||||
"nym-multisig-contract-common",
|
"nym-multisig-contract-common",
|
||||||
"nym-name-service-common",
|
"nym-name-service-common",
|
||||||
@@ -5138,44 +5145,47 @@ dependencies = [
|
|||||||
"tap",
|
"tap",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
|
"tokio",
|
||||||
"toml 0.5.11",
|
"toml 0.5.11",
|
||||||
"url",
|
"url",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nym-client"
|
name = "nym-client"
|
||||||
version = "1.1.32"
|
version = "1.1.33"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bs58 0.5.0",
|
||||||
"clap 4.4.7",
|
"clap 4.4.7",
|
||||||
"dirs 4.0.0",
|
"dirs 4.0.0",
|
||||||
"futures",
|
"futures",
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"nym-bandwidth-controller",
|
"nym-bandwidth-controller",
|
||||||
"nym-bin-common",
|
"nym-bin-common",
|
||||||
"nym-client-core",
|
"nym-client-core",
|
||||||
"nym-client-websocket-requests",
|
"nym-client-websocket-requests",
|
||||||
"nym-coconut-interface",
|
|
||||||
"nym-config",
|
"nym-config",
|
||||||
"nym-credential-storage",
|
"nym-credential-storage",
|
||||||
"nym-credentials",
|
"nym-credentials",
|
||||||
"nym-crypto",
|
"nym-crypto",
|
||||||
"nym-gateway-requests",
|
"nym-gateway-requests",
|
||||||
|
"nym-id",
|
||||||
"nym-network-defaults",
|
"nym-network-defaults",
|
||||||
"nym-pemstore",
|
"nym-pemstore",
|
||||||
"nym-sphinx",
|
"nym-sphinx",
|
||||||
"nym-task",
|
"nym-task",
|
||||||
"nym-topology",
|
"nym-topology",
|
||||||
"nym-validator-client",
|
"nym-validator-client",
|
||||||
"pretty_env_logger",
|
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tap",
|
"tap",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
"url",
|
"url",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5266,7 +5276,7 @@ name = "nym-coconut"
|
|||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bls12_381",
|
"bls12_381",
|
||||||
"bs58 0.4.0",
|
"bs58 0.5.0",
|
||||||
"criterion",
|
"criterion",
|
||||||
"digest 0.9.0",
|
"digest 0.9.0",
|
||||||
"doc-comment",
|
"doc-comment",
|
||||||
@@ -5308,17 +5318,6 @@ dependencies = [
|
|||||||
"nym-multisig-contract-common",
|
"nym-multisig-contract-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nym-coconut-interface"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"bs58 0.4.0",
|
|
||||||
"getset",
|
|
||||||
"nym-coconut",
|
|
||||||
"serde",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nym-config"
|
name = "nym-config"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -5336,7 +5335,7 @@ dependencies = [
|
|||||||
name = "nym-contracts-common"
|
name = "nym-contracts-common"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bs58 0.4.0",
|
"bs58 0.5.0",
|
||||||
"cosmwasm-schema",
|
"cosmwasm-schema",
|
||||||
"cosmwasm-std",
|
"cosmwasm-std",
|
||||||
"schemars",
|
"schemars",
|
||||||
@@ -5354,6 +5353,7 @@ dependencies = [
|
|||||||
"sqlx",
|
"sqlx",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5363,6 +5363,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"nym-bandwidth-controller",
|
"nym-bandwidth-controller",
|
||||||
"nym-client-core",
|
"nym-client-core",
|
||||||
|
"nym-coconut",
|
||||||
"nym-config",
|
"nym-config",
|
||||||
"nym-credential-storage",
|
"nym-credential-storage",
|
||||||
"nym-credentials",
|
"nym-credentials",
|
||||||
@@ -5375,25 +5376,38 @@ dependencies = [
|
|||||||
name = "nym-credentials"
|
name = "nym-credentials"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bincode",
|
||||||
"bls12_381",
|
"bls12_381",
|
||||||
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
|
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
|
||||||
"log",
|
"log",
|
||||||
"nym-api-requests",
|
"nym-api-requests",
|
||||||
"nym-coconut-interface",
|
"nym-credentials-interface",
|
||||||
"nym-crypto",
|
"nym-crypto",
|
||||||
"nym-validator-client",
|
"nym-validator-client",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"time",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nym-credentials-interface"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bls12_381",
|
||||||
|
"nym-coconut",
|
||||||
|
"serde",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nym-crypto"
|
name = "nym-crypto"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes 0.8.3",
|
"aes 0.8.3",
|
||||||
"blake3",
|
"blake3",
|
||||||
"bs58 0.4.0",
|
"bs58 0.5.0",
|
||||||
"cipher 0.4.4",
|
"cipher 0.4.4",
|
||||||
"ctr 0.9.2",
|
"ctr 0.9.2",
|
||||||
"digest 0.10.7",
|
"digest 0.10.7",
|
||||||
@@ -5419,7 +5433,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitvec",
|
"bitvec",
|
||||||
"bls12_381",
|
"bls12_381",
|
||||||
"bs58 0.4.0",
|
"bs58 0.5.0",
|
||||||
"criterion",
|
"criterion",
|
||||||
"ff 0.13.0",
|
"ff 0.13.0",
|
||||||
"group 0.13.0",
|
"group 0.13.0",
|
||||||
@@ -5493,13 +5507,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nym-gateway"
|
name = "nym-gateway"
|
||||||
version = "1.1.32"
|
version = "1.1.33"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"atty",
|
"atty",
|
||||||
"bip39",
|
"bip39",
|
||||||
"bs58 0.4.0",
|
"bs58 0.5.0",
|
||||||
"clap 4.4.7",
|
"clap 4.4.7",
|
||||||
"colored",
|
"colored",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
@@ -5510,13 +5524,12 @@ dependencies = [
|
|||||||
"humantime-serde",
|
"humantime-serde",
|
||||||
"hyper",
|
"hyper",
|
||||||
"ipnetwork 0.16.0",
|
"ipnetwork 0.16.0",
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"nym-api-requests",
|
"nym-api-requests",
|
||||||
"nym-bin-common",
|
"nym-bin-common",
|
||||||
"nym-coconut-interface",
|
|
||||||
"nym-config",
|
"nym-config",
|
||||||
"nym-credentials",
|
"nym-credentials",
|
||||||
|
"nym-credentials-interface",
|
||||||
"nym-crypto",
|
"nym-crypto",
|
||||||
"nym-gateway-requests",
|
"nym-gateway-requests",
|
||||||
"nym-ip-packet-router",
|
"nym-ip-packet-router",
|
||||||
@@ -5541,6 +5554,7 @@ dependencies = [
|
|||||||
"sqlx",
|
"sqlx",
|
||||||
"subtle-encoding",
|
"subtle-encoding",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
@@ -5559,8 +5573,8 @@ dependencies = [
|
|||||||
"gloo-utils",
|
"gloo-utils",
|
||||||
"log",
|
"log",
|
||||||
"nym-bandwidth-controller",
|
"nym-bandwidth-controller",
|
||||||
"nym-coconut-interface",
|
|
||||||
"nym-credential-storage",
|
"nym-credential-storage",
|
||||||
|
"nym-credentials",
|
||||||
"nym-crypto",
|
"nym-crypto",
|
||||||
"nym-gateway-requests",
|
"nym-gateway-requests",
|
||||||
"nym-network-defaults",
|
"nym-network-defaults",
|
||||||
@@ -5586,12 +5600,12 @@ dependencies = [
|
|||||||
name = "nym-gateway-requests"
|
name = "nym-gateway-requests"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bs58 0.4.0",
|
"bs58 0.5.0",
|
||||||
"futures",
|
"futures",
|
||||||
"generic-array 0.14.7",
|
"generic-array 0.14.7",
|
||||||
"log",
|
"log",
|
||||||
"nym-coconut-interface",
|
|
||||||
"nym-credentials",
|
"nym-credentials",
|
||||||
|
"nym-credentials-interface",
|
||||||
"nym-crypto",
|
"nym-crypto",
|
||||||
"nym-pemstore",
|
"nym-pemstore",
|
||||||
"nym-sphinx",
|
"nym-sphinx",
|
||||||
@@ -5614,6 +5628,32 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nym-id"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"nym-credential-storage",
|
||||||
|
"nym-credentials",
|
||||||
|
"thiserror",
|
||||||
|
"time",
|
||||||
|
"tracing",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nym-id-cli"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bs58 0.5.0",
|
||||||
|
"clap 4.4.7",
|
||||||
|
"nym-bin-common",
|
||||||
|
"nym-credential-storage",
|
||||||
|
"nym-id",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nym-inclusion-probability"
|
name = "nym-inclusion-probability"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -5643,12 +5683,11 @@ name = "nym-ip-packet-router"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"bs58 0.4.0",
|
"bs58 0.5.0",
|
||||||
"bytes",
|
"bytes",
|
||||||
"clap 4.4.7",
|
"clap 4.4.7",
|
||||||
"etherparse",
|
"etherparse",
|
||||||
"futures",
|
"futures",
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"nym-bin-common",
|
"nym-bin-common",
|
||||||
"nym-client-core",
|
"nym-client-core",
|
||||||
@@ -5713,11 +5752,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nym-mixnode"
|
name = "nym-mixnode"
|
||||||
version = "1.1.34"
|
version = "1.1.35"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
"bs58 0.4.0",
|
"bs58 0.5.0",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"clap 4.4.7",
|
"clap 4.4.7",
|
||||||
"colored",
|
"colored",
|
||||||
@@ -5726,7 +5765,6 @@ dependencies = [
|
|||||||
"dirs 4.0.0",
|
"dirs 4.0.0",
|
||||||
"futures",
|
"futures",
|
||||||
"humantime-serde",
|
"humantime-serde",
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"nym-bin-common",
|
"nym-bin-common",
|
||||||
"nym-config",
|
"nym-config",
|
||||||
@@ -5833,27 +5871,28 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nym-network-requester"
|
name = "nym-network-requester"
|
||||||
version = "1.1.32"
|
version = "1.1.33"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"addr",
|
"addr",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-file-watcher",
|
"async-file-watcher",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bs58 0.4.0",
|
"bs58 0.5.0",
|
||||||
"clap 4.4.7",
|
"clap 4.4.7",
|
||||||
"dirs 4.0.0",
|
"dirs 4.0.0",
|
||||||
"futures",
|
"futures",
|
||||||
"humantime-serde",
|
"humantime-serde",
|
||||||
"ipnetwork 0.20.0",
|
"ipnetwork 0.20.0",
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"nym-bin-common",
|
"nym-bin-common",
|
||||||
"nym-client-core",
|
"nym-client-core",
|
||||||
"nym-client-websocket-requests",
|
"nym-client-websocket-requests",
|
||||||
"nym-config",
|
"nym-config",
|
||||||
"nym-credential-storage",
|
"nym-credential-storage",
|
||||||
|
"nym-credentials",
|
||||||
"nym-crypto",
|
"nym-crypto",
|
||||||
"nym-exit-policy",
|
"nym-exit-policy",
|
||||||
|
"nym-id",
|
||||||
"nym-network-defaults",
|
"nym-network-defaults",
|
||||||
"nym-ordered-buffer",
|
"nym-ordered-buffer",
|
||||||
"nym-sdk",
|
"nym-sdk",
|
||||||
@@ -5875,14 +5914,16 @@ dependencies = [
|
|||||||
"tap",
|
"tap",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
"url",
|
"url",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nym-network-statistics"
|
name = "nym-network-statistics"
|
||||||
version = "1.1.32"
|
version = "1.1.33"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs 4.0.0",
|
"dirs 4.0.0",
|
||||||
"log",
|
"log",
|
||||||
@@ -6127,33 +6168,34 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nym-socks5-client"
|
name = "nym-socks5-client"
|
||||||
version = "1.1.32"
|
version = "1.1.33"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bs58 0.5.0",
|
||||||
"clap 4.4.7",
|
"clap 4.4.7",
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"nym-bin-common",
|
"nym-bin-common",
|
||||||
"nym-client-core",
|
"nym-client-core",
|
||||||
"nym-coconut-interface",
|
|
||||||
"nym-config",
|
"nym-config",
|
||||||
"nym-credential-storage",
|
"nym-credential-storage",
|
||||||
"nym-credentials",
|
"nym-credentials",
|
||||||
"nym-crypto",
|
"nym-crypto",
|
||||||
"nym-gateway-requests",
|
"nym-gateway-requests",
|
||||||
|
"nym-id",
|
||||||
"nym-network-defaults",
|
"nym-network-defaults",
|
||||||
"nym-ordered-buffer",
|
"nym-ordered-buffer",
|
||||||
"nym-pemstore",
|
"nym-pemstore",
|
||||||
"nym-socks5-client-core",
|
"nym-socks5-client-core",
|
||||||
"nym-sphinx",
|
"nym-sphinx",
|
||||||
"nym-topology",
|
"nym-topology",
|
||||||
"pretty_env_logger",
|
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tap",
|
"tap",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6297,7 +6339,7 @@ dependencies = [
|
|||||||
name = "nym-sphinx-anonymous-replies"
|
name = "nym-sphinx-anonymous-replies"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bs58 0.4.0",
|
"bs58 0.5.0",
|
||||||
"nym-crypto",
|
"nym-crypto",
|
||||||
"nym-sphinx-addressing",
|
"nym-sphinx-addressing",
|
||||||
"nym-sphinx-params",
|
"nym-sphinx-params",
|
||||||
@@ -6437,7 +6479,7 @@ name = "nym-topology"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bs58 0.4.0",
|
"bs58 0.5.0",
|
||||||
"log",
|
"log",
|
||||||
"nym-api-requests",
|
"nym-api-requests",
|
||||||
"nym-bin-common",
|
"nym-bin-common",
|
||||||
@@ -6480,7 +6522,6 @@ dependencies = [
|
|||||||
"hmac 0.12.1",
|
"hmac 0.12.1",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
"log",
|
"log",
|
||||||
"nym-coconut-interface",
|
|
||||||
"nym-config",
|
"nym-config",
|
||||||
"nym-crypto",
|
"nym-crypto",
|
||||||
"nym-mixnet-contract-common",
|
"nym-mixnet-contract-common",
|
||||||
@@ -6522,9 +6563,9 @@ dependencies = [
|
|||||||
"itertools 0.10.5",
|
"itertools 0.10.5",
|
||||||
"log",
|
"log",
|
||||||
"nym-api-requests",
|
"nym-api-requests",
|
||||||
|
"nym-coconut",
|
||||||
"nym-coconut-bandwidth-contract-common",
|
"nym-coconut-bandwidth-contract-common",
|
||||||
"nym-coconut-dkg-common",
|
"nym-coconut-dkg-common",
|
||||||
"nym-coconut-interface",
|
|
||||||
"nym-config",
|
"nym-config",
|
||||||
"nym-contracts-common",
|
"nym-contracts-common",
|
||||||
"nym-ephemera-common",
|
"nym-ephemera-common",
|
||||||
@@ -6694,6 +6735,7 @@ dependencies = [
|
|||||||
"tendermint",
|
"tendermint",
|
||||||
"tendermint-rpc",
|
"tendermint-rpc",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@@ -7985,7 +8027,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite 0.2.13",
|
"pin-project-lite 0.2.13",
|
||||||
"rustls 0.21.7",
|
"rustls 0.21.10",
|
||||||
"rustls-native-certs",
|
"rustls-native-certs",
|
||||||
"rustls-pemfile",
|
"rustls-pemfile",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -8370,12 +8412,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.21.7"
|
version = "0.21.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8"
|
checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"ring 0.16.20",
|
"ring 0.17.4",
|
||||||
"rustls-webpki",
|
"rustls-webpki",
|
||||||
"sct 0.7.0",
|
"sct 0.7.0",
|
||||||
]
|
]
|
||||||
@@ -8403,12 +8445,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.101.6"
|
version = "0.101.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe"
|
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring 0.16.20",
|
"ring 0.17.4",
|
||||||
"untrusted 0.7.1",
|
"untrusted 0.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -8574,6 +8616,7 @@ dependencies = [
|
|||||||
"der 0.7.8",
|
"der 0.7.8",
|
||||||
"generic-array 0.14.7",
|
"generic-array 0.14.7",
|
||||||
"pkcs8 0.10.2",
|
"pkcs8 0.10.2",
|
||||||
|
"serdect",
|
||||||
"subtle 2.4.1",
|
"subtle 2.4.1",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -8806,6 +8849,16 @@ dependencies = [
|
|||||||
"unsafe-libyaml",
|
"unsafe-libyaml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serdect"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177"
|
||||||
|
dependencies = [
|
||||||
|
"base16ct 0.2.0",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha-1"
|
name = "sha-1"
|
||||||
version = "0.9.8"
|
version = "0.9.8"
|
||||||
@@ -9740,7 +9793,7 @@ version = "0.24.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
|
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls 0.21.7",
|
"rustls 0.21.10",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -9801,7 +9854,7 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"log",
|
"log",
|
||||||
"rustls 0.21.7",
|
"rustls 0.21.10",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tungstenite",
|
"tungstenite",
|
||||||
]
|
]
|
||||||
@@ -10233,7 +10286,7 @@ dependencies = [
|
|||||||
"httparse",
|
"httparse",
|
||||||
"log",
|
"log",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"rustls 0.21.7",
|
"rustls 0.21.10",
|
||||||
"sha1",
|
"sha1",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"url",
|
"url",
|
||||||
|
|||||||
+4
-1
@@ -27,7 +27,6 @@ members = [
|
|||||||
"common/client-libs/gateway-client",
|
"common/client-libs/gateway-client",
|
||||||
"common/client-libs/mixnet-client",
|
"common/client-libs/mixnet-client",
|
||||||
"common/client-libs/validator-client",
|
"common/client-libs/validator-client",
|
||||||
"common/coconut-interface",
|
|
||||||
"common/commands",
|
"common/commands",
|
||||||
"common/config",
|
"common/config",
|
||||||
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
|
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
|
||||||
@@ -43,6 +42,7 @@ members = [
|
|||||||
"common/credential-storage",
|
"common/credential-storage",
|
||||||
"common/credentials",
|
"common/credentials",
|
||||||
"common/credential-utils",
|
"common/credential-utils",
|
||||||
|
"common/credentials-interface",
|
||||||
"common/crypto",
|
"common/crypto",
|
||||||
"common/dkg",
|
"common/dkg",
|
||||||
"common/execute",
|
"common/execute",
|
||||||
@@ -56,6 +56,7 @@ members = [
|
|||||||
"common/node-tester-utils",
|
"common/node-tester-utils",
|
||||||
"common/nonexhaustive-delayqueue",
|
"common/nonexhaustive-delayqueue",
|
||||||
"common/nymcoconut",
|
"common/nymcoconut",
|
||||||
|
"common/nym-id",
|
||||||
"common/nymsphinx",
|
"common/nymsphinx",
|
||||||
"common/nymsphinx/acknowledgements",
|
"common/nymsphinx/acknowledgements",
|
||||||
"common/nymsphinx/addressing",
|
"common/nymsphinx/addressing",
|
||||||
@@ -106,6 +107,7 @@ members = [
|
|||||||
"tools/internal/ssl-inject",
|
"tools/internal/ssl-inject",
|
||||||
# "tools/internal/sdk-version-bump",
|
# "tools/internal/sdk-version-bump",
|
||||||
"tools/nym-cli",
|
"tools/nym-cli",
|
||||||
|
"tools/nym-id-cli",
|
||||||
"tools/nym-nr-query",
|
"tools/nym-nr-query",
|
||||||
"tools/nymvisor",
|
"tools/nymvisor",
|
||||||
"tools/ts-rs-cli",
|
"tools/ts-rs-cli",
|
||||||
@@ -143,6 +145,7 @@ anyhow = "1.0.71"
|
|||||||
async-trait = "0.1.68"
|
async-trait = "0.1.68"
|
||||||
axum = "0.6.20"
|
axum = "0.6.20"
|
||||||
base64 = "0.21.4"
|
base64 = "0.21.4"
|
||||||
|
bs58 = "0.5.0"
|
||||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||||
clap = "4.4.7"
|
clap = "4.4.7"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nym-client"
|
name = "nym-client"
|
||||||
version = "1.1.32"
|
version = "1.1.33"
|
||||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||||
description = "Implementation of the Nym Client"
|
description = "Implementation of the Nym Client"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
@@ -21,24 +21,24 @@ futures = { workspace = true } # bunch of futures stuff, however, now that I thi
|
|||||||
# and the single instance of abortable we have should really be refactored anyway
|
# and the single instance of abortable we have should really be refactored anyway
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
|
|
||||||
|
bs58 = { workspace = true }
|
||||||
clap = { workspace = true, features = ["cargo", "derive"] }
|
clap = { workspace = true, features = ["cargo", "derive"] }
|
||||||
dirs = "4.0"
|
dirs = "4.0"
|
||||||
lazy_static = "1.4.0"
|
|
||||||
log = { workspace = true } # self explanatory
|
log = { workspace = true } # self explanatory
|
||||||
pretty_env_logger = "0.4" # for formatting log messages
|
|
||||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use
|
rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use
|
||||||
serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization
|
serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tap = "1.0.1"
|
tap = "1.0.1"
|
||||||
|
time = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] } # async runtime
|
tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] } # async runtime
|
||||||
tokio-tungstenite = { workspace = true }
|
tokio-tungstenite = { workspace = true }
|
||||||
|
zeroize = { workspace = true }
|
||||||
|
|
||||||
## internal
|
## internal
|
||||||
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
||||||
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
|
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
|
||||||
nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage", "cli"] }
|
nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage", "cli"] }
|
||||||
nym-coconut-interface = { path = "../../common/coconut-interface" }
|
|
||||||
nym-config = { path = "../../common/config" }
|
nym-config = { path = "../../common/config" }
|
||||||
nym-credential-storage = { path = "../../common/credential-storage" }
|
nym-credential-storage = { path = "../../common/credential-storage" }
|
||||||
nym-credentials = { path = "../../common/credentials" }
|
nym-credentials = { path = "../../common/credentials" }
|
||||||
@@ -51,5 +51,6 @@ nym-task = { path = "../../common/task" }
|
|||||||
nym-topology = { path = "../../common/topology" }
|
nym-topology = { path = "../../common/topology" }
|
||||||
nym-validator-client = { path = "../../common/client-libs/validator-client", features = ["http-client"] }
|
nym-validator-client = { path = "../../common/client-libs/validator-client", features = ["http-client"] }
|
||||||
nym-client-websocket-requests = { path = "websocket-requests" }
|
nym-client-websocket-requests = { path = "websocket-requests" }
|
||||||
|
nym-id = { path = "../../common/nym-id" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -2160,9 +2160,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ip": {
|
"node_modules/ip": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
|
||||||
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
|
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
@@ -6157,9 +6157,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"ip": {
|
"ip": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
|
||||||
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
|
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"ipaddr.js": {
|
"ipaddr.js": {
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::commands::try_load_current_config;
|
||||||
|
use crate::error::ClientError;
|
||||||
|
use clap::ArgGroup;
|
||||||
|
|
||||||
|
use nym_id::import_credential;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
|
||||||
|
bs58::decode(raw).into_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Args)]
|
||||||
|
#[clap(group(ArgGroup::new("cred_data").required(true)))]
|
||||||
|
pub(crate) struct Args {
|
||||||
|
/// Id of client that is going to import the credential
|
||||||
|
#[clap(long)]
|
||||||
|
pub id: String,
|
||||||
|
|
||||||
|
/// Explicitly provide the encoded credential data (as base58)
|
||||||
|
#[clap(long, group = "cred_data", value_parser = parse_encoded_credential_data)]
|
||||||
|
pub(crate) credential_data: Option<Vec<u8>>,
|
||||||
|
|
||||||
|
/// Specifies the path to file containing binary credential data
|
||||||
|
#[clap(long, group = "cred_data")]
|
||||||
|
pub(crate) credential_path: Option<PathBuf>,
|
||||||
|
|
||||||
|
// currently hidden as there exists only a single serialization standard
|
||||||
|
#[clap(long, hide = true)]
|
||||||
|
pub(crate) version: Option<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn execute(args: Args) -> Result<(), ClientError> {
|
||||||
|
let config = try_load_current_config(&args.id)?;
|
||||||
|
|
||||||
|
let credentials_store = nym_credential_storage::initialise_persistent_storage(
|
||||||
|
&config.storage_paths.common_paths.credentials_database,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let raw_credential = match args.credential_data {
|
||||||
|
Some(data) => data,
|
||||||
|
None => {
|
||||||
|
// SAFETY: one of those arguments must have been set
|
||||||
|
fs::read(args.credential_path.unwrap())?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
import_credential(credentials_store, raw_credential, args.version).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -8,7 +8,6 @@ use crate::client::config::{BaseClientConfig, Config};
|
|||||||
use crate::error::ClientError;
|
use crate::error::ClientError;
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use nym_bin_common::bin_info;
|
use nym_bin_common::bin_info;
|
||||||
use nym_bin_common::completions::{fig_generate, ArgShell};
|
use nym_bin_common::completions::{fig_generate, ArgShell};
|
||||||
@@ -21,18 +20,16 @@ use nym_client_core::error::ClientCoreError;
|
|||||||
use nym_config::OptionalSet;
|
use nym_config::OptionalSet;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
pub(crate) mod build_info;
|
pub(crate) mod build_info;
|
||||||
|
pub(crate) mod import_credential;
|
||||||
pub(crate) mod init;
|
pub(crate) mod init;
|
||||||
pub(crate) mod run;
|
pub(crate) mod run;
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper for passing LONG_VERSION to clap
|
|
||||||
fn pretty_build_info_static() -> &'static str {
|
fn pretty_build_info_static() -> &'static str {
|
||||||
&PRETTY_BUILD_INFORMATION
|
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
|
||||||
|
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
@@ -58,6 +55,9 @@ pub(crate) enum Commands {
|
|||||||
/// Run the Nym client with provided configuration client optionally overriding set parameters
|
/// Run the Nym client with provided configuration client optionally overriding set parameters
|
||||||
Run(run::Run),
|
Run(run::Run),
|
||||||
|
|
||||||
|
/// Import a pre-generated credential
|
||||||
|
ImportCredential(import_credential::Args),
|
||||||
|
|
||||||
/// Show build information of this binary
|
/// Show build information of this binary
|
||||||
BuildInfo(build_info::BuildInfo),
|
BuildInfo(build_info::BuildInfo),
|
||||||
|
|
||||||
@@ -86,6 +86,7 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync
|
|||||||
match args.command {
|
match args.command {
|
||||||
Commands::Init(m) => init::execute(m).await?,
|
Commands::Init(m) => init::execute(m).await?,
|
||||||
Commands::Run(m) => run::execute(m).await?,
|
Commands::Run(m) => run::execute(m).await?,
|
||||||
|
Commands::ImportCredential(m) => import_credential::execute(m).await?,
|
||||||
Commands::BuildInfo(m) => build_info::execute(m),
|
Commands::BuildInfo(m) => build_info::execute(m),
|
||||||
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
|
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
|
||||||
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
|
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
use nym_client_core::error::ClientCoreError;
|
use nym_client_core::error::ClientCoreError;
|
||||||
|
|
||||||
|
use nym_id::NymIdError;
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum ClientError {
|
pub enum ClientError {
|
||||||
#[error("I/O error: {0}")]
|
#[error("I/O error: {0}")]
|
||||||
IoError(#[from] std::io::Error),
|
IoError(#[from] std::io::Error),
|
||||||
|
|
||||||
#[error("client-core error: {0}")]
|
#[error(transparent)]
|
||||||
ClientCoreError(#[from] ClientCoreError),
|
ClientCoreError(#[from] ClientCoreError),
|
||||||
|
|
||||||
#[error("Failed to load config for: {0}")]
|
#[error("Failed to load config for: {0}")]
|
||||||
@@ -20,4 +22,7 @@ pub enum ClientError {
|
|||||||
|
|
||||||
#[error("Attempted to start the client in invalid socket mode")]
|
#[error("Attempted to start the client in invalid socket mode")]
|
||||||
InvalidSocketMode,
|
InvalidSocketMode,
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
NymIdError(#[from] NymIdError),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nym-socks5-client"
|
name = "nym-socks5-client"
|
||||||
version = "1.1.32"
|
version = "1.1.33"
|
||||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
@@ -8,22 +8,22 @@ rust-version = "1.56"
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bs58 = { workspace = true }
|
||||||
clap = { workspace = true, features = ["cargo", "derive"] }
|
clap = { workspace = true, features = ["cargo", "derive"] }
|
||||||
lazy_static = "1.4.0"
|
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
pretty_env_logger = "0.4"
|
|
||||||
serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization
|
serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
tap = "1.0.1"
|
tap = "1.0.1"
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] }
|
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] }
|
||||||
rand = "0.7.3"
|
rand = "0.7.3"
|
||||||
|
time = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
|
zeroize = { workspace = true }
|
||||||
|
|
||||||
# internal
|
# internal
|
||||||
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
|
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
|
||||||
nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage", "cli"] }
|
nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage", "cli"] }
|
||||||
nym-coconut-interface = { path = "../../common/coconut-interface" }
|
|
||||||
nym-config = { path = "../../common/config" }
|
nym-config = { path = "../../common/config" }
|
||||||
nym-credentials = { path = "../../common/credentials" }
|
nym-credentials = { path = "../../common/credentials" }
|
||||||
nym-crypto = { path = "../../common/crypto" }
|
nym-crypto = { path = "../../common/crypto" }
|
||||||
@@ -35,6 +35,7 @@ nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
|
|||||||
nym-pemstore = { path = "../../common/pemstore" }
|
nym-pemstore = { path = "../../common/pemstore" }
|
||||||
nym-topology = { path = "../../common/topology" }
|
nym-topology = { path = "../../common/topology" }
|
||||||
nym-socks5-client-core = { path = "../../common/socks5-client-core" }
|
nym-socks5-client-core = { path = "../../common/socks5-client-core" }
|
||||||
|
nym-id = { path = "../../common/nym-id" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::commands::try_load_current_config;
|
||||||
|
use crate::error::Socks5ClientError;
|
||||||
|
use clap::ArgGroup;
|
||||||
|
|
||||||
|
use nym_id::import_credential;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
|
||||||
|
bs58::decode(raw).into_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Args)]
|
||||||
|
#[clap(group(ArgGroup::new("cred_data").required(true)))]
|
||||||
|
pub(crate) struct Args {
|
||||||
|
/// Id of client that is going to import the credential
|
||||||
|
#[clap(long)]
|
||||||
|
pub id: String,
|
||||||
|
|
||||||
|
/// Explicitly provide the encoded credential data (as base58)
|
||||||
|
#[clap(long, group = "cred_data", value_parser = parse_encoded_credential_data)]
|
||||||
|
pub(crate) credential_data: Option<Vec<u8>>,
|
||||||
|
|
||||||
|
/// Specifies the path to file containing binary credential data
|
||||||
|
#[clap(long, group = "cred_data")]
|
||||||
|
pub(crate) credential_path: Option<PathBuf>,
|
||||||
|
|
||||||
|
// currently hidden as there exists only a single serialization standard
|
||||||
|
#[clap(long, hide = true)]
|
||||||
|
pub(crate) version: Option<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn execute(args: Args) -> Result<(), Socks5ClientError> {
|
||||||
|
let config = try_load_current_config(&args.id)?;
|
||||||
|
|
||||||
|
let credentials_store = nym_credential_storage::initialise_persistent_storage(
|
||||||
|
&config.storage_paths.common_paths.credentials_database,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let raw_credential = match args.credential_data {
|
||||||
|
Some(data) => data,
|
||||||
|
None => {
|
||||||
|
// SAFETY: one of those arguments must have been set
|
||||||
|
fs::read(args.credential_path.unwrap())?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
import_credential(credentials_store, raw_credential, args.version).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -9,7 +9,6 @@ use crate::config::{BaseClientConfig, Config, SocksClientPaths};
|
|||||||
use crate::error::Socks5ClientError;
|
use crate::error::Socks5ClientError;
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use nym_bin_common::bin_info;
|
use nym_bin_common::bin_info;
|
||||||
use nym_bin_common::completions::{fig_generate, ArgShell};
|
use nym_bin_common::completions::{fig_generate, ArgShell};
|
||||||
@@ -24,18 +23,16 @@ use nym_config::OptionalSet;
|
|||||||
use nym_sphinx::params::{PacketSize, PacketType};
|
use nym_sphinx::params::{PacketSize, PacketType};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
pub(crate) mod build_info;
|
pub(crate) mod build_info;
|
||||||
|
mod import_credential;
|
||||||
pub mod init;
|
pub mod init;
|
||||||
pub(crate) mod run;
|
pub(crate) mod run;
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper for passing LONG_VERSION to clap
|
|
||||||
fn pretty_build_info_static() -> &'static str {
|
fn pretty_build_info_static() -> &'static str {
|
||||||
&PRETTY_BUILD_INFORMATION
|
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
|
||||||
|
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
@@ -61,6 +58,9 @@ pub(crate) enum Commands {
|
|||||||
/// Run the Nym client with provided configuration client optionally overriding set parameters
|
/// Run the Nym client with provided configuration client optionally overriding set parameters
|
||||||
Run(run::Run),
|
Run(run::Run),
|
||||||
|
|
||||||
|
/// Import a pre-generated credential
|
||||||
|
ImportCredential(import_credential::Args),
|
||||||
|
|
||||||
/// Show build information of this binary
|
/// Show build information of this binary
|
||||||
BuildInfo(build_info::BuildInfo),
|
BuildInfo(build_info::BuildInfo),
|
||||||
|
|
||||||
@@ -92,6 +92,7 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync
|
|||||||
match args.command {
|
match args.command {
|
||||||
Commands::Init(m) => init::execute(m).await?,
|
Commands::Init(m) => init::execute(m).await?,
|
||||||
Commands::Run(m) => run::execute(m).await?,
|
Commands::Run(m) => run::execute(m).await?,
|
||||||
|
Commands::ImportCredential(m) => import_credential::execute(m).await?,
|
||||||
Commands::BuildInfo(m) => build_info::execute(m),
|
Commands::BuildInfo(m) => build_info::execute(m),
|
||||||
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
|
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
|
||||||
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
|
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use nym_client_core::error::ClientCoreError;
|
use nym_client_core::error::ClientCoreError;
|
||||||
|
|
||||||
|
use nym_id::NymIdError;
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum Socks5ClientError {
|
pub enum Socks5ClientError {
|
||||||
#[error("I/O error: {0}")]
|
#[error("I/O error: {0}")]
|
||||||
@@ -18,6 +20,9 @@ pub enum Socks5ClientError {
|
|||||||
#[error("Fail to bind address")]
|
#[error("Fail to bind address")]
|
||||||
FailToBindAddress,
|
FailToBindAddress,
|
||||||
|
|
||||||
#[error("client-core error: {0}")]
|
#[error(transparent)]
|
||||||
ClientCoreError(#[from] ClientCoreError),
|
ClientCoreError(#[from] ClientCoreError),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
NymIdError(#[from] NymIdError),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,16 @@ license.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bip39 = { workspace = true }
|
bip39 = { workspace = true }
|
||||||
|
log = { workspace = true }
|
||||||
rand = "0.7.3"
|
rand = "0.7.3"
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
zeroize = { workspace = true }
|
zeroize = { workspace = true }
|
||||||
|
|
||||||
nym-coconut-interface = { path = "../coconut-interface" }
|
nym-coconut = { path = "../nymcoconut" }
|
||||||
nym-credential-storage = { path = "../credential-storage" }
|
nym-credential-storage = { path = "../credential-storage" }
|
||||||
nym-credentials = { path = "../credentials" }
|
nym-credentials = { path = "../credentials" }
|
||||||
|
nym-credentials-interface = { path = "../credentials-interface" }
|
||||||
nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
|
nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
|
||||||
nym-network-defaults = { path = "../network-defaults" }
|
nym-network-defaults = { path = "../network-defaults" }
|
||||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::error::BandwidthControllerError;
|
use crate::error::BandwidthControllerError;
|
||||||
use nym_coconut_interface::Base58;
|
use nym_credential_storage::models::StorableIssuedCredential;
|
||||||
use nym_credential_storage::storage::Storage;
|
use nym_credential_storage::storage::Storage;
|
||||||
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
|
use nym_credentials::coconut::bandwidth::{CredentialType, IssuanceBandwidthCredential};
|
||||||
use nym_credentials::coconut::utils::obtain_aggregate_signature;
|
use nym_credentials::coconut::utils::obtain_aggregate_signature;
|
||||||
use nym_crypto::asymmetric::{encryption, identity};
|
use nym_crypto::asymmetric::{encryption, identity};
|
||||||
use nym_network_defaults::VOUCHER_INFO;
|
|
||||||
use nym_validator_client::coconut::all_coconut_api_clients;
|
use nym_validator_client::coconut::all_coconut_api_clients;
|
||||||
use nym_validator_client::nyxd::contract_traits::CoconutBandwidthSigningClient;
|
use nym_validator_client::nyxd::contract_traits::CoconutBandwidthSigningClient;
|
||||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||||
use nym_validator_client::nyxd::Coin;
|
use nym_validator_client::nyxd::Coin;
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use state::State;
|
use state::State;
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
pub mod state;
|
pub mod state;
|
||||||
|
|
||||||
@@ -24,13 +24,11 @@ where
|
|||||||
let mut rng = OsRng;
|
let mut rng = OsRng;
|
||||||
let signing_key = identity::PrivateKey::new(&mut rng);
|
let signing_key = identity::PrivateKey::new(&mut rng);
|
||||||
let encryption_key = encryption::PrivateKey::new(&mut rng);
|
let encryption_key = encryption::PrivateKey::new(&mut rng);
|
||||||
let params = BandwidthVoucher::default_parameters();
|
|
||||||
let voucher_value = amount.amount.to_string();
|
|
||||||
|
|
||||||
let tx_hash = client
|
let tx_hash = client
|
||||||
.deposit(
|
.deposit(
|
||||||
amount,
|
amount.clone(),
|
||||||
String::from(VOUCHER_INFO),
|
CredentialType::Voucher.to_string(),
|
||||||
signing_key.public_key().to_base58_string(),
|
signing_key.public_key().to_base58_string(),
|
||||||
encryption_key.public_key().to_base58_string(),
|
encryption_key.public_key().to_base58_string(),
|
||||||
None,
|
None,
|
||||||
@@ -38,21 +36,15 @@ where
|
|||||||
.await?
|
.await?
|
||||||
.transaction_hash;
|
.transaction_hash;
|
||||||
|
|
||||||
let voucher = BandwidthVoucher::new(
|
let voucher =
|
||||||
¶ms,
|
IssuanceBandwidthCredential::new_voucher(amount, tx_hash, signing_key, encryption_key);
|
||||||
voucher_value,
|
|
||||||
VOUCHER_INFO.to_string(),
|
|
||||||
tx_hash,
|
|
||||||
signing_key,
|
|
||||||
encryption_key,
|
|
||||||
);
|
|
||||||
|
|
||||||
let state = State { voucher, params };
|
let state = State { voucher };
|
||||||
|
|
||||||
Ok(state)
|
Ok(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_credential<C, St>(
|
pub async fn get_bandwidth_voucher<C, St>(
|
||||||
state: &State,
|
state: &State,
|
||||||
client: &C,
|
client: &C,
|
||||||
storage: &St,
|
storage: &St,
|
||||||
@@ -62,6 +54,9 @@ where
|
|||||||
St: Storage,
|
St: Storage,
|
||||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
|
// temporary
|
||||||
|
assert!(state.voucher.typ().is_voucher());
|
||||||
|
|
||||||
let epoch_id = client.get_current_epoch().await?.epoch_id;
|
let epoch_id = client.get_current_epoch().await?.epoch_id;
|
||||||
let threshold = client
|
let threshold = client
|
||||||
.get_current_epoch_threshold()
|
.get_current_epoch_threshold()
|
||||||
@@ -70,22 +65,23 @@ where
|
|||||||
|
|
||||||
let coconut_api_clients = all_coconut_api_clients(client, epoch_id).await?;
|
let coconut_api_clients = all_coconut_api_clients(client, epoch_id).await?;
|
||||||
|
|
||||||
let signature = obtain_aggregate_signature(
|
let signature =
|
||||||
&state.params,
|
obtain_aggregate_signature(&state.voucher, &coconut_api_clients, threshold).await?;
|
||||||
&state.voucher,
|
let issued = state.voucher.to_issued_credential(signature, epoch_id);
|
||||||
&coconut_api_clients,
|
|
||||||
threshold,
|
// make sure the data gets zeroized after persisting it
|
||||||
)
|
let credential_data = Zeroizing::new(issued.pack_v1());
|
||||||
.await?;
|
let storable = StorableIssuedCredential {
|
||||||
|
serialization_revision: issued.current_serialization_revision(),
|
||||||
|
credential_data: credential_data.as_ref(),
|
||||||
|
credential_type: issued.typ().to_string(),
|
||||||
|
epoch_id: epoch_id
|
||||||
|
.try_into()
|
||||||
|
.expect("our epoch is has run over u32::MAX!"),
|
||||||
|
};
|
||||||
|
|
||||||
storage
|
storage
|
||||||
.insert_coconut_credential(
|
.insert_issued_credential(storable)
|
||||||
state.voucher.get_voucher_value(),
|
|
||||||
VOUCHER_INFO.to_string(),
|
|
||||||
state.voucher.get_private_attributes()[0].to_bs58(),
|
|
||||||
state.voucher.get_private_attributes()[1].to_bs58(),
|
|
||||||
signature.to_bs58(),
|
|
||||||
epoch_id.to_string(),
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))
|
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,14 @@
|
|||||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use nym_coconut_interface::Parameters;
|
use nym_credentials::coconut::bandwidth::IssuanceBandwidthCredential;
|
||||||
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
|
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub voucher: BandwidthVoucher,
|
pub voucher: IssuanceBandwidthCredential,
|
||||||
pub params: Parameters,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub fn new(voucher: BandwidthVoucher) -> Self {
|
pub fn new(voucher: IssuanceBandwidthCredential) -> Self {
|
||||||
State {
|
State { voucher }
|
||||||
voucher,
|
|
||||||
params: BandwidthVoucher::default_parameters(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use nym_coconut_interface::CoconutError;
|
use nym_coconut::CoconutError;
|
||||||
use nym_credential_storage::error::StorageError;
|
use nym_credential_storage::error::StorageError;
|
||||||
use nym_credentials::error::Error as CredentialsError;
|
use nym_credentials::error::Error as CredentialsError;
|
||||||
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
|
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
|
||||||
@@ -21,6 +21,9 @@ pub enum BandwidthControllerError {
|
|||||||
#[error("There was a credential storage error - {0}")]
|
#[error("There was a credential storage error - {0}")]
|
||||||
CredentialStorageError(Box<dyn std::error::Error + Send + Sync>),
|
CredentialStorageError(Box<dyn std::error::Error + Send + Sync>),
|
||||||
|
|
||||||
|
#[error("the credential storage does not contain any usable credentials")]
|
||||||
|
NoCredentialsAvailable,
|
||||||
|
|
||||||
// this should really be fully incorporated into the above, but messing with coconut is the last thing I want to do now
|
// this should really be fully incorporated into the above, but messing with coconut is the last thing I want to do now
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
StorageError(#[from] StorageError),
|
StorageError(#[from] StorageError),
|
||||||
@@ -45,4 +48,7 @@ pub enum BandwidthControllerError {
|
|||||||
|
|
||||||
#[error("Threshold not set yet")]
|
#[error("Threshold not set yet")]
|
||||||
NoThreshold,
|
NoThreshold,
|
||||||
|
|
||||||
|
#[error("can't handle recovering storage with revision {stored}. {expected} was expected")]
|
||||||
|
UnsupportedCredentialStorageRevision { stored: u8, expected: u8 },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +1,140 @@
|
|||||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::error::BandwidthControllerError;
|
use crate::error::BandwidthControllerError;
|
||||||
use nym_credential_storage::error::StorageError;
|
use crate::utils::stored_credential_to_issued_bandwidth;
|
||||||
|
use log::{debug, error, warn};
|
||||||
use nym_credential_storage::storage::Storage;
|
use nym_credential_storage::storage::Storage;
|
||||||
|
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
|
||||||
|
use nym_credentials::coconut::bandwidth::CredentialSpendingData;
|
||||||
|
use nym_credentials::coconut::utils::obtain_aggregate_verification_key;
|
||||||
|
use nym_credentials::IssuedBandwidthCredential;
|
||||||
|
use nym_credentials_interface::VerificationKey;
|
||||||
use nym_validator_client::coconut::all_coconut_api_clients;
|
use nym_validator_client::coconut::all_coconut_api_clients;
|
||||||
|
use nym_validator_client::nym_api::EpochId;
|
||||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||||
use std::str::FromStr;
|
|
||||||
use zeroize::Zeroizing;
|
|
||||||
use {
|
|
||||||
nym_coconut_interface::Base58,
|
|
||||||
nym_credentials::coconut::{
|
|
||||||
bandwidth::prepare_for_spending, utils::obtain_aggregate_verification_key,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod acquire;
|
pub mod acquire;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
pub struct BandwidthController<C, St> {
|
pub struct BandwidthController<C, St> {
|
||||||
storage: St,
|
storage: St,
|
||||||
client: C,
|
client: C,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct PreparedCredential {
|
||||||
|
/// The cryptographic material required for spending the underlying credential.
|
||||||
|
pub data: CredentialSpendingData,
|
||||||
|
|
||||||
|
/// The (DKG) epoch id under which the credential has been issued so that the verifier
|
||||||
|
/// could use correct verification key for validation.
|
||||||
|
pub epoch_id: EpochId,
|
||||||
|
|
||||||
|
/// The database id of the stored credential.
|
||||||
|
pub credential_id: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RetrievedCredential {
|
||||||
|
pub credential: IssuedBandwidthCredential,
|
||||||
|
pub credential_id: i64,
|
||||||
|
}
|
||||||
|
|
||||||
impl<C, St: Storage> BandwidthController<C, St> {
|
impl<C, St: Storage> BandwidthController<C, St> {
|
||||||
pub fn new(storage: St, client: C) -> Self {
|
pub fn new(storage: St, client: C) -> Self {
|
||||||
BandwidthController { storage, client }
|
BandwidthController { storage, client }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tries to retrieve one of the stored, unused credentials that hasn't yet expired.
|
||||||
|
/// It marks any retrieved intermediate credentials as expired.
|
||||||
|
pub async fn get_next_usable_credential(
|
||||||
|
&self,
|
||||||
|
) -> Result<RetrievedCredential, BandwidthControllerError>
|
||||||
|
where
|
||||||
|
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
loop {
|
||||||
|
let Some(maybe_next) = self
|
||||||
|
.storage
|
||||||
|
.get_next_unspent_credential()
|
||||||
|
.await
|
||||||
|
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?
|
||||||
|
else {
|
||||||
|
return Err(BandwidthControllerError::NoCredentialsAvailable);
|
||||||
|
};
|
||||||
|
let id = maybe_next.id;
|
||||||
|
|
||||||
|
// try to deserialize it
|
||||||
|
let valid_credential = match stored_credential_to_issued_bandwidth(maybe_next) {
|
||||||
|
// check if it has already expired
|
||||||
|
Ok(credential) => match credential.variant_data() {
|
||||||
|
BandwidthCredentialIssuedDataVariant::Voucher(_) => {
|
||||||
|
debug!("credential {id} is a bandwidth voucher");
|
||||||
|
credential
|
||||||
|
}
|
||||||
|
BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => {
|
||||||
|
debug!("credential {id} is a free pass");
|
||||||
|
if freepass_info.expired() {
|
||||||
|
warn!("the free pass (id: {id}) has already expired! The expiration was set to {}", freepass_info.expiry_date());
|
||||||
|
self.storage.mark_expired(id).await.map_err(|err| {
|
||||||
|
BandwidthControllerError::CredentialStorageError(Box::new(err))
|
||||||
|
})?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
credential
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
error!("failed to deserialize credential with id {id}: {err}. it may need to be manually removed from the storage");
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return Ok(RetrievedCredential {
|
||||||
|
credential: valid_credential,
|
||||||
|
credential_id: id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn storage(&self) -> &St {
|
pub fn storage(&self) -> &St {
|
||||||
&self.storage
|
&self.storage
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn prepare_coconut_credential(
|
async fn get_aggregate_verification_key(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<(nym_coconut_interface::Credential, i64), BandwidthControllerError>
|
epoch_id: EpochId,
|
||||||
|
) -> Result<VerificationKey, BandwidthControllerError>
|
||||||
where
|
where
|
||||||
C: DkgQueryClient + Sync + Send,
|
C: DkgQueryClient + Sync + Send,
|
||||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let bandwidth_credential = self
|
|
||||||
.storage
|
|
||||||
.get_next_coconut_credential()
|
|
||||||
.await
|
|
||||||
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?;
|
|
||||||
let voucher_value = u64::from_str(&bandwidth_credential.voucher_value)
|
|
||||||
.map_err(|_| StorageError::InconsistentData)?;
|
|
||||||
let voucher_info = bandwidth_credential.voucher_info.clone();
|
|
||||||
let serial_number = Zeroizing::new(nym_coconut_interface::Attribute::try_from_bs58(
|
|
||||||
bandwidth_credential.serial_number,
|
|
||||||
)?);
|
|
||||||
let binding_number = Zeroizing::new(nym_coconut_interface::Attribute::try_from_bs58(
|
|
||||||
bandwidth_credential.binding_number,
|
|
||||||
)?);
|
|
||||||
let signature =
|
|
||||||
nym_coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?;
|
|
||||||
let epoch_id = u64::from_str(&bandwidth_credential.epoch_id)
|
|
||||||
.map_err(|_| StorageError::InconsistentData)?;
|
|
||||||
|
|
||||||
let coconut_api_clients = all_coconut_api_clients(&self.client, epoch_id).await?;
|
let coconut_api_clients = all_coconut_api_clients(&self.client, epoch_id).await?;
|
||||||
|
Ok(obtain_aggregate_verification_key(&coconut_api_clients)?)
|
||||||
|
}
|
||||||
|
|
||||||
let verification_key = obtain_aggregate_verification_key(&coconut_api_clients).await?;
|
pub async fn prepare_bandwidth_credential(
|
||||||
|
&self,
|
||||||
|
) -> Result<PreparedCredential, BandwidthControllerError>
|
||||||
|
where
|
||||||
|
C: DkgQueryClient + Sync + Send,
|
||||||
|
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
let retrieved_credential = self.get_next_usable_credential().await?;
|
||||||
|
|
||||||
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
|
let epoch_id = retrieved_credential.credential.epoch_id();
|
||||||
Ok((
|
let credential_id = retrieved_credential.credential_id;
|
||||||
prepare_for_spending(
|
|
||||||
voucher_value,
|
let verification_key = self.get_aggregate_verification_key(epoch_id).await?;
|
||||||
voucher_info,
|
|
||||||
&serial_number,
|
let spend_request = retrieved_credential
|
||||||
&binding_number,
|
.credential
|
||||||
epoch_id,
|
.prepare_for_spending(&verification_key)?;
|
||||||
&signature,
|
|
||||||
&verification_key,
|
Ok(PreparedCredential {
|
||||||
)?,
|
data: spend_request,
|
||||||
bandwidth_credential.id,
|
epoch_id,
|
||||||
))
|
credential_id,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn consume_credential(&self, id: i64) -> Result<(), BandwidthControllerError>
|
pub async fn consume_credential(&self, id: i64) -> Result<(), BandwidthControllerError>
|
||||||
@@ -93,7 +153,7 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
|||||||
impl<C, St> Clone for BandwidthController<C, St>
|
impl<C, St> Clone for BandwidthController<C, St>
|
||||||
where
|
where
|
||||||
C: Clone,
|
C: Clone,
|
||||||
St: Storage + Clone,
|
St: Clone,
|
||||||
{
|
{
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
BandwidthController {
|
BandwidthController {
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::error::BandwidthControllerError;
|
||||||
|
use nym_credential_storage::models::StoredIssuedCredential;
|
||||||
|
use nym_credentials::coconut::bandwidth::issued::CURRENT_SERIALIZATION_REVISION;
|
||||||
|
use nym_credentials::coconut::bandwidth::IssuedBandwidthCredential;
|
||||||
|
|
||||||
|
pub fn stored_credential_to_issued_bandwidth(
|
||||||
|
cred: StoredIssuedCredential,
|
||||||
|
) -> Result<IssuedBandwidthCredential, BandwidthControllerError> {
|
||||||
|
if cred.serialization_revision != CURRENT_SERIALIZATION_REVISION {
|
||||||
|
return Err(
|
||||||
|
BandwidthControllerError::UnsupportedCredentialStorageRevision {
|
||||||
|
stored: cred.serialization_revision,
|
||||||
|
expected: CURRENT_SERIALIZATION_REVISION,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(IssuedBandwidthCredential::unpack_v1(&cred.credential_data)?)
|
||||||
|
}
|
||||||
@@ -69,6 +69,35 @@ impl BinaryBuildInformation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Varient where we want to use the metadata generated by vergen in the consuming crate.
|
||||||
|
pub const fn new_with_local_vergen(
|
||||||
|
binary_name: &'static str,
|
||||||
|
build_timestamp: &'static str,
|
||||||
|
build_version: &'static str,
|
||||||
|
commit_sha: &'static str,
|
||||||
|
commit_timestamp: &'static str,
|
||||||
|
commit_branch: &'static str,
|
||||||
|
) -> Self {
|
||||||
|
let cargo_debug = env!("VERGEN_CARGO_DEBUG");
|
||||||
|
let cargo_profile = if const_str::equal!(cargo_debug, "true") {
|
||||||
|
"debug"
|
||||||
|
} else {
|
||||||
|
"release"
|
||||||
|
};
|
||||||
|
|
||||||
|
BinaryBuildInformation {
|
||||||
|
binary_name,
|
||||||
|
build_timestamp,
|
||||||
|
build_version,
|
||||||
|
commit_sha,
|
||||||
|
commit_timestamp,
|
||||||
|
commit_branch,
|
||||||
|
rustc_version: env!("VERGEN_RUSTC_SEMVER"),
|
||||||
|
rustc_channel: env!("VERGEN_RUSTC_CHANNEL"),
|
||||||
|
cargo_profile,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_owned(&self) -> BinaryBuildInformationOwned {
|
pub fn to_owned(&self) -> BinaryBuildInformationOwned {
|
||||||
BinaryBuildInformationOwned {
|
BinaryBuildInformationOwned {
|
||||||
binary_name: self.binary_name.to_owned(),
|
binary_name: self.binary_name.to_owned(),
|
||||||
@@ -187,3 +216,33 @@ macro_rules! bin_info_owned {
|
|||||||
.to_owned()
|
.to_owned()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// variant that picks up the vergen build information generated by the build.rs in the consumer
|
||||||
|
// crate.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! bin_info_local_vergen {
|
||||||
|
() => {
|
||||||
|
$crate::build_information::BinaryBuildInformation::new_with_local_vergen(
|
||||||
|
env!("CARGO_PKG_NAME"),
|
||||||
|
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||||
|
env!("CARGO_PKG_VERSION"),
|
||||||
|
env!("VERGEN_GIT_SHA"),
|
||||||
|
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
|
||||||
|
env!("VERGEN_GIT_BRANCH"),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! bin_info_local_vergen_owned {
|
||||||
|
() => {
|
||||||
|
$crate::build_information::BinaryBuildInformation::new_with_local_vergen(
|
||||||
|
env!("CARGO_PKG_NAME"),
|
||||||
|
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||||
|
env!("CARGO_PKG_VERSION"),
|
||||||
|
env!("VERGEN_GIT_SHA"),
|
||||||
|
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
|
||||||
|
env!("VERGEN_GIT_BRANCH"),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ use nym_topology::provider_trait::TopologyProvider;
|
|||||||
use nym_topology::HardcodedTopologyProvider;
|
use nym_topology::HardcodedTopologyProvider;
|
||||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use std::os::raw::c_int as RawFd;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@@ -103,6 +104,12 @@ pub struct ClientState {
|
|||||||
pub shared_lane_queue_lengths: LaneQueueLengths,
|
pub shared_lane_queue_lengths: LaneQueueLengths,
|
||||||
pub reply_controller_sender: ReplyControllerSender,
|
pub reply_controller_sender: ReplyControllerSender,
|
||||||
pub topology_accessor: TopologyAccessor,
|
pub topology_accessor: TopologyAccessor,
|
||||||
|
pub gateway_connection: GatewayConnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct GatewayConnection {
|
||||||
|
pub gateway_ws_fd: Option<RawFd>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ClientInputStatus {
|
pub enum ClientInputStatus {
|
||||||
@@ -666,6 +673,7 @@ where
|
|||||||
shutdown.fork("gateway_transceiver"),
|
shutdown.fork("gateway_transceiver"),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
let gateway_ws_fd = gateway_transceiver.ws_fd();
|
||||||
|
|
||||||
let reply_storage = Self::setup_persistent_reply_storage(
|
let reply_storage = Self::setup_persistent_reply_storage(
|
||||||
reply_storage_backend,
|
reply_storage_backend,
|
||||||
@@ -759,6 +767,7 @@ where
|
|||||||
shared_lane_queue_lengths,
|
shared_lane_queue_lengths,
|
||||||
reply_controller_sender,
|
reply_controller_sender,
|
||||||
topology_accessor: shared_topology_accessor,
|
topology_accessor: shared_topology_accessor,
|
||||||
|
gateway_connection: GatewayConnection { gateway_ws_fd },
|
||||||
},
|
},
|
||||||
task_handle: shutdown,
|
task_handle: shutdown,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use nym_gateway_client::GatewayClient;
|
|||||||
pub use nym_gateway_client::{GatewayPacketRouter, PacketRouter};
|
pub use nym_gateway_client::{GatewayPacketRouter, PacketRouter};
|
||||||
use nym_sphinx::forwarding::packet::MixPacket;
|
use nym_sphinx::forwarding::packet::MixPacket;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use std::os::raw::c_int as RawFd;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
@@ -25,6 +26,7 @@ fn erase_err<E: std::error::Error + Send + Sync + 'static>(err: E) -> ErasedGate
|
|||||||
/// This combines combines the functionalities of being able to send and receive mix packets.
|
/// This combines combines the functionalities of being able to send and receive mix packets.
|
||||||
pub trait GatewayTransceiver: GatewaySender + GatewayReceiver {
|
pub trait GatewayTransceiver: GatewaySender + GatewayReceiver {
|
||||||
fn gateway_identity(&self) -> identity::PublicKey;
|
fn gateway_identity(&self) -> identity::PublicKey;
|
||||||
|
fn ws_fd(&self) -> Option<RawFd>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This trait defines the functionality of sending `MixPacket` into the mixnet,
|
/// This trait defines the functionality of sending `MixPacket` into the mixnet,
|
||||||
@@ -66,6 +68,9 @@ impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
|
|||||||
fn gateway_identity(&self) -> identity::PublicKey {
|
fn gateway_identity(&self) -> identity::PublicKey {
|
||||||
(**self).gateway_identity()
|
(**self).gateway_identity()
|
||||||
}
|
}
|
||||||
|
fn ws_fd(&self) -> Option<RawFd> {
|
||||||
|
(**self).ws_fd()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||||
@@ -112,6 +117,9 @@ where
|
|||||||
fn gateway_identity(&self) -> identity::PublicKey {
|
fn gateway_identity(&self) -> identity::PublicKey {
|
||||||
self.gateway_client.gateway_identity()
|
self.gateway_client.gateway_identity()
|
||||||
}
|
}
|
||||||
|
fn ws_fd(&self) -> Option<RawFd> {
|
||||||
|
self.gateway_client.ws_fd()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||||
@@ -187,6 +195,9 @@ mod nonwasm_sealed {
|
|||||||
fn gateway_identity(&self) -> identity::PublicKey {
|
fn gateway_identity(&self) -> identity::PublicKey {
|
||||||
self.local_identity
|
self.local_identity
|
||||||
}
|
}
|
||||||
|
fn ws_fd(&self) -> Option<RawFd> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -259,4 +270,7 @@ impl GatewayTransceiver for MockGateway {
|
|||||||
fn gateway_identity(&self) -> identity::PublicKey {
|
fn gateway_identity(&self) -> identity::PublicKey {
|
||||||
self.dummy_identity
|
self.dummy_identity
|
||||||
}
|
}
|
||||||
|
fn ws_fd(&self) -> Option<RawFd> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ tokio = { version = "1.24.1", features = ["macros"] }
|
|||||||
|
|
||||||
# internal
|
# internal
|
||||||
nym-bandwidth-controller = { path = "../../bandwidth-controller" }
|
nym-bandwidth-controller = { path = "../../bandwidth-controller" }
|
||||||
nym-coconut-interface = { path = "../../coconut-interface" }
|
nym-credentials = { path = "../../credentials" }
|
||||||
nym-credential-storage = { path = "../../credential-storage" }
|
nym-credential-storage = { path = "../../credential-storage" }
|
||||||
nym-crypto = { path = "../../crypto" }
|
nym-crypto = { path = "../../crypto" }
|
||||||
nym-gateway-requests = { path = "../../../gateway/gateway-requests" }
|
nym-gateway-requests = { path = "../../../gateway/gateway-requests" }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::error::GatewayClientError;
|
use crate::error::GatewayClientError;
|
||||||
@@ -6,20 +6,23 @@ use crate::packet_router::PacketRouter;
|
|||||||
pub use crate::packet_router::{
|
pub use crate::packet_router::{
|
||||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||||
};
|
};
|
||||||
use crate::socket_state::{PartiallyDelegated, SocketState};
|
use crate::socket_state::{ws_fd, PartiallyDelegated, SocketState};
|
||||||
use crate::traits::GatewayPacketRouter;
|
use crate::traits::GatewayPacketRouter;
|
||||||
use crate::{cleanup_socket_message, try_decrypt_binary_message};
|
use crate::{cleanup_socket_message, try_decrypt_binary_message};
|
||||||
use futures::{SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt};
|
||||||
use log::*;
|
use log::*;
|
||||||
use nym_bandwidth_controller::BandwidthController;
|
use nym_bandwidth_controller::BandwidthController;
|
||||||
use nym_coconut_interface::Credential;
|
|
||||||
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
|
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
|
||||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||||
|
use nym_credentials::CredentialSpendingData;
|
||||||
use nym_crypto::asymmetric::identity;
|
use nym_crypto::asymmetric::identity;
|
||||||
use nym_gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
|
use nym_gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
|
||||||
use nym_gateway_requests::iv::IV;
|
use nym_gateway_requests::iv::IV;
|
||||||
use nym_gateway_requests::registration::handshake::{client_handshake, SharedKeys};
|
use nym_gateway_requests::registration::handshake::{client_handshake, SharedKeys};
|
||||||
use nym_gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse, PROTOCOL_VERSION};
|
use nym_gateway_requests::{
|
||||||
|
BinaryRequest, ClientControlRequest, ServerResponse, CREDENTIAL_UPDATE_V1_PROTOCOL_VERSION,
|
||||||
|
CURRENT_PROTOCOL_VERSION,
|
||||||
|
};
|
||||||
use nym_network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN};
|
use nym_network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN};
|
||||||
use nym_sphinx::forwarding::packet::MixPacket;
|
use nym_sphinx::forwarding::packet::MixPacket;
|
||||||
use nym_task::TaskClient;
|
use nym_task::TaskClient;
|
||||||
@@ -30,11 +33,15 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tungstenite::protocol::Message;
|
use tungstenite::protocol::Message;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::fd::RawFd;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use tokio_tungstenite::connect_async;
|
use tokio_tungstenite::connect_async;
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
use std::os::raw::c_int as RawFd;
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use wasm_utils::websocket::JSWebsocket;
|
use wasm_utils::websocket::JSWebsocket;
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
@@ -79,6 +86,9 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
|||||||
/// Delay between each subsequent reconnection attempt.
|
/// Delay between each subsequent reconnection attempt.
|
||||||
reconnection_backoff: Duration,
|
reconnection_backoff: Duration,
|
||||||
|
|
||||||
|
// currently unused (but populated)
|
||||||
|
negotiated_protocol: Option<u8>,
|
||||||
|
|
||||||
/// Listen to shutdown messages.
|
/// Listen to shutdown messages.
|
||||||
shutdown: TaskClient,
|
shutdown: TaskClient,
|
||||||
}
|
}
|
||||||
@@ -108,6 +118,7 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
should_reconnect_on_failure: true,
|
should_reconnect_on_failure: true,
|
||||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||||
|
negotiated_protocol: None,
|
||||||
shutdown,
|
shutdown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,6 +157,14 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
self.gateway_identity
|
self.gateway_identity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ws_fd(&self) -> Option<RawFd> {
|
||||||
|
match &self.connection {
|
||||||
|
SocketState::Available(conn) => ws_fd(conn.as_ref()),
|
||||||
|
SocketState::PartiallyDelegated(conn) => conn.ws_fd(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn remaining_bandwidth(&self) -> i64 {
|
pub fn remaining_bandwidth(&self) -> i64 {
|
||||||
self.bandwidth_remaining
|
self.bandwidth_remaining
|
||||||
}
|
}
|
||||||
@@ -376,6 +395,8 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
&self,
|
&self,
|
||||||
gateway_protocol: Option<u8>,
|
gateway_protocol: Option<u8>,
|
||||||
) -> Result<(), GatewayClientError> {
|
) -> Result<(), GatewayClientError> {
|
||||||
|
debug!("gateway protocol: {gateway_protocol:?}, ours: {CURRENT_PROTOCOL_VERSION}");
|
||||||
|
|
||||||
// right now there are no failure cases here, but this might change in the future
|
// right now there are no failure cases here, but this might change in the future
|
||||||
match gateway_protocol {
|
match gateway_protocol {
|
||||||
None => {
|
None => {
|
||||||
@@ -383,17 +404,17 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
// note: in +1.2.0 we will have to return a hard error here
|
// note: in +1.2.0 we will have to return a hard error here
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Some(v) if v != PROTOCOL_VERSION => {
|
Some(v) if v > CURRENT_PROTOCOL_VERSION => {
|
||||||
let err = GatewayClientError::IncompatibleProtocol {
|
let err = GatewayClientError::IncompatibleProtocol {
|
||||||
gateway: Some(v),
|
gateway: Some(v),
|
||||||
current: PROTOCOL_VERSION,
|
current: CURRENT_PROTOCOL_VERSION,
|
||||||
};
|
};
|
||||||
error!("{err}");
|
error!("{err}");
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
info!("the gateway is using exactly the same protocol version as we are. We're good to continue!");
|
info!("the gateway is using exactly the same (or older) protocol version as we are. We're good to continue!");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -439,6 +460,10 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
if self.authenticated {
|
if self.authenticated {
|
||||||
self.shared_key = Some(Arc::new(shared_key));
|
self.shared_key = Some(Arc::new(shared_key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// populate the negotiated protocol for future uses
|
||||||
|
self.negotiated_protocol = gateway_protocol;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,6 +506,7 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
self.check_gateway_protocol(protocol_version)?;
|
self.check_gateway_protocol(protocol_version)?;
|
||||||
self.authenticated = status;
|
self.authenticated = status;
|
||||||
self.bandwidth_remaining = bandwidth_remaining;
|
self.bandwidth_remaining = bandwidth_remaining;
|
||||||
|
self.negotiated_protocol = protocol_version;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||||
@@ -515,13 +541,13 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
|
|
||||||
async fn claim_coconut_bandwidth(
|
async fn claim_coconut_bandwidth(
|
||||||
&mut self,
|
&mut self,
|
||||||
credential: Credential,
|
credential: CredentialSpendingData,
|
||||||
) -> Result<(), GatewayClientError> {
|
) -> Result<(), GatewayClientError> {
|
||||||
let mut rng = OsRng;
|
let mut rng = OsRng;
|
||||||
let iv = IV::new_random(&mut rng);
|
let iv = IV::new_random(&mut rng);
|
||||||
|
|
||||||
let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential(
|
let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential_v2(
|
||||||
&credential,
|
credential,
|
||||||
self.shared_key.as_ref().unwrap(),
|
self.shared_key.as_ref().unwrap(),
|
||||||
iv,
|
iv,
|
||||||
)
|
)
|
||||||
@@ -567,18 +593,31 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
return self.try_claim_testnet_bandwidth().await;
|
return self.try_claim_testnet_bandwidth().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (credential, credential_id) = self
|
let Some(gateway_protocol) = self.negotiated_protocol else {
|
||||||
|
return Err(GatewayClientError::OutdatedGatewayCredentialVersion {
|
||||||
|
negotiated_protocol: None,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if gateway_protocol < CREDENTIAL_UPDATE_V1_PROTOCOL_VERSION {
|
||||||
|
return Err(GatewayClientError::OutdatedGatewayCredentialVersion {
|
||||||
|
negotiated_protocol: Some(gateway_protocol),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let prepared_credential = self
|
||||||
.bandwidth_controller
|
.bandwidth_controller
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.prepare_coconut_credential()
|
.prepare_bandwidth_credential()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.claim_coconut_bandwidth(credential).await?;
|
self.claim_coconut_bandwidth(prepared_credential.data)
|
||||||
|
.await?;
|
||||||
self.bandwidth_controller
|
self.bandwidth_controller
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.consume_credential(credential_id)
|
.consume_credential(prepared_credential.credential_id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -817,6 +856,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
|||||||
should_reconnect_on_failure: false,
|
should_reconnect_on_failure: false,
|
||||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||||
|
negotiated_protocol: None,
|
||||||
shutdown,
|
shutdown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -848,6 +888,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
|||||||
should_reconnect_on_failure: self.should_reconnect_on_failure,
|
should_reconnect_on_failure: self.should_reconnect_on_failure,
|
||||||
reconnection_attempts: self.reconnection_attempts,
|
reconnection_attempts: self.reconnection_attempts,
|
||||||
reconnection_backoff: self.reconnection_backoff,
|
reconnection_backoff: self.reconnection_backoff,
|
||||||
|
negotiated_protocol: self.negotiated_protocol,
|
||||||
shutdown,
|
shutdown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ pub enum GatewayClientError {
|
|||||||
#[error("Credential could not be serialized")]
|
#[error("Credential could not be serialized")]
|
||||||
SerializeCredential,
|
SerializeCredential,
|
||||||
|
|
||||||
|
#[error("can not spend bandwidth credential with the gateway as it's using outdated protocol (version: {negotiated_protocol:?})")]
|
||||||
|
OutdatedGatewayCredentialVersion { negotiated_protocol: Option<u8> },
|
||||||
|
|
||||||
#[error("Client is not authenticated")]
|
#[error("Client is not authenticated")]
|
||||||
NotAuthenticated,
|
NotAuthenticated,
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,12 @@ use futures::{SinkExt, StreamExt};
|
|||||||
use log::*;
|
use log::*;
|
||||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||||
use nym_task::TaskClient;
|
use nym_task::TaskClient;
|
||||||
|
use std::os::raw::c_int as RawFd;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tungstenite::Message;
|
use tungstenite::Message;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::fd::AsRawFd;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
@@ -37,9 +40,22 @@ type WsConn = JSWebsocket;
|
|||||||
|
|
||||||
type SplitStreamReceiver = oneshot::Receiver<Result<SplitStream<WsConn>, GatewayClientError>>;
|
type SplitStreamReceiver = oneshot::Receiver<Result<SplitStream<WsConn>, GatewayClientError>>;
|
||||||
|
|
||||||
|
pub(crate) fn ws_fd(_conn: &WsConn) -> Option<RawFd> {
|
||||||
|
#[cfg(unix)]
|
||||||
|
match _conn.get_ref() {
|
||||||
|
MaybeTlsStream::Plain(stream) => Some(stream.as_raw_fd()),
|
||||||
|
&_ => unreachable!(
|
||||||
|
"If tls features are enabled, the inner stream needs to be unpacked into raw fd"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct PartiallyDelegated {
|
pub(crate) struct PartiallyDelegated {
|
||||||
sink_half: SplitSink<WsConn, Message>,
|
sink_half: SplitSink<WsConn, Message>,
|
||||||
delegated_stream: (SplitStreamReceiver, oneshot::Sender<()>),
|
delegated_stream: (SplitStreamReceiver, oneshot::Sender<()>),
|
||||||
|
ws_fd: Option<RawFd>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartiallyDelegated {
|
impl PartiallyDelegated {
|
||||||
@@ -92,6 +108,8 @@ impl PartiallyDelegated {
|
|||||||
let (notify_sender, notify_receiver) = oneshot::channel();
|
let (notify_sender, notify_receiver) = oneshot::channel();
|
||||||
let (stream_sender, stream_receiver) = oneshot::channel();
|
let (stream_sender, stream_receiver) = oneshot::channel();
|
||||||
|
|
||||||
|
let ws_fd = ws_fd(&conn);
|
||||||
|
|
||||||
let (sink, mut stream) = conn.split();
|
let (sink, mut stream) = conn.split();
|
||||||
|
|
||||||
let mixnet_receiver_future = async move {
|
let mixnet_receiver_future = async move {
|
||||||
@@ -141,11 +159,16 @@ impl PartiallyDelegated {
|
|||||||
tokio::spawn(mixnet_receiver_future);
|
tokio::spawn(mixnet_receiver_future);
|
||||||
|
|
||||||
PartiallyDelegated {
|
PartiallyDelegated {
|
||||||
|
ws_fd,
|
||||||
sink_half: sink,
|
sink_half: sink,
|
||||||
delegated_stream: (stream_receiver, notify_sender),
|
delegated_stream: (stream_receiver, notify_sender),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn ws_fd(&self) -> Option<RawFd> {
|
||||||
|
self.ws_fd
|
||||||
|
}
|
||||||
|
|
||||||
// if we want to send a message and don't care about response, we can don't need to reunite the split,
|
// if we want to send a message and don't care about response, we can don't need to reunite the split,
|
||||||
// the sink itself is enough
|
// the sink itself is enough
|
||||||
pub(crate) async fn send_without_response(
|
pub(crate) async fn send_without_response(
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ url = { workspace = true, features = ["serde"] }
|
|||||||
tokio = { workspace = true, features = ["sync", "time"] }
|
tokio = { workspace = true, features = ["sync", "time"] }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
|
|
||||||
nym-coconut-interface = { path = "../../coconut-interface" }
|
nym-coconut = { path = "../../nymcoconut" }
|
||||||
nym-network-defaults = { path = "../../network-defaults" }
|
nym-network-defaults = { path = "../../network-defaults" }
|
||||||
nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
|
nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ use crate::{
|
|||||||
nym_api, DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient,
|
nym_api, DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient,
|
||||||
ReqwestRpcClient, ValidatorClientError,
|
ReqwestRpcClient, ValidatorClientError,
|
||||||
};
|
};
|
||||||
|
use nym_api_requests::coconut::models::FreePassNonceResponse;
|
||||||
use nym_api_requests::coconut::{
|
use nym_api_requests::coconut::{
|
||||||
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
|
BlindSignRequestBody, BlindedSignatureResponse, FreePassRequest, VerifyCredentialBody,
|
||||||
|
VerifyCredentialResponse,
|
||||||
};
|
};
|
||||||
use nym_api_requests::models::{DescribedGateway, MixNodeBondAnnotated};
|
use nym_api_requests::models::{DescribedGateway, MixNodeBondAnnotated};
|
||||||
use nym_api_requests::models::{
|
use nym_api_requests::models::{
|
||||||
@@ -348,4 +350,15 @@ impl NymApiClient {
|
|||||||
.verify_bandwidth_credential(request_body)
|
.verify_bandwidth_credential(request_body)
|
||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn free_pass_nonce(&self) -> Result<FreePassNonceResponse, ValidatorClientError> {
|
||||||
|
Ok(self.nym_api.free_pass_nonce().await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn issue_free_pass_credential(
|
||||||
|
&self,
|
||||||
|
request: &FreePassRequest,
|
||||||
|
) -> Result<BlindedSignatureResponse, ValidatorClientError> {
|
||||||
|
Ok(self.nym_api.free_pass(request).await?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
use crate::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient};
|
use crate::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient};
|
||||||
use crate::nyxd::error::NyxdError;
|
use crate::nyxd::error::NyxdError;
|
||||||
use crate::NymApiClient;
|
use crate::NymApiClient;
|
||||||
|
use nym_coconut::{Base58, CoconutError, VerificationKey};
|
||||||
use nym_coconut_dkg_common::types::{EpochId, NodeIndex};
|
use nym_coconut_dkg_common::types::{EpochId, NodeIndex};
|
||||||
use nym_coconut_dkg_common::verification_key::ContractVKShare;
|
use nym_coconut_dkg_common::verification_key::ContractVKShare;
|
||||||
use nym_coconut_interface::{Base58, CoconutError, VerificationKey};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ pub mod error;
|
|||||||
pub mod routes;
|
pub mod routes;
|
||||||
|
|
||||||
pub use http_api_client::Client;
|
pub use http_api_client::Client;
|
||||||
|
use nym_api_requests::coconut::models::FreePassNonceResponse;
|
||||||
|
use nym_api_requests::coconut::FreePassRequest;
|
||||||
|
|
||||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||||
@@ -373,6 +375,36 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn free_pass_nonce(&self) -> Result<FreePassNonceResponse, NymAPIError> {
|
||||||
|
self.get_json(
|
||||||
|
&[
|
||||||
|
routes::API_VERSION,
|
||||||
|
routes::COCONUT_ROUTES,
|
||||||
|
routes::BANDWIDTH,
|
||||||
|
routes::COCONUT_FREE_PASS_NONCE,
|
||||||
|
],
|
||||||
|
NO_PARAMS,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn free_pass(
|
||||||
|
&self,
|
||||||
|
request: &FreePassRequest,
|
||||||
|
) -> Result<BlindedSignatureResponse, NymAPIError> {
|
||||||
|
self.post_json(
|
||||||
|
&[
|
||||||
|
routes::API_VERSION,
|
||||||
|
routes::COCONUT_ROUTES,
|
||||||
|
routes::BANDWIDTH,
|
||||||
|
routes::COCONUT_FREE_PASS,
|
||||||
|
],
|
||||||
|
NO_PARAMS,
|
||||||
|
request,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
async fn blind_sign(
|
async fn blind_sign(
|
||||||
&self,
|
&self,
|
||||||
request_body: &BlindSignRequestBody,
|
request_body: &BlindSignRequestBody,
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ pub const REWARDED: &str = "rewarded";
|
|||||||
pub const COCONUT_ROUTES: &str = "coconut";
|
pub const COCONUT_ROUTES: &str = "coconut";
|
||||||
pub const BANDWIDTH: &str = "bandwidth";
|
pub const BANDWIDTH: &str = "bandwidth";
|
||||||
|
|
||||||
|
pub const COCONUT_FREE_PASS: &str = "free-pass";
|
||||||
|
pub const COCONUT_FREE_PASS_NONCE: &str = "free-pass-nonce";
|
||||||
pub const COCONUT_BLIND_SIGN: &str = "blind-sign";
|
pub const COCONUT_BLIND_SIGN: &str = "blind-sign";
|
||||||
pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential";
|
pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential";
|
||||||
pub const COCONUT_EPOCH_CREDENTIALS: &str = "epoch-credentials";
|
pub const COCONUT_EPOCH_CREDENTIALS: &str = "epoch-credentials";
|
||||||
|
|||||||
@@ -7,19 +7,20 @@ use crate::nyxd::error::NyxdError;
|
|||||||
use crate::nyxd::CosmWasmClient;
|
use crate::nyxd::CosmWasmClient;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use cosmrs::AccountId;
|
use cosmrs::AccountId;
|
||||||
use nym_coconut_dkg_common::types::ChunkIndex;
|
use cosmwasm_std::Addr;
|
||||||
|
use log::trace;
|
||||||
|
use nym_coconut_dkg_common::types::{ChunkIndex, NodeIndex, StateAdvanceResponse};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use nym_coconut_dkg_common::dealer::RegisteredDealerDetails;
|
||||||
pub use nym_coconut_dkg_common::{
|
pub use nym_coconut_dkg_common::{
|
||||||
dealer::{DealerDetailsResponse, PagedDealerResponse},
|
dealer::{DealerDetailsResponse, PagedDealerIndexResponse, PagedDealerResponse},
|
||||||
dealing::{
|
dealing::{
|
||||||
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
|
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
|
||||||
DealingMetadataResponse, DealingStatusResponse,
|
DealingMetadataResponse, DealingStatusResponse,
|
||||||
},
|
},
|
||||||
msg::QueryMsg as DkgQueryMsg,
|
msg::QueryMsg as DkgQueryMsg,
|
||||||
types::{
|
types::{DealerDetails, DealingIndex, Epoch, EpochId, EpochState, State},
|
||||||
DealerDetails, DealingIndex, Epoch, EpochId, EpochState, InitialReplacementData, State,
|
|
||||||
},
|
|
||||||
verification_key::{ContractVKShare, PagedVKSharesResponse, VkShareResponse},
|
verification_key::{ContractVKShare, PagedVKSharesResponse, VkShareResponse},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,13 +41,25 @@ pub trait DkgQueryClient {
|
|||||||
self.query_dkg_contract(request).await
|
self.query_dkg_contract(request).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn can_advance_state(&self) -> Result<StateAdvanceResponse, NyxdError> {
|
||||||
|
let request = DkgQueryMsg::CanAdvanceState {};
|
||||||
|
self.query_dkg_contract(request).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_current_epoch_threshold(&self) -> Result<Option<u64>, NyxdError> {
|
async fn get_current_epoch_threshold(&self) -> Result<Option<u64>, NyxdError> {
|
||||||
let request = DkgQueryMsg::GetCurrentEpochThreshold {};
|
let request = DkgQueryMsg::GetCurrentEpochThreshold {};
|
||||||
self.query_dkg_contract(request).await
|
self.query_dkg_contract(request).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_initial_dealers(&self) -> Result<Option<InitialReplacementData>, NyxdError> {
|
async fn get_registered_dealer_details(
|
||||||
let request = DkgQueryMsg::GetInitialDealers {};
|
&self,
|
||||||
|
address: &AccountId,
|
||||||
|
epoch_id: Option<EpochId>,
|
||||||
|
) -> Result<RegisteredDealerDetails, NyxdError> {
|
||||||
|
let request = DkgQueryMsg::GetRegisteredDealer {
|
||||||
|
dealer_address: address.to_string(),
|
||||||
|
epoch_id,
|
||||||
|
};
|
||||||
self.query_dkg_contract(request).await
|
self.query_dkg_contract(request).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,12 +82,12 @@ pub trait DkgQueryClient {
|
|||||||
self.query_dkg_contract(request).await
|
self.query_dkg_contract(request).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_past_dealers_paged(
|
async fn get_dealer_indices_paged(
|
||||||
&self,
|
&self,
|
||||||
start_after: Option<String>,
|
start_after: Option<String>,
|
||||||
limit: Option<u32>,
|
limit: Option<u32>,
|
||||||
) -> Result<PagedDealerResponse, NyxdError> {
|
) -> Result<PagedDealerIndexResponse, NyxdError> {
|
||||||
let request = DkgQueryMsg::GetPastDealers { start_after, limit };
|
let request = DkgQueryMsg::GetDealerIndices { start_after, limit };
|
||||||
self.query_dkg_contract(request).await
|
self.query_dkg_contract(request).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,8 +203,8 @@ pub trait PagedDkgQueryClient: DkgQueryClient {
|
|||||||
collect_paged!(self, get_current_dealers_paged, dealers)
|
collect_paged!(self, get_current_dealers_paged, dealers)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_all_past_dealers(&self) -> Result<Vec<DealerDetails>, NyxdError> {
|
async fn get_all_dealer_indices(&self) -> Result<Vec<(Addr, NodeIndex)>, NyxdError> {
|
||||||
collect_paged!(self, get_past_dealers_paged, dealers)
|
collect_paged!(self, get_dealer_indices_paged, indices)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_all_verification_key_shares(
|
async fn get_all_verification_key_shares(
|
||||||
@@ -218,6 +231,7 @@ where
|
|||||||
let dkg_contract_address = &self
|
let dkg_contract_address = &self
|
||||||
.dkg_contract_address()
|
.dkg_contract_address()
|
||||||
.ok_or_else(|| NyxdError::unavailable_contract_address("dkg contract"))?;
|
.ok_or_else(|| NyxdError::unavailable_contract_address("dkg contract"))?;
|
||||||
|
trace!("using the following dkg contract: {dkg_contract_address}");
|
||||||
self.query_contract_smart(dkg_contract_address, &query)
|
self.query_contract_smart(dkg_contract_address, &query)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@@ -238,18 +252,24 @@ mod tests {
|
|||||||
match msg {
|
match msg {
|
||||||
DkgQueryMsg::GetState {} => client.get_state().ignore(),
|
DkgQueryMsg::GetState {} => client.get_state().ignore(),
|
||||||
DkgQueryMsg::GetCurrentEpochState {} => client.get_current_epoch().ignore(),
|
DkgQueryMsg::GetCurrentEpochState {} => client.get_current_epoch().ignore(),
|
||||||
|
DkgQueryMsg::CanAdvanceState {} => client.can_advance_state().ignore(),
|
||||||
DkgQueryMsg::GetCurrentEpochThreshold {} => {
|
DkgQueryMsg::GetCurrentEpochThreshold {} => {
|
||||||
client.get_current_epoch_threshold().ignore()
|
client.get_current_epoch_threshold().ignore()
|
||||||
}
|
}
|
||||||
DkgQueryMsg::GetInitialDealers {} => client.get_initial_dealers().ignore(),
|
DkgQueryMsg::GetRegisteredDealer {
|
||||||
|
dealer_address,
|
||||||
|
epoch_id,
|
||||||
|
} => client
|
||||||
|
.get_registered_dealer_details(&dealer_address.parse().unwrap(), epoch_id)
|
||||||
|
.ignore(),
|
||||||
DkgQueryMsg::GetDealerDetails { dealer_address } => client
|
DkgQueryMsg::GetDealerDetails { dealer_address } => client
|
||||||
.get_dealer_details(&dealer_address.parse().unwrap())
|
.get_dealer_details(&dealer_address.parse().unwrap())
|
||||||
.ignore(),
|
.ignore(),
|
||||||
DkgQueryMsg::GetCurrentDealers { limit, start_after } => client
|
DkgQueryMsg::GetCurrentDealers { limit, start_after } => client
|
||||||
.get_current_dealers_paged(start_after, limit)
|
.get_current_dealers_paged(start_after, limit)
|
||||||
.ignore(),
|
.ignore(),
|
||||||
DkgQueryMsg::GetPastDealers { limit, start_after } => {
|
DkgQueryMsg::GetDealerIndices { limit, start_after } => {
|
||||||
client.get_past_dealers_paged(start_after, limit).ignore()
|
client.get_dealer_indices_paged(start_after, limit).ignore()
|
||||||
}
|
}
|
||||||
DkgQueryMsg::GetDealingStatus {
|
DkgQueryMsg::GetDealingStatus {
|
||||||
epoch_id,
|
epoch_id,
|
||||||
|
|||||||
+19
-12
@@ -39,13 +39,6 @@ pub trait DkgSigningClient {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn surpass_threshold(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
|
|
||||||
let req = DkgExecuteMsg::SurpassedThreshold {};
|
|
||||||
|
|
||||||
self.execute_dkg_contract(fee, req, "surpass DKG threshold".to_string(), vec![])
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn register_dealer(
|
async fn register_dealer(
|
||||||
&self,
|
&self,
|
||||||
bte_key: EncodedBTEPublicKeyWithProof,
|
bte_key: EncodedBTEPublicKeyWithProof,
|
||||||
@@ -85,10 +78,9 @@ pub trait DkgSigningClient {
|
|||||||
async fn submit_dealing_chunk(
|
async fn submit_dealing_chunk(
|
||||||
&self,
|
&self,
|
||||||
chunk: PartialContractDealing,
|
chunk: PartialContractDealing,
|
||||||
resharing: bool,
|
|
||||||
fee: Option<Fee>,
|
fee: Option<Fee>,
|
||||||
) -> Result<ExecuteResult, NyxdError> {
|
) -> Result<ExecuteResult, NyxdError> {
|
||||||
let req = DkgExecuteMsg::CommitDealingsChunk { chunk, resharing };
|
let req = DkgExecuteMsg::CommitDealingsChunk { chunk };
|
||||||
|
|
||||||
self.execute_dkg_contract(fee, req, "dealing chunk commitment".to_string(), vec![])
|
self.execute_dkg_contract(fee, req, "dealing chunk commitment".to_string(), vec![])
|
||||||
.await
|
.await
|
||||||
@@ -130,6 +122,20 @@ pub trait DkgSigningClient {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn trigger_dkg_reset(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
|
||||||
|
let req = DkgExecuteMsg::TriggerReset {};
|
||||||
|
|
||||||
|
self.execute_dkg_contract(fee, req, "trigger DKG reset".to_string(), vec![])
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn trigger_dkg_resharing(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
|
||||||
|
let req = DkgExecuteMsg::TriggerResharing {};
|
||||||
|
|
||||||
|
self.execute_dkg_contract(fee, req, "trigger DKG resharing".to_string(), vec![])
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||||
@@ -192,8 +198,8 @@ mod tests {
|
|||||||
} => client
|
} => client
|
||||||
.submit_dealing_metadata(dealing_index, chunks, resharing, None)
|
.submit_dealing_metadata(dealing_index, chunks, resharing, None)
|
||||||
.ignore(),
|
.ignore(),
|
||||||
DkgExecuteMsg::CommitDealingsChunk { chunk, resharing } => {
|
DkgExecuteMsg::CommitDealingsChunk { chunk } => {
|
||||||
client.submit_dealing_chunk(chunk, resharing, None).ignore()
|
client.submit_dealing_chunk(chunk, None).ignore()
|
||||||
}
|
}
|
||||||
DkgExecuteMsg::CommitVerificationKeyShare { share, resharing } => client
|
DkgExecuteMsg::CommitVerificationKeyShare { share, resharing } => client
|
||||||
.submit_verification_key_share(share, resharing, None)
|
.submit_verification_key_share(share, resharing, None)
|
||||||
@@ -201,8 +207,9 @@ mod tests {
|
|||||||
DkgExecuteMsg::VerifyVerificationKeyShare { owner, resharing } => client
|
DkgExecuteMsg::VerifyVerificationKeyShare { owner, resharing } => client
|
||||||
.verify_verification_key_share(&owner.parse().unwrap(), resharing, None)
|
.verify_verification_key_share(&owner.parse().unwrap(), resharing, None)
|
||||||
.ignore(),
|
.ignore(),
|
||||||
DkgExecuteMsg::SurpassedThreshold {} => client.surpass_threshold(None).ignore(),
|
|
||||||
DkgExecuteMsg::AdvanceEpochState {} => client.advance_dkg_epoch_state(None).ignore(),
|
DkgExecuteMsg::AdvanceEpochState {} => client.advance_dkg_epoch_state(None).ignore(),
|
||||||
|
DkgExecuteMsg::TriggerReset {} => client.trigger_dkg_reset(None).ignore(),
|
||||||
|
DkgExecuteMsg::TriggerResharing {} => client.trigger_dkg_resharing(None).ignore(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,9 +140,6 @@ pub enum NyxdError {
|
|||||||
#[error("Cosmwasm std error: {0}")]
|
#[error("Cosmwasm std error: {0}")]
|
||||||
CosmwasmStdError(#[from] cosmwasm_std::StdError),
|
CosmwasmStdError(#[from] cosmwasm_std::StdError),
|
||||||
|
|
||||||
#[error("Coconut interface error: {0}")]
|
|
||||||
CoconutInterfaceError(#[from] nym_coconut_interface::error::CoconutInterfaceError),
|
|
||||||
|
|
||||||
#[error("Account had an unexpected bech32 prefix. Expected: {expected}, got: {got}")]
|
#[error("Account had an unexpected bech32 prefix. Expected: {expected}, got: {got}")]
|
||||||
UnexpectedBech32Prefix { got: String, expected: String },
|
UnexpectedBech32Prefix { got: String, expected: String },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -358,6 +358,10 @@ where
|
|||||||
S: OfflineSigner + Send + Sync,
|
S: OfflineSigner + Send + Sync,
|
||||||
NyxdError: From<<S as OfflineSigner>::Error>,
|
NyxdError: From<<S as OfflineSigner>::Error>,
|
||||||
{
|
{
|
||||||
|
pub fn signing_account(&self) -> Result<AccountData, NyxdError> {
|
||||||
|
Ok(self.find_account(&self.address())?)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn address(&self) -> AccountId {
|
pub fn address(&self) -> AccountId {
|
||||||
match self.client.signer_addresses() {
|
match self.client.signer_addresses() {
|
||||||
Ok(addresses) => addresses[0].clone(),
|
Ok(addresses) => addresses[0].clone(),
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "nym-coconut-interface"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
description = "Crutch library until there is proper SerDe support for coconut structs"
|
|
||||||
license.workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bs58 = "0.4.0"
|
|
||||||
getset = "0.1.1"
|
|
||||||
serde = { workspace = true, features = ["derive"] }
|
|
||||||
thiserror = { workspace = true }
|
|
||||||
|
|
||||||
nym-coconut = {path = "../nymcoconut" }
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use nym_coconut::CoconutError;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum CoconutInterfaceError {
|
|
||||||
#[error("not enough bytes: {0} received, minimum {1} required")]
|
|
||||||
InvalidByteLength(usize, usize),
|
|
||||||
|
|
||||||
#[error("Could not decode base 58 string - {0}")]
|
|
||||||
MalformedString(#[from] bs58::decode::Error),
|
|
||||||
|
|
||||||
#[error("Coconut error - {0}")]
|
|
||||||
CoconutError(#[from] CoconutError),
|
|
||||||
}
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
pub mod error;
|
|
||||||
|
|
||||||
use getset::{CopyGetters, Getters};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use error::CoconutInterfaceError;
|
|
||||||
|
|
||||||
// We list these explicity instead of glob export due to shadowing warnings with the pub tests
|
|
||||||
// module.
|
|
||||||
pub use nym_coconut::{
|
|
||||||
aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar,
|
|
||||||
prepare_blind_sign, prove_bandwidth_credential, Attribute, Base58, BlindSignRequest,
|
|
||||||
BlindedSignature, Bytable, CoconutError, KeyPair, Parameters, PrivateAttribute,
|
|
||||||
PublicAttribute, SecretKey, Signature, SignatureShare, Theta, VerificationKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq, Eq)]
|
|
||||||
pub struct Credential {
|
|
||||||
#[getset(get = "pub")]
|
|
||||||
n_params: u32,
|
|
||||||
|
|
||||||
#[getset(get = "pub")]
|
|
||||||
theta: Theta,
|
|
||||||
|
|
||||||
voucher_value: u64,
|
|
||||||
|
|
||||||
voucher_info: String,
|
|
||||||
|
|
||||||
#[getset(get = "pub")]
|
|
||||||
epoch_id: u64,
|
|
||||||
}
|
|
||||||
impl Credential {
|
|
||||||
pub fn new(
|
|
||||||
n_params: u32,
|
|
||||||
theta: Theta,
|
|
||||||
voucher_value: u64,
|
|
||||||
voucher_info: String,
|
|
||||||
epoch_id: u64,
|
|
||||||
) -> Credential {
|
|
||||||
Credential {
|
|
||||||
n_params,
|
|
||||||
theta,
|
|
||||||
voucher_value,
|
|
||||||
voucher_info,
|
|
||||||
epoch_id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn blinded_serial_number(&self) -> String {
|
|
||||||
self.theta.blinded_serial_number_bs58()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_blinded_serial_number(
|
|
||||||
&self,
|
|
||||||
blinded_serial_number_bs58: &str,
|
|
||||||
) -> Result<bool, CoconutInterfaceError> {
|
|
||||||
Ok(self
|
|
||||||
.theta
|
|
||||||
.has_blinded_serial_number(blinded_serial_number_bs58)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn voucher_value(&self) -> u64 {
|
|
||||||
self.voucher_value
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify(&self, verification_key: &VerificationKey) -> bool {
|
|
||||||
let params = Parameters::new(self.n_params).unwrap();
|
|
||||||
|
|
||||||
let hashed_value = hash_to_scalar(self.voucher_value.to_string());
|
|
||||||
let hashed_info = hash_to_scalar(&self.voucher_info);
|
|
||||||
let public_attributes = &[&hashed_value, &hashed_info];
|
|
||||||
|
|
||||||
nym_coconut::verify_credential(¶ms, verification_key, &self.theta, public_attributes)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_bytes(&self) -> Vec<u8> {
|
|
||||||
let n_params_bytes = self.n_params.to_be_bytes();
|
|
||||||
let theta_bytes = self.theta.to_bytes();
|
|
||||||
let theta_bytes_len = theta_bytes.len();
|
|
||||||
let voucher_value_bytes = self.voucher_value.to_be_bytes();
|
|
||||||
let epoch_id_bytes = self.epoch_id.to_be_bytes();
|
|
||||||
let voucher_info_bytes = self.voucher_info.as_bytes();
|
|
||||||
let voucher_info_len = voucher_info_bytes.len();
|
|
||||||
|
|
||||||
let mut bytes = Vec::with_capacity(28 + theta_bytes_len + voucher_info_len);
|
|
||||||
bytes.extend_from_slice(&n_params_bytes);
|
|
||||||
bytes.extend_from_slice(&(theta_bytes_len as u64).to_be_bytes());
|
|
||||||
bytes.extend_from_slice(&theta_bytes);
|
|
||||||
bytes.extend_from_slice(&voucher_value_bytes);
|
|
||||||
bytes.extend_from_slice(&epoch_id_bytes);
|
|
||||||
bytes.extend_from_slice(voucher_info_bytes);
|
|
||||||
|
|
||||||
bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, CoconutError> {
|
|
||||||
if bytes.len() < 28 {
|
|
||||||
return Err(CoconutError::Deserialization(String::from(
|
|
||||||
"To few bytes in credential",
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
let mut four_byte = [0u8; 4];
|
|
||||||
let mut eight_byte = [0u8; 8];
|
|
||||||
|
|
||||||
four_byte.copy_from_slice(&bytes[..4]);
|
|
||||||
let n_params = u32::from_be_bytes(four_byte);
|
|
||||||
eight_byte.copy_from_slice(&bytes[4..12]);
|
|
||||||
let theta_len = u64::from_be_bytes(eight_byte);
|
|
||||||
if bytes.len() < 28 + theta_len as usize {
|
|
||||||
return Err(CoconutError::Deserialization(String::from(
|
|
||||||
"To few bytes in credential",
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
let theta = Theta::from_bytes(&bytes[12..12 + theta_len as usize])
|
|
||||||
.map_err(|e| CoconutError::Deserialization(e.to_string()))?;
|
|
||||||
eight_byte.copy_from_slice(&bytes[12 + theta_len as usize..20 + theta_len as usize]);
|
|
||||||
let voucher_value = u64::from_be_bytes(eight_byte);
|
|
||||||
eight_byte.copy_from_slice(&bytes[20 + theta_len as usize..28 + theta_len as usize]);
|
|
||||||
let epoch_id = u64::from_be_bytes(eight_byte);
|
|
||||||
let voucher_info = String::from_utf8(bytes[28 + theta_len as usize..].to_vec())
|
|
||||||
.map_err(|e| CoconutError::Deserialization(e.to_string()))?;
|
|
||||||
|
|
||||||
Ok(Credential {
|
|
||||||
n_params,
|
|
||||||
theta,
|
|
||||||
voucher_value,
|
|
||||||
voucher_info,
|
|
||||||
epoch_id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Bytable for Credential {
|
|
||||||
fn to_byte_vec(&self) -> Vec<u8> {
|
|
||||||
self.as_bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, CoconutError> {
|
|
||||||
Credential::from_bytes(slice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Base58 for Credential {}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use nym_coconut::{prove_bandwidth_credential, Signature};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn serde_coconut_credential() {
|
|
||||||
let voucher_value = 1000000u64;
|
|
||||||
let voucher_info = String::from("BandwidthVoucher");
|
|
||||||
let serial_number =
|
|
||||||
Attribute::try_from_bs58("7Rp3imcuNX3w9se9wm5th8gSvc2czsnMrGsdt5HsrycA").unwrap();
|
|
||||||
let binding_number =
|
|
||||||
Attribute::try_from_bs58("Auf8yVEgyEAWNHaXUZmimS4n9g5YiYnNYqp6F9BtBe9E").unwrap();
|
|
||||||
let signature = Signature::try_from_bs58(
|
|
||||||
"ta3pM9ffj5T6YGbwjSBp2W118rcwyP9PXStc\
|
|
||||||
7ssb91g5GQYMQHhuTNajbdZcjxUFBFL5rhED8EHpRzE8r432ss3qbPBfpNev4CdkfMkQ3wepyM7hy7q1W6Rn9WmFoZL\
|
|
||||||
ZR9j",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let params = Parameters::new(4).unwrap();
|
|
||||||
let verification_key = VerificationKey::try_from_bs58("8CFtVVXdwLy4WHMQPE4\
|
|
||||||
woe89q3DRHoNxBSchftrEjSBPWA4r4xZv4Y9qSvS5x5bMmFtp7BX6ikECAnuXr5EjXWSsgjirZJmpS5XDUynVfht1cD\
|
|
||||||
FWGDvy2XFrRCuoCMotNXi3PoF6wYqdTR9Rqcfoj3i2H5Nid422WBaLtVoC9QNobvpvaqq6vX5PbsSyPayvU8HCXFxM6\
|
|
||||||
JjScYpbRTxQtdwefWLrk3LmXyJQBWi7c2VAhSxu9msp7VTBycqdwQNgxHETStZuwXsozxaGQ2KssVUCaaoYPR4g2RqK\
|
|
||||||
UAvtWwA7pMiAQNcbkXcbsjCgVjWaCpMWC37XA31cLcFf3zbjHD9e5tXjAcqa4M89fbFhuvvSXxowSAZ5NoWrN32kd5d\
|
|
||||||
wxJm1JW3Tt2h6yDDBe84oMy71462dZn7N78DVk2mFNGwBCibrZWA7oUzRBMfYxiQrksoFcou7QfLLd58zoNYmPQPt84\
|
|
||||||
1VpQopEBfdQ7Nf9zoXxBt3zMy7g5NsFGvzh7KTbDUyeeXrdkKJPQBs6dqaizr9sS8CPPmR4uk96vDTRh8CJ5FbSsmb8\
|
|
||||||
nP71dRvvwRZJHGzwYirMo6SXS3ZYxFuiA3mkxYuqDHCwkTWDuRCcAaztrDYRZg7VCMo4Q446AaEso5eqpeWpHZQt53E\
|
|
||||||
ZRpqmNYKASGwMhTeEHPSLgSmtoAAUcaRWpGRzYfd6kzEma8tdGLwyP4rLXgvSvtDLP37dU7YgF3LEXbGAz57U9ATy46\
|
|
||||||
6sroLpHPdaCWB8RF11wvB6Tu196JnJd2KyQBP1iUWP3rtZs3GhAF1QVcxquh8BqDZzAcpQ6wCS1P9c5GxKgww77FVF5\
|
|
||||||
Kp83XtoxSrw3GaYVyKTGxNh3vcKPR31txCjTxPaN2fg7TaPLhoQJX4YaAroFSXqrqbbRsisuHhhCeUP2YwDjHedes9y")
|
|
||||||
.unwrap();
|
|
||||||
let theta = prove_bandwidth_credential(
|
|
||||||
¶ms,
|
|
||||||
&verification_key,
|
|
||||||
&signature,
|
|
||||||
&serial_number,
|
|
||||||
&binding_number,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let credential = Credential::new(4, theta, voucher_value, voucher_info, 42);
|
|
||||||
|
|
||||||
let serialized_credential = credential.as_bytes();
|
|
||||||
let deserialized_credential = Credential::from_bytes(&serialized_credential).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(credential, deserialized_credential);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,12 +9,13 @@ license.workspace = true
|
|||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
bip39 = { workspace = true }
|
bip39 = { workspace = true }
|
||||||
bs58 = "0.4"
|
bs58 = { workspace = true }
|
||||||
comfy-table = "6.0.0"
|
comfy-table = "6.0.0"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
clap = { workspace = true, features = ["derive"] }
|
clap = { workspace = true, features = ["derive"] }
|
||||||
csv = "1.3.0"
|
csv = "1.3.0"
|
||||||
cw-utils = { workspace = true }
|
cw-utils = { workspace = true }
|
||||||
|
futures = { workspace = true }
|
||||||
handlebars = "3.0.1"
|
handlebars = "3.0.1"
|
||||||
humantime-serde = "1.0"
|
humantime-serde = "1.0"
|
||||||
inquire = "0.6.2"
|
inquire = "0.6.2"
|
||||||
@@ -25,9 +26,11 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
time = { workspace = true, features = ["parsing", "formatting"] }
|
time = { workspace = true, features = ["parsing", "formatting"] }
|
||||||
|
tokio = { workspace = true, features = ["sync"]}
|
||||||
toml = "0.5.6"
|
toml = "0.5.6"
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
tap = "1"
|
tap = "1"
|
||||||
|
zeroize = { workspace = true }
|
||||||
|
|
||||||
cosmrs = { workspace = true }
|
cosmrs = { workspace = true }
|
||||||
cosmwasm-std = { workspace = true }
|
cosmwasm-std = { workspace = true }
|
||||||
@@ -49,8 +52,10 @@ nym-sphinx = { path = "../../common/nymsphinx" }
|
|||||||
nym-client-core = { path = "../../common/client-core" }
|
nym-client-core = { path = "../../common/client-core" }
|
||||||
nym-config = { path = "../../common/config" }
|
nym-config = { path = "../../common/config" }
|
||||||
nym-credentials = { path = "../../common/credentials" }
|
nym-credentials = { path = "../../common/credentials" }
|
||||||
|
nym-credentials-interface = { path = "../../common/credentials-interface" }
|
||||||
nym-credential-storage = { path = "../../common/credential-storage" }
|
nym-credential-storage = { path = "../../common/credential-storage" }
|
||||||
nym-credential-utils = { path = "../../common/credential-utils" }
|
nym-credential-utils = { path = "../../common/credential-utils" }
|
||||||
|
nym-id = { path = "../nym-id" }
|
||||||
|
|
||||||
nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
|
nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
|
||||||
nym-types = { path = "../../common/types" }
|
nym-types = { path = "../../common/types" }
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use anyhow::{anyhow, bail};
|
||||||
|
use clap::ArgGroup;
|
||||||
|
use clap::Parser;
|
||||||
|
use futures::StreamExt;
|
||||||
|
use log::{error, info};
|
||||||
|
use nym_coconut_dkg_common::types::EpochId;
|
||||||
|
use nym_credential_utils::utils::block_until_coconut_is_available;
|
||||||
|
use nym_credentials::coconut::bandwidth::freepass::MAX_FREE_PASS_VALIDITY;
|
||||||
|
use nym_credentials::{
|
||||||
|
obtain_aggregate_verification_key, IssuanceBandwidthCredential, IssuedBandwidthCredential,
|
||||||
|
};
|
||||||
|
use nym_credentials_interface::VerificationKey;
|
||||||
|
use nym_validator_client::coconut::all_coconut_api_clients;
|
||||||
|
use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, NymContractsProvider};
|
||||||
|
use nym_validator_client::nyxd::CosmWasmClient;
|
||||||
|
use nym_validator_client::signing::AccountData;
|
||||||
|
use nym_validator_client::CoconutApiClient;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use time::format_description::well_known::Rfc3339;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
fn parse_rfc3339_expiration_date(raw: &str) -> Result<OffsetDateTime, time::error::Parse> {
|
||||||
|
OffsetDateTime::parse(raw, &Rfc3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[clap(group(ArgGroup::new("expiration").required(true)))]
|
||||||
|
pub struct Args {
|
||||||
|
/// Specifies the expiration date of the free pass(es)
|
||||||
|
/// Can't be set to more than a week into the future.
|
||||||
|
#[clap(long, group = "expiration", value_parser = parse_rfc3339_expiration_date)]
|
||||||
|
pub(crate) expiration_date: Option<OffsetDateTime>,
|
||||||
|
|
||||||
|
/// The expiration of the free pass(es) expresses as unix timestamp.
|
||||||
|
/// Can't be set to more than a week into the future.
|
||||||
|
#[clap(long, group = "expiration")]
|
||||||
|
pub(crate) expiration_timestamp: Option<i64>,
|
||||||
|
|
||||||
|
/// The number of free passes to issue
|
||||||
|
#[clap(long, default_value = "1")]
|
||||||
|
pub(crate) amount: u64,
|
||||||
|
|
||||||
|
/// Path to the output directory for generated free passes.
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) output_dir: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_freepass(
|
||||||
|
api_clients: Vec<CoconutApiClient>,
|
||||||
|
aggregate_vk: &VerificationKey,
|
||||||
|
threshold: u64,
|
||||||
|
epoch_id: EpochId,
|
||||||
|
signing_account: &AccountData,
|
||||||
|
expiration_date: OffsetDateTime,
|
||||||
|
) -> anyhow::Result<IssuedBandwidthCredential> {
|
||||||
|
let issuance_pass = IssuanceBandwidthCredential::new_freepass(Some(expiration_date));
|
||||||
|
let signing_data = issuance_pass.prepare_for_signing();
|
||||||
|
|
||||||
|
let credential_shares = Arc::new(tokio::sync::Mutex::new(Vec::new()));
|
||||||
|
|
||||||
|
futures::stream::iter(api_clients)
|
||||||
|
.for_each_concurrent(None, |client| async {
|
||||||
|
// move the client into the block
|
||||||
|
let client = client;
|
||||||
|
let api_url = client.api_client.api_url();
|
||||||
|
|
||||||
|
info!("contacting {api_url} for blinded free pass");
|
||||||
|
|
||||||
|
match issuance_pass
|
||||||
|
.obtain_partial_freepass_credential(
|
||||||
|
&client.api_client,
|
||||||
|
signing_account,
|
||||||
|
&client.verification_key,
|
||||||
|
signing_data.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(partial_credential) => {
|
||||||
|
credential_shares
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.push((partial_credential, client.node_id).into());
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("failed to obtain partial free pass from {api_url}: {err}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// SAFETY: the futures have completed, so we MUST have the only arc reference
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let credential_shares = Arc::into_inner(credential_shares).unwrap().into_inner();
|
||||||
|
|
||||||
|
if credential_shares.len() < threshold as usize {
|
||||||
|
bail!("we managed to obtain only {} partial credentials while the minimum threshold is {threshold}", credential_shares.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
let signature = issuance_pass.aggregate_signature_shares(aggregate_vk, &credential_shares)?;
|
||||||
|
Ok(issuance_pass.into_issued_credential(signature, epoch_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> {
|
||||||
|
let address = client.address();
|
||||||
|
|
||||||
|
if !args.output_dir.is_dir() {
|
||||||
|
bail!("the provided output directory is not a directory!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.output_dir.read_dir()?.next().is_some() {
|
||||||
|
bail!("the provided output directory is not empty!");
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(bandwidth_contract) = client.coconut_bandwidth_contract_address() else {
|
||||||
|
bail!("the bandwidth contract address is not set")
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(bandwidth_admin) = client
|
||||||
|
.get_contract(bandwidth_contract)
|
||||||
|
.await
|
||||||
|
.map(|c| c.contract_info.admin)?
|
||||||
|
else {
|
||||||
|
bail!("the bandwidth contract doesn't have any admin set")
|
||||||
|
};
|
||||||
|
|
||||||
|
// sanity checks since nym-apis will reject invalid requests anyway
|
||||||
|
if address != bandwidth_admin {
|
||||||
|
bail!("the provided mnemonic does not correspond to the current admin of the bandwidth contract")
|
||||||
|
}
|
||||||
|
|
||||||
|
let expiration_date = match args.expiration_date {
|
||||||
|
Some(date) => date,
|
||||||
|
// SAFETY: one of those arguments must have been set
|
||||||
|
None => OffsetDateTime::from_unix_timestamp(args.expiration_timestamp.unwrap())?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let now = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
|
if expiration_date > now + MAX_FREE_PASS_VALIDITY {
|
||||||
|
bail!("the provided free pass request has too long expiry (expiry is set to on {expiration_date})")
|
||||||
|
}
|
||||||
|
|
||||||
|
// issuance start
|
||||||
|
block_until_coconut_is_available(&client).await?;
|
||||||
|
|
||||||
|
let signing_account = client.signing_account()?;
|
||||||
|
|
||||||
|
let epoch_id = client.get_current_epoch().await?.epoch_id;
|
||||||
|
let threshold = client
|
||||||
|
.get_current_epoch_threshold()
|
||||||
|
.await?
|
||||||
|
.ok_or(anyhow!("no threshold available"))?;
|
||||||
|
let api_clients = all_coconut_api_clients(&client, epoch_id).await?;
|
||||||
|
|
||||||
|
if api_clients.len() < threshold as usize {
|
||||||
|
bail!(
|
||||||
|
"we have only {} api clients available while the minimum threshold is {threshold}",
|
||||||
|
api_clients.len()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let aggregate_vk = obtain_aggregate_verification_key(&api_clients)?;
|
||||||
|
|
||||||
|
for i in 0..args.amount {
|
||||||
|
let human_index = i + 1;
|
||||||
|
info!("trying to obtain free pass {human_index}/{}", args.amount);
|
||||||
|
let free_pass = get_freepass(
|
||||||
|
api_clients.clone(),
|
||||||
|
&aggregate_vk,
|
||||||
|
threshold,
|
||||||
|
epoch_id,
|
||||||
|
&signing_account,
|
||||||
|
expiration_date,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let credential_data = Zeroizing::new(free_pass.pack_v1());
|
||||||
|
let output = args.output_dir.join(format!("freepass_{i}.nym"));
|
||||||
|
info!("saving the freepass to '{}'", output.display());
|
||||||
|
File::create(output)?.write_all(&credential_data)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::utils::CommonConfigsWrapper;
|
||||||
|
use anyhow::bail;
|
||||||
|
use clap::ArgGroup;
|
||||||
|
use clap::Parser;
|
||||||
|
use nym_credential_storage::initialise_persistent_storage;
|
||||||
|
use nym_id::import_credential;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
|
||||||
|
bs58::decode(raw).into_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[clap(group(ArgGroup::new("cred_data").required(true)))]
|
||||||
|
pub struct Args {
|
||||||
|
/// Config file of the client that is supposed to use the credential.
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) client_config: PathBuf,
|
||||||
|
|
||||||
|
/// Explicitly provide the encoded credential data (as base58)
|
||||||
|
#[clap(long, group = "cred_data", value_parser = parse_encoded_credential_data)]
|
||||||
|
pub(crate) credential_data: Option<Vec<u8>>,
|
||||||
|
|
||||||
|
/// Specifies the path to file containing binary credential data
|
||||||
|
#[clap(long, group = "cred_data")]
|
||||||
|
pub(crate) credential_path: Option<PathBuf>,
|
||||||
|
|
||||||
|
// currently hidden as there exists only a single serialization standard
|
||||||
|
#[clap(long, hide = true)]
|
||||||
|
pub(crate) version: Option<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute(args: Args) -> anyhow::Result<()> {
|
||||||
|
let loaded = CommonConfigsWrapper::try_load(args.client_config)?;
|
||||||
|
|
||||||
|
if let Ok(id) = loaded.try_get_id() {
|
||||||
|
println!("loaded config file for client '{id}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
let Ok(credentials_store) = loaded.try_get_credentials_store() else {
|
||||||
|
bail!("the loaded config does not have a credentials store information")
|
||||||
|
};
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"using credentials store at '{}'",
|
||||||
|
credentials_store.display()
|
||||||
|
);
|
||||||
|
let credentials_store = initialise_persistent_storage(credentials_store).await;
|
||||||
|
|
||||||
|
let raw_credential = match args.credential_data {
|
||||||
|
Some(data) => data,
|
||||||
|
None => {
|
||||||
|
// SAFETY: one of those arguments must have been set
|
||||||
|
fs::read(args.credential_path.unwrap())?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
import_credential(credentials_store, raw_credential, args.version).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
use clap::{Args, Subcommand};
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
|
pub mod generate_freepass;
|
||||||
|
pub mod import_credential;
|
||||||
pub mod issue_credentials;
|
pub mod issue_credentials;
|
||||||
pub mod recover_credentials;
|
pub mod recover_credentials;
|
||||||
|
|
||||||
@@ -15,6 +17,8 @@ pub struct Coconut {
|
|||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub enum CoconutCommands {
|
pub enum CoconutCommands {
|
||||||
|
GenerateFreepass(generate_freepass::Args),
|
||||||
IssueCredentials(issue_credentials::Args),
|
IssueCredentials(issue_credentials::Args),
|
||||||
RecoverCredentials(recover_credentials::Args),
|
RecoverCredentials(recover_credentials::Args),
|
||||||
|
ImportCredential(import_credential::Args),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,20 +14,32 @@ pub struct DealerDetails {
|
|||||||
pub assigned_index: NodeIndex,
|
pub assigned_index: NodeIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cw_serde]
|
||||||
|
pub struct DealerRegistrationDetails {
|
||||||
|
pub bte_public_key_with_proof: EncodedBTEPublicKeyWithProof,
|
||||||
|
pub ed25519_identity: String,
|
||||||
|
pub announce_address: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
#[derive(Copy)]
|
#[derive(Copy)]
|
||||||
pub enum DealerType {
|
pub enum DealerType {
|
||||||
Current,
|
Current { assigned_index: NodeIndex },
|
||||||
Past,
|
Past { assigned_index: NodeIndex },
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DealerType {
|
impl DealerType {
|
||||||
pub fn is_current(&self) -> bool {
|
pub fn is_current(&self) -> bool {
|
||||||
matches!(&self, DealerType::Current)
|
matches!(&self, DealerType::Current { .. })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cw_serde]
|
||||||
|
pub struct RegisteredDealerDetails {
|
||||||
|
pub details: Option<DealerRegistrationDetails>,
|
||||||
|
}
|
||||||
|
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct DealerDetailsResponse {
|
pub struct DealerDetailsResponse {
|
||||||
pub details: Option<DealerDetails>,
|
pub details: Option<DealerDetails>,
|
||||||
@@ -65,3 +77,20 @@ impl PagedDealerResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cw_serde]
|
||||||
|
pub struct PagedDealerIndexResponse {
|
||||||
|
pub indices: Vec<(Addr, NodeIndex)>,
|
||||||
|
|
||||||
|
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
|
||||||
|
pub start_next_after: Option<Addr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PagedDealerIndexResponse {
|
||||||
|
pub fn new(indices: Vec<(Addr, NodeIndex)>, start_next_after: Option<Addr>) -> Self {
|
||||||
|
PagedDealerIndexResponse {
|
||||||
|
indices,
|
||||||
|
start_next_after,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,12 +11,15 @@ use cosmwasm_schema::cw_serde;
|
|||||||
|
|
||||||
#[cfg(feature = "schema")]
|
#[cfg(feature = "schema")]
|
||||||
use crate::{
|
use crate::{
|
||||||
dealer::{DealerDetailsResponse, PagedDealerResponse},
|
dealer::{
|
||||||
|
DealerDetailsResponse, PagedDealerIndexResponse, PagedDealerResponse,
|
||||||
|
RegisteredDealerDetails,
|
||||||
|
},
|
||||||
dealing::{
|
dealing::{
|
||||||
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
|
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
|
||||||
DealingMetadataResponse, DealingStatusResponse,
|
DealingMetadataResponse, DealingStatusResponse,
|
||||||
},
|
},
|
||||||
types::{Epoch, InitialReplacementData, State},
|
types::{Epoch, State, StateAdvanceResponse},
|
||||||
verification_key::{PagedVKSharesResponse, VkShareResponse},
|
verification_key::{PagedVKSharesResponse, VkShareResponse},
|
||||||
};
|
};
|
||||||
#[cfg(feature = "schema")]
|
#[cfg(feature = "schema")]
|
||||||
@@ -53,7 +56,6 @@ pub enum ExecuteMsg {
|
|||||||
|
|
||||||
CommitDealingsChunk {
|
CommitDealingsChunk {
|
||||||
chunk: PartialContractDealing,
|
chunk: PartialContractDealing,
|
||||||
resharing: bool,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
CommitVerificationKeyShare {
|
CommitVerificationKeyShare {
|
||||||
@@ -66,9 +68,11 @@ pub enum ExecuteMsg {
|
|||||||
resharing: bool,
|
resharing: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
SurpassedThreshold {},
|
|
||||||
|
|
||||||
AdvanceEpochState {},
|
AdvanceEpochState {},
|
||||||
|
|
||||||
|
TriggerReset {},
|
||||||
|
|
||||||
|
TriggerResharing {},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
@@ -83,8 +87,14 @@ pub enum QueryMsg {
|
|||||||
#[cfg_attr(feature = "schema", returns(u64))]
|
#[cfg_attr(feature = "schema", returns(u64))]
|
||||||
GetCurrentEpochThreshold {},
|
GetCurrentEpochThreshold {},
|
||||||
|
|
||||||
#[cfg_attr(feature = "schema", returns(Option<InitialReplacementData>))]
|
#[cfg_attr(feature = "schema", returns(StateAdvanceResponse))]
|
||||||
GetInitialDealers {},
|
CanAdvanceState {},
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "schema", returns(RegisteredDealerDetails))]
|
||||||
|
GetRegisteredDealer {
|
||||||
|
dealer_address: String,
|
||||||
|
epoch_id: Option<EpochId>,
|
||||||
|
},
|
||||||
|
|
||||||
#[cfg_attr(feature = "schema", returns(DealerDetailsResponse))]
|
#[cfg_attr(feature = "schema", returns(DealerDetailsResponse))]
|
||||||
GetDealerDetails { dealer_address: String },
|
GetDealerDetails { dealer_address: String },
|
||||||
@@ -95,8 +105,8 @@ pub enum QueryMsg {
|
|||||||
start_after: Option<String>,
|
start_after: Option<String>,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[cfg_attr(feature = "schema", returns(PagedDealerResponse))]
|
#[cfg_attr(feature = "schema", returns(PagedDealerIndexResponse))]
|
||||||
GetPastDealers {
|
GetDealerIndices {
|
||||||
limit: Option<u32>,
|
limit: Option<u32>,
|
||||||
start_after: Option<String>,
|
start_after: Option<String>,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use cosmwasm_schema::cw_serde;
|
|||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
pub use crate::dealer::{DealerDetails, PagedDealerResponse};
|
pub use crate::dealer::{DealerDetails, DealerRegistrationDetails, PagedDealerResponse};
|
||||||
pub use contracts_common::dealings::ContractSafeBytes;
|
pub use contracts_common::dealings::ContractSafeBytes;
|
||||||
pub use cosmwasm_std::{Addr, Coin, Timestamp};
|
pub use cosmwasm_std::{Addr, Coin, Timestamp};
|
||||||
pub use cw4::Cw4Contract;
|
pub use cw4::Cw4Contract;
|
||||||
@@ -22,9 +22,19 @@ pub type ChunkIndex = u16;
|
|||||||
pub type PartialContractDealingData = ContractSafeBytes;
|
pub type PartialContractDealingData = ContractSafeBytes;
|
||||||
|
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct InitialReplacementData {
|
#[derive(Copy, Default)]
|
||||||
pub initial_dealers: Vec<Addr>,
|
pub struct StateAdvanceResponse {
|
||||||
pub initial_height: u64,
|
pub current_state: EpochState,
|
||||||
|
pub progress: StateProgress,
|
||||||
|
pub deadline: Option<Timestamp>,
|
||||||
|
pub reached_deadline: bool,
|
||||||
|
pub is_complete: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StateAdvanceResponse {
|
||||||
|
pub fn can_advance(&self) -> bool {
|
||||||
|
self.reached_deadline || self.is_complete
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
@@ -40,6 +50,26 @@ pub struct TimeConfiguration {
|
|||||||
pub in_progress_time_secs: u64,
|
pub in_progress_time_secs: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TimeConfiguration {
|
||||||
|
pub fn state_duration(&self, state: EpochState) -> Option<u64> {
|
||||||
|
match state {
|
||||||
|
EpochState::WaitingInitialisation => None,
|
||||||
|
EpochState::PublicKeySubmission { .. } => Some(self.public_key_submission_time_secs),
|
||||||
|
EpochState::DealingExchange { .. } => Some(self.dealing_exchange_time_secs),
|
||||||
|
EpochState::VerificationKeySubmission { .. } => {
|
||||||
|
Some(self.verification_key_submission_time_secs)
|
||||||
|
}
|
||||||
|
EpochState::VerificationKeyValidation { .. } => {
|
||||||
|
Some(self.verification_key_validation_time_secs)
|
||||||
|
}
|
||||||
|
EpochState::VerificationKeyFinalization { .. } => {
|
||||||
|
Some(self.verification_key_finalization_time_secs)
|
||||||
|
}
|
||||||
|
EpochState::InProgress => Some(self.in_progress_time_secs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FromStr for TimeConfiguration {
|
impl FromStr for TimeConfiguration {
|
||||||
type Err = String;
|
type Err = String;
|
||||||
|
|
||||||
@@ -87,13 +117,41 @@ pub struct State {
|
|||||||
pub key_size: u32,
|
pub key_size: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cw_serde]
|
||||||
|
#[derive(Copy, Default)]
|
||||||
|
pub struct StateProgress {
|
||||||
|
/// Counts the number of dealers that have registered in this epoch.
|
||||||
|
// ideally we want to have here all group members
|
||||||
|
pub registered_dealers: u32,
|
||||||
|
|
||||||
|
/// Counts the number of resharing dealers that have registered in this epoch.
|
||||||
|
/// This field is only populated during a resharing exchange.
|
||||||
|
/// It is always <= registered_dealers.
|
||||||
|
pub registered_resharing_dealers: u32,
|
||||||
|
|
||||||
|
/// Counts the number of fully received dealings (i.e. full chunks) from all the allowed dealers.
|
||||||
|
// we expect registered_dealers * state.key_size number of dealings here (each dealer has to submit key_size number of dealings)
|
||||||
|
pub submitted_dealings: u32,
|
||||||
|
|
||||||
|
/// Counts the number of submitted verification key shared from the dealers.
|
||||||
|
// we expect registered_dealers number of keys here
|
||||||
|
pub submitted_key_shares: u32,
|
||||||
|
|
||||||
|
/// Counts the number of verified key shares.
|
||||||
|
// we expect submitted_key_shares number of verified keys here
|
||||||
|
pub verified_keys: u32,
|
||||||
|
}
|
||||||
|
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
#[derive(Copy, Default)]
|
#[derive(Copy, Default)]
|
||||||
pub struct Epoch {
|
pub struct Epoch {
|
||||||
pub state: EpochState,
|
pub state: EpochState,
|
||||||
pub epoch_id: EpochId,
|
pub epoch_id: EpochId,
|
||||||
|
pub state_progress: StateProgress,
|
||||||
pub time_configuration: TimeConfiguration,
|
pub time_configuration: TimeConfiguration,
|
||||||
pub finish_timestamp: Option<Timestamp>,
|
|
||||||
|
#[serde(alias = "finish_timestamp")]
|
||||||
|
pub deadline: Option<Timestamp>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Epoch {
|
impl Epoch {
|
||||||
@@ -103,35 +161,45 @@ impl Epoch {
|
|||||||
time_configuration: TimeConfiguration,
|
time_configuration: TimeConfiguration,
|
||||||
current_timestamp: Timestamp,
|
current_timestamp: Timestamp,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let duration = match state {
|
let duration = time_configuration.state_duration(state);
|
||||||
EpochState::WaitingInitialisation => None,
|
|
||||||
EpochState::PublicKeySubmission { .. } => {
|
|
||||||
Some(time_configuration.public_key_submission_time_secs)
|
|
||||||
}
|
|
||||||
EpochState::DealingExchange { .. } => {
|
|
||||||
Some(time_configuration.dealing_exchange_time_secs)
|
|
||||||
}
|
|
||||||
EpochState::VerificationKeySubmission { .. } => {
|
|
||||||
Some(time_configuration.verification_key_submission_time_secs)
|
|
||||||
}
|
|
||||||
EpochState::VerificationKeyValidation { .. } => {
|
|
||||||
Some(time_configuration.verification_key_validation_time_secs)
|
|
||||||
}
|
|
||||||
EpochState::VerificationKeyFinalization { .. } => {
|
|
||||||
Some(time_configuration.verification_key_finalization_time_secs)
|
|
||||||
}
|
|
||||||
EpochState::InProgress => Some(time_configuration.in_progress_time_secs),
|
|
||||||
};
|
|
||||||
Epoch {
|
Epoch {
|
||||||
state,
|
state,
|
||||||
epoch_id,
|
epoch_id,
|
||||||
|
state_progress: Default::default(),
|
||||||
time_configuration,
|
time_configuration,
|
||||||
finish_timestamp: duration.map(|d| current_timestamp.plus_seconds(d)),
|
deadline: duration.map(|d| current_timestamp.plus_seconds(d)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update(mut self, next_state: EpochState, current_timestamp: Timestamp) -> Self {
|
||||||
|
self.state = next_state;
|
||||||
|
let duration = self.time_configuration.state_duration(next_state);
|
||||||
|
self.deadline = duration.map(|d| current_timestamp.plus_seconds(d));
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_reset(self, current_timestamp: Timestamp) -> Self {
|
||||||
|
Epoch::new(
|
||||||
|
EpochState::PublicKeySubmission { resharing: false },
|
||||||
|
self.epoch_id + 1,
|
||||||
|
self.time_configuration,
|
||||||
|
current_timestamp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_resharing(self, current_timestamp: Timestamp) -> Self {
|
||||||
|
Epoch::new(
|
||||||
|
EpochState::PublicKeySubmission { resharing: true },
|
||||||
|
self.epoch_id + 1,
|
||||||
|
self.time_configuration,
|
||||||
|
current_timestamp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn final_timestamp_secs(&self) -> Option<u64> {
|
pub fn final_timestamp_secs(&self) -> Option<u64> {
|
||||||
let mut finish = self.finish_timestamp?.seconds();
|
let mut finish = self.deadline?.seconds();
|
||||||
let time_configuration = self.time_configuration;
|
let time_configuration = self.time_configuration;
|
||||||
let mut curr_epoch_state = self.state;
|
let mut curr_epoch_state = self.state;
|
||||||
while let Some(state) = curr_epoch_state.next() {
|
while let Some(state) = curr_epoch_state.next() {
|
||||||
@@ -256,4 +324,8 @@ impl EpochState {
|
|||||||
pub fn is_in_progress(&self) -> bool {
|
pub fn is_in_progress(&self) -> bool {
|
||||||
matches!(self, EpochState::InProgress)
|
matches!(self, EpochState::InProgress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_dealing_exchange(&self) -> bool {
|
||||||
|
matches!(self, EpochState::DealingExchange { .. })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ license = { workspace = true }
|
|||||||
repository = { workspace = true }
|
repository = { workspace = true }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bs58 = "0.4.0"
|
bs58 = { workspace = true }
|
||||||
cosmwasm-std = { workspace = true }
|
cosmwasm-std = { workspace = true }
|
||||||
cosmwasm-schema = { workspace = true }
|
cosmwasm-schema = { workspace = true }
|
||||||
schemars = "0.8"
|
schemars = "0.8"
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ async-trait = { workspace = true }
|
|||||||
|
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tokio = { version = "1.24.1", features = ["sync"]}
|
tokio = { workspace = true, features = ["sync"]}
|
||||||
|
zeroize = { workspace = true, features = ["zeroize_derive"] }
|
||||||
|
|
||||||
|
|
||||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
DROP TABLE coconut_credentials;
|
||||||
|
CREATE TABLE coconut_credentials
|
||||||
|
(
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
|
||||||
|
-- introduce a way for us to introduce breaking changes in serialization
|
||||||
|
serialization_revision INTEGER NOT NULL,
|
||||||
|
credential_type TEXT NOT NULL,
|
||||||
|
credential_data BLOB NOT NULL,
|
||||||
|
epoch_id INTEGER NOT NULL,
|
||||||
|
consumed BOOLEAN NOT NULL,
|
||||||
|
expired BOOLEAN NOT NULL
|
||||||
|
);
|
||||||
@@ -1,59 +1,65 @@
|
|||||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::models::CoconutCredential;
|
use crate::models::StoredIssuedCredential;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CoconutCredentialManager {
|
pub struct CoconutCredentialManager {
|
||||||
inner: Arc<RwLock<Vec<CoconutCredential>>>,
|
inner: Arc<RwLock<CoconutCredentialManagerInner>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct CoconutCredentialManagerInner {
|
||||||
|
data: Vec<StoredIssuedCredential>,
|
||||||
|
_next_id: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CoconutCredentialManagerInner {
|
||||||
|
fn next_id(&mut self) -> i64 {
|
||||||
|
let next = self._next_id;
|
||||||
|
self._next_id += 1;
|
||||||
|
next
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoconutCredentialManager {
|
impl CoconutCredentialManager {
|
||||||
/// Creates new empty instance of the `CoconutCredentialManager`.
|
/// Creates new empty instance of the `CoconutCredentialManager`.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
CoconutCredentialManager {
|
CoconutCredentialManager {
|
||||||
inner: Arc::new(RwLock::new(Vec::new())),
|
inner: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts provided signature into the database.
|
pub async fn insert_issued_credential(
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `voucher_value`: Plaintext bandwidth value of the credential.
|
|
||||||
/// * `voucher_info`: Plaintext information of the credential.
|
|
||||||
/// * `serial_number`: Base58 representation of the serial number attribute.
|
|
||||||
/// * `binding_number`: Base58 representation of the binding number attribute.
|
|
||||||
/// * `signature`: Coconut credential in the form of a signature.
|
|
||||||
pub async fn insert_coconut_credential(
|
|
||||||
&self,
|
&self,
|
||||||
voucher_value: String,
|
credential_type: String,
|
||||||
voucher_info: String,
|
serialization_revision: u8,
|
||||||
serial_number: String,
|
credential_data: &[u8],
|
||||||
binding_number: String,
|
epoch_id: u32,
|
||||||
signature: String,
|
|
||||||
epoch_id: String,
|
|
||||||
) {
|
) {
|
||||||
let mut creds = self.inner.write().await;
|
let mut inner = self.inner.write().await;
|
||||||
let id = creds.len() as i64;
|
let id = inner.next_id();
|
||||||
creds.push(CoconutCredential {
|
inner.data.push(StoredIssuedCredential {
|
||||||
id,
|
id,
|
||||||
voucher_value,
|
serialization_revision,
|
||||||
voucher_info,
|
credential_data: credential_data.to_vec(),
|
||||||
serial_number,
|
credential_type,
|
||||||
binding_number,
|
|
||||||
signature,
|
|
||||||
epoch_id,
|
epoch_id,
|
||||||
consumed: false,
|
consumed: false,
|
||||||
});
|
expired: false,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to retrieve one of the stored, unused credentials.
|
/// Tries to retrieve one of the stored, unused credentials.
|
||||||
pub async fn get_next_coconut_credential(&self) -> Option<CoconutCredential> {
|
pub async fn get_next_unspent_credential(&self) -> Option<StoredIssuedCredential> {
|
||||||
let creds = self.inner.read().await;
|
let creds = self.inner.read().await;
|
||||||
creds.iter().find(|c| !c.consumed).cloned()
|
creds
|
||||||
|
.data
|
||||||
|
.iter()
|
||||||
|
.find(|c| !c.consumed && !c.expired)
|
||||||
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consumes in the database the specified credential.
|
/// Consumes in the database the specified credential.
|
||||||
@@ -63,8 +69,20 @@ impl CoconutCredentialManager {
|
|||||||
/// * `id`: Database id.
|
/// * `id`: Database id.
|
||||||
pub async fn consume_coconut_credential(&self, id: i64) {
|
pub async fn consume_coconut_credential(&self, id: i64) {
|
||||||
let mut creds = self.inner.write().await;
|
let mut creds = self.inner.write().await;
|
||||||
if let Some(cred) = creds.get_mut(id as usize) {
|
if let Some(cred) = creds.data.get_mut(id as usize) {
|
||||||
cred.consumed = true;
|
cred.consumed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Marks the specified credential as expired
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `id`: Id of the credential to mark as expired.
|
||||||
|
pub async fn mark_expired(&self, id: i64) {
|
||||||
|
let mut creds = self.inner.write().await;
|
||||||
|
if let Some(cred) = creds.data.get_mut(id as usize) {
|
||||||
|
cred.expired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::models::CoconutCredential;
|
use crate::models::StoredIssuedCredential;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CoconutCredentialManager {
|
pub struct CoconutCredentialManager {
|
||||||
@@ -18,40 +18,28 @@ impl CoconutCredentialManager {
|
|||||||
CoconutCredentialManager { connection_pool }
|
CoconutCredentialManager { connection_pool }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts provided signature into the database.
|
pub async fn insert_issued_credential(
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `voucher_value`: Plaintext bandwidth value of the credential.
|
|
||||||
/// * `voucher_info`: Plaintext information of the credential.
|
|
||||||
/// * `serial_number`: Base58 representation of the serial number attribute.
|
|
||||||
/// * `binding_number`: Base58 representation of the binding number attribute.
|
|
||||||
/// * `signature`: Coconut credential in the form of a signature.
|
|
||||||
pub async fn insert_coconut_credential(
|
|
||||||
&self,
|
&self,
|
||||||
voucher_value: String,
|
credential_type: String,
|
||||||
voucher_info: String,
|
serialization_revision: u8,
|
||||||
serial_number: String,
|
credential_data: &[u8],
|
||||||
binding_number: String,
|
epoch_id: u32,
|
||||||
signature: String,
|
|
||||||
epoch_id: String,
|
|
||||||
) -> Result<(), sqlx::Error> {
|
) -> Result<(), sqlx::Error> {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"INSERT INTO coconut_credentials(voucher_value, voucher_info, serial_number, binding_number, signature, epoch_id, consumed) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
r#"
|
||||||
voucher_value, voucher_info, serial_number, binding_number, signature, epoch_id, false
|
INSERT INTO coconut_credentials(serialization_revision, credential_type, credential_data, epoch_id, consumed, expired)
|
||||||
)
|
VALUES (?, ?, ?, ?, false, false)
|
||||||
.execute(&self.connection_pool)
|
"#,
|
||||||
.await?;
|
serialization_revision, credential_type, credential_data, epoch_id
|
||||||
|
).execute(&self.connection_pool).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to retrieve one of the stored, unused credentials.
|
pub async fn get_next_unspent_credential(
|
||||||
pub async fn get_next_coconut_credential(
|
|
||||||
&self,
|
&self,
|
||||||
) -> Result<Option<CoconutCredential>, sqlx::Error> {
|
) -> Result<Option<StoredIssuedCredential>, sqlx::Error> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as(
|
||||||
CoconutCredential,
|
"SELECT * FROM coconut_credentials WHERE NOT consumed AND NOT expired LIMIT 1",
|
||||||
"SELECT * FROM coconut_credentials WHERE NOT consumed"
|
|
||||||
)
|
)
|
||||||
.fetch_optional(&self.connection_pool)
|
.fetch_optional(&self.connection_pool)
|
||||||
.await
|
.await
|
||||||
@@ -71,4 +59,19 @@ impl CoconutCredentialManager {
|
|||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Marks the specified credential as expired
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `id`: Id of the credential to mark as expired.
|
||||||
|
pub async fn mark_expired(&self, id: i64) -> Result<(), sqlx::Error> {
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE coconut_credentials SET expired = TRUE WHERE id = ?",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.execute(&self.connection_pool)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
use crate::backends::memory::CoconutCredentialManager;
|
use crate::backends::memory::CoconutCredentialManager;
|
||||||
use crate::error::StorageError;
|
use crate::error::StorageError;
|
||||||
use crate::models::CoconutCredential;
|
use crate::models::{StorableIssuedCredential, StoredIssuedCredential};
|
||||||
use crate::storage::Storage;
|
use crate::storage::Storage;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
@@ -27,37 +27,28 @@ impl Default for EphemeralStorage {
|
|||||||
impl Storage for EphemeralStorage {
|
impl Storage for EphemeralStorage {
|
||||||
type StorageError = StorageError;
|
type StorageError = StorageError;
|
||||||
|
|
||||||
async fn insert_coconut_credential(
|
async fn insert_issued_credential<'a>(
|
||||||
&self,
|
&self,
|
||||||
voucher_value: String,
|
bandwidth_credential: StorableIssuedCredential<'a>,
|
||||||
voucher_info: String,
|
|
||||||
serial_number: String,
|
|
||||||
binding_number: String,
|
|
||||||
signature: String,
|
|
||||||
epoch_id: String,
|
|
||||||
) -> Result<(), StorageError> {
|
) -> Result<(), StorageError> {
|
||||||
self.coconut_credential_manager
|
self.coconut_credential_manager
|
||||||
.insert_coconut_credential(
|
.insert_issued_credential(
|
||||||
voucher_value,
|
bandwidth_credential.credential_type,
|
||||||
voucher_info,
|
bandwidth_credential.serialization_revision,
|
||||||
serial_number,
|
bandwidth_credential.credential_data,
|
||||||
binding_number,
|
bandwidth_credential.epoch_id,
|
||||||
signature,
|
|
||||||
epoch_id,
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, StorageError> {
|
async fn get_next_unspent_credential(
|
||||||
let credential = self
|
&self,
|
||||||
|
) -> Result<Option<StoredIssuedCredential>, Self::StorageError> {
|
||||||
|
Ok(self
|
||||||
.coconut_credential_manager
|
.coconut_credential_manager
|
||||||
.get_next_coconut_credential()
|
.get_next_unspent_credential()
|
||||||
.await
|
.await)
|
||||||
.ok_or(StorageError::NoCredential)?;
|
|
||||||
|
|
||||||
Ok(credential)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError> {
|
async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError> {
|
||||||
@@ -67,4 +58,10 @@ impl Storage for EphemeralStorage {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn mark_expired(&self, id: i64) -> Result<(), Self::StorageError> {
|
||||||
|
self.coconut_credential_manager.mark_expired(id).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,39 @@
|
|||||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
#[derive(Clone)]
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
pub struct CoconutCredential {
|
|
||||||
#[allow(dead_code)]
|
// #[derive(Clone)]
|
||||||
|
// pub struct CoconutCredential {
|
||||||
|
// #[allow(dead_code)]
|
||||||
|
// pub id: i64,
|
||||||
|
// pub voucher_value: String,
|
||||||
|
// pub voucher_info: String,
|
||||||
|
// pub serial_number: String,
|
||||||
|
// pub binding_number: String,
|
||||||
|
// pub signature: String,
|
||||||
|
// pub epoch_id: String,
|
||||||
|
// pub consumed: bool,
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))]
|
||||||
|
#[derive(Zeroize, ZeroizeOnDrop, Clone)]
|
||||||
|
pub struct StoredIssuedCredential {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub voucher_value: String,
|
|
||||||
pub voucher_info: String,
|
pub serialization_revision: u8,
|
||||||
pub serial_number: String,
|
pub credential_data: Vec<u8>,
|
||||||
pub binding_number: String,
|
pub credential_type: String,
|
||||||
pub signature: String,
|
|
||||||
pub epoch_id: String,
|
pub epoch_id: u32,
|
||||||
pub consumed: bool,
|
pub consumed: bool,
|
||||||
|
pub expired: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StorableIssuedCredential<'a> {
|
||||||
|
pub serialization_revision: u8,
|
||||||
|
pub credential_data: &'a [u8],
|
||||||
|
pub credential_type: String,
|
||||||
|
|
||||||
|
pub epoch_id: u32,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::backends::sqlite::CoconutCredentialManager;
|
|||||||
use crate::error::StorageError;
|
use crate::error::StorageError;
|
||||||
use crate::storage::Storage;
|
use crate::storage::Storage;
|
||||||
|
|
||||||
use crate::models::CoconutCredential;
|
use crate::models::{StorableIssuedCredential, StoredIssuedCredential};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use sqlx::ConnectOptions;
|
use sqlx::ConnectOptions;
|
||||||
@@ -58,37 +58,29 @@ impl PersistentStorage {
|
|||||||
impl Storage for PersistentStorage {
|
impl Storage for PersistentStorage {
|
||||||
type StorageError = StorageError;
|
type StorageError = StorageError;
|
||||||
|
|
||||||
async fn insert_coconut_credential(
|
async fn insert_issued_credential<'a>(
|
||||||
&self,
|
&self,
|
||||||
voucher_value: String,
|
bandwidth_credential: StorableIssuedCredential<'a>,
|
||||||
voucher_info: String,
|
) -> Result<(), Self::StorageError> {
|
||||||
serial_number: String,
|
|
||||||
binding_number: String,
|
|
||||||
signature: String,
|
|
||||||
epoch_id: String,
|
|
||||||
) -> Result<(), StorageError> {
|
|
||||||
self.coconut_credential_manager
|
self.coconut_credential_manager
|
||||||
.insert_coconut_credential(
|
.insert_issued_credential(
|
||||||
voucher_value,
|
bandwidth_credential.credential_type,
|
||||||
voucher_info,
|
bandwidth_credential.serialization_revision,
|
||||||
serial_number,
|
bandwidth_credential.credential_data,
|
||||||
binding_number,
|
bandwidth_credential.epoch_id,
|
||||||
signature,
|
|
||||||
epoch_id,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, StorageError> {
|
async fn get_next_unspent_credential(
|
||||||
let credential = self
|
&self,
|
||||||
|
) -> Result<Option<StoredIssuedCredential>, Self::StorageError> {
|
||||||
|
Ok(self
|
||||||
.coconut_credential_manager
|
.coconut_credential_manager
|
||||||
.get_next_coconut_credential()
|
.get_next_unspent_credential()
|
||||||
.await?
|
.await?)
|
||||||
.ok_or(StorageError::NoCredential)?;
|
|
||||||
|
|
||||||
Ok(credential)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError> {
|
async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError> {
|
||||||
@@ -98,4 +90,10 @@ impl Storage for PersistentStorage {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn mark_expired(&self, id: i64) -> Result<(), Self::StorageError> {
|
||||||
|
self.coconut_credential_manager.mark_expired(id).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::models::CoconutCredential;
|
use crate::models::{StorableIssuedCredential, StoredIssuedCredential};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
@@ -9,28 +9,16 @@ use std::error::Error;
|
|||||||
pub trait Storage: Send + Sync {
|
pub trait Storage: Send + Sync {
|
||||||
type StorageError: Error;
|
type StorageError: Error;
|
||||||
|
|
||||||
/// Inserts provided signature into the database.
|
async fn insert_issued_credential<'a>(
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `voucher_value`: How much bandwidth is in the credential.
|
|
||||||
/// * `voucher_info`: What type of credential it is.
|
|
||||||
/// * `serial_number`: Serial number of the credential.
|
|
||||||
/// * `binding_number`: Binding number of the credential.
|
|
||||||
/// * `signature`: Coconut credential in the form of a signature.
|
|
||||||
/// * `epoch_id`: The epoch when it was signed.
|
|
||||||
async fn insert_coconut_credential(
|
|
||||||
&self,
|
&self,
|
||||||
voucher_value: String,
|
bandwidth_credential: StorableIssuedCredential<'a>,
|
||||||
voucher_info: String,
|
|
||||||
serial_number: String,
|
|
||||||
binding_number: String,
|
|
||||||
signature: String,
|
|
||||||
epoch_id: String,
|
|
||||||
) -> Result<(), Self::StorageError>;
|
) -> Result<(), Self::StorageError>;
|
||||||
|
|
||||||
/// Tries to retrieve one of the stored, unused credentials.
|
/// Tries to retrieve one of the stored, unused credentials,
|
||||||
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, Self::StorageError>;
|
/// that is also not marked as expired
|
||||||
|
async fn get_next_unspent_credential(
|
||||||
|
&self,
|
||||||
|
) -> Result<Option<StoredIssuedCredential>, Self::StorageError>;
|
||||||
|
|
||||||
/// Marks as consumed in the database the specified credential.
|
/// Marks as consumed in the database the specified credential.
|
||||||
///
|
///
|
||||||
@@ -38,4 +26,11 @@ pub trait Storage: Send + Sync {
|
|||||||
///
|
///
|
||||||
/// * `id`: Id of the credential to be consumed.
|
/// * `id`: Id of the credential to be consumed.
|
||||||
async fn consume_coconut_credential(&self, id: i64) -> Result<(), Self::StorageError>;
|
async fn consume_coconut_credential(&self, id: i64) -> Result<(), Self::StorageError>;
|
||||||
|
|
||||||
|
/// Marks the specified credential as expired
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `id`: Id of the credential to mark as expired.
|
||||||
|
async fn mark_expired(&self, id: i64) -> Result<(), Self::StorageError>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ thiserror = { workspace = true }
|
|||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|
||||||
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
||||||
|
nym-coconut = { path = "../nymcoconut" }
|
||||||
nym-credentials = { path = "../../common/credentials" }
|
nym-credentials = { path = "../../common/credentials" }
|
||||||
nym-credential-storage = { path = "../../common/credential-storage" }
|
nym-credential-storage = { path = "../../common/credential-storage" }
|
||||||
nym-validator-client = { path = "../../common/client-libs/validator-client" }
|
nym-validator-client = { path = "../../common/client-libs/validator-client" }
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
|
|
||||||
use crate::errors::Result;
|
use crate::errors::Result;
|
||||||
use log::error;
|
use log::error;
|
||||||
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
|
use nym_credentials::coconut::bandwidth::IssuanceBandwidthCredential;
|
||||||
use std::fs::{create_dir_all, read_dir, File};
|
use std::fs::{create_dir_all, read_dir, File};
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub const DUMPED_VOUCHER_EXTENSION: &str = "credentialrecovery";
|
||||||
|
|
||||||
pub struct RecoveryStorage {
|
pub struct RecoveryStorage {
|
||||||
recovery_dir: PathBuf,
|
recovery_dir: PathBuf,
|
||||||
}
|
}
|
||||||
@@ -18,14 +20,16 @@ impl RecoveryStorage {
|
|||||||
Ok(Self { recovery_dir })
|
Ok(Self { recovery_dir })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unconsumed_vouchers(&self) -> Result<Vec<BandwidthVoucher>> {
|
pub fn unconsumed_vouchers(&self) -> Result<Vec<IssuanceBandwidthCredential>> {
|
||||||
let entries = read_dir(&self.recovery_dir)?;
|
let entries = read_dir(&self.recovery_dir)?;
|
||||||
|
|
||||||
let mut paths = vec![];
|
let mut paths = vec![];
|
||||||
for entry in entries.flatten() {
|
for entry in entries.flatten() {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if path.is_file() {
|
if let Some(extension) = path.extension() {
|
||||||
paths.push(path)
|
if extension == DUMPED_VOUCHER_EXTENSION {
|
||||||
|
paths.push(path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +38,7 @@ impl RecoveryStorage {
|
|||||||
if let Ok(mut file) = File::open(&path) {
|
if let Ok(mut file) = File::open(&path) {
|
||||||
let mut buff = Vec::new();
|
let mut buff = Vec::new();
|
||||||
if file.read_to_end(&mut buff).is_ok() {
|
if file.read_to_end(&mut buff).is_ok() {
|
||||||
match BandwidthVoucher::try_from_bytes(&buff) {
|
match IssuanceBandwidthCredential::try_from_recovered_bytes(&buff) {
|
||||||
Ok(voucher) => vouchers.push(voucher),
|
Ok(voucher) => vouchers.push(voucher),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("failed to parse the voucher at {}: {err}", path.display())
|
error!("failed to parse the voucher at {}: {err}", path.display())
|
||||||
@@ -47,11 +51,17 @@ impl RecoveryStorage {
|
|||||||
Ok(vouchers)
|
Ok(vouchers)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_voucher(&self, voucher: &BandwidthVoucher) -> Result<PathBuf> {
|
pub fn voucher_filename(voucher: &IssuanceBandwidthCredential) -> String {
|
||||||
let file_name = voucher.tx_hash().to_string();
|
let prefix = voucher.typ().to_string();
|
||||||
|
let suffix = voucher.blinded_serial_number_bs58();
|
||||||
|
format!("{prefix}-{suffix}.{DUMPED_VOUCHER_EXTENSION}")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_voucher(&self, voucher: &IssuanceBandwidthCredential) -> Result<PathBuf> {
|
||||||
|
let file_name = Self::voucher_filename(voucher);
|
||||||
let file_path = self.recovery_dir.join(file_name);
|
let file_path = self.recovery_dir.join(file_name);
|
||||||
let mut file = File::create(&file_path)?;
|
let mut file = File::create(&file_path)?;
|
||||||
let buff = voucher.to_bytes();
|
let buff = voucher.to_recovery_bytes();
|
||||||
file.write_all(&buff)?;
|
file.write_all(&buff)?;
|
||||||
|
|
||||||
Ok(file_path)
|
Ok(file_path)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use nym_bandwidth_controller::acquire::state::State;
|
|||||||
use nym_client_core::config::disk_persistence::CommonClientPaths;
|
use nym_client_core::config::disk_persistence::CommonClientPaths;
|
||||||
use nym_config::DEFAULT_DATA_DIR;
|
use nym_config::DEFAULT_DATA_DIR;
|
||||||
use nym_credential_storage::persistent_storage::PersistentStorage;
|
use nym_credential_storage::persistent_storage::PersistentStorage;
|
||||||
|
use nym_credentials::coconut::bandwidth::CredentialType;
|
||||||
use nym_validator_client::nyxd::contract_traits::{
|
use nym_validator_client::nyxd::contract_traits::{
|
||||||
dkg_query_client::EpochState, CoconutBandwidthSigningClient, DkgQueryClient,
|
dkg_query_client::EpochState, CoconutBandwidthSigningClient, DkgQueryClient,
|
||||||
};
|
};
|
||||||
@@ -43,7 +44,7 @@ where
|
|||||||
|
|
||||||
let state = nym_bandwidth_controller::acquire::deposit(client, amount.clone()).await?;
|
let state = nym_bandwidth_controller::acquire::deposit(client, amount.clone()).await?;
|
||||||
|
|
||||||
if nym_bandwidth_controller::acquire::get_credential(&state, client, persistent_storage)
|
if nym_bandwidth_controller::acquire::get_bandwidth_voucher(&state, client, persistent_storage)
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
@@ -91,7 +92,7 @@ where
|
|||||||
.as_secs();
|
.as_secs();
|
||||||
|
|
||||||
if epoch.state.is_final() {
|
if epoch.state.is_final() {
|
||||||
if let Some(finish_timestamp) = epoch.finish_timestamp {
|
if let Some(finish_timestamp) = epoch.deadline {
|
||||||
if current_timestamp_secs + SAFETY_BUFFER_SECS >= finish_timestamp.seconds() {
|
if current_timestamp_secs + SAFETY_BUFFER_SECS >= finish_timestamp.seconds() {
|
||||||
info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60);
|
info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60);
|
||||||
exit(0);
|
exit(0);
|
||||||
@@ -128,19 +129,29 @@ where
|
|||||||
{
|
{
|
||||||
let mut recovered_amount: u128 = 0;
|
let mut recovered_amount: u128 = 0;
|
||||||
for voucher in recovery_storage.unconsumed_vouchers()? {
|
for voucher in recovery_storage.unconsumed_vouchers()? {
|
||||||
let voucher_value = voucher.get_voucher_value();
|
let voucher_value = match voucher.typ() {
|
||||||
|
CredentialType::Voucher => voucher.get_bandwidth_attribute(),
|
||||||
|
CredentialType::FreePass => {
|
||||||
|
error!("unimplemented recovery of free pass credentials");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
recovered_amount += voucher_value.parse::<u128>()?;
|
recovered_amount += voucher_value.parse::<u128>()?;
|
||||||
|
|
||||||
|
let voucher_name = RecoveryStorage::voucher_filename(&voucher);
|
||||||
let state = State::new(voucher);
|
let state = State::new(voucher);
|
||||||
let voucher = state.voucher.tx_hash();
|
|
||||||
if let Err(e) =
|
if let Err(e) =
|
||||||
nym_bandwidth_controller::acquire::get_credential(&state, client, shared_storage).await
|
nym_bandwidth_controller::acquire::get_bandwidth_voucher(&state, client, shared_storage)
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
error!("Could not recover deposit {voucher} due to {e}, try again later",)
|
error!("Could not recover deposit {voucher_name} due to {e}, try again later",)
|
||||||
} else {
|
} else {
|
||||||
info!("Converted deposit {voucher} to a credential, removing recovery data for it",);
|
info!(
|
||||||
if let Err(e) = recovery_storage.remove_voucher(voucher.to_string()) {
|
"Converted deposit {voucher_name} to a credential, removing recovery data for it",
|
||||||
warn!("Could not remove recovery data: {e}");
|
);
|
||||||
|
if let Err(err) = recovery_storage.remove_voucher(voucher_name) {
|
||||||
|
warn!("Could not remove recovery data: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "nym-credentials-interface"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
documentation.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bls12_381 = { workspace = true, default-features = false }
|
||||||
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
|
nym-coconut = { path = "../nymcoconut" }
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use bls12_381::Scalar;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub use nym_coconut::{
|
||||||
|
aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar, keygen,
|
||||||
|
prepare_blind_sign, prove_bandwidth_credential, verify_credential, Attribute, Base58,
|
||||||
|
BlindSignRequest, BlindedSerialNumber, BlindedSignature, Bytable, CoconutError, KeyPair,
|
||||||
|
Parameters, PrivateAttribute, PublicAttribute, SecretKey, Signature, SignatureShare,
|
||||||
|
VerificationKey, VerifyCredentialRequest,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const VOUCHER_INFO_TYPE: &str = "BandwidthVoucher";
|
||||||
|
pub const FREE_PASS_INFO_TYPE: &str = "FreeBandwidthPass";
|
||||||
|
|
||||||
|
// pub trait NymCredential {
|
||||||
|
// fn prove_credential(&self) -> Result<(), ()>;
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[error("{0} is not a valid credential type")]
|
||||||
|
pub struct UnknownCredentialType(String);
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||||
|
pub enum CredentialType {
|
||||||
|
Voucher,
|
||||||
|
FreePass,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for CredentialType {
|
||||||
|
type Err = UnknownCredentialType;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if s == VOUCHER_INFO_TYPE {
|
||||||
|
Ok(CredentialType::Voucher)
|
||||||
|
} else if s == FREE_PASS_INFO_TYPE {
|
||||||
|
Ok(CredentialType::FreePass)
|
||||||
|
} else {
|
||||||
|
Err(UnknownCredentialType(s.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CredentialType {
|
||||||
|
pub fn validate(&self, type_plain: &str) -> bool {
|
||||||
|
match self {
|
||||||
|
CredentialType::Voucher => type_plain == VOUCHER_INFO_TYPE,
|
||||||
|
CredentialType::FreePass => type_plain == FREE_PASS_INFO_TYPE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_free_pass(&self) -> bool {
|
||||||
|
matches!(self, CredentialType::FreePass)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_voucher(&self) -> bool {
|
||||||
|
matches!(self, CredentialType::Voucher)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CredentialType {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
CredentialType::Voucher => VOUCHER_INFO_TYPE.fmt(f),
|
||||||
|
CredentialType::FreePass => FREE_PASS_INFO_TYPE.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CredentialSigningData {
|
||||||
|
pub pedersen_commitments_openings: Vec<Scalar>,
|
||||||
|
|
||||||
|
pub blind_sign_request: BlindSignRequest,
|
||||||
|
|
||||||
|
pub public_attributes_plain: Vec<String>,
|
||||||
|
|
||||||
|
pub typ: CredentialType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
||||||
|
pub struct CredentialSpendingData {
|
||||||
|
pub embedded_private_attributes: usize,
|
||||||
|
|
||||||
|
pub verify_credential_request: VerifyCredentialRequest,
|
||||||
|
|
||||||
|
pub public_attributes_plain: Vec<String>,
|
||||||
|
|
||||||
|
pub typ: CredentialType,
|
||||||
|
|
||||||
|
/// The (DKG) epoch id under which the credential has been issued so that the verifier could use correct verification key for validation.
|
||||||
|
pub epoch_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CredentialSpendingData {
|
||||||
|
pub fn verify(&self, params: &Parameters, verification_key: &VerificationKey) -> bool {
|
||||||
|
let hashed_public_attributes = self
|
||||||
|
.public_attributes_plain
|
||||||
|
.iter()
|
||||||
|
.map(hash_to_scalar)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// get references to the attributes
|
||||||
|
let public_attributes = hashed_public_attributes.iter().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
verify_credential(
|
||||||
|
params,
|
||||||
|
verification_key,
|
||||||
|
&self.verify_credential_request,
|
||||||
|
&public_attributes,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_type_attribute(&self) -> bool {
|
||||||
|
// the first attribute is variant specific bandwidth encoding, the second one should be the type
|
||||||
|
let Some(type_plain) = self.public_attributes_plain.get(1) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.typ.validate(type_plain)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_bandwidth_attribute(&self) -> Option<&String> {
|
||||||
|
// the first attribute is variant specific bandwidth encoding, the second one should be the type
|
||||||
|
self.public_attributes_plain.first()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blinded_serial_number(&self) -> BlindedSerialNumber {
|
||||||
|
self.verify_credential_request.blinded_serial_number()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,14 +8,17 @@ license.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bls12_381 = { workspace = true, default-features = false, features = ["pairings", "alloc", "experimental"] }
|
bls12_381 = { workspace = true, default-features = false, features = ["pairings", "alloc", "experimental"] }
|
||||||
|
bincode = "1.3.3"
|
||||||
cosmrs = { workspace = true }
|
cosmrs = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
|
time = { workspace = true, features = ["serde"] }
|
||||||
|
serde = { workspace = true, features = ["derive"] }
|
||||||
zeroize = { workspace = true }
|
zeroize = { workspace = true }
|
||||||
|
|
||||||
# I guess temporarily until we get serde support in coconut up and running
|
# I guess temporarily until we get serde support in coconut up and running
|
||||||
nym-coconut-interface = { path = "../coconut-interface" }
|
nym-credentials-interface = { path = "../credentials-interface" }
|
||||||
nym-crypto = { path = "../crypto", features = ["rand", "asymmetric"] }
|
nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "serde"] }
|
||||||
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
|
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
|
||||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||||
|
|
||||||
|
|||||||
@@ -1,428 +0,0 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
// for time being assume the bandwidth credential consists of public identity of the requester
|
|
||||||
// and private (though known... just go along with it) infinite bandwidth value
|
|
||||||
// right now this has no double-spending protection, spender binding, etc
|
|
||||||
// it's the simplest possible case
|
|
||||||
|
|
||||||
use cosmrs::tendermint::hash::Algorithm;
|
|
||||||
use cosmrs::tendermint::Hash;
|
|
||||||
use nym_coconut_interface::{
|
|
||||||
hash_to_scalar, prepare_blind_sign, Attribute, BlindSignRequest, Credential, Parameters,
|
|
||||||
PrivateAttribute, PublicAttribute, Signature, VerificationKey,
|
|
||||||
};
|
|
||||||
use nym_crypto::asymmetric::{encryption, identity};
|
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
|
||||||
|
|
||||||
use super::utils::prepare_credential_for_spending;
|
|
||||||
use crate::error::Error;
|
|
||||||
|
|
||||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
|
||||||
pub struct BandwidthVoucher {
|
|
||||||
// private attributes
|
|
||||||
/// a random secret value generated by the client used for double-spending detection
|
|
||||||
serial_number: PrivateAttribute,
|
|
||||||
|
|
||||||
/// a random secret value generated by the client used to bind multiple credentials together
|
|
||||||
binding_number: PrivateAttribute,
|
|
||||||
|
|
||||||
// public atttributes:
|
|
||||||
/// the plain text value (e.g., bandwidth) encoded in this voucher
|
|
||||||
// TODO: in another PR change the value from `"1000"` to `"1000unym"`
|
|
||||||
voucher_value_plain: String,
|
|
||||||
|
|
||||||
/// the plain text information
|
|
||||||
voucher_info_plain: String,
|
|
||||||
|
|
||||||
/// the precomputed value (e.g., bandwidth) encoded in this voucher
|
|
||||||
_voucher_value_prehashed: PublicAttribute,
|
|
||||||
|
|
||||||
/// the precomputed field with public information, e.g., type of voucher, interval etc.
|
|
||||||
_voucher_info_prehashed: PublicAttribute,
|
|
||||||
|
|
||||||
/// the hash of the deposit transaction
|
|
||||||
#[zeroize(skip)]
|
|
||||||
tx_hash: Hash,
|
|
||||||
|
|
||||||
/// base58 encoded private key ensuring the depositer requested these attributes
|
|
||||||
signing_key: identity::PrivateKey,
|
|
||||||
|
|
||||||
/// base58 encoded private key ensuring only this client receives the signature share
|
|
||||||
unused_ed25519: encryption::PrivateKey,
|
|
||||||
|
|
||||||
pedersen_commitments_openings: Vec<Attribute>,
|
|
||||||
|
|
||||||
#[zeroize(skip)]
|
|
||||||
blind_sign_request: BlindSignRequest,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BandwidthVoucher {
|
|
||||||
pub const PUBLIC_ATTRIBUTES: u32 = 2;
|
|
||||||
pub const PRIVATE_ATTRIBUTES: u32 = 2;
|
|
||||||
pub const ENCODED_ATTRIBUTES: u32 = 4;
|
|
||||||
|
|
||||||
pub fn default_parameters() -> Parameters {
|
|
||||||
// safety: the unwrap is fine here as Self::ENCODED_ATTRIBUTES is non-zero
|
|
||||||
Parameters::new(Self::ENCODED_ATTRIBUTES).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(
|
|
||||||
params: &Parameters,
|
|
||||||
voucher_value: String,
|
|
||||||
voucher_info: String,
|
|
||||||
tx_hash: Hash,
|
|
||||||
signing_key: identity::PrivateKey,
|
|
||||||
encryption_key: encryption::PrivateKey,
|
|
||||||
) -> Self {
|
|
||||||
let serial_number = params.random_scalar();
|
|
||||||
let binding_number = params.random_scalar();
|
|
||||||
let voucher_value_plain = voucher_value.clone();
|
|
||||||
let voucher_info_plain = voucher_info.clone();
|
|
||||||
|
|
||||||
let _voucher_value_prehashed = hash_to_scalar(voucher_value);
|
|
||||||
let _voucher_info_prehashed = hash_to_scalar(voucher_info);
|
|
||||||
|
|
||||||
let (pedersen_commitments_openings, blind_sign_request) = prepare_blind_sign(
|
|
||||||
params,
|
|
||||||
&[&serial_number, &binding_number],
|
|
||||||
&[&_voucher_value_prehashed, &_voucher_info_prehashed],
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
BandwidthVoucher {
|
|
||||||
serial_number,
|
|
||||||
binding_number,
|
|
||||||
_voucher_value_prehashed,
|
|
||||||
voucher_value_plain,
|
|
||||||
_voucher_info_prehashed,
|
|
||||||
voucher_info_plain,
|
|
||||||
tx_hash,
|
|
||||||
signing_key,
|
|
||||||
unused_ed25519: encryption_key,
|
|
||||||
pedersen_commitments_openings,
|
|
||||||
blind_sign_request,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_bytes(&self) -> Vec<u8> {
|
|
||||||
let serial_number_b = self.serial_number.to_bytes();
|
|
||||||
let binding_number_b = self.binding_number.to_bytes();
|
|
||||||
let voucher_value_plain_b = self.voucher_value_plain.as_bytes();
|
|
||||||
let voucher_info_plain_b = self.voucher_info_plain.as_bytes();
|
|
||||||
let tx_hash_b = self.tx_hash.as_bytes();
|
|
||||||
let signing_key_b = self.signing_key.to_bytes();
|
|
||||||
let encryption_key_b = self.unused_ed25519.to_bytes();
|
|
||||||
let blind_sign_request_b = self.blind_sign_request.to_bytes();
|
|
||||||
|
|
||||||
let mut ret = Vec::new();
|
|
||||||
|
|
||||||
ret.extend_from_slice(&serial_number_b);
|
|
||||||
ret.extend_from_slice(&binding_number_b);
|
|
||||||
ret.extend_from_slice(tx_hash_b);
|
|
||||||
ret.extend_from_slice(&signing_key_b);
|
|
||||||
ret.extend_from_slice(&encryption_key_b);
|
|
||||||
ret.extend_from_slice(&(voucher_value_plain_b.len() as u64).to_be_bytes());
|
|
||||||
ret.extend_from_slice(&(voucher_info_plain_b.len() as u64).to_be_bytes());
|
|
||||||
ret.extend_from_slice(&(blind_sign_request_b.len() as u64).to_be_bytes());
|
|
||||||
ret.extend_from_slice(&(self.pedersen_commitments_openings.len() as u64).to_be_bytes());
|
|
||||||
ret.extend_from_slice(voucher_value_plain_b);
|
|
||||||
ret.extend_from_slice(voucher_info_plain_b);
|
|
||||||
ret.extend_from_slice(&blind_sign_request_b);
|
|
||||||
for commitment in self.pedersen_commitments_openings.iter() {
|
|
||||||
ret.extend_from_slice(&commitment.to_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, Error> {
|
|
||||||
if bytes.len() < 32 * 5 + 4 * 8 {
|
|
||||||
return Err(Error::BandwidthVoucherDeserializationError(format!(
|
|
||||||
"Less then {} bytes needed",
|
|
||||||
32 * 5 + 4 * 8
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
let mut buff = [0u8; 32];
|
|
||||||
let mut small_buff = [0u8; 8];
|
|
||||||
let scalar_err =
|
|
||||||
|| Error::BandwidthVoucherDeserializationError(String::from("Invalid Scalar"));
|
|
||||||
buff.copy_from_slice(&bytes[..32]);
|
|
||||||
let serial_number = Option::<PrivateAttribute>::from(PrivateAttribute::from_bytes(&buff))
|
|
||||||
.ok_or_else(scalar_err)?;
|
|
||||||
buff.copy_from_slice(&bytes[32..2 * 32]);
|
|
||||||
let binding_number = Option::<PrivateAttribute>::from(PrivateAttribute::from_bytes(&buff))
|
|
||||||
.ok_or_else(scalar_err)?;
|
|
||||||
buff.copy_from_slice(&bytes[2 * 32..3 * 32]);
|
|
||||||
let tx_hash = Hash::from_bytes(Algorithm::Sha256, &buff).map_err(|_| {
|
|
||||||
Error::BandwidthVoucherDeserializationError(String::from("Invalid transaction Hash"))
|
|
||||||
})?;
|
|
||||||
buff.copy_from_slice(&bytes[3 * 32..4 * 32]);
|
|
||||||
let signing_key = identity::PrivateKey::from_bytes(&buff).map_err(|_| {
|
|
||||||
Error::BandwidthVoucherDeserializationError(String::from("Invalid key"))
|
|
||||||
})?;
|
|
||||||
buff.copy_from_slice(&bytes[4 * 32..5 * 32]);
|
|
||||||
let encryption_key = encryption::PrivateKey::from_bytes(&buff).map_err(|_| {
|
|
||||||
Error::BandwidthVoucherDeserializationError(String::from("Invalid key"))
|
|
||||||
})?;
|
|
||||||
small_buff.copy_from_slice(&bytes[5 * 32..5 * 32 + 8]);
|
|
||||||
let voucher_value_plain_no = u64::from_be_bytes(small_buff) as usize;
|
|
||||||
small_buff.copy_from_slice(&bytes[5 * 32 + 8..5 * 32 + 2 * 8]);
|
|
||||||
let voucher_info_plain_no = u64::from_be_bytes(small_buff) as usize;
|
|
||||||
small_buff.copy_from_slice(&bytes[5 * 32 + 2 * 8..5 * 32 + 3 * 8]);
|
|
||||||
let blind_sign_request_no = u64::from_be_bytes(small_buff) as usize;
|
|
||||||
small_buff.copy_from_slice(&bytes[5 * 32 + 3 * 8..5 * 32 + 4 * 8]);
|
|
||||||
let pedersen_commitments_openings_no = u64::from_be_bytes(small_buff) as usize;
|
|
||||||
|
|
||||||
let total_length = 32 * 5
|
|
||||||
+ 4 * 8
|
|
||||||
+ voucher_value_plain_no
|
|
||||||
+ voucher_info_plain_no
|
|
||||||
+ blind_sign_request_no
|
|
||||||
+ pedersen_commitments_openings_no * 32;
|
|
||||||
if bytes.len() != total_length {
|
|
||||||
return Err(Error::BandwidthVoucherDeserializationError(format!(
|
|
||||||
"Expected {total_length} bytes",
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let utf_err = |_| {
|
|
||||||
Err(Error::BandwidthVoucherDeserializationError(String::from(
|
|
||||||
"Invalid UTF8 string",
|
|
||||||
)))
|
|
||||||
};
|
|
||||||
let mut var_length_pointer = 5 * 32 + 4 * 8;
|
|
||||||
let voucher_value_plain = String::from_utf8(
|
|
||||||
bytes[var_length_pointer..var_length_pointer + voucher_value_plain_no].to_vec(),
|
|
||||||
)
|
|
||||||
.or_else(utf_err)?;
|
|
||||||
let _voucher_value_prehashed = hash_to_scalar(&voucher_value_plain);
|
|
||||||
var_length_pointer += voucher_value_plain_no;
|
|
||||||
let voucher_info_plain = String::from_utf8(
|
|
||||||
bytes[var_length_pointer..var_length_pointer + voucher_info_plain_no].to_vec(),
|
|
||||||
)
|
|
||||||
.or_else(utf_err)?;
|
|
||||||
let _voucher_info_prehashed = hash_to_scalar(&voucher_info_plain);
|
|
||||||
var_length_pointer += voucher_info_plain_no;
|
|
||||||
let blind_sign_request = BlindSignRequest::from_bytes(
|
|
||||||
&bytes[var_length_pointer..var_length_pointer + blind_sign_request_no],
|
|
||||||
)?;
|
|
||||||
var_length_pointer += blind_sign_request_no;
|
|
||||||
|
|
||||||
let mut pedersen_commitments_openings = Vec::new();
|
|
||||||
for _ in 0..pedersen_commitments_openings_no {
|
|
||||||
buff.copy_from_slice(&bytes[var_length_pointer..var_length_pointer + 32]);
|
|
||||||
let commitment =
|
|
||||||
Option::<Attribute>::from(Attribute::from_bytes(&buff)).ok_or_else(scalar_err)?;
|
|
||||||
var_length_pointer += 32;
|
|
||||||
pedersen_commitments_openings.push(commitment);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
serial_number,
|
|
||||||
binding_number,
|
|
||||||
_voucher_value_prehashed,
|
|
||||||
voucher_value_plain,
|
|
||||||
_voucher_info_prehashed,
|
|
||||||
voucher_info_plain,
|
|
||||||
tx_hash,
|
|
||||||
signing_key,
|
|
||||||
unused_ed25519: encryption_key,
|
|
||||||
pedersen_commitments_openings,
|
|
||||||
blind_sign_request,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the plain values correspond to the PublicAttributes
|
|
||||||
pub fn verify_against_plain(values: &[&PublicAttribute], plain_values: &[String]) -> bool {
|
|
||||||
values.len() == 2
|
|
||||||
&& plain_values.len() == 2
|
|
||||||
&& values[0] == &hash_to_scalar(&plain_values[0])
|
|
||||||
&& values[1] == &hash_to_scalar(&plain_values[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tx_hash(&self) -> Hash {
|
|
||||||
self.tx_hash
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_public_attributes(&self) -> Vec<&PublicAttribute> {
|
|
||||||
vec![
|
|
||||||
&self._voucher_value_prehashed,
|
|
||||||
&self._voucher_info_prehashed,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn identity_key(&self) -> &identity::PrivateKey {
|
|
||||||
&self.signing_key
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encryption_key(&self) -> &encryption::PrivateKey {
|
|
||||||
&self.unused_ed25519
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pedersen_commitments_openings(&self) -> &Vec<Attribute> {
|
|
||||||
&self.pedersen_commitments_openings
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn blind_sign_request(&self) -> &BlindSignRequest {
|
|
||||||
&self.blind_sign_request
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_voucher_value(&self) -> String {
|
|
||||||
self.voucher_value_plain.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_public_attributes_plain(&self) -> Vec<String> {
|
|
||||||
vec![
|
|
||||||
self.voucher_value_plain.clone(),
|
|
||||||
self.voucher_info_plain.clone(),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_private_attributes(&self) -> Vec<&PrivateAttribute> {
|
|
||||||
vec![&self.serial_number, &self.binding_number]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn signable_plaintext(request: &BlindSignRequest, tx_hash: Hash) -> Vec<u8> {
|
|
||||||
let mut message = request.to_bytes();
|
|
||||||
message.extend_from_slice(tx_hash.as_bytes());
|
|
||||||
message
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sign(&self) -> identity::Signature {
|
|
||||||
let message = Self::signable_plaintext(&self.blind_sign_request, self.tx_hash);
|
|
||||||
self.signing_key.sign(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prepare_for_spending(
|
|
||||||
voucher_value: u64,
|
|
||||||
voucher_info: String,
|
|
||||||
serial_number: &PrivateAttribute,
|
|
||||||
binding_number: &PrivateAttribute,
|
|
||||||
epoch_id: u64,
|
|
||||||
signature: &Signature,
|
|
||||||
verification_key: &VerificationKey,
|
|
||||||
) -> Result<Credential, Error> {
|
|
||||||
let params = Parameters::new(BandwidthVoucher::ENCODED_ATTRIBUTES)?;
|
|
||||||
|
|
||||||
prepare_credential_for_spending(
|
|
||||||
¶ms,
|
|
||||||
voucher_value,
|
|
||||||
voucher_info,
|
|
||||||
serial_number,
|
|
||||||
binding_number,
|
|
||||||
epoch_id,
|
|
||||||
signature,
|
|
||||||
verification_key,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use cosmrs::tendermint::hash::Algorithm;
|
|
||||||
use nym_coconut_interface::Base58;
|
|
||||||
use rand::rngs::OsRng;
|
|
||||||
|
|
||||||
fn voucher_fixture() -> BandwidthVoucher {
|
|
||||||
let params = Parameters::new(4).unwrap();
|
|
||||||
let mut rng = OsRng;
|
|
||||||
BandwidthVoucher::new(
|
|
||||||
¶ms,
|
|
||||||
"1234".to_string(),
|
|
||||||
"voucher info".to_string(),
|
|
||||||
Hash::from_bytes(Algorithm::Sha256, &[0; 32]).unwrap(),
|
|
||||||
identity::PrivateKey::from_base58_string(
|
|
||||||
identity::KeyPair::new(&mut rng)
|
|
||||||
.private_key()
|
|
||||||
.to_base58_string(),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
encryption::PrivateKey::from_bytes(
|
|
||||||
&encryption::KeyPair::new(&mut rng).private_key().to_bytes(),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn serde_voucher() {
|
|
||||||
let voucher = voucher_fixture();
|
|
||||||
let bytes = voucher.to_bytes();
|
|
||||||
let deserialized_voucher = BandwidthVoucher::try_from_bytes(&bytes).unwrap();
|
|
||||||
assert_eq!(voucher.serial_number, deserialized_voucher.serial_number);
|
|
||||||
assert_eq!(voucher.binding_number, deserialized_voucher.binding_number);
|
|
||||||
assert_eq!(
|
|
||||||
voucher.voucher_value_plain,
|
|
||||||
deserialized_voucher.voucher_value_plain
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
voucher.voucher_info_plain,
|
|
||||||
deserialized_voucher.voucher_info_plain
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
voucher._voucher_value_prehashed,
|
|
||||||
deserialized_voucher._voucher_value_prehashed
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
voucher._voucher_info_prehashed,
|
|
||||||
deserialized_voucher._voucher_info_prehashed
|
|
||||||
);
|
|
||||||
assert_eq!(voucher.tx_hash, deserialized_voucher.tx_hash);
|
|
||||||
assert_eq!(
|
|
||||||
voucher.signing_key.to_string(),
|
|
||||||
deserialized_voucher.signing_key.to_string()
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
voucher.unused_ed25519.to_string(),
|
|
||||||
deserialized_voucher.unused_ed25519.to_string()
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
voucher.pedersen_commitments_openings,
|
|
||||||
deserialized_voucher.pedersen_commitments_openings
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
voucher.blind_sign_request.to_bs58(),
|
|
||||||
deserialized_voucher.blind_sign_request.to_bs58()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn voucher_consistency() {
|
|
||||||
let voucher = voucher_fixture();
|
|
||||||
assert!(!BandwidthVoucher::verify_against_plain(
|
|
||||||
&[],
|
|
||||||
&voucher.get_public_attributes_plain()
|
|
||||||
));
|
|
||||||
assert!(!BandwidthVoucher::verify_against_plain(
|
|
||||||
&voucher.get_public_attributes(),
|
|
||||||
&[],
|
|
||||||
));
|
|
||||||
assert!(!BandwidthVoucher::verify_against_plain(
|
|
||||||
&voucher.get_public_attributes(),
|
|
||||||
&[
|
|
||||||
voucher.get_public_attributes_plain()[0].clone(),
|
|
||||||
String::new()
|
|
||||||
]
|
|
||||||
));
|
|
||||||
assert!(!BandwidthVoucher::verify_against_plain(
|
|
||||||
&voucher.get_public_attributes(),
|
|
||||||
&[
|
|
||||||
String::new(),
|
|
||||||
voucher.get_public_attributes_plain()[1].clone()
|
|
||||||
]
|
|
||||||
));
|
|
||||||
assert!(!BandwidthVoucher::verify_against_plain(
|
|
||||||
&[voucher.get_public_attributes()[0], &Attribute::one()],
|
|
||||||
&voucher.get_public_attributes_plain()
|
|
||||||
));
|
|
||||||
assert!(!BandwidthVoucher::verify_against_plain(
|
|
||||||
&[&Attribute::one(), voucher.get_public_attributes()[1]],
|
|
||||||
&voucher.get_public_attributes_plain()
|
|
||||||
));
|
|
||||||
assert!(BandwidthVoucher::verify_against_plain(
|
|
||||||
&voucher.get_public_attributes(),
|
|
||||||
&voucher.get_public_attributes_plain()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::coconut::utils::scalar_serde_helper;
|
||||||
|
use crate::error::Error;
|
||||||
|
use nym_api_requests::coconut::FreePassRequest;
|
||||||
|
use nym_credentials_interface::{
|
||||||
|
hash_to_scalar, Attribute, BlindedSignature, CredentialSigningData, PublicAttribute,
|
||||||
|
};
|
||||||
|
use nym_validator_client::signing::AccountData;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use time::{Duration, OffsetDateTime, Time};
|
||||||
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
|
pub const MAX_FREE_PASS_VALIDITY: Duration = Duration::WEEK; // 1 week
|
||||||
|
|
||||||
|
#[derive(Debug, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
|
||||||
|
pub struct FreePassIssuedData {
|
||||||
|
/// the plain validity value of this credential expressed as unix timestamp
|
||||||
|
#[zeroize(skip)]
|
||||||
|
expiry_date: OffsetDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a FreePassIssuanceData> for FreePassIssuedData {
|
||||||
|
fn from(value: &'a FreePassIssuanceData) -> Self {
|
||||||
|
FreePassIssuedData {
|
||||||
|
expiry_date: value.expiry_date,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FreePassIssuedData {
|
||||||
|
pub fn expired(&self) -> bool {
|
||||||
|
self.expiry_date <= OffsetDateTime::now_utc()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expiry_date(&self) -> OffsetDateTime {
|
||||||
|
self.expiry_date
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expiry_date_plain(&self) -> String {
|
||||||
|
self.expiry_date.unix_timestamp().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Zeroize, Serialize, Deserialize)]
|
||||||
|
pub struct FreePassIssuanceData {
|
||||||
|
/// the plain validity value of this credential expressed as unix timestamp
|
||||||
|
#[zeroize(skip)]
|
||||||
|
expiry_date: OffsetDateTime,
|
||||||
|
|
||||||
|
// the expiry date, as unix timestamp, hashed into a scalar
|
||||||
|
#[serde(with = "scalar_serde_helper")]
|
||||||
|
expiry_date_prehashed: PublicAttribute,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FreePassIssuanceData {
|
||||||
|
pub fn new(expiry_date: Option<OffsetDateTime>) -> Self {
|
||||||
|
// ideally we should have implemented a proper error handling here, sure.
|
||||||
|
// but given it's meant to only be used by nym, imo it's fine to just panic here in case of invalid arguments
|
||||||
|
let expiry_date = if let Some(provided) = expiry_date {
|
||||||
|
if provided - OffsetDateTime::now_utc() > MAX_FREE_PASS_VALIDITY {
|
||||||
|
panic!("the provided expiry date is bigger than the maximum value of {MAX_FREE_PASS_VALIDITY}");
|
||||||
|
}
|
||||||
|
|
||||||
|
provided
|
||||||
|
} else {
|
||||||
|
Self::default_expiry_date()
|
||||||
|
};
|
||||||
|
|
||||||
|
let expiry_date_prehashed = hash_to_scalar(expiry_date.unix_timestamp().to_string());
|
||||||
|
|
||||||
|
FreePassIssuanceData {
|
||||||
|
expiry_date,
|
||||||
|
expiry_date_prehashed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_expiry_date() -> OffsetDateTime {
|
||||||
|
// set it to furthest midnight in the future such as it's no more than a week away,
|
||||||
|
// i.e. if it's currently for example 9:43 on 2nd March 2024, it will set it to 0:00 on 9th March 2024
|
||||||
|
(OffsetDateTime::now_utc() + MAX_FREE_PASS_VALIDITY).replace_time(Time::MIDNIGHT)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expiry_date_attribute(&self) -> &Attribute {
|
||||||
|
&self.expiry_date_prehashed
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expiry_date_plain(&self) -> String {
|
||||||
|
self.expiry_date.unix_timestamp().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn obtain_free_pass_nonce(
|
||||||
|
&self,
|
||||||
|
client: &nym_validator_client::client::NymApiClient,
|
||||||
|
) -> Result<[u8; 16], Error> {
|
||||||
|
let server_response = client.free_pass_nonce().await?;
|
||||||
|
Ok(server_response.current_nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_free_pass_request(
|
||||||
|
&self,
|
||||||
|
signing_request: &CredentialSigningData,
|
||||||
|
account_data: &AccountData,
|
||||||
|
issuer_nonce: [u8; 16],
|
||||||
|
) -> Result<FreePassRequest, Error> {
|
||||||
|
let nonce_signature = account_data
|
||||||
|
.private_key()
|
||||||
|
.sign(&issuer_nonce)
|
||||||
|
.map_err(|_| Error::Secp256k1SignFailure)?;
|
||||||
|
|
||||||
|
Ok(FreePassRequest {
|
||||||
|
cosmos_pubkey: account_data.public_key(),
|
||||||
|
inner_sign_request: signing_request.blind_sign_request.clone(),
|
||||||
|
used_nonce: issuer_nonce,
|
||||||
|
nonce_signature,
|
||||||
|
public_attributes_plain: signing_request.public_attributes_plain.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn obtain_blinded_credential(
|
||||||
|
&self,
|
||||||
|
client: &nym_validator_client::client::NymApiClient,
|
||||||
|
request: &FreePassRequest,
|
||||||
|
) -> Result<BlindedSignature, Error> {
|
||||||
|
let server_response = client.issue_free_pass_credential(request).await?;
|
||||||
|
Ok(server_response.blinded_signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn request_blinded_credential(
|
||||||
|
&self,
|
||||||
|
signing_request: &CredentialSigningData,
|
||||||
|
account_data: &AccountData,
|
||||||
|
client: &nym_validator_client::client::NymApiClient,
|
||||||
|
) -> Result<BlindedSignature, Error> {
|
||||||
|
let signing_nonce = self.obtain_free_pass_nonce(client).await?;
|
||||||
|
let request =
|
||||||
|
self.create_free_pass_request(signing_request, account_data, signing_nonce)?;
|
||||||
|
self.obtain_blinded_credential(client, &request).await
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::coconut::bandwidth::freepass::FreePassIssuanceData;
|
||||||
|
use crate::coconut::bandwidth::issued::IssuedBandwidthCredential;
|
||||||
|
use crate::coconut::bandwidth::voucher::BandwidthVoucherIssuanceData;
|
||||||
|
use crate::coconut::bandwidth::{
|
||||||
|
bandwidth_credential_params, CredentialSigningData, CredentialType,
|
||||||
|
};
|
||||||
|
use crate::coconut::utils::scalar_serde_helper;
|
||||||
|
use crate::error::Error;
|
||||||
|
use nym_credentials_interface::{
|
||||||
|
aggregate_signature_shares, hash_to_scalar, prepare_blind_sign, Attribute, BlindedSerialNumber,
|
||||||
|
BlindedSignature, Parameters, PrivateAttribute, PublicAttribute, Signature, SignatureShare,
|
||||||
|
VerificationKey,
|
||||||
|
};
|
||||||
|
use nym_crypto::asymmetric::{encryption, identity};
|
||||||
|
use nym_validator_client::nym_api::EpochId;
|
||||||
|
use nym_validator_client::nyxd::{Coin, Hash};
|
||||||
|
use nym_validator_client::signing::AccountData;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
|
#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
|
||||||
|
pub enum BandwidthCredentialIssuanceDataVariant {
|
||||||
|
Voucher(BandwidthVoucherIssuanceData),
|
||||||
|
FreePass(FreePassIssuanceData),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FreePassIssuanceData> for BandwidthCredentialIssuanceDataVariant {
|
||||||
|
fn from(value: FreePassIssuanceData) -> Self {
|
||||||
|
BandwidthCredentialIssuanceDataVariant::FreePass(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BandwidthVoucherIssuanceData> for BandwidthCredentialIssuanceDataVariant {
|
||||||
|
fn from(value: BandwidthVoucherIssuanceData) -> Self {
|
||||||
|
BandwidthCredentialIssuanceDataVariant::Voucher(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BandwidthCredentialIssuanceDataVariant {
|
||||||
|
pub fn info(&self) -> CredentialType {
|
||||||
|
match self {
|
||||||
|
BandwidthCredentialIssuanceDataVariant::Voucher(..) => CredentialType::Voucher,
|
||||||
|
BandwidthCredentialIssuanceDataVariant::FreePass(..) => CredentialType::FreePass,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently this works under the assumption of there being a single unique public attribute for given variant
|
||||||
|
pub fn public_value(&self) -> &Attribute {
|
||||||
|
match self {
|
||||||
|
BandwidthCredentialIssuanceDataVariant::Voucher(voucher) => voucher.value_attribute(),
|
||||||
|
BandwidthCredentialIssuanceDataVariant::FreePass(freepass) => {
|
||||||
|
freepass.expiry_date_attribute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently this works under the assumption of there being a single unique public attribute for given variant
|
||||||
|
pub fn public_value_plain(&self) -> String {
|
||||||
|
match self {
|
||||||
|
BandwidthCredentialIssuanceDataVariant::Voucher(voucher) => voucher.value_plain(),
|
||||||
|
BandwidthCredentialIssuanceDataVariant::FreePass(freepass) => {
|
||||||
|
freepass.expiry_date_plain()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn voucher_data(&self) -> Option<&BandwidthVoucherIssuanceData> {
|
||||||
|
match self {
|
||||||
|
BandwidthCredentialIssuanceDataVariant::Voucher(voucher) => Some(voucher),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// all types of bandwidth credentials contain serial number and binding number
|
||||||
|
#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
|
||||||
|
pub struct IssuanceBandwidthCredential {
|
||||||
|
// private attributes
|
||||||
|
/// a random secret value generated by the client used for double-spending detection
|
||||||
|
#[serde(with = "scalar_serde_helper")]
|
||||||
|
serial_number: PrivateAttribute,
|
||||||
|
|
||||||
|
/// a random secret value generated by the client used to bind multiple credentials together
|
||||||
|
#[serde(with = "scalar_serde_helper")]
|
||||||
|
binding_number: PrivateAttribute,
|
||||||
|
|
||||||
|
/// data specific to given bandwidth credential, for example a value for bandwidth voucher and expiry date for the free pass
|
||||||
|
variant_data: BandwidthCredentialIssuanceDataVariant,
|
||||||
|
|
||||||
|
/// type of the bandwdith credential hashed onto a scalar
|
||||||
|
#[serde(with = "scalar_serde_helper")]
|
||||||
|
type_prehashed: PublicAttribute,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IssuanceBandwidthCredential {
|
||||||
|
pub const PUBLIC_ATTRIBUTES: u32 = 2;
|
||||||
|
pub const PRIVATE_ATTRIBUTES: u32 = 2;
|
||||||
|
pub const ENCODED_ATTRIBUTES: u32 = Self::PUBLIC_ATTRIBUTES + Self::PRIVATE_ATTRIBUTES;
|
||||||
|
|
||||||
|
pub fn default_parameters() -> Parameters {
|
||||||
|
// safety: the unwrap is fine here as Self::ENCODED_ATTRIBUTES is non-zero
|
||||||
|
Parameters::new(Self::ENCODED_ATTRIBUTES).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new<B: Into<BandwidthCredentialIssuanceDataVariant>>(variant_data: B) -> Self {
|
||||||
|
let variant_data = variant_data.into();
|
||||||
|
let type_prehashed = hash_to_scalar(variant_data.info().to_string());
|
||||||
|
|
||||||
|
let params = bandwidth_credential_params();
|
||||||
|
let serial_number = params.random_scalar();
|
||||||
|
let binding_number = params.random_scalar();
|
||||||
|
|
||||||
|
IssuanceBandwidthCredential {
|
||||||
|
serial_number,
|
||||||
|
binding_number,
|
||||||
|
variant_data,
|
||||||
|
type_prehashed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_voucher(
|
||||||
|
value: impl Into<Coin>,
|
||||||
|
deposit_tx_hash: Hash,
|
||||||
|
signing_key: identity::PrivateKey,
|
||||||
|
unused_ed25519: encryption::PrivateKey,
|
||||||
|
) -> Self {
|
||||||
|
Self::new(BandwidthVoucherIssuanceData::new(
|
||||||
|
value,
|
||||||
|
deposit_tx_hash,
|
||||||
|
signing_key,
|
||||||
|
unused_ed25519,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_freepass(expiry_date: Option<OffsetDateTime>) -> Self {
|
||||||
|
Self::new(FreePassIssuanceData::new(expiry_date))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blind_serial_number(&self) -> BlindedSerialNumber {
|
||||||
|
(bandwidth_credential_params().gen2() * self.serial_number).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blinded_serial_number_bs58(&self) -> String {
|
||||||
|
use nym_credentials_interface::Base58;
|
||||||
|
|
||||||
|
self.blind_serial_number().to_bs58()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn typ(&self) -> CredentialType {
|
||||||
|
self.variant_data.info()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_private_attributes(&self) -> Vec<&PrivateAttribute> {
|
||||||
|
vec![&self.serial_number, &self.binding_number]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_public_attributes(&self) -> Vec<&PublicAttribute> {
|
||||||
|
vec![self.variant_data.public_value(), &self.type_prehashed]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_plain_public_attributes(&self) -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
self.variant_data.public_value_plain(),
|
||||||
|
self.typ().to_string(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_variant_data(&self) -> &BandwidthCredentialIssuanceDataVariant {
|
||||||
|
&self.variant_data
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_bandwidth_attribute(&self) -> String {
|
||||||
|
self.variant_data.public_value_plain()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare_for_signing(&self) -> CredentialSigningData {
|
||||||
|
let params = bandwidth_credential_params();
|
||||||
|
|
||||||
|
// safety: the creation of the request can only fail if one provided invalid parameters
|
||||||
|
// and we created then specific to this type of the credential so the unwrap is fine
|
||||||
|
let (pedersen_commitments_openings, blind_sign_request) = prepare_blind_sign(
|
||||||
|
params,
|
||||||
|
&[&self.serial_number, &self.binding_number],
|
||||||
|
&self.get_public_attributes(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
CredentialSigningData {
|
||||||
|
pedersen_commitments_openings,
|
||||||
|
blind_sign_request,
|
||||||
|
public_attributes_plain: self.get_plain_public_attributes(),
|
||||||
|
typ: self.typ(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unblind_signature(
|
||||||
|
&self,
|
||||||
|
validator_vk: &VerificationKey,
|
||||||
|
signing_data: &CredentialSigningData,
|
||||||
|
blinded_signature: BlindedSignature,
|
||||||
|
) -> Result<Signature, Error> {
|
||||||
|
let public_attributes = self.get_public_attributes();
|
||||||
|
let private_attributes = self.get_private_attributes();
|
||||||
|
|
||||||
|
let params = bandwidth_credential_params();
|
||||||
|
let unblinded_signature = blinded_signature.unblind_and_verify(
|
||||||
|
params,
|
||||||
|
validator_vk,
|
||||||
|
&private_attributes,
|
||||||
|
&public_attributes,
|
||||||
|
&signing_data.blind_sign_request.get_commitment_hash(),
|
||||||
|
&signing_data.pedersen_commitments_openings,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(unblinded_signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn obtain_partial_freepass_credential(
|
||||||
|
&self,
|
||||||
|
client: &nym_validator_client::client::NymApiClient,
|
||||||
|
account_data: &AccountData,
|
||||||
|
validator_vk: &VerificationKey,
|
||||||
|
signing_data: impl Into<Option<CredentialSigningData>>,
|
||||||
|
) -> Result<Signature, Error> {
|
||||||
|
// if we provided signing data, do use them, otherwise generate fresh data
|
||||||
|
let signing_data = signing_data
|
||||||
|
.into()
|
||||||
|
.unwrap_or_else(|| self.prepare_for_signing());
|
||||||
|
|
||||||
|
let blinded_signature = match &self.variant_data {
|
||||||
|
BandwidthCredentialIssuanceDataVariant::FreePass(freepass) => {
|
||||||
|
freepass
|
||||||
|
.request_blinded_credential(&signing_data, account_data, client)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
_ => return Err(Error::NotAFreePass),
|
||||||
|
};
|
||||||
|
self.unblind_signature(validator_vk, &signing_data, blinded_signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ideally this would have been generic over credential type, but we really don't need secp256k1 keys for bandwidth vouchers
|
||||||
|
pub async fn obtain_partial_bandwidth_voucher_credential(
|
||||||
|
&self,
|
||||||
|
client: &nym_validator_client::client::NymApiClient,
|
||||||
|
validator_vk: &VerificationKey,
|
||||||
|
signing_data: impl Into<Option<CredentialSigningData>>,
|
||||||
|
) -> Result<Signature, Error> {
|
||||||
|
// if we provided signing data, do use them, otherwise generate fresh data
|
||||||
|
let signing_data = signing_data
|
||||||
|
.into()
|
||||||
|
.unwrap_or_else(|| self.prepare_for_signing());
|
||||||
|
|
||||||
|
let blinded_signature = match &self.variant_data {
|
||||||
|
BandwidthCredentialIssuanceDataVariant::Voucher(voucher) => {
|
||||||
|
// TODO: the request can be re-used between different apis
|
||||||
|
let request = voucher.create_blind_sign_request_body(&signing_data);
|
||||||
|
voucher.obtain_blinded_credential(client, &request).await?
|
||||||
|
}
|
||||||
|
_ => return Err(Error::NotABandwdithVoucher),
|
||||||
|
};
|
||||||
|
self.unblind_signature(validator_vk, &signing_data, blinded_signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn aggregate_signature_shares(
|
||||||
|
&self,
|
||||||
|
verification_key: &VerificationKey,
|
||||||
|
shares: &[SignatureShare],
|
||||||
|
) -> Result<Signature, Error> {
|
||||||
|
let public_attributes = self.get_public_attributes();
|
||||||
|
let private_attributes = self.get_private_attributes();
|
||||||
|
|
||||||
|
let params = bandwidth_credential_params();
|
||||||
|
|
||||||
|
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
|
||||||
|
attributes.extend_from_slice(&private_attributes);
|
||||||
|
attributes.extend_from_slice(&public_attributes);
|
||||||
|
|
||||||
|
aggregate_signature_shares(params, verification_key, &attributes, shares)
|
||||||
|
.map_err(Error::SignatureAggregationError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// also drops self after the conversion
|
||||||
|
pub fn into_issued_credential(
|
||||||
|
self,
|
||||||
|
aggregate_signature: Signature,
|
||||||
|
epoch_id: EpochId,
|
||||||
|
) -> IssuedBandwidthCredential {
|
||||||
|
self.to_issued_credential(aggregate_signature, epoch_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_issued_credential(
|
||||||
|
&self,
|
||||||
|
aggregate_signature: Signature,
|
||||||
|
epoch_id: EpochId,
|
||||||
|
) -> IssuedBandwidthCredential {
|
||||||
|
IssuedBandwidthCredential::new(
|
||||||
|
self.serial_number,
|
||||||
|
self.binding_number,
|
||||||
|
aggregate_signature,
|
||||||
|
(&self.variant_data).into(),
|
||||||
|
self.type_prehashed,
|
||||||
|
epoch_id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: is that actually needed?
|
||||||
|
pub fn to_recovery_bytes(&self) -> Vec<u8> {
|
||||||
|
use bincode::Options;
|
||||||
|
// safety: our data format is stable and thus the serialization should not fail
|
||||||
|
make_recovery_bincode_serializer().serialize(self).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: is that actually needed?
|
||||||
|
// idea: make it consistent with the issued credential and its vX serde
|
||||||
|
pub fn try_from_recovered_bytes(bytes: &[u8]) -> Result<Self, Error> {
|
||||||
|
use bincode::Options;
|
||||||
|
make_recovery_bincode_serializer()
|
||||||
|
.deserialize(bytes)
|
||||||
|
.map_err(|source| Error::RecoveryCredentialDeserializationFailure { source })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_recovery_bincode_serializer() -> impl bincode::Options {
|
||||||
|
use bincode::Options;
|
||||||
|
bincode::DefaultOptions::new()
|
||||||
|
.with_big_endian()
|
||||||
|
.with_varint_encoding()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
|
||||||
|
|
||||||
|
fn assert_zeroize<T: Zeroize>() {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn credential_is_zeroized() {
|
||||||
|
assert_zeroize::<IssuanceBandwidthCredential>();
|
||||||
|
assert_zeroize_on_drop::<IssuanceBandwidthCredential>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::coconut::bandwidth::bandwidth_credential_params;
|
||||||
|
use crate::coconut::bandwidth::freepass::FreePassIssuedData;
|
||||||
|
use crate::coconut::bandwidth::issuance::{
|
||||||
|
BandwidthCredentialIssuanceDataVariant, IssuanceBandwidthCredential,
|
||||||
|
};
|
||||||
|
use crate::coconut::bandwidth::voucher::BandwidthVoucherIssuedData;
|
||||||
|
use crate::coconut::bandwidth::{CredentialSpendingData, CredentialType};
|
||||||
|
use crate::coconut::utils::scalar_serde_helper;
|
||||||
|
use crate::error::Error;
|
||||||
|
use nym_credentials_interface::prove_bandwidth_credential;
|
||||||
|
use nym_credentials_interface::{
|
||||||
|
Parameters, PrivateAttribute, PublicAttribute, Signature, VerificationKey,
|
||||||
|
};
|
||||||
|
use nym_validator_client::nym_api::EpochId;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
|
pub const CURRENT_SERIALIZATION_REVISION: u8 = 1;
|
||||||
|
|
||||||
|
#[derive(Debug, Zeroize, Serialize, Deserialize)]
|
||||||
|
pub enum BandwidthCredentialIssuedDataVariant {
|
||||||
|
Voucher(BandwidthVoucherIssuedData),
|
||||||
|
FreePass(FreePassIssuedData),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a BandwidthCredentialIssuanceDataVariant> for BandwidthCredentialIssuedDataVariant {
|
||||||
|
fn from(value: &'a BandwidthCredentialIssuanceDataVariant) -> Self {
|
||||||
|
match value {
|
||||||
|
BandwidthCredentialIssuanceDataVariant::Voucher(voucher) => {
|
||||||
|
BandwidthCredentialIssuedDataVariant::Voucher(voucher.into())
|
||||||
|
}
|
||||||
|
BandwidthCredentialIssuanceDataVariant::FreePass(freepass) => {
|
||||||
|
BandwidthCredentialIssuedDataVariant::FreePass(freepass.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FreePassIssuedData> for BandwidthCredentialIssuedDataVariant {
|
||||||
|
fn from(value: FreePassIssuedData) -> Self {
|
||||||
|
BandwidthCredentialIssuedDataVariant::FreePass(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BandwidthVoucherIssuedData> for BandwidthCredentialIssuedDataVariant {
|
||||||
|
fn from(value: BandwidthVoucherIssuedData) -> Self {
|
||||||
|
BandwidthCredentialIssuedDataVariant::Voucher(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BandwidthCredentialIssuedDataVariant {
|
||||||
|
pub fn info(&self) -> CredentialType {
|
||||||
|
match self {
|
||||||
|
BandwidthCredentialIssuedDataVariant::Voucher(..) => CredentialType::Voucher,
|
||||||
|
BandwidthCredentialIssuedDataVariant::FreePass(..) => CredentialType::FreePass,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently this works under the assumption of there being a single unique public attribute for given variant
|
||||||
|
pub fn public_value_plain(&self) -> String {
|
||||||
|
match self {
|
||||||
|
BandwidthCredentialIssuedDataVariant::Voucher(voucher) => voucher.value_plain(),
|
||||||
|
BandwidthCredentialIssuedDataVariant::FreePass(freepass) => {
|
||||||
|
freepass.expiry_date_plain()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the only important thing to zeroize here are the private attributes, the rest can be made fully public for what we're concerned
|
||||||
|
#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
|
||||||
|
pub struct IssuedBandwidthCredential {
|
||||||
|
// private attributes
|
||||||
|
/// a random secret value generated by the client used for double-spending detection
|
||||||
|
#[serde(with = "scalar_serde_helper")]
|
||||||
|
serial_number: PrivateAttribute,
|
||||||
|
|
||||||
|
/// a random secret value generated by the client used to bind multiple credentials together
|
||||||
|
#[serde(with = "scalar_serde_helper")]
|
||||||
|
binding_number: PrivateAttribute,
|
||||||
|
|
||||||
|
/// the underlying aggregated signature on the attributes
|
||||||
|
#[zeroize(skip)]
|
||||||
|
signature: Signature,
|
||||||
|
|
||||||
|
/// data specific to given bandwidth credential, for example a value for bandwidth voucher and expiry date for the free pass
|
||||||
|
variant_data: BandwidthCredentialIssuedDataVariant,
|
||||||
|
|
||||||
|
/// type of the bandwdith credential hashed onto a scalar
|
||||||
|
#[serde(with = "scalar_serde_helper")]
|
||||||
|
type_prehashed: PublicAttribute,
|
||||||
|
|
||||||
|
/// Specifies the (DKG) epoch id when this credential has been issued
|
||||||
|
epoch_id: EpochId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IssuedBandwidthCredential {
|
||||||
|
pub fn new(
|
||||||
|
serial_number: PrivateAttribute,
|
||||||
|
binding_number: PrivateAttribute,
|
||||||
|
signature: Signature,
|
||||||
|
variant_data: BandwidthCredentialIssuedDataVariant,
|
||||||
|
type_prehashed: PublicAttribute,
|
||||||
|
epoch_id: EpochId,
|
||||||
|
) -> Self {
|
||||||
|
IssuedBandwidthCredential {
|
||||||
|
serial_number,
|
||||||
|
binding_number,
|
||||||
|
signature,
|
||||||
|
variant_data,
|
||||||
|
type_prehashed,
|
||||||
|
epoch_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_unpack(bytes: &[u8], revision: impl Into<Option<u8>>) -> Result<Self, Error> {
|
||||||
|
let revision = revision.into().unwrap_or(CURRENT_SERIALIZATION_REVISION);
|
||||||
|
|
||||||
|
match revision {
|
||||||
|
1 => Self::unpack_v1(bytes),
|
||||||
|
_ => Err(Error::UnknownSerializationRevision { revision }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn epoch_id(&self) -> EpochId {
|
||||||
|
self.epoch_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn variant_data(&self) -> &BandwidthCredentialIssuedDataVariant {
|
||||||
|
&self.variant_data
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_serialization_revision(&self) -> u8 {
|
||||||
|
CURRENT_SERIALIZATION_REVISION
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pack (serialize) this credential data into a stream of bytes using v1 serializer.
|
||||||
|
pub fn pack_v1(&self) -> Vec<u8> {
|
||||||
|
use bincode::Options;
|
||||||
|
// safety: our data format is stable and thus the serialization should not fail
|
||||||
|
make_storable_bincode_serializer().serialize(self).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unpack (deserialize) the credential data from the given bytes using v1 serializer.
|
||||||
|
pub fn unpack_v1(bytes: &[u8]) -> Result<Self, Error> {
|
||||||
|
use bincode::Options;
|
||||||
|
make_storable_bincode_serializer()
|
||||||
|
.deserialize(bytes)
|
||||||
|
.map_err(|source| Error::SerializationFailure {
|
||||||
|
source,
|
||||||
|
revision: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn randomise_signature(&mut self) {
|
||||||
|
let signature_prime = self.signature.randomise(bandwidth_credential_params());
|
||||||
|
self.signature = signature_prime.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_parameters() -> Parameters {
|
||||||
|
IssuanceBandwidthCredential::default_parameters()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn typ(&self) -> CredentialType {
|
||||||
|
self.variant_data.info()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_plain_public_attributes(&self) -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
self.variant_data.public_value_plain(),
|
||||||
|
self.typ().to_string(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare_for_spending(
|
||||||
|
&self,
|
||||||
|
verification_key: &VerificationKey,
|
||||||
|
) -> Result<CredentialSpendingData, Error> {
|
||||||
|
let params = bandwidth_credential_params();
|
||||||
|
|
||||||
|
let verify_credential_request = prove_bandwidth_credential(
|
||||||
|
params,
|
||||||
|
verification_key,
|
||||||
|
&self.signature,
|
||||||
|
&self.serial_number,
|
||||||
|
&self.binding_number,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(CredentialSpendingData {
|
||||||
|
embedded_private_attributes: IssuanceBandwidthCredential::PRIVATE_ATTRIBUTES as usize,
|
||||||
|
verify_credential_request,
|
||||||
|
public_attributes_plain: self.get_plain_public_attributes(),
|
||||||
|
typ: self.typ(),
|
||||||
|
epoch_id: self.epoch_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_storable_bincode_serializer() -> impl bincode::Options {
|
||||||
|
use bincode::Options;
|
||||||
|
bincode::DefaultOptions::new()
|
||||||
|
.with_big_endian()
|
||||||
|
.with_varint_encoding()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
|
||||||
|
|
||||||
|
fn assert_zeroize<T: Zeroize>() {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn credential_is_zeroized() {
|
||||||
|
assert_zeroize::<IssuedBandwidthCredential>();
|
||||||
|
assert_zeroize_on_drop::<IssuedBandwidthCredential>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
pub use issuance::IssuanceBandwidthCredential;
|
||||||
|
pub use issued::IssuedBandwidthCredential;
|
||||||
|
pub use nym_credentials_interface::{
|
||||||
|
CredentialSigningData, CredentialSpendingData, CredentialType, Parameters,
|
||||||
|
UnknownCredentialType,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod freepass;
|
||||||
|
pub mod issuance;
|
||||||
|
pub mod issued;
|
||||||
|
pub mod voucher;
|
||||||
|
|
||||||
|
// works under the assumption of having 4 attributes in the underlying credential(s)
|
||||||
|
pub fn bandwidth_credential_params() -> &'static Parameters {
|
||||||
|
static BANDWIDTH_CREDENTIAL_PARAMS: OnceLock<Parameters> = OnceLock::new();
|
||||||
|
BANDWIDTH_CREDENTIAL_PARAMS.get_or_init(IssuanceBandwidthCredential::default_parameters)
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::coconut::bandwidth::CredentialSigningData;
|
||||||
|
use crate::coconut::utils::scalar_serde_helper;
|
||||||
|
use crate::error::Error;
|
||||||
|
use nym_api_requests::coconut::BlindSignRequestBody;
|
||||||
|
use nym_credentials_interface::{
|
||||||
|
hash_to_scalar, Attribute, BlindSignRequest, BlindedSignature, PublicAttribute,
|
||||||
|
};
|
||||||
|
use nym_crypto::asymmetric::{encryption, identity};
|
||||||
|
use nym_validator_client::nyxd::{Coin, Hash};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
|
#[derive(Debug, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
|
||||||
|
pub struct BandwidthVoucherIssuedData {
|
||||||
|
/// the plain value (e.g., bandwidth) encoded in this voucher
|
||||||
|
// note: for legacy reasons we're only using the value of the coin and ignoring the denom
|
||||||
|
#[zeroize(skip)]
|
||||||
|
value: Coin,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a BandwidthVoucherIssuanceData> for BandwidthVoucherIssuedData {
|
||||||
|
fn from(value: &'a BandwidthVoucherIssuanceData) -> Self {
|
||||||
|
BandwidthVoucherIssuedData {
|
||||||
|
value: value.value.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BandwidthVoucherIssuedData {
|
||||||
|
pub fn value(&self) -> &Coin {
|
||||||
|
&self.value
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value_plain(&self) -> String {
|
||||||
|
self.value.amount.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
|
||||||
|
pub struct BandwidthVoucherIssuanceData {
|
||||||
|
/// the plain value (e.g., bandwidth) encoded in this voucher
|
||||||
|
// note: for legacy reasons we're only using the value of the coin and ignoring the denom
|
||||||
|
#[zeroize(skip)]
|
||||||
|
value: Coin,
|
||||||
|
|
||||||
|
// note: as mentioned above, we're only hashing the value of the coin!
|
||||||
|
#[serde(with = "scalar_serde_helper")]
|
||||||
|
value_prehashed: PublicAttribute,
|
||||||
|
|
||||||
|
/// the hash of the deposit transaction
|
||||||
|
#[zeroize(skip)]
|
||||||
|
deposit_tx_hash: Hash,
|
||||||
|
|
||||||
|
/// base58 encoded private key ensuring the depositer requested these attributes
|
||||||
|
signing_key: identity::PrivateKey,
|
||||||
|
|
||||||
|
/// base58 encoded private key ensuring only this client receives the signature share
|
||||||
|
unused_ed25519: encryption::PrivateKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BandwidthVoucherIssuanceData {
|
||||||
|
pub fn new(
|
||||||
|
value: impl Into<Coin>,
|
||||||
|
deposit_tx_hash: Hash,
|
||||||
|
signing_key: identity::PrivateKey,
|
||||||
|
unused_ed25519: encryption::PrivateKey,
|
||||||
|
) -> Self {
|
||||||
|
let value = value.into();
|
||||||
|
let value_prehashed = hash_to_scalar(value.amount.to_string());
|
||||||
|
|
||||||
|
BandwidthVoucherIssuanceData {
|
||||||
|
value,
|
||||||
|
value_prehashed,
|
||||||
|
deposit_tx_hash,
|
||||||
|
signing_key,
|
||||||
|
unused_ed25519,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request_plaintext(request: &BlindSignRequest, tx_hash: Hash) -> Vec<u8> {
|
||||||
|
let mut message = request.to_bytes();
|
||||||
|
message.extend_from_slice(tx_hash.as_bytes());
|
||||||
|
message
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_signature(&self, signing_request: &CredentialSigningData) -> identity::Signature {
|
||||||
|
let message =
|
||||||
|
Self::request_plaintext(&signing_request.blind_sign_request, self.deposit_tx_hash);
|
||||||
|
self.signing_key.sign(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_blind_sign_request_body(
|
||||||
|
&self,
|
||||||
|
signing_request: &CredentialSigningData,
|
||||||
|
) -> BlindSignRequestBody {
|
||||||
|
let request_signature = self.request_signature(signing_request);
|
||||||
|
|
||||||
|
BlindSignRequestBody::new(
|
||||||
|
signing_request.blind_sign_request.clone(),
|
||||||
|
self.deposit_tx_hash,
|
||||||
|
request_signature,
|
||||||
|
signing_request.public_attributes_plain.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn obtain_blinded_credential(
|
||||||
|
&self,
|
||||||
|
client: &nym_validator_client::client::NymApiClient,
|
||||||
|
request_body: &BlindSignRequestBody,
|
||||||
|
) -> Result<BlindedSignature, Error> {
|
||||||
|
let server_response = client.blind_sign(request_body).await?;
|
||||||
|
Ok(server_response.blinded_signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value_plain(&self) -> String {
|
||||||
|
self.value.amount.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value_attribute(&self) -> &Attribute {
|
||||||
|
&self.value_prehashed
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tx_hash(&self) -> Hash {
|
||||||
|
self.deposit_tx_hash
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn identity_key(&self) -> &identity::PrivateKey {
|
||||||
|
&self.signing_key
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encryption_key(&self) -> &encryption::PrivateKey {
|
||||||
|
&self.unused_ed25519
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
pub mod bandwidth;
|
pub mod bandwidth;
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::coconut::bandwidth::BandwidthVoucher;
|
use crate::coconut::bandwidth::IssuanceBandwidthCredential;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use nym_api_requests::coconut::BlindSignRequestBody;
|
use nym_credentials_interface::{
|
||||||
use nym_coconut_interface::{
|
aggregate_verification_keys, Signature, SignatureShare, VerificationKey,
|
||||||
aggregate_signature_shares, aggregate_verification_keys, prove_bandwidth_credential, Attribute,
|
|
||||||
Credential, Parameters, Signature, SignatureShare, VerificationKey,
|
|
||||||
};
|
};
|
||||||
use nym_validator_client::client::CoconutApiClient;
|
use nym_validator_client::client::CoconutApiClient;
|
||||||
|
|
||||||
pub async fn obtain_aggregate_verification_key(
|
pub fn obtain_aggregate_verification_key(
|
||||||
api_clients: &[CoconutApiClient],
|
api_clients: &[CoconutApiClient],
|
||||||
) -> Result<VerificationKey, Error> {
|
) -> Result<VerificationKey, Error> {
|
||||||
if api_clients.is_empty() {
|
if api_clients.is_empty() {
|
||||||
@@ -30,44 +28,8 @@ pub async fn obtain_aggregate_verification_key(
|
|||||||
Ok(aggregate_verification_keys(&shares, Some(&indices))?)
|
Ok(aggregate_verification_keys(&shares, Some(&indices))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn obtain_partial_credential(
|
|
||||||
params: &Parameters,
|
|
||||||
voucher: &BandwidthVoucher,
|
|
||||||
client: &nym_validator_client::client::NymApiClient,
|
|
||||||
validator_vk: &VerificationKey,
|
|
||||||
) -> Result<Signature, Error> {
|
|
||||||
let public_attributes_plain = voucher.get_public_attributes_plain();
|
|
||||||
let blind_sign_request = voucher.blind_sign_request();
|
|
||||||
let request_signature = voucher.sign();
|
|
||||||
|
|
||||||
let blind_sign_request_body = BlindSignRequestBody::new(
|
|
||||||
blind_sign_request.clone(),
|
|
||||||
voucher.tx_hash(),
|
|
||||||
request_signature,
|
|
||||||
public_attributes_plain,
|
|
||||||
);
|
|
||||||
let response = client.blind_sign(&blind_sign_request_body).await?;
|
|
||||||
|
|
||||||
let blinded_signature = response.blinded_signature;
|
|
||||||
|
|
||||||
let public_attributes = voucher.get_public_attributes();
|
|
||||||
let private_attributes = voucher.get_private_attributes();
|
|
||||||
|
|
||||||
let unblinded_signature = blinded_signature.unblind_and_verify(
|
|
||||||
params,
|
|
||||||
validator_vk,
|
|
||||||
&private_attributes,
|
|
||||||
&public_attributes,
|
|
||||||
&blind_sign_request.get_commitment_hash(),
|
|
||||||
voucher.pedersen_commitments_openings(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(unblinded_signature)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn obtain_aggregate_signature(
|
pub async fn obtain_aggregate_signature(
|
||||||
params: &Parameters,
|
voucher: &IssuanceBandwidthCredential,
|
||||||
voucher: &BandwidthVoucher,
|
|
||||||
coconut_api_clients: &[CoconutApiClient],
|
coconut_api_clients: &[CoconutApiClient],
|
||||||
threshold: u64,
|
threshold: u64,
|
||||||
) -> Result<Signature, Error> {
|
) -> Result<Signature, Error> {
|
||||||
@@ -75,16 +37,9 @@ pub async fn obtain_aggregate_signature(
|
|||||||
return Err(Error::NoValidatorsAvailable);
|
return Err(Error::NoValidatorsAvailable);
|
||||||
}
|
}
|
||||||
let mut shares = Vec::with_capacity(coconut_api_clients.len());
|
let mut shares = Vec::with_capacity(coconut_api_clients.len());
|
||||||
let validators_partial_vks: Vec<_> = coconut_api_clients
|
let verification_key = obtain_aggregate_verification_key(coconut_api_clients)?;
|
||||||
.iter()
|
|
||||||
.map(|api_client| api_client.verification_key.clone())
|
let request = voucher.prepare_for_signing();
|
||||||
.collect();
|
|
||||||
let indices: Vec<_> = coconut_api_clients
|
|
||||||
.iter()
|
|
||||||
.map(|api_client| api_client.node_id)
|
|
||||||
.collect();
|
|
||||||
let verification_key =
|
|
||||||
aggregate_verification_keys(&validators_partial_vks, Some(indices.as_ref()))?;
|
|
||||||
|
|
||||||
for coconut_api_client in coconut_api_clients.iter() {
|
for coconut_api_client in coconut_api_clients.iter() {
|
||||||
debug!(
|
debug!(
|
||||||
@@ -92,13 +47,13 @@ pub async fn obtain_aggregate_signature(
|
|||||||
coconut_api_client.api_client.api_url()
|
coconut_api_client.api_client.api_url()
|
||||||
);
|
);
|
||||||
|
|
||||||
match obtain_partial_credential(
|
match voucher
|
||||||
params,
|
.obtain_partial_bandwidth_voucher_credential(
|
||||||
voucher,
|
&coconut_api_client.api_client,
|
||||||
&coconut_api_client.api_client,
|
&coconut_api_client.verification_key,
|
||||||
&coconut_api_client.verification_key,
|
Some(request.clone()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(signature) => {
|
Ok(signature) => {
|
||||||
let share = SignatureShare::new(signature, coconut_api_client.node_id);
|
let share = SignatureShare::new(signature, coconut_api_client.node_id);
|
||||||
@@ -116,42 +71,27 @@ pub async fn obtain_aggregate_signature(
|
|||||||
return Err(Error::NotEnoughShares);
|
return Err(Error::NotEnoughShares);
|
||||||
}
|
}
|
||||||
|
|
||||||
let public_attributes = voucher.get_public_attributes();
|
voucher.aggregate_signature_shares(&verification_key, &shares)
|
||||||
let private_attributes = voucher.get_private_attributes();
|
|
||||||
|
|
||||||
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
|
|
||||||
attributes.extend_from_slice(&private_attributes);
|
|
||||||
attributes.extend_from_slice(&public_attributes);
|
|
||||||
|
|
||||||
aggregate_signature_shares(params, &verification_key, &attributes, &shares)
|
|
||||||
.map_err(Error::SignatureAggregationError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: better type flow
|
pub(crate) mod scalar_serde_helper {
|
||||||
#[allow(clippy::too_many_arguments)]
|
use bls12_381::Scalar;
|
||||||
pub fn prepare_credential_for_spending(
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
params: &Parameters,
|
use zeroize::Zeroizing;
|
||||||
voucher_value: u64,
|
|
||||||
voucher_info: String,
|
|
||||||
serial_number: &Attribute,
|
|
||||||
binding_number: &Attribute,
|
|
||||||
epoch_id: u64,
|
|
||||||
signature: &Signature,
|
|
||||||
verification_key: &VerificationKey,
|
|
||||||
) -> Result<Credential, Error> {
|
|
||||||
let theta = prove_bandwidth_credential(
|
|
||||||
params,
|
|
||||||
verification_key,
|
|
||||||
signature,
|
|
||||||
serial_number,
|
|
||||||
binding_number,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(Credential::new(
|
pub fn serialize<S: Serializer>(scalar: &Scalar, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
BandwidthVoucher::ENCODED_ATTRIBUTES,
|
scalar.to_bytes().serialize(serializer)
|
||||||
theta,
|
}
|
||||||
voucher_value,
|
|
||||||
voucher_info,
|
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Scalar, D::Error> {
|
||||||
epoch_id,
|
let b = <[u8; 32]>::deserialize(deserializer)?;
|
||||||
))
|
|
||||||
|
// make sure the bytes get zeroed
|
||||||
|
let bytes = Zeroizing::new(b);
|
||||||
|
|
||||||
|
let maybe_scalar: Option<Scalar> = Scalar::from_bytes(&bytes).into();
|
||||||
|
maybe_scalar.ok_or(serde::de::Error::custom(
|
||||||
|
"did not construct a valid bls12-381 scalar out of the provided bytes",
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use nym_coconut_interface::CoconutError;
|
use nym_credentials_interface::CoconutError;
|
||||||
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
|
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
|
||||||
use nym_validator_client::ValidatorClientError;
|
use nym_validator_client::ValidatorClientError;
|
||||||
|
|
||||||
|
use crate::coconut::bandwidth::issued::CURRENT_SERIALIZATION_REVISION;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
@@ -12,6 +13,19 @@ pub enum Error {
|
|||||||
#[error("IO error")]
|
#[error("IO error")]
|
||||||
IOError(#[from] std::io::Error),
|
IOError(#[from] std::io::Error),
|
||||||
|
|
||||||
|
#[error("failed to deserialize a recovery credential: {source}")]
|
||||||
|
RecoveryCredentialDeserializationFailure { source: bincode::Error },
|
||||||
|
|
||||||
|
#[error("failed to (de)serialize provided credential using revision {revision}: {source}")]
|
||||||
|
SerializationFailure {
|
||||||
|
#[source]
|
||||||
|
source: bincode::Error,
|
||||||
|
revision: u8,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("unknown credential serializatio revision {revision}. the current (and max supported) version is {CURRENT_SERIALIZATION_REVISION}")]
|
||||||
|
UnknownSerializationRevision { revision: u8 },
|
||||||
|
|
||||||
#[error("The detailed description is yet to be determined")]
|
#[error("The detailed description is yet to be determined")]
|
||||||
BandwidthCredentialError,
|
BandwidthCredentialError,
|
||||||
|
|
||||||
@@ -41,4 +55,13 @@ pub enum Error {
|
|||||||
|
|
||||||
#[error("Could not deserialize bandwidth voucher - {0}")]
|
#[error("Could not deserialize bandwidth voucher - {0}")]
|
||||||
BandwidthVoucherDeserializationError(String),
|
BandwidthVoucherDeserializationError(String),
|
||||||
|
|
||||||
|
#[error("the provided issuance data wasn't prepared for a bandwidth voucher")]
|
||||||
|
NotABandwdithVoucher,
|
||||||
|
|
||||||
|
#[error("the provided issuance data wasn't prepared for a free pass")]
|
||||||
|
NotAFreePass,
|
||||||
|
|
||||||
|
#[error("failed to create a secp256k1 signature")]
|
||||||
|
Secp256k1SignFailure,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,9 @@
|
|||||||
pub mod coconut;
|
pub mod coconut;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
|
pub use coconut::bandwidth::{
|
||||||
|
CredentialSigningData, CredentialSpendingData, IssuanceBandwidthCredential,
|
||||||
|
IssuedBandwidthCredential,
|
||||||
|
};
|
||||||
pub use coconut::utils::{obtain_aggregate_signature, obtain_aggregate_verification_key};
|
pub use coconut::utils::{obtain_aggregate_signature, obtain_aggregate_verification_key};
|
||||||
|
pub use error::Error;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ repository = { workspace = true }
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aes = { version = "0.8.1", optional = true }
|
aes = { version = "0.8.1", optional = true }
|
||||||
bs58 = "0.4.0"
|
bs58 = { workspace = true }
|
||||||
blake3 = { version = "1.3.1", features = ["traits-preview"], optional = true }
|
blake3 = { version = "1.3.1", features = ["traits-preview"], optional = true }
|
||||||
ctr = { version = "0.9.1", optional = true }
|
ctr = { version = "0.9.1", optional = true }
|
||||||
digest = { version = "0.10.3", optional = true }
|
digest = { version = "0.10.3", optional = true }
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ bitvec = "1.0.0"
|
|||||||
# as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm
|
# as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm
|
||||||
bls12_381 = { workspace = true, default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] }
|
bls12_381 = { workspace = true, default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] }
|
||||||
nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common", optional = true }
|
nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common", optional = true }
|
||||||
bs58 = "0.4"
|
bs58 = { workspace = true }
|
||||||
|
|
||||||
|
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
|||||||
@@ -1,8 +1,32 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
|
|
||||||
pub mod codec;
|
pub mod codec;
|
||||||
pub mod request;
|
pub mod request;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
|
||||||
pub const CURRENT_VERSION: u8 = 3;
|
// version 3: initial version
|
||||||
|
// version 4: IPv6 support
|
||||||
|
pub const CURRENT_VERSION: u8 = 4;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct IpPair {
|
||||||
|
pub ipv4: Ipv4Addr,
|
||||||
|
pub ipv6: Ipv6Addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IpPair {
|
||||||
|
pub fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self {
|
||||||
|
IpPair { ipv4, ipv6 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for IpPair {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
writeln!(f, "IPv4: {}, IPV6: {}", self.ipv4, self.ipv6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn make_bincode_serializer() -> impl bincode::Options {
|
fn make_bincode_serializer() -> impl bincode::Options {
|
||||||
use bincode::Options;
|
use bincode::Options;
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
use std::net::IpAddr;
|
|
||||||
|
|
||||||
use nym_sphinx::addressing::clients::Recipient;
|
use nym_sphinx::addressing::clients::Recipient;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{make_bincode_serializer, CURRENT_VERSION};
|
use crate::{make_bincode_serializer, IpPair, CURRENT_VERSION};
|
||||||
|
|
||||||
fn generate_random() -> u64 {
|
fn generate_random() -> u64 {
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
@@ -19,7 +17,7 @@ pub struct IpPacketRequest {
|
|||||||
|
|
||||||
impl IpPacketRequest {
|
impl IpPacketRequest {
|
||||||
pub fn new_static_connect_request(
|
pub fn new_static_connect_request(
|
||||||
ip: IpAddr,
|
ips: IpPair,
|
||||||
reply_to: Recipient,
|
reply_to: Recipient,
|
||||||
reply_to_hops: Option<u8>,
|
reply_to_hops: Option<u8>,
|
||||||
reply_to_avg_mix_delays: Option<f64>,
|
reply_to_avg_mix_delays: Option<f64>,
|
||||||
@@ -31,7 +29,7 @@ impl IpPacketRequest {
|
|||||||
version: CURRENT_VERSION,
|
version: CURRENT_VERSION,
|
||||||
data: IpPacketRequestData::StaticConnect(StaticConnectRequest {
|
data: IpPacketRequestData::StaticConnect(StaticConnectRequest {
|
||||||
request_id,
|
request_id,
|
||||||
ip,
|
ips,
|
||||||
reply_to,
|
reply_to,
|
||||||
reply_to_hops,
|
reply_to_hops,
|
||||||
reply_to_avg_mix_delays,
|
reply_to_avg_mix_delays,
|
||||||
@@ -137,7 +135,7 @@ pub enum IpPacketRequestData {
|
|||||||
pub struct StaticConnectRequest {
|
pub struct StaticConnectRequest {
|
||||||
pub request_id: u64,
|
pub request_id: u64,
|
||||||
|
|
||||||
pub ip: IpAddr,
|
pub ips: IpPair,
|
||||||
|
|
||||||
// The nym-address the response should be sent back to
|
// The nym-address the response should be sent back to
|
||||||
pub reply_to: Recipient,
|
pub reply_to: Recipient,
|
||||||
@@ -210,6 +208,8 @@ pub struct HealthRequest {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_size_of_request() {
|
fn check_size_of_request() {
|
||||||
@@ -218,15 +218,15 @@ mod tests {
|
|||||||
data: IpPacketRequestData::StaticConnect(
|
data: IpPacketRequestData::StaticConnect(
|
||||||
StaticConnectRequest {
|
StaticConnectRequest {
|
||||||
request_id: 123,
|
request_id: 123,
|
||||||
ip: IpAddr::from([10, 0, 0, 1]),
|
ips: IpPair::new(Ipv4Addr::from_str("10.0.0.1").unwrap(), Ipv6Addr::from_str("2001:db8:a160::1").unwrap()),
|
||||||
reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(),
|
reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(),
|
||||||
reply_to_hops: None,
|
reply_to_hops: None,
|
||||||
reply_to_avg_mix_delays: None,
|
reply_to_avg_mix_delays: None,
|
||||||
buffer_timeout: None,
|
buffer_timeout: None,
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
assert_eq!(connect.to_bytes().unwrap().len(), 108);
|
assert_eq!(connect.to_bytes().unwrap().len(), 123);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
use std::net::IpAddr;
|
|
||||||
|
|
||||||
use nym_sphinx::addressing::clients::Recipient;
|
use nym_sphinx::addressing::clients::Recipient;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{make_bincode_serializer, CURRENT_VERSION};
|
use crate::{make_bincode_serializer, IpPair, CURRENT_VERSION};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct IpPacketResponse {
|
pub struct IpPacketResponse {
|
||||||
@@ -38,13 +36,13 @@ impl IpPacketResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_dynamic_connect_success(request_id: u64, reply_to: Recipient, ip: IpAddr) -> Self {
|
pub fn new_dynamic_connect_success(request_id: u64, reply_to: Recipient, ips: IpPair) -> Self {
|
||||||
Self {
|
Self {
|
||||||
version: CURRENT_VERSION,
|
version: CURRENT_VERSION,
|
||||||
data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse {
|
data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse {
|
||||||
request_id,
|
request_id,
|
||||||
reply_to,
|
reply_to,
|
||||||
reply: DynamicConnectResponseReply::Success(DynamicConnectSuccess { ip }),
|
reply: DynamicConnectResponseReply::Success(DynamicConnectSuccess { ips }),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -263,7 +261,7 @@ impl DynamicConnectResponseReply {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct DynamicConnectSuccess {
|
pub struct DynamicConnectSuccess {
|
||||||
pub ip: IpAddr,
|
pub ips: IpPair,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||||
|
|||||||
@@ -457,6 +457,9 @@ pub const ETH_ERC20_APPROVE_FUNCTION_NAME: &str = "approve";
|
|||||||
/// How much bandwidth (in bytes) one token can buy
|
/// How much bandwidth (in bytes) one token can buy
|
||||||
pub const BYTES_PER_UTOKEN: u64 = 1024;
|
pub const BYTES_PER_UTOKEN: u64 = 1024;
|
||||||
|
|
||||||
|
/// How much bandwidth (in bytes) one freepass provides
|
||||||
|
pub const BYTES_PER_FREEPASS: u64 = 1024 * 1024 * 1024; // 1GB
|
||||||
|
|
||||||
/// Threshold for claiming more bandwidth: 1 MB
|
/// Threshold for claiming more bandwidth: 1 MB
|
||||||
pub const REMAINING_BANDWIDTH_THRESHOLD: i64 = 1024 * 1024;
|
pub const REMAINING_BANDWIDTH_THRESHOLD: i64 = 1024 * 1024;
|
||||||
/// How many ERC20 tokens should be burned to buy bandwidth
|
/// How many ERC20 tokens should be burned to buy bandwidth
|
||||||
@@ -466,10 +469,6 @@ pub const UTOKENS_TO_BURN: u64 = TOKENS_TO_BURN * 1000000;
|
|||||||
/// Default bandwidth (in bytes) that we try to buy
|
/// Default bandwidth (in bytes) that we try to buy
|
||||||
pub const BANDWIDTH_VALUE: u64 = UTOKENS_TO_BURN * BYTES_PER_UTOKEN;
|
pub const BANDWIDTH_VALUE: u64 = UTOKENS_TO_BURN * BYTES_PER_UTOKEN;
|
||||||
|
|
||||||
pub const VOUCHER_INFO: &str = "BandwidthVoucher";
|
|
||||||
|
|
||||||
pub const ETH_MIN_BLOCK_DEPTH: usize = 7;
|
|
||||||
|
|
||||||
/// Defaults Cosmos Hub/ATOM path
|
/// Defaults Cosmos Hub/ATOM path
|
||||||
pub const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0";
|
pub const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0";
|
||||||
// as set by validators in their configs
|
// as set by validators in their configs
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "nym-id"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
documentation.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
thiserror.workspace = true
|
||||||
|
time.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
zeroize.workspace = true
|
||||||
|
|
||||||
|
nym-credential-storage = { path = "../credential-storage" }
|
||||||
|
nym-credentials = { path = "../credentials" }
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
use thiserror::Error;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum NymIdError {
|
||||||
|
#[error("failed to deserialize provided credential: {source}")]
|
||||||
|
CredentialDeserializationFailure { source: nym_credentials::Error },
|
||||||
|
|
||||||
|
#[error("attempted to import an expired credential (it expired on {expiration})")]
|
||||||
|
ExpiredCredentialImport { expiration: OffsetDateTime },
|
||||||
|
|
||||||
|
#[error("failed to store credential in the provided store: {source}")]
|
||||||
|
StorageError {
|
||||||
|
source: Box<dyn Error + Send + Sync>,
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::NymIdError;
|
||||||
|
use nym_credential_storage::models::StorableIssuedCredential;
|
||||||
|
use nym_credential_storage::storage::Storage;
|
||||||
|
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
|
||||||
|
use nym_credentials::IssuedBandwidthCredential;
|
||||||
|
use tracing::{debug, warn};
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
pub async fn import_credential<S>(
|
||||||
|
credentials_store: S,
|
||||||
|
raw_credential: Vec<u8>,
|
||||||
|
credential_version: impl Into<Option<u8>>,
|
||||||
|
) -> Result<(), NymIdError>
|
||||||
|
where
|
||||||
|
S: Storage,
|
||||||
|
<S as Storage>::StorageError: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
let raw_credential = Zeroizing::new(raw_credential);
|
||||||
|
|
||||||
|
// note: the type itself implements ZeroizeOnDrop
|
||||||
|
let credential = IssuedBandwidthCredential::try_unpack(&raw_credential, credential_version)
|
||||||
|
.map_err(|source| NymIdError::CredentialDeserializationFailure { source })?;
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"attempting to import credential of type {}",
|
||||||
|
credential.typ()
|
||||||
|
);
|
||||||
|
|
||||||
|
match credential.variant_data() {
|
||||||
|
BandwidthCredentialIssuedDataVariant::Voucher(voucher_info) => {
|
||||||
|
debug!("with value of {}", voucher_info.value())
|
||||||
|
}
|
||||||
|
BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => {
|
||||||
|
debug!("with expiry at {}", freepass_info.expiry_date());
|
||||||
|
if freepass_info.expired() {
|
||||||
|
warn!("the free pass has already expired!");
|
||||||
|
|
||||||
|
// technically we can import it, but the gateway will just reject it so what's the point
|
||||||
|
return Err(NymIdError::ExpiredCredentialImport {
|
||||||
|
expiration: freepass_info.expiry_date(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// for the epoch to run over u32::MAX, we'd have to advance it for few centuries every block...
|
||||||
|
// the alternative is a very particularly malformed serialized data, but at that point blowing up is the right call
|
||||||
|
// because we can't rely on it anyway
|
||||||
|
#[allow(clippy::expect_used)]
|
||||||
|
let storable = StorableIssuedCredential {
|
||||||
|
serialization_revision: credential.current_serialization_revision(),
|
||||||
|
credential_data: &raw_credential,
|
||||||
|
credential_type: credential.typ().to_string(),
|
||||||
|
epoch_id: credential
|
||||||
|
.epoch_id()
|
||||||
|
.try_into()
|
||||||
|
.expect("our epoch is has run over u32::MAX!"),
|
||||||
|
};
|
||||||
|
|
||||||
|
credentials_store
|
||||||
|
.insert_issued_credential(storable)
|
||||||
|
.await
|
||||||
|
.map_err(|source| NymIdError::StorageError {
|
||||||
|
source: Box::new(source),
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
#![warn(clippy::expect_used)]
|
||||||
|
#![warn(clippy::unwrap_used)]
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
pub mod import_credential;
|
||||||
|
|
||||||
|
pub use error::NymIdError;
|
||||||
|
pub use import_credential::import_credential;
|
||||||
@@ -15,7 +15,7 @@ rand = "0.8"
|
|||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
bs58 = "0.4.0"
|
bs58 = { workspace = true }
|
||||||
sha2 = "0.9"
|
sha2 = "0.9"
|
||||||
zeroize = { workspace = true, optional = true }
|
zeroize = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{BlindSignRequest, BlindedSignature, Bytable, Theta};
|
use crate::{BlindSignRequest, BlindedSignature, Bytable, VerifyCredentialRequest};
|
||||||
|
|
||||||
macro_rules! impl_clone {
|
macro_rules! impl_clone {
|
||||||
($struct:ident) => {
|
($struct:ident) => {
|
||||||
@@ -12,4 +12,4 @@ macro_rules! impl_clone {
|
|||||||
|
|
||||||
impl_clone!(BlindSignRequest);
|
impl_clone!(BlindSignRequest);
|
||||||
impl_clone!(BlindedSignature);
|
impl_clone!(BlindedSignature);
|
||||||
impl_clone!(Theta);
|
impl_clone!(VerifyCredentialRequest);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use crate::elgamal::PrivateKey;
|
use crate::elgamal::PrivateKey;
|
||||||
use crate::scheme::SecretKey;
|
use crate::scheme::SecretKey;
|
||||||
use crate::{
|
use crate::{
|
||||||
Base58, BlindSignRequest, BlindedSignature, PublicKey, Signature, Theta, VerificationKey,
|
Base58, BlindSignRequest, BlindedSignature, PublicKey, Signature, VerificationKey,
|
||||||
|
VerifyCredentialRequest,
|
||||||
};
|
};
|
||||||
use serde::de::Unexpected;
|
use serde::de::Unexpected;
|
||||||
use serde::{de::Error, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{de::Error, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
@@ -53,4 +54,4 @@ impl_serde!(PrivateKey, V4);
|
|||||||
impl_serde!(BlindSignRequest, V5);
|
impl_serde!(BlindSignRequest, V5);
|
||||||
impl_serde!(BlindedSignature, V6);
|
impl_serde!(BlindedSignature, V6);
|
||||||
impl_serde!(Signature, V7);
|
impl_serde!(Signature, V7);
|
||||||
impl_serde!(Theta, V8);
|
impl_serde!(VerifyCredentialRequest, V8);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ pub use scheme::issuance::blind_sign;
|
|||||||
pub use scheme::issuance::prepare_blind_sign;
|
pub use scheme::issuance::prepare_blind_sign;
|
||||||
pub use scheme::issuance::verify_partial_blind_signature;
|
pub use scheme::issuance::verify_partial_blind_signature;
|
||||||
pub use scheme::issuance::BlindSignRequest;
|
pub use scheme::issuance::BlindSignRequest;
|
||||||
|
pub use scheme::keygen::keygen;
|
||||||
pub use scheme::keygen::ttp_keygen;
|
pub use scheme::keygen::ttp_keygen;
|
||||||
pub use scheme::keygen::KeyPair;
|
pub use scheme::keygen::KeyPair;
|
||||||
pub use scheme::keygen::SecretKey;
|
pub use scheme::keygen::SecretKey;
|
||||||
@@ -23,7 +24,8 @@ pub use scheme::setup::Parameters;
|
|||||||
pub use scheme::verification::check_vk_pairing;
|
pub use scheme::verification::check_vk_pairing;
|
||||||
pub use scheme::verification::prove_bandwidth_credential;
|
pub use scheme::verification::prove_bandwidth_credential;
|
||||||
pub use scheme::verification::verify_credential;
|
pub use scheme::verification::verify_credential;
|
||||||
pub use scheme::verification::Theta;
|
pub use scheme::verification::BlindedSerialNumber;
|
||||||
|
pub use scheme::verification::VerifyCredentialRequest;
|
||||||
pub use scheme::BlindedSignature;
|
pub use scheme::BlindedSignature;
|
||||||
pub use scheme::Signature;
|
pub use scheme::Signature;
|
||||||
pub use scheme::SignatureShare;
|
pub use scheme::SignatureShare;
|
||||||
|
|||||||
@@ -1,17 +1,46 @@
|
|||||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use bls12_381::G2Projective;
|
|
||||||
use group::Curve;
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::convert::TryInto;
|
|
||||||
|
|
||||||
use crate::error::{CoconutError, Result};
|
use crate::error::{CoconutError, Result};
|
||||||
use crate::traits::{Base58, Bytable};
|
use crate::traits::{Base58, Bytable};
|
||||||
use crate::utils::try_deserialize_g2_projective;
|
use crate::utils::try_deserialize_g2_projective;
|
||||||
|
use bls12_381::{G2Affine, G2Projective};
|
||||||
|
use group::Curve;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::fmt::{Debug, Formatter};
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
pub struct BlindedSerialNumber {
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
pub(crate) inner: G2Projective,
|
pub struct BlindedSerialNumber(G2Projective);
|
||||||
|
|
||||||
|
// use custom Debug implementation to show base58 encoding (rather than raw curve elements)
|
||||||
|
impl Debug for BlindedSerialNumber {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_tuple("BlindedSerialNumber")
|
||||||
|
.field(&self.to_bs58())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<G2Projective> for BlindedSerialNumber {
|
||||||
|
fn from(value: G2Projective) -> Self {
|
||||||
|
BlindedSerialNumber(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<G2Affine> for BlindedSerialNumber {
|
||||||
|
fn from(value: G2Affine) -> Self {
|
||||||
|
BlindedSerialNumber(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for BlindedSerialNumber {
|
||||||
|
type Target = G2Projective;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&[u8]> for BlindedSerialNumber {
|
impl TryFrom<&[u8]> for BlindedSerialNumber {
|
||||||
@@ -34,13 +63,13 @@ impl TryFrom<&[u8]> for BlindedSerialNumber {
|
|||||||
),
|
),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(BlindedSerialNumber { inner })
|
Ok(BlindedSerialNumber(inner))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bytable for BlindedSerialNumber {
|
impl Bytable for BlindedSerialNumber {
|
||||||
fn to_byte_vec(&self) -> Vec<u8> {
|
fn to_byte_vec(&self) -> Vec<u8> {
|
||||||
self.inner.to_affine().to_compressed().to_vec()
|
self.0.to_affine().to_compressed().to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||||
|
|||||||
@@ -565,7 +565,6 @@ impl TryFrom<&[u8]> for KeyPair {
|
|||||||
/// Generate a single Coconut keypair ((x, y0, y1...), (g2^x, g2^y0, ...)).
|
/// Generate a single Coconut keypair ((x, y0, y1...), (g2^x, g2^y0, ...)).
|
||||||
/// It is not suitable for threshold credentials as all subsequent calls to `keygen` generate keys
|
/// It is not suitable for threshold credentials as all subsequent calls to `keygen` generate keys
|
||||||
/// that are independent of each other.
|
/// that are independent of each other.
|
||||||
#[cfg(test)]
|
|
||||||
pub fn keygen(params: &Parameters) -> KeyPair {
|
pub fn keygen(params: &Parameters) -> KeyPair {
|
||||||
let attributes = params.gen_hs().len();
|
let attributes = params.gen_hs().len();
|
||||||
|
|
||||||
|
|||||||
@@ -248,6 +248,15 @@ pub struct SignatureShare {
|
|||||||
index: SignerIndex,
|
index: SignerIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<(Signature, SignerIndex)> for SignatureShare {
|
||||||
|
fn from(value: (Signature, SignerIndex)) -> Self {
|
||||||
|
SignatureShare {
|
||||||
|
signature: value.0,
|
||||||
|
index: value.1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SignatureShare {
|
impl SignatureShare {
|
||||||
pub fn new(signature: Signature, index: SignerIndex) -> Self {
|
pub fn new(signature: Signature, index: SignerIndex) -> Self {
|
||||||
SignatureShare { signature, index }
|
SignatureShare { signature, index }
|
||||||
|
|||||||
@@ -44,11 +44,11 @@ impl Parameters {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn gen1(&self) -> &G1Affine {
|
pub fn gen1(&self) -> &G1Affine {
|
||||||
&self.g1
|
&self.g1
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn gen2(&self) -> &G2Affine {
|
pub fn gen2(&self) -> &G2Affine {
|
||||||
&self.g2
|
&self.g2
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ impl Parameters {
|
|||||||
&self._g2_prepared_miller
|
&self._g2_prepared_miller
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn gen_hs(&self) -> &[G1Affine] {
|
pub fn gen_hs(&self) -> &[G1Affine] {
|
||||||
&self.hs
|
&self.hs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +1,40 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use core::ops::Neg;
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::convert::TryInto;
|
|
||||||
|
|
||||||
use bls12_381::{multi_miller_loop, G1Affine, G2Prepared, G2Projective, Scalar};
|
|
||||||
use group::{Curve, Group};
|
|
||||||
|
|
||||||
use crate::error::{CoconutError, Result};
|
use crate::error::{CoconutError, Result};
|
||||||
use crate::proofs::ProofKappaZeta;
|
use crate::proofs::ProofKappaZeta;
|
||||||
use crate::scheme::double_use::BlindedSerialNumber;
|
|
||||||
use crate::scheme::setup::Parameters;
|
use crate::scheme::setup::Parameters;
|
||||||
use crate::scheme::Signature;
|
use crate::scheme::Signature;
|
||||||
use crate::scheme::VerificationKey;
|
use crate::scheme::VerificationKey;
|
||||||
use crate::traits::{Base58, Bytable};
|
use crate::traits::{Base58, Bytable};
|
||||||
use crate::utils::try_deserialize_g2_projective;
|
use crate::utils::try_deserialize_g2_projective;
|
||||||
use crate::Attribute;
|
use crate::Attribute;
|
||||||
|
use bls12_381::{multi_miller_loop, G1Affine, G2Prepared, G2Projective, Scalar};
|
||||||
|
use core::ops::Neg;
|
||||||
|
use group::{Curve, Group};
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
pub use crate::scheme::double_use::BlindedSerialNumber;
|
||||||
|
|
||||||
// TODO NAMING: this whole thing
|
// TODO NAMING: this whole thing
|
||||||
// Theta
|
// Theta
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct Theta {
|
pub struct VerifyCredentialRequest {
|
||||||
// blinded_message (kappa)
|
// blinded_message (kappa)
|
||||||
pub blinded_message: G2Projective,
|
pub blinded_message: G2Projective,
|
||||||
// blinded serial number (zeta)
|
// blinded serial number (zeta)
|
||||||
pub blinded_serial_number: G2Projective,
|
pub blinded_serial_number: BlindedSerialNumber,
|
||||||
// sigma
|
// sigma
|
||||||
pub credential: Signature,
|
pub credential: Signature,
|
||||||
// pi_v
|
// pi_v
|
||||||
pub pi_v: ProofKappaZeta,
|
pub pi_v: ProofKappaZeta,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&[u8]> for Theta {
|
impl TryFrom<&[u8]> for VerifyCredentialRequest {
|
||||||
type Error = CoconutError;
|
type Error = CoconutError;
|
||||||
|
|
||||||
fn try_from(bytes: &[u8]) -> Result<Theta> {
|
fn try_from(bytes: &[u8]) -> Result<VerifyCredentialRequest> {
|
||||||
if bytes.len() < 288 {
|
if bytes.len() < 288 {
|
||||||
return Err(
|
return Err(
|
||||||
CoconutError::Deserialization(
|
CoconutError::Deserialization(
|
||||||
@@ -53,20 +52,15 @@ impl TryFrom<&[u8]> for Theta {
|
|||||||
),
|
),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// safety: we just checked for the length so the unwraps are fine
|
let blinded_serial_number_bytes = &bytes[96..192];
|
||||||
#[allow(clippy::unwrap_used)]
|
let blinded_serial_number =
|
||||||
let blinded_serial_number_bytes = bytes[96..192].try_into().unwrap();
|
BlindedSerialNumber::try_from_byte_slice(blinded_serial_number_bytes)?;
|
||||||
let blinded_serial_number = try_deserialize_g2_projective(
|
|
||||||
&blinded_serial_number_bytes,
|
|
||||||
CoconutError::Deserialization(
|
|
||||||
"failed to deserialize the blinded serial number (zeta)".to_string(),
|
|
||||||
),
|
|
||||||
)?;
|
|
||||||
let credential = Signature::try_from(&bytes[192..288])?;
|
let credential = Signature::try_from(&bytes[192..288])?;
|
||||||
|
|
||||||
let pi_v = ProofKappaZeta::from_bytes(&bytes[288..])?;
|
let pi_v = ProofKappaZeta::from_bytes(&bytes[288..])?;
|
||||||
|
|
||||||
Ok(Theta {
|
Ok(VerifyCredentialRequest {
|
||||||
blinded_message,
|
blinded_message,
|
||||||
blinded_serial_number,
|
blinded_serial_number,
|
||||||
credential,
|
credential,
|
||||||
@@ -75,7 +69,7 @@ impl TryFrom<&[u8]> for Theta {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Theta {
|
impl VerifyCredentialRequest {
|
||||||
fn verify_proof(&self, params: &Parameters, verification_key: &VerificationKey) -> bool {
|
fn verify_proof(&self, params: &Parameters, verification_key: &VerificationKey) -> bool {
|
||||||
self.pi_v.verify(
|
self.pi_v.verify(
|
||||||
params,
|
params,
|
||||||
@@ -87,7 +81,7 @@ impl Theta {
|
|||||||
|
|
||||||
pub fn has_blinded_serial_number(&self, blinded_serial_number_bs58: &str) -> Result<bool> {
|
pub fn has_blinded_serial_number(&self, blinded_serial_number_bs58: &str) -> Result<bool> {
|
||||||
let blinded_serial_number = BlindedSerialNumber::try_from_bs58(blinded_serial_number_bs58)?;
|
let blinded_serial_number = BlindedSerialNumber::try_from_bs58(blinded_serial_number_bs58)?;
|
||||||
let ret = self.blinded_serial_number.eq(&blinded_serial_number.inner);
|
let ret = self.blinded_serial_number.eq(&blinded_serial_number);
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,29 +101,30 @@ impl Theta {
|
|||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_bytes(bytes: &[u8]) -> Result<Theta> {
|
pub fn from_bytes(bytes: &[u8]) -> Result<VerifyCredentialRequest> {
|
||||||
Theta::try_from(bytes)
|
VerifyCredentialRequest::try_from(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blinded_serial_number(&self) -> BlindedSerialNumber {
|
||||||
|
self.blinded_serial_number
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn blinded_serial_number_bs58(&self) -> String {
|
pub fn blinded_serial_number_bs58(&self) -> String {
|
||||||
let blinded_serial_nuumber = BlindedSerialNumber {
|
self.blinded_serial_number.to_bs58()
|
||||||
inner: self.blinded_serial_number,
|
|
||||||
};
|
|
||||||
blinded_serial_nuumber.to_bs58()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bytable for Theta {
|
impl Bytable for VerifyCredentialRequest {
|
||||||
fn to_byte_vec(&self) -> Vec<u8> {
|
fn to_byte_vec(&self) -> Vec<u8> {
|
||||||
self.to_bytes()
|
self.to_bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||||
Theta::try_from(slice)
|
VerifyCredentialRequest::try_from(slice)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Base58 for Theta {}
|
impl Base58 for VerifyCredentialRequest {}
|
||||||
|
|
||||||
pub fn compute_kappa(
|
pub fn compute_kappa(
|
||||||
params: &Parameters,
|
params: &Parameters,
|
||||||
@@ -156,7 +151,7 @@ pub fn prove_bandwidth_credential(
|
|||||||
signature: &Signature,
|
signature: &Signature,
|
||||||
serial_number: &Attribute,
|
serial_number: &Attribute,
|
||||||
binding_number: &Attribute,
|
binding_number: &Attribute,
|
||||||
) -> Result<Theta> {
|
) -> Result<VerifyCredentialRequest> {
|
||||||
if verification_key.beta_g2.len() < 2 {
|
if verification_key.beta_g2.len() < 2 {
|
||||||
return Err(
|
return Err(
|
||||||
CoconutError::Verification(
|
CoconutError::Verification(
|
||||||
@@ -196,9 +191,9 @@ pub fn prove_bandwidth_credential(
|
|||||||
&blinded_serial_number,
|
&blinded_serial_number,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(Theta {
|
Ok(VerifyCredentialRequest {
|
||||||
blinded_message,
|
blinded_message,
|
||||||
blinded_serial_number,
|
blinded_serial_number: blinded_serial_number.into(),
|
||||||
credential: signature_prime,
|
credential: signature_prime,
|
||||||
pi_v,
|
pi_v,
|
||||||
})
|
})
|
||||||
@@ -256,7 +251,7 @@ pub fn check_vk_pairing(
|
|||||||
pub fn verify_credential(
|
pub fn verify_credential(
|
||||||
params: &Parameters,
|
params: &Parameters,
|
||||||
verification_key: &VerificationKey,
|
verification_key: &VerificationKey,
|
||||||
theta: &Theta,
|
theta: &VerifyCredentialRequest,
|
||||||
public_attributes: &[&Attribute],
|
public_attributes: &[&Attribute],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if public_attributes.len() + theta.pi_v.private_attributes_len()
|
if public_attributes.len() + theta.pi_v.private_attributes_len()
|
||||||
@@ -358,6 +353,9 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let bytes = theta.to_bytes();
|
let bytes = theta.to_bytes();
|
||||||
assert_eq!(Theta::try_from(bytes.as_slice()).unwrap(), theta);
|
assert_eq!(
|
||||||
|
VerifyCredentialRequest::try_from(bytes.as_slice()).unwrap(),
|
||||||
|
theta
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ pub fn theta_from_keys_and_attributes(
|
|||||||
coconut_keypairs: &Vec<KeyPair>,
|
coconut_keypairs: &Vec<KeyPair>,
|
||||||
indices: &[scheme::SignerIndex],
|
indices: &[scheme::SignerIndex],
|
||||||
public_attributes: &[&PublicAttribute],
|
public_attributes: &[&PublicAttribute],
|
||||||
) -> Result<Theta, CoconutError> {
|
) -> Result<VerifyCredentialRequest, CoconutError> {
|
||||||
let serial_number = params.random_scalar();
|
let serial_number = params.random_scalar();
|
||||||
let binding_number = params.random_scalar();
|
let binding_number = params.random_scalar();
|
||||||
let private_attributes = vec![&serial_number, &binding_number];
|
let private_attributes = vec![&serial_number, &binding_number];
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ repository = { workspace = true }
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||||
bs58 = "0.4"
|
bs58 = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros
|
|||||||
tendermint.workspace = true
|
tendermint.workspace = true
|
||||||
tendermint-rpc = { workspace = true, features = ["websocket-client", "http-client"] }
|
tendermint-rpc = { workspace = true, features = ["websocket-client", "http-client"] }
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
time = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["full"] }
|
tokio = { workspace = true, features = ["full"] }
|
||||||
tokio-stream = "0.1.14"
|
tokio-stream = "0.1.14"
|
||||||
tokio-util = { version = "0.7.10", features = ["rt"]}
|
tokio-util = { version = "0.7.10", features = ["rt"]}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ pub enum ScraperError {
|
|||||||
EmptyBlockData { query: String },
|
EmptyBlockData { query: String },
|
||||||
|
|
||||||
#[error("reached maximum number of allowed errors for subscription events")]
|
#[error("reached maximum number of allowed errors for subscription events")]
|
||||||
MaximumSubscriptionFailures,
|
MaximumWebSocketFailures,
|
||||||
|
|
||||||
#[error("failed to begin storage tx: {source}")]
|
#[error("failed to begin storage tx: {source}")]
|
||||||
StorageTxBeginFailure {
|
StorageTxBeginFailure {
|
||||||
|
|||||||
@@ -6,11 +6,10 @@ use crate::block_requester::BlockRequester;
|
|||||||
use crate::error::ScraperError;
|
use crate::error::ScraperError;
|
||||||
use crate::modules::{BlockModule, MsgModule, TxModule};
|
use crate::modules::{BlockModule, MsgModule, TxModule};
|
||||||
use crate::rpc_client::RpcClient;
|
use crate::rpc_client::RpcClient;
|
||||||
use crate::scraper::subscriber::{run_websocket_driver, ChainSubscriber};
|
use crate::scraper::subscriber::ChainSubscriber;
|
||||||
use crate::storage::ScraperStorage;
|
use crate::storage::ScraperStorage;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tendermint_rpc::WebSocketClientDriver;
|
|
||||||
use tokio::sync::mpsc::{channel, unbounded_channel};
|
use tokio::sync::mpsc::{channel, unbounded_channel};
|
||||||
use tokio::sync::Notify;
|
use tokio::sync::Notify;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
@@ -67,20 +66,15 @@ impl NyxdScraperBuilder {
|
|||||||
block_processor.set_tx_modules(self.tx_modules);
|
block_processor.set_tx_modules(self.tx_modules);
|
||||||
block_processor.set_msg_modules(self.msg_modules);
|
block_processor.set_msg_modules(self.msg_modules);
|
||||||
|
|
||||||
let mut chain_subscriber = ChainSubscriber::new(
|
let chain_subscriber = ChainSubscriber::new(
|
||||||
&scraper.config.websocket_url,
|
&scraper.config.websocket_url,
|
||||||
scraper.cancel_token.clone(),
|
scraper.cancel_token.clone(),
|
||||||
|
scraper.task_tracker.clone(),
|
||||||
processing_tx,
|
processing_tx,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let ws_driver = chain_subscriber.ws_driver();
|
|
||||||
|
|
||||||
scraper.start_tasks(
|
scraper.start_tasks(block_requester, block_processor, chain_subscriber);
|
||||||
block_requester,
|
|
||||||
block_processor,
|
|
||||||
chain_subscriber,
|
|
||||||
ws_driver,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(scraper)
|
Ok(scraper)
|
||||||
}
|
}
|
||||||
@@ -141,7 +135,6 @@ impl NyxdScraper {
|
|||||||
mut block_requester: BlockRequester,
|
mut block_requester: BlockRequester,
|
||||||
mut block_processor: BlockProcessor,
|
mut block_processor: BlockProcessor,
|
||||||
mut chain_subscriber: ChainSubscriber,
|
mut chain_subscriber: ChainSubscriber,
|
||||||
ws_driver: WebSocketClientDriver,
|
|
||||||
) {
|
) {
|
||||||
self.task_tracker
|
self.task_tracker
|
||||||
.spawn(async move { block_requester.run().await });
|
.spawn(async move { block_requester.run().await });
|
||||||
@@ -149,8 +142,7 @@ impl NyxdScraper {
|
|||||||
.spawn(async move { block_processor.run().await });
|
.spawn(async move { block_processor.run().await });
|
||||||
self.task_tracker
|
self.task_tracker
|
||||||
.spawn(async move { chain_subscriber.run().await });
|
.spawn(async move { chain_subscriber.run().await });
|
||||||
self.task_tracker
|
|
||||||
.spawn(run_websocket_driver(ws_driver, self.cancel_token.clone()));
|
|
||||||
self.task_tracker.close();
|
self.task_tracker.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,21 +168,16 @@ impl NyxdScraper {
|
|||||||
rpc_client,
|
rpc_client,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let mut chain_subscriber = ChainSubscriber::new(
|
let chain_subscriber = ChainSubscriber::new(
|
||||||
&self.config.websocket_url,
|
&self.config.websocket_url,
|
||||||
self.cancel_token.clone(),
|
self.cancel_token.clone(),
|
||||||
|
self.task_tracker.clone(),
|
||||||
processing_tx,
|
processing_tx,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let ws_driver = chain_subscriber.ws_driver();
|
|
||||||
|
|
||||||
// spawn them
|
// spawn them
|
||||||
self.start_tasks(
|
self.start_tasks(block_requester, block_processor, chain_subscriber);
|
||||||
block_requester,
|
|
||||||
block_processor,
|
|
||||||
chain_subscriber,
|
|
||||||
ws_driver,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -201,10 +188,18 @@ impl NyxdScraper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn stop(self) {
|
pub async fn stop(self) {
|
||||||
info!("stopping the chain scrapper");
|
info!("stopping the chain scraper");
|
||||||
assert!(self.task_tracker.is_closed());
|
assert!(self.task_tracker.is_closed());
|
||||||
|
|
||||||
self.cancel_token.cancel();
|
self.cancel_token.cancel();
|
||||||
self.task_tracker.wait().await
|
self.task_tracker.wait().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cancel_token(&self) -> CancellationToken {
|
||||||
|
self.cancel_token.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_cancelled(&self) -> bool {
|
||||||
|
self.cancel_token.is_cancelled()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,25 @@ use crate::error::ScraperError;
|
|||||||
use tendermint_rpc::event::Event;
|
use tendermint_rpc::event::Event;
|
||||||
use tendermint_rpc::query::EventType;
|
use tendermint_rpc::query::EventType;
|
||||||
use tendermint_rpc::{SubscriptionClient, WebSocketClient, WebSocketClientDriver};
|
use tendermint_rpc::{SubscriptionClient, WebSocketClient, WebSocketClientDriver};
|
||||||
|
use time::{Duration, OffsetDateTime};
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
use tokio_stream::StreamExt;
|
use tokio_stream::StreamExt;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
use tokio_util::task::TaskTracker;
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
const MAX_FAILURES: usize = 10;
|
const MAX_FAILURES: usize = 10;
|
||||||
|
const MAX_RECONNECTION_ATTEMPTS: usize = 8;
|
||||||
|
const SOCKET_FAILURE_RESET: Duration = Duration::hours(2);
|
||||||
|
|
||||||
pub struct ChainSubscriber {
|
pub struct ChainSubscriber {
|
||||||
cancel: CancellationToken,
|
cancel: CancellationToken,
|
||||||
|
task_tracker: TaskTracker,
|
||||||
|
|
||||||
block_sender: UnboundedSender<BlockToProcess>,
|
block_sender: UnboundedSender<BlockToProcess>,
|
||||||
|
|
||||||
|
websocket_endpoint: Url,
|
||||||
websocket_client: WebSocketClient,
|
websocket_client: WebSocketClient,
|
||||||
websocket_driver: Option<WebSocketClientDriver>,
|
websocket_driver: Option<WebSocketClientDriver>,
|
||||||
}
|
}
|
||||||
@@ -26,6 +33,7 @@ impl ChainSubscriber {
|
|||||||
pub async fn new(
|
pub async fn new(
|
||||||
websocket_endpoint: &Url,
|
websocket_endpoint: &Url,
|
||||||
cancel: CancellationToken,
|
cancel: CancellationToken,
|
||||||
|
task_tracker: TaskTracker,
|
||||||
block_sender: UnboundedSender<BlockToProcess>,
|
block_sender: UnboundedSender<BlockToProcess>,
|
||||||
) -> Result<Self, ScraperError> {
|
) -> Result<Self, ScraperError> {
|
||||||
// sure, we could have just used websocket client entirely, but let's keep the logic for
|
// sure, we could have just used websocket client entirely, but let's keep the logic for
|
||||||
@@ -39,7 +47,9 @@ impl ChainSubscriber {
|
|||||||
|
|
||||||
Ok(ChainSubscriber {
|
Ok(ChainSubscriber {
|
||||||
cancel,
|
cancel,
|
||||||
|
task_tracker,
|
||||||
block_sender,
|
block_sender,
|
||||||
|
websocket_endpoint: websocket_endpoint.clone(),
|
||||||
websocket_client: client,
|
websocket_client: client,
|
||||||
websocket_driver: Some(driver),
|
websocket_driver: Some(driver),
|
||||||
})
|
})
|
||||||
@@ -53,8 +63,48 @@ impl ChainSubscriber {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run(&mut self) -> Result<(), ScraperError> {
|
async fn remake_connection(&mut self) -> Result<(), ScraperError> {
|
||||||
let _drop_guard = self.cancel.clone().drop_guard();
|
info!(
|
||||||
|
"attempting to reestablish connection to {}",
|
||||||
|
self.websocket_endpoint
|
||||||
|
);
|
||||||
|
|
||||||
|
let (client, driver) = WebSocketClient::new(self.websocket_endpoint.as_str())
|
||||||
|
.await
|
||||||
|
.map_err(|source| ScraperError::WebSocketConnectionFailure {
|
||||||
|
url: self.websocket_endpoint.to_string(),
|
||||||
|
source,
|
||||||
|
})?;
|
||||||
|
self.websocket_client = client;
|
||||||
|
self.websocket_driver = Some(driver);
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"managed to reestablish the websocket connection to {}",
|
||||||
|
self.websocket_endpoint
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the method exited due to the cancellation
|
||||||
|
async fn run_chain_subscription(&mut self) -> Result<bool, ScraperError> {
|
||||||
|
let Some(ws_driver) = self.websocket_driver.take() else {
|
||||||
|
error!("the websocket driver hasn't been created - we probably failed to establish the connection");
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
let driver_cancel = CancellationToken::new();
|
||||||
|
let _driver_guard = driver_cancel.clone().drop_guard();
|
||||||
|
|
||||||
|
// spawn the websocket driver task
|
||||||
|
let driver_handle = {
|
||||||
|
self.task_tracker.reopen();
|
||||||
|
let handle = self
|
||||||
|
.task_tracker
|
||||||
|
.spawn(run_websocket_driver(ws_driver, driver_cancel));
|
||||||
|
self.task_tracker.close();
|
||||||
|
handle
|
||||||
|
};
|
||||||
|
tokio::pin!(driver_handle);
|
||||||
|
|
||||||
info!("creating chain subscription");
|
info!("creating chain subscription");
|
||||||
let mut subs = self
|
let mut subs = self
|
||||||
@@ -70,12 +120,17 @@ impl ChainSubscriber {
|
|||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = self.cancel.cancelled() => {
|
_ = self.cancel.cancelled() => {
|
||||||
info!("received cancellation token");
|
info!("received cancellation token");
|
||||||
break
|
// note: `_driver_guard` will get dropped here thus causing cancellation of the driver task
|
||||||
|
return Ok(true)
|
||||||
|
}
|
||||||
|
_ = &mut driver_handle => {
|
||||||
|
error!("our websocket driver has finished execution");
|
||||||
|
return Ok(self.cancel.is_cancelled())
|
||||||
}
|
}
|
||||||
maybe_event = subs.next() => {
|
maybe_event = subs.next() => {
|
||||||
let Some(maybe_event) = maybe_event else {
|
let Some(maybe_event) = maybe_event else {
|
||||||
warn!("stopped receiving new events");
|
warn!("stopped receiving new events");
|
||||||
break;
|
return Ok(false)
|
||||||
};
|
};
|
||||||
match maybe_event {
|
match maybe_event {
|
||||||
Ok(event) => {
|
Ok(event) => {
|
||||||
@@ -92,38 +147,95 @@ impl ChainSubscriber {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if failures >= MAX_FAILURES {
|
if failures >= MAX_FAILURES {
|
||||||
// note: the drop_guard will get dropped and thus cause a shutdown
|
return Ok(false)
|
||||||
return Err(ScraperError::MaximumSubscriptionFailures);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn ws_driver(&mut self) -> WebSocketClientDriver {
|
async fn websocket_backoff(&mut self, failure_count: usize) -> bool {
|
||||||
#[allow(clippy::expect_used)]
|
const MINIMUM_WAIT_MS: u64 = 10_000;
|
||||||
self.websocket_driver
|
const INCREMENTAL_WAIT_MS: u64 = 30_000;
|
||||||
.take()
|
|
||||||
.expect("websocket driver has already been started!")
|
let backoff_duration_ms = MINIMUM_WAIT_MS + INCREMENTAL_WAIT_MS * failure_count as u64;
|
||||||
|
info!("going to wait {backoff_duration_ms} ms before re-attempting the reconnection");
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
_ = self.cancel.cancelled() => {
|
||||||
|
info!("received cancellation token");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ = tokio::time::sleep(std::time::Duration::from_millis(backoff_duration_ms)) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn run(&mut self) -> Result<(), ScraperError> {
|
||||||
|
let _drop_guard = self.cancel.clone().drop_guard();
|
||||||
|
let mut socket_failures = 0;
|
||||||
|
let mut last_failure = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if self.cancel.is_cancelled() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.run_chain_subscription().await {
|
||||||
|
Ok(cancelled) => {
|
||||||
|
if cancelled {
|
||||||
|
// we're in the middle of a shutdown
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
socket_failures += 1;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("failed to create chain subscription: {err}");
|
||||||
|
socket_failures += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
warn!("current socket failure count: {socket_failures}. the last failure was at {last_failure}");
|
||||||
|
|
||||||
|
let now = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
|
// if it's been a while since the last failure, reset the count
|
||||||
|
if now - last_failure > SOCKET_FAILURE_RESET {
|
||||||
|
warn!("resetting the failure count to 1");
|
||||||
|
socket_failures = 1;
|
||||||
|
}
|
||||||
|
last_failure = now;
|
||||||
|
|
||||||
|
if socket_failures >= MAX_RECONNECTION_ATTEMPTS {
|
||||||
|
error!("reached the maximum allowed failure count");
|
||||||
|
return Err(ScraperError::MaximumWebSocketFailures);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BACKOFF
|
||||||
|
let cancelled = self.websocket_backoff(socket_failures).await;
|
||||||
|
if cancelled {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(err) = self.remake_connection().await {
|
||||||
|
error!("failed to re-establish the websocket connection: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run_websocket_driver(driver: WebSocketClientDriver, cancel: CancellationToken) {
|
pub async fn run_websocket_driver(driver: WebSocketClientDriver, driver_cancel: CancellationToken) {
|
||||||
info!("starting websocket driver");
|
info!("starting websocket driver");
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = cancel.cancelled() => {
|
_ = driver_cancel.cancelled() => {
|
||||||
info!("received cancellation token")
|
info!("received cancellation token")
|
||||||
}
|
}
|
||||||
res = driver.run() => {
|
res = driver.run() => {
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => info!("our websocket driver has finished execution"),
|
Ok(_) => info!("our websocket driver has finished execution"),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// TODO: in the future just attempt to reconnect
|
error!("our websocket driver has errored out: {err}");
|
||||||
error!("our websocket driver has errored out: {err}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cancel.cancel()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ documentation = { workspace = true }
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bs58 = "0.4"
|
bs58 = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::net::Ipv6Addr;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
net::{IpAddr, Ipv4Addr},
|
net::{IpAddr, Ipv4Addr},
|
||||||
@@ -19,6 +20,12 @@ const TUN_WRITE_TIMEOUT_MS: u64 = 1000;
|
|||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum TunDeviceError {
|
pub enum TunDeviceError {
|
||||||
|
#[error("{0}")]
|
||||||
|
IO(#[from] std::io::Error),
|
||||||
|
|
||||||
|
#[error("{0}")]
|
||||||
|
TokioTun(#[from] tokio_tun::Error),
|
||||||
|
|
||||||
#[error("timeout writing to tun device, dropping packet")]
|
#[error("timeout writing to tun device, dropping packet")]
|
||||||
TunWriteTimeout,
|
TunWriteTimeout,
|
||||||
|
|
||||||
@@ -44,14 +51,18 @@ pub enum TunDeviceError {
|
|||||||
FailedToLockPeer,
|
FailedToLockPeer,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_tokio_tun_device(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> tokio_tun::Tun {
|
fn setup_tokio_tun_device(
|
||||||
|
name: &str,
|
||||||
|
address: Ipv4Addr,
|
||||||
|
netmask: Ipv4Addr,
|
||||||
|
) -> Result<tokio_tun::Tun, TunDeviceError> {
|
||||||
log::info!("Creating TUN device with: address={address}, netmask={netmask}");
|
log::info!("Creating TUN device with: address={address}, netmask={netmask}");
|
||||||
// Read MTU size from env variable NYM_MTU_SIZE, else default to 1420.
|
// Read MTU size from env variable NYM_MTU_SIZE, else default to 1420.
|
||||||
let mtu = std::env::var("NYM_MTU_SIZE")
|
let mtu = std::env::var("NYM_MTU_SIZE")
|
||||||
.map(|mtu| mtu.parse().expect("NYM_MTU_SIZE must be a valid integer"))
|
.map(|mtu| mtu.parse().expect("NYM_MTU_SIZE must be a valid integer"))
|
||||||
.unwrap_or(1420);
|
.unwrap_or(1420);
|
||||||
log::info!("Using MTU size: {mtu}");
|
log::info!("Using MTU size: {mtu}");
|
||||||
tokio_tun::Tun::builder()
|
Ok(tokio_tun::Tun::builder()
|
||||||
.name(name)
|
.name(name)
|
||||||
.tap(false)
|
.tap(false)
|
||||||
.packet_info(false)
|
.packet_info(false)
|
||||||
@@ -59,8 +70,7 @@ fn setup_tokio_tun_device(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> t
|
|||||||
.up()
|
.up()
|
||||||
.address(address)
|
.address(address)
|
||||||
.netmask(netmask)
|
.netmask(netmask)
|
||||||
.try_build()
|
.try_build()?)
|
||||||
.expect("Failed to setup tun device, do you have permission?")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TunDevice {
|
pub struct TunDevice {
|
||||||
@@ -103,16 +113,18 @@ pub struct NatInner {
|
|||||||
|
|
||||||
pub struct TunDeviceConfig {
|
pub struct TunDeviceConfig {
|
||||||
pub base_name: String,
|
pub base_name: String,
|
||||||
pub ip: Ipv4Addr,
|
pub ipv4: Ipv4Addr,
|
||||||
pub netmask: Ipv4Addr,
|
pub netmaskv4: Ipv4Addr,
|
||||||
|
pub ipv6: Ipv6Addr,
|
||||||
|
pub netmaskv6: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TunDevice {
|
impl TunDevice {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
routing_mode: RoutingMode,
|
routing_mode: RoutingMode,
|
||||||
config: TunDeviceConfig,
|
config: TunDeviceConfig,
|
||||||
) -> (Self, TunTaskTx, TunTaskResponseRx) {
|
) -> Result<(Self, TunTaskTx, TunTaskResponseRx), TunDeviceError> {
|
||||||
let tun = Self::new_device_only(config);
|
let tun = Self::new_device_only(config)?;
|
||||||
|
|
||||||
// Channels to communicate with the other tasks
|
// Channels to communicate with the other tasks
|
||||||
let (tun_task_tx, tun_task_rx) = tun_task_channel();
|
let (tun_task_tx, tun_task_rx) = tun_task_channel();
|
||||||
@@ -125,20 +137,32 @@ impl TunDevice {
|
|||||||
routing_mode,
|
routing_mode,
|
||||||
};
|
};
|
||||||
|
|
||||||
(tun_device, tun_task_tx, tun_task_response_rx)
|
Ok((tun_device, tun_task_tx, tun_task_response_rx))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_device_only(config: TunDeviceConfig) -> tokio_tun::Tun {
|
pub fn new_device_only(config: TunDeviceConfig) -> Result<tokio_tun::Tun, TunDeviceError> {
|
||||||
let TunDeviceConfig {
|
let TunDeviceConfig {
|
||||||
base_name,
|
base_name,
|
||||||
ip,
|
ipv4,
|
||||||
netmask,
|
netmaskv4,
|
||||||
|
ipv6,
|
||||||
|
netmaskv6,
|
||||||
} = config;
|
} = config;
|
||||||
let name = format!("{base_name}%d");
|
let name = format!("{base_name}%d");
|
||||||
|
|
||||||
let tun = setup_tokio_tun_device(&name, ip, netmask);
|
let tun = setup_tokio_tun_device(&name, ipv4, netmaskv4)?;
|
||||||
log::info!("Created TUN device: {}", tun.name());
|
log::info!("Created TUN device: {}", tun.name());
|
||||||
tun
|
std::process::Command::new("ip")
|
||||||
|
.args([
|
||||||
|
"-6",
|
||||||
|
"addr",
|
||||||
|
"add",
|
||||||
|
&format!("{}/{}", ipv6, netmaskv6),
|
||||||
|
"dev",
|
||||||
|
&tun.name(),
|
||||||
|
])
|
||||||
|
.output()?;
|
||||||
|
Ok(tun)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send outbound packets out on the wild internet
|
// Send outbound packets out on the wild internet
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ nym-validator-client = { path = "../../common/client-libs/validator-client" }
|
|||||||
nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" }
|
nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" }
|
||||||
nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" }
|
nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" }
|
||||||
nym-config = { path = "../../common/config" }
|
nym-config = { path = "../../common/config" }
|
||||||
nym-coconut-interface = { path = "../../common/coconut-interface" }
|
|
||||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] }
|
nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
Generated
+34
-23
@@ -116,6 +116,15 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
|
checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bs58"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.12.1"
|
version = "3.12.1"
|
||||||
@@ -178,6 +187,7 @@ dependencies = [
|
|||||||
name = "coconut-test"
|
name = "coconut-test"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bs58 0.4.0",
|
||||||
"cosmwasm-std",
|
"cosmwasm-std",
|
||||||
"cosmwasm-storage",
|
"cosmwasm-storage",
|
||||||
"cw-controllers",
|
"cw-controllers",
|
||||||
@@ -194,8 +204,10 @@ dependencies = [
|
|||||||
"nym-coconut-dkg-common",
|
"nym-coconut-dkg-common",
|
||||||
"nym-group-contract-common",
|
"nym-group-contract-common",
|
||||||
"nym-multisig-contract-common",
|
"nym-multisig-contract-common",
|
||||||
|
"rand_chacha 0.2.2",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
|
"subtle-encoding",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -819,7 +831,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.32",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1225,7 +1237,6 @@ dependencies = [
|
|||||||
"cw4-group",
|
"cw4-group",
|
||||||
"nym-coconut-dkg-common",
|
"nym-coconut-dkg-common",
|
||||||
"nym-group-contract-common",
|
"nym-group-contract-common",
|
||||||
"rusty-fork",
|
|
||||||
"semver",
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
@@ -1248,7 +1259,7 @@ dependencies = [
|
|||||||
name = "nym-contracts-common"
|
name = "nym-contracts-common"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bs58",
|
"bs58 0.5.0",
|
||||||
"cosmwasm-schema",
|
"cosmwasm-schema",
|
||||||
"cosmwasm-std",
|
"cosmwasm-std",
|
||||||
"schemars",
|
"schemars",
|
||||||
@@ -1260,7 +1271,7 @@ dependencies = [
|
|||||||
name = "nym-crypto"
|
name = "nym-crypto"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bs58",
|
"bs58 0.5.0",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"nym-pemstore",
|
"nym-pemstore",
|
||||||
"nym-sphinx-types",
|
"nym-sphinx-types",
|
||||||
@@ -1316,7 +1327,7 @@ dependencies = [
|
|||||||
name = "nym-mixnet-contract"
|
name = "nym-mixnet-contract"
|
||||||
version = "1.5.1"
|
version = "1.5.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bs58",
|
"bs58 0.4.0",
|
||||||
"cosmwasm-derive",
|
"cosmwasm-derive",
|
||||||
"cosmwasm-schema",
|
"cosmwasm-schema",
|
||||||
"cosmwasm-std",
|
"cosmwasm-std",
|
||||||
@@ -1339,7 +1350,7 @@ dependencies = [
|
|||||||
name = "nym-mixnet-contract-common"
|
name = "nym-mixnet-contract-common"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bs58",
|
"bs58 0.4.0",
|
||||||
"cosmwasm-schema",
|
"cosmwasm-schema",
|
||||||
"cosmwasm-std",
|
"cosmwasm-std",
|
||||||
"cw2",
|
"cw2",
|
||||||
@@ -1374,7 +1385,7 @@ name = "nym-name-service"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bs58",
|
"bs58 0.4.0",
|
||||||
"cosmwasm-schema",
|
"cosmwasm-schema",
|
||||||
"cosmwasm-std",
|
"cosmwasm-std",
|
||||||
"cw-controllers",
|
"cw-controllers",
|
||||||
@@ -1421,7 +1432,7 @@ name = "nym-service-provider-directory"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bs58",
|
"bs58 0.4.0",
|
||||||
"cosmwasm-schema",
|
"cosmwasm-schema",
|
||||||
"cosmwasm-std",
|
"cosmwasm-std",
|
||||||
"cw-controllers",
|
"cw-controllers",
|
||||||
@@ -1608,9 +1619,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.63"
|
version = "1.0.78"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
|
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -1646,9 +1657,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.30"
|
version = "1.0.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5907a1b7c277254a8b15170f6e7c97cfa60ee7872a3217663bb81151e48184bb"
|
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@@ -1888,9 +1899,9 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.190"
|
version = "1.0.196"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
|
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
@@ -1906,13 +1917,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.190"
|
version = "1.0.196"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
|
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.32",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1945,7 +1956,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.32",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2000,7 +2011,7 @@ dependencies = [
|
|||||||
"aes",
|
"aes",
|
||||||
"arrayref",
|
"arrayref",
|
||||||
"blake2",
|
"blake2",
|
||||||
"bs58",
|
"bs58 0.4.0",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"chacha",
|
"chacha",
|
||||||
"curve25519-dalek",
|
"curve25519-dalek",
|
||||||
@@ -2059,9 +2070,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.32"
|
version = "2.0.49"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2"
|
checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2098,7 +2109,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.32",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2447,5 +2458,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.32",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user