Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b66701c5d | |||
| 3a8b99db10 | |||
| 6e7cf42831 | |||
| 0e42f977ee | |||
| ef379cdeb3 | |||
| 4886312e43 | |||
| 3a152366d2 | |||
| d920bcb2f4 | |||
| 268a1817ab | |||
| f5c493500f | |||
| 7533f304ff | |||
| 755d4f2388 | |||
| 6884395daa | |||
| 5299b94f9e | |||
| f701787802 | |||
| 0b58e0ae24 | |||
| 03226d1487 | |||
| 534f8c6b99 | |||
| e544a6d2fa | |||
| d696535da5 | |||
| 7e444b7c42 | |||
| 0d377d98a7 | |||
| 5bdbee6dae | |||
| 014374e33e | |||
| 9adb4fbbbc | |||
| 5293766856 | |||
| a40bb3ae4f | |||
| 226c37cfea | |||
| ea8f36dfd2 | |||
| b466cb6b3d | |||
| bb0328137a | |||
| e07974ad2b | |||
| e8bb96b7d0 | |||
| 9bd06dbfcc | |||
| 055b10f7f2 | |||
| 44ca339ee5 | |||
| b6a43787b3 | |||
| 9cb22d9bde | |||
| 68485f8998 | |||
| 9c22c082c0 | |||
| 28d7400304 | |||
| 056d189e0e | |||
| b76eab2363 | |||
| b8a2ac5719 | |||
| c861e40047 | |||
| 20426d001e | |||
| 2eed9fd247 | |||
| a1887356dc | |||
| ef2420f847 | |||
| 6f26475055 | |||
| b9bf6e4521 | |||
| 7e392ff6c3 | |||
| 9e834ebaef | |||
| f96a4ffed3 | |||
| 8e9c0d2b0d | |||
| 33c356b3ec | |||
| c902e8eaa5 | |||
| 1dc8b5894f | |||
| ad285af0fa | |||
| 6621e5c72f | |||
| 969a93cf80 | |||
| f52ee0431c | |||
| a41cfd47a4 | |||
| 0b098687e1 | |||
| 09709dd8e4 | |||
| ef0867a7e7 | |||
| bfa7754ea6 | |||
| a4377bf1a6 | |||
| 9e30f9a346 | |||
| 7878895651 | |||
| fcc940a4d6 | |||
| 036092bb9c | |||
| dcde42c45d | |||
| 5b5bbc2a3d | |||
| 0c1cdc4b63 | |||
| 953c1eddd9 | |||
| 6ae0913aa1 | |||
| 92834ff9b8 | |||
| bfba92de97 | |||
| d37b63f788 | |||
| 63dd26ca1b | |||
| f24d6e224d | |||
| f9a154b36c | |||
| 72d994880b | |||
| fe02bc4631 | |||
| 1fb8e1bbaf | |||
| 96aa355db3 | |||
| 8dfb9c8173 | |||
| 382d3e130e | |||
| a8246621e1 | |||
| 66b6a8aeef | |||
| 93165ad699 | |||
| 2ebb498589 | |||
| 929a780315 | |||
| c7cdd1e1b4 | |||
| 8575a72a22 | |||
| 9d03bec14b | |||
| 7c826ef881 | |||
| ac666b7a1d | |||
| 83a9b993a1 | |||
| fe4fcc7fdf | |||
| a6e40443a4 | |||
| 0a583bd48a | |||
| 96ccce1049 | |||
| 89d0ff5624 | |||
| 2391d11758 | |||
| 902c674ff1 | |||
| 160328a08e | |||
| 9fea869bbc | |||
| c66d7ed489 | |||
| 19d603ba1a | |||
| b30f680549 | |||
| 379dd1f02b | |||
| 38804279e8 | |||
| 3907cd17fe | |||
| 67c69cbec9 | |||
| abc5dd8b92 | |||
| c791de426a | |||
| 4c5d4ac4a4 | |||
| 48c1fcaf93 | |||
| 31594c7a79 | |||
| 8ec3c04a39 | |||
| ba7fecde6f | |||
| 835a915f25 | |||
| 9c16e5ebfa | |||
| 5dc57d2a3e | |||
| fd619cad51 | |||
| 953c0915d1 | |||
| acb1aa8df0 | |||
| b9ef848523 | |||
| b7bc713cd4 | |||
| b966f962c8 | |||
| 698cdc524d | |||
| 448aba0917 | |||
| b813e1fee0 | |||
| 23de430f93 | |||
| 9462bc726d | |||
| 3d2eaeeabb | |||
| a732a676e0 | |||
| d6a8fcda9c | |||
| 3628cd92c9 | |||
| 8d26acbc7e | |||
| 07b971fe92 | |||
| 2a539dc3cc | |||
| 3fb9737db4 | |||
| 0575f01f9b |
@@ -0,0 +1,63 @@
|
||||
name: CI for ts-packages
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'ts-packages/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: custom-runner-linux
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install rsync
|
||||
run: sudo apt-get install rsync
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
- name: Build
|
||||
run: yarn && yarn build && yarn build:ci
|
||||
- name: Deploy branch to CI www (storybook)
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "ts-packages/dist/storybook/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ts-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Deploy branch to CI www (example)
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "ts-packages/dist/example/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ts-${{ env.GITHUB_REF_SLUG }}-example
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Keybase - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Keybase - Send Notification
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: ts-packages
|
||||
NYM_PROJECT_NAME: "ts-packages"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "ts-${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
|
||||
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
|
||||
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
|
||||
KEYBASE_NYM_CHANNEL: "ci-ts-packages"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
@@ -33,13 +33,13 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --all
|
||||
args: --workspace
|
||||
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all
|
||||
args: --workspace --all-features
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
@@ -57,19 +57,19 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
args: --workspace -- -D warnings
|
||||
|
||||
- name: Build all binaries with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --all --features=coconut
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Run all tests with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all --features=coconut
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Run clippy with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
RUSTFLAGS: '-C link-arg=-s'
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path contracts/Cargo.toml --all --target wasm32-unknown-unknown
|
||||
args: --manifest-path contracts/Cargo.toml --workspace --target wasm32-unknown-unknown
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
@@ -61,4 +61,4 @@ jobs:
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --manifest-path contracts/Cargo.toml --all -- -D warnings
|
||||
args: --manifest-path contracts/Cargo.toml --workspace -- -D warnings
|
||||
|
||||
@@ -16,8 +16,9 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
- run: npm install
|
||||
node-version: '16'
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
- name: Run ESLint
|
||||
# GitHub should automatically annotate the PR
|
||||
run: npm run lint
|
||||
run: yarn && yarn lint
|
||||
@@ -19,14 +19,17 @@ jobs:
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
- run: npm install
|
||||
node-version: '16'
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
continue-on-error: true
|
||||
- name: Build shared packages
|
||||
run: cd .. && yarn && yarn build
|
||||
- name: Set environment from the example
|
||||
run: cp .env.prod .env
|
||||
- run: npm run test
|
||||
continue-on-error: true
|
||||
- run: npm run build
|
||||
# - run: yarn test
|
||||
# continue-on-error: true
|
||||
- run: yarn && yarn build
|
||||
continue-on-error: true
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
|
||||
@@ -42,13 +42,13 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --all
|
||||
args: --workspace
|
||||
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all
|
||||
args: --workspace
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
@@ -56,6 +56,12 @@ jobs:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- name: Reclaim some disk space (because Windows is being annoying)
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
name: Clippy checks
|
||||
with:
|
||||
@@ -67,33 +73,28 @@ jobs:
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
args: --workspace --all-targets -- -D warnings
|
||||
|
||||
# COCONUT stuff
|
||||
- name: Reclaim some disk space (because Windows is being annoying)
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
# COCONUT stuff
|
||||
- name: Build all binaries with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --all --features=coconut
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Run all tests with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all --features=coconut
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Run clippy with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --features=coconut -- -D warnings
|
||||
args: --workspace --all-targets --features=coconut -- -D warnings
|
||||
notification:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
name: Generate TS types
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- "explorer/**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- "explorer/**"
|
||||
|
||||
jobs:
|
||||
nym-wallet-types:
|
||||
runs-on: [ self-hosted, custom-linux-exoscale ]
|
||||
# Enable sccache
|
||||
env:
|
||||
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: sudo apt-get update && sudo apt-get install -y libpango1.0-dev libatk1.0-dev libgdk-pixbuf2.0-dev libsoup2.4-dev librust-gdk-dev libwebkit2gtk-4.0-dev
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Generate TS
|
||||
run: cd nym-wallet/src-tauri && cargo test
|
||||
- uses: EndBug/add-and-commit@v7.2.1 # https://github.com/marketplace/actions/add-commit
|
||||
with:
|
||||
add: '["nym-wallet"]'
|
||||
message: "[ci skip] Generate TS types"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -3,7 +3,7 @@ require('dotenv').config();
|
||||
const Bot = require('keybase-bot');
|
||||
|
||||
let context = {
|
||||
kinds: ['network-explorer', 'nightly'],
|
||||
kinds: ['ts-packages', 'network-explorer', 'nightly'],
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
const Handlebars = require('handlebars');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
async function addToContextAndValidate(context) {
|
||||
if (!context.env.NYM_CI_WWW_LOCATION) {
|
||||
throw new Error('Please ensure the env var NYM_CI_WWW_LOCATION is set');
|
||||
}
|
||||
if (!context.env.NYM_CI_WWW_BASE) {
|
||||
throw new Error('Please ensure the env var NYM_CI_WWW_BASE is set');
|
||||
}
|
||||
}
|
||||
|
||||
async function getMessageBody(context) {
|
||||
const source = fs
|
||||
.readFileSync(
|
||||
context.env.IS_SUCCESS === 'true'
|
||||
? path.resolve(__dirname, 'templates', 'success')
|
||||
: path.resolve(__dirname, 'templates', 'failure'),
|
||||
)
|
||||
.toString();
|
||||
const template = Handlebars.compile(source);
|
||||
return template(context);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addToContextAndValidate,
|
||||
getMessageBody,
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥
|
||||
> :rocket: {{ env.NYM_PROJECT_NAME }}
|
||||
> 🔴 **FAILURE** :cry:
|
||||
> `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 }}
|
||||
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
|
||||
|
||||
Commit message:
|
||||
```
|
||||
{{ env.GIT_COMMIT_MESSAGE }}
|
||||
```
|
||||
@@ -0,0 +1,16 @@
|
||||
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
|
||||
> :rocket: {{ env.NYM_PROJECT_NAME }}
|
||||
> ✅ **SUCCESS**
|
||||
|
||||
> ➡️➡️➡️➡️➡️ **View output:**
|
||||
> `storybook`: https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}
|
||||
> `example`: https://{{ env.NYM_CI_WWW_LOCATION }}-example.{{ env.NYM_CI_WWW_BASE }}
|
||||
|
||||
> `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 }}
|
||||
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
|
||||
|
||||
Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}:
|
||||
```
|
||||
{{ env.GIT_COMMIT_MESSAGE }}
|
||||
```
|
||||
@@ -35,4 +35,6 @@ contracts/mixnet/Justfile
|
||||
contracts/mixnet/Makefile
|
||||
validator-config
|
||||
*.patch
|
||||
validator-api-config.toml
|
||||
validator-api-config.toml
|
||||
dist
|
||||
storybook-static
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"mainnet":[{
|
||||
"nymd_url":"https://rpc.nyx.nodes.guru/",
|
||||
"api_url":"https://api.nyx.nodes.guru/"
|
||||
}]
|
||||
}
|
||||
@@ -17,7 +17,7 @@ members = [
|
||||
"clients/native",
|
||||
"clients/native/websocket-requests",
|
||||
"clients/socks5",
|
||||
"clients/tauri-client/src-tauri",
|
||||
# "clients/tauri-client/src-tauri",
|
||||
"common/client-libs/gateway-client",
|
||||
"common/client-libs/mixnet-client",
|
||||
"common/client-libs/validator-client",
|
||||
@@ -33,6 +33,9 @@ members = [
|
||||
"common/network-defaults",
|
||||
"common/nonexhaustive-delayqueue",
|
||||
"common/nymcoconut",
|
||||
"common/nym_offline_compact_ecash",
|
||||
"common/nym_offline_divisible_ecash",
|
||||
"common/nym_online_divisible_ecash",
|
||||
"common/nymsphinx",
|
||||
"common/nymsphinx/acknowledgements",
|
||||
"common/nymsphinx/addressing",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
test: build clippy-all cargo-test wasm fmt
|
||||
no-clippy: build cargo-test wasm fmt
|
||||
happy: fmt clippy-happy test
|
||||
clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet
|
||||
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet
|
||||
cargo-test: test-main test-contracts test-wallet
|
||||
build: build-main build-contracts build-wallet
|
||||
build: build-contracts build-wallet build-main
|
||||
fmt: fmt-main fmt-contracts fmt-wallet
|
||||
|
||||
clippy-happy-main:
|
||||
@@ -12,20 +13,20 @@ clippy-happy-main:
|
||||
clippy-happy-contracts:
|
||||
cargo clippy --manifest-path contracts/Cargo.toml --target wasm32-unknown-unknown
|
||||
|
||||
clippy-happy-wallet:
|
||||
clippy-happy-wallet:
|
||||
cargo clippy --manifest-path nym-wallet/Cargo.toml
|
||||
|
||||
clippy-all-main:
|
||||
cargo clippy --all-features -- -D warnings
|
||||
cargo clippy --workspace --all-features -- -D warnings
|
||||
|
||||
clippy-all-contracts:
|
||||
cargo clippy --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
|
||||
cargo clippy --workspace --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
|
||||
|
||||
clippy-all-wallet:
|
||||
cargo clippy --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
|
||||
clippy-all-wallet:
|
||||
cargo clippy --workspace --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
|
||||
|
||||
test-main:
|
||||
cargo test --all-features
|
||||
cargo test --all-features --workspace
|
||||
|
||||
test-contracts:
|
||||
cargo test --manifest-path contracts/Cargo.toml --all-features
|
||||
@@ -34,13 +35,13 @@ test-wallet:
|
||||
cargo test --manifest-path nym-wallet/Cargo.toml --all-features
|
||||
|
||||
build-main:
|
||||
cargo build --all
|
||||
cargo build --workspace
|
||||
|
||||
build-contracts:
|
||||
cargo build --manifest-path contracts/Cargo.toml --all
|
||||
cargo build --manifest-path contracts/Cargo.toml --workspace
|
||||
|
||||
build-wallet:
|
||||
cargo build --manifest-path nym-wallet/Cargo.toml --all
|
||||
cargo build --manifest-path nym-wallet/Cargo.toml --workspace
|
||||
|
||||
fmt-main:
|
||||
cargo fmt --all
|
||||
|
||||
@@ -28,6 +28,8 @@ Wallet build instructions are also available on [our docs site](https://nymtech.
|
||||
|
||||
There's a `.env.sample-dev` file provided which you can rename to `.env` if you want convenient logging, backtrace, or other environment variables pre-set. The `.env` file is ignored so you don't need to worry about checking it in.
|
||||
|
||||
For Typescript components, please see [ts-packages](./ts-packages).
|
||||
|
||||
### Developer chat
|
||||
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Shared assets
|
||||
|
||||
This directory contains asset files shared by many projects in this repo.
|
||||
|
||||
You will find:
|
||||
|
||||
- favicons
|
||||
- logos
|
||||
- shared fonts
|
||||
- shared icon SVGs
|
||||
|
||||
See [ts-packages/react-webpack-with-theme-example](../ts-packages/react-webpack-with-theme-example) for examples of usage.
|
||||
|
After Width: | Height: | Size: 545 KiB |
@@ -0,0 +1,10 @@
|
||||
<svg width="64" height="64" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M40 78.5C61.263 78.5 78.5 61.263 78.5 40C78.5 18.737 61.263 1.5 40 1.5C18.737 1.5 1.5 18.737 1.5 40C1.5 61.263 18.737 78.5 40 78.5Z" fill="#070B15" stroke="url(#paint0_linear_0_1)" stroke-width="3"/>
|
||||
<path d="M31.4894 27.56L41.8623 56H48.5106H56V24H48.5106V52.4L38.1777 24H31.4894H24V56H31.4894V27.56Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_0_1" x1="0.839161" y1="80" x2="80" y2="80" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.09375" stop-color="#FB6E4E"/>
|
||||
<stop offset="1" stop-color="#F51473"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 672 B |
@@ -0,0 +1,6 @@
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src: url('./OpenSans-VariableFont_wdth,wght.ttf') format('truetype-variations'),
|
||||
url('./OpenSans-Italic-VariableFont_wdth,wght.ttf') format('truetype-variations');
|
||||
font-weight: 100 1000;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<svg width="64" height="64" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M40 78.5C61.263 78.5 78.5 61.263 78.5 40C78.5 18.737 61.263 1.5 40 1.5C18.737 1.5 1.5 18.737 1.5 40C1.5 61.263 18.737 78.5 40 78.5Z" fill="#070B15" stroke="url(#paint0_linear_0_1)" stroke-width="3"/>
|
||||
<path d="M31.4894 27.56L41.8623 56H48.5106H56V24H48.5106V52.4L38.1777 24H31.4894H24V56H31.4894V27.56Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_0_1" x1="0.839161" y1="80" x2="80" y2="80" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.09375" stop-color="#FB6E4E"/>
|
||||
<stop offset="1" stop-color="#F51473"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 714 B |
@@ -0,0 +1,13 @@
|
||||
<svg width="300" height="300" viewBox="0 0 296 296" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M148 296C229.738 296 296 229.738 296 148C296 66.2619 229.738 0 148 0C66.2619 0 0 66.2619 0 148C0 229.738 66.2619 296 148 296Z" fill="url(#paint0_linear_113_1244)"/>
|
||||
<path d="M148 285.875C224.147 285.875 285.875 224.146 285.875 148C285.875 71.8536 224.147 10.1248 148 10.1248C71.8538 10.1248 10.125 71.8536 10.125 148C10.125 224.146 71.8538 285.875 148 285.875Z" fill="#121725"/>
|
||||
<path d="M88.8829 120.143H88.7169V120.281V168.637L68.3289 120.226L68.3012 120.143H68.1905H56.6272H43.653H43.5146V120.281V175.719V175.857H43.653H56.6272H56.7655V175.719V127.28L77.2365 175.774L77.2642 175.857H77.3748H88.8829H101.829H101.968V175.719V120.281V120.143H101.829H88.8829Z" fill="white"/>
|
||||
<path d="M252.347 120.143H227.616H227.477L227.45 120.253L214.78 168.858L202.082 120.253L202.054 120.143H201.944H177.157H176.991V120.281V175.719V175.857H177.157H190.104H190.242V175.719V127.667L202.774 175.747L202.801 175.857H202.94H226.564H226.675L226.703 175.747L239.234 127.667V175.719V175.857H239.373H252.347H252.485V175.719V120.281V120.143H252.347Z" fill="white"/>
|
||||
<path d="M155.663 120.143H155.58L155.552 120.198L139.812 147.557L123.988 120.198L123.96 120.143H123.877H108.911H108.635L108.773 120.364L133.145 162.579V175.719V175.857H133.283H146.257H146.396V175.719V162.579L170.767 120.364L170.905 120.143H170.629H155.663Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_113_1244" x1="0" y1="148" x2="296" y2="148" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.09375" stop-color="#FB6E4E"/>
|
||||
<stop offset="1" stop-color="#FC1D60"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="210" height="56" viewBox="0 0 210 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M45.8829 0.142822H45.7169V0.28114V48.637L25.3289 0.225818L25.3012 0.142822H25.1905H13.6272H0.652966H0.514648V0.28114V55.7189V55.8572H0.652966H13.6272H13.7655V55.7189V7.28002L34.2365 55.7742L34.2642 55.8572H34.3748H45.8829H58.8294H58.9677V55.7189V0.28114V0.142822H58.8294H45.8829Z"/>
|
||||
<path d="M209.347 0.142822H184.616H184.477L184.45 0.253483L171.78 48.8583L159.082 0.253483L159.054 0.142822H158.944H134.157H133.991V0.28114V55.7189V55.8572H134.157H147.104H147.242V55.7189V7.66731L159.774 55.7466L159.801 55.8572H159.94H183.564H183.675L183.703 55.7466L196.234 7.66731V55.7189V55.8572H196.373H209.347H209.485V55.7189V0.28114V0.142822H209.347Z"/>
|
||||
<path d="M112.663 0.142822H112.58L112.552 0.198153L96.8116 27.5574L80.988 0.198153L80.9604 0.142822H80.8774H65.9114H65.6348L65.7731 0.364136L90.1447 42.5787V55.7189V55.8572H90.283H103.257H103.396V55.7189V42.5787L127.767 0.364136L127.905 0.142822H127.629H112.663Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M170.7 29.3001C131.7 -9.7999 68.3001 -9.7999 29.3001 29.3001C-9.7999 68.4001 -9.7999 131.7 29.3001 170.7C68.4001 209.8 131.7 209.8 170.7 170.7C209.8 131.7 209.8 68.3001 170.7 29.3001ZM162.1 162.1C127.8 196.4 72.1001 196.4 37.8001 162.1C3.5001 127.8 3.5001 72.1001 37.8001 37.8001C72.1001 3.5001 127.8 3.5001 162.1 37.8001C196.5 72.2001 196.5 127.8 162.1 162.1Z" fill="white"/>
|
||||
<path d="M162.1 37.9C127.8 3.60005 72.1002 3.60005 37.8002 37.9C3.50019 72.2 3.50019 127.9 37.8002 162.2C72.1002 196.5 127.8 196.5 162.1 162.2C196.5 127.8 196.5 72.2 162.1 37.9ZM63.0002 170.7C56.8002 167.4 51.1002 163.2 46.1002 158.4V41.7C51.3002 36.7 57.2002 32.5 63.6002 29.1L137 140.9V29.3C143.2 32.6 148.9 36.8 153.9 41.6V158.3C148.7 163.3 142.8 167.5 136.4 170.9L63.0002 59.1V170.7Z" fill="#070B15"/>
|
||||
<path d="M154 158.3V41.7C148.9 36.9 143.2 32.7 137.1 29.4V140.9L63.5 29C57.1 32.4 51.2 36.6 46 41.6V158.3C51.1 163.1 56.8 167.3 62.9 170.6V59.1L136.5 171C142.9 167.6 148.8 163.3 154 158.3Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M170.7 29.3001C131.7 -9.7999 68.3001 -9.7999 29.3001 29.3001C-9.7999 68.4001 -9.7999 131.7 29.3001 170.7C68.4001 209.8 131.7 209.8 170.7 170.7C209.8 131.7 209.8 68.3001 170.7 29.3001ZM162.1 162.1C127.8 196.4 72.1001 196.4 37.8001 162.1C3.5001 127.8 3.5001 72.1001 37.8001 37.8001C72.1001 3.5001 127.8 3.5001 162.1 37.8001C196.5 72.2001 196.5 127.8 162.1 162.1Z" fill="#141521"/>
|
||||
<path d="M162.1 37.9C127.8 3.60005 72.1002 3.60005 37.8002 37.9C3.50019 72.2 3.50019 127.9 37.8002 162.2C72.1002 196.5 127.8 196.5 162.1 162.2C196.5 127.8 196.5 72.2 162.1 37.9ZM63.0002 170.7C56.8002 167.4 51.1002 163.2 46.1002 158.4V41.7C51.3002 36.7 57.2002 32.5 63.6002 29.1L137 140.9V29.3C143.2 32.6 148.9 36.8 153.9 41.6V158.3C148.7 163.3 142.8 167.5 136.4 170.9L63.0002 59.1V170.7Z" fill="white"/>
|
||||
<path d="M154 158.3V41.7C148.9 36.9 143.2 32.7 137.1 29.4V140.9L63.5 29C57.1 32.4 51.2 36.6 46 41.6V158.3C51.1 163.1 56.8 167.3 62.9 170.6V59.1L136.5 171C142.9 167.6 148.8 163.3 154 158.3Z" fill="#141521"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,10 +1,10 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crypto::generic_array::typenum::Unsigned;
|
||||
use log::*;
|
||||
use nymsphinx::anonymous_replies::{
|
||||
encryption_key::EncryptionKeyDigest, encryption_key::Unsigned, SurbEncryptionKey,
|
||||
SurbEncryptionKeySize,
|
||||
encryption_key::EncryptionKeyDigest, SurbEncryptionKey, SurbEncryptionKeySize,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
@@ -43,7 +43,7 @@ impl ReplyKeyStorage {
|
||||
// if this fails it means we have some database corruption and we
|
||||
// absolutely can't continue
|
||||
|
||||
if key_bytes_ref.len() != SurbEncryptionKeySize::to_usize() {
|
||||
if key_bytes_ref.len() != SurbEncryptionKeySize::USIZE {
|
||||
error!("REPLY KEY STORAGE DATA CORRUPTION - ENCRYPTION KEY HAS INVALID LENGTH");
|
||||
panic!("REPLY KEY STORAGE DATA CORRUPTION - ENCRYPTION KEY HAS INVALID LENGTH");
|
||||
}
|
||||
|
||||
@@ -6085,9 +6085,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.7",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.7.tgz",
|
||||
"integrity": "sha512-HxWkieX+STA38EDk7CE9MEryFeHCKzgagxlGvsdS7WBImq9Mk+PGwiT56w82WI3aicwJA8REp42Cxo98c8FZMA==",
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"querystringify": "^2.1.1",
|
||||
@@ -11733,9 +11733,9 @@
|
||||
}
|
||||
},
|
||||
"url-parse": {
|
||||
"version": "1.5.7",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.7.tgz",
|
||||
"integrity": "sha512-HxWkieX+STA38EDk7CE9MEryFeHCKzgagxlGvsdS7WBImq9Mk+PGwiT56w82WI3aicwJA8REp42Cxo98c8FZMA==",
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"querystringify": "^2.1.1",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@rollup/plugin-node-resolve": "^8.0.0",
|
||||
"@rollup/plugin-replace": "^2.4.0",
|
||||
"@rollup/plugin-url": "^5.0.0",
|
||||
"@tauri-apps/cli": "^1.0.0-beta.5",
|
||||
"@tauri-apps/cli": "^1.0.0-rc.5",
|
||||
"rollup": "^2.3.4",
|
||||
"rollup-plugin-svelte": "^7.0.0",
|
||||
"rollup-plugin-terser": "^7.0.0",
|
||||
|
||||
@@ -12,15 +12,19 @@ build = "src/build.rs"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.0.0-beta.2" }
|
||||
tauri-build = { version = "=1.0.0-rc.2" }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.0.0-beta.4", features = [] }
|
||||
tauri = { version = "=1.0.0-rc.2", features = [] }
|
||||
tokio = "1.4"
|
||||
url = "2.2"
|
||||
|
||||
tauri-codegen = "=1.0.0-rc.1"
|
||||
tauri-macros = "=1.0.0-rc.1"
|
||||
core-graphics = "=0.22.2"
|
||||
|
||||
coconut-interface = { path = "../../../common/coconut-interface" }
|
||||
credentials = { path = "../../../common/credentials" }
|
||||
validator-client = {path = "../../../common/client-libs/validator-client"}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# CLIENT INIT
|
||||
NYMD_URL=https://sandbox-validator.nymtech.net
|
||||
VALIDATOR_API=https://sandbox-validator.nymtech.net/api
|
||||
MIXNET_CONTRACT=nymt1ghd753shjuwexxywmgs4xz7x2q732vcnstz02j
|
||||
VESTING_CONTRACT=nymt1nc5tatafv6eyq7llkr2gv50ff9e22mnfp9pc5s
|
||||
CURRENCY_PREFIX=nymt
|
||||
CHAIN_ID=nym-sandbox
|
||||
|
||||
# USER DETAILS
|
||||
USER_MNEMONIC=
|
||||
USER_WALLET_ADDRESS=
|
||||
|
||||
# MIXNODE DETAILS
|
||||
MIXNODE_IDENTITY=
|
||||
MIXNODE_SPHINX_KEY=
|
||||
MIXNODE_SIGNATURE=
|
||||
MIXNODE_HOST="1.1.1.1"
|
||||
MIXNODE_VERSION="0.12.1"
|
||||
|
||||
# GATEWAY DETAILS
|
||||
GATEWAY_IDENTITY=
|
||||
GATEWAY_SPHINX=
|
||||
GATEAWAY_LOCATION=
|
||||
GATEWAY_HOST="1.1.1.1"
|
||||
GATEWAY_VERSION="0.12.1"
|
||||
@@ -3,20 +3,26 @@ Nym Validator Client
|
||||
|
||||
A TypeScript client for interacting with CosmWasm smart contracts in Nym validators.
|
||||
|
||||
Running examples
|
||||
-----------------
|
||||
|
||||
With the code checked out, `cd examples`. This folder contains runnable example code that will set up a blockchain and allow you to interact with it through the client.
|
||||
|
||||
Running tests
|
||||
-------------
|
||||
|
||||
The tests will be separated into three categories: unit, integration and mock.
|
||||
|
||||
Currently the command to run all tests:
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
|
||||
The tests require `.env.example` being renamed to `.env`. The variables and their values for these tests are currently pointing to the `nym-sandbox` environment.
|
||||
You can also trigger test execution with a test watcher. I don't have the centuries of life left to me that are needed to fight through the arcana of wiring up a working TypeScript mocha triggered execution setup, so for now my Cargo-based hack is:
|
||||
|
||||
`Tests are still in development` - the test libary is `jest` and the test script will execute currently with: `--coverage --verbosity false`
|
||||
|
||||
```
|
||||
cargo watch -s "cd clients/validator && npm test"
|
||||
```
|
||||
|
||||
It's ugly but works fine if you have Cargo installed. TypeScript setup help happily accepted here.
|
||||
|
||||
Generating Documentation
|
||||
------------------------
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
||||
setupFiles: ["dotenv/config"],
|
||||
testTimeout: 20000
|
||||
};
|
||||
@@ -6,7 +6,9 @@
|
||||
"main": "./dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"test": "jest --verbose false",
|
||||
"build": "tsc",
|
||||
"test": "ts-mocha tests/**/*.test.ts",
|
||||
"coverage": "nyc npm test",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"docs": "typedoc --out docs src/index.ts"
|
||||
@@ -19,21 +21,27 @@
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@types/jest": "27.4.0",
|
||||
"@types/chai": "^4.2.15",
|
||||
"@types/expect": "^24.3.0",
|
||||
"@types/mocha": "^8.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.7.0",
|
||||
"@typescript-eslint/parser": "^5.7.0",
|
||||
"chai": "^4.2.0",
|
||||
"eslint": "^7.18.0",
|
||||
"eslint-config-airbnb": "^19.0.2",
|
||||
"eslint-config-airbnb-typescript": "^16.1.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-import-resolver-root-import": "^1.0.4",
|
||||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-mocha": "^10.0.3",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"jest": "^27.4.5",
|
||||
"mocha": "^8.2.1",
|
||||
"moq.ts": "^7.2.0",
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^2.5.1",
|
||||
"ts-jest": "^27.1.2",
|
||||
"ts-mocha": "^8.0.0",
|
||||
"typedoc": "^0.20.27",
|
||||
"typescript": "^4.5.4"
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cosmjs/cosmwasm-stargate": "^0.27.0-rc2",
|
||||
@@ -43,8 +51,6 @@
|
||||
"@cosmjs/stargate": "^0.27.0-rc2",
|
||||
"@cosmjs/tendermint-rpc": "^0.27.0-rc2",
|
||||
"axios": "^0.21.1",
|
||||
"cosmjs-types": "^0.4.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"moq.ts": "^7.3.4"
|
||||
"cosmjs-types": "^0.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,15 +64,23 @@ export default class ValidatorClient implements INymClient {
|
||||
|
||||
readonly vestingContract: string;
|
||||
|
||||
readonly mainnetDenom = "unym";
|
||||
|
||||
readonly mainnetPrefix = "n";
|
||||
|
||||
private constructor(
|
||||
client: SigningClient | QueryClient,
|
||||
prefix: string,
|
||||
mixnetContract: string,
|
||||
vestingContract: string,
|
||||
vestingContract: string
|
||||
) {
|
||||
this.client = client;
|
||||
this.prefix = prefix;
|
||||
this.denom = `u${prefix}`;
|
||||
if (prefix == this.mainnetPrefix) {
|
||||
this.denom = this.mainnetDenom;
|
||||
} else {
|
||||
this.denom = `u${prefix}`;
|
||||
}
|
||||
|
||||
this.mixnetContract = mixnetContract;
|
||||
this.vestingContract = vestingContract;
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
import validator from "../../src/index";
|
||||
import { ExecuteResult } from "@cosmjs/cosmwasm-stargate";
|
||||
import { config } from "../test-utils/config";
|
||||
import {buildCoin, buildWallet, profitPercentage} from "../test-utils/utils"
|
||||
import {
|
||||
Gateway,
|
||||
GatewayOwnershipResponse,
|
||||
MixNode,
|
||||
MixOwnershipResponse,
|
||||
} from "../../src/types";
|
||||
|
||||
let response: ExecuteResult;
|
||||
let validatorClient: validator;
|
||||
let ownsMixNode: MixOwnershipResponse;
|
||||
let ownsGateway: GatewayOwnershipResponse;
|
||||
|
||||
beforeEach(async () => {
|
||||
validatorClient = await validator.connect(
|
||||
config.USER_MNEMONIC,
|
||||
config.NYMD_URL,
|
||||
config.VALIDATOR_API,
|
||||
config.NETWORK_BECH,
|
||||
config.MIXNET_CONTRACT,
|
||||
config.VESTING_CONTRACT
|
||||
);
|
||||
});
|
||||
|
||||
describe("long running e2e tests", () => {
|
||||
test.skip("token transfer", async () => {
|
||||
try {
|
||||
//make sure there's enough balance in the wallet
|
||||
|
||||
let coin = buildCoin("50000", "nymt");
|
||||
let userAddress = await buildWallet();
|
||||
let send = await validatorClient.send(
|
||||
userAddress,
|
||||
Array(coin),
|
||||
"auto",
|
||||
"send-tokens"
|
||||
);
|
||||
let jsonParse = JSON.parse(send.rawLog as string);
|
||||
|
||||
//check successful network broadcast - via events
|
||||
//1 - get key attributes values for sender an assert them
|
||||
//2 - get key attributes for receiver assert they match
|
||||
//3 - transaction hash present in response
|
||||
|
||||
// { array of events -> attribute -> event information }
|
||||
expect(jsonParse[0].events[1].attributes[1].value).toStrictEqual(
|
||||
config.USER_WALLET_ADDRESS
|
||||
);
|
||||
expect(jsonParse[0].events[1].attributes[0].value).toStrictEqual(
|
||||
userAddress
|
||||
);
|
||||
expect(jsonParse[0].events[1].type).toStrictEqual(
|
||||
"transfer"
|
||||
);
|
||||
expect(send.transactionHash).toStrictEqual(expect.any(String));
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
test.skip("update mixnode profit percentage", async () => {
|
||||
const nodeIdentity = config.MIXNODE_IDENTITY;
|
||||
const profitPercent = profitPercentage();
|
||||
|
||||
try {
|
||||
//use auto fees - simulated gas
|
||||
response = await validatorClient.updateMixnodeConfig(nodeIdentity, 'auto', profitPercent);
|
||||
}
|
||||
catch (error) {
|
||||
throw error;
|
||||
}
|
||||
try {
|
||||
ownsMixNode = await validatorClient.client.ownsMixNode(config.MIXNET_CONTRACT, config.USER_WALLET_ADDRESS);
|
||||
}
|
||||
catch (error) {
|
||||
throw error;
|
||||
}
|
||||
expect(ownsMixNode.mixnode?.mix_node.profit_margin_percent).toStrictEqual(profitPercent);
|
||||
});
|
||||
|
||||
test.skip("unbond and bond mixnode", async () => {
|
||||
|
||||
try {
|
||||
await validatorClient.unbondMixNode("auto", "unbond-mixnode");
|
||||
}
|
||||
catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const profitPercent = profitPercentage();
|
||||
|
||||
const mixnodeDetails = <MixNode>{
|
||||
host: config.MIXNODE_HOST,
|
||||
mix_port: 1789,
|
||||
verloc_port: 1790,
|
||||
http_api_port: 8080,
|
||||
identity_key: config.MIXNODE_IDENTITY,
|
||||
sphinx_key: config.MIXNODE_SPHINX_KEY,
|
||||
version: config.MIXNODE_VERSION,
|
||||
profit_margin_percent: profitPercent
|
||||
};
|
||||
|
||||
const bond = buildCoin("100000000", config.CURRENCY_DENOM)
|
||||
|
||||
try {
|
||||
response = await validatorClient.bondMixNode(
|
||||
mixnodeDetails,
|
||||
config.MIXNODE_SIGNATURE,
|
||||
bond,
|
||||
"auto"
|
||||
);
|
||||
}
|
||||
catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
ownsMixNode = await validatorClient.client.ownsMixNode(config.MIXNET_CONTRACT, config.USER_WALLET_ADDRESS);
|
||||
expect(ownsMixNode.mixnode?.mix_node.profit_margin_percent).toStrictEqual(profitPercent);
|
||||
});
|
||||
|
||||
test.skip("unbond and bond gateway", async () => {
|
||||
//gateway requires different user wallet
|
||||
//init inside test
|
||||
//todo
|
||||
|
||||
try {
|
||||
await validatorClient.unbondGateway("auto", "unbonding gateway");
|
||||
}
|
||||
catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const gateway = <Gateway>{
|
||||
host: config.GATEWAY_HOST,
|
||||
mix_port: 1789,
|
||||
clients_port: 9000,
|
||||
version: config.GATEWAY_VERSION,
|
||||
sphinx_key: config.GATEWAY_SPHINX,
|
||||
identity_key: config.GATEWAY_IDENTITY,
|
||||
location: "earth"
|
||||
};
|
||||
|
||||
const bond = buildCoin("100000000", config.CURRENCY_DENOM)
|
||||
|
||||
try {
|
||||
response = await validatorClient.bondGateway(
|
||||
gateway,
|
||||
config.GATEWAY_SIGNATURE,
|
||||
bond,
|
||||
"auto"
|
||||
);
|
||||
}
|
||||
catch (error) {
|
||||
throw error;
|
||||
}
|
||||
ownsGateway = await validatorClient.client.ownsGateway(config.MIXNET_CONTRACT, config.USER_WALLET_ADDRESS);
|
||||
expect(ownsGateway.gateway?.bond_amount).toStrictEqual(bond.amount);
|
||||
expect(ownsGateway.address).toStrictEqual(config.USER_WALLET_ADDRESS);
|
||||
});
|
||||
|
||||
test.skip("delegate to mixnode, then undelegate", async () => {
|
||||
|
||||
const pledge = buildCoin("100000000", config.CURRENCY_DENOM)
|
||||
|
||||
try {
|
||||
response = await validatorClient.delegateToMixNode(
|
||||
config.MIXNODE_IDENTITY,
|
||||
pledge,
|
||||
"auto"
|
||||
);
|
||||
//todo - we can assert the events for responses
|
||||
response.logs.forEach((log) => {
|
||||
console.log(log.events);
|
||||
console.log(log.log);
|
||||
console.log(log.msg_index);
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
throw error;
|
||||
}
|
||||
try {
|
||||
const unbond = await validatorClient.undelegateFromMixNode(
|
||||
config.MIXNODE_IDENTITY,
|
||||
"auto"
|
||||
);
|
||||
|
||||
//todo - we can assert the events for responses
|
||||
unbond.logs.forEach((logs) => {
|
||||
logs.events.forEach((events) => {
|
||||
console.log(events.type);
|
||||
console.log(events.attributes);
|
||||
})
|
||||
});
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,46 +0,0 @@
|
||||
import { Mock, Times } from "moq.ts";
|
||||
import { Block, BlockHeader } from "@cosmjs/stargate";
|
||||
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
|
||||
|
||||
describe("implement cosmwasm client test", () => {
|
||||
test.only("get height of a block then search for it", async () => {
|
||||
let height = Promise.resolve(200);
|
||||
|
||||
let blockHeader = <BlockHeader>{
|
||||
version: {
|
||||
block: "200",
|
||||
app: "testing",
|
||||
},
|
||||
height: 200,
|
||||
chainId: "nym",
|
||||
time: "today",
|
||||
};
|
||||
|
||||
let block = Promise.resolve(<Block>{
|
||||
header: blockHeader,
|
||||
id: "test",
|
||||
txs: [],
|
||||
});
|
||||
|
||||
const getheight = new Mock<CosmWasmClient>()
|
||||
.setup((nym) => nym.getHeight())
|
||||
.returns(height);
|
||||
|
||||
const getblock = new Mock<CosmWasmClient>()
|
||||
.setup((nym) => nym.getBlock(200))
|
||||
.returns(block);
|
||||
|
||||
let heightC = getheight.object();
|
||||
let blockC = getblock.object();
|
||||
|
||||
let executeHeight = await heightC.getHeight();
|
||||
let executeBlock = await blockC.getBlock(200);
|
||||
|
||||
getheight.verify((nym) => nym.getHeight(), Times.Exactly(1));
|
||||
getblock.verify((nym) => nym.getBlock(200), Times.Exactly(1));
|
||||
|
||||
expect(executeHeight).toStrictEqual(await height);
|
||||
expect(executeBlock.header.height).toStrictEqual(await height);
|
||||
expect(executeBlock.header.chainId).toStrictEqual("nym");
|
||||
});
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Mock, Times } from "moq.ts";
|
||||
import { INymdQuery } from "../../src/query-client";
|
||||
|
||||
describe("nym-client mocks", () => {
|
||||
test.only("gets interval rewarding percent", async () => {
|
||||
let contract = "mixnet_contract";
|
||||
let response = Promise.resolve(Number(2));
|
||||
|
||||
const client = new Mock<INymdQuery>()
|
||||
.setup((nym) => nym.getIntervalRewardPercent(contract))
|
||||
.returns(response);
|
||||
|
||||
const obj = client.object();
|
||||
|
||||
let execute = await obj.getIntervalRewardPercent(contract);
|
||||
|
||||
client.verify(
|
||||
(nym) => nym.getIntervalRewardPercent(contract),
|
||||
Times.Exactly(1)
|
||||
);
|
||||
|
||||
expect(execute).toStrictEqual(await response);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
import { Coin } from "@cosmjs/proto-signing";
|
||||
import { Mock, Times } from "moq.ts";
|
||||
import ValidatorClient from "../../src/index";
|
||||
import { Gateway, MixNode } from "../../src/types";
|
||||
import { config } from "../test-utils/config";
|
||||
import { buildWallet, buildCoin, profitPercentage } from "../test-utils/utils";
|
||||
import { promiseExecuteResult } from "../test-utils/expectedResults";
|
||||
import { promiseTxResult } from "../test-utils/expectedResults"
|
||||
|
||||
describe("mock validator client tests", () => {
|
||||
test.skip("token transfer", async () => {
|
||||
|
||||
//arrange
|
||||
//todo -- add more here
|
||||
let recipientAddress = "nymt14ev4p8qaa7ayr06cg3z7y2u2kxc9a8f4h9gkch";
|
||||
let sender = "nymt1cv59jumgvz2chn7ffst8tzvnapqzp282m5vat2";
|
||||
|
||||
const coin = buildCoin("50000", "nymt");
|
||||
|
||||
let transaction = promiseTxResult();
|
||||
|
||||
let mockClient = new Mock<ValidatorClient>()
|
||||
.setup((nym) => nym.send(recipientAddress, [coin], "auto", "test")).returns(transaction);
|
||||
|
||||
let token = mockClient.object();
|
||||
|
||||
//act
|
||||
let response = await token.send(recipientAddress, [coin], "auto", "test");
|
||||
|
||||
//assert
|
||||
mockClient.verify(cl => cl.send(recipientAddress, [coin], "auto"), Times.Exactly(1));
|
||||
});
|
||||
|
||||
test.only("bond mixnode test", async () => {
|
||||
//arrange
|
||||
let ownerSignature = "ownersignature";
|
||||
let coin = buildCoin("50000", "nymt");
|
||||
let expectedResult = promiseExecuteResult();
|
||||
|
||||
const profitPercent = profitPercentage();
|
||||
|
||||
const mixnode = <MixNode>{
|
||||
host: "1.1.1.1",
|
||||
mix_port: 1789,
|
||||
verloc_port: 1790,
|
||||
http_api_port: 8080,
|
||||
identity_key: "identity",
|
||||
sphinx_key: "identity",
|
||||
version: "0.12.1",
|
||||
profit_margin_percent: profitPercent,
|
||||
};
|
||||
|
||||
let client = new Mock<ValidatorClient>()
|
||||
.setup((client) =>
|
||||
client.bondMixNode(mixnode, ownerSignature, coin, "auto")
|
||||
)
|
||||
.returns(expectedResult);
|
||||
|
||||
let mixnodeBond = client.object();
|
||||
|
||||
//act
|
||||
let response = await mixnodeBond.bondMixNode(
|
||||
mixnode,
|
||||
ownerSignature,
|
||||
coin,
|
||||
"auto"
|
||||
);
|
||||
client.verify((cl) =>
|
||||
cl.bondMixNode(mixnode, ownerSignature, coin, "auto")
|
||||
);
|
||||
|
||||
//assert
|
||||
expect(response.logs[0].log).toStrictEqual("test");
|
||||
expect(response.transactionHash).toStrictEqual(
|
||||
"9C7BF465AB5CAB0D62446CBB251CF89CD173A640C5DE8DBC14A4BB950916114E"
|
||||
);
|
||||
});
|
||||
|
||||
test.only("un-bond mixnode", async () => {
|
||||
//arrange
|
||||
let expectedResult = promiseExecuteResult();
|
||||
|
||||
let client = new Mock<ValidatorClient>()
|
||||
.setup((client) => client.unbondMixNode("auto"))
|
||||
.returns(expectedResult);
|
||||
|
||||
let unbondNode = client.object();
|
||||
|
||||
//act
|
||||
let response = await unbondNode.unbondMixNode("auto");
|
||||
client.verify((cl) => cl.unbondMixNode("auto"));
|
||||
|
||||
//assert
|
||||
expect(response.logs[0].log).toStrictEqual("test");
|
||||
expect(response.transactionHash).toStrictEqual(
|
||||
"9C7BF465AB5CAB0D62446CBB251CF89CD173A640C5DE8DBC14A4BB950916114E"
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
test.only("bond gateway", async () => {
|
||||
//arrange
|
||||
let expectedResult = promiseExecuteResult();
|
||||
let ownerSignature = "ownersigntature";
|
||||
let coin = buildCoin("50000", "nymt");
|
||||
|
||||
const gateway = <Gateway>{
|
||||
host: '1.2.3.4',
|
||||
mix_port: 1789,
|
||||
clients_port: 9000,
|
||||
version: "0.12.1",
|
||||
sphinx_key: "sphinx_key",
|
||||
identity_key: "identity_key",
|
||||
location: "earth"
|
||||
};
|
||||
|
||||
let client = new Mock<ValidatorClient>()
|
||||
.setup((client) => client.bondGateway(gateway, ownerSignature, coin, "auto", "memo"))
|
||||
.returns(expectedResult);
|
||||
|
||||
let mock = client.object();
|
||||
|
||||
//act
|
||||
let response = await mock.bondGateway(gateway, ownerSignature, coin, "auto", "memo");
|
||||
client.verify((cl) => cl.bondGateway(gateway, ownerSignature, coin, "auto", "memo"));
|
||||
|
||||
//assert
|
||||
expect(response.logs[0].log).toStrictEqual("test");
|
||||
expect(response.transactionHash).toStrictEqual(
|
||||
"9C7BF465AB5CAB0D62446CBB251CF89CD173A640C5DE8DBC14A4BB950916114E"
|
||||
);
|
||||
});
|
||||
|
||||
test.only("unbond gateway", async () => {
|
||||
//arrange
|
||||
let expectedResult = promiseExecuteResult();
|
||||
let client = new Mock<ValidatorClient>()
|
||||
.setup((client) => client.unbondGateway())
|
||||
.returns(expectedResult);
|
||||
|
||||
let mock = client.object();
|
||||
|
||||
//act
|
||||
let response = await mock.unbondGateway();
|
||||
client.verify((cl) => cl.unbondGateway());
|
||||
|
||||
//assert
|
||||
expect(response.logs[0].log).toStrictEqual("test");
|
||||
expect(response.transactionHash).toStrictEqual(
|
||||
"9C7BF465AB5CAB0D62446CBB251CF89CD173A640C5DE8DBC14A4BB950916114E"
|
||||
);
|
||||
});
|
||||
|
||||
test.only("retrieve a newly created account and the balance should be empty", async () => {
|
||||
let nymWallet = await buildWallet();
|
||||
|
||||
let coin = Promise.resolve(<Coin>{
|
||||
denom: `${config.CURRENCY_DENOM}`,
|
||||
amount: "0",
|
||||
});
|
||||
|
||||
let client = new Mock<ValidatorClient>()
|
||||
.setup((nym) => nym.getBalance(nymWallet))
|
||||
.returns(coin);
|
||||
|
||||
let obj = client.object();
|
||||
|
||||
let execute = await obj.getBalance(nymWallet);
|
||||
|
||||
client.verify((nym) => nym.getBalance(nymWallet), Times.Exactly(1));
|
||||
|
||||
expect(execute).toStrictEqual(await coin);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
export const config = {
|
||||
NYMD_URL: process.env.NYMD_URL as string,
|
||||
VALIDATOR_API: process.env.VALIDATOR_API as string,
|
||||
MIXNET_CONTRACT: process.env.MIXNET_CONTRACT as string,
|
||||
VESTING_CONTRACT: process.env.VESTING_CONTRACT as string,
|
||||
USER_MNEMONIC: process.env.USER_MNEMONIC as string,
|
||||
USER_WALLET_ADDRESS: process.env.USER_WALLET_ADDRESS as string,
|
||||
CURRENCY_DENOM: process.env.CURRENCY_DENOM as string,
|
||||
CHAIN_ID: process.env.CHAIN_ID as string,
|
||||
MIXNODE_IDENTITY: process.env.MIXNODE_IDENTITY as string,
|
||||
MIXNODE_SPHINX_KEY: process.env.MIXNODE_SPHINX_KEY as string,
|
||||
MIXNODE_SIGNATURE: process.env.MIXNODE_SIGNATURE as string,
|
||||
MIXNODE_HOST: process.env.MIXNODE_HOST as string,
|
||||
MIXNODE_VERSION: process.env.MIXNODE_VERSION as string,
|
||||
GATEWAY_IDENTITY: process.env.GATEWAY_IDENTITY as string,
|
||||
GATEWAY_SIGNATURE: process.env.GATEWAY_SIGNATURE as string,
|
||||
GATEWAY_SPHINX: process.env.GATEWAY_SPHINX as string,
|
||||
GATEWAY_LOCATION: process.env.GATEWAY_LOCATION as string,
|
||||
GATEWAY_HOST: process.env.GATEWAY_HOST as string,
|
||||
GATEWAY_VERSION: process.env.GATEWAY_VERSION as string,
|
||||
NETWORK_BECH: process.env.NETWORK_BECH as string,
|
||||
};
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { ExecuteResult } from "@cosmjs/cosmwasm-stargate";
|
||||
import { DeliverTxResponse, logs } from "@cosmjs/stargate";
|
||||
|
||||
export const promiseExecuteResult = (): Promise<ExecuteResult> => {
|
||||
let log = <logs.Log>{
|
||||
msg_index: 0,
|
||||
log: "test",
|
||||
events: [],
|
||||
};
|
||||
return Promise.resolve(<ExecuteResult>{
|
||||
logs: [log],
|
||||
transactionHash:
|
||||
"9C7BF465AB5CAB0D62446CBB251CF89CD173A640C5DE8DBC14A4BB950916114E",
|
||||
});
|
||||
};
|
||||
|
||||
export const promiseTxResult = (): Promise<DeliverTxResponse> => {
|
||||
return Promise.resolve(<DeliverTxResponse>{
|
||||
code: 0,
|
||||
height: 1208302,
|
||||
rawLog: "[]",
|
||||
transactionHash:
|
||||
"9C7BF465AB5CAB0D62446CBB251CF89CD173A640C5DE8DBC14A4BB950916114E",
|
||||
gasUsed: 65042,
|
||||
gasWanted: 67977,
|
||||
});
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
// timer actions
|
||||
|
||||
import ValidatorClient, { Coin } from "../../src";
|
||||
import { config } from "./config";
|
||||
|
||||
// Store current time as `start`
|
||||
export const now = (eventName = null) => {
|
||||
if (eventName) {
|
||||
console.log(`Started ${eventName}..`);
|
||||
}
|
||||
return new Date().getTime();
|
||||
};
|
||||
|
||||
//takes arg of start time
|
||||
export const elapsed = (beginning: number, log = false) => {
|
||||
const duration = new Date().getTime() - beginning;
|
||||
if (log) {
|
||||
console.log(`${duration / 1000}s`);
|
||||
}
|
||||
return duration;
|
||||
};
|
||||
|
||||
export const profitPercentage = (): number => {
|
||||
return Math.floor(Math.random() * 100) + 1;
|
||||
};
|
||||
|
||||
|
||||
export const buildCoin = (amount: string, denomination: string): Coin => {
|
||||
return {
|
||||
denom: `u${denomination}`,
|
||||
amount: amount,
|
||||
};
|
||||
};
|
||||
|
||||
export const buildWallet = async (): Promise<string> => {
|
||||
let mnemonic = ValidatorClient.randomMnemonic();
|
||||
|
||||
const randomAddress = await ValidatorClient.buildWallet(
|
||||
mnemonic,
|
||||
config.NETWORK_BECH
|
||||
);
|
||||
let accountdetails = await randomAddress.getAccounts();
|
||||
let nymWallet = accountdetails[0].address;
|
||||
return nymWallet;
|
||||
};
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
const currency = require("../../src/currency");
|
||||
|
||||
describe("currency module tests", () => {
|
||||
test.skip("convert to native balance", () => {
|
||||
const decimal = "12.0346";
|
||||
const value = currency.printableBalanceToNative(decimal);
|
||||
|
||||
expect(value).toStrictEqual("12034600");
|
||||
});
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
const stargate = require("../../src/stargate-helper");
|
||||
import { config } from "../test-utils/config";
|
||||
|
||||
describe("test the stargate functions within the project", () => {
|
||||
test.skip("gas price is returned correctly", () => {
|
||||
const nymCurrency = config.CURRENCY_DENOM;
|
||||
const getGasPrice = stargate.nymGasPrice(nymCurrency);
|
||||
|
||||
expect(getGasPrice.denom).toBe(`u${nymCurrency}`);
|
||||
});
|
||||
|
||||
test.skip("provide invalid type returns an error message", () => {
|
||||
//pass invalid type
|
||||
expect(() => {
|
||||
const nymCurrency = 13;
|
||||
stargate.nymGasPrice(nymCurrency);
|
||||
}).toThrow("13 is not of type string");
|
||||
});
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
import validator from "../../src/index";
|
||||
|
||||
describe("validator build network mnemonic", () => {
|
||||
test.skip("get mnemonic", async () => {
|
||||
const mnemonic = validator.randomMnemonic();
|
||||
const mnemonicCount = mnemonic.split(" ").length;
|
||||
|
||||
expect(mnemonicCount).toStrictEqual(24);
|
||||
});
|
||||
});
|
||||
@@ -5,11 +5,7 @@
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
]
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"typedocOptions": {
|
||||
"entryPoints": [
|
||||
@@ -20,11 +16,7 @@
|
||||
"exclude": [
|
||||
"dist",
|
||||
"examples",
|
||||
"node_modules"
|
||||
],
|
||||
"include": [
|
||||
"tests",
|
||||
"./tests/*/*.tsx",
|
||||
"./tests/*/*.ts"
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -39,8 +39,9 @@ flate2 = { version = "1.0.20", optional = true }
|
||||
sha2 = { version = "0.9.5", optional = true }
|
||||
itertools = { version = "0.10", optional = true }
|
||||
cosmwasm-std = { version = "1.0.0-beta3", optional = true }
|
||||
# Leaving it as * so that it inherits whatever the wallet is using
|
||||
ts-rs = { version = "*", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
[features]
|
||||
nymd-client = [
|
||||
@@ -54,4 +55,3 @@ nymd-client = [
|
||||
"itertools",
|
||||
"cosmwasm-std",
|
||||
]
|
||||
typescript-types = ["ts-rs", "validator-api-requests/ts-rs"]
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
use crate::{validator_api, ValidatorClientError};
|
||||
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
|
||||
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
|
||||
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, Interval, MixNodeBond};
|
||||
use network_defaults::DEFAULT_NETWORK;
|
||||
use url::Url;
|
||||
use validator_api_requests::models::{
|
||||
CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
|
||||
@@ -19,7 +20,7 @@ use mixnet_contract_common::ContractStateParams;
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use mixnet_contract_common::{
|
||||
Delegation, IdentityKey, Interval, MixnetContractVersion, MixnodeRewardingStatusResponse,
|
||||
Delegation, IdentityKey, MixnetContractVersion, MixnodeRewardingStatusResponse,
|
||||
RewardedSetNodeStatus, RewardedSetUpdateDetails,
|
||||
};
|
||||
#[cfg(feature = "nymd-client")]
|
||||
@@ -29,6 +30,7 @@ use std::str::FromStr;
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[must_use]
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
network: network_defaults::all::Network,
|
||||
api_url: Url,
|
||||
@@ -90,7 +92,7 @@ impl Config {
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
pub struct Client<C> {
|
||||
network: network_defaults::all::Network,
|
||||
pub network: network_defaults::all::Network,
|
||||
mixnet_contract_address: Option<cosmrs::AccountId>,
|
||||
vesting_contract_address: Option<cosmrs::AccountId>,
|
||||
erc20_bridge_contract_address: Option<cosmrs::AccountId>,
|
||||
@@ -159,12 +161,10 @@ impl Client<QueryNymdClient> {
|
||||
let nymd_client = NymdClient::connect(
|
||||
config.nymd_url.as_str(),
|
||||
Some(config.mixnet_contract_address.clone().unwrap_or_else(|| {
|
||||
cosmrs::AccountId::from_str(network_defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS)
|
||||
.unwrap()
|
||||
cosmrs::AccountId::from_str(DEFAULT_NETWORK.mixnet_contract_address()).unwrap()
|
||||
})),
|
||||
Some(config.vesting_contract_address.clone().unwrap_or_else(|| {
|
||||
cosmrs::AccountId::from_str(network_defaults::DEFAULT_VESTING_CONTRACT_ADDRESS)
|
||||
.unwrap()
|
||||
cosmrs::AccountId::from_str(DEFAULT_NETWORK.vesting_contract_address()).unwrap()
|
||||
})),
|
||||
Some(
|
||||
config
|
||||
@@ -172,7 +172,7 @@ impl Client<QueryNymdClient> {
|
||||
.clone()
|
||||
.unwrap_or_else(|| {
|
||||
cosmrs::AccountId::from_str(
|
||||
network_defaults::DEFAULT_BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
|
||||
DEFAULT_NETWORK.bandwidth_claim_contract_address(),
|
||||
)
|
||||
.unwrap()
|
||||
}),
|
||||
@@ -248,6 +248,35 @@ impl<C> Client<C> {
|
||||
Ok(self.nymd.get_contract_settings().await?)
|
||||
}
|
||||
|
||||
pub async fn get_operator_rewards(&self, address: String) -> Result<u128, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_operator_rewards(address).await?.u128())
|
||||
}
|
||||
|
||||
pub async fn get_delegator_rewards(
|
||||
&self,
|
||||
address: String,
|
||||
mix_identity: IdentityKey,
|
||||
) -> Result<u128, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self
|
||||
.nymd
|
||||
.get_delegator_rewards(address, mix_identity)
|
||||
.await?
|
||||
.u128())
|
||||
}
|
||||
|
||||
pub async fn get_current_epoch(&self) -> Result<Option<Interval>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_current_epoch().await?)
|
||||
}
|
||||
|
||||
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
@@ -276,13 +305,6 @@ impl<C> Client<C> {
|
||||
Ok(self.nymd.get_reward_pool().await?.u128())
|
||||
}
|
||||
|
||||
pub async fn get_current_interval(&self) -> Result<Interval, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_current_interval().await?)
|
||||
}
|
||||
|
||||
pub async fn get_circulating_supply(&self) -> Result<u128, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
@@ -304,6 +326,13 @@ impl<C> Client<C> {
|
||||
Ok(self.nymd.get_active_set_work_factor().await?)
|
||||
}
|
||||
|
||||
pub async fn get_epochs_in_interval(&self) -> Result<u64, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_epochs_in_interval().await?)
|
||||
}
|
||||
|
||||
pub async fn get_interval_reward_percent(&self) -> Result<u8, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
|
||||
@@ -21,7 +21,7 @@ where
|
||||
}
|
||||
|
||||
// maybe the wallet could be made into a generic, but for now, let's just have this one implementation
|
||||
pub fn connect_with_signer<U>(
|
||||
pub fn connect_with_signer<U: Clone>(
|
||||
endpoint: U,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
gas_price: GasPrice,
|
||||
|
||||
@@ -641,7 +641,7 @@ pub struct Client {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn connect_with_signer<U>(
|
||||
pub fn connect_with_signer<U: Clone>(
|
||||
endpoint: U,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
gas_price: GasPrice,
|
||||
|
||||
@@ -72,7 +72,7 @@ impl FromStr for GasPrice {
|
||||
}
|
||||
|
||||
impl GasPrice {
|
||||
pub fn new_with_default_price(denom: String) -> Result<Self, NymdError> {
|
||||
pub fn new_with_default_price(denom: &str) -> Result<Self, NymdError> {
|
||||
format!("{}{}", defaults::GAS_PRICE_AMOUNT, denom).parse()
|
||||
}
|
||||
}
|
||||
@@ -83,8 +83,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn default_gas_price_is_valid() {
|
||||
let denom = "unym".parse().unwrap();
|
||||
let _ = GasPrice::new_with_default_price(denom);
|
||||
let denom = "unym";
|
||||
GasPrice::new_with_default_price(denom).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -7,7 +7,11 @@ use cosmrs::Coin;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(export, export_to = "../../../nym-wallet/src/types/rust/operation.ts")
|
||||
)]
|
||||
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub enum Operation {
|
||||
Upload,
|
||||
@@ -43,9 +47,12 @@ pub enum Operation {
|
||||
CreatePeriodicVestingAccount,
|
||||
|
||||
AdvanceCurrentInterval,
|
||||
AdvanceCurrentEpoch,
|
||||
WriteRewardedSet,
|
||||
ClearRewardedSet,
|
||||
UpdateMixnetAddress,
|
||||
CheckpointMixnodes,
|
||||
ReconcileDelegations,
|
||||
}
|
||||
|
||||
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
|
||||
@@ -87,6 +94,9 @@ impl fmt::Display for Operation {
|
||||
Operation::WriteRewardedSet => f.write_str("WriteRewardedSet"),
|
||||
Operation::ClearRewardedSet => f.write_str("ClearRewardedSet"),
|
||||
Operation::UpdateMixnetAddress => f.write_str("UpdateMixnetAddress"),
|
||||
Operation::CheckpointMixnodes => f.write_str("CheckpointMixnodes"),
|
||||
Operation::ReconcileDelegations => f.write_str("ReconcileDelegations"),
|
||||
Operation::AdvanceCurrentEpoch => f.write_str("AdvanceCurrentEpoch"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,6 +138,9 @@ impl Operation {
|
||||
Operation::WriteRewardedSet => 175_000u64.into(),
|
||||
Operation::ClearRewardedSet => 175_000u64.into(),
|
||||
Operation::UpdateMixnetAddress => 80_000u64.into(),
|
||||
Operation::CheckpointMixnodes => 175_000u64.into(),
|
||||
Operation::ReconcileDelegations => 500_000u64.into(),
|
||||
Operation::AdvanceCurrentEpoch => 175_000u64.into(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ use crate::nymd::cosmwasm_client::types::{
|
||||
use crate::nymd::error::NymdError;
|
||||
use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
use cosmrs::rpc::endpoint::broadcast;
|
||||
use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
|
||||
use cosmrs::rpc::Error as TendermintRpcError;
|
||||
use cosmrs::rpc::HttpClientUrl;
|
||||
use cosmwasm_std::{Coin, Uint128};
|
||||
pub use fee::gas_price::GasPrice;
|
||||
use fee::helpers::Operation;
|
||||
@@ -82,7 +83,7 @@ impl NymdClient<QueryNymdClient> {
|
||||
|
||||
impl NymdClient<SigningNymdClient> {
|
||||
// maybe the wallet could be made into a generic, but for now, let's just have this one implementation
|
||||
pub fn connect_with_signer<U>(
|
||||
pub fn connect_with_signer<U: Clone>(
|
||||
network: config::defaults::all::Network,
|
||||
endpoint: U,
|
||||
mixnet_contract_address: Option<AccountId>,
|
||||
@@ -113,7 +114,7 @@ impl NymdClient<SigningNymdClient> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn connect_with_mnemonic<U>(
|
||||
pub fn connect_with_mnemonic<U: Clone>(
|
||||
network: config::defaults::all::Network,
|
||||
endpoint: U,
|
||||
mixnet_contract_address: Option<AccountId>,
|
||||
@@ -289,6 +290,43 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_operator_rewards(&self, address: String) -> Result<Uint128, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::QueryOperatorReward { address };
|
||||
self.client
|
||||
.query_contract_smart(self.mixnet_contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_delegator_rewards(
|
||||
&self,
|
||||
address: String,
|
||||
mix_identity: IdentityKey,
|
||||
) -> Result<Uint128, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::QueryDelegatorReward {
|
||||
address,
|
||||
mix_identity,
|
||||
};
|
||||
self.client
|
||||
.query_contract_smart(self.mixnet_contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_current_epoch(&self) -> Result<Option<Interval>, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetCurrentEpoch {};
|
||||
self.client
|
||||
.query_contract_smart(self.mixnet_contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
@@ -368,16 +406,6 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_current_interval(&self) -> Result<Interval, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetCurrentInterval {};
|
||||
self.client
|
||||
.query_contract_smart(self.mixnet_contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_reward_pool(&self) -> Result<Uint128, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
@@ -428,6 +456,16 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_epochs_in_interval(&self) -> Result<u64, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetEpochsInInterval {};
|
||||
self.client
|
||||
.query_contract_smart(self.mixnet_contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Checks whether there is a bonded mixnode associated with the provided client's address
|
||||
pub async fn owns_mixnode(&self, address: &AccountId) -> Result<Option<MixNodeBond>, NymdError>
|
||||
where
|
||||
@@ -496,7 +534,7 @@ impl<C> NymdClient<C> {
|
||||
pub async fn get_mix_delegations_paged(
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
start_after: Option<String>,
|
||||
start_after: Option<(String, u64)>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedMixDelegationsResponse, NymdError>
|
||||
where
|
||||
@@ -515,7 +553,7 @@ impl<C> NymdClient<C> {
|
||||
/// Gets list of all mixnode delegations on particular page.
|
||||
pub async fn get_all_network_delegations_paged(
|
||||
&self,
|
||||
start_after: Option<(IdentityKey, String)>,
|
||||
start_after: Option<(IdentityKey, Vec<u8>, u64)>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedAllDelegationsResponse, NymdError>
|
||||
where
|
||||
@@ -1175,20 +1213,58 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn advance_current_interval(&self) -> Result<ExecuteResult, NymdError>
|
||||
pub async fn advance_current_epoch(&self) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = self.operation_fee(Operation::AdvanceCurrentInterval);
|
||||
let fee = self.operation_fee(Operation::AdvanceCurrentEpoch);
|
||||
|
||||
let req = ExecuteMsg::AdvanceCurrentInterval {};
|
||||
let req = ExecuteMsg::AdvanceCurrentEpoch {};
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.mixnet_contract_address()?,
|
||||
&req,
|
||||
fee,
|
||||
"Advancing current interval",
|
||||
"Advance current epoch",
|
||||
Vec::new(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn reconcile_delegations(&self) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = self.operation_fee(Operation::ReconcileDelegations);
|
||||
|
||||
let req = ExecuteMsg::ReconcileDelegations {};
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.mixnet_contract_address()?,
|
||||
&req,
|
||||
fee,
|
||||
"Reconciling delegation events",
|
||||
Vec::new(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn checkpoint_mixnodes(&self) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = self.operation_fee(Operation::CheckpointMixnodes);
|
||||
|
||||
let req = ExecuteMsg::CheckpointMixnodes {};
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.mixnet_contract_address()?,
|
||||
&req,
|
||||
fee,
|
||||
"Snapshotting mixnodes",
|
||||
Vec::new(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -57,16 +57,16 @@ pub struct DirectSecp256k1HdWallet {
|
||||
}
|
||||
|
||||
impl DirectSecp256k1HdWallet {
|
||||
pub fn builder(prefix: String) -> DirectSecp256k1HdWalletBuilder {
|
||||
pub fn builder(prefix: &str) -> DirectSecp256k1HdWalletBuilder {
|
||||
DirectSecp256k1HdWalletBuilder::new(prefix)
|
||||
}
|
||||
|
||||
/// Restores a wallet from the given BIP39 mnemonic using default options.
|
||||
pub fn from_mnemonic(prefix: String, mnemonic: bip39::Mnemonic) -> Result<Self, NymdError> {
|
||||
pub fn from_mnemonic(prefix: &str, mnemonic: bip39::Mnemonic) -> Result<Self, NymdError> {
|
||||
DirectSecp256k1HdWalletBuilder::new(prefix).build(mnemonic)
|
||||
}
|
||||
|
||||
pub fn generate(prefix: String, word_count: usize) -> Result<Self, NymdError> {
|
||||
pub fn generate(prefix: &str, word_count: usize) -> Result<Self, NymdError> {
|
||||
let mneomonic = bip39::Mnemonic::generate(word_count)?;
|
||||
Self::from_mnemonic(prefix, mneomonic)
|
||||
}
|
||||
@@ -146,11 +146,11 @@ pub struct DirectSecp256k1HdWalletBuilder {
|
||||
}
|
||||
|
||||
impl DirectSecp256k1HdWalletBuilder {
|
||||
pub fn new(prefix: String) -> Self {
|
||||
pub fn new(prefix: &str) -> Self {
|
||||
DirectSecp256k1HdWalletBuilder {
|
||||
bip39_password: String::new(),
|
||||
hd_paths: vec![defaults::COSMOS_DERIVATION_PATH.parse().unwrap()],
|
||||
prefix,
|
||||
prefix: prefix.into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,11 +197,11 @@ impl DirectSecp256k1HdWalletBuilder {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use network_defaults::{default_network, BECH32_PREFIX};
|
||||
use network_defaults::DEFAULT_NETWORK;
|
||||
|
||||
#[test]
|
||||
fn generating_account_addresses() {
|
||||
let (addr1, addr2, addr3) = match BECH32_PREFIX {
|
||||
let (addr1, addr2, addr3) = match DEFAULT_NETWORK.bech32_prefix() {
|
||||
"punk" => (
|
||||
"punk1jw6mp7d5xqc7w6xm79lha27glmd0vdt32a3fj2",
|
||||
"punk1h5hgn94nsq4kh99rjj794hr5h5q6yfm22mcqqn",
|
||||
@@ -222,7 +222,7 @@ mod tests {
|
||||
];
|
||||
|
||||
for (mnemonic, address) in mnemonic_address.into_iter() {
|
||||
let prefix = default_network().bech32_prefix();
|
||||
let prefix = DEFAULT_NETWORK.bech32_prefix();
|
||||
let wallet =
|
||||
DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.parse().unwrap()).unwrap();
|
||||
assert_eq!(
|
||||
|
||||
@@ -83,8 +83,6 @@ impl VerifyCredentialBody {
|
||||
pub struct BlindSignRequestBody {
|
||||
#[getset(get = "pub")]
|
||||
blind_sign_request: BlindSignRequest,
|
||||
#[getset(get = "pub")]
|
||||
public_key: nymcoconut::PublicKey,
|
||||
public_attributes: Vec<String>,
|
||||
#[getset(get = "pub")]
|
||||
total_params: u32,
|
||||
@@ -93,13 +91,11 @@ pub struct BlindSignRequestBody {
|
||||
impl BlindSignRequestBody {
|
||||
pub fn new(
|
||||
blind_sign_request: &BlindSignRequest,
|
||||
public_key: &nymcoconut::PublicKey,
|
||||
public_attributes: &[Attribute],
|
||||
total_params: u32,
|
||||
) -> BlindSignRequestBody {
|
||||
BlindSignRequestBody {
|
||||
blind_sign_request: blind_sign_request.clone(),
|
||||
public_key: public_key.clone(),
|
||||
public_attributes: public_attributes
|
||||
.iter()
|
||||
.map(|attr| attr.to_bs58())
|
||||
|
||||
@@ -12,8 +12,6 @@ cosmwasm-std = "1.0.0-beta3"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_repr = "0.1"
|
||||
schemars = "0.8"
|
||||
# Leaving it as * so that it inherits whatever the wallet is using
|
||||
ts-rs = { version = "*", optional = true }
|
||||
thiserror = "1.0"
|
||||
network-defaults = { path = "../../network-defaults" }
|
||||
fixed = { version = "1.1", features = ["serde"] }
|
||||
@@ -25,7 +23,7 @@ contracts-common = { path = "../contracts-common" }
|
||||
|
||||
[dev-dependencies]
|
||||
time = { version = "0.3.5", features = ["serde", "macros"] }
|
||||
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -37,8 +37,12 @@ impl Delegation {
|
||||
}
|
||||
|
||||
// TODO: change that to use .joined_key() and return Vec<u8>
|
||||
pub fn storage_key(&self) -> (IdentityKey, Addr) {
|
||||
(self.node_identity(), self.owner())
|
||||
pub fn storage_key(&self) -> (IdentityKey, Vec<u8>, u64) {
|
||||
(
|
||||
self.node_identity(),
|
||||
self.owner().as_bytes().to_vec(),
|
||||
self.block_height(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn increment_amount(&mut self, amount: Uint128, at_height: Option<u64>) {
|
||||
@@ -78,14 +82,14 @@ impl Display for Delegation {
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedMixDelegationsResponse {
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<String>,
|
||||
pub start_next_after: Option<(String, u64)>,
|
||||
}
|
||||
|
||||
impl PagedMixDelegationsResponse {
|
||||
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<Addr>) -> Self {
|
||||
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<(Addr, u64)>) -> Self {
|
||||
PagedMixDelegationsResponse {
|
||||
delegations,
|
||||
start_next_after: start_next_after.map(|s| s.to_string()),
|
||||
start_next_after: start_next_after.map(|(s, h)| (s.to_string(), h)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,17 +112,17 @@ impl PagedDelegatorDelegationsResponse {
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedAllDelegationsResponse {
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<(IdentityKey, String)>,
|
||||
pub start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
|
||||
}
|
||||
|
||||
impl PagedAllDelegationsResponse {
|
||||
pub fn new(
|
||||
delegations: Vec<Delegation>,
|
||||
start_next_after: Option<(IdentityKey, Addr)>,
|
||||
start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
|
||||
) -> Self {
|
||||
PagedAllDelegationsResponse {
|
||||
delegations,
|
||||
start_next_after: start_next_after.map(|(id, addr)| (id, addr.to_string())),
|
||||
start_next_after: start_next_after.map(|(id, addr, height)| (id, addr, height)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,4 +6,11 @@ pub enum MixnetContractError {
|
||||
OverflowError(#[from] cosmwasm_std::OverflowError),
|
||||
#[error("reward_blockstamp field not set, set_reward_blockstamp must be called before attempting to issue rewards")]
|
||||
BlockstampNotSet,
|
||||
#[error("{source}")]
|
||||
TryFromIntError {
|
||||
#[from]
|
||||
source: std::num::TryFromIntError,
|
||||
},
|
||||
#[error("Error casting from U128")]
|
||||
CastError,
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::mixnode::NodeRewardResult;
|
||||
use crate::{ContractStateParams, Delegation, IdentityKeyRef, Interval, Layer};
|
||||
use crate::{ContractStateParams, IdentityKeyRef, Interval, Layer};
|
||||
use cosmwasm_std::{Addr, Coin, Event, Uint128};
|
||||
|
||||
pub use contracts_common::events::*;
|
||||
|
||||
// FIXME: This should becoma an Enum
|
||||
// event types
|
||||
pub const DELEGATION_EVENT_TYPE: &str = "delegation";
|
||||
pub const PENDING_DELEGATION_EVENT_TYPE: &str = "pending_delegation";
|
||||
pub const RECONCILE_DELEGATION_EVENT_TYPE: &str = "reconcile_delegation";
|
||||
pub const UNDELEGATION_EVENT_TYPE: &str = "undelegation";
|
||||
pub const PENDING_UNDELEGATION_EVENT_TYPE: &str = "pending_undelegation";
|
||||
pub const GATEWAY_BONDING_EVENT_TYPE: &str = "gateway_bonding";
|
||||
pub const GATEWAY_UNBONDING_EVENT_TYPE: &str = "gateway_unbonding";
|
||||
pub const MIXNODE_BONDING_EVENT_TYPE: &str = "mixnode_bonding";
|
||||
@@ -19,6 +21,10 @@ pub const OPERATOR_REWARDING_EVENT_TYPE: &str = "mix_rewarding";
|
||||
pub const MIX_DELEGATORS_REWARDING_EVENT_TYPE: &str = "mix_delegators_rewarding";
|
||||
pub const CHANGE_REWARDED_SET_EVENT_TYPE: &str = "change_rewarded_set";
|
||||
pub const ADVANCE_INTERVAL_EVENT_TYPE: &str = "advance_interval";
|
||||
pub const ADVANCE_EPOCH_EVENT_TYPE: &str = "advance_epoch";
|
||||
pub const COMPOUND_DELEGATOR_REWARD_EVENT_TYPE: &str = "compound_delegator_reward";
|
||||
pub const COMPOUND_OPERATOR_REWARD_EVENT_TYPE: &str = "compound_operator_reward";
|
||||
pub const SNAPSHOT_MIXNODES_EVENT: &str = "snapshot_mixnodes";
|
||||
|
||||
// attributes that are used in multiple places
|
||||
pub const OWNER_KEY: &str = "owner";
|
||||
@@ -70,6 +76,19 @@ pub const NODES_IN_REWARDED_SET_KEY: &str = "nodes_in_rewarded_set";
|
||||
pub const CURRENT_INTERVAL_ID_KEY: &str = "current_interval";
|
||||
|
||||
pub const NEW_CURRENT_INTERVAL_KEY: &str = "new_current_interval";
|
||||
pub const NEW_CURRENT_EPOCH_KEY: &str = "new_current_epoch";
|
||||
pub const BLOCK_HEIGHT_KEY: &str = "block_height";
|
||||
pub const CHECKPOINT_MIXNODES_EVENT: &str = "checkpoint_mixnodes";
|
||||
pub const RECONCILIATION_ERROR_EVENT: &str = "reconciliation_error";
|
||||
|
||||
pub fn new_checkpoint_mixnodes_event(block_height: u64) -> Event {
|
||||
Event::new(CHECKPOINT_MIXNODES_EVENT)
|
||||
.add_attribute(BLOCK_HEIGHT_KEY, format!("{}", block_height))
|
||||
}
|
||||
|
||||
pub fn new_error_event(err: String) -> Event {
|
||||
Event::new(RECONCILIATION_ERROR_EVENT).add_attribute("error", err)
|
||||
}
|
||||
|
||||
pub fn new_delegation_event(
|
||||
delegator: &Addr,
|
||||
@@ -89,11 +108,74 @@ pub fn new_delegation_event(
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
|
||||
}
|
||||
|
||||
pub fn new_pending_delegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
mix_identity: IdentityKeyRef<'_>,
|
||||
) -> Event {
|
||||
let mut event =
|
||||
Event::new(PENDING_DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
|
||||
|
||||
if let Some(proxy) = proxy {
|
||||
event = event.add_attribute(PROXY_KEY, proxy)
|
||||
}
|
||||
|
||||
// coin implements Display trait and we use that implementation here
|
||||
event
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
|
||||
}
|
||||
|
||||
pub fn new_reconcile_delegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
mix_identity: IdentityKeyRef<'_>,
|
||||
) -> Event {
|
||||
let mut event =
|
||||
Event::new(RECONCILE_DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
|
||||
|
||||
if let Some(proxy) = proxy {
|
||||
event = event.add_attribute(PROXY_KEY, proxy)
|
||||
}
|
||||
|
||||
// coin implements Display trait and we use that implementation here
|
||||
event
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
|
||||
}
|
||||
|
||||
pub fn new_compound_operator_reward_event(owner: &Addr, amount: Uint128) -> Event {
|
||||
let event = Event::new(COMPOUND_OPERATOR_REWARD_EVENT_TYPE).add_attribute(OWNER_KEY, owner);
|
||||
event.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
}
|
||||
|
||||
pub fn new_compound_delegator_reward_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: Uint128,
|
||||
mix_identity: IdentityKeyRef<'_>,
|
||||
) -> Event {
|
||||
let mut event =
|
||||
Event::new(COMPOUND_DELEGATOR_REWARD_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
|
||||
|
||||
if let Some(proxy) = proxy {
|
||||
event = event.add_attribute(PROXY_KEY, proxy)
|
||||
}
|
||||
|
||||
// coin implements Display trait and we use that implementation here
|
||||
event
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
}
|
||||
|
||||
pub fn new_undelegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
old_delegation: &Delegation,
|
||||
mix_identity: IdentityKeyRef<'_>,
|
||||
amount: Uint128,
|
||||
) -> Event {
|
||||
let mut event = Event::new(UNDELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
|
||||
|
||||
@@ -103,14 +185,26 @@ pub fn new_undelegation_event(
|
||||
|
||||
// coin implements Display trait and we use that implementation here
|
||||
event
|
||||
.add_attribute(AMOUNT_KEY, old_delegation.amount.to_string())
|
||||
.add_attribute(
|
||||
DELEGATION_HEIGHT_KEY,
|
||||
old_delegation.block_height.to_string(),
|
||||
)
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
|
||||
}
|
||||
|
||||
pub fn new_pending_undelegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
mix_identity: IdentityKeyRef<'_>,
|
||||
) -> Event {
|
||||
let mut event =
|
||||
Event::new(PENDING_UNDELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
|
||||
|
||||
if let Some(proxy) = proxy {
|
||||
event = event.add_attribute(PROXY_KEY, proxy)
|
||||
}
|
||||
|
||||
// coin implements Display trait and we use that implementation here
|
||||
event.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
|
||||
}
|
||||
|
||||
pub fn new_gateway_bonding_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
@@ -280,9 +374,6 @@ pub fn new_mix_operator_rewarding_event(
|
||||
node_reward_result: NodeRewardResult,
|
||||
node_pledge: Uint128,
|
||||
node_delegation: Uint128,
|
||||
operator_reward: Uint128,
|
||||
delegation_rewards_distributed: Uint128,
|
||||
further_delegations: bool,
|
||||
) -> Event {
|
||||
Event::new(OPERATOR_REWARDING_EVENT_TYPE)
|
||||
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
|
||||
@@ -295,15 +386,6 @@ pub fn new_mix_operator_rewarding_event(
|
||||
)
|
||||
.add_attribute(LAMBDA_KEY, node_reward_result.lambda().to_string())
|
||||
.add_attribute(SIGMA_KEY, node_reward_result.sigma().to_string())
|
||||
.add_attribute(OPERATOR_REWARD_KEY, operator_reward)
|
||||
.add_attribute(
|
||||
DISTRIBUTED_DELEGATION_REWARDS_KEY,
|
||||
delegation_rewards_distributed,
|
||||
)
|
||||
.add_attribute(
|
||||
FURTHER_DELEGATIONS_TO_REWARD_KEY,
|
||||
further_delegations.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_mix_delegators_rewarding_event(
|
||||
@@ -330,16 +412,18 @@ pub fn new_change_rewarded_set_event(
|
||||
active_set_size: u32,
|
||||
rewarded_set_size: u32,
|
||||
nodes_in_rewarded_set: u32,
|
||||
current_interval_id: u32,
|
||||
) -> Event {
|
||||
Event::new(CHANGE_REWARDED_SET_EVENT_TYPE)
|
||||
.add_attribute(ACTIVE_SET_SIZE_KEY, active_set_size.to_string())
|
||||
.add_attribute(REWARDED_SET_SIZE_KEY, rewarded_set_size.to_string())
|
||||
.add_attribute(NODES_IN_REWARDED_SET_KEY, nodes_in_rewarded_set.to_string())
|
||||
.add_attribute(CURRENT_INTERVAL_ID_KEY, current_interval_id.to_string())
|
||||
}
|
||||
|
||||
pub fn new_advance_interval_event(interval: Interval) -> Event {
|
||||
Event::new(ADVANCE_INTERVAL_EVENT_TYPE)
|
||||
.add_attribute(NEW_CURRENT_INTERVAL_KEY, interval.to_string())
|
||||
}
|
||||
|
||||
pub fn new_advance_epoch_event(interval: Interval) -> Event {
|
||||
Event::new(ADVANCE_EPOCH_EVENT_TYPE).add_attribute(NEW_CURRENT_EPOCH_KEY, interval.to_string())
|
||||
}
|
||||
|
||||
@@ -8,7 +8,11 @@ use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(export, export_to = "../../../nym-wallet/src/types/rust/gateway.ts")
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
|
||||
pub struct Gateway {
|
||||
pub host: String,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::Env;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::{Display, Formatter};
|
||||
@@ -65,14 +66,28 @@ pub struct Interval {
|
||||
}
|
||||
|
||||
impl Interval {
|
||||
/// Creates new interval instance.
|
||||
pub const fn new(id: u32, start: OffsetDateTime, length: Duration) -> Self {
|
||||
Interval { id, start, length }
|
||||
/// Initialize epoch in the contract with default values.
|
||||
/// FIXME: THIS unwrap!
|
||||
pub fn init_epoch(env: Env) -> Self {
|
||||
Interval {
|
||||
id: 0,
|
||||
start: OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap(),
|
||||
length: Duration::from_secs(3600),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_over(&self, env: Env) -> bool {
|
||||
self.end_unix_timestamp() <= env.block.time.seconds() as i64
|
||||
}
|
||||
|
||||
pub fn in_progress(&self, env: Env) -> bool {
|
||||
let block_time = env.block.time.seconds() as i64;
|
||||
self.start_unix_timestamp() <= block_time && block_time < self.end_unix_timestamp()
|
||||
}
|
||||
|
||||
/// Returns the next interval.
|
||||
#[must_use]
|
||||
pub fn next_interval(&self) -> Self {
|
||||
pub fn next(&self) -> Self {
|
||||
Interval {
|
||||
id: self.id + 1,
|
||||
start: self.end(),
|
||||
@@ -80,8 +95,19 @@ impl Interval {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_on_chain(&self, env: Env) -> Self {
|
||||
let start = self
|
||||
.end()
|
||||
.max(OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap());
|
||||
Interval {
|
||||
id: self.id + 1,
|
||||
start,
|
||||
length: self.length,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the last interval.
|
||||
pub fn previous_interval(&self) -> Option<Self> {
|
||||
pub fn previous(&self) -> Option<Self> {
|
||||
if self.id > 0 {
|
||||
Some(Interval {
|
||||
id: self.id - 1,
|
||||
@@ -125,14 +151,14 @@ impl Interval {
|
||||
if candidate.contains(now) {
|
||||
return Some(candidate);
|
||||
}
|
||||
candidate = candidate.next_interval();
|
||||
candidate = candidate.next();
|
||||
}
|
||||
} else {
|
||||
loop {
|
||||
if candidate.contains(now) {
|
||||
return Some(candidate);
|
||||
}
|
||||
candidate = candidate.previous_interval()?;
|
||||
candidate = candidate.previous()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,14 +177,14 @@ impl Interval {
|
||||
if candidate.contains_timestamp(now_unix) {
|
||||
return Some(candidate);
|
||||
}
|
||||
candidate = candidate.next_interval();
|
||||
candidate = candidate.next();
|
||||
}
|
||||
} else {
|
||||
loop {
|
||||
if candidate.contains_timestamp(now_unix) {
|
||||
return Some(candidate);
|
||||
}
|
||||
candidate = candidate.previous_interval()?;
|
||||
candidate = candidate.previous()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,7 +265,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn previous_interval() {
|
||||
fn previous() {
|
||||
let interval = Interval {
|
||||
id: 1,
|
||||
start: time::macros::datetime!(2021-08-23 12:00 UTC),
|
||||
@@ -250,18 +276,18 @@ mod tests {
|
||||
start: time::macros::datetime!(2021-08-22 12:00 UTC),
|
||||
length: Duration::from_secs(24 * 60 * 60),
|
||||
};
|
||||
assert_eq!(expected, interval.previous_interval().unwrap());
|
||||
assert_eq!(expected, interval.previous().unwrap());
|
||||
|
||||
let genesis_interval = Interval {
|
||||
id: 0,
|
||||
start: time::macros::datetime!(2021-08-23 12:00 UTC),
|
||||
length: Duration::from_secs(24 * 60 * 60),
|
||||
};
|
||||
assert!(genesis_interval.previous_interval().is_none());
|
||||
assert!(genesis_interval.previous().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_interval() {
|
||||
fn next() {
|
||||
let interval = Interval {
|
||||
id: 0,
|
||||
start: time::macros::datetime!(2021-08-23 12:00 UTC),
|
||||
@@ -273,7 +299,7 @@ mod tests {
|
||||
length: Duration::from_secs(24 * 60 * 60),
|
||||
};
|
||||
|
||||
assert_eq!(expected, interval.next_interval())
|
||||
assert_eq!(expected, interval.next())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -291,8 +317,8 @@ mod tests {
|
||||
let in_the_midle = interval.start + Duration::from_secs(interval.length.as_secs() / 2);
|
||||
assert!(interval.contains(in_the_midle));
|
||||
|
||||
assert!(!interval.contains(interval.next_interval().end()));
|
||||
assert!(!interval.contains(interval.previous_interval().unwrap().start()));
|
||||
assert!(!interval.contains(interval.next().end()));
|
||||
assert!(!interval.contains(interval.previous().unwrap().start()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -305,10 +331,7 @@ mod tests {
|
||||
|
||||
// interval just before
|
||||
let fake_now = first_interval.start - Duration::from_secs(123);
|
||||
assert_eq!(
|
||||
first_interval.previous_interval(),
|
||||
first_interval.current(fake_now)
|
||||
);
|
||||
assert_eq!(first_interval.previous(), first_interval.current(fake_now));
|
||||
|
||||
// this interval (start boundary)
|
||||
assert_eq!(
|
||||
@@ -329,7 +352,7 @@ mod tests {
|
||||
// next interval
|
||||
let fake_now = first_interval.end() + Duration::from_secs(123);
|
||||
assert_eq!(
|
||||
first_interval.next_interval(),
|
||||
first_interval.next(),
|
||||
first_interval.current(fake_now).unwrap()
|
||||
);
|
||||
|
||||
@@ -340,11 +363,11 @@ mod tests {
|
||||
- first_interval.length;
|
||||
assert_eq!(
|
||||
first_interval
|
||||
.previous_interval()
|
||||
.previous()
|
||||
.unwrap()
|
||||
.previous_interval()
|
||||
.previous()
|
||||
.unwrap()
|
||||
.previous_interval()
|
||||
.previous()
|
||||
.unwrap(),
|
||||
first_interval.current(fake_now).unwrap()
|
||||
);
|
||||
@@ -355,10 +378,7 @@ mod tests {
|
||||
+ first_interval.length
|
||||
+ first_interval.length;
|
||||
assert_eq!(
|
||||
first_interval
|
||||
.next_interval()
|
||||
.next_interval()
|
||||
.next_interval(),
|
||||
first_interval.next().next().next(),
|
||||
first_interval.current(fake_now).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ mod gateway;
|
||||
mod interval;
|
||||
pub mod mixnode;
|
||||
mod msg;
|
||||
pub mod reward_params;
|
||||
mod types;
|
||||
|
||||
pub const MIXNODE_DELEGATORS_PAGE_LIMIT: usize = 250;
|
||||
@@ -24,3 +25,9 @@ pub use mixnode::{
|
||||
};
|
||||
pub use msg::*;
|
||||
pub use types::*;
|
||||
|
||||
pub type U128 = fixed::types::U75F53;
|
||||
|
||||
fixed::const_fixed_from_int! {
|
||||
const ONE: U128 = 1;
|
||||
}
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
// due to code generated by JsonSchema
|
||||
#![allow(clippy::field_reassign_with_default)]
|
||||
|
||||
use crate::{IdentityKey, SphinxKey};
|
||||
use crate::error::MixnetContractError;
|
||||
use crate::reward_params::RewardParams;
|
||||
use crate::{Delegation, IdentityKey, SphinxKey};
|
||||
use crate::{ONE, U128};
|
||||
use az::CheckedCast;
|
||||
use cosmwasm_std::{coin, Addr, Coin, Uint128};
|
||||
use log::error;
|
||||
use network_defaults::DEFAULT_OPERATOR_INTERVAL_COST;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
|
||||
type U128 = fixed::types::U75F53; // u128 with 18 significant digits
|
||||
|
||||
fixed::const_fixed_from_int! {
|
||||
const ONE: U128 = 1;
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(
|
||||
export,
|
||||
export_to = "../../../nym-wallet/src/types/rust/rewardedsetnodestatus.ts"
|
||||
)
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
|
||||
pub enum RewardedSetNodeStatus {
|
||||
Active,
|
||||
Standby,
|
||||
@@ -31,7 +34,57 @@ impl RewardedSetNodeStatus {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub enum DelegationEvent {
|
||||
Delegate(Delegation),
|
||||
Undelegate(PendingUndelegate),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct PendingUndelegate {
|
||||
mix_identity: IdentityKey,
|
||||
delegate: Addr,
|
||||
proxy: Option<Addr>,
|
||||
block_height: u64,
|
||||
}
|
||||
|
||||
impl PendingUndelegate {
|
||||
pub fn new(
|
||||
mix_identity: IdentityKey,
|
||||
delegate: Addr,
|
||||
proxy: Option<Addr>,
|
||||
block_height: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
mix_identity,
|
||||
delegate,
|
||||
proxy,
|
||||
block_height,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mix_identity(&self) -> IdentityKey {
|
||||
self.mix_identity.clone()
|
||||
}
|
||||
|
||||
pub fn delegate(&self) -> Addr {
|
||||
self.delegate.clone()
|
||||
}
|
||||
|
||||
pub fn proxy(&self) -> Option<Addr> {
|
||||
self.proxy.clone()
|
||||
}
|
||||
|
||||
pub fn block_height(&self) -> u64 {
|
||||
self.block_height
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(export, export_to = "../../../nym-wallet/src/types/rust/mixnode.ts")
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
|
||||
pub struct MixNode {
|
||||
pub host: String,
|
||||
@@ -76,112 +129,6 @@ impl From<Layer> for String {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct NodeRewardParams {
|
||||
period_reward_pool: Uint128,
|
||||
rewarded_set_size: Uint128,
|
||||
active_set_size: Uint128,
|
||||
reward_blockstamp: u64,
|
||||
circulating_supply: Uint128,
|
||||
uptime: Uint128,
|
||||
sybil_resistance_percent: u8,
|
||||
in_active_set: bool,
|
||||
active_set_work_factor: u8,
|
||||
}
|
||||
|
||||
impl NodeRewardParams {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
period_reward_pool: u128,
|
||||
rewarded_set_size: u128,
|
||||
active_set_size: u128,
|
||||
reward_blockstamp: u64,
|
||||
circulating_supply: u128,
|
||||
uptime: u128,
|
||||
sybil_resistance_percent: u8,
|
||||
in_active_set: bool,
|
||||
active_set_work_factor: u8,
|
||||
) -> NodeRewardParams {
|
||||
NodeRewardParams {
|
||||
period_reward_pool: Uint128::new(period_reward_pool),
|
||||
rewarded_set_size: Uint128::new(rewarded_set_size),
|
||||
active_set_size: Uint128::new(active_set_size),
|
||||
reward_blockstamp,
|
||||
circulating_supply: Uint128::new(circulating_supply),
|
||||
uptime: Uint128::new(uptime),
|
||||
sybil_resistance_percent,
|
||||
in_active_set,
|
||||
active_set_work_factor,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn omega(&self) -> U128 {
|
||||
// As per keybase://chat/nymtech#tokeneconomics/1179
|
||||
let denom = self.active_set_work_factor() * U128::from_num(self.rewarded_set_size())
|
||||
- (self.active_set_work_factor() - ONE) * U128::from_num(self.idle_nodes().u128());
|
||||
|
||||
if self.in_active_set() {
|
||||
// work_active = factor / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
|
||||
self.active_set_work_factor() / denom * self.rewarded_set_size()
|
||||
} else {
|
||||
// work_idle = 1 / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
|
||||
ONE / denom * self.rewarded_set_size()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn idle_nodes(&self) -> Uint128 {
|
||||
self.rewarded_set_size - self.active_set_size
|
||||
}
|
||||
|
||||
pub fn active_set_work_factor(&self) -> U128 {
|
||||
U128::from_num(self.active_set_work_factor)
|
||||
}
|
||||
|
||||
pub fn in_active_set(&self) -> bool {
|
||||
self.in_active_set
|
||||
}
|
||||
|
||||
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_INTERVAL_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 rewarded_set_size(&self) -> u128 {
|
||||
self.rewarded_set_size.u128()
|
||||
}
|
||||
|
||||
pub fn circulating_supply(&self) -> u128 {
|
||||
self.circulating_supply.u128()
|
||||
}
|
||||
|
||||
pub fn reward_blockstamp(&self) -> u64 {
|
||||
self.reward_blockstamp
|
||||
}
|
||||
|
||||
pub fn uptime(&self) -> u128 {
|
||||
self.uptime.u128()
|
||||
}
|
||||
|
||||
pub fn one_over_k(&self) -> U128 {
|
||||
ONE / U128::from_num(self.rewarded_set_size.u128())
|
||||
}
|
||||
|
||||
pub fn alpha(&self) -> U128 {
|
||||
U128::from_num(self.sybil_resistance_percent) / U128::from_num(100)
|
||||
}
|
||||
}
|
||||
|
||||
// cosmwasm's limited serde doesn't work with U128 directly
|
||||
#[allow(non_snake_case)]
|
||||
pub mod fixed_U128_as_string {
|
||||
@@ -215,7 +162,7 @@ pub mod fixed_U128_as_string {
|
||||
// everything required to reward delegator of given mixnode
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DelegatorRewardParams {
|
||||
node_reward_params: NodeRewardParams,
|
||||
reward_params: RewardParams,
|
||||
|
||||
// to be completely honest I don't understand all consequences of using `#[schemars(with = "String")]`
|
||||
// for U128 here, but it seems that CosmWasm is using the same attribute for their Uint128
|
||||
@@ -231,19 +178,24 @@ pub struct DelegatorRewardParams {
|
||||
}
|
||||
|
||||
impl DelegatorRewardParams {
|
||||
pub fn new(mixnode_bond: &MixNodeBond, node_reward_params: NodeRewardParams) -> Self {
|
||||
pub fn new(
|
||||
sigma: U128,
|
||||
profit_margin: U128,
|
||||
node_profit: U128,
|
||||
reward_params: RewardParams,
|
||||
) -> Self {
|
||||
DelegatorRewardParams {
|
||||
sigma: mixnode_bond.sigma(&node_reward_params),
|
||||
profit_margin: mixnode_bond.profit_margin(),
|
||||
node_profit: mixnode_bond.node_profit(&node_reward_params),
|
||||
node_reward_params,
|
||||
sigma,
|
||||
profit_margin,
|
||||
node_profit,
|
||||
reward_params,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn determine_delegation_reward(&self, delegation_amount: Uint128) -> u128 {
|
||||
// change all values into their fixed representations
|
||||
let delegation_amount = U128::from_num(delegation_amount.u128());
|
||||
let circulating_supply = U128::from_num(self.node_reward_params.circulating_supply());
|
||||
let circulating_supply = U128::from_num(self.reward_params.circulating_supply());
|
||||
|
||||
let scaled_delegation_amount = delegation_amount / circulating_supply;
|
||||
let delegator_reward =
|
||||
@@ -261,8 +213,56 @@ impl DelegatorRewardParams {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node_reward_params(&self) -> &NodeRewardParams {
|
||||
&self.node_reward_params
|
||||
pub fn node_reward_params(&self) -> RewardParams {
|
||||
self.reward_params
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct StoredNodeRewardResult {
|
||||
reward: Uint128,
|
||||
lambda: Uint128,
|
||||
sigma: Uint128,
|
||||
}
|
||||
|
||||
impl StoredNodeRewardResult {
|
||||
pub fn reward(&self) -> Uint128 {
|
||||
self.reward
|
||||
}
|
||||
|
||||
pub fn lambda(&self) -> Uint128 {
|
||||
self.lambda
|
||||
}
|
||||
|
||||
pub fn sigma(&self) -> Uint128 {
|
||||
self.sigma
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NodeRewardResult> for StoredNodeRewardResult {
|
||||
type Error = MixnetContractError;
|
||||
|
||||
fn try_from(node_reward_result: NodeRewardResult) -> Result<Self, Self::Error> {
|
||||
Ok(StoredNodeRewardResult {
|
||||
reward: Uint128::new(
|
||||
node_reward_result
|
||||
.reward()
|
||||
.checked_cast()
|
||||
.ok_or(MixnetContractError::CastError)?,
|
||||
),
|
||||
lambda: Uint128::new(
|
||||
node_reward_result
|
||||
.lambda()
|
||||
.checked_cast()
|
||||
.ok_or(MixnetContractError::CastError)?,
|
||||
),
|
||||
sigma: Uint128::new(
|
||||
node_reward_result
|
||||
.sigma()
|
||||
.checked_cast()
|
||||
.ok_or(MixnetContractError::CastError)?,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,6 +296,7 @@ pub struct MixNodeBond {
|
||||
pub block_height: u64,
|
||||
pub mix_node: MixNode,
|
||||
pub proxy: Option<Addr>,
|
||||
pub accumulated_rewards: Option<Uint128>,
|
||||
}
|
||||
|
||||
impl MixNodeBond {
|
||||
@@ -315,9 +316,14 @@ impl MixNodeBond {
|
||||
block_height,
|
||||
mix_node,
|
||||
proxy,
|
||||
accumulated_rewards: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accumulated_rewards(&self) -> Uint128 {
|
||||
self.accumulated_rewards.unwrap_or_else(Uint128::zero)
|
||||
}
|
||||
|
||||
pub fn profit_margin(&self) -> U128 {
|
||||
U128::from_num(self.mix_node.profit_margin_percent) / U128::from_num(100)
|
||||
}
|
||||
@@ -338,11 +344,16 @@ impl MixNodeBond {
|
||||
&self.mix_node
|
||||
}
|
||||
|
||||
// Takes into account accumulated rewards as well as current pledge and delegation amounts
|
||||
pub fn total_bond(&self) -> Option<u128> {
|
||||
if self.pledge_amount.denom != self.total_delegation.denom {
|
||||
None
|
||||
} else {
|
||||
Some(self.pledge_amount.amount.u128() + self.total_delegation.amount.u128())
|
||||
Some(
|
||||
self.pledge_amount.amount.u128()
|
||||
+ self.total_delegation.amount.u128()
|
||||
+ self.accumulated_rewards().u128(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,6 +366,10 @@ impl MixNodeBond {
|
||||
* U128::from_num(rewarded_set_size)
|
||||
}
|
||||
|
||||
// TODO: There is an effect here when adding accumulted rewards to the total bond, ie accumulated rewards will not
|
||||
// affect lambda, but will affect sigma, in turn over time, if left unclaimed operator rewards will not compound, but
|
||||
// behave similarly to delegations.
|
||||
// The question is should this be taken into account when calculating operator rewards?
|
||||
pub fn pledge_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
|
||||
U128::from_num(self.pledge_amount().amount.u128()) / U128::from_num(circulating_supply)
|
||||
}
|
||||
@@ -364,26 +379,46 @@ impl MixNodeBond {
|
||||
/ U128::from_num(circulating_supply)
|
||||
}
|
||||
|
||||
pub fn lambda(&self, params: &NodeRewardParams) -> U128 {
|
||||
pub fn lambda(&self, params: &RewardParams) -> U128 {
|
||||
// Ratio of a bond to the token circulating supply
|
||||
let pledge_to_circulating_supply_ratio =
|
||||
self.pledge_to_circulating_supply(params.circulating_supply());
|
||||
pledge_to_circulating_supply_ratio.min(params.one_over_k())
|
||||
}
|
||||
|
||||
pub fn sigma(&self, params: &NodeRewardParams) -> U128 {
|
||||
pub fn sigma(&self, params: &RewardParams) -> U128 {
|
||||
// Ratio of a delegation to the the token circulating supply
|
||||
let total_bond_to_circulating_supply_ratio =
|
||||
self.total_bond_to_circulating_supply(params.circulating_supply());
|
||||
total_bond_to_circulating_supply_ratio.min(params.one_over_k())
|
||||
}
|
||||
|
||||
pub fn reward(&self, params: &NodeRewardParams) -> NodeRewardResult {
|
||||
pub fn estimate_reward(
|
||||
&self,
|
||||
params: &RewardParams,
|
||||
) -> Result<(u64, u64, u64), MixnetContractError> {
|
||||
let total_node_reward = self.reward(params);
|
||||
let operator_reward = self.operator_reward(params);
|
||||
// TODO: This overestimates the reward by a lot, it should take a Uint128 and return estiamte for that
|
||||
let delegators_reward = self.reward_delegation(self.total_delegation().amount, params);
|
||||
|
||||
Ok((
|
||||
total_node_reward
|
||||
.reward()
|
||||
.checked_to_num::<u128>()
|
||||
.unwrap_or_default()
|
||||
.try_into()?,
|
||||
operator_reward.try_into()?,
|
||||
delegators_reward.try_into()?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn reward(&self, params: &RewardParams) -> NodeRewardResult {
|
||||
let lambda = self.lambda(params);
|
||||
let sigma = self.sigma(params);
|
||||
|
||||
let reward = params.performance()
|
||||
* params.period_reward_pool()
|
||||
* params.epoch_reward_pool()
|
||||
* (sigma * params.omega()
|
||||
+ params.alpha() * lambda * sigma * params.rewarded_set_size())
|
||||
/ (ONE + params.alpha());
|
||||
@@ -395,22 +430,22 @@ impl MixNodeBond {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node_profit(&self, params: &NodeRewardParams) -> U128 {
|
||||
if self.reward(params).reward() < params.operator_cost() {
|
||||
U128::from_num(0)
|
||||
pub fn node_profit(&self, params: &RewardParams) -> U128 {
|
||||
if self.reward(params).reward() < params.node.operator_cost() {
|
||||
U128::from_num(0u128)
|
||||
} else {
|
||||
self.reward(params).reward() - params.operator_cost()
|
||||
self.reward(params).reward() - params.node.operator_cost()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operator_reward(&self, params: &NodeRewardParams) -> u128 {
|
||||
pub fn operator_reward(&self, params: &RewardParams) -> u128 {
|
||||
let reward = self.reward(params);
|
||||
let profit = if reward.reward < params.operator_cost() {
|
||||
U128::from_num(0)
|
||||
let profit = if reward.reward < params.node.operator_cost() {
|
||||
U128::from_num(0u128)
|
||||
} else {
|
||||
reward.reward - params.operator_cost()
|
||||
reward.reward - params.node.operator_cost()
|
||||
};
|
||||
let operator_base_reward = reward.reward.min(params.operator_cost());
|
||||
let operator_base_reward = reward.reward.min(params.node.operator_cost());
|
||||
let operator_reward = (self.profit_margin()
|
||||
+ (ONE - self.profit_margin()) * reward.lambda / reward.sigma)
|
||||
* profit;
|
||||
@@ -429,7 +464,7 @@ impl MixNodeBond {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sigma_ratio(&self, params: &NodeRewardParams) -> U128 {
|
||||
pub fn sigma_ratio(&self, params: &RewardParams) -> U128 {
|
||||
if self.total_bond_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
|
||||
{
|
||||
self.total_bond_to_circulating_supply(params.circulating_supply())
|
||||
@@ -438,8 +473,13 @@ impl MixNodeBond {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reward_delegation(&self, delegation_amount: Uint128, params: &NodeRewardParams) -> u128 {
|
||||
let reward_params = DelegatorRewardParams::new(self, *params);
|
||||
pub fn reward_delegation(&self, delegation_amount: Uint128, params: &RewardParams) -> u128 {
|
||||
let reward_params = DelegatorRewardParams::new(
|
||||
self.sigma(params),
|
||||
self.profit_margin(),
|
||||
self.node_profit(params),
|
||||
params.to_owned(),
|
||||
);
|
||||
reward_params.determine_delegation_reward(delegation_amount)
|
||||
}
|
||||
}
|
||||
@@ -578,6 +618,7 @@ mod tests {
|
||||
block_height: 100,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Some(Uint128::zero()),
|
||||
};
|
||||
|
||||
let mix2 = MixNodeBond {
|
||||
@@ -588,6 +629,7 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Some(Uint128::zero()),
|
||||
};
|
||||
|
||||
let mix3 = MixNodeBond {
|
||||
@@ -598,6 +640,7 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Some(Uint128::zero()),
|
||||
};
|
||||
|
||||
let mix4 = MixNodeBond {
|
||||
@@ -608,6 +651,7 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Some(Uint128::zero()),
|
||||
};
|
||||
|
||||
let mix5 = MixNodeBond {
|
||||
@@ -618,6 +662,7 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Some(Uint128::zero()),
|
||||
};
|
||||
|
||||
// summary:
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::mixnode::NodeRewardParams;
|
||||
use crate::reward_params::NodeRewardParams;
|
||||
use crate::ContractStateParams;
|
||||
use crate::{Gateway, IdentityKey, MixNode};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
type BlockHeight = u64;
|
||||
type DelegateAddress = Vec<u8>;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct InstantiateMsg {
|
||||
pub rewarding_validator_address: String,
|
||||
@@ -15,6 +18,19 @@ pub struct InstantiateMsg {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecuteMsg {
|
||||
ReconcileDelegations {},
|
||||
CheckpointMixnodes {},
|
||||
CompoundOperatorRewardOnBehalf {
|
||||
owner: String,
|
||||
},
|
||||
CompoundDelegatorRewardOnBehalf {
|
||||
owner: String,
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
CompoundOperatorReward {},
|
||||
CompoundDelegatorReward {
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
BondMixnode {
|
||||
mix_node: MixNode,
|
||||
owner_signature: String,
|
||||
@@ -50,11 +66,11 @@ pub enum ExecuteMsg {
|
||||
// id of the current rewarding interval
|
||||
interval_id: u32,
|
||||
},
|
||||
RewardNextMixDelegators {
|
||||
mix_identity: IdentityKey,
|
||||
// id of the current rewarding interval
|
||||
interval_id: u32,
|
||||
},
|
||||
// RewardNextMixDelegators {
|
||||
// mix_identity: IdentityKey,
|
||||
// // id of the current rewarding interval
|
||||
// interval_id: u32,
|
||||
// },
|
||||
DelegateToMixnodeOnBehalf {
|
||||
mix_identity: IdentityKey,
|
||||
delegate: String,
|
||||
@@ -83,7 +99,8 @@ pub enum ExecuteMsg {
|
||||
rewarded_set: Vec<IdentityKey>,
|
||||
expected_active_set_size: u32,
|
||||
},
|
||||
AdvanceCurrentInterval {},
|
||||
// AdvanceCurrentInterval {},
|
||||
AdvanceCurrentEpoch {},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
@@ -108,7 +125,7 @@ pub enum QueryMsg {
|
||||
// gets all [paged] delegations in the entire network
|
||||
// TODO: do we even want that?
|
||||
GetAllNetworkDelegations {
|
||||
start_after: Option<(IdentityKey, String)>,
|
||||
start_after: Option<(IdentityKey, DelegateAddress, BlockHeight)>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
// gets all [paged] delegations associated with particular mixnode
|
||||
@@ -116,7 +133,7 @@ pub enum QueryMsg {
|
||||
mix_identity: IdentityKey,
|
||||
// since `start_after` is user-provided input, we can't use `Addr` as we
|
||||
// can't guarantee it's validated.
|
||||
start_after: Option<String>,
|
||||
start_after: Option<(String, u64)>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
// gets all [paged] delegations associated with particular delegator
|
||||
@@ -147,13 +164,18 @@ pub enum QueryMsg {
|
||||
start_after: Option<IdentityKey>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
GetRewardedSetHeightsForInterval {
|
||||
interval_id: u32,
|
||||
},
|
||||
GetRewardedSetUpdateDetails {},
|
||||
GetCurrentRewardedSetHeight {},
|
||||
GetCurrentInterval {},
|
||||
GetRewardedSetRefreshBlocks {},
|
||||
GetCurrentEpoch {},
|
||||
GetEpochsInInterval {},
|
||||
QueryOperatorReward {
|
||||
address: String,
|
||||
},
|
||||
QueryDelegatorReward {
|
||||
address: String,
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
use crate::{error::MixnetContractError, mixnode::StoredNodeRewardResult, ONE, U128};
|
||||
use az::CheckedCast;
|
||||
use cosmwasm_std::Uint128;
|
||||
use network_defaults::DEFAULT_OPERATOR_INTERVAL_COST;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct NodeEpochRewards {
|
||||
params: NodeRewardParams,
|
||||
result: StoredNodeRewardResult,
|
||||
epoch_id: u32,
|
||||
}
|
||||
|
||||
impl NodeEpochRewards {
|
||||
pub fn new(params: NodeRewardParams, result: StoredNodeRewardResult, epoch_id: u32) -> Self {
|
||||
Self {
|
||||
params,
|
||||
result,
|
||||
epoch_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn epoch_id(&self) -> u32 {
|
||||
self.epoch_id
|
||||
}
|
||||
|
||||
pub fn sigma(&self) -> Uint128 {
|
||||
self.result.sigma()
|
||||
}
|
||||
|
||||
pub fn lambda(&self) -> Uint128 {
|
||||
self.result.lambda()
|
||||
}
|
||||
|
||||
pub fn params(&self) -> NodeRewardParams {
|
||||
self.params
|
||||
}
|
||||
|
||||
pub fn reward(&self) -> Uint128 {
|
||||
self.result.reward()
|
||||
}
|
||||
|
||||
pub fn operator_cost(&self) -> U128 {
|
||||
U128::from_num(self.params.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
|
||||
}
|
||||
|
||||
pub fn node_profit(&self) -> U128 {
|
||||
let reward = U128::from_num(self.reward().u128());
|
||||
if reward < self.operator_cost() {
|
||||
U128::from_num(0u128)
|
||||
} else {
|
||||
reward - self.operator_cost()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operator_reward(&self, profit_margin: U128) -> Result<Uint128, MixnetContractError> {
|
||||
let reward = self.node_profit();
|
||||
let operator_base_reward = reward.min(self.operator_cost());
|
||||
let operator_reward = (profit_margin
|
||||
+ (ONE - profit_margin) * U128::from_num(self.lambda().u128())
|
||||
/ U128::from_num(self.sigma().u128()))
|
||||
* reward;
|
||||
|
||||
let reward = (operator_reward + operator_base_reward).max(U128::from_num(0u128));
|
||||
|
||||
if let Some(int_reward) = reward.checked_cast() {
|
||||
Ok(Uint128::new(int_reward))
|
||||
} else {
|
||||
Err(MixnetContractError::CastError)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delegation_reward(
|
||||
&self,
|
||||
delegation_amount: Uint128,
|
||||
profit_margin: U128,
|
||||
epoch_reward_params: EpochRewardParams,
|
||||
) -> Result<Uint128, MixnetContractError> {
|
||||
// change all values into their fixed representations
|
||||
let delegation_amount = U128::from_num(delegation_amount.u128());
|
||||
let circulating_supply = U128::from_num(epoch_reward_params.circulating_supply());
|
||||
|
||||
let scaled_delegation_amount = delegation_amount / circulating_supply;
|
||||
let delegator_reward = (ONE - profit_margin) * scaled_delegation_amount
|
||||
/ U128::from_num(self.sigma().u128())
|
||||
* self.node_profit();
|
||||
|
||||
let reward = delegator_reward.max(U128::ZERO);
|
||||
if let Some(int_reward) = reward.checked_cast() {
|
||||
Ok(Uint128::new(int_reward))
|
||||
} else {
|
||||
Err(MixnetContractError::CastError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct EpochRewardParams {
|
||||
epoch_reward_pool: Uint128,
|
||||
rewarded_set_size: Uint128,
|
||||
active_set_size: Uint128,
|
||||
circulating_supply: Uint128,
|
||||
sybil_resistance_percent: u8,
|
||||
active_set_work_factor: u8,
|
||||
}
|
||||
|
||||
impl EpochRewardParams {
|
||||
pub fn new(
|
||||
epoch_reward_pool: u128,
|
||||
rewarded_set_size: u128,
|
||||
active_set_size: u128,
|
||||
circulating_supply: u128,
|
||||
sybil_resistance_percent: u8,
|
||||
active_set_work_factor: u8,
|
||||
) -> EpochRewardParams {
|
||||
EpochRewardParams {
|
||||
epoch_reward_pool: Uint128::new(epoch_reward_pool),
|
||||
rewarded_set_size: Uint128::new(rewarded_set_size),
|
||||
active_set_size: Uint128::new(active_set_size),
|
||||
circulating_supply: Uint128::new(circulating_supply),
|
||||
sybil_resistance_percent,
|
||||
active_set_work_factor,
|
||||
}
|
||||
}
|
||||
|
||||
// technically it's identical to what would have been derived with a Default implementation,
|
||||
// however, I prefer to be explicit about it, as a `Default::default` value makes no sense
|
||||
// apart from the `ValidatorCacheInner` context, where this value is not going to be touched anyway
|
||||
// (it's guarded behind an `initialised` flag)
|
||||
pub fn new_empty() -> Self {
|
||||
EpochRewardParams {
|
||||
epoch_reward_pool: Uint128::new(0),
|
||||
circulating_supply: Uint128::new(0),
|
||||
sybil_resistance_percent: 0,
|
||||
rewarded_set_size: Uint128::new(0),
|
||||
active_set_size: Uint128::new(0),
|
||||
active_set_work_factor: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewarded_set_size(&self) -> u128 {
|
||||
self.rewarded_set_size.u128()
|
||||
}
|
||||
|
||||
pub fn active_set_size(&self) -> u128 {
|
||||
self.active_set_size.u128()
|
||||
}
|
||||
|
||||
pub fn circulating_supply(&self) -> u128 {
|
||||
self.circulating_supply.u128()
|
||||
}
|
||||
|
||||
pub fn epoch_reward_pool(&self) -> u128 {
|
||||
self.epoch_reward_pool.u128()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct NodeRewardParams {
|
||||
reward_blockstamp: u64,
|
||||
uptime: Uint128,
|
||||
in_active_set: bool,
|
||||
}
|
||||
|
||||
impl NodeRewardParams {
|
||||
pub fn new(reward_blockstamp: u64, uptime: u128, in_active_set: bool) -> NodeRewardParams {
|
||||
NodeRewardParams {
|
||||
reward_blockstamp,
|
||||
uptime: Uint128::new(uptime),
|
||||
in_active_set,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operator_cost(&self) -> U128 {
|
||||
U128::from_num(self.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
|
||||
}
|
||||
|
||||
pub fn uptime(&self) -> u128 {
|
||||
self.uptime.u128()
|
||||
}
|
||||
|
||||
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
|
||||
self.reward_blockstamp = blockstamp;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct RewardParams {
|
||||
pub epoch: EpochRewardParams,
|
||||
pub node: NodeRewardParams,
|
||||
}
|
||||
|
||||
impl RewardParams {
|
||||
pub fn new(epoch: EpochRewardParams, node: NodeRewardParams) -> RewardParams {
|
||||
RewardParams { epoch, node }
|
||||
}
|
||||
|
||||
pub fn omega(&self) -> U128 {
|
||||
// As per keybase://chat/nymtech#tokeneconomics/1179
|
||||
let denom = self.active_set_work_factor() * U128::from_num(self.rewarded_set_size())
|
||||
- (self.active_set_work_factor() - ONE) * U128::from_num(self.idle_nodes().u128());
|
||||
|
||||
if self.in_active_set() {
|
||||
// work_active = factor / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
|
||||
self.active_set_work_factor() / denom * self.rewarded_set_size()
|
||||
} else {
|
||||
// work_idle = 1 / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
|
||||
ONE / denom * self.rewarded_set_size()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn idle_nodes(&self) -> Uint128 {
|
||||
self.epoch.rewarded_set_size - self.epoch.active_set_size
|
||||
}
|
||||
|
||||
pub fn active_set_work_factor(&self) -> U128 {
|
||||
U128::from_num(self.epoch.active_set_work_factor)
|
||||
}
|
||||
|
||||
pub fn in_active_set(&self) -> bool {
|
||||
self.node.in_active_set
|
||||
}
|
||||
|
||||
pub fn performance(&self) -> U128 {
|
||||
U128::from_num(self.node.uptime.u128()) / U128::from_num(100)
|
||||
}
|
||||
|
||||
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
|
||||
self.node.reward_blockstamp = blockstamp;
|
||||
}
|
||||
|
||||
pub fn epoch_reward_pool(&self) -> u128 {
|
||||
self.epoch.epoch_reward_pool.u128()
|
||||
}
|
||||
|
||||
pub fn rewarded_set_size(&self) -> u128 {
|
||||
self.epoch.rewarded_set_size.u128()
|
||||
}
|
||||
|
||||
pub fn circulating_supply(&self) -> u128 {
|
||||
self.epoch.circulating_supply.u128()
|
||||
}
|
||||
|
||||
pub fn reward_blockstamp(&self) -> u64 {
|
||||
self.node.reward_blockstamp
|
||||
}
|
||||
|
||||
pub fn uptime(&self) -> u128 {
|
||||
self.node.uptime.u128()
|
||||
}
|
||||
|
||||
pub fn one_over_k(&self) -> U128 {
|
||||
ONE / U128::from_num(self.epoch.rewarded_set_size.u128())
|
||||
}
|
||||
|
||||
pub fn alpha(&self) -> U128 {
|
||||
U128::from_num(self.epoch.sybil_resistance_percent) / U128::from_num(100)
|
||||
}
|
||||
}
|
||||
@@ -73,8 +73,7 @@ impl Display for ContractStateParams {
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RewardingResult {
|
||||
pub operator_reward: Uint128,
|
||||
pub total_delegator_reward: Uint128,
|
||||
pub node_reward: Uint128,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -124,7 +123,7 @@ pub type IdentityKey = String;
|
||||
pub type IdentityKeyRef<'a> = &'a str;
|
||||
pub type SphinxKey = String;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
|
||||
pub struct PagedRewardedSetResponse {
|
||||
pub identities: Vec<(IdentityKey, RewardedSetNodeStatus)>,
|
||||
pub start_next_after: Option<IdentityKey>,
|
||||
|
||||
@@ -12,4 +12,6 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
schemars = "0.8"
|
||||
cw-storage-plus = "0.11.1"
|
||||
config = { path = "../../config" }
|
||||
ts-rs = { version = "*", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
@@ -12,7 +12,11 @@ pub fn one_ucoin() -> Coin {
|
||||
Coin::new(1, DENOM)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(export, export_to = "../../../nym-wallet/src/types/rust/period.ts")
|
||||
)]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, JsonSchema)]
|
||||
pub enum Period {
|
||||
Before,
|
||||
|
||||
@@ -6,11 +6,12 @@ edition = "2021"
|
||||
# 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"] }
|
||||
thiserror = "1.0"
|
||||
url = "2.2"
|
||||
|
||||
# I guess temporarily until we get serde support in coconut up and running
|
||||
coconut-interface = { path = "../coconut-interface" }
|
||||
crypto = { path = "../crypto" }
|
||||
crypto = { path = "../crypto", features = ["asymmetric"] }
|
||||
network-defaults = { path = "../network-defaults" }
|
||||
validator-client = { path = "../client-libs/validator-client" }
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bls12_381::Scalar;
|
||||
use coconut_interface::{
|
||||
aggregate_signature_shares, aggregate_verification_keys, prepare_blind_sign,
|
||||
prove_bandwidth_credential, Attribute, BlindSignRequestBody, Credential, Parameters, Signature,
|
||||
SignatureShare, VerificationKey,
|
||||
prove_bandwidth_credential, Attribute, BlindSignRequest, BlindSignRequestBody, Credential,
|
||||
Parameters, Signature, SignatureShare, VerificationKey,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
@@ -47,13 +48,13 @@ pub async fn obtain_aggregate_verification_key(
|
||||
let mut client = validator_client::ApiClient::new(validators[0].clone());
|
||||
let response = client.get_coconut_verification_key().await?;
|
||||
|
||||
indices.push(0);
|
||||
indices.push(1);
|
||||
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);
|
||||
indices.push((id + 1) as u64);
|
||||
shares.push(response.key);
|
||||
}
|
||||
|
||||
@@ -64,20 +65,13 @@ async fn obtain_partial_credential(
|
||||
params: &Parameters,
|
||||
public_attributes: &[Attribute],
|
||||
private_attributes: &[Attribute],
|
||||
pedersen_commitments_openings: &[Scalar],
|
||||
blind_sign_request: &BlindSignRequest,
|
||||
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(),
|
||||
blind_sign_request,
|
||||
public_attributes,
|
||||
(public_attributes.len() + private_attributes.len()) as u32,
|
||||
);
|
||||
@@ -89,11 +83,11 @@ async fn obtain_partial_credential(
|
||||
|
||||
let unblinded_signature = blinded_signature.unblind(
|
||||
params,
|
||||
elgamal_keypair.private_key(),
|
||||
validator_vk,
|
||||
private_attributes,
|
||||
public_attributes,
|
||||
&blind_sign_request.get_commitment_hash(),
|
||||
&*pedersen_commitments_openings,
|
||||
)?;
|
||||
|
||||
Ok(unblinded_signature)
|
||||
@@ -116,15 +110,20 @@ pub async fn obtain_aggregate_signature(
|
||||
let validator_partial_vk = client.get_coconut_verification_key().await?;
|
||||
validators_partial_vks.push(validator_partial_vk.key.clone());
|
||||
|
||||
let (pedersen_commitments_openings, blind_sign_request) =
|
||||
prepare_blind_sign(params, private_attributes, public_attributes)?;
|
||||
|
||||
let first = obtain_partial_credential(
|
||||
params,
|
||||
public_attributes,
|
||||
private_attributes,
|
||||
&pedersen_commitments_openings,
|
||||
&blind_sign_request,
|
||||
&client,
|
||||
&validator_partial_vk.key,
|
||||
)
|
||||
.await?;
|
||||
shares.push(SignatureShare::new(first, 0));
|
||||
shares.push(SignatureShare::new(first, 1));
|
||||
|
||||
for (id, validator_url) in validators.iter().enumerate().skip(1) {
|
||||
client.change_validator_api(validator_url.clone());
|
||||
@@ -134,11 +133,13 @@ pub async fn obtain_aggregate_signature(
|
||||
params,
|
||||
public_attributes,
|
||||
private_attributes,
|
||||
&pedersen_commitments_openings,
|
||||
&blind_sign_request,
|
||||
&client,
|
||||
&validator_partial_vk.key,
|
||||
)
|
||||
.await?;
|
||||
let share = SignatureShare::new(signature, id as u64);
|
||||
let share = SignatureShare::new(signature, (id + 1) as u64);
|
||||
shares.push(share)
|
||||
}
|
||||
|
||||
@@ -148,7 +149,7 @@ pub async fn obtain_aggregate_signature(
|
||||
|
||||
let mut indices: Vec<u64> = Vec::with_capacity(validators_partial_vks.len());
|
||||
for i in 0..validators_partial_vks.len() {
|
||||
indices.push(i as u64);
|
||||
indices.push((i + 1) as u64);
|
||||
}
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&validators_partial_vks, Some(indices.as_ref()))?;
|
||||
|
||||
@@ -7,21 +7,29 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
aes = { version = "0.7.4", features = ["ctr"] }
|
||||
aes = { version = "0.8.1", optional = true }
|
||||
bs58 = "0.4.0"
|
||||
blake3 = { version = "~1.2.0", features = ["traits-preview"] }
|
||||
digest = "0.9.0"
|
||||
generic-array = "0.14"
|
||||
hkdf = "0.11.0"
|
||||
hmac = "0.11.0"
|
||||
cipher = "0.3.0"
|
||||
x25519-dalek = "1.1"
|
||||
ed25519-dalek = "1.0"
|
||||
log = "0.4"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
blake3 = { version = "1.3.1", features = ["traits-preview"], optional = true }
|
||||
ctr = { version = "0.9.1", optional = true }
|
||||
digest = { version = "0.10.3", optional = true }
|
||||
generic-array = { version = "0.14", optional = true }
|
||||
hkdf = { version = "0.12.3", optional = true }
|
||||
hmac = { version = "0.12.1", optional = true }
|
||||
cipher = { version = "0.4.3", optional = true }
|
||||
x25519-dalek = { version = "1.1", optional = true }
|
||||
ed25519-dalek = { version = "1.0", optional = true }
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"], optional = true }
|
||||
subtle-encoding = { version = "0.5", features = ["bech32-preview"]}
|
||||
|
||||
# internal
|
||||
nymsphinx-types = { path = "../nymsphinx/types" }
|
||||
pemstore = { path = "../../common/pemstore" }
|
||||
config = { path="../../common/config" }
|
||||
|
||||
[dev-dependencies]
|
||||
rand_chacha = "0.2"
|
||||
|
||||
[features]
|
||||
asymmetric = ["x25519-dalek", "ed25519-dalek"]
|
||||
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array"]
|
||||
symmetric = ["aes", "ctr", "cipher", "generic-array"]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
#[cfg(feature = "rand")]
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
@@ -46,6 +47,7 @@ pub struct KeyPair {
|
||||
}
|
||||
|
||||
impl KeyPair {
|
||||
#[cfg(feature = "rand")]
|
||||
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
|
||||
let private_key = x25519_dalek::StaticSecret::new(rng);
|
||||
let public_key = (&private_key).into();
|
||||
|
||||
@@ -6,6 +6,7 @@ pub use ed25519_dalek::SignatureError;
|
||||
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
|
||||
use nymsphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
|
||||
use pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
#[cfg(feature = "rand")]
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
@@ -45,6 +46,7 @@ pub struct KeyPair {
|
||||
}
|
||||
|
||||
impl KeyPair {
|
||||
#[cfg(feature = "rand")]
|
||||
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
|
||||
let ed25519_keypair = ed25519_dalek::Keypair::generate(rng);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use config::defaults;
|
||||
use config::defaults::DEFAULT_NETWORK;
|
||||
use subtle_encoding::bech32;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -18,12 +18,12 @@ pub fn try_bech32_decode(address: &str) -> Result<String, Bech32Error> {
|
||||
pub fn validate_bech32_prefix(address: &str) -> Result<(), Bech32Error> {
|
||||
let prefix = try_bech32_decode(address)?;
|
||||
|
||||
if prefix == defaults::BECH32_PREFIX {
|
||||
if prefix == DEFAULT_NETWORK.bech32_prefix() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Bech32Error::WrongPrefix(format!(
|
||||
"your bech32 address prefix should be {}, not {}",
|
||||
defaults::BECH32_PREFIX,
|
||||
DEFAULT_NETWORK.bech32_prefix(),
|
||||
prefix
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use digest::{BlockInput, Digest, FixedOutput, Reset, Update};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use digest::{Digest, Output};
|
||||
|
||||
pub fn compute_digest<D>(data: &[u8]) -> GenericArray<u8, <D as Digest>::OutputSize>
|
||||
pub fn compute_digest<D>(data: &[u8]) -> Output<D>
|
||||
where
|
||||
D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
|
||||
D::BlockSize: ArrayLength<u8>,
|
||||
D::OutputSize: ArrayLength<u8>,
|
||||
D: Digest,
|
||||
{
|
||||
D::digest(data)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use digest::{BlockInput, FixedOutput, Reset, Update};
|
||||
use generic_array::ArrayLength;
|
||||
use hkdf::Hkdf;
|
||||
use hkdf::{
|
||||
hmac::{
|
||||
digest::{crypto_common::BlockSizeUser, Digest},
|
||||
SimpleHmac,
|
||||
},
|
||||
Hkdf,
|
||||
};
|
||||
|
||||
/// Perform HKDF `extract` then `expand` as a single step.
|
||||
pub fn extract_then_expand<D>(
|
||||
@@ -13,14 +17,12 @@ pub fn extract_then_expand<D>(
|
||||
okm_length: usize,
|
||||
) -> Result<Vec<u8>, hkdf::InvalidLength>
|
||||
where
|
||||
D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
|
||||
D::BlockSize: ArrayLength<u8>,
|
||||
D::OutputSize: ArrayLength<u8>,
|
||||
D: Digest + BlockSizeUser + Clone,
|
||||
{
|
||||
// TODO: this would need to change if we ever needed the generated pseudorandom key, but
|
||||
// realistically I don't see any reasons why we might need it
|
||||
|
||||
let hkdf = Hkdf::<D>::new(salt, ikm);
|
||||
let hkdf = Hkdf::<D, SimpleHmac<D>>::new(salt, ikm);
|
||||
let mut okm = vec![0u8; okm_length];
|
||||
hkdf.expand(info.unwrap_or(&[]), &mut okm)?;
|
||||
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use digest::{BlockInput, FixedOutput, Reset, Update};
|
||||
use generic_array::{typenum::Unsigned, ArrayLength, GenericArray};
|
||||
use hmac::{crypto_mac, Hmac, Mac, NewMac};
|
||||
use hmac::{
|
||||
digest::{crypto_common::BlockSizeUser, CtOutput, Digest, Output},
|
||||
Mac, SimpleHmac,
|
||||
};
|
||||
|
||||
pub use hmac;
|
||||
|
||||
// Type alias for ease of use so that it would not require explicit import of crypto_mac or Hmac
|
||||
pub type HmacOutput<D> = crypto_mac::Output<Hmac<D>>;
|
||||
// TODO: We should probably change it to use some sealed trait to allow for both `Hmac` and `SimpleHmac`
|
||||
pub type HmacOutput<D> = CtOutput<SimpleHmac<D>>;
|
||||
|
||||
/// Compute keyed hmac
|
||||
pub fn compute_keyed_hmac<D>(key: &[u8], data: &[u8]) -> HmacOutput<D>
|
||||
where
|
||||
D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
|
||||
D::BlockSize: ArrayLength<u8>,
|
||||
D::OutputSize: ArrayLength<u8>,
|
||||
D: Digest + BlockSizeUser,
|
||||
{
|
||||
let mut hmac =
|
||||
Hmac::<D>::new_from_slice(key).expect("HMAC should be able to take key of any size!");
|
||||
let mut hmac = SimpleHmac::<D>::new_from_slice(key)
|
||||
.expect("HMAC was instantiated with a key of an invalid size!");
|
||||
hmac.update(data);
|
||||
hmac.finalize()
|
||||
}
|
||||
@@ -26,32 +25,28 @@ where
|
||||
/// Compute keyed hmac and performs constant time equality check with the provided tag value.
|
||||
pub fn recompute_keyed_hmac_and_verify_tag<D>(key: &[u8], data: &[u8], tag: &[u8]) -> bool
|
||||
where
|
||||
D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
|
||||
D::BlockSize: ArrayLength<u8>,
|
||||
D::OutputSize: ArrayLength<u8>,
|
||||
D: Digest + BlockSizeUser,
|
||||
{
|
||||
let mut hmac =
|
||||
Hmac::<D>::new_from_slice(key).expect("HMAC should be able to take key of any size!");
|
||||
let mut hmac = SimpleHmac::<D>::new_from_slice(key)
|
||||
.expect("HMAC was instantiated with a key of an invalid size!");
|
||||
hmac.update(data);
|
||||
|
||||
let tag_arr = Output::<D>::from_slice(tag);
|
||||
// note, under the hood ct_eq is called
|
||||
hmac.verify(tag).is_ok()
|
||||
hmac.verify(tag_arr).is_ok()
|
||||
}
|
||||
|
||||
/// Verifies tag of an hmac output.
|
||||
pub fn verify_tag<D>(tag: &[u8], out: HmacOutput<D>) -> bool
|
||||
where
|
||||
D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
|
||||
D::BlockSize: ArrayLength<u8>,
|
||||
D::OutputSize: ArrayLength<u8>,
|
||||
D: Digest + BlockSizeUser,
|
||||
{
|
||||
if tag.len() != D::OutputSize::to_usize() {
|
||||
if tag.len() != <D as Digest>::output_size() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let tag_bytes = GenericArray::clone_from_slice(tag);
|
||||
let tag_out = HmacOutput::new(tag_bytes);
|
||||
// note, under the hood ct_eq is called
|
||||
out == tag_out
|
||||
let tag_arr = Output::<D>::from_slice(tag);
|
||||
out == tag_arr.into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(feature = "asymmetric")]
|
||||
pub mod asymmetric;
|
||||
pub mod bech32_address_validation;
|
||||
#[cfg(feature = "hashing")]
|
||||
pub mod crypto_hash;
|
||||
#[cfg(feature = "hashing")]
|
||||
pub mod hkdf;
|
||||
#[cfg(feature = "hashing")]
|
||||
pub mod hmac;
|
||||
#[cfg(all(feature = "asymmetric", feature = "hashing", feature = "symmetric"))]
|
||||
pub mod shared_key;
|
||||
#[cfg(feature = "symmetric")]
|
||||
pub mod symmetric;
|
||||
|
||||
pub use digest::Digest;
|
||||
#[cfg(feature = "hashing")]
|
||||
pub use digest::{Digest, OutputSizeUser};
|
||||
#[cfg(any(feature = "hashing", feature = "symmetric"))]
|
||||
pub use generic_array;
|
||||
|
||||
// with the below my idea was to try to introduce having a single place of importing all hashing, encryption,
|
||||
// etc. algorithms and import them elsewhere as needed via common/crypto
|
||||
#[cfg(feature = "symmetric")]
|
||||
pub use aes;
|
||||
#[cfg(feature = "hashing")]
|
||||
pub use blake3;
|
||||
#[cfg(feature = "symmetric")]
|
||||
pub use ctr;
|
||||
|
||||
// TODO: this function uses all three modules: asymmetric crypto, symmetric crypto and derives key...,
|
||||
// so I don't know where to put it...
|
||||
|
||||
@@ -3,22 +3,22 @@
|
||||
|
||||
use crate::asymmetric::encryption;
|
||||
use crate::hkdf;
|
||||
use cipher::{CipherKey, NewCipher, StreamCipher};
|
||||
use digest::{BlockInput, FixedOutput, Reset, Update};
|
||||
use generic_array::{typenum::Unsigned, ArrayLength};
|
||||
use cipher::{Key, KeyIvInit, StreamCipher};
|
||||
use digest::crypto_common::BlockSizeUser;
|
||||
use digest::Digest;
|
||||
#[cfg(feature = "rand")]
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
/// Generate an ephemeral encryption keypair and perform diffie-hellman to establish
|
||||
/// shared key with the remote.
|
||||
#[cfg(feature = "rand")]
|
||||
pub fn new_ephemeral_shared_key<C, D, R>(
|
||||
rng: &mut R,
|
||||
remote_key: &encryption::PublicKey,
|
||||
) -> (encryption::KeyPair, CipherKey<C>)
|
||||
) -> (encryption::KeyPair, Key<C>)
|
||||
where
|
||||
C: StreamCipher + NewCipher,
|
||||
D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
|
||||
D::BlockSize: ArrayLength<u8>,
|
||||
D::OutputSize: ArrayLength<u8>,
|
||||
C: StreamCipher + KeyIvInit,
|
||||
D: Digest + BlockSizeUser + Clone,
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
let ephemeral_keypair = encryption::KeyPair::new(rng);
|
||||
@@ -27,11 +27,11 @@ where
|
||||
let dh_result = ephemeral_keypair.private_key().diffie_hellman(remote_key);
|
||||
|
||||
// there is no reason for this to fail as our okm is expected to be only C::KeySize bytes
|
||||
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::KeySize::to_usize())
|
||||
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::key_size())
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
let derived_shared_key =
|
||||
CipherKey::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!");
|
||||
Key::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!");
|
||||
|
||||
(ephemeral_keypair, derived_shared_key)
|
||||
}
|
||||
@@ -40,18 +40,16 @@ where
|
||||
pub fn recompute_shared_key<C, D>(
|
||||
remote_key: &encryption::PublicKey,
|
||||
local_key: &encryption::PrivateKey,
|
||||
) -> CipherKey<C>
|
||||
) -> Key<C>
|
||||
where
|
||||
C: StreamCipher + NewCipher,
|
||||
D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
|
||||
D::BlockSize: ArrayLength<u8>,
|
||||
D::OutputSize: ArrayLength<u8>,
|
||||
C: StreamCipher + KeyIvInit,
|
||||
D: Digest + BlockSizeUser + Clone,
|
||||
{
|
||||
let dh_result = local_key.diffie_hellman(remote_key);
|
||||
|
||||
// there is no reason for this to fail as our okm is expected to be only C::KeySize bytes
|
||||
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::KeySize::to_usize())
|
||||
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::key_size())
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
CipherKey::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!")
|
||||
Key::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!")
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cipher::{Nonce, StreamCipher};
|
||||
use generic_array::{typenum::Unsigned, GenericArray};
|
||||
use cipher::{Iv, StreamCipher};
|
||||
pub use cipher::{IvSizeUser, KeyIvInit, KeySizeUser};
|
||||
#[cfg(feature = "rand")]
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
// re-export this for ease of use
|
||||
pub use cipher::{CipherKey, NewCipher};
|
||||
pub use cipher::Key as CipherKey;
|
||||
|
||||
// SECURITY:
|
||||
// TODO: note that this is not the most secure approach here
|
||||
@@ -19,49 +20,51 @@ pub use cipher::{CipherKey, NewCipher};
|
||||
|
||||
// I think 'IV' looks better than 'Iv', feel free to change that.
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub type IV<C> = Nonce<C>;
|
||||
pub type IV<C> = Iv<C>;
|
||||
|
||||
#[cfg(feature = "rand")]
|
||||
pub fn generate_key<C, R>(rng: &mut R) -> CipherKey<C>
|
||||
where
|
||||
C: NewCipher,
|
||||
C: KeyIvInit,
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
let mut key = GenericArray::default();
|
||||
let mut key = CipherKey::<C>::default();
|
||||
rng.fill_bytes(&mut key);
|
||||
key
|
||||
}
|
||||
|
||||
#[cfg(feature = "rand")]
|
||||
pub fn random_iv<C, R>(rng: &mut R) -> IV<C>
|
||||
where
|
||||
C: NewCipher,
|
||||
C: KeyIvInit,
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
let mut iv = GenericArray::default();
|
||||
let mut iv = IV::<C>::default();
|
||||
rng.fill_bytes(&mut iv);
|
||||
iv
|
||||
}
|
||||
|
||||
pub fn zero_iv<C>() -> IV<C>
|
||||
where
|
||||
C: NewCipher,
|
||||
C: KeyIvInit,
|
||||
{
|
||||
GenericArray::default()
|
||||
Iv::<C>::default()
|
||||
}
|
||||
|
||||
pub fn iv_from_slice<C>(b: &[u8]) -> &IV<C>
|
||||
where
|
||||
C: NewCipher,
|
||||
C: KeyIvInit,
|
||||
{
|
||||
if b.len() != C::NonceSize::to_usize() {
|
||||
if b.len() != C::iv_size() {
|
||||
// `from_slice` would have caused a panic about this issue anyway.
|
||||
// Now we at least have slightly more information
|
||||
panic!(
|
||||
"Tried to convert {} bytes to IV. Expected {}",
|
||||
b.len(),
|
||||
C::NonceSize::to_usize()
|
||||
C::iv_size()
|
||||
)
|
||||
}
|
||||
GenericArray::from_slice(b)
|
||||
IV::<C>::from_slice(b)
|
||||
}
|
||||
|
||||
// TODO: there's really no way to use more parts of the keystream if it was required at some point.
|
||||
@@ -70,7 +73,7 @@ where
|
||||
#[inline]
|
||||
pub fn encrypt<C>(key: &CipherKey<C>, iv: &IV<C>, data: &[u8]) -> Vec<u8>
|
||||
where
|
||||
C: StreamCipher + NewCipher,
|
||||
C: StreamCipher + KeyIvInit,
|
||||
{
|
||||
let mut ciphertext = data.to_vec();
|
||||
encrypt_in_place::<C>(key, iv, &mut ciphertext);
|
||||
@@ -80,7 +83,7 @@ where
|
||||
#[inline]
|
||||
pub fn encrypt_in_place<C>(key: &CipherKey<C>, iv: &IV<C>, data: &mut [u8])
|
||||
where
|
||||
C: StreamCipher + NewCipher,
|
||||
C: StreamCipher + KeyIvInit,
|
||||
{
|
||||
let mut cipher = C::new(key, iv);
|
||||
cipher.apply_keystream(data)
|
||||
@@ -89,7 +92,7 @@ where
|
||||
#[inline]
|
||||
pub fn decrypt<C>(key: &CipherKey<C>, iv: &IV<C>, ciphertext: &[u8]) -> Vec<u8>
|
||||
where
|
||||
C: StreamCipher + NewCipher,
|
||||
C: StreamCipher + KeyIvInit,
|
||||
{
|
||||
let mut data = ciphertext.to_vec();
|
||||
decrypt_in_place::<C>(key, iv, &mut data);
|
||||
@@ -99,7 +102,7 @@ where
|
||||
#[inline]
|
||||
pub fn decrypt_in_place<C>(key: &CipherKey<C>, iv: &IV<C>, data: &mut [u8])
|
||||
where
|
||||
C: StreamCipher + NewCipher,
|
||||
C: StreamCipher + KeyIvInit,
|
||||
{
|
||||
let mut cipher = C::new(key, iv);
|
||||
cipher.apply_keystream(data)
|
||||
@@ -108,12 +111,12 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand::rngs::OsRng;
|
||||
use rand_chacha::rand_core::SeedableRng;
|
||||
|
||||
#[cfg(test)]
|
||||
mod aes_ctr128 {
|
||||
use super::*;
|
||||
use aes::Aes128Ctr;
|
||||
type Aes128Ctr = ctr::Ctr64LE<aes::Aes128>;
|
||||
|
||||
#[test]
|
||||
fn zero_iv_is_actually_zero() {
|
||||
@@ -125,7 +128,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn decryption_is_reciprocal_to_encryption() {
|
||||
let mut rng = OsRng;
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let arr_input = [42; 200];
|
||||
let vec_input = vec![123, 200];
|
||||
@@ -148,7 +152,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn in_place_variants_work_same_way() {
|
||||
let mut rng = OsRng;
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let mut data = [42; 200];
|
||||
let original_data = data;
|
||||
|
||||
@@ -9,6 +9,7 @@ edition = "2021"
|
||||
[dependencies]
|
||||
cfg-if = "1.0.0"
|
||||
hex-literal = "0.3.3"
|
||||
once_cell = "1.7.2"
|
||||
serde = {version = "1.0", features = ["derive"]}
|
||||
thiserror = "1.0"
|
||||
url = "2.2"
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, fmt, str::FromStr};
|
||||
|
||||
use crate::{mainnet, qa, sandbox, ValidatorDetails};
|
||||
use crate::{
|
||||
DefaultNetworkDetails, ValidatorDetails, MAINNET_DEFAULTS, QA_DEFAULTS, SANDBOX_DEFAULTS,
|
||||
};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -22,20 +24,40 @@ pub enum Network {
|
||||
}
|
||||
|
||||
impl Network {
|
||||
pub fn bech32_prefix(&self) -> String {
|
||||
fn details(&self) -> &DefaultNetworkDetails<'_> {
|
||||
match self {
|
||||
Self::QA => String::from(qa::BECH32_PREFIX),
|
||||
Self::SANDBOX => String::from(sandbox::BECH32_PREFIX),
|
||||
Self::MAINNET => String::from(mainnet::BECH32_PREFIX),
|
||||
Self::QA => &QA_DEFAULTS,
|
||||
Self::SANDBOX => &SANDBOX_DEFAULTS,
|
||||
Self::MAINNET => &MAINNET_DEFAULTS,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn denom(&self) -> String {
|
||||
match self {
|
||||
Self::QA => String::from(qa::DENOM),
|
||||
Self::SANDBOX => String::from(sandbox::DENOM),
|
||||
Self::MAINNET => String::from(mainnet::DENOM),
|
||||
}
|
||||
pub fn bech32_prefix(&self) -> &str {
|
||||
self.details().bech32_prefix
|
||||
}
|
||||
|
||||
pub fn denom(&self) -> &str {
|
||||
self.details().denom
|
||||
}
|
||||
|
||||
pub fn mixnet_contract_address(&self) -> &str {
|
||||
self.details().mixnet_contract_address
|
||||
}
|
||||
|
||||
pub fn vesting_contract_address(&self) -> &str {
|
||||
self.details().vesting_contract_address
|
||||
}
|
||||
|
||||
pub fn bandwidth_claim_contract_address(&self) -> &str {
|
||||
self.details().bandwidth_claim_contract_address
|
||||
}
|
||||
|
||||
pub fn rewarding_validator_address(&self) -> &str {
|
||||
self.details().rewarding_validator_address
|
||||
}
|
||||
|
||||
pub fn validators(&self) -> impl Iterator<Item = &ValidatorDetails> {
|
||||
self.details().validators.iter()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +86,7 @@ impl fmt::Display for Network {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct NetworkDetails {
|
||||
bech32_prefix: String,
|
||||
denom: String,
|
||||
@@ -75,67 +97,33 @@ pub struct NetworkDetails {
|
||||
validators: Vec<ValidatorDetails>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
impl From<&DefaultNetworkDetails<'_>> for NetworkDetails {
|
||||
fn from(details: &DefaultNetworkDetails<'_>) -> Self {
|
||||
NetworkDetails {
|
||||
bech32_prefix: details.bech32_prefix.into(),
|
||||
denom: details.denom.into(),
|
||||
mixnet_contract_address: details.mixnet_contract_address.into(),
|
||||
vesting_contract_address: details.vesting_contract_address.into(),
|
||||
bandwidth_claim_contract_address: details.bandwidth_claim_contract_address.into(),
|
||||
rewarding_validator_address: details.rewarding_validator_address.into(),
|
||||
validators: details.validators.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct SupportedNetworks {
|
||||
networks: HashMap<Network, NetworkDetails>,
|
||||
}
|
||||
|
||||
impl SupportedNetworks {
|
||||
pub fn new(support: Vec<Network>) -> Self {
|
||||
let mut networks = HashMap::new();
|
||||
|
||||
for network in support {
|
||||
match network {
|
||||
Network::MAINNET => networks.insert(
|
||||
Network::MAINNET,
|
||||
NetworkDetails {
|
||||
bech32_prefix: String::from(mainnet::BECH32_PREFIX),
|
||||
denom: String::from(mainnet::DENOM),
|
||||
mixnet_contract_address: String::from(mainnet::MIXNET_CONTRACT_ADDRESS),
|
||||
vesting_contract_address: String::from(mainnet::VESTING_CONTRACT_ADDRESS),
|
||||
bandwidth_claim_contract_address: String::from(
|
||||
mainnet::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
|
||||
),
|
||||
rewarding_validator_address: String::from(
|
||||
mainnet::REWARDING_VALIDATOR_ADDRESS,
|
||||
),
|
||||
validators: mainnet::validators(),
|
||||
},
|
||||
),
|
||||
|
||||
Network::SANDBOX => networks.insert(
|
||||
Network::SANDBOX,
|
||||
NetworkDetails {
|
||||
bech32_prefix: String::from(sandbox::BECH32_PREFIX),
|
||||
denom: String::from(sandbox::DENOM),
|
||||
mixnet_contract_address: String::from(sandbox::MIXNET_CONTRACT_ADDRESS),
|
||||
vesting_contract_address: String::from(sandbox::VESTING_CONTRACT_ADDRESS),
|
||||
bandwidth_claim_contract_address: String::from(
|
||||
sandbox::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
|
||||
),
|
||||
rewarding_validator_address: String::from(
|
||||
sandbox::REWARDING_VALIDATOR_ADDRESS,
|
||||
),
|
||||
validators: sandbox::validators(),
|
||||
},
|
||||
),
|
||||
Network::QA => networks.insert(
|
||||
Network::QA,
|
||||
NetworkDetails {
|
||||
bech32_prefix: String::from(qa::BECH32_PREFIX),
|
||||
denom: String::from(qa::DENOM),
|
||||
mixnet_contract_address: String::from(qa::MIXNET_CONTRACT_ADDRESS),
|
||||
vesting_contract_address: String::from(qa::VESTING_CONTRACT_ADDRESS),
|
||||
bandwidth_claim_contract_address: String::from(
|
||||
qa::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
|
||||
),
|
||||
rewarding_validator_address: String::from(qa::REWARDING_VALIDATOR_ADDRESS),
|
||||
validators: qa::validators(),
|
||||
},
|
||||
),
|
||||
};
|
||||
SupportedNetworks {
|
||||
networks: support
|
||||
.into_iter()
|
||||
.map(|n| (n, n.details().into()))
|
||||
.collect(),
|
||||
}
|
||||
SupportedNetworks { networks }
|
||||
}
|
||||
|
||||
pub fn bech32_prefix(&self, network: Network) -> Option<&str> {
|
||||
@@ -174,9 +162,11 @@ impl SupportedNetworks {
|
||||
.map(|network_details| network_details.rewarding_validator_address.as_str())
|
||||
}
|
||||
|
||||
pub fn validators(&self, network: Network) -> Option<&Vec<ValidatorDetails>> {
|
||||
pub fn validators(&self, network: Network) -> impl Iterator<Item = &ValidatorDetails> {
|
||||
self.networks
|
||||
.get(&network)
|
||||
.map(|network_details| &network_details.validators)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
@@ -9,65 +11,80 @@ pub mod mainnet;
|
||||
pub mod qa;
|
||||
pub mod sandbox;
|
||||
|
||||
// The set of defaults that are decided at compile time. Ideally we want to reduce these to a
|
||||
// minimum.
|
||||
// Keep DENOM around mostly for use in contracts. (TODO: consider moving it there, or renaming?)
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(network = "mainnet")] {
|
||||
pub const BECH32_PREFIX: &str = mainnet::BECH32_PREFIX;
|
||||
pub const DEFAULT_NETWORK: all::Network = all::Network::MAINNET;
|
||||
pub const DENOM: &str = mainnet::DENOM;
|
||||
|
||||
pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = mainnet::MIXNET_CONTRACT_ADDRESS;
|
||||
pub const DEFAULT_VESTING_CONTRACT_ADDRESS: &str = mainnet::VESTING_CONTRACT_ADDRESS;
|
||||
pub const DEFAULT_BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = mainnet::BANDWIDTH_CLAIM_CONTRACT_ADDRESS;
|
||||
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_CONTRACT_ADDRESS;
|
||||
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_ERC20_CONTRACT_ADDRESS;
|
||||
pub const DEFAULT_REWARDING_VALIDATOR_ADDRESS: &str = mainnet::REWARDING_VALIDATOR_ADDRESS;
|
||||
|
||||
pub fn default_validators() -> Vec<ValidatorDetails> {
|
||||
mainnet::validators()
|
||||
}
|
||||
|
||||
pub fn default_network() -> all::Network {
|
||||
all::Network::MAINNET
|
||||
}
|
||||
} else if #[cfg(network = "qa")] {
|
||||
pub const BECH32_PREFIX: &str = qa::BECH32_PREFIX;
|
||||
pub const DEFAULT_NETWORK: all::Network = all::Network::QA;
|
||||
pub const DENOM: &str = qa::DENOM;
|
||||
|
||||
pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = qa::MIXNET_CONTRACT_ADDRESS;
|
||||
pub const DEFAULT_VESTING_CONTRACT_ADDRESS: &str = qa::VESTING_CONTRACT_ADDRESS;
|
||||
pub const DEFAULT_BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = qa::BANDWIDTH_CLAIM_CONTRACT_ADDRESS;
|
||||
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = qa::_ETH_CONTRACT_ADDRESS;
|
||||
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = qa::_ETH_ERC20_CONTRACT_ADDRESS;
|
||||
pub const DEFAULT_REWARDING_VALIDATOR: &str = qa::REWARDING_VALIDATOR_ADDRESS;
|
||||
|
||||
pub fn default_validators() -> Vec<ValidatorDetails> {
|
||||
qa::validators()
|
||||
}
|
||||
|
||||
pub fn default_network() -> all::Network {
|
||||
all::Network::QA
|
||||
}
|
||||
} else if #[cfg(network = "sandbox")] {
|
||||
pub const BECH32_PREFIX: &str = sandbox::BECH32_PREFIX;
|
||||
pub const DEFAULT_NETWORK: all::Network = all::Network::SANDBOX;
|
||||
pub const DENOM: &str = sandbox::DENOM;
|
||||
|
||||
pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = sandbox::MIXNET_CONTRACT_ADDRESS;
|
||||
pub const DEFAULT_VESTING_CONTRACT_ADDRESS: &str = sandbox::VESTING_CONTRACT_ADDRESS;
|
||||
pub const DEFAULT_BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = sandbox::BANDWIDTH_CLAIM_CONTRACT_ADDRESS;
|
||||
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = sandbox::_ETH_CONTRACT_ADDRESS;
|
||||
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = sandbox::_ETH_ERC20_CONTRACT_ADDRESS;
|
||||
pub const DEFAULT_REWARDING_VALIDATOR: &str = sandbox::REWARDING_VALIDATOR_ADDRESS;
|
||||
|
||||
pub fn default_validators() -> Vec<ValidatorDetails> {
|
||||
sandbox::validators()
|
||||
}
|
||||
|
||||
pub fn default_network() -> all::Network {
|
||||
all::Network::SANDBOX
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
// Since these are lazily constructed, we can afford to switch some of them to stronger types in the
|
||||
// future. If we do this, and also get rid of the references we could potentially unify with
|
||||
// `NetworkDetails`.
|
||||
#[derive(Debug)]
|
||||
pub struct DefaultNetworkDetails<'a> {
|
||||
bech32_prefix: &'a str,
|
||||
denom: &'a str,
|
||||
mixnet_contract_address: &'a str,
|
||||
vesting_contract_address: &'a str,
|
||||
bandwidth_claim_contract_address: &'a str,
|
||||
rewarding_validator_address: &'a str,
|
||||
validators: Vec<ValidatorDetails>,
|
||||
}
|
||||
|
||||
static MAINNET_DEFAULTS: Lazy<DefaultNetworkDetails<'static>> =
|
||||
Lazy::new(|| DefaultNetworkDetails {
|
||||
bech32_prefix: mainnet::BECH32_PREFIX,
|
||||
denom: mainnet::DENOM,
|
||||
mixnet_contract_address: mainnet::MIXNET_CONTRACT_ADDRESS,
|
||||
vesting_contract_address: mainnet::VESTING_CONTRACT_ADDRESS,
|
||||
bandwidth_claim_contract_address: mainnet::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
|
||||
rewarding_validator_address: mainnet::REWARDING_VALIDATOR_ADDRESS,
|
||||
validators: mainnet::validators(),
|
||||
});
|
||||
|
||||
static SANDBOX_DEFAULTS: Lazy<DefaultNetworkDetails<'static>> =
|
||||
Lazy::new(|| DefaultNetworkDetails {
|
||||
bech32_prefix: sandbox::BECH32_PREFIX,
|
||||
denom: sandbox::DENOM,
|
||||
mixnet_contract_address: sandbox::MIXNET_CONTRACT_ADDRESS,
|
||||
vesting_contract_address: sandbox::VESTING_CONTRACT_ADDRESS,
|
||||
bandwidth_claim_contract_address: sandbox::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
|
||||
rewarding_validator_address: sandbox::REWARDING_VALIDATOR_ADDRESS,
|
||||
validators: sandbox::validators(),
|
||||
});
|
||||
|
||||
static QA_DEFAULTS: Lazy<DefaultNetworkDetails<'static>> = Lazy::new(|| DefaultNetworkDetails {
|
||||
bech32_prefix: qa::BECH32_PREFIX,
|
||||
denom: qa::DENOM,
|
||||
mixnet_contract_address: qa::MIXNET_CONTRACT_ADDRESS,
|
||||
vesting_contract_address: qa::VESTING_CONTRACT_ADDRESS,
|
||||
bandwidth_claim_contract_address: qa::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
|
||||
rewarding_validator_address: qa::REWARDING_VALIDATOR_ADDRESS,
|
||||
validators: qa::validators(),
|
||||
});
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub struct ValidatorDetails {
|
||||
// it is assumed those values are always valid since they're being provided in our defaults file
|
||||
pub nymd_url: String,
|
||||
@@ -78,10 +95,9 @@ pub struct ValidatorDetails {
|
||||
|
||||
impl ValidatorDetails {
|
||||
pub fn new(nymd_url: &str, api_url: Option<&str>) -> Self {
|
||||
let api_url = api_url.map(|api_url_str| api_url_str.to_string());
|
||||
ValidatorDetails {
|
||||
nymd_url: nymd_url.to_string(),
|
||||
api_url,
|
||||
api_url: api_url.map(ToString::to_string),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,16 +115,16 @@ impl ValidatorDetails {
|
||||
}
|
||||
|
||||
pub fn default_nymd_endpoints() -> Vec<Url> {
|
||||
default_validators()
|
||||
.iter()
|
||||
.map(|validator| validator.nymd_url())
|
||||
DEFAULT_NETWORK
|
||||
.validators()
|
||||
.map(ValidatorDetails::nymd_url)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn default_api_endpoints() -> Vec<Url> {
|
||||
default_validators()
|
||||
.iter()
|
||||
.filter_map(|validator| validator.api_url())
|
||||
DEFAULT_NETWORK
|
||||
.validators()
|
||||
.filter_map(ValidatorDetails::api_url)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -162,7 +178,8 @@ pub const VALIDATOR_API_VERSION: &str = "v1";
|
||||
// REWARDING
|
||||
|
||||
/// 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 interval costs to Nyms. We'll also assume a cost of 40$ per interval(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms
|
||||
pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 40_000_000; // 40$/(30 days) at 1 Nym == 1$
|
||||
// pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 40_000_000; // 40$/(30 days) at 1 Nym == 1$
|
||||
pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 55_556; // 40$/1hr 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;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "nym_compact_ecash"
|
||||
version = "0.1.0"
|
||||
authors = ["Ania Piotrowska <ania@nymtech.net>"]
|
||||
edition = "2021"
|
||||
|
||||
# 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"] }
|
||||
bls12_381 = { path = "/Users/ania/Documents/Git/andrew_bls12_381", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] }
|
||||
itertools = "0.10"
|
||||
digest = "0.9"
|
||||
rand = "0.8"
|
||||
thiserror = "1.0"
|
||||
sha2 = "0.9"
|
||||
bs58 = "0.4.0"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
|
||||
[dependencies.ff]
|
||||
version = "0.11"
|
||||
default-features = false
|
||||
|
||||
[dependencies.group]
|
||||
version = "0.11"
|
||||
default-features = false
|
||||
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
@@ -0,0 +1,360 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::ops::Neg;
|
||||
use std::time::Duration;
|
||||
|
||||
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt, multi_miller_loop, Scalar};
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use ff::Field;
|
||||
use group::{Curve, Group};
|
||||
use itertools::izip;
|
||||
use rand::seq::SliceRandom;
|
||||
|
||||
use nym_compact_ecash::{
|
||||
aggregate_verification_keys, aggregate_wallets, generate_keypair_user,
|
||||
issue_verify, issue_wallet, PartialWallet,
|
||||
PayInfo, PublicKeyUser, SecretKeyUser, ttp_keygen, VerificationKeyAuth, withdrawal_request,
|
||||
};
|
||||
use nym_compact_ecash::identify::{identify, IdentifyResult};
|
||||
use nym_compact_ecash::setup::setup;
|
||||
|
||||
#[allow(unused)]
|
||||
fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
|
||||
let gt1 = bls12_381::pairing(g11, g21);
|
||||
let gt2 = bls12_381::pairing(g12, g22);
|
||||
assert_eq!(gt1, gt2)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn single_pairing(g11: &G1Affine, g21: &G2Affine) {
|
||||
let gt1 = bls12_381::pairing(g11, g21);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn exponent_in_g1(g1: G1Projective, r: Scalar) {
|
||||
let g11 = (g1 * r);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn exponent_in_g2(g2: G2Projective, r: Scalar) {
|
||||
let g22 = (g2 * r);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn exponent_in_gt(gt: Gt, r: Scalar) {
|
||||
let gtt = (gt * r);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn multi_miller_pairing_affine(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
|
||||
let miller_loop_result = multi_miller_loop(&[
|
||||
(g11, &G2Prepared::from(*g21)),
|
||||
(&g12.neg(), &G2Prepared::from(*g22)),
|
||||
]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn multi_miller_pairing_with_prepared(
|
||||
g11: &G1Affine,
|
||||
g21: &G2Prepared,
|
||||
g12: &G1Affine,
|
||||
g22: &G2Prepared,
|
||||
) {
|
||||
let miller_loop_result = multi_miller_loop(&[(g11, g21), (&g12.neg(), g22)]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
// the case of being able to prepare G2 generator
|
||||
#[allow(unused)]
|
||||
fn multi_miller_pairing_with_semi_prepared(
|
||||
g11: &G1Affine,
|
||||
g21: &G2Affine,
|
||||
g12: &G1Affine,
|
||||
g22: &G2Prepared,
|
||||
) {
|
||||
let miller_loop_result =
|
||||
multi_miller_loop(&[(g11, &G2Prepared::from(*g21)), (&g12.neg(), g22)]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn bench_pairings(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("benchmark-pairings");
|
||||
group.measurement_time(Duration::from_secs(200));
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let g1 = G1Affine::generator();
|
||||
let g2 = G2Affine::generator();
|
||||
let r = Scalar::random(&mut rng);
|
||||
let s = Scalar::random(&mut rng);
|
||||
|
||||
let g11 = (g1 * r).to_affine();
|
||||
let g21 = (g2 * s).to_affine();
|
||||
let g21_prep = G2Prepared::from(g21);
|
||||
|
||||
let g12 = (g1 * s).to_affine();
|
||||
let g22 = (g2 * r).to_affine();
|
||||
let g22_prep = G2Prepared::from(g22);
|
||||
|
||||
let gt = bls12_381::pairing(&g11, &g21);
|
||||
let gen1 = G1Projective::generator();
|
||||
let gen2 = G2Projective::generator();
|
||||
|
||||
group.bench_function("exponent operation in G1", |b| {
|
||||
b.iter(|| exponent_in_g1(gen1, r))
|
||||
});
|
||||
|
||||
group.bench_function("exponent operation in G2", |b| {
|
||||
b.iter(|| exponent_in_g2(gen2, r))
|
||||
});
|
||||
|
||||
group.bench_function("exponent operation in Gt", |b| {
|
||||
b.iter(|| exponent_in_gt(gt, r))
|
||||
});
|
||||
|
||||
group.bench_function("single pairing", |b| {
|
||||
b.iter(|| single_pairing(&g11, &g21))
|
||||
});
|
||||
|
||||
group.bench_function("double pairing", |b| {
|
||||
b.iter(|| double_pairing(&g11, &g21, &g12, &g22))
|
||||
});
|
||||
|
||||
group.bench_function("multi miller in affine", |b| {
|
||||
b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22))
|
||||
});
|
||||
|
||||
group.bench_function("multi miller with prepared g2", |b| {
|
||||
b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep))
|
||||
});
|
||||
|
||||
group.bench_function("multi miller with semi-prepared g2", |b| {
|
||||
b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep))
|
||||
});
|
||||
}
|
||||
|
||||
struct BenchCase {
|
||||
num_authorities: u64,
|
||||
threshold_p: f32,
|
||||
L: u64,
|
||||
spend_vv: u64,
|
||||
case_nr_pub_keys: u64,
|
||||
}
|
||||
|
||||
fn bench_compact_ecash(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("benchmark-compact-ecash");
|
||||
// group.sample_size(300);
|
||||
// group.measurement_time(Duration::from_secs(1500));
|
||||
|
||||
let case = BenchCase {
|
||||
num_authorities: 100,
|
||||
threshold_p: 0.7,
|
||||
L: 100,
|
||||
spend_vv: 1,
|
||||
case_nr_pub_keys: 99,
|
||||
};
|
||||
|
||||
let params = setup(case.L);
|
||||
let grp = params.grp();
|
||||
let user_keypair = generate_keypair_user(&grp);
|
||||
let threshold = (case.threshold_p * case.num_authorities as f32).round() as u64;
|
||||
let authorities_keypairs = ttp_keygen(&grp, threshold, case.num_authorities).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let indices: Vec<u64> = (1..case.num_authorities + 1).collect();
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
|
||||
// ISSUANCE PHASE
|
||||
|
||||
let (req, req_info) = withdrawal_request(grp, &user_keypair.secret_key()).unwrap();
|
||||
|
||||
// CLIENT BENCHMARK: prepare a single withdrawal request
|
||||
// group.bench_function(
|
||||
// &format!(
|
||||
// "[Client] withdrawal_request_{}_authorities_{}_L_{}_threshold",
|
||||
// case.num_authorities, case.L, case.threshold_p,
|
||||
// ),
|
||||
// |b| b.iter(|| withdrawal_request(grp, &user_keypair.secret_key()).unwrap()),
|
||||
// );
|
||||
|
||||
// ISSUING AUTHRORITY BENCHMARK: Benchmark the issue_wallet function
|
||||
// called by an authority to issue a blind signature on a partial wallet
|
||||
let mut rng = rand::thread_rng();
|
||||
let keypair = authorities_keypairs.choose(&mut rng).unwrap();
|
||||
// group.bench_function(
|
||||
// &format!("[Issuing Authority] issue_partial_wallet_with_L_{}", case.L, ),
|
||||
// |b| {
|
||||
// b.iter(|| {
|
||||
// issue_wallet(
|
||||
// &grp,
|
||||
// keypair.secret_key(),
|
||||
// user_keypair.public_key(),
|
||||
// &req,
|
||||
// ).unwrap()
|
||||
// })
|
||||
// },
|
||||
// );
|
||||
|
||||
let mut wallet_blinded_signatures = Vec::new();
|
||||
for auth_keypair in authorities_keypairs {
|
||||
let blind_signature = issue_wallet(
|
||||
&grp,
|
||||
auth_keypair.secret_key(),
|
||||
user_keypair.public_key(),
|
||||
&req,
|
||||
);
|
||||
wallet_blinded_signatures.push(blind_signature.unwrap());
|
||||
}
|
||||
|
||||
// CLIENT BENCHMARK: verify the issued partial wallet
|
||||
let w = wallet_blinded_signatures.get(0).clone().unwrap();
|
||||
let vk = verification_keys_auth.get(0).clone().unwrap();
|
||||
// group.bench_function(
|
||||
// &format!("[Client] issue_verify_a_partial_wallet_with_L_{}", case.L, ),
|
||||
// |b| b.iter(|| issue_verify(&grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap()),
|
||||
// );
|
||||
|
||||
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
|
||||
wallet_blinded_signatures.iter(),
|
||||
verification_keys_auth.iter()
|
||||
)
|
||||
.map(|(w, vk)| issue_verify(&grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
|
||||
.collect();
|
||||
|
||||
// CLIENT BENCHMARK: aggregating all partial wallets
|
||||
// group.bench_function(
|
||||
// &format!(
|
||||
// "[Client] aggregate_wallets_with_L_{}_threshold_{}",
|
||||
// case.L, case.threshold_p,
|
||||
// ),
|
||||
// |b| {
|
||||
// b.iter(|| {
|
||||
// aggregate_wallets(
|
||||
// &grp,
|
||||
// &verification_key,
|
||||
// &user_keypair.secret_key(),
|
||||
// &unblinded_wallet_shares,
|
||||
// &req_info,
|
||||
// )
|
||||
// .unwrap()
|
||||
// })
|
||||
// },
|
||||
// );
|
||||
|
||||
// Aggregate partial wallets
|
||||
let aggr_wallet = aggregate_wallets(
|
||||
&grp,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&unblinded_wallet_shares,
|
||||
&req_info,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// SPENDING PHASE
|
||||
let pay_info = PayInfo { info: [6u8; 32] };
|
||||
// CLIENT BENCHMARK: spend a single coin from the wallet
|
||||
// group.bench_function(
|
||||
// &format!(
|
||||
// "[Client] spend_a_single_coin_L_{}_threshold_{}",
|
||||
// case.L, case.threshold_p,
|
||||
// ),
|
||||
// |b| {
|
||||
// b.iter(|| {
|
||||
// aggr_wallet
|
||||
// .spend(
|
||||
// ¶ms,
|
||||
// &verification_key,
|
||||
// &user_keypair.secret_key(),
|
||||
// &pay_info,
|
||||
// true,
|
||||
// case.spend_vv,
|
||||
// )
|
||||
// .unwrap()
|
||||
// })
|
||||
// },
|
||||
// );
|
||||
|
||||
let (payment, upd_wallet) = aggr_wallet
|
||||
.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info,
|
||||
false,
|
||||
case.spend_vv,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// MERCHANT BENCHMARK: verify whether the submitted payment is legit
|
||||
// group.bench_function(
|
||||
// &format!(
|
||||
// "[Merchant] spend_verify_of_a_single_payment_L_{}_threshold_{}",
|
||||
// case.L, case.threshold_p,
|
||||
// ),
|
||||
// |b| {
|
||||
// b.iter(|| {
|
||||
// payment
|
||||
// .spend_verify(¶ms, &verification_key, &pay_info)
|
||||
// .unwrap()
|
||||
// })
|
||||
// },
|
||||
// );
|
||||
|
||||
// BENCHMARK IDENTIFICATION
|
||||
// Let's generate a double spending payment
|
||||
|
||||
// let's reverse the spending counter in the wallet to create a double spending payment
|
||||
let current_l = aggr_wallet.l.get();
|
||||
aggr_wallet.l.set(current_l - case.spend_vv);
|
||||
|
||||
let pay_info2 = PayInfo { info: [7u8; 32] };
|
||||
let (payment2, _) = aggr_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info2,
|
||||
true,
|
||||
case.spend_vv,
|
||||
).unwrap();
|
||||
|
||||
// GENERATE KEYS FOR OTHER USERS
|
||||
let mut public_keys: Vec<PublicKeyUser> = Default::default();
|
||||
for i in 0..case.case_nr_pub_keys {
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = sk_user.public_key(&grp);
|
||||
public_keys.push(pk_user);
|
||||
}
|
||||
public_keys.push(user_keypair.public_key());
|
||||
|
||||
// MERCHANT BENCHMARK: identify double spending
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Merchant] identify_L_{}_threshold_{}_spend_vv_{}_pks_{}",
|
||||
case.L, case.threshold_p, case.spend_vv, public_keys.len()
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
identify(¶ms, &verification_key, payment.clone(), payment2.clone(), pay_info.clone(), pay_info2.clone()).unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
let identify_result = identify(¶ms, &verification_key, payment, payment2, pay_info.clone(), pay_info2.clone()).unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key()));
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_compact_ecash);
|
||||
criterion_main!(benches);
|
||||
@@ -0,0 +1,49 @@
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, CompactEcashError>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CompactEcashError {
|
||||
#[error("Setup error: {0}")]
|
||||
Setup(String),
|
||||
|
||||
#[error("Aggregation error: {0}")]
|
||||
Aggregation(String),
|
||||
|
||||
#[error("Withdrawal Request Verification related error: {0}")]
|
||||
WithdrawalRequestVerification(String),
|
||||
|
||||
#[error("Deserialization error: {0}")]
|
||||
Deserialization(String),
|
||||
|
||||
#[error("Interpolation error: {0}")]
|
||||
Interpolation(String),
|
||||
|
||||
#[error("Issuance Verification related error: {0}")]
|
||||
IssuanceVfy(String),
|
||||
|
||||
#[error("Spend Verification related error: {0}")]
|
||||
Spend(String),
|
||||
|
||||
#[error("ZKP Proof related error: {0}")]
|
||||
RangeProofOutOfBound(String),
|
||||
|
||||
#[error("Identify Verification related error: {0}")]
|
||||
Identify(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,40 @@
|
||||
use std::convert::TryInto;
|
||||
|
||||
use bls12_381::Scalar;
|
||||
|
||||
pub use scheme::aggregation::aggregate_verification_keys;
|
||||
pub use scheme::aggregation::aggregate_wallets;
|
||||
pub use scheme::identify;
|
||||
pub use scheme::keygen::{PublicKeyUser, SecretKeyUser, VerificationKeyAuth};
|
||||
pub use scheme::keygen::generate_keypair_user;
|
||||
pub use scheme::keygen::ttp_keygen;
|
||||
pub use scheme::PartialWallet;
|
||||
pub use scheme::PayInfo;
|
||||
pub use scheme::setup;
|
||||
pub use scheme::withdrawal::issue_verify;
|
||||
pub use scheme::withdrawal::issue_wallet;
|
||||
pub use scheme::withdrawal::withdrawal_request;
|
||||
pub use traits::Base58;
|
||||
|
||||
use crate::error::CompactEcashError;
|
||||
use crate::traits::Bytable;
|
||||
|
||||
mod error;
|
||||
mod proofs;
|
||||
mod scheme;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod traits;
|
||||
mod utils;
|
||||
|
||||
pub type Attribute = Scalar;
|
||||
|
||||
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, CompactEcashError> {
|
||||
Ok(Attribute::from_bytes(slice.try_into().unwrap()).unwrap())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use bls12_381::Scalar;
|
||||
use digest::Digest;
|
||||
use digest::generic_array::typenum::Unsigned;
|
||||
use sha2::Sha256;
|
||||
|
||||
pub mod proof_spend;
|
||||
pub mod proof_withdrawal;
|
||||
|
||||
type ChallengeDigest = Sha256;
|
||||
|
||||
/// 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_replacement: &Scalar, challenge: &Scalar, secret: &Scalar) -> Scalar {
|
||||
witness_replacement - 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()
|
||||
}
|
||||
@@ -0,0 +1,777 @@
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
use bls12_381::{G1Projective, G2Projective, Scalar};
|
||||
use group::{Curve, GroupEncoding};
|
||||
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::proofs::{ChallengeDigest, compute_challenge, produce_response, produce_responses};
|
||||
use crate::scheme::keygen::VerificationKeyAuth;
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::utils::{try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar_vec, try_deserialize_scalar};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct SpendInstance {
|
||||
pub kappa: G2Projective,
|
||||
pub cc: G1Projective,
|
||||
pub aa: Vec<G1Projective>,
|
||||
pub ss: Vec<G1Projective>,
|
||||
pub tt: Vec<G1Projective>,
|
||||
pub kappa_k: Vec<G2Projective>,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for SpendInstance {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<SpendInstance> {
|
||||
if bytes.len() < 48 * 5 + 2 * 96 || (bytes.len()) % 48 != 0 {
|
||||
return Err(CompactEcashError::DeserializationInvalidLength {
|
||||
actual: bytes.len(),
|
||||
modulus_target: bytes.len(),
|
||||
target: 48 * 5 + 2 * 96,
|
||||
modulus: 48,
|
||||
object: "spend instance".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut j = 0;
|
||||
let kappa_bytes = bytes[j..j + 96].try_into().unwrap();
|
||||
let kappa = try_deserialize_g2_projective(
|
||||
&kappa_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize kappa".to_string()),
|
||||
)?;
|
||||
j += 96;
|
||||
|
||||
let a_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
|
||||
j += 8;
|
||||
if bytes[j..].len() < a_len as usize * 48 {
|
||||
return Err(CompactEcashError::DeserializationMinLength {
|
||||
min: a_len as usize * 48,
|
||||
actual: bytes[j..].len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut aa = Vec::with_capacity(a_len as usize);
|
||||
for i in 0..a_len as usize {
|
||||
let start = j + i * 48;
|
||||
let end = start + 48;
|
||||
|
||||
let aa_elem_bytes = bytes[start..end].try_into().unwrap();
|
||||
let aa_elem = try_deserialize_g1_projective(
|
||||
&aa_elem_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize compressed A values".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
aa.push(aa_elem)
|
||||
}
|
||||
j += j + a_len as usize * 48;
|
||||
|
||||
|
||||
let cc_bytes = bytes[j..j + 48].try_into().unwrap();
|
||||
let cc = try_deserialize_g1_projective(
|
||||
&cc_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize C".to_string()),
|
||||
)?;
|
||||
j += 48;
|
||||
|
||||
let s_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
|
||||
j += 8;
|
||||
if bytes[j..].len() < s_len as usize * 48 {
|
||||
return Err(CompactEcashError::DeserializationMinLength {
|
||||
min: s_len as usize * 48,
|
||||
actual: bytes[j..].len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut ss = Vec::with_capacity(s_len as usize);
|
||||
for i in 0..s_len as usize {
|
||||
let start = j + i * 48;
|
||||
let end = start + 48;
|
||||
|
||||
let ss_elem_bytes = bytes[start..end].try_into().unwrap();
|
||||
let ss_elem = try_deserialize_g1_projective(
|
||||
&ss_elem_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize compressed S values".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
ss.push(ss_elem)
|
||||
}
|
||||
j += j + s_len as usize * 48;
|
||||
|
||||
let t_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
|
||||
j += 8;
|
||||
if bytes[j..].len() < t_len as usize * 48 {
|
||||
return Err(CompactEcashError::DeserializationMinLength {
|
||||
min: t_len as usize * 48,
|
||||
actual: bytes[j..].len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut tt = Vec::with_capacity(t_len as usize);
|
||||
for i in 0..t_len as usize {
|
||||
let start = j + i * 48;
|
||||
let end = start + 48;
|
||||
|
||||
let tt_elem_bytes = bytes[start..end].try_into().unwrap();
|
||||
let tt_elem = try_deserialize_g1_projective(
|
||||
&tt_elem_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize compressed T values".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
tt.push(tt_elem)
|
||||
}
|
||||
j += j + t_len as usize * 48;
|
||||
|
||||
|
||||
let kappa_k_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
|
||||
j += 8;
|
||||
if bytes[j..].len() < kappa_k_len as usize * 96 {
|
||||
return Err(CompactEcashError::DeserializationMinLength {
|
||||
min: kappa_k_len as usize * 96,
|
||||
actual: bytes[j..].len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut kappa_k = Vec::with_capacity(kappa_k_len as usize);
|
||||
for i in 0..kappa_k_len as usize {
|
||||
let start = j + i * 48;
|
||||
let end = start + 48;
|
||||
|
||||
let kappa_k_elem_bytes = bytes[start..end].try_into().unwrap();
|
||||
let kappa_k_elem = try_deserialize_g2_projective(
|
||||
&kappa_k_elem_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize compressed kappa_k values".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
kappa_k.push(kappa_k_elem)
|
||||
}
|
||||
|
||||
Ok(SpendInstance {
|
||||
kappa,
|
||||
aa,
|
||||
cc,
|
||||
ss,
|
||||
tt,
|
||||
kappa_k,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SpendInstance {
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes: Vec<u8> = Default::default();
|
||||
bytes.extend_from_slice(self.kappa.to_bytes().as_ref());
|
||||
|
||||
bytes.extend_from_slice(&self.aa.len().to_le_bytes());
|
||||
for a in &self.aa {
|
||||
bytes.extend_from_slice(&a.to_affine().to_compressed());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(self.cc.to_bytes().as_ref());
|
||||
|
||||
bytes.extend_from_slice(&self.ss.len().to_le_bytes());
|
||||
for s in &self.ss {
|
||||
bytes.extend_from_slice(&s.to_affine().to_compressed());
|
||||
}
|
||||
bytes.extend_from_slice(&self.tt.len().to_le_bytes());
|
||||
for t in &self.tt {
|
||||
bytes.extend_from_slice(&t.to_affine().to_compressed());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&self.kappa_k.len().to_le_bytes());
|
||||
for k in &self.kappa_k {
|
||||
bytes.extend_from_slice(&k.to_affine().to_compressed());
|
||||
}
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpendWitness {
|
||||
// includes skUser, v, t
|
||||
pub attributes: Vec<Scalar>,
|
||||
// signature randomizing element
|
||||
pub r: Scalar,
|
||||
pub o_c: Scalar,
|
||||
pub lk: Vec<Scalar>,
|
||||
pub o_a: Vec<Scalar>,
|
||||
pub mu: Vec<Scalar>,
|
||||
pub o_mu: Vec<Scalar>,
|
||||
pub r_k: Vec<Scalar>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SpendProof {
|
||||
challenge: Scalar,
|
||||
response_r: Scalar,
|
||||
response_r_l: Vec<Scalar>,
|
||||
response_l: Vec<Scalar>,
|
||||
response_o_a: Vec<Scalar>,
|
||||
response_o_c: Scalar,
|
||||
response_mu: Vec<Scalar>,
|
||||
response_o_mu: Vec<Scalar>,
|
||||
response_attributes: Vec<Scalar>,
|
||||
}
|
||||
|
||||
impl SpendProof {
|
||||
pub fn construct(
|
||||
params: &Parameters,
|
||||
instance: &SpendInstance,
|
||||
witness: &SpendWitness,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
rr: &[Scalar],
|
||||
) -> Self {
|
||||
let grparams = params.grp();
|
||||
// generate random values to replace each witness
|
||||
let r_attributes = grparams.n_random_scalars(witness.attributes.len());
|
||||
let r_sk = r_attributes[0];
|
||||
let r_v = r_attributes[1];
|
||||
let r_r = grparams.random_scalar();
|
||||
let r_o_c = grparams.random_scalar();
|
||||
|
||||
let r_r_lk = grparams.n_random_scalars(witness.r_k.len());
|
||||
let r_lk = grparams.n_random_scalars(witness.lk.len());
|
||||
let r_o_a = grparams.n_random_scalars(witness.o_a.len());
|
||||
let r_mu = grparams.n_random_scalars(witness.mu.len());
|
||||
let r_o_mu = grparams.n_random_scalars(witness.o_mu.len());
|
||||
|
||||
let g1 = *grparams.gen1();
|
||||
let gamma1 = *grparams.gamma1();
|
||||
let beta2_bytes = verification_key
|
||||
.beta_g2
|
||||
.iter()
|
||||
.map(|beta_i| beta_i.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// compute zkp commitment for each instance
|
||||
let zkcm_kappa = grparams.gen2() * r_r
|
||||
+ verification_key.alpha
|
||||
+ r_attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(attr, beta_i)| beta_i * attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
let zkcm_cc = g1 * r_o_c + gamma1 * r_v;
|
||||
|
||||
let zkcm_aa: Vec<G1Projective> =
|
||||
r_o_a
|
||||
.iter()
|
||||
.zip(r_lk.iter()).map(|(r_o_a_k, r_l_k)| g1 * r_o_a_k + gamma1 * r_l_k)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_aa_bytes = zkcm_aa
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_ss = r_mu.iter().map(|r_mu_k| grparams.delta() * r_mu_k).collect::<Vec<_>>();
|
||||
|
||||
let zkcm_ss_bytes = zkcm_ss
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_tt = rr
|
||||
.iter()
|
||||
.zip(r_mu.iter()).map(|(rr_k, r_mu_k)| g1 * r_sk + (g1 * rr_k) * r_mu_k).collect::<Vec<_>>();
|
||||
|
||||
let zkcm_tt_bytes = zkcm_tt
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_gamma11 = instance.aa
|
||||
.iter()
|
||||
.zip(r_mu.iter())
|
||||
.zip(r_o_mu.iter())
|
||||
.map(|((aa_k, r_mu_k), r_o_mu_k)| (aa_k + instance.cc + gamma1) * r_mu_k + g1 * r_o_mu_k)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_gamma11_bytes = zkcm_gamma11
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_kappa_k = r_lk.iter()
|
||||
.zip(r_r_lk.iter())
|
||||
.map(|(r_k, r_r_k)| params.pk_rp().alpha + params.pk_rp().beta * r_k + grparams.gen2() * r_r_k)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_kappa_k_bytes = zkcm_kappa_k
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// compute the challenge
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(grparams.gen1().to_bytes().as_ref())
|
||||
.chain(std::iter::once(gamma1.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(verification_key.alpha.to_bytes().as_ref()))
|
||||
.chain(beta2_bytes.iter().map(|b| b.as_ref()))
|
||||
.chain(std::iter::once(instance.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_kappa.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_cc.to_bytes().as_ref()))
|
||||
.chain(zkcm_aa_bytes.iter().map(|x| x.as_ref()))
|
||||
.chain(zkcm_ss_bytes.iter().map(|x| x.as_ref()))
|
||||
.chain(zkcm_kappa_k_bytes.iter().map(|x| x.as_ref()))
|
||||
.chain(zkcm_tt_bytes.iter().map(|x| x.as_ref()))
|
||||
.chain(zkcm_gamma11_bytes.iter().map(|x| x.as_ref()))
|
||||
);
|
||||
|
||||
// compute response for each witness
|
||||
let response_attributes = produce_responses(
|
||||
&r_attributes,
|
||||
&challenge,
|
||||
&witness.attributes.iter().collect::<Vec<_>>(),
|
||||
);
|
||||
let response_r = produce_response(&r_r, &challenge, &witness.r);
|
||||
let response_r_l = produce_responses(&r_r_lk, &challenge, &witness.r_k);
|
||||
let response_l = produce_responses(&r_lk, &challenge, &witness.lk);
|
||||
let response_o_a = produce_responses(&r_o_a, &challenge, &witness.o_a);
|
||||
let response_o_c = produce_response(&r_o_c, &challenge, &witness.o_c);
|
||||
|
||||
let response_mu = produce_responses(&r_mu, &challenge, &witness.mu);
|
||||
let response_o_mu = produce_responses(&r_o_mu, &challenge, &witness.o_mu);
|
||||
|
||||
SpendProof {
|
||||
challenge,
|
||||
response_r,
|
||||
response_r_l,
|
||||
response_l,
|
||||
response_o_a,
|
||||
response_o_c,
|
||||
response_mu,
|
||||
response_o_mu,
|
||||
response_attributes,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
instance: &SpendInstance,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
rr: &[Scalar],
|
||||
) -> bool {
|
||||
let grparams = params.grp();
|
||||
let g1 = *grparams.gen1();
|
||||
let gamma1 = *grparams.gamma1();
|
||||
let beta2_bytes = verification_key
|
||||
.beta_g2
|
||||
.iter()
|
||||
.map(|beta_i| beta_i.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// re-compute each zkp commitment
|
||||
let zkcm_kappa = instance.kappa * self.challenge
|
||||
+ grparams.gen2() * self.response_r
|
||||
+ verification_key.alpha * (Scalar::one() - self.challenge)
|
||||
+ self
|
||||
.response_attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(attr, beta_i)| beta_i * attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
|
||||
let zkcm_aa = self.response_o_a
|
||||
.iter()
|
||||
.zip(self.response_l.iter())
|
||||
.zip(instance.aa.iter())
|
||||
.map(|((resp_o_a_k, resp_l_k), aa_k)| g1 * resp_o_a_k + gamma1 * resp_l_k + aa_k * self.challenge)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_aa_bytes = zkcm_aa
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_cc = g1 * self.response_o_c
|
||||
+ gamma1 * self.response_attributes[1]
|
||||
+ instance.cc * self.challenge;
|
||||
|
||||
let zkcm_ss = self.response_mu
|
||||
.iter()
|
||||
.zip(instance.ss.iter())
|
||||
.map(|(resp_mu_k, ss_k)| grparams.delta() * resp_mu_k + ss_k * self.challenge)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_ss_bytes = zkcm_ss
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_tt = self.response_mu
|
||||
.iter()
|
||||
.zip(rr.iter())
|
||||
.zip(instance.tt.iter())
|
||||
.map(|((resp_mu_k, rr_k), tt_k)| g1 * self.response_attributes[0] + (g1 * rr_k) * resp_mu_k + tt_k * self.challenge)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_tt_bytes = zkcm_tt
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_gamma11 = instance.aa
|
||||
.iter()
|
||||
.zip(self.response_mu.iter())
|
||||
.zip(self.response_o_mu.iter())
|
||||
.map(|((aa_k, resp_mu_k), resp_o_mu_k)| (aa_k + instance.cc + gamma1) * resp_mu_k
|
||||
+ g1 * resp_o_mu_k + gamma1 * self.challenge)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_gamma11_bytes = zkcm_gamma11
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
||||
let zkcm_kappa_k = instance.kappa_k
|
||||
.iter()
|
||||
.zip(self.response_r_l.iter())
|
||||
.zip(self.response_l.iter())
|
||||
.map(|((kappa_k, resp_r_k), resp_r_l_k)| kappa_k * self.challenge + grparams.gen2() * resp_r_k + params.pk_rp().alpha * (Scalar::one() - self.challenge) + params.pk_rp().beta * resp_r_l_k)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_kappa_k_bytes = zkcm_kappa_k
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// re-compute the challenge
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(grparams.gen1().to_bytes().as_ref())
|
||||
.chain(std::iter::once(gamma1.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(verification_key.alpha.to_bytes().as_ref()))
|
||||
.chain(beta2_bytes.iter().map(|b| b.as_ref()))
|
||||
.chain(std::iter::once(instance.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_kappa.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_cc.to_bytes().as_ref()))
|
||||
.chain(zkcm_aa_bytes.iter().map(|x| x.as_ref()))
|
||||
.chain(zkcm_ss_bytes.iter().map(|x| x.as_ref()))
|
||||
.chain(zkcm_kappa_k_bytes.iter().map(|x| x.as_ref()))
|
||||
.chain(zkcm_tt_bytes.iter().map(|x| x.as_ref()))
|
||||
.chain(zkcm_gamma11_bytes.iter().map(|x| x.as_ref()))
|
||||
);
|
||||
|
||||
challenge == self.challenge
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let challenge_bytes = self.challenge.to_bytes();
|
||||
let response_r_bytes = self.response_r.to_bytes();
|
||||
|
||||
let rrl_len = self.response_r_l.len();
|
||||
let rrl_len_bytes = rrl_len.to_le_bytes();
|
||||
|
||||
let rl_len = self.response_l.len();
|
||||
let rl_len_bytes = rl_len.to_le_bytes();
|
||||
|
||||
let roa_len = self.response_o_a.len();
|
||||
let roa_len_bytes = roa_len.to_le_bytes();
|
||||
|
||||
let roc_bytes = self.response_o_c.to_bytes();
|
||||
|
||||
let rmu_len = self.response_mu.len();
|
||||
let rmu_len_bytes = rmu_len.to_le_bytes();
|
||||
|
||||
let romu_len = self.response_o_mu.len();
|
||||
let romu_len_bytes = romu_len.to_le_bytes();
|
||||
|
||||
let rattributes_len = self.response_attributes.len();
|
||||
let rattributes_len_bytes = rattributes_len.to_le_bytes();
|
||||
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(
|
||||
96 + (rrl_len + rl_len + roa_len + rmu_len + romu_len + rattributes_len) * 8
|
||||
+ (rrl_len + rl_len + roa_len + rmu_len + romu_len + rattributes_len) * 32);
|
||||
|
||||
bytes.extend_from_slice(&challenge_bytes);
|
||||
bytes.extend_from_slice(&response_r_bytes);
|
||||
bytes.extend_from_slice(&roc_bytes);
|
||||
|
||||
bytes.extend_from_slice(&rrl_len_bytes);
|
||||
for rrl in &self.response_r_l {
|
||||
bytes.extend_from_slice(&rrl.to_bytes());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&rl_len_bytes);
|
||||
for rl in &self.response_l {
|
||||
bytes.extend_from_slice(&rl.to_bytes());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&roa_len_bytes);
|
||||
for roa in &self.response_o_a {
|
||||
bytes.extend_from_slice(&roa.to_bytes());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&rmu_len_bytes);
|
||||
for rmu in &self.response_mu {
|
||||
bytes.extend_from_slice(&rmu.to_bytes());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&romu_len_bytes);
|
||||
for romu in &self.response_o_mu {
|
||||
bytes.extend_from_slice(&romu.to_bytes());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&rattributes_len_bytes);
|
||||
for rattr in &self.response_attributes {
|
||||
bytes.extend_from_slice(&rattr.to_bytes());
|
||||
}
|
||||
|
||||
bytes
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for SpendProof {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<SpendProof> {
|
||||
if bytes.len() < 336 || (bytes.len() - 96 - 48) % 32 != 0 {
|
||||
return Err(CompactEcashError::Deserialization(
|
||||
"tried to deserialize proof of spending 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_r_bytes = bytes[idx..idx + 32].try_into().unwrap();
|
||||
idx += 32;
|
||||
let response_o_c_bytes = bytes[idx..idx + 32].try_into().unwrap();
|
||||
idx += 32;
|
||||
|
||||
let challenge = try_deserialize_scalar(
|
||||
&challenge_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize challenge".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r = try_deserialize_scalar(
|
||||
&response_r_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize response_r".to_string()),
|
||||
)?;
|
||||
|
||||
let response_o_c = try_deserialize_scalar(
|
||||
&response_o_c_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize response_o_c".to_string()),
|
||||
)?;
|
||||
|
||||
let rrl_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
|
||||
idx += 8;
|
||||
if bytes[idx..].len() < rrl_len as usize * 32 {
|
||||
return Err(
|
||||
CompactEcashError::Deserialization(
|
||||
"tried to deserialize response_r_l".to_string()),
|
||||
);
|
||||
}
|
||||
let rrl_end = idx + rrl_len as usize * 32;
|
||||
let response_r_l = try_deserialize_scalar_vec(
|
||||
rrl_len,
|
||||
&bytes[idx..rrl_end],
|
||||
CompactEcashError::Deserialization("Failed to deserialize response_r_l".to_string()),
|
||||
)?;
|
||||
|
||||
let rl_len = u64::from_le_bytes(bytes[rrl_end..rrl_end + 8].try_into().unwrap());
|
||||
let response_l_start = rrl_end + 8;
|
||||
if bytes[response_l_start..].len() < rl_len as usize * 32 {
|
||||
return Err(CompactEcashError::Deserialization(
|
||||
"tried to deserialize response_l".to_string(),
|
||||
));
|
||||
}
|
||||
let rl_end = response_l_start + rl_len as usize * 32;
|
||||
let response_l = try_deserialize_scalar_vec(
|
||||
rl_len,
|
||||
&bytes[response_l_start..rl_end],
|
||||
CompactEcashError::Deserialization("Failed to deserialize response_l".to_string()),
|
||||
)?;
|
||||
|
||||
let roa_len = u64::from_le_bytes(bytes[rl_end..rl_end + 8].try_into().unwrap());
|
||||
let roa_end = rl_end + 8;
|
||||
if bytes[roa_end..].len() < roa_len as usize * 32 {
|
||||
return Err(CompactEcashError::Deserialization(
|
||||
"tried to deserialize response_o_a".to_string(),
|
||||
));
|
||||
}
|
||||
let roa_end = roa_end + roa_len as usize * 32;
|
||||
let response_o_a = try_deserialize_scalar_vec(
|
||||
roa_len,
|
||||
&bytes[rl_end + 8..roa_end],
|
||||
CompactEcashError::Deserialization("Failed to deserialize response_o_a".to_string()),
|
||||
)?;
|
||||
|
||||
let response_mu_len = u64::from_le_bytes(bytes[roa_end..roa_end + 8].try_into().unwrap());
|
||||
let response_mu_end = roa_end + 8;
|
||||
if bytes[response_mu_end..].len() < response_mu_len as usize * 32 {
|
||||
return Err(CompactEcashError::Deserialization(
|
||||
"tried to deserialize response_mu".to_string(),
|
||||
));
|
||||
}
|
||||
let response_mu_end = response_mu_end + response_mu_len as usize * 32;
|
||||
let response_mu = try_deserialize_scalar_vec(
|
||||
response_mu_len,
|
||||
&bytes[roa_end + 8..response_mu_end],
|
||||
CompactEcashError::Deserialization("Failed to deserialize response_mu".to_string()),
|
||||
)?;
|
||||
|
||||
let response_o_mu_len = u64::from_le_bytes(bytes[response_mu_end..response_mu_end + 8].try_into().unwrap());
|
||||
let response_o_mu_end = response_mu_end + 8;
|
||||
if bytes[response_o_mu_end..].len() < response_o_mu_len as usize * 32 {
|
||||
return Err(CompactEcashError::Deserialization(
|
||||
"tried to deserialize response_o_mu".to_string(),
|
||||
));
|
||||
}
|
||||
let response_o_mu_end = response_o_mu_end + response_o_mu_len as usize * 32;
|
||||
let response_o_mu = try_deserialize_scalar_vec(
|
||||
response_o_mu_len,
|
||||
&bytes[response_mu_end + 8..response_o_mu_end],
|
||||
CompactEcashError::Deserialization("Failed to deserialize response_o_mu".to_string()),
|
||||
)?;
|
||||
|
||||
let response_attributes_len = u64::from_le_bytes(bytes[response_o_mu_end..response_o_mu_end + 8].try_into().unwrap());
|
||||
let response_attributes_end = response_o_mu_end + 8;
|
||||
if bytes[response_attributes_end..].len() < response_attributes_len as usize * 32 {
|
||||
return Err(CompactEcashError::Deserialization(
|
||||
"tried to deserialize response_attributes".to_string(),
|
||||
));
|
||||
}
|
||||
let response_attributes_end = response_attributes_end + response_attributes_len as usize * 32;
|
||||
let response_attributes = try_deserialize_scalar_vec(
|
||||
response_attributes_len,
|
||||
&bytes[response_o_mu_end + 8..response_attributes_end],
|
||||
CompactEcashError::Deserialization("Failed to deserialize response_attributes".to_string()),
|
||||
)?;
|
||||
|
||||
|
||||
// Construct the SpendProof struct from the deserialized data
|
||||
let spend_proof = SpendProof {
|
||||
challenge,
|
||||
response_r,
|
||||
response_o_c,
|
||||
response_r_l,
|
||||
response_l,
|
||||
response_o_a,
|
||||
response_mu,
|
||||
response_o_mu,
|
||||
response_attributes,
|
||||
};
|
||||
|
||||
Ok(spend_proof)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bls12_381::{G2Projective, Scalar};
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness};
|
||||
use crate::scheme::{pseudorandom_f_delta_v, pseudorandom_f_g_v};
|
||||
use crate::scheme::aggregation::aggregate_verification_keys;
|
||||
use crate::scheme::keygen::{PublicKeyUser, ttp_keygen, VerificationKeyAuth};
|
||||
use crate::scheme::PayInfo;
|
||||
use crate::scheme::setup::setup;
|
||||
use crate::utils::hash_to_scalar;
|
||||
|
||||
#[test]
|
||||
fn spend_proof_construct_and_verify() {
|
||||
let _rng = thread_rng();
|
||||
let L = 32;
|
||||
let params = setup(L);
|
||||
let grparams = params.grp();
|
||||
let sk = grparams.random_scalar();
|
||||
let _pk_user = PublicKeyUser {
|
||||
pk: grparams.gen1() * sk,
|
||||
};
|
||||
let authorities_keypairs = ttp_keygen(&grparams, 2, 3).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
let v = grparams.random_scalar();
|
||||
let t = grparams.random_scalar();
|
||||
let attributes = vec![sk, v, t];
|
||||
// the below value must be from range 0 to params.L()
|
||||
let l = 5;
|
||||
let gamma1 = *grparams.gamma1();
|
||||
let g1 = *grparams.gen1();
|
||||
|
||||
let r = grparams.random_scalar();
|
||||
let kappa = grparams.gen2() * r
|
||||
+ verification_key.alpha
|
||||
+ attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
let o_a = grparams.random_scalar();
|
||||
let o_c = grparams.random_scalar();
|
||||
|
||||
// compute commitments A, C, D
|
||||
let aa = g1 * o_a + gamma1 * Scalar::from(l);
|
||||
let cc = g1 * o_c + gamma1 * v;
|
||||
|
||||
// compute hash of the payment info
|
||||
let pay_info = PayInfo { info: [37u8; 32] };
|
||||
let rr = hash_to_scalar(pay_info.info);
|
||||
|
||||
// evaluate the pseudorandom functions
|
||||
let ss = pseudorandom_f_delta_v(&grparams, v, l);
|
||||
let tt = g1 * sk + pseudorandom_f_g_v(&grparams, v, l) * rr;
|
||||
|
||||
// compute values mu, o_mu, lambda, o_lambda
|
||||
let mu: Scalar = (v + Scalar::from(l) + Scalar::from(1)).invert().unwrap();
|
||||
let o_mu = ((o_a + o_c) * mu).neg();
|
||||
|
||||
// parse the signature associated with value l
|
||||
let sign_l = params.get_sign_by_idx(l).unwrap();
|
||||
// randomise the signature associated with value l
|
||||
let (_sign_l_prime, r_l) = sign_l.randomise(grparams);
|
||||
// compute kappa_l
|
||||
let kappa_k =
|
||||
grparams.gen2() * r_l + params.pk_rp().alpha + params.pk_rp().beta * Scalar::from(l);
|
||||
|
||||
let instance = SpendInstance {
|
||||
kappa,
|
||||
aa: vec![aa],
|
||||
cc,
|
||||
ss: vec![ss],
|
||||
tt: vec![tt],
|
||||
kappa_k: vec![kappa_k],
|
||||
};
|
||||
|
||||
let witness = SpendWitness {
|
||||
attributes,
|
||||
r,
|
||||
o_c,
|
||||
lk: vec![Scalar::from(l)],
|
||||
o_a: vec![o_a],
|
||||
mu: vec![mu],
|
||||
o_mu: vec![o_mu],
|
||||
r_k: vec![r_l],
|
||||
};
|
||||
|
||||
let zk_proof = SpendProof::construct(¶ms, &instance, &witness, &verification_key, &[rr]);
|
||||
assert!(zk_proof.verify(¶ms, &instance, &verification_key, &[rr]));
|
||||
|
||||
let zk_proof_bytes = zk_proof.to_bytes();
|
||||
let zk_proof2 = SpendProof::try_from(zk_proof_bytes.as_slice()).unwrap();
|
||||
assert_eq!(zk_proof, zk_proof2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
use bls12_381::{G1Projective, Scalar};
|
||||
use group::GroupEncoding;
|
||||
use itertools::izip;
|
||||
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::proofs::{ChallengeDigest, compute_challenge, produce_response, produce_responses};
|
||||
use crate::scheme::keygen::PublicKeyUser;
|
||||
use crate::scheme::setup::GroupParameters;
|
||||
use crate::utils::{try_deserialize_g1_projective, try_deserialize_scalar_vec, try_deserialize_scalar};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
// instance: g, gamma1, gamma2, gamma3, com, h, com1, com2, com3, pkUser
|
||||
pub struct WithdrawalReqInstance {
|
||||
// Joined commitment to all attributes
|
||||
pub com: G1Projective,
|
||||
// Hash of the joined commitment com
|
||||
pub h: G1Projective,
|
||||
// Pedersen commitments to each attribute
|
||||
pub pc_coms: Vec<G1Projective>,
|
||||
// Public key of a user
|
||||
pub pk_user: PublicKeyUser,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for WithdrawalReqInstance {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<WithdrawalReqInstance> {
|
||||
if bytes.len() < 48 * 4 + 8 || (bytes.len() - 8) % 48 != 0 {
|
||||
return Err(CompactEcashError::DeserializationInvalidLength {
|
||||
actual: bytes.len(),
|
||||
modulus_target: bytes.len() - 8,
|
||||
target: 48 * 4 + 8,
|
||||
modulus: 48,
|
||||
object: "withdrawal request zkp instance".to_string(),
|
||||
});
|
||||
}
|
||||
let com_bytes: [u8; 48] = bytes[..48].try_into().unwrap();
|
||||
let com = try_deserialize_g1_projective(
|
||||
&com_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize com".to_string()),
|
||||
)?;
|
||||
let h_bytes: [u8; 48] = bytes[48..96].try_into().unwrap();
|
||||
let h = try_deserialize_g1_projective(
|
||||
&h_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize h".to_string()),
|
||||
)?;
|
||||
let pc_coms_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
|
||||
let actual_pc_coms_len = (bytes.len() - 152) / 48;
|
||||
if pc_coms_len as usize != actual_pc_coms_len {
|
||||
return Err(CompactEcashError::Deserialization(format!(
|
||||
"Tried to deserialize pedersen commitments with inconsistent pc_coms_len (expected {}, got {})",
|
||||
pc_coms_len, actual_pc_coms_len
|
||||
)));
|
||||
}
|
||||
let mut pc_coms = Vec::new();
|
||||
let mut pc_coms_end: usize = 0;
|
||||
for i in 0..pc_coms_len {
|
||||
let start = (104 + i * 48) as usize;
|
||||
let end = (start + 48) as usize;
|
||||
let pc_i_bytes = bytes[start..end].try_into().unwrap();
|
||||
let pc_i = try_deserialize_g1_projective(
|
||||
&pc_i_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize pedersen commitment".to_string(),
|
||||
),
|
||||
)?;
|
||||
pc_coms_end = end;
|
||||
pc_coms.push(pc_i);
|
||||
}
|
||||
let pk_bytes = bytes[pc_coms_end..].try_into().unwrap();
|
||||
let pk = try_deserialize_g1_projective(
|
||||
&pk_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize user's public key".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
Ok(WithdrawalReqInstance {
|
||||
com,
|
||||
h,
|
||||
pc_coms,
|
||||
pk_user: PublicKeyUser { pk },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl WithdrawalReqInstance {
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let pc_coms_len = self.pc_coms.len();
|
||||
let mut bytes = Vec::with_capacity(8 + (pc_coms_len + 3) as usize * 48);
|
||||
bytes.extend_from_slice(self.com.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.h.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(&pc_coms_len.to_le_bytes());
|
||||
for pc in self.pc_coms.iter() {
|
||||
bytes.extend_from_slice((pc.to_bytes()).as_ref());
|
||||
}
|
||||
bytes.extend_from_slice(self.pk_user.pk.to_bytes().as_ref());
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<WithdrawalReqInstance> {
|
||||
WithdrawalReqInstance::try_from(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
// witness: m1, m2, m3, o, o1, o2, o3,
|
||||
pub struct WithdrawalReqWitness {
|
||||
pub attributes: Vec<Scalar>,
|
||||
// Opening for the joined commitment com
|
||||
pub com_opening: Scalar,
|
||||
// Openings for the pedersen commitments
|
||||
pub pc_coms_openings: Vec<Scalar>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct WithdrawalReqProof {
|
||||
challenge: Scalar,
|
||||
response_opening: Scalar,
|
||||
response_openings: Vec<Scalar>,
|
||||
response_attributes: Vec<Scalar>,
|
||||
}
|
||||
|
||||
impl WithdrawalReqProof {
|
||||
pub(crate) fn construct(
|
||||
params: &GroupParameters,
|
||||
instance: &WithdrawalReqInstance,
|
||||
witness: &WithdrawalReqWitness,
|
||||
) -> Self {
|
||||
// generate random values to replace the witnesses
|
||||
let r_com_opening = params.random_scalar();
|
||||
let r_pedcom_openings = params.n_random_scalars(witness.pc_coms_openings.len());
|
||||
let r_attributes = params.n_random_scalars(witness.attributes.len());
|
||||
|
||||
// compute zkp commitments for each instance
|
||||
let zkcm_com = params.gen1() * r_com_opening
|
||||
+ r_attributes
|
||||
.iter()
|
||||
.zip(params.gammas().iter())
|
||||
.map(|(rm_i, gamma_i)| gamma_i * rm_i)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
let zkcm_pedcom = r_pedcom_openings
|
||||
.iter()
|
||||
.zip(r_attributes.iter())
|
||||
.map(|(o_j, m_j)| params.gen1() * o_j + instance.h * m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_user_sk = params.gen1() * r_attributes[0];
|
||||
|
||||
// covert to bytes
|
||||
let gammas_bytes = params
|
||||
.gammas()
|
||||
.iter()
|
||||
.map(|gamma| gamma.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_pedcom_bytes = zkcm_pedcom
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// compute zkp challenge using g1, gammas, c, h, c1, c2, c3, zk commitments
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(params.gen1().to_bytes().as_ref())
|
||||
.chain(gammas_bytes.iter().map(|gamma| gamma.as_ref()))
|
||||
.chain(std::iter::once(instance.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_com.to_bytes().as_ref()))
|
||||
.chain(zkcm_pedcom_bytes.iter().map(|c| c.as_ref()))
|
||||
.chain(std::iter::once(zkcm_user_sk.to_bytes().as_ref())),
|
||||
);
|
||||
|
||||
// compute response
|
||||
let response_opening = produce_response(&r_com_opening, &challenge, &witness.com_opening);
|
||||
let response_openings = produce_responses(
|
||||
&r_pedcom_openings,
|
||||
&challenge,
|
||||
&witness.pc_coms_openings.iter().collect::<Vec<_>>(),
|
||||
);
|
||||
let response_attributes = produce_responses(
|
||||
&r_attributes,
|
||||
&challenge,
|
||||
&witness.attributes.iter().collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
WithdrawalReqProof {
|
||||
challenge,
|
||||
response_opening,
|
||||
response_openings,
|
||||
response_attributes,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn verify(
|
||||
&self,
|
||||
params: &GroupParameters,
|
||||
instance: &WithdrawalReqInstance,
|
||||
) -> bool {
|
||||
// recompute zk commitments for each instance
|
||||
let zkcm_com = instance.com * self.challenge
|
||||
+ params.gen1() * self.response_opening
|
||||
+ self
|
||||
.response_attributes
|
||||
.iter()
|
||||
.zip(params.gammas().iter())
|
||||
.map(|(m_i, gamma_i)| gamma_i * m_i)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
let zkcm_pedcom = izip!(
|
||||
instance.pc_coms.iter(),
|
||||
self.response_openings.iter(),
|
||||
self.response_attributes.iter()
|
||||
)
|
||||
.map(|(cm_j, resp_o_j, resp_m_j)| {
|
||||
cm_j * self.challenge + params.gen1() * resp_o_j + instance.h * resp_m_j
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zk_commitment_user_sk =
|
||||
instance.pk_user.pk * self.challenge + params.gen1() * self.response_attributes[0];
|
||||
|
||||
// covert to bytes
|
||||
let gammas_bytes = params
|
||||
.gammas()
|
||||
.iter()
|
||||
.map(|gamma| gamma.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_pedcom_bytes = zkcm_pedcom
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// recompute zkp challenge
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(params.gen1().to_bytes().as_ref())
|
||||
.chain(gammas_bytes.iter().map(|hs| hs.as_ref()))
|
||||
.chain(std::iter::once(instance.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_com.to_bytes().as_ref()))
|
||||
.chain(zkcm_pedcom_bytes.iter().map(|c| c.as_ref()))
|
||||
.chain(std::iter::once(zk_commitment_user_sk.to_bytes().as_ref())),
|
||||
);
|
||||
|
||||
challenge == self.challenge
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8>{
|
||||
let challenge_bytes = self.challenge.to_bytes();
|
||||
let response_opening_bytes = self.response_opening.to_bytes();
|
||||
let ro_len = self.response_openings.len() as u64;
|
||||
let ra_len = self.response_attributes.len() as u64;
|
||||
|
||||
let mut bytes = Vec::with_capacity(32 + 32 + 8 + ro_len as usize * 32 + 8 + ra_len as usize * 32);
|
||||
bytes.extend_from_slice(&challenge_bytes);
|
||||
bytes.extend_from_slice(&response_opening_bytes);
|
||||
bytes.extend_from_slice(&ro_len.to_le_bytes());
|
||||
for ro in &self.response_openings {
|
||||
bytes.extend_from_slice(&ro.to_bytes());
|
||||
}
|
||||
bytes.extend_from_slice(&ra_len.to_le_bytes());
|
||||
for ra in &self.response_attributes {
|
||||
bytes.extend_from_slice(&ra.to_bytes());
|
||||
}
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for WithdrawalReqProof {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<WithdrawalReqProof> {
|
||||
if bytes.len() < 32 + 32 + 16 + 32 + 32 || (bytes.len() - 16) % 32 != 0 {
|
||||
return Err(CompactEcashError::Deserialization(
|
||||
"tried to deserialize proof of withdrawal 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 challenge = try_deserialize_scalar(
|
||||
&challenge_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize challenge".to_string()),
|
||||
)?;
|
||||
|
||||
let response_opening = try_deserialize_scalar(
|
||||
&response_opening_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize the response to the random".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
let ro_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
|
||||
idx += 8;
|
||||
if bytes[idx..].len() < ro_len as usize * 32 + 8 {
|
||||
return Err(
|
||||
CompactEcashError::Deserialization(
|
||||
"tried to deserialize response openings".to_string()),
|
||||
);
|
||||
}
|
||||
let ro_end = idx + ro_len as usize * 32;
|
||||
let response_openings = try_deserialize_scalar_vec(
|
||||
ro_len,
|
||||
&bytes[idx..ro_end],
|
||||
CompactEcashError::Deserialization("Failed to deserialize openings response".to_string()),
|
||||
)?;
|
||||
|
||||
let ra_len = u64::from_le_bytes(bytes[ro_end..ro_end + 8].try_into().unwrap());
|
||||
let response_attributes = try_deserialize_scalar_vec(
|
||||
ra_len,
|
||||
&bytes[ro_end + 8..],
|
||||
CompactEcashError::Deserialization("Failed to deserialize attributes response".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(WithdrawalReqProof{
|
||||
challenge,
|
||||
response_opening,
|
||||
response_openings,
|
||||
response_attributes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use group::Group;
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::utils::hash_g1;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn withdrawal_request_instance_roundtrip() {
|
||||
let mut rng = thread_rng();
|
||||
let params = GroupParameters::new().unwrap();
|
||||
let instance = WithdrawalReqInstance {
|
||||
com: G1Projective::random(&mut rng),
|
||||
h: G1Projective::random(&mut rng),
|
||||
pc_coms: vec![
|
||||
G1Projective::random(&mut rng),
|
||||
G1Projective::random(&mut rng),
|
||||
G1Projective::random(&mut rng),
|
||||
],
|
||||
pk_user: PublicKeyUser {
|
||||
pk: params.gen1() * params.random_scalar(),
|
||||
},
|
||||
};
|
||||
|
||||
let instance_bytes = instance.to_bytes();
|
||||
let instance_p = WithdrawalReqInstance::from_bytes(&instance_bytes).unwrap();
|
||||
assert_eq!(instance, instance_p)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn withdrawal_proof_construct_and_verify() {
|
||||
let _rng = thread_rng();
|
||||
let params = GroupParameters::new().unwrap();
|
||||
let sk = params.random_scalar();
|
||||
let pk_user = PublicKeyUser {
|
||||
pk: params.gen1() * sk,
|
||||
};
|
||||
let v = params.random_scalar();
|
||||
let t = params.random_scalar();
|
||||
let attr = vec![sk, v, t];
|
||||
|
||||
let com_opening = params.random_scalar();
|
||||
let com = params.gen1() * com_opening
|
||||
+ attr
|
||||
.iter()
|
||||
.zip(params.gammas())
|
||||
.map(|(&m, gamma)| gamma * m)
|
||||
.sum::<G1Projective>();
|
||||
let h = hash_g1(com.to_bytes());
|
||||
|
||||
let pc_openings = params.n_random_scalars(attr.len());
|
||||
let pc_coms = pc_openings
|
||||
.iter()
|
||||
.zip(attr.iter())
|
||||
.map(|(o_j, m_j)| params.gen1() * o_j + h * m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance = WithdrawalReqInstance {
|
||||
com,
|
||||
h,
|
||||
pc_coms,
|
||||
pk_user,
|
||||
};
|
||||
|
||||
let witness = WithdrawalReqWitness {
|
||||
attributes: attr,
|
||||
com_opening,
|
||||
pc_coms_openings: pc_openings,
|
||||
};
|
||||
let zk_proof = WithdrawalReqProof::construct(¶ms, &instance, &witness);
|
||||
assert!(zk_proof.verify(¶ms, &instance))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
use core::iter::Sum;
|
||||
use core::ops::Mul;
|
||||
use std::cell::Cell;
|
||||
|
||||
use bls12_381::{G2Prepared, G2Projective, Scalar};
|
||||
use group::Curve;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::Attribute;
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::scheme::{PartialWallet, Wallet};
|
||||
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
|
||||
use crate::scheme::setup::GroupParameters;
|
||||
use crate::scheme::withdrawal::RequestInfo;
|
||||
use crate::utils::{
|
||||
check_bilinear_pairing, PartialSignature, perform_lagrangian_interpolation_at_origin,
|
||||
Signature, SignatureShare, SignerIndex,
|
||||
};
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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(CompactEcashError::Aggregation(
|
||||
"Empty set of values".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(indices) = indices {
|
||||
if !Self::check_unique_indices(indices) {
|
||||
return Err(CompactEcashError::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(|| CompactEcashError::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: &[VerificationKeyAuth]) -> bool {
|
||||
keys.iter().map(|vk| vk.beta_g1.len()).all_equal()
|
||||
&& keys.iter().map(|vk| vk.beta_g2.len()).all_equal()
|
||||
}
|
||||
|
||||
pub fn aggregate_verification_keys(
|
||||
keys: &[VerificationKeyAuth],
|
||||
indices: Option<&[SignerIndex]>,
|
||||
) -> Result<VerificationKeyAuth> {
|
||||
if !check_same_key_size(keys) {
|
||||
return Err(CompactEcashError::Aggregation(
|
||||
"Verification keys are of different sizes".to_string(),
|
||||
));
|
||||
}
|
||||
Aggregatable::aggregate(keys, indices)
|
||||
}
|
||||
|
||||
pub fn aggregate_signature_shares(
|
||||
params: &GroupParameters,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
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),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn aggregate_signatures(
|
||||
params: &GroupParameters,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
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_g2.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(CompactEcashError::Aggregation(
|
||||
"Verification of the aggregated signature failed".to_string(),
|
||||
));
|
||||
}
|
||||
Ok(signature)
|
||||
}
|
||||
|
||||
pub fn aggregate_wallets(
|
||||
params: &GroupParameters,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
sk_user: &SecretKeyUser,
|
||||
wallets: &[PartialWallet],
|
||||
req_info: &RequestInfo,
|
||||
) -> Result<Wallet> {
|
||||
// Aggregate partial wallets
|
||||
let signature_shares: Vec<SignatureShare> = wallets
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, wallet)| SignatureShare::new(*wallet.signature(), (idx + 1) as u64))
|
||||
.collect();
|
||||
|
||||
let attributes = vec![sk_user.sk, req_info.get_v()];
|
||||
let aggregated_signature =
|
||||
aggregate_signature_shares(¶ms, &verification_key, &attributes, &signature_shares)?;
|
||||
|
||||
Ok(Wallet {
|
||||
sig: aggregated_signature,
|
||||
v: req_info.get_v(),
|
||||
l: Cell::new(0),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
use crate::{PayInfo, VerificationKeyAuth};
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::scheme::keygen::PublicKeyUser;
|
||||
use crate::scheme::Payment;
|
||||
use crate::scheme::setup::Parameters;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum IdentifyResult {
|
||||
NotADuplicatePayment,
|
||||
DuplicatePayInfo(PayInfo),
|
||||
DoubleSpendingPublicKeys(PublicKeyUser),
|
||||
}
|
||||
|
||||
pub fn identify(params: &Parameters, verification_key: &VerificationKeyAuth, payment1: Payment, payment2: Payment, pay_info1: PayInfo, pay_info2: PayInfo) -> Result<IdentifyResult> {
|
||||
let mut k = 0;
|
||||
let mut j = 0;
|
||||
for (id1, pay1_ss) in payment1.ss.iter().enumerate() {
|
||||
for (id2, pay2_ss) in payment2.ss.iter().enumerate() {
|
||||
if pay1_ss == pay2_ss {
|
||||
k = id1;
|
||||
j = id2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if payment1.ss.iter().any(|pay1_ss| payment2.ss.contains(pay1_ss)) {
|
||||
if pay_info1 == pay_info2 {
|
||||
Ok(IdentifyResult::DuplicatePayInfo(pay_info1))
|
||||
} else {
|
||||
let rr_diff = payment1.rr[k] - payment2.rr[j];
|
||||
let pk = (payment2.tt[j] * payment1.rr[k] - payment1.tt[k] * payment2.rr[j]) * rr_diff.invert().unwrap();
|
||||
let pk_user = PublicKeyUser { pk };
|
||||
Ok(IdentifyResult::DoubleSpendingPublicKeys(pk_user))
|
||||
}
|
||||
} else {
|
||||
Ok(IdentifyResult::NotADuplicatePayment)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use itertools::izip;
|
||||
|
||||
use crate::{aggregate_verification_keys, aggregate_wallets, generate_keypair_user, issue_verify, issue_wallet, PartialWallet, PayInfo, ttp_keygen, VerificationKeyAuth, withdrawal_request};
|
||||
use crate::scheme::identify::{identify, IdentifyResult};
|
||||
use crate::scheme::keygen::{PublicKeyUser, SecretKeyUser};
|
||||
use crate::scheme::setup::setup;
|
||||
|
||||
#[test]
|
||||
fn duplicate_payments_with_the_same_pay_info() {
|
||||
let L = 32;
|
||||
let params = setup(L);
|
||||
let grparams = params.grp();
|
||||
let user_keypair = generate_keypair_user(&grparams);
|
||||
|
||||
let (req, req_info) = withdrawal_request(grparams, &user_keypair.secret_key()).unwrap();
|
||||
let authorities_keypairs = ttp_keygen(&grparams, 2, 3).unwrap();
|
||||
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key = aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
let mut wallet_blinded_signatures = Vec::new();
|
||||
for auth_keypair in authorities_keypairs {
|
||||
let blind_signature = issue_wallet(
|
||||
&grparams,
|
||||
auth_keypair.secret_key(),
|
||||
user_keypair.public_key(),
|
||||
&req,
|
||||
);
|
||||
wallet_blinded_signatures.push(blind_signature.unwrap());
|
||||
}
|
||||
|
||||
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
|
||||
wallet_blinded_signatures.iter(),
|
||||
verification_keys_auth.iter()
|
||||
)
|
||||
.map(|(w, vk)| issue_verify(&grparams, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
|
||||
.collect();
|
||||
|
||||
// Aggregate partial wallets
|
||||
let aggr_wallet = aggregate_wallets(
|
||||
&grparams,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&unblinded_wallet_shares,
|
||||
&req_info,
|
||||
).unwrap();
|
||||
|
||||
// Let's try to spend some coins
|
||||
let pay_info1 = PayInfo { info: [6u8; 32] };
|
||||
let spend_vv = 1;
|
||||
|
||||
let (payment1, _upd_wallet) = aggr_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info1,
|
||||
false,
|
||||
spend_vv,
|
||||
).unwrap();
|
||||
|
||||
assert!(payment1
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
let payment2 = payment1.clone();
|
||||
assert!(payment2
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
let pay_info2 = pay_info1.clone();
|
||||
let identify_result = identify(¶ms, &verification_key, payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::DuplicatePayInfo(pay_info1.clone()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_if_two_different_payments() {
|
||||
let L = 32;
|
||||
let params = setup(L);
|
||||
let grparams = params.grp();
|
||||
let user_keypair = generate_keypair_user(&grparams);
|
||||
|
||||
let (req, req_info) = withdrawal_request(grparams, &user_keypair.secret_key()).unwrap();
|
||||
let authorities_keypairs = ttp_keygen(&grparams, 2, 3).unwrap();
|
||||
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key = aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
let mut wallet_blinded_signatures = Vec::new();
|
||||
for auth_keypair in authorities_keypairs {
|
||||
let blind_signature = issue_wallet(
|
||||
&grparams,
|
||||
auth_keypair.secret_key(),
|
||||
user_keypair.public_key(),
|
||||
&req,
|
||||
);
|
||||
wallet_blinded_signatures.push(blind_signature.unwrap());
|
||||
}
|
||||
|
||||
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
|
||||
wallet_blinded_signatures.iter(),
|
||||
verification_keys_auth.iter()
|
||||
)
|
||||
.map(|(w, vk)| issue_verify(&grparams, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
|
||||
.collect();
|
||||
|
||||
// Aggregate partial wallets
|
||||
let aggr_wallet = aggregate_wallets(
|
||||
&grparams,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&unblinded_wallet_shares,
|
||||
&req_info,
|
||||
).unwrap();
|
||||
|
||||
// Let's try to spend some coins
|
||||
let pay_info1 = PayInfo { info: [6u8; 32] };
|
||||
let spend_vv = 1;
|
||||
|
||||
let (payment1, upd_wallet) = aggr_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info1,
|
||||
false,
|
||||
spend_vv,
|
||||
).unwrap();
|
||||
|
||||
assert!(payment1
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
|
||||
let pay_info2 = PayInfo { info: [7u8; 32] };
|
||||
let (payment2, _) = upd_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info2,
|
||||
false,
|
||||
spend_vv,
|
||||
).unwrap();
|
||||
|
||||
assert!(payment2
|
||||
.spend_verify(¶ms, &verification_key, &pay_info2)
|
||||
.unwrap());
|
||||
|
||||
let identify_result = identify(¶ms, &verification_key, payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::NotADuplicatePayment);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_payments_with_one_repeating_serial_number_but_different_pay_info() {
|
||||
let L = 32;
|
||||
let params = setup(L);
|
||||
let grp = params.grp();
|
||||
let user_keypair = generate_keypair_user(&grp);
|
||||
|
||||
// GENERATE KEYS FOR OTHER USERS
|
||||
let mut public_keys: Vec<PublicKeyUser> = Default::default();
|
||||
for _i in 0..50 {
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = sk_user.public_key(&grp);
|
||||
public_keys.push(pk_user.clone());
|
||||
}
|
||||
public_keys.push(user_keypair.public_key().clone());
|
||||
|
||||
|
||||
let (req, req_info) = withdrawal_request(grp, &user_keypair.secret_key()).unwrap();
|
||||
let authorities_keypairs = ttp_keygen(&grp, 2, 3).unwrap();
|
||||
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key = aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
let mut wallet_blinded_signatures = Vec::new();
|
||||
for auth_keypair in authorities_keypairs {
|
||||
let blind_signature = issue_wallet(
|
||||
&grp,
|
||||
auth_keypair.secret_key(),
|
||||
user_keypair.public_key(),
|
||||
&req,
|
||||
);
|
||||
wallet_blinded_signatures.push(blind_signature.unwrap());
|
||||
}
|
||||
|
||||
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
|
||||
wallet_blinded_signatures.iter(),
|
||||
verification_keys_auth.iter()
|
||||
)
|
||||
.map(|(w, vk)| issue_verify(&grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
|
||||
.collect();
|
||||
|
||||
// Aggregate partial wallets
|
||||
let aggr_wallet = aggregate_wallets(
|
||||
&grp,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&unblinded_wallet_shares,
|
||||
&req_info,
|
||||
).unwrap();
|
||||
|
||||
// Let's try to spend some coins
|
||||
let pay_info1 = PayInfo { info: [6u8; 32] };
|
||||
let spend_vv = 1;
|
||||
|
||||
let (payment1, _upd_wallet) = aggr_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info1,
|
||||
false,
|
||||
spend_vv,
|
||||
).unwrap();
|
||||
|
||||
assert!(payment1
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
// let's reverse the spending counter in the wallet to create a double spending payment
|
||||
let current_l = aggr_wallet.l.get();
|
||||
aggr_wallet.l.set(current_l - 1);
|
||||
|
||||
let pay_info2 = PayInfo { info: [7u8; 32] };
|
||||
|
||||
let (payment2, _) = aggr_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info2,
|
||||
false,
|
||||
spend_vv,
|
||||
).unwrap();
|
||||
|
||||
assert!(payment2
|
||||
.spend_verify(¶ms, &verification_key, &pay_info2)
|
||||
.unwrap());
|
||||
|
||||
let identify_result = identify(¶ms, &verification_key, payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_payments_with_multiple_repeating_serial_numbers_but_different_pay_info() {
|
||||
let L = 32;
|
||||
let params = setup(L);
|
||||
let grp = params.grp();
|
||||
let user_keypair = generate_keypair_user(&grp);
|
||||
|
||||
// GENERATE KEYS FOR OTHER USERS
|
||||
let mut public_keys: Vec<PublicKeyUser> = Default::default();
|
||||
for i in 0..50 {
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = sk_user.public_key(&grp);
|
||||
public_keys.push(pk_user.clone());
|
||||
}
|
||||
public_keys.push(user_keypair.public_key().clone());
|
||||
|
||||
let (req, req_info) = withdrawal_request(grp, &user_keypair.secret_key()).unwrap();
|
||||
let authorities_keypairs = ttp_keygen(&grp, 2, 3).unwrap();
|
||||
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key = aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
let mut wallet_blinded_signatures = Vec::new();
|
||||
for auth_keypair in authorities_keypairs {
|
||||
let blind_signature = issue_wallet(
|
||||
&grp,
|
||||
auth_keypair.secret_key(),
|
||||
user_keypair.public_key(),
|
||||
&req,
|
||||
);
|
||||
wallet_blinded_signatures.push(blind_signature.unwrap());
|
||||
}
|
||||
|
||||
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
|
||||
wallet_blinded_signatures.iter(),
|
||||
verification_keys_auth.iter()
|
||||
)
|
||||
.map(|(w, vk)| issue_verify(&grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
|
||||
.collect();
|
||||
|
||||
// Aggregate partial wallets
|
||||
let aggr_wallet = aggregate_wallets(
|
||||
&grp,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&unblinded_wallet_shares,
|
||||
&req_info,
|
||||
).unwrap();
|
||||
|
||||
// Let's try to spend some coins
|
||||
let pay_info1 = PayInfo { info: [6u8; 32] };
|
||||
let spend_vv = 10;
|
||||
|
||||
let (payment1, _) = aggr_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info1,
|
||||
false,
|
||||
spend_vv,
|
||||
).unwrap();
|
||||
|
||||
assert!(payment1
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
// let's reverse the spending counter in the wallet to create a double spending payment
|
||||
let current_l = aggr_wallet.l.get();
|
||||
aggr_wallet.l.set(current_l - 10);
|
||||
|
||||
let pay_info2 = PayInfo { info: [7u8; 32] };
|
||||
let (payment2, _) = aggr_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info2,
|
||||
false,
|
||||
spend_vv,
|
||||
).unwrap();
|
||||
|
||||
|
||||
let identify_result = identify(¶ms, &verification_key, payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,442 @@
|
||||
use core::borrow::Borrow;
|
||||
use core::iter::Sum;
|
||||
use core::ops::{Add, Mul};
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
|
||||
use bls12_381::{G1Projective, G2Projective, Scalar};
|
||||
use group::Curve;
|
||||
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::scheme::aggregation::aggregate_verification_keys;
|
||||
use crate::scheme::setup::GroupParameters;
|
||||
use crate::scheme::SignerIndex;
|
||||
use crate::utils::{
|
||||
try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar,
|
||||
try_deserialize_scalar_vec,
|
||||
};
|
||||
use crate::utils::Polynomial;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct SecretKeyAuth {
|
||||
pub(crate) x: Scalar,
|
||||
pub(crate) ys: Vec<Scalar>,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for SecretKeyAuth {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<SecretKeyAuth> {
|
||||
// There should be x and at least one y
|
||||
if bytes.len() < 32 * 2 + 8 || (bytes.len() - 8) % 32 != 0 {
|
||||
return Err(CompactEcashError::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(CompactEcashError::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,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize secret key scalar".to_string(),
|
||||
),
|
||||
)?;
|
||||
let ys = try_deserialize_scalar_vec(
|
||||
ys_len,
|
||||
&bytes[40..],
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize secret key scalars".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
Ok(SecretKeyAuth { x, ys })
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretKeyAuth {
|
||||
pub fn verification_key(&self, params: &GroupParameters) -> VerificationKeyAuth {
|
||||
let g1 = params.gen1();
|
||||
let g2 = params.gen2();
|
||||
VerificationKeyAuth {
|
||||
alpha: g2 * self.x,
|
||||
beta_g1: self.ys.iter().map(|y| g1 * y).collect(),
|
||||
beta_g2: self.ys.iter().map(|y| g2 * y).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let ys_len = self.ys.len();
|
||||
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<SecretKeyAuth> {
|
||||
SecretKeyAuth::try_from(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct VerificationKeyAuth {
|
||||
pub(crate) alpha: G2Projective,
|
||||
pub(crate) beta_g1: Vec<G1Projective>,
|
||||
pub(crate) beta_g2: Vec<G2Projective>,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for VerificationKeyAuth {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<VerificationKeyAuth> {
|
||||
// There should be at least alpha, one betaG1 and one betaG2 and their length
|
||||
if bytes.len() < 96 * 2 + 48 + 8 || (bytes.len() - 8 - 96) % (96 + 48) != 0 {
|
||||
return Err(CompactEcashError::DeserializationInvalidLength {
|
||||
actual: bytes.len(),
|
||||
modulus_target: bytes.len() - 8 - 96,
|
||||
target: 96 * 2 + 48 + 8,
|
||||
modulus: 96 + 48,
|
||||
object: "verification 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 betas_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
|
||||
|
||||
let actual_betas_len = (bytes.len() - 104) / (96 + 48);
|
||||
|
||||
if betas_len as usize != actual_betas_len {
|
||||
return Err(
|
||||
CompactEcashError::Deserialization(
|
||||
format!("Tried to deserialize verification key with inconsistent betas len (expected {}, got {})",
|
||||
betas_len, actual_betas_len
|
||||
)));
|
||||
}
|
||||
|
||||
let alpha = try_deserialize_g2_projective(
|
||||
&alpha_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize verification key G2 point (alpha)".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
let mut beta_g1 = Vec::with_capacity(betas_len as usize);
|
||||
let mut beta_g1_end: u64 = 0;
|
||||
for i in 0..betas_len {
|
||||
let start = (104 + i * 48) as usize;
|
||||
let end = (start + 48) as usize;
|
||||
let beta_i_bytes = bytes[start..end].try_into().unwrap();
|
||||
let beta_i = try_deserialize_g1_projective(
|
||||
&beta_i_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize verification key G1 point (beta)".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
beta_g1_end = end as u64;
|
||||
beta_g1.push(beta_i)
|
||||
}
|
||||
|
||||
let mut beta_g2 = Vec::with_capacity(betas_len as usize);
|
||||
for i in 0..betas_len {
|
||||
let start = (beta_g1_end + i * 96) as usize;
|
||||
let end = (start + 96) as usize;
|
||||
let beta_i_bytes = bytes[start..end].try_into().unwrap();
|
||||
let beta_i = try_deserialize_g2_projective(
|
||||
&beta_i_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize verification key G2 point (beta)".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
beta_g2.push(beta_i)
|
||||
}
|
||||
|
||||
Ok(VerificationKeyAuth {
|
||||
alpha,
|
||||
beta_g1,
|
||||
beta_g2,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> Add<&'b VerificationKeyAuth> for VerificationKeyAuth {
|
||||
type Output = VerificationKeyAuth;
|
||||
|
||||
#[inline]
|
||||
fn add(self, rhs: &'b VerificationKeyAuth) -> VerificationKeyAuth {
|
||||
// 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_g1.len(),
|
||||
rhs.beta_g1.len(),
|
||||
"trying to add verification keys generated for different number of attributes [G1]"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
self.beta_g2.len(),
|
||||
rhs.beta_g2.len(),
|
||||
"trying to add verification keys generated for different number of attributes [G2]"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
self.beta_g1.len(),
|
||||
self.beta_g2.len(),
|
||||
"this key is incorrect - the number of elements G1 and G2 does not match"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
rhs.beta_g1.len(),
|
||||
rhs.beta_g2.len(),
|
||||
"they key you want to add is incorrect - the number of elements G1 and G2 does not match"
|
||||
);
|
||||
|
||||
VerificationKeyAuth {
|
||||
alpha: self.alpha + rhs.alpha,
|
||||
beta_g1: self
|
||||
.beta_g1
|
||||
.iter()
|
||||
.zip(rhs.beta_g1.iter())
|
||||
.map(|(self_beta_g1, rhs_beta_g1)| self_beta_g1 + rhs_beta_g1)
|
||||
.collect(),
|
||||
beta_g2: self
|
||||
.beta_g2
|
||||
.iter()
|
||||
.zip(rhs.beta_g2.iter())
|
||||
.map(|(self_beta_g2, rhs_beta_g2)| self_beta_g2 + rhs_beta_g2)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mul<Scalar> for &'a VerificationKeyAuth {
|
||||
type Output = VerificationKeyAuth;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, rhs: Scalar) -> Self::Output {
|
||||
VerificationKeyAuth {
|
||||
alpha: self.alpha * rhs,
|
||||
beta_g1: self.beta_g1.iter().map(|b_i| b_i * rhs).collect(),
|
||||
beta_g2: self.beta_g2.iter().map(|b_i| b_i * rhs).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Sum<T> for VerificationKeyAuth
|
||||
where
|
||||
T: Borrow<VerificationKeyAuth>,
|
||||
{
|
||||
#[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_g2.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 VerificationKeyAuth::identity(0);
|
||||
}
|
||||
};
|
||||
|
||||
peekable.fold(
|
||||
VerificationKeyAuth::identity(head_attributes),
|
||||
|acc, item| acc + item.borrow(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl VerificationKeyAuth {
|
||||
/// Create a (kinda) identity verification key using specified
|
||||
/// number of 'beta' elements
|
||||
pub(crate) fn identity(beta_size: usize) -> Self {
|
||||
VerificationKeyAuth {
|
||||
alpha: G2Projective::identity(),
|
||||
beta_g1: vec![G1Projective::identity(); beta_size],
|
||||
beta_g2: 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_g1(&self) -> &Vec<G1Projective> {
|
||||
&self.beta_g1
|
||||
}
|
||||
|
||||
pub fn beta_g2(&self) -> &Vec<G2Projective> {
|
||||
&self.beta_g2
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let beta_g1_len = self.beta_g1.len();
|
||||
let beta_g2_len = self.beta_g2.len();
|
||||
let mut bytes = Vec::with_capacity(96 + 8 + beta_g1_len * 48 + beta_g2_len * 96);
|
||||
|
||||
bytes.extend_from_slice(&self.alpha.to_affine().to_compressed());
|
||||
|
||||
bytes.extend_from_slice(&beta_g1_len.to_le_bytes());
|
||||
|
||||
for beta_g1 in self.beta_g1.iter() {
|
||||
bytes.extend_from_slice(&beta_g1.to_affine().to_compressed())
|
||||
}
|
||||
|
||||
for beta_g2 in self.beta_g2.iter() {
|
||||
bytes.extend_from_slice(&beta_g2.to_affine().to_compressed())
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<VerificationKeyAuth> {
|
||||
VerificationKeyAuth::try_from(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct SecretKeyUser {
|
||||
pub sk: Scalar,
|
||||
}
|
||||
|
||||
impl SecretKeyUser {
|
||||
pub fn public_key(&self, params: &GroupParameters) -> PublicKeyUser {
|
||||
PublicKeyUser {
|
||||
pk: params.gen1() * self.sk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
pub struct PublicKeyUser {
|
||||
pub(crate) pk: G1Projective,
|
||||
}
|
||||
|
||||
pub struct KeyPairAuth {
|
||||
secret_key: SecretKeyAuth,
|
||||
verification_key: VerificationKeyAuth,
|
||||
/// Optional index value specifying polynomial point used during threshold key generation.
|
||||
pub index: Option<SignerIndex>,
|
||||
}
|
||||
|
||||
impl KeyPairAuth {
|
||||
pub fn secret_key(&self) -> SecretKeyAuth {
|
||||
self.secret_key.clone()
|
||||
}
|
||||
|
||||
pub fn verification_key(&self) -> VerificationKeyAuth {
|
||||
self.verification_key.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KeyPairUser {
|
||||
secret_key: SecretKeyUser,
|
||||
public_key: PublicKeyUser,
|
||||
}
|
||||
|
||||
impl KeyPairUser {
|
||||
pub fn secret_key(&self) -> SecretKeyUser {
|
||||
self.secret_key.clone()
|
||||
}
|
||||
|
||||
pub fn public_key(&self) -> PublicKeyUser {
|
||||
self.public_key.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_keypair_user(params: &GroupParameters) -> KeyPairUser {
|
||||
let sk_user = SecretKeyUser {
|
||||
sk: params.random_scalar(),
|
||||
};
|
||||
let pk_user = PublicKeyUser {
|
||||
pk: params.gen1() * sk_user.sk,
|
||||
};
|
||||
|
||||
KeyPairUser {
|
||||
secret_key: sk_user,
|
||||
public_key: pk_user,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ttp_keygen(
|
||||
params: &GroupParameters,
|
||||
threshold: u64,
|
||||
num_authorities: u64,
|
||||
) -> Result<Vec<KeyPairAuth>> {
|
||||
if threshold == 0 {
|
||||
return Err(CompactEcashError::Setup(
|
||||
"Tried to generate threshold keys with a 0 threshold value".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if threshold > num_authorities {
|
||||
return Err(
|
||||
CompactEcashError::Setup(
|
||||
"Tried to generate threshold keys for threshold value being higher than number of the signing authorities".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let attributes = params.gammas().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)| SecretKeyAuth { x, ys });
|
||||
|
||||
let keypairs = secret_keys
|
||||
.zip(polynomial_indices.iter())
|
||||
.map(|(secret_key, index)| {
|
||||
let verification_key = secret_key.verification_key(params);
|
||||
KeyPairAuth {
|
||||
secret_key,
|
||||
verification_key,
|
||||
index: Some(*index),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(keypairs)
|
||||
}
|
||||
@@ -0,0 +1,593 @@
|
||||
use std::cell::Cell;
|
||||
|
||||
use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar};
|
||||
use group::Curve;
|
||||
|
||||
use crate::Attribute;
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness};
|
||||
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
|
||||
use crate::scheme::setup::{GroupParameters, Parameters};
|
||||
use crate::utils::{
|
||||
check_bilinear_pairing, hash_to_scalar, Signature, SignerIndex,
|
||||
try_deserialize_g1_projective, try_deserialize_scalar, try_deserialize_g2_projective,
|
||||
};
|
||||
|
||||
pub mod aggregation;
|
||||
pub mod identify;
|
||||
pub mod keygen;
|
||||
pub mod setup;
|
||||
pub mod withdrawal;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PartialWallet {
|
||||
sig: Signature,
|
||||
v: Scalar,
|
||||
idx: Option<SignerIndex>,
|
||||
}
|
||||
|
||||
impl PartialWallet {
|
||||
pub fn signature(&self) -> &Signature {
|
||||
&self.sig
|
||||
}
|
||||
pub fn v(&self) -> Scalar {
|
||||
self.v
|
||||
}
|
||||
pub fn index(&self) -> Option<SignerIndex> {
|
||||
self.idx
|
||||
}
|
||||
pub fn to_bytes(&self) -> [u8; 136]{
|
||||
let mut bytes = [0u8; 136];
|
||||
bytes[0..96].copy_from_slice(&self.sig.to_bytes());
|
||||
bytes[96..128].copy_from_slice(&self.v.to_bytes());
|
||||
// Check if idx is Some and copy its bytes if it exists
|
||||
if let Some(idx) = &self.idx {
|
||||
bytes[128..136].copy_from_slice(&idx.to_le_bytes());
|
||||
}
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for PartialWallet {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<PartialWallet> {
|
||||
if bytes.len() != 136 {
|
||||
return Err(CompactEcashError::Deserialization(format!(
|
||||
"PartialWallet should be exactly 136 bytes, got {}",
|
||||
bytes.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let sig_bytes: &[u8; 96] = &bytes[..96].try_into().expect("Slice size != 96");
|
||||
let v_bytes: &[u8; 32] = &bytes[96..128].try_into().expect("Slice size != 32");
|
||||
let idx_bytes: &[u8; 8] = &bytes[128..136].try_into().expect("Slice size != 8");
|
||||
|
||||
let sig = Signature::try_from(sig_bytes.as_slice()).unwrap();
|
||||
let v = Scalar::from_bytes(&v_bytes).unwrap();
|
||||
let idx = None;
|
||||
if !idx_bytes.iter().all(|&x| x == 0){
|
||||
let idx = Some(u64::from_le_bytes(*idx_bytes));
|
||||
}
|
||||
|
||||
Ok(PartialWallet{
|
||||
sig,
|
||||
v,
|
||||
idx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Wallet {
|
||||
sig: Signature,
|
||||
v: Scalar,
|
||||
pub l: Cell<u64>,
|
||||
}
|
||||
|
||||
impl Wallet {
|
||||
pub fn signature(&self) -> &Signature {
|
||||
&self.sig
|
||||
}
|
||||
|
||||
pub fn v(&self) -> Scalar {
|
||||
self.v
|
||||
}
|
||||
|
||||
pub fn l(&self) -> u64 {
|
||||
self.l.get()
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> [u8; 136]{
|
||||
let mut bytes = [0u8; 136];
|
||||
bytes[0..96].copy_from_slice(&self.sig.to_bytes());
|
||||
bytes[96..128].copy_from_slice(&self.v.to_bytes());
|
||||
bytes[128..136].copy_from_slice(&self.l.get().to_le_bytes());
|
||||
bytes
|
||||
}
|
||||
fn up(&self) {
|
||||
self.l.set(self.l.get() + 1);
|
||||
}
|
||||
|
||||
|
||||
pub fn spend(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
sk_user: &SecretKeyUser,
|
||||
pay_info: &PayInfo,
|
||||
bench_flag: bool,
|
||||
spend_vv: u64,
|
||||
) -> Result<(Payment, &Self)> {
|
||||
if self.l() + spend_vv > params.L() {
|
||||
return Err(CompactEcashError::Spend(
|
||||
"The counter l is higher than max L".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let grparams = params.grp();
|
||||
// randomize signature in the wallet
|
||||
let (signature_prime, sign_blinding_factor) = self.signature().randomise(grparams);
|
||||
// construct kappa i.e., blinded attributes for show
|
||||
let attributes = vec![sk_user.sk, self.v()];
|
||||
// compute kappa
|
||||
let kappa = compute_kappa(
|
||||
&grparams,
|
||||
&verification_key,
|
||||
&attributes,
|
||||
sign_blinding_factor,
|
||||
);
|
||||
|
||||
// pick random openings o_c
|
||||
let o_c = grparams.random_scalar();
|
||||
|
||||
// compute commitments C
|
||||
let cc = grparams.gen1() * o_c + grparams.gamma1() * self.v();
|
||||
|
||||
|
||||
let mut aa: Vec<G1Projective> = Default::default();
|
||||
let mut ss: Vec<G1Projective> = Default::default();
|
||||
let mut tt: Vec<G1Projective> = Default::default();
|
||||
let mut rr: Vec<Scalar> = Default::default();
|
||||
let mut o_a: Vec<Scalar> = Default::default();
|
||||
let mut o_mu: Vec<Scalar> = Default::default();
|
||||
let mut mu: Vec<Scalar> = Default::default();
|
||||
let mut r_k_vec: Vec<Scalar> = Default::default();
|
||||
let mut kappa_k_vec: Vec<G2Projective> = Default::default();
|
||||
let mut sign_lk_prime_vec: Vec<Signature> = Default::default();
|
||||
let mut lk: Vec<Scalar> = Default::default();
|
||||
|
||||
for k in 0..spend_vv {
|
||||
lk.push(Scalar::from(self.l() + k));
|
||||
|
||||
// compute hashes R_k of the payment info
|
||||
let rr_k = hash_to_scalar(pay_info.info);
|
||||
rr.push(rr_k);
|
||||
|
||||
let o_a_k = grparams.random_scalar();
|
||||
o_a.push(o_a_k);
|
||||
let aa_k = grparams.gen1() * o_a_k + grparams.gamma1() * Scalar::from(self.l() + k);
|
||||
aa.push(aa_k);
|
||||
|
||||
// evaluate the pseudorandom functions
|
||||
let ss_k = pseudorandom_f_delta_v(&grparams, self.v(), self.l() + k);
|
||||
ss.push(ss_k);
|
||||
let tt_k =
|
||||
grparams.gen1() * sk_user.sk + pseudorandom_f_g_v(&grparams, self.v(), self.l() + k) * rr_k;
|
||||
tt.push(tt_k);
|
||||
|
||||
// compute values mu, o_mu, lambda, o_lambda
|
||||
let mu_k: Scalar = (self.v() + Scalar::from(self.l() + k) + Scalar::from(1))
|
||||
.invert()
|
||||
.unwrap();
|
||||
mu.push(mu_k);
|
||||
|
||||
let o_mu_k = ((o_a_k + o_c) * mu_k).neg();
|
||||
o_mu.push(o_mu_k);
|
||||
|
||||
// parse the signature associated with value l+k
|
||||
let sign_lk = params.get_sign_by_idx(self.l() + k)?;
|
||||
// randomise the signature associated with value l+k
|
||||
let (sign_lk_prime, r_k) = sign_lk.randomise(grparams);
|
||||
sign_lk_prime_vec.push(sign_lk_prime);
|
||||
r_k_vec.push(r_k);
|
||||
// compute kappa_k
|
||||
let kappa_k = grparams.gen2() * r_k
|
||||
+ params.pk_rp().alpha
|
||||
+ params.pk_rp().beta * Scalar::from(self.l() + k);
|
||||
kappa_k_vec.push(kappa_k);
|
||||
}
|
||||
|
||||
|
||||
// construct the zkp proof
|
||||
let spend_instance = SpendInstance {
|
||||
kappa,
|
||||
cc,
|
||||
aa: aa.clone(),
|
||||
ss: ss.clone(),
|
||||
tt: tt.clone(),
|
||||
kappa_k: kappa_k_vec.clone(),
|
||||
};
|
||||
let spend_witness = SpendWitness {
|
||||
attributes,
|
||||
r: sign_blinding_factor,
|
||||
o_c,
|
||||
lk,
|
||||
o_a,
|
||||
mu,
|
||||
o_mu,
|
||||
r_k: r_k_vec,
|
||||
};
|
||||
let zk_proof = SpendProof::construct(
|
||||
¶ms,
|
||||
&spend_instance,
|
||||
&spend_witness,
|
||||
&verification_key,
|
||||
&rr,
|
||||
);
|
||||
|
||||
// output pay and updated wallet
|
||||
let pay = Payment {
|
||||
kappa,
|
||||
sig: signature_prime,
|
||||
ss: ss.clone(),
|
||||
tt: tt.clone(),
|
||||
aa: aa.clone(),
|
||||
rr: rr.clone(),
|
||||
kappa_k: kappa_k_vec.clone(),
|
||||
sig_lk: sign_lk_prime_vec,
|
||||
cc,
|
||||
zk_proof,
|
||||
vv: spend_vv,
|
||||
};
|
||||
|
||||
// The number of samples collected by the benchmark process is way higher than the
|
||||
// MAX_WALLET_VALUE we ever consider. Thus, we would execute the spending too many times
|
||||
// and the initial condition at the top of this function will crush. Thus, we need a
|
||||
// benchmark flag to signal that we don't want to increase the spending couter but only
|
||||
// care about the function performance.
|
||||
if !bench_flag {
|
||||
let current_l = self.l();
|
||||
self.l.set(current_l + spend_vv);
|
||||
}
|
||||
|
||||
Ok((pay, self))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for Wallet {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Wallet> {
|
||||
if bytes.len() != 136 {
|
||||
return Err(CompactEcashError::Deserialization(format!(
|
||||
"Wallet should be exactly 136 bytes, got {}",
|
||||
bytes.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let sig_bytes: &[u8; 96] = &bytes[..96].try_into().expect("Slice size != 96");
|
||||
let v_bytes: &[u8; 32] = &bytes[96..128].try_into().expect("Slice size != 32");
|
||||
let l_bytes: &[u8; 8] = &bytes[128..136].try_into().expect("Slice size != 8");
|
||||
|
||||
let sig = Signature::try_from(sig_bytes.as_slice()).unwrap();
|
||||
let v = Scalar::from_bytes(&v_bytes).unwrap();
|
||||
let l = Cell::new(u64::from_le_bytes(*l_bytes));
|
||||
|
||||
Ok(Wallet{
|
||||
sig,
|
||||
v,
|
||||
l
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pseudorandom_f_delta_v(params: &GroupParameters, v: Scalar, l: u64) -> G1Projective {
|
||||
let pow = (v + Scalar::from(l) + Scalar::from(1)).invert().unwrap();
|
||||
params.delta() * pow
|
||||
}
|
||||
|
||||
pub fn pseudorandom_f_g_v(params: &GroupParameters, v: Scalar, l: u64) -> G1Projective {
|
||||
let pow = (v + Scalar::from(l) + Scalar::from(1)).invert().unwrap();
|
||||
params.gen1() * pow
|
||||
}
|
||||
|
||||
pub fn compute_kappa(
|
||||
params: &GroupParameters,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
attributes: &[Attribute],
|
||||
blinding_factor: Scalar,
|
||||
) -> G2Projective {
|
||||
params.gen2() * blinding_factor
|
||||
+ verification_key.alpha
|
||||
+ attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
|
||||
.sum::<G2Projective>()
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub struct PayInfo {
|
||||
pub info: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Payment {
|
||||
pub kappa: G2Projective,
|
||||
pub sig: Signature,
|
||||
pub ss: Vec<G1Projective>,
|
||||
pub tt: Vec<G1Projective>,
|
||||
pub aa: Vec<G1Projective>,
|
||||
pub rr: Vec<Scalar>,
|
||||
pub kappa_k: Vec<G2Projective>,
|
||||
pub sig_lk: Vec<Signature>,
|
||||
pub cc: G1Projective,
|
||||
pub zk_proof: SpendProof,
|
||||
pub vv: u64,
|
||||
}
|
||||
|
||||
impl Payment {
|
||||
pub fn spend_verify(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
pay_info: &PayInfo,
|
||||
) -> Result<bool> {
|
||||
if bool::from(self.sig.0.is_identity()) {
|
||||
return Err(CompactEcashError::Spend(
|
||||
"The element h of the signature equals the identity".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if !check_bilinear_pairing(
|
||||
&self.sig.0.to_affine(),
|
||||
&G2Prepared::from(self.kappa.to_affine()),
|
||||
&self.sig.1.to_affine(),
|
||||
params.grp().prepared_miller_g2(),
|
||||
) {
|
||||
return Err(CompactEcashError::Spend(
|
||||
"The bilinear check for kappa failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
for k in 0..self.vv {
|
||||
if bool::from(self.sig_lk[k as usize].0.is_identity()) {
|
||||
return Err(CompactEcashError::Spend(
|
||||
"The element h of the signature on l equals the identity".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if !check_bilinear_pairing(
|
||||
&self.sig_lk[k as usize].0.to_affine(),
|
||||
&G2Prepared::from(self.kappa_k[k as usize].to_affine()),
|
||||
&self.sig_lk[k as usize].1.to_affine(),
|
||||
params.grp().prepared_miller_g2(),
|
||||
) {
|
||||
return Err(CompactEcashError::Spend(
|
||||
"The bilinear check for kappa_l failed".to_string(),
|
||||
));
|
||||
}
|
||||
// verify integrity of R_k
|
||||
if !(self.rr[k as usize] == hash_to_scalar(pay_info.info)) {
|
||||
return Err(CompactEcashError::Spend(
|
||||
"Integrity of R_k does not hold".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: verify whether payinfo contains merchent's identifier
|
||||
|
||||
// verify the zk proof
|
||||
let instance = SpendInstance {
|
||||
kappa: self.kappa,
|
||||
aa: self.aa.clone(),
|
||||
cc: self.cc,
|
||||
ss: self.ss.clone(),
|
||||
tt: self.tt.clone(),
|
||||
kappa_k: self.kappa_k.clone(),
|
||||
};
|
||||
|
||||
if !self
|
||||
.zk_proof
|
||||
.verify(¶ms, &instance, &verification_key, &self.rr)
|
||||
{
|
||||
return Err(CompactEcashError::Spend(
|
||||
"ZkProof verification failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let kappa_bytes = self.kappa.to_affine().to_compressed();
|
||||
let sig_bytes = self.sig.to_bytes();
|
||||
let cc_bytes = self.cc.to_affine().to_compressed();
|
||||
let vv_bytes: [u8; 8] = self.vv.to_le_bytes();
|
||||
let ss_len = self.ss.len() as u64;
|
||||
let tt_len = self.tt.len() as u64;
|
||||
let aa_len = self.aa.len() as u64;
|
||||
let rr_len = self.rr.len() as u64;
|
||||
let kappa_k_len = self.kappa_k.len() as u64;
|
||||
let sig_lk_len = self.sig_lk.len() as u64;
|
||||
let zk_proof_bytes = self.zk_proof.to_bytes();
|
||||
let zk_proof_bytes_len = self.zk_proof.to_bytes().len() as u64;
|
||||
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(
|
||||
(96 + 96 + 48 + 8 + ss_len * 48 + 8 + tt_len * 48 + 8 + aa_len * 48 + 8 + rr_len * 32 + 8 + kappa_k_len * 96 + 8 + sig_lk_len * 96 + zk_proof_bytes_len) as usize);
|
||||
|
||||
|
||||
bytes.extend_from_slice(&kappa_bytes);
|
||||
bytes.extend_from_slice(&sig_bytes);
|
||||
bytes.extend_from_slice(&cc_bytes);
|
||||
bytes.extend_from_slice(&vv_bytes);
|
||||
|
||||
|
||||
let ss_len_bytes = ss_len.to_le_bytes();
|
||||
bytes.extend_from_slice(&ss_len_bytes);
|
||||
for s in &self.ss {
|
||||
bytes.extend_from_slice(&s.to_affine().to_compressed());
|
||||
}
|
||||
|
||||
let tt_len_bytes = tt_len.to_le_bytes();
|
||||
bytes.extend_from_slice(&tt_len_bytes);
|
||||
for t in &self.tt {
|
||||
bytes.extend_from_slice(&t.to_affine().to_compressed());
|
||||
}
|
||||
|
||||
let aa_len_bytes = aa_len.to_le_bytes();
|
||||
bytes.extend_from_slice(&aa_len_bytes);
|
||||
for a in &self.aa {
|
||||
bytes.extend_from_slice(&a.to_affine().to_compressed());
|
||||
}
|
||||
|
||||
let rr_len_bytes = rr_len.to_le_bytes();
|
||||
bytes.extend_from_slice(&rr_len_bytes);
|
||||
for r in &self.rr {
|
||||
bytes.extend_from_slice(&r.to_bytes());
|
||||
}
|
||||
|
||||
let kappa_k_len_bytes = kappa_k_len.to_le_bytes();
|
||||
bytes.extend_from_slice(&kappa_k_len_bytes);
|
||||
for kk in &self.kappa_k {
|
||||
bytes.extend_from_slice(&kk.to_affine().to_compressed());
|
||||
}
|
||||
|
||||
let sig_lk_len_bytes = sig_lk_len.to_le_bytes();
|
||||
bytes.extend_from_slice(&sig_lk_len_bytes);
|
||||
for sig in &self.sig_lk {
|
||||
bytes.extend_from_slice(&sig.to_bytes());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&zk_proof_bytes);
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for Payment {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Payment> {
|
||||
if bytes.len() < 656 {
|
||||
return Err(CompactEcashError::Deserialization(
|
||||
"Invalid byte array for Payment deserialization".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let kappa_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
|
||||
let sig_bytes: [u8; 96] = bytes[96..192].try_into().unwrap();
|
||||
let cc_bytes: [u8; 48] = bytes[192..240].try_into().unwrap();
|
||||
let vv_bytes: [u8; 8] = bytes[240..248].try_into().unwrap();
|
||||
let ss_len = u64::from_le_bytes(bytes[248..256].try_into().unwrap()) as usize;
|
||||
|
||||
// Convert the byte arrays back into their respective types
|
||||
let kappa = try_deserialize_g2_projective(
|
||||
&kappa_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize kappa".to_string()),
|
||||
)?;
|
||||
let sig = Signature::try_from(sig_bytes.as_slice())?;
|
||||
|
||||
let cc = try_deserialize_g1_projective(
|
||||
&cc_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize cc".to_string()),
|
||||
)?;
|
||||
let vv = u64::from_le_bytes(vv_bytes);
|
||||
|
||||
let mut idx = 256;
|
||||
let mut ss = Vec::with_capacity(ss_len);
|
||||
for _ in 0..ss_len {
|
||||
let ss_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap();
|
||||
let ss_elem = try_deserialize_g1_projective(
|
||||
&ss_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize ss element".to_string()),
|
||||
)?;
|
||||
ss.push(ss_elem);
|
||||
idx += 48;
|
||||
}
|
||||
|
||||
let tt_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize;
|
||||
idx += 8;
|
||||
let mut tt = Vec::with_capacity(tt_len);
|
||||
for _ in 0..tt_len {
|
||||
let tt_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap();
|
||||
let tt_elem = try_deserialize_g1_projective(
|
||||
&tt_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize tt element".to_string()),
|
||||
)?;
|
||||
tt.push(tt_elem);
|
||||
idx += 48;
|
||||
}
|
||||
|
||||
let aa_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize;
|
||||
idx += 8;
|
||||
let mut aa = Vec::with_capacity(aa_len);
|
||||
for _ in 0..aa_len {
|
||||
let aa_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap();
|
||||
let aa_elem = try_deserialize_g1_projective(
|
||||
&aa_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize aa element".to_string()),
|
||||
)?;
|
||||
aa.push(aa_elem);
|
||||
idx += 48;
|
||||
}
|
||||
|
||||
let rr_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize;
|
||||
idx += 8;
|
||||
let mut rr = Vec::with_capacity(rr_len);
|
||||
for _ in 0..rr_len {
|
||||
let rr_bytes: [u8; 32] = bytes[idx..idx + 32].try_into().unwrap();
|
||||
let rr_elem = try_deserialize_scalar(
|
||||
&rr_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize rr element".to_string()),
|
||||
)?;
|
||||
rr.push(rr_elem);
|
||||
idx += 32;
|
||||
}
|
||||
|
||||
let kappa_k_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize;
|
||||
idx += 8;
|
||||
let mut kappa_k = Vec::with_capacity(kappa_k_len);
|
||||
for _ in 0..kappa_k_len {
|
||||
let kappa_k_bytes: [u8; 96] = bytes[idx..idx + 96].try_into().unwrap();
|
||||
let kappa_k_elem = try_deserialize_g2_projective(
|
||||
&kappa_k_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize kappa_k element".to_string()),
|
||||
)?;
|
||||
kappa_k.push(kappa_k_elem);
|
||||
idx += 96;
|
||||
}
|
||||
|
||||
// sig_lk
|
||||
let sig_lk_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize;
|
||||
idx += 8;
|
||||
let mut sig_lk = Vec::with_capacity(sig_lk_len);
|
||||
for _ in 0..sig_lk_len {
|
||||
let sig_lk_bytes: [u8; 96] = bytes[idx..idx + 96].try_into().unwrap();
|
||||
let sig_lk_elem = Signature::try_from(sig_lk_bytes.as_slice())?;
|
||||
sig_lk.push(sig_lk_elem);
|
||||
idx += 96;
|
||||
}
|
||||
|
||||
// Deserialize the SpendProof struct
|
||||
let zk_proof_bytes = &bytes[idx..];
|
||||
let zk_proof = SpendProof::try_from(zk_proof_bytes)?;
|
||||
|
||||
// Construct the Payment struct from the deserialized data
|
||||
let payment = Payment {
|
||||
kappa,
|
||||
sig,
|
||||
ss,
|
||||
tt,
|
||||
aa,
|
||||
rr,
|
||||
kappa_k,
|
||||
sig_lk,
|
||||
cc,
|
||||
zk_proof,
|
||||
vv,
|
||||
};
|
||||
|
||||
Ok(payment)
|
||||
}
|
||||
}
|
||||