Compare commits

..

2 Commits

Author SHA1 Message Date
Tommy 8764ddbb6c wallet-name changed 2021-10-26 16:09:50 +03:00
Tommy 7338640f36 remove 2021-10-26 15:06:05 +03:00
416 changed files with 11310 additions and 94109 deletions
+1 -1
View File
@@ -36,5 +36,5 @@ Cargo.* @durch @futurechimp @jstuczyn @neacsu
# Explorer and wallet should probably get looked by the product team
/explorer/ @nymtech/product
/nym-wallet/ @nymtech/product
/tauri-wallet/ @nymtech/product
/wallet-web/ @nymtech/product
-32
View File
@@ -1,32 +0,0 @@
---
name: Feature request
about: Suggest an enhancement to the product
title: "[Feature Request]"
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is...
**Is your request a feature not related to an existing problem? A new feature.**
For example.
- Given I am using the nym wallet
- When I transfer nym tokens across the network
- Then I want to have an url link in the wallet which navigates outside the application to the nym-explorer
**Where does the feature fit in the Nym real estate?**
- Application / UI
**What is this solving?**
How will this improve the product...
**Is this an update to packages or libraries?**
If so, please list them. If not, please ignore this section.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
-43
View File
@@ -1,43 +0,0 @@
---
name: Report
about: To help identify and reproduce issues
title: "[Issue]"
labels: bug, bug-needs-triage, qa
assignees: tommyv1987
---
**Describe the issue**
A clear and concise description of what the issue is...
**Expected behaviour**
A clear and concise description of what you expected to happen...
**Stack Traces**
If there are stack traces or logs, please provide them here...
**Steps to Reproduce**
Steps to reproduce the behaviour, if you're familiar with BDD syntax, please write it in this style:
- Given I was doing X
- And I installed Y
- When I actioned Y
- Then I expect Z
*An example:*
- Given I was setting up a mix-node following the instructions in the docs
- And I successfully bonded my node via the the wallet
- When I went to start my mixnode
- Then I was presented with an error
**Screenshots**
If applicable, add screenshots to help explain your problem...
**Which area of Nym were you using?**
- UI: [e.g. Websites - network-explorer, nym-website]
- Application: [e.g Gateway, Client, Wallet]
- OS: [e.g. Ubuntu 20.x, MacOs Big Sur, Windows 10]
- Browser: [e.g Chrome (if applicable)]
- Version: [e.g. nym binary(0.11.0), browser(94.0)]
**Additional context**
Please provide any other information
+70 -36
View File
@@ -1,32 +1,16 @@
name: Continuous integration
on:
push:
paths-ignore:
- 'explorer/**'
pull_request:
paths-ignore:
- 'explorer/**'
on: [push, pull_request]
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from build_matrix_includes.json
- uses: actions/checkout@v2
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/build_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
build:
needs: matrix_prep
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.os == 'windows-latest' }}
strategy:
matrix:
rust: [stable, beta, nightly]
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
@@ -61,13 +45,6 @@ jobs:
command: fmt
args: --all -- --check
- uses: actions-rs/clippy-check@v1
name: Clippy checks
# if: matrix.os == 'ubuntu-latest' && matrix.rust == 'stable'
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
- name: Run clippy
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
@@ -82,21 +59,78 @@ jobs:
with:
command: clean
- name: Build all binaries with coconut enabled
# BUILD
- name: Build gateway with coconut feature
uses: actions-rs/cargo@v1
with:
command: build
args: --all --features=coconut
args: --bin nym-gateway --features=coconut
- name: Run all tests with coconut enabled
- name: Build native client with coconut feature
uses: actions-rs/cargo@v1
with:
command: build
args: --bin nym-client --features=coconut
- name: Build socks5 client with coconut feature
uses: actions-rs/cargo@v1
with:
command: build
args: --bin nym-socks5-client --features=coconut
- name: Build validator-api with coconut feature
uses: actions-rs/cargo@v1
with:
command: build
args: --bin nym-validator-api --features=coconut
# TEST
- name: Test gateway with coconut feature
uses: actions-rs/cargo@v1
with:
command: test
args: --all --features=coconut
args: --bin nym-gateway --features=coconut
- name: Run clippy with coconut enabled
- name: Test native client with coconut feature
uses: actions-rs/cargo@v1
with:
command: test
args: --bin nym-client --features=coconut
- name: Test socks5 client with coconut feature
uses: actions-rs/cargo@v1
with:
command: test
args: --bin nym-socks5-client --features=coconut
- name: Test validator-api with coconut feature
uses: actions-rs/cargo@v1
with:
command: test
args: --bin nym-validator-api --features=coconut
# CLIPPY
- name: Run clippy on gateway with coconut feature
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --features=coconut -- -D warnings
args: --bin nym-gateway --features=coconut -- -D warnings
- name: Run clippy on native client with coconut feature
uses: actions-rs/cargo@v1
with:
command: clippy
args: --bin nym-client --features=coconut -- -D warnings
- name: Run clippy on socks5 client with coconut feature
uses: actions-rs/cargo@v1
with:
command: clippy
args: --bin nym-socks5-client --features=coconut -- -D warnings
- name: Run clippy on validator-api with coconut feature
uses: actions-rs/cargo@v1
with:
command: clippy
args: --bin nym-validator-api --features=coconut -- -D warnings
@@ -1,50 +0,0 @@
[
{
"os":"ubuntu-latest",
"rust":"stable",
"runOnEvent":"always"
},
{
"os":"windows-latest",
"rust":"stable",
"runOnEvent":"pull_request"
},
{
"os":"macos-latest",
"rust":"stable",
"runOnEvent":"pull_request"
},
{
"os":"ubuntu-latest",
"rust":"beta",
"runOnEvent":"pull_request"
},
{
"os":"windows-latest",
"rust":"beta",
"runOnEvent":"pull_request"
},
{
"os":"macos-latest",
"rust":"beta",
"runOnEvent":"pull_request"
},
{
"os":"ubuntu-latest",
"rust":"nightly",
"runOnEvent":"pull_request"
},
{
"os":"windows-latest",
"rust":"nightly",
"runOnEvent":"pull_request"
},
{
"os":"macos-latest",
"rust":"nightly",
"runOnEvent":"pull_request"
}
]
+14
View File
@@ -0,0 +1,14 @@
name: Clippy check
on: push
jobs:
clippy_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: rustup component add clippy
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
@@ -1,14 +0,0 @@
[
{
"rust":"stable",
"runOnEvent":"always"
},
{
"rust":"beta",
"runOnEvent":"pull_request"
},
{
"rust":"nightly",
"runOnEvent":"pull_request"
}
]
@@ -1,58 +0,0 @@
name: ERC20 Bridge Contract
on: [ push, pull_request ]
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from build_matrix_includes.json
- uses: actions/checkout@v2
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/contract_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
erc20-bridge-contract:
needs: matrix_prep
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
# since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.rust == 'nightly' }}
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
- uses: actions-rs/cargo@v1
env:
RUSTFLAGS: '-C link-arg=-s'
with:
command: build
args: --manifest-path contracts/erc20-bridge/Cargo.toml --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path contracts/erc20-bridge/Cargo.toml
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path contracts/erc20-bridge/Cargo.toml -- --check
- uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/erc20-bridge/Cargo.toml -- -D warnings
+3 -23
View File
@@ -1,34 +1,16 @@
name: Mixnet Contract
on:
push:
paths-ignore:
- 'explorer/**'
pull_request:
paths-ignore:
- 'explorer/**'
on: [push, pull_request]
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from build_matrix_includes.json
- uses: actions/checkout@v2
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/contract_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
mixnet-contract:
# since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.rust == 'nightly' }}
needs: matrix_prep
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
matrix:
rust: [ stable, beta, nightly ]
steps:
- uses: actions/checkout@v2
@@ -41,8 +23,6 @@ jobs:
components: rustfmt, clippy
- uses: actions-rs/cargo@v1
env:
RUSTFLAGS: '-C link-arg=-s'
with:
command: build
args: --manifest-path contracts/mixnet/Cargo.toml --target wasm32-unknown-unknown
@@ -1,23 +0,0 @@
name: Linting for Network Explorer (eslint/prettier)
on:
pull_request:
paths:
- 'explorer/**'
defaults:
run:
working-directory: explorer
jobs:
build:
runs-on: custom-runner-linux
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: npm install
- name: Run ESLint
# GitHub should automatically annotate the PR
run: npm run lint
+2 -3
View File
@@ -11,7 +11,7 @@ defaults:
jobs:
build:
runs-on: custom-runner-linux
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install rsync
@@ -35,7 +35,7 @@ jobs:
SOURCE: "explorer/dist/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/network-explorer-${{ env.GITHUB_REF_SLUG }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/${{ env.GITHUB_REF_SLUG }}
EXCLUDE: "/dist/, /node_modules/"
- name: Keybase - Node Install
run: npm install
@@ -44,7 +44,6 @@ jobs:
env:
NYM_PROJECT_NAME: "Network Explorer"
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
NYM_CI_WWW_LOCATION: "network-explorer-${{ env.GITHUB_REF_SLUG }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
+16 -21
View File
@@ -1,18 +1,18 @@
name: Webdriverio tests for nym wallet
on:
push:
on:
push:
paths:
- "nym-wallet/**"
- 'tauri-wallet/**'
defaults:
run:
working-directory: nym-wallet
working-directory: tauri-wallet
jobs:
test:
name: wallet tests
runs-on: ubuntu-latest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -30,8 +30,8 @@ jobs:
- name: Install minimal stable
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
profile: minimal
toolchain: stable
- name: Node v16
uses: actions/setup-node@v1
@@ -39,32 +39,27 @@ jobs:
node-version: 16.x
- name: Install yarn for building application
run: yarn install
run: yarn install
- name: Build application
run: yarn run webpack:build & yarn run tauri:build
- name: Check binary exists
run: |
cd target/release/
(test -f nym-wallet && echo nym binary exists) || echo wallet does not exist
- name: Install dependencies
run: yarn install
working-directory: nym-wallet/webdriver
working-directory: tauri-wallet/webdriver
- name: Remove existing user datafile
uses: JesseTG/rm@v1.0.2
with:
path: nym-wallet/webdriver/common/data/user-data.json
path: tauri-wallet/webdriver/common/data/user-data.json
- name: Create user data json file
id: create-json
uses: jsdaniell/create-json@1.1.2
with:
name: "user-data.json"
json: ${{ secrets.WALLET_USERDATA }}
dir: "nym-wallet/webdriver/common/data/"
name: "user-data.json"
json: ${{ secrets.WALLET_USERDATA }}
dir: 'tauri-wallet/webdriver/common/data/'
- name: Install tauri-driver
uses: actions-rs/cargo@v1
@@ -73,5 +68,5 @@ jobs:
args: tauri-driver
- name: Launch tests
run: xvfb-run yarn test:runall
working-directory: nym-wallet/webdriver
run: xvfb-run yarn test:newuser
working-directory: tauri-wallet/webdriver
@@ -1,5 +1,5 @@
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
> :rocket: {{ env.NYM_PROJECT_NAME }} ➡️➡️➡️➡️➡️ **View output:** https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}/
> :rocket: {{ env.NYM_PROJECT_NAME }} ➡️➡️➡️➡️➡️ **View output:** https://{{ env.GITHUB_REF_SLUG }}.{{ env.NYM_CI_WWW_BASE }}/
> ✅ **SUCCESS**
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
@@ -1,12 +0,0 @@
name: Publish Tauri Wallet
on:
push:
tags:
- nym-wallet-*
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Run a one-line script
run: echo Hello, world!
+6 -13
View File
@@ -1,17 +1,10 @@
name: Generate TS types
on:
push:
paths-ignore:
- "explorer/**"
pull_request:
paths-ignore:
- "explorer/**"
on: push
jobs:
nym-wallet-types:
tauri-wallet-types:
runs-on: ubuntu-latest
if: ${{ github.event_name != 'pull_request' }}
steps:
- name: Prepare
run: sudo apt-get update && sudo apt-get install -y libpango1.0-dev libatk1.0-dev libgdk-pixbuf2.0-dev libsoup2.4-dev librust-gdk-dev libwebkit2gtk-4.0-dev
@@ -20,10 +13,10 @@ jobs:
with:
toolchain: stable
- name: Generate TS
run: cd nym-wallet/src-tauri && cargo test
run: cd tauri-wallet/src-tauri && cargo test
- uses: EndBug/add-and-commit@v7.2.1 # https://github.com/marketplace/actions/add-commit
with:
add: '["nym-wallet"]'
message: "[ci skip] Generate TS types"
add: '["tauri-wallet"]'
message: '[ci skip] Generate TS types'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+6 -10
View File
@@ -1,9 +1,6 @@
name: Wasm Client
on:
pull_request:
paths-ignore:
- 'explorer/**'
on: [push, pull_request]
jobs:
wasm:
@@ -19,11 +16,10 @@ jobs:
override: true
components: rustfmt, clippy
# token credentials (non-coconut) don't work for wasm right now
# - uses: actions-rs/cargo@v1
# with:
# command: build
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
@@ -45,4 +41,4 @@ jobs:
# - uses: actions-rs/cargo@v1
# with:
# command: clippy
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown -- -D warnings
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown -- -D warnings
+1 -6
View File
@@ -11,6 +11,7 @@ target
/.vscode/settings.json
validator/.vscode
sample-configs/validator-config.toml
.vscode
scripts/deploy_qa.sh
scripts/run_gate.sh
scripts/run_mix.sh
@@ -24,14 +25,8 @@ v6-topology.json
/explorer/downloads/topology.json
/explorer/public/downloads/mixmining.json
/explorer/public/downloads/topology.json
/nym-wallet/dist/*
/clients/validator/examples/nym-driver-example/current-contract.txt
validator-api/v4.json
validator-api/v6.json
**/node_modules
validator-api/keypair
contracts/mixnet/code_id
contracts/mixnet/Justfile
contracts/mixnet/Makefile
validator-config
*.patch
Generated
+122 -626
View File
File diff suppressed because it is too large Load Diff
+1 -4
View File
@@ -4,7 +4,6 @@
[profile.release]
panic = "abort"
opt-level = "s"
overflow-checks = true
[profile.dev]
panic = "abort"
@@ -25,12 +24,10 @@ members = [
"common/config",
"common/credentials",
"common/crypto",
"common/erc20-bridge-contract",
"common/mixnet-contract",
"common/mixnode-common",
"common/network-defaults",
"common/nonexhaustive-delayqueue",
"common/nymcoconut",
"common/nymsphinx",
"common/nymsphinx/acknowledgements",
"common/nymsphinx/addressing",
@@ -64,4 +61,4 @@ default-members = [
"validator-api",
]
exclude = ["explorer", "contracts", "tokenomics-py"]
exclude = ["explorer", "contracts"]
+2 -39
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: Apache-2.0
## The Nym Privacy Platform
This repository contains the Nym mixnet.
The platform is composed of multiple Rust crates. Top-level executable binary crates include:
* nym-mixnode - shuffles [Sphinx](https://github.com/nymtech/sphinx) packets together to provide privacy against network-level attackers.
@@ -31,45 +33,6 @@ There's a `.env.sample-dev` file provided which you can rename to `.env` if you
You can chat to us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes places in the **#dev** channel. Node operators should be in the **#node-operators** channel.
### Rewards
Node, node operator and delegator rewards are determined according to the principles laid out in the section 6 of [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf). Below is a TLDR of the variables and formulas involved in calculating the epoch rewards. Initial reward pool is set to 250 million Nym, making the circulating supply 750 million Nym.
|Symbol|Definition|
|---|---|
|<img src="https://render.githubusercontent.com/render/math?math=R">|global share of rewards available, starts at 2% of the reward pool.
|<img src="https://render.githubusercontent.com/render/math?math=R_{i}">|node reward for mixnode `i`.
|<img src="https://render.githubusercontent.com/render/math?math=\sigma_{i}">|ratio of total node stake (node bond + all delegations) to the token circulating supply.
|<img src="https://render.githubusercontent.com/render/math?math=\lambda_{i}">|ratio of stake operator has plaged to their node to the token circulating supply.
|<img src="https://render.githubusercontent.com/render/math?math=\omega_{i}">|fraction of total effort undertaken by node `i`, set to `1/k` in testnet Milhon.
|<img src="https://render.githubusercontent.com/render/math?math=k">|number of nodes stakeholders are incentivised to create, set by the validators, a matter of governance. Currently determined by the `active set` size, and set to 5000 in testnet Milhon.
|<img src="https://render.githubusercontent.com/render/math?math=\alpha">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitivness gets for a Sybil attacker.
|<img src="https://render.githubusercontent.com/render/math?math=PM_{i}">|declared profit margin of operator `i`, defaults to 10% in testnet Milhon.
|<img src="https://render.githubusercontent.com/render/math?math=PF_{i}">|uptime of node `i`, scaled to 0 - 1, for the rewarding epoch
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}">|cost of operating node `i` for the duration of the rewarding eopoch, set to 40 Nym for testnet Milhon.
Node reward for node `i` is determined as:
<img src="https://render.githubusercontent.com/render/math?math=R_{i}=PF_{i} \cdot R \cdot (\sigma^'_{i} \cdot \omega_{i} \cdot k %2b \alpha \cdot \lambda^'_{i} \cdot \sigma^'_{i} \cdot k)/(1 %2b \alpha)">
where:
<img src="https://render.githubusercontent.com/render/math?math=\sigma^'_{i} = min\{\sigma_{i}, 1/k\}">
and
<img src="https://render.githubusercontent.com/render/math?math=\lambda^'_{i} = min\{\lambda_{i}, 1/k\}">
Operator of node `i` is credited with the following amount:
<img src="https://render.githubusercontent.com/render/math?math=min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}">
Delegate with stake `s` recieves:
<img src="https://render.githubusercontent.com/render/math?math=max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}">
where `s'` is stake `s` scaled over total token circulating supply.
### Licensing and copyright information
This program is available as open source under the terms of the Apache 2.0 license. However, some elements are being licensed under CC0-1.0 and MIT. For accurate information, please check individual files.
-3
View File
@@ -30,6 +30,3 @@ validator-client = { path = "../../common/client-libs/validator-client" }
[dev-dependencies]
tempfile = "3.1.0"
[features]
coconut = []
@@ -257,7 +257,7 @@ impl TopologyRefresher {
Ok(mixes) => mixes,
};
let gateways = match self.validator_client.get_cached_gateways().await {
let gateways = match self.validator_client.get_cached_active_gateways().await {
Err(err) => {
error!("failed to get network gateways - {}", err);
return None;
+1 -65
View File
@@ -22,10 +22,7 @@ const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20)
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50);
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
// bandwidth bridging protocol, we can come back to a smaller timeout value
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
pub fn missing_string_value() -> String {
MISSING_VALUE.to_string()
@@ -103,17 +100,6 @@ impl<T: NymConfig> Config<T> {
self::Client::<T>::default_reply_encryption_key_store_path(&id);
}
#[cfg(not(feature = "coconut"))]
if self
.client
.backup_bandwidth_token_keys_dir
.as_os_str()
.is_empty()
{
self.client.backup_bandwidth_token_keys_dir =
self::Client::<T>::default_backup_bandwidth_token_keys_dir(&id);
}
self.client.id = id;
}
@@ -125,16 +111,6 @@ impl<T: NymConfig> Config<T> {
self.client.gateway_listener = gateway_listener.into();
}
#[cfg(not(feature = "coconut"))]
pub fn with_eth_private_key<S: Into<String>>(&mut self, eth_private_key: S) {
self.client.eth_private_key = eth_private_key.into();
}
#[cfg(not(feature = "coconut"))]
pub fn with_eth_endpoint<S: Into<String>>(&mut self, eth_endpoint: S) {
self.client.eth_endpoint = eth_endpoint.into();
}
pub fn set_custom_validator_apis(&mut self, validator_api_urls: Vec<Url>) {
self.client.validator_api_urls = validator_api_urls;
}
@@ -197,21 +173,6 @@ impl<T: NymConfig> Config<T> {
self.client.gateway_listener.clone()
}
#[cfg(not(feature = "coconut"))]
pub fn get_backup_bandwidth_token_keys_dir(&self) -> PathBuf {
self.client.backup_bandwidth_token_keys_dir.clone()
}
#[cfg(not(feature = "coconut"))]
pub fn get_eth_endpoint(&self) -> String {
self.client.eth_endpoint.clone()
}
#[cfg(not(feature = "coconut"))]
pub fn get_eth_private_key(&self) -> String {
self.client.eth_private_key.clone()
}
// Debug getters
pub fn get_average_packet_delay(&self) -> Duration {
self.debug.average_packet_delay
@@ -307,20 +268,6 @@ pub struct Client<T> {
/// Address of the gateway listener to which all client requests should be sent.
gateway_listener: String,
/// Path to directory containing public/private keys used for bandwidth token purchase.
/// Those are saved in case of emergency, to be able to reclaim bandwidth tokens.
/// The public key is the name of the file, while the private key is the content.
#[cfg(not(feature = "coconut"))]
backup_bandwidth_token_keys_dir: PathBuf,
/// Ethereum private key.
#[cfg(not(feature = "coconut"))]
eth_private_key: String,
/// Address to an Ethereum full node.
#[cfg(not(feature = "coconut"))]
eth_endpoint: String,
/// nym_home_directory specifies absolute path to the home nym Clients directory.
/// It is expected to use default value and hence .toml file should not redefine this field.
nym_root_directory: PathBuf,
@@ -345,12 +292,6 @@ impl<T: NymConfig> Default for Client<T> {
reply_encryption_key_store_path: Default::default(),
gateway_id: "".to_string(),
gateway_listener: "".to_string(),
#[cfg(not(feature = "coconut"))]
backup_bandwidth_token_keys_dir: Default::default(),
#[cfg(not(feature = "coconut"))]
eth_private_key: "".to_string(),
#[cfg(not(feature = "coconut"))]
eth_endpoint: "".to_string(),
nym_root_directory: T::default_root_directory(),
super_struct: Default::default(),
}
@@ -385,11 +326,6 @@ impl<T: NymConfig> Client<T> {
fn default_reply_encryption_key_store_path(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("reply_key_store")
}
#[cfg(not(feature = "coconut"))]
fn default_backup_bandwidth_token_keys_dir(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("backup_bandwidth_token_keys")
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
+1 -3
View File
@@ -3,7 +3,6 @@ name = "nym-client"
version = "0.11.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2018"
rust-version = "1.56"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -24,7 +23,7 @@ dirs = "3.0" # for determining default store directories in config
dotenv = "0.15.0" # for obtaining environmental variables (only used for RUST_LOG for time being)
log = "0.4" # 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 = { version = "1.0.104", features = ["derive"] } # for config serialization/deserialization
sled = "0.34" # for storage of replySURB decryption keys
tokio = { version = "1.4", features = ["rt-multi-thread", "net", "signal"] } # async runtime
@@ -44,7 +43,6 @@ topology = { path = "../../common/topology" }
websocket-requests = { path = "websocket-requests" }
validator-client = { path = "../../common/client-libs/validator-client" }
version-checker = { path = "../../common/version-checker" }
network-defaults = { path = "../../common/network-defaults" }
[features]
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
@@ -42,17 +42,6 @@ public_encryption_key_file = '{{ client.public_encryption_key_file }}'
# sent but not received back.
reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
# Path to directory containing public/private keys used for bandwidth token purchase.
# Those are saved in case of emergency, to be able to reclaim bandwidth tokens.
# The public key is the name of the file, while the private key is the content.
backup_bandwidth_token_keys_dir = '{{ client.backup_bandwidth_token_keys_dir }}'
# Ethereum private key.
eth_private_key = '{{ client.eth_private_key }}'
# Addess to an Ethereum full node.
eth_endpoint = '{{ client.eth_endpoint }}'
##### additional client config options #####
# ID of the gateway from which the client should be fetching messages.
+36 -16
View File
@@ -1,6 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, SocketType};
use crate::websocket;
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender,
@@ -22,7 +24,6 @@ use client_core::client::topology_control::{
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender,
@@ -34,8 +35,10 @@ use nymsphinx::anonymous_replies::ReplySurb;
use nymsphinx::receiver::ReconstructedMessage;
use tokio::runtime::Runtime;
use crate::client::config::{Config, SocketType};
use crate::websocket;
#[cfg(feature = "coconut")]
use coconut_interface::Credential;
#[cfg(feature = "coconut")]
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
pub(crate) mod config;
@@ -165,6 +168,31 @@ impl NymClient {
.start(self.runtime.handle())
}
#[cfg(feature = "coconut")]
async fn prepare_coconut_credential(&self) -> Credential {
let verification_key = obtain_aggregate_verification_key(
&self.config.get_base().get_validator_api_endpoints(),
)
.await
.expect("could not obtain aggregate verification key of validators");
let bandwidth_credential = credentials::bandwidth::obtain_signature(
&self.key_manager.identity_keypair().public_key().to_bytes(),
&self.config.get_base().get_validator_api_endpoints(),
)
.await
.expect("could not obtain bandwidth credential");
// the above would presumably be loaded from a file
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
prepare_for_spending(
&self.key_manager.identity_keypair().public_key().to_bytes(),
&bandwidth_credential,
&verification_key,
)
.expect("could not prepare out bandwidth credential for spending")
}
fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
@@ -184,17 +212,7 @@ impl NymClient {
self.runtime.block_on(async {
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
let coconut_credential = self.prepare_coconut_credential().await;
let mut gateway_client = GatewayClient::new(
gateway_address,
@@ -204,11 +222,13 @@ impl NymClient {
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
gateway_client
.authenticate_and_start()
.authenticate_and_start(
#[cfg(feature = "coconut")]
Some(coconut_credential),
)
.await
.expect("could not authenticate and start up the gateway connection");
+7 -62
View File
@@ -1,23 +1,15 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use crate::commands::override_config;
use clap::{App, Arg, ArgMatches};
use client_core::client::key_manager::KeyManager;
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
#[cfg(feature = "coconut")]
use coconut_interface::{hash_to_scalar, Credential, Parameters};
use config::NymConfig;
#[cfg(feature = "coconut")]
use credentials::coconut::bandwidth::{
obtain_signature, prepare_for_spending, BandwidthVoucherAttributes, TOTAL_ATTRIBUTES,
};
#[cfg(feature = "coconut")]
use credentials::obtain_aggregate_verification_key;
use crypto::asymmetric::{encryption, identity};
use gateway_client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys;
#[cfg(feature = "coconut")]
use network_defaults::BANDWIDTH_VALUE;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use rand::rngs::OsRng;
@@ -29,11 +21,8 @@ use std::time::Duration;
use topology::{filter::VersionFilterable, gateway};
use url::Url;
use crate::client::config::Config;
use crate::commands::override_config;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("init")
App::new("init")
.about("Initialise a Nym client. Do this first!")
.arg(Arg::with_name("id")
.long("id")
@@ -47,9 +36,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.takes_value(true)
)
.arg(Arg::with_name("validators")
.long("validators")
.help("Comma separated list of rest endpoints of the validators")
.takes_value(true),
.long("validators")
.help("Comma separated list of rest endpoints of the validators")
.takes_value(true),
)
.arg(Arg::with_name("disable-socket")
.long("disable-socket")
@@ -65,51 +54,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.long("fastmode")
.hidden(true) // this will prevent this flag from being displayed in `--help`
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
);
#[cfg(not(feature = "coconut"))]
let app = app
.arg(Arg::with_name("eth_endpoint")
.long("eth_endpoint")
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
.takes_value(true)
.required(true))
.arg(Arg::with_name("eth_private_key")
.long("eth_private_key")
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
.takes_value(true)
.required(true));
app
}
// this behaviour should definitely be changed, we shouldn't
// need to get bandwidth credential for registration
#[cfg(feature = "coconut")]
async fn _prepare_temporary_credential(validators: &[Url], raw_identity: &[u8]) -> Credential {
let verification_key = obtain_aggregate_verification_key(validators)
.await
.expect("could not obtain aggregate verification key of validators");
let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
let bandwidth_credential_attributes = BandwidthVoucherAttributes {
serial_number: params.random_scalar(),
binding_number: params.random_scalar(),
voucher_value: hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes()),
voucher_info: hash_to_scalar(String::from("BandwidthVoucher").as_bytes()),
};
let bandwidth_credential =
obtain_signature(&params, &bandwidth_credential_attributes, validators)
.await
.expect("could not obtain bandwidth credential");
prepare_for_spending(
raw_identity,
&bandwidth_credential,
&bandwidth_credential_attributes,
&verification_key,
)
.expect("could not prepare out bandwidth credential for spending")
)
}
async fn register_with_gateway(
-9
View File
@@ -43,14 +43,5 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi
config = config.with_port(port.unwrap());
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_endpoint) = matches.value_of("eth_endpoint") {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_private_key) = matches.value_of("eth_private_key") {
config.get_base_mut().with_eth_private_key(eth_private_key);
}
config
}
+2 -14
View File
@@ -10,7 +10,7 @@ use log::*;
use version_checker::is_minor_version_compatible;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("run")
App::new("run")
.about("Run the Nym client with provided configuration client optionally overriding set parameters")
.arg(Arg::with_name("id")
.long("id")
@@ -38,19 +38,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.long("port")
.help("Port for the socket (if applicable) to listen on")
.takes_value(true)
);
#[cfg(not(feature = "coconut"))]
let app = app
.arg(Arg::with_name("eth_endpoint")
.long("eth_endpoint")
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
.takes_value(true))
.arg(Arg::with_name("eth_private_key")
.long("eth_private_key")
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
.takes_value(true));
app
)
}
// this only checks compatibility between config the binary. It does not take into consideration
+3 -3
View File
@@ -1,8 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) use handler::Handler;
pub(crate) use listener::Listener;
pub(crate) mod handler;
pub(crate) mod listener;
pub(crate) use handler::Handler;
pub(crate) use listener::Listener;
+1 -3
View File
@@ -3,7 +3,6 @@ name = "nym-socks5-client"
version = "0.11.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2018"
rust-version = "1.56"
[lib]
name = "nym_socks5"
@@ -32,14 +31,13 @@ crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
gateway-requests = { path = "../../gateway/gateway-requests" }
nymsphinx = { path = "../../common/nymsphinx" }
ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
ordered-buffer = {path = "../../common/socks5/ordered-buffer"}
socks5-requests = { path = "../../common/socks5/requests" }
topology = { path = "../../common/topology" }
pemstore = { path = "../../common/pemstore" }
proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
validator-client = { path = "../../common/client-libs/validator-client" }
version-checker = { path = "../../common/version-checker" }
network-defaults = { path = "../../common/network-defaults" }
[features]
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
@@ -42,17 +42,6 @@ public_encryption_key_file = '{{ client.public_encryption_key_file }}'
# sent but not received back.
reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
# Path to directory containing public/private keys used for bandwidth token purchase.
# Those are saved in case of emergency, to be able to reclaim bandwidth tokens.
# The public key is the name of the file, while the private key is the content.
backup_bandwidth_token_keys_dir = '{{ client.backup_bandwidth_token_keys_dir }}'
# Ethereum private key.
eth_private_key = '{{ client.eth_private_key }}'
# Addess to an Ethereum full node.
eth_endpoint = '{{ client.eth_endpoint }}'
##### additional client config options #####
# ID of the gateway from which the client should be fetching messages.
+39 -19
View File
@@ -1,6 +1,11 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer,
};
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender,
@@ -20,7 +25,6 @@ use client_core::client::topology_control::{
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender,
@@ -30,11 +34,10 @@ use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use tokio::runtime::Runtime;
use crate::client::config::Config;
use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer,
};
#[cfg(feature = "coconut")]
use coconut_interface::Credential;
#[cfg(feature = "coconut")]
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
pub(crate) mod config;
@@ -153,6 +156,31 @@ impl NymClient {
.start(self.runtime.handle())
}
#[cfg(feature = "coconut")]
async fn prepare_coconut_credential(&self) -> Credential {
let verification_key = obtain_aggregate_verification_key(
&self.config.get_base().get_validator_api_endpoints(),
)
.await
.expect("could not obtain aggregate verification key of validators");
let bandwidth_credential = credentials::bandwidth::obtain_signature(
&self.key_manager.identity_keypair().public_key().to_bytes(),
&self.config.get_base().get_validator_api_endpoints(),
)
.await
.expect("could not obtain bandwidth credential");
// the above would presumably be loaded from a file
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
prepare_for_spending(
&self.key_manager.identity_keypair().public_key().to_bytes(),
&bandwidth_credential,
&verification_key,
)
.expect("could not prepare out bandwidth credential for spending")
}
fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
@@ -172,17 +200,7 @@ impl NymClient {
self.runtime.block_on(async {
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
let coconut_credential = self.prepare_coconut_credential().await;
let mut gateway_client = GatewayClient::new(
gateway_address,
@@ -192,11 +210,13 @@ impl NymClient {
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
gateway_client
.authenticate_and_start()
.authenticate_and_start(
#[cfg(feature = "coconut")]
Some(coconut_credential),
)
.await
.expect("could not authenticate and start up the gateway connection");
+7 -62
View File
@@ -1,23 +1,15 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use crate::commands::override_config;
use clap::{App, Arg, ArgMatches};
use client_core::client::key_manager::KeyManager;
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
#[cfg(feature = "coconut")]
use coconut_interface::{hash_to_scalar, Credential, Parameters};
use config::NymConfig;
#[cfg(feature = "coconut")]
use credentials::coconut::bandwidth::{
obtain_signature, prepare_for_spending, BandwidthVoucherAttributes, TOTAL_ATTRIBUTES,
};
#[cfg(feature = "coconut")]
use credentials::obtain_aggregate_verification_key;
use crypto::asymmetric::{encryption, identity};
use gateway_client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys;
#[cfg(feature = "coconut")]
use network_defaults::BANDWIDTH_VALUE;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use rand::{prelude::SliceRandom, rngs::OsRng, thread_rng};
@@ -27,11 +19,8 @@ use std::time::Duration;
use topology::{filter::VersionFilterable, gateway};
use url::Url;
use crate::client::config::Config;
use crate::commands::override_config;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("init")
App::new("init")
.about("Initialise a Nym client. Do this first!")
.arg(Arg::with_name("id")
.long("id")
@@ -51,9 +40,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.takes_value(true)
)
.arg(Arg::with_name("validators")
.long("validators")
.help("Comma separated list of rest endpoints of the validators")
.takes_value(true),
.long("validators")
.help("Comma separated list of rest endpoints of the validators")
.takes_value(true),
)
.arg(Arg::with_name("port")
.short("p")
@@ -65,51 +54,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.long("fastmode")
.hidden(true) // this will prevent this flag from being displayed in `--help`
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
);
#[cfg(not(feature = "coconut"))]
let app = app
.arg(Arg::with_name("eth_endpoint")
.long("eth_endpoint")
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
.takes_value(true)
.required(true))
.arg(Arg::with_name("eth_private_key")
.long("eth_private_key")
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
.takes_value(true)
.required(true));
app
}
// this behaviour should definitely be changed, we shouldn't
// need to get bandwidth credential for registration
#[cfg(feature = "coconut")]
async fn _prepare_temporary_credential(validators: &[Url], raw_identity: &[u8]) -> Credential {
let verification_key = obtain_aggregate_verification_key(validators)
.await
.expect("could not obtain aggregate verification key of validators");
let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
let bandwidth_credential_attributes = BandwidthVoucherAttributes {
serial_number: params.random_scalar(),
binding_number: params.random_scalar(),
voucher_value: hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes()),
voucher_info: hash_to_scalar("BandwidthVoucher"),
};
let bandwidth_credential =
obtain_signature(&params, &bandwidth_credential_attributes, validators)
.await
.expect("could not obtain bandwidth credential");
prepare_for_spending(
raw_identity,
&bandwidth_credential,
&bandwidth_credential_attributes,
&verification_key,
)
.expect("could not prepare out bandwidth credential for spending")
)
}
async fn register_with_gateway(
-9
View File
@@ -39,14 +39,5 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi
config = config.with_port(port.unwrap());
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_endpoint) = matches.value_of("eth_endpoint") {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_private_key) = matches.value_of("eth_private_key") {
config.get_base_mut().with_eth_private_key(eth_private_key);
}
config
}
+2 -14
View File
@@ -10,7 +10,7 @@ use log::*;
use version_checker::is_minor_version_compatible;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("run")
App::new("run")
.about("Run the Nym client with provided configuration client optionally overriding set parameters")
.arg(Arg::with_name("id")
.long("id")
@@ -44,19 +44,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.long("port")
.help("Port for the socket to listen on")
.takes_value(true)
);
#[cfg(not(feature = "coconut"))]
let app = app
.arg(Arg::with_name("eth_endpoint")
.long("eth_endpoint")
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
.takes_value(true))
.arg(Arg::with_name("eth_private_key")
.long("eth_private_key")
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
.takes_value(true));
app
)
}
// this only checks compatibility between config the binary. It does not take into consideration
+17 -29
View File
@@ -3,24 +3,21 @@
windows_subsystem = "windows"
)]
use std::sync::Arc;
use tokio::sync::RwLock;
use url::Url;
use coconut_interface::{
self, hash_to_scalar, Attribute, Credential, Parameters, Signature, Theta, VerificationKey,
};
use credentials::{obtain_aggregate_signature, obtain_aggregate_verification_key};
use std::sync::Arc;
use tokio::sync::RwLock;
use url::Url;
struct State {
signatures: Vec<Signature>,
n_attributes: u32,
params: Parameters,
serial_number: Attribute,
binding_number: Attribute,
voucher_value: Attribute,
voucher_info: Attribute,
public_attributes_bytes: Vec<Vec<u8>>,
public_attributes: Vec<Attribute>,
private_attributes: Vec<Attribute>,
aggregated_verification_key: Option<VerificationKey>,
}
@@ -40,10 +37,9 @@ impl State {
signatures: Vec::new(),
n_attributes,
params,
serial_number: private_attributes[0],
binding_number: private_attributes[1],
voucher_value: public_attributes[0],
voucher_info: public_attributes[1],
public_attributes_bytes,
public_attributes,
private_attributes,
aggregated_verification_key: None,
}
}
@@ -67,8 +63,8 @@ async fn randomise_credential(
) -> Result<Vec<Signature>, String> {
let mut state = state.write().await;
let signature = state.signatures.remove(idx);
let (new_signature, _) = signature.randomise(&state.params);
state.signatures.insert(idx, new_signature);
let new = signature.randomise(&state.params);
state.signatures.insert(idx, new);
Ok(state.signatures.clone())
}
@@ -121,15 +117,14 @@ async fn prove_credential(
let state = state.read().await;
if let Some(signature) = state.signatures.get(idx) {
match coconut_interface::prove_bandwidth_credential(
match coconut_interface::prove_credential(
&state.params,
&verification_key,
signature,
state.serial_number,
state.binding_number,
&state.private_attributes,
) {
Ok(theta) => Ok(theta),
Err(e) => Err(format!("{:?}", e)),
Err(e) => Err(format!("{}", e)),
}
} else {
Err("Got invalid Signature idx".to_string())
@@ -149,15 +144,10 @@ async fn verify_credential(
let state = state.read().await;
let public_attributes_bytes = vec![
state.voucher_value.to_bytes().to_vec(),
state.voucher_info.to_bytes().to_vec(),
];
let credential = Credential::new(
state.n_attributes,
theta,
public_attributes_bytes,
state.public_attributes_bytes.clone(),
state
.signatures
.get(idx)
@@ -174,13 +164,11 @@ async fn get_credential(
) -> Result<Vec<Signature>, String> {
let guard = state.read().await;
let parsed_urls = parse_url_validators(&validator_urls)?;
let public_attributes = vec![guard.voucher_value, guard.voucher_info];
let private_attributes = vec![guard.serial_number, guard.binding_number];
let signature = obtain_aggregate_signature(
&guard.params,
&public_attributes,
&private_attributes,
&guard.public_attributes,
&guard.private_attributes,
&parsed_urls,
)
.await
-1
View File
@@ -7,7 +7,6 @@ keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
repository = "https://github.com/nymtech/nym"
description = "A webassembly client which can be used to interact with the the Nym privacy platform. Wasm is used for Sphinx packet generation."
rust-version = "1.56"
[lib]
crate-type = ["cdylib", "rlib"]
+37 -11
View File
@@ -3,7 +3,6 @@
use crypto::asymmetric::{encryption, identity};
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
use gateway_client::GatewayClient;
use nymsphinx::acknowledgements::AckKey;
use nymsphinx::addressing::clients::Recipient;
@@ -18,6 +17,11 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use wasm_utils::{console_log, console_warn};
#[cfg(feature = "coconut")]
use coconut_interface::Credential;
#[cfg(feature = "coconut")]
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
pub(crate) mod received_processor;
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
@@ -99,15 +103,35 @@ impl NymClient {
self.self_recipient().to_string()
}
#[cfg(feature = "coconut")]
async fn prepare_coconut_credential(validators: &[Url], identity_bytes: &[u8]) -> Credential {
let verification_key = obtain_aggregate_verification_key(validators)
.await
.expect("could not obtain aggregate verification key of validators");
let bandwidth_credential =
credentials::bandwidth::obtain_signature(identity_bytes, validators)
.await
.expect("could not obtain bandwidth credential");
// the above would presumably be loaded from a file
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
prepare_for_spending(identity_bytes, &bandwidth_credential, &verification_key)
.expect("could not prepare out bandwidth credential for spending")
}
// Right now it's impossible to have async exported functions to take `&self` rather than self
pub async fn initial_setup(self) -> Self {
#[cfg(feature = "coconut")]
let bandwidth_controller = Some(BandwidthController::new(
vec![self.validator_server.clone()],
*self.identity.public_key(),
));
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = None;
let coconut_credential = {
let validator_server = self.validator_server.clone();
let identity_public_key = self.identity.public_key().clone();
Self::prepare_coconut_credential(
&vec![validator_server],
&identity_public_key.to_bytes(),
)
.await
};
let mut client = self.get_and_update_topology().await;
let gateway = client.choose_gateway();
@@ -123,11 +147,13 @@ impl NymClient {
mixnet_messages_sender,
ack_sender,
DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
bandwidth_controller,
);
gateway_client
.authenticate_and_start()
.authenticate_and_start(
#[cfg(feature = "coconut")]
Some(coconut_credential),
)
.await
.expect("could not authenticate and start up the gateway connection");
@@ -249,11 +275,11 @@ impl NymClient {
let validator_client = validator_client::ApiClient::new(self.validator_server.clone());
let mixnodes = match validator_client.get_cached_active_mixnodes().await {
Err(err) => panic!("{:?}", err),
Err(err) => panic!("{}", err),
Ok(mixes) => mixes,
};
let gateways = match validator_client.get_cached_gateways().await {
let gateways = match validator_client.get_cached_active_gateways().await {
Err(err) => panic!("{}", err),
Ok(gateways) => gateways,
};
@@ -10,19 +10,14 @@ edition = "2018"
# TODO: (for this and other crates), similarly to 'tokio', import only required "futures" modules rather than
# the entire crate
futures = "0.3"
json = "0.12.4"
log = "0.4"
thiserror = "1.0"
url = "2.2"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
# internal
credentials = { path = "../../credentials" }
crypto = { path = "../../crypto" }
gateway-requests = { path = "../../../gateway/gateway-requests" }
nymsphinx = { path = "../../nymsphinx" }
coconut-interface = { path = "../../coconut-interface", optional = true }
network-defaults = { path = "../../network-defaults" }
[dependencies.tungstenite]
version = "0.13"
@@ -36,12 +31,6 @@ features = ["macros", "rt", "net", "sync", "time"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
version = "0.14"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.secp256k1]
version = "0.20.3"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.web3]
version = "0.17.0"
# wasm-only dependencies
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
version = "0.2"
@@ -1,247 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(feature = "coconut")]
use credentials::coconut::{
bandwidth::{
obtain_signature, prepare_for_spending, BandwidthVoucherAttributes, TOTAL_ATTRIBUTES,
},
utils::obtain_aggregate_verification_key,
};
#[cfg(not(feature = "coconut"))]
use credentials::token::bandwidth::TokenCredential;
#[cfg(not(feature = "coconut"))]
use crypto::asymmetric::identity;
use crypto::asymmetric::identity::PublicKey;
use network_defaults::BANDWIDTH_VALUE;
#[cfg(not(feature = "coconut"))]
use network_defaults::{
eth_contract::ETH_JSON_ABI, ETH_BURN_FUNCTION_NAME, ETH_CONTRACT_ADDRESS, ETH_MIN_BLOCK_DEPTH,
TOKENS_TO_BURN,
};
#[cfg(not(feature = "coconut"))]
use rand::rngs::OsRng;
#[cfg(not(feature = "coconut"))]
use secp256k1::SecretKey;
#[cfg(not(feature = "coconut"))]
use std::io::Write;
#[cfg(not(feature = "coconut"))]
use std::str::FromStr;
#[cfg(not(feature = "coconut"))]
use web3::{
contract::{Contract, Options},
transports::Http,
types::{Address, Bytes, U256, U64},
Web3,
};
use crate::error::GatewayClientError;
#[cfg(not(feature = "coconut"))]
pub fn eth_contract(web3: Web3<Http>) -> Contract<Http> {
Contract::from_json(
web3.eth(),
Address::from(ETH_CONTRACT_ADDRESS),
json::parse(ETH_JSON_ABI)
.expect("Invalid json abi")
.dump()
.as_bytes(),
)
.expect("Invalid json abi")
}
#[derive(Clone)]
pub struct BandwidthController {
#[cfg(feature = "coconut")]
validator_endpoints: Vec<url::Url>,
#[cfg(feature = "coconut")]
identity: PublicKey,
#[cfg(not(feature = "coconut"))]
contract: Contract<Http>,
#[cfg(not(feature = "coconut"))]
eth_private_key: SecretKey,
#[cfg(not(feature = "coconut"))]
backup_bandwidth_token_keys_dir: std::path::PathBuf,
}
impl BandwidthController {
#[cfg(feature = "coconut")]
pub fn new(validator_endpoints: Vec<url::Url>, identity: PublicKey) -> Self {
BandwidthController {
validator_endpoints,
identity,
}
}
#[cfg(not(feature = "coconut"))]
pub fn new(
eth_endpoint: String,
eth_private_key: String,
backup_bandwidth_token_keys_dir: std::path::PathBuf,
) -> Result<Self, GatewayClientError> {
// Fail early, on invalid url
let transport =
Http::new(&eth_endpoint).map_err(|_| GatewayClientError::InvalidURL(eth_endpoint))?;
let web3 = web3::Web3::new(transport);
// Fail early, on invalid abi
let contract = eth_contract(web3);
let eth_private_key = secp256k1::SecretKey::from_str(&eth_private_key)
.map_err(|_| GatewayClientError::InvalidEthereumPrivateKey)?;
Ok(BandwidthController {
contract,
eth_private_key,
backup_bandwidth_token_keys_dir,
})
}
#[cfg(not(feature = "coconut"))]
fn backup_keypair(&self, keypair: &identity::KeyPair) -> Result<(), GatewayClientError> {
std::fs::create_dir_all(&self.backup_bandwidth_token_keys_dir)?;
let file_path = self
.backup_bandwidth_token_keys_dir
.join(keypair.public_key().to_base58_string());
let mut file = std::fs::File::create(file_path)?;
file.write_all(&keypair.private_key().to_bytes())?;
Ok(())
}
#[cfg(feature = "coconut")]
pub async fn prepare_coconut_credential(
&self,
) -> Result<coconut_interface::Credential, GatewayClientError> {
let verification_key = obtain_aggregate_verification_key(&self.validator_endpoints).await?;
let params = coconut_interface::Parameters::new(TOTAL_ATTRIBUTES).unwrap();
// TODO: Decide what is the value and additional info associated with the bandwidth voucher
let bandwidth_credential_attributes = BandwidthVoucherAttributes {
serial_number: params.random_scalar(),
binding_number: params.random_scalar(),
voucher_value: coconut_interface::hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes()),
voucher_info: coconut_interface::hash_to_scalar(
String::from("BandwidthVoucher").as_bytes(),
),
};
let bandwidth_credential = obtain_signature(
&params,
&bandwidth_credential_attributes,
&self.validator_endpoints,
)
.await?;
// the above would presumably be loaded from a file
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
Ok(prepare_for_spending(
&self.identity.to_bytes(),
&bandwidth_credential,
&bandwidth_credential_attributes,
&verification_key,
)?)
}
#[cfg(not(feature = "coconut"))]
pub async fn prepare_token_credential(
&self,
gateway_identity: PublicKey,
) -> Result<TokenCredential, GatewayClientError> {
let mut rng = OsRng;
let kp = identity::KeyPair::new(&mut rng);
self.backup_keypair(&kp)?;
let verification_key = *kp.public_key();
let signed_verification_key = kp.private_key().sign(&verification_key.to_bytes());
self.buy_token_credential(verification_key, signed_verification_key)
.await?;
let message: Vec<u8> = verification_key
.to_bytes()
.iter()
.chain(gateway_identity.to_bytes().iter())
.copied()
.collect();
let signature = kp.private_key().sign(&message);
Ok(TokenCredential::new(
verification_key,
gateway_identity,
BANDWIDTH_VALUE,
signature,
))
}
#[cfg(not(feature = "coconut"))]
pub async fn buy_token_credential(
&self,
verification_key: PublicKey,
signed_verification_key: identity::Signature,
) -> Result<(), GatewayClientError> {
// 0 means a transaction failure, 1 means success
let confirmations = if cfg!(debug_assertions) {
1
} else {
ETH_MIN_BLOCK_DEPTH
};
// 15 seconds per confirmation block + 10 seconds of network overhead
log::info!(
"Waiting for Ethereum transaction. This should take about {} seconds",
confirmations * 15 + 10
);
let recipt = self
.contract
.signed_call_with_confirmations(
ETH_BURN_FUNCTION_NAME,
(
U256::from(TOKENS_TO_BURN),
U256::from(&verification_key.to_bytes()),
Bytes(signed_verification_key.to_bytes().to_vec()),
),
Options::default(),
confirmations,
&self.eth_private_key,
)
.await?;
if Some(U64::from(0)) == recipt.status {
Err(GatewayClientError::BurnTokenError(
web3::Error::InvalidResponse(format!(
"Transaction status is 0 (failure): {:?}",
recipt.logs,
)),
))
} else {
log::info!(
"Bought bandwidth on Ethereum: {} MB",
BANDWIDTH_VALUE / 1024 / 1024
);
Ok(())
}
}
}
#[cfg(not(feature = "coconut"))]
#[cfg(test)]
mod tests {
use network_defaults::ETH_EVENT_NAME;
use super::*;
#[test]
fn parse_contract() {
let transport =
Http::new("https://rinkeby.infura.io/v3/00000000000000000000000000000000").unwrap();
let web3 = web3::Web3::new(transport);
// test no panic occurs
eth_contract(web3);
}
#[test]
fn check_event_name_constant_against_abi() {
let transport =
Http::new("https://rinkeby.infura.io/v3/00000000000000000000000000000000").unwrap();
let web3 = web3::Web3::new(transport);
let contract = eth_contract(web3);
assert!(contract.abi().event(ETH_EVENT_NAME).is_ok());
}
}
+57 -101
View File
@@ -1,7 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::bandwidth::BandwidthController;
use crate::cleanup_socket_message;
use crate::error::GatewayClientError;
use crate::packet_router::PacketRouter;
@@ -9,10 +8,6 @@ pub use crate::packet_router::{
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
};
use crate::socket_state::{PartiallyDelegated, SocketState};
#[cfg(feature = "coconut")]
use coconut_interface::Credential;
#[cfg(not(feature = "coconut"))]
use credentials::token::bandwidth::TokenCredential;
use crypto::asymmetric::identity;
use futures::{FutureExt, SinkExt, StreamExt};
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
@@ -35,11 +30,15 @@ use fluvio_wasm_timer as wasm_timer;
#[cfg(target_arch = "wasm32")]
use wasm_utils::websocket::JSWebsocket;
#[cfg(feature = "coconut")]
use coconut_interface::Credential;
const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10;
const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
pub struct GatewayClient {
authenticated: bool,
#[cfg(feature = "coconut")]
bandwidth_remaining: i64,
gateway_address: String,
gateway_identity: identity::PublicKey,
@@ -48,7 +47,6 @@ pub struct GatewayClient {
connection: SocketState,
packet_router: PacketRouter,
response_timeout_duration: Duration,
bandwidth_controller: Option<BandwidthController>,
// reconnection related variables
/// Specifies whether client should try to reconnect to gateway on connection failure.
@@ -71,19 +69,20 @@ impl GatewayClient {
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
response_timeout_duration: Duration,
bandwidth_controller: Option<BandwidthController>,
) -> Self {
GatewayClient {
authenticated: false,
#[cfg(feature = "coconut")]
bandwidth_remaining: 0,
gateway_address,
gateway_identity,
local_identity,
shared_key,
connection: SocketState::NotConnected,
packet_router: PacketRouter::new(ack_sender, mixnet_message_sender),
response_timeout_duration,
bandwidth_controller,
should_reconnect_on_failure: true,
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
@@ -119,7 +118,10 @@ impl GatewayClient {
GatewayClient {
authenticated: false,
#[cfg(feature = "coconut")]
bandwidth_remaining: 0,
gateway_address,
gateway_identity,
local_identity,
@@ -127,7 +129,6 @@ impl GatewayClient {
connection: SocketState::NotConnected,
packet_router,
response_timeout_duration,
bandwidth_controller: None,
should_reconnect_on_failure: false,
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
@@ -138,10 +139,6 @@ impl GatewayClient {
self.gateway_identity
}
pub fn remaining_bandwidth(&self) -> i64 {
self.bandwidth_remaining
}
#[cfg(not(target_arch = "wasm32"))]
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
@@ -206,7 +203,14 @@ impl GatewayClient {
for i in 1..self.reconnection_attempts {
info!("attempt {}...", i);
if self.authenticate_and_start().await.is_ok() {
if self
.authenticate_and_start(
#[cfg(feature = "coconut")]
None,
)
.await
.is_ok()
{
info!("managed to reconnect!");
return Ok(());
}
@@ -225,7 +229,13 @@ impl GatewayClient {
// final attempt (done separately to be able to return a proper error)
info!("attempt {}", self.reconnection_attempts);
match self.authenticate_and_start().await {
match self
.authenticate_and_start(
#[cfg(feature = "coconut")]
None,
)
.await
{
Ok(_) => {
info!("managed to reconnect!");
Ok(())
@@ -436,12 +446,8 @@ impl GatewayClient {
ClientControlRequest::new_authenticate(self_address, encrypted_address, iv).into();
match self.send_websocket_message(msg).await? {
ServerResponse::Authenticate {
status,
bandwidth_remaining,
} => {
ServerResponse::Authenticate { status } => {
self.authenticated = status;
self.bandwidth_remaining = bandwidth_remaining;
Ok(())
}
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
@@ -467,15 +473,22 @@ impl GatewayClient {
}
#[cfg(feature = "coconut")]
async fn claim_coconut_bandwidth(
pub async fn claim_coconut_bandwidth(
&mut self,
credential: Credential,
coconut_credential: Credential,
) -> Result<(), GatewayClientError> {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
if self.shared_key.is_none() {
return Err(GatewayClientError::NoSharedKeyAvailable);
}
let mut rng = OsRng;
let iv = IV::new_random(&mut rng);
let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential(
&credential,
&coconut_credential,
self.shared_key.as_ref().unwrap(),
iv,
)
@@ -489,64 +502,7 @@ impl GatewayClient {
Ok(())
}
#[cfg(not(feature = "coconut"))]
async fn claim_token_bandwidth(
&mut self,
credential: TokenCredential,
) -> Result<(), GatewayClientError> {
let mut rng = OsRng;
let iv = IV::new_random(&mut rng);
let msg = ClientControlRequest::new_enc_token_bandwidth_credential(
&credential,
self.shared_key.as_ref().unwrap(),
iv,
)
.into();
self.bandwidth_remaining = match self.send_websocket_message(msg).await? {
ServerResponse::Bandwidth { available_total } => Ok(available_total),
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
_ => Err(GatewayClientError::UnexpectedResponse),
}?;
Ok(())
}
pub async fn claim_bandwidth(&mut self) -> Result<(), GatewayClientError> {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
if self.shared_key.is_none() {
return Err(GatewayClientError::NoSharedKeyAvailable);
}
if self.bandwidth_controller.is_none() {
return Err(GatewayClientError::NoBandwidthControllerAvailable);
}
warn!("Not enough bandwidth. Trying to get more bandwidth, this might take a while");
#[cfg(feature = "coconut")]
let credential = self
.bandwidth_controller
.as_ref()
.unwrap()
.prepare_coconut_credential()
.await?;
#[cfg(not(feature = "coconut"))]
let credential = self
.bandwidth_controller
.as_ref()
.unwrap()
.prepare_token_credential(self.gateway_identity)
.await?;
#[cfg(feature = "coconut")]
return self.claim_coconut_bandwidth(credential).await;
#[cfg(not(feature = "coconut"))]
return self.claim_token_bandwidth(credential).await;
}
#[cfg(feature = "coconut")]
fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 {
packets
.iter()
@@ -561,16 +517,9 @@ impl GatewayClient {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
if self.estimate_required_bandwidth(&packets) > self.bandwidth_remaining {
// Try to claim more bandwidth first, and return an error only if that is still not
// enough (the current granularity for bandwidth should be sufficient)
self.claim_bandwidth().await?;
if self.estimate_required_bandwidth(&packets) > self.bandwidth_remaining {
return Err(GatewayClientError::NotEnoughBandwidth(
self.estimate_required_bandwidth(&packets),
self.bandwidth_remaining,
));
}
#[cfg(feature = "coconut")]
if self.estimate_required_bandwidth(&packets) < self.bandwidth_remaining {
return Err(GatewayClientError::NotEnoughBandwidth);
}
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
@@ -636,16 +585,9 @@ impl GatewayClient {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
#[cfg(feature = "coconut")]
if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
// Try to claim more bandwidth first, and return an error only if that is still not
// enough
self.claim_bandwidth().await?;
if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
return Err(GatewayClientError::NotEnoughBandwidth(
mix_packet.sphinx_packet().len() as i64,
self.bandwidth_remaining,
));
}
return Err(GatewayClientError::NotEnoughBandwidth);
}
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
@@ -682,6 +624,10 @@ impl GatewayClient {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
#[cfg(feature = "coconut")]
if self.bandwidth_remaining <= 0 {
return Err(GatewayClientError::NotEnoughBandwidth);
}
if self.connection.is_partially_delegated() {
return Ok(());
}
@@ -709,12 +655,22 @@ impl GatewayClient {
Ok(())
}
pub async fn authenticate_and_start(&mut self) -> Result<Arc<SharedKeys>, GatewayClientError> {
pub async fn authenticate_and_start(
&mut self,
#[cfg(feature = "coconut")] coconut_credential: Option<Credential>,
) -> Result<Arc<SharedKeys>, GatewayClientError> {
if !self.connection.is_established() {
self.establish_connection().await?;
}
let shared_key = self.perform_initial_authentication().await?;
#[cfg(feature = "coconut")]
{
if let Some(coconut_credential) = coconut_credential {
self.claim_coconut_bandwidth(coconut_credential).await?;
}
}
// this call is NON-blocking
self.start_listening_for_mixnet_messages()?;
+67 -54
View File
@@ -2,85 +2,41 @@
// SPDX-License-Identifier: Apache-2.0
use gateway_requests::registration::handshake::error::HandshakeError;
use std::fmt::{self, Error, Formatter};
use std::io;
use thiserror::Error;
use tungstenite::Error as WsError;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsValue;
#[cfg(not(feature = "coconut"))]
use web3::Error as Web3Error;
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum GatewayClientError {
#[error("Connection to the gateway is not established")]
ConnectionNotEstablished,
#[error("Gateway returned an error response - {0}")]
GatewayError(String),
#[error("There was a network error - {0}")]
NetworkError(#[from] WsError),
NetworkError(WsError),
// TODO: see if `JsValue` is a reasonable type for this
#[cfg(target_arch = "wasm32")]
#[error("There was a network error")]
NetworkErrorWasm(JsValue),
#[cfg(not(feature = "coconut"))]
#[error("Could not backup keypair - {0}")]
IOError(#[from] std::io::Error),
#[cfg(not(feature = "coconut"))]
#[error("Could not burn ERC20 token in Ethereum smart contract - {0}")]
BurnTokenError(#[from] Web3Error),
#[cfg(not(feature = "coconut"))]
#[error("Invalid Ethereum private key")]
InvalidEthereumPrivateKey,
#[error("Invalid URL - {0}")]
InvalidURL(String),
#[error("No shared key was provided or obtained")]
NoSharedKeyAvailable,
#[error("No bandwidth controller provided")]
NoBandwidthControllerAvailable,
#[error("Credential error - {0}")]
CredentialError(#[from] credentials::error::Error),
#[error("Connection was abruptly closed")]
ConnectionAbruptlyClosed,
#[error("Received response was malformed")]
MalformedResponse,
#[error("Credential could not be serialized")]
SerializeCredential,
#[error("Client is not authenticated")]
NotAuthenticated,
#[error("Client does not have enough bandwidth: estimated {0}, remaining: {1}")]
NotEnoughBandwidth(i64, i64),
#[error("Received an unexpected response")]
NotEnoughBandwidth,
UnexpectedResponse,
#[error("Connection is in an invalid state - please send a bug report")]
ConnectionInInvalidState,
#[error("Failed to finish registration handshake - {0}")]
RegistrationFailure(HandshakeError),
#[error("Authentication failure")]
AuthenticationFailure,
#[error("Timed out")]
Timeout,
}
impl From<WsError> for GatewayClientError {
fn from(err: WsError) -> Self {
GatewayClientError::NetworkError(err)
}
}
impl GatewayClientError {
pub fn is_closed_connection(&self) -> bool {
match self {
@@ -98,3 +54,60 @@ impl GatewayClientError {
}
}
}
#[cfg(target_arch = "wasm32")]
impl From<JsValue> for GatewayClientError {
fn from(err: JsValue) -> Self {
GatewayClientError::NetworkErrorWasm(err)
}
}
// better human readable representation of the error, mostly so that GatewayClientError
// would implement std::error::Error
impl fmt::Display for GatewayClientError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
match self {
GatewayClientError::ConnectionNotEstablished => {
write!(f, "connection to the gateway is not established")
}
GatewayClientError::NoSharedKeyAvailable => {
write!(f, "no shared key was provided or obtained")
}
GatewayClientError::NotAuthenticated => write!(f, "client is not authenticated"),
GatewayClientError::NetworkError(err) => {
write!(f, "there was a network error - {}", err)
}
#[cfg(target_arch = "wasm32")]
GatewayClientError::NetworkErrorWasm(err) => {
write!(f, "there was a network error - {:?}", err)
}
GatewayClientError::ConnectionAbruptlyClosed => {
write!(f, "connection was abruptly closed")
}
GatewayClientError::Timeout => write!(f, "timed out"),
GatewayClientError::MalformedResponse => write!(f, "received response was malformed"),
GatewayClientError::ConnectionInInvalidState => write!(
f,
"connection is in an invalid state - please send a bug report"
),
GatewayClientError::RegistrationFailure(handshake_err) => write!(
f,
"failed to finish registration handshake - {}",
handshake_err
),
GatewayClientError::AuthenticationFailure => write!(f, "authentication failure"),
GatewayClientError::GatewayError(err) => {
write!(f, "gateway returned an error response - {}", err)
}
GatewayClientError::UnexpectedResponse => write!(f, "received an unexpected response"),
GatewayClientError::NotEnoughBandwidth => {
write!(f, "client does not have enough bandwidth")
}
GatewayClientError::SerializeCredential => {
write!(f, "credential could not be serialized")
}
}
}
}
@@ -8,7 +8,6 @@ pub use packet_router::{
};
use tungstenite::{protocol::Message, Error as WsError};
pub mod bandwidth;
pub mod client;
pub mod error;
pub mod packet_router;
@@ -119,7 +119,7 @@ impl PartiallyDelegated {
}
.is_err()
{
warn!("failed to send back `mixnet_receiver_future` result on the oneshot channel")
panic!("failed to send back `mixnet_receiver_future` result on the oneshot channel")
}
};
@@ -173,9 +173,9 @@ impl PartiallyDelegated {
// this call failing is incredibly unlikely, but not impossible.
// basically the gateway connection must have failed after executing previous line but
// before starting execution of this one.
notify
.send(())
.map_err(|_| GatewayClientError::ConnectionAbruptlyClosed)?;
if notify.send(()).is_err() {
return Err(GatewayClientError::ConnectionAbruptlyClosed);
}
let stream_results: Result<_, GatewayClientError> = stream_receiver
.await
@@ -3,7 +3,6 @@ name = "validator-client"
version = "0.1.0"
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2018"
rust-version = "1.56"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -26,13 +25,13 @@ network-defaults = { path = "../../network-defaults" }
async-trait = { version = "0.1.51", optional = true }
bip39 = { version = "1", features = ["rand"], optional = true }
config = { path = "../../config", optional = true }
cosmrs = { version = "0.3", features = ["rpc", "bip32", "cosmwasm"], optional = true }
prost = { version = "0.9", default-features = false, optional = true }
cosmrs = { version = "0.1", features = ["rpc", "bip32", "cosmwasm"], optional = true }
prost = { version = "0.7", default-features = false, optional = true }
flate2 = { version = "1.0.20", optional = true }
sha2 = { version = "0.9.5", optional = true }
itertools = { version = "0.10", optional = true }
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", optional = true }
ts-rs = {version = "5.1", optional = true}
ts-rs = "3.0"
[features]
nymd-client = ["async-trait", "bip39", "config", "cosmrs", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"]
+128 -53
View File
@@ -10,9 +10,9 @@ use mixnet_contract::StateParams;
use crate::{validator_api, ValidatorClientError};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
use mixnet_contract::{GatewayBond, MixNodeBond, MixnodeRewardingStatusResponse};
#[cfg(feature = "nymd-client")]
use mixnet_contract::{RawDelegationData, RewardingIntervalResponse};
use mixnet_contract::RawDelegationData;
use mixnet_contract::{GatewayBond, MixNodeBond};
use url::Url;
#[cfg(feature = "nymd-client")]
@@ -24,6 +24,7 @@ pub struct Config {
mixnode_page_limit: Option<u32>,
gateway_page_limit: Option<u32>,
mixnode_delegations_page_limit: Option<u32>,
gateway_delegations_page_limit: Option<u32>,
}
#[cfg(feature = "nymd-client")]
@@ -40,6 +41,7 @@ impl Config {
mixnode_page_limit: None,
gateway_page_limit: None,
mixnode_delegations_page_limit: None,
gateway_delegations_page_limit: None,
}
}
@@ -57,6 +59,11 @@ impl Config {
self.mixnode_delegations_page_limit = limit;
self
}
pub fn with_gateway_delegations_page_limit(mut self, limit: Option<u32>) -> Config {
self.gateway_delegations_page_limit = limit;
self
}
}
#[cfg(feature = "nymd-client")]
@@ -67,6 +74,7 @@ pub struct Client<C> {
mixnode_page_limit: Option<u32>,
gateway_page_limit: Option<u32>,
mixnode_delegations_page_limit: Option<u32>,
gateway_delegations_page_limit: Option<u32>,
// ideally they would have been read-only, but unfortunately rust doesn't have such features
pub validator_api: validator_api::Client,
@@ -92,6 +100,7 @@ impl Client<SigningNymdClient> {
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
gateway_delegations_page_limit: config.gateway_delegations_page_limit,
validator_api: validator_api_client,
nymd: nymd_client,
})
@@ -127,6 +136,7 @@ impl Client<QueryNymdClient> {
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
gateway_delegations_page_limit: config.gateway_delegations_page_limit,
validator_api: validator_api_client,
nymd: nymd_client,
})
@@ -172,57 +182,6 @@ impl<C> Client<C> {
Ok(self.nymd.get_state_params().await?)
}
pub async fn get_current_rewarding_interval(
&self,
) -> Result<RewardingIntervalResponse, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_current_rewarding_interval().await?)
}
pub async fn get_rewarding_status(
&self,
mix_identity: mixnet_contract::IdentityKey,
rewarding_interval_nonce: u32,
) -> Result<MixnodeRewardingStatusResponse, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_rewarding_status(mix_identity, rewarding_interval_nonce)
.await?)
}
pub async fn get_reward_pool(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_reward_pool().await?.u128())
}
pub async fn get_circulating_supply(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_circulating_supply().await?.u128())
}
pub async fn get_sybil_resistance_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_sybil_resistance_percent().await?)
}
pub async fn get_epoch_reward_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_epoch_reward_percent().await?)
}
// basically handles paging for us
pub async fn get_all_nymd_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
@@ -380,6 +339,116 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_nymd_single_gateway_delegations(
&self,
identity: mixnet_contract::IdentityKey,
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_gateway_delegations(
identity.clone(),
start_after.take(),
self.gateway_delegations_page_limit,
)
.await?;
delegations.append(&mut paged_response.delegations);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(delegations)
}
pub async fn get_all_nymd_gateway_delegations(
&self,
) -> Result<Vec<mixnet_contract::UnpackedDelegation<RawDelegationData>>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_all_gateway_delegations(
start_after.take(),
self.gateway_delegations_page_limit,
)
.await?;
delegations.append(&mut paged_response.delegations);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(delegations)
}
pub async fn get_all_nymd_reverse_gateway_delegations(
&self,
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<mixnet_contract::IdentityKey>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_reverse_gateway_delegations_paged(
mixnet_contract::Addr::unchecked(delegation_owner.as_ref()),
start_after.take(),
self.mixnode_delegations_page_limit,
)
.await?;
delegations.append(&mut paged_response.delegated_nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(delegations)
}
pub async fn get_all_nymd_gateway_delegations_of_owner(
&self,
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut delegations = Vec::new();
for node_identity in self
.get_all_nymd_reverse_gateway_delegations(delegation_owner)
.await?
{
let delegation = self
.nymd
.get_gateway_delegation(node_identity, delegation_owner)
.await?;
delegations.push(delegation);
}
Ok(delegations)
}
pub async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
@@ -419,6 +488,12 @@ impl ApiClient {
Ok(self.validator_api.get_active_mixnodes().await?)
}
pub async fn get_cached_active_gateways(
&self,
) -> Result<Vec<GatewayBond>, ValidatorClientError> {
Ok(self.validator_api.get_active_gateways().await?)
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes().await?)
}
@@ -22,7 +22,7 @@ use cosmrs::rpc::query::Query;
use cosmrs::rpc::{self, HttpClient, Order};
use cosmrs::tendermint::abci::Transaction;
use cosmrs::tendermint::{abci, block, chain};
use cosmrs::{tx, AccountId, Coin, Denom};
use cosmrs::{AccountId, Coin, Denom};
use prost::Message;
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
@@ -153,9 +153,12 @@ pub trait CosmWasmClient: rpc::Client {
.map_err(|_| NymdError::SerializationError("Coins".to_owned()))
}
async fn get_tx(&self, id: tx::Hash) -> Result<TxResponse, NymdError> {
Ok(self.tx(id, false).await?)
}
// disabled until https://github.com/tendermint/tendermint/issues/6802
// and consequently https://github.com/informalsystems/tendermint-rs/issues/942 is resolved
//
// async fn get_tx(&self, id: tx::Hash) -> Result<TxResponse, NymdError> {
// Ok(self.tx(id, false).await?)
// }
async fn search_tx(&self, query: Query) -> Result<Vec<TxResponse>, NymdError> {
// according to https://docs.tendermint.com/master/rpc/#/Info/tx_search
@@ -13,8 +13,8 @@ use cosmrs::distribution::MsgWithdrawDelegatorReward;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
use cosmrs::tx::{Fee, Msg, SignDoc, SignerInfo};
use cosmrs::{cosmwasm, rpc, tx, AccountId, Any, Coin};
use cosmrs::tx::{Fee, Msg, MsgType, SignDoc, SignerInfo};
use cosmrs::{cosmwasm, rpc, tx, AccountId, Coin};
use log::debug;
use serde::Serialize;
use sha2::Digest;
@@ -52,7 +52,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.unwrap_or_default(),
instantiate_permission: Default::default(),
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgStoreCode".to_owned()))?;
let tx_res = self
@@ -114,7 +114,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
init_msg: serde_json::to_vec(msg)?,
funds: options.map(|options| options.funds).unwrap_or_default(),
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgInstantiateContract".to_owned()))?;
let tx_res = self
@@ -154,7 +154,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
new_admin: new_admin.clone(),
contract: contract_address.clone(),
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgUpdateAdmin".to_owned()))?;
let tx_res = self
@@ -179,7 +179,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
sender: sender_address.clone(),
contract: contract_address.clone(),
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgClearAdmin".to_owned()))?;
let tx_res = self
@@ -211,7 +211,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
code_id,
migrate_msg: serde_json::to_vec(msg)?,
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgMigrateContract".to_owned()))?;
let tx_res = self
@@ -243,7 +243,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
msg: serde_json::to_vec(msg)?,
funds,
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))?;
let tx_res = self
@@ -278,7 +278,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
msg: serde_json::to_vec(&msg)?,
funds,
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))
})
.collect::<Result<_, _>>()?;
@@ -312,7 +312,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
to_address: recipient_address.clone(),
amount,
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?;
self.sign_and_broadcast_commit(sender_address, vec![send_msg], fee, memo)
@@ -332,7 +332,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
validator_address: validator_address.to_owned(),
amount,
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![delegate_msg], fee, memo)
@@ -352,7 +352,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
validator_address: validator_address.to_owned(),
amount: Some(amount),
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgUndelegate".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![undelegate_msg], fee, memo)
@@ -370,7 +370,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
delegator_address: delegator_address.to_owned(),
validator_address: validator_address.to_owned(),
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgWithdrawDelegatorReward".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![withdraw_msg], fee, memo)
@@ -381,7 +381,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
async fn sign_and_broadcast_async(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
messages: Vec<Msg>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_async::Response, NymdError> {
@@ -397,7 +397,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
async fn sign_and_broadcast_sync(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
messages: Vec<Msg>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_sync::Response, NymdError> {
@@ -413,7 +413,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
async fn sign_and_broadcast_commit(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
messages: Vec<Msg>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_commit::Response, NymdError> {
@@ -428,7 +428,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
fn sign_direct(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
messages: Vec<Msg>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
signer_data: SignerData,
@@ -466,7 +466,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
async fn sign(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
messages: Vec<Msg>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<tx::Raw, NymdError> {
@@ -506,7 +506,7 @@ impl Client {
#[async_trait]
impl rpc::Client for Client {
async fn perform<R>(&self, request: R) -> Result<R::Response, rpc::Error>
async fn perform<R>(&self, request: R) -> rpc::Result<R::Response>
where
R: SimpleRequest,
{
@@ -7,10 +7,7 @@ use cosmrs::{bip32, tx, AccountId};
use std::io;
use thiserror::Error;
pub use cosmrs::rpc::error::{
Error as TendermintRpcError, ErrorDetail as TendermintRpcErrorDetail,
};
pub use cosmrs::rpc::response_error::{Code, ResponseError};
pub use cosmrs::rpc::error::{Code, Error as TendermintRpcError};
#[derive(Debug, Error)]
pub enum NymdError {
@@ -105,21 +102,17 @@ pub enum NymdError {
}
impl NymdError {
pub fn is_tendermint_response_timeout(&self) -> bool {
pub fn is_tendermint_timeout(&self) -> bool {
match &self {
NymdError::TendermintError(TendermintRpcError(
TendermintRpcErrorDetail::Response(err),
_,
)) => {
let response = &err.source;
if response.code() == Code::InternalError {
NymdError::TendermintError(tm_err) => {
if tm_err.code() == Code::InternalError {
// 0.34 (and earlier) versions of tendermint seemed to be using phrase "timed out waiting ..."
// (https://github.com/tendermint/tendermint/blob/v0.34.13/rpc/core/mempool.go#L124)
// while 0.35+ has "timeout waiting for ..."
// https://github.com/tendermint/tendermint/blob/v0.35.0-rc3/internal/rpc/core/mempool.go#L99
// note that as of the time of writing this comment (08.10.2021), the most recent version
// of cosmos-sdk (v0.44.1) uses tendermint 0.34.13
if let Some(data) = response.data() {
if let Some(data) = tm_err.data() {
data.contains("timed out") || data.contains("timeout")
} else {
false
@@ -132,18 +125,14 @@ impl NymdError {
}
}
pub fn is_tendermint_response_duplicate(&self) -> bool {
pub fn is_tendermint_duplicate(&self) -> bool {
match &self {
NymdError::TendermintError(TendermintRpcError(
TendermintRpcErrorDetail::Response(err),
_,
)) => {
let response = &err.source;
if response.code() == Code::InternalError {
NymdError::TendermintError(tm_err) => {
if tm_err.code() == Code::InternalError {
// this particular error message seems to be unchanged between 0.34 and newer versions
// https://github.com/tendermint/tendermint/blob/v0.34.13/mempool/errors.go#L10
// https://github.com/tendermint/tendermint/blob/v0.35.0-rc3/types/mempool.go#L10
if let Some(data) = response.data() {
if let Some(data) = tm_err.data() {
data.contains("tx already exists in cache")
} else {
false
@@ -6,9 +6,9 @@ use cosmrs::tx::{Fee, Gas};
use cosmrs::Coin;
use serde::{Deserialize, Serialize};
use std::fmt;
use ts_rs::TS;
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, TS)]
pub enum Operation {
Upload,
Init,
@@ -23,11 +23,10 @@ pub enum Operation {
BondGateway,
UnbondGateway,
DelegateToGateway,
UndelegateFromGateway,
UpdateStateParams,
BeginMixnodeRewarding,
FinishMixnodeRewarding,
}
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
@@ -44,13 +43,13 @@ impl fmt::Display for Operation {
Operation::Send => f.write_str("Send"),
Operation::BondMixnode => f.write_str("BondMixnode"),
Operation::UnbondMixnode => f.write_str("UnbondMixnode"),
Operation::BondGateway => f.write_str("BondGateway"),
Operation::UnbondGateway => f.write_str("UnbondGateway"),
Operation::DelegateToMixnode => f.write_str("DelegateToMixnode"),
Operation::UndelegateFromMixnode => f.write_str("UndelegateFromMixnode"),
Operation::BondGateway => f.write_str("BondGateway"),
Operation::UnbondGateway => f.write_str("UnbondGateway"),
Operation::DelegateToGateway => f.write_str("DelegateToGateway"),
Operation::UndelegateFromGateway => f.write_str("UndelegateFromGateway"),
Operation::UpdateStateParams => f.write_str("UpdateStateParams"),
Operation::BeginMixnodeRewarding => f.write_str("BeginMixnodeRewarding"),
Operation::FinishMixnodeRewarding => f.write_str("FinishMixnodeRewarding"),
}
}
}
@@ -72,10 +71,10 @@ impl Operation {
Operation::BondGateway => 175_000u64.into(),
Operation::UnbondGateway => 175_000u64.into(),
Operation::DelegateToGateway => 175_000u64.into(),
Operation::UndelegateFromGateway => 175_000u64.into(),
Operation::UpdateStateParams => 175_000u64.into(),
Operation::BeginMixnodeRewarding => 175_000u64.into(),
Operation::FinishMixnodeRewarding => 175_000u64.into(),
}
}
@@ -11,13 +11,13 @@ use crate::nymd::fee_helpers::Operation;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
use cosmwasm_std::{Coin, Uint128};
use cosmwasm_std::Coin;
use mixnet_contract::{
Addr, Delegation, ExecuteMsg, Gateway, GatewayOwnershipResponse, IdentityKey,
LayerDistribution, MixNode, MixOwnershipResponse, MixnodeRewardingStatusResponse,
PagedAllDelegationsResponse, PagedGatewayResponse, PagedMixDelegationsResponse,
PagedMixnodeResponse, PagedReverseMixDelegationsResponse, QueryMsg, RawDelegationData,
RewardingIntervalResponse, StateParams,
LayerDistribution, MixNode, MixOwnershipResponse, PagedAllDelegationsResponse,
PagedGatewayDelegationsResponse, PagedGatewayResponse, PagedMixDelegationsResponse,
PagedMixnodeResponse, PagedReverseGatewayDelegationsResponse,
PagedReverseMixDelegationsResponse, QueryMsg, RawDelegationData, StateParams,
};
use serde::Serialize;
use std::collections::HashMap;
@@ -28,11 +28,10 @@ pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
pub use crate::nymd::gas_price::GasPrice;
pub use cosmrs::rpc::HttpClient as QueryNymdClient;
pub use cosmrs::tendermint::block::Height;
pub use cosmrs::tendermint::hash;
pub use cosmrs::tendermint::Time as TendermintTime;
pub use cosmrs::tx::{Fee, Gas};
pub use cosmrs::Coin as CosmosCoin;
pub use cosmrs::{AccountId, Decimal, Denom};
pub use cosmrs::{AccountId, Denom};
pub use signing_client::Client as SigningNymdClient;
pub mod cosmwasm_client;
@@ -186,21 +185,6 @@ impl<C> NymdClient<C> {
self.client.get_height().await
}
/// Obtains the hash of a block specified by the provided height.
///
/// # Arguments
///
/// * `height`: height of the block for which we want to obtain the hash.
pub async fn get_block_hash(&self, height: u32) -> Result<hash::Hash, NymdError>
where
C: CosmWasmClient + Sync,
{
self.client
.get_block(Some(height))
.await
.map(|block| block.block_id.hash)
}
pub async fn get_balance(&self, address: &AccountId) -> Result<Option<CosmosCoin>, NymdError>
where
C: CosmWasmClient + Sync,
@@ -218,35 +202,6 @@ impl<C> NymdClient<C> {
.await
}
pub async fn get_current_rewarding_interval(
&self,
) -> Result<RewardingIntervalResponse, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::CurrentRewardingInterval {};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
pub async fn get_rewarding_status(
&self,
mix_identity: mixnet_contract::IdentityKey,
rewarding_interval_nonce: u32,
) -> Result<MixnodeRewardingStatusResponse, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetRewardingStatus {
mix_identity,
rewarding_interval_nonce,
};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
pub async fn get_layer_distribution(&self) -> Result<LayerDistribution, NymdError>
where
C: CosmWasmClient + Sync,
@@ -257,46 +212,6 @@ impl<C> NymdClient<C> {
.await
}
pub async fn get_reward_pool(&self) -> Result<Uint128, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetRewardPool {};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
pub async fn get_circulating_supply(&self) -> Result<Uint128, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetCirculatingSupply {};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
pub async fn get_sybil_resistance_percent(&self) -> Result<u8, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetSybilResistancePercent {};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
pub async fn get_epoch_reward_percent(&self) -> Result<u8, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetEpochRewardPercent {};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
/// Checks whether there is a bonded mixnode associated with the provided client's address
pub async fn owns_mixnode(&self, address: &AccountId) -> Result<bool, NymdError>
where
@@ -439,6 +354,82 @@ impl<C> NymdClient<C> {
.await
}
/// Gets list of all delegations towards particular gateway on particular page.
pub async fn get_gateway_delegations(
&self,
gateway_identity: IdentityKey,
start_after: Option<Addr>,
page_limit: Option<u32>,
) -> Result<PagedGatewayDelegationsResponse, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetGatewayDelegations {
gateway_identity,
start_after,
limit: page_limit,
};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
/// Gets list of all gateway delegations on particular page.
pub async fn get_all_gateway_delegations(
&self,
start_after: Option<Vec<u8>>,
page_limit: Option<u32>,
) -> Result<PagedAllDelegationsResponse<RawDelegationData>, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetAllGatewayDelegations {
start_after,
limit: page_limit,
};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
/// Gets list of all the gateways on which a particular address delegated.
pub async fn get_reverse_gateway_delegations_paged(
&self,
delegation_owner: Addr,
start_after: Option<IdentityKey>,
page_limit: Option<u32>,
) -> Result<PagedReverseGatewayDelegationsResponse, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetReverseGatewayDelegations {
delegation_owner,
start_after,
limit: page_limit,
};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
/// Checks value of delegation of given client towards particular gateway.
pub async fn get_gateway_delegation(
&self,
gateway_identity: IdentityKey,
delegator: &AccountId,
) -> Result<Delegation, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetGatewayDelegation {
gateway_identity,
address: Addr::unchecked(delegator.as_ref()),
};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
/// Send funds from one address to another
pub async fn send(
&self,
@@ -707,6 +698,57 @@ impl<C> NymdClient<C> {
.await
}
/// Delegates specified amount of stake to particular gateway.
pub async fn delegate_to_gateway(
&self,
gateway_identity: &str,
amount: &Coin,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.get_fee(Operation::DelegateToGateway);
let req = ExecuteMsg::DelegateToGateway {
gateway_identity: gateway_identity.to_string(),
};
self.client
.execute(
self.address(),
self.contract_address()?,
&req,
fee,
"Delegating to gateway from rust!",
vec![cosmwasm_coin_ptr_to_cosmos_coin(amount)],
)
.await
}
/// Removes stake delegation from a particular gateway.
pub async fn remove_gateway_delegation(
&self,
gateway_identity: &str,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.get_fee(Operation::UndelegateFromGateway);
let req = ExecuteMsg::UndelegateFromGateway {
gateway_identity: gateway_identity.to_string(),
};
self.client
.execute(
self.address(),
self.contract_address()?,
&req,
fee,
"Removing gateway delegation from rust!",
Vec::new(),
)
.await
}
pub async fn update_state_params(
&self,
new_params: StateParams,
@@ -728,54 +770,6 @@ impl<C> NymdClient<C> {
)
.await
}
pub async fn begin_mixnode_rewarding(
&self,
rewarding_interval_nonce: u32,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.get_fee(Operation::BeginMixnodeRewarding);
let req = ExecuteMsg::BeginMixnodeRewarding {
rewarding_interval_nonce,
};
self.client
.execute(
self.address(),
self.contract_address()?,
&req,
fee,
"Beginning mixnode rewarding procedure",
Vec::new(),
)
.await
}
pub async fn finish_mixnode_rewarding(
&self,
rewarding_interval_nonce: u32,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.get_fee(Operation::FinishMixnodeRewarding);
let req = ExecuteMsg::FinishMixnodeRewarding {
rewarding_interval_nonce,
};
self.client
.execute(
self.address(),
self.contract_address()?,
&req,
fee,
"Finishing mixnode rewarding procedure",
Vec::new(),
)
.await
}
}
fn cosmwasm_coin_to_cosmos_coin(coin: Coin) -> CosmosCoin {
@@ -73,6 +73,11 @@ impl Client {
.await
}
pub async fn get_active_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::GATEWAYS, routes::ACTIVE])
.await
}
pub async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
+1 -1
View File
@@ -8,4 +8,4 @@ description = "Crutch library until there is proper SerDe support for coconut st
serde = { version = "1.0", features = ["derive"] }
getset = "0.1.1"
nymcoconut = {path = "../nymcoconut" }
coconut-rs = { git = "https://github.com/nymtech/coconut.git", branch = "0.5.0" }
+6 -6
View File
@@ -4,7 +4,7 @@
use getset::{CopyGetters, Getters};
use serde::{Deserialize, Serialize};
pub use nymcoconut::*;
pub use coconut_rs::*;
#[derive(Serialize, Deserialize, Getters, CopyGetters, Clone)]
pub struct Credential {
@@ -42,7 +42,7 @@ impl Credential {
.iter()
.map(hash_to_scalar)
.collect::<Vec<Attribute>>();
nymcoconut::verify_credential(&params, verification_key, &self.theta, &public_attributes)
coconut_rs::verify_credential(&params, verification_key, &self.theta, &public_attributes)
}
}
@@ -84,7 +84,7 @@ pub struct BlindSignRequestBody {
#[getset(get = "pub")]
blind_sign_request: BlindSignRequest,
#[getset(get = "pub")]
public_key: nymcoconut::PublicKey,
public_key: coconut_rs::PublicKey,
public_attributes: Vec<String>,
#[getset(get = "pub")]
total_params: u32,
@@ -92,13 +92,13 @@ pub struct BlindSignRequestBody {
impl BlindSignRequestBody {
pub fn new(
blind_sign_request: &BlindSignRequest,
public_key: &nymcoconut::PublicKey,
blind_sign_request: BlindSignRequest,
public_key: &coconut_rs::PublicKey,
public_attributes: &[Attribute],
total_params: u32,
) -> BlindSignRequestBody {
BlindSignRequestBody {
blind_sign_request: blind_sign_request.clone(),
blind_sign_request,
public_key: public_key.clone(),
public_attributes: public_attributes
.iter()
-2
View File
@@ -11,6 +11,4 @@ url = "2.2"
# I guess temporarily until we get serde support in coconut up and running
coconut-interface = { path = "../coconut-interface" }
crypto = { path = "../crypto" }
network-defaults = { path = "../network-defaults" }
validator-client = { path = "../client-libs/validator-client" }
+48
View File
@@ -0,0 +1,48 @@
// 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 url::Url;
use crate::error::Error;
use crate::utils::{obtain_aggregate_signature, prepare_credential_for_spending};
use coconut_interface::{hash_to_scalar, Credential, Parameters, Signature, VerificationKey};
const BANDWIDTH_VALUE: u64 = 10 * 1024 * 1024 * 1024; // 10 GB
pub const PUBLIC_ATTRIBUTES: u32 = 1;
pub const PRIVATE_ATTRIBUTES: u32 = 1;
pub const TOTAL_ATTRIBUTES: u32 = PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES;
// TODO: this definitely has to be moved somewhere else. It's just a temporary solution
pub async fn obtain_signature(raw_identity: &[u8], validators: &[Url]) -> Result<Signature, Error> {
let public_attributes = vec![hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes())];
let private_attributes = vec![hash_to_scalar(raw_identity)];
let params = Parameters::new(TOTAL_ATTRIBUTES)?;
obtain_aggregate_signature(&params, &public_attributes, &private_attributes, validators).await
}
pub fn prepare_for_spending(
raw_identity: &[u8],
signature: &Signature,
verification_key: &VerificationKey,
) -> Result<Credential, Error> {
let public_attributes = vec![BANDWIDTH_VALUE.to_be_bytes().to_vec()];
let private_attributes = vec![raw_identity.to_vec()];
let params = Parameters::new(TOTAL_ATTRIBUTES)?;
prepare_credential_for_spending(
&params,
public_attributes,
private_attributes,
signature,
verification_key,
)
}
@@ -1,77 +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 coconut_interface::{
Credential, Parameters, PrivateAttribute, PublicAttribute, Signature, VerificationKey,
};
use network_defaults::BANDWIDTH_VALUE;
use url::Url;
use crate::error::Error;
use super::utils::{obtain_aggregate_signature, prepare_credential_for_spending};
pub const PUBLIC_ATTRIBUTES: u32 = 2;
pub const PRIVATE_ATTRIBUTES: u32 = 2;
pub const TOTAL_ATTRIBUTES: u32 = PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES;
pub struct BandwidthVoucherAttributes {
// a random secret value generated by the client used for double-spending detection
pub serial_number: PrivateAttribute,
// a random secret value generated by the client used to bind multiple credentials together
pub binding_number: PrivateAttribute,
// the value (e.g., bandwidth) encoded in this voucher
pub voucher_value: PublicAttribute,
// a field with public information, e.g., type of voucher, epoch etc.
pub voucher_info: PublicAttribute,
}
impl BandwidthVoucherAttributes {
pub fn get_public_attributes(&self) -> Vec<PublicAttribute> {
vec![self.voucher_value, self.voucher_info]
}
pub fn get_private_attributes(&self) -> Vec<PrivateAttribute> {
vec![self.serial_number, self.binding_number]
}
}
// TODO: this definitely has to be moved somewhere else. It's just a temporary solution
pub async fn obtain_signature(
params: &Parameters,
attributes: &BandwidthVoucherAttributes,
validators: &[Url],
) -> Result<Signature, Error> {
let public_attributes = attributes.get_public_attributes();
let private_attributes = attributes.get_private_attributes();
obtain_aggregate_signature(params, &public_attributes, &private_attributes, validators).await
}
pub fn prepare_for_spending(
raw_identity: &[u8],
signature: &Signature,
attributes: &BandwidthVoucherAttributes,
verification_key: &VerificationKey,
) -> Result<Credential, Error> {
let public_attributes = vec![
raw_identity.to_vec(),
BANDWIDTH_VALUE.to_be_bytes().to_vec(),
];
let params = Parameters::new(TOTAL_ATTRIBUTES)?;
prepare_credential_for_spending(
&params,
public_attributes,
attributes.serial_number,
attributes.binding_number,
signature,
verification_key,
)
}
-5
View File
@@ -1,5 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod bandwidth;
pub mod utils;
+3 -3
View File
@@ -1,8 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod coconut;
pub mod bandwidth;
pub mod error;
pub mod token;
mod utils;
pub use coconut::utils::{obtain_aggregate_signature, obtain_aggregate_verification_key};
pub use utils::{obtain_aggregate_signature, obtain_aggregate_verification_key};
-140
View File
@@ -1,140 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crypto::asymmetric::identity::{PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH};
use crate::error::Error;
use std::convert::TryInto;
#[cfg(not(feature = "coconut"))]
pub struct TokenCredential {
verification_key: PublicKey,
gateway_identity: PublicKey,
bandwidth: u64,
signature: Signature,
}
#[cfg(not(feature = "coconut"))]
impl TokenCredential {
pub fn new(
verification_key: PublicKey,
gateway_identity: PublicKey,
bandwidth: u64,
signature: Signature,
) -> Self {
TokenCredential {
verification_key,
gateway_identity,
bandwidth,
signature,
}
}
pub fn verification_key(&self) -> PublicKey {
self.verification_key
}
pub fn gateway_identity(&self) -> PublicKey {
self.gateway_identity
}
pub fn bandwidth(&self) -> u64 {
self.bandwidth
}
pub fn signature_bytes(&self) -> [u8; 64] {
self.signature.to_bytes()
}
pub fn verify_signature(&self) -> bool {
let message: Vec<u8> = self
.verification_key
.to_bytes()
.iter()
.chain(self.gateway_identity.to_bytes().iter())
.copied()
.collect();
self.verification_key
.verify(&message, &self.signature)
.is_ok()
}
pub fn to_bytes(&self) -> Vec<u8> {
self.verification_key
.to_bytes()
.iter()
.chain(self.gateway_identity.to_bytes().iter())
.chain(self.bandwidth.to_be_bytes().iter())
.chain(self.signature.to_bytes().iter())
.copied()
.collect()
}
pub fn from_bytes(b: &[u8]) -> Result<Self, Error> {
if b.len() != 2 * PUBLIC_KEY_LENGTH + 8 + SIGNATURE_LENGTH {
return Err(Error::BandwidthCredentialError);
}
let verification_key = PublicKey::from_bytes(&b[..PUBLIC_KEY_LENGTH])
.map_err(|_| Error::BandwidthCredentialError)?;
let gateway_identity = PublicKey::from_bytes(&b[PUBLIC_KEY_LENGTH..2 * PUBLIC_KEY_LENGTH])
.map_err(|_| Error::BandwidthCredentialError)?;
let bandwidth = u64::from_be_bytes(
b[2 * PUBLIC_KEY_LENGTH..2 * PUBLIC_KEY_LENGTH + 8]
.try_into()
// unwrapping is safe because we know we have 8 bytes
.unwrap(),
);
let signature = Signature::from_bytes(&b[2 * PUBLIC_KEY_LENGTH + 8..])
.map_err(|_| Error::BandwidthCredentialError)?;
Ok(TokenCredential {
verification_key,
gateway_identity,
bandwidth,
signature,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(feature = "coconut"))]
#[test]
fn token_serde() {
// pre-generated, valid values
let verification_key = PublicKey::from_bytes(&[
103, 105, 71, 177, 149, 245, 26, 32, 73, 121, 76, 50, 94, 88, 119, 231, 91, 229, 167,
56, 39, 62, 185, 39, 83, 246, 153, 27, 17, 155, 109, 73,
])
.unwrap();
let gateway_identity = PublicKey::from_bytes(&[
37, 113, 137, 189, 157, 82, 35, 2, 187, 136, 61, 119, 98, 5, 245, 82, 46, 124, 67, 45,
165, 255, 53, 222, 185, 252, 6, 148, 128, 15, 206, 19,
])
.unwrap();
let signature = Signature::from_bytes(&[
117, 251, 162, 217, 57, 2, 50, 210, 206, 81, 236, 90, 74, 201, 69, 237, 240, 247, 214,
158, 220, 89, 235, 222, 85, 134, 73, 73, 8, 60, 25, 39, 183, 28, 83, 193, 31, 174, 25,
24, 38, 215, 205, 228, 159, 135, 35, 4, 171, 59, 100, 157, 12, 249, 77, 52, 143, 4, 32,
28, 147, 70, 182, 14,
])
.unwrap();
let credential = TokenCredential::new(verification_key, gateway_identity, 1024, signature);
let serialized_credential = credential.to_bytes();
let deserialized_credential = TokenCredential::from_bytes(&serialized_credential).unwrap();
assert_eq!(
credential.verification_key,
deserialized_credential.verification_key
);
assert_eq!(
credential.gateway_identity,
deserialized_credential.gateway_identity
);
assert_eq!(credential.bandwidth, deserialized_credential.bandwidth);
assert_eq!(
credential.signature.to_bytes(),
deserialized_credential.signature.to_bytes()
);
}
}
-4
View File
@@ -1,4 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod bandwidth;
@@ -1,16 +1,14 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::Error;
use coconut_interface::{
aggregate_signature_shares, aggregate_verification_keys, prepare_blind_sign,
prove_bandwidth_credential, Attribute, BlindSignRequestBody, Credential, Parameters, Signature,
aggregate_signature_shares, aggregate_verification_keys, hash_to_scalar, prepare_blind_sign,
prove_credential, Attribute, BlindSignRequestBody, Credential, Parameters, Signature,
SignatureShare, VerificationKey,
};
use url::Url;
use crate::coconut::bandwidth::PRIVATE_ATTRIBUTES;
use crate::error::Error;
/// Contacts all provided validators and then aggregate their verification keys.
///
/// # Arguments
@@ -65,18 +63,17 @@ async fn obtain_partial_credential(
public_attributes: &[Attribute],
private_attributes: &[Attribute],
client: &validator_client::ApiClient,
validator_vk: &VerificationKey,
) -> Result<Signature, Error> {
let elgamal_keypair = coconut_interface::elgamal_keygen(params);
let blind_sign_request = prepare_blind_sign(
params,
&elgamal_keypair,
elgamal_keypair.public_key(),
private_attributes,
public_attributes,
)?;
let blind_sign_request_body = BlindSignRequestBody::new(
&blind_sign_request,
blind_sign_request,
elgamal_keypair.public_key(),
public_attributes,
(public_attributes.len() + private_attributes.len()) as u32,
@@ -86,17 +83,7 @@ async fn obtain_partial_credential(
.blind_sign(&blind_sign_request_body)
.await?
.blinded_signature;
let unblinded_signature = blinded_signature.unblind(
params,
elgamal_keypair.private_key(),
validator_vk,
private_attributes,
public_attributes,
&blind_sign_request.get_commitment_hash(),
)?;
Ok(unblinded_signature)
Ok(blinded_signature.unblind(elgamal_keypair.private_key()))
}
pub async fn obtain_aggregate_signature(
@@ -110,76 +97,40 @@ pub async fn obtain_aggregate_signature(
}
let mut shares = Vec::with_capacity(validators.len());
let mut validators_partial_vks: Vec<VerificationKey> = Vec::with_capacity(validators.len());
let mut client = validator_client::ApiClient::new(validators[0].clone());
let validator_partial_vk = client.get_coconut_verification_key().await?;
validators_partial_vks.push(validator_partial_vk.key.clone());
let first = obtain_partial_credential(
params,
public_attributes,
private_attributes,
&client,
&validator_partial_vk.key,
)
.await?;
let first =
obtain_partial_credential(params, public_attributes, private_attributes, &client).await?;
shares.push(SignatureShare::new(first, 0));
for (id, validator_url) in validators.iter().enumerate().skip(1) {
client.change_validator_api(validator_url.clone());
let validator_partial_vk = client.get_coconut_verification_key().await?;
validators_partial_vks.push(validator_partial_vk.key.clone());
let signature = obtain_partial_credential(
params,
public_attributes,
private_attributes,
&client,
&validator_partial_vk.key,
)
.await?;
let signature =
obtain_partial_credential(params, public_attributes, private_attributes, &client)
.await?;
let share = SignatureShare::new(signature, id as u64);
shares.push(share)
}
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
attributes.extend_from_slice(private_attributes);
attributes.extend_from_slice(public_attributes);
let mut indices: Vec<u64> = Vec::with_capacity(validators_partial_vks.len());
for i in 1..validators_partial_vks.len() {
indices.push(i as u64);
}
let verification_key =
aggregate_verification_keys(&validators_partial_vks, Some(indices.as_ref()))?;
Ok(aggregate_signature_shares(
params,
&verification_key,
&attributes,
&shares,
)?)
Ok(aggregate_signature_shares(&shares)?)
}
// TODO: better type flow
pub fn prepare_credential_for_spending(
params: &Parameters,
public_attributes: Vec<Vec<u8>>,
serial_number: Attribute,
binding_number: Attribute,
private_attributes: Vec<Vec<u8>>,
signature: &Signature,
verification_key: &VerificationKey,
) -> Result<Credential, Error> {
let theta = prove_bandwidth_credential(
params,
verification_key,
signature,
serial_number,
binding_number,
)?;
let private_attributes = private_attributes
.iter()
.map(hash_to_scalar)
.collect::<Vec<Attribute>>();
let theta = prove_credential(params, verification_key, signature, &private_attributes)?;
Ok(Credential::new(
(public_attributes.len() + PRIVATE_ATTRIBUTES as usize) as u32,
(public_attributes.len() + private_attributes.len()) as u32,
theta,
public_attributes,
signature,
+9 -2
View File
@@ -5,13 +5,18 @@ use digest::{BlockInput, FixedOutput, Reset, Update};
use generic_array::ArrayLength;
use hkdf::Hkdf;
#[derive(Debug)]
pub enum HkdfError {
InvalidOkmLength,
}
/// Perform HKDF `extract` then `expand` as a single step.
pub fn extract_then_expand<D>(
salt: Option<&[u8]>,
ikm: &[u8],
info: Option<&[u8]>,
okm_length: usize,
) -> Result<Vec<u8>, hkdf::InvalidLength>
) -> Result<Vec<u8>, HkdfError>
where
D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
D::BlockSize: ArrayLength<u8>,
@@ -22,7 +27,9 @@ where
let hkdf = Hkdf::<D>::new(salt, ikm);
let mut okm = vec![0u8; okm_length];
hkdf.expand(info.unwrap_or_else(|| &[]), &mut okm)?;
if hkdf.expand(info.unwrap_or_else(|| &[]), &mut okm).is_err() {
return Err(HkdfError::InvalidOkmLength);
}
Ok(okm)
}
-10
View File
@@ -1,10 +0,0 @@
[package]
name = "erc20-bridge-contract"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
-45
View File
@@ -1,45 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
// Serializable structures for what we find in common/crypto
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, JsonSchema)]
pub struct PublicKey([u8; 32]);
impl PublicKey {
pub fn new(bytes: [u8; 32]) -> Self {
PublicKey(bytes)
}
pub fn to_bytes(&self) -> [u8; 32] {
self.0
}
}
impl AsRef<[u8]> for PublicKey {
#[inline]
fn as_ref(&self) -> &[u8] {
&self.0
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Signature([u8; 32], [u8; 32]);
impl Signature {
pub fn new(bytes: [u8; 64]) -> Self {
let mut sig1 = [0u8; 32];
let mut sig2 = [0u8; 32];
sig1.copy_from_slice(&bytes[..32]);
sig2.copy_from_slice(&bytes[32..]);
Signature(sig1, sig2)
}
pub fn to_bytes(&self) -> [u8; 64] {
let mut res = [0u8; 64];
res[..32].copy_from_slice(&self.0);
res[32..].copy_from_slice(&self.1);
res
}
}
-3
View File
@@ -1,3 +0,0 @@
pub mod keys;
pub mod msg;
pub mod payment;
-30
View File
@@ -1,30 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::keys::PublicKey;
use crate::payment::LinkPaymentData;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
LinkPayment { data: LinkPaymentData },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GetPayments {
limit: Option<u32>,
start_after: Option<PublicKey>,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {}
@@ -1,73 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::keys::{PublicKey, Signature};
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct Payment {
verification_key: PublicKey,
gateway_identity: PublicKey,
bandwidth: u64,
}
impl Payment {
pub fn new(verification_key: PublicKey, gateway_identity: PublicKey, bandwidth: u64) -> Self {
Payment {
verification_key,
gateway_identity,
bandwidth,
}
}
pub fn verification_key(&self) -> PublicKey {
self.verification_key
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct LinkPaymentData {
pub verification_key: PublicKey,
pub gateway_identity: PublicKey,
pub bandwidth: u64,
pub signature: Signature,
}
impl LinkPaymentData {
pub fn new(
verification_key: [u8; 32],
gateway_identity: [u8; 32],
bandwidth: u64,
signature: [u8; 64],
) -> Self {
LinkPaymentData {
verification_key: PublicKey::new(verification_key),
gateway_identity: PublicKey::new(gateway_identity),
bandwidth,
signature: Signature::new(signature),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedPaymentResponse {
pub payments: Vec<Payment>,
pub per_page: usize,
pub start_next_after: Option<PublicKey>,
}
impl PagedPaymentResponse {
pub fn new(
payments: Vec<Payment>,
per_page: usize,
start_next_after: Option<PublicKey>,
) -> Self {
PagedPaymentResponse {
payments,
per_page,
start_next_after,
}
}
}
+2 -10
View File
@@ -8,18 +8,10 @@ edition = "2018"
[dependencies]
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch = "0.14.1-updatedk256" }
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256" }
#cosmwasm-std = { version = "0.14.1" }
serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1"
schemars = "0.8"
ts-rs = { version = "5.1", optional = true }
thiserror = "1.0"
network-defaults = { path = "../network-defaults" }
fixed = { version = "1.1", features = ["serde"] }
az = "1.1"
log = "0.4.14"
[features]
default = []
ts-rs = "3.0"
+42
View File
@@ -120,6 +120,48 @@ impl PagedReverseMixDelegationsResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedGatewayDelegationsResponse {
pub node_identity: IdentityKey,
pub delegations: Vec<Delegation>,
pub start_next_after: Option<Addr>,
}
impl PagedGatewayDelegationsResponse {
pub fn new(
node_identity: IdentityKey,
delegations: Vec<Delegation>,
start_next_after: Option<Addr>,
) -> Self {
PagedGatewayDelegationsResponse {
node_identity,
delegations,
start_next_after,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedReverseGatewayDelegationsResponse {
pub delegation_owner: Addr,
pub delegated_nodes: Vec<IdentityKey>,
pub start_next_after: Option<IdentityKey>,
}
impl PagedReverseGatewayDelegationsResponse {
pub fn new(
delegation_owner: Addr,
delegated_nodes: Vec<IdentityKey>,
start_next_after: Option<IdentityKey>,
) -> Self {
PagedReverseGatewayDelegationsResponse {
delegation_owner,
delegated_nodes,
start_next_after,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedAllDelegationsResponse<T> {
pub delegations: Vec<UnpackedDelegation<T>>,
-9
View File
@@ -1,9 +0,0 @@
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum MixnetContractError {
#[error("Overflow Error")]
OverflowError(#[from] cosmwasm_std::OverflowError),
#[error("reward_blockstamp field not set, set_reward_blockstamp must be called before attempting to issue rewards")]
BlockstampNotSet,
}
+48 -13
View File
@@ -2,14 +2,16 @@
#![allow(clippy::field_reassign_with_default)]
use crate::{IdentityKey, SphinxKey};
use cosmwasm_std::{Addr, Coin};
use cosmwasm_std::{coin, Addr, Coin};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt::Display;
use ts_rs::TS;
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
use crate::current_block_height;
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema, TS)]
pub struct Gateway {
pub host: String,
pub mix_port: u16,
@@ -24,7 +26,9 @@ pub struct Gateway {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct GatewayBond {
pub bond_amount: Coin,
pub total_delegation: Coin,
pub owner: Addr,
#[serde(default = "current_block_height")]
pub block_height: u64,
pub gateway: Gateway,
}
@@ -32,6 +36,7 @@ pub struct GatewayBond {
impl GatewayBond {
pub fn new(bond_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self {
GatewayBond {
total_delegation: coin(0, &bond_amount.denom),
bond_amount,
owner,
block_height,
@@ -59,11 +64,27 @@ impl GatewayBond {
impl PartialOrd for GatewayBond {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
// first remove invalid cases
if self.bond_amount.denom != self.total_delegation.denom {
return None;
}
if other.bond_amount.denom != other.total_delegation.denom {
return None;
}
if self.bond_amount.denom != other.bond_amount.denom {
return None;
}
// try to order by total bond
// try to order by total bond + delegation
let total_cmp = (self.bond_amount.amount + self.total_delegation.amount)
.partial_cmp(&(self.bond_amount.amount + self.total_delegation.amount))?;
if total_cmp != Ordering::Equal {
return Some(total_cmp);
}
// then if those are equal, prefer higher bond over delegation
let bond_cmp = self
.bond_amount
.amount
@@ -72,6 +93,15 @@ impl PartialOrd for GatewayBond {
return Some(bond_cmp);
}
// then look at delegation (I'm not sure we can get here, but better safe than sorry)
let delegation_cmp = self
.total_delegation
.amount
.partial_cmp(&other.total_delegation.amount)?;
if delegation_cmp != Ordering::Equal {
return Some(delegation_cmp);
}
// then check block height
let height_cmp = self.block_height.partial_cmp(&other.block_height)?;
if height_cmp != Ordering::Equal {
@@ -145,19 +175,20 @@ mod tests {
#[test]
fn gateway_bond_partial_ord() {
let _150foos = Coin::new(150, "foo");
let _140foos = Coin::new(140, "foo");
let _50foos = Coin::new(50, "foo");
let _0foos = Coin::new(0, "foo");
let gate1 = GatewayBond {
bond_amount: _150foos.clone(),
total_delegation: _50foos.clone(),
owner: Addr::unchecked("foo1"),
block_height: 100,
gateway: gateway_fixture(),
};
let gate2 = GatewayBond {
bond_amount: _150foos,
bond_amount: _150foos.clone(),
total_delegation: _50foos.clone(),
owner: Addr::unchecked("foo2"),
block_height: 120,
gateway: gateway_fixture(),
@@ -165,13 +196,15 @@ mod tests {
let gate3 = GatewayBond {
bond_amount: _50foos,
total_delegation: _150foos.clone(),
owner: Addr::unchecked("foo3"),
block_height: 120,
gateway: gateway_fixture(),
};
let gate4 = GatewayBond {
bond_amount: _140foos,
bond_amount: _150foos.clone(),
total_delegation: _0foos.clone(),
owner: Addr::unchecked("foo4"),
block_height: 120,
gateway: gateway_fixture(),
@@ -179,19 +212,21 @@ mod tests {
let gate5 = GatewayBond {
bond_amount: _0foos,
total_delegation: _150foos,
owner: Addr::unchecked("foo5"),
block_height: 120,
gateway: gateway_fixture(),
};
// summary:
// gate1: 150bond, foo1, 100
// gate2: 150bond, foo2, 120
// gate3: 50bond, foo3, 120
// gate4: 140bond, foo4, 120
// gate5: 0bond, foo5, 120
// gate1: 150bond + 50delegation, foo1, 100
// gate2: 150bond + 50delegation, foo2, 120
// gate3: 50bond + 150delegation, foo3, 120
// gate4: 150bond + 0delegation, foo4, 120
// gate5: 0bond + 150delegation, foo5, 120
// highest total bond is used
// highest total bond+delegation is used
// then bond followed by delegation
// finally just the rest of the fields
// gate1 has higher total than gate4 or gate5
+11 -6
View File
@@ -2,20 +2,25 @@
// SPDX-License-Identifier: Apache-2.0
mod delegation;
pub mod error;
mod gateway;
pub mod mixnode;
mod mixnode;
mod msg;
mod types;
pub const MIXNODE_DELEGATORS_PAGE_LIMIT: usize = 250;
pub use cosmwasm_std::{Addr, Coin};
pub use delegation::{
Delegation, PagedAllDelegationsResponse, PagedMixDelegationsResponse,
Delegation, PagedAllDelegationsResponse, PagedGatewayDelegationsResponse,
PagedMixDelegationsResponse, PagedReverseGatewayDelegationsResponse,
PagedReverseMixDelegationsResponse, RawDelegationData, UnpackedDelegation,
};
pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse};
pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse};
pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
pub use types::*;
pub use types::{IdentityKey, IdentityKeyRef, LayerDistribution, SphinxKey, StateParams};
use std::sync::atomic::{AtomicU64, Ordering};
pub static CURRENT_BLOCK_HEIGHT: AtomicU64 = AtomicU64::new(0);
pub fn current_block_height() -> u64 {
CURRENT_BLOCK_HEIGHT.load(Ordering::Relaxed)
}
+6 -311
View File
@@ -2,24 +2,17 @@
#![allow(clippy::field_reassign_with_default)]
use crate::{IdentityKey, SphinxKey};
use az::CheckedCast;
use cosmwasm_std::{coin, Addr, Coin, Uint128};
use log::error;
use network_defaults::{DEFAULT_OPERATOR_EPOCH_COST, DEFAULT_PROFIT_MARGIN};
use cosmwasm_std::{coin, Addr, Coin};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::cmp::Ordering;
use std::fmt::Display;
use ts_rs::TS;
type U128 = fixed::types::U75F53; // u128 with 18 significant digits
use crate::current_block_height;
fixed::const_fixed_from_int! {
const ONE: U128 = 1;
}
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema, TS)]
pub struct MixNode {
pub host: String,
pub mix_port: u16,
@@ -32,17 +25,7 @@ pub struct MixNode {
}
#[derive(
Copy,
Clone,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize_repr,
Deserialize_repr,
JsonSchema,
Copy, Clone, Debug, Serialize_repr, PartialEq, PartialOrd, Deserialize_repr, JsonSchema,
)]
#[repr(u8)]
pub enum Layer {
@@ -52,190 +35,15 @@ pub enum Layer {
Three = 3,
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct NodeRewardParams {
period_reward_pool: Uint128,
k: Uint128,
reward_blockstamp: u64,
circulating_supply: Uint128,
uptime: Uint128,
sybil_resistance_percent: u8,
}
impl NodeRewardParams {
pub fn new(
period_reward_pool: u128,
k: u128,
reward_blockstamp: u64,
circulating_supply: u128,
uptime: u128,
sybil_resistance_percent: u8,
) -> NodeRewardParams {
NodeRewardParams {
period_reward_pool: Uint128(period_reward_pool),
k: Uint128(k),
reward_blockstamp,
circulating_supply: Uint128(circulating_supply),
uptime: Uint128(uptime),
sybil_resistance_percent,
}
}
pub fn performance(&self) -> U128 {
U128::from_num(self.uptime.u128()) / U128::from_num(100)
}
pub fn operator_cost(&self) -> U128 {
U128::from_num(self.uptime.u128() / 100u128 * DEFAULT_OPERATOR_EPOCH_COST as u128)
}
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
self.reward_blockstamp = blockstamp;
}
pub fn period_reward_pool(&self) -> u128 {
self.period_reward_pool.u128()
}
pub fn k(&self) -> u128 {
self.k.u128()
}
pub fn circulating_supply(&self) -> u128 {
self.circulating_supply.u128()
}
pub fn reward_blockstamp(&self) -> u64 {
self.reward_blockstamp
}
pub fn uptime(&self) -> u128 {
self.uptime.u128()
}
pub fn one_over_k(&self) -> U128 {
ONE / U128::from_num(self.k.u128())
}
pub fn alpha(&self) -> U128 {
U128::from_num(self.sybil_resistance_percent) / U128::from_num(100)
}
}
// cosmwasm's limited serde doesn't work with U128 directly
#[allow(non_snake_case)]
pub mod fixed_U128_as_string {
use super::U128;
use serde::de::Error;
use serde::Deserialize;
use std::str::FromStr;
pub fn serialize<S>(val: &U128, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s = (*val).to_string();
serializer.serialize_str(&s)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<U128, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
U128::from_str(&s).map_err(|err| {
D::Error::custom(format!(
"failed to deserialize U128 with its string representation - {}",
err
))
})
}
}
// everything required to reward delegator of given mixnode
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
pub struct DelegatorRewardParams {
node_reward_params: NodeRewardParams,
// to be completely honest I don't understand all consequences of using `#[schemars(with = "String")]`
// for U128 here, but it seems that CosmWasm is using the same attribute for their Uint128
#[schemars(with = "String")]
#[serde(with = "fixed_U128_as_string")]
sigma: U128,
#[schemars(with = "String")]
#[serde(with = "fixed_U128_as_string")]
profit_margin: U128,
#[schemars(with = "String")]
#[serde(with = "fixed_U128_as_string")]
node_profit: U128,
}
impl DelegatorRewardParams {
pub fn new(mixnode_bond: &MixNodeBond, node_reward_params: NodeRewardParams) -> Self {
DelegatorRewardParams {
sigma: mixnode_bond.sigma(&node_reward_params),
profit_margin: mixnode_bond.profit_margin(),
node_profit: mixnode_bond.node_profit(&node_reward_params),
node_reward_params,
}
}
pub fn determine_delegation_reward(&self, delegation_amount: Uint128) -> u128 {
// change all values into their fixed representations
let delegation_amount = U128::from_num(delegation_amount.u128());
let circulating_supply = U128::from_num(self.node_reward_params.circulating_supply());
let scaled_delegation_amount = delegation_amount / circulating_supply;
let delegator_reward =
(ONE - self.profit_margin) * scaled_delegation_amount / self.sigma * self.node_profit;
let reward = delegator_reward.max(U128::ZERO);
if let Some(int_reward) = reward.checked_cast() {
int_reward
} else {
error!(
"Could not cast delegator reward ({}) to u128, returning 0",
reward,
);
0u128
}
}
pub fn node_reward_params(&self) -> &NodeRewardParams {
&self.node_reward_params
}
}
#[derive(Debug)]
pub struct NodeRewardResult {
reward: U128,
lambda: U128,
sigma: U128,
}
impl NodeRewardResult {
pub fn reward(&self) -> U128 {
self.reward
}
pub fn lambda(&self) -> U128 {
self.lambda
}
pub fn sigma(&self) -> U128 {
self.sigma
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixNodeBond {
pub bond_amount: Coin,
pub total_delegation: Coin,
pub owner: Addr,
pub layer: Layer,
#[serde(default = "current_block_height")]
pub block_height: u64,
pub mix_node: MixNode,
pub profit_margin_percent: Option<u8>,
}
impl MixNodeBond {
@@ -245,7 +53,6 @@ impl MixNodeBond {
layer: Layer,
block_height: u64,
mix_node: MixNode,
profit_margin_percent: Option<u8>,
) -> Self {
MixNodeBond {
total_delegation: coin(0, &bond_amount.denom),
@@ -254,15 +61,9 @@ impl MixNodeBond {
layer,
block_height,
mix_node,
profit_margin_percent,
}
}
pub fn profit_margin(&self) -> U128 {
U128::from_num(self.profit_margin_percent.unwrap_or(DEFAULT_PROFIT_MARGIN))
/ U128::from_num(100)
}
pub fn identity(&self) -> &String {
&self.mix_node.identity_key
}
@@ -278,107 +79,6 @@ impl MixNodeBond {
pub fn mix_node(&self) -> &MixNode {
&self.mix_node
}
pub fn total_stake(&self) -> Option<u128> {
if self.bond_amount.denom != self.total_delegation.denom {
None
} else {
Some(self.bond_amount.amount.u128() + self.total_delegation.amount.u128())
}
}
pub fn total_delegation(&self) -> Coin {
self.total_delegation.clone()
}
pub fn bond_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
U128::from_num(self.bond_amount().amount.u128()) / U128::from_num(circulating_supply)
}
pub fn total_stake_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
U128::from_num(self.bond_amount().amount.u128() + self.total_delegation().amount.u128())
/ U128::from_num(circulating_supply)
}
pub fn lambda(&self, params: &NodeRewardParams) -> U128 {
// Ratio of a bond to the token circulating supply
let bond_to_circulating_supply_ratio =
self.bond_to_circulating_supply(params.circulating_supply());
bond_to_circulating_supply_ratio.min(params.one_over_k())
}
pub fn sigma(&self, params: &NodeRewardParams) -> U128 {
// Ratio of a delegation to the the token circulating supply
let total_stake_to_circulating_supply_ratio =
self.total_stake_to_circulating_supply(params.circulating_supply());
total_stake_to_circulating_supply_ratio.min(params.one_over_k())
}
pub fn reward(&self, params: &NodeRewardParams) -> NodeRewardResult {
// Assuming uniform work distribution across the network this is one_over_k * k
let omega_k = ONE;
let lambda = self.lambda(params);
let sigma = self.sigma(params);
let reward = params.performance()
* params.period_reward_pool()
* (sigma * omega_k + params.alpha() * lambda * sigma * params.k())
/ (ONE + params.alpha());
NodeRewardResult {
reward,
lambda,
sigma,
}
}
pub fn node_profit(&self, params: &NodeRewardParams) -> U128 {
if self.reward(params).reward() < params.operator_cost() {
U128::from_num(0)
} else {
self.reward(params).reward() - params.operator_cost()
}
}
pub fn operator_reward(&self, params: &NodeRewardParams) -> u128 {
let reward = self.reward(params);
let profit = if reward.reward < params.operator_cost() {
U128::from_num(0)
} else {
reward.reward - params.operator_cost()
};
let operator_base_reward = reward.reward.min(params.operator_cost());
let operator_reward = (self.profit_margin()
+ (ONE - self.profit_margin()) * reward.lambda / reward.sigma)
* profit;
let reward = (operator_reward + operator_base_reward).max(U128::from_num(0));
if let Some(int_reward) = reward.checked_cast() {
int_reward
} else {
error!(
"Could not cast reward ({}) to u128, returning 0 - mixnode {}",
reward,
self.identity()
);
0u128
}
}
pub fn sigma_ratio(&self, params: &NodeRewardParams) -> U128 {
if self.total_stake_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
{
self.total_stake_to_circulating_supply(params.circulating_supply())
} else {
params.one_over_k()
}
}
pub fn reward_delegation(&self, delegation_amount: Uint128, params: &NodeRewardParams) -> u128 {
let reward_params = DelegatorRewardParams::new(self, *params);
reward_params.determine_delegation_reward(delegation_amount)
}
}
impl PartialOrd for MixNodeBond {
@@ -510,7 +210,6 @@ mod tests {
layer: Layer::One,
block_height: 100,
mix_node: mixnode_fixture(),
profit_margin_percent: Some(10),
};
let mix2 = MixNodeBond {
@@ -520,7 +219,6 @@ mod tests {
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
profit_margin_percent: Some(10),
};
let mix3 = MixNodeBond {
@@ -530,7 +228,6 @@ mod tests {
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
profit_margin_percent: Some(10),
};
let mix4 = MixNodeBond {
@@ -540,7 +237,6 @@ mod tests {
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
profit_margin_percent: Some(10),
};
let mix5 = MixNodeBond {
@@ -550,7 +246,6 @@ mod tests {
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
profit_margin_percent: Some(10),
};
// summary:
+28 -25
View File
@@ -1,7 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::NodeRewardParams;
use crate::StateParams;
use crate::{Gateway, IdentityKey, MixNode};
use cosmwasm_std::Addr;
@@ -32,29 +31,24 @@ pub enum ExecuteMsg {
mix_identity: IdentityKey,
},
BeginMixnodeRewarding {
// nonce of the current rewarding interval
rewarding_interval_nonce: u32,
DelegateToGateway {
gateway_identity: IdentityKey,
},
FinishMixnodeRewarding {
// nonce of the current rewarding interval
rewarding_interval_nonce: u32,
UndelegateFromGateway {
gateway_identity: IdentityKey,
},
RewardMixnodeV2 {
RewardMixnode {
identity: IdentityKey,
// percentage value in range 0-100
params: NodeRewardParams,
// nonce of the current rewarding interval
rewarding_interval_nonce: u32,
uptime: u32,
},
RewardNextMixDelegators {
mix_identity: IdentityKey,
// nonce of the current rewarding interval
rewarding_interval_nonce: u32,
RewardGateway {
identity: IdentityKey,
// percentage value in range 0-100
uptime: u32,
},
}
@@ -76,7 +70,6 @@ pub enum QueryMsg {
address: Addr,
},
StateParams {},
CurrentRewardingInterval {},
GetMixDelegations {
mix_identity: IdentityKey,
start_after: Option<Addr>,
@@ -95,15 +88,25 @@ pub enum QueryMsg {
mix_identity: IdentityKey,
address: Addr,
},
LayerDistribution {},
GetRewardPool {},
GetCirculatingSupply {},
GetEpochRewardPercent {},
GetSybilResistancePercent {},
GetRewardingStatus {
mix_identity: IdentityKey,
rewarding_interval_nonce: u32,
GetGatewayDelegations {
gateway_identity: IdentityKey,
start_after: Option<Addr>,
limit: Option<u32>,
},
GetAllGatewayDelegations {
start_after: Option<Vec<u8>>,
limit: Option<u32>,
},
GetReverseGatewayDelegations {
delegation_owner: Addr,
start_after: Option<IdentityKey>,
limit: Option<u32>,
},
GetGatewayDelegation {
gateway_identity: IdentityKey,
address: Addr,
},
LayerDistribution {},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+31 -49
View File
@@ -1,9 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::DelegatorRewardParams;
use crate::Layer;
use cosmwasm_std::Uint128;
use cosmwasm_std::{Decimal, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter};
@@ -27,76 +26,59 @@ impl LayerDistribution {
}
}
#[derive(Debug, Default, Serialize, Deserialize, Copy, Clone, Eq, PartialEq)]
pub struct RewardingIntervalResponse {
pub current_rewarding_interval_starting_block: u64,
pub current_rewarding_interval_nonce: u32,
pub rewarding_in_progress: bool,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct StateParams {
// so currently epoch_length is being unused and validator API performs rewarding
// based on its own epoch length config value. I guess that's fine for time being
// however, in the future, the contract constant should be controlling it instead.
// pub epoch_length: u32, // length of a rewarding epoch/interval, expressed in hours
pub epoch_length: u32, // length of an epoch, expressed in hours
pub minimum_mixnode_bond: Uint128, // minimum amount a mixnode must bond to get into the system
pub minimum_gateway_bond: Uint128, // minimum amount a gateway must bond to get into the system
// number of mixnode that are going to get rewarded during current rewarding interval (k_m)
// based on overall demand for private bandwidth-
pub mixnode_rewarded_set_size: u32,
// subset of rewarded mixnodes that are actively receiving mix traffic
// used to handle shorter-term (e.g. hourly) fluctuations of demand
pub mixnode_bond_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
pub gateway_bond_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
pub mixnode_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
pub gateway_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
pub mixnode_active_set_size: u32,
pub gateway_active_set_size: u32,
}
impl Display for StateParams {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Contract state parameters: [ ")?;
write!(f, "epoch length: {}; ", self.epoch_length)?;
write!(f, "minimum mixnode bond: {}; ", self.minimum_mixnode_bond)?;
write!(f, "minimum gateway bond: {}; ", self.minimum_gateway_bond)?;
write!(
f,
"mixnode rewarded set size: {}",
self.mixnode_rewarded_set_size
"mixnode bond reward rate: {}; ",
self.mixnode_bond_reward_rate
)?;
write!(
f,
"gateway bond reward rate: {}; ",
self.gateway_bond_reward_rate
)?;
write!(
f,
"mixnode delegation reward rate: {}; ",
self.mixnode_delegation_reward_rate
)?;
write!(
f,
"gateway delegation reward rate: {}; ",
self.gateway_delegation_reward_rate
)?;
write!(
f,
"mixnode active set size: {}",
self.mixnode_active_set_size
)?;
write!(
f,
"gateway active set size: {} ]",
self.gateway_active_set_size
)
}
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct RewardingResult {
pub operator_reward: Uint128,
pub total_delegator_reward: Uint128,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PendingDelegatorRewarding {
// keep track of the running rewarding results so we'd known how much was the operator and its delegators rewarded
pub running_results: RewardingResult,
pub next_start: String,
pub rewarding_params: DelegatorRewardParams,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum RewardingStatus {
Complete(RewardingResult),
PendingNextDelegatorPage(PendingDelegatorRewarding),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MixnodeRewardingStatusResponse {
pub status: Option<RewardingStatus>,
}
// type aliases for better reasoning about available data
pub type IdentityKey = String;
pub type IdentityKeyRef<'a> = &'a str;
-1
View File
@@ -7,7 +7,6 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hex-literal = "0.3.3"
serde = {version = "1.0", features = ["derive"]}
url = "2.2"
time = { version = "0.3", features = ["macros"] }
-132
View File
@@ -1,132 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// This should be modified whenever an updated Ethereum contract is uploaded
pub const ETH_JSON_ABI: &str = r#"
[
{
"inputs": [
{
"internalType": "contract ERC20Burnable",
"name": "_erc20",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "Bandwidth",
"type": "uint256"
},
{
"indexed": true,
"internalType": "uint256",
"name": "VerificationKey",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bytes",
"name": "SignedVerificationKey",
"type": "bytes"
}
],
"name": "Burned",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "verificationKey",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "signedVerificationKey",
"type": "bytes"
}
],
"name": "burnTokenForAccessCode",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "erc20",
"outputs": [
{
"internalType": "contract ERC20Burnable",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
"#;
+2 -28
View File
@@ -5,8 +5,6 @@ use std::time::Duration;
use time::OffsetDateTime;
use url::Url;
pub mod eth_contract;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ValidatorDetails {
// it is assumed those values are always valid since they're being provided in our defaults file
@@ -63,24 +61,7 @@ pub fn default_api_endpoints() -> Vec<Url> {
}
pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "punk10pyejy66429refv3g35g2t7am0was7yalwrzen";
pub const REWARDING_VALIDATOR_ADDRESS: &str = "punk1v9qauwdq5terag6uvfsdytcs2d0sdmfdy7hgk3";
/// How much bandwidth (in bytes) one token can buy
const BYTES_PER_TOKEN: u64 = 1024 * 1024 * 1024;
/// How many ERC20 tokens should be burned to buy bandwidth
pub const TOKENS_TO_BURN: u64 = 10;
/// Default bandwidth (in bytes) that we try to buy
pub const BANDWIDTH_VALUE: u64 = TOKENS_TO_BURN * BYTES_PER_TOKEN;
// Ethereum constants used for token bridge
pub const ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("9fEE3e28c17dbB87310A51F13C4fbf4331A6f102");
pub const ETH_MIN_BLOCK_DEPTH: usize = 7;
pub const COSMOS_CONTRACT_ADDRESS: &str = "punk1jld76tqw4wnpfenmay2xkv86nr3j0w426eka82";
// Name of the event triggered by the eth contract. If the event name is changed,
// this would also need to be changed; It is currently tested against the json abi
pub const ETH_EVENT_NAME: &str = "Burned";
pub const ETH_BURN_FUNCTION_NAME: &str = "burnTokenForAccessCode";
pub const NETWORK_MONITOR_ADDRESS: &str = "punk1v9qauwdq5terag6uvfsdytcs2d0sdmfdy7hgk3";
/// Defaults Cosmos Hub/ATOM path
pub const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0";
@@ -112,11 +93,4 @@ pub const VALIDATOR_API_VERSION: &str = "v1";
// REWARDING
pub const DEFAULT_FIRST_EPOCH_START: OffsetDateTime = time::macros::datetime!(2021-08-23 12:00 UTC);
pub const DEFAULT_EPOCH_LENGTH: Duration = Duration::from_secs(24 * 60 * 60 * 30); // 30 days
/// We'll be assuming a few more things, profit margin and cost function. Since we don't have relialable package measurement, we'll be using uptime. We'll also set the value of 1 Nym to 1 $, to be able to translate epoch costs to Nyms. We'll also assume a cost of 40$ per epoch(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms
pub const DEFAULT_OPERATOR_EPOCH_COST: u64 = 40_000_000; // 40$/(30 days) at 1 Nym == 1$
// TODO: is there a way to get this from the chain
pub const TOTAL_SUPPLY: u128 = 1_000_000_000_000_000;
pub const DEFAULT_PROFIT_MARGIN: u8 = 10;
pub const DEFAULT_EPOCH_LENGTH: Duration = Duration::from_secs(24 * 60 * 60); // 24h
-43
View File
@@ -1,43 +0,0 @@
[package]
name = "nymcoconut"
version = "0.5.0"
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>", "Ania Piotrowska <ania@nymtech.net>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
itertools = "0.10"
digest = "0.9"
rand = "0.8"
thiserror = "1.0"
serde = "1.0"
serde_derive = "1.0"
bs58 = "0.4.0"
sha2 = "0.9"
[dependencies.ff]
version = "0.10"
default-features = false
[dependencies.group]
version = "0.10"
default-features = false
[dev-dependencies]
criterion = { version="0.3", features=["html_reports"] }
doc-comment = "0.3"
[dev-dependencies.bincode]
version = "1"
#[[bench]]
#name = "benchmarks"
#harness = false
[features]
default = []
[target.'cfg(target_env = "wasm32-unknown-unknown")'.dependencies]
getrandom = { version="0.2", features=["js"] }
-322
View File
@@ -1,322 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use core::ops::{Deref, Mul};
use std::convert::TryFrom;
use std::convert::TryInto;
use bls12_381::{G1Projective, Scalar};
use group::Curve;
use serde_derive::{Deserialize, Serialize};
use crate::error::{CoconutError, Result};
use crate::scheme::setup::Parameters;
use crate::traits::{Base58, Bytable};
use crate::utils::{try_deserialize_g1_projective, try_deserialize_scalar};
/// Type alias for the ephemeral key generated during ElGamal encryption
pub type EphemeralKey = Scalar;
/// Two G1 points representing ElGamal ciphertext
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Ciphertext(pub(crate) G1Projective, pub(crate) G1Projective);
impl TryFrom<&[u8]> for Ciphertext {
type Error = CoconutError;
fn try_from(bytes: &[u8]) -> Result<Ciphertext> {
if bytes.len() != 96 {
return Err(CoconutError::Deserialization(format!(
"Ciphertext must be exactly 96 bytes, got {}",
bytes.len()
)));
}
let c1_bytes: &[u8; 48] = &bytes[..48].try_into().unwrap();
let c2_bytes: &[u8; 48] = &bytes[48..].try_into().unwrap();
let c1 = try_deserialize_g1_projective(
c1_bytes,
CoconutError::Deserialization("Failed to deserialize compressed c1".to_string()),
)?;
let c2 = try_deserialize_g1_projective(
c2_bytes,
CoconutError::Deserialization("Failed to deserialize compressed c2".to_string()),
)?;
Ok(Ciphertext(c1, c2))
}
}
impl Ciphertext {
pub fn c1(&self) -> &G1Projective {
&self.0
}
pub fn c2(&self) -> &G1Projective {
&self.1
}
pub fn to_bytes(&self) -> [u8; 96] {
let mut bytes = [0u8; 96];
bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed());
bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Ciphertext> {
Ciphertext::try_from(bytes)
}
}
/// PrivateKey used in the ElGamal encryption scheme to recover the plaintext
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct PrivateKey(pub(crate) Scalar);
impl PrivateKey {
/// Decrypt takes the ElGamal encryption of a message and returns a point on the G1 curve
/// that represents original h^m.
pub fn decrypt(&self, ciphertext: &Ciphertext) -> G1Projective {
let (c1, c2) = &(ciphertext.0, ciphertext.1);
// (gamma^k * h^m) / (g1^{d * k}) | note: gamma = g1^d
c2 - c1 * self.0
}
pub fn public_key(&self, params: &Parameters) -> PublicKey {
PublicKey(params.gen1() * self.0)
}
pub fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
}
pub fn from_bytes(bytes: &[u8; 32]) -> Result<PrivateKey> {
try_deserialize_scalar(
bytes,
CoconutError::Deserialization(
"Failed to deserialize ElGamal private key - it was not in the canonical form"
.to_string(),
),
)
.map(PrivateKey)
}
}
impl Bytable for PrivateKey {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
PrivateKey::from_bytes(slice.try_into().unwrap())
}
}
impl Base58 for PrivateKey {}
// TODO: perhaps be more explicit and apart from gamma also store generator and group order?
/// PublicKey used in the ElGamal encryption scheme to produce the ciphertext
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct PublicKey(G1Projective);
impl PublicKey {
/// Encrypt encrypts the given message in the form of h^m,
/// where h is a point on the G1 curve using the given public key.
/// The random k is returned alongside the encryption
/// as it is required by the Coconut Scheme to create proofs of knowledge.
pub fn encrypt(
&self,
params: &Parameters,
h: &G1Projective,
msg: &Scalar,
) -> (Ciphertext, EphemeralKey) {
let k = params.random_scalar();
// c1 = g1^k
let c1 = params.gen1() * k;
// c2 = gamma^k * h^m
let c2 = self.0 * k + h * msg;
(Ciphertext(c1, c2), k)
}
pub fn to_bytes(&self) -> [u8; 48] {
self.to_byte_vec().try_into().unwrap()
}
pub fn from_bytes(bytes: &[u8; 48]) -> Result<PublicKey> {
Ok(PublicKey::try_from(bytes.to_vec().as_slice()).unwrap())
}
}
impl Bytable for PublicKey {
fn to_byte_vec(&self) -> Vec<u8> {
self.0.to_affine().to_compressed().into()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
Ok(PublicKey::from_bytes(slice.try_into().unwrap()).unwrap())
}
}
impl TryFrom<&[u8]> for PublicKey {
type Error = CoconutError;
fn try_from(slice: &[u8]) -> Result<PublicKey> {
try_deserialize_g1_projective(
slice.try_into().unwrap(),
CoconutError::Deserialization(
"Failed to deserialize compressed ElGamal public key".to_string(),
),
)
.map(PublicKey)
}
}
impl Base58 for PublicKey {}
impl Deref for PublicKey {
type Target = G1Projective;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a, 'b> Mul<&'b Scalar> for &'a PublicKey {
type Output = G1Projective;
fn mul(self, rhs: &'b Scalar) -> Self::Output {
self.0 * rhs
}
}
#[derive(Serialize, Deserialize)]
/// A convenient wrapper for both keys of the ElGamal keypair
pub struct ElGamalKeyPair {
private_key: PrivateKey,
public_key: PublicKey,
}
impl ElGamalKeyPair {
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
pub fn private_key(&self) -> &PrivateKey {
&self.private_key
}
}
/// Generate a fresh ElGamal keypair using the group generator specified by the provided [Parameters]
pub fn elgamal_keygen(params: &Parameters) -> ElGamalKeyPair {
let private_key = params.random_scalar();
let gamma = params.gen1() * private_key;
ElGamalKeyPair {
private_key: PrivateKey(private_key),
public_key: PublicKey(gamma),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn keygen() {
let params = Parameters::default();
let keypair = super::elgamal_keygen(&params);
let expected = params.gen1() * keypair.private_key.0;
let gamma = keypair.public_key.0;
assert_eq!(
expected, gamma,
"Public key, gamma, should be equal to g1^d, where d is the private key"
);
}
#[test]
fn encryption() {
let params = Parameters::default();
let keypair = super::elgamal_keygen(&params);
let r = params.random_scalar();
let h = params.gen1() * r;
let m = params.random_scalar();
let (ciphertext, ephemeral_key) = keypair.public_key.encrypt(&params, &h, &m);
let expected_c1 = params.gen1() * ephemeral_key;
assert_eq!(expected_c1, ciphertext.0, "c1 should be equal to g1^k");
let expected_c2 = keypair.public_key.0 * ephemeral_key + h * m;
assert_eq!(
expected_c2, ciphertext.1,
"c2 should be equal to gamma^k * h^m"
);
}
#[test]
fn decryption() {
let params = Parameters::default();
let keypair = super::elgamal_keygen(&params);
let r = params.random_scalar();
let h = params.gen1() * r;
let m = params.random_scalar();
let (ciphertext, _) = keypair.public_key.encrypt(&params, &h, &m);
let dec = keypair.private_key.decrypt(&ciphertext);
let expected = h * m;
assert_eq!(
expected, dec,
"after ElGamal decryption, original h^m should be obtained"
);
}
#[test]
fn private_key_bytes_roundtrip() {
let params = Parameters::default();
let private_key = PrivateKey(params.random_scalar());
let bytes = private_key.to_bytes();
// also make sure it is equivalent to the internal scalar's bytes
assert_eq!(private_key.0.to_bytes(), bytes);
assert_eq!(private_key, PrivateKey::from_bytes(&bytes).unwrap())
}
#[test]
fn public_key_bytes_roundtrip() {
let params = Parameters::default();
let r = params.random_scalar();
let public_key = PublicKey(params.gen1() * r);
let bytes = public_key.to_bytes();
// also make sure it is equivalent to the internal g1 compressed bytes
assert_eq!(public_key.0.to_affine().to_compressed(), bytes);
assert_eq!(public_key, PublicKey::from_bytes(&bytes).unwrap())
}
#[test]
fn ciphertext_bytes_roundtrip() {
let params = Parameters::default();
let r = params.random_scalar();
let s = params.random_scalar();
let ciphertext = Ciphertext(params.gen1() * r, params.gen1() * s);
let bytes = ciphertext.to_bytes();
// also make sure it is equivalent to the internal g1 compressed bytes concatenated
let expected_bytes = [
ciphertext.0.to_affine().to_compressed(),
ciphertext.1.to_affine().to_compressed(),
]
.concat();
assert_eq!(expected_bytes, bytes);
assert_eq!(ciphertext, Ciphertext::try_from(&bytes[..]).unwrap())
}
}
-53
View File
@@ -1,53 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use thiserror::Error;
/// A `Result` alias where the `Err` case is `coconut_rs::Error`.
pub type Result<T> = std::result::Result<T, CoconutError>;
#[derive(Error, Debug)]
pub enum CoconutError {
#[error("Setup error: {0}")]
Setup(String),
#[error("encountered error during keygen")]
Keygen,
#[error("Issuance related error: {0}")]
Issuance(String),
#[error("Tried to prepare blind sign request for higher than specified number of attributes (max: {}, requested: {})", max, requested)]
IssuanceMaxAttributes { max: usize, requested: usize },
#[error("Interpolation error: {0}")]
Interpolation(String),
#[error("Aggregation error: {0}")]
Aggregation(String),
#[error("Unblind error: {0}")]
Unblind(String),
#[error("Verification error: {0}")]
Verification(String),
#[error("Deserialization error: {0}")]
Deserialization(String),
#[error(
"Deserailization error, expected at least {} bytes, got {}",
min,
actual
)]
DeserializationMinLength { min: usize, actual: usize },
#[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {} or {modulus_target} % {modulus} == 0")]
DeserializationInvalidLength {
actual: usize,
target: usize,
modulus_target: usize,
modulus: usize,
object: String,
},
}
-15
View File
@@ -1,15 +0,0 @@
use crate::{BlindSignRequest, BlindedSignature, Bytable, Theta};
macro_rules! impl_clone {
($struct:ident) => {
impl Clone for $struct {
fn clone(&self) -> Self {
Self::try_from_byte_slice(&self.to_byte_vec()).unwrap()
}
}
};
}
impl_clone!(BlindSignRequest);
impl_clone!(BlindedSignature);
impl_clone!(Theta);
-2
View File
@@ -1,2 +0,0 @@
mod clone;
mod serde;
-56
View File
@@ -1,56 +0,0 @@
use crate::elgamal::PrivateKey;
use crate::scheme::SecretKey;
use crate::{
Base58, BlindSignRequest, BlindedSignature, PublicKey, Signature, Theta, VerificationKey,
};
use serde::de::Unexpected;
use serde::{de::Error, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
macro_rules! impl_serde {
($struct:ident, $visitor:ident) => {
pub struct $visitor {}
impl Serialize for $struct {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_bs58())
}
}
impl<'de> Visitor<'de> for $visitor {
type Value = $struct;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "A base58 encoded struct")
}
fn visit_str<E: Error>(self, s: &str) -> Result<Self::Value, E> {
match $struct::try_from_bs58(s) {
Ok(x) => Ok(x),
Err(_) => Err(Error::invalid_value(Unexpected::Str(s), &self)),
}
}
}
impl<'de> Deserialize<'de> for $struct {
fn deserialize<D>(deserializer: D) -> Result<$struct, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str($visitor {})
}
}
};
}
impl_serde!(SecretKey, V1);
impl_serde!(VerificationKey, V2);
impl_serde!(PublicKey, V3);
impl_serde!(PrivateKey, V4);
impl_serde!(BlindSignRequest, V5);
impl_serde!(BlindedSignature, V6);
impl_serde!(Signature, V7);
impl_serde!(Theta, V8);
-57
View File
@@ -1,57 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::convert::TryInto;
use bls12_381::Scalar;
pub use elgamal::elgamal_keygen;
pub use elgamal::ElGamalKeyPair;
pub use elgamal::PublicKey;
pub use error::CoconutError;
pub use scheme::aggregation::aggregate_signature_shares;
pub use scheme::aggregation::aggregate_verification_keys;
pub use scheme::issuance::blind_sign;
pub use scheme::issuance::prepare_blind_sign;
pub use scheme::issuance::BlindSignRequest;
pub use scheme::keygen::ttp_keygen;
pub use scheme::keygen::KeyPair;
pub use scheme::keygen::VerificationKey;
pub use scheme::setup::setup;
pub use scheme::setup::Parameters;
pub use scheme::verification::prove_bandwidth_credential;
pub use scheme::verification::verify_credential;
pub use scheme::verification::Theta;
pub use scheme::BlindedSignature;
pub use scheme::Signature;
pub use scheme::SignatureShare;
pub use traits::Base58;
pub use utils::hash_to_scalar;
use crate::traits::Bytable;
pub mod elgamal;
mod error;
mod impls;
mod proofs;
mod scheme;
#[cfg(test)]
mod tests;
mod traits;
mod utils;
pub type Attribute = Scalar;
pub type PrivateAttribute = Attribute;
pub type PublicAttribute = Attribute;
impl Bytable for Attribute {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, CoconutError> {
Ok(Attribute::from_bytes(slice.try_into().unwrap()).unwrap())
}
}
impl Base58 for Attribute {}
-663
View File
@@ -1,663 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// TODO: look at https://crates.io/crates/merlin to perhaps use it instead?
use std::borrow::Borrow;
use std::convert::TryInto;
use bls12_381::{G1Projective, G2Projective, Scalar};
use digest::generic_array::typenum::Unsigned;
use digest::Digest;
use group::GroupEncoding;
use itertools::izip;
use sha2::Sha256;
use crate::elgamal::Ciphertext;
use crate::error::{CoconutError, Result};
use crate::scheme::setup::Parameters;
use crate::scheme::VerificationKey;
use crate::utils::{hash_g1, try_deserialize_scalar, try_deserialize_scalar_vec};
use crate::{elgamal, Attribute, ElGamalKeyPair};
// as per the reference python implementation
type ChallengeDigest = Sha256;
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ProofCmCs {
challenge: Scalar,
response_opening: Scalar,
response_private_elgamal_key: Scalar,
response_keys: Vec<Scalar>,
response_attributes: Vec<Scalar>,
}
// note: this is slightly different from the reference python implementation
// as we omit the unnecessary string conversion. Instead we concatenate byte
// representations together and hash that.
// note2: G1 and G2 elements are using their compressed representations
// and as per the bls12-381 library all elements are using big-endian form
/// Generates a Scalar [or Fp] challenge by hashing a number of elliptic curve points.
fn compute_challenge<D, I, B>(iter: I) -> Scalar
where
D: Digest,
I: Iterator<Item = B>,
B: AsRef<[u8]>,
{
let mut h = D::new();
for point_representation in iter {
h.update(point_representation);
}
let digest = h.finalize();
// TODO: I don't like the 0 padding here (though it's what we've been using before,
// but we never had a security audit anyway...)
// instead we could maybe use the `from_bytes` variant and adding some suffix
// when computing the digest until we produce a valid scalar.
let mut bytes = [0u8; 64];
let pad_size = 64usize
.checked_sub(D::OutputSize::to_usize())
.unwrap_or_default();
bytes[pad_size..].copy_from_slice(&digest);
Scalar::from_bytes_wide(&bytes)
}
fn produce_response(witness: &Scalar, challenge: &Scalar, secret: &Scalar) -> Scalar {
witness - challenge * secret
}
// note: it's caller's responsibility to ensure witnesses.len() = secrets.len()
fn produce_responses<S>(witnesses: &[Scalar], challenge: &Scalar, secrets: &[S]) -> Vec<Scalar>
where
S: Borrow<Scalar>,
{
debug_assert_eq!(witnesses.len(), secrets.len());
witnesses
.iter()
.zip(secrets.iter())
.map(|(w, x)| produce_response(w, challenge, x.borrow()))
.collect()
}
impl ProofCmCs {
/// Construct non-interactive zero-knowledge proof of correctness of the ciphertexts and the commitment
/// using the Fiat-Shamir heuristic.
pub(crate) fn construct(
params: &Parameters,
elgamal_keypair: &ElGamalKeyPair,
ephemeral_keys: &[elgamal::EphemeralKey],
commitment: &G1Projective,
commitment_opening: &Scalar,
private_attributes: &[Attribute],
priv_attributes_ciphertexts: &[Ciphertext],
) -> Self {
// note: this is only called from `prepare_blind_sign` that already checks
// whether private attributes are non-empty and whether we don't have too many
// attributes in total to sign.
// we also know, due to the single call place, that ephemeral_keys.len() == private_attributes.len()
// witness creation
let witness_commitment_opening = params.random_scalar();
let witness_private_elgamal_key = params.random_scalar();
let witness_keys = params.n_random_scalars(ephemeral_keys.len());
let witness_attributes = params.n_random_scalars(private_attributes.len());
// recompute h
let h = hash_g1(commitment.to_bytes());
let hs_bytes = params
.gen_hs()
.iter()
.map(|h| h.to_bytes())
.collect::<Vec<_>>();
let g1 = params.gen1();
// compute commitments
let commitment_private_key_elgamal = g1 * witness_private_elgamal_key;
// Aw[i] = (wk[i] * g1)
let commitment_keys1_bytes = witness_keys
.iter()
.map(|wk_i| g1 * wk_i)
.map(|witness| witness.to_bytes())
.collect::<Vec<_>>();
// Bw[i] = (wm[i] * h) + (wk[i] * gamma)
let commitment_keys2_bytes = witness_keys
.iter()
.zip(witness_attributes.iter())
.map(|(wk_i, wm_i)| elgamal_keypair.public_key() * wk_i + h * wm_i)
.map(|witness| witness.to_bytes())
.collect::<Vec<_>>();
// zkp commitment for the attributes commitment cm
// Ccm = (wr * g1) + (wm[0] * hs[0]) + ... + (wm[i] * hs[i])
let commitment_attributes = g1 * witness_commitment_opening
+ witness_attributes
.iter()
.zip(params.gen_hs().iter())
.map(|(wm_i, hs_i)| hs_i * wm_i)
.sum::<G1Projective>();
let ciphertexts_bytes = priv_attributes_ciphertexts
.iter()
.map(|c| c.to_bytes())
.collect::<Vec<_>>();
// compute challenge
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(params.gen1().to_bytes().as_ref())
.chain(hs_bytes.iter().map(|hs| hs.as_ref()))
.chain(std::iter::once(h.to_bytes().as_ref()))
.chain(std::iter::once(
elgamal_keypair.public_key().to_bytes().as_ref(),
))
.chain(std::iter::once(commitment.to_bytes().as_ref()))
.chain(std::iter::once(commitment_attributes.to_bytes().as_ref()))
.chain(std::iter::once(
commitment_private_key_elgamal.to_bytes().as_ref(),
))
.chain(commitment_keys1_bytes.iter().map(|aw| aw.as_ref()))
.chain(commitment_keys2_bytes.iter().map(|bw| bw.as_ref()))
.chain(ciphertexts_bytes.iter().map(|c| c.as_ref())),
);
// Responses
let response_opening =
produce_response(&witness_commitment_opening, &challenge, commitment_opening);
let response_private_elgamal_key = produce_response(
&witness_private_elgamal_key,
&challenge,
&elgamal_keypair.private_key().0,
);
let response_keys = produce_responses(&witness_keys, &challenge, ephemeral_keys);
let response_attributes = produce_responses(
&witness_attributes,
&challenge,
&private_attributes.iter().collect::<Vec<_>>(),
);
ProofCmCs {
challenge,
response_opening,
response_private_elgamal_key,
response_keys,
response_attributes,
}
}
pub(crate) fn verify(
&self,
params: &Parameters,
pub_key: &elgamal::PublicKey,
commitment: &G1Projective,
attributes_ciphertexts: &[elgamal::Ciphertext],
) -> bool {
if self.response_keys.len() != attributes_ciphertexts.len() {
return false;
}
// recompute h
let h = hash_g1(commitment.to_bytes());
let g1 = params.gen1();
let hs_bytes = params
.gen_hs()
.iter()
.map(|h| h.to_bytes())
.collect::<Vec<_>>();
// recompute witnesses commitments
let commitment_private_key_elgamal =
pub_key * &self.challenge + g1 * self.response_private_elgamal_key;
// Aw[i] = (c * c1[i]) + (rk[i] * g1)
let commitment_keys1_bytes = attributes_ciphertexts
.iter()
.map(|ciphertext| ciphertext.c1())
.zip(self.response_keys.iter())
.map(|(c1, res_k)| c1 * self.challenge + g1 * res_k)
.map(|witness| witness.to_bytes())
.collect::<Vec<_>>();
// Bw[i] = (c * c2[i]) + (rk[i] * gamma) + (rm[i] * h)
let commitment_keys2_bytes = izip!(
attributes_ciphertexts
.iter()
.map(|ciphertext| ciphertext.c2()),
self.response_keys.iter(),
self.response_attributes.iter()
)
.map(|(c2, res_key, res_attr)| c2 * self.challenge + pub_key * res_key + h * res_attr)
.map(|witness| witness.to_bytes())
.collect::<Vec<_>>();
// Cw = (cm * c) + (rr * g1) + (rm[0] * hs[0]) + ... + (rm[n] * hs[n])
let commitment_attributes = commitment * self.challenge
+ g1 * self.response_opening
+ self
.response_attributes
.iter()
.zip(params.gen_hs().iter())
.map(|(res_attr, hs)| hs * res_attr)
.sum::<G1Projective>();
let ciphertexts_bytes = attributes_ciphertexts
.iter()
.map(|c| c.to_bytes())
.collect::<Vec<_>>();
// re-compute the challenge
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(params.gen1().to_bytes().as_ref())
.chain(hs_bytes.iter().map(|hs| hs.as_ref()))
.chain(std::iter::once(h.to_bytes().as_ref()))
.chain(std::iter::once(pub_key.to_bytes().as_ref()))
.chain(std::iter::once(commitment.to_bytes().as_ref()))
.chain(std::iter::once(commitment_attributes.to_bytes().as_ref()))
.chain(std::iter::once(
commitment_private_key_elgamal.to_bytes().as_ref(),
))
.chain(commitment_keys1_bytes.iter().map(|aw| aw.as_ref()))
.chain(commitment_keys2_bytes.iter().map(|bw| bw.as_ref()))
.chain(ciphertexts_bytes.iter().map(|c| c.as_ref())),
);
challenge == self.challenge
}
// challenge || response opening || response private elgamal key || keys len || response keys || attributes len || response attributes
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let keys_len = self.response_keys.len() as u64;
let attributes_len = self.response_attributes.len() as u64;
let mut bytes = Vec::with_capacity(16 + (keys_len + attributes_len + 3) as usize * 32);
bytes.extend_from_slice(&self.challenge.to_bytes());
bytes.extend_from_slice(&self.response_opening.to_bytes());
bytes.extend_from_slice(&self.response_private_elgamal_key.to_bytes());
bytes.extend_from_slice(&keys_len.to_le_bytes());
for rk in &self.response_keys {
bytes.extend_from_slice(&rk.to_bytes());
}
bytes.extend_from_slice(&attributes_len.to_le_bytes());
for rm in &self.response_attributes {
bytes.extend_from_slice(&rm.to_bytes());
}
bytes
}
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self> {
// at the very minimum there must be a single attribute being proven
if bytes.len() < 32 * 5 + 16 || (bytes.len() - 16) % 32 != 0 {
return Err(
CoconutError::Deserialization(
"tried to deserialize proof of ciphertexts and commitment with bytes of invalid length".to_string())
);
}
let mut idx = 0;
let challenge_bytes = bytes[idx..idx + 32].try_into().unwrap();
idx += 32;
let response_opening_bytes = bytes[idx..idx + 32].try_into().unwrap();
idx += 32;
let response_private_elgamal_key_bytes = bytes[idx..idx + 32].try_into().unwrap();
idx += 32;
let challenge = try_deserialize_scalar(
&challenge_bytes,
CoconutError::Deserialization("Failed to deserialize challenge".to_string()),
)?;
let response_opening = try_deserialize_scalar(
&response_opening_bytes,
CoconutError::Deserialization(
"Failed to deserialize the response to the random".to_string(),
),
)?;
let response_private_elgamal_key = try_deserialize_scalar(
&response_private_elgamal_key_bytes,
CoconutError::Deserialization(
"Failed to deserialize the response to the private ElGamal key".to_string(),
),
)?;
let rk_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
idx += 8;
if bytes[idx..].len() < rk_len as usize * 32 + 8 {
return Err(
CoconutError::Deserialization(
"tried to deserialize proof of ciphertexts and commitment with insufficient number of bytes provided".to_string()),
);
}
let rk_end = idx + rk_len as usize * 32;
let response_keys = try_deserialize_scalar_vec(
rk_len,
&bytes[idx..rk_end],
CoconutError::Deserialization("Failed to deserialize keys response".to_string()),
)?;
let rm_len = u64::from_le_bytes(bytes[rk_end..rk_end + 8].try_into().unwrap());
let response_attributes = try_deserialize_scalar_vec(
rm_len,
&bytes[rk_end + 8..],
CoconutError::Deserialization("Failed to deserialize attributes response".to_string()),
)?;
Ok(ProofCmCs {
challenge,
response_opening,
response_private_elgamal_key,
response_keys,
response_attributes,
})
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ProofKappaZeta {
// c
challenge: Scalar,
// responses
response_serial_number: Scalar,
response_binding_number: Scalar,
response_blinder: Scalar,
}
impl ProofKappaZeta {
pub(crate) fn construct(
params: &Parameters,
verification_key: &VerificationKey,
serial_number: &Attribute,
binding_number: &Attribute,
blinding_factor: &Scalar,
blinded_message: &G2Projective,
blinded_serial_number: &G2Projective,
) -> Self {
// create the witnesses
let witness_blinder = params.random_scalar();
let witness_serial_number = params.random_scalar();
let witness_binding_number = params.random_scalar();
let witness_attributes = vec![witness_serial_number, witness_binding_number];
let beta_bytes = verification_key
.beta
.iter()
.map(|beta_i| beta_i.to_bytes())
.collect::<Vec<_>>();
// witnesses commitments
// Aw = g2 * wt + alpha + beta[0] * wm[0] + ... + beta[i] * wm[i]
let commitment_kappa = params.gen2() * witness_blinder
+ verification_key.alpha
+ witness_attributes
.iter()
.zip(verification_key.beta.iter())
.map(|(wm_i, beta_i)| beta_i * wm_i)
.sum::<G2Projective>();
// zeta is the public value associated with the serial number
let commitment_zeta = params.gen2() * witness_serial_number;
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(params.gen2().to_bytes().as_ref())
.chain(std::iter::once(blinded_message.to_bytes().as_ref()))
.chain(std::iter::once(blinded_serial_number.to_bytes().as_ref()))
.chain(std::iter::once(verification_key.alpha.to_bytes().as_ref()))
.chain(beta_bytes.iter().map(|b| b.as_ref()))
.chain(std::iter::once(commitment_kappa.to_bytes().as_ref()))
.chain(std::iter::once(commitment_zeta.to_bytes().as_ref())),
);
// responses
let response_blinder = produce_response(&witness_blinder, &challenge, blinding_factor);
let response_serial_number =
produce_response(&witness_serial_number, &challenge, serial_number);
let response_binding_number =
produce_response(&witness_binding_number, &challenge, binding_number);
ProofKappaZeta {
challenge,
response_serial_number,
response_binding_number,
response_blinder,
}
}
pub(crate) fn private_attributes_len(&self) -> usize {
2
}
pub(crate) fn verify(
&self,
params: &Parameters,
verification_key: &VerificationKey,
kappa: &G2Projective,
zeta: &G2Projective,
) -> bool {
let beta_bytes = verification_key
.beta
.iter()
.map(|beta_i| beta_i.to_bytes())
.collect::<Vec<_>>();
let response_attributes = vec![self.response_serial_number, self.response_binding_number];
// re-compute witnesses commitments
// Aw = (c * kappa) + (rt * g2) + ((1 - c) * alpha) + (rm[0] * beta[0]) + ... + (rm[i] * beta[i])
let commitment_kappa = kappa * self.challenge
+ params.gen2() * self.response_blinder
+ verification_key.alpha * (Scalar::one() - self.challenge)
+ response_attributes
.iter()
.zip(verification_key.beta.iter())
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
.sum::<G2Projective>();
// zeta is the public value associated with the serial number
let commitment_zeta = zeta * self.challenge + params.gen2() * self.response_serial_number;
// compute the challenge
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(params.gen2().to_bytes().as_ref())
.chain(std::iter::once(kappa.to_bytes().as_ref()))
.chain(std::iter::once(zeta.to_bytes().as_ref()))
.chain(std::iter::once(verification_key.alpha.to_bytes().as_ref()))
.chain(beta_bytes.iter().map(|b| b.as_ref()))
.chain(std::iter::once(commitment_kappa.to_bytes().as_ref()))
.chain(std::iter::once(commitment_zeta.to_bytes().as_ref())),
);
challenge == self.challenge
}
// challenge || response serial number || response binding number || repose blinder
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let attributes_len = 2; // because we have serial number and the binding number
let mut bytes = Vec::with_capacity((1 + attributes_len + 1) as usize * 32);
bytes.extend_from_slice(&self.challenge.to_bytes());
bytes.extend_from_slice(&self.response_serial_number.to_bytes());
bytes.extend_from_slice(&self.response_binding_number.to_bytes());
bytes.extend_from_slice(&self.response_blinder.to_bytes());
bytes
}
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self> {
// at the very minimum there must be a single attribute being proven
if bytes.len() < 32 * 4 || (bytes.len()) % 32 != 0 {
return Err(CoconutError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len(),
modulus: 32,
object: "kappa and zeta".to_string(),
target: 32 * 4,
});
}
let challenge_bytes = bytes[..32].try_into().unwrap();
let challenge = try_deserialize_scalar(
&challenge_bytes,
CoconutError::Deserialization("Failed to deserialize challenge".to_string()),
)?;
let serial_number_bytes = &bytes[32..64].try_into().unwrap();
let response_serial_number = try_deserialize_scalar(
serial_number_bytes,
CoconutError::Deserialization("failed to deserialize the serial number".to_string()),
)?;
let binding_number_bytes = &bytes[64..96].try_into().unwrap();
let response_binding_number = try_deserialize_scalar(
binding_number_bytes,
CoconutError::Deserialization("failed to deserialize the binding number".to_string()),
)?;
let blinder_bytes = bytes[96..].try_into().unwrap();
let response_blinder = try_deserialize_scalar(
&blinder_bytes,
CoconutError::Deserialization("failed to deserialize the blinder".to_string()),
)?;
Ok(ProofKappaZeta {
challenge,
response_serial_number,
response_binding_number,
response_blinder,
})
}
}
// proof builder:
// - commitment
// - challenge
// - responses
#[cfg(test)]
mod tests {
use group::Group;
use rand::thread_rng;
use crate::scheme::issuance::{compute_attribute_encryption, compute_commitment_hash};
use crate::scheme::keygen::keygen;
use crate::scheme::setup::setup;
use crate::scheme::verification::{compute_kappa, compute_zeta};
use super::*;
#[test]
fn proof_cm_cs_bytes_roundtrip() {
let mut rng = thread_rng();
let mut params = setup(1).unwrap();
let elgamal_keypair = elgamal::elgamal_keygen(&params);
let private_attributes = params.n_random_scalars(1);
// we don't care about 'correctness' of the proof. only whether we can correctly recover it from bytes
let cm = G1Projective::random(&mut rng);
let r = params.random_scalar();
let commitment_hash = compute_commitment_hash(cm);
let (attributes_ciphertexts, _): (Vec<_>, Vec<_>) = compute_attribute_encryption(
&params,
private_attributes.as_ref(),
elgamal_keypair.public_key(),
commitment_hash,
);
let ephemeral_keys = params.n_random_scalars(1);
// 0 public 1 private
let pi_s = ProofCmCs::construct(
&mut params,
&elgamal_keypair,
&ephemeral_keys,
&cm,
&r,
&private_attributes,
&*attributes_ciphertexts,
);
let bytes = pi_s.to_bytes();
assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s);
// 2 private
let private_attributes = params.n_random_scalars(2);
let ephemeral_keys = params.n_random_scalars(2);
let pi_s = ProofCmCs::construct(
&mut params,
&elgamal_keypair,
&ephemeral_keys,
&cm,
&r,
&private_attributes,
&*attributes_ciphertexts,
);
let bytes = pi_s.to_bytes();
assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s);
}
#[test]
fn proof_kappa_zeta_bytes_roundtrip() {
let mut params = setup(4).unwrap();
let keypair = keygen(&mut params);
// we don't care about 'correctness' of the proof. only whether we can correctly recover it from bytes
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let private_attributes = vec![serial_number, binding_number];
let r = params.random_scalar();
let kappa = compute_kappa(&params, &keypair.verification_key(), &private_attributes, r);
let zeta = compute_zeta(&params, serial_number);
// 0 public 2 private
let pi_v = ProofKappaZeta::construct(
&mut params,
&keypair.verification_key(),
&serial_number,
&binding_number,
&r,
&kappa,
&zeta,
);
let proof_bytes = pi_v.to_bytes();
let proof_from_bytes = ProofKappaZeta::from_bytes(&proof_bytes).unwrap();
assert_eq!(proof_from_bytes, pi_v);
// 2 public 2 private
let mut params = setup(4).unwrap();
let keypair = keygen(&mut params);
let pi_v = ProofKappaZeta::construct(
&mut params,
&keypair.verification_key(),
&serial_number,
&binding_number,
&r,
&kappa,
&zeta,
);
let proof_bytes = pi_v.to_bytes();
let proof_from_bytes = ProofKappaZeta::from_bytes(&proof_bytes).unwrap();
assert_eq!(proof_from_bytes, pi_v);
}
}
-377
View File
@@ -1,377 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use core::iter::Sum;
use core::ops::Mul;
use bls12_381::{G2Prepared, G2Projective, Scalar};
use group::Curve;
use itertools::Itertools;
use crate::error::{CoconutError, Result};
use crate::scheme::verification::check_bilinear_pairing;
use crate::scheme::{PartialSignature, Signature, SignatureShare, SignerIndex, VerificationKey};
use crate::utils::perform_lagrangian_interpolation_at_origin;
use crate::{Attribute, Parameters};
pub(crate) trait Aggregatable: Sized {
fn aggregate(aggregatable: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self>;
fn check_unique_indices(indices: &[SignerIndex]) -> bool {
// if aggregation is a threshold one, all indices should be unique
indices.iter().unique_by(|&index| index).count() == indices.len()
}
}
// includes `VerificationKey`
impl<T> Aggregatable for T
where
T: Sum,
for<'a> T: Sum<&'a T>,
for<'a> &'a T: Mul<Scalar, Output = T>,
{
fn aggregate(aggregatable: &[T], indices: Option<&[u64]>) -> Result<T> {
if aggregatable.is_empty() {
return Err(CoconutError::Aggregation("Empty set of values".to_string()));
}
if let Some(indices) = indices {
if !Self::check_unique_indices(indices) {
return Err(CoconutError::Aggregation("Non-unique indices".to_string()));
}
perform_lagrangian_interpolation_at_origin(indices, aggregatable)
} else {
// non-threshold
Ok(aggregatable.iter().sum())
}
}
}
impl Aggregatable for PartialSignature {
fn aggregate(sigs: &[PartialSignature], indices: Option<&[u64]>) -> Result<Signature> {
let h = sigs
.get(0)
.ok_or_else(|| CoconutError::Aggregation("Empty set of signatures".to_string()))?
.sig1();
// TODO: is it possible to avoid this allocation?
let sigmas = sigs.iter().map(|sig| *sig.sig2()).collect::<Vec<_>>();
let aggr_sigma = Aggregatable::aggregate(&sigmas, indices)?;
Ok(Signature(*h, aggr_sigma))
}
}
/// Ensures all provided verification keys were generated to verify the same number of attributes.
fn check_same_key_size(keys: &[VerificationKey]) -> bool {
keys.iter().map(|vk| vk.beta.len()).all_equal()
}
pub fn aggregate_verification_keys(
keys: &[VerificationKey],
indices: Option<&[SignerIndex]>,
) -> Result<VerificationKey> {
if !check_same_key_size(keys) {
return Err(CoconutError::Aggregation(
"Verification keys are of different sizes".to_string(),
));
}
Aggregatable::aggregate(keys, indices)
}
pub fn aggregate_signatures(
params: &Parameters,
verification_key: &VerificationKey,
attributes: &[Attribute],
signatures: &[PartialSignature],
indices: Option<&[SignerIndex]>,
) -> Result<Signature> {
// aggregate the signature
let signature = match Aggregatable::aggregate(signatures, indices) {
Ok(res) => res,
Err(err) => return Err(err),
};
// Verify the signature
let alpha = verification_key.alpha;
let tmp = attributes
.iter()
.zip(verification_key.beta.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
if !check_bilinear_pairing(
&signature.0.to_affine(),
&G2Prepared::from((alpha + tmp).to_affine()),
&signature.1.to_affine(),
params.prepared_miller_g2(),
) {
return Err(CoconutError::Aggregation(
"Verification of the aggregated signature failed".to_string(),
));
}
Ok(signature)
}
pub fn aggregate_signature_shares(
params: &Parameters,
verification_key: &VerificationKey,
attributes: &[Attribute],
shares: &[SignatureShare],
) -> Result<Signature> {
let (signatures, indices): (Vec<_>, Vec<_>) = shares
.iter()
.map(|share| (*share.signature(), share.index()))
.unzip();
aggregate_signatures(
params,
verification_key,
attributes,
&signatures,
Some(&indices),
)
}
#[cfg(test)]
mod tests {
use bls12_381::G1Projective;
use group::Group;
use crate::scheme::issuance::sign;
use crate::scheme::keygen::ttp_keygen;
use crate::scheme::setup::Parameters;
use crate::scheme::verification::verify;
use super::*;
#[test]
fn key_aggregation_works_for_any_subset_of_keys() {
let mut params = Parameters::new(2).unwrap();
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
let vks = keypairs
.into_iter()
.map(|keypair| keypair.verification_key())
.collect::<Vec<_>>();
let aggr_vk1 = aggregate_verification_keys(&vks[..3], Some(&[1, 2, 3])).unwrap();
let aggr_vk2 = aggregate_verification_keys(&vks[2..], Some(&[3, 4, 5])).unwrap();
assert_eq!(aggr_vk1, aggr_vk2);
// TODO: should those two actually work or not?
// aggregating threshold+1
let aggr_more = aggregate_verification_keys(&vks[1..], Some(&[2, 3, 4, 5])).unwrap();
assert_eq!(aggr_vk1, aggr_more);
// aggregating all
let aggr_all = aggregate_verification_keys(&vks, Some(&[1, 2, 3, 4, 5])).unwrap();
assert_eq!(aggr_all, aggr_vk1);
// not taking enough points (threshold was 3)
let aggr_not_enough = aggregate_verification_keys(&vks[..2], Some(&[1, 2])).unwrap();
assert_ne!(aggr_not_enough, aggr_vk1);
// taking wrong index
let aggr_bad = aggregate_verification_keys(&vks[2..], Some(&[42, 123, 100])).unwrap();
assert_ne!(aggr_vk1, aggr_bad);
}
#[test]
fn key_aggregation_doesnt_work_for_empty_set_of_keys() {
let keys: Vec<VerificationKey> = vec![];
assert!(aggregate_verification_keys(&keys, None).is_err());
}
#[test]
fn key_aggregation_doesnt_work_if_indices_have_invalid_length() {
let keys = vec![VerificationKey::identity(3)];
assert!(aggregate_verification_keys(&keys, Some(&[])).is_err());
assert!(aggregate_verification_keys(&keys, Some(&[1, 2])).is_err());
}
#[test]
fn key_aggregation_doesnt_work_for_non_unique_indices() {
let keys = vec![VerificationKey::identity(3), VerificationKey::identity(3)];
assert!(aggregate_verification_keys(&keys, Some(&[1, 1])).is_err());
}
#[test]
fn key_aggregation_doesnt_work_for_keys_of_different_size() {
let keys = vec![VerificationKey::identity(3), VerificationKey::identity(1)];
assert!(aggregate_verification_keys(&keys, None).is_err())
}
#[test]
fn signature_aggregation_works_for_any_subset_of_signatures() {
let mut params = Parameters::new(2).unwrap();
let attributes = params.n_random_scalars(2);
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
let (sks, vks): (Vec<_>, Vec<_>) = keypairs
.into_iter()
.map(|keypair| (keypair.secret_key(), keypair.verification_key()))
.unzip();
let sigs = sks
.iter()
.map(|sk| sign(&mut params, sk, &attributes).unwrap())
.collect::<Vec<_>>();
// aggregating (any) threshold works
let aggr_vk_1 = aggregate_verification_keys(&vks[..3], Some(&[1, 2, 3])).unwrap();
let aggr_sig1 = aggregate_signatures(
&params,
&aggr_vk_1,
&attributes,
&sigs[..3],
Some(&[1, 2, 3]),
)
.unwrap();
let aggr_vk_2 = aggregate_verification_keys(&vks[2..], Some(&[3, 4, 5])).unwrap();
let aggr_sig2 = aggregate_signatures(
&params,
&aggr_vk_1,
&attributes,
&sigs[2..],
Some(&[3, 4, 5]),
)
.unwrap();
assert_eq!(aggr_sig1, aggr_sig2);
// verify credential for good measure
assert!(verify(&params, &aggr_vk_1, &attributes, &aggr_sig1));
assert!(verify(&params, &aggr_vk_2, &attributes, &aggr_sig2));
// aggregating threshold+1 works
let aggr_vk_more = aggregate_verification_keys(&vks[1..], Some(&[2, 3, 4, 5])).unwrap();
let aggr_more = aggregate_signatures(
&params,
&aggr_vk_more,
&attributes,
&sigs[1..],
Some(&[2, 3, 4, 5]),
)
.unwrap();
assert_eq!(aggr_sig1, aggr_more);
// aggregating all
let aggr_vk_all = aggregate_verification_keys(&vks, Some(&[1, 2, 3, 4, 5])).unwrap();
let aggr_all = aggregate_signatures(
&params,
&aggr_vk_all,
&attributes,
&sigs,
Some(&[1, 2, 3, 4, 5]),
)
.unwrap();
assert_eq!(aggr_all, aggr_sig1);
// not taking enough points (threshold was 3) should fail
let aggr_vk_not_enough = aggregate_verification_keys(&vks[..2], Some(&[1, 2])).unwrap();
let aggr_not_enough = aggregate_signatures(
&params,
&aggr_vk_not_enough,
&attributes,
&sigs[..2],
Some(&[1, 2]),
)
.unwrap();
assert_ne!(aggr_not_enough, aggr_sig1);
// taking wrong index should fail
let aggr_vk_bad = aggregate_verification_keys(&vks[2..], Some(&[1, 2, 3])).unwrap();
assert!(aggregate_signatures(
&params,
&aggr_vk_bad,
&attributes,
&sigs[2..],
Some(&[42, 123, 100]),
)
.is_err());
}
fn random_signature() -> Signature {
let mut rng = rand::thread_rng();
Signature(
G1Projective::random(&mut rng),
G1Projective::random(&mut rng),
)
}
#[test]
fn signature_aggregation_doesnt_work_for_empty_set_of_signatures() {
let signatures: Vec<Signature> = vec![];
let mut params = Parameters::new(2).unwrap();
let attributes = params.n_random_scalars(2);
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
let (_, vks): (Vec<_>, Vec<_>) = keypairs
.into_iter()
.map(|keypair| (keypair.secret_key(), keypair.verification_key()))
.unzip();
let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap();
assert!(
aggregate_signatures(&params, &aggr_vk_all, &attributes, &signatures, None).is_err()
);
}
#[test]
fn signature_aggregation_doesnt_work_if_indices_have_invalid_length() {
let signatures = vec![random_signature()];
let mut params = Parameters::new(2).unwrap();
let attributes = params.n_random_scalars(2);
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
let (_, vks): (Vec<_>, Vec<_>) = keypairs
.into_iter()
.map(|keypair| (keypair.secret_key(), keypair.verification_key()))
.unzip();
let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap();
assert!(
aggregate_signatures(&params, &aggr_vk_all, &attributes, &signatures, Some(&[]))
.is_err()
);
assert!(aggregate_signatures(
&params,
&aggr_vk_all,
&attributes,
&signatures,
Some(&[1, 2]),
)
.is_err());
}
#[test]
fn signature_aggregation_doesnt_work_for_non_unique_indices() {
let signatures = vec![random_signature(), random_signature()];
let mut params = Parameters::new(2).unwrap();
let attributes = params.n_random_scalars(2);
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
let (_, vks): (Vec<_>, Vec<_>) = keypairs
.into_iter()
.map(|keypair| (keypair.secret_key(), keypair.verification_key()))
.unzip();
let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap();
assert!(aggregate_signatures(
&params,
&aggr_vk_all,
&attributes,
&signatures,
Some(&[1, 1]),
)
.is_err());
}
// TODO: test for aggregating non-threshold keys
}
-382
View File
@@ -1,382 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::convert::TryFrom;
use std::convert::TryInto;
use bls12_381::{G1Affine, G1Projective, Scalar};
use group::{Curve, GroupEncoding};
use crate::elgamal::{Ciphertext, EphemeralKey};
use crate::error::{CoconutError, Result};
use crate::proofs::ProofCmCs;
use crate::scheme::setup::Parameters;
use crate::scheme::BlindedSignature;
use crate::scheme::SecretKey;
/// Creates a Coconut Signature under a given secret key on a set of public attributes only.
#[cfg(test)]
use crate::Signature;
use crate::{elgamal, Attribute, ElGamalKeyPair};
// TODO: possibly completely remove those two functions.
// They only exist to have a simpler and smaller code snippets to test
// basic functionalities.
use crate::traits::{Base58, Bytable};
use crate::utils::{hash_g1, try_deserialize_g1_projective};
// TODO NAMING: double check this one
// Lambda
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct BlindSignRequest {
// cm
commitment: G1Projective,
// h
commitment_hash: G1Projective,
// c
private_attributes_ciphertexts: Vec<elgamal::Ciphertext>,
// pi_s
pi_s: ProofCmCs,
}
impl TryFrom<&[u8]> for BlindSignRequest {
type Error = CoconutError;
fn try_from(bytes: &[u8]) -> Result<BlindSignRequest> {
if bytes.len() < 48 + 48 + 8 + 96 {
return Err(CoconutError::DeserializationMinLength {
min: 48 + 48 + 8 + 96,
actual: bytes.len(),
});
}
let mut j = 0;
let commitment_bytes_len = 48;
let commitment_hash_bytes_len = 48;
let cm_bytes = bytes[..j + commitment_bytes_len].try_into().unwrap();
let commitment = try_deserialize_g1_projective(
&cm_bytes,
CoconutError::Deserialization(
"Failed to deserialize compressed commitment".to_string(),
),
)?;
j += commitment_bytes_len;
let cm_hash_bytes = bytes[j..j + commitment_hash_bytes_len].try_into().unwrap();
let commitment_hash = try_deserialize_g1_projective(
&cm_hash_bytes,
CoconutError::Deserialization(
"Failed to deserialize compressed commitment hash".to_string(),
),
)?;
j += commitment_hash_bytes_len;
let c_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < c_len as usize * 96 {
return Err(CoconutError::DeserializationMinLength {
min: c_len as usize * 96,
actual: bytes[56..].len(),
});
}
let mut private_attributes_ciphertexts = Vec::with_capacity(c_len as usize);
for i in 0..c_len as usize {
let start = j + i * 96;
let end = start + 96;
private_attributes_ciphertexts.push(Ciphertext::try_from(&bytes[start..end])?)
}
let pi_s = ProofCmCs::from_bytes(&bytes[j + c_len as usize * 96..])?;
Ok(BlindSignRequest {
commitment,
commitment_hash,
private_attributes_ciphertexts,
pi_s,
})
}
}
impl Bytable for BlindSignRequest {
fn to_byte_vec(&self) -> Vec<u8> {
let cm_bytes = self.commitment.to_affine().to_compressed();
let cm_hash_bytes = self.commitment_hash.to_affine().to_compressed();
let c_len = self.private_attributes_ciphertexts.len() as u64;
let proof_bytes = self.pi_s.to_bytes();
let mut bytes = Vec::with_capacity(48 + 48 + 8 + c_len as usize * 96 + proof_bytes.len());
bytes.extend_from_slice(&cm_bytes);
bytes.extend_from_slice(&cm_hash_bytes);
bytes.extend_from_slice(&c_len.to_le_bytes());
for c in &self.private_attributes_ciphertexts {
bytes.extend_from_slice(&c.to_bytes());
}
bytes.extend_from_slice(&proof_bytes);
bytes
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
BlindSignRequest::from_bytes(slice)
}
}
impl Base58 for BlindSignRequest {}
impl BlindSignRequest {
fn verify_proof(&self, params: &Parameters, pub_key: &elgamal::PublicKey) -> bool {
self.pi_s.verify(
params,
pub_key,
&self.commitment,
&self.private_attributes_ciphertexts,
)
}
pub fn get_commitment_hash(&self) -> G1Projective {
self.commitment_hash
}
pub fn to_bytes(&self) -> Vec<u8> {
self.to_byte_vec()
}
pub fn from_bytes(bytes: &[u8]) -> Result<BlindSignRequest> {
BlindSignRequest::try_from(bytes)
}
}
pub fn compute_private_attributes_commitment(
params: &Parameters,
private_attributes: &[Attribute],
hs: &[G1Affine],
) -> (Scalar, G1Projective) {
let commitment_opening = params.random_scalar();
// Produces h0 ^ m0 * h1^m1 * .... * hn^mn
// where m0, m1, ...., mn are private attributes
let attr_cm = private_attributes
.iter()
.zip(hs)
.map(|(&m, h)| h * m)
.sum::<G1Projective>();
// Produces g1^r * h0 ^ m0 * h1^m1 * .... * hn^mn
let commitment = params.gen1() * commitment_opening + attr_cm;
(commitment_opening, commitment)
}
pub fn compute_commitment_hash(commitment: G1Projective) -> G1Projective {
hash_g1(commitment.to_bytes())
}
pub fn compute_attribute_encryption(
params: &Parameters,
private_attributes: &[Attribute],
pub_key: &elgamal::PublicKey,
commitment_hash: G1Projective,
) -> (Vec<Ciphertext>, Vec<EphemeralKey>) {
private_attributes
.iter()
.map(|m| pub_key.encrypt(params, &commitment_hash, m))
.unzip()
}
/// Builds cryptographic material required for blind sign.
pub fn prepare_blind_sign(
params: &Parameters,
elgamal_keypair: &ElGamalKeyPair,
private_attributes: &[Attribute],
public_attributes: &[Attribute],
) -> Result<BlindSignRequest> {
if private_attributes.is_empty() {
return Err(CoconutError::Issuance(
"Tried to prepare blind sign request for an empty set of private attributes"
.to_string(),
));
}
let hs = params.gen_hs();
if private_attributes.len() + public_attributes.len() > hs.len() {
return Err(CoconutError::IssuanceMaxAttributes {
max: hs.len(),
requested: private_attributes.len() + public_attributes.len(),
});
}
let (commitment_opening, commitment) =
compute_private_attributes_commitment(params, private_attributes, hs);
// Compute the challenge as the commitment hash
let commitment_hash = compute_commitment_hash(commitment);
// build ElGamal encryption
let (private_attributes_ciphertexts, ephemeral_keys): (Vec<_>, Vec<_>) =
compute_attribute_encryption(
params,
private_attributes,
elgamal_keypair.public_key(),
commitment_hash,
);
let pi_s = ProofCmCs::construct(
params,
elgamal_keypair,
&ephemeral_keys,
&commitment,
&commitment_opening,
private_attributes,
&*private_attributes_ciphertexts,
);
Ok(BlindSignRequest {
commitment,
commitment_hash,
private_attributes_ciphertexts,
pi_s,
})
}
pub fn blind_sign(
params: &Parameters,
signing_secret_key: &SecretKey,
prover_pub_key: &elgamal::PublicKey,
blind_sign_request: &BlindSignRequest,
public_attributes: &[Attribute],
) -> Result<BlindedSignature> {
let num_private = blind_sign_request.private_attributes_ciphertexts.len();
let hs = params.gen_hs();
if num_private + public_attributes.len() > hs.len() {
return Err(CoconutError::IssuanceMaxAttributes {
max: hs.len(),
requested: num_private + public_attributes.len(),
});
}
// Verify the commitment hash
let h = hash_g1(blind_sign_request.commitment.to_bytes());
if !(h == blind_sign_request.commitment_hash) {
return Err(CoconutError::Issuance(
"Failed to verify the commitment hash".to_string(),
));
}
// Verify the ZK proof
if !blind_sign_request.verify_proof(params, prover_pub_key) {
return Err(CoconutError::Issuance(
"Failed to verify the proof of knowledge".to_string(),
));
}
// in python implementation there are n^2 G1 multiplications, let's do it with a single one instead.
// i.e. compute h ^ (pub_m[0] * y[m + 1] + ... + pub_m[n] * y[m + n]) directly (where m is number of PRIVATE attributes)
// rather than ((h ^ pub_m[0]) ^ y[m + 1] , (h ^ pub_m[1]) ^ y[m + 2] , ...).sum() separately
let signed_public = h * public_attributes
.iter()
.zip(signing_secret_key.ys.iter().skip(num_private))
.map(|(attr, yi)| attr * yi)
.sum::<Scalar>();
// c1[0] ^ y[0] * ... * c1[m] ^ y[m]
let sig_1 = blind_sign_request
.private_attributes_ciphertexts
.iter()
.map(|ciphertext| ciphertext.c1())
.zip(signing_secret_key.ys.iter())
.map(|(c1, yi)| c1 * yi)
.sum();
// h ^ x + c2[0] ^ y[0] + ... c2[m] ^ y[m] + h ^ (pub_m[0] * y[m + 1] + ... + pub_m[n] * y[m + n])
let sig_2 = blind_sign_request
.private_attributes_ciphertexts
.iter()
.map(|ciphertext| ciphertext.c2())
.zip(signing_secret_key.ys.iter())
.map(|(c2, yi)| c2 * yi)
.chain(std::iter::once(h * signing_secret_key.x))
.chain(std::iter::once(signed_public))
.sum();
Ok(BlindedSignature(h, elgamal::Ciphertext(sig_1, sig_2)))
}
#[cfg(test)]
pub fn sign(
params: &mut Parameters,
secret_key: &SecretKey,
public_attributes: &[Attribute],
) -> Result<Signature> {
if public_attributes.len() > secret_key.ys.len() {
return Err(CoconutError::IssuanceMaxAttributes {
max: secret_key.ys.len(),
requested: public_attributes.len(),
});
}
// TODO: why in the python implementation this hash onto the curve is present
// while it's not used in the paper? the paper uses random exponent instead.
// (the python implementation hashes string representation of all attributes onto the curve,
// but I think the same can be achieved by just summing the attributes thus avoiding the unnecessary
// transformation. If I'm wrong, please correct me.)
let attributes_sum = public_attributes.iter().sum::<Scalar>();
let h = hash_g1((params.gen1() * attributes_sum).to_bytes());
// x + m0 * y0 + m1 * y1 + ... mn * yn
let exponent = secret_key.x
+ public_attributes
.iter()
.zip(secret_key.ys.iter())
.map(|(m_i, y_i)| m_i * y_i)
.sum::<Scalar>();
let sig2 = h * exponent;
Ok(Signature(h, sig2))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blind_sign_request_bytes_roundtrip() {
let mut params = Parameters::new(1).unwrap();
let public_attributes = params.n_random_scalars(0);
let private_attributes = params.n_random_scalars(1);
let elgamal_keypair = elgamal::elgamal_keygen(&params);
let lambda = prepare_blind_sign(
&mut params,
&elgamal_keypair,
&private_attributes,
&public_attributes,
)
.unwrap();
let bytes = lambda.to_bytes();
println!("{:?}", bytes.len());
assert_eq!(
BlindSignRequest::try_from(bytes.as_slice()).unwrap(),
lambda
);
let mut params = Parameters::new(4).unwrap();
let public_attributes = params.n_random_scalars(2);
let private_attributes = params.n_random_scalars(2);
let lambda = prepare_blind_sign(
&mut params,
&elgamal_keypair,
&private_attributes,
&public_attributes,
)
.unwrap();
let bytes = lambda.to_bytes();
assert_eq!(
BlindSignRequest::try_from(bytes.as_slice()).unwrap(),
lambda
);
}
}
-539
View File
@@ -1,539 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use core::borrow::Borrow;
use core::iter::Sum;
use core::ops::{Add, Mul};
use std::convert::TryFrom;
use std::convert::TryInto;
use bls12_381::{G2Projective, Scalar};
use group::Curve;
use serde_derive::{Deserialize, Serialize};
use crate::error::{CoconutError, Result};
use crate::scheme::aggregation::aggregate_verification_keys;
use crate::scheme::setup::Parameters;
use crate::scheme::SignerIndex;
use crate::traits::Bytable;
use crate::utils::{
try_deserialize_g2_projective, try_deserialize_scalar, try_deserialize_scalar_vec, Polynomial,
};
use crate::Base58;
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct SecretKey {
pub(crate) x: Scalar,
pub(crate) ys: Vec<Scalar>,
}
impl TryFrom<&[u8]> for SecretKey {
type Error = CoconutError;
fn try_from(bytes: &[u8]) -> Result<SecretKey> {
if bytes.len() < 32 * 2 + 8 || (bytes.len() - 8) % 32 != 0 {
return Err(CoconutError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8,
target: 32 * 2 + 8,
modulus: 32,
object: "secret key".to_string(),
});
}
// this conversion will not fail as we are taking the same length of data
let x_bytes: [u8; 32] = bytes[..32].try_into().unwrap();
let ys_len = u64::from_le_bytes(bytes[32..40].try_into().unwrap());
let actual_ys_len = (bytes.len() - 40) / 32;
if ys_len as usize != actual_ys_len {
return Err(CoconutError::Deserialization(format!(
"Tried to deserialize secret key with inconsistent ys len (expected {}, got {})",
ys_len, actual_ys_len
)));
}
let x = try_deserialize_scalar(
&x_bytes,
CoconutError::Deserialization("Failed to deserialize secret key scalar".to_string()),
)?;
let ys = try_deserialize_scalar_vec(
ys_len,
&bytes[40..],
CoconutError::Deserialization("Failed to deserialize secret key scalars".to_string()),
)?;
Ok(SecretKey { x, ys })
}
}
impl SecretKey {
/// Derive verification key using this secret key.
pub fn verification_key(&self, params: &Parameters) -> VerificationKey {
let g2 = params.gen2();
VerificationKey {
alpha: g2 * self.x,
beta: self.ys.iter().map(|y| g2 * y).collect(),
}
}
// x || ys.len() || ys
pub fn to_bytes(&self) -> Vec<u8> {
let ys_len = self.ys.len() as u64;
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) as usize * 32);
bytes.extend_from_slice(&self.x.to_bytes());
bytes.extend_from_slice(&ys_len.to_le_bytes());
for y in self.ys.iter() {
bytes.extend_from_slice(&y.to_bytes())
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<SecretKey> {
SecretKey::try_from(bytes)
}
}
impl Bytable for SecretKey {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
SecretKey::try_from(slice)
}
}
impl Base58 for SecretKey {}
// TODO: perhaps change points to affine representation
// to make verification slightly more efficient?
#[derive(Debug, PartialEq, Clone)]
pub struct VerificationKey {
// TODO add gen2 as per the paper or imply it from the fact library is using bls381?
pub(crate) alpha: G2Projective,
pub(crate) beta: Vec<G2Projective>,
}
impl TryFrom<&[u8]> for VerificationKey {
type Error = CoconutError;
fn try_from(bytes: &[u8]) -> Result<VerificationKey> {
if bytes.len() < 96 * 2 + 8 || (bytes.len() - 8) % 96 != 0 {
return Err(CoconutError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8,
target: 96 * 2 + 8,
modulus: 96,
object: "secret key".to_string(),
});
}
// this conversion will not fail as we are taking the same length of data
let alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
let beta_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
let actual_beta_len = (bytes.len() - 104) / 96;
if beta_len as usize != actual_beta_len {
return Err(
CoconutError::Deserialization(
format!("Tried to deserialize verification key with inconsistent beta len (expected {}, got {})",
beta_len, actual_beta_len
)));
}
let alpha = try_deserialize_g2_projective(
&alpha_bytes,
CoconutError::Deserialization(
"Failed to deserialize verification key G2 point (alpha)".to_string(),
),
)?;
let mut beta = Vec::with_capacity(actual_beta_len);
for i in 0..actual_beta_len {
let start = 104 + i * 96;
let end = start + 96;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g2_projective(
&beta_i_bytes,
CoconutError::Deserialization(
"Failed to deserialize verification key G2 point (beta)".to_string(),
),
)?;
beta.push(beta_i)
}
Ok(VerificationKey { alpha, beta })
}
}
impl<'b> Add<&'b VerificationKey> for VerificationKey {
type Output = VerificationKey;
#[inline]
fn add(self, rhs: &'b VerificationKey) -> VerificationKey {
// If you're trying to add two keys together that were created
// for different number of attributes, just panic as it's a
// nonsense operation.
assert_eq!(
self.beta.len(),
rhs.beta.len(),
"trying to add verification keys generated for different number of attributes"
);
VerificationKey {
alpha: self.alpha + rhs.alpha,
beta: self
.beta
.iter()
.zip(rhs.beta.iter())
.map(|(self_beta, rhs_beta)| self_beta + rhs_beta)
.collect(),
}
}
}
impl<'a> Mul<Scalar> for &'a VerificationKey {
type Output = VerificationKey;
#[inline]
fn mul(self, rhs: Scalar) -> Self::Output {
VerificationKey {
alpha: self.alpha * rhs,
beta: self.beta.iter().map(|b_i| b_i * rhs).collect(),
}
}
}
impl<T> Sum<T> for VerificationKey
where
T: Borrow<VerificationKey>,
{
#[inline]
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = T>,
{
let mut peekable = iter.peekable();
let head_attributes = match peekable.peek() {
Some(head) => head.borrow().beta.len(),
None => {
// TODO: this is a really weird edge case. You're trying to sum an EMPTY iterator
// of VerificationKey. So should it panic here or just return some nonsense value?
return VerificationKey::identity(0);
}
};
peekable.fold(VerificationKey::identity(head_attributes), |acc, item| {
acc + item.borrow()
})
}
}
impl VerificationKey {
/// Create a (kinda) identity verification key using specified
/// number of 'beta' elements
pub(crate) fn identity(beta_size: usize) -> Self {
VerificationKey {
alpha: G2Projective::identity(),
beta: vec![G2Projective::identity(); beta_size],
}
}
pub fn aggregate(sigs: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self> {
aggregate_verification_keys(sigs, indices)
}
pub fn alpha(&self) -> &G2Projective {
&self.alpha
}
pub fn beta(&self) -> &Vec<G2Projective> {
&self.beta
}
pub fn to_bytes(&self) -> Vec<u8> {
let beta_len = self.beta.len() as u64;
let mut bytes = Vec::with_capacity(8 + (beta_len + 1) as usize * 96);
bytes.extend_from_slice(&self.alpha.to_affine().to_compressed());
bytes.extend_from_slice(&beta_len.to_le_bytes());
for beta in self.beta.iter() {
bytes.extend_from_slice(&beta.to_affine().to_compressed())
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<VerificationKey> {
VerificationKey::try_from(bytes)
}
}
impl Bytable for VerificationKey {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
VerificationKey::try_from(slice)
}
}
impl Base58 for VerificationKey {}
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct KeyPair {
secret_key: SecretKey,
verification_key: VerificationKey,
/// Optional index value specifying polynomial point used during threshold key generation.
pub index: Option<SignerIndex>,
}
impl KeyPair {
const MARKER_BYTES: &'static [u8] = b"coconutkeypair";
pub fn secret_key(&self) -> SecretKey {
self.secret_key.clone()
}
pub fn verification_key(&self) -> VerificationKey {
self.verification_key.clone()
}
pub fn to_bytes(&self) -> Vec<u8> {
// Schema is coconutkeypair[14]|secret_key_len[8]|secret_key[secret_key_len]|verification_key_len[8]|verification_key[verification_key_len]|signer_index[8] - optional
self.to_byte_vec()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
KeyPair::try_from_byte_slice(bytes)
}
}
impl Bytable for KeyPair {
fn to_byte_vec(&self) -> Vec<u8> {
// Schema is coconutkeypair[14]|secret_key_len[8]|secret_key[secret_key_len]|verification_key_len[8]|verification_key[verification_key_len]|signer_index[8] - optional
let mut byts = vec![];
let secret_key_bytes = self.secret_key.to_bytes();
let secret_key_len = (secret_key_bytes.len() as u64).to_le_bytes();
let verification_key_bytes = self.verification_key.to_bytes();
let verification_key_len = (verification_key_bytes.len() as u64).to_le_bytes();
byts.extend_from_slice(Self::MARKER_BYTES);
byts.extend_from_slice(&secret_key_len);
byts.extend_from_slice(&secret_key_bytes);
byts.extend_from_slice(&verification_key_len);
byts.extend_from_slice(&verification_key_bytes);
if let Some(index) = self.index {
byts.extend_from_slice(&index.to_le_bytes())
}
byts
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
KeyPair::try_from(slice)
}
}
impl Base58 for KeyPair {}
impl TryFrom<&[u8]> for KeyPair {
type Error = CoconutError;
fn try_from(bytes: &[u8]) -> Result<KeyPair> {
let header_len = Self::MARKER_BYTES.len();
// we must be able to at the very least read the length of secret key which is past the header
// and is 8 bytes long
if bytes.len() < header_len + 8 {
return Err(CoconutError::DeserializationMinLength {
min: header_len + 8,
actual: bytes.len(),
});
}
let secret_key_len =
u64::from_le_bytes(bytes[header_len..header_len + 8].try_into().unwrap()) as usize;
let secret_key_start = header_len + 8;
let secret_key =
SecretKey::try_from(&bytes[secret_key_start..secret_key_start + secret_key_len])?;
// we must be able to read the length of verification key
if bytes.len() < secret_key_start + secret_key_len + 8 {
return Err(CoconutError::DeserializationMinLength {
min: secret_key_start + secret_key_len + 8,
actual: bytes.len(),
});
}
let verification_key_len = u64::from_le_bytes(
bytes[secret_key_start + secret_key_len..secret_key_start + secret_key_len + 8]
.try_into()
.unwrap(),
) as usize;
let verification_key_start = secret_key_start + secret_key_len + 8;
let verification_key = VerificationKey::try_from(
&bytes[verification_key_start..verification_key_start + verification_key_len],
)?;
let consumed_bytes = verification_key_start + verification_key_len;
let index = if consumed_bytes < bytes.len() && [consumed_bytes..].len() == 8 {
Some(u64::from_le_bytes(
bytes[consumed_bytes..consumed_bytes + 8]
.try_into()
.unwrap(),
))
} else {
None
};
Ok(KeyPair {
secret_key,
verification_key,
index,
})
}
}
/// 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
/// that are independent of each other.
#[cfg(test)]
pub fn keygen(params: &Parameters) -> KeyPair {
let attributes = params.gen_hs().len();
let x = params.random_scalar();
let ys = params.n_random_scalars(attributes);
let secret_key = SecretKey { x, ys };
let verification_key = secret_key.verification_key(params);
KeyPair {
secret_key,
verification_key,
index: None,
}
}
/// Generate a set of n Coconut keypairs [((x, y0, y1...), (g2^x, g2^y0, ...)), ...],
/// such that they support threshold aggregation by `threshold` number of parties.
/// It is expected that this procedure is executed by a Trusted Third Party.
pub fn ttp_keygen(
params: &Parameters,
threshold: u64,
num_authorities: u64,
) -> Result<Vec<KeyPair>> {
if threshold == 0 {
return Err(CoconutError::Setup(
"Tried to generate threshold keys with a 0 threshold value".to_string(),
));
}
if threshold > num_authorities {
return Err(
CoconutError::Setup(
"Tried to generate threshold keys for threshold value being higher than number of the signing authorities".to_string(),
));
}
let attributes = params.gen_hs().len();
// generate polynomials
let v = Polynomial::new_random(params, threshold - 1);
let ws = (0..attributes)
.map(|_| Polynomial::new_random(params, threshold - 1))
.collect::<Vec<_>>();
// TODO: potentially if we had some known authority identifier we could use that instead
// of the increasing (1,2,3,...) sequence
let polynomial_indices = (1..=num_authorities).collect::<Vec<_>>();
// generate polynomial shares
let x = polynomial_indices
.iter()
.map(|&id| v.evaluate(&Scalar::from(id)));
let ys = polynomial_indices.iter().map(|&id| {
ws.iter()
.map(|w| w.evaluate(&Scalar::from(id)))
.collect::<Vec<_>>()
});
// finally set the keys
let secret_keys = x.zip(ys).map(|(x, ys)| SecretKey { x, ys });
let keypairs = secret_keys
.zip(polynomial_indices.iter())
.map(|(secret_key, index)| {
let verification_key = secret_key.verification_key(params);
KeyPair {
secret_key,
verification_key,
index: Some(*index),
}
})
.collect();
Ok(keypairs)
}
#[cfg(test)]
mod tests {
use crate::scheme::setup::setup;
use super::*;
#[test]
fn keypair_bytes_roundtrip() {
let mut params1 = setup(1).unwrap();
let mut params5 = setup(5).unwrap();
let keypair1 = keygen(&mut params1);
let keypair5 = keygen(&mut params5);
let bytes1 = keypair1.to_bytes();
let bytes5 = keypair5.to_bytes();
assert_eq!(KeyPair::from_bytes(&bytes1).unwrap(), keypair1);
assert_eq!(KeyPair::from_bytes(&bytes5).unwrap(), keypair5);
}
#[test]
fn secret_key_bytes_roundtrip() {
let mut params1 = setup(1).unwrap();
let mut params5 = setup(5).unwrap();
let keypair1 = keygen(&mut params1);
let keypair5 = keygen(&mut params5);
let bytes1 = keypair1.secret_key.to_bytes();
let bytes5 = keypair5.secret_key.to_bytes();
assert_eq!(SecretKey::from_bytes(&bytes1).unwrap(), keypair1.secret_key);
assert_eq!(SecretKey::from_bytes(&bytes5).unwrap(), keypair5.secret_key);
}
#[test]
fn verification_key_bytes_roundtrip() {
let mut params1 = setup(1).unwrap();
let mut params5 = setup(5).unwrap();
let keypair1 = &keygen(&mut params1);
let keypair5 = &keygen(&mut params5);
let bytes1: Vec<u8> = keypair1.verification_key.to_bytes();
let bytes5: Vec<u8> = keypair5.verification_key.to_bytes();
assert_eq!(
VerificationKey::try_from(bytes1.as_slice()).unwrap(),
keypair1.verification_key
);
assert_eq!(
VerificationKey::try_from(bytes5.as_slice()).unwrap(),
keypair5.verification_key
);
}
}
-661
View File
@@ -1,661 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// TODO: implement https://crates.io/crates/signature traits?
use std::convert::TryFrom;
use std::convert::TryInto;
use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar};
use group::Curve;
pub use keygen::{SecretKey, VerificationKey};
use crate::elgamal::Ciphertext;
use crate::error::{CoconutError, Result};
use crate::scheme::setup::Parameters;
use crate::scheme::verification::check_bilinear_pairing;
use crate::traits::{Base58, Bytable};
use crate::utils::try_deserialize_g1_projective;
use crate::{elgamal, Attribute};
pub mod aggregation;
pub mod issuance;
pub mod keygen;
pub mod setup;
pub mod verification;
pub type SignerIndex = u64;
// (h, s)
#[derive(Debug, Clone, Copy)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Signature(pub(crate) G1Projective, pub(crate) G1Projective);
pub type PartialSignature = Signature;
impl TryFrom<&[u8]> for Signature {
type Error = CoconutError;
fn try_from(bytes: &[u8]) -> Result<Signature> {
if bytes.len() != 96 {
return Err(CoconutError::Deserialization(format!(
"Signature must be exactly 96 bytes, got {}",
bytes.len()
)));
}
let sig1_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48");
let sig2_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48");
let sig1 = try_deserialize_g1_projective(
sig1_bytes,
CoconutError::Deserialization("Failed to deserialize compressed sig1".to_string()),
)?;
let sig2 = try_deserialize_g1_projective(
sig2_bytes,
CoconutError::Deserialization("Failed to deserialize compressed sig2".to_string()),
)?;
Ok(Signature(sig1, sig2))
}
}
impl Signature {
pub(crate) fn sig1(&self) -> &G1Projective {
&self.0
}
pub(crate) fn sig2(&self) -> &G1Projective {
&self.1
}
pub fn randomise(&self, params: &Parameters) -> (Signature, Scalar) {
let r = params.random_scalar();
let r_prime = params.random_scalar();
let h_prime = self.0 * r_prime;
let s_prime = (self.1 * r_prime) + (h_prime * r);
(Signature(h_prime, s_prime), r)
}
pub fn to_bytes(self) -> [u8; 96] {
let mut bytes = [0u8; 96];
bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed());
bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Signature> {
Signature::try_from(bytes)
}
}
impl Bytable for Signature {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
Signature::from_bytes(slice)
}
}
impl Base58 for Signature {}
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct BlindedSignature(G1Projective, elgamal::Ciphertext);
impl Bytable for BlindedSignature {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
Self::from_bytes(slice)
}
}
impl Base58 for BlindedSignature {}
impl TryFrom<&[u8]> for BlindedSignature {
type Error = CoconutError;
fn try_from(bytes: &[u8]) -> Result<BlindedSignature> {
if bytes.len() != 144 {
return Err(CoconutError::Deserialization(format!(
"BlindedSignature must be exactly 144 bytes, got {}",
bytes.len()
)));
}
let h_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48");
let h = try_deserialize_g1_projective(
h_bytes,
CoconutError::Deserialization("Failed to deserialize compressed h".to_string()),
)?;
let c_tilde = Ciphertext::try_from(&bytes[48..])?;
Ok(BlindedSignature(h, c_tilde))
}
}
impl BlindedSignature {
pub fn unblind(
&self,
params: &Parameters,
private_key: &elgamal::PrivateKey,
partial_verification_key: &VerificationKey,
private_attributes: &[Attribute],
public_attributes: &[Attribute],
commitment_hash: &G1Projective,
) -> Result<Signature> {
// parse the signature
let h = &self.0;
let c = &self.1;
let sig2 = private_key.decrypt(c);
// Verify the commitment hash
if !(commitment_hash == h) {
return Err(CoconutError::Unblind(
"Verification of commitment hash from signature failed".to_string(),
));
}
let alpha = partial_verification_key.alpha;
let tmp = private_attributes
.iter()
.chain(public_attributes.iter())
.zip(partial_verification_key.beta.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
// Verify the signature share
if !check_bilinear_pairing(
&h.to_affine(),
&G2Prepared::from((alpha + tmp).to_affine()),
&sig2.to_affine(),
params.prepared_miller_g2(),
) {
return Err(CoconutError::Unblind(
"Verification of signature share failed".to_string(),
));
}
Ok(Signature(self.0, sig2))
}
pub fn to_bytes(&self) -> [u8; 144] {
let mut bytes = [0u8; 144];
bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed());
bytes[48..].copy_from_slice(&self.1.to_bytes());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<BlindedSignature> {
BlindedSignature::try_from(bytes)
}
}
// perhaps this should take signature by reference? we'll see how it goes
pub struct SignatureShare {
signature: Signature,
index: SignerIndex,
}
impl SignatureShare {
pub fn new(signature: Signature, index: SignerIndex) -> Self {
SignatureShare { signature, index }
}
pub fn signature(&self) -> &Signature {
&self.signature
}
pub fn index(&self) -> SignerIndex {
self.index
}
// pub fn aggregate(shares: &[Self]) -> Result<Signature> {
// aggregate_signature_shares(shares)
// }
}
#[cfg(test)]
mod tests {
use group::GroupEncoding;
use crate::hash_to_scalar;
use crate::scheme::aggregation::{aggregate_signatures, aggregate_verification_keys};
use crate::scheme::issuance::{blind_sign, prepare_blind_sign, sign};
use crate::scheme::keygen::{keygen, ttp_keygen};
use crate::scheme::verification::{prove_bandwidth_credential, verify, verify_credential};
use crate::utils::hash_g1;
use super::*;
#[test]
fn unblind_returns_error_if_integrity_check_on_commitment_hash_fails() {
let mut params = Parameters::new(2).unwrap();
let private_attributes = params.n_random_scalars(2 as usize);
let elgamal_keypair = elgamal::elgamal_keygen(&mut params);
let lambda =
prepare_blind_sign(&mut params, &elgamal_keypair, &private_attributes, &[]).unwrap();
let keypair1 = keygen(&mut params);
let sig1 = blind_sign(
&mut params,
&keypair1.secret_key(),
elgamal_keypair.public_key(),
&lambda,
&[],
)
.unwrap();
let wrong_commitment_opening = params.random_scalar();
let wrong_commitment = params.gen1() * wrong_commitment_opening;
let fake_commitment_hash = hash_g1(wrong_commitment.to_bytes());
assert!(sig1
.unblind(
&params,
elgamal_keypair.private_key(),
&keypair1.verification_key(),
&private_attributes,
&[],
&fake_commitment_hash,
)
.is_err());
}
#[test]
fn unblind_returns_error_if_signature_verification_fails() {
let mut params = Parameters::new(2).unwrap();
let private_attributes = vec![hash_to_scalar("Attribute1"), hash_to_scalar("Attribute2")];
let private_attributes2 = vec![hash_to_scalar("Attribute3"), hash_to_scalar("Attribute4")];
let elgamal_keypair = elgamal::elgamal_keygen(&mut params);
let lambda =
prepare_blind_sign(&mut params, &elgamal_keypair, &private_attributes, &[]).unwrap();
let keypair1 = keygen(&mut params);
let sig1 = blind_sign(
&mut params,
&keypair1.secret_key(),
elgamal_keypair.public_key(),
&lambda,
&[],
)
.unwrap();
assert!(sig1
.unblind(
&params,
elgamal_keypair.private_key(),
&keypair1.verification_key(),
&private_attributes2,
&[],
&lambda.get_commitment_hash(),
)
.is_err());
}
#[test]
fn verification_on_two_private_attributes() {
let mut params = Parameters::new(2).unwrap();
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let private_attributes = vec![serial_number, binding_number];
let elgamal_keypair = elgamal::elgamal_keygen(&mut params);
let keypair1 = keygen(&mut params);
let keypair2 = keygen(&mut params);
let lambda =
prepare_blind_sign(&mut params, &elgamal_keypair, &private_attributes, &[]).unwrap();
let sig1 = blind_sign(
&mut params,
&keypair1.secret_key(),
elgamal_keypair.public_key(),
&lambda,
&[],
)
.unwrap()
.unblind(
&params,
elgamal_keypair.private_key(),
&keypair1.verification_key(),
&private_attributes,
&[],
&lambda.get_commitment_hash(),
)
.unwrap();
let sig2 = blind_sign(
&mut params,
&keypair2.secret_key(),
elgamal_keypair.public_key(),
&lambda,
&[],
)
.unwrap()
.unblind(
&params,
elgamal_keypair.private_key(),
&keypair2.verification_key(),
&private_attributes,
&[],
&lambda.get_commitment_hash(),
)
.unwrap();
let theta1 = prove_bandwidth_credential(
&mut params,
&keypair1.verification_key(),
&sig1,
serial_number,
binding_number,
)
.unwrap();
let theta2 = prove_bandwidth_credential(
&mut params,
&keypair2.verification_key(),
&sig2,
serial_number,
binding_number,
)
.unwrap();
assert!(verify_credential(
&params,
&keypair1.verification_key(),
&theta1,
&[],
));
assert!(verify_credential(
&params,
&keypair2.verification_key(),
&theta2,
&[],
));
assert!(!verify_credential(
&params,
&keypair1.verification_key(),
&theta2,
&[],
));
}
#[test]
fn verification_on_two_public_attributes() {
let mut params = Parameters::new(2).unwrap();
let attributes = params.n_random_scalars(2);
let keypair1 = keygen(&mut params);
let keypair2 = keygen(&mut params);
let sig1 = sign(&mut params, &keypair1.secret_key(), &attributes).unwrap();
let sig2 = sign(&mut params, &keypair2.secret_key(), &attributes).unwrap();
assert!(verify(
&params,
&keypair1.verification_key(),
&attributes,
&sig1,
));
assert!(!verify(
&params,
&keypair2.verification_key(),
&attributes,
&sig1,
));
assert!(!verify(
&params,
&keypair1.verification_key(),
&attributes,
&sig2,
));
}
#[test]
fn verification_on_two_public_and_two_private_attributes() {
let mut params = Parameters::new(4).unwrap();
let public_attributes = params.n_random_scalars(2);
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let private_attributes = vec![serial_number, binding_number];
let elgamal_keypair = elgamal::elgamal_keygen(&mut params);
let keypair1 = keygen(&mut params);
let keypair2 = keygen(&mut params);
let lambda = prepare_blind_sign(
&mut params,
&elgamal_keypair,
&private_attributes,
&public_attributes,
)
.unwrap();
let sig1 = blind_sign(
&mut params,
&keypair1.secret_key(),
elgamal_keypair.public_key(),
&lambda,
&public_attributes,
)
.unwrap()
.unblind(
&params,
elgamal_keypair.private_key(),
&keypair1.verification_key(),
&private_attributes,
&public_attributes,
&lambda.get_commitment_hash(),
)
.unwrap();
let sig2 = blind_sign(
&mut params,
&keypair2.secret_key(),
elgamal_keypair.public_key(),
&lambda,
&public_attributes,
)
.unwrap()
.unblind(
&params,
elgamal_keypair.private_key(),
&keypair2.verification_key(),
&private_attributes,
&public_attributes,
&lambda.get_commitment_hash(),
)
.unwrap();
let theta1 = prove_bandwidth_credential(
&mut params,
&keypair1.verification_key(),
&sig1,
serial_number,
binding_number,
)
.unwrap();
let theta2 = prove_bandwidth_credential(
&mut params,
&keypair2.verification_key(),
&sig2,
serial_number,
binding_number,
)
.unwrap();
assert!(verify_credential(
&params,
&keypair1.verification_key(),
&theta1,
&public_attributes,
));
assert!(verify_credential(
&params,
&keypair2.verification_key(),
&theta2,
&public_attributes,
));
assert!(!verify_credential(
&params,
&keypair1.verification_key(),
&theta2,
&public_attributes,
));
}
#[test]
fn verification_on_two_public_and_two_private_attributes_from_two_signers() {
let mut params = Parameters::new(4).unwrap();
let public_attributes = params.n_random_scalars(2);
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let private_attributes = vec![serial_number, binding_number];
let elgamal_keypair = elgamal::elgamal_keygen(&params);
let keypairs = ttp_keygen(&mut params, 2, 3).unwrap();
let lambda = prepare_blind_sign(
&mut params,
&elgamal_keypair,
&private_attributes,
&public_attributes,
)
.unwrap();
let sigs = keypairs
.iter()
.map(|keypair| {
blind_sign(
&mut params,
&keypair.secret_key(),
elgamal_keypair.public_key(),
&lambda,
&public_attributes,
)
.unwrap()
.unblind(
&params,
elgamal_keypair.private_key(),
&keypair.verification_key(),
&private_attributes,
&public_attributes,
&lambda.get_commitment_hash(),
)
.unwrap()
})
.collect::<Vec<_>>();
let vks = keypairs
.into_iter()
.map(|keypair| keypair.verification_key())
.collect::<Vec<_>>();
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
attributes.extend_from_slice(&private_attributes);
attributes.extend_from_slice(&public_attributes);
let aggr_vk = aggregate_verification_keys(&vks[..2], Some(&[1, 2])).unwrap();
let aggr_sig =
aggregate_signatures(&params, &aggr_vk, &attributes, &sigs[..2], Some(&[1, 2]))
.unwrap();
let theta = prove_bandwidth_credential(
&mut params,
&aggr_vk,
&aggr_sig,
serial_number,
binding_number,
)
.unwrap();
assert!(verify_credential(
&params,
&aggr_vk,
&theta,
&public_attributes,
));
// taking different subset of keys and credentials
let aggr_vk = aggregate_verification_keys(&vks[1..], Some(&[2, 3])).unwrap();
let aggr_sig =
aggregate_signatures(&params, &aggr_vk, &attributes, &sigs[1..], Some(&[2, 3]))
.unwrap();
let theta = prove_bandwidth_credential(
&mut params,
&aggr_vk,
&aggr_sig,
serial_number,
binding_number,
)
.unwrap();
assert!(verify_credential(
&params,
&aggr_vk,
&theta,
&public_attributes,
));
}
#[test]
fn signature_bytes_roundtrip() {
let params = Parameters::default();
let r = params.random_scalar();
let s = params.random_scalar();
let signature = Signature(params.gen1() * r, params.gen1() * s);
let bytes = signature.to_bytes();
// also make sure it is equivalent to the internal g1 compressed bytes concatenated
let expected_bytes = [
signature.0.to_affine().to_compressed(),
signature.1.to_affine().to_compressed(),
]
.concat();
assert_eq!(expected_bytes, bytes);
assert_eq!(signature, Signature::try_from(&bytes[..]).unwrap())
}
#[test]
fn blinded_signature_bytes_roundtrip() {
let params = Parameters::default();
let r = params.random_scalar();
let s = params.random_scalar();
let t = params.random_scalar();
let blinded_sig = BlindedSignature(
params.gen1() * t,
Ciphertext(params.gen1() * r, params.gen1() * s),
);
let bytes = blinded_sig.to_bytes();
// also make sure it is equivalent to the internal g1 compressed bytes concatenated
let expected_bytes = [
blinded_sig.0.to_affine().to_compressed(),
blinded_sig.1 .0.to_affine().to_compressed(),
blinded_sig.1 .1.to_affine().to_compressed(),
]
.concat();
assert_eq!(expected_bytes, bytes);
assert_eq!(blinded_sig, BlindedSignature::try_from(&bytes[..]).unwrap())
}
}
-90
View File
@@ -1,90 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bls12_381::{G1Affine, G2Affine, G2Prepared, Scalar};
use ff::Field;
use group::Curve;
use rand::thread_rng;
use crate::error::{CoconutError, Result};
use crate::utils::hash_g1;
/// System-wide parameters used for the protocol
pub struct Parameters {
/// Generator of the G1 group
g1: G1Affine,
/// Additional generators of the G1 group
hs: Vec<G1Affine>,
/// Generator of the G2 group
g2: G2Affine,
/// Precomputed G2 generator used for the miller loop
_g2_prepared_miller: G2Prepared,
}
impl Parameters {
pub fn new(num_attributes: u32) -> Result<Parameters> {
if num_attributes == 0 {
return Err(CoconutError::Setup(
"Tried to setup the scheme for 0 attributes".to_string(),
));
}
let hs = (1..=num_attributes)
.map(|i| hash_g1(format!("h{}", i)).to_affine())
.collect();
Ok(Parameters {
g1: G1Affine::generator(),
hs,
g2: G2Affine::generator(),
_g2_prepared_miller: G2Prepared::from(G2Affine::generator()),
})
}
pub(crate) fn gen1(&self) -> &G1Affine {
&self.g1
}
pub(crate) fn gen2(&self) -> &G2Affine {
&self.g2
}
pub(crate) fn prepared_miller_g2(&self) -> &G2Prepared {
&self._g2_prepared_miller
}
pub(crate) fn gen_hs(&self) -> &[G1Affine] {
&self.hs
}
pub fn random_scalar(&self) -> Scalar {
// lazily-initialized thread-local random number generator, seeded by the system
let mut rng = thread_rng();
Scalar::random(&mut rng)
}
pub fn n_random_scalars(&self, n: usize) -> Vec<Scalar> {
(0..n).map(|_| self.random_scalar()).collect()
}
}
pub fn setup(num_attributes: u32) -> Result<Parameters> {
Parameters::new(num_attributes)
}
// for ease of use in tests requiring params
// TODO: not sure if this will have to go away when tests require some specific number of generators
#[cfg(test)]
impl Default for Parameters {
fn default() -> Self {
Parameters {
g1: G1Affine::generator(),
hs: Vec::new(),
g2: G2Affine::generator(),
_g2_prepared_miller: G2Prepared::from(G2Affine::generator()),
}
}
}
@@ -1,296 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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::proofs::ProofKappaZeta;
use crate::scheme::setup::Parameters;
use crate::scheme::Signature;
use crate::scheme::VerificationKey;
use crate::traits::{Base58, Bytable};
use crate::utils::try_deserialize_g2_projective;
use crate::Attribute;
// TODO NAMING: this whole thing
// Theta
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Theta {
// blinded_message (kappa)
pub blinded_message: G2Projective,
// blinded serial number (zeta)
pub blinded_serial_number: G2Projective,
// sigma
pub credential: Signature,
// pi_v
pub pi_v: ProofKappaZeta,
}
impl TryFrom<&[u8]> for Theta {
type Error = CoconutError;
fn try_from(bytes: &[u8]) -> Result<Theta> {
if bytes.len() < 288 {
return Err(
CoconutError::Deserialization(
format!("Tried to deserialize theta with insufficient number of bytes, expected >= 288, got {}", bytes.len()),
));
}
let blinded_message_bytes = bytes[..96].try_into().unwrap();
let blinded_message = try_deserialize_g2_projective(
&blinded_message_bytes,
CoconutError::Deserialization(
"failed to deserialize the blinded message (kappa)".to_string(),
),
)?;
let blinded_serial_number_bytes = bytes[96..192].try_into().unwrap();
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 pi_v = ProofKappaZeta::from_bytes(&bytes[288..])?;
Ok(Theta {
blinded_message,
blinded_serial_number,
credential,
pi_v,
})
}
}
impl Theta {
fn verify_proof(&self, params: &Parameters, verification_key: &VerificationKey) -> bool {
self.pi_v.verify(
params,
verification_key,
&self.blinded_message,
&self.blinded_serial_number,
)
}
// blinded message (kappa) || blinded serial number (zeta) || credential || pi_v
pub fn to_bytes(&self) -> Vec<u8> {
let blinded_message_bytes = self.blinded_message.to_affine().to_compressed();
let blinded_serial_number_bytes = self.blinded_serial_number.to_affine().to_compressed();
let credential_bytes = self.credential.to_bytes();
let proof_bytes = self.pi_v.to_bytes();
let mut bytes = Vec::with_capacity(288 + proof_bytes.len());
bytes.extend_from_slice(&blinded_message_bytes);
bytes.extend_from_slice(&blinded_serial_number_bytes);
bytes.extend_from_slice(&credential_bytes);
bytes.extend_from_slice(&proof_bytes);
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Theta> {
Theta::try_from(bytes)
}
}
impl Bytable for Theta {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
Theta::try_from(slice)
}
}
impl Base58 for Theta {}
pub fn compute_kappa(
params: &Parameters,
verification_key: &VerificationKey,
private_attributes: &[Attribute],
blinding_factor: Scalar,
) -> G2Projective {
params.gen2() * blinding_factor
+ verification_key.alpha
+ private_attributes
.iter()
.zip(verification_key.beta.iter())
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
.sum::<G2Projective>()
}
pub fn compute_zeta(params: &Parameters, serial_number: Attribute) -> G2Projective {
params.gen2() * serial_number
}
pub fn prove_bandwidth_credential(
params: &Parameters,
verification_key: &VerificationKey,
signature: &Signature,
serial_number: Attribute,
binding_number: Attribute,
) -> Result<Theta> {
if verification_key.beta.len() < 2 {
return Err(
CoconutError::Verification(
format!("Tried to prove a credential for higher than supported by the provided verification key number of attributes (max: {}, requested: 2)",
verification_key.beta.len()
)));
}
// Randomize the signature
let (signature_prime, sign_blinding_factor) = signature.randomise(params);
// blinded_message : kappa in the paper.
// Value kappa is needed since we want to show a signature sigma'.
// In order to verify sigma' we need both the verification key vk and the message m.
// However, we do not want to reveal m to whomever we are showing the signature.
// Thus, we need kappa which allows us to verify sigma'. In particular,
// kappa is computed on m as input, but thanks to the use or random value r,
// it does not reveal any information about m.
let private_attributes = vec![serial_number, binding_number];
let blinded_message = compute_kappa(
params,
verification_key,
&private_attributes,
sign_blinding_factor,
);
// zeta is a commitment to the serial number (i.e., a public value associated with the serial number)
let blinded_serial_number = compute_zeta(params, serial_number);
let pi_v = ProofKappaZeta::construct(
params,
verification_key,
&serial_number,
&binding_number,
&sign_blinding_factor,
&blinded_message,
&blinded_serial_number,
);
Ok(Theta {
blinded_message,
blinded_serial_number,
credential: signature_prime,
pi_v,
})
}
/// Checks whether e(P, Q) * e(-R, S) == id
pub fn check_bilinear_pairing(p: &G1Affine, q: &G2Prepared, r: &G1Affine, s: &G2Prepared) -> bool {
// checking e(P, Q) * e(-R, S) == id
// is equivalent to checking e(P, Q) == e(R, S)
// but requires only a single final exponentiation rather than two of them
// and therefore, as seen via benchmarks.rs, is almost 50% faster
// (1.47ms vs 2.45ms, tested on R9 5900X)
let multi_miller = multi_miller_loop(&[(p, q), (&r.neg(), s)]);
multi_miller.final_exponentiation().is_identity().into()
}
pub fn verify_credential(
params: &Parameters,
verification_key: &VerificationKey,
theta: &Theta,
public_attributes: &[Attribute],
) -> bool {
if public_attributes.len() + theta.pi_v.private_attributes_len() > verification_key.beta.len() {
return false;
}
if !theta.verify_proof(params, verification_key) {
return false;
}
let kappa = if public_attributes.is_empty() {
theta.blinded_message
} else {
let signed_public_attributes = public_attributes
.iter()
.zip(
verification_key
.beta
.iter()
.skip(theta.pi_v.private_attributes_len()),
)
.map(|(pub_attr, beta_i)| beta_i * pub_attr)
.sum::<G2Projective>();
theta.blinded_message + signed_public_attributes
};
check_bilinear_pairing(
&theta.credential.0.to_affine(),
&G2Prepared::from(kappa.to_affine()),
&(theta.credential.1).to_affine(),
params.prepared_miller_g2(),
) && !bool::from(theta.credential.0.is_identity())
}
// Used in tests only
#[cfg(test)]
pub fn verify(
params: &Parameters,
verification_key: &VerificationKey,
public_attributes: &[Attribute],
sig: &Signature,
) -> bool {
let kappa = (verification_key.alpha
+ public_attributes
.iter()
.zip(verification_key.beta.iter())
.map(|(m_i, b_i)| b_i * m_i)
.sum::<G2Projective>())
.to_affine();
check_bilinear_pairing(
&sig.0.to_affine(),
&G2Prepared::from(kappa),
&sig.1.to_affine(),
params.prepared_miller_g2(),
) && !bool::from(sig.0.is_identity())
}
#[cfg(test)]
mod tests {
use crate::scheme::keygen::keygen;
use crate::scheme::setup::setup;
use super::*;
#[test]
fn theta_bytes_roundtrip() {
let mut params = setup(2).unwrap();
let keypair = keygen(&mut params);
let r = params.random_scalar();
let s = params.random_scalar();
let signature = Signature(params.gen1() * r, params.gen1() * s);
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let theta = prove_bandwidth_credential(
&mut params,
&keypair.verification_key(),
&signature,
serial_number,
binding_number,
)
.unwrap();
let bytes = theta.to_bytes();
assert_eq!(Theta::try_from(bytes.as_slice()).unwrap(), theta);
}
}
-106
View File
@@ -1,106 +0,0 @@
use crate::{
aggregate_signature_shares, aggregate_verification_keys, blind_sign, elgamal_keygen,
prepare_blind_sign, prove_bandwidth_credential, setup, ttp_keygen, verify_credential,
CoconutError, Signature, SignatureShare, VerificationKey,
};
#[test]
fn main() -> Result<(), CoconutError> {
let params = setup(5)?;
let public_attributes = params.n_random_scalars(2);
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let private_attributes = vec![serial_number, binding_number];
let elgamal_keypair = elgamal_keygen(&params);
// generate commitment and encryption
let blind_sign_request = prepare_blind_sign(
&params,
&elgamal_keypair,
&private_attributes,
&public_attributes,
)?;
// generate_keys
let coconut_keypairs = ttp_keygen(&params, 2, 3)?;
let verification_keys: Vec<VerificationKey> = coconut_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
// aggregate verification keys
let verification_key = aggregate_verification_keys(&verification_keys, Some(&[1, 2, 3]))?;
// generate blinded signatures
let mut blinded_signatures = Vec::new();
for keypair in coconut_keypairs {
let blinded_signature = blind_sign(
&params,
&keypair.secret_key(),
&elgamal_keypair.public_key(),
&blind_sign_request,
&public_attributes,
)?;
blinded_signatures.push(blinded_signature)
}
// Unblind
let unblinded_signatures: Vec<Signature> = blinded_signatures
.into_iter()
.zip(verification_keys.iter())
.map(|(signature, verification_key)| {
signature
.unblind(
&params,
&elgamal_keypair.private_key(),
&verification_key,
&private_attributes,
&public_attributes,
&blind_sign_request.get_commitment_hash(),
)
.unwrap()
})
.collect();
// Aggregate signatures
let signature_shares: Vec<SignatureShare> = unblinded_signatures
.iter()
.enumerate()
.map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64))
.collect();
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
attributes.extend_from_slice(&private_attributes);
attributes.extend_from_slice(&public_attributes);
// Randomize credentials and generate any cryptographic material to verify them
let signature =
aggregate_signature_shares(&params, &verification_key, &attributes, &signature_shares)?;
// Generate cryptographic material to verify them
let theta = prove_bandwidth_credential(
&params,
&verification_key,
&signature,
serial_number,
binding_number,
)?;
// Verify credentials
assert!(verify_credential(
&params,
&verification_key,
&theta,
&public_attributes,
));
Ok(())
}
-1
View File
@@ -1 +0,0 @@
mod e2e;
-22
View File
@@ -1,22 +0,0 @@
use crate::CoconutError;
pub trait Bytable
where
Self: Sized,
{
fn to_byte_vec(&self) -> Vec<u8>;
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, CoconutError>;
}
pub trait Base58
where
Self: Bytable,
{
fn try_from_bs58<S: AsRef<str>>(x: S) -> Result<Self, CoconutError> {
Self::try_from_byte_slice(&bs58::decode(x.as_ref()).into_vec().unwrap())
}
fn to_bs58(&self) -> String {
bs58::encode(self.to_byte_vec()).into_string()
}
}
-386
View File
@@ -1,386 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use core::iter::Sum;
use core::ops::Mul;
use std::convert::TryInto;
use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField};
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Projective, Scalar};
use ff::Field;
use crate::error::{CoconutError, Result};
use crate::scheme::setup::Parameters;
use crate::scheme::SignerIndex;
pub struct Polynomial {
coefficients: Vec<Scalar>,
}
impl Polynomial {
// for polynomial of degree n, we generate n+1 values
// (for example for degree 1, like y = x + 2, we need [2,1])
pub fn new_random(params: &Parameters, degree: u64) -> Self {
Polynomial {
coefficients: params.n_random_scalars((degree + 1) as usize),
}
}
/// Evaluates the polynomial at point x.
pub fn evaluate(&self, x: &Scalar) -> Scalar {
if self.coefficients.is_empty() {
Scalar::zero()
// if x is zero then we can ignore most of the expensive computation and
// just return the last term of the polynomial
} else if x.is_zero() {
// we checked that coefficients are not empty so unwrap here is fine
*self.coefficients.first().unwrap()
} else {
self.coefficients
.iter()
.enumerate()
// coefficient[n] * x ^ n
.map(|(i, coefficient)| coefficient * x.pow(&[i as u64, 0, 0, 0]))
.sum()
}
}
}
#[inline]
fn generate_lagrangian_coefficients_at_origin(points: &[u64]) -> Vec<Scalar> {
let x = Scalar::zero();
points
.iter()
.enumerate()
.map(|(i, point_i)| {
let mut numerator = Scalar::one();
let mut denominator = Scalar::one();
let xi = Scalar::from(*point_i);
for (j, point_j) in points.iter().enumerate() {
if j != i {
let xj = Scalar::from(*point_j);
// numerator = (x - xs[0]) * ... * (x - xs[j]), j != i
numerator *= x - xj;
// denominator = (xs[i] - x[0]) * ... * (xs[i] - x[j]), j != i
denominator *= xi - xj;
}
}
// numerator / denominator
numerator * denominator.invert().unwrap()
})
.collect()
}
/// Performs a Lagrange interpolation at the origin for a polynomial defined by `points` and `values`.
/// It can be used for Scalars, G1 and G2 points.
pub(crate) fn perform_lagrangian_interpolation_at_origin<T>(
points: &[SignerIndex],
values: &[T],
) -> Result<T>
where
T: Sum,
for<'a> &'a T: Mul<Scalar, Output = T>,
{
if points.is_empty() || values.is_empty() {
return Err(CoconutError::Interpolation(
"Tried to perform lagrangian interpolation for an empty set of coordinates".to_string(),
));
}
if points.len() != values.len() {
return Err(CoconutError::Interpolation(
"Tried to perform lagrangian interpolation for an incomplete set of coordinates"
.to_string(),
));
}
let coefficients = generate_lagrangian_coefficients_at_origin(points);
Ok(coefficients
.into_iter()
.zip(values.iter())
.map(|(coeff, val)| val * coeff)
.sum())
}
// A temporary way of hashing particular message into G1.
// Implementation idea was taken from `threshold_crypto`:
// https://github.com/poanetwork/threshold_crypto/blob/7709462f2df487ada3bb3243060504b5881f2628/src/lib.rs#L691
// Eventually it should get replaced by, most likely, the osswu map
// method once ideally it's implemented inside the pairing crate.
// note: I have absolutely no idea what are the correct domains for those. I just used whatever
// was given in the test vectors of `Hashing to Elliptic Curves draft-irtf-cfrg-hash-to-curve-11`
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-J.9.1
const G1_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_";
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-K.1
const SCALAR_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-expander";
pub(crate) fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G1_HASH_DOMAIN)
}
pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
let mut output = vec![Scalar::zero()];
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>>(
msg.as_ref(),
SCALAR_HASH_DOMAIN,
&mut output,
);
output[0]
}
pub(crate) fn try_deserialize_scalar_vec(
expected_len: u64,
bytes: &[u8],
err: CoconutError,
) -> Result<Vec<Scalar>> {
if bytes.len() != expected_len as usize * 32 {
return Err(err);
}
let mut out = Vec::with_capacity(expected_len as usize);
for i in 0..expected_len as usize {
let s_bytes = bytes[i * 32..(i + 1) * 32].try_into().unwrap();
let s = match Into::<Option<Scalar>>::into(Scalar::from_bytes(&s_bytes)) {
None => return Err(err),
Some(scalar) => scalar,
};
out.push(s)
}
Ok(out)
}
pub(crate) fn try_deserialize_scalar(bytes: &[u8; 32], err: CoconutError) -> Result<Scalar> {
Into::<Option<Scalar>>::into(Scalar::from_bytes(bytes)).ok_or(err)
}
pub(crate) fn try_deserialize_g1_projective(
bytes: &[u8; 48],
err: CoconutError,
) -> Result<G1Projective> {
Into::<Option<G1Affine>>::into(G1Affine::from_compressed(bytes))
.ok_or(err)
.map(G1Projective::from)
}
pub(crate) fn try_deserialize_g2_projective(
bytes: &[u8; 96],
err: CoconutError,
) -> Result<G2Projective> {
Into::<Option<G2Affine>>::into(G2Affine::from_compressed(bytes))
.ok_or(err)
.map(G2Projective::from)
}
// use core::fmt;
// #[cfg(feature = "serde")]
// use serde::de::Visitor;
// #[cfg(feature = "serde")]
// use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
//
// // #[cfg(feature = "serde")]
// #[serde(remote = "Scalar")]
// pub(crate) struct ScalarDef(pub Scalar);
//
// // #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
//
// impl Serialize for ScalarDef {
// fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
// where
// S: Serializer,
// {
// use serde::ser::SerializeTuple;
// let mut tup = serializer.serialize_tuple(32)?;
// for byte in self.0.to_bytes().iter() {
// tup.serialize_element(byte)?;
// }
// tup.end()
// }
// }
//
// impl<'de> Deserialize<'de> for ScalarDef {
// fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
// where
// D: Deserializer<'de>,
// {
// struct ScalarVisitor;
//
// impl<'de> Visitor<'de> for ScalarVisitor {
// type Value = ScalarDef;
//
// fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
// formatter.write_str("a 32-byte canonical bls12_381 scalar")
// }
//
// fn visit_seq<A>(self, mut seq: A) -> core::result::Result<ScalarDef, A::Error>
// where
// A: serde::de::SeqAccess<'de>,
// {
// let mut bytes = [0u8; 32];
// for i in 0..32 {
// bytes[i] = seq
// .next_element()?
// .ok_or_else(|| serde::de::Error::invalid_length(i, &"expected 32 bytes"))?;
// }
//
// let res = Scalar::from_bytes(&bytes);
// if res.is_some().into() {
// Ok(ScalarDef(res.unwrap()))
// } else {
// Err(serde::de::Error::custom(
// &"scalar was not canonically encoded",
// ))
// }
// }
// }
//
// deserializer.deserialize_tuple(32, ScalarVisitor)
// }
// }
//
// #[cfg(feature = "serde")]
// pub(crate) struct G1ProjectiveSerdeHelper(Scalar);
//
// #[cfg(feature = "serde")]
// pub(crate) struct G2ProjectiveSerdeHelper(Scalar);
#[cfg(test)]
mod tests {
use rand::RngCore;
use super::*;
#[test]
fn polynomial_evaluation() {
// y = 42 (it should be 42 regardless of x)
let poly = Polynomial {
coefficients: vec![Scalar::from(42)],
};
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(1)));
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(0)));
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(10)));
// y = x + 10, at x = 2 (exp: 12)
let poly = Polynomial {
coefficients: vec![Scalar::from(10), Scalar::from(1)],
};
assert_eq!(Scalar::from(12), poly.evaluate(&Scalar::from(2)));
// y = x^4 - 5x^2 + 2x - 3, at x = 3 (exp: 39)
let poly = Polynomial {
coefficients: vec![
(-Scalar::from(3)),
Scalar::from(2),
(-Scalar::from(5)),
Scalar::zero(),
Scalar::from(1),
],
};
assert_eq!(Scalar::from(39), poly.evaluate(&Scalar::from(3)));
// empty polynomial
let poly = Polynomial {
coefficients: vec![],
};
// should always be 0
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(1)));
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(0)));
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(10)));
}
#[test]
fn performing_lagrangian_scalar_interpolation_at_origin() {
// x^2 + 3
// x, f(x):
// 1, 4,
// 2, 7,
// 3, 12,
let points = vec![1, 2, 3];
let values = vec![Scalar::from(4), Scalar::from(7), Scalar::from(12)];
assert_eq!(
Scalar::from(3),
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
);
// x^3 + 3x^2 - 5x + 11
// x, f(x):
// 1, 10
// 2, 21
// 3, 50
// 4, 103
let points = vec![1, 2, 3, 4];
let values = vec![
Scalar::from(10),
Scalar::from(21),
Scalar::from(50),
Scalar::from(103),
];
assert_eq!(
Scalar::from(11),
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
);
// more points than it is required
// x^2 + x + 10
// x, f(x)
// 1, 12
// 2, 16
// 3, 22
// 4, 30
// 5, 40
let points = vec![1, 2, 3, 4, 5];
let values = vec![
Scalar::from(12),
Scalar::from(16),
Scalar::from(22),
Scalar::from(30),
Scalar::from(40),
];
assert_eq!(
Scalar::from(10),
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
);
}
#[test]
fn hash_g1_sanity_check() {
let mut rng = rand::thread_rng();
let mut msg1 = [0u8; 1024];
rng.fill_bytes(&mut msg1);
let mut msg2 = [0u8; 1024];
rng.fill_bytes(&mut msg2);
assert_eq!(hash_g1(msg1), hash_g1(msg1));
assert_eq!(hash_g1(msg2), hash_g1(msg2));
assert_ne!(hash_g1(msg1), hash_g1(msg2));
}
#[test]
fn hash_scalar_sanity_check() {
let mut rng = rand::thread_rng();
let mut msg1 = [0u8; 1024];
rng.fill_bytes(&mut msg1);
let mut msg2 = [0u8; 1024];
rng.fill_bytes(&mut msg2);
assert_eq!(hash_to_scalar(msg1), hash_to_scalar(msg1));
assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2));
assert_ne!(hash_to_scalar(msg1), hash_to_scalar(msg2));
}
}
+6 -3
View File
@@ -156,9 +156,11 @@ impl MessageReceiver {
};
// Finally, remove the zero padding from the message
Self::remove_padding(&mut message).map_err(|_| {
MessageRecoveryError::MalformedReconstructedMessage(used_sets.clone())
})?;
if Self::remove_padding(&mut message).is_err() {
return Err(MessageRecoveryError::MalformedReconstructedMessage(
used_sets,
));
};
Ok(Some((
ReconstructedMessage {
@@ -268,6 +270,7 @@ mod message_receiver {
vec![gateway::Node {
owner: "foomp4".to_string(),
stake: 123,
delegation: 456,
location: "unknown".to_string(),
host: "1.2.3.4".parse().unwrap(),
mix_host: "1.2.3.4:1789".parse().unwrap(),
+2
View File
@@ -72,6 +72,7 @@ pub struct Node {
// somebody correct me if I'm wrong, but we should only ever have a single denom of currency
// on the network at a type, right?
pub stake: u128,
pub delegation: u128,
pub location: String,
pub host: NetworkAddress,
// we're keeping this as separate resolved field since we do not want to be resolving the potential
@@ -126,6 +127,7 @@ impl<'a> TryFrom<&'a GatewayBond> for Node {
Ok(Node {
owner: bond.owner.as_str().to_owned(),
stake: bond.bond_amount.amount.into(),
delegation: bond.total_delegation.amount.into(),
location: bond.gateway.location.clone(),
host,
mix_host,

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