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 and wallet should probably get looked by the product team
|
||||||
/explorer/ @nymtech/product
|
/explorer/ @nymtech/product
|
||||||
/nym-wallet/ @nymtech/product
|
/tauri-wallet/ @nymtech/product
|
||||||
/wallet-web/ @nymtech/product
|
/wallet-web/ @nymtech/product
|
||||||
|
|||||||
+70
-36
@@ -1,32 +1,16 @@
|
|||||||
name: Continuous integration
|
name: Continuous integration
|
||||||
|
|
||||||
on:
|
on: [push, pull_request]
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- 'explorer/**'
|
|
||||||
pull_request:
|
|
||||||
paths-ignore:
|
|
||||||
- 'explorer/**'
|
|
||||||
|
|
||||||
jobs:
|
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:
|
build:
|
||||||
needs: matrix_prep
|
|
||||||
strategy:
|
|
||||||
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.os == 'windows-latest' }}
|
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:
|
steps:
|
||||||
- name: Install Dependencies (Linux)
|
- 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
|
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
|
command: fmt
|
||||||
args: --all -- --check
|
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
|
- name: Run clippy
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
if: ${{ matrix.rust != 'nightly' }}
|
if: ${{ matrix.rust != 'nightly' }}
|
||||||
@@ -82,21 +59,78 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
command: clean
|
command: clean
|
||||||
|
|
||||||
- name: Build all binaries with coconut enabled
|
# BUILD
|
||||||
|
- name: Build gateway with coconut feature
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: build
|
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
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: test
|
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
|
uses: actions-rs/cargo@v1
|
||||||
if: ${{ matrix.rust != 'nightly' }}
|
|
||||||
with:
|
with:
|
||||||
command: clippy
|
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
|
name: Mixnet Contract
|
||||||
|
|
||||||
on:
|
on: [push, pull_request]
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- 'explorer/**'
|
|
||||||
pull_request:
|
|
||||||
paths-ignore:
|
|
||||||
- 'explorer/**'
|
|
||||||
|
|
||||||
jobs:
|
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:
|
mixnet-contract:
|
||||||
# since it's going to be compiled into wasm, there's absolutely
|
# since it's going to be compiled into wasm, there's absolutely
|
||||||
# no point in running CI on different OS-es
|
# no point in running CI on different OS-es
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: ${{ matrix.rust == 'nightly' }}
|
continue-on-error: ${{ matrix.rust == 'nightly' }}
|
||||||
needs: matrix_prep
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
|
matrix:
|
||||||
|
rust: [ stable, beta, nightly ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
@@ -41,8 +23,6 @@ jobs:
|
|||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
- uses: actions-rs/cargo@v1
|
- uses: actions-rs/cargo@v1
|
||||||
env:
|
|
||||||
RUSTFLAGS: '-C link-arg=-s'
|
|
||||||
with:
|
with:
|
||||||
command: build
|
command: build
|
||||||
args: --manifest-path contracts/mixnet/Cargo.toml --target wasm32-unknown-unknown
|
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:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: custom-runner-linux
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Install rsync
|
- name: Install rsync
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
SOURCE: "explorer/dist/"
|
SOURCE: "explorer/dist/"
|
||||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
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/"
|
EXCLUDE: "/dist/, /node_modules/"
|
||||||
- name: Keybase - Node Install
|
- name: Keybase - Node Install
|
||||||
run: npm install
|
run: npm install
|
||||||
@@ -44,7 +44,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
NYM_PROJECT_NAME: "Network Explorer"
|
NYM_PROJECT_NAME: "Network Explorer"
|
||||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
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_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||||
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
|
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
name: Webdriverio tests for nym wallet
|
name: Webdriverio tests for nym wallet
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
- "nym-wallet/**"
|
- 'tauri-wallet/**'
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: nym-wallet
|
working-directory: tauri-wallet
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: wallet tests
|
name: wallet tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
@@ -30,8 +30,8 @@ jobs:
|
|||||||
- name: Install minimal stable
|
- name: Install minimal stable
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
|
|
||||||
- name: Node v16
|
- name: Node v16
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
@@ -39,32 +39,32 @@ jobs:
|
|||||||
node-version: 16.x
|
node-version: 16.x
|
||||||
|
|
||||||
- name: Install yarn for building application
|
- name: Install yarn for building application
|
||||||
run: yarn install
|
run: yarn install
|
||||||
|
|
||||||
- name: Build application
|
- name: Build application
|
||||||
run: yarn run webpack:build & yarn run tauri:build
|
run: yarn run webpack:build & yarn run tauri:build
|
||||||
|
|
||||||
- name: Check binary exists
|
- name: Check binary exists
|
||||||
run: |
|
run: |
|
||||||
cd target/release/
|
cd target/release/
|
||||||
(test -f nym-wallet && echo nym binary exists) || echo wallet does not exist
|
(test -f nym-wallet && echo nym binary exists) || echo wallet does not exist
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn install
|
run: yarn install
|
||||||
working-directory: nym-wallet/webdriver
|
working-directory: tauri-wallet/webdriver
|
||||||
|
|
||||||
- name: Remove existing user datafile
|
- name: Remove existing user datafile
|
||||||
uses: JesseTG/rm@v1.0.2
|
uses: JesseTG/rm@v1.0.2
|
||||||
with:
|
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
|
- name: Create user data json file
|
||||||
id: create-json
|
id: create-json
|
||||||
uses: jsdaniell/create-json@1.1.2
|
uses: jsdaniell/create-json@1.1.2
|
||||||
with:
|
with:
|
||||||
name: "user-data.json"
|
name: "user-data.json"
|
||||||
json: ${{ secrets.WALLET_USERDATA }}
|
json: ${{ secrets.WALLET_USERDATA }}
|
||||||
dir: "nym-wallet/webdriver/common/data/"
|
dir: 'tauri-wallet/webdriver/common/data/'
|
||||||
|
|
||||||
- name: Install tauri-driver
|
- name: Install tauri-driver
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
@@ -73,5 +73,5 @@ jobs:
|
|||||||
args: tauri-driver
|
args: tauri-driver
|
||||||
|
|
||||||
- name: Launch tests
|
- name: Launch tests
|
||||||
run: xvfb-run yarn test:runall
|
run: xvfb-run yarn test:newuser
|
||||||
working-directory: nym-wallet/webdriver
|
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**
|
> ✅ **SUCCESS**
|
||||||
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
|
> `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 }}
|
> `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
|
name: Generate TS types
|
||||||
|
|
||||||
on:
|
on: push
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- "explorer/**"
|
|
||||||
pull_request:
|
|
||||||
paths-ignore:
|
|
||||||
- "explorer/**"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
nym-wallet-types:
|
tauri-wallet-types:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Prepare
|
- 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
|
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:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
- name: Generate TS
|
- 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
|
- uses: EndBug/add-and-commit@v7.2.1 # https://github.com/marketplace/actions/add-commit
|
||||||
with:
|
with:
|
||||||
add: '["nym-wallet"]'
|
add: '["tauri-wallet"]'
|
||||||
message: "[ci skip] Generate TS types"
|
message: '[ci skip] Generate TS types'
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
name: Wasm Client
|
name: Wasm Client
|
||||||
|
|
||||||
on:
|
on: [push, pull_request]
|
||||||
pull_request:
|
|
||||||
paths-ignore:
|
|
||||||
- 'explorer/**'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
wasm:
|
wasm:
|
||||||
@@ -19,11 +16,10 @@ jobs:
|
|||||||
override: true
|
override: true
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
# token credentials (non-coconut) don't work for wasm right now
|
- uses: actions-rs/cargo@v1
|
||||||
# - uses: actions-rs/cargo@v1
|
with:
|
||||||
# with:
|
command: build
|
||||||
# command: build
|
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
|
||||||
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
|
|
||||||
|
|
||||||
- uses: actions-rs/cargo@v1
|
- uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
@@ -45,4 +41,4 @@ jobs:
|
|||||||
# - uses: actions-rs/cargo@v1
|
# - uses: actions-rs/cargo@v1
|
||||||
# with:
|
# with:
|
||||||
# command: clippy
|
# 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
|
/.vscode/settings.json
|
||||||
validator/.vscode
|
validator/.vscode
|
||||||
sample-configs/validator-config.toml
|
sample-configs/validator-config.toml
|
||||||
|
.vscode
|
||||||
scripts/deploy_qa.sh
|
scripts/deploy_qa.sh
|
||||||
scripts/run_gate.sh
|
scripts/run_gate.sh
|
||||||
scripts/run_mix.sh
|
scripts/run_mix.sh
|
||||||
@@ -29,8 +30,3 @@ validator-api/v4.json
|
|||||||
validator-api/v6.json
|
validator-api/v6.json
|
||||||
**/node_modules
|
**/node_modules
|
||||||
validator-api/keypair
|
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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
|
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arrayvec"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@@ -234,12 +228,6 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "az"
|
|
||||||
version = "1.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9d6dff4a1892b54d70af377bf7a17064192e822865791d812957f21e3108c325"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base-x"
|
name = "base-x"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
@@ -308,18 +296,6 @@ version = "1.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
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]]
|
[[package]]
|
||||||
name = "blake2"
|
name = "blake2"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
@@ -339,7 +315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "dcd555c66291d5f836dbb6883b48660ece810fe25a31f3bdfb911945dff2691f"
|
checksum = "dcd555c66291d5f836dbb6883b48660ece810fe25a31f3bdfb911945dff2691f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayref",
|
"arrayref",
|
||||||
"arrayvec 0.7.1",
|
"arrayvec",
|
||||||
"cc",
|
"cc",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"constant_time_eq",
|
"constant_time_eq",
|
||||||
@@ -420,7 +396,10 @@ version = "0.2.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -429,24 +408,12 @@ version = "3.7.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538"
|
checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "byte-slice-cast"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ca0796d76a983651b4a0ddda16203032759f2fd9103d9181f7c65c06ee8872e6"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byte-tools"
|
name = "byte-tools"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bytemuck"
|
|
||||||
version = "1.7.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.4.3"
|
version = "1.4.3"
|
||||||
@@ -504,6 +471,15 @@ dependencies = [
|
|||||||
"system-deps 3.2.0",
|
"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]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.70"
|
version = "1.0.70"
|
||||||
@@ -667,30 +643,11 @@ dependencies = [
|
|||||||
name = "coconut-interface"
|
name = "coconut-interface"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"coconut-rs",
|
|
||||||
"getset",
|
"getset",
|
||||||
|
"nymcoconut",
|
||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "colored"
|
name = "colored"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@@ -979,13 +936,47 @@ name = "credentials"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"coconut-interface",
|
"coconut-interface",
|
||||||
"crypto",
|
|
||||||
"network-defaults",
|
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"url",
|
"url",
|
||||||
"validator-client",
|
"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]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -1125,6 +1116,28 @@ dependencies = [
|
|||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "ct-logs"
|
name = "ct-logs"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -1576,14 +1589,6 @@ dependencies = [
|
|||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "erc20-bridge-contract"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"schemars",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "error-chain"
|
name = "error-chain"
|
||||||
version = "0.12.4"
|
version = "0.12.4"
|
||||||
@@ -1593,49 +1598,6 @@ dependencies = [
|
|||||||
"version_check",
|
"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]]
|
[[package]]
|
||||||
name = "explorer-api"
|
name = "explorer-api"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -1731,30 +1693,6 @@ dependencies = [
|
|||||||
"winapi",
|
"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]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
@@ -1851,12 +1789,6 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "funty"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futf"
|
name = "futf"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@@ -1966,12 +1898,6 @@ version = "0.3.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
|
checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-timer"
|
|
||||||
version = "3.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-util"
|
name = "futures-util"
|
||||||
version = "0.3.17"
|
version = "0.3.17"
|
||||||
@@ -2007,27 +1933,20 @@ name = "gateway-client"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"coconut-interface",
|
"coconut-interface",
|
||||||
"credentials",
|
|
||||||
"crypto",
|
"crypto",
|
||||||
"fluvio-wasm-timer",
|
"fluvio-wasm-timer",
|
||||||
"futures",
|
"futures",
|
||||||
"gateway-requests",
|
"gateway-requests",
|
||||||
"getrandom 0.2.3",
|
"getrandom 0.2.3",
|
||||||
"json",
|
|
||||||
"log",
|
"log",
|
||||||
"network-defaults",
|
|
||||||
"nymsphinx",
|
"nymsphinx",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"secp256k1",
|
|
||||||
"thiserror",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
"tungstenite",
|
"tungstenite",
|
||||||
"url",
|
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"wasm-utils",
|
"wasm-utils",
|
||||||
"web3",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2405,9 +2324,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "half"
|
name = "half"
|
||||||
version = "1.8.2"
|
version = "1.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "handlebars"
|
name = "handlebars"
|
||||||
@@ -2490,12 +2409,6 @@ version = "0.4.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hex-literal"
|
|
||||||
version = "0.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "21e4590e13640f19f249fe3e4eca5113bc4289f2497710378190e7f4bd96f45b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hkd32"
|
name = "hkd32"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
@@ -2727,44 +2640,6 @@ dependencies = [
|
|||||||
"winapi-util",
|
"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]]
|
[[package]]
|
||||||
name = "indenter"
|
name = "indenter"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -2923,27 +2798,6 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"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]]
|
[[package]]
|
||||||
name = "k256"
|
name = "k256"
|
||||||
version = "0.9.6"
|
version = "0.9.6"
|
||||||
@@ -3189,15 +3043,10 @@ dependencies = [
|
|||||||
name = "mixnet-contract"
|
name = "mixnet-contract"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"az",
|
|
||||||
"cosmwasm-std",
|
"cosmwasm-std",
|
||||||
"fixed",
|
|
||||||
"log",
|
|
||||||
"network-defaults",
|
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
"thiserror",
|
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3315,7 +3164,6 @@ checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d"
|
|||||||
name = "network-defaults"
|
name = "network-defaults"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hex-literal",
|
|
||||||
"serde",
|
"serde",
|
||||||
"time 0.3.3",
|
"time 0.3.3",
|
||||||
"url",
|
"url",
|
||||||
@@ -3508,7 +3356,6 @@ dependencies = [
|
|||||||
name = "nym-gateway"
|
name = "nym-gateway"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bip39",
|
|
||||||
"clap",
|
"clap",
|
||||||
"coconut-interface",
|
"coconut-interface",
|
||||||
"config",
|
"config",
|
||||||
@@ -3517,15 +3364,12 @@ dependencies = [
|
|||||||
"dashmap",
|
"dashmap",
|
||||||
"dirs",
|
"dirs",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"erc20-bridge-contract",
|
|
||||||
"futures",
|
"futures",
|
||||||
"gateway-client",
|
|
||||||
"gateway-requests",
|
"gateway-requests",
|
||||||
"humantime-serde",
|
"humantime-serde",
|
||||||
"log",
|
"log",
|
||||||
"mixnet-client",
|
"mixnet-client",
|
||||||
"mixnode-common",
|
"mixnode-common",
|
||||||
"network-defaults",
|
|
||||||
"nymsphinx",
|
"nymsphinx",
|
||||||
"pemstore",
|
"pemstore",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
@@ -3540,7 +3384,6 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
"validator-client",
|
"validator-client",
|
||||||
"version-checker",
|
"version-checker",
|
||||||
"web3",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3652,8 +3495,6 @@ dependencies = [
|
|||||||
"pin-project",
|
"pin-project",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"rand 0.8.4",
|
|
||||||
"rand_chacha 0.3.1",
|
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rocket",
|
"rocket",
|
||||||
"rocket_cors",
|
"rocket_cors",
|
||||||
@@ -3670,6 +3511,27 @@ dependencies = [
|
|||||||
"version-checker",
|
"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]]
|
[[package]]
|
||||||
name = "nymsphinx"
|
name = "nymsphinx"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -3834,6 +3696,12 @@ version = "1.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oorandom"
|
||||||
|
version = "11.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opaque-debug"
|
name = "opaque-debug"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -3929,32 +3797,6 @@ dependencies = [
|
|||||||
"system-deps 3.2.0",
|
"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]]
|
[[package]]
|
||||||
name = "parking"
|
name = "parking"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@@ -4264,6 +4106,34 @@ version = "0.3.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb"
|
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]]
|
[[package]]
|
||||||
name = "pmutil"
|
name = "pmutil"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
@@ -4321,19 +4191,6 @@ dependencies = [
|
|||||||
"log",
|
"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]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@@ -4504,12 +4361,6 @@ dependencies = [
|
|||||||
"scheduled-thread-pool",
|
"scheduled-thread-pool",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "radium"
|
|
||||||
version = "0.6.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.6.5"
|
version = "0.6.5"
|
||||||
@@ -4810,6 +4661,12 @@ dependencies = [
|
|||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.25"
|
version = "0.6.25"
|
||||||
@@ -4909,16 +4766,6 @@ dependencies = [
|
|||||||
"opaque-debug 0.3.0",
|
"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]]
|
[[package]]
|
||||||
name = "rocket"
|
name = "rocket"
|
||||||
version = "0.5.0-rc.1"
|
version = "0.5.0-rc.1"
|
||||||
@@ -5067,12 +4914,6 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustc-hex"
|
|
||||||
version = "2.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -5091,6 +4932,15 @@ dependencies = [
|
|||||||
"semver 0.11.0",
|
"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]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.19.1"
|
version = "0.19.1"
|
||||||
@@ -5203,24 +5053,6 @@ dependencies = [
|
|||||||
"untrusted",
|
"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]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "2.4.2"
|
version = "2.4.2"
|
||||||
@@ -5330,6 +5162,16 @@ dependencies = [
|
|||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.130"
|
version = "1.0.130"
|
||||||
@@ -5587,21 +5429,6 @@ dependencies = [
|
|||||||
"ordered-buffer",
|
"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]]
|
[[package]]
|
||||||
name = "soup-sys"
|
name = "soup-sys"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@@ -6201,12 +6028,6 @@ dependencies = [
|
|||||||
"x11-dl",
|
"x11-dl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tap"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tar"
|
name = "tar"
|
||||||
version = "0.4.37"
|
version = "0.4.37"
|
||||||
@@ -6600,12 +6421,13 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiny-keccak"
|
name = "tinytemplate"
|
||||||
version = "2.0.2"
|
version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crunchy",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6705,7 +6527,6 @@ checksum = "08d3725d3efa29485e87311c5b699de63cde14b00ed4d256b8318aa30ca452cd"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"log",
|
"log",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@@ -6770,9 +6591,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ts-rs"
|
name = "ts-rs"
|
||||||
version = "5.1.1"
|
version = "3.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f34750e8cbb4d87d09f6d7441921cc6d9435dcc58df53bbaec01f5f9945d4801"
|
checksum = "369e48de67506679b3a576b0faf666fa9f9acf2fd00b4c61e28bdb6c8e08ec06"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dprint-plugin-typescript",
|
"dprint-plugin-typescript",
|
||||||
"ts-rs-macros",
|
"ts-rs-macros",
|
||||||
@@ -6780,9 +6601,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ts-rs-macros"
|
name = "ts-rs-macros"
|
||||||
version = "5.1.0"
|
version = "3.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c78e4905e64bb23c814098885e08778dfed7fd796ea503df29aa3ba5fead009"
|
checksum = "f269e8fd28e26b4cdbd01f81f345aaf666131511e54a735a76a614b5062d0a5a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -7177,52 +6998,6 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"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]]
|
[[package]]
|
||||||
name = "webkit2gtk"
|
name = "webkit2gtk"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@@ -7433,12 +7208,6 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wyz"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "x11-dl"
|
name = "x11-dl"
|
||||||
version = "2.19.1"
|
version = "2.19.1"
|
||||||
|
|||||||
+2
-2
@@ -25,11 +25,11 @@ members = [
|
|||||||
"common/config",
|
"common/config",
|
||||||
"common/credentials",
|
"common/credentials",
|
||||||
"common/crypto",
|
"common/crypto",
|
||||||
"common/erc20-bridge-contract",
|
|
||||||
"common/mixnet-contract",
|
"common/mixnet-contract",
|
||||||
"common/mixnode-common",
|
"common/mixnode-common",
|
||||||
"common/network-defaults",
|
"common/network-defaults",
|
||||||
"common/nonexhaustive-delayqueue",
|
"common/nonexhaustive-delayqueue",
|
||||||
|
"common/nymcoconut",
|
||||||
"common/nymsphinx",
|
"common/nymsphinx",
|
||||||
"common/nymsphinx/acknowledgements",
|
"common/nymsphinx/acknowledgements",
|
||||||
"common/nymsphinx/addressing",
|
"common/nymsphinx/addressing",
|
||||||
@@ -63,4 +63,4 @@ default-members = [
|
|||||||
"validator-api",
|
"validator-api",
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = ["explorer", "contracts", "tokenomics-py"]
|
exclude = ["explorer", "contracts"]
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ SPDX-License-Identifier: Apache-2.0
|
|||||||
|
|
||||||
## The Nym Privacy Platform
|
## The Nym Privacy Platform
|
||||||
|
|
||||||
|
This repository contains the Nym mixnet.
|
||||||
|
|
||||||
The platform is composed of multiple Rust crates. Top-level executable binary crates include:
|
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.
|
* 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.
|
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
|
### 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.
|
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]
|
[dev-dependencies]
|
||||||
tempfile = "3.1.0"
|
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_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50);
|
||||||
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
|
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
|
||||||
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
|
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
|
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||||
// 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);
|
|
||||||
|
|
||||||
pub fn missing_string_value() -> String {
|
pub fn missing_string_value() -> String {
|
||||||
MISSING_VALUE.to_string()
|
MISSING_VALUE.to_string()
|
||||||
@@ -103,17 +100,6 @@ impl<T: NymConfig> Config<T> {
|
|||||||
self::Client::<T>::default_reply_encryption_key_store_path(&id);
|
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;
|
self.client.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,16 +111,6 @@ impl<T: NymConfig> Config<T> {
|
|||||||
self.client.gateway_listener = gateway_listener.into();
|
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>) {
|
pub fn set_custom_validator_apis(&mut self, validator_api_urls: Vec<Url>) {
|
||||||
self.client.validator_api_urls = validator_api_urls;
|
self.client.validator_api_urls = validator_api_urls;
|
||||||
}
|
}
|
||||||
@@ -197,21 +173,6 @@ impl<T: NymConfig> Config<T> {
|
|||||||
self.client.gateway_listener.clone()
|
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
|
// Debug getters
|
||||||
pub fn get_average_packet_delay(&self) -> Duration {
|
pub fn get_average_packet_delay(&self) -> Duration {
|
||||||
self.debug.average_packet_delay
|
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.
|
/// Address of the gateway listener to which all client requests should be sent.
|
||||||
gateway_listener: String,
|
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.
|
/// 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.
|
/// It is expected to use default value and hence .toml file should not redefine this field.
|
||||||
nym_root_directory: PathBuf,
|
nym_root_directory: PathBuf,
|
||||||
@@ -345,12 +292,6 @@ impl<T: NymConfig> Default for Client<T> {
|
|||||||
reply_encryption_key_store_path: Default::default(),
|
reply_encryption_key_store_path: Default::default(),
|
||||||
gateway_id: "".to_string(),
|
gateway_id: "".to_string(),
|
||||||
gateway_listener: "".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(),
|
nym_root_directory: T::default_root_directory(),
|
||||||
super_struct: Default::default(),
|
super_struct: Default::default(),
|
||||||
}
|
}
|
||||||
@@ -385,11 +326,6 @@ impl<T: NymConfig> Client<T> {
|
|||||||
fn default_reply_encryption_key_store_path(id: &str) -> PathBuf {
|
fn default_reply_encryption_key_store_path(id: &str) -> PathBuf {
|
||||||
T::default_data_directory(Some(id)).join("reply_key_store")
|
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)]
|
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||||
|
|||||||
@@ -42,17 +42,6 @@ public_encryption_key_file = '{{ client.public_encryption_key_file }}'
|
|||||||
# sent but not received back.
|
# sent but not received back.
|
||||||
reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
|
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 #####
|
##### additional client config options #####
|
||||||
|
|
||||||
# ID of the gateway from which the client should be fetching messages.
|
# ID of the gateway from which the client should be fetching messages.
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::client::config::{Config, SocketType};
|
use futures::channel::mpsc;
|
||||||
use crate::websocket;
|
use log::*;
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
|
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||||
use client_core::client::inbound_messages::{
|
use client_core::client::inbound_messages::{
|
||||||
InputMessage, InputMessageReceiver, InputMessageSender,
|
InputMessage, InputMessageReceiver, InputMessageSender,
|
||||||
@@ -22,20 +24,26 @@ use client_core::client::topology_control::{
|
|||||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||||
};
|
};
|
||||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
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 crypto::asymmetric::identity;
|
||||||
use futures::channel::mpsc;
|
|
||||||
use gateway_client::{
|
use gateway_client::{
|
||||||
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
|
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
|
||||||
MixnetMessageSender,
|
MixnetMessageSender,
|
||||||
};
|
};
|
||||||
use log::*;
|
|
||||||
use nymsphinx::addressing::clients::Recipient;
|
use nymsphinx::addressing::clients::Recipient;
|
||||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||||
use nymsphinx::anonymous_replies::ReplySurb;
|
use nymsphinx::anonymous_replies::ReplySurb;
|
||||||
use nymsphinx::receiver::ReconstructedMessage;
|
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;
|
pub(crate) mod config;
|
||||||
|
|
||||||
@@ -165,6 +173,41 @@ impl NymClient {
|
|||||||
.start(self.runtime.handle())
|
.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(
|
fn start_gateway_client(
|
||||||
&mut self,
|
&mut self,
|
||||||
mixnet_message_sender: MixnetMessageSender,
|
mixnet_message_sender: MixnetMessageSender,
|
||||||
@@ -184,17 +227,7 @@ impl NymClient {
|
|||||||
|
|
||||||
self.runtime.block_on(async {
|
self.runtime.block_on(async {
|
||||||
#[cfg(feature = "coconut")]
|
#[cfg(feature = "coconut")]
|
||||||
let bandwidth_controller = BandwidthController::new(
|
let coconut_credential = self.prepare_coconut_credential().await;
|
||||||
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 mut gateway_client = GatewayClient::new(
|
let mut gateway_client = GatewayClient::new(
|
||||||
gateway_address,
|
gateway_address,
|
||||||
@@ -204,11 +237,13 @@ impl NymClient {
|
|||||||
mixnet_message_sender,
|
mixnet_message_sender,
|
||||||
ack_sender,
|
ack_sender,
|
||||||
self.config.get_base().get_gateway_response_timeout(),
|
self.config.get_base().get_gateway_response_timeout(),
|
||||||
Some(bandwidth_controller),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
gateway_client
|
gateway_client
|
||||||
.authenticate_and_start()
|
.authenticate_and_start(
|
||||||
|
#[cfg(feature = "coconut")]
|
||||||
|
Some(coconut_credential),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.expect("could not authenticate and start up the gateway connection");
|
.expect("could not authenticate and start up the gateway connection");
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,39 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::client::config::Config;
|
use std::convert::TryInto;
|
||||||
use crate::commands::override_config;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use clap::{App, Arg, ArgMatches};
|
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::client::key_manager::KeyManager;
|
||||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||||
|
#[cfg(feature = "coconut")]
|
||||||
|
use coconut_interface::{hash_to_scalar, Credential, Parameters};
|
||||||
use config::NymConfig;
|
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 crypto::asymmetric::{encryption, identity};
|
||||||
use gateway_client::GatewayClient;
|
use gateway_client::GatewayClient;
|
||||||
use gateway_requests::registration::handshake::SharedKeys;
|
use gateway_requests::registration::handshake::SharedKeys;
|
||||||
use nymsphinx::addressing::clients::Recipient;
|
use nymsphinx::addressing::clients::Recipient;
|
||||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
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 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> {
|
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!")
|
.about("Initialise a Nym client. Do this first!")
|
||||||
.arg(Arg::with_name("id")
|
.arg(Arg::with_name("id")
|
||||||
.long("id")
|
.long("id")
|
||||||
@@ -36,9 +47,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
|||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
)
|
)
|
||||||
.arg(Arg::with_name("validators")
|
.arg(Arg::with_name("validators")
|
||||||
.long("validators")
|
.long("validators")
|
||||||
.help("Comma separated list of rest endpoints of the validators")
|
.help("Comma separated list of rest endpoints of the validators")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
.arg(Arg::with_name("disable-socket")
|
.arg(Arg::with_name("disable-socket")
|
||||||
.long("disable-socket")
|
.long("disable-socket")
|
||||||
@@ -54,21 +65,40 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
|||||||
.long("fastmode")
|
.long("fastmode")
|
||||||
.hidden(true) // this will prevent this flag from being displayed in `--help`
|
.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")
|
.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(
|
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());
|
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
|
config
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use log::*;
|
|||||||
use version_checker::is_minor_version_compatible;
|
use version_checker::is_minor_version_compatible;
|
||||||
|
|
||||||
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
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")
|
.about("Run the Nym client with provided configuration client optionally overriding set parameters")
|
||||||
.arg(Arg::with_name("id")
|
.arg(Arg::with_name("id")
|
||||||
.long("id")
|
.long("id")
|
||||||
@@ -38,19 +38,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
|||||||
.long("port")
|
.long("port")
|
||||||
.help("Port for the socket (if applicable) to listen on")
|
.help("Port for the socket (if applicable) to listen on")
|
||||||
.takes_value(true)
|
.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
|
// 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.
|
# sent but not received back.
|
||||||
reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
|
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 #####
|
##### additional client config options #####
|
||||||
|
|
||||||
# ID of the gateway from which the client should be fetching messages.
|
# ID of the gateway from which the client should be fetching messages.
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::client::config::Config;
|
use futures::channel::mpsc;
|
||||||
use crate::socks::{
|
use log::*;
|
||||||
authentication::{AuthenticationMethods, Authenticator, User},
|
use tokio::runtime::Runtime;
|
||||||
server::SphinxSocksServer,
|
|
||||||
};
|
|
||||||
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
|
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||||
use client_core::client::inbound_messages::{
|
use client_core::client::inbound_messages::{
|
||||||
InputMessage, InputMessageReceiver, InputMessageSender,
|
InputMessage, InputMessageReceiver, InputMessageSender,
|
||||||
@@ -23,18 +22,27 @@ use client_core::client::topology_control::{
|
|||||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||||
};
|
};
|
||||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
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 crypto::asymmetric::identity;
|
||||||
use futures::channel::mpsc;
|
|
||||||
use gateway_client::{
|
use gateway_client::{
|
||||||
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
|
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
|
||||||
MixnetMessageSender,
|
MixnetMessageSender,
|
||||||
};
|
};
|
||||||
use log::*;
|
|
||||||
use nymsphinx::addressing::clients::Recipient;
|
use nymsphinx::addressing::clients::Recipient;
|
||||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
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;
|
pub(crate) mod config;
|
||||||
|
|
||||||
@@ -153,6 +161,41 @@ impl NymClient {
|
|||||||
.start(self.runtime.handle())
|
.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(
|
fn start_gateway_client(
|
||||||
&mut self,
|
&mut self,
|
||||||
mixnet_message_sender: MixnetMessageSender,
|
mixnet_message_sender: MixnetMessageSender,
|
||||||
@@ -172,17 +215,7 @@ impl NymClient {
|
|||||||
|
|
||||||
self.runtime.block_on(async {
|
self.runtime.block_on(async {
|
||||||
#[cfg(feature = "coconut")]
|
#[cfg(feature = "coconut")]
|
||||||
let bandwidth_controller = BandwidthController::new(
|
let coconut_credential = self.prepare_coconut_credential().await;
|
||||||
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 mut gateway_client = GatewayClient::new(
|
let mut gateway_client = GatewayClient::new(
|
||||||
gateway_address,
|
gateway_address,
|
||||||
@@ -192,11 +225,13 @@ impl NymClient {
|
|||||||
mixnet_message_sender,
|
mixnet_message_sender,
|
||||||
ack_sender,
|
ack_sender,
|
||||||
self.config.get_base().get_gateway_response_timeout(),
|
self.config.get_base().get_gateway_response_timeout(),
|
||||||
Some(bandwidth_controller),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
gateway_client
|
gateway_client
|
||||||
.authenticate_and_start()
|
.authenticate_and_start(
|
||||||
|
#[cfg(feature = "coconut")]
|
||||||
|
Some(coconut_credential),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.expect("could not authenticate and start up the gateway connection");
|
.expect("could not authenticate and start up the gateway connection");
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,37 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::client::config::Config;
|
use std::convert::TryInto;
|
||||||
use crate::commands::override_config;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use clap::{App, Arg, ArgMatches};
|
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::client::key_manager::KeyManager;
|
||||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||||
|
#[cfg(feature = "coconut")]
|
||||||
|
use coconut_interface::{hash_to_scalar, Credential, Parameters};
|
||||||
use config::NymConfig;
|
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 crypto::asymmetric::{encryption, identity};
|
||||||
use gateway_client::GatewayClient;
|
use gateway_client::GatewayClient;
|
||||||
use gateway_requests::registration::handshake::SharedKeys;
|
use gateway_requests::registration::handshake::SharedKeys;
|
||||||
use nymsphinx::addressing::clients::Recipient;
|
use nymsphinx::addressing::clients::Recipient;
|
||||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
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 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> {
|
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!")
|
.about("Initialise a Nym client. Do this first!")
|
||||||
.arg(Arg::with_name("id")
|
.arg(Arg::with_name("id")
|
||||||
.long("id")
|
.long("id")
|
||||||
@@ -40,9 +51,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
|||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
)
|
)
|
||||||
.arg(Arg::with_name("validators")
|
.arg(Arg::with_name("validators")
|
||||||
.long("validators")
|
.long("validators")
|
||||||
.help("Comma separated list of rest endpoints of the validators")
|
.help("Comma separated list of rest endpoints of the validators")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
.arg(Arg::with_name("port")
|
.arg(Arg::with_name("port")
|
||||||
.short("p")
|
.short("p")
|
||||||
@@ -54,21 +65,40 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
|||||||
.long("fastmode")
|
.long("fastmode")
|
||||||
.hidden(true) // this will prevent this flag from being displayed in `--help`
|
.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")
|
.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(
|
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());
|
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
|
config
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use log::*;
|
|||||||
use version_checker::is_minor_version_compatible;
|
use version_checker::is_minor_version_compatible;
|
||||||
|
|
||||||
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
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")
|
.about("Run the Nym client with provided configuration client optionally overriding set parameters")
|
||||||
.arg(Arg::with_name("id")
|
.arg(Arg::with_name("id")
|
||||||
.long("id")
|
.long("id")
|
||||||
@@ -44,19 +44,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
|||||||
.long("port")
|
.long("port")
|
||||||
.help("Port for the socket to listen on")
|
.help("Port for the socket to listen on")
|
||||||
.takes_value(true)
|
.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
|
// this only checks compatibility between config the binary. It does not take into consideration
|
||||||
|
|||||||
@@ -3,21 +3,24 @@
|
|||||||
windows_subsystem = "windows"
|
windows_subsystem = "windows"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use coconut_interface::{
|
use coconut_interface::{
|
||||||
self, hash_to_scalar, Attribute, Credential, Parameters, Signature, Theta, VerificationKey,
|
self, hash_to_scalar, Attribute, Credential, Parameters, Signature, Theta, VerificationKey,
|
||||||
};
|
};
|
||||||
use credentials::{obtain_aggregate_signature, obtain_aggregate_verification_key};
|
use credentials::{obtain_aggregate_signature, obtain_aggregate_verification_key};
|
||||||
use std::sync::Arc;
|
|
||||||
use tokio::sync::RwLock;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
signatures: Vec<Signature>,
|
signatures: Vec<Signature>,
|
||||||
n_attributes: u32,
|
n_attributes: u32,
|
||||||
params: Parameters,
|
params: Parameters,
|
||||||
public_attributes_bytes: Vec<Vec<u8>>,
|
serial_number: Attribute,
|
||||||
public_attributes: Vec<Attribute>,
|
binding_number: Attribute,
|
||||||
private_attributes: Vec<Attribute>,
|
voucher_value: Attribute,
|
||||||
|
voucher_info: Attribute,
|
||||||
aggregated_verification_key: Option<VerificationKey>,
|
aggregated_verification_key: Option<VerificationKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,9 +40,10 @@ impl State {
|
|||||||
signatures: Vec::new(),
|
signatures: Vec::new(),
|
||||||
n_attributes,
|
n_attributes,
|
||||||
params,
|
params,
|
||||||
public_attributes_bytes,
|
serial_number: private_attributes[0],
|
||||||
public_attributes,
|
binding_number: private_attributes[1],
|
||||||
private_attributes,
|
voucher_value: public_attributes[0],
|
||||||
|
voucher_info: public_attributes[1],
|
||||||
aggregated_verification_key: None,
|
aggregated_verification_key: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,8 +67,8 @@ async fn randomise_credential(
|
|||||||
) -> Result<Vec<Signature>, String> {
|
) -> Result<Vec<Signature>, String> {
|
||||||
let mut state = state.write().await;
|
let mut state = state.write().await;
|
||||||
let signature = state.signatures.remove(idx);
|
let signature = state.signatures.remove(idx);
|
||||||
let new = signature.randomise(&state.params);
|
let (new_signature, _) = signature.randomise(&state.params);
|
||||||
state.signatures.insert(idx, new);
|
state.signatures.insert(idx, new_signature);
|
||||||
Ok(state.signatures.clone())
|
Ok(state.signatures.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,14 +121,15 @@ async fn prove_credential(
|
|||||||
let state = state.read().await;
|
let state = state.read().await;
|
||||||
|
|
||||||
if let Some(signature) = state.signatures.get(idx) {
|
if let Some(signature) = state.signatures.get(idx) {
|
||||||
match coconut_interface::prove_credential(
|
match coconut_interface::prove_bandwidth_credential(
|
||||||
&state.params,
|
&state.params,
|
||||||
&verification_key,
|
&verification_key,
|
||||||
signature,
|
signature,
|
||||||
&state.private_attributes,
|
state.serial_number,
|
||||||
|
state.binding_number,
|
||||||
) {
|
) {
|
||||||
Ok(theta) => Ok(theta),
|
Ok(theta) => Ok(theta),
|
||||||
Err(e) => Err(format!("{}", e)),
|
Err(e) => Err(format!("{:?}", e)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err("Got invalid Signature idx".to_string())
|
Err("Got invalid Signature idx".to_string())
|
||||||
@@ -144,10 +149,15 @@ async fn verify_credential(
|
|||||||
|
|
||||||
let state = state.read().await;
|
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(
|
let credential = Credential::new(
|
||||||
state.n_attributes,
|
state.n_attributes,
|
||||||
theta,
|
theta,
|
||||||
state.public_attributes_bytes.clone(),
|
public_attributes_bytes,
|
||||||
state
|
state
|
||||||
.signatures
|
.signatures
|
||||||
.get(idx)
|
.get(idx)
|
||||||
@@ -164,11 +174,13 @@ async fn get_credential(
|
|||||||
) -> Result<Vec<Signature>, String> {
|
) -> Result<Vec<Signature>, String> {
|
||||||
let guard = state.read().await;
|
let guard = state.read().await;
|
||||||
let parsed_urls = parse_url_validators(&validator_urls)?;
|
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(
|
let signature = obtain_aggregate_signature(
|
||||||
&guard.params,
|
&guard.params,
|
||||||
&guard.public_attributes,
|
&public_attributes,
|
||||||
&guard.private_attributes,
|
&private_attributes,
|
||||||
&parsed_urls,
|
&parsed_urls,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|||||||
Generated
+45
-5600
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,6 @@
|
|||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"run_cli": "clear && ts-node src/cli.ts",
|
|
||||||
"test": "ts-mocha tests/**/*.test.ts",
|
"test": "ts-mocha tests/**/*.test.ts",
|
||||||
"coverage": "nyc npm test",
|
"coverage": "nyc npm test",
|
||||||
"lint": "eslint \"**/*.ts\"",
|
"lint": "eslint \"**/*.ts\"",
|
||||||
@@ -23,7 +22,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chai": "^4.2.15",
|
"@types/chai": "^4.2.15",
|
||||||
"@types/expect": "^24.3.0",
|
"@types/expect": "^24.3.0",
|
||||||
"@types/inquirer": "^8.1.3",
|
|
||||||
"@types/mocha": "^8.2.1",
|
"@types/mocha": "^8.2.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.14.0",
|
"@typescript-eslint/eslint-plugin": "^4.14.0",
|
||||||
"@typescript-eslint/parser": "^4.14.0",
|
"@typescript-eslint/parser": "^4.14.0",
|
||||||
@@ -37,11 +35,10 @@
|
|||||||
"typescript": "^4.1.3"
|
"typescript": "^4.1.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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",
|
"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 {MixNodeBond, PagedMixnodeResponse} from "../types";
|
||||||
import { INetClient } from "../net-client";
|
import { INetClient } from "../net-client"
|
||||||
import { IQueryClient } from "../query-client";
|
import {IQueryClient} from "../query-client";
|
||||||
import { VALIDATOR_API_MIXNODES, VALIDATOR_API_PORT } from "../index";
|
import {VALIDATOR_API_MIXNODES, VALIDATOR_API_PORT} from "../index";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
export { MixnodesCache };
|
export { MixnodesCache };
|
||||||
@@ -13,52 +13,48 @@ export { MixnodesCache };
|
|||||||
* available for querying.
|
* available for querying.
|
||||||
* */
|
* */
|
||||||
export default class MixnodesCache {
|
export default class MixnodesCache {
|
||||||
mixNodes: MixNodeBond[];
|
mixNodes: MixNodeBond[]
|
||||||
client: INetClient | IQueryClient;
|
client: INetClient | IQueryClient
|
||||||
perPage: number;
|
perPage: number
|
||||||
|
|
||||||
constructor(client: INetClient | IQueryClient, perPage: number) {
|
constructor(client: INetClient | IQueryClient, perPage: number) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.mixNodes = [];
|
this.mixNodes = [];
|
||||||
this.perPage = perPage;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mixNodes = newMixnodes;
|
/// Makes repeated requests to assemble a full list of nodes.
|
||||||
return this.mixNodes;
|
/// 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
|
this.mixNodes = newMixnodes
|
||||||
async refreshValidatorAPIMixNodes(urls: string[]): Promise<MixNodeBond[]> {
|
return this.mixNodes;
|
||||||
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");
|
|
||||||
}
|
/// 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>
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crypto::asymmetric::{encryption, identity};
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use futures::channel::mpsc;
|
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 gateway_client::GatewayClient;
|
||||||
use nymsphinx::acknowledgements::AckKey;
|
use nymsphinx::acknowledgements::AckKey;
|
||||||
use nymsphinx::addressing::clients::Recipient;
|
use nymsphinx::addressing::clients::Recipient;
|
||||||
use nymsphinx::preparer::MessagePreparer;
|
use nymsphinx::preparer::MessagePreparer;
|
||||||
use rand::rngs::OsRng;
|
|
||||||
use received_processor::ReceivedMessagesProcessor;
|
use received_processor::ReceivedMessagesProcessor;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
|
||||||
use topology::{gateway, nym_topology_from_bonds, NymTopology};
|
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 wasm_utils::{console_log, console_warn};
|
||||||
|
|
||||||
use gateway_client::bandwidth::BandwidthController;
|
|
||||||
|
|
||||||
pub(crate) mod received_processor;
|
pub(crate) mod received_processor;
|
||||||
|
|
||||||
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
|
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
|
||||||
@@ -100,15 +108,51 @@ impl NymClient {
|
|||||||
self.self_recipient().to_string()
|
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
|
// Right now it's impossible to have async exported functions to take `&self` rather than self
|
||||||
pub async fn initial_setup(self) -> Self {
|
pub async fn initial_setup(self) -> Self {
|
||||||
#[cfg(feature = "coconut")]
|
#[cfg(feature = "coconut")]
|
||||||
let bandwidth_controller = Some(BandwidthController::new(
|
let coconut_credential = {
|
||||||
vec![self.validator_server.clone()],
|
let validator_server = self.validator_server.clone();
|
||||||
*self.identity.public_key(),
|
let identity_public_key = self.identity.public_key().clone();
|
||||||
));
|
Self::prepare_coconut_credential(
|
||||||
#[cfg(not(feature = "coconut"))]
|
&vec![validator_server],
|
||||||
let bandwidth_controller = None;
|
&identity_public_key.to_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
};
|
||||||
|
|
||||||
let mut client = self.get_and_update_topology().await;
|
let mut client = self.get_and_update_topology().await;
|
||||||
let gateway = client.choose_gateway();
|
let gateway = client.choose_gateway();
|
||||||
@@ -124,11 +168,13 @@ impl NymClient {
|
|||||||
mixnet_messages_sender,
|
mixnet_messages_sender,
|
||||||
ack_sender,
|
ack_sender,
|
||||||
DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||||
bandwidth_controller,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
gateway_client
|
gateway_client
|
||||||
.authenticate_and_start()
|
.authenticate_and_start(
|
||||||
|
#[cfg(feature = "coconut")]
|
||||||
|
Some(coconut_credential),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.expect("could not authenticate and start up the gateway connection");
|
.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 validator_client = validator_client::ApiClient::new(self.validator_server.clone());
|
||||||
|
|
||||||
let mixnodes = match validator_client.get_cached_active_mixnodes().await {
|
let mixnodes = match validator_client.get_cached_active_mixnodes().await {
|
||||||
Err(err) => panic!("{}", err),
|
Err(err) => panic!("{:?}", err),
|
||||||
Ok(mixes) => mixes,
|
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
|
# TODO: (for this and other crates), similarly to 'tokio', import only required "futures" modules rather than
|
||||||
# the entire crate
|
# the entire crate
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
json = "0.12.4"
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
thiserror = "1.0"
|
|
||||||
url = "2.2"
|
|
||||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||||
|
|
||||||
# internal
|
# internal
|
||||||
credentials = { path = "../../credentials" }
|
|
||||||
crypto = { path = "../../crypto" }
|
crypto = { path = "../../crypto" }
|
||||||
gateway-requests = { path = "../../../gateway/gateway-requests" }
|
gateway-requests = { path = "../../../gateway/gateway-requests" }
|
||||||
nymsphinx = { path = "../../nymsphinx" }
|
nymsphinx = { path = "../../nymsphinx" }
|
||||||
coconut-interface = { path = "../../coconut-interface", optional = true }
|
coconut-interface = { path = "../../coconut-interface", optional = true }
|
||||||
network-defaults = { path = "../../network-defaults" }
|
|
||||||
|
|
||||||
[dependencies.tungstenite]
|
[dependencies.tungstenite]
|
||||||
version = "0.13"
|
version = "0.13"
|
||||||
@@ -36,12 +31,6 @@ features = ["macros", "rt", "net", "sync", "time"]
|
|||||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
|
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
|
||||||
version = "0.14"
|
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
|
# wasm-only dependencies
|
||||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
|
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
|
||||||
version = "0.2"
|
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>
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::bandwidth::BandwidthController;
|
|
||||||
use crate::cleanup_socket_message;
|
use crate::cleanup_socket_message;
|
||||||
use crate::error::GatewayClientError;
|
use crate::error::GatewayClientError;
|
||||||
use crate::packet_router::PacketRouter;
|
use crate::packet_router::PacketRouter;
|
||||||
@@ -9,10 +8,6 @@ pub use crate::packet_router::{
|
|||||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||||
};
|
};
|
||||||
use crate::socket_state::{PartiallyDelegated, SocketState};
|
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 crypto::asymmetric::identity;
|
||||||
use futures::{FutureExt, SinkExt, StreamExt};
|
use futures::{FutureExt, SinkExt, StreamExt};
|
||||||
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
|
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
|
||||||
@@ -35,11 +30,15 @@ use fluvio_wasm_timer as wasm_timer;
|
|||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use wasm_utils::websocket::JSWebsocket;
|
use wasm_utils::websocket::JSWebsocket;
|
||||||
|
|
||||||
|
#[cfg(feature = "coconut")]
|
||||||
|
use coconut_interface::Credential;
|
||||||
|
|
||||||
const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10;
|
const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10;
|
||||||
const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
|
const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
|
||||||
|
|
||||||
pub struct GatewayClient {
|
pub struct GatewayClient {
|
||||||
authenticated: bool,
|
authenticated: bool,
|
||||||
|
#[cfg(feature = "coconut")]
|
||||||
bandwidth_remaining: i64,
|
bandwidth_remaining: i64,
|
||||||
gateway_address: String,
|
gateway_address: String,
|
||||||
gateway_identity: identity::PublicKey,
|
gateway_identity: identity::PublicKey,
|
||||||
@@ -48,7 +47,6 @@ pub struct GatewayClient {
|
|||||||
connection: SocketState,
|
connection: SocketState,
|
||||||
packet_router: PacketRouter,
|
packet_router: PacketRouter,
|
||||||
response_timeout_duration: Duration,
|
response_timeout_duration: Duration,
|
||||||
bandwidth_controller: Option<BandwidthController>,
|
|
||||||
|
|
||||||
// reconnection related variables
|
// reconnection related variables
|
||||||
/// Specifies whether client should try to reconnect to gateway on connection failure.
|
/// Specifies whether client should try to reconnect to gateway on connection failure.
|
||||||
@@ -71,19 +69,20 @@ impl GatewayClient {
|
|||||||
mixnet_message_sender: MixnetMessageSender,
|
mixnet_message_sender: MixnetMessageSender,
|
||||||
ack_sender: AcknowledgementSender,
|
ack_sender: AcknowledgementSender,
|
||||||
response_timeout_duration: Duration,
|
response_timeout_duration: Duration,
|
||||||
bandwidth_controller: Option<BandwidthController>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
GatewayClient {
|
GatewayClient {
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
|
|
||||||
|
#[cfg(feature = "coconut")]
|
||||||
bandwidth_remaining: 0,
|
bandwidth_remaining: 0,
|
||||||
gateway_address,
|
gateway_address,
|
||||||
|
|
||||||
gateway_identity,
|
gateway_identity,
|
||||||
local_identity,
|
local_identity,
|
||||||
shared_key,
|
shared_key,
|
||||||
connection: SocketState::NotConnected,
|
connection: SocketState::NotConnected,
|
||||||
packet_router: PacketRouter::new(ack_sender, mixnet_message_sender),
|
packet_router: PacketRouter::new(ack_sender, mixnet_message_sender),
|
||||||
response_timeout_duration,
|
response_timeout_duration,
|
||||||
bandwidth_controller,
|
|
||||||
should_reconnect_on_failure: true,
|
should_reconnect_on_failure: true,
|
||||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||||
@@ -119,7 +118,10 @@ impl GatewayClient {
|
|||||||
|
|
||||||
GatewayClient {
|
GatewayClient {
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
|
|
||||||
|
#[cfg(feature = "coconut")]
|
||||||
bandwidth_remaining: 0,
|
bandwidth_remaining: 0,
|
||||||
|
|
||||||
gateway_address,
|
gateway_address,
|
||||||
gateway_identity,
|
gateway_identity,
|
||||||
local_identity,
|
local_identity,
|
||||||
@@ -127,7 +129,6 @@ impl GatewayClient {
|
|||||||
connection: SocketState::NotConnected,
|
connection: SocketState::NotConnected,
|
||||||
packet_router,
|
packet_router,
|
||||||
response_timeout_duration,
|
response_timeout_duration,
|
||||||
bandwidth_controller: None,
|
|
||||||
should_reconnect_on_failure: false,
|
should_reconnect_on_failure: false,
|
||||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||||
@@ -138,10 +139,6 @@ impl GatewayClient {
|
|||||||
self.gateway_identity
|
self.gateway_identity
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remaining_bandwidth(&self) -> i64 {
|
|
||||||
self.bandwidth_remaining
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
|
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||||
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
|
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
|
||||||
@@ -206,7 +203,14 @@ impl GatewayClient {
|
|||||||
|
|
||||||
for i in 1..self.reconnection_attempts {
|
for i in 1..self.reconnection_attempts {
|
||||||
info!("attempt {}...", i);
|
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!");
|
info!("managed to reconnect!");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -225,7 +229,13 @@ impl GatewayClient {
|
|||||||
|
|
||||||
// final attempt (done separately to be able to return a proper error)
|
// final attempt (done separately to be able to return a proper error)
|
||||||
info!("attempt {}", self.reconnection_attempts);
|
info!("attempt {}", self.reconnection_attempts);
|
||||||
match self.authenticate_and_start().await {
|
match self
|
||||||
|
.authenticate_and_start(
|
||||||
|
#[cfg(feature = "coconut")]
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("managed to reconnect!");
|
info!("managed to reconnect!");
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -436,12 +446,8 @@ impl GatewayClient {
|
|||||||
ClientControlRequest::new_authenticate(self_address, encrypted_address, iv).into();
|
ClientControlRequest::new_authenticate(self_address, encrypted_address, iv).into();
|
||||||
|
|
||||||
match self.send_websocket_message(msg).await? {
|
match self.send_websocket_message(msg).await? {
|
||||||
ServerResponse::Authenticate {
|
ServerResponse::Authenticate { status } => {
|
||||||
status,
|
|
||||||
bandwidth_remaining,
|
|
||||||
} => {
|
|
||||||
self.authenticated = status;
|
self.authenticated = status;
|
||||||
self.bandwidth_remaining = bandwidth_remaining;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||||
@@ -467,15 +473,22 @@ impl GatewayClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "coconut")]
|
#[cfg(feature = "coconut")]
|
||||||
async fn claim_coconut_bandwidth(
|
pub async fn claim_coconut_bandwidth(
|
||||||
&mut self,
|
&mut self,
|
||||||
credential: Credential,
|
coconut_credential: Credential,
|
||||||
) -> Result<(), GatewayClientError> {
|
) -> Result<(), GatewayClientError> {
|
||||||
|
if !self.authenticated {
|
||||||
|
return Err(GatewayClientError::NotAuthenticated);
|
||||||
|
}
|
||||||
|
if self.shared_key.is_none() {
|
||||||
|
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
let mut rng = OsRng;
|
let mut rng = OsRng;
|
||||||
let iv = IV::new_random(&mut rng);
|
let iv = IV::new_random(&mut rng);
|
||||||
|
|
||||||
let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential(
|
let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential(
|
||||||
&credential,
|
&coconut_credential,
|
||||||
self.shared_key.as_ref().unwrap(),
|
self.shared_key.as_ref().unwrap(),
|
||||||
iv,
|
iv,
|
||||||
)
|
)
|
||||||
@@ -489,64 +502,7 @@ impl GatewayClient {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "coconut"))]
|
#[cfg(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 {
|
fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 {
|
||||||
packets
|
packets
|
||||||
.iter()
|
.iter()
|
||||||
@@ -561,16 +517,9 @@ impl GatewayClient {
|
|||||||
if !self.authenticated {
|
if !self.authenticated {
|
||||||
return Err(GatewayClientError::NotAuthenticated);
|
return Err(GatewayClientError::NotAuthenticated);
|
||||||
}
|
}
|
||||||
if self.estimate_required_bandwidth(&packets) > self.bandwidth_remaining {
|
#[cfg(feature = "coconut")]
|
||||||
// Try to claim more bandwidth first, and return an error only if that is still not
|
if self.estimate_required_bandwidth(&packets) < self.bandwidth_remaining {
|
||||||
// enough (the current granularity for bandwidth should be sufficient)
|
return Err(GatewayClientError::NotEnoughBandwidth);
|
||||||
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,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !self.connection.is_established() {
|
if !self.connection.is_established() {
|
||||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||||
@@ -636,16 +585,9 @@ impl GatewayClient {
|
|||||||
if !self.authenticated {
|
if !self.authenticated {
|
||||||
return Err(GatewayClientError::NotAuthenticated);
|
return Err(GatewayClientError::NotAuthenticated);
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "coconut")]
|
||||||
if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
|
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
|
return Err(GatewayClientError::NotEnoughBandwidth);
|
||||||
// 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,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !self.connection.is_established() {
|
if !self.connection.is_established() {
|
||||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||||
@@ -682,6 +624,10 @@ impl GatewayClient {
|
|||||||
if !self.authenticated {
|
if !self.authenticated {
|
||||||
return Err(GatewayClientError::NotAuthenticated);
|
return Err(GatewayClientError::NotAuthenticated);
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "coconut")]
|
||||||
|
if self.bandwidth_remaining <= 0 {
|
||||||
|
return Err(GatewayClientError::NotEnoughBandwidth);
|
||||||
|
}
|
||||||
if self.connection.is_partially_delegated() {
|
if self.connection.is_partially_delegated() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -709,12 +655,22 @@ impl GatewayClient {
|
|||||||
Ok(())
|
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() {
|
if !self.connection.is_established() {
|
||||||
self.establish_connection().await?;
|
self.establish_connection().await?;
|
||||||
}
|
}
|
||||||
let shared_key = self.perform_initial_authentication().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
|
// this call is NON-blocking
|
||||||
self.start_listening_for_mixnet_messages()?;
|
self.start_listening_for_mixnet_messages()?;
|
||||||
|
|
||||||
|
|||||||
@@ -2,85 +2,41 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use gateway_requests::registration::handshake::error::HandshakeError;
|
use gateway_requests::registration::handshake::error::HandshakeError;
|
||||||
|
use std::fmt::{self, Error, Formatter};
|
||||||
use std::io;
|
use std::io;
|
||||||
use thiserror::Error;
|
|
||||||
use tungstenite::Error as WsError;
|
use tungstenite::Error as WsError;
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
#[cfg(not(feature = "coconut"))]
|
|
||||||
use web3::Error as Web3Error;
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum GatewayClientError {
|
pub enum GatewayClientError {
|
||||||
#[error("Connection to the gateway is not established")]
|
|
||||||
ConnectionNotEstablished,
|
ConnectionNotEstablished,
|
||||||
|
|
||||||
#[error("Gateway returned an error response - {0}")]
|
|
||||||
GatewayError(String),
|
GatewayError(String),
|
||||||
|
NetworkError(WsError),
|
||||||
#[error("There was a network error - {0}")]
|
|
||||||
NetworkError(#[from] WsError),
|
|
||||||
|
|
||||||
// TODO: see if `JsValue` is a reasonable type for this
|
// TODO: see if `JsValue` is a reasonable type for this
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
#[error("There was a network error")]
|
|
||||||
NetworkErrorWasm(JsValue),
|
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,
|
NoSharedKeyAvailable,
|
||||||
|
|
||||||
#[error("No bandwidth controller provided")]
|
|
||||||
NoBandwidthControllerAvailable,
|
|
||||||
|
|
||||||
#[error("Credential error - {0}")]
|
|
||||||
CredentialError(#[from] credentials::error::Error),
|
|
||||||
|
|
||||||
#[error("Connection was abruptly closed")]
|
|
||||||
ConnectionAbruptlyClosed,
|
ConnectionAbruptlyClosed,
|
||||||
|
|
||||||
#[error("Received response was malformed")]
|
|
||||||
MalformedResponse,
|
MalformedResponse,
|
||||||
|
|
||||||
#[error("Credential could not be serialized")]
|
|
||||||
SerializeCredential,
|
SerializeCredential,
|
||||||
|
|
||||||
#[error("Client is not authenticated")]
|
|
||||||
NotAuthenticated,
|
NotAuthenticated,
|
||||||
|
NotEnoughBandwidth,
|
||||||
#[error("Client does not have enough bandwidth: estimated {0}, remaining: {1}")]
|
|
||||||
NotEnoughBandwidth(i64, i64),
|
|
||||||
|
|
||||||
#[error("Received an unexpected response")]
|
|
||||||
UnexpectedResponse,
|
UnexpectedResponse,
|
||||||
|
|
||||||
#[error("Connection is in an invalid state - please send a bug report")]
|
|
||||||
ConnectionInInvalidState,
|
ConnectionInInvalidState,
|
||||||
|
|
||||||
#[error("Failed to finish registration handshake - {0}")]
|
|
||||||
RegistrationFailure(HandshakeError),
|
RegistrationFailure(HandshakeError),
|
||||||
|
|
||||||
#[error("Authentication failure")]
|
|
||||||
AuthenticationFailure,
|
AuthenticationFailure,
|
||||||
|
|
||||||
#[error("Timed out")]
|
|
||||||
Timeout,
|
Timeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<WsError> for GatewayClientError {
|
||||||
|
fn from(err: WsError) -> Self {
|
||||||
|
GatewayClientError::NetworkError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl GatewayClientError {
|
impl GatewayClientError {
|
||||||
pub fn is_closed_connection(&self) -> bool {
|
pub fn is_closed_connection(&self) -> bool {
|
||||||
match self {
|
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};
|
use tungstenite::{protocol::Message, Error as WsError};
|
||||||
|
|
||||||
pub mod bandwidth;
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod packet_router;
|
pub mod packet_router;
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ impl PartiallyDelegated {
|
|||||||
}
|
}
|
||||||
.is_err()
|
.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 }
|
sha2 = { version = "0.9.5", optional = true }
|
||||||
itertools = { version = "0.10", optional = true }
|
itertools = { version = "0.10", optional = true }
|
||||||
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", 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]
|
[features]
|
||||||
nymd-client = ["async-trait", "bip39", "config", "cosmrs", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"]
|
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 crate::{validator_api, ValidatorClientError};
|
||||||
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
|
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
|
||||||
use mixnet_contract::{GatewayBond, MixNodeBond};
|
|
||||||
#[cfg(feature = "nymd-client")]
|
#[cfg(feature = "nymd-client")]
|
||||||
use mixnet_contract::{RawDelegationData, RewardingIntervalResponse};
|
use mixnet_contract::RawDelegationData;
|
||||||
|
use mixnet_contract::{GatewayBond, MixNodeBond};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[cfg(feature = "nymd-client")]
|
#[cfg(feature = "nymd-client")]
|
||||||
@@ -172,43 +172,6 @@ impl<C> Client<C> {
|
|||||||
Ok(self.nymd.get_state_params().await?)
|
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
|
// basically handles paging for us
|
||||||
pub async fn get_all_nymd_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
|
pub async fn get_all_nymd_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ use cosmrs::tx::{Fee, Gas};
|
|||||||
use cosmrs::Coin;
|
use cosmrs::Coin;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
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, TS)]
|
||||||
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
|
||||||
pub enum Operation {
|
pub enum Operation {
|
||||||
Upload,
|
Upload,
|
||||||
Init,
|
Init,
|
||||||
@@ -23,11 +23,10 @@ pub enum Operation {
|
|||||||
|
|
||||||
BondGateway,
|
BondGateway,
|
||||||
UnbondGateway,
|
UnbondGateway,
|
||||||
|
DelegateToGateway,
|
||||||
|
UndelegateFromGateway,
|
||||||
|
|
||||||
UpdateStateParams,
|
UpdateStateParams,
|
||||||
|
|
||||||
BeginMixnodeRewarding,
|
|
||||||
FinishMixnodeRewarding,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
|
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::Send => f.write_str("Send"),
|
||||||
Operation::BondMixnode => f.write_str("BondMixnode"),
|
Operation::BondMixnode => f.write_str("BondMixnode"),
|
||||||
Operation::UnbondMixnode => f.write_str("UnbondMixnode"),
|
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::DelegateToMixnode => f.write_str("DelegateToMixnode"),
|
||||||
Operation::UndelegateFromMixnode => f.write_str("UndelegateFromMixnode"),
|
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::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::BondGateway => 175_000u64.into(),
|
||||||
Operation::UnbondGateway => 175_000u64.into(),
|
Operation::UnbondGateway => 175_000u64.into(),
|
||||||
|
Operation::DelegateToGateway => 175_000u64.into(),
|
||||||
|
Operation::UndelegateFromGateway => 175_000u64.into(),
|
||||||
|
|
||||||
Operation::UpdateStateParams => 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 crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||||
use cosmrs::rpc::endpoint::broadcast;
|
use cosmrs::rpc::endpoint::broadcast;
|
||||||
use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
|
use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
|
||||||
use cosmwasm_std::{Coin, Uint128};
|
use cosmwasm_std::Coin;
|
||||||
use mixnet_contract::{
|
use mixnet_contract::{
|
||||||
Addr, Delegation, ExecuteMsg, Gateway, GatewayOwnershipResponse, IdentityKey,
|
Addr, Delegation, ExecuteMsg, Gateway, GatewayOwnershipResponse, IdentityKey,
|
||||||
LayerDistribution, MixNode, MixOwnershipResponse, PagedAllDelegationsResponse,
|
LayerDistribution, MixNode, MixOwnershipResponse, PagedAllDelegationsResponse,
|
||||||
PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse,
|
PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse,
|
||||||
PagedReverseMixDelegationsResponse, QueryMsg, RawDelegationData, RewardingIntervalResponse,
|
PagedReverseMixDelegationsResponse, QueryMsg, RawDelegationData, StateParams,
|
||||||
StateParams,
|
|
||||||
};
|
};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::collections::HashMap;
|
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 crate::nymd::gas_price::GasPrice;
|
||||||
pub use cosmrs::rpc::HttpClient as QueryNymdClient;
|
pub use cosmrs::rpc::HttpClient as QueryNymdClient;
|
||||||
pub use cosmrs::tendermint::block::Height;
|
pub use cosmrs::tendermint::block::Height;
|
||||||
pub use cosmrs::tendermint::hash;
|
|
||||||
pub use cosmrs::tendermint::Time as TendermintTime;
|
pub use cosmrs::tendermint::Time as TendermintTime;
|
||||||
pub use cosmrs::tx::{Fee, Gas};
|
pub use cosmrs::tx::{Fee, Gas};
|
||||||
pub use cosmrs::Coin as CosmosCoin;
|
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 use signing_client::Client as SigningNymdClient;
|
||||||
|
|
||||||
pub mod cosmwasm_client;
|
pub mod cosmwasm_client;
|
||||||
@@ -186,21 +184,6 @@ impl<C> NymdClient<C> {
|
|||||||
self.client.get_height().await
|
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>
|
pub async fn get_balance(&self, address: &AccountId) -> Result<Option<CosmosCoin>, NymdError>
|
||||||
where
|
where
|
||||||
C: CosmWasmClient + Sync,
|
C: CosmWasmClient + Sync,
|
||||||
@@ -218,18 +201,6 @@ impl<C> NymdClient<C> {
|
|||||||
.await
|
.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>
|
pub async fn get_layer_distribution(&self) -> Result<LayerDistribution, NymdError>
|
||||||
where
|
where
|
||||||
C: CosmWasmClient + Sync,
|
C: CosmWasmClient + Sync,
|
||||||
@@ -240,46 +211,6 @@ impl<C> NymdClient<C> {
|
|||||||
.await
|
.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
|
/// 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>
|
pub async fn owns_mixnode(&self, address: &AccountId) -> Result<bool, NymdError>
|
||||||
where
|
where
|
||||||
@@ -711,54 +642,6 @@ impl<C> NymdClient<C> {
|
|||||||
)
|
)
|
||||||
.await
|
.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 {
|
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"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
getset = "0.1.1"
|
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 getset::{CopyGetters, Getters};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub use coconut_rs::*;
|
pub use nymcoconut::*;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Getters, CopyGetters, Clone)]
|
#[derive(Serialize, Deserialize, Getters, CopyGetters, Clone)]
|
||||||
pub struct Credential {
|
pub struct Credential {
|
||||||
@@ -42,7 +42,7 @@ impl Credential {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(hash_to_scalar)
|
.map(hash_to_scalar)
|
||||||
.collect::<Vec<Attribute>>();
|
.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")]
|
#[getset(get = "pub")]
|
||||||
blind_sign_request: BlindSignRequest,
|
blind_sign_request: BlindSignRequest,
|
||||||
#[getset(get = "pub")]
|
#[getset(get = "pub")]
|
||||||
public_key: coconut_rs::PublicKey,
|
public_key: nymcoconut::PublicKey,
|
||||||
public_attributes: Vec<String>,
|
public_attributes: Vec<String>,
|
||||||
#[getset(get = "pub")]
|
#[getset(get = "pub")]
|
||||||
total_params: u32,
|
total_params: u32,
|
||||||
@@ -92,13 +92,13 @@ pub struct BlindSignRequestBody {
|
|||||||
|
|
||||||
impl BlindSignRequestBody {
|
impl BlindSignRequestBody {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
blind_sign_request: BlindSignRequest,
|
blind_sign_request: &BlindSignRequest,
|
||||||
public_key: &coconut_rs::PublicKey,
|
public_key: &nymcoconut::PublicKey,
|
||||||
public_attributes: &[Attribute],
|
public_attributes: &[Attribute],
|
||||||
total_params: u32,
|
total_params: u32,
|
||||||
) -> BlindSignRequestBody {
|
) -> BlindSignRequestBody {
|
||||||
BlindSignRequestBody {
|
BlindSignRequestBody {
|
||||||
blind_sign_request,
|
blind_sign_request: blind_sign_request.clone(),
|
||||||
public_key: public_key.clone(),
|
public_key: public_key.clone(),
|
||||||
public_attributes: public_attributes
|
public_attributes: public_attributes
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -11,6 +11,4 @@ url = "2.2"
|
|||||||
|
|
||||||
# I guess temporarily until we get serde support in coconut up and running
|
# I guess temporarily until we get serde support in coconut up and running
|
||||||
coconut-interface = { path = "../coconut-interface" }
|
coconut-interface = { path = "../coconut-interface" }
|
||||||
crypto = { path = "../crypto" }
|
|
||||||
network-defaults = { path = "../network-defaults" }
|
|
||||||
validator-client = { path = "../client-libs/validator-client" }
|
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>
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
pub mod coconut;
|
pub mod bandwidth;
|
||||||
pub mod error;
|
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]
|
[dependencies]
|
||||||
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
|
# 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" }
|
#cosmwasm-std = { version = "0.14.1" }
|
||||||
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_repr = "0.1"
|
serde_repr = "0.1"
|
||||||
schemars = "0.8"
|
schemars = "0.8"
|
||||||
ts-rs = { version = "5.1", optional = true }
|
ts-rs = "3.0"
|
||||||
thiserror = "1.0"
|
|
||||||
network-defaults = { path = "../network-defaults" }
|
|
||||||
fixed = "1.1"
|
|
||||||
az = "1.1"
|
|
||||||
log = "0.4.14"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = []
|
|
||||||
|
|||||||
@@ -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 serde::{Deserialize, Serialize};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::fmt::Display;
|
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, TS)]
|
||||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
|
|
||||||
pub struct Gateway {
|
pub struct Gateway {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub mix_port: u16,
|
pub mix_port: u16,
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
mod delegation;
|
mod delegation;
|
||||||
pub mod error;
|
|
||||||
mod gateway;
|
mod gateway;
|
||||||
pub mod mixnode;
|
mod mixnode;
|
||||||
mod msg;
|
mod msg;
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
@@ -16,7 +15,4 @@ pub use delegation::{
|
|||||||
pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse};
|
pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse};
|
||||||
pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse};
|
pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse};
|
||||||
pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||||
pub use types::{
|
pub use types::{IdentityKey, IdentityKeyRef, LayerDistribution, SphinxKey, StateParams};
|
||||||
IdentityKey, IdentityKeyRef, LayerDistribution, RewardingIntervalResponse, SphinxKey,
|
|
||||||
StateParams,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -2,20 +2,15 @@
|
|||||||
#![allow(clippy::field_reassign_with_default)]
|
#![allow(clippy::field_reassign_with_default)]
|
||||||
|
|
||||||
use crate::{IdentityKey, SphinxKey};
|
use crate::{IdentityKey, SphinxKey};
|
||||||
use az::CheckedCast;
|
use cosmwasm_std::{coin, Addr, Coin};
|
||||||
use cosmwasm_std::{coin, Addr, Coin, Uint128};
|
|
||||||
use log::error;
|
|
||||||
use network_defaults::{DEFAULT_OPERATOR_EPOCH_COST, DEFAULT_PROFIT_MARGIN};
|
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
type U128 = fixed::types::U75F53; // u128 with 18 significant digits
|
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema, TS)]
|
||||||
|
|
||||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
|
||||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
|
|
||||||
pub struct MixNode {
|
pub struct MixNode {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub mix_port: u16,
|
pub mix_port: u16,
|
||||||
@@ -28,17 +23,7 @@ pub struct MixNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Copy,
|
Copy, Clone, Debug, Serialize_repr, PartialEq, PartialOrd, Deserialize_repr, JsonSchema,
|
||||||
Clone,
|
|
||||||
Debug,
|
|
||||||
PartialEq,
|
|
||||||
Eq,
|
|
||||||
PartialOrd,
|
|
||||||
Ord,
|
|
||||||
Hash,
|
|
||||||
Serialize_repr,
|
|
||||||
Deserialize_repr,
|
|
||||||
JsonSchema,
|
|
||||||
)]
|
)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum Layer {
|
pub enum Layer {
|
||||||
@@ -48,97 +33,6 @@ pub enum Layer {
|
|||||||
Three = 3,
|
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)]
|
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||||
pub struct MixNodeBond {
|
pub struct MixNodeBond {
|
||||||
pub bond_amount: Coin,
|
pub bond_amount: Coin,
|
||||||
@@ -147,7 +41,6 @@ pub struct MixNodeBond {
|
|||||||
pub layer: Layer,
|
pub layer: Layer,
|
||||||
pub block_height: u64,
|
pub block_height: u64,
|
||||||
pub mix_node: MixNode,
|
pub mix_node: MixNode,
|
||||||
pub profit_margin_percent: Option<u8>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MixNodeBond {
|
impl MixNodeBond {
|
||||||
@@ -157,7 +50,6 @@ impl MixNodeBond {
|
|||||||
layer: Layer,
|
layer: Layer,
|
||||||
block_height: u64,
|
block_height: u64,
|
||||||
mix_node: MixNode,
|
mix_node: MixNode,
|
||||||
profit_margin_percent: Option<u8>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
MixNodeBond {
|
MixNodeBond {
|
||||||
total_delegation: coin(0, &bond_amount.denom),
|
total_delegation: coin(0, &bond_amount.denom),
|
||||||
@@ -166,15 +58,9 @@ impl MixNodeBond {
|
|||||||
layer,
|
layer,
|
||||||
block_height,
|
block_height,
|
||||||
mix_node,
|
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 {
|
pub fn identity(&self) -> &String {
|
||||||
&self.mix_node.identity_key
|
&self.mix_node.identity_key
|
||||||
}
|
}
|
||||||
@@ -190,124 +76,6 @@ impl MixNodeBond {
|
|||||||
pub fn mix_node(&self) -> &MixNode {
|
pub fn mix_node(&self) -> &MixNode {
|
||||||
&self.mix_node
|
&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 {
|
impl PartialOrd for MixNodeBond {
|
||||||
@@ -439,7 +207,6 @@ mod tests {
|
|||||||
layer: Layer::One,
|
layer: Layer::One,
|
||||||
block_height: 100,
|
block_height: 100,
|
||||||
mix_node: mixnode_fixture(),
|
mix_node: mixnode_fixture(),
|
||||||
profit_margin_percent: Some(10),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mix2 = MixNodeBond {
|
let mix2 = MixNodeBond {
|
||||||
@@ -449,7 +216,6 @@ mod tests {
|
|||||||
layer: Layer::One,
|
layer: Layer::One,
|
||||||
block_height: 120,
|
block_height: 120,
|
||||||
mix_node: mixnode_fixture(),
|
mix_node: mixnode_fixture(),
|
||||||
profit_margin_percent: Some(10),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mix3 = MixNodeBond {
|
let mix3 = MixNodeBond {
|
||||||
@@ -459,7 +225,6 @@ mod tests {
|
|||||||
layer: Layer::One,
|
layer: Layer::One,
|
||||||
block_height: 120,
|
block_height: 120,
|
||||||
mix_node: mixnode_fixture(),
|
mix_node: mixnode_fixture(),
|
||||||
profit_margin_percent: Some(10),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mix4 = MixNodeBond {
|
let mix4 = MixNodeBond {
|
||||||
@@ -469,7 +234,6 @@ mod tests {
|
|||||||
layer: Layer::One,
|
layer: Layer::One,
|
||||||
block_height: 120,
|
block_height: 120,
|
||||||
mix_node: mixnode_fixture(),
|
mix_node: mixnode_fixture(),
|
||||||
profit_margin_percent: Some(10),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mix5 = MixNodeBond {
|
let mix5 = MixNodeBond {
|
||||||
@@ -479,7 +243,6 @@ mod tests {
|
|||||||
layer: Layer::One,
|
layer: Layer::One,
|
||||||
block_height: 120,
|
block_height: 120,
|
||||||
mix_node: mixnode_fixture(),
|
mix_node: mixnode_fixture(),
|
||||||
profit_margin_percent: Some(10),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// summary:
|
// summary:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::mixnode::NodeRewardParams;
|
|
||||||
use crate::StateParams;
|
use crate::StateParams;
|
||||||
use crate::{Gateway, IdentityKey, MixNode};
|
use crate::{Gateway, IdentityKey, MixNode};
|
||||||
use cosmwasm_std::Addr;
|
use cosmwasm_std::Addr;
|
||||||
@@ -32,32 +31,10 @@ pub enum ExecuteMsg {
|
|||||||
mix_identity: IdentityKey,
|
mix_identity: IdentityKey,
|
||||||
},
|
},
|
||||||
|
|
||||||
BeginMixnodeRewarding {
|
|
||||||
// nonce of the current rewarding interval
|
|
||||||
rewarding_interval_nonce: u32,
|
|
||||||
},
|
|
||||||
|
|
||||||
RewardMixnode {
|
RewardMixnode {
|
||||||
identity: IdentityKey,
|
identity: IdentityKey,
|
||||||
// percentage value in range 0-100
|
// percentage value in range 0-100
|
||||||
uptime: u32,
|
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,
|
address: Addr,
|
||||||
},
|
},
|
||||||
StateParams {},
|
StateParams {},
|
||||||
CurrentRewardingInterval {},
|
|
||||||
GetMixDelegations {
|
GetMixDelegations {
|
||||||
mix_identity: IdentityKey,
|
mix_identity: IdentityKey,
|
||||||
start_after: Option<Addr>,
|
start_after: Option<Addr>,
|
||||||
@@ -99,10 +75,6 @@ pub enum QueryMsg {
|
|||||||
address: Addr,
|
address: Addr,
|
||||||
},
|
},
|
||||||
LayerDistribution {},
|
LayerDistribution {},
|
||||||
GetRewardPool {},
|
|
||||||
GetCirculatingSupply {},
|
|
||||||
GetEpochRewardPercent {},
|
|
||||||
GetSybilResistancePercent {},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
#[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)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||||
pub struct StateParams {
|
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_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 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_bond_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
|
||||||
pub mixnode_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
|
pub 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,
|
pub mixnode_active_set_size: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,11 +54,6 @@ impl Display for StateParams {
|
|||||||
"mixnode delegation reward rate: {}; ",
|
"mixnode delegation reward rate: {}; ",
|
||||||
self.mixnode_delegation_reward_rate
|
self.mixnode_delegation_reward_rate
|
||||||
)?;
|
)?;
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"mixnode rewarded set size: {}",
|
|
||||||
self.mixnode_rewarded_set_size
|
|
||||||
)?;
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"mixnode active set size: {}",
|
"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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hex-literal = "0.3.3"
|
|
||||||
serde = {version = "1.0", features = ["derive"]}
|
serde = {version = "1.0", features = ["derive"]}
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
time = { version = "0.3", features = ["macros"] }
|
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 time::OffsetDateTime;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub mod eth_contract;
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct ValidatorDetails {
|
pub struct ValidatorDetails {
|
||||||
// it is assumed those values are always valid since they're being provided in our defaults file
|
// 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 DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "punk10pyejy66429refv3g35g2t7am0was7yalwrzen";
|
||||||
pub const REWARDING_VALIDATOR_ADDRESS: &str = "punk1v9qauwdq5terag6uvfsdytcs2d0sdmfdy7hgk3";
|
pub const NETWORK_MONITOR_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";
|
|
||||||
|
|
||||||
/// Defaults Cosmos Hub/ATOM path
|
/// Defaults Cosmos Hub/ATOM path
|
||||||
pub const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0";
|
pub const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0";
|
||||||
@@ -112,11 +93,4 @@ pub const VALIDATOR_API_VERSION: &str = "v1";
|
|||||||
|
|
||||||
// REWARDING
|
// REWARDING
|
||||||
pub const DEFAULT_FIRST_EPOCH_START: OffsetDateTime = time::macros::datetime!(2021-08-23 12:00 UTC);
|
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
|
pub const DEFAULT_EPOCH_LENGTH: Duration = Duration::from_secs(24 * 60 * 60); // 24h
|
||||||
/// 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;
|
|
||||||
|
|||||||
@@ -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