Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33c162f3b4 | |||
| 2809c23722 | |||
| 66830d0a8d | |||
| 802449d8af | |||
| 5dd4245d4c | |||
| c49498a669 | |||
| 7d123b9fce | |||
| 3c76b8386a | |||
| bdb021994e | |||
| f732d83479 | |||
| 221d042a21 | |||
| 4dea20e15b | |||
| 6ae54b2f89 | |||
| fe2386eab2 | |||
| 2a551610ae | |||
| b00fa15b55 | |||
| 4d904257a9 | |||
| f3a192a023 | |||
| c67c114af6 | |||
| 87c8b512b0 | |||
| 36d8502fed | |||
| ec2bf0beaf | |||
| 36b20d4e6e | |||
| 9554cebb2b | |||
| b9c4922e1a | |||
| 65ef3f02e7 | |||
| a75ca9f114 | |||
| aa2410c99b | |||
| c1df8eef62 | |||
| f5726a07f3 | |||
| 639fb802a4 | |||
| 9b945acba3 | |||
| 70a28d3767 | |||
| 533f6c5b24 | |||
| fba653fcfd | |||
| 22c0669f5d | |||
| edfd9531af | |||
| bbd35feb81 | |||
| bd26bc192e | |||
| 39af6abec7 | |||
| d1563c079e | |||
| 8250dca8be | |||
| 5bf0a14c20 | |||
| 6a3d10bab7 | |||
| 842b58751a | |||
| 544355bbfd | |||
| bd64a29932 | |||
| ed2cf1ced6 | |||
| 890b0bb677 | |||
| 09efb62fa1 | |||
| 4e80c57076 | |||
| 8c2361757e | |||
| 0eaad032f6 | |||
| 371467cede | |||
| 9110a8eefd | |||
| 2e8a0e9a72 | |||
| b068dde7c7 | |||
| d93918e99b |
+1
-1
@@ -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
|
||||
|
||||
+70
-36
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 }}"
|
||||
|
||||
@@ -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,32 @@ 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
|
||||
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 +73,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!
|
||||
@@ -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 }}
|
||||
@@ -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
-5
@@ -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
|
||||
@@ -29,8 +30,3 @@ 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
+163
-394
@@ -93,12 +93,6 @@ version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.1"
|
||||
@@ -234,12 +228,6 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "az"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d6dff4a1892b54d70af377bf7a17064192e822865791d812957f21e3108c325"
|
||||
|
||||
[[package]]
|
||||
name = "base-x"
|
||||
version = "0.2.8"
|
||||
@@ -308,18 +296,6 @@ version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "0.20.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7774144344a4faa177370406a7ff5f1da24303817368584c6206c8303eb07848"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.8.1"
|
||||
@@ -339,7 +315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcd555c66291d5f836dbb6883b48660ece810fe25a31f3bdfb911945dff2691f"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec 0.7.1",
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if 1.0.0",
|
||||
"constant_time_eq",
|
||||
@@ -420,7 +396,10 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -429,24 +408,12 @@ version = "3.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538"
|
||||
|
||||
[[package]]
|
||||
name = "byte-slice-cast"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca0796d76a983651b4a0ddda16203032759f2fd9103d9181f7c65c06ee8872e6"
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
@@ -504,6 +471,15 @@ dependencies = [
|
||||
"system-deps 3.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a"
|
||||
dependencies = [
|
||||
"rustc_version 0.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.70"
|
||||
@@ -667,30 +643,11 @@ dependencies = [
|
||||
name = "coconut-interface"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"coconut-rs",
|
||||
"getset",
|
||||
"nymcoconut",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coconut-rs"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/nymtech/coconut.git?branch=0.5.0#a1b72d51aa2a67b73b9f58d707030ae6dc70af7f"
|
||||
dependencies = [
|
||||
"bls12_381",
|
||||
"bs58",
|
||||
"digest 0.9.0",
|
||||
"ff",
|
||||
"getrandom 0.2.3",
|
||||
"group",
|
||||
"itertools",
|
||||
"rand 0.8.4",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.0.0"
|
||||
@@ -979,13 +936,47 @@ name = "credentials"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"coconut-interface",
|
||||
"crypto",
|
||||
"network-defaults",
|
||||
"thiserror",
|
||||
"url",
|
||||
"validator-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"cast",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"csv",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_cbor",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"tinytemplate",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.1"
|
||||
@@ -1125,6 +1116,28 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"csv-core",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ct-logs"
|
||||
version = "0.8.0"
|
||||
@@ -1576,14 +1589,6 @@ dependencies = [
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "erc20-bridge-contract"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "error-chain"
|
||||
version = "0.12.4"
|
||||
@@ -1593,49 +1598,6 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethabi"
|
||||
version = "14.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a01317735d563b3bad2d5f90d2e1799f414165408251abb762510f40e790e69a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ethereum-types",
|
||||
"hex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha3",
|
||||
"thiserror",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethbloom"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfb684ac8fa8f6c5759f788862bb22ec6fe3cb392f6bfd08e3c64b603661e3f8"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
"fixed-hash",
|
||||
"impl-rlp",
|
||||
"impl-serde",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethereum-types"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f64b5df66a228d85e4b17e5d6c6aa43b0310898ffe8a85988c4c032357aaabfd"
|
||||
dependencies = [
|
||||
"ethbloom",
|
||||
"fixed-hash",
|
||||
"impl-rlp",
|
||||
"impl-serde",
|
||||
"primitive-types",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "0.1.0"
|
||||
@@ -1731,30 +1693,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d333a26ec13a023c6dff4b7584de4d323cfee2e508f5dd2bbee6669e4f7efdf"
|
||||
dependencies = [
|
||||
"az",
|
||||
"bytemuck",
|
||||
"half",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed-hash"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"rand 0.8.4",
|
||||
"rustc-hex",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.22"
|
||||
@@ -1851,12 +1789,6 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||
|
||||
[[package]]
|
||||
name = "futf"
|
||||
version = "0.1.4"
|
||||
@@ -1966,12 +1898,6 @@ version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.17"
|
||||
@@ -2007,27 +1933,20 @@ name = "gateway-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"coconut-interface",
|
||||
"credentials",
|
||||
"crypto",
|
||||
"fluvio-wasm-timer",
|
||||
"futures",
|
||||
"gateway-requests",
|
||||
"getrandom 0.2.3",
|
||||
"json",
|
||||
"log",
|
||||
"network-defaults",
|
||||
"nymsphinx",
|
||||
"rand 0.7.3",
|
||||
"secp256k1",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tungstenite",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-utils",
|
||||
"web3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2405,9 +2324,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "1.8.2"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||
checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
|
||||
|
||||
[[package]]
|
||||
name = "handlebars"
|
||||
@@ -2490,12 +2409,6 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hex-literal"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21e4590e13640f19f249fe3e4eca5113bc4289f2497710378190e7f4bd96f45b"
|
||||
|
||||
[[package]]
|
||||
name = "hkd32"
|
||||
version = "0.6.0"
|
||||
@@ -2727,44 +2640,6 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impl-codec"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "161ebdfec3c8e3b52bf61c4f3550a1eea4f9579d10dc1b936f3171ebdcd6c443"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impl-rlp"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808"
|
||||
dependencies = [
|
||||
"rlp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impl-serde"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b47ca4d2b6931707a55fce5cf66aff80e2178c8b63bbb4ecb5695cbc870ddf6f"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impl-trait-for-tuples"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5dacb10c5b3bb92d46ba347505a9041e676bb20ad220101326bffb0c93031ee"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
version = "0.3.3"
|
||||
@@ -2923,27 +2798,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "json"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpc-core"
|
||||
version = "18.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"futures-executor",
|
||||
"futures-util",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "k256"
|
||||
version = "0.9.6"
|
||||
@@ -3189,15 +3043,10 @@ dependencies = [
|
||||
name = "mixnet-contract"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"az",
|
||||
"cosmwasm-std",
|
||||
"fixed",
|
||||
"log",
|
||||
"network-defaults",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"thiserror",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
@@ -3315,7 +3164,6 @@ checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d"
|
||||
name = "network-defaults"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal",
|
||||
"serde",
|
||||
"time 0.3.3",
|
||||
"url",
|
||||
@@ -3508,7 +3356,6 @@ dependencies = [
|
||||
name = "nym-gateway"
|
||||
version = "0.11.0"
|
||||
dependencies = [
|
||||
"bip39",
|
||||
"clap",
|
||||
"coconut-interface",
|
||||
"config",
|
||||
@@ -3517,15 +3364,12 @@ dependencies = [
|
||||
"dashmap",
|
||||
"dirs",
|
||||
"dotenv",
|
||||
"erc20-bridge-contract",
|
||||
"futures",
|
||||
"gateway-client",
|
||||
"gateway-requests",
|
||||
"humantime-serde",
|
||||
"log",
|
||||
"mixnet-client",
|
||||
"mixnode-common",
|
||||
"network-defaults",
|
||||
"nymsphinx",
|
||||
"pemstore",
|
||||
"pretty_env_logger",
|
||||
@@ -3540,7 +3384,6 @@ dependencies = [
|
||||
"url",
|
||||
"validator-client",
|
||||
"version-checker",
|
||||
"web3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3652,8 +3495,6 @@ dependencies = [
|
||||
"pin-project",
|
||||
"pretty_env_logger",
|
||||
"rand 0.7.3",
|
||||
"rand 0.8.4",
|
||||
"rand_chacha 0.3.1",
|
||||
"reqwest",
|
||||
"rocket",
|
||||
"rocket_cors",
|
||||
@@ -3670,6 +3511,27 @@ dependencies = [
|
||||
"version-checker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nymcoconut"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bls12_381",
|
||||
"bs58",
|
||||
"criterion",
|
||||
"digest 0.9.0",
|
||||
"doc-comment",
|
||||
"ff",
|
||||
"getrandom 0.2.3",
|
||||
"group",
|
||||
"itertools",
|
||||
"rand 0.8.4",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nymsphinx"
|
||||
version = "0.1.0"
|
||||
@@ -3834,6 +3696,12 @@ version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.2.3"
|
||||
@@ -3929,32 +3797,6 @@ dependencies = [
|
||||
"system-deps 3.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parity-scale-codec"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "373b1a4c1338d9cd3d1fa53b3a11bdab5ab6bd80a20f7f7becd76953ae2be909"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.1",
|
||||
"bitvec",
|
||||
"byte-slice-cast",
|
||||
"impl-trait-for-tuples",
|
||||
"parity-scale-codec-derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parity-scale-codec-derive"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27"
|
||||
dependencies = [
|
||||
"proc-macro-crate 1.1.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.0.0"
|
||||
@@ -4264,6 +4106,34 @@ version = "0.3.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
"plotters-svg",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters-backend"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c"
|
||||
|
||||
[[package]]
|
||||
name = "plotters-svg"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pmutil"
|
||||
version = "0.5.3"
|
||||
@@ -4321,19 +4191,6 @@ dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "primitive-types"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06345ee39fbccfb06ab45f3a1a5798d9dafa04cb8921a76d227040003a234b0e"
|
||||
dependencies = [
|
||||
"fixed-hash",
|
||||
"impl-codec",
|
||||
"impl-rlp",
|
||||
"impl-serde",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "0.1.5"
|
||||
@@ -4504,12 +4361,6 @@ dependencies = [
|
||||
"scheduled-thread-pool",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.6.5"
|
||||
@@ -4810,6 +4661,12 @@ dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
@@ -4909,16 +4766,6 @@ dependencies = [
|
||||
"opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rlp"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"rustc-hex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rocket"
|
||||
version = "0.5.0-rc.1"
|
||||
@@ -5067,12 +4914,6 @@ dependencies = [
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hex"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
@@ -5091,6 +4932,15 @@ dependencies = [
|
||||
"semver 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||
dependencies = [
|
||||
"semver 1.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.19.1"
|
||||
@@ -5203,24 +5053,6 @@ dependencies = [
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secp256k1"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a"
|
||||
dependencies = [
|
||||
"secp256k1-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secp256k1-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "827cb7cce42533829c792fc51b82fbf18b125b45a702ef2c8be77fce65463a7b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.4.2"
|
||||
@@ -5330,6 +5162,16 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_cbor"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
|
||||
dependencies = [
|
||||
"half",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.130"
|
||||
@@ -5587,21 +5429,6 @@ dependencies = [
|
||||
"ordered-buffer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "soketto"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4919971d141dbadaa0e82b5d369e2d7666c98e4625046140615ca363e50d4daa"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"futures",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.8.4",
|
||||
"sha-1 0.9.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "soup-sys"
|
||||
version = "0.10.0"
|
||||
@@ -6201,12 +6028,6 @@ dependencies = [
|
||||
"x11-dl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tar"
|
||||
version = "0.4.37"
|
||||
@@ -6600,12 +6421,13 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6705,7 +6527,6 @@ checksum = "08d3725d3efa29485e87311c5b699de63cde14b00ed4d256b8318aa30ca452cd"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
@@ -6770,9 +6591,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs"
|
||||
version = "5.1.1"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f34750e8cbb4d87d09f6d7441921cc6d9435dcc58df53bbaec01f5f9945d4801"
|
||||
checksum = "369e48de67506679b3a576b0faf666fa9f9acf2fd00b4c61e28bdb6c8e08ec06"
|
||||
dependencies = [
|
||||
"dprint-plugin-typescript",
|
||||
"ts-rs-macros",
|
||||
@@ -6780,9 +6601,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs-macros"
|
||||
version = "5.1.0"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c78e4905e64bb23c814098885e08778dfed7fd796ea503df29aa3ba5fead009"
|
||||
checksum = "f269e8fd28e26b4cdbd01f81f345aaf666131511e54a735a76a614b5062d0a5a"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
@@ -7177,52 +6998,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web3"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd24abe6f2b68e0677f843059faea87bcbd4892e39f02886f366d8222c3c540d"
|
||||
dependencies = [
|
||||
"arrayvec 0.5.2",
|
||||
"base64",
|
||||
"bytes",
|
||||
"derive_more",
|
||||
"ethabi",
|
||||
"ethereum-types",
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"headers",
|
||||
"hex",
|
||||
"jsonrpc-core",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"pin-project",
|
||||
"reqwest",
|
||||
"rlp",
|
||||
"secp256k1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"soketto",
|
||||
"tiny-keccak",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"url",
|
||||
"web3-async-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web3-async-native-tls"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f6d8d1636b2627fe63518d5a9b38a569405d9c9bc665c43c9c341de57227ebb"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webkit2gtk"
|
||||
version = "0.14.0"
|
||||
@@ -7433,12 +7208,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
||||
|
||||
[[package]]
|
||||
name = "x11-dl"
|
||||
version = "2.19.1"
|
||||
|
||||
+2
-2
@@ -25,11 +25,11 @@ 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",
|
||||
@@ -63,4 +63,4 @@ default-members = [
|
||||
"validator-api",
|
||||
]
|
||||
|
||||
exclude = ["explorer", "contracts", "tokenomics-py"]
|
||||
exclude = ["explorer", "contracts"]
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -30,6 +30,3 @@ validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[features]
|
||||
coconut = []
|
||||
@@ -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)]
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::{Config, SocketType};
|
||||
use crate::websocket;
|
||||
use futures::channel::mpsc;
|
||||
use log::*;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
use client_core::client::inbound_messages::{
|
||||
InputMessage, InputMessageReceiver, InputMessageSender,
|
||||
@@ -22,20 +24,26 @@ use client_core::client::topology_control::{
|
||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
};
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::{hash_to_scalar, Credential, Parameters};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::bandwidth::{
|
||||
prepare_for_spending, BandwidthVoucherAttributes, BANDWIDTH_VALUE, TOTAL_ATTRIBUTES,
|
||||
};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
|
||||
MixnetMessageSender,
|
||||
};
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
use nymsphinx::receiver::ReconstructedMessage;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use gateway_client::bandwidth::BandwidthController;
|
||||
use crate::client::config::{Config, SocketType};
|
||||
use crate::websocket;
|
||||
|
||||
pub(crate) mod config;
|
||||
|
||||
@@ -165,6 +173,41 @@ 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 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 = credentials::bandwidth::obtain_signature(
|
||||
¶ms,
|
||||
&bandwidth_credential_attributes,
|
||||
&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,
|
||||
&bandwidth_credential_attributes,
|
||||
&verification_key,
|
||||
)
|
||||
.expect("could not prepare out bandwidth credential for spending")
|
||||
}
|
||||
|
||||
fn start_gateway_client(
|
||||
&mut self,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
@@ -184,17 +227,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 +237,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");
|
||||
|
||||
|
||||
@@ -1,28 +1,39 @@
|
||||
// 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 std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use rand::rngs::OsRng;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use url::Url;
|
||||
|
||||
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::bandwidth::{
|
||||
prepare_for_spending, BandwidthVoucherAttributes, BANDWIDTH_VALUE, 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;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
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")
|
||||
@@ -36,9 +47,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")
|
||||
@@ -54,21 +65,40 @@ 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 = credentials::bandwidth::obtain_signature(
|
||||
¶ms,
|
||||
&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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// 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 futures::channel::mpsc;
|
||||
use log::*;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
use client_core::client::inbound_messages::{
|
||||
InputMessage, InputMessageReceiver, InputMessageSender,
|
||||
@@ -23,18 +22,27 @@ use client_core::client::topology_control::{
|
||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
};
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::{hash_to_scalar, Credential, Parameters};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::bandwidth::{
|
||||
prepare_for_spending, BandwidthVoucherAttributes, BANDWIDTH_VALUE, TOTAL_ATTRIBUTES,
|
||||
};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
|
||||
MixnetMessageSender,
|
||||
};
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use gateway_client::bandwidth::BandwidthController;
|
||||
use crate::client::config::Config;
|
||||
use crate::socks::{
|
||||
authentication::{AuthenticationMethods, Authenticator, User},
|
||||
server::SphinxSocksServer,
|
||||
};
|
||||
|
||||
pub(crate) mod config;
|
||||
|
||||
@@ -153,6 +161,41 @@ 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 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 = credentials::bandwidth::obtain_signature(
|
||||
¶ms,
|
||||
&bandwidth_credential_attributes,
|
||||
&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,
|
||||
&bandwidth_credential_attributes,
|
||||
&verification_key,
|
||||
)
|
||||
.expect("could not prepare out bandwidth credential for spending")
|
||||
}
|
||||
|
||||
fn start_gateway_client(
|
||||
&mut self,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
@@ -172,17 +215,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 +225,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");
|
||||
|
||||
|
||||
@@ -1,26 +1,37 @@
|
||||
// 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 std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use rand::{prelude::SliceRandom, rngs::OsRng, thread_rng};
|
||||
use url::Url;
|
||||
|
||||
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::bandwidth::{
|
||||
prepare_for_spending, BandwidthVoucherAttributes, BANDWIDTH_VALUE, 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;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use rand::{prelude::SliceRandom, rngs::OsRng, thread_rng};
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
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")
|
||||
@@ -40,9 +51,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")
|
||||
@@ -54,21 +65,40 @@ 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 = credentials::bandwidth::obtain_signature(
|
||||
¶ms,
|
||||
&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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,21 +3,24 @@
|
||||
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,
|
||||
public_attributes_bytes: Vec<Vec<u8>>,
|
||||
public_attributes: Vec<Attribute>,
|
||||
private_attributes: Vec<Attribute>,
|
||||
serial_number: Attribute,
|
||||
binding_number: Attribute,
|
||||
voucher_value: Attribute,
|
||||
voucher_info: Attribute,
|
||||
aggregated_verification_key: Option<VerificationKey>,
|
||||
}
|
||||
|
||||
@@ -37,9 +40,10 @@ impl State {
|
||||
signatures: Vec::new(),
|
||||
n_attributes,
|
||||
params,
|
||||
public_attributes_bytes,
|
||||
public_attributes,
|
||||
private_attributes,
|
||||
serial_number: private_attributes[0],
|
||||
binding_number: private_attributes[1],
|
||||
voucher_value: public_attributes[0],
|
||||
voucher_info: public_attributes[1],
|
||||
aggregated_verification_key: None,
|
||||
}
|
||||
}
|
||||
@@ -63,8 +67,8 @@ async fn randomise_credential(
|
||||
) -> Result<Vec<Signature>, String> {
|
||||
let mut state = state.write().await;
|
||||
let signature = state.signatures.remove(idx);
|
||||
let new = signature.randomise(&state.params);
|
||||
state.signatures.insert(idx, new);
|
||||
let (new_signature, _) = signature.randomise(&state.params);
|
||||
state.signatures.insert(idx, new_signature);
|
||||
Ok(state.signatures.clone())
|
||||
}
|
||||
|
||||
@@ -117,14 +121,15 @@ async fn prove_credential(
|
||||
let state = state.read().await;
|
||||
|
||||
if let Some(signature) = state.signatures.get(idx) {
|
||||
match coconut_interface::prove_credential(
|
||||
match coconut_interface::prove_bandwidth_credential(
|
||||
&state.params,
|
||||
&verification_key,
|
||||
signature,
|
||||
&state.private_attributes,
|
||||
state.serial_number,
|
||||
state.binding_number,
|
||||
) {
|
||||
Ok(theta) => Ok(theta),
|
||||
Err(e) => Err(format!("{}", e)),
|
||||
Err(e) => Err(format!("{:?}", e)),
|
||||
}
|
||||
} else {
|
||||
Err("Got invalid Signature idx".to_string())
|
||||
@@ -144,10 +149,15 @@ 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,
|
||||
state.public_attributes_bytes.clone(),
|
||||
public_attributes_bytes,
|
||||
state
|
||||
.signatures
|
||||
.get(idx)
|
||||
@@ -164,11 +174,13 @@ 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,
|
||||
&guard.public_attributes,
|
||||
&guard.private_attributes,
|
||||
&public_attributes,
|
||||
&private_attributes,
|
||||
&parsed_urls,
|
||||
)
|
||||
.await
|
||||
|
||||
Generated
+45
-5600
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,6 @@
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"run_cli": "clear && ts-node src/cli.ts",
|
||||
"test": "ts-mocha tests/**/*.test.ts",
|
||||
"coverage": "nyc npm test",
|
||||
"lint": "eslint \"**/*.ts\"",
|
||||
@@ -23,7 +22,6 @@
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.2.15",
|
||||
"@types/expect": "^24.3.0",
|
||||
"@types/inquirer": "^8.1.3",
|
||||
"@types/mocha": "^8.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.14.0",
|
||||
"@typescript-eslint/parser": "^4.14.0",
|
||||
@@ -37,11 +35,10 @@
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cosmjs/cosmwasm-stargate": "^0.25.5",
|
||||
"@cosmjs/math": "^0.25.5",
|
||||
"@cosmjs/proto-signing": "^0.25.5",
|
||||
"@cosmjs/stargate": "^0.25.5",
|
||||
"axios": "^0.21.1",
|
||||
"inquirer": "^8.2.0"
|
||||
"@cosmjs/cosmwasm-stargate": "^0.25.5",
|
||||
"@cosmjs/stargate": "^0.25.5",
|
||||
"@cosmjs/math": "^0.25.5",
|
||||
"@cosmjs/proto-signing": "^0.25.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MixNodeBond, PagedMixnodeResponse } from "../types";
|
||||
import { INetClient } from "../net-client";
|
||||
import { IQueryClient } from "../query-client";
|
||||
import { VALIDATOR_API_MIXNODES, VALIDATOR_API_PORT } from "../index";
|
||||
import {MixNodeBond, PagedMixnodeResponse} from "../types";
|
||||
import { INetClient } from "../net-client"
|
||||
import {IQueryClient} from "../query-client";
|
||||
import {VALIDATOR_API_MIXNODES, VALIDATOR_API_PORT} from "../index";
|
||||
import axios from "axios";
|
||||
|
||||
export { MixnodesCache };
|
||||
@@ -13,52 +13,48 @@ export { MixnodesCache };
|
||||
* available for querying.
|
||||
* */
|
||||
export default class MixnodesCache {
|
||||
mixNodes: MixNodeBond[];
|
||||
client: INetClient | IQueryClient;
|
||||
perPage: number;
|
||||
mixNodes: MixNodeBond[]
|
||||
client: INetClient | IQueryClient
|
||||
perPage: number
|
||||
|
||||
constructor(client: INetClient | IQueryClient, perPage: number) {
|
||||
this.client = client;
|
||||
this.mixNodes = [];
|
||||
this.perPage = perPage;
|
||||
}
|
||||
|
||||
/// Makes repeated requests to assemble a full list of nodes.
|
||||
/// Requests continue to be make as long as `shouldMakeAnotherRequest()`
|
||||
// returns true.
|
||||
async refreshMixNodes(contractAddress: string): Promise<MixNodeBond[]> {
|
||||
let newMixnodes: MixNodeBond[] = [];
|
||||
let response: PagedMixnodeResponse;
|
||||
let next: string | undefined = undefined;
|
||||
for (;;) {
|
||||
response = await this.client.getMixNodes(
|
||||
contractAddress,
|
||||
this.perPage,
|
||||
next
|
||||
);
|
||||
newMixnodes = newMixnodes.concat(response.nodes);
|
||||
next = response.start_next_after;
|
||||
// if `start_next_after` is not set, we're done
|
||||
if (!next) {
|
||||
break;
|
||||
}
|
||||
constructor(client: INetClient | IQueryClient, perPage: number) {
|
||||
this.client = client;
|
||||
this.mixNodes = [];
|
||||
this.perPage = perPage;
|
||||
}
|
||||
|
||||
this.mixNodes = newMixnodes;
|
||||
return this.mixNodes;
|
||||
}
|
||||
/// Makes repeated requests to assemble a full list of nodes.
|
||||
/// Requests continue to be make as long as `shouldMakeAnotherRequest()`
|
||||
// returns true.
|
||||
async refreshMixNodes(contractAddress: string): Promise<MixNodeBond[]> {
|
||||
let newMixnodes: MixNodeBond[] = [];
|
||||
let response: PagedMixnodeResponse;
|
||||
let next: string | undefined = undefined;
|
||||
for (;;) {
|
||||
response = await this.client.getMixNodes(contractAddress, this.perPage, next);
|
||||
newMixnodes = newMixnodes.concat(response.nodes)
|
||||
next = response.start_next_after;
|
||||
// if `start_next_after` is not set, we're done
|
||||
if (!next) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes requests to assemble a full list of mixnodes from validator-api
|
||||
async refreshValidatorAPIMixNodes(urls: string[]): Promise<MixNodeBond[]> {
|
||||
for (const url of urls) {
|
||||
const validator_api_url = new URL(url);
|
||||
validator_api_url.port = VALIDATOR_API_PORT;
|
||||
validator_api_url.pathname += VALIDATOR_API_MIXNODES;
|
||||
const response = await axios.get(validator_api_url.toString());
|
||||
if (response.status == 200) {
|
||||
return response.data;
|
||||
}
|
||||
this.mixNodes = newMixnodes
|
||||
return this.mixNodes;
|
||||
}
|
||||
throw new Error("None of the provided validators seem to be alive");
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes requests to assemble a full list of mixnodes from validator-api
|
||||
async refreshValidatorAPIMixNodes(urls: string[]): Promise<MixNodeBond[]> {
|
||||
for (const url of urls) {
|
||||
const validator_api_url = new URL(url);
|
||||
validator_api_url.port = VALIDATOR_API_PORT;
|
||||
validator_api_url.pathname += VALIDATOR_API_MIXNODES;
|
||||
const response = await axios.get(validator_api_url.toString());
|
||||
if (response.status == 200) {
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
throw new Error("None of the provided validators seem to be alive")
|
||||
}
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
import ValidatorClient from "./index";
|
||||
import inquirer from "inquirer";
|
||||
// This script runs a CLI to consume the Validator and provide mixnet information to the user
|
||||
|
||||
const VALIDATOR_URLS: string[] = [
|
||||
"https://testnet-milhon-validator1.nymtech.net",
|
||||
// "https://testnet-milhon-validator2.nymtech.net", // <-- val 2 doesnt work apparently.
|
||||
];
|
||||
const DENOM = "punk";
|
||||
const MOCK_MNEMONIC =
|
||||
"vault risk throw flat garlic pretty clay senior birth correct panic floor around pen horror mail entry arrest zoo devote message evoke street total";
|
||||
// ^^ addr: punk10dxwmqjy72s9nkm9x9pluyn6pyx0gkptjhs4k9
|
||||
// curr balance: 899999747
|
||||
|
||||
// const MOCK_MNEMONIC =
|
||||
// "oil once motion cute crawl patch happy wave donkey zoo retreat matrix emerge adult very universe aware error snap credit actress couple upset engine";
|
||||
// ^^ addr: punk1yzr7gtmtlfd0s7s9wpexhteeu05y4xlcvh65eh
|
||||
// curr balance: 5045 UPUNK
|
||||
|
||||
// const MOCK_MNEMONIC =
|
||||
// "sample menu edit midnight guard review call record horn antenna stairs awkward fringe document during amazing twelve wise wide escape matter betray staff someone";
|
||||
// ^^ addr: punk1wn8lwxe5hvdtx60c6p7ekskmu75agwfrslf0qs
|
||||
// curr balance:
|
||||
|
||||
type AccountType = {
|
||||
addr: string;
|
||||
client: any;
|
||||
mnemonic?: string;
|
||||
};
|
||||
function validatorCli() {
|
||||
// define funcs to be used in CLI switch-case
|
||||
|
||||
let state: AccountType = {
|
||||
addr: "",
|
||||
client: null,
|
||||
mnemonic: "",
|
||||
};
|
||||
|
||||
function restartApp() {
|
||||
setTimeout(() => {
|
||||
validatorCli();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function generateNewAccount() {
|
||||
const mnemonic = ValidatorClient.randomMnemonic();
|
||||
ValidatorClient.mnemonicToAddress(mnemonic, "punk")
|
||||
.then((address) => {
|
||||
console.log("Your address is: ", address);
|
||||
console.log("Your mnemonic is: ", mnemonic);
|
||||
return address;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("err", err);
|
||||
});
|
||||
restartApp();
|
||||
}
|
||||
|
||||
function sendFundsMenu() {
|
||||
inquirer
|
||||
.prompt([
|
||||
{
|
||||
name: "recipient",
|
||||
type: "input",
|
||||
message: "please enter the receipient:",
|
||||
},
|
||||
{
|
||||
name: "amount",
|
||||
type: "input",
|
||||
message: "please enter the amount (UPUNK):",
|
||||
},
|
||||
])
|
||||
.then(async ({ recipient, amount }) => {
|
||||
const { addr, client } = state;
|
||||
console.log(
|
||||
`🔥 Hold Tight - Sending ${amount}UPUNK to ${recipient} 🚀`
|
||||
);
|
||||
|
||||
const res = await client.send(addr, recipient, [
|
||||
{
|
||||
denom: "upunk",
|
||||
amount: amount,
|
||||
},
|
||||
]);
|
||||
console.log("Funds Transfer Response:", res);
|
||||
restartApp();
|
||||
});
|
||||
}
|
||||
|
||||
async function delegateGateway() {
|
||||
console.log(
|
||||
"unfortunately - gateway delegation is switched off at the moment."
|
||||
);
|
||||
startTransactionMenu();
|
||||
// const id = "punk1yzr7gtmtlfd0s7s9wpexhteeu05y4xlcvh65eh";
|
||||
// const gatewayID = "EQhjPpUuy4i1u87nfQMW21WiBT5mJk4dcq4ju7Vct7cB";
|
||||
// const coin = {
|
||||
// denom: "upunk",
|
||||
// amount: "101",
|
||||
// };
|
||||
// const res = await state.client.delegateToMixnode(gatewayID, coin);
|
||||
// console.log("delegateMixnode ==> ", res);
|
||||
}
|
||||
|
||||
async function delegateMixnode() {
|
||||
const mixNodeID = "2cFpCe7yP79CcuRpf6JBRdJaSp7JF5YcA5SHi8JVm1d2";
|
||||
// const mixNodeID = "2Vrr7s2peGiWsPh6xY3ZFEMDRmMNv8xLBUtV5XMyQLSB";
|
||||
const coin = {
|
||||
denom: "upunk",
|
||||
amount: "1001",
|
||||
};
|
||||
const res = await state.client.delegateToMixnode(mixNodeID, coin);
|
||||
console.log("delegate to mixnode response: ", res);
|
||||
}
|
||||
async function findMinimumMixnodeBond() {
|
||||
const res = await state.client.minimumMixnodeBond();
|
||||
console.log("res is back ", res);
|
||||
}
|
||||
|
||||
async function bondMixnode() {
|
||||
state.client.bondMixnode();
|
||||
}
|
||||
|
||||
async function checkOwnsMixnodes() {
|
||||
const res = await state.client.ownsMixNode();
|
||||
console.log("owns mixnode? ", res);
|
||||
}
|
||||
function startTransactionMenu() {
|
||||
inquirer
|
||||
.prompt([
|
||||
{
|
||||
type: "list",
|
||||
name: "task",
|
||||
message: "What now?",
|
||||
choices: [
|
||||
"send_funds",
|
||||
"get_mixnodes",
|
||||
"refresh_mixnodes",
|
||||
"refresh_val_api_mixnodes",
|
||||
"min_mixn_bond",
|
||||
"bond_mixnode",
|
||||
"delegate_mixnode",
|
||||
"delegate_gateway",
|
||||
"check_owns_mixnode",
|
||||
],
|
||||
},
|
||||
])
|
||||
.then(({ task }) => {
|
||||
switch (task) {
|
||||
case "send_funds":
|
||||
sendFundsMenu();
|
||||
break;
|
||||
case "get_mixnodes":
|
||||
getMixnodes();
|
||||
break;
|
||||
case "refresh_mixnodes":
|
||||
refreshMixnodes();
|
||||
break;
|
||||
case "refresh_val_api_mixnodes":
|
||||
refreshValApiMixnodes();
|
||||
break;
|
||||
case "min_mixn_bond":
|
||||
findMinimumMixnodeBond();
|
||||
break;
|
||||
case "bond_mixnode":
|
||||
bondMixnode();
|
||||
break;
|
||||
case "delegate_gateway":
|
||||
delegateGateway();
|
||||
break;
|
||||
case "delegate_mixnode":
|
||||
delegateMixnode();
|
||||
break;
|
||||
case "check_owns_mixnode":
|
||||
checkOwnsMixnodes();
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function queryUserAccount() {
|
||||
inquirer
|
||||
.prompt([
|
||||
{
|
||||
type: "input",
|
||||
name: "query_user",
|
||||
message: "Please enter the public address of user you wish to query",
|
||||
},
|
||||
])
|
||||
.then(async ({ query_user }) => {
|
||||
let response = "";
|
||||
try {
|
||||
const client = await ValidatorClient.connectForQuery(
|
||||
query_user,
|
||||
VALIDATOR_URLS,
|
||||
DENOM
|
||||
);
|
||||
const balance = await client.getBalance(query_user);
|
||||
response = `User ${query_user} has a balance of ${balance?.amount}${balance?.denom}`;
|
||||
console.log(response);
|
||||
return validatorCli();
|
||||
} catch (error) {
|
||||
console.log("error back ", error);
|
||||
return validatorCli();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function refreshMixnodes() {
|
||||
const res = await state.client.refreshMixNodes(
|
||||
"punk1yksauczytk60x5cejaras8w6nwf7r772n3kwkp"
|
||||
);
|
||||
console.log("done:", res);
|
||||
}
|
||||
function connectAccount() {
|
||||
inquirer
|
||||
.prompt([
|
||||
{
|
||||
name: "user_mnemonic",
|
||||
type: "input",
|
||||
message: "please enter your mnemonic:",
|
||||
},
|
||||
])
|
||||
.then(async ({ user_mnemonic }) => {
|
||||
console.log("Connecting...");
|
||||
const addr = await ValidatorClient.mnemonicToAddress(
|
||||
MOCK_MNEMONIC,
|
||||
// user_mnemonic,
|
||||
"punk"
|
||||
);
|
||||
|
||||
const client = await ValidatorClient.connect(
|
||||
addr,
|
||||
MOCK_MNEMONIC,
|
||||
VALIDATOR_URLS,
|
||||
DENOM
|
||||
);
|
||||
|
||||
state = {
|
||||
addr,
|
||||
mnemonic: MOCK_MNEMONIC,
|
||||
client,
|
||||
};
|
||||
|
||||
const balance = await client.getBalance(addr);
|
||||
console.log(`connected to validator, our address is ${client.address}`);
|
||||
console.log("connected to validator", client.urls[0]);
|
||||
console.log(
|
||||
`💰 Your balance is ${balance?.amount}${balance?.denom.toUpperCase()}`
|
||||
);
|
||||
|
||||
startTransactionMenu();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("error: ", err);
|
||||
});
|
||||
}
|
||||
function buildAWallet() {
|
||||
inquirer
|
||||
.prompt([
|
||||
{
|
||||
message: "enter your mnemonic to build wallet:",
|
||||
type: "input",
|
||||
name: "mnemonic",
|
||||
},
|
||||
])
|
||||
.then(async ({ mnemonic }) => {
|
||||
const res = await ValidatorClient.buildWallet(mnemonic, DENOM);
|
||||
console.log("Build_Wallet Response: ", res);
|
||||
});
|
||||
}
|
||||
async function refreshValApiMixnodes() {
|
||||
const res = await state.client.refreshValidatorAPIMixNodes();
|
||||
console.log("res is back: ", res);
|
||||
}
|
||||
function getMixnodes() {
|
||||
const res = state.client.mixNodesCache;
|
||||
console.log("Mixnodes", res);
|
||||
}
|
||||
// app provides a list of possible tasks
|
||||
inquirer
|
||||
.prompt([
|
||||
{
|
||||
type: "list",
|
||||
name: "task",
|
||||
message: "Yo, What would you like to do today?",
|
||||
choices: [
|
||||
"create_account",
|
||||
"connect_account",
|
||||
"build_wallet",
|
||||
"query_user",
|
||||
],
|
||||
},
|
||||
])
|
||||
.then(({ task }) => {
|
||||
switch (task) {
|
||||
case "create_account":
|
||||
generateNewAccount();
|
||||
break;
|
||||
case "connect_account":
|
||||
connectAccount();
|
||||
break;
|
||||
case "build_wallet":
|
||||
buildAWallet();
|
||||
break;
|
||||
case "query_user":
|
||||
queryUserAccount();
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
validatorCli();
|
||||
+586
-884
File diff suppressed because it is too large
Load Diff
+2259
-2517
File diff suppressed because it is too large
Load Diff
@@ -1,24 +1,32 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use rand::rngs::OsRng;
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::{hash_to_scalar, Parameters};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::bandwidth::{BandwidthVoucherAttributes, BANDWIDTH_VALUE, TOTAL_ATTRIBUTES};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use gateway_client::GatewayClient;
|
||||
use nymsphinx::acknowledgements::AckKey;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::preparer::MessagePreparer;
|
||||
use rand::rngs::OsRng;
|
||||
use received_processor::ReceivedMessagesProcessor;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use topology::{gateway, nym_topology_from_bonds, NymTopology};
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use wasm_utils::{console_log, console_warn};
|
||||
|
||||
use gateway_client::bandwidth::BandwidthController;
|
||||
|
||||
pub(crate) mod received_processor;
|
||||
|
||||
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
|
||||
@@ -100,15 +108,51 @@ 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 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 = credentials::bandwidth::obtain_signature(
|
||||
¶ms,
|
||||
&bandwidth_credential_attributes,
|
||||
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,
|
||||
&bandwidth_credential_attributes,
|
||||
&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();
|
||||
@@ -124,11 +168,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");
|
||||
|
||||
@@ -250,7 +296,7 @@ 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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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,226 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::GatewayClientError;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::coconut::{
|
||||
bandwidth::{obtain_signature, prepare_for_spending},
|
||||
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;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use network_defaults::{
|
||||
eth_contract::ETH_JSON_ABI, BANDWIDTH_VALUE, 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,
|
||||
};
|
||||
|
||||
#[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(ð_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(ð_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 bandwidth_credential =
|
||||
obtain_signature(&self.identity.to_bytes(), &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,
|
||||
&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 super::*;
|
||||
use network_defaults::ETH_EVENT_NAME;
|
||||
|
||||
#[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());
|
||||
}
|
||||
}
|
||||
@@ -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()?;
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ 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"]
|
||||
|
||||
@@ -10,9 +10,9 @@ use mixnet_contract::StateParams;
|
||||
|
||||
use crate::{validator_api, ValidatorClientError};
|
||||
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
|
||||
use mixnet_contract::{GatewayBond, MixNodeBond};
|
||||
#[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")]
|
||||
@@ -172,43 +172,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_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
|
||||
|
||||
@@ -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,12 @@ 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, PagedAllDelegationsResponse,
|
||||
PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse,
|
||||
PagedReverseMixDelegationsResponse, QueryMsg, RawDelegationData, RewardingIntervalResponse,
|
||||
StateParams,
|
||||
PagedReverseMixDelegationsResponse, QueryMsg, RawDelegationData, StateParams,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
@@ -28,11 +27,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 +184,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,18 +201,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_layer_distribution(&self) -> Result<LayerDistribution, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
@@ -240,46 +211,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
|
||||
@@ -711,54 +642,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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
coconut-rs = { git = "https://github.com/nymtech/coconut.git", branch = "0.5.0" }
|
||||
nymcoconut = {path = "../nymcoconut" }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use getset::{CopyGetters, Getters};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub use coconut_rs::*;
|
||||
pub use nymcoconut::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, Getters, CopyGetters, Clone)]
|
||||
pub struct Credential {
|
||||
@@ -42,7 +42,7 @@ impl Credential {
|
||||
.iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<Attribute>>();
|
||||
coconut_rs::verify_credential(¶ms, verification_key, &self.theta, &public_attributes)
|
||||
nymcoconut::verify_credential(¶ms, 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: coconut_rs::PublicKey,
|
||||
public_key: nymcoconut::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: &coconut_rs::PublicKey,
|
||||
blind_sign_request: &BlindSignRequest,
|
||||
public_key: &nymcoconut::PublicKey,
|
||||
public_attributes: &[Attribute],
|
||||
total_params: u32,
|
||||
) -> BlindSignRequestBody {
|
||||
BlindSignRequestBody {
|
||||
blind_sign_request,
|
||||
blind_sign_request: blind_sign_request.clone(),
|
||||
public_key: public_key.clone(),
|
||||
public_attributes: public_attributes
|
||||
.iter()
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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 coconut_interface::{
|
||||
Credential, Parameters, PrivateAttribute, PublicAttribute, Signature, VerificationKey,
|
||||
};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::utils::{obtain_aggregate_signature, prepare_credential_for_spending};
|
||||
|
||||
pub const BANDWIDTH_VALUE: u64 = 10 * 1024 * 1024 * 1024; // 10 GB
|
||||
|
||||
pub const PUBLIC_ATTRIBUTES: u32 = 2;
|
||||
pub const PRIVATE_ATTRIBUTES: u32 = 2;
|
||||
pub const TOTAL_ATTRIBUTES: u32 = PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES;
|
||||
|
||||
pub const SERIAL_NUMBER_LEN: usize = 47;
|
||||
pub const BINDING_NUMBER_LEN: usize = 47;
|
||||
pub const VOUCHER_INFO_LEN: usize = 47;
|
||||
|
||||
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(
|
||||
¶ms,
|
||||
public_attributes,
|
||||
attributes.serial_number,
|
||||
attributes.binding_number,
|
||||
signature,
|
||||
verification_key,
|
||||
)
|
||||
}
|
||||
@@ -1,47 +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 url::Url;
|
||||
|
||||
use super::utils::{obtain_aggregate_signature, prepare_credential_for_spending};
|
||||
use crate::error::Error;
|
||||
use coconut_interface::{hash_to_scalar, Credential, Parameters, Signature, VerificationKey};
|
||||
use network_defaults::BANDWIDTH_VALUE;
|
||||
|
||||
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(¶ms, &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(
|
||||
¶ms,
|
||||
public_attributes,
|
||||
private_attributes,
|
||||
signature,
|
||||
verification_key,
|
||||
)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod bandwidth;
|
||||
pub mod utils;
|
||||
@@ -1,138 +0,0 @@
|
||||
// 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, hash_to_scalar, prepare_blind_sign,
|
||||
prove_credential, Attribute, BlindSignRequestBody, Credential, Parameters, Signature,
|
||||
SignatureShare, VerificationKey,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
/// Contacts all provided validators and then aggregate their verification keys.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `validators`: list of validators to obtain verification keys from.
|
||||
///
|
||||
/// Note: list of validators must be correctly ordered by the polynomial coordinates used
|
||||
/// during key generation and it is responsibility of the caller to ensure that correct
|
||||
/// number of them is provided
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use url::{Url, ParseError};
|
||||
/// use credentials::obtain_aggregate_verification_key;
|
||||
///
|
||||
/// async fn example() -> Result<(), ParseError> {
|
||||
/// let validators = vec!["https://testnet-milhon-validator1.nymtech.net/api".parse()?, "https://testnet-milhon-validator2.nymtech.net/api".parse()?];
|
||||
/// let aggregated_key = obtain_aggregate_verification_key(&validators).await;
|
||||
/// // deal with the obtained Result
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn obtain_aggregate_verification_key(
|
||||
validators: &[Url],
|
||||
) -> Result<VerificationKey, Error> {
|
||||
if validators.is_empty() {
|
||||
return Err(Error::NoValidatorsAvailable);
|
||||
}
|
||||
|
||||
let mut indices = Vec::with_capacity(validators.len());
|
||||
let mut shares = Vec::with_capacity(validators.len());
|
||||
|
||||
let mut client = validator_client::ApiClient::new(validators[0].clone());
|
||||
let response = client.get_coconut_verification_key().await?;
|
||||
|
||||
indices.push(0);
|
||||
shares.push(response.key);
|
||||
|
||||
for (id, validator_url) in validators.iter().enumerate().skip(1) {
|
||||
client.change_validator_api(validator_url.clone());
|
||||
let response = client.get_coconut_verification_key().await?;
|
||||
indices.push(id as u64);
|
||||
shares.push(response.key);
|
||||
}
|
||||
|
||||
Ok(aggregate_verification_keys(&shares, Some(&indices))?)
|
||||
}
|
||||
|
||||
async fn obtain_partial_credential(
|
||||
params: &Parameters,
|
||||
public_attributes: &[Attribute],
|
||||
private_attributes: &[Attribute],
|
||||
client: &validator_client::ApiClient,
|
||||
) -> Result<Signature, Error> {
|
||||
let elgamal_keypair = coconut_interface::elgamal_keygen(params);
|
||||
let blind_sign_request = prepare_blind_sign(
|
||||
params,
|
||||
elgamal_keypair.public_key(),
|
||||
private_attributes,
|
||||
public_attributes,
|
||||
)?;
|
||||
|
||||
let blind_sign_request_body = BlindSignRequestBody::new(
|
||||
blind_sign_request,
|
||||
elgamal_keypair.public_key(),
|
||||
public_attributes,
|
||||
(public_attributes.len() + private_attributes.len()) as u32,
|
||||
);
|
||||
|
||||
let blinded_signature = client
|
||||
.blind_sign(&blind_sign_request_body)
|
||||
.await?
|
||||
.blinded_signature;
|
||||
Ok(blinded_signature.unblind(elgamal_keypair.private_key()))
|
||||
}
|
||||
|
||||
pub async fn obtain_aggregate_signature(
|
||||
params: &Parameters,
|
||||
public_attributes: &[Attribute],
|
||||
private_attributes: &[Attribute],
|
||||
validators: &[Url],
|
||||
) -> Result<Signature, Error> {
|
||||
if validators.is_empty() {
|
||||
return Err(Error::NoValidatorsAvailable);
|
||||
}
|
||||
|
||||
let mut shares = Vec::with_capacity(validators.len());
|
||||
|
||||
let mut client = validator_client::ApiClient::new(validators[0].clone());
|
||||
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 signature =
|
||||
obtain_partial_credential(params, public_attributes, private_attributes, &client)
|
||||
.await?;
|
||||
let share = SignatureShare::new(signature, id as u64);
|
||||
shares.push(share)
|
||||
}
|
||||
|
||||
Ok(aggregate_signature_shares(&shares)?)
|
||||
}
|
||||
|
||||
// TODO: better type flow
|
||||
pub fn prepare_credential_for_spending(
|
||||
params: &Parameters,
|
||||
public_attributes: Vec<Vec<u8>>,
|
||||
private_attributes: Vec<Vec<u8>>,
|
||||
signature: &Signature,
|
||||
verification_key: &VerificationKey,
|
||||
) -> Result<Credential, Error> {
|
||||
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.len()) as u32,
|
||||
theta,
|
||||
public_attributes,
|
||||
signature,
|
||||
))
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
// 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::{
|
||||
blind_sign_partial_credential, create_aggregate_verification_key, get_verification_keys,
|
||||
obtain_aggregate_signature, obtain_aggregate_verification_key,
|
||||
};
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod bandwidth;
|
||||
@@ -0,0 +1,245 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use coconut_interface::{
|
||||
aggregate_signature_shares, aggregate_verification_keys, prepare_blind_sign,
|
||||
prove_bandwidth_credential, Attribute, BlindSignRequest, BlindSignRequestBody,
|
||||
BlindedSignature, Credential, ElGamalKeyPair, Parameters, Signature, SignatureShare,
|
||||
VerificationKey,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
use crate::bandwidth::PRIVATE_ATTRIBUTES;
|
||||
use crate::error::Error;
|
||||
|
||||
/// Contacts all provided validators and then aggregate their verification keys.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `validators`: list of validators to obtain verification keys from.
|
||||
///
|
||||
/// Note: list of validators must be correctly ordered by the polynomial coordinates used
|
||||
/// during key generation and it is responsibility of the caller to ensure that correct
|
||||
/// number of them is provided
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use url::{Url, ParseError};
|
||||
/// use credentials::obtain_aggregate_verification_key;
|
||||
///
|
||||
/// async fn example() -> Result<(), ParseError> {
|
||||
/// let validators = vec!["https://testnet-milhon-validator1.nymtech.net/api".parse()?, "https://testnet-milhon-validator2.nymtech.net/api".parse()?];
|
||||
/// let aggregated_key = obtain_aggregate_verification_key(&validators).await;
|
||||
/// // deal with the obtained Result
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn get_verification_keys(validators: &[Url]) -> Result<Vec<VerificationKey>, Error> {
|
||||
if validators.is_empty() {
|
||||
return Err(Error::NoValidatorsAvailable);
|
||||
}
|
||||
|
||||
let mut shares = Vec::with_capacity(validators.len());
|
||||
|
||||
let mut client = validator_client::ApiClient::new(validators[0].clone());
|
||||
let response = client.get_coconut_verification_key().await?;
|
||||
|
||||
shares.push(response.key);
|
||||
|
||||
for validator_url in validators.iter().enumerate().skip(1) {
|
||||
client.change_validator_api(validator_url.1.clone());
|
||||
let response = client.get_coconut_verification_key().await?;
|
||||
shares.push(response.key);
|
||||
}
|
||||
|
||||
Ok(shares)
|
||||
}
|
||||
|
||||
pub fn create_aggregate_verification_key(
|
||||
verification_keys: &Vec<VerificationKey>,
|
||||
) -> Result<VerificationKey, Error> {
|
||||
if verification_keys.is_empty() {
|
||||
return Err(Error::NoValidatorsAvailable);
|
||||
}
|
||||
|
||||
// creates a vec of [1, 2, .. n] where n is length of verification_keys, e.g. [1,2,3] for 3 keys
|
||||
let indices: Vec<u64> = (1u64..(verification_keys.len() + 1) as u64).collect();
|
||||
|
||||
Ok(aggregate_verification_keys(
|
||||
&verification_keys,
|
||||
Some(&indices.as_slice()),
|
||||
)?)
|
||||
}
|
||||
|
||||
pub async fn obtain_aggregate_verification_key(
|
||||
validators: &[Url],
|
||||
) -> Result<VerificationKey, Error> {
|
||||
if validators.is_empty() {
|
||||
return Err(Error::NoValidatorsAvailable);
|
||||
}
|
||||
|
||||
let mut indices = Vec::with_capacity(validators.len());
|
||||
let mut shares = Vec::with_capacity(validators.len());
|
||||
|
||||
let mut client = validator_client::ApiClient::new(validators[0].clone());
|
||||
let response = client.get_coconut_verification_key().await?;
|
||||
|
||||
indices.push(0);
|
||||
shares.push(response.key);
|
||||
|
||||
for (id, validator_url) in validators.iter().enumerate().skip(1) {
|
||||
client.change_validator_api(validator_url.clone());
|
||||
let response = client.get_coconut_verification_key().await?;
|
||||
indices.push(id as u64);
|
||||
shares.push(response.key);
|
||||
}
|
||||
|
||||
Ok(aggregate_verification_keys(&shares, Some(&indices))?)
|
||||
}
|
||||
|
||||
pub async fn blind_sign_partial_credential(
|
||||
validator_url: &Url,
|
||||
elgamal_keypair: &ElGamalKeyPair,
|
||||
blind_sign_request: &BlindSignRequest,
|
||||
public_attributes: &[Attribute],
|
||||
total_params: u32,
|
||||
) -> Result<BlindedSignature, Error> {
|
||||
let client = validator_client::ApiClient::new(validator_url.clone());
|
||||
|
||||
let blind_sign_request_body = BlindSignRequestBody::new(
|
||||
&blind_sign_request,
|
||||
elgamal_keypair.public_key(),
|
||||
public_attributes,
|
||||
total_params,
|
||||
);
|
||||
|
||||
Ok(client
|
||||
.blind_sign(&blind_sign_request_body)
|
||||
.await?
|
||||
.blinded_signature)
|
||||
}
|
||||
|
||||
async fn obtain_partial_credential(
|
||||
params: &Parameters,
|
||||
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,
|
||||
private_attributes,
|
||||
public_attributes,
|
||||
)?;
|
||||
|
||||
let blind_sign_request_body = BlindSignRequestBody::new(
|
||||
&blind_sign_request,
|
||||
elgamal_keypair.public_key(),
|
||||
public_attributes,
|
||||
(public_attributes.len() + private_attributes.len()) as u32,
|
||||
);
|
||||
|
||||
let blinded_signature = client
|
||||
.blind_sign(&blind_sign_request_body)
|
||||
.await?
|
||||
.blinded_signature;
|
||||
Ok(blinded_signature
|
||||
.unblind(
|
||||
params,
|
||||
elgamal_keypair.private_key(),
|
||||
validator_vk,
|
||||
private_attributes,
|
||||
public_attributes,
|
||||
&blind_sign_request.get_commitment_hash(),
|
||||
)
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
pub async fn obtain_aggregate_signature(
|
||||
params: &Parameters,
|
||||
public_attributes: &[Attribute],
|
||||
private_attributes: &[Attribute],
|
||||
validators: &[Url],
|
||||
) -> Result<Signature, Error> {
|
||||
if validators.is_empty() {
|
||||
return Err(Error::NoValidatorsAvailable);
|
||||
}
|
||||
|
||||
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?;
|
||||
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 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())).unwrap();
|
||||
Ok(aggregate_signature_shares(
|
||||
params,
|
||||
&verification_key,
|
||||
&attributes,
|
||||
&shares,
|
||||
)?)
|
||||
}
|
||||
|
||||
// TODO: better type flow
|
||||
pub fn prepare_credential_for_spending(
|
||||
params: &Parameters,
|
||||
public_attributes: Vec<Vec<u8>>,
|
||||
serial_number: Attribute,
|
||||
binding_number: Attribute,
|
||||
signature: &Signature,
|
||||
verification_key: &VerificationKey,
|
||||
) -> Result<Credential, Error> {
|
||||
let theta = prove_bandwidth_credential(
|
||||
params,
|
||||
verification_key,
|
||||
signature,
|
||||
serial_number,
|
||||
binding_number,
|
||||
)?;
|
||||
|
||||
Ok(Credential::new(
|
||||
(public_attributes.len() + PRIVATE_ATTRIBUTES as usize) as u32,
|
||||
theta,
|
||||
public_attributes,
|
||||
signature,
|
||||
))
|
||||
}
|
||||
@@ -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"] }
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod keys;
|
||||
pub mod msg;
|
||||
pub mod payment;
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = "1.1"
|
||||
az = "1.1"
|
||||
log = "0.4.14"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
ts-rs = "3.0"
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -7,9 +7,9 @@ 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)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema, TS)]
|
||||
pub struct Gateway {
|
||||
pub host: String,
|
||||
pub mix_port: u16,
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod delegation;
|
||||
pub mod error;
|
||||
mod gateway;
|
||||
pub mod mixnode;
|
||||
mod mixnode;
|
||||
mod msg;
|
||||
mod types;
|
||||
|
||||
@@ -16,7 +15,4 @@ pub use delegation::{
|
||||
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::{
|
||||
IdentityKey, IdentityKeyRef, LayerDistribution, RewardingIntervalResponse, SphinxKey,
|
||||
StateParams,
|
||||
};
|
||||
pub use types::{IdentityKey, IdentityKeyRef, LayerDistribution, SphinxKey, StateParams};
|
||||
|
||||
@@ -2,20 +2,15 @@
|
||||
#![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
|
||||
|
||||
#[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,
|
||||
@@ -28,17 +23,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 {
|
||||
@@ -48,97 +33,6 @@ 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 {
|
||||
U128::from_num(1) / U128::from_num(self.k.u128())
|
||||
}
|
||||
|
||||
pub fn alpha(&self) -> U128 {
|
||||
U128::from_num(self.sybil_resistance_percent) / U128::from_num(100)
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
@@ -147,7 +41,6 @@ pub struct MixNodeBond {
|
||||
pub layer: Layer,
|
||||
pub block_height: u64,
|
||||
pub mix_node: MixNode,
|
||||
pub profit_margin_percent: Option<u8>,
|
||||
}
|
||||
|
||||
impl MixNodeBond {
|
||||
@@ -157,7 +50,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),
|
||||
@@ -166,15 +58,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
|
||||
}
|
||||
@@ -190,124 +76,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 = U128::from_num(1u128);
|
||||
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())
|
||||
/ (U128::from_num(1) + 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()
|
||||
+ (U128::from_num(1) - 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 scaled_delegation_amount =
|
||||
U128::from_num(delegation_amount.u128()) / U128::from_num(params.circulating_supply());
|
||||
|
||||
let delegator_reward = (U128::from_num(1) - self.profit_margin())
|
||||
* scaled_delegation_amount
|
||||
/ self.sigma(params)
|
||||
* self.node_profit(params);
|
||||
|
||||
let reward = delegator_reward.max(U128::from_num(0));
|
||||
if let Some(int_reward) = reward.checked_cast() {
|
||||
int_reward
|
||||
} else {
|
||||
error!(
|
||||
"Could not cast delegator reward ({}) to u128, returning 0 - mixnode {}",
|
||||
reward,
|
||||
self.identity()
|
||||
);
|
||||
0u128
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for MixNodeBond {
|
||||
@@ -439,7 +207,6 @@ mod tests {
|
||||
layer: Layer::One,
|
||||
block_height: 100,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin_percent: Some(10),
|
||||
};
|
||||
|
||||
let mix2 = MixNodeBond {
|
||||
@@ -449,7 +216,6 @@ mod tests {
|
||||
layer: Layer::One,
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin_percent: Some(10),
|
||||
};
|
||||
|
||||
let mix3 = MixNodeBond {
|
||||
@@ -459,7 +225,6 @@ mod tests {
|
||||
layer: Layer::One,
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin_percent: Some(10),
|
||||
};
|
||||
|
||||
let mix4 = MixNodeBond {
|
||||
@@ -469,7 +234,6 @@ mod tests {
|
||||
layer: Layer::One,
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin_percent: Some(10),
|
||||
};
|
||||
|
||||
let mix5 = MixNodeBond {
|
||||
@@ -479,7 +243,6 @@ mod tests {
|
||||
layer: Layer::One,
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin_percent: Some(10),
|
||||
};
|
||||
|
||||
// summary:
|
||||
|
||||
@@ -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,32 +31,10 @@ pub enum ExecuteMsg {
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
|
||||
BeginMixnodeRewarding {
|
||||
// nonce of the current rewarding interval
|
||||
rewarding_interval_nonce: u32,
|
||||
},
|
||||
|
||||
RewardMixnode {
|
||||
identity: IdentityKey,
|
||||
// percentage value in range 0-100
|
||||
uptime: u32,
|
||||
|
||||
// nonce of the current rewarding interval
|
||||
rewarding_interval_nonce: u32,
|
||||
},
|
||||
|
||||
FinishMixnodeRewarding {
|
||||
// nonce of the current rewarding interval
|
||||
rewarding_interval_nonce: u32,
|
||||
},
|
||||
|
||||
RewardMixnodeV2 {
|
||||
identity: IdentityKey,
|
||||
// percentage value in range 0-100
|
||||
params: NodeRewardParams,
|
||||
|
||||
// nonce of the current rewarding interval
|
||||
rewarding_interval_nonce: u32,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -79,7 +56,6 @@ pub enum QueryMsg {
|
||||
address: Addr,
|
||||
},
|
||||
StateParams {},
|
||||
CurrentRewardingInterval {},
|
||||
GetMixDelegations {
|
||||
mix_identity: IdentityKey,
|
||||
start_after: Option<Addr>,
|
||||
@@ -99,10 +75,6 @@ pub enum QueryMsg {
|
||||
address: Addr,
|
||||
},
|
||||
LayerDistribution {},
|
||||
GetRewardPool {},
|
||||
GetCirculatingSupply {},
|
||||
GetEpochRewardPercent {},
|
||||
GetSybilResistancePercent {},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
|
||||
@@ -26,29 +26,15 @@ 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 {
|
||||
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
|
||||
|
||||
pub mixnode_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
|
||||
|
||||
// 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_active_set_size: u32,
|
||||
}
|
||||
|
||||
@@ -68,11 +54,6 @@ impl Display for StateParams {
|
||||
"mixnode delegation reward rate: {}; ",
|
||||
self.mixnode_delegation_reward_rate
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"mixnode rewarded set size: {}",
|
||||
self.mixnode_rewarded_set_size
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"mixnode active set size: {}",
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
"#;
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
[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"] }
|
||||
@@ -0,0 +1,333 @@
|
||||
// Copyright 2021 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
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(¶ms);
|
||||
|
||||
let expected = params.gen1() * keypair.private_key.0;
|
||||
let gamma = keypair.public_key.0;
|
||||
assert_eq!(
|
||||
expected, gamma,
|
||||
"Public key, gamma, should be equal to g1^d, where d is the private key"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encryption() {
|
||||
let params = Parameters::default();
|
||||
let keypair = super::elgamal_keygen(¶ms);
|
||||
|
||||
let r = params.random_scalar();
|
||||
let h = params.gen1() * r;
|
||||
let m = params.random_scalar();
|
||||
|
||||
let (ciphertext, ephemeral_key) = keypair.public_key.encrypt(¶ms, &h, &m);
|
||||
|
||||
let expected_c1 = params.gen1() * ephemeral_key;
|
||||
assert_eq!(expected_c1, ciphertext.0, "c1 should be equal to g1^k");
|
||||
|
||||
let expected_c2 = keypair.public_key.0 * ephemeral_key + h * m;
|
||||
assert_eq!(
|
||||
expected_c2, ciphertext.1,
|
||||
"c2 should be equal to gamma^k * h^m"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decryption() {
|
||||
let params = Parameters::default();
|
||||
let keypair = super::elgamal_keygen(¶ms);
|
||||
|
||||
let r = params.random_scalar();
|
||||
let h = params.gen1() * r;
|
||||
let m = params.random_scalar();
|
||||
|
||||
let (ciphertext, _) = keypair.public_key.encrypt(¶ms, &h, &m);
|
||||
let dec = keypair.private_key.decrypt(&ciphertext);
|
||||
|
||||
let expected = h * m;
|
||||
assert_eq!(
|
||||
expected, dec,
|
||||
"after ElGamal decryption, original h^m should be obtained"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_key_bytes_roundtrip() {
|
||||
let params = Parameters::default();
|
||||
let private_key = PrivateKey(params.random_scalar());
|
||||
let bytes = private_key.to_bytes();
|
||||
|
||||
// also make sure it is equivalent to the internal scalar's bytes
|
||||
assert_eq!(private_key.0.to_bytes(), bytes);
|
||||
assert_eq!(private_key, PrivateKey::from_bytes(&bytes).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_key_bytes_roundtrip() {
|
||||
let params = Parameters::default();
|
||||
let r = params.random_scalar();
|
||||
let public_key = PublicKey(params.gen1() * r);
|
||||
let bytes = public_key.to_bytes();
|
||||
|
||||
// also make sure it is equivalent to the internal g1 compressed bytes
|
||||
assert_eq!(public_key.0.to_affine().to_compressed(), bytes);
|
||||
assert_eq!(public_key, PublicKey::from_bytes(&bytes).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ciphertext_bytes_roundtrip() {
|
||||
let params = Parameters::default();
|
||||
let r = params.random_scalar();
|
||||
let s = params.random_scalar();
|
||||
let ciphertext = Ciphertext(params.gen1() * r, params.gen1() * s);
|
||||
let bytes = ciphertext.to_bytes();
|
||||
|
||||
// also make sure it is equivalent to the internal g1 compressed bytes concatenated
|
||||
let expected_bytes = [
|
||||
ciphertext.0.to_affine().to_compressed(),
|
||||
ciphertext.1.to_affine().to_compressed(),
|
||||
]
|
||||
.concat();
|
||||
assert_eq!(expected_bytes, bytes);
|
||||
assert_eq!(ciphertext, Ciphertext::try_from(&bytes[..]).unwrap())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2021 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
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);
|
||||
@@ -0,0 +1,2 @@
|
||||
mod clone;
|
||||
mod serde;
|
||||
@@ -0,0 +1,56 @@
|
||||
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);
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2021 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
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;
|
||||
pub mod proofs;
|
||||
pub mod scheme;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub 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 {}
|
||||
@@ -0,0 +1,836 @@
|
||||
// Copyright 2021 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// 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 || rr || rk.len() || rk || rm.len() || rm
|
||||
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 * 4 + 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 ProofKappa {
|
||||
challenge: Scalar,
|
||||
response_attributes: Vec<Scalar>,
|
||||
response_blinder: Scalar,
|
||||
}
|
||||
|
||||
impl ProofKappa {
|
||||
pub fn construct(
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKey,
|
||||
blinding_factor: &Scalar,
|
||||
blinded_message: &G2Projective,
|
||||
private_attributes: &[Attribute],
|
||||
verifier_id: &[u8; 32],
|
||||
timestamp: &[u8; 32],
|
||||
) -> Self {
|
||||
// create the witnesses
|
||||
let witness_blinder = params.random_scalar();
|
||||
let witness_attributes = params.n_random_scalars(private_attributes.len());
|
||||
|
||||
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>();
|
||||
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(params.gen2().to_bytes().as_ref())
|
||||
.chain(std::iter::once(blinded_message.to_bytes().as_ref())) //kappa
|
||||
.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(verifier_id.as_ref()))
|
||||
.chain(std::iter::once(timestamp.as_ref())),
|
||||
);
|
||||
|
||||
// responses
|
||||
let response_blinder = produce_response(&witness_blinder, &challenge, blinding_factor);
|
||||
let response_attributes =
|
||||
produce_responses(&witness_attributes, &challenge, private_attributes);
|
||||
|
||||
ProofKappa {
|
||||
challenge,
|
||||
response_attributes,
|
||||
response_blinder,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn private_attributes_len(&self) -> usize {
|
||||
self.response_attributes.len()
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKey,
|
||||
kappa: &G2Projective,
|
||||
verifier_id: &[u8; 32],
|
||||
timestamp: &[u8; 32],
|
||||
) -> bool {
|
||||
let beta_bytes = verification_key
|
||||
.beta
|
||||
.iter()
|
||||
.map(|beta_i| beta_i.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// 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)
|
||||
+ self
|
||||
.response_attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta.iter())
|
||||
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
// 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(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(verifier_id.as_ref()))
|
||||
.chain(std::iter::once(timestamp.as_ref())),
|
||||
);
|
||||
|
||||
challenge == self.challenge
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let attributes_len = self.response_attributes.len() as u64;
|
||||
let mut bytes = Vec::with_capacity(8 + (attributes_len + 2) as usize * 32);
|
||||
|
||||
bytes.extend_from_slice(&self.challenge.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_blinder.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 fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
// at the very minimum there must be a single attribute being proven
|
||||
if bytes.len() < 32 * 2 + 8 || (bytes.len() - 8) % 32 != 0 {
|
||||
return Err(
|
||||
CoconutError::Deserialization(
|
||||
"tried to deserialize proof of ciphertexts and commitment with bytes of invalid length".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
let challenge_bytes = bytes[..32].try_into().unwrap();
|
||||
let challenge = try_deserialize_scalar(
|
||||
&challenge_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize challenge".to_string()),
|
||||
)?;
|
||||
|
||||
let blinder_bytes = bytes[32..64].try_into().unwrap();
|
||||
let response_blinder = try_deserialize_scalar(
|
||||
&blinder_bytes,
|
||||
CoconutError::Deserialization("failed to deserialize the blinder".to_string()),
|
||||
)?;
|
||||
|
||||
let rm_len = u64::from_le_bytes(bytes[64..64 + 8].try_into().unwrap());
|
||||
let response_attributes = try_deserialize_scalar_vec(
|
||||
rm_len,
|
||||
&bytes[64 + 8..],
|
||||
CoconutError::Deserialization("Failed to deserialize attributes response".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(ProofKappa {
|
||||
challenge,
|
||||
response_attributes,
|
||||
response_blinder,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct ProofKappaNu {
|
||||
// c
|
||||
challenge: Scalar,
|
||||
|
||||
// responses
|
||||
response_serial_number: Scalar,
|
||||
response_binding_number: Scalar,
|
||||
response_blinder: Scalar,
|
||||
}
|
||||
|
||||
impl ProofKappaNu {
|
||||
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())) //kappa
|
||||
.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);
|
||||
|
||||
ProofKappaNu {
|
||||
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 || rm.len() || rm || rt
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
//let attributes_len = self.response_attributes.len() as u64;
|
||||
let attributes_len = 2;
|
||||
let mut bytes = Vec::with_capacity((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 * 3 || (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 * 3 + 8,
|
||||
});
|
||||
}
|
||||
|
||||
let challenge_bytes = bytes[..32].try_into().unwrap();
|
||||
let challenge = try_deserialize_scalar(
|
||||
&challenge_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize challenge".to_string()),
|
||||
)?;
|
||||
|
||||
// let rm_len = u64::from_le_bytes(bytes[32..40].try_into().unwrap());
|
||||
if bytes[32..].len() != (2 + 1) as usize * 32 {
|
||||
return Err(
|
||||
CoconutError::Deserialization(
|
||||
format!("Tried to deserialize proof of kappa and zeta with insufficient number of bytes provided, expected {} got {}.", (2 + 1) as usize * 32, bytes[32..].len())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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(ProofKappaNu {
|
||||
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(¶ms);
|
||||
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(
|
||||
¶ms,
|
||||
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_nu_bytes_roundtrip() {
|
||||
let mut params = setup(1).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(¶ms, &keypair.verification_key(), &private_attributes, r);
|
||||
let zeta = compute_zeta(¶ms, serial_number);
|
||||
|
||||
// 0 public 2 private
|
||||
let pi_v = ProofKappaNu::construct(
|
||||
&mut params,
|
||||
&keypair.verification_key(),
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
&r,
|
||||
&kappa,
|
||||
&zeta,
|
||||
);
|
||||
|
||||
let bytes = pi_v.to_bytes();
|
||||
assert_eq!(ProofKappaNu::from_bytes(&bytes).unwrap(), pi_v);
|
||||
|
||||
// 2 public 2 private
|
||||
let mut params = setup(4).unwrap();
|
||||
let keypair = keygen(&mut params);
|
||||
|
||||
let pi_v = ProofKappaNu::construct(
|
||||
&mut params,
|
||||
&keypair.verification_key(),
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
&r,
|
||||
&kappa,
|
||||
&zeta,
|
||||
);
|
||||
|
||||
let bytes = pi_v.to_bytes();
|
||||
assert_eq!(ProofKappaNu::from_bytes(&bytes).unwrap(), pi_v);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
// Copyright 2021 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
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(
|
||||
¶ms,
|
||||
&aggr_vk_1,
|
||||
&attributes,
|
||||
&sigs[..3],
|
||||
Some(&[1, 2, 3]),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let aggr_vk_2 = aggregate_verification_keys(&vks[2..], Some(&[3, 4, 5])).unwrap();
|
||||
let aggr_sig2 = aggregate_signatures(
|
||||
¶ms,
|
||||
&aggr_vk_1,
|
||||
&attributes,
|
||||
&sigs[2..],
|
||||
Some(&[3, 4, 5]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(aggr_sig1, aggr_sig2);
|
||||
|
||||
// verify credential for good measure
|
||||
assert!(verify(¶ms, &aggr_vk_1, &attributes, &aggr_sig1));
|
||||
assert!(verify(¶ms, &aggr_vk_2, &attributes, &aggr_sig2));
|
||||
|
||||
// aggregating threshold+1 works
|
||||
let aggr_vk_more = aggregate_verification_keys(&vks[1..], Some(&[2, 3, 4, 5])).unwrap();
|
||||
let aggr_more = aggregate_signatures(
|
||||
¶ms,
|
||||
&aggr_vk_more,
|
||||
&attributes,
|
||||
&sigs[1..],
|
||||
Some(&[2, 3, 4, 5]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(aggr_sig1, aggr_more);
|
||||
|
||||
// aggregating all
|
||||
let aggr_vk_all = aggregate_verification_keys(&vks, Some(&[1, 2, 3, 4, 5])).unwrap();
|
||||
let aggr_all = aggregate_signatures(
|
||||
¶ms,
|
||||
&aggr_vk_all,
|
||||
&attributes,
|
||||
&sigs,
|
||||
Some(&[1, 2, 3, 4, 5]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(aggr_all, aggr_sig1);
|
||||
|
||||
// not taking enough points (threshold was 3) should fail
|
||||
let aggr_vk_not_enough = aggregate_verification_keys(&vks[..2], Some(&[1, 2])).unwrap();
|
||||
let aggr_not_enough = aggregate_signatures(
|
||||
¶ms,
|
||||
&aggr_vk_not_enough,
|
||||
&attributes,
|
||||
&sigs[..2],
|
||||
Some(&[1, 2]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_ne!(aggr_not_enough, aggr_sig1);
|
||||
|
||||
// taking wrong index should fail
|
||||
let aggr_vk_bad = aggregate_verification_keys(&vks[2..], Some(&[1, 2, 3])).unwrap();
|
||||
assert!(aggregate_signatures(
|
||||
¶ms,
|
||||
&aggr_vk_bad,
|
||||
&attributes,
|
||||
&sigs[2..],
|
||||
Some(&[42, 123, 100]),
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
fn random_signature() -> Signature {
|
||||
let mut rng = rand::thread_rng();
|
||||
Signature(
|
||||
G1Projective::random(&mut rng),
|
||||
G1Projective::random(&mut rng),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_aggregation_doesnt_work_for_empty_set_of_signatures() {
|
||||
let signatures: Vec<Signature> = vec![];
|
||||
let 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(¶ms, &aggr_vk_all, &attributes, &signatures, None).is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_aggregation_doesnt_work_if_indices_have_invalid_length() {
|
||||
let signatures = vec![random_signature()];
|
||||
let 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(¶ms, &aggr_vk_all, &attributes, &signatures, Some(&[]))
|
||||
.is_err()
|
||||
);
|
||||
assert!(aggregate_signatures(
|
||||
¶ms,
|
||||
&aggr_vk_all,
|
||||
&attributes,
|
||||
&signatures,
|
||||
Some(&[1, 2]),
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_aggregation_doesnt_work_for_non_unique_indices() {
|
||||
let signatures = vec![random_signature(), random_signature()];
|
||||
let 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(
|
||||
¶ms,
|
||||
&aggr_vk_all,
|
||||
&attributes,
|
||||
&signatures,
|
||||
Some(&[1, 1]),
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
// TODO: test for aggregating non-threshold keys
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
// Copyright 2021 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
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 + 9,
|
||||
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
|
||||
}
|
||||
|
||||
// TODO: perhaps also include pi_s.len()?
|
||||
// to be determined once we implement serde to make sure its 1:1 compatible
|
||||
// with bincode
|
||||
// cm || c.len() || c || pi_s
|
||||
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(¶ms);
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,550 @@
|
||||
// Copyright 2021 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
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 alpha: G2Projective,
|
||||
pub 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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,600 @@
|
||||
// Copyright 2021 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// 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 G1Projective, pub 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 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 super::*;
|
||||
|
||||
#[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(
|
||||
¶ms,
|
||||
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(
|
||||
¶ms,
|
||||
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(
|
||||
¶ms,
|
||||
&keypair1.verification_key(),
|
||||
&theta1,
|
||||
&[],
|
||||
));
|
||||
|
||||
assert!(verify_credential(
|
||||
¶ms,
|
||||
&keypair2.verification_key(),
|
||||
&theta2,
|
||||
&[],
|
||||
));
|
||||
|
||||
assert!(!verify_credential(
|
||||
¶ms,
|
||||
&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(
|
||||
¶ms,
|
||||
&keypair1.verification_key(),
|
||||
&attributes,
|
||||
&sig1,
|
||||
));
|
||||
|
||||
assert!(!verify(
|
||||
¶ms,
|
||||
&keypair2.verification_key(),
|
||||
&attributes,
|
||||
&sig1,
|
||||
));
|
||||
|
||||
assert!(!verify(
|
||||
¶ms,
|
||||
&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(
|
||||
¶ms,
|
||||
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(
|
||||
¶ms,
|
||||
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(
|
||||
¶ms,
|
||||
&keypair1.verification_key(),
|
||||
&theta1,
|
||||
&public_attributes,
|
||||
));
|
||||
|
||||
assert!(verify_credential(
|
||||
¶ms,
|
||||
&keypair2.verification_key(),
|
||||
&theta2,
|
||||
&public_attributes,
|
||||
));
|
||||
|
||||
assert!(!verify_credential(
|
||||
¶ms,
|
||||
&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(¶ms);
|
||||
|
||||
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(
|
||||
¶ms,
|
||||
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(¶ms, &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(
|
||||
¶ms,
|
||||
&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(¶ms, &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(
|
||||
¶ms,
|
||||
&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())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright 2021 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::utils::hash_g1;
|
||||
use bls12_381::{G1Affine, G2Affine, G2Prepared, Scalar};
|
||||
use ff::Field;
|
||||
use group::Curve;
|
||||
use rand::thread_rng;
|
||||
/// 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 fn gen2(&self) -> &G2Affine {
|
||||
&self.g2
|
||||
}
|
||||
|
||||
pub fn prepared_miller_g2(&self) -> &G2Prepared {
|
||||
&self._g2_prepared_miller
|
||||
}
|
||||
|
||||
pub 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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
// Copyright 2021 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
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::{ProofKappa, ProofKappaNu};
|
||||
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 ThetaCovid {
|
||||
// blinded_message (kappa)
|
||||
pub blinded_message: G2Projective,
|
||||
// sigma
|
||||
pub credential: Signature,
|
||||
// pi_v
|
||||
pub pi_v: ProofKappa,
|
||||
}
|
||||
|
||||
impl ThetaCovid {
|
||||
pub fn verify_proof(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKey,
|
||||
verifier_id: &[u8; 32],
|
||||
timestamp: &[u8; 32],
|
||||
) -> bool {
|
||||
self.pi_v.verify(
|
||||
params,
|
||||
verification_key,
|
||||
&self.blinded_message,
|
||||
verifier_id,
|
||||
timestamp,
|
||||
)
|
||||
}
|
||||
|
||||
// kappa || credential || proof
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let blinded_message_bytes = self.blinded_message.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(192 + proof_bytes.len());
|
||||
bytes.extend_from_slice(&blinded_message_bytes);
|
||||
bytes.extend_from_slice(&credential_bytes);
|
||||
bytes.extend_from_slice(&proof_bytes);
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn to_bytes_tuple(&self) -> ([u8; 96], [u8; 96], Vec<u8>) {
|
||||
let blinded_message_bytes = self.blinded_message.to_affine().to_compressed();
|
||||
let credential_bytes = self.credential.to_bytes();
|
||||
let proof_bytes = self.pi_v.to_bytes();
|
||||
|
||||
(blinded_message_bytes, credential_bytes, proof_bytes)
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<ThetaCovid> {
|
||||
if bytes.len() < 192 {
|
||||
return Err(
|
||||
CoconutError::Deserialization(
|
||||
format!("Tried to deserialize theta with insufficient number of bytes, expected >= 192, 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 kappa".to_string()),
|
||||
)?;
|
||||
|
||||
let credential = Signature::try_from(&bytes[96..192])?;
|
||||
|
||||
let pi_v = ProofKappa::from_bytes(&bytes[192..])?;
|
||||
|
||||
Ok(ThetaCovid {
|
||||
blinded_message,
|
||||
credential,
|
||||
pi_v,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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: ProofKappaNu,
|
||||
}
|
||||
|
||||
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 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 zeta".to_string()),
|
||||
)?;
|
||||
let credential = Signature::try_from(&bytes[192..288])?;
|
||||
|
||||
let pi_v = ProofKappaNu::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,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: perhaps also include pi_v.len()?
|
||||
// to be determined once we implement serde to make sure its 1:1 compatible
|
||||
// with bincode
|
||||
// kappa || nu || 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_covid_credential(
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKey,
|
||||
signature: &Signature,
|
||||
private_attributes: &[Attribute],
|
||||
verifier_id: &[u8; 32],
|
||||
timestamp: &[u8; 32],
|
||||
) -> Result<ThetaCovid> {
|
||||
if verification_key.beta.len() < params.gen_hs().len() {
|
||||
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 blinded_message = compute_kappa(
|
||||
params,
|
||||
verification_key,
|
||||
&private_attributes,
|
||||
sign_blinding_factor,
|
||||
);
|
||||
|
||||
let pi_v = ProofKappa::construct(
|
||||
params,
|
||||
verification_key,
|
||||
&sign_blinding_factor,
|
||||
&blinded_message,
|
||||
&private_attributes,
|
||||
verifier_id,
|
||||
timestamp,
|
||||
);
|
||||
|
||||
Ok(ThetaCovid {
|
||||
blinded_message,
|
||||
credential: signature_prime,
|
||||
pi_v,
|
||||
})
|
||||
}
|
||||
|
||||
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 = ProofKappaNu::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_covid_credential(
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKey,
|
||||
theta: &ThetaCovid,
|
||||
public_attributes: &[Attribute],
|
||||
verifier_id: &[u8; 32],
|
||||
timestamp: &[u8; 32],
|
||||
) -> bool {
|
||||
if public_attributes.len() + theta.pi_v.private_attributes_len() > verification_key.beta.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !theta.verify_proof(params, verification_key, verifier_id, timestamp) {
|
||||
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())
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
use crate::{
|
||||
aggregate_signature_shares, aggregate_verification_keys, blind_sign, elgamal_keygen,
|
||||
hash_to_scalar, 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(¶ms);
|
||||
|
||||
// generate commitment and encryption
|
||||
let blind_sign_request = prepare_blind_sign(
|
||||
¶ms,
|
||||
&elgamal_keypair,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
)?;
|
||||
|
||||
// generate_keys
|
||||
let coconut_keypairs = ttp_keygen(¶ms, 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(
|
||||
¶ms,
|
||||
&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(
|
||||
¶ms,
|
||||
&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(¶ms, &verification_key, &attributes, &signature_shares)?;
|
||||
|
||||
// Generate cryptographic material to verify them
|
||||
|
||||
let theta = prove_bandwidth_credential(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&signature,
|
||||
serial_number,
|
||||
binding_number,
|
||||
)?;
|
||||
|
||||
// Verify credentials
|
||||
|
||||
assert!(verify_credential(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&theta,
|
||||
&public_attributes,
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
use crate::scheme::verification::{prove_covid_credential, verify_covid_credential, ThetaCovid};
|
||||
use crate::{
|
||||
aggregate_signature_shares, aggregate_verification_keys, blind_sign, elgamal_keygen,
|
||||
hash_to_scalar, prepare_blind_sign, setup, ttp_keygen, CoconutError, Signature, SignatureShare,
|
||||
VerificationKey,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn main() -> Result<(), CoconutError> {
|
||||
let params = setup(15)?;
|
||||
|
||||
// validators keys
|
||||
let coconut_keypairs = ttp_keygen(¶ms, 2, 3)?;
|
||||
let verification_keys: Vec<VerificationKey> = coconut_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
let verification_key = aggregate_verification_keys(&verification_keys, Some(&[1, 2, 3]))?;
|
||||
|
||||
// user's ElGamal keypair
|
||||
let elgamal_keypair = elgamal_keygen(¶ms);
|
||||
|
||||
// attributes to consider
|
||||
let patient_id = hash_to_scalar(String::from("NHS678777").as_bytes());
|
||||
let full_name = hash_to_scalar(String::from("JaneDoe").as_bytes());
|
||||
let vaccine_medication_product_id = hash_to_scalar(String::from("EU/1/20/1528").as_bytes());
|
||||
let country_of_vaccination = hash_to_scalar(String::from("UK").as_bytes());
|
||||
let issuer = hash_to_scalar(String::from("NHS").as_bytes());
|
||||
let dob = hash_to_scalar(String::from("2021-11-05").as_bytes());
|
||||
|
||||
let public_attributes = vec![
|
||||
patient_id,
|
||||
full_name,
|
||||
vaccine_medication_product_id,
|
||||
country_of_vaccination,
|
||||
issuer,
|
||||
dob,
|
||||
];
|
||||
let user_secret = params.random_scalar();
|
||||
let private_attributes = vec![user_secret];
|
||||
|
||||
// ISSUANCE PROTOCOL
|
||||
let blind_sign_request = prepare_blind_sign(
|
||||
¶ms,
|
||||
&elgamal_keypair,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
)?;
|
||||
|
||||
// generate blinded signatures
|
||||
let mut blinded_signatures = Vec::new();
|
||||
|
||||
let is_vaccinated = hash_to_scalar(String::from("TRUE").as_bytes());
|
||||
let is_over_18 = hash_to_scalar(String::from("TRUE").as_bytes());
|
||||
let is_over_21 = hash_to_scalar(String::from("TRUE").as_bytes());
|
||||
|
||||
// These are the attributes on which the validator issues a signature
|
||||
let public_attributes = [
|
||||
patient_id,
|
||||
full_name,
|
||||
vaccine_medication_product_id,
|
||||
country_of_vaccination,
|
||||
issuer,
|
||||
dob,
|
||||
is_vaccinated,
|
||||
is_over_18,
|
||||
is_over_21,
|
||||
];
|
||||
|
||||
for keypair in coconut_keypairs {
|
||||
let blinded_signature = blind_sign(
|
||||
¶ms,
|
||||
&keypair.secret_key(),
|
||||
&elgamal_keypair.public_key(),
|
||||
&blind_sign_request,
|
||||
&public_attributes,
|
||||
)?;
|
||||
blinded_signatures.push(blinded_signature)
|
||||
}
|
||||
|
||||
let unblinded_signatures: Vec<Signature> = blinded_signatures
|
||||
.into_iter()
|
||||
.zip(verification_keys.iter())
|
||||
.map(|(signature, verification_key)| {
|
||||
signature
|
||||
.unblind(
|
||||
¶ms,
|
||||
&elgamal_keypair.private_key(),
|
||||
&verification_key,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&blind_sign_request.get_commitment_hash(),
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
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(1 + 9);
|
||||
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(¶ms, &verification_key, &attributes, &signature_shares)?;
|
||||
|
||||
// SHOW PROTOCOL
|
||||
let verifier_id = [11u8; 32];
|
||||
let timestamp = [12u8; 32];
|
||||
|
||||
let show_private_attributes = vec![
|
||||
user_secret,
|
||||
patient_id,
|
||||
full_name,
|
||||
vaccine_medication_product_id,
|
||||
country_of_vaccination,
|
||||
issuer,
|
||||
dob,
|
||||
];
|
||||
|
||||
// Prove covid credential
|
||||
let theta_covid = prove_covid_credential(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&signature,
|
||||
&show_private_attributes,
|
||||
&verifier_id,
|
||||
×tamp,
|
||||
)?;
|
||||
|
||||
let theta_covid_bytes = theta_covid.to_bytes();
|
||||
println!("Length of theta in bytes: {:?}", theta_covid_bytes.len());
|
||||
|
||||
let theta_covid_from_bytes = ThetaCovid::from_bytes(&*theta_covid_bytes).unwrap();
|
||||
|
||||
// Verify covid credentials
|
||||
let disclosed_attributes = vec![is_vaccinated, is_over_18, is_over_21];
|
||||
assert!(verify_covid_credential(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&theta_covid_from_bytes,
|
||||
disclosed_attributes.as_ref(),
|
||||
&verifier_id,
|
||||
×tamp,
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod e2e;
|
||||
mod e2e_covid;
|
||||
@@ -0,0 +1,22 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
// Copyright 2021 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
Generated
-863
@@ -1,863 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
|
||||
dependencies = [
|
||||
"block-padding",
|
||||
"byte-tools",
|
||||
"byteorder",
|
||||
"generic-array 0.12.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
|
||||
dependencies = [
|
||||
"byte-tools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"handlebars",
|
||||
"humantime-serde",
|
||||
"network-defaults",
|
||||
"serde",
|
||||
"toml",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdab415d6744056100f40250a66bc430c1a46f7a02e20bc11c94c79a0f0464df"
|
||||
|
||||
[[package]]
|
||||
name = "cosmos_contract"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
"erc20-bridge-contract",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ed25519-zebra",
|
||||
"k256",
|
||||
"rand_core 0.5.1",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cosmwasm-crypto",
|
||||
"cosmwasm-derive",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde-json-wasm",
|
||||
"thiserror",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-storage"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-bigint"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d12477e115c0d570c12a2dfd859f80b55b60ddb5075df210d3af06d133a69f45"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-mac"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"digest 0.9.0",
|
||||
"rand_core 0.5.1",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28e98c534e9c8a0483aa01d6f6913bc063de254311bd267c9cf535e9b70e15b2"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
||||
dependencies = [
|
||||
"generic-array 0.12.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372"
|
||||
dependencies = [
|
||||
"der",
|
||||
"elliptic-curve",
|
||||
"hmac",
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-zebra"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"hex",
|
||||
"rand_core 0.5.1",
|
||||
"serde",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b"
|
||||
dependencies = [
|
||||
"crypto-bigint",
|
||||
"ff",
|
||||
"generic-array 0.14.4",
|
||||
"group",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "erc20-bridge-contract"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fake-simd"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f"
|
||||
dependencies = [
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.10.2+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912"
|
||||
dependencies = [
|
||||
"ff",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "handlebars"
|
||||
version = "3.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"quick-error",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hex-literal"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21e4590e13640f19f249fe3e4eca5113bc4289f2497710378190e7f4bd96f45b"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
|
||||
dependencies = [
|
||||
"crypto-mac",
|
||||
"digest 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "humantime-serde"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac34a56cfd4acddb469cc7fff187ed5ac36f498ba085caf8bbc725e3ff474058"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||
|
||||
[[package]]
|
||||
name = "k256"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "network-defaults"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal",
|
||||
"serde",
|
||||
"time",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||
dependencies = [
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
|
||||
dependencies = [
|
||||
"maplit",
|
||||
"pest",
|
||||
"sha-1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
dependencies = [
|
||||
"getrandom 0.1.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7a48d098c2a7fdf5740b19deb1181b4fb8a9e68e03ae517c14cde04b5725409"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a9ea2a613fe4cd7118b2bb101a25d8ae6192e1975179b67b2f17afd11e70ac8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-json-wasm"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50eef3672ec8fa45f3457fd423ba131117786784a895548021976117c1ded449"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
|
||||
dependencies = [
|
||||
"block-buffer 0.7.3",
|
||||
"digest 0.8.1",
|
||||
"fake-simd",
|
||||
"opaque-debug 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
|
||||
dependencies = [
|
||||
"block-buffer 0.9.0",
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest 0.9.0",
|
||||
"opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c19772be3c4dd2ceaacf03cb41d5885f2a02c4d8804884918e3a258480803335"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"rand_core 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32"
|
||||
dependencies = [
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99beeb0daeac2bd1e86ac2c21caddecb244b39a093594da1a661ec2060c7aedd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6"
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||
|
||||
[[package]]
|
||||
name = "uint"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crunchy",
|
||||
"hex",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
|
||||
@@ -1,40 +0,0 @@
|
||||
[package]
|
||||
name = "cosmos_contract"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[workspace] # adding a blank workspace to keep it out of the global workspace.
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
debug = false
|
||||
rpath = false
|
||||
lto = true
|
||||
debug-assertions = false
|
||||
codegen-units = 1
|
||||
panic = 'abort'
|
||||
incremental = false
|
||||
overflow-checks = true
|
||||
|
||||
[features]
|
||||
# for more explicit tests, cargo test --features=backtraces
|
||||
backtraces = ["cosmwasm-std/backtraces"]
|
||||
|
||||
[dev-dependencies]
|
||||
config = { path = "../../common/config"}
|
||||
|
||||
[dependencies]
|
||||
erc20-bridge-contract = { path = "../../common/erc20-bridge-contract" }
|
||||
|
||||
# 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", features = ["iterator"] }
|
||||
cosmwasm-storage = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", features = ["iterator"] }
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = "1.0.23"
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{StdError, VerificationError};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Custom errors for contract failure conditions.
|
||||
///
|
||||
/// Add any other custom errors you like here.
|
||||
/// Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details.
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
pub enum ContractError {
|
||||
#[error("{0}")]
|
||||
Std(#[from] StdError),
|
||||
|
||||
#[error("Invalid size for signature items")]
|
||||
InvalidSignatureSize,
|
||||
|
||||
#[error("This payment has already been claimed by someone")]
|
||||
PaymentAlreadyClaimed,
|
||||
|
||||
#[error("Error while verifying ed25519 signature - {0}")]
|
||||
VerificationError(#[from] VerificationError),
|
||||
|
||||
#[error("The payment is not properly signed")]
|
||||
BadSignature,
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod error;
|
||||
mod queries;
|
||||
mod storage;
|
||||
mod support;
|
||||
mod transactions;
|
||||
|
||||
use cosmwasm_std::{
|
||||
entry_point, to_binary, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
|
||||
};
|
||||
|
||||
use crate::error::ContractError;
|
||||
use erc20_bridge_contract::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||
|
||||
/// Instantiate the contract.
|
||||
///
|
||||
/// `deps` contains Storage, API and Querier
|
||||
/// `env` contains block, message and contract info
|
||||
/// `msg` is the contract initialization message, sort of like a constructor call.
|
||||
#[entry_point]
|
||||
pub fn instantiate(
|
||||
_deps: DepsMut,
|
||||
_env: Env,
|
||||
_info: MessageInfo,
|
||||
_msg: InstantiateMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
/// Handle an incoming message
|
||||
#[entry_point]
|
||||
pub fn execute(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::LinkPayment { data } => transactions::link_payment(deps, env, info, data),
|
||||
}
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
|
||||
let query_res = match msg {
|
||||
QueryMsg::GetPayments { start_after, limit } => {
|
||||
to_binary(&queries::query_payments_paged(deps, start_after, limit)?)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(query_res?)
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
|
||||
use cosmwasm_std::{coins, from_binary};
|
||||
use erc20_bridge_contract::payment::PagedPaymentResponse;
|
||||
|
||||
#[test]
|
||||
fn initialize_contract() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let env = mock_env();
|
||||
let msg = InstantiateMsg {};
|
||||
let info = mock_info("creator", &[]);
|
||||
|
||||
let res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
|
||||
assert_eq!(0, res.messages.len());
|
||||
|
||||
// payments should be empty after initialization
|
||||
let res = query(
|
||||
deps.as_ref(),
|
||||
env.clone(),
|
||||
QueryMsg::GetPayments {
|
||||
start_after: None,
|
||||
limit: Option::from(2),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let page: PagedPaymentResponse = from_binary(&res).unwrap();
|
||||
assert_eq!(0, page.payments.len()); // there are no payments in the list when it's just been initialized
|
||||
|
||||
// Contract balance should match what we initialized it as
|
||||
assert_eq!(
|
||||
coins(0, DENOM),
|
||||
vec![deps
|
||||
.as_ref()
|
||||
.querier
|
||||
.query_balance(env.contract.address, DENOM)
|
||||
.unwrap()]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{Deps, Order, StdResult};
|
||||
|
||||
use crate::storage::payments_read;
|
||||
use erc20_bridge_contract::keys::PublicKey;
|
||||
use erc20_bridge_contract::payment::{PagedPaymentResponse, Payment};
|
||||
|
||||
const PAYMENT_PAGE_MAX_LIMIT: u32 = 100;
|
||||
const PAYMENT_PAGE_DEFAULT_LIMIT: u32 = 50;
|
||||
|
||||
/// Adds a 0 byte to terminate the `start_after` value given. This allows CosmWasm
|
||||
/// to get the succeeding key as the start of the next page.
|
||||
fn calculate_start_value<B: AsRef<[u8]>>(start_after: Option<B>) -> Option<Vec<u8>> {
|
||||
start_after.as_ref().map(|identity| {
|
||||
identity
|
||||
.as_ref()
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(std::iter::once(0))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn query_payments_paged(
|
||||
deps: Deps,
|
||||
start_after: Option<PublicKey>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedPaymentResponse> {
|
||||
let limit = limit
|
||||
.unwrap_or(PAYMENT_PAGE_DEFAULT_LIMIT)
|
||||
.min(PAYMENT_PAGE_MAX_LIMIT) as usize;
|
||||
let start = calculate_start_value(start_after);
|
||||
|
||||
let payments = payments_read(deps.storage)
|
||||
.range(start.as_deref(), None, Order::Ascending)
|
||||
.take(limit)
|
||||
.map(|res| res.map(|item| item.1))
|
||||
.collect::<StdResult<Vec<Payment>>>()?;
|
||||
|
||||
let start_next_after = payments.last().map(|payment| payment.verification_key());
|
||||
|
||||
Ok(PagedPaymentResponse::new(payments, limit, start_next_after))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::storage::payments;
|
||||
use crate::support::tests::helpers;
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[test]
|
||||
fn payments_empty_on_init() {
|
||||
let deps = helpers::init_contract();
|
||||
let response = query_payments_paged(deps.as_ref(), None, Option::from(2)).unwrap();
|
||||
assert_eq!(0, response.payments.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn payments_paged_retrieval_obeys_limits() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let storage = deps.as_mut().storage;
|
||||
let limit = 2;
|
||||
for n in 0u32..10000 {
|
||||
let bytes: Vec<u8> = std::iter::repeat(n.to_be_bytes())
|
||||
.take(8)
|
||||
.flatten()
|
||||
.collect();
|
||||
let verification_key = PublicKey::new(bytes.try_into().unwrap());
|
||||
let payment = helpers::payment_fixture();
|
||||
payments(storage)
|
||||
.save(&verification_key.to_bytes(), &payment)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let page1 = query_payments_paged(deps.as_ref(), None, Option::from(limit)).unwrap();
|
||||
assert_eq!(limit, page1.payments.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn payments_paged_retrieval_has_default_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let storage = deps.as_mut().storage;
|
||||
for n in 0u32..100 {
|
||||
let bytes: Vec<u8> = std::iter::repeat(n.to_be_bytes())
|
||||
.take(8)
|
||||
.flatten()
|
||||
.collect();
|
||||
let verification_key = PublicKey::new(bytes.try_into().unwrap());
|
||||
let payment = helpers::payment_fixture();
|
||||
payments(storage)
|
||||
.save(&verification_key.to_bytes(), &payment)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// query without explicitly setting a limit
|
||||
let page1 = query_payments_paged(deps.as_ref(), None, None).unwrap();
|
||||
|
||||
assert_eq!(PAYMENT_PAGE_DEFAULT_LIMIT, page1.payments.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn payments_paged_retrieval_has_max_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let storage = deps.as_mut().storage;
|
||||
for n in 0u32..10000 {
|
||||
let bytes: Vec<u8> = std::iter::repeat(n.to_be_bytes())
|
||||
.take(8)
|
||||
.flatten()
|
||||
.collect();
|
||||
let verification_key = PublicKey::new(bytes.try_into().unwrap());
|
||||
let payment = helpers::payment_fixture();
|
||||
payments(storage)
|
||||
.save(&verification_key.to_bytes(), &payment)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// query with a crazily high limit in an attempt to use too many resources
|
||||
let crazy_limit = 1000;
|
||||
let page1 = query_payments_paged(deps.as_ref(), None, Option::from(crazy_limit)).unwrap();
|
||||
|
||||
// we default to a decent sized upper bound instead
|
||||
assert_eq!(PAYMENT_PAGE_MAX_LIMIT, page1.payments.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn payments_pagination_works() {
|
||||
let key1 = PublicKey::new([1; 32]);
|
||||
let key2 = PublicKey::new([2; 32]);
|
||||
let key3 = PublicKey::new([3; 32]);
|
||||
let key4 = PublicKey::new([4; 32]);
|
||||
|
||||
let mut deps = helpers::init_contract();
|
||||
let payment = helpers::payment_fixture();
|
||||
payments(&mut deps.storage)
|
||||
.save(&key1.to_bytes(), &payment)
|
||||
.unwrap();
|
||||
|
||||
let per_page = 2;
|
||||
let page1 = query_payments_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
|
||||
// page should have 1 result on it
|
||||
assert_eq!(1, page1.payments.len());
|
||||
|
||||
// save another
|
||||
payments(&mut deps.storage)
|
||||
.save(&key2.to_bytes(), &payment)
|
||||
.unwrap();
|
||||
|
||||
// page1 should have 2 results on it
|
||||
let page1 = query_payments_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
assert_eq!(2, page1.payments.len());
|
||||
|
||||
payments(&mut deps.storage)
|
||||
.save(&key3.to_bytes(), &payment)
|
||||
.unwrap();
|
||||
|
||||
// page1 still has 2 results
|
||||
let page1 = query_payments_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
assert_eq!(2, page1.payments.len());
|
||||
|
||||
// retrieving the next page should start after the last key on this page
|
||||
let start_after = key2;
|
||||
let page2 = query_payments_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, page2.payments.len());
|
||||
|
||||
// save another one
|
||||
payments(&mut deps.storage)
|
||||
.save(&key4.to_bytes(), &payment)
|
||||
.unwrap();
|
||||
|
||||
let start_after = key2;
|
||||
let page2 = query_payments_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// now we have 2 pages, with 2 results on the second page
|
||||
assert_eq!(2, page2.payments.len());
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::Storage;
|
||||
use cosmwasm_storage::{bucket, bucket_read, Bucket, ReadonlyBucket};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use erc20_bridge_contract::payment::Payment;
|
||||
|
||||
// buckets
|
||||
const PREFIX_PAYMENTS: &[u8] = b"payments";
|
||||
const PREFIX_STATUS: &[u8] = b"status";
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub enum Status {
|
||||
Unchecked,
|
||||
Checked,
|
||||
Spent,
|
||||
}
|
||||
|
||||
pub fn payments(storage: &mut dyn Storage) -> Bucket<Payment> {
|
||||
bucket(storage, PREFIX_PAYMENTS)
|
||||
}
|
||||
|
||||
pub fn payments_read(storage: &dyn Storage) -> ReadonlyBucket<Payment> {
|
||||
bucket_read(storage, PREFIX_PAYMENTS)
|
||||
}
|
||||
|
||||
pub fn status(storage: &mut dyn Storage) -> Bucket<Status> {
|
||||
bucket(storage, PREFIX_STATUS)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::support::tests::helpers;
|
||||
use cosmwasm_std::testing::MockStorage;
|
||||
use erc20_bridge_contract::keys::PublicKey;
|
||||
|
||||
#[test]
|
||||
fn payments_single_read_retrieval() {
|
||||
let mut storage = MockStorage::new();
|
||||
let key1 = PublicKey::new([1; 32]);
|
||||
let key2 = PublicKey::new([2; 32]);
|
||||
let payment1 = helpers::payment_fixture();
|
||||
let payment2 = helpers::payment_fixture();
|
||||
payments(&mut storage)
|
||||
.save(key1.as_ref(), &payment1)
|
||||
.unwrap();
|
||||
payments(&mut storage)
|
||||
.save(key2.as_ref(), &payment2)
|
||||
.unwrap();
|
||||
|
||||
let res1 = payments_read(&storage).load(key1.as_ref()).unwrap();
|
||||
let res2 = payments_read(&storage).load(key2.as_ref()).unwrap();
|
||||
assert_eq!(payment1, res1);
|
||||
assert_eq!(payment2, res2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn status_single_read_retrieval() {
|
||||
let mut storage = MockStorage::new();
|
||||
let key1 = PublicKey::new([1; 32]);
|
||||
let key2 = PublicKey::new([2; 32]);
|
||||
let status_value = Status::Unchecked;
|
||||
status(&mut storage)
|
||||
.save(key1.as_ref(), &status_value)
|
||||
.unwrap();
|
||||
status(&mut storage)
|
||||
.save(key2.as_ref(), &status_value)
|
||||
.unwrap();
|
||||
|
||||
let res1 = status(&mut storage).load(key1.as_ref()).unwrap();
|
||||
assert_eq!(status_value, res1);
|
||||
let res2 = status(&mut storage).load(key2.as_ref()).unwrap();
|
||||
assert_eq!(status_value, res2);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pub mod tests;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user